diff --git a/packages/passport/sdk-sample-app/src/components/PassportMethods.tsx b/packages/passport/sdk-sample-app/src/components/PassportMethods.tsx index 66dc0f5d3e..867b4cf441 100644 --- a/packages/passport/sdk-sample-app/src/components/PassportMethods.tsx +++ b/packages/passport/sdk-sample-app/src/components/PassportMethods.tsx @@ -8,7 +8,9 @@ import WorkflowButton from '@/components/WorkflowButton'; function PassportMethods() { const { isLoading } = useStatusProvider(); const { + userProfile, logout, + signIn, getIdToken, getAccessToken, getUserInfo, @@ -18,6 +20,14 @@ function PassportMethods() { return ( + {!userProfile && ( + + Login + + )} void; connectImxSilent: () => void; connectZkEvm: () => void; logout: () => void; + signIn: () => void; getIdToken: () => Promise; getAccessToken: () => Promise; getUserInfo: () => Promise; @@ -20,10 +22,12 @@ const PassportContext = createContext<{ }>({ imxProvider: undefined, zkEvmProvider: undefined, + userProfile: undefined, connectImx: () => undefined, connectImxSilent: () => undefined, connectZkEvm: () => undefined, logout: () => undefined, + signIn: () => Promise.resolve(undefined), getIdToken: () => Promise.resolve(undefined), getAccessToken: () => Promise.resolve(undefined), getUserInfo: () => Promise.resolve(undefined), @@ -35,6 +39,7 @@ export function PassportProvider({ }: { children: JSX.Element | JSX.Element[] }) { const [imxProvider, setImxProvider] = useState(); const [zkEvmProvider, setZkEvmProvider] = useState(); + const [userProfile, setUserProfile] = useState(); const { addMessage, setIsLoading } = useStatusProvider(); const { passportClient } = useImmutableProvider(); @@ -127,6 +132,20 @@ export function PassportProvider({ await passportClient?.logout(); setImxProvider(undefined); setZkEvmProvider(undefined); + setUserProfile(undefined); + } catch (err) { + addMessage('Logout', err); + console.error(err); + } finally { + setIsLoading(false); + } + }, [addMessage, passportClient, setIsLoading]); + + const signIn = useCallback(async () => { + try { + setIsLoading(true); + const signInuser = await passportClient?.signIn(); + setUserProfile(signInuser); } catch (err) { addMessage('Logout', err); console.error(err); @@ -138,10 +157,12 @@ export function PassportProvider({ const providerValues = useMemo(() => ({ imxProvider, zkEvmProvider, + userProfile, connectImx, connectImxSilent, connectZkEvm, logout, + signIn, getIdToken, getAccessToken, getUserInfo, @@ -149,10 +170,12 @@ export function PassportProvider({ }), [ imxProvider, zkEvmProvider, + userProfile, connectImx, connectImxSilent, connectZkEvm, logout, + signIn, getIdToken, getAccessToken, getUserInfo, @@ -168,11 +191,13 @@ export function PassportProvider({ export function usePassportProvider() { const { + userProfile, imxProvider, zkEvmProvider, connectImx, connectImxSilent, connectZkEvm, + signIn, logout, getIdToken, getAccessToken, @@ -180,11 +205,13 @@ export function usePassportProvider() { getLinkedAddresses, } = useContext(PassportContext); return { + userProfile, imxProvider, zkEvmProvider, connectImx, connectImxSilent, connectZkEvm, + signIn, logout, getIdToken, getAccessToken, diff --git a/packages/passport/sdk/src/Passport.test.ts b/packages/passport/sdk/src/Passport.test.ts index dd539330ac..c4a2a7ff74 100644 --- a/packages/passport/sdk/src/Passport.test.ts +++ b/packages/passport/sdk/src/Passport.test.ts @@ -7,7 +7,7 @@ import { ConfirmationScreen } from './confirmation'; import { Passport } from './Passport'; import { PassportImxProvider, PassportImxProviderFactory } from './starkEx'; import { Networks, OidcConfiguration } from './types'; -import { mockUser, mockLinkedAddresses } from './test/mocks'; +import { mockUser, mockLinkedAddresses, mockUserImx } from './test/mocks'; jest.mock('./authManager'); jest.mock('./magicAdapter'); @@ -126,6 +126,17 @@ describe('Passport', () => { expect(result).toBe(passportImxProvider); expect(getProviderMock).toHaveBeenCalled(); }); + + it('should call getProviderSilent if useCachedSession is true', async () => { + const passportImxProvider = {} as PassportImxProvider; + getProviderSilentMock.mockResolvedValue(passportImxProvider); + + const result = await passport.connectImx({ useCachedSession: true }); + + expect(result).toBe(passportImxProvider); + expect(getProviderSilentMock).toHaveBeenCalled(); + expect(getProviderMock).not.toHaveBeenCalled(); + }); }); describe('connectImxSilent', () => { @@ -247,4 +258,34 @@ describe('Passport', () => { expect(result).toHaveLength(0); }); }); + + describe('signIn', () => { + it('should login silently if there is a user', async () => { + loginSilentMock.mockReturnValue(mockUserImx); + const user = await passport.signIn(); + + expect(loginSilentMock).toBeCalledTimes(1); + expect(authLoginMock).toBeCalledTimes(0); + expect(user).toEqual(mockUser.profile); + }); + + it('should signIn and get a user', async () => { + loginSilentMock.mockReturnValue(null); + authLoginMock.mockReturnValue(mockUserImx); + const user = await passport.signIn(); + + expect(loginSilentMock).toBeCalledTimes(1); + expect(authLoginMock).toBeCalledTimes(1); + expect(user).toEqual(mockUserImx.profile); + }); + + it('should only login silently if useCachedSession is true', async () => { + loginSilentMock.mockReturnValue(mockUserImx); + const user = await passport.signIn({ useCachedSession: true }); + + expect(loginSilentMock).toBeCalledTimes(1); + expect(authLoginMock).toBeCalledTimes(0); + expect(user).toEqual(mockUser.profile); + }); + }); }); diff --git a/packages/passport/sdk/src/Passport.ts b/packages/passport/sdk/src/Passport.ts index 679c600c40..0f855a37c0 100644 --- a/packages/passport/sdk/src/Passport.ts +++ b/packages/passport/sdk/src/Passport.ts @@ -57,11 +57,30 @@ export class Passport { }); } + public async signIn(options?: { useCachedSession: boolean }): Promise { + const { useCachedSession = false } = options || {}; + let user = await this.authManager.loginSilent(); + if (!user && !useCachedSession) { + user = await this.authManager.login(); + } + if (!user) { + return null; + } + return user.profile; + } + + /** + * @deprecated The method connectImx(useCachedSession) should be used instead + */ public async connectImxSilent(): Promise { return this.passportImxProviderFactory.getProviderSilent(); } - public async connectImx(): Promise { + public async connectImx(options?: { useCachedSession: boolean }): Promise { + const { useCachedSession = false } = options || {}; + if (useCachedSession) { + return this.passportImxProviderFactory.getProviderSilent(); + } return this.passportImxProviderFactory.getProvider(); } diff --git a/packages/passport/sdk/src/starkEx/passportImxProviderFactory.test.ts b/packages/passport/sdk/src/starkEx/passportImxProviderFactory.test.ts index c1a25e5926..3822c8e39b 100644 --- a/packages/passport/sdk/src/starkEx/passportImxProviderFactory.test.ts +++ b/packages/passport/sdk/src/starkEx/passportImxProviderFactory.test.ts @@ -90,6 +90,7 @@ describe('PassportImxProviderFactory', () => { mockAuthManager.login.mockResolvedValue(mockUser); mockMagicAdapter.login.mockResolvedValue(mockMagicProvider); + mockAuthManager.loginSilent.mockResolvedValueOnce(null); mockAuthManager.loginSilent.mockResolvedValue(mockUser); await expect(() => passportImxProviderFactory.getProvider()).rejects.toThrow( @@ -107,10 +108,11 @@ describe('PassportImxProviderFactory', () => { starkSigner: mockStarkSigner, usersApi: immutableXClient.usersApi, }, mockUser.accessToken); - expect(mockAuthManager.loginSilent).toHaveBeenCalledTimes(4); - expect(mockAuthManager.loginSilent).toHaveBeenNthCalledWith(1, { forceRefresh: true }); + expect(mockAuthManager.loginSilent).toHaveBeenCalledTimes(5); + expect(mockAuthManager.loginSilent).toHaveBeenNthCalledWith(1); expect(mockAuthManager.loginSilent).toHaveBeenNthCalledWith(2, { forceRefresh: true }); expect(mockAuthManager.loginSilent).toHaveBeenNthCalledWith(3, { forceRefresh: true }); + expect(mockAuthManager.loginSilent).toHaveBeenNthCalledWith(4, { forceRefresh: true }); expect(mockAuthManager.loginSilent).toHaveBeenCalledWith({ forceRefresh: true }); }); }); @@ -121,6 +123,7 @@ describe('PassportImxProviderFactory', () => { mockAuthManager.login.mockResolvedValue(mockUser); mockMagicAdapter.login.mockResolvedValue(mockMagicProvider); + mockAuthManager.loginSilent.mockResolvedValueOnce(null); mockAuthManager.loginSilent.mockResolvedValue(mockUserImx); const result = await passportImxProviderFactory.getProvider(); @@ -134,7 +137,7 @@ describe('PassportImxProviderFactory', () => { starkSigner: mockStarkSigner, usersApi: immutableXClient.usersApi, }, mockUserImx.accessToken); - expect(mockAuthManager.loginSilent).toHaveBeenCalledTimes(1); + expect(mockAuthManager.loginSilent).toHaveBeenCalledTimes(2); expect(mockAuthManager.loginSilent).toHaveBeenCalledWith({ forceRefresh: true }); expect(PassportImxProvider).toHaveBeenCalledWith({ authManager: mockAuthManager, @@ -154,16 +157,17 @@ describe('PassportImxProviderFactory', () => { mockAuthManager.login.mockResolvedValue(mockUserImx); mockMagicAdapter.login.mockResolvedValue(mockMagicProvider); - mockAuthManager.loginSilent.mockResolvedValue(mockUserImx); + mockAuthManager.loginSilent.mockResolvedValue(null); + mockAuthManager.login.mockResolvedValue(mockUserImx); const result = await passportImxProviderFactory.getProvider(); expect(result).toBe(mockPassportImxProvider); + expect(mockAuthManager.loginSilent).toHaveBeenCalledTimes(1); expect(mockAuthManager.login).toHaveBeenCalledTimes(1); expect(mockMagicAdapter.login).toHaveBeenCalledWith(mockUserImx.idToken); expect(mockGetSigner).toHaveBeenCalledTimes(1); expect(registerPassportStarkEx).not.toHaveBeenCalled(); - expect(mockAuthManager.loginSilent).not.toHaveBeenCalled(); expect(PassportImxProvider).toHaveBeenCalledWith({ authManager: mockAuthManager, starkSigner: mockStarkSigner, diff --git a/packages/passport/sdk/src/starkEx/passportImxProviderFactory.ts b/packages/passport/sdk/src/starkEx/passportImxProviderFactory.ts index eda20017d3..8742b5f927 100644 --- a/packages/passport/sdk/src/starkEx/passportImxProviderFactory.ts +++ b/packages/passport/sdk/src/starkEx/passportImxProviderFactory.ts @@ -54,7 +54,7 @@ export class PassportImxProviderFactory { } public async getProvider(): Promise { - const user = await this.authManager.login(); + const user = await this.authManager.loginSilent() || await this.authManager.login(); return this.createProviderInstance(user); }