diff --git a/README.md b/README.md index 9d19b070e4..166f844880 100644 --- a/README.md +++ b/README.md @@ -498,206 +498,6 @@ runtimes: - Bun 1.0+ - React Native -### Customizing Fetch Client - -The SDK defaults to `node-fetch` but will use the global fetch client if present. For most use cases, you can simply ensure your preferred fetch implementation is available globally. - -```typescript -import { ManagementClient } from "auth0"; - -// Example 1: Add request logging using a custom fetcher -const client = new ManagementClient({ - domain: "your-tenant.auth0.com", - token: "YOUR_TOKEN", - fetcher: async (args) => { - console.log(`Making ${args.method} request to: ${args.url}`); - - // Use the global fetch (built into Node.js 18+) - const response = await fetch(args.url, { - method: args.method, - headers: args.headers as Record, - body: args.body ? JSON.stringify(args.body) : undefined, - }); - - const rawResponse = { - headers: response.headers, - redirected: response.redirected, - status: response.status, - statusText: response.statusText, - type: response.type, - url: response.url, - }; - - if (response.ok) { - return { - ok: true as const, - body: await response.json(), - headers: response.headers, - rawResponse, - }; - } else { - return { - ok: false as const, - error: { - reason: "status-code" as const, - statusCode: response.status, - body: await response.text(), - }, - rawResponse, - }; - } - }, -}); - -// Example 2: Add custom headers to all requests -const clientWithHeaders = new ManagementClient({ - domain: "your-tenant.auth0.com", - token: "YOUR_TOKEN", - fetcher: async (args) => { - // Add custom headers - const headers = { - ...(args.headers as Record), - "X-Custom-Header": "my-value", - "X-Request-ID": crypto.randomUUID(), - }; - - const response = await fetch(args.url, { - method: args.method, - headers, - body: args.body ? JSON.stringify(args.body) : undefined, - }); - - const rawResponse = { - headers: response.headers, - redirected: response.redirected, - status: response.status, - statusText: response.statusText, - type: response.type, - url: response.url, - }; - - if (response.ok) { - return { - ok: true as const, - body: await response.json(), - headers: response.headers, - rawResponse, - }; - } else { - return { - ok: false as const, - error: { - reason: "status-code" as const, - statusCode: response.status, - body: await response.text(), - }, - rawResponse, - }; - } - }, -}); - -// Example 3: Complete custom implementation with axios (advanced) -import axios from "axios"; - -const clientWithAxios = new ManagementClient({ - domain: "your-tenant.auth0.com", - token: "YOUR_TOKEN", - fetcher: async (args) => { - try { - // Process headers to resolve any Supplier functions/promises - const resolvedHeaders: Record = {}; - if (args.headers) { - for (const [key, value] of Object.entries(args.headers)) { - if (value !== undefined) { - // Handle Supplier type: could be string, Promise, or () => string | Promise - const resolvedValue = typeof value === "function" ? await value() : await value; - if (typeof resolvedValue === "string") { - resolvedHeaders[key] = resolvedValue; - } else if (resolvedValue != null) { - resolvedHeaders[key] = String(resolvedValue); - } - } - } - } - - const response = await axios({ - url: args.url, - method: args.method, - headers: resolvedHeaders, // Now properly typed for axios - data: args.body, - timeout: args.timeoutMs || 60000, - params: args.queryParameters, - validateStatus: () => true, // Don't throw on HTTP error status codes - }); - - // Convert axios headers to Web API Headers format for rawResponse - const rawHeaders = new Headers(); - Object.entries(response.headers).forEach(([key, value]) => { - if (typeof value === "string") { - rawHeaders.set(key, value); - } else if (Array.isArray(value)) { - rawHeaders.set(key, value.join(", ")); - } else if (value != null) { - rawHeaders.set(key, String(value)); - } - }); - - if (response.status >= 200 && response.status < 400) { - return { - ok: true, - body: response.data, - headers: response.headers, // axios headers for legacy compatibility - rawResponse: { - headers: rawHeaders, // Web API Headers for rawResponse - redirected: false, // axios doesn't provide this info directly - status: response.status, - statusText: response.statusText, - type: "basic", // axios doesn't provide this, default to 'basic' - url: response.config.url || args.url, - }, - }; - } else { - return { - ok: false, - error: { - reason: "status-code", - statusCode: response.status, - body: response.data, - }, - rawResponse: { - headers: rawHeaders, - redirected: false, - status: response.status, - statusText: response.statusText, - type: "basic", - url: response.config.url || args.url, - }, - }; - } - } catch (error: any) { - return { - ok: false, - error: { - reason: "unknown", - errorMessage: error?.message || "Unknown error occurred", - }, - rawResponse: { - headers: new Headers(), // Empty Headers object for network errors - redirected: false, - status: 0, - statusText: "Network Error", - type: "error", - url: args.url, - }, - }; - } - }, -}); -``` - -Note: When using a custom `fetcher`, you need to return an object that matches the expected API response format with `ok`, `body`, `headers`, and `rawResponse` properties. - ## Feedback ### Contributing diff --git a/src/management/tests/unit/management-client-custom-domain.test.ts b/src/management/tests/unit/management-client-custom-domain.test.ts index 8128a2de54..8fcde7a2c6 100644 --- a/src/management/tests/unit/management-client-custom-domain.test.ts +++ b/src/management/tests/unit/management-client-custom-domain.test.ts @@ -243,11 +243,13 @@ describe("ManagementClient with custom domain header", () => { }, }); + // TODO: Casting to any to bypass type checks for testing purposes. + // This is done only because the fetcher type is being hidden for now. const client = new ManagementClient({ ...mockConfig, withCustomDomainHeader: "auth.example.com", fetcher: mockCustomFetcher, - }); + } as any); expect(client).toBeInstanceOf(ManagementClient); }); @@ -267,10 +269,12 @@ describe("ManagementClient with custom domain header", () => { }, }); + // TODO: Casting to any to bypass type checks for testing purposes. + // This is done only because the fetcher type is being hidden for now. const client = new ManagementClient({ ...mockConfig, fetcher: mockCustomFetcher, - }); + } as any); expect(client).toBeInstanceOf(ManagementClient); }); diff --git a/src/management/wrapper/ManagementClient.ts b/src/management/wrapper/ManagementClient.ts index e1b1ac17f2..b695ff952a 100644 --- a/src/management/wrapper/ManagementClient.ts +++ b/src/management/wrapper/ManagementClient.ts @@ -22,7 +22,7 @@ export declare namespace ManagementClient { * @group Management API * @public */ - export interface ManagementClientOptions extends Omit { + export interface ManagementClientOptions extends Omit { /** Auth0 domain (e.g., 'your-tenant.auth0.com') */ domain: string; /** @@ -205,6 +205,9 @@ export class ManagementClient extends FernClient { const headers = createTelemetryHeaders(_options); const token = createTokenSupplier(_options); + // Temporarily remove fetcher from options to avoid people passing it for now + delete (_options as any).fetcher; + // Prepare the base client options let clientOptions: any = { ..._options, diff --git a/tests/management/token-provider.test.ts b/tests/management/token-provider.test.ts index 2fe4a2eb6c..0e2cebd50d 100644 --- a/tests/management/token-provider.test.ts +++ b/tests/management/token-provider.test.ts @@ -127,10 +127,12 @@ describe("TokenProvider", () => { .fn() .mockImplementation((url: URL | RequestInfo, init?: RequestInit) => fetch(url, init)); + // TODO: Casting to any to bypass type checks for testing purposes. + // This is done only because the fetcher type is being hidden for now. const tp = new TokenProvider({ ...opts, fetcher: customFetch as any, - }); + } as any); expect(await tp.getAccessToken()).toBe("my-access-token"); expect(customFetch).toHaveBeenCalled(); });