Compare commits

...

3 Commits

Author SHA1 Message Date
anonpenguin23
272c6b872c Update package version from 0.1.5 to 0.2.5 in package.json 2025-10-30 09:18:18 +02:00
anonpenguin23
8c9a900e88 Update HttpClient to include proxy operations in API key usage logic and enhance request header logging for proxy paths. 2025-10-30 06:52:29 +02:00
anonpenguin23
23a742e5d4 Add proxy request functionality to NetworkClient and update README with usage examples
- Introduced `proxyAnon` method in `NetworkClient` to facilitate anonymous HTTP requests through the Anyone network.
- Added `ProxyRequest` and `ProxyResponse` interfaces to define the structure of proxy requests and responses.
- Updated README.md with detailed examples on how to use the new proxy functionality, including GET and POST requests.
- Enhanced error handling for proxy requests to ensure graceful failure in case of issues.
2025-10-30 06:21:29 +02:00
6 changed files with 139 additions and 15 deletions

View File

@ -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

View File

@ -1,6 +1,6 @@
{
"name": "@debros/network-ts-sdk",
"version": "0.1.5",
"version": "0.2.5",
"description": "TypeScript SDK for DeBros Network Gateway",
"type": "module",
"main": "./dist/index.js",

View File

@ -54,13 +54,14 @@ export class HttpClient {
private getAuthHeaders(path: string): Record<string, string> {
const headers: Record<string, string> = {};
// For database and pubsub operations, ONLY use API key to avoid JWT user context
// For database, pubsub, and proxy operations, ONLY use API key to avoid JWT user context
// interfering with namespace-level authorization
const isDbOperation = path.includes("/v1/rqlite/");
const isPubSubOperation = path.includes("/v1/pubsub/");
const isProxyOperation = path.includes("/v1/proxy/");
if (isDbOperation || isPubSubOperation) {
// For database/pubsub operations: use only API key (preferred for namespace operations)
if (isDbOperation || isPubSubOperation || isProxyOperation) {
// For database/pubsub/proxy operations: use only API key (preferred for namespace operations)
if (this.apiKey) {
headers["X-API-Key"] = this.apiKey;
} else if (this.jwt) {
@ -116,7 +117,8 @@ export class HttpClient {
(path.includes("/db/") ||
path.includes("/query") ||
path.includes("/auth/") ||
path.includes("/pubsub/"))
path.includes("/pubsub/") ||
path.includes("/proxy/"))
) {
console.log("[HttpClient] Request headers for", path, {
hasAuth: !!headers["Authorization"],

View File

@ -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<HttpClientConfig, "fetch"> {
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";

View File

@ -14,6 +14,20 @@ export interface NetworkStatus {
uptime: number;
}
export interface ProxyRequest {
url: string;
method: string;
headers?: Record<string, string>;
body?: string;
}
export interface ProxyResponse {
status_code: number;
headers: Record<string, string>;
body: string;
error?: string;
}
export class NetworkClient {
private httpClient: HttpClient;
@ -37,7 +51,9 @@ export class NetworkClient {
* Get network status.
*/
async status(): Promise<NetworkStatus> {
const response = await this.httpClient.get<NetworkStatus>("/v1/network/status");
const response = await this.httpClient.get<NetworkStatus>(
"/v1/network/status"
);
return response;
}
@ -64,4 +80,40 @@ export class NetworkClient {
async disconnect(peerId: string): Promise<void> {
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<ProxyResponse> {
const response = await this.httpClient.post<ProxyResponse>(
"/v1/proxy/anon",
request
);
// Check if the response contains an error
if (response.error) {
throw new Error(`Proxy request failed: ${response.error}`);
}
return response;
}
}

View File

@ -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();
});
});