@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.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { EdgeEnvironment, EdgeLinkSuccess, EdgeLinkExit, EdgeScope } from '@edge-markets/connect';
2
- export { ALL_EDGE_SCOPES, EDGE_SCOPES, EdgeEnvironment, EdgeError, EdgeLinkExit, EdgeLinkSuccess, EdgePopupBlockedError, EdgeScope, EdgeStateMismatchError, isEdgeError } from '@edge-markets/connect';
1
+ import { EdgeLinkConfigBase, EdgeScope, EdgeLinkSuccess, EdgeLinkExit, SdkGeolocation, PKCEPair } from '@edge-markets/connect';
2
+ export { ALL_EDGE_SCOPES, EDGE_SCOPES, EdgeEnvironment, EdgeError, EdgeLinkEvent, EdgeLinkEventName, EdgeLinkExit, EdgeLinkSuccess, EdgePopupBlockedError, EdgeScope, EdgeStateMismatchError, PKCEPair, SdkGeolocation, isEdgeError } from '@edge-markets/connect';
3
3
 
4
4
  /**
5
5
  * EdgeLink - Popup Authentication for EDGE Connect
@@ -51,99 +51,7 @@ export { ALL_EDGE_SCOPES, EDGE_SCOPES, EdgeEnvironment, EdgeError, EdgeLinkExit,
51
51
  * Only `clientId`, `environment`, and `onSuccess` are required.
52
52
  * Everything else has sensible defaults.
53
53
  */
54
- interface EdgeLinkConfig {
55
- /**
56
- * Your OAuth client ID from the EdgeBoost partner portal.
57
- * This is public and safe to include in frontend code.
58
- */
59
- clientId: string;
60
- /**
61
- * Environment to connect to.
62
- * - `'production'` - Live environment with real money
63
- * - `'staging'` - Test environment for development
64
- * - `'sandbox'` - Isolated mock environment (coming soon)
65
- */
66
- environment: EdgeEnvironment;
67
- /**
68
- * Called when user successfully authenticates and grants consent.
69
- *
70
- * Send the `code` and `codeVerifier` to your backend for token exchange.
71
- * **Never exchange tokens in the frontend** - that would expose your client secret.
72
- *
73
- * @example
74
- * ```typescript
75
- * onSuccess: async (result) => {
76
- * const response = await fetch('/api/edge/exchange', {
77
- * method: 'POST',
78
- * headers: { 'Content-Type': 'application/json' },
79
- * body: JSON.stringify({
80
- * code: result.code,
81
- * codeVerifier: result.codeVerifier,
82
- * }),
83
- * })
84
- * if (response.ok) {
85
- * showSuccess('EdgeBoost connected!')
86
- * }
87
- * }
88
- * ```
89
- */
90
- onSuccess: (result: EdgeLinkSuccess) => void;
91
- /**
92
- * Called when user exits the Link flow (closes popup, error, etc.).
93
- *
94
- * Use this to handle errors and provide user feedback.
95
- *
96
- * @example
97
- * ```typescript
98
- * onExit: (metadata) => {
99
- * switch (metadata.reason) {
100
- * case 'user_closed':
101
- * // User closed popup - maybe show "Connect later" option
102
- * break
103
- * case 'popup_blocked':
104
- * showMessage('Please allow popups and try again')
105
- * break
106
- * case 'error':
107
- * showError(metadata.error?.message || 'Something went wrong')
108
- * break
109
- * }
110
- * }
111
- * ```
112
- */
113
- onExit?: (metadata: EdgeLinkExit) => void;
114
- /**
115
- * Called for various events during the Link flow.
116
- * Useful for analytics and debugging.
117
- *
118
- * @example
119
- * ```typescript
120
- * onEvent: (event) => {
121
- * analytics.track('edge_link_event', {
122
- * eventName: event.eventName,
123
- * timestamp: event.timestamp,
124
- * })
125
- * }
126
- * ```
127
- */
128
- onEvent?: (event: EdgeLinkEvent) => void;
129
- /**
130
- * OAuth scopes to request.
131
- * @default All available scopes
132
- *
133
- * @example
134
- * ```typescript
135
- * // Request only what you need
136
- * scopes: ['user.read', 'balance.read']
137
- * ```
138
- */
139
- scopes?: EdgeScope[];
140
- /**
141
- * Custom URL for the Link page.
142
- * Only use for local development.
143
- *
144
- * @default Derived from environment config
145
- */
146
- linkUrl?: string;
54
+ interface EdgeLinkConfig extends EdgeLinkConfigBase {
147
55
  /**
148
56
  * Custom redirect URI after authentication.
149
57
  * Must be registered in your OAuth client settings.
@@ -152,21 +60,6 @@ interface EdgeLinkConfig {
152
60
  */
153
61
  redirectUri?: string;
154
62
  }
155
- /**
156
- * Event emitted during the Link flow.
157
- */
158
- interface EdgeLinkEvent {
159
- /** Name of the event */
160
- eventName: EdgeLinkEventName;
161
- /** Unix timestamp when event occurred */
162
- timestamp: number;
163
- /** Additional event-specific data */
164
- metadata?: Record<string, unknown>;
165
- }
166
- /**
167
- * Possible Link events.
168
- */
169
- type EdgeLinkEventName = 'OPEN' | 'CLOSE' | 'HANDOFF' | 'TRANSITION' | 'ERROR' | 'SUCCESS';
170
63
  /**
171
64
  * Options for opening Link.
172
65
  */
@@ -206,6 +99,7 @@ declare class EdgeLink {
206
99
  private state;
207
100
  private messageHandler;
208
101
  private isDestroyed;
102
+ private isInitializing;
209
103
  /**
210
104
  * Creates a new EdgeLink instance.
211
105
  *
@@ -317,6 +211,589 @@ interface UseEdgeLinkReturn {
317
211
  }
318
212
  declare function useEdgeLink(config: UseEdgeLinkConfig): UseEdgeLinkReturn;
319
213
 
214
+ /**
215
+ * EdgeTransferVerify - Transfer Verification Launcher for EDGE Connect
216
+ *
217
+ * EdgeTransferVerify provides a simple, reliable way to verify transfers
218
+ * using either an embedded iframe or a popup window. It mirrors the EdgeLink
219
+ * ergonomics but is purpose-built for the post-auth transfer verification flow.
220
+ *
221
+ * **Key Features:**
222
+ * - Callback-based API (onSuccess, onError, onCancel, onExpired, onLoaded, onEvent)
223
+ * - Supports both iframe (default) and popup display modes
224
+ * - Cross-origin communication via postMessage with strict origin validation
225
+ * - Nonce correlation to prevent replay attacks
226
+ * - Automatic cleanup on destroy
227
+ *
228
+ * @module @edge-markets/connect-link
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * import { EdgeTransferVerify } from '@edge-markets/connect-link'
233
+ *
234
+ * const verify = new EdgeTransferVerify({
235
+ * verificationUrl: session.verificationUrl,
236
+ * sessionId: session.sessionId,
237
+ * mode: 'iframe',
238
+ * container: document.getElementById('verify-container')!,
239
+ * onSuccess: (event) => {
240
+ * console.log(`Transfer ${event.transferId} verified!`)
241
+ * },
242
+ * onError: (event) => {
243
+ * console.error(`Verification failed: ${event.error}`)
244
+ * },
245
+ * onCancel: (event) => {
246
+ * console.log('User cancelled verification')
247
+ * },
248
+ * })
249
+ *
250
+ * // Launch the verification UI
251
+ * verify.open()
252
+ *
253
+ * // Clean up when done
254
+ * verify.destroy()
255
+ * ```
256
+ */
257
+
258
+ /**
259
+ * Event types emitted during the transfer verification flow.
260
+ *
261
+ * The verification page posts these message types back to the parent window:
262
+ * - `success` - User completed verification (OTP accepted)
263
+ * - `error` - Verification failed (invalid OTP, server error, etc.)
264
+ * - `cancel` - User cancelled the verification flow
265
+ * - `expired` - Verification session or OTP expired
266
+ * - `loaded` - Verification UI finished loading and is ready
267
+ */
268
+ type TransferVerifyEventType = 'edge:transfer-verify:success' | 'edge:transfer-verify:error' | 'edge:transfer-verify:cancel' | 'edge:transfer-verify:expired' | 'edge:transfer-verify:loaded';
269
+ /**
270
+ * Event emitted from the transfer verification UI via postMessage.
271
+ *
272
+ * All events include the sessionId, transferId, and nonce for correlation.
273
+ * Error events also include an `error` string with a description.
274
+ */
275
+ interface TransferVerifyEvent {
276
+ /** The type of verification event */
277
+ type: TransferVerifyEventType;
278
+ /** The verification session ID */
279
+ sessionId: string;
280
+ /** The transfer ID being verified */
281
+ transferId: string;
282
+ /** Nonce for replay protection — must match the value sent at launch */
283
+ nonce: string;
284
+ /** ISO 8601 timestamp of when the event was generated */
285
+ timestamp: string;
286
+ /** Error description (only present on error events) */
287
+ error?: string;
288
+ }
289
+ /**
290
+ * Display mode for the verification UI.
291
+ *
292
+ * - `'iframe'` (default) — Embeds verification inline within a container element.
293
+ * Best for desktop flows where you want the verification to appear seamlessly
294
+ * within your page layout.
295
+ *
296
+ * - `'popup'` — Opens verification in a popup window.
297
+ * Useful when you want a modal-like experience or when there's no suitable
298
+ * container element. Must be called from a user click handler.
299
+ */
300
+ type TransferVerifyMode = 'iframe' | 'popup';
301
+ /**
302
+ * Configuration for EdgeTransferVerify.
303
+ *
304
+ * `verificationUrl` and `sessionId` are required. Everything else has sensible defaults.
305
+ */
306
+ interface EdgeTransferVerifyConfig {
307
+ /**
308
+ * The URL for the verification page.
309
+ * Obtained from the `createVerificationSession` API response.
310
+ */
311
+ verificationUrl: string;
312
+ /**
313
+ * The verification session ID.
314
+ * Obtained from the `createVerificationSession` API response.
315
+ */
316
+ sessionId: string;
317
+ /**
318
+ * Optional nonce for replay protection.
319
+ * If not provided, a cryptographically random nonce is generated automatically.
320
+ * The nonce is appended to the verification URL and validated on all incoming messages.
321
+ */
322
+ nonce?: string;
323
+ /**
324
+ * Display mode for the verification UI.
325
+ * @default 'iframe'
326
+ */
327
+ mode?: TransferVerifyMode;
328
+ /**
329
+ * Container element for iframe mode.
330
+ * **Required** when `mode` is `'iframe'` (or when mode is omitted, since iframe is the default).
331
+ * The iframe will be appended as a child of this element.
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * container: document.getElementById('verify-container')!
336
+ * ```
337
+ */
338
+ container?: HTMLElement;
339
+ /**
340
+ * Called when transfer verification succeeds.
341
+ *
342
+ * The transfer has been verified — you can now poll the transfer status API
343
+ * to confirm completion on your backend.
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * onSuccess: (event) => {
348
+ * console.log(`Transfer ${event.transferId} verified at ${event.timestamp}`)
349
+ * pollTransferStatus(event.transferId)
350
+ * }
351
+ * ```
352
+ */
353
+ onSuccess?: (event: TransferVerifyEvent) => void;
354
+ /**
355
+ * Called when verification fails.
356
+ *
357
+ * Check `event.error` for the reason (invalid OTP, server error, etc.).
358
+ *
359
+ * @example
360
+ * ```typescript
361
+ * onError: (event) => {
362
+ * showError(`Verification failed: ${event.error}`)
363
+ * }
364
+ * ```
365
+ */
366
+ onError?: (event: TransferVerifyEvent) => void;
367
+ /**
368
+ * Called when the user cancels the verification flow.
369
+ *
370
+ * @example
371
+ * ```typescript
372
+ * onCancel: (event) => {
373
+ * showMessage('Verification was cancelled')
374
+ * }
375
+ * ```
376
+ */
377
+ onCancel?: (event: TransferVerifyEvent) => void;
378
+ /**
379
+ * Called when the verification session or OTP expires.
380
+ *
381
+ * You may want to create a new verification session and retry.
382
+ *
383
+ * @example
384
+ * ```typescript
385
+ * onExpired: (event) => {
386
+ * showMessage('Verification expired — please try again')
387
+ * createNewSession()
388
+ * }
389
+ * ```
390
+ */
391
+ onExpired?: (event: TransferVerifyEvent) => void;
392
+ /**
393
+ * Called when the verification UI has finished loading and is ready.
394
+ *
395
+ * Useful for hiding your own loading indicators.
396
+ *
397
+ * @example
398
+ * ```typescript
399
+ * onLoaded: () => {
400
+ * hideSpinner()
401
+ * }
402
+ * ```
403
+ */
404
+ onLoaded?: (event: TransferVerifyEvent) => void;
405
+ /**
406
+ * Called for ALL verification events.
407
+ * Useful for analytics, logging, and debugging.
408
+ *
409
+ * This fires in addition to the specific callbacks above.
410
+ *
411
+ * @example
412
+ * ```typescript
413
+ * onEvent: (event) => {
414
+ * analytics.track('transfer_verify_event', {
415
+ * type: event.type,
416
+ * sessionId: event.sessionId,
417
+ * timestamp: event.timestamp,
418
+ * })
419
+ * }
420
+ * ```
421
+ */
422
+ onEvent?: (event: TransferVerifyEvent) => void;
423
+ /**
424
+ * SDK-reported geolocation to forward to the verification page.
425
+ *
426
+ * Collect this using `collectGeolocation()` before opening the verification UI.
427
+ * The server cross-references it against IP-based geolocation for stronger
428
+ * state-level enforcement.
429
+ *
430
+ * @example
431
+ * ```typescript
432
+ * import { collectGeolocation } from '@edge-markets/connect-link'
433
+ *
434
+ * const geo = await collectGeolocation()
435
+ * const verify = new EdgeTransferVerify({
436
+ * // ...
437
+ * geolocation: geo ?? undefined,
438
+ * })
439
+ * ```
440
+ */
441
+ geolocation?: SdkGeolocation;
442
+ }
443
+ /**
444
+ * EdgeTransferVerify - Transfer verification launcher for EDGE Connect.
445
+ *
446
+ * Create one instance per verification session. Call `open()` to launch the
447
+ * verification UI. Call `destroy()` when done or when unmounting.
448
+ *
449
+ * @example
450
+ * ```typescript
451
+ * // Iframe mode (default) — embedded in your page
452
+ * const verify = new EdgeTransferVerify({
453
+ * verificationUrl: session.verificationUrl,
454
+ * sessionId: session.sessionId,
455
+ * container: document.getElementById('verify-container')!,
456
+ * onSuccess: (event) => {
457
+ * console.log(`Transfer ${event.transferId} verified!`)
458
+ * },
459
+ * onError: (event) => {
460
+ * console.error(`Verification failed: ${event.error}`)
461
+ * },
462
+ * })
463
+ *
464
+ * verify.open()
465
+ *
466
+ * // Popup mode — opens in a new window
467
+ * const verify = new EdgeTransferVerify({
468
+ * verificationUrl: session.verificationUrl,
469
+ * sessionId: session.sessionId,
470
+ * mode: 'popup',
471
+ * onSuccess: handleSuccess,
472
+ * })
473
+ *
474
+ * // Must be called from a click handler for popup mode
475
+ * button.onclick = () => verify.open()
476
+ *
477
+ * // Clean up
478
+ * verify.destroy()
479
+ * ```
480
+ */
481
+ declare class EdgeTransferVerify {
482
+ private readonly config;
483
+ private readonly nonce;
484
+ private readonly expectedOrigin;
485
+ private readonly mode;
486
+ private popupManager;
487
+ private iframeManager;
488
+ private messageHandler;
489
+ private isDestroyed;
490
+ private isOpened;
491
+ /**
492
+ * Creates a new EdgeTransferVerify instance.
493
+ *
494
+ * @param config - Configuration options
495
+ * @throws Error if required config is missing or invalid
496
+ */
497
+ constructor(config: EdgeTransferVerifyConfig);
498
+ /**
499
+ * Opens the transfer verification UI.
500
+ *
501
+ * In iframe mode, this mounts the iframe into the configured container.
502
+ * In popup mode, this opens a popup window.
503
+ *
504
+ * **Popup mode MUST be called directly from a user click handler!**
505
+ * Iframe mode can be called from any context.
506
+ *
507
+ * @throws EdgePopupBlockedError if popup mode and popup is blocked
508
+ * @throws Error if the instance has been destroyed or is already open
509
+ *
510
+ * @example
511
+ * ```typescript
512
+ * // Iframe mode — can call anywhere
513
+ * verify.open()
514
+ *
515
+ * // Popup mode — must call from click handler
516
+ * button.onclick = () => verify.open()
517
+ * ```
518
+ */
519
+ open(): void;
520
+ /**
521
+ * Closes the verification UI.
522
+ *
523
+ * In popup mode, closes the popup window.
524
+ * In iframe mode, removes the iframe from the container.
525
+ * Does not trigger any callbacks.
526
+ */
527
+ close(): void;
528
+ /**
529
+ * Destroys the EdgeTransferVerify instance.
530
+ *
531
+ * Closes the UI, removes the postMessage listener, and cleans up all resources.
532
+ * After destroy(), the instance cannot be reused.
533
+ *
534
+ * @example
535
+ * ```typescript
536
+ * // React cleanup
537
+ * useEffect(() => {
538
+ * const verify = new EdgeTransferVerify({ ... })
539
+ * verify.open()
540
+ * return () => verify.destroy()
541
+ * }, [])
542
+ * ```
543
+ */
544
+ destroy(): void;
545
+ /**
546
+ * Checks if the verification UI is currently displayed.
547
+ */
548
+ isOpen(): boolean;
549
+ /**
550
+ * Returns the nonce being used for this verification session.
551
+ * Useful for debugging and logging.
552
+ */
553
+ getNonce(): string;
554
+ /**
555
+ * Builds the verification URL with nonce and parent origin parameters.
556
+ *
557
+ * Appends:
558
+ * - `nonce` — for replay protection / message correlation
559
+ * - `origin` — so the verification page knows where to postMessage back to
560
+ */
561
+ private buildVerificationUrl;
562
+ /**
563
+ * Opens the verification UI in a popup window.
564
+ *
565
+ * Uses PopupManager from the existing EdgeLink infrastructure.
566
+ * The popup opens immediately (preserving user gesture) and navigates
567
+ * to the verification URL.
568
+ *
569
+ * @param url - The verification URL to load
570
+ * @throws EdgePopupBlockedError if the popup is blocked
571
+ */
572
+ private openPopup;
573
+ /**
574
+ * Opens the verification UI in an iframe.
575
+ *
576
+ * Uses IframeManager to create and mount the iframe into the configured container.
577
+ *
578
+ * @param url - The verification URL to load
579
+ */
580
+ private openIframe;
581
+ /**
582
+ * Sets up the postMessage listener for receiving events from the verification page.
583
+ *
584
+ * SECURITY: Every incoming message is validated for:
585
+ * 1. Origin matches the expected EDGE verification origin
586
+ * 2. Message schema matches TransferVerifyMessage
587
+ * 3. Nonce matches the nonce we sent
588
+ * 4. SessionId matches the session we opened
589
+ */
590
+ private setupMessageListener;
591
+ /**
592
+ * Removes the postMessage listener.
593
+ */
594
+ private removeMessageListener;
595
+ /**
596
+ * Handles a successful verification event.
597
+ *
598
+ * Closes the UI and notifies the consumer.
599
+ */
600
+ private handleSuccess;
601
+ /**
602
+ * Handles a verification error event.
603
+ *
604
+ * Does NOT close the UI — the verification page may allow retry.
605
+ * The consumer can call close() or destroy() if they want to dismiss.
606
+ */
607
+ private handleError;
608
+ /**
609
+ * Handles a cancellation event from the user.
610
+ *
611
+ * Closes the UI and notifies the consumer.
612
+ */
613
+ private handleCancel;
614
+ /**
615
+ * Handles a session/OTP expiration event.
616
+ *
617
+ * Closes the UI and notifies the consumer.
618
+ */
619
+ private handleExpired;
620
+ /**
621
+ * Handles the loaded event from the verification page.
622
+ *
623
+ * The verification UI is ready for user interaction.
624
+ * If geolocation data was provided, forwards it to the verification page
625
+ * via postMessage for server-side cross-referencing.
626
+ */
627
+ private handleLoaded;
628
+ /**
629
+ * Handles user closing the popup manually (popup mode only).
630
+ *
631
+ * Treated as a cancellation — we fire onCancel with a synthetic event.
632
+ */
633
+ private handleUserClose;
634
+ }
635
+
636
+ /**
637
+ * Iframe Manager
638
+ *
639
+ * Manages an embedded iframe for EDGE Connect transfer verification.
640
+ * This is the iframe counterpart to PopupManager — it creates, configures,
641
+ * and cleans up an iframe element within a provided container.
642
+ *
643
+ * **Why iframe for verification?**
644
+ * Unlike authentication (which uses a popup to avoid phishing), transfer
645
+ * verification is a post-auth flow that benefits from inline embedding:
646
+ * - No popup blockers
647
+ * - Better mobile experience
648
+ * - Feels native to the partner's UI
649
+ *
650
+ * @module @edge-markets/connect-link/iframe-manager
651
+ */
652
+ /**
653
+ * Configuration for the iframe element.
654
+ */
655
+ interface IframeConfig {
656
+ /** URL to load in the iframe */
657
+ url: string;
658
+ /** Container element to append the iframe to */
659
+ container: HTMLElement;
660
+ /** Width of the iframe (default: '100%') */
661
+ width?: string;
662
+ /** Height of the iframe (default: '100%') */
663
+ height?: string;
664
+ /** Title for accessibility (screen readers) */
665
+ title?: string;
666
+ }
667
+ /**
668
+ * Callbacks for iframe lifecycle events.
669
+ */
670
+ interface IframeCallbacks {
671
+ /** Called when the iframe finishes loading */
672
+ onLoad?: () => void;
673
+ /** Called if the iframe fails to load */
674
+ onError?: (error: Error) => void;
675
+ }
676
+ /**
677
+ * Manages the EDGE Connect transfer verification iframe.
678
+ *
679
+ * Handles:
680
+ * - Creating and configuring the iframe element
681
+ * - Rendering a loading state while the iframe loads
682
+ * - Clean removal of DOM elements on destroy
683
+ * - Sandbox permissions for security
684
+ *
685
+ * @example
686
+ * ```typescript
687
+ * const manager = new IframeManager()
688
+ *
689
+ * manager.mount({
690
+ * url: verificationUrl,
691
+ * container: document.getElementById('verify-container')!,
692
+ * }, {
693
+ * onLoad: () => console.log('Verification UI ready'),
694
+ * onError: (err) => console.error('Failed to load:', err),
695
+ * })
696
+ *
697
+ * // Later, clean up
698
+ * manager.destroy()
699
+ * ```
700
+ */
701
+ declare class IframeManager {
702
+ private iframe;
703
+ private loadingEl;
704
+ private container;
705
+ private callbacks;
706
+ /**
707
+ * Creates and mounts the iframe into the provided container.
708
+ *
709
+ * Displays a branded loading state while the iframe loads,
710
+ * then swaps to the iframe once it's ready.
711
+ *
712
+ * @param config - Iframe configuration
713
+ * @param callbacks - Optional lifecycle callbacks
714
+ */
715
+ mount(config: IframeConfig, callbacks?: IframeCallbacks): void;
716
+ /**
717
+ * Gets the iframe's content window for postMessage communication.
718
+ *
719
+ * @returns The iframe's contentWindow, or null if not mounted
720
+ */
721
+ getContentWindow(): WindowProxy | null;
722
+ /**
723
+ * Checks if the iframe is currently mounted in the DOM.
724
+ */
725
+ isMounted(): boolean;
726
+ /**
727
+ * Removes the iframe and loading state from the DOM.
728
+ * Cleans up all references for garbage collection.
729
+ */
730
+ destroy(): void;
731
+ /**
732
+ * Renders a branded loading state in the container.
733
+ *
734
+ * This displays immediately while the iframe loads the verification URL.
735
+ * Matches the visual style of the PopupManager loading state.
736
+ */
737
+ private renderLoadingState;
738
+ /**
739
+ * Hides the loading state and shows the iframe.
740
+ */
741
+ private hideLoadingState;
742
+ }
743
+
744
+ /**
745
+ * Browser Geolocation Collector for EDGE Connect
746
+ *
747
+ * Collects the user's geolocation from the Browser Geolocation API.
748
+ * The result can be passed to {@link EdgeTransferVerify} for cross-referencing
749
+ * against server-side IP-based geolocation.
750
+ *
751
+ * This function **never throws** — it returns `null` on permission denial,
752
+ * timeout, or API unavailability.
753
+ *
754
+ * @module @edge-markets/connect-link
755
+ */
756
+
757
+ /**
758
+ * Options for geolocation collection.
759
+ */
760
+ interface GeolocationOptions {
761
+ /** Timeout in milliseconds (default: 10000) */
762
+ timeout?: number;
763
+ /** Maximum age of a cached position in milliseconds (default: 60000) */
764
+ maximumAge?: number;
765
+ /** Whether to request high-accuracy positioning (default: false) */
766
+ enableHighAccuracy?: boolean;
767
+ }
768
+ /**
769
+ * Collects the user's geolocation from the Browser Geolocation API.
770
+ *
771
+ * Returns `null` silently if:
772
+ * - The Geolocation API is not available
773
+ * - The user denies permission
774
+ * - The request times out
775
+ * - Any other error occurs
776
+ *
777
+ * @param options - Optional configuration for the geolocation request
778
+ * @returns The user's geolocation, or `null` if unavailable
779
+ *
780
+ * @example
781
+ * ```typescript
782
+ * import { collectGeolocation, EdgeTransferVerify } from '@edge-markets/connect-link'
783
+ *
784
+ * const geo = await collectGeolocation()
785
+ *
786
+ * const verify = new EdgeTransferVerify({
787
+ * verificationUrl: session.verificationUrl,
788
+ * sessionId: session.sessionId,
789
+ * container: document.getElementById('verify-container')!,
790
+ * geolocation: geo ?? undefined,
791
+ * onSuccess: (event) => console.log('Verified!'),
792
+ * })
793
+ * ```
794
+ */
795
+ declare function collectGeolocation(options?: GeolocationOptions): Promise<SdkGeolocation | null>;
796
+
320
797
  /**
321
798
  * PKCE (Proof Key for Code Exchange) Utilities
322
799
  *
@@ -335,24 +812,7 @@ declare function useEdgeLink(config: UseEdgeLinkConfig): UseEdgeLinkReturn;
335
812
  *
336
813
  * @module @edge-markets/connect-link/pkce
337
814
  */
338
- /**
339
- * A PKCE code verifier and challenge pair.
340
- *
341
- * Keep the `verifier` secret until token exchange.
342
- * Send the `challenge` in the authorization URL.
343
- */
344
- interface PKCEPair {
345
- /**
346
- * High-entropy random string (43-128 characters).
347
- * Keep this secret - only send during token exchange.
348
- */
349
- verifier: string;
350
- /**
351
- * SHA-256 hash of verifier, base64url encoded.
352
- * This is public - include in authorization URL.
353
- */
354
- challenge: string;
355
- }
815
+
356
816
  /**
357
817
  * Generates a PKCE code verifier and challenge pair.
358
818
  *
@@ -418,4 +878,4 @@ declare function generateState(): string;
418
878
  */
419
879
  declare function assertCryptoAvailable(): void;
420
880
 
421
- export { EdgeLink, type EdgeLinkConfig, type EdgeLinkEvent, type EdgeLinkEventName, type EdgeLinkOpenOptions, type PKCEPair, type UseEdgeLinkConfig, type UseEdgeLinkReturn, assertCryptoAvailable, generatePKCE, generateState, useEdgeLink };
881
+ export { EdgeLink, type EdgeLinkConfig, type EdgeLinkOpenOptions, EdgeTransferVerify, type EdgeTransferVerifyConfig, type GeolocationOptions, type IframeCallbacks, type IframeConfig, IframeManager, type TransferVerifyEvent, type TransferVerifyEventType, type TransferVerifyMode, type UseEdgeLinkConfig, type UseEdgeLinkReturn, assertCryptoAvailable, collectGeolocation, generatePKCE, generateState, useEdgeLink };