Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions packages/no-modal/src/noModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,6 @@ export class Web3AuthNoModal extends SafeEventEmitter<Web3AuthNoModalEvents> imp
this.removeListener(CONNECTOR_EVENTS.CONNECTED, onConnected);
this.removeListener(CONNECTOR_EVENTS.ERRORED, onErrored);
this.removeListener(CONNECTOR_EVENTS.AUTHORIZED, onAuthorized);
this.removeListener(CONNECTOR_EVENTS.CONSENT_ACCEPTED, onConsentAccepted);
};

const checkCompletion = async () => {
Expand Down Expand Up @@ -453,10 +452,6 @@ export class Web3AuthNoModal extends SafeEventEmitter<Web3AuthNoModalEvents> imp
await checkCompletion();
};

const onConsentAccepted = async () => {
await completeConnection();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale listeners from connectTo when consent required

Medium Severity

Removing the CONSENT_ACCEPTED listener from connectTo causes the promise to hang indefinitely when consentRequired is true. The modal SDK sets consentRequired = true and internally calls connectTo from onSocialLogin and onExternalWalletLogin. When consent is required, the subscribeToConnectorEvents CONNECTED handler emits CONSENT_REQUIRING instead of CONNECTED, so connectTo's onConnected never fires and cleanup() is never called. The stale once listeners for CONNECTED, ERRORED, and AUTHORIZED accumulate on the SDK and can fire on subsequent connections, causing duplicate CONNECTION_COMPLETED analytics events.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit df7fc66. Configure here.

const onErrored = async (err: Web3AuthError) => {
// track connection failed event
this.analytics.track(ANALYTICS_EVENTS.CONNECTION_FAILED, {
Expand All @@ -472,9 +467,6 @@ export class Web3AuthNoModal extends SafeEventEmitter<Web3AuthNoModalEvents> imp
if (finalLoginParams.getAuthTokenInfo) {
this.once(CONNECTOR_EVENTS.AUTHORIZED, onAuthorized);
}
if (this.consentRequired) {
this.once(CONNECTOR_EVENTS.CONSENT_ACCEPTED, onConsentAccepted);
}
this.once(CONNECTOR_EVENTS.ERRORED, onErrored);
connector.connect(finalLoginParams);
this.setCurrentChain(initialChain.chainId);
Expand Down
77 changes: 27 additions & 50 deletions packages/no-modal/test/noModal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,20 +286,8 @@ describe("Web3AuthNoModal", () => {
const ethereumProvider = { request: vi.fn().mockResolvedValue(["0xAbC123"]) };
const authorizedListener = vi.fn();
sdk.on(CONNECTOR_EVENTS.AUTHORIZED, authorizedListener);
const connector = new MockConnector({ name: WALLET_CONNECTORS.METAMASK } as never);
(sdk as unknown as { connectors: MockConnector[] }).connectors = [connector];
sdk.exposeSubscribeToConnectorEvents(connector);
(sdk as unknown as { commonJRPCProvider: Record<string, unknown> }).commonJRPCProvider = {
updateProviderEngineProxy: vi.fn(),
removeAllListeners: vi.fn(),
};

connector.emit(CONNECTOR_EVENTS.CONNECTED, {
connectorName: WALLET_CONNECTORS.METAMASK,
ethereumProvider: ethereumProvider as never,
solanaWallet: null,
reconnected: false,
});
const connector = emitMetaMaskConnected(sdk, ethereumProvider);
await vi.waitFor(() => {
expect(sdk.status).toBe(CONNECTOR_STATUS.CONNECTED);
});
Expand Down Expand Up @@ -328,29 +316,15 @@ describe("Web3AuthNoModal", () => {
sdk.on(CONNECTOR_EVENTS.CONSENT_REQUIRING, consentRequiredListener);

const ethereumProvider = { request: vi.fn().mockResolvedValue(["0xBEEF"]) };
const connector = new MockConnector({ name: WALLET_CONNECTORS.METAMASK } as never);
connector.connect = vi.fn(async () => {
connector.emit(CONNECTOR_EVENTS.CONNECTED, {
connectorName: WALLET_CONNECTORS.METAMASK,
ethereumProvider: ethereumProvider as never,
solanaWallet: null,
reconnected: false,
});
return null;
});
(sdk as unknown as { connectors: MockConnector[] }).connectors = [connector];
sdk.exposeSubscribeToConnectorEvents(connector);
(sdk as unknown as { commonJRPCProvider: Record<string, unknown> }).commonJRPCProvider = {
updateProviderEngineProxy: vi.fn(),
removeAllListeners: vi.fn(),
};

const connectionPromise = sdk.connectTo(WALLET_CONNECTORS.METAMASK);
emitMetaMaskConnected(sdk, ethereumProvider);
await vi.waitFor(() => {
expect(consentRequiredListener).toHaveBeenCalledTimes(1);
});

expect(sdk.status).toBe(CONNECTOR_STATUS.CONSENT_REQUIRING);
await sdk.exposeCompleteConsentAcceptance();
await expect(connectionPromise).resolves.not.toBeNull();
expect(sdk.status).toBe(CONNECTOR_STATUS.CONNECTED);
});

it("persists user consent when user accepts consent UI", async () => {
Expand All @@ -364,29 +338,12 @@ describe("Web3AuthNoModal", () => {
const consentRequiredListener = vi.fn();
sdk.on(CONNECTOR_EVENTS.CONSENT_REQUIRING, consentRequiredListener);
const ethereumProvider = { request: vi.fn().mockResolvedValue(["0xFEEd"]) };
const connector = new MockConnector({ name: WALLET_CONNECTORS.METAMASK } as never);
connector.connect = vi.fn(async () => {
connector.emit(CONNECTOR_EVENTS.CONNECTED, {
connectorName: WALLET_CONNECTORS.METAMASK,
ethereumProvider: ethereumProvider as never,
solanaWallet: null,
reconnected: false,
});
return null;
});
(sdk as unknown as { connectors: MockConnector[] }).connectors = [connector];
sdk.exposeSubscribeToConnectorEvents(connector);
(sdk as unknown as { commonJRPCProvider: Record<string, unknown> }).commonJRPCProvider = {
updateProviderEngineProxy: vi.fn(),
removeAllListeners: vi.fn(),
};

const connectionPromise = sdk.connectTo(WALLET_CONNECTORS.METAMASK);
emitMetaMaskConnected(sdk, ethereumProvider);
await vi.waitFor(() => {
expect(consentRequiredListener).toHaveBeenCalledTimes(1);
});
await sdk.exposeCompleteConsentAcceptance();
await connectionPromise;

const stateJson = await storage.get(WEB3AUTH_STATE_STORAGE_KEY);
const state = JSON.parse(stateJson!);
Expand Down Expand Up @@ -434,16 +391,36 @@ describe("Web3AuthNoModal", () => {
});
});

function createSdk(overrides: Record<string, unknown> = {}, initialState?: Record<string, unknown>) {
function createSdk(overrides: Record<string, unknown> = {}, initialState?: Record<string, unknown>): TestWeb3AuthNoModal {
const storage = createMockStorage();
return new TestWeb3AuthNoModal(
{
clientId: "test-client-id",
web3AuthNetwork: "sapphire_devnet",
chains: [createChain()],
disableAnalytics: true,
storage: { sessionId: storage },
...overrides,
} as never,
initialState as never
);
}

function emitMetaMaskConnected(sdk: TestWeb3AuthNoModal, ethereumProvider: unknown): MockConnector {
const connector = new MockConnector({ name: WALLET_CONNECTORS.METAMASK } as never);
(sdk as unknown as { connectors: MockConnector[] }).connectors = [connector];
sdk.exposeSubscribeToConnectorEvents(connector);
(sdk as unknown as { commonJRPCProvider: Record<string, unknown> }).commonJRPCProvider = {
updateProviderEngineProxy: vi.fn(),
removeAllListeners: vi.fn(),
};

connector.emit(CONNECTOR_EVENTS.CONNECTED, {
connectorName: WALLET_CONNECTORS.METAMASK,
ethereumProvider: ethereumProvider as never,
solanaWallet: null,
reconnected: false,
});

return connector;
}
Loading