@edge-markets/connect-link 1.2.0 → 1.4.0
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/dist/index.d.mts +589 -129
- package/dist/index.d.ts +589 -129
- package/dist/index.js +599 -9
- package/dist/index.mjs +595 -17
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -20,16 +20,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
ALL_EDGE_SCOPES: () =>
|
|
24
|
-
EDGE_SCOPES: () =>
|
|
25
|
-
EdgeError: () =>
|
|
23
|
+
ALL_EDGE_SCOPES: () => import_connect4.ALL_EDGE_SCOPES,
|
|
24
|
+
EDGE_SCOPES: () => import_connect4.EDGE_SCOPES,
|
|
25
|
+
EdgeError: () => import_connect3.EdgeError,
|
|
26
26
|
EdgeLink: () => EdgeLink,
|
|
27
|
-
EdgePopupBlockedError: () =>
|
|
28
|
-
EdgeStateMismatchError: () =>
|
|
27
|
+
EdgePopupBlockedError: () => import_connect3.EdgePopupBlockedError,
|
|
28
|
+
EdgeStateMismatchError: () => import_connect3.EdgeStateMismatchError,
|
|
29
|
+
EdgeTransferVerify: () => EdgeTransferVerify,
|
|
30
|
+
IframeManager: () => IframeManager,
|
|
29
31
|
assertCryptoAvailable: () => assertCryptoAvailable,
|
|
32
|
+
collectGeolocation: () => collectGeolocation,
|
|
30
33
|
generatePKCE: () => generatePKCE,
|
|
31
34
|
generateState: () => generateState,
|
|
32
|
-
isEdgeError: () =>
|
|
35
|
+
isEdgeError: () => import_connect3.isEdgeError,
|
|
33
36
|
useEdgeLink: () => useEdgeLink
|
|
34
37
|
});
|
|
35
38
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -330,6 +333,7 @@ var EdgeLink = class {
|
|
|
330
333
|
this.state = null;
|
|
331
334
|
this.messageHandler = null;
|
|
332
335
|
this.isDestroyed = false;
|
|
336
|
+
this.isInitializing = false;
|
|
333
337
|
if (!config.clientId) {
|
|
334
338
|
throw new Error("EdgeLink: clientId is required");
|
|
335
339
|
}
|
|
@@ -374,7 +378,7 @@ var EdgeLink = class {
|
|
|
374
378
|
if (this.isDestroyed) {
|
|
375
379
|
throw new Error("EdgeLink: Cannot open - instance has been destroyed");
|
|
376
380
|
}
|
|
377
|
-
if (this.popup.isOpen()) {
|
|
381
|
+
if (this.popup.isOpen() || this.isInitializing) {
|
|
378
382
|
this.popup.focus();
|
|
379
383
|
return;
|
|
380
384
|
}
|
|
@@ -437,6 +441,7 @@ var EdgeLink = class {
|
|
|
437
441
|
* Initializes PKCE and navigates popup to auth URL.
|
|
438
442
|
*/
|
|
439
443
|
async initializeAuth(scopes) {
|
|
444
|
+
this.isInitializing = true;
|
|
440
445
|
try {
|
|
441
446
|
this.pkce = await generatePKCE();
|
|
442
447
|
this.state = generateState();
|
|
@@ -453,6 +458,8 @@ var EdgeLink = class {
|
|
|
453
458
|
message: error instanceof Error ? error.message : "Failed to initialize"
|
|
454
459
|
}
|
|
455
460
|
});
|
|
461
|
+
} finally {
|
|
462
|
+
this.isInitializing = false;
|
|
456
463
|
}
|
|
457
464
|
}
|
|
458
465
|
/**
|
|
@@ -484,6 +491,7 @@ var EdgeLink = class {
|
|
|
484
491
|
*/
|
|
485
492
|
setupMessageListener() {
|
|
486
493
|
this.messageHandler = (event) => {
|
|
494
|
+
if (this.isDestroyed) return;
|
|
487
495
|
if (event.origin !== this.expectedOrigin) {
|
|
488
496
|
return;
|
|
489
497
|
}
|
|
@@ -528,7 +536,7 @@ var EdgeLink = class {
|
|
|
528
536
|
}
|
|
529
537
|
});
|
|
530
538
|
this.close();
|
|
531
|
-
|
|
539
|
+
return;
|
|
532
540
|
}
|
|
533
541
|
if (!this.pkce?.verifier) {
|
|
534
542
|
this.config.onExit?.({
|
|
@@ -645,9 +653,588 @@ function useEdgeLink(config) {
|
|
|
645
653
|
return { open, ready, error, isOpen };
|
|
646
654
|
}
|
|
647
655
|
|
|
648
|
-
// src/
|
|
656
|
+
// src/edge-transfer-verify.ts
|
|
649
657
|
var import_connect2 = require("@edge-markets/connect");
|
|
658
|
+
|
|
659
|
+
// src/iframe-manager.ts
|
|
660
|
+
var DEFAULT_TITLE = "EDGE Connect Transfer Verification";
|
|
661
|
+
var IFRAME_SANDBOX = "allow-scripts allow-same-origin allow-forms";
|
|
662
|
+
var IframeManager = class {
|
|
663
|
+
constructor() {
|
|
664
|
+
this.iframe = null;
|
|
665
|
+
this.loadingEl = null;
|
|
666
|
+
this.container = null;
|
|
667
|
+
this.callbacks = {};
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Creates and mounts the iframe into the provided container.
|
|
671
|
+
*
|
|
672
|
+
* Displays a branded loading state while the iframe loads,
|
|
673
|
+
* then swaps to the iframe once it's ready.
|
|
674
|
+
*
|
|
675
|
+
* @param config - Iframe configuration
|
|
676
|
+
* @param callbacks - Optional lifecycle callbacks
|
|
677
|
+
*/
|
|
678
|
+
mount(config, callbacks = {}) {
|
|
679
|
+
this.destroy();
|
|
680
|
+
this.container = config.container;
|
|
681
|
+
this.callbacks = callbacks;
|
|
682
|
+
this.renderLoadingState(config.container);
|
|
683
|
+
this.iframe = document.createElement("iframe");
|
|
684
|
+
this.iframe.src = config.url;
|
|
685
|
+
this.iframe.width = config.width || "100%";
|
|
686
|
+
this.iframe.height = config.height || "100%";
|
|
687
|
+
this.iframe.title = config.title || DEFAULT_TITLE;
|
|
688
|
+
this.iframe.setAttribute("sandbox", IFRAME_SANDBOX);
|
|
689
|
+
this.iframe.setAttribute("allow", "clipboard-write");
|
|
690
|
+
this.iframe.style.border = "none";
|
|
691
|
+
this.iframe.style.display = "none";
|
|
692
|
+
this.iframe.style.width = config.width || "100%";
|
|
693
|
+
this.iframe.style.height = config.height || "100%";
|
|
694
|
+
this.iframe.style.minHeight = "400px";
|
|
695
|
+
this.iframe.style.borderRadius = "8px";
|
|
696
|
+
this.iframe.addEventListener("load", () => {
|
|
697
|
+
this.hideLoadingState();
|
|
698
|
+
this.callbacks.onLoad?.();
|
|
699
|
+
});
|
|
700
|
+
this.iframe.addEventListener("error", () => {
|
|
701
|
+
this.hideLoadingState();
|
|
702
|
+
const error = new Error("Failed to load transfer verification iframe");
|
|
703
|
+
this.callbacks.onError?.(error);
|
|
704
|
+
});
|
|
705
|
+
config.container.appendChild(this.iframe);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Gets the iframe's content window for postMessage communication.
|
|
709
|
+
*
|
|
710
|
+
* @returns The iframe's contentWindow, or null if not mounted
|
|
711
|
+
*/
|
|
712
|
+
getContentWindow() {
|
|
713
|
+
return this.iframe?.contentWindow ?? null;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Checks if the iframe is currently mounted in the DOM.
|
|
717
|
+
*/
|
|
718
|
+
isMounted() {
|
|
719
|
+
return this.iframe !== null && this.iframe.isConnected;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Removes the iframe and loading state from the DOM.
|
|
723
|
+
* Cleans up all references for garbage collection.
|
|
724
|
+
*/
|
|
725
|
+
destroy() {
|
|
726
|
+
if (this.loadingEl && this.loadingEl.parentNode) {
|
|
727
|
+
this.loadingEl.parentNode.removeChild(this.loadingEl);
|
|
728
|
+
}
|
|
729
|
+
this.loadingEl = null;
|
|
730
|
+
if (this.iframe && this.iframe.parentNode) {
|
|
731
|
+
this.iframe.parentNode.removeChild(this.iframe);
|
|
732
|
+
}
|
|
733
|
+
this.iframe = null;
|
|
734
|
+
this.container = null;
|
|
735
|
+
this.callbacks = {};
|
|
736
|
+
}
|
|
737
|
+
// ===========================================================================
|
|
738
|
+
// PRIVATE METHODS
|
|
739
|
+
// ===========================================================================
|
|
740
|
+
/**
|
|
741
|
+
* Renders a branded loading state in the container.
|
|
742
|
+
*
|
|
743
|
+
* This displays immediately while the iframe loads the verification URL.
|
|
744
|
+
* Matches the visual style of the PopupManager loading state.
|
|
745
|
+
*/
|
|
746
|
+
renderLoadingState(container) {
|
|
747
|
+
this.loadingEl = document.createElement("div");
|
|
748
|
+
this.loadingEl.setAttribute("data-edge-loading", "true");
|
|
749
|
+
this.loadingEl.innerHTML = `
|
|
750
|
+
<div style="
|
|
751
|
+
display: flex;
|
|
752
|
+
align-items: center;
|
|
753
|
+
justify-content: center;
|
|
754
|
+
min-height: 400px;
|
|
755
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
|
756
|
+
border-radius: 8px;
|
|
757
|
+
color: white;
|
|
758
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
759
|
+
">
|
|
760
|
+
<div style="text-align: center; padding: 40px;">
|
|
761
|
+
<div style="
|
|
762
|
+
width: 64px;
|
|
763
|
+
height: 64px;
|
|
764
|
+
margin: 0 auto 24px;
|
|
765
|
+
background: linear-gradient(135deg, #00d4aa 0%, #00a080 100%);
|
|
766
|
+
border-radius: 16px;
|
|
767
|
+
display: flex;
|
|
768
|
+
align-items: center;
|
|
769
|
+
justify-content: center;
|
|
770
|
+
font-size: 28px;
|
|
771
|
+
font-weight: bold;
|
|
772
|
+
box-shadow: 0 8px 32px rgba(0, 212, 170, 0.3);
|
|
773
|
+
">E</div>
|
|
774
|
+
<div style="
|
|
775
|
+
width: 40px;
|
|
776
|
+
height: 40px;
|
|
777
|
+
border: 3px solid rgba(255, 255, 255, 0.2);
|
|
778
|
+
border-top-color: #00d4aa;
|
|
779
|
+
border-radius: 50%;
|
|
780
|
+
animation: edge-spin 1s linear infinite;
|
|
781
|
+
margin: 0 auto 24px;
|
|
782
|
+
"></div>
|
|
783
|
+
<h1 style="
|
|
784
|
+
font-size: 22px;
|
|
785
|
+
font-weight: 600;
|
|
786
|
+
margin-bottom: 8px;
|
|
787
|
+
letter-spacing: -0.5px;
|
|
788
|
+
">Transfer Verification</h1>
|
|
789
|
+
<p style="
|
|
790
|
+
font-size: 15px;
|
|
791
|
+
color: rgba(255, 255, 255, 0.7);
|
|
792
|
+
line-height: 1.5;
|
|
793
|
+
">Loading secure verification...</p>
|
|
794
|
+
<div style="
|
|
795
|
+
display: flex;
|
|
796
|
+
align-items: center;
|
|
797
|
+
justify-content: center;
|
|
798
|
+
gap: 6px;
|
|
799
|
+
margin-top: 32px;
|
|
800
|
+
font-size: 13px;
|
|
801
|
+
color: rgba(255, 255, 255, 0.5);
|
|
802
|
+
">
|
|
803
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
804
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
|
805
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
|
806
|
+
</svg>
|
|
807
|
+
<span>256-bit encryption</span>
|
|
808
|
+
</div>
|
|
809
|
+
</div>
|
|
810
|
+
</div>
|
|
811
|
+
<style>
|
|
812
|
+
@keyframes edge-spin {
|
|
813
|
+
to { transform: rotate(360deg); }
|
|
814
|
+
}
|
|
815
|
+
</style>
|
|
816
|
+
`;
|
|
817
|
+
container.appendChild(this.loadingEl);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Hides the loading state and shows the iframe.
|
|
821
|
+
*/
|
|
822
|
+
hideLoadingState() {
|
|
823
|
+
if (this.loadingEl && this.loadingEl.parentNode) {
|
|
824
|
+
this.loadingEl.parentNode.removeChild(this.loadingEl);
|
|
825
|
+
this.loadingEl = null;
|
|
826
|
+
}
|
|
827
|
+
if (this.iframe) {
|
|
828
|
+
this.iframe.style.display = "block";
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
// src/edge-transfer-verify.ts
|
|
834
|
+
function generateNonce() {
|
|
835
|
+
const array = new Uint8Array(32);
|
|
836
|
+
crypto.getRandomValues(array);
|
|
837
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
838
|
+
}
|
|
839
|
+
function isValidTransferVerifyMessage(data) {
|
|
840
|
+
if (!data || typeof data !== "object") return false;
|
|
841
|
+
const msg = data;
|
|
842
|
+
const validTypes = [
|
|
843
|
+
"edge:transfer-verify:success",
|
|
844
|
+
"edge:transfer-verify:error",
|
|
845
|
+
"edge:transfer-verify:cancel",
|
|
846
|
+
"edge:transfer-verify:expired",
|
|
847
|
+
"edge:transfer-verify:loaded"
|
|
848
|
+
];
|
|
849
|
+
if (typeof msg.type !== "string" || !validTypes.includes(msg.type)) {
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
if (typeof msg.sessionId !== "string" || msg.sessionId.length === 0) return false;
|
|
853
|
+
if (typeof msg.transferId !== "string" || msg.transferId.length === 0) return false;
|
|
854
|
+
if (typeof msg.nonce !== "string" || msg.nonce.length === 0) return false;
|
|
855
|
+
if (typeof msg.timestamp !== "string" || msg.timestamp.length === 0) return false;
|
|
856
|
+
if (msg.error !== void 0 && typeof msg.error !== "string") return false;
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
var EdgeTransferVerify = class {
|
|
860
|
+
/**
|
|
861
|
+
* Creates a new EdgeTransferVerify instance.
|
|
862
|
+
*
|
|
863
|
+
* @param config - Configuration options
|
|
864
|
+
* @throws Error if required config is missing or invalid
|
|
865
|
+
*/
|
|
866
|
+
constructor(config) {
|
|
867
|
+
this.popupManager = null;
|
|
868
|
+
this.iframeManager = null;
|
|
869
|
+
this.messageHandler = null;
|
|
870
|
+
this.isDestroyed = false;
|
|
871
|
+
this.isOpened = false;
|
|
872
|
+
if (!config.verificationUrl) {
|
|
873
|
+
throw new Error("EdgeTransferVerify: verificationUrl is required");
|
|
874
|
+
}
|
|
875
|
+
if (!config.sessionId) {
|
|
876
|
+
throw new Error("EdgeTransferVerify: sessionId is required");
|
|
877
|
+
}
|
|
878
|
+
const mode = config.mode || "iframe";
|
|
879
|
+
if (mode === "iframe" && !config.container) {
|
|
880
|
+
throw new Error("EdgeTransferVerify: container is required for iframe mode");
|
|
881
|
+
}
|
|
882
|
+
this.config = config;
|
|
883
|
+
this.mode = mode;
|
|
884
|
+
this.nonce = config.nonce || generateNonce();
|
|
885
|
+
this.expectedOrigin = new URL(config.verificationUrl).origin;
|
|
886
|
+
this.setupMessageListener();
|
|
887
|
+
}
|
|
888
|
+
// ===========================================================================
|
|
889
|
+
// PUBLIC API
|
|
890
|
+
// ===========================================================================
|
|
891
|
+
/**
|
|
892
|
+
* Opens the transfer verification UI.
|
|
893
|
+
*
|
|
894
|
+
* In iframe mode, this mounts the iframe into the configured container.
|
|
895
|
+
* In popup mode, this opens a popup window.
|
|
896
|
+
*
|
|
897
|
+
* **Popup mode MUST be called directly from a user click handler!**
|
|
898
|
+
* Iframe mode can be called from any context.
|
|
899
|
+
*
|
|
900
|
+
* @throws EdgePopupBlockedError if popup mode and popup is blocked
|
|
901
|
+
* @throws Error if the instance has been destroyed or is already open
|
|
902
|
+
*
|
|
903
|
+
* @example
|
|
904
|
+
* ```typescript
|
|
905
|
+
* // Iframe mode — can call anywhere
|
|
906
|
+
* verify.open()
|
|
907
|
+
*
|
|
908
|
+
* // Popup mode — must call from click handler
|
|
909
|
+
* button.onclick = () => verify.open()
|
|
910
|
+
* ```
|
|
911
|
+
*/
|
|
912
|
+
open() {
|
|
913
|
+
if (this.isDestroyed) {
|
|
914
|
+
throw new Error("EdgeTransferVerify: Cannot open - instance has been destroyed");
|
|
915
|
+
}
|
|
916
|
+
if (this.isOpened) {
|
|
917
|
+
if (this.mode === "popup" && this.popupManager?.isOpen()) {
|
|
918
|
+
this.popupManager.focus();
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
if (this.mode === "iframe" && this.iframeManager?.isMounted()) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const url = this.buildVerificationUrl();
|
|
926
|
+
if (this.mode === "popup") {
|
|
927
|
+
this.openPopup(url);
|
|
928
|
+
} else {
|
|
929
|
+
this.openIframe(url);
|
|
930
|
+
}
|
|
931
|
+
this.isOpened = true;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Closes the verification UI.
|
|
935
|
+
*
|
|
936
|
+
* In popup mode, closes the popup window.
|
|
937
|
+
* In iframe mode, removes the iframe from the container.
|
|
938
|
+
* Does not trigger any callbacks.
|
|
939
|
+
*/
|
|
940
|
+
close() {
|
|
941
|
+
if (this.mode === "popup") {
|
|
942
|
+
this.popupManager?.close();
|
|
943
|
+
this.popupManager = null;
|
|
944
|
+
} else {
|
|
945
|
+
this.iframeManager?.destroy();
|
|
946
|
+
this.iframeManager = null;
|
|
947
|
+
}
|
|
948
|
+
this.isOpened = false;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Destroys the EdgeTransferVerify instance.
|
|
952
|
+
*
|
|
953
|
+
* Closes the UI, removes the postMessage listener, and cleans up all resources.
|
|
954
|
+
* After destroy(), the instance cannot be reused.
|
|
955
|
+
*
|
|
956
|
+
* @example
|
|
957
|
+
* ```typescript
|
|
958
|
+
* // React cleanup
|
|
959
|
+
* useEffect(() => {
|
|
960
|
+
* const verify = new EdgeTransferVerify({ ... })
|
|
961
|
+
* verify.open()
|
|
962
|
+
* return () => verify.destroy()
|
|
963
|
+
* }, [])
|
|
964
|
+
* ```
|
|
965
|
+
*/
|
|
966
|
+
destroy() {
|
|
967
|
+
this.isDestroyed = true;
|
|
968
|
+
this.close();
|
|
969
|
+
this.removeMessageListener();
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Checks if the verification UI is currently displayed.
|
|
973
|
+
*/
|
|
974
|
+
isOpen() {
|
|
975
|
+
if (this.mode === "popup") {
|
|
976
|
+
return this.popupManager?.isOpen() ?? false;
|
|
977
|
+
}
|
|
978
|
+
return this.iframeManager?.isMounted() ?? false;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Returns the nonce being used for this verification session.
|
|
982
|
+
* Useful for debugging and logging.
|
|
983
|
+
*/
|
|
984
|
+
getNonce() {
|
|
985
|
+
return this.nonce;
|
|
986
|
+
}
|
|
987
|
+
// ===========================================================================
|
|
988
|
+
// PRIVATE — LAUNCH METHODS
|
|
989
|
+
// ===========================================================================
|
|
990
|
+
/**
|
|
991
|
+
* Builds the verification URL with nonce and parent origin parameters.
|
|
992
|
+
*
|
|
993
|
+
* Appends:
|
|
994
|
+
* - `nonce` — for replay protection / message correlation
|
|
995
|
+
* - `origin` — so the verification page knows where to postMessage back to
|
|
996
|
+
*/
|
|
997
|
+
buildVerificationUrl() {
|
|
998
|
+
const url = new URL(this.config.verificationUrl);
|
|
999
|
+
url.searchParams.set("nonce", this.nonce);
|
|
1000
|
+
url.searchParams.set("origin", window.location.origin);
|
|
1001
|
+
return url.toString();
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Opens the verification UI in a popup window.
|
|
1005
|
+
*
|
|
1006
|
+
* Uses PopupManager from the existing EdgeLink infrastructure.
|
|
1007
|
+
* The popup opens immediately (preserving user gesture) and navigates
|
|
1008
|
+
* to the verification URL.
|
|
1009
|
+
*
|
|
1010
|
+
* @param url - The verification URL to load
|
|
1011
|
+
* @throws EdgePopupBlockedError if the popup is blocked
|
|
1012
|
+
*/
|
|
1013
|
+
openPopup(url) {
|
|
1014
|
+
this.popupManager = new PopupManager();
|
|
1015
|
+
const win = this.popupManager.open({
|
|
1016
|
+
onUserClose: () => this.handleUserClose()
|
|
1017
|
+
});
|
|
1018
|
+
if (!win) {
|
|
1019
|
+
this.popupManager = null;
|
|
1020
|
+
this.config.onError?.({
|
|
1021
|
+
type: "edge:transfer-verify:error",
|
|
1022
|
+
sessionId: this.config.sessionId,
|
|
1023
|
+
transferId: "",
|
|
1024
|
+
nonce: this.nonce,
|
|
1025
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1026
|
+
error: "Popup was blocked by the browser. Please allow popups for this site."
|
|
1027
|
+
});
|
|
1028
|
+
throw new import_connect2.EdgePopupBlockedError();
|
|
1029
|
+
}
|
|
1030
|
+
this.popupManager.navigateTo(url);
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Opens the verification UI in an iframe.
|
|
1034
|
+
*
|
|
1035
|
+
* Uses IframeManager to create and mount the iframe into the configured container.
|
|
1036
|
+
*
|
|
1037
|
+
* @param url - The verification URL to load
|
|
1038
|
+
*/
|
|
1039
|
+
openIframe(url) {
|
|
1040
|
+
this.iframeManager = new IframeManager();
|
|
1041
|
+
this.iframeManager.mount(
|
|
1042
|
+
{
|
|
1043
|
+
url,
|
|
1044
|
+
container: this.config.container,
|
|
1045
|
+
title: "EDGE Connect Transfer Verification"
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
onError: (error) => {
|
|
1049
|
+
this.config.onError?.({
|
|
1050
|
+
type: "edge:transfer-verify:error",
|
|
1051
|
+
sessionId: this.config.sessionId,
|
|
1052
|
+
transferId: "",
|
|
1053
|
+
nonce: this.nonce,
|
|
1054
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1055
|
+
error: error.message
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
// ===========================================================================
|
|
1062
|
+
// PRIVATE — MESSAGE HANDLING
|
|
1063
|
+
// ===========================================================================
|
|
1064
|
+
/**
|
|
1065
|
+
* Sets up the postMessage listener for receiving events from the verification page.
|
|
1066
|
+
*
|
|
1067
|
+
* SECURITY: Every incoming message is validated for:
|
|
1068
|
+
* 1. Origin matches the expected EDGE verification origin
|
|
1069
|
+
* 2. Message schema matches TransferVerifyMessage
|
|
1070
|
+
* 3. Nonce matches the nonce we sent
|
|
1071
|
+
* 4. SessionId matches the session we opened
|
|
1072
|
+
*/
|
|
1073
|
+
setupMessageListener() {
|
|
1074
|
+
this.messageHandler = (event) => {
|
|
1075
|
+
if (event.origin !== this.expectedOrigin) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const data = event.data;
|
|
1079
|
+
if (!isValidTransferVerifyMessage(data)) {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
if (data.nonce !== this.nonce) {
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (data.sessionId !== this.config.sessionId) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const verifiedEvent = {
|
|
1089
|
+
type: data.type,
|
|
1090
|
+
sessionId: data.sessionId,
|
|
1091
|
+
transferId: data.transferId,
|
|
1092
|
+
nonce: data.nonce,
|
|
1093
|
+
timestamp: data.timestamp,
|
|
1094
|
+
error: data.error
|
|
1095
|
+
};
|
|
1096
|
+
this.config.onEvent?.(verifiedEvent);
|
|
1097
|
+
switch (data.type) {
|
|
1098
|
+
case "edge:transfer-verify:success":
|
|
1099
|
+
this.handleSuccess(verifiedEvent);
|
|
1100
|
+
break;
|
|
1101
|
+
case "edge:transfer-verify:error":
|
|
1102
|
+
this.handleError(verifiedEvent);
|
|
1103
|
+
break;
|
|
1104
|
+
case "edge:transfer-verify:cancel":
|
|
1105
|
+
this.handleCancel(verifiedEvent);
|
|
1106
|
+
break;
|
|
1107
|
+
case "edge:transfer-verify:expired":
|
|
1108
|
+
this.handleExpired(verifiedEvent);
|
|
1109
|
+
break;
|
|
1110
|
+
case "edge:transfer-verify:loaded":
|
|
1111
|
+
this.handleLoaded(verifiedEvent);
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
window.addEventListener("message", this.messageHandler);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Removes the postMessage listener.
|
|
1119
|
+
*/
|
|
1120
|
+
removeMessageListener() {
|
|
1121
|
+
if (this.messageHandler) {
|
|
1122
|
+
window.removeEventListener("message", this.messageHandler);
|
|
1123
|
+
this.messageHandler = null;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
// ===========================================================================
|
|
1127
|
+
// PRIVATE — EVENT HANDLERS
|
|
1128
|
+
// ===========================================================================
|
|
1129
|
+
/**
|
|
1130
|
+
* Handles a successful verification event.
|
|
1131
|
+
*
|
|
1132
|
+
* Closes the UI and notifies the consumer.
|
|
1133
|
+
*/
|
|
1134
|
+
handleSuccess(event) {
|
|
1135
|
+
this.close();
|
|
1136
|
+
this.config.onSuccess?.(event);
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Handles a verification error event.
|
|
1140
|
+
*
|
|
1141
|
+
* Does NOT close the UI — the verification page may allow retry.
|
|
1142
|
+
* The consumer can call close() or destroy() if they want to dismiss.
|
|
1143
|
+
*/
|
|
1144
|
+
handleError(event) {
|
|
1145
|
+
this.config.onError?.(event);
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Handles a cancellation event from the user.
|
|
1149
|
+
*
|
|
1150
|
+
* Closes the UI and notifies the consumer.
|
|
1151
|
+
*/
|
|
1152
|
+
handleCancel(event) {
|
|
1153
|
+
this.close();
|
|
1154
|
+
this.config.onCancel?.(event);
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Handles a session/OTP expiration event.
|
|
1158
|
+
*
|
|
1159
|
+
* Closes the UI and notifies the consumer.
|
|
1160
|
+
*/
|
|
1161
|
+
handleExpired(event) {
|
|
1162
|
+
this.close();
|
|
1163
|
+
this.config.onExpired?.(event);
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Handles the loaded event from the verification page.
|
|
1167
|
+
*
|
|
1168
|
+
* The verification UI is ready for user interaction.
|
|
1169
|
+
* If geolocation data was provided, forwards it to the verification page
|
|
1170
|
+
* via postMessage for server-side cross-referencing.
|
|
1171
|
+
*/
|
|
1172
|
+
handleLoaded(event) {
|
|
1173
|
+
this.config.onLoaded?.(event);
|
|
1174
|
+
if (this.config.geolocation) {
|
|
1175
|
+
const target = this.mode === "popup" ? this.popupManager?.getWindow?.() : this.iframeManager?.getContentWindow?.();
|
|
1176
|
+
if (target) {
|
|
1177
|
+
target.postMessage(
|
|
1178
|
+
{
|
|
1179
|
+
type: "edge:transfer-verify:geo",
|
|
1180
|
+
nonce: this.nonce,
|
|
1181
|
+
latitude: this.config.geolocation.latitude,
|
|
1182
|
+
longitude: this.config.geolocation.longitude,
|
|
1183
|
+
accuracy: this.config.geolocation.accuracy,
|
|
1184
|
+
timestamp: this.config.geolocation.timestamp
|
|
1185
|
+
},
|
|
1186
|
+
this.expectedOrigin
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Handles user closing the popup manually (popup mode only).
|
|
1193
|
+
*
|
|
1194
|
+
* Treated as a cancellation — we fire onCancel with a synthetic event.
|
|
1195
|
+
*/
|
|
1196
|
+
handleUserClose() {
|
|
1197
|
+
this.isOpened = false;
|
|
1198
|
+
this.popupManager = null;
|
|
1199
|
+
const syntheticEvent = {
|
|
1200
|
+
type: "edge:transfer-verify:cancel",
|
|
1201
|
+
sessionId: this.config.sessionId,
|
|
1202
|
+
transferId: "",
|
|
1203
|
+
nonce: this.nonce,
|
|
1204
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1205
|
+
};
|
|
1206
|
+
this.config.onEvent?.(syntheticEvent);
|
|
1207
|
+
this.config.onCancel?.(syntheticEvent);
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// src/geolocation.ts
|
|
1212
|
+
async function collectGeolocation(options) {
|
|
1213
|
+
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
try {
|
|
1217
|
+
const position = await new Promise((resolve, reject) => {
|
|
1218
|
+
navigator.geolocation.getCurrentPosition(resolve, reject, {
|
|
1219
|
+
timeout: options?.timeout ?? 1e4,
|
|
1220
|
+
maximumAge: options?.maximumAge ?? 6e4,
|
|
1221
|
+
enableHighAccuracy: options?.enableHighAccuracy ?? false
|
|
1222
|
+
});
|
|
1223
|
+
});
|
|
1224
|
+
return {
|
|
1225
|
+
latitude: position.coords.latitude,
|
|
1226
|
+
longitude: position.coords.longitude,
|
|
1227
|
+
accuracy: position.coords.accuracy,
|
|
1228
|
+
timestamp: new Date(position.timestamp).toISOString()
|
|
1229
|
+
};
|
|
1230
|
+
} catch {
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// src/index.ts
|
|
650
1236
|
var import_connect3 = require("@edge-markets/connect");
|
|
1237
|
+
var import_connect4 = require("@edge-markets/connect");
|
|
651
1238
|
// Annotate the CommonJS export names for ESM import in node:
|
|
652
1239
|
0 && (module.exports = {
|
|
653
1240
|
ALL_EDGE_SCOPES,
|
|
@@ -656,7 +1243,10 @@ var import_connect3 = require("@edge-markets/connect");
|
|
|
656
1243
|
EdgeLink,
|
|
657
1244
|
EdgePopupBlockedError,
|
|
658
1245
|
EdgeStateMismatchError,
|
|
1246
|
+
EdgeTransferVerify,
|
|
1247
|
+
IframeManager,
|
|
659
1248
|
assertCryptoAvailable,
|
|
1249
|
+
collectGeolocation,
|
|
660
1250
|
generatePKCE,
|
|
661
1251
|
generateState,
|
|
662
1252
|
isEdgeError,
|