Skip to content
Draft
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
12 changes: 12 additions & 0 deletions docs-site/src/pages/GettingStarted.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,18 @@ export default function OrganizationManagementPage() {
positioning, duration, and custom toast methods
</td>
</tr>
<tr>
<td className="px-4 py-2 whitespace-nowrap text-sm font-medium text-gray-900">
customFetch
</td>
<td className="px-4 py-2 text-sm text-gray-500">
<code className="text-xs">CustomFetch</code>
</td>
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-500">No</td>
<td className="px-4 py-2 text-sm text-gray-500">
Custom fetch implementation to override the default fetch behavior
</td>
</tr>
<tr>
<td className="px-4 py-2 whitespace-nowrap text-sm font-medium text-gray-900">
loader
Expand Down
31 changes: 30 additions & 1 deletion packages/core/src/auth/__tests__/core-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,43 @@ describe('createCoreClient', () => {
expect(initializeMyOrganizationClientMock).toHaveBeenCalledWith(
authDetails,
mockTokenManager,
undefined,
);
});

it('initializes MyAccount client with auth and token manager', async () => {
const authDetails = createAuthDetails();
await createCoreClient(authDetails);

expect(initializeMyAccountClientMock).toHaveBeenCalledWith(authDetails, mockTokenManager);
expect(initializeMyAccountClientMock).toHaveBeenCalledWith(
authDetails,
mockTokenManager,
undefined,
);
});

it('passes customFetch to MyOrg client when provided', async () => {
const authDetails = createAuthDetails();
const customFetch = vi.fn();
await createCoreClient(authDetails, undefined, customFetch);

expect(initializeMyOrganizationClientMock).toHaveBeenCalledWith(
authDetails,
mockTokenManager,
customFetch,
);
});

it('passes customFetch to MyAccount client when provided', async () => {
const authDetails = createAuthDetails();
const customFetch = vi.fn();
await createCoreClient(authDetails, undefined, customFetch);

expect(initializeMyAccountClientMock).toHaveBeenCalledWith(
authDetails,
mockTokenManager,
customFetch,
);
});
});

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/auth/auth-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { ArbitraryObject } from '@core/types';

import type { I18nServiceInterface } from '../i18n';

export type CustomFetch = (url: string, init?: RequestInit | undefined) => Promise<Response>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not the same as SPA-JS?

info: RequestInfo | URL,
init?: RequestInit,


export type TokenEndpointResponse = {
id_token: string;
access_token: string;
Expand Down
12 changes: 9 additions & 3 deletions packages/core/src/auth/core-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { initializeMyOrganizationClient } from '@core/services/my-organization/m
import type { I18nInitOptions } from '../i18n';
import { createI18nService } from '../i18n';

import type { AuthDetails, BaseCoreClientInterface, CoreClientInterface } from './auth-types';
import type {
AuthDetails,
BaseCoreClientInterface,
CoreClientInterface,
CustomFetch,
} from './auth-types';
import { createTokenManager } from './token-manager';

function isProxyMode(auth: AuthDetails): boolean {
Expand All @@ -18,6 +23,7 @@ function initializeAuthDetails(authDetails: AuthDetails): AuthDetails {
export async function createCoreClient(
authDetails: AuthDetails,
i18nOptions?: I18nInitOptions,
customFetch?: CustomFetch,
): Promise<CoreClientInterface> {
const i18nService = await createI18nService(
i18nOptions || { currentLanguage: 'en-US', fallbackLanguage: 'en-US' },
Expand All @@ -26,9 +32,9 @@ export async function createCoreClient(

const tokenManagerService = createTokenManager(auth);
const { client: myOrganizationApiClient, setLatestScopes: setOrgScopes } =
initializeMyOrganizationClient(auth, tokenManagerService);
initializeMyOrganizationClient(auth, tokenManagerService, customFetch);
const { client: myAccountApiClient, setLatestScopes: setAccountScopes } =
initializeMyAccountClient(auth, tokenManagerService);
initializeMyAccountClient(auth, tokenManagerService, customFetch);

const baseCoreClient: BaseCoreClientInterface = {
auth,
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ export * from './api';

export { createCoreClient } from './auth/core-client';

export { AuthDetails, CoreClientInterface, BasicAuth0ContextInterface } from './auth/auth-types';
export {
AuthDetails,
CoreClientInterface,
BasicAuth0ContextInterface,
CustomFetch,
} from './auth/auth-types';

export * from './schemas';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getExpectedProxyBaseUrl,
mockScopes,
mockTokens,
mockRequestInits,
expectedErrors,
} from './__mocks__/my-account-api-service.mocks';

Expand Down Expand Up @@ -278,6 +279,32 @@ describe('initializeMyAccountClient', () => {
});
});

describe('customFetch parameter in proxy mode', () => {
it('should use customFetch when provided', async () => {
const tokenManager = createMockTokenManager();
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

initializeMyAccountClient(mockAuthWithProxyUrl, tokenManager, customFetch);

const fetcher = getFetcherFromMockCalls(mockMyAccountClient);
await fetcher!(TEST_URL, mockRequestInits.post);

expect(customFetch).toHaveBeenCalledTimes(1);
});

it('should pass url and init to customFetch', async () => {
const tokenManager = createMockTokenManager();
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

initializeMyAccountClient(mockAuthWithProxyUrl, tokenManager, customFetch);

const fetcher = getFetcherFromMockCalls(mockMyAccountClient);
await fetcher!(TEST_URL, mockRequestInits.post);

expect(customFetch).toHaveBeenCalledWith(TEST_URL, mockRequestInits.post);
});
});

describe('URL handling', () => {
it('should handle proxy URL with path', () => {
const authWithPath = { authProxyUrl: 'https://example.com/api/v1' };
Expand Down Expand Up @@ -529,6 +556,45 @@ describe('initializeMyAccountClient', () => {
});
});

describe('customFetch parameter in domain mode', () => {
it('should use customFetch when provided', async () => {
const tokenManager = createMockTokenManager();
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

initializeMyAccountClient(mockAuthWithDomain, tokenManager, customFetch);

const fetcher = getFetcherFromMockCalls(mockMyAccountClient);
await fetcher!(TEST_URL, mockRequestInits.post);

expect(customFetch).toHaveBeenCalledTimes(1);
});

it('should pass url and init to customFetch', async () => {
const tokenManager = createMockTokenManager();
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

initializeMyAccountClient(mockAuthWithDomain, tokenManager, customFetch);

const fetcher = getFetcherFromMockCalls(mockMyAccountClient);
await fetcher!(TEST_URL, mockRequestInits.post);

expect(customFetch).toHaveBeenCalledWith(TEST_URL, mockRequestInits.post);
});

it('should not call getToken when customFetch is provided', async () => {
const tokenManager = createMockTokenManager();
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

initializeMyAccountClient(mockAuthWithDomain, tokenManager, customFetch);

const fetcher = getFetcherFromMockCalls(mockMyAccountClient);
await fetcher!(TEST_URL, mockRequestInits.post);

// tokenManager is not used when customFetch is provided
expect(tokenManager.getToken).not.toHaveBeenCalled();
});
});

describe('token retrieval', () => {
it('should request token with latest scopes', async () => {
const mockFetch = createMockFetch();
Expand Down
55 changes: 30 additions & 25 deletions packages/core/src/services/my-account/my-account-api-service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { MyAccountClient } from '@auth0/myaccount-js';

import type { AuthDetails } from '../../auth/auth-types';
import type { AuthDetails, CustomFetch } from '../../auth/auth-types';
import type { createTokenManager } from '../../auth/token-manager';

export function initializeMyAccountClient(
auth: AuthDetails,
tokenManagerService: ReturnType<typeof createTokenManager>,
customFetch?: CustomFetch,
): {
client: MyAccountClient;
setLatestScopes: (scopes: string) => void;
Expand All @@ -19,16 +20,18 @@ export function initializeMyAccountClient(
if (auth.authProxyUrl) {
const myAccountProxyPath = 'me';
const myAccountBaseUrl = `${auth.authProxyUrl.replace(/\/$/, '')}/${myAccountProxyPath}`;
const fetcher = async (url: string, init?: RequestInit) => {
return fetch(url, {
...init,
headers: {
...init?.headers,
...(init?.body && { 'Content-Type': 'application/json' }),
...(latestScopes && { 'auth0-scope': latestScopes }),
},
});
};
const fetcher = customFetch
? customFetch
: async (url: string, init?: RequestInit) => {
Comment on lines +23 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a small thing we should use ?? to avoid this redundant checking pattern

return fetch(url, {
...init,
headers: {
...init?.headers,
...(init?.body && { 'Content-Type': 'application/json' }),
...(latestScopes && { 'auth0-scope': latestScopes }),
},
});
};
return {
client: new MyAccountClient({
domain: '',
Expand All @@ -39,22 +42,24 @@ export function initializeMyAccountClient(
setLatestScopes,
};
} else if (auth.domain) {
const fetcher = async (url: string, init?: RequestInit) => {
const token = await tokenManagerService.getToken(latestScopes, 'me');
const fetcher = customFetch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar applies here

? customFetch
: async (url: string, init?: RequestInit) => {
const token = await tokenManagerService.getToken(latestScopes, 'me');

const headers = new Headers(init?.headers);
if (init?.body && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
const headers = new Headers(init?.headers);
if (init?.body && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}

return fetch(url, {
...init,
headers,
});
};
return fetch(url, {
...init,
headers,
});
};
return {
client: new MyAccountClient({
domain: auth.domain.trim(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,38 @@ describe('initializeMyOrganizationClient', () => {
});
});

describe('customFetch parameter in proxy mode', () => {
it('should use customFetch when provided', async () => {
const tokenManager = createMockTokenManager();
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

initializeMyOrganizationClient(mockAuthWithProxyUrl, tokenManager, customFetch);

const fetcher = getFetcherFromMockCalls(mockMyOrganizationClient);
await fetcher!(TEST_URL, mockRequestInits.post);

expect(customFetch).toHaveBeenCalledTimes(1);
expect(mockFetch).not.toHaveBeenCalled();
});

it('should pass url and init to customFetch', async () => {
const tokenManager = createMockTokenManager();
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

initializeMyOrganizationClient(mockAuthWithProxyUrl, tokenManager, customFetch);

const fetcher = getFetcherFromMockCalls(mockMyOrganizationClient);
await fetcher!(TEST_URL, mockRequestInits.post);

expect(customFetch).toHaveBeenCalledWith(
TEST_URL,
expect.objectContaining({
body: mockRequestInits.post.body,
}),
);
});
});

describe('URL handling', () => {
it('should handle proxy URL with path', () => {
const tokenManager = createMockTokenManager();
Expand Down Expand Up @@ -520,6 +552,51 @@ describe('initializeMyOrganizationClient', () => {
expect(headers.get('Content-Type')).toBeNull();
});
});

describe('customFetch parameter in domain mode', () => {
it('should use customFetch when provided', async () => {
const tokenManager = createMockTokenManager(mockTokens.standard);
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

initializeMyOrganizationClient(mockAuthWithDomain, tokenManager, customFetch);

const fetcher = getFetcherFromMockCalls(mockMyOrganizationClient);
await fetcher!(TEST_URL, mockRequestInits.post);

expect(customFetch).toHaveBeenCalledTimes(1);
expect(mockFetch).not.toHaveBeenCalled();
});

it('should pass url and init to customFetch', async () => {
const tokenManager = createMockTokenManager();
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

initializeMyOrganizationClient(mockAuthWithDomain, tokenManager, customFetch);

const fetcher = getFetcherFromMockCalls(mockMyOrganizationClient);
await fetcher!(TEST_URL, mockRequestInits.post);

expect(customFetch).toHaveBeenCalledWith(TEST_URL, mockRequestInits.post);
});

it('should not call getToken when customFetch is provided', async () => {
const tokenManager = createMockTokenManager();
const customFetch = vi.fn().mockResolvedValue(createMockFetch());

const { setLatestScopes } = initializeMyOrganizationClient(
mockAuthWithDomain,
tokenManager,
customFetch,
);

const fetcher = getFetcherFromMockCalls(mockMyOrganizationClient);
setLatestScopes(mockScopes.organizationRead);
await fetcher!(TEST_URL, mockRequestInits.post);

// tokenManager is not used when customFetch is provided
expect(tokenManager.getToken).not.toHaveBeenCalled();
});
});
});

describe('priority and mode selection', () => {
Expand Down
Loading
Loading