Skip to content

Commit fb00879

Browse files
committed
fix: ensure iframe is initialized once
1 parent 4842da8 commit fb00879

File tree

1 file changed

+28
-5
lines changed

1 file changed

+28
-5
lines changed

packages/keyring-eth-ledger-bridge/src/ledger-iframe-bridge.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { createDeferredPromise, DeferredPromise } from '@metamask/utils';
2+
13
import {
24
GetPublicKeyParams,
35
GetPublicKeyResponse,
@@ -13,7 +15,7 @@ import { projectLogger as log } from './logger';
1315

1416
const LEDGER_IFRAME_ID = 'LEDGER-IFRAME';
1517

16-
const IFRAME_INIT_TIMEOUT = 4000;
18+
export const IFRAME_INIT_TIMEOUT = 4000;
1719

1820
export enum IFrameMessageAction {
1921
LedgerConnectionChange = 'ledger-connection-change',
@@ -113,6 +115,8 @@ export class LedgerIframeBridge
113115
messageCallbacks: Record<number, (response: IFrameMessageResponse) => void> =
114116
{};
115117

118+
#iframeInitPromise?: DeferredPromise;
119+
116120
constructor(
117121
opts: LedgerIframeBridgeOptions = {
118122
bridgeUrl: 'https://metamask.github.io/eth-ledger-bridge-keyring',
@@ -299,15 +303,33 @@ export class LedgerIframeBridge
299303
}
300304

301305
async #init(): Promise<void> {
302-
await this.#setupIframe(this.#opts.bridgeUrl);
303-
this.eventListener = this.#eventListener.bind(this, this.#opts.bridgeUrl);
304-
window.addEventListener('message', this.eventListener);
306+
if (this.#iframeInitPromise) {
307+
// if the iframe is already being initialized, we return the promise
308+
// to avoid multiple initialization attempts
309+
return this.#iframeInitPromise.promise;
310+
}
311+
312+
this.#iframeInitPromise = createDeferredPromise({
313+
suppressUnhandledRejection: true,
314+
});
315+
316+
try {
317+
await this.#setupIframe(this.#opts.bridgeUrl);
318+
this.eventListener = this.#eventListener.bind(this, this.#opts.bridgeUrl);
319+
window.addEventListener('message', this.eventListener);
320+
this.#iframeInitPromise.resolve();
321+
} catch (error) {
322+
this.#iframeInitPromise.reject(error);
323+
throw error;
324+
} finally {
325+
this.#iframeInitPromise = undefined;
326+
}
305327
}
306328

307329
async #setupIframe(bridgeUrl: string): Promise<void> {
308330
const timeout = new Promise((_, reject) => {
309331
setTimeout(() => {
310-
reject(new Error('Ledger initialization timed out'));
332+
reject(new Error('Bridge initialization timed out'));
311333
}, IFRAME_INIT_TIMEOUT);
312334
});
313335

@@ -322,6 +344,7 @@ export class LedgerIframeBridge
322344
this.iframe.onerror = (): void => {
323345
if (this.iframe) {
324346
document.head.removeChild(this.iframe);
347+
this.iframe = undefined;
325348
}
326349
this.iframeLoaded = false;
327350
reject(new Error('Failed to load iframe'));

0 commit comments

Comments
 (0)