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