Skip to content

Commit 295311f

Browse files
fix(wallet): prevent login popup on load and emit ACCOUNTS_CHANGED on LOGGED_IN
- Add silent option to GetUserFunction to avoid popup when checking session on page load - Add LOGGED_IN listener in ZkEvmProvider to emit ACCOUNTS_CHANGED after redirect login - Fixes Issue 1: popup opening automatically on load - Fixes Issue 2: UI not updating after login via redirect
1 parent e7838e1 commit 295311f

5 files changed

Lines changed: 57 additions & 16 deletions

File tree

packages/passport/sdk/src/Passport.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,11 @@ export class Passport {
257257

258258
// Use connectWallet to create the provider (it will create WalletConfiguration internally)
259259
const provider = await connectWallet({
260-
getUser: (forceRefresh) => (forceRefresh
261-
? this.auth.forceUserRefresh()
262-
: this.auth.getUserOrLogin()),
260+
getUser: (forceRefresh, getUserOptions) => {
261+
if (forceRefresh) return this.auth.forceUserRefresh();
262+
if (getUserOptions?.silent) return this.auth.getUser();
263+
return this.auth.getUserOrLogin();
264+
},
263265
clientId: this.passportConfig.oidcConfiguration.clientId,
264266
chains: [chainConfig],
265267
crossSdkBridgeEnabled: this.passportConfig.crossSdkBridgeEnabled,

packages/wallet/src/connectWallet.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ describe('connectWallet', () => {
128128
);
129129
});
130130

131-
it('uses getUserOrLogin from internal Auth', async () => {
131+
it('uses getUser from internal Auth when silent (avoids popup on page load)', async () => {
132132
await connectWallet({ chains: [zkEvmChain] });
133133

134-
// Internal Auth's getUserOrLogin should be called during setup
135-
expect(mockAuthInstance.getUserOrLogin).toHaveBeenCalled();
134+
// Internal Auth's getUser should be called during setup (silent mode to avoid popup)
135+
expect(mockAuthInstance.getUser).toHaveBeenCalled();
136136
});
137137

138138
it('derives passportDomain from chain apiUrl', async () => {
@@ -491,7 +491,7 @@ describe('connectWallet', () => {
491491

492492
describe('error handling', () => {
493493
it('handles auth failure gracefully', async () => {
494-
mockAuthInstance.getUserOrLogin.mockRejectedValueOnce(new Error('Auth failed'));
494+
mockAuthInstance.getUser.mockRejectedValueOnce(new Error('Auth failed'));
495495

496496
const provider = await connectWallet({ chains: [zkEvmChain] });
497497

packages/wallet/src/connectWallet.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,13 @@ function createDefaultGetUser(initialChain: ChainConfig, options: ConnectWalletO
148148
});
149149
}
150150

151-
// Return getUser function that wraps Auth.getUserOrLogin
151+
// Return getUser function that wraps Auth.getUserOrLogin/getUser based on options
152152
return {
153-
getUser: async () => auth.getUserOrLogin(),
153+
getUser: async (forceRefresh?: boolean, getUserOptions?: { silent?: boolean }) => {
154+
if (forceRefresh) return auth.forceUserRefresh();
155+
if (getUserOptions?.silent) return auth.getUser();
156+
return auth.getUserOrLogin();
157+
},
154158
clientId,
155159
};
156160
}
@@ -222,8 +226,9 @@ export async function connectWallet(
222226
clientId = defaultAuth.clientId;
223227
}
224228

225-
// 4. Get current user (may be null if not logged in)
226-
const user = await getUser().catch(() => null);
229+
// 4. Get current user (may be null if not logged in).
230+
// Use silent: true to avoid triggering login popup on page load.
231+
const user = await getUser(undefined, { silent: true }).catch(() => null);
227232

228233
// 5. Create wallet configuration with concrete URLs
229234
const passportDomain = initialChain.passportDomain || initialChain.apiUrl.replace('api.', 'passport.');

packages/wallet/src/types.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type { RollupType } from '@imtbl/auth';
2323
// Wallet events
2424
export enum WalletEvents {
2525
ACCOUNTS_REQUESTED = 'accountsRequested',
26+
LOGGED_IN = 'loggedIn',
2627
LOGGED_OUT = 'loggedOut',
2728
}
2829

@@ -35,8 +36,10 @@ export type AccountsRequestedEvent = {
3536
};
3637

3738
// WalletEventMap for internal wallet events
39+
// LOGGED_IN uses same event name as AuthEvents.LOGGED_IN (auth.eventEmitter is shared)
3840
export interface WalletEventMap extends Record<string, any> {
3941
[WalletEvents.ACCOUNTS_REQUESTED]: [AccountsRequestedEvent];
42+
[WalletEvents.LOGGED_IN]: [User];
4043
[WalletEvents.LOGGED_OUT]: [];
4144
}
4245

@@ -214,6 +217,17 @@ export interface PopupOverlayOptions {
214217
disableBlockedPopupOverlay?: boolean;
215218
}
216219

220+
/**
221+
* Options for GetUserFunction calls.
222+
*/
223+
export interface GetUserOptions {
224+
/**
225+
* When true, do not trigger login (e.g. open popup) if user is not authenticated.
226+
* Returns null instead. Use during page load to avoid unwanted popups.
227+
*/
228+
silent?: boolean;
229+
}
230+
217231
/**
218232
* Function type for getting the current user.
219233
* Used as an alternative to passing an Auth instance.
@@ -222,8 +236,13 @@ export interface PopupOverlayOptions {
222236
* @param forceRefresh - When true, the auth layer should trigger a server-side
223237
* token refresh to get updated claims (e.g., after zkEVM registration).
224238
* This ensures the returned user has the latest data from the identity provider.
239+
* @param options - Optional. When options.silent is true, return null if not
240+
* authenticated instead of triggering login (avoids popup on page load).
225241
*/
226-
export type GetUserFunction = (forceRefresh?: boolean) => Promise<User | null>;
242+
export type GetUserFunction = (
243+
forceRefresh?: boolean,
244+
options?: GetUserOptions,
245+
) => Promise<User | null>;
227246

228247
/**
229248
* Options for connecting a wallet via connectWallet()

packages/wallet/src/zkEvm/zkEvmProvider.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,9 @@ export class ZkEvmProvider implements Provider {
121121
this.#callSessionActivity(user.zkEvm.ethAddress);
122122
}
123123

124-
// Listen for logout events
124+
// Listen for auth events
125125
walletEventEmitter.on(WalletEvents.LOGGED_OUT, this.#handleLogout);
126+
walletEventEmitter.on(WalletEvents.LOGGED_IN, this.#handleLoggedIn);
126127
walletEventEmitter.on(
127128
WalletEvents.ACCOUNTS_REQUESTED,
128129
trackSessionActivity,
@@ -133,11 +134,25 @@ export class ZkEvmProvider implements Provider {
133134
this.#providerEventEmitter.emit(ProviderEvent.ACCOUNTS_CHANGED, []);
134135
};
135136

137+
#handleLoggedIn = (user: User) => {
138+
if (user && isZkEvmUser(user)) {
139+
this.#providerEventEmitter.emit(ProviderEvent.ACCOUNTS_CHANGED, [
140+
user.zkEvm.ethAddress,
141+
]);
142+
this.#callSessionActivity(user.zkEvm.ethAddress);
143+
}
144+
// If user doesn't have zkEvm yet, app must call eth_requestAccounts to register
145+
};
146+
136147
/**
137148
* Get the current user using getUser function.
149+
* @param silent - When true, use getUser(undefined, { silent: true }) to avoid
150+
* triggering login popup on read-only checks (e.g. eth_accounts).
138151
*/
139-
async #getCurrentUser(): Promise<User | null> {
140-
return this.#getUser();
152+
async #getCurrentUser(silent = false): Promise<User | null> {
153+
return silent
154+
? this.#getUser(undefined, { silent: true })
155+
: this.#getUser();
141156
}
142157

143158
async #callSessionActivity(zkEvmAddress: string) {
@@ -174,7 +189,7 @@ export class ZkEvmProvider implements Provider {
174189
// Used to get the registered zkEvm address from the User session
175190
async #getZkEvmAddress() {
176191
try {
177-
const user = await this.#getCurrentUser();
192+
const user = await this.#getCurrentUser(true); // silent: avoid popup on read-only checks
178193
if (user && isZkEvmUser(user)) {
179194
return user.zkEvm.ethAddress;
180195
}

0 commit comments

Comments
 (0)