Skip to content

Commit

Permalink
Merge branch 'main' into fix/checkout-widget-provider-initialised-event
Browse files Browse the repository at this point in the history
  • Loading branch information
jhesgodi authored Sep 5, 2024
2 parents f6b6524 + 7942f50 commit 4e44745
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 34 deletions.
2 changes: 1 addition & 1 deletion packages/game-bridge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ window.callFunction = async (jsonData: string) => {
case PASSPORT_FUNCTIONS.init: {
const request = JSON.parse(data);
const redirect: string | null = request?.redirectUri;
const logoutMode: 'silent' | 'redirect' = request?.logoutMode === 'silent' ? 'silent' : 'redirect';
const logoutMode: 'silent' | 'redirect' = request?.isSilentLogout === true ? 'silent' : 'redirect';
if (!passportClient) {
const passportConfig = {
baseConfig: new config.ImmutableConfiguration({
Expand Down
1 change: 1 addition & 0 deletions packages/passport/sdk/src/Passport.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe('Passport', () => {
(Magic as jest.Mock).mockImplementation(() => ({
openid: { loginWithOIDC: mockLoginWithOidc },
rpcProvider: { request: mockMagicRequest },
preload: jest.fn(),
}));
});

Expand Down
33 changes: 11 additions & 22 deletions packages/passport/sdk/src/magicAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import MagicAdapter from './magicAdapter';
import { PassportConfiguration } from './config';
import { PassportError, PassportErrorType } from './errors/passportError';

const loginWithOIDCMock: jest.MockedFunction<(args: LoginWithOpenIdParams) => Promise<void>> = jest.fn();
const loginWithOIDCMock:jest.MockedFunction<(args: LoginWithOpenIdParams) => Promise<void>> = jest.fn();

const rpcProvider = {};

Expand All @@ -23,6 +23,7 @@ describe('MagicWallet', () => {
magicProviderId: providerId,
} as PassportConfiguration;
const idToken = 'e30=.e30=.e30=';
const preload = jest.fn();

beforeEach(() => {
jest.resetAllMocks();
Expand All @@ -34,6 +35,7 @@ describe('MagicWallet', () => {
logout: logoutMock,
},
rpcProvider,
preload,
}));
});

Expand All @@ -54,9 +56,10 @@ describe('MagicWallet', () => {
});
it('starts initialising the magicClient', () => {
jest.spyOn(window.document, 'readyState', 'get').mockReturnValue('complete');
preload.mockResolvedValue(Promise.resolve());
const magicAdapter = new MagicAdapter(config);
// @ts-expect-error: client is private
expect(magicAdapter.client).toBeDefined();
// @ts-ignore
expect(magicAdapter.lazyMagicClient).toBeDefined();
});
});

Expand All @@ -72,31 +75,15 @@ describe('MagicWallet', () => {

it('does nothing', () => {
const magicAdapter = new MagicAdapter(config);
// @ts-expect-error: client is private
expect(magicAdapter.client).toBeUndefined();
});

it('should throw a browser error for loginWithOIDC', async () => {
const magicAdapter = new MagicAdapter(config);

let type = '';
let message = '';

try {
await magicAdapter.login(idToken);
} catch (e: any) {
type = e.type;
message = e.message;
}

expect(type).toEqual(PassportErrorType.WALLET_CONNECTION_ERROR);
expect(message).toEqual('Cannot perform this action outside of the browser');
// @ts-ignore
expect(magicAdapter.magicClientPromise).toBeUndefined();
});
});
});

describe('login', () => {
it('should call loginWithOIDC and initialise the provider with the correct arguments', async () => {
preload.mockResolvedValue(Promise.resolve());
const magicAdapter = new MagicAdapter(config);
const magicProvider = await magicAdapter.login(idToken);

Expand All @@ -114,6 +101,7 @@ describe('MagicWallet', () => {
});

it('should throw a PassportError when an error is thrown', async () => {
preload.mockResolvedValue(Promise.resolve());
const magicAdapter = new MagicAdapter(config);

loginWithOIDCMock.mockImplementation(() => {
Expand All @@ -133,6 +121,7 @@ describe('MagicWallet', () => {

describe('logout', () => {
it('calls the logout function', async () => {
preload.mockResolvedValue(Promise.resolve());
const magicAdapter = new MagicAdapter(config);
await magicAdapter.login(idToken);
await magicAdapter.logout();
Expand Down
29 changes: 18 additions & 11 deletions packages/passport/sdk/src/magicAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ethers } from 'ethers';
import { trackDuration } from '@imtbl/metrics';
import { PassportErrorType, withPassportError } from './errors/passportError';
import { PassportConfiguration } from './config';
import { lazyDocumentReady } from './utils/lazyLoad';

type MagicClient = InstanceWithExtensions<SDKBase, [OpenIdExtension]>;

Expand All @@ -13,24 +14,28 @@ const MAINNET = 'mainnet';
export default class MagicAdapter {
private readonly config: PassportConfiguration;

private readonly client?: MagicClient;
private readonly lazyMagicClient?: Promise<MagicClient>;

constructor(config: PassportConfiguration) {
this.config = config;
if (typeof window !== 'undefined') {
this.client = new Magic(this.config.magicPublishableApiKey, {
extensions: [new OpenIdExtension()],
network: MAINNET, // We always connect to mainnet to ensure addresses are the same across envs
this.lazyMagicClient = lazyDocumentReady<MagicClient>(() => {
const client = new Magic(this.config.magicPublishableApiKey, {
extensions: [new OpenIdExtension()],
network: MAINNET, // We always connect to mainnet to ensure addresses are the same across envs
});
client.preload();
return client;
});
}
}

private get magicClient(): MagicClient {
if (!this.client) {
private get magicClient(): Promise<MagicClient> {
if (!this.lazyMagicClient) {
throw new Error('Cannot perform this action outside of the browser');
}

return this.client;
return this.lazyMagicClient;
}

async login(
Expand All @@ -39,7 +44,8 @@ export default class MagicAdapter {
return withPassportError<ethers.providers.ExternalProvider>(async () => {
const startTime = performance.now();

await this.magicClient.openid.loginWithOIDC({
const magicClient = await this.magicClient;
await magicClient.openid.loginWithOIDC({
jwt: idToken,
providerId: this.config.magicProviderId,
});
Expand All @@ -50,13 +56,14 @@ export default class MagicAdapter {
Math.round(performance.now() - startTime),
);

return this.magicClient.rpcProvider as unknown as ethers.providers.ExternalProvider;
return magicClient.rpcProvider as unknown as ethers.providers.ExternalProvider;
}, PassportErrorType.WALLET_CONNECTION_ERROR);
}

async logout() {
if (this.magicClient.user) {
await this.magicClient.user.logout();
const magicClient = await this.magicClient;
if (magicClient.user) {
await magicClient.user.logout();
}
}
}
56 changes: 56 additions & 0 deletions packages/passport/sdk/src/utils/lazyLoad.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { lazyDocumentReady, lazyLoad } from './lazyLoad';

describe('lazyLoad', () => {
it('should return call initFunction and returns the value', async () => {
const initFunction = jest.fn().mockReturnValue('test');
const promiseToAwait = jest.fn().mockResolvedValue(undefined);
const result = await lazyLoad(promiseToAwait, initFunction);
expect(result).toEqual('test');
expect(initFunction).toHaveBeenCalled();
});
});

describe('lazyDocumentReady', () => {
let mockInitialiseFunction: jest.Mock<any, any>;
let originalDocument: Document | undefined;

beforeEach(() => {
mockInitialiseFunction = jest.fn();
originalDocument = window.document;
const mockDocument = {
...window.document,
readyState: 'complete',
};
(window as any).document = mockDocument;
});

afterEach(() => {
// Restore the original document.readyState value after each test
(window as any).document = originalDocument;
});

it('should call the initialiseFunction when the document is already ready', async () => {
jest.spyOn(window.document, 'readyState', 'get').mockReturnValue('complete');

await lazyDocumentReady(mockInitialiseFunction);

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

it('should call the initialiseFunction when the document becomes ready', async () => {
jest.spyOn(window.document, 'readyState', 'get').mockReturnValue('loading');
const mockAddEventListener = jest.spyOn(window.document, 'addEventListener');

const lazyDocumentPromise = lazyDocumentReady(mockInitialiseFunction);
expect(mockInitialiseFunction).toHaveBeenCalledTimes(0);

jest.spyOn(window.document, 'readyState', 'get').mockReturnValue('complete');
const mockEvent = new Event('readystatechange');
window.document.dispatchEvent(mockEvent);

await lazyDocumentPromise;

expect(mockAddEventListener).toHaveBeenCalledWith('readystatechange', expect.any(Function));
expect(mockInitialiseFunction).toHaveBeenCalledTimes(1);
});
});
22 changes: 22 additions & 0 deletions packages/passport/sdk/src/utils/lazyLoad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const lazyLoad = <T, Y = void>(
promiseToAwait: () => Promise<Y>,
initialiseFunction: (arg: Y) => Promise<T> | T,
): Promise<T> => promiseToAwait().then(initialiseFunction);

export const lazyDocumentReady = <T>(initialiseFunction: () => Promise<T> | T): Promise<T> => {
const documentReadyPromise = () => new Promise<void>((resolve) => {
if (window.document.readyState === 'complete') {
resolve();
} else {
const onReadyStateChange = () => {
if (window.document.readyState === 'complete') {
resolve();
window.document.removeEventListener('readystatechange', onReadyStateChange);
}
};
window.document.addEventListener('readystatechange', onReadyStateChange);
}
});

return lazyLoad(documentReadyPromise, initialiseFunction);
};

0 comments on commit 4e44745

Please sign in to comment.