diff --git a/packages/game-bridge/src/index.ts b/packages/game-bridge/src/index.ts index 633d75a871..089d5bf6f4 100644 --- a/packages/game-bridge/src/index.ts +++ b/packages/game-bridge/src/index.ts @@ -2,6 +2,7 @@ import * as passport from '@imtbl/passport'; import * as config from '@imtbl/config'; import * as provider from '@imtbl/x-provider'; +import * as xClient from '@imtbl/x-client'; import { track, trackError, @@ -212,28 +213,68 @@ window.callFunction = async (jsonData: string) => { requestId = json[keyRequestId]; const data = json[keyData]; - track(moduleName, 'startedCallFunction', { - function: fxName, - requestId, - }); switch (fxName) { case PASSPORT_FUNCTIONS.init: { const request = JSON.parse(data); const redirect: string | null = request?.redirectUri; const logoutMode: 'silent' | 'redirect' = request?.isSilentLogout === true ? 'silent' : 'redirect'; if (!passportClient) { - const passportConfig = { - baseConfig: new config.ImmutableConfiguration({ - environment: request.environment, - }), - clientId: request.clientId, - audience, - scope, - redirectUri: redirect ?? redirectUri, - logoutRedirectUri: request?.logoutRedirectUri, - crossSdkBridgeEnabled: true, - logoutMode, - }; + console.log(`Connecting to ${request.environment} environment`); + + let passportConfig: passport.PassportModuleConfiguration; + + const environment = request.environment === 'production' + ? config.Environment.PRODUCTION + : config.Environment.SANDBOX; + const baseConfig = new config.ImmutableConfiguration({ environment }); + + if (request.environment === 'dev' || request.environment === 'development') { + passportConfig = { + baseConfig, + clientId: request.clientId, + redirectUri: redirect ?? redirectUri, + logoutRedirectUri: request?.logoutRedirectUri, + audience, + scope, + crossSdkBridgeEnabled: true, + logoutMode, + overrides: { + authenticationDomain: 'https://auth.dev.immutable.com', + magicPublishableApiKey: 'pk_live_4058236363130CA9', // Public key + magicProviderId: 'C9odf7hU4EQ5EufcfgYfcBaT5V6LhocXyiPRhIjw2EY=', // Public key + passportDomain: 'https://passport.dev.immutable.com', + imxPublicApiDomain: 'https://api.dev.immutable.com', + immutableXClient: new xClient.IMXClient({ + baseConfig, + overrides: { + immutableXConfig: xClient.createConfig({ + basePath: 'https://api.dev.x.immutable.com', + chainID: 5, + coreContractAddress: '0xd05323731807A35599BF9798a1DE15e89d6D6eF1', + registrationContractAddress: '0x7EB840223a3b1E0e8D54bF8A6cd83df5AFfC88B2', + }), + }, + }), + zkEvmRpcUrl: 'https://rpc.dev.immutable.com', + relayerUrl: 'https://api.dev.immutable.com/relayer-mr', + indexerMrBasePath: 'https://api.dev.immutable.com', + orderBookMrBasePath: 'https://api.dev.immutable.com', + passportMrBasePath: 'https://api.dev.immutable.com', + }, + }; + } else { + passportConfig = { + baseConfig, + clientId: request.clientId, + audience, + scope, + redirectUri: redirect ?? redirectUri, + logoutRedirectUri: request?.logoutRedirectUri, + crossSdkBridgeEnabled: true, + logoutMode, + }; + } + passportClient = new passport.Passport(passportConfig); trackDuration(moduleName, 'initialisedPassport', mt(markStart)); } diff --git a/packages/passport/sdk/src/zkEvm/zkEvmProvider.test.ts b/packages/passport/sdk/src/zkEvm/zkEvmProvider.test.ts index 58edb9f9e2..7a930291b1 100644 --- a/packages/passport/sdk/src/zkEvm/zkEvmProvider.test.ts +++ b/packages/passport/sdk/src/zkEvm/zkEvmProvider.test.ts @@ -10,7 +10,7 @@ import { RelayerClient } from './relayerClient'; import { Provider, RequestArguments } from './types'; import { PassportEventMap, PassportEvents } from '../types'; import TypedEventEmitter from '../utils/typedEventEmitter'; -import { mockUserZkEvm, testConfig } from '../test/mocks'; +import { mockUser, mockUserZkEvm, testConfig } from '../test/mocks'; import { signTypedDataV4 } from './signTypedDataV4'; import MagicAdapter from '../magicAdapter'; @@ -62,13 +62,91 @@ describe('ZkEvmProvider', () => { return new ZkEvmProvider(constructorParameters as ZkEvmProviderInput); }; - describe('eth_requestAccounts', () => { - it('constructor tries to automatically connect existing user session when provider is instantiated', async () => { - authManager.getUser.mockReturnValue(Promise.resolve(mockUserZkEvm)); - getProvider(); - expect(authManager.getUser).toHaveBeenCalledTimes(1); + describe('constructor', () => { + describe('when an application session exists', () => { + it('initialises the signer', async () => { + authManager.getUser.mockResolvedValue(mockUserZkEvm); + getProvider(); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(authManager.getUser).toBeCalledTimes(1); + expect(magicAdapter.login).toBeCalledTimes(1); + expect(Web3Provider).toBeCalledTimes(1); + }); + + describe('and the user has not registered before', () => { + it('does not call session activity', async () => { + const onAccountsRequested = jest.fn(); + passportEventEmitter.on(PassportEvents.ACCOUNTS_REQUESTED, onAccountsRequested); + authManager.getUser.mockResolvedValue(mockUser); + getProvider(); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(authManager.getUser).toBeCalledTimes(1); + expect(onAccountsRequested).not.toHaveBeenCalled(); + }); + }); + describe('and the user has registered before', () => { + it('calls session activity', async () => { + const onAccountsRequested = jest.fn(); + passportEventEmitter.on(PassportEvents.ACCOUNTS_REQUESTED, onAccountsRequested); + authManager.getUser.mockResolvedValue(mockUserZkEvm); + getProvider(); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(authManager.getUser).toBeCalledTimes(1); + expect(onAccountsRequested).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('when a login occurs outside of the zkEvm provider', () => { + beforeEach(() => { + authManager.getUser.mockResolvedValue(null); + }); + + it('initialises the signer', async () => { + getProvider(); + passportEventEmitter.emit(PassportEvents.LOGGED_IN, mockUserZkEvm); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(magicAdapter.login).toBeCalledTimes(1); + expect(Web3Provider).toBeCalledTimes(1); + }); + + describe('and the user has not registered before', () => { + it('does not call session activity', async () => { + const onAccountsRequested = jest.fn(); + passportEventEmitter.on(PassportEvents.ACCOUNTS_REQUESTED, onAccountsRequested); + getProvider(); + passportEventEmitter.emit(PassportEvents.LOGGED_IN, mockUser); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(onAccountsRequested).not.toHaveBeenCalled(); + }); + + describe('and the user has registered before', () => { + it('calls session activity', async () => { + const onAccountsRequested = jest.fn(); + passportEventEmitter.on(PassportEvents.ACCOUNTS_REQUESTED, onAccountsRequested); + getProvider(); + passportEventEmitter.emit(PassportEvents.LOGGED_IN, mockUserZkEvm); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(onAccountsRequested).toHaveBeenCalledTimes(1); + }); + }); + }); }); + }); + describe('eth_requestAccounts', () => { it('should return the ethAddress if already logged in', async () => { authManager.getUser.mockReturnValue(Promise.resolve(mockUserZkEvm)); const provider = getProvider(); @@ -103,7 +181,7 @@ describe('ZkEvmProvider', () => { it('should throw an error if the signer initialisation fails', async () => { authManager.getUserOrLogin.mockReturnValue(mockUserZkEvm); - authManager.getUser.mockReturnValue(Promise.resolve(mockUserZkEvm)); + authManager.getUser.mockResolvedValue(mockUserZkEvm); (Web3Provider as unknown as jest.Mock).mockImplementation(() => ({ getSigner: () => { @@ -117,6 +195,24 @@ describe('ZkEvmProvider', () => { new JsonRpcError(RpcErrorCode.INTERNAL_ERROR, 'Something went wrong'), ); }); + + it('should not reinitialise the ethSigner when it has been set during the constructor', async () => { + authManager.getUser.mockResolvedValue(mockUserZkEvm); + const provider = getProvider(); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(magicAdapter.login).toBeCalledTimes(1); + expect(Web3Provider).toBeCalledTimes(1); + + await provider.request({ method: 'eth_requestAccounts' }); + + // Add a delay so that we can check if the ethSigner is initialised again + await new Promise(process.nextTick); + + expect(magicAdapter.login).toBeCalledTimes(1); + expect(Web3Provider).toBeCalledTimes(1); + }); }); describe('eth_sendTransaction', () => { diff --git a/packages/passport/sdk/src/zkEvm/zkEvmProvider.ts b/packages/passport/sdk/src/zkEvm/zkEvmProvider.ts index f4c9f760cf..d7469824e3 100644 --- a/packages/passport/sdk/src/zkEvm/zkEvmProvider.ts +++ b/packages/passport/sdk/src/zkEvm/zkEvmProvider.ts @@ -116,14 +116,20 @@ export class ZkEvmProvider implements Provider { // Automatically connect an existing user session to Passport this.#authManager.getUser().then((user) => { - if (user && isZkEvmUser(user)) { + if (user) { this.#initialiseEthSigner(user); + if (isZkEvmUser(user)) { + this.#callSessionActivity(user.zkEvm.ethAddress); + } } - }).catch(() => { - // User does not exist, don't initialise an eth signer - }); + }).catch(); // User does not exist, don't initialise an eth signer - passportEventEmitter.on(PassportEvents.LOGGED_IN, (user: User) => this.#initialiseEthSigner(user)); + passportEventEmitter.on(PassportEvents.LOGGED_IN, (user: User) => { + this.#initialiseEthSigner(user); + if (isZkEvmUser(user)) { + this.#callSessionActivity(user.zkEvm.ethAddress); + } + }); passportEventEmitter.on(PassportEvents.LOGGED_OUT, this.#handleLogout); passportEventEmitter.on( PassportEvents.ACCOUNTS_REQUESTED, @@ -231,7 +237,9 @@ export class ZkEvmProvider implements Provider { const user = await this.#authManager.getUserOrLogin(); flow.addEvent('endGetUserOrLogin'); - this.#initialiseEthSigner(user); + if (!this.#ethSigner) { + this.#initialiseEthSigner(user); + } let userZkEvmEthAddress;