Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 0 additions & 200 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>,
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<string, string>),
"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<string, string> = {};
if (args.headers) {
for (const [key, value] of Object.entries(args.headers)) {
if (value !== undefined) {
// Handle Supplier type: could be string, Promise<string>, or () => string | Promise<string>
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand All @@ -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);
});
Expand Down
5 changes: 4 additions & 1 deletion src/management/wrapper/ManagementClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export declare namespace ManagementClient {
* @group Management API
* @public
*/
export interface ManagementClientOptions extends Omit<FernClient.Options, "token" | "environment"> {
export interface ManagementClientOptions extends Omit<FernClient.Options, "token" | "environment" | "fetcher"> {
/** Auth0 domain (e.g., 'your-tenant.auth0.com') */
domain: string;
/**
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion tests/management/token-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,12 @@ describe("TokenProvider", () => {
.fn<FetchAPI>()
.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();
});
Expand Down
Loading