@dynamic-labs-wallet/browser-wallet-client 0.0.350 → 0.0.352
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 +342 -222
- package/index.esm.js +342 -223
- package/package.json +2 -2
- package/src/client/client.d.ts +2 -1
- package/src/client/client.d.ts.map +1 -1
- package/src/client/iframeManager/IframeManager.d.ts +24 -12
- package/src/client/iframeManager/IframeManager.d.ts.map +1 -1
- package/src/index.d.ts +2 -1
- package/src/index.d.ts.map +1 -1
- package/src/services/createIframeWaasSDKContainer.d.ts +3 -0
- package/src/services/createIframeWaasSDKContainer.d.ts.map +1 -0
package/index.cjs
CHANGED
|
@@ -237,19 +237,136 @@ const setupMessageTransportBridge = (messageTransport$1, iframe, iframeOrigin)=>
|
|
|
237
237
|
};
|
|
238
238
|
};
|
|
239
239
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
240
|
+
const createIframeWaasSDKContainer = ()=>{
|
|
241
|
+
let iframe = null;
|
|
242
|
+
const messageHandlers = new Set();
|
|
243
|
+
const errorHandlers = new Set();
|
|
244
|
+
let windowListenerAttached = false;
|
|
245
|
+
let cspListenerAttached = false;
|
|
246
|
+
const windowMessageListener = (event)=>{
|
|
247
|
+
if (event.source !== (iframe == null ? void 0 : iframe.contentWindow)) return;
|
|
248
|
+
// Defense in depth: verify event.origin matches the iframe's URL origin.
|
|
249
|
+
// The source check alone is insufficient — a cross-origin navigation can
|
|
250
|
+
// keep contentWindow stable while changing event.origin to an attacker.
|
|
251
|
+
let expectedOrigin;
|
|
252
|
+
try {
|
|
253
|
+
expectedOrigin = new URL(iframe.src).origin;
|
|
254
|
+
} catch (e) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (event.origin !== expectedOrigin) return;
|
|
258
|
+
// Only forward payloads that match a known shape so a compromised iframe
|
|
259
|
+
// cannot inject arbitrary data into subscribers. Two shapes are accepted:
|
|
260
|
+
// 1. Handshake: a non-empty string (e.g. `iframe-ready-${instanceId}`).
|
|
261
|
+
// 2. Transport message: a plain object explicitly tagged `origin: 'webview'`.
|
|
262
|
+
const { data } = event;
|
|
263
|
+
const isHandshake = typeof data === 'string' && data.length > 0;
|
|
264
|
+
const isTransportMessage = typeof data === 'object' && data !== null && !Array.isArray(data) && 'origin' in data && data.origin === 'webview';
|
|
265
|
+
if (!isHandshake && !isTransportMessage) return;
|
|
266
|
+
for (const handler of messageHandlers){
|
|
267
|
+
handler(data);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
const fireError = (error)=>{
|
|
271
|
+
for (const handler of errorHandlers){
|
|
272
|
+
handler(error);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const cspViolationListener = (event)=>{
|
|
276
|
+
var _event_blockedURI, _event_violatedDirective, _event_violatedDirective1;
|
|
277
|
+
const iframeOrigin = (iframe == null ? void 0 : iframe.src) ? new URL(iframe.src).origin : undefined;
|
|
278
|
+
const affectsIframe = iframeOrigin && ((_event_blockedURI = event.blockedURI) == null ? void 0 : _event_blockedURI.includes(iframeOrigin)) || ((_event_violatedDirective = event.violatedDirective) == null ? void 0 : _event_violatedDirective.includes('frame-src')) || ((_event_violatedDirective1 = event.violatedDirective) == null ? void 0 : _event_violatedDirective1.includes('child-src'));
|
|
279
|
+
if (!affectsIframe) return;
|
|
280
|
+
logger.error('CSP violation detected that may affect iframe loading:', {
|
|
281
|
+
blockedURI: event.blockedURI,
|
|
282
|
+
violatedDirective: event.violatedDirective,
|
|
283
|
+
originalPolicy: event.originalPolicy,
|
|
284
|
+
sourceFile: event.sourceFile,
|
|
285
|
+
lineNumber: event.lineNumber,
|
|
286
|
+
columnNumber: event.columnNumber,
|
|
287
|
+
iframeOrigin
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
return {
|
|
291
|
+
async setUrl (url) {
|
|
292
|
+
iframe = document.createElement('iframe');
|
|
293
|
+
iframe.style.display = 'none';
|
|
294
|
+
iframe.style.position = 'fixed';
|
|
295
|
+
iframe.style.top = '0';
|
|
296
|
+
iframe.style.left = '0';
|
|
297
|
+
iframe.style.width = '0';
|
|
298
|
+
iframe.style.height = '0';
|
|
299
|
+
iframe.style.border = 'none';
|
|
300
|
+
iframe.style.pointerEvents = 'none';
|
|
301
|
+
iframe.setAttribute('title', 'Dynamic Wallet Iframe');
|
|
302
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-downloads');
|
|
303
|
+
iframe.setAttribute('referrerpolicy', 'origin');
|
|
304
|
+
iframe.onerror = (error)=>{
|
|
305
|
+
fireError(error);
|
|
306
|
+
};
|
|
307
|
+
if (!cspListenerAttached) {
|
|
308
|
+
document.addEventListener('securitypolicyviolation', cspViolationListener);
|
|
309
|
+
cspListenerAttached = true;
|
|
310
|
+
}
|
|
311
|
+
iframe.src = url;
|
|
312
|
+
document.body.appendChild(iframe);
|
|
313
|
+
},
|
|
314
|
+
sendMessage (data) {
|
|
315
|
+
if (!(iframe == null ? void 0 : iframe.contentWindow)) {
|
|
316
|
+
throw new Error('Cannot send message: iframe not loaded');
|
|
317
|
+
}
|
|
318
|
+
const origin = new URL(iframe.src).origin;
|
|
319
|
+
try {
|
|
320
|
+
iframe.contentWindow.postMessage(data, origin);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
// This catches "Attempt to postMessage on disconnected port" errors
|
|
323
|
+
// which occur when the iframe has been navigated away or destroyed.
|
|
324
|
+
logger.error('Failed to post message to iframe:', error);
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
onMessage (handler) {
|
|
329
|
+
messageHandlers.add(handler);
|
|
330
|
+
if (!windowListenerAttached) {
|
|
331
|
+
window.addEventListener('message', windowMessageListener);
|
|
332
|
+
windowListenerAttached = true;
|
|
333
|
+
}
|
|
334
|
+
return ()=>{
|
|
335
|
+
messageHandlers.delete(handler);
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
onError (handler) {
|
|
339
|
+
errorHandlers.add(handler);
|
|
340
|
+
return ()=>{
|
|
341
|
+
errorHandlers.delete(handler);
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
isAlive () {
|
|
345
|
+
var _iframe_isConnected;
|
|
346
|
+
// contentWindow goes null when the iframe is removed from the DOM or its
|
|
347
|
+
// page is torn down (relogin, RN modal unmount, etc.). isConnected falls
|
|
348
|
+
// back to true on jsdom where the property is absent so the production
|
|
349
|
+
// contract still holds.
|
|
350
|
+
return !!(iframe == null ? void 0 : iframe.contentWindow) && ((_iframe_isConnected = iframe.isConnected) != null ? _iframe_isConnected : true);
|
|
351
|
+
},
|
|
352
|
+
destroy () {
|
|
353
|
+
if (windowListenerAttached) {
|
|
354
|
+
window.removeEventListener('message', windowMessageListener);
|
|
355
|
+
windowListenerAttached = false;
|
|
356
|
+
}
|
|
357
|
+
if (cspListenerAttached) {
|
|
358
|
+
document.removeEventListener('securitypolicyviolation', cspViolationListener);
|
|
359
|
+
cspListenerAttached = false;
|
|
360
|
+
}
|
|
361
|
+
messageHandlers.clear();
|
|
362
|
+
errorHandlers.clear();
|
|
363
|
+
iframe == null ? void 0 : iframe.remove();
|
|
364
|
+
iframe = null;
|
|
365
|
+
}
|
|
366
|
+
};
|
|
252
367
|
};
|
|
368
|
+
|
|
369
|
+
const FRAME_ANCESTORS_QUERY_PARAM = 'frameAncestors';
|
|
253
370
|
/**
|
|
254
371
|
* Builds the iframe message transport with the recovery + block decorators.
|
|
255
372
|
*
|
|
@@ -300,6 +417,96 @@ class IframeManager {
|
|
|
300
417
|
throw error;
|
|
301
418
|
}
|
|
302
419
|
}
|
|
420
|
+
buildIframeUrlSearchParams() {
|
|
421
|
+
var _this_instanceId, _this_sdkVersion, _this_baseClientKeysharesRelayApiUrl;
|
|
422
|
+
const params = new URLSearchParams({
|
|
423
|
+
instanceId: (_this_instanceId = this.instanceId) != null ? _this_instanceId : '',
|
|
424
|
+
hostOrigin: window.location.origin,
|
|
425
|
+
environmentId: this.environmentId,
|
|
426
|
+
baseApiUrl: this.baseApiUrl,
|
|
427
|
+
baseMPCRelayApiUrl: this.baseMPCRelayApiUrl,
|
|
428
|
+
sdkVersion: (_this_sdkVersion = this.sdkVersion) != null ? _this_sdkVersion : '',
|
|
429
|
+
debug: String(this.debug),
|
|
430
|
+
baseClientKeysharesRelayApiUrl: (_this_baseClientKeysharesRelayApiUrl = this.baseClientKeysharesRelayApiUrl) != null ? _this_baseClientKeysharesRelayApiUrl : '',
|
|
431
|
+
secureStorage: this.secureStorage ? 'true' : ''
|
|
432
|
+
});
|
|
433
|
+
const seenAdditional = new Set();
|
|
434
|
+
for (const raw of this.additionalTrustedOrigins){
|
|
435
|
+
const trimmed = raw.trim();
|
|
436
|
+
if (!trimmed) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (trimmed.length > IframeManager.maxTrustedOriginStringLength) {
|
|
440
|
+
this.logger.warn('Skipping additionalTrustedOrigin (exceeds max length)');
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
const u = new URL(trimmed);
|
|
445
|
+
if (u.protocol !== 'http:' && u.protocol !== 'https:') {
|
|
446
|
+
this.logger.warn('Skipping additionalTrustedOrigin (unsupported scheme)', {
|
|
447
|
+
trimmed
|
|
448
|
+
});
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
const origin = u.origin;
|
|
452
|
+
if (seenAdditional.has(origin)) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (seenAdditional.size >= IframeManager.maxAdditionalTrustedOrigins) {
|
|
456
|
+
this.logger.warn('additionalTrustedOrigin limit reached, extra entries ignored');
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
seenAdditional.add(origin);
|
|
460
|
+
params.append(FRAME_ANCESTORS_QUERY_PARAM, origin);
|
|
461
|
+
} catch (e) {
|
|
462
|
+
this.logger.warn('Skipping invalid additionalTrustedOrigin', {
|
|
463
|
+
trimmed
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return params;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Build the URL that the transport provider should load.
|
|
471
|
+
*/ buildIframeUrl() {
|
|
472
|
+
const params = this.buildIframeUrlSearchParams();
|
|
473
|
+
return `${this.iframeDomain}/waas-v1/${this.environmentId}?${params.toString()}`;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Set up the bridge between the MessageTransport and the WaasSDKContainer.
|
|
477
|
+
* - Host-origin messages from the transport are forwarded to the sandbox host via sendMessage.
|
|
478
|
+
* - Messages received from the sandbox host are parsed and emitted into the transport.
|
|
479
|
+
*
|
|
480
|
+
* Returns a teardown function so callers can detach listeners when the
|
|
481
|
+
* sandbox host is rebuilt (needed by the recovery flow — the transport
|
|
482
|
+
* survives the rebuild and would otherwise accumulate stale listeners).
|
|
483
|
+
*/ setupWaasSDKContainerBridge(transport, waasSDKContainer) {
|
|
484
|
+
// Forward outgoing host messages to the sandbox host
|
|
485
|
+
const transportListener = (message)=>{
|
|
486
|
+
if (message.origin === 'host') {
|
|
487
|
+
waasSDKContainer.sendMessage(message);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
transport.on(transportListener);
|
|
491
|
+
// Forward incoming messages from the sandbox host to the transport
|
|
492
|
+
const unsubscribeFromWaasSDKContainer = waasSDKContainer.onMessage((data)=>{
|
|
493
|
+
if (typeof data !== 'object' || data === null) return;
|
|
494
|
+
if (!('origin' in data) || data.origin !== 'webview') return;
|
|
495
|
+
try {
|
|
496
|
+
const parsed = messageTransport.parseMessageTransportData(data);
|
|
497
|
+
if (!parsed) return;
|
|
498
|
+
transport.emit(parsed);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (!(error instanceof SyntaxError)) {
|
|
501
|
+
this.logger.error('Error handling incoming message:', error);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
return ()=>{
|
|
506
|
+
transport.off(transportListener);
|
|
507
|
+
unsubscribeFromWaasSDKContainer();
|
|
508
|
+
};
|
|
509
|
+
}
|
|
303
510
|
/**
|
|
304
511
|
* initialize the message transport after iframe is successfully loaded
|
|
305
512
|
*/ async initializeMessageTransport() {
|
|
@@ -310,10 +517,16 @@ class IframeManager {
|
|
|
310
517
|
await this.initializeIframeCommunication();
|
|
311
518
|
const transport = createIframeMessageTransport();
|
|
312
519
|
this.messageTransport = transport;
|
|
313
|
-
|
|
314
|
-
|
|
520
|
+
var _this_waasSDKContainer;
|
|
521
|
+
const waasSDKContainer = (_this_waasSDKContainer = this.waasSDKContainer) != null ? _this_waasSDKContainer : IframeManager.sharedWaasSDKContainer;
|
|
522
|
+
if (waasSDKContainer) {
|
|
523
|
+
this.cleanupBridge = this.setupWaasSDKContainerBridge(transport, waasSDKContainer);
|
|
524
|
+
} else if (this.iframe) {
|
|
525
|
+
// Fallback for the display container flow which still uses raw iframes
|
|
526
|
+
this.cleanupBridge = setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
|
|
527
|
+
} else {
|
|
528
|
+
throw new Error('No sandbox host or iframe available');
|
|
315
529
|
}
|
|
316
|
-
this.cleanupBridge = setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
|
|
317
530
|
this.iframeMessageHandler = new iframeMessageHandler(this.messageTransport);
|
|
318
531
|
// Set up request channel to handle messages from iframe (for secureStorage and getSignedSessionId)
|
|
319
532
|
if (this.secureStorage || this.getSignedSessionIdCallback) {
|
|
@@ -339,51 +552,60 @@ class IframeManager {
|
|
|
339
552
|
await this.initAuthToken();
|
|
340
553
|
}
|
|
341
554
|
/**
|
|
342
|
-
* Rebuild the
|
|
555
|
+
* Rebuild the sandbox host and its bridge while preserving the message transport
|
|
343
556
|
* (which owns the request channel's retry timer and recovery manager).
|
|
344
557
|
*
|
|
345
558
|
* Triggered by the request channel's recovery flow when an outgoing message
|
|
346
559
|
* goes 5s without an ack — typically because the iframe was torn down by a
|
|
347
560
|
* relogin or RN modal unmount and the host still holds a stale reference.
|
|
348
561
|
*/ async recoverIframe(transport) {
|
|
349
|
-
|
|
350
|
-
//
|
|
351
|
-
// server, overloaded MPC ceremony,
|
|
352
|
-
// case actively breaks things: it
|
|
353
|
-
// under in-flight operations and amplifies retries when
|
|
354
|
-
// message also times out → endless cascade.
|
|
562
|
+
var _IframeManager_sharedWaasSDKContainer;
|
|
563
|
+
// If the shared container is still alive, the request channel's 5s timeout
|
|
564
|
+
// fired for a *non-death* reason — slow server, overloaded MPC ceremony,
|
|
565
|
+
// etc. Rebuilding in that case actively breaks things: it tears a healthy
|
|
566
|
+
// container out from under in-flight operations and amplifies retries when
|
|
567
|
+
// the next message also times out → endless cascade.
|
|
355
568
|
//
|
|
356
569
|
// Just unblock the transport (the request channel's own retry will
|
|
357
|
-
// re-send the message on the same live
|
|
358
|
-
// real dead-
|
|
359
|
-
if (
|
|
360
|
-
this.logger.info('(recoverIframe)
|
|
361
|
-
this.
|
|
570
|
+
// re-send the message on the same live container) and bail out. Only the
|
|
571
|
+
// real dead-container case below falls through to the rebuild.
|
|
572
|
+
if ((_IframeManager_sharedWaasSDKContainer = IframeManager.sharedWaasSDKContainer) == null ? void 0 : _IframeManager_sharedWaasSDKContainer.isAlive()) {
|
|
573
|
+
this.logger.info('(recoverIframe) Container still alive — request-channel timeout, skipping rebuild');
|
|
574
|
+
this.waasSDKContainer = IframeManager.sharedWaasSDKContainer;
|
|
362
575
|
transport.unblock();
|
|
363
576
|
return;
|
|
364
577
|
}
|
|
365
|
-
// Detach listeners attached to the dead
|
|
578
|
+
// Detach listeners attached to the dead container, then destroy it.
|
|
579
|
+
// The transport, iframeMessageHandler, and iframeRequestChannel are
|
|
580
|
+
// intentionally kept — they're tied to the request channel's in-flight
|
|
581
|
+
// retry that we want to flush after rebuild.
|
|
366
582
|
this.cleanupBridge == null ? void 0 : this.cleanupBridge.call(this);
|
|
367
583
|
this.cleanupBridge = null;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
//
|
|
584
|
+
if (IframeManager.sharedWaasSDKContainer) {
|
|
585
|
+
IframeManager.sharedWaasSDKContainer.destroy();
|
|
586
|
+
IframeManager.sharedWaasSDKContainer = null;
|
|
587
|
+
}
|
|
588
|
+
IframeManager.iframeLoadPromise = null;
|
|
589
|
+
this.waasSDKContainer = null;
|
|
590
|
+
// Defer the rebuild + concurrency control to loadIframe(): the first
|
|
591
|
+
// chain to land here will fall through to the remount branch (shared
|
|
592
|
+
// container is dead, iframeLoadPromise is null), set iframeLoadPromise,
|
|
593
|
+
// and start the mount. Subsequent chains' loadIframe() calls during this
|
|
594
|
+
// window see iframeLoadPromise set and await the same load — concurrent
|
|
595
|
+
// rebuilds collapse for free, no extra static needed.
|
|
375
596
|
//
|
|
376
597
|
// The transport, iframeMessageHandler, and iframeRequestChannel are
|
|
377
598
|
// intentionally kept — they're tied to the request channel's in-flight
|
|
378
599
|
// retry that we want to flush after rebuild.
|
|
379
|
-
this.logger.info('(recoverIframe) Rebuilding shared
|
|
600
|
+
this.logger.info('(recoverIframe) Rebuilding shared container');
|
|
380
601
|
await this.loadIframe();
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
602
|
+
var _this_waasSDKContainer;
|
|
603
|
+
const waasSDKContainer = (_this_waasSDKContainer = this.waasSDKContainer) != null ? _this_waasSDKContainer : IframeManager.sharedWaasSDKContainer;
|
|
604
|
+
if (!waasSDKContainer) {
|
|
605
|
+
throw new Error('Failed to mount fresh container during recovery');
|
|
384
606
|
}
|
|
385
|
-
this.cleanupBridge =
|
|
386
|
-
// Re-send auth token to the new
|
|
607
|
+
this.cleanupBridge = this.setupWaasSDKContainerBridge(transport, waasSDKContainer);
|
|
608
|
+
// Re-send auth token to the new sandbox host before the queued retry flushes.
|
|
387
609
|
// sendAuthToken bypasses the block (see bypassBlockIf in
|
|
388
610
|
// createIframeMessageTransport), so this resolves before unblock() runs
|
|
389
611
|
// and the queued operation hits an authenticated iframe.
|
|
@@ -461,44 +683,52 @@ class IframeManager {
|
|
|
461
683
|
}
|
|
462
684
|
}
|
|
463
685
|
/**
|
|
464
|
-
* Reset the shared
|
|
465
|
-
*/
|
|
686
|
+
* Reset the shared provider and iframe load promise, and iframe instance count
|
|
687
|
+
*/ resetSharedWaasSDKContainer() {
|
|
466
688
|
this.cleanupBridge == null ? void 0 : this.cleanupBridge.call(this);
|
|
467
689
|
this.cleanupBridge = null;
|
|
468
|
-
IframeManager.
|
|
690
|
+
if (IframeManager.sharedWaasSDKContainer) {
|
|
691
|
+
IframeManager.sharedWaasSDKContainer.destroy();
|
|
692
|
+
IframeManager.sharedWaasSDKContainer = null;
|
|
693
|
+
}
|
|
469
694
|
IframeManager.iframeInstanceCount = 0;
|
|
470
695
|
IframeManager.iframeLoadPromise = null;
|
|
471
|
-
this.
|
|
696
|
+
this.waasSDKContainer = null;
|
|
472
697
|
this.iframeMessageHandler = null;
|
|
473
698
|
this.messageTransport = null;
|
|
474
699
|
// Double the timeout and cap at 60 seconds to give more time for slow networks
|
|
475
700
|
IframeManager.iframeLoadTimeout = Math.min(IframeManager.iframeLoadTimeout * 2, 60000);
|
|
476
701
|
}
|
|
477
702
|
async loadIframe() {
|
|
478
|
-
var
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
this.assignExistingIframe();
|
|
703
|
+
var _IframeManager_sharedWaasSDKContainer;
|
|
704
|
+
// If the shared container is still alive, reuse it. The alive check (not
|
|
705
|
+
// just `if sharedWaasSDKContainer`) is what lets recoverIframe simply call
|
|
706
|
+
// loadIframe again on a dead container: we fall through to the remount
|
|
707
|
+
// branch below instead of adopting the corpse.
|
|
708
|
+
if ((_IframeManager_sharedWaasSDKContainer = IframeManager.sharedWaasSDKContainer) == null ? void 0 : _IframeManager_sharedWaasSDKContainer.isAlive()) {
|
|
709
|
+
this.waasSDKContainer = IframeManager.sharedWaasSDKContainer;
|
|
710
|
+
IframeManager.iframeInstanceCount++;
|
|
487
711
|
return Promise.resolve();
|
|
488
712
|
}
|
|
489
713
|
// If a load is in progress, wait for it, then assign
|
|
490
714
|
if (IframeManager.iframeLoadPromise) {
|
|
491
715
|
return IframeManager.iframeLoadPromise.then(()=>{
|
|
492
|
-
this.
|
|
716
|
+
this.waasSDKContainer = IframeManager.sharedWaasSDKContainer;
|
|
717
|
+
IframeManager.iframeInstanceCount++;
|
|
493
718
|
});
|
|
494
719
|
}
|
|
495
|
-
(
|
|
496
|
-
|
|
497
|
-
|
|
720
|
+
// sharedWaasSDKContainer is dead (or never existed) and no load is in
|
|
721
|
+
// flight. Destroy the stale corpse so createWaasSDKContainerLoadPromise
|
|
722
|
+
// mounts a fresh one.
|
|
723
|
+
if (IframeManager.sharedWaasSDKContainer) {
|
|
724
|
+
IframeManager.sharedWaasSDKContainer.destroy();
|
|
725
|
+
IframeManager.sharedWaasSDKContainer = null;
|
|
726
|
+
}
|
|
727
|
+
const loadPromise = IframeManager.iframeLoadPromise = this.createWaasSDKContainerLoadPromise();
|
|
498
728
|
// Clear iframeLoadPromise once the load settles so the "in flight" branch
|
|
499
729
|
// above only fires while a load is actually pending. Otherwise it would
|
|
500
730
|
// keep returning a resolved promise pointing at the (potentially later
|
|
501
|
-
// dead)
|
|
731
|
+
// dead) container, and the next caller would adopt a corpse without ever
|
|
502
732
|
// taking the remount branch.
|
|
503
733
|
//
|
|
504
734
|
// The trailing .catch swallows the rejection that finally re-throws —
|
|
@@ -514,95 +744,63 @@ class IframeManager {
|
|
|
514
744
|
});
|
|
515
745
|
return loadPromise;
|
|
516
746
|
}
|
|
517
|
-
|
|
518
|
-
this.iframe = IframeManager.sharedIframe;
|
|
519
|
-
IframeManager.iframeInstanceCount++;
|
|
520
|
-
}
|
|
521
|
-
createIframeLoadPromise() {
|
|
747
|
+
createWaasSDKContainerLoadPromise() {
|
|
522
748
|
return new Promise((resolve, reject)=>{
|
|
523
|
-
// Listen for CSP violations that might block the iframe
|
|
524
|
-
const cspViolationListener = (event)=>{
|
|
525
|
-
var _event_blockedURI, _event_violatedDirective, _event_violatedDirective1;
|
|
526
|
-
if (this.iframeDomain && ((_event_blockedURI = event.blockedURI) == null ? void 0 : _event_blockedURI.includes(this.iframeDomain)) || ((_event_violatedDirective = event.violatedDirective) == null ? void 0 : _event_violatedDirective.includes('frame-src')) || ((_event_violatedDirective1 = event.violatedDirective) == null ? void 0 : _event_violatedDirective1.includes('child-src'))) {
|
|
527
|
-
this.logger.error('CSP violation detected that may affect iframe loading:', {
|
|
528
|
-
blockedURI: event.blockedURI,
|
|
529
|
-
violatedDirective: event.violatedDirective,
|
|
530
|
-
originalPolicy: event.originalPolicy,
|
|
531
|
-
sourceFile: event.sourceFile,
|
|
532
|
-
lineNumber: event.lineNumber,
|
|
533
|
-
columnNumber: event.columnNumber,
|
|
534
|
-
iframeDomain: this.iframeDomain
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
document.addEventListener('securitypolicyviolation', cspViolationListener);
|
|
539
|
-
const cleanupCspListener = ()=>{
|
|
540
|
-
document.removeEventListener('securitypolicyviolation', cspViolationListener);
|
|
541
|
-
};
|
|
542
749
|
const attemptLoad = ()=>{
|
|
543
750
|
IframeManager.iframeLoadAttempts++;
|
|
544
751
|
this.logger.debug(`Loading iframe for waas wallet client... (attempt ${IframeManager.iframeLoadAttempts}/${IframeManager.maxRetryAttempts + 1})`, this.getIframeContext());
|
|
545
|
-
const
|
|
546
|
-
let messageListener = null;
|
|
752
|
+
const waasSDKContainer = this.createWaasSDKContainer();
|
|
547
753
|
const context = _extends({}, this.getIframeContext(), {
|
|
548
754
|
attempt: IframeManager.iframeLoadAttempts
|
|
549
755
|
});
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
756
|
+
const handleError = (error)=>{
|
|
757
|
+
clearTimeout(timeoutId);
|
|
758
|
+
unsubscribeHandshake();
|
|
759
|
+
unsubscribeError();
|
|
760
|
+
waasSDKContainer.destroy();
|
|
761
|
+
// Check if we should retry
|
|
762
|
+
if (IframeManager.iframeLoadAttempts <= IframeManager.maxRetryAttempts) {
|
|
763
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
764
|
+
this.logger.warn(`(loadIframe) Iframe failed to load on attempt ${IframeManager.iframeLoadAttempts}, retrying... context: ${JSON.stringify(context)}, error: ${errorMsg}`);
|
|
765
|
+
// Retry after a short delay
|
|
766
|
+
setTimeout(attemptLoad, 1000);
|
|
767
|
+
} else {
|
|
768
|
+
// Max retries reached, give up
|
|
769
|
+
this.logger.error('Iframe failed to load after all retry attempts: ', error);
|
|
770
|
+
this.resetSharedWaasSDKContainer();
|
|
771
|
+
IframeManager.iframeLoadAttempts = 0;
|
|
772
|
+
reject(new Error(`Failed to load iframe after all retry attempts... context: ${JSON.stringify(context)}`));
|
|
554
773
|
}
|
|
555
|
-
}, IframeManager.iframeLoadTimeout);
|
|
556
|
-
const resolveWithCleanup = ()=>{
|
|
557
|
-
cleanupCspListener();
|
|
558
|
-
resolve();
|
|
559
|
-
};
|
|
560
|
-
const rejectWithCleanup = (reason)=>{
|
|
561
|
-
cleanupCspListener();
|
|
562
|
-
reject(reason);
|
|
563
774
|
};
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
775
|
+
// Set up timeout (handleError closure captures timeoutId/unsubscribe* declared below;
|
|
776
|
+
// they are assigned synchronously before any async callback can fire).
|
|
777
|
+
const timeoutId = setTimeout(()=>{
|
|
778
|
+
handleError('Iframe load timeout');
|
|
779
|
+
}, IframeManager.iframeLoadTimeout);
|
|
780
|
+
// Surface container-level errors (e.g. iframe.onerror) so we don't wait for the full timeout.
|
|
781
|
+
const unsubscribeError = waasSDKContainer.onError(handleError);
|
|
782
|
+
// Listen for the handshake message through the sandbox host's onMessage
|
|
783
|
+
const unsubscribeHandshake = waasSDKContainer.onMessage((data)=>{
|
|
784
|
+
if (data === `iframe-ready-${this.instanceId}`) {
|
|
785
|
+
clearTimeout(timeoutId);
|
|
786
|
+
unsubscribeHandshake();
|
|
787
|
+
unsubscribeError();
|
|
788
|
+
IframeManager.sharedWaasSDKContainer = waasSDKContainer;
|
|
789
|
+
this.waasSDKContainer = waasSDKContainer;
|
|
790
|
+
IframeManager.iframeInstanceCount++;
|
|
791
|
+
IframeManager.iframeLoadAttempts = 0; // Reset retry counter on success
|
|
792
|
+
resolve();
|
|
793
|
+
this.logger.debug('Iframe loaded successfully...', this.getIframeContext());
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
const url = this.buildIframeUrl();
|
|
797
|
+
this.logger.debug('Creating iframe with src:', url);
|
|
798
|
+
waasSDKContainer.setUrl(url).catch(handleError);
|
|
571
799
|
};
|
|
572
800
|
// Start the first attempt
|
|
573
801
|
attemptLoad();
|
|
574
802
|
});
|
|
575
803
|
}
|
|
576
|
-
setupIframeEventHandlersWithRetry(iframe, messageListener, iframeTimeoutId, reject, attemptLoad, context) {
|
|
577
|
-
iframe.onload = ()=>{
|
|
578
|
-
this.logger.debug('Iframe onload fired, waiting for ready message...');
|
|
579
|
-
};
|
|
580
|
-
iframe.onerror = (error)=>{
|
|
581
|
-
if (messageListener) {
|
|
582
|
-
window.removeEventListener('message', messageListener);
|
|
583
|
-
}
|
|
584
|
-
clearTimeout(iframeTimeoutId);
|
|
585
|
-
// Check if we should retry
|
|
586
|
-
if (IframeManager.iframeLoadAttempts <= IframeManager.maxRetryAttempts) {
|
|
587
|
-
const errorMsg = error instanceof Error ? error.message : 'Unknown error occurred.';
|
|
588
|
-
this.logger.warn(`(loadIframe) Iframe failed to load on attempt ${IframeManager.iframeLoadAttempts}, retrying... context: ${JSON.stringify(context)}, error: ${errorMsg}`);
|
|
589
|
-
// Clean up current attempt
|
|
590
|
-
if (iframe.parentNode) {
|
|
591
|
-
iframe.parentNode.removeChild(iframe);
|
|
592
|
-
}
|
|
593
|
-
// Retry after a short delay
|
|
594
|
-
setTimeout(()=>{
|
|
595
|
-
attemptLoad();
|
|
596
|
-
}, 1000); // 1 second delay between retries
|
|
597
|
-
} else {
|
|
598
|
-
// Max retries reached, give up
|
|
599
|
-
this.logger.error('Iframe failed to load after all retry attempts: ', error);
|
|
600
|
-
this.resetSharedIframe();
|
|
601
|
-
IframeManager.iframeLoadAttempts = 0;
|
|
602
|
-
reject(new Error(`Failed to load iframe after all retry attempts... context: ${JSON.stringify(context)}`));
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
804
|
getIframeContext() {
|
|
607
805
|
var _this_sdkVersion;
|
|
608
806
|
return {
|
|
@@ -614,87 +812,6 @@ class IframeManager {
|
|
|
614
812
|
iframeLoadTimeout: IframeManager.iframeLoadTimeout
|
|
615
813
|
};
|
|
616
814
|
}
|
|
617
|
-
createMessageListener(iframe, iframeTimeoutId, resolve) {
|
|
618
|
-
const messageListener = (event)=>{
|
|
619
|
-
if (event.source === iframe.contentWindow && event.data === `iframe-ready-${this.instanceId}`) {
|
|
620
|
-
window.removeEventListener('message', messageListener);
|
|
621
|
-
clearTimeout(iframeTimeoutId);
|
|
622
|
-
IframeManager.sharedIframe = iframe;
|
|
623
|
-
this.iframe = iframe;
|
|
624
|
-
IframeManager.iframeInstanceCount++;
|
|
625
|
-
IframeManager.iframeLoadAttempts = 0; // Reset retry counter on success
|
|
626
|
-
resolve();
|
|
627
|
-
this.logger.debug('Iframe loaded successfully...', this.getIframeContext());
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
return messageListener;
|
|
631
|
-
}
|
|
632
|
-
configureIframe(iframe) {
|
|
633
|
-
iframe.style.display = 'none';
|
|
634
|
-
iframe.setAttribute('title', 'Dynamic Wallet Iframe');
|
|
635
|
-
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-downloads');
|
|
636
|
-
iframe.setAttribute('referrerpolicy', 'origin');
|
|
637
|
-
iframe.style.position = 'fixed';
|
|
638
|
-
iframe.style.top = '0';
|
|
639
|
-
iframe.style.left = '0';
|
|
640
|
-
iframe.style.width = '0';
|
|
641
|
-
iframe.style.height = '0';
|
|
642
|
-
iframe.style.border = 'none';
|
|
643
|
-
iframe.style.pointerEvents = 'none';
|
|
644
|
-
}
|
|
645
|
-
buildIframeUrlSearchParams() {
|
|
646
|
-
var _this_instanceId, _this_sdkVersion, _this_baseClientKeysharesRelayApiUrl;
|
|
647
|
-
const params = new URLSearchParams({
|
|
648
|
-
instanceId: (_this_instanceId = this.instanceId) != null ? _this_instanceId : '',
|
|
649
|
-
hostOrigin: window.location.origin,
|
|
650
|
-
environmentId: this.environmentId,
|
|
651
|
-
baseApiUrl: this.baseApiUrl,
|
|
652
|
-
baseMPCRelayApiUrl: this.baseMPCRelayApiUrl,
|
|
653
|
-
sdkVersion: (_this_sdkVersion = this.sdkVersion) != null ? _this_sdkVersion : '',
|
|
654
|
-
debug: String(this.debug),
|
|
655
|
-
baseClientKeysharesRelayApiUrl: (_this_baseClientKeysharesRelayApiUrl = this.baseClientKeysharesRelayApiUrl) != null ? _this_baseClientKeysharesRelayApiUrl : '',
|
|
656
|
-
secureStorage: this.secureStorage ? 'true' : ''
|
|
657
|
-
});
|
|
658
|
-
const seenAdditional = new Set();
|
|
659
|
-
for (const raw of this.additionalTrustedOrigins){
|
|
660
|
-
const trimmed = raw.trim();
|
|
661
|
-
if (!trimmed) {
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
if (trimmed.length > IframeManager.maxTrustedOriginStringLength) {
|
|
665
|
-
this.logger.warn('Skipping additionalTrustedOrigin (exceeds max length)');
|
|
666
|
-
continue;
|
|
667
|
-
}
|
|
668
|
-
try {
|
|
669
|
-
const u = new URL(trimmed);
|
|
670
|
-
if (u.protocol !== 'http:' && u.protocol !== 'https:') {
|
|
671
|
-
this.logger.warn('Skipping additionalTrustedOrigin (unsupported scheme)', {
|
|
672
|
-
trimmed
|
|
673
|
-
});
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
const origin = u.origin;
|
|
677
|
-
if (seenAdditional.has(origin)) {
|
|
678
|
-
continue;
|
|
679
|
-
}
|
|
680
|
-
if (seenAdditional.size >= IframeManager.maxAdditionalTrustedOrigins) {
|
|
681
|
-
this.logger.warn('additionalTrustedOrigin limit reached, extra entries ignored');
|
|
682
|
-
break;
|
|
683
|
-
}
|
|
684
|
-
seenAdditional.add(origin);
|
|
685
|
-
params.append(FRAME_ANCESTORS_QUERY_PARAM, origin);
|
|
686
|
-
} catch (e) {
|
|
687
|
-
this.logger.warn('Skipping invalid additionalTrustedOrigin', {
|
|
688
|
-
trimmed
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
return params;
|
|
693
|
-
}
|
|
694
|
-
setIframeSource(iframe) {
|
|
695
|
-
const params = this.buildIframeUrlSearchParams();
|
|
696
|
-
iframe.src = `${this.iframeDomain}/waas-v1/${this.environmentId}?${params.toString()}`;
|
|
697
|
-
}
|
|
698
815
|
/**
|
|
699
816
|
* Load an iframe for a specific container
|
|
700
817
|
* @param {HTMLElement} container - The container to which the iframe will be attached
|
|
@@ -750,7 +867,6 @@ class IframeManager {
|
|
|
750
867
|
window.removeEventListener('message', messageListener);
|
|
751
868
|
}
|
|
752
869
|
clearTimeout(iframeTimeoutId);
|
|
753
|
-
IframeManager.sharedIframe = iframe;
|
|
754
870
|
this.iframe = iframe;
|
|
755
871
|
IframeManager.iframeInstanceCount++;
|
|
756
872
|
resolve(iframe);
|
|
@@ -834,16 +950,16 @@ class IframeManager {
|
|
|
834
950
|
error: error instanceof Error ? error.message : error
|
|
835
951
|
});
|
|
836
952
|
}
|
|
837
|
-
if (this.
|
|
953
|
+
if (this.waasSDKContainer) {
|
|
838
954
|
IframeManager.iframeInstanceCount--;
|
|
839
|
-
if (IframeManager.
|
|
955
|
+
if (IframeManager.sharedWaasSDKContainer && IframeManager.iframeInstanceCount === 0) {
|
|
840
956
|
this.cleanupBridge == null ? void 0 : this.cleanupBridge.call(this);
|
|
841
957
|
this.cleanupBridge = null;
|
|
842
|
-
|
|
843
|
-
IframeManager.
|
|
958
|
+
IframeManager.sharedWaasSDKContainer.destroy();
|
|
959
|
+
IframeManager.sharedWaasSDKContainer = null;
|
|
844
960
|
IframeManager.iframeLoadPromise = null;
|
|
845
961
|
}
|
|
846
|
-
this.
|
|
962
|
+
this.waasSDKContainer = null;
|
|
847
963
|
}
|
|
848
964
|
}
|
|
849
965
|
constructor({ environmentId, baseApiUrl, baseMPCRelayApiUrl, chainName, sdkVersion, authMode = core.AuthMode.HEADER, authToken, debug, baseClientKeysharesRelayApiUrl, additionalTrustedOrigins }, internalOptions){
|
|
@@ -856,6 +972,7 @@ class IframeManager {
|
|
|
856
972
|
* listeners. Tracked so we can rebuild the bridge on iframe recovery without
|
|
857
973
|
* leaking listeners against the dead iframe. */ this.cleanupBridge = null;
|
|
858
974
|
this.iframe = null;
|
|
975
|
+
this.waasSDKContainer = null;
|
|
859
976
|
this.environmentId = environmentId;
|
|
860
977
|
this.authToken = authToken;
|
|
861
978
|
this.authMode = authMode;
|
|
@@ -875,6 +992,8 @@ class IframeManager {
|
|
|
875
992
|
if (internalOptions == null ? void 0 : internalOptions.getSignedSessionId) {
|
|
876
993
|
this.getSignedSessionIdCallback = internalOptions.getSignedSessionId;
|
|
877
994
|
}
|
|
995
|
+
var _internalOptions_createWaasSDKContainer;
|
|
996
|
+
this.createWaasSDKContainer = (_internalOptions_createWaasSDKContainer = internalOptions == null ? void 0 : internalOptions.createWaasSDKContainer) != null ? _internalOptions_createWaasSDKContainer : createIframeWaasSDKContainer;
|
|
878
997
|
const environment = core.getEnvironmentFromUrl(baseApiUrl);
|
|
879
998
|
this.iframeDomain = core.IFRAME_DOMAIN_MAP[environment];
|
|
880
999
|
if (this.authMode === core.AuthMode.COOKIE) {
|
|
@@ -890,7 +1009,7 @@ IframeManager.iframeLoadPromise = null;
|
|
|
890
1009
|
IframeManager.iframeLoadTimeout = 10000;
|
|
891
1010
|
IframeManager.iframeLoadAttempts = 0;
|
|
892
1011
|
IframeManager.maxRetryAttempts = 1;
|
|
893
|
-
IframeManager.
|
|
1012
|
+
IframeManager.sharedWaasSDKContainer = null;
|
|
894
1013
|
IframeManager.iframeInstanceCount = 0;
|
|
895
1014
|
IframeManager.maxAdditionalTrustedOrigins = 32;
|
|
896
1015
|
IframeManager.maxTrustedOriginStringLength = 200;
|
|
@@ -1321,3 +1440,4 @@ Object.defineProperty(exports, "WalletOperation", {
|
|
|
1321
1440
|
get: function () { return core.WalletOperation; }
|
|
1322
1441
|
});
|
|
1323
1442
|
exports.DynamicWalletClient = DynamicWalletClient;
|
|
1443
|
+
exports.createIframeWaasSDKContainer = createIframeWaasSDKContainer;
|