@dynamic-labs-wallet/browser-wallet-client 1.1.6 → 1.1.8
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 +178 -20
- package/index.esm.js +178 -20
- package/package.json +2 -2
- package/src/client/iframeManager/IframeManager.d.ts +30 -0
- package/src/client/iframeManager/IframeManager.d.ts.map +1 -1
- package/src/client/iframeManager/IframeRequestHandlerRegistry.d.ts +89 -0
- package/src/client/iframeManager/IframeRequestHandlerRegistry.d.ts.map +1 -0
package/index.cjs
CHANGED
|
@@ -15,6 +15,19 @@ function _extends() {
|
|
|
15
15
|
return _extends.apply(this, arguments);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function _object_without_properties_loose(source, excluded) {
|
|
19
|
+
if (source == null) return {};
|
|
20
|
+
var target = {};
|
|
21
|
+
var sourceKeys = Object.keys(source);
|
|
22
|
+
var key, i;
|
|
23
|
+
for(i = 0; i < sourceKeys.length; i++){
|
|
24
|
+
key = sourceKeys[i];
|
|
25
|
+
if (excluded.indexOf(key) >= 0) continue;
|
|
26
|
+
target[key] = source[key];
|
|
27
|
+
}
|
|
28
|
+
return target;
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
class IframeMessageHandler {
|
|
19
32
|
// Handle error response from iframe message handler
|
|
20
33
|
handleIframeMessageResponseError(response) {
|
|
@@ -612,6 +625,100 @@ const createIframeWaasSDKContainer = ()=>{
|
|
|
612
625
|
}
|
|
613
626
|
};
|
|
614
627
|
|
|
628
|
+
/**
|
|
629
|
+
* One `setupIframeRequestHandlers` call's worth of registrations, grouped by
|
|
630
|
+
* lifetime.
|
|
631
|
+
*/ /** Unregisters every handler in `disposers` and empties the list. */ const drainDisposers = (disposers)=>{
|
|
632
|
+
disposers.forEach((unsubscribe)=>unsubscribe());
|
|
633
|
+
disposers.length = 0;
|
|
634
|
+
};
|
|
635
|
+
/** Unregisters both halves of a registration. */ const disposeAllHandlers = ({ disposeSessionHandlers, disposeStorageHandlers })=>{
|
|
636
|
+
disposeSessionHandlers();
|
|
637
|
+
disposeStorageHandlers();
|
|
638
|
+
};
|
|
639
|
+
/**
|
|
640
|
+
* Lifetime bookkeeping for the iframe → host ("reverse channel") request
|
|
641
|
+
* handlers one IframeManager has attached to the shared container.
|
|
642
|
+
*
|
|
643
|
+
* A page has one WaaS iframe but every client built on it stays bridged to it,
|
|
644
|
+
* so the reverse channel is first-response-wins: a superseded manager with a
|
|
645
|
+
* revoked session callback rejects `getSignedSessionId` instantly and beats the
|
|
646
|
+
* live client's round-trip. This registry is what lets such a manager leave the
|
|
647
|
+
* race (see {@link detach}) without disturbing the key-share handlers, which
|
|
648
|
+
* the iframe still calls back during teardown.
|
|
649
|
+
*/ class IframeRequestHandlerRegistry {
|
|
650
|
+
get sessionHandlerCount() {
|
|
651
|
+
return this.sessionDisposers.length;
|
|
652
|
+
}
|
|
653
|
+
get storageHandlerCount() {
|
|
654
|
+
return this.storageDisposers.length;
|
|
655
|
+
}
|
|
656
|
+
/** Capture the current generations before awaiting the iframe. */ snapshot() {
|
|
657
|
+
return {
|
|
658
|
+
cleanupGeneration: this.cleanupGeneration,
|
|
659
|
+
detachGeneration: this.detachGeneration
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Takes ownership of a registration whose setup started at `epoch`, keeping
|
|
664
|
+
* whichever halves are still legitimate, and reports whether the registration
|
|
665
|
+
* is the manager's current one (the caller's channel is only worth keeping
|
|
666
|
+
* then). The host doesn't await `initialize()`, so a detach or a cleanup can
|
|
667
|
+
* land while the iframe is still loading — which one moved decides:
|
|
668
|
+
*
|
|
669
|
+
* - **Torn down** (cleanup): keep nothing. The lists this would land on are
|
|
670
|
+
* already drained, so anything tracked here would stay attached to a
|
|
671
|
+
* logged-out manager forever.
|
|
672
|
+
* - **Superseded** (detach): the session handlers would put this manager back
|
|
673
|
+
* into the race, but its key-share handlers still answer honestly — and on a
|
|
674
|
+
* first initialization they are the only ones the page has.
|
|
675
|
+
* - **Neither**: track both.
|
|
676
|
+
*/ adopt(epoch, registration) {
|
|
677
|
+
if (this.cleanupGeneration !== epoch.cleanupGeneration) {
|
|
678
|
+
disposeAllHandlers(registration);
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
if (this.detachGeneration !== epoch.detachGeneration) {
|
|
682
|
+
registration.disposeSessionHandlers();
|
|
683
|
+
this.storageDisposers.push(registration.disposeStorageHandlers);
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
this.sessionDisposers.push(registration.disposeSessionHandlers);
|
|
687
|
+
this.storageDisposers.push(registration.disposeStorageHandlers);
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Stops answering the session-scoped requests, and nothing else. Safe to call
|
|
692
|
+
* repeatedly, and mid-initialization: a registration still in flight is
|
|
693
|
+
* adopted as "superseded".
|
|
694
|
+
*/ detach() {
|
|
695
|
+
this.detachGeneration += 1;
|
|
696
|
+
drainDisposers(this.sessionDisposers);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* First phase of a teardown: leave the session race immediately, and mark
|
|
700
|
+
* anything still initializing as torn down rather than merely superseded. The
|
|
701
|
+
* storage handlers stay until {@link disposeStorageHandlers}, because the
|
|
702
|
+
* iframe's own cleanup clears each wallet's shares over this channel.
|
|
703
|
+
*/ markTornDown() {
|
|
704
|
+
this.cleanupGeneration += 1;
|
|
705
|
+
this.detach();
|
|
706
|
+
}
|
|
707
|
+
/** Second phase of a teardown: drop the key-share handlers, once the iframe has finished calling back. */ disposeStorageHandlers() {
|
|
708
|
+
drainDisposers(this.storageDisposers);
|
|
709
|
+
}
|
|
710
|
+
constructor(){
|
|
711
|
+
/**
|
|
712
|
+
* Lists, not single entries: `initializeMessageTransport` checks its guard
|
|
713
|
+
* *then* awaits, so one manager can end up registered on two transports that
|
|
714
|
+
* both answer, and overwriting would orphan the first.
|
|
715
|
+
*/ this.sessionDisposers = [];
|
|
716
|
+
this.storageDisposers = [];
|
|
717
|
+
/** Bumped by {@link detach}. */ this.detachGeneration = 0;
|
|
718
|
+
/** Bumped by {@link markTornDown}. */ this.cleanupGeneration = 0;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
615
722
|
const FRAME_ANCESTORS_QUERY_PARAM = 'frameAncestors';
|
|
616
723
|
/**
|
|
617
724
|
* Builds the iframe message transport with the recovery + block decorators.
|
|
@@ -853,6 +960,8 @@ class IframeManager {
|
|
|
853
960
|
this.logger.debug('Skipping initializeMessageTransport: transport and message handler already initialized');
|
|
854
961
|
return;
|
|
855
962
|
}
|
|
963
|
+
// Captured before the await: detach() or cleanup() can run while the iframe loads.
|
|
964
|
+
const epoch = this.requestHandlers.snapshot();
|
|
856
965
|
await this.initializeIframeCommunication();
|
|
857
966
|
const transport = createIframeMessageTransport();
|
|
858
967
|
this.messageTransport = transport;
|
|
@@ -870,7 +979,12 @@ class IframeManager {
|
|
|
870
979
|
// Set up request channel to handle messages from iframe (for secureStorage,
|
|
871
980
|
// getSignedSessionId, and the unauthorized notification)
|
|
872
981
|
if (this.secureStorage || this.getSignedSessionIdCallback || this.onUnauthorized) {
|
|
873
|
-
|
|
982
|
+
const _this_setupIframeRequestHandlers = this.setupIframeRequestHandlers(this.messageTransport), { requestChannel } = _this_setupIframeRequestHandlers, registration = _object_without_properties_loose(_this_setupIframeRequestHandlers, [
|
|
983
|
+
"requestChannel"
|
|
984
|
+
]);
|
|
985
|
+
if (this.requestHandlers.adopt(epoch, registration)) {
|
|
986
|
+
this.iframeRequestChannel = requestChannel;
|
|
987
|
+
}
|
|
874
988
|
}
|
|
875
989
|
// The recovery callback runs synchronously when the request channel's
|
|
876
990
|
// timeout calls triggerRecovery. block() must run before we await anything
|
|
@@ -961,26 +1075,37 @@ class IframeManager {
|
|
|
961
1075
|
}
|
|
962
1076
|
/**
|
|
963
1077
|
* Sets up message handlers for iframe → host requests (secureStorage and getSignedSessionId)
|
|
1078
|
+
*
|
|
1079
|
+
* Returns the channel plus one disposer per lifetime group, covering exactly the
|
|
1080
|
+
* handlers this call attached. Scoped per call because three different transports
|
|
1081
|
+
* use it — pooling them would let a display flow take ownership of the shared
|
|
1082
|
+
* container's handlers.
|
|
964
1083
|
*/ setupIframeRequestHandlers(transport) {
|
|
965
1084
|
// Create a request channel to handle requests FROM iframe
|
|
966
1085
|
const requestChannel = messageTransport.createRequestChannel(transport);
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
requestChannel.handle('
|
|
972
|
-
|
|
973
|
-
requestChannel.handle('removeClientKeyShare', this.handleRemoveClientKeyShare.bind(this))
|
|
974
|
-
|
|
1086
|
+
// handle() returns the unsubscribe; each entry here is one registration.
|
|
1087
|
+
// secureStorage: key-share reads/writes, answered identically by any manager
|
|
1088
|
+
// on the page, so they are not part of a detach.
|
|
1089
|
+
const storageDisposers = this.secureStorage ? [
|
|
1090
|
+
requestChannel.handle('getClientKeyShare', this.handleGetClientKeyShare.bind(this)),
|
|
1091
|
+
requestChannel.handle('setClientKeyShare', this.handleSetClientKeyShare.bind(this)),
|
|
1092
|
+
requestChannel.handle('removeClientKeyShare', this.handleRemoveClientKeyShare.bind(this))
|
|
1093
|
+
] : [];
|
|
1094
|
+
// Session-scoped: both answer from this client's own session state, so a
|
|
1095
|
+
// superseded manager must stop answering them the moment it is detached.
|
|
1096
|
+
const sessionDisposers = [];
|
|
975
1097
|
if (this.getSignedSessionIdCallback) {
|
|
976
|
-
|
|
977
|
-
requestChannel.handle('getSignedSessionId', this.handleGetSignedSessionId.bind(this));
|
|
1098
|
+
sessionDisposers.push(requestChannel.handle('getSignedSessionId', this.handleGetSignedSessionId.bind(this)));
|
|
978
1099
|
}
|
|
1100
|
+
// The iframe's 401 notification (iframe → host)
|
|
979
1101
|
if (this.onUnauthorized) {
|
|
980
|
-
|
|
981
|
-
requestChannel.handle('notifyUnauthorized', this.handleNotifyUnauthorized.bind(this));
|
|
1102
|
+
sessionDisposers.push(requestChannel.handle('notifyUnauthorized', this.handleNotifyUnauthorized.bind(this)));
|
|
982
1103
|
}
|
|
983
|
-
return
|
|
1104
|
+
return {
|
|
1105
|
+
disposeSessionHandlers: ()=>drainDisposers(sessionDisposers),
|
|
1106
|
+
disposeStorageHandlers: ()=>drainDisposers(storageDisposers),
|
|
1107
|
+
requestChannel
|
|
1108
|
+
};
|
|
984
1109
|
}
|
|
985
1110
|
/**
|
|
986
1111
|
* Handler for the iframe's `notifyUnauthorized` reverse-channel request.
|
|
@@ -1303,9 +1428,7 @@ class IframeManager {
|
|
|
1303
1428
|
const iframeDisplay = new IframeMessageHandler(transport);
|
|
1304
1429
|
// Set up iframe request handlers on this transport so the display iframe
|
|
1305
1430
|
// can access key shares (e.g. for exportPrivateKey) and signed session ID
|
|
1306
|
-
|
|
1307
|
-
this.setupIframeRequestHandlers(transport);
|
|
1308
|
-
}
|
|
1431
|
+
const requestHandlers = this.secureStorage || this.getSignedSessionIdCallback ? this.setupIframeRequestHandlers(transport) : undefined;
|
|
1309
1432
|
var _this_authMode;
|
|
1310
1433
|
// if authMode is header: inform iframe the authMode with auth token
|
|
1311
1434
|
// if authMode is cookie: inform iframe the authMode with empty authToken
|
|
@@ -1314,6 +1437,9 @@ class IframeManager {
|
|
|
1314
1437
|
iframe,
|
|
1315
1438
|
iframeDisplay,
|
|
1316
1439
|
cleanup: ()=>{
|
|
1440
|
+
if (requestHandlers) {
|
|
1441
|
+
disposeAllHandlers(requestHandlers);
|
|
1442
|
+
}
|
|
1317
1443
|
cleanupBridge();
|
|
1318
1444
|
iframe.remove();
|
|
1319
1445
|
}
|
|
@@ -1400,9 +1526,7 @@ class IframeManager {
|
|
|
1400
1526
|
// Set up iframe request handlers on this transport so the display
|
|
1401
1527
|
// container can access key shares (e.g. for exportPrivateKey) and the
|
|
1402
1528
|
// signed session ID — mirrors initializeIframeDisplayForContainer.
|
|
1403
|
-
|
|
1404
|
-
this.setupIframeRequestHandlers(transport);
|
|
1405
|
-
}
|
|
1529
|
+
const requestHandlers = this.secureStorage || this.getSignedSessionIdCallback ? this.setupIframeRequestHandlers(transport) : undefined;
|
|
1406
1530
|
var _this_authMode;
|
|
1407
1531
|
// if authMode is header: inform the container of the authMode with auth token
|
|
1408
1532
|
// if authMode is cookie: inform the container of the authMode with empty authToken
|
|
@@ -1410,6 +1534,9 @@ class IframeManager {
|
|
|
1410
1534
|
return {
|
|
1411
1535
|
iframeDisplay,
|
|
1412
1536
|
cleanup: ()=>{
|
|
1537
|
+
if (requestHandlers) {
|
|
1538
|
+
disposeAllHandlers(requestHandlers);
|
|
1539
|
+
}
|
|
1413
1540
|
cleanupBridge();
|
|
1414
1541
|
waasSDKContainer.destroy();
|
|
1415
1542
|
}
|
|
@@ -1419,7 +1546,30 @@ class IframeManager {
|
|
|
1419
1546
|
throw error;
|
|
1420
1547
|
}
|
|
1421
1548
|
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Stops this manager answering the iframe's *session-scoped* reverse-channel
|
|
1551
|
+
* requests (iframe → host), and nothing else. Call it on a manager superseded
|
|
1552
|
+
* by a newer one for the same page.
|
|
1553
|
+
*
|
|
1554
|
+
* Every bridged manager answers every iframe → host request and the channel is
|
|
1555
|
+
* first-response-wins, so a superseded manager with revoked host callbacks
|
|
1556
|
+
* rejects `getSignedSessionId` instantly and beats the live one's round-trip —
|
|
1557
|
+
* killing wallet creation for the life of the iframe.
|
|
1558
|
+
*
|
|
1559
|
+
* Narrow on purpose. The key-share handlers stay: they proxy host storage by
|
|
1560
|
+
* address, so any manager answers them identically, and the iframe still needs
|
|
1561
|
+
* them to clear shares during {@link cleanup}. Transport, bridge, recovery
|
|
1562
|
+
* subscription and container membership stay too, so in-flight operations still
|
|
1563
|
+
* get their responses (tearing them down makes an in-flight wallet creation hang
|
|
1564
|
+
* forever). Use {@link cleanup} for a real teardown, and call this any time —
|
|
1565
|
+
* including mid-initialization.
|
|
1566
|
+
*/ detach() {
|
|
1567
|
+
this.requestHandlers.detach();
|
|
1568
|
+
}
|
|
1422
1569
|
async cleanup() {
|
|
1570
|
+
// Detach up front, so a logged-out manager stops answering signed-session
|
|
1571
|
+
// requests immediately instead of once the ACK below returns (or times out).
|
|
1572
|
+
this.requestHandlers.markTornDown();
|
|
1423
1573
|
// Best-effort ACK (the iframe clears its storage), only if already
|
|
1424
1574
|
// initialized. Do NOT initialize here: that mounts a fresh container just to
|
|
1425
1575
|
// ACK and then tears it down, churning the shared container across
|
|
@@ -1449,6 +1599,9 @@ class IframeManager {
|
|
|
1449
1599
|
// detached one); re-arm the unauthorized guard for a future session.
|
|
1450
1600
|
this.messageTransport = null;
|
|
1451
1601
|
this.iframeMessageHandler = null;
|
|
1602
|
+
// Key-share handlers go last: the iframe's cleanup above clears each wallet's
|
|
1603
|
+
// shares over this channel and needs a host still answering.
|
|
1604
|
+
this.requestHandlers.disposeStorageHandlers();
|
|
1452
1605
|
this.iframeRequestChannel = undefined;
|
|
1453
1606
|
this.unauthorizedHandled = false;
|
|
1454
1607
|
}
|
|
@@ -1465,6 +1618,11 @@ class IframeManager {
|
|
|
1465
1618
|
* it — otherwise the request channel's retry timer keeps firing recovery against
|
|
1466
1619
|
* a torn-down transport on the next session. */ this.cleanupRecoverySubscription = null;
|
|
1467
1620
|
this.iframe = null;
|
|
1621
|
+
/**
|
|
1622
|
+
* Lifetimes of the shared container's reverse-channel handlers: which of them
|
|
1623
|
+
* a superseded manager stops answering ({@link detach}) and which outlive it
|
|
1624
|
+
* until teardown ({@link cleanup}).
|
|
1625
|
+
*/ this.requestHandlers = new IframeRequestHandlerRegistry();
|
|
1468
1626
|
this.waasSDKContainer = null;
|
|
1469
1627
|
/** One-shot guard so a burst of 401 notifications triggers a single logout. */ this.unauthorizedHandled = false;
|
|
1470
1628
|
this.environmentId = environmentId;
|
package/index.esm.js
CHANGED
|
@@ -14,6 +14,19 @@ function _extends() {
|
|
|
14
14
|
return _extends.apply(this, arguments);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function _object_without_properties_loose(source, excluded) {
|
|
18
|
+
if (source == null) return {};
|
|
19
|
+
var target = {};
|
|
20
|
+
var sourceKeys = Object.keys(source);
|
|
21
|
+
var key, i;
|
|
22
|
+
for(i = 0; i < sourceKeys.length; i++){
|
|
23
|
+
key = sourceKeys[i];
|
|
24
|
+
if (excluded.indexOf(key) >= 0) continue;
|
|
25
|
+
target[key] = source[key];
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
class IframeMessageHandler {
|
|
18
31
|
// Handle error response from iframe message handler
|
|
19
32
|
handleIframeMessageResponseError(response) {
|
|
@@ -611,6 +624,100 @@ const createIframeWaasSDKContainer = ()=>{
|
|
|
611
624
|
}
|
|
612
625
|
};
|
|
613
626
|
|
|
627
|
+
/**
|
|
628
|
+
* One `setupIframeRequestHandlers` call's worth of registrations, grouped by
|
|
629
|
+
* lifetime.
|
|
630
|
+
*/ /** Unregisters every handler in `disposers` and empties the list. */ const drainDisposers = (disposers)=>{
|
|
631
|
+
disposers.forEach((unsubscribe)=>unsubscribe());
|
|
632
|
+
disposers.length = 0;
|
|
633
|
+
};
|
|
634
|
+
/** Unregisters both halves of a registration. */ const disposeAllHandlers = ({ disposeSessionHandlers, disposeStorageHandlers })=>{
|
|
635
|
+
disposeSessionHandlers();
|
|
636
|
+
disposeStorageHandlers();
|
|
637
|
+
};
|
|
638
|
+
/**
|
|
639
|
+
* Lifetime bookkeeping for the iframe → host ("reverse channel") request
|
|
640
|
+
* handlers one IframeManager has attached to the shared container.
|
|
641
|
+
*
|
|
642
|
+
* A page has one WaaS iframe but every client built on it stays bridged to it,
|
|
643
|
+
* so the reverse channel is first-response-wins: a superseded manager with a
|
|
644
|
+
* revoked session callback rejects `getSignedSessionId` instantly and beats the
|
|
645
|
+
* live client's round-trip. This registry is what lets such a manager leave the
|
|
646
|
+
* race (see {@link detach}) without disturbing the key-share handlers, which
|
|
647
|
+
* the iframe still calls back during teardown.
|
|
648
|
+
*/ class IframeRequestHandlerRegistry {
|
|
649
|
+
get sessionHandlerCount() {
|
|
650
|
+
return this.sessionDisposers.length;
|
|
651
|
+
}
|
|
652
|
+
get storageHandlerCount() {
|
|
653
|
+
return this.storageDisposers.length;
|
|
654
|
+
}
|
|
655
|
+
/** Capture the current generations before awaiting the iframe. */ snapshot() {
|
|
656
|
+
return {
|
|
657
|
+
cleanupGeneration: this.cleanupGeneration,
|
|
658
|
+
detachGeneration: this.detachGeneration
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Takes ownership of a registration whose setup started at `epoch`, keeping
|
|
663
|
+
* whichever halves are still legitimate, and reports whether the registration
|
|
664
|
+
* is the manager's current one (the caller's channel is only worth keeping
|
|
665
|
+
* then). The host doesn't await `initialize()`, so a detach or a cleanup can
|
|
666
|
+
* land while the iframe is still loading — which one moved decides:
|
|
667
|
+
*
|
|
668
|
+
* - **Torn down** (cleanup): keep nothing. The lists this would land on are
|
|
669
|
+
* already drained, so anything tracked here would stay attached to a
|
|
670
|
+
* logged-out manager forever.
|
|
671
|
+
* - **Superseded** (detach): the session handlers would put this manager back
|
|
672
|
+
* into the race, but its key-share handlers still answer honestly — and on a
|
|
673
|
+
* first initialization they are the only ones the page has.
|
|
674
|
+
* - **Neither**: track both.
|
|
675
|
+
*/ adopt(epoch, registration) {
|
|
676
|
+
if (this.cleanupGeneration !== epoch.cleanupGeneration) {
|
|
677
|
+
disposeAllHandlers(registration);
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
if (this.detachGeneration !== epoch.detachGeneration) {
|
|
681
|
+
registration.disposeSessionHandlers();
|
|
682
|
+
this.storageDisposers.push(registration.disposeStorageHandlers);
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
this.sessionDisposers.push(registration.disposeSessionHandlers);
|
|
686
|
+
this.storageDisposers.push(registration.disposeStorageHandlers);
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Stops answering the session-scoped requests, and nothing else. Safe to call
|
|
691
|
+
* repeatedly, and mid-initialization: a registration still in flight is
|
|
692
|
+
* adopted as "superseded".
|
|
693
|
+
*/ detach() {
|
|
694
|
+
this.detachGeneration += 1;
|
|
695
|
+
drainDisposers(this.sessionDisposers);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* First phase of a teardown: leave the session race immediately, and mark
|
|
699
|
+
* anything still initializing as torn down rather than merely superseded. The
|
|
700
|
+
* storage handlers stay until {@link disposeStorageHandlers}, because the
|
|
701
|
+
* iframe's own cleanup clears each wallet's shares over this channel.
|
|
702
|
+
*/ markTornDown() {
|
|
703
|
+
this.cleanupGeneration += 1;
|
|
704
|
+
this.detach();
|
|
705
|
+
}
|
|
706
|
+
/** Second phase of a teardown: drop the key-share handlers, once the iframe has finished calling back. */ disposeStorageHandlers() {
|
|
707
|
+
drainDisposers(this.storageDisposers);
|
|
708
|
+
}
|
|
709
|
+
constructor(){
|
|
710
|
+
/**
|
|
711
|
+
* Lists, not single entries: `initializeMessageTransport` checks its guard
|
|
712
|
+
* *then* awaits, so one manager can end up registered on two transports that
|
|
713
|
+
* both answer, and overwriting would orphan the first.
|
|
714
|
+
*/ this.sessionDisposers = [];
|
|
715
|
+
this.storageDisposers = [];
|
|
716
|
+
/** Bumped by {@link detach}. */ this.detachGeneration = 0;
|
|
717
|
+
/** Bumped by {@link markTornDown}. */ this.cleanupGeneration = 0;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
614
721
|
const FRAME_ANCESTORS_QUERY_PARAM = 'frameAncestors';
|
|
615
722
|
/**
|
|
616
723
|
* Builds the iframe message transport with the recovery + block decorators.
|
|
@@ -852,6 +959,8 @@ class IframeManager {
|
|
|
852
959
|
this.logger.debug('Skipping initializeMessageTransport: transport and message handler already initialized');
|
|
853
960
|
return;
|
|
854
961
|
}
|
|
962
|
+
// Captured before the await: detach() or cleanup() can run while the iframe loads.
|
|
963
|
+
const epoch = this.requestHandlers.snapshot();
|
|
855
964
|
await this.initializeIframeCommunication();
|
|
856
965
|
const transport = createIframeMessageTransport();
|
|
857
966
|
this.messageTransport = transport;
|
|
@@ -869,7 +978,12 @@ class IframeManager {
|
|
|
869
978
|
// Set up request channel to handle messages from iframe (for secureStorage,
|
|
870
979
|
// getSignedSessionId, and the unauthorized notification)
|
|
871
980
|
if (this.secureStorage || this.getSignedSessionIdCallback || this.onUnauthorized) {
|
|
872
|
-
|
|
981
|
+
const _this_setupIframeRequestHandlers = this.setupIframeRequestHandlers(this.messageTransport), { requestChannel } = _this_setupIframeRequestHandlers, registration = _object_without_properties_loose(_this_setupIframeRequestHandlers, [
|
|
982
|
+
"requestChannel"
|
|
983
|
+
]);
|
|
984
|
+
if (this.requestHandlers.adopt(epoch, registration)) {
|
|
985
|
+
this.iframeRequestChannel = requestChannel;
|
|
986
|
+
}
|
|
873
987
|
}
|
|
874
988
|
// The recovery callback runs synchronously when the request channel's
|
|
875
989
|
// timeout calls triggerRecovery. block() must run before we await anything
|
|
@@ -960,26 +1074,37 @@ class IframeManager {
|
|
|
960
1074
|
}
|
|
961
1075
|
/**
|
|
962
1076
|
* Sets up message handlers for iframe → host requests (secureStorage and getSignedSessionId)
|
|
1077
|
+
*
|
|
1078
|
+
* Returns the channel plus one disposer per lifetime group, covering exactly the
|
|
1079
|
+
* handlers this call attached. Scoped per call because three different transports
|
|
1080
|
+
* use it — pooling them would let a display flow take ownership of the shared
|
|
1081
|
+
* container's handlers.
|
|
963
1082
|
*/ setupIframeRequestHandlers(transport) {
|
|
964
1083
|
// Create a request channel to handle requests FROM iframe
|
|
965
1084
|
const requestChannel = createRequestChannel(transport);
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
requestChannel.handle('
|
|
971
|
-
|
|
972
|
-
requestChannel.handle('removeClientKeyShare', this.handleRemoveClientKeyShare.bind(this))
|
|
973
|
-
|
|
1085
|
+
// handle() returns the unsubscribe; each entry here is one registration.
|
|
1086
|
+
// secureStorage: key-share reads/writes, answered identically by any manager
|
|
1087
|
+
// on the page, so they are not part of a detach.
|
|
1088
|
+
const storageDisposers = this.secureStorage ? [
|
|
1089
|
+
requestChannel.handle('getClientKeyShare', this.handleGetClientKeyShare.bind(this)),
|
|
1090
|
+
requestChannel.handle('setClientKeyShare', this.handleSetClientKeyShare.bind(this)),
|
|
1091
|
+
requestChannel.handle('removeClientKeyShare', this.handleRemoveClientKeyShare.bind(this))
|
|
1092
|
+
] : [];
|
|
1093
|
+
// Session-scoped: both answer from this client's own session state, so a
|
|
1094
|
+
// superseded manager must stop answering them the moment it is detached.
|
|
1095
|
+
const sessionDisposers = [];
|
|
974
1096
|
if (this.getSignedSessionIdCallback) {
|
|
975
|
-
|
|
976
|
-
requestChannel.handle('getSignedSessionId', this.handleGetSignedSessionId.bind(this));
|
|
1097
|
+
sessionDisposers.push(requestChannel.handle('getSignedSessionId', this.handleGetSignedSessionId.bind(this)));
|
|
977
1098
|
}
|
|
1099
|
+
// The iframe's 401 notification (iframe → host)
|
|
978
1100
|
if (this.onUnauthorized) {
|
|
979
|
-
|
|
980
|
-
requestChannel.handle('notifyUnauthorized', this.handleNotifyUnauthorized.bind(this));
|
|
1101
|
+
sessionDisposers.push(requestChannel.handle('notifyUnauthorized', this.handleNotifyUnauthorized.bind(this)));
|
|
981
1102
|
}
|
|
982
|
-
return
|
|
1103
|
+
return {
|
|
1104
|
+
disposeSessionHandlers: ()=>drainDisposers(sessionDisposers),
|
|
1105
|
+
disposeStorageHandlers: ()=>drainDisposers(storageDisposers),
|
|
1106
|
+
requestChannel
|
|
1107
|
+
};
|
|
983
1108
|
}
|
|
984
1109
|
/**
|
|
985
1110
|
* Handler for the iframe's `notifyUnauthorized` reverse-channel request.
|
|
@@ -1302,9 +1427,7 @@ class IframeManager {
|
|
|
1302
1427
|
const iframeDisplay = new IframeMessageHandler(transport);
|
|
1303
1428
|
// Set up iframe request handlers on this transport so the display iframe
|
|
1304
1429
|
// can access key shares (e.g. for exportPrivateKey) and signed session ID
|
|
1305
|
-
|
|
1306
|
-
this.setupIframeRequestHandlers(transport);
|
|
1307
|
-
}
|
|
1430
|
+
const requestHandlers = this.secureStorage || this.getSignedSessionIdCallback ? this.setupIframeRequestHandlers(transport) : undefined;
|
|
1308
1431
|
var _this_authMode;
|
|
1309
1432
|
// if authMode is header: inform iframe the authMode with auth token
|
|
1310
1433
|
// if authMode is cookie: inform iframe the authMode with empty authToken
|
|
@@ -1313,6 +1436,9 @@ class IframeManager {
|
|
|
1313
1436
|
iframe,
|
|
1314
1437
|
iframeDisplay,
|
|
1315
1438
|
cleanup: ()=>{
|
|
1439
|
+
if (requestHandlers) {
|
|
1440
|
+
disposeAllHandlers(requestHandlers);
|
|
1441
|
+
}
|
|
1316
1442
|
cleanupBridge();
|
|
1317
1443
|
iframe.remove();
|
|
1318
1444
|
}
|
|
@@ -1399,9 +1525,7 @@ class IframeManager {
|
|
|
1399
1525
|
// Set up iframe request handlers on this transport so the display
|
|
1400
1526
|
// container can access key shares (e.g. for exportPrivateKey) and the
|
|
1401
1527
|
// signed session ID — mirrors initializeIframeDisplayForContainer.
|
|
1402
|
-
|
|
1403
|
-
this.setupIframeRequestHandlers(transport);
|
|
1404
|
-
}
|
|
1528
|
+
const requestHandlers = this.secureStorage || this.getSignedSessionIdCallback ? this.setupIframeRequestHandlers(transport) : undefined;
|
|
1405
1529
|
var _this_authMode;
|
|
1406
1530
|
// if authMode is header: inform the container of the authMode with auth token
|
|
1407
1531
|
// if authMode is cookie: inform the container of the authMode with empty authToken
|
|
@@ -1409,6 +1533,9 @@ class IframeManager {
|
|
|
1409
1533
|
return {
|
|
1410
1534
|
iframeDisplay,
|
|
1411
1535
|
cleanup: ()=>{
|
|
1536
|
+
if (requestHandlers) {
|
|
1537
|
+
disposeAllHandlers(requestHandlers);
|
|
1538
|
+
}
|
|
1412
1539
|
cleanupBridge();
|
|
1413
1540
|
waasSDKContainer.destroy();
|
|
1414
1541
|
}
|
|
@@ -1418,7 +1545,30 @@ class IframeManager {
|
|
|
1418
1545
|
throw error;
|
|
1419
1546
|
}
|
|
1420
1547
|
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Stops this manager answering the iframe's *session-scoped* reverse-channel
|
|
1550
|
+
* requests (iframe → host), and nothing else. Call it on a manager superseded
|
|
1551
|
+
* by a newer one for the same page.
|
|
1552
|
+
*
|
|
1553
|
+
* Every bridged manager answers every iframe → host request and the channel is
|
|
1554
|
+
* first-response-wins, so a superseded manager with revoked host callbacks
|
|
1555
|
+
* rejects `getSignedSessionId` instantly and beats the live one's round-trip —
|
|
1556
|
+
* killing wallet creation for the life of the iframe.
|
|
1557
|
+
*
|
|
1558
|
+
* Narrow on purpose. The key-share handlers stay: they proxy host storage by
|
|
1559
|
+
* address, so any manager answers them identically, and the iframe still needs
|
|
1560
|
+
* them to clear shares during {@link cleanup}. Transport, bridge, recovery
|
|
1561
|
+
* subscription and container membership stay too, so in-flight operations still
|
|
1562
|
+
* get their responses (tearing them down makes an in-flight wallet creation hang
|
|
1563
|
+
* forever). Use {@link cleanup} for a real teardown, and call this any time —
|
|
1564
|
+
* including mid-initialization.
|
|
1565
|
+
*/ detach() {
|
|
1566
|
+
this.requestHandlers.detach();
|
|
1567
|
+
}
|
|
1421
1568
|
async cleanup() {
|
|
1569
|
+
// Detach up front, so a logged-out manager stops answering signed-session
|
|
1570
|
+
// requests immediately instead of once the ACK below returns (or times out).
|
|
1571
|
+
this.requestHandlers.markTornDown();
|
|
1422
1572
|
// Best-effort ACK (the iframe clears its storage), only if already
|
|
1423
1573
|
// initialized. Do NOT initialize here: that mounts a fresh container just to
|
|
1424
1574
|
// ACK and then tears it down, churning the shared container across
|
|
@@ -1448,6 +1598,9 @@ class IframeManager {
|
|
|
1448
1598
|
// detached one); re-arm the unauthorized guard for a future session.
|
|
1449
1599
|
this.messageTransport = null;
|
|
1450
1600
|
this.iframeMessageHandler = null;
|
|
1601
|
+
// Key-share handlers go last: the iframe's cleanup above clears each wallet's
|
|
1602
|
+
// shares over this channel and needs a host still answering.
|
|
1603
|
+
this.requestHandlers.disposeStorageHandlers();
|
|
1451
1604
|
this.iframeRequestChannel = undefined;
|
|
1452
1605
|
this.unauthorizedHandled = false;
|
|
1453
1606
|
}
|
|
@@ -1464,6 +1617,11 @@ class IframeManager {
|
|
|
1464
1617
|
* it — otherwise the request channel's retry timer keeps firing recovery against
|
|
1465
1618
|
* a torn-down transport on the next session. */ this.cleanupRecoverySubscription = null;
|
|
1466
1619
|
this.iframe = null;
|
|
1620
|
+
/**
|
|
1621
|
+
* Lifetimes of the shared container's reverse-channel handlers: which of them
|
|
1622
|
+
* a superseded manager stops answering ({@link detach}) and which outlive it
|
|
1623
|
+
* until teardown ({@link cleanup}).
|
|
1624
|
+
*/ this.requestHandlers = new IframeRequestHandlerRegistry();
|
|
1467
1625
|
this.waasSDKContainer = null;
|
|
1468
1626
|
/** One-shot guard so a burst of 401 notifications triggers a single logout. */ this.unauthorizedHandled = false;
|
|
1469
1627
|
this.environmentId = environmentId;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamic-labs-wallet/browser-wallet-client",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@dynamic-labs-wallet/core": "1.1.
|
|
7
|
+
"@dynamic-labs-wallet/core": "1.1.8",
|
|
8
8
|
"@dynamic-labs/logger": "^5.0.0",
|
|
9
9
|
"@dynamic-labs/message-transport": "^5.0.0"
|
|
10
10
|
},
|
|
@@ -92,6 +92,12 @@ export declare class IframeManager {
|
|
|
92
92
|
* Used for secureStorage operations and signed session ID retrieval
|
|
93
93
|
*/
|
|
94
94
|
protected iframeRequestChannel?: RequestChannel<Pick<IframeRequestMessages, 'getClientKeyShare' | 'setClientKeyShare' | 'getSignedSessionId' | 'notifyUnauthorized'>>;
|
|
95
|
+
/**
|
|
96
|
+
* Lifetimes of the shared container's reverse-channel handlers: which of them
|
|
97
|
+
* a superseded manager stops answering ({@link detach}) and which outlive it
|
|
98
|
+
* until teardown ({@link cleanup}).
|
|
99
|
+
*/
|
|
100
|
+
private readonly requestHandlers;
|
|
95
101
|
private static readonly maxAdditionalTrustedOrigins;
|
|
96
102
|
private static readonly maxTrustedOriginStringLength;
|
|
97
103
|
private readonly additionalTrustedOrigins;
|
|
@@ -220,6 +226,11 @@ export declare class IframeManager {
|
|
|
220
226
|
private recoverIframe;
|
|
221
227
|
/**
|
|
222
228
|
* Sets up message handlers for iframe → host requests (secureStorage and getSignedSessionId)
|
|
229
|
+
*
|
|
230
|
+
* Returns the channel plus one disposer per lifetime group, covering exactly the
|
|
231
|
+
* handlers this call attached. Scoped per call because three different transports
|
|
232
|
+
* use it — pooling them would let a display flow take ownership of the shared
|
|
233
|
+
* container's handlers.
|
|
223
234
|
*/
|
|
224
235
|
private setupIframeRequestHandlers;
|
|
225
236
|
/**
|
|
@@ -320,6 +331,25 @@ export declare class IframeManager {
|
|
|
320
331
|
iframeDisplay: IframeMessageHandler;
|
|
321
332
|
cleanup: () => void;
|
|
322
333
|
}>;
|
|
334
|
+
/**
|
|
335
|
+
* Stops this manager answering the iframe's *session-scoped* reverse-channel
|
|
336
|
+
* requests (iframe → host), and nothing else. Call it on a manager superseded
|
|
337
|
+
* by a newer one for the same page.
|
|
338
|
+
*
|
|
339
|
+
* Every bridged manager answers every iframe → host request and the channel is
|
|
340
|
+
* first-response-wins, so a superseded manager with revoked host callbacks
|
|
341
|
+
* rejects `getSignedSessionId` instantly and beats the live one's round-trip —
|
|
342
|
+
* killing wallet creation for the life of the iframe.
|
|
343
|
+
*
|
|
344
|
+
* Narrow on purpose. The key-share handlers stay: they proxy host storage by
|
|
345
|
+
* address, so any manager answers them identically, and the iframe still needs
|
|
346
|
+
* them to clear shares during {@link cleanup}. Transport, bridge, recovery
|
|
347
|
+
* subscription and container membership stay too, so in-flight operations still
|
|
348
|
+
* get their responses (tearing them down makes an in-flight wallet creation hang
|
|
349
|
+
* forever). Use {@link cleanup} for a real teardown, and call this any time —
|
|
350
|
+
* including mid-initialization.
|
|
351
|
+
*/
|
|
352
|
+
detach(): void;
|
|
323
353
|
cleanup(): Promise<void>;
|
|
324
354
|
}
|
|
325
355
|
//# sourceMappingURL=IframeManager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../../src/client/iframeManager/IframeManager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,KAAK,sBAAsB,EAK3B,KAAK,qBAAqB,EAG1B,KAAK,oBAAoB,EAEzB,KAAK,gBAAgB,EACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAOL,KAAK,iCAAiC,EACtC,KAAK,cAAc,EAEpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../../src/client/iframeManager/IframeManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One `setupIframeRequestHandlers` call's worth of registrations, grouped by
|
|
3
|
+
* lifetime.
|
|
4
|
+
*/
|
|
5
|
+
export type IframeRequestHandlerRegistration = {
|
|
6
|
+
/**
|
|
7
|
+
* Unregisters the handlers that answer from this client's own session state
|
|
8
|
+
* (`getSignedSessionId`, `notifyUnauthorized`) — the ones a superseded manager
|
|
9
|
+
* must stop answering.
|
|
10
|
+
*/
|
|
11
|
+
disposeSessionHandlers: VoidFunction;
|
|
12
|
+
/**
|
|
13
|
+
* Unregisters the handlers that proxy host key-share storage by address. Any
|
|
14
|
+
* manager on the page answers them identically, so they outlive a detach.
|
|
15
|
+
*/
|
|
16
|
+
disposeStorageHandlers: VoidFunction;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Registry generations, captured before an await so a registration landing
|
|
20
|
+
* afterwards can be told apart from a current one. See {@link IframeRequestHandlerRegistry.adopt}.
|
|
21
|
+
*/
|
|
22
|
+
export type IframeRequestHandlerEpoch = {
|
|
23
|
+
detachGeneration: number;
|
|
24
|
+
cleanupGeneration: number;
|
|
25
|
+
};
|
|
26
|
+
/** Unregisters every handler in `disposers` and empties the list. */
|
|
27
|
+
export declare const drainDisposers: (disposers: VoidFunction[]) => void;
|
|
28
|
+
/** Unregisters both halves of a registration. */
|
|
29
|
+
export declare const disposeAllHandlers: ({ disposeSessionHandlers, disposeStorageHandlers, }: IframeRequestHandlerRegistration) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Lifetime bookkeeping for the iframe → host ("reverse channel") request
|
|
32
|
+
* handlers one IframeManager has attached to the shared container.
|
|
33
|
+
*
|
|
34
|
+
* A page has one WaaS iframe but every client built on it stays bridged to it,
|
|
35
|
+
* so the reverse channel is first-response-wins: a superseded manager with a
|
|
36
|
+
* revoked session callback rejects `getSignedSessionId` instantly and beats the
|
|
37
|
+
* live client's round-trip. This registry is what lets such a manager leave the
|
|
38
|
+
* race (see {@link detach}) without disturbing the key-share handlers, which
|
|
39
|
+
* the iframe still calls back during teardown.
|
|
40
|
+
*/
|
|
41
|
+
export declare class IframeRequestHandlerRegistry {
|
|
42
|
+
/**
|
|
43
|
+
* Lists, not single entries: `initializeMessageTransport` checks its guard
|
|
44
|
+
* *then* awaits, so one manager can end up registered on two transports that
|
|
45
|
+
* both answer, and overwriting would orphan the first.
|
|
46
|
+
*/
|
|
47
|
+
private readonly sessionDisposers;
|
|
48
|
+
private readonly storageDisposers;
|
|
49
|
+
/** Bumped by {@link detach}. */
|
|
50
|
+
private detachGeneration;
|
|
51
|
+
/** Bumped by {@link markTornDown}. */
|
|
52
|
+
private cleanupGeneration;
|
|
53
|
+
get sessionHandlerCount(): number;
|
|
54
|
+
get storageHandlerCount(): number;
|
|
55
|
+
/** Capture the current generations before awaiting the iframe. */
|
|
56
|
+
snapshot(): IframeRequestHandlerEpoch;
|
|
57
|
+
/**
|
|
58
|
+
* Takes ownership of a registration whose setup started at `epoch`, keeping
|
|
59
|
+
* whichever halves are still legitimate, and reports whether the registration
|
|
60
|
+
* is the manager's current one (the caller's channel is only worth keeping
|
|
61
|
+
* then). The host doesn't await `initialize()`, so a detach or a cleanup can
|
|
62
|
+
* land while the iframe is still loading — which one moved decides:
|
|
63
|
+
*
|
|
64
|
+
* - **Torn down** (cleanup): keep nothing. The lists this would land on are
|
|
65
|
+
* already drained, so anything tracked here would stay attached to a
|
|
66
|
+
* logged-out manager forever.
|
|
67
|
+
* - **Superseded** (detach): the session handlers would put this manager back
|
|
68
|
+
* into the race, but its key-share handlers still answer honestly — and on a
|
|
69
|
+
* first initialization they are the only ones the page has.
|
|
70
|
+
* - **Neither**: track both.
|
|
71
|
+
*/
|
|
72
|
+
adopt(epoch: IframeRequestHandlerEpoch, registration: IframeRequestHandlerRegistration): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Stops answering the session-scoped requests, and nothing else. Safe to call
|
|
75
|
+
* repeatedly, and mid-initialization: a registration still in flight is
|
|
76
|
+
* adopted as "superseded".
|
|
77
|
+
*/
|
|
78
|
+
detach(): void;
|
|
79
|
+
/**
|
|
80
|
+
* First phase of a teardown: leave the session race immediately, and mark
|
|
81
|
+
* anything still initializing as torn down rather than merely superseded. The
|
|
82
|
+
* storage handlers stay until {@link disposeStorageHandlers}, because the
|
|
83
|
+
* iframe's own cleanup clears each wallet's shares over this channel.
|
|
84
|
+
*/
|
|
85
|
+
markTornDown(): void;
|
|
86
|
+
/** Second phase of a teardown: drop the key-share handlers, once the iframe has finished calling back. */
|
|
87
|
+
disposeStorageHandlers(): void;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=IframeRequestHandlerRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IframeRequestHandlerRegistry.d.ts","sourceRoot":"","sources":["../../../src/client/iframeManager/IframeRequestHandlerRegistry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C;;;;OAIG;IACH,sBAAsB,EAAE,YAAY,CAAC;IACrC;;;OAGG;IACH,sBAAsB,EAAE,YAAY,CAAC;CACtC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,cAAc,cAAe,YAAY,EAAE,KAAG,IAG1D,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,kBAAkB,wDAG5B,gCAAgC,KAAG,IAGrC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,4BAA4B;IACvC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IACvD,gCAAgC;IAChC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,sCAAsC;IACtC,OAAO,CAAC,iBAAiB,CAAK;IAE9B,IAAW,mBAAmB,IAAI,MAAM,CAEvC;IAED,IAAW,mBAAmB,IAAI,MAAM,CAEvC;IAED,kEAAkE;IAC3D,QAAQ,IAAI,yBAAyB;IAI5C;;;;;;;;;;;;;;OAcG;IACI,KAAK,CAAC,KAAK,EAAE,yBAAyB,EAAE,YAAY,EAAE,gCAAgC,GAAG,OAAO;IAiBvG;;;;OAIG;IACI,MAAM,IAAI,IAAI;IAKrB;;;;;OAKG;IACI,YAAY,IAAI,IAAI;IAK3B,0GAA0G;IACnG,sBAAsB,IAAI,IAAI;CAGtC"}
|