diff --git a/README.md b/README.md index 2dcce81..a722fcb 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,40 @@ peers.forEach((peer) => { }); ``` +#### Proxy Requests Through Anyone Network + +Make anonymous HTTP requests through the Anyone network: + +```typescript +// Simple GET request +const response = await client.network.proxyAnon({ + url: "https://api.example.com/data", + method: "GET", + headers: { + Accept: "application/json", + }, +}); + +console.log(response.status_code); // 200 +console.log(response.body); // Response data as string +console.log(response.headers); // Response headers + +// POST request with body +const postResponse = await client.network.proxyAnon({ + url: "https://api.example.com/submit", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ key: "value" }), +}); + +// Parse JSON response +const data = JSON.parse(postResponse.body); +``` + +**Note:** The proxy endpoint requires authentication (API key or JWT) and only works when the Anyone relay is running on the gateway server. + ## Configuration ### ClientConfig diff --git a/src/index.ts b/src/index.ts index 465c478..c490fde 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,11 @@ import { DBClient } from "./db/client"; import { PubSubClient } from "./pubsub/client"; import { NetworkClient } from "./network/client"; import { WSClientConfig } from "./core/ws"; -import { StorageAdapter, MemoryStorage, LocalStorageAdapter } from "./auth/types"; +import { + StorageAdapter, + MemoryStorage, + LocalStorageAdapter, +} from "./auth/types"; export interface ClientConfig extends Omit { apiKey?: string; @@ -38,8 +42,9 @@ export function createClient(config: ClientConfig): Client { }); // Derive WebSocket URL from baseURL if not explicitly provided - const wsURL = config.wsConfig?.wsURL ?? - config.baseURL.replace(/^http/, 'ws').replace(/\/$/, ''); + const wsURL = + config.wsConfig?.wsURL ?? + config.baseURL.replace(/^http/, "ws").replace(/\/$/, ""); const db = new DBClient(httpClient); const pubsub = new PubSubClient(httpClient, { @@ -67,11 +72,7 @@ export { PubSubClient, Subscription } from "./pubsub/client"; export { NetworkClient } from "./network/client"; export { SDKError } from "./errors"; export { MemoryStorage, LocalStorageAdapter } from "./auth/types"; -export type { - StorageAdapter, - AuthConfig, - WhoAmI, -} from "./auth/types"; +export type { StorageAdapter, AuthConfig, WhoAmI } from "./auth/types"; export type * from "./db/types"; export type { Message, @@ -79,4 +80,9 @@ export type { ErrorHandler, CloseHandler, } from "./pubsub/client"; -export type { PeerInfo, NetworkStatus } from "./network/client"; +export type { + PeerInfo, + NetworkStatus, + ProxyRequest, + ProxyResponse, +} from "./network/client"; diff --git a/src/network/client.ts b/src/network/client.ts index 0a50a63..c9180bd 100644 --- a/src/network/client.ts +++ b/src/network/client.ts @@ -14,6 +14,20 @@ export interface NetworkStatus { uptime: number; } +export interface ProxyRequest { + url: string; + method: string; + headers?: Record; + body?: string; +} + +export interface ProxyResponse { + status_code: number; + headers: Record; + body: string; + error?: string; +} + export class NetworkClient { private httpClient: HttpClient; @@ -37,7 +51,9 @@ export class NetworkClient { * Get network status. */ async status(): Promise { - const response = await this.httpClient.get("/v1/network/status"); + const response = await this.httpClient.get( + "/v1/network/status" + ); return response; } @@ -64,4 +80,40 @@ export class NetworkClient { async disconnect(peerId: string): Promise { await this.httpClient.post("/v1/network/disconnect", { peer_id: peerId }); } + + /** + * Proxy an HTTP request through the Anyone network. + * Requires authentication (API key or JWT). + * + * @param request - The proxy request configuration + * @returns The proxied response + * @throws {SDKError} If the Anyone proxy is not available or the request fails + * + * @example + * ```ts + * const response = await client.network.proxyAnon({ + * url: 'https://api.example.com/data', + * method: 'GET', + * headers: { + * 'Accept': 'application/json' + * } + * }); + * + * console.log(response.status_code); // 200 + * console.log(response.body); // Response data + * ``` + */ + async proxyAnon(request: ProxyRequest): Promise { + const response = await this.httpClient.post( + "/v1/proxy/anon", + request + ); + + // Check if the response contains an error + if (response.error) { + throw new Error(`Proxy request failed: ${response.error}`); + } + + return response; + } } diff --git a/tests/e2e/network.test.ts b/tests/e2e/network.test.ts index df3fc1d..d86de1b 100644 --- a/tests/e2e/network.test.ts +++ b/tests/e2e/network.test.ts @@ -27,4 +27,34 @@ describe("Network", () => { const peers = await client.network.peers(); expect(Array.isArray(peers)).toBe(true); }); + + it("should proxy request through Anyone network", async () => { + const client = await createTestClient(); + + // Test with a simple GET request + const response = await client.network.proxyAnon({ + url: "https://httpbin.org/get", + method: "GET", + headers: { + "User-Agent": "DeBros-SDK-Test/1.0", + }, + }); + + expect(response).toBeDefined(); + expect(response.status_code).toBe(200); + expect(response.body).toBeDefined(); + expect(typeof response.body).toBe("string"); + }); + + it("should handle proxy errors gracefully", async () => { + const client = await createTestClient(); + + // Test with invalid URL + await expect( + client.network.proxyAnon({ + url: "http://localhost:1/invalid", + method: "GET", + }) + ).rejects.toThrow(); + }); });