@dynamic-labs-wallet/browser-wallet-client 0.0.337 → 0.0.338
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs +109 -27
- package/index.esm.js +110 -28
- package/package.json +2 -2
- package/src/client/iframeManager/IframeManager.d.ts +13 -0
- package/src/client/iframeManager/IframeManager.d.ts.map +1 -1
- package/src/services/messageTransportBridge.d.ts +1 -1
- package/src/services/messageTransportBridge.d.ts.map +1 -1
package/index.cjs
CHANGED
|
@@ -182,29 +182,26 @@ class iframeMessageHandler {
|
|
|
182
182
|
const logger = new logger$1.Logger('DynamicWaasWalletClient');
|
|
183
183
|
|
|
184
184
|
const setupMessageTransportBridge = (messageTransport$1, iframe, iframeOrigin)=>{
|
|
185
|
-
if (!(iframe == null ? void 0 : iframe.contentWindow)) {
|
|
186
|
-
throw new Error('Iframe or contentWindow not available');
|
|
187
|
-
}
|
|
188
185
|
const logger = new logger$1.Logger('debug');
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
} catch (error) {
|
|
200
|
-
// This catches "Attempt to postMessage on disconnected port" errors
|
|
201
|
-
// which occur when the iframe has been navigated away or destroyed
|
|
202
|
-
logger.error('Failed to post message to iframe:', error);
|
|
203
|
-
// Re-throw to trigger recovery mechanism and timeout handling
|
|
204
|
-
throw error;
|
|
205
|
-
}
|
|
186
|
+
function hostListener(message) {
|
|
187
|
+
if (message.origin !== 'host') return;
|
|
188
|
+
// Detached iframe (relogin / RN modal unmount left a stale ref): log and
|
|
189
|
+
// drop. The request channel's 5s timeout will trigger the transport's
|
|
190
|
+
// recovery manager, which rebuilds the iframe and retries on a fresh
|
|
191
|
+
// bridge. Throwing here would break that recovery flow because the
|
|
192
|
+
// listener runs synchronously inside emit().
|
|
193
|
+
if (!(iframe == null ? void 0 : iframe.contentWindow) || iframe.isConnected === false) {
|
|
194
|
+
logger.error('Cannot send message to iframe: contentWindow is unavailable');
|
|
195
|
+
return;
|
|
206
196
|
}
|
|
207
|
-
|
|
197
|
+
try {
|
|
198
|
+
iframe.contentWindow.postMessage(message, iframeOrigin);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
// "Attempt to postMessage on disconnected port" (Firefox) — same path: log + drop.
|
|
201
|
+
logger.error('Failed to post message to iframe:', error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
messageTransport$1.on(hostListener);
|
|
208
205
|
const handleIncomingMessage = (message)=>{
|
|
209
206
|
const { data } = message;
|
|
210
207
|
if (!data) return;
|
|
@@ -231,9 +228,41 @@ const setupMessageTransportBridge = (messageTransport$1, iframe, iframeOrigin)=>
|
|
|
231
228
|
/**
|
|
232
229
|
* Handle incoming message from iOS client
|
|
233
230
|
*/ window.addEventListener('message', handleIncomingMessage);
|
|
231
|
+
return ()=>{
|
|
232
|
+
messageTransport$1.off(hostListener);
|
|
233
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
234
|
+
// @ts-expect-error
|
|
235
|
+
document.removeEventListener('message', handleIncomingMessage);
|
|
236
|
+
window.removeEventListener('message', handleIncomingMessage);
|
|
237
|
+
};
|
|
234
238
|
};
|
|
235
239
|
|
|
236
240
|
const FRAME_ANCESTORS_QUERY_PARAM = 'frameAncestors';
|
|
241
|
+
/**
|
|
242
|
+
* Builds the iframe message transport with the recovery + block decorators.
|
|
243
|
+
*
|
|
244
|
+
* The transport stack from outside in:
|
|
245
|
+
* applyRecoveryManager — exposes recoveryManager + retry tracking used by
|
|
246
|
+
* the request channel's 5s timeout.
|
|
247
|
+
* applyDefaultMessageOrigin — stamps outgoing messages with origin: 'host'.
|
|
248
|
+
* makeWaitForUnblock — lets us pause emit() while we rebuild a torn-down
|
|
249
|
+
* iframe so in-flight retries land on a fresh bridge
|
|
250
|
+
* instead of the dead one. bypassBlockIf lets the
|
|
251
|
+
* re-auth message and incoming webview messages flow
|
|
252
|
+
* during the blocked window — without that bypass,
|
|
253
|
+
* the queued retry would arrive at the rebuilt iframe
|
|
254
|
+
* before its auth token and get rejected.
|
|
255
|
+
*/ function createIframeMessageTransport() {
|
|
256
|
+
return messageTransport.applyRecoveryManager({
|
|
257
|
+
messageTransport: messageTransport.applyDefaultMessageOrigin({
|
|
258
|
+
defaultOrigin: 'host',
|
|
259
|
+
messageTransport: messageTransport.makeWaitForUnblock({
|
|
260
|
+
messageTransport: messageTransport.createMessageTransport(),
|
|
261
|
+
bypassBlockIf: (message)=>message.origin === 'webview' || message.type === 'sendAuthToken'
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
});
|
|
265
|
+
}
|
|
237
266
|
class IframeManager {
|
|
238
267
|
// Simply load the iframe from localhost
|
|
239
268
|
async initialize() {
|
|
@@ -267,21 +296,66 @@ class IframeManager {
|
|
|
267
296
|
return;
|
|
268
297
|
}
|
|
269
298
|
await this.initializeIframeCommunication();
|
|
270
|
-
const transport =
|
|
271
|
-
defaultOrigin: 'host',
|
|
272
|
-
messageTransport: messageTransport.createMessageTransport()
|
|
273
|
-
});
|
|
299
|
+
const transport = createIframeMessageTransport();
|
|
274
300
|
this.messageTransport = transport;
|
|
275
301
|
if (!this.iframe) {
|
|
276
302
|
throw new Error('Iframe not available');
|
|
277
303
|
}
|
|
278
|
-
setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
|
|
304
|
+
this.cleanupBridge = setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
|
|
279
305
|
this.iframeMessageHandler = new iframeMessageHandler(this.messageTransport);
|
|
280
306
|
// Set up request channel to handle messages from iframe (for secureStorage and getSignedSessionId)
|
|
281
307
|
if (this.secureStorage || this.getSignedSessionIdCallback) {
|
|
282
308
|
this.iframeRequestChannel = this.setupIframeRequestHandlers(this.messageTransport);
|
|
283
309
|
}
|
|
310
|
+
// The recovery callback runs synchronously when the request channel's
|
|
311
|
+
// timeout calls triggerRecovery. block() must run before we await anything
|
|
312
|
+
// so the request channel's recursive sendMessage() lands on the queue
|
|
313
|
+
// instead of the dead bridge.
|
|
314
|
+
transport.recoveryManager.onRecoveryRequested(()=>{
|
|
315
|
+
this.logger.info('(recoverIframe) Iframe transport recovery requested — rebuilding iframe');
|
|
316
|
+
transport.block();
|
|
317
|
+
void this.recoverIframe(transport).catch((error)=>{
|
|
318
|
+
this.logger.error('(recoverIframe) Iframe recovery failed:', error);
|
|
319
|
+
// Unblock so the queued retry rejects with a real error rather than
|
|
320
|
+
// hanging forever waiting on a transport that will never flow.
|
|
321
|
+
transport.unblock();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
// Transport starts blocked (default of makeWaitForUnblock). Release it
|
|
325
|
+
// before sending the auth token so the initial message flows normally.
|
|
326
|
+
transport.unblock();
|
|
327
|
+
await this.initAuthToken();
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Rebuild the iframe and its bridge while preserving the message transport
|
|
331
|
+
* (which owns the request channel's retry timer and recovery manager).
|
|
332
|
+
*
|
|
333
|
+
* Triggered by the request channel's recovery flow when an outgoing message
|
|
334
|
+
* goes 5s without an ack — typically because the iframe was torn down by a
|
|
335
|
+
* relogin or RN modal unmount and the host still holds a stale reference.
|
|
336
|
+
*/ async recoverIframe(transport) {
|
|
337
|
+
var // Drop iframe-only state. The transport, iframeMessageHandler, and
|
|
338
|
+
// iframeRequestChannel are intentionally kept — they're tied to the
|
|
339
|
+
// request channel's in-flight retry that we want to flush after rebuild.
|
|
340
|
+
_IframeManager_sharedIframe;
|
|
341
|
+
// Detach listeners attached to the dead iframe.
|
|
342
|
+
this.cleanupBridge == null ? void 0 : this.cleanupBridge.call(this);
|
|
343
|
+
this.cleanupBridge = null;
|
|
344
|
+
(_IframeManager_sharedIframe = IframeManager.sharedIframe) == null ? void 0 : _IframeManager_sharedIframe.remove();
|
|
345
|
+
IframeManager.sharedIframe = null;
|
|
346
|
+
IframeManager.iframeLoadPromise = null;
|
|
347
|
+
this.iframe = null;
|
|
348
|
+
await this.loadIframe();
|
|
349
|
+
if (!this.iframe) {
|
|
350
|
+
throw new Error('Failed to mount fresh iframe during recovery');
|
|
351
|
+
}
|
|
352
|
+
this.cleanupBridge = setupMessageTransportBridge(transport, this.iframe, this.iframeDomain);
|
|
353
|
+
// Re-send auth token to the new iframe before the queued retry flushes.
|
|
354
|
+
// sendAuthToken bypasses the block (see bypassBlockIf in
|
|
355
|
+
// createIframeMessageTransport), so this resolves before unblock() runs
|
|
356
|
+
// and the queued operation hits an authenticated iframe.
|
|
284
357
|
await this.initAuthToken();
|
|
358
|
+
transport.unblock();
|
|
285
359
|
}
|
|
286
360
|
/**
|
|
287
361
|
* Sets up message handlers for iframe → host requests (secureStorage and getSignedSessionId)
|
|
@@ -356,6 +430,8 @@ class IframeManager {
|
|
|
356
430
|
/**
|
|
357
431
|
* Reset the shared iframe and iframe load promise, and iframe instance count
|
|
358
432
|
*/ async resetSharedIframe() {
|
|
433
|
+
this.cleanupBridge == null ? void 0 : this.cleanupBridge.call(this);
|
|
434
|
+
this.cleanupBridge = null;
|
|
359
435
|
IframeManager.sharedIframe = null;
|
|
360
436
|
IframeManager.iframeInstanceCount = 0;
|
|
361
437
|
IframeManager.iframeLoadPromise = null;
|
|
@@ -648,7 +724,7 @@ class IframeManager {
|
|
|
648
724
|
defaultOrigin: 'host',
|
|
649
725
|
messageTransport: messageTransport.createMessageTransport()
|
|
650
726
|
});
|
|
651
|
-
setupMessageTransportBridge(transport, iframe, this.iframeDomain);
|
|
727
|
+
const cleanupBridge = setupMessageTransportBridge(transport, iframe, this.iframeDomain);
|
|
652
728
|
const iframeDisplay = new iframeMessageHandler(transport);
|
|
653
729
|
// Set up iframe request handlers on this transport so the display iframe
|
|
654
730
|
// can access key shares (e.g. for exportPrivateKey) and signed session ID
|
|
@@ -663,6 +739,7 @@ class IframeManager {
|
|
|
663
739
|
iframe,
|
|
664
740
|
iframeDisplay,
|
|
665
741
|
cleanup: ()=>{
|
|
742
|
+
cleanupBridge();
|
|
666
743
|
container.removeChild(iframe);
|
|
667
744
|
}
|
|
668
745
|
};
|
|
@@ -680,6 +757,8 @@ class IframeManager {
|
|
|
680
757
|
if (this.iframe) {
|
|
681
758
|
IframeManager.iframeInstanceCount--;
|
|
682
759
|
if (IframeManager.sharedIframe && IframeManager.iframeInstanceCount === 0) {
|
|
760
|
+
this.cleanupBridge == null ? void 0 : this.cleanupBridge.call(this);
|
|
761
|
+
this.cleanupBridge = null;
|
|
683
762
|
document.body.removeChild(IframeManager.sharedIframe);
|
|
684
763
|
IframeManager.sharedIframe = null;
|
|
685
764
|
IframeManager.iframeLoadPromise = null;
|
|
@@ -693,6 +772,9 @@ class IframeManager {
|
|
|
693
772
|
this.iframeDomain = null;
|
|
694
773
|
this.messageTransport = null;
|
|
695
774
|
this.iframeMessageHandler = null;
|
|
775
|
+
/** Teardown function returned by setupMessageTransportBridge — detaches all
|
|
776
|
+
* listeners. Tracked so we can rebuild the bridge on iframe recovery without
|
|
777
|
+
* leaking listeners against the dead iframe. */ this.cleanupBridge = null;
|
|
696
778
|
this.iframe = null;
|
|
697
779
|
this.environmentId = environmentId;
|
|
698
780
|
this.authToken = authToken;
|
package/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AuthMode, getEnvironmentFromUrl, IFRAME_DOMAIN_MAP, WalletOperation } from '@dynamic-labs-wallet/core';
|
|
2
2
|
export { AuthMode, MPC_RELAY_PREPROD_API_URL, MPC_RELAY_PROD_API_URL, ThresholdSignatureScheme, WalletOperation } from '@dynamic-labs-wallet/core';
|
|
3
|
-
import { createRequestChannel, parseMessageTransportData, applyDefaultMessageOrigin, createMessageTransport } from '@dynamic-labs/message-transport';
|
|
3
|
+
import { createRequestChannel, parseMessageTransportData, applyDefaultMessageOrigin, createMessageTransport, applyRecoveryManager, makeWaitForUnblock } from '@dynamic-labs/message-transport';
|
|
4
4
|
import { Logger } from '@dynamic-labs/logger';
|
|
5
5
|
|
|
6
6
|
function _extends() {
|
|
@@ -181,29 +181,26 @@ class iframeMessageHandler {
|
|
|
181
181
|
const logger = new Logger('DynamicWaasWalletClient');
|
|
182
182
|
|
|
183
183
|
const setupMessageTransportBridge = (messageTransport, iframe, iframeOrigin)=>{
|
|
184
|
-
if (!(iframe == null ? void 0 : iframe.contentWindow)) {
|
|
185
|
-
throw new Error('Iframe or contentWindow not available');
|
|
186
|
-
}
|
|
187
184
|
const logger = new Logger('debug');
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
} catch (error) {
|
|
199
|
-
// This catches "Attempt to postMessage on disconnected port" errors
|
|
200
|
-
// which occur when the iframe has been navigated away or destroyed
|
|
201
|
-
logger.error('Failed to post message to iframe:', error);
|
|
202
|
-
// Re-throw to trigger recovery mechanism and timeout handling
|
|
203
|
-
throw error;
|
|
204
|
-
}
|
|
185
|
+
function hostListener(message) {
|
|
186
|
+
if (message.origin !== 'host') return;
|
|
187
|
+
// Detached iframe (relogin / RN modal unmount left a stale ref): log and
|
|
188
|
+
// drop. The request channel's 5s timeout will trigger the transport's
|
|
189
|
+
// recovery manager, which rebuilds the iframe and retries on a fresh
|
|
190
|
+
// bridge. Throwing here would break that recovery flow because the
|
|
191
|
+
// listener runs synchronously inside emit().
|
|
192
|
+
if (!(iframe == null ? void 0 : iframe.contentWindow) || iframe.isConnected === false) {
|
|
193
|
+
logger.error('Cannot send message to iframe: contentWindow is unavailable');
|
|
194
|
+
return;
|
|
205
195
|
}
|
|
206
|
-
|
|
196
|
+
try {
|
|
197
|
+
iframe.contentWindow.postMessage(message, iframeOrigin);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
// "Attempt to postMessage on disconnected port" (Firefox) — same path: log + drop.
|
|
200
|
+
logger.error('Failed to post message to iframe:', error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
messageTransport.on(hostListener);
|
|
207
204
|
const handleIncomingMessage = (message)=>{
|
|
208
205
|
const { data } = message;
|
|
209
206
|
if (!data) return;
|
|
@@ -230,9 +227,41 @@ const setupMessageTransportBridge = (messageTransport, iframe, iframeOrigin)=>{
|
|
|
230
227
|
/**
|
|
231
228
|
* Handle incoming message from iOS client
|
|
232
229
|
*/ window.addEventListener('message', handleIncomingMessage);
|
|
230
|
+
return ()=>{
|
|
231
|
+
messageTransport.off(hostListener);
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
233
|
+
// @ts-expect-error
|
|
234
|
+
document.removeEventListener('message', handleIncomingMessage);
|
|
235
|
+
window.removeEventListener('message', handleIncomingMessage);
|
|
236
|
+
};
|
|
233
237
|
};
|
|
234
238
|
|
|
235
239
|
const FRAME_ANCESTORS_QUERY_PARAM = 'frameAncestors';
|
|
240
|
+
/**
|
|
241
|
+
* Builds the iframe message transport with the recovery + block decorators.
|
|
242
|
+
*
|
|
243
|
+
* The transport stack from outside in:
|
|
244
|
+
* applyRecoveryManager — exposes recoveryManager + retry tracking used by
|
|
245
|
+
* the request channel's 5s timeout.
|
|
246
|
+
* applyDefaultMessageOrigin — stamps outgoing messages with origin: 'host'.
|
|
247
|
+
* makeWaitForUnblock — lets us pause emit() while we rebuild a torn-down
|
|
248
|
+
* iframe so in-flight retries land on a fresh bridge
|
|
249
|
+
* instead of the dead one. bypassBlockIf lets the
|
|
250
|
+
* re-auth message and incoming webview messages flow
|
|
251
|
+
* during the blocked window — without that bypass,
|
|
252
|
+
* the queued retry would arrive at the rebuilt iframe
|
|
253
|
+
* before its auth token and get rejected.
|
|
254
|
+
*/ function createIframeMessageTransport() {
|
|
255
|
+
return applyRecoveryManager({
|
|
256
|
+
messageTransport: applyDefaultMessageOrigin({
|
|
257
|
+
defaultOrigin: 'host',
|
|
258
|
+
messageTransport: makeWaitForUnblock({
|
|
259
|
+
messageTransport: createMessageTransport(),
|
|
260
|
+
bypassBlockIf: (message)=>message.origin === 'webview' || message.type === 'sendAuthToken'
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
});
|
|
264
|
+
}
|
|
236
265
|
class IframeManager {
|
|
237
266
|
// Simply load the iframe from localhost
|
|
238
267
|
async initialize() {
|
|
@@ -266,21 +295,66 @@ class IframeManager {
|
|
|
266
295
|
return;
|
|
267
296
|
}
|
|
268
297
|
await this.initializeIframeCommunication();
|
|
269
|
-
const transport =
|
|
270
|
-
defaultOrigin: 'host',
|
|
271
|
-
messageTransport: createMessageTransport()
|
|
272
|
-
});
|
|
298
|
+
const transport = createIframeMessageTransport();
|
|
273
299
|
this.messageTransport = transport;
|
|
274
300
|
if (!this.iframe) {
|
|
275
301
|
throw new Error('Iframe not available');
|
|
276
302
|
}
|
|
277
|
-
setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
|
|
303
|
+
this.cleanupBridge = setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
|
|
278
304
|
this.iframeMessageHandler = new iframeMessageHandler(this.messageTransport);
|
|
279
305
|
// Set up request channel to handle messages from iframe (for secureStorage and getSignedSessionId)
|
|
280
306
|
if (this.secureStorage || this.getSignedSessionIdCallback) {
|
|
281
307
|
this.iframeRequestChannel = this.setupIframeRequestHandlers(this.messageTransport);
|
|
282
308
|
}
|
|
309
|
+
// The recovery callback runs synchronously when the request channel's
|
|
310
|
+
// timeout calls triggerRecovery. block() must run before we await anything
|
|
311
|
+
// so the request channel's recursive sendMessage() lands on the queue
|
|
312
|
+
// instead of the dead bridge.
|
|
313
|
+
transport.recoveryManager.onRecoveryRequested(()=>{
|
|
314
|
+
this.logger.info('(recoverIframe) Iframe transport recovery requested — rebuilding iframe');
|
|
315
|
+
transport.block();
|
|
316
|
+
void this.recoverIframe(transport).catch((error)=>{
|
|
317
|
+
this.logger.error('(recoverIframe) Iframe recovery failed:', error);
|
|
318
|
+
// Unblock so the queued retry rejects with a real error rather than
|
|
319
|
+
// hanging forever waiting on a transport that will never flow.
|
|
320
|
+
transport.unblock();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
// Transport starts blocked (default of makeWaitForUnblock). Release it
|
|
324
|
+
// before sending the auth token so the initial message flows normally.
|
|
325
|
+
transport.unblock();
|
|
326
|
+
await this.initAuthToken();
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Rebuild the iframe and its bridge while preserving the message transport
|
|
330
|
+
* (which owns the request channel's retry timer and recovery manager).
|
|
331
|
+
*
|
|
332
|
+
* Triggered by the request channel's recovery flow when an outgoing message
|
|
333
|
+
* goes 5s without an ack — typically because the iframe was torn down by a
|
|
334
|
+
* relogin or RN modal unmount and the host still holds a stale reference.
|
|
335
|
+
*/ async recoverIframe(transport) {
|
|
336
|
+
var // Drop iframe-only state. The transport, iframeMessageHandler, and
|
|
337
|
+
// iframeRequestChannel are intentionally kept — they're tied to the
|
|
338
|
+
// request channel's in-flight retry that we want to flush after rebuild.
|
|
339
|
+
_IframeManager_sharedIframe;
|
|
340
|
+
// Detach listeners attached to the dead iframe.
|
|
341
|
+
this.cleanupBridge == null ? void 0 : this.cleanupBridge.call(this);
|
|
342
|
+
this.cleanupBridge = null;
|
|
343
|
+
(_IframeManager_sharedIframe = IframeManager.sharedIframe) == null ? void 0 : _IframeManager_sharedIframe.remove();
|
|
344
|
+
IframeManager.sharedIframe = null;
|
|
345
|
+
IframeManager.iframeLoadPromise = null;
|
|
346
|
+
this.iframe = null;
|
|
347
|
+
await this.loadIframe();
|
|
348
|
+
if (!this.iframe) {
|
|
349
|
+
throw new Error('Failed to mount fresh iframe during recovery');
|
|
350
|
+
}
|
|
351
|
+
this.cleanupBridge = setupMessageTransportBridge(transport, this.iframe, this.iframeDomain);
|
|
352
|
+
// Re-send auth token to the new iframe before the queued retry flushes.
|
|
353
|
+
// sendAuthToken bypasses the block (see bypassBlockIf in
|
|
354
|
+
// createIframeMessageTransport), so this resolves before unblock() runs
|
|
355
|
+
// and the queued operation hits an authenticated iframe.
|
|
283
356
|
await this.initAuthToken();
|
|
357
|
+
transport.unblock();
|
|
284
358
|
}
|
|
285
359
|
/**
|
|
286
360
|
* Sets up message handlers for iframe → host requests (secureStorage and getSignedSessionId)
|
|
@@ -355,6 +429,8 @@ class IframeManager {
|
|
|
355
429
|
/**
|
|
356
430
|
* Reset the shared iframe and iframe load promise, and iframe instance count
|
|
357
431
|
*/ async resetSharedIframe() {
|
|
432
|
+
this.cleanupBridge == null ? void 0 : this.cleanupBridge.call(this);
|
|
433
|
+
this.cleanupBridge = null;
|
|
358
434
|
IframeManager.sharedIframe = null;
|
|
359
435
|
IframeManager.iframeInstanceCount = 0;
|
|
360
436
|
IframeManager.iframeLoadPromise = null;
|
|
@@ -647,7 +723,7 @@ class IframeManager {
|
|
|
647
723
|
defaultOrigin: 'host',
|
|
648
724
|
messageTransport: createMessageTransport()
|
|
649
725
|
});
|
|
650
|
-
setupMessageTransportBridge(transport, iframe, this.iframeDomain);
|
|
726
|
+
const cleanupBridge = setupMessageTransportBridge(transport, iframe, this.iframeDomain);
|
|
651
727
|
const iframeDisplay = new iframeMessageHandler(transport);
|
|
652
728
|
// Set up iframe request handlers on this transport so the display iframe
|
|
653
729
|
// can access key shares (e.g. for exportPrivateKey) and signed session ID
|
|
@@ -662,6 +738,7 @@ class IframeManager {
|
|
|
662
738
|
iframe,
|
|
663
739
|
iframeDisplay,
|
|
664
740
|
cleanup: ()=>{
|
|
741
|
+
cleanupBridge();
|
|
665
742
|
container.removeChild(iframe);
|
|
666
743
|
}
|
|
667
744
|
};
|
|
@@ -679,6 +756,8 @@ class IframeManager {
|
|
|
679
756
|
if (this.iframe) {
|
|
680
757
|
IframeManager.iframeInstanceCount--;
|
|
681
758
|
if (IframeManager.sharedIframe && IframeManager.iframeInstanceCount === 0) {
|
|
759
|
+
this.cleanupBridge == null ? void 0 : this.cleanupBridge.call(this);
|
|
760
|
+
this.cleanupBridge = null;
|
|
682
761
|
document.body.removeChild(IframeManager.sharedIframe);
|
|
683
762
|
IframeManager.sharedIframe = null;
|
|
684
763
|
IframeManager.iframeLoadPromise = null;
|
|
@@ -692,6 +771,9 @@ class IframeManager {
|
|
|
692
771
|
this.iframeDomain = null;
|
|
693
772
|
this.messageTransport = null;
|
|
694
773
|
this.iframeMessageHandler = null;
|
|
774
|
+
/** Teardown function returned by setupMessageTransportBridge — detaches all
|
|
775
|
+
* listeners. Tracked so we can rebuild the bridge on iframe recovery without
|
|
776
|
+
* leaking listeners against the dead iframe. */ this.cleanupBridge = null;
|
|
695
777
|
this.iframe = null;
|
|
696
778
|
this.environmentId = environmentId;
|
|
697
779
|
this.authToken = authToken;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamic-labs-wallet/browser-wallet-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.338",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@dynamic-labs-wallet/core": "0.0.
|
|
7
|
+
"@dynamic-labs-wallet/core": "0.0.338",
|
|
8
8
|
"@dynamic-labs/logger": "^4.45.2",
|
|
9
9
|
"@dynamic-labs/message-transport": "^4.45.2"
|
|
10
10
|
},
|
|
@@ -13,6 +13,10 @@ export declare class IframeManager {
|
|
|
13
13
|
baseMPCRelayApiUrl: string;
|
|
14
14
|
protected messageTransport: MessageTransportWithDefaultOrigin | null;
|
|
15
15
|
protected iframeMessageHandler: iframeMessageHandler | null;
|
|
16
|
+
/** Teardown function returned by setupMessageTransportBridge — detaches all
|
|
17
|
+
* listeners. Tracked so we can rebuild the bridge on iframe recovery without
|
|
18
|
+
* leaking listeners against the dead iframe. */
|
|
19
|
+
private cleanupBridge;
|
|
16
20
|
private static iframeLoadPromise;
|
|
17
21
|
protected iframe: HTMLIFrameElement | null;
|
|
18
22
|
private readonly debug;
|
|
@@ -73,6 +77,15 @@ export declare class IframeManager {
|
|
|
73
77
|
* initialize the message transport after iframe is successfully loaded
|
|
74
78
|
*/
|
|
75
79
|
protected initializeMessageTransport(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Rebuild the iframe and its bridge while preserving the message transport
|
|
82
|
+
* (which owns the request channel's retry timer and recovery manager).
|
|
83
|
+
*
|
|
84
|
+
* Triggered by the request channel's recovery flow when an outgoing message
|
|
85
|
+
* goes 5s without an ack — typically because the iframe was torn down by a
|
|
86
|
+
* relogin or RN modal unmount and the host still holds a stale reference.
|
|
87
|
+
*/
|
|
88
|
+
private recoverIframe;
|
|
76
89
|
/**
|
|
77
90
|
* Sets up message handlers for iframe → host requests (secureStorage and getSignedSessionId)
|
|
78
91
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../../src/client/iframeManager/IframeManager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAOR,KAAK,qBAAqB,EAE1B,KAAK,oBAAoB,EAE1B,MAAM,2BAA2B,CAAC;AACnC,OAAO,
|
|
1
|
+
{"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../../src/client/iframeManager/IframeManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type MessageTransportWithDefaultOrigin } from '@dynamic-labs/message-transport';
|
|
2
|
-
export declare const setupMessageTransportBridge: (messageTransport: MessageTransportWithDefaultOrigin, iframe: HTMLIFrameElement, iframeOrigin: string) => void;
|
|
2
|
+
export declare const setupMessageTransportBridge: (messageTransport: MessageTransportWithDefaultOrigin, iframe: HTMLIFrameElement, iframeOrigin: string) => (() => void);
|
|
3
3
|
//# sourceMappingURL=messageTransportBridge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messageTransportBridge.d.ts","sourceRoot":"","sources":["../../src/services/messageTransportBridge.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iCAAiC,EAGvC,MAAM,iCAAiC,CAAC;AAGzC,eAAO,MAAM,2BAA2B,qBACpB,iCAAiC,UAC3C,iBAAiB,gBACX,MAAM,
|
|
1
|
+
{"version":3,"file":"messageTransportBridge.d.ts","sourceRoot":"","sources":["../../src/services/messageTransportBridge.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iCAAiC,EAGvC,MAAM,iCAAiC,CAAC;AAGzC,eAAO,MAAM,2BAA2B,qBACpB,iCAAiC,UAC3C,iBAAiB,gBACX,MAAM,KACnB,CAAC,MAAM,IAAI,CAmEb,CAAC"}
|