Skip to content

Commit

Permalink
feat: Support for isDismissed from FedCM (#862)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaokevin1 authored Dec 18, 2024
1 parent 727ef4b commit 27e977c
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 3 deletions.
44 changes: 41 additions & 3 deletions packages/sdks/web-js-sdk/src/sdk/fedcm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ type OneTapInitialize = ({

type PromptNotification = {
isSkippedMoment: () => boolean;
isDismissedMoment: () => boolean;
getDismissedReason: () => string;
getSkippedReason: () => string;
};

/**
Expand All @@ -112,7 +115,8 @@ const createFedCM = (sdk: CoreSdk, projectId: string) => ({
provider?: string,
oneTapConfig?: OneTapConfig,
loginOptions?: LoginOptions,
onSkip?: () => void,
onSkip?: (reason?: string) => void,
onDismissed?: (reason?: string) => void,
) {
const readyProvider = provider ?? 'google';
const startResponse = await sdk.oauth.startNative(
Expand Down Expand Up @@ -150,8 +154,17 @@ const createFedCM = (sdk: CoreSdk, projectId: string) => ({
});

googleClient.prompt((notification) => {
if (notification?.isSkippedMoment()) {
onSkip?.();
if (onDismissed && notification?.isDismissedMoment()) {
const reason = notification.getDismissedReason?.();
onDismissed?.(reason);
return;
}

// Fallback to onSkip
if (onSkip && notification?.isSkippedMoment()) {
const reason = notification.getSkippedReason?.();
onSkip?.(reason);
return;
}
});
});
Expand Down Expand Up @@ -179,6 +192,31 @@ const createFedCM = (sdk: CoreSdk, projectId: string) => ({
isSupported(): boolean {
return IS_BROWSER && 'IdentityCredential' in window;
},
async isLoggedIn(
context?: IdentityCredentialRequestOptionsContext,
): Promise<boolean> {
const configURL = sdk.httpClient.buildUrl(
projectId + apiPaths.fedcm.config,
);
try {
const req: FedCMCredentialRequestOptions = {
identity: {
context: context || 'signin',
providers: [
{
configURL,
clientId: projectId,
},
],
},
};
const res = await navigator.credentials?.get(req as any);
return !!res && !!(res as any as FedCMAssertionResponse).token;
} catch (e) {
// Any error likely indicates no active session.
return false;
}
},
});

// Helpers functions
Expand Down
75 changes: 75 additions & 0 deletions packages/sdks/web-js-sdk/test/fedcm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,34 @@ describe('fedcm', () => {
promptCallback({ isSkippedMoment: () => true });
expect(onSkip).toHaveBeenCalled();
});
it('call onDismissed callback on prompt dismiss with detailed reason', async () => {
coreJs.oauth.startNative.mockResolvedValue({
ok: true,
data: { clientId: 'C123', stateId: 'S123', nonce: 'N123' },
});

const onDismissed = jest.fn();

// Call oneTap with onDismissed callback
sdk.fedcm.oneTap(
'google',
{ auto_select: true },
{ stepup: false },
undefined,
onDismissed,
);

await new Promise(process.nextTick);

// Simulate prompt callback with isDismissedMoment and getDismissedReason
const promptCallback = googleClient.prompt.mock.calls[0][0];
promptCallback({
isDismissedMoment: () => true,
getDismissedReason: () => 'credential_returned',
});

expect(onDismissed).toHaveBeenCalledWith('credential_returned');
});
});
describe('launch', () => {
it('should call navigator.credentials.get with correct parameters', async () => {
Expand Down Expand Up @@ -189,4 +217,51 @@ describe('fedcm', () => {
expect(sdk.fedcm.isSupported()).toBe(false);
});
});
describe('isLoggedIn', () => {
it('should return true if navigator.credentials.get returns a valid token', async () => {
const mockGet = jest.fn();
// @ts-ignore
global.navigator.credentials = { get: mockGet };
mockGet.mockResolvedValue({ token: 'mockToken' });

const result = await sdk.fedcm.isLoggedIn();

expect(mockGet).toHaveBeenCalledWith({
identity: {
context: 'signin',
providers: [
{
configURL: 'http://localhost:3000/P123/fedcm/config',
clientId: 'P123',
},
],
},
});
expect(result).toBe(true);
});

it('should return false if navigator.credentials.get returns null', async () => {
const mockGet = jest.fn();
// @ts-ignore
global.navigator.credentials = { get: mockGet };
mockGet.mockResolvedValue(null);

const result = await sdk.fedcm.isLoggedIn();

expect(mockGet).toHaveBeenCalled();
expect(result).toBe(false);
});

it('should return false if navigator.credentials.get throws an error', async () => {
const mockGet = jest.fn();
// @ts-ignore
global.navigator.credentials = { get: mockGet };
mockGet.mockRejectedValue(new Error('Test Error'));

const result = await sdk.fedcm.isLoggedIn();

expect(mockGet).toHaveBeenCalled();
expect(result).toBe(false);
});
});
});

0 comments on commit 27e977c

Please sign in to comment.