@edge-markets/connect-link 1.3.0 → 1.5.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/README.md CHANGED
@@ -94,11 +94,19 @@ interface EdgeLinkConfig {
94
94
  onEvent?: (event) => void // Called for analytics events
95
95
  scopes?: EdgeScope[] // Scopes to request (default: all)
96
96
  linkUrl?: string // Custom Link URL (dev only)
97
- redirectUri?: string // Custom redirect URI (default: window.location.origin + '/oauth/edge/callback')
97
+ redirectUri?: string // Legacy popup callback URI (migration only)
98
98
  }
99
99
  ```
100
100
 
101
- The `redirectUri` is automatically set to `${window.location.origin}/oauth/edge/callback` if not provided.
101
+ Popup integrations are origin-based. Register your frontend origin with EDGE
102
+ for `postMessage` validation, for example:
103
+
104
+ - `https://app.partner.com`
105
+ - `http://localhost:3000`
106
+ - `http://localhost:5173`
107
+
108
+ `redirectUri` is no longer required for popup completion. Keep it only while
109
+ migrating an older integration that still sends a registered callback URL.
102
110
 
103
111
  ## Callbacks
104
112
 
@@ -323,4 +331,3 @@ MIT
323
331
 
324
332
 
325
333
 
326
-
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { EdgeLinkConfigBase, EdgeScope, EdgeLinkSuccess, EdgeLinkExit, PKCEPair } from '@edge-markets/connect';
2
- export { ALL_EDGE_SCOPES, EDGE_SCOPES, EdgeEnvironment, EdgeError, EdgeLinkEvent, EdgeLinkEventName, EdgeLinkExit, EdgeLinkSuccess, EdgePopupBlockedError, EdgeScope, EdgeStateMismatchError, PKCEPair, 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
@@ -53,10 +53,11 @@ export { ALL_EDGE_SCOPES, EDGE_SCOPES, EdgeEnvironment, EdgeError, EdgeLinkEvent
53
53
  */
54
54
  interface EdgeLinkConfig extends EdgeLinkConfigBase {
55
55
  /**
56
- * Custom redirect URI after authentication.
57
- * Must be registered in your OAuth client settings.
56
+ * Legacy redirect URI to include in the popup request.
58
57
  *
59
- * @default `${window.location.origin}/oauth/edge/callback`
58
+ * Popup completion uses `postMessage` to your frontend origin and does not
59
+ * require a callback path by default. Keep this only while migrating older
60
+ * integrations that still expect a registered callback URL.
60
61
  */
61
62
  redirectUri?: string;
62
63
  }
@@ -99,6 +100,7 @@ declare class EdgeLink {
99
100
  private state;
100
101
  private messageHandler;
101
102
  private isDestroyed;
103
+ private isInitializing;
102
104
  /**
103
105
  * Creates a new EdgeLink instance.
104
106
  *
@@ -253,6 +255,7 @@ declare function useEdgeLink(config: UseEdgeLinkConfig): UseEdgeLinkReturn;
253
255
  * verify.destroy()
254
256
  * ```
255
257
  */
258
+
256
259
  /**
257
260
  * Event types emitted during the transfer verification flow.
258
261
  *
@@ -418,6 +421,25 @@ interface EdgeTransferVerifyConfig {
418
421
  * ```
419
422
  */
420
423
  onEvent?: (event: TransferVerifyEvent) => void;
424
+ /**
425
+ * SDK-reported geolocation to forward to the verification page.
426
+ *
427
+ * Collect this using `collectGeolocation()` before opening the verification UI.
428
+ * The server cross-references it against IP-based geolocation for stronger
429
+ * state-level enforcement.
430
+ *
431
+ * @example
432
+ * ```typescript
433
+ * import { collectGeolocation } from '@edge-markets/connect-link'
434
+ *
435
+ * const geo = await collectGeolocation()
436
+ * const verify = new EdgeTransferVerify({
437
+ * // ...
438
+ * geolocation: geo ?? undefined,
439
+ * })
440
+ * ```
441
+ */
442
+ geolocation?: SdkGeolocation;
421
443
  }
422
444
  /**
423
445
  * EdgeTransferVerify - Transfer verification launcher for EDGE Connect.
@@ -600,6 +622,8 @@ declare class EdgeTransferVerify {
600
622
  * Handles the loaded event from the verification page.
601
623
  *
602
624
  * The verification UI is ready for user interaction.
625
+ * If geolocation data was provided, forwards it to the verification page
626
+ * via postMessage for server-side cross-referencing.
603
627
  */
604
628
  private handleLoaded;
605
629
  /**
@@ -718,6 +742,59 @@ declare class IframeManager {
718
742
  private hideLoadingState;
719
743
  }
720
744
 
745
+ /**
746
+ * Browser Geolocation Collector for EDGE Connect
747
+ *
748
+ * Collects the user's geolocation from the Browser Geolocation API.
749
+ * The result can be passed to {@link EdgeTransferVerify} for cross-referencing
750
+ * against server-side IP-based geolocation.
751
+ *
752
+ * This function **never throws** — it returns `null` on permission denial,
753
+ * timeout, or API unavailability.
754
+ *
755
+ * @module @edge-markets/connect-link
756
+ */
757
+
758
+ /**
759
+ * Options for geolocation collection.
760
+ */
761
+ interface GeolocationOptions {
762
+ /** Timeout in milliseconds (default: 10000) */
763
+ timeout?: number;
764
+ /** Maximum age of a cached position in milliseconds (default: 60000) */
765
+ maximumAge?: number;
766
+ /** Whether to request high-accuracy positioning (default: false) */
767
+ enableHighAccuracy?: boolean;
768
+ }
769
+ /**
770
+ * Collects the user's geolocation from the Browser Geolocation API.
771
+ *
772
+ * Returns `null` silently if:
773
+ * - The Geolocation API is not available
774
+ * - The user denies permission
775
+ * - The request times out
776
+ * - Any other error occurs
777
+ *
778
+ * @param options - Optional configuration for the geolocation request
779
+ * @returns The user's geolocation, or `null` if unavailable
780
+ *
781
+ * @example
782
+ * ```typescript
783
+ * import { collectGeolocation, EdgeTransferVerify } from '@edge-markets/connect-link'
784
+ *
785
+ * const geo = await collectGeolocation()
786
+ *
787
+ * const verify = new EdgeTransferVerify({
788
+ * verificationUrl: session.verificationUrl,
789
+ * sessionId: session.sessionId,
790
+ * container: document.getElementById('verify-container')!,
791
+ * geolocation: geo ?? undefined,
792
+ * onSuccess: (event) => console.log('Verified!'),
793
+ * })
794
+ * ```
795
+ */
796
+ declare function collectGeolocation(options?: GeolocationOptions): Promise<SdkGeolocation | null>;
797
+
721
798
  /**
722
799
  * PKCE (Proof Key for Code Exchange) Utilities
723
800
  *
@@ -802,4 +879,4 @@ declare function generateState(): string;
802
879
  */
803
880
  declare function assertCryptoAvailable(): void;
804
881
 
805
- export { EdgeLink, type EdgeLinkConfig, type EdgeLinkOpenOptions, EdgeTransferVerify, type EdgeTransferVerifyConfig, type IframeCallbacks, type IframeConfig, IframeManager, type TransferVerifyEvent, type TransferVerifyEventType, type TransferVerifyMode, type UseEdgeLinkConfig, type UseEdgeLinkReturn, assertCryptoAvailable, generatePKCE, generateState, useEdgeLink };
882
+ 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 };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { EdgeLinkConfigBase, EdgeScope, EdgeLinkSuccess, EdgeLinkExit, PKCEPair } from '@edge-markets/connect';
2
- export { ALL_EDGE_SCOPES, EDGE_SCOPES, EdgeEnvironment, EdgeError, EdgeLinkEvent, EdgeLinkEventName, EdgeLinkExit, EdgeLinkSuccess, EdgePopupBlockedError, EdgeScope, EdgeStateMismatchError, PKCEPair, 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
@@ -53,10 +53,11 @@ export { ALL_EDGE_SCOPES, EDGE_SCOPES, EdgeEnvironment, EdgeError, EdgeLinkEvent
53
53
  */
54
54
  interface EdgeLinkConfig extends EdgeLinkConfigBase {
55
55
  /**
56
- * Custom redirect URI after authentication.
57
- * Must be registered in your OAuth client settings.
56
+ * Legacy redirect URI to include in the popup request.
58
57
  *
59
- * @default `${window.location.origin}/oauth/edge/callback`
58
+ * Popup completion uses `postMessage` to your frontend origin and does not
59
+ * require a callback path by default. Keep this only while migrating older
60
+ * integrations that still expect a registered callback URL.
60
61
  */
61
62
  redirectUri?: string;
62
63
  }
@@ -99,6 +100,7 @@ declare class EdgeLink {
99
100
  private state;
100
101
  private messageHandler;
101
102
  private isDestroyed;
103
+ private isInitializing;
102
104
  /**
103
105
  * Creates a new EdgeLink instance.
104
106
  *
@@ -253,6 +255,7 @@ declare function useEdgeLink(config: UseEdgeLinkConfig): UseEdgeLinkReturn;
253
255
  * verify.destroy()
254
256
  * ```
255
257
  */
258
+
256
259
  /**
257
260
  * Event types emitted during the transfer verification flow.
258
261
  *
@@ -418,6 +421,25 @@ interface EdgeTransferVerifyConfig {
418
421
  * ```
419
422
  */
420
423
  onEvent?: (event: TransferVerifyEvent) => void;
424
+ /**
425
+ * SDK-reported geolocation to forward to the verification page.
426
+ *
427
+ * Collect this using `collectGeolocation()` before opening the verification UI.
428
+ * The server cross-references it against IP-based geolocation for stronger
429
+ * state-level enforcement.
430
+ *
431
+ * @example
432
+ * ```typescript
433
+ * import { collectGeolocation } from '@edge-markets/connect-link'
434
+ *
435
+ * const geo = await collectGeolocation()
436
+ * const verify = new EdgeTransferVerify({
437
+ * // ...
438
+ * geolocation: geo ?? undefined,
439
+ * })
440
+ * ```
441
+ */
442
+ geolocation?: SdkGeolocation;
421
443
  }
422
444
  /**
423
445
  * EdgeTransferVerify - Transfer verification launcher for EDGE Connect.
@@ -600,6 +622,8 @@ declare class EdgeTransferVerify {
600
622
  * Handles the loaded event from the verification page.
601
623
  *
602
624
  * The verification UI is ready for user interaction.
625
+ * If geolocation data was provided, forwards it to the verification page
626
+ * via postMessage for server-side cross-referencing.
603
627
  */
604
628
  private handleLoaded;
605
629
  /**
@@ -718,6 +742,59 @@ declare class IframeManager {
718
742
  private hideLoadingState;
719
743
  }
720
744
 
745
+ /**
746
+ * Browser Geolocation Collector for EDGE Connect
747
+ *
748
+ * Collects the user's geolocation from the Browser Geolocation API.
749
+ * The result can be passed to {@link EdgeTransferVerify} for cross-referencing
750
+ * against server-side IP-based geolocation.
751
+ *
752
+ * This function **never throws** — it returns `null` on permission denial,
753
+ * timeout, or API unavailability.
754
+ *
755
+ * @module @edge-markets/connect-link
756
+ */
757
+
758
+ /**
759
+ * Options for geolocation collection.
760
+ */
761
+ interface GeolocationOptions {
762
+ /** Timeout in milliseconds (default: 10000) */
763
+ timeout?: number;
764
+ /** Maximum age of a cached position in milliseconds (default: 60000) */
765
+ maximumAge?: number;
766
+ /** Whether to request high-accuracy positioning (default: false) */
767
+ enableHighAccuracy?: boolean;
768
+ }
769
+ /**
770
+ * Collects the user's geolocation from the Browser Geolocation API.
771
+ *
772
+ * Returns `null` silently if:
773
+ * - The Geolocation API is not available
774
+ * - The user denies permission
775
+ * - The request times out
776
+ * - Any other error occurs
777
+ *
778
+ * @param options - Optional configuration for the geolocation request
779
+ * @returns The user's geolocation, or `null` if unavailable
780
+ *
781
+ * @example
782
+ * ```typescript
783
+ * import { collectGeolocation, EdgeTransferVerify } from '@edge-markets/connect-link'
784
+ *
785
+ * const geo = await collectGeolocation()
786
+ *
787
+ * const verify = new EdgeTransferVerify({
788
+ * verificationUrl: session.verificationUrl,
789
+ * sessionId: session.sessionId,
790
+ * container: document.getElementById('verify-container')!,
791
+ * geolocation: geo ?? undefined,
792
+ * onSuccess: (event) => console.log('Verified!'),
793
+ * })
794
+ * ```
795
+ */
796
+ declare function collectGeolocation(options?: GeolocationOptions): Promise<SdkGeolocation | null>;
797
+
721
798
  /**
722
799
  * PKCE (Proof Key for Code Exchange) Utilities
723
800
  *
@@ -802,4 +879,4 @@ declare function generateState(): string;
802
879
  */
803
880
  declare function assertCryptoAvailable(): void;
804
881
 
805
- export { EdgeLink, type EdgeLinkConfig, type EdgeLinkOpenOptions, EdgeTransferVerify, type EdgeTransferVerifyConfig, type IframeCallbacks, type IframeConfig, IframeManager, type TransferVerifyEvent, type TransferVerifyEventType, type TransferVerifyMode, type UseEdgeLinkConfig, type UseEdgeLinkReturn, assertCryptoAvailable, generatePKCE, generateState, useEdgeLink };
882
+ 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 };
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ __export(index_exports, {
29
29
  EdgeTransferVerify: () => EdgeTransferVerify,
30
30
  IframeManager: () => IframeManager,
31
31
  assertCryptoAvailable: () => assertCryptoAvailable,
32
+ collectGeolocation: () => collectGeolocation,
32
33
  generatePKCE: () => generatePKCE,
33
34
  generateState: () => generateState,
34
35
  isEdgeError: () => import_connect3.isEdgeError,
@@ -320,6 +321,16 @@ var PopupManager = class {
320
321
  };
321
322
 
322
323
  // src/edge-link.ts
324
+ var hasWarnedLegacyRedirectUri = false;
325
+ function warnLegacyRedirectUriUsage(redirectUri) {
326
+ if (hasWarnedLegacyRedirectUri) {
327
+ return;
328
+ }
329
+ hasWarnedLegacyRedirectUri = true;
330
+ console.warn(
331
+ `EdgeLink: popup flows no longer require redirectUri. Received legacy redirectUri "${redirectUri}". Register your frontend origin with EDGE and remove this option when possible.`
332
+ );
333
+ }
323
334
  var EdgeLink = class {
324
335
  /**
325
336
  * Creates a new EdgeLink instance.
@@ -332,6 +343,7 @@ var EdgeLink = class {
332
343
  this.state = null;
333
344
  this.messageHandler = null;
334
345
  this.isDestroyed = false;
346
+ this.isInitializing = false;
335
347
  if (!config.clientId) {
336
348
  throw new Error("EdgeLink: clientId is required");
337
349
  }
@@ -376,7 +388,7 @@ var EdgeLink = class {
376
388
  if (this.isDestroyed) {
377
389
  throw new Error("EdgeLink: Cannot open - instance has been destroyed");
378
390
  }
379
- if (this.popup.isOpen()) {
391
+ if (this.popup.isOpen() || this.isInitializing) {
380
392
  this.popup.focus();
381
393
  return;
382
394
  }
@@ -439,6 +451,7 @@ var EdgeLink = class {
439
451
  * Initializes PKCE and navigates popup to auth URL.
440
452
  */
441
453
  async initializeAuth(scopes) {
454
+ this.isInitializing = true;
442
455
  try {
443
456
  this.pkce = await generatePKCE();
444
457
  this.state = generateState();
@@ -455,6 +468,8 @@ var EdgeLink = class {
455
468
  message: error instanceof Error ? error.message : "Failed to initialize"
456
469
  }
457
470
  });
471
+ } finally {
472
+ this.isInitializing = false;
458
473
  }
459
474
  }
460
475
  /**
@@ -476,9 +491,12 @@ var EdgeLink = class {
476
491
  url.searchParams.set("code_challenge_method", "S256");
477
492
  const formattedScopes = (0, import_connect.formatScopesForEnvironment)(scopes, this.config.environment);
478
493
  url.searchParams.set("scope", formattedScopes.join(" "));
479
- const redirectUri = this.config.redirectUri || `${window.location.origin}/oauth/edge/callback`;
480
- url.searchParams.set("redirect_uri", redirectUri);
494
+ url.searchParams.set("flow", "popup");
481
495
  url.searchParams.set("origin", window.location.origin);
496
+ if (this.config.redirectUri) {
497
+ url.searchParams.set("redirect_uri", this.config.redirectUri);
498
+ warnLegacyRedirectUriUsage(this.config.redirectUri);
499
+ }
482
500
  return url.toString();
483
501
  }
484
502
  /**
@@ -486,6 +504,7 @@ var EdgeLink = class {
486
504
  */
487
505
  setupMessageListener() {
488
506
  this.messageHandler = (event) => {
507
+ if (this.isDestroyed) return;
489
508
  if (event.origin !== this.expectedOrigin) {
490
509
  return;
491
510
  }
@@ -530,7 +549,7 @@ var EdgeLink = class {
530
549
  }
531
550
  });
532
551
  this.close();
533
- throw new import_connect.EdgeStateMismatchError();
552
+ return;
534
553
  }
535
554
  if (!this.pkce?.verifier) {
536
555
  this.config.onExit?.({
@@ -1160,9 +1179,27 @@ var EdgeTransferVerify = class {
1160
1179
  * Handles the loaded event from the verification page.
1161
1180
  *
1162
1181
  * The verification UI is ready for user interaction.
1182
+ * If geolocation data was provided, forwards it to the verification page
1183
+ * via postMessage for server-side cross-referencing.
1163
1184
  */
1164
1185
  handleLoaded(event) {
1165
1186
  this.config.onLoaded?.(event);
1187
+ if (this.config.geolocation) {
1188
+ const target = this.mode === "popup" ? this.popupManager?.getWindow?.() : this.iframeManager?.getContentWindow?.();
1189
+ if (target) {
1190
+ target.postMessage(
1191
+ {
1192
+ type: "edge:transfer-verify:geo",
1193
+ nonce: this.nonce,
1194
+ latitude: this.config.geolocation.latitude,
1195
+ longitude: this.config.geolocation.longitude,
1196
+ accuracy: this.config.geolocation.accuracy,
1197
+ timestamp: this.config.geolocation.timestamp
1198
+ },
1199
+ this.expectedOrigin
1200
+ );
1201
+ }
1202
+ }
1166
1203
  }
1167
1204
  /**
1168
1205
  * Handles user closing the popup manually (popup mode only).
@@ -1184,6 +1221,30 @@ var EdgeTransferVerify = class {
1184
1221
  }
1185
1222
  };
1186
1223
 
1224
+ // src/geolocation.ts
1225
+ async function collectGeolocation(options) {
1226
+ if (typeof navigator === "undefined" || !navigator.geolocation) {
1227
+ return null;
1228
+ }
1229
+ try {
1230
+ const position = await new Promise((resolve, reject) => {
1231
+ navigator.geolocation.getCurrentPosition(resolve, reject, {
1232
+ timeout: options?.timeout ?? 1e4,
1233
+ maximumAge: options?.maximumAge ?? 6e4,
1234
+ enableHighAccuracy: options?.enableHighAccuracy ?? false
1235
+ });
1236
+ });
1237
+ return {
1238
+ latitude: position.coords.latitude,
1239
+ longitude: position.coords.longitude,
1240
+ accuracy: position.coords.accuracy,
1241
+ timestamp: new Date(position.timestamp).toISOString()
1242
+ };
1243
+ } catch {
1244
+ return null;
1245
+ }
1246
+ }
1247
+
1187
1248
  // src/index.ts
1188
1249
  var import_connect3 = require("@edge-markets/connect");
1189
1250
  var import_connect4 = require("@edge-markets/connect");
@@ -1198,6 +1259,7 @@ var import_connect4 = require("@edge-markets/connect");
1198
1259
  EdgeTransferVerify,
1199
1260
  IframeManager,
1200
1261
  assertCryptoAvailable,
1262
+ collectGeolocation,
1201
1263
  generatePKCE,
1202
1264
  generateState,
1203
1265
  isEdgeError,
package/dist/index.mjs CHANGED
@@ -1,10 +1,9 @@
1
1
  // src/edge-link.ts
2
2
  import {
3
- getEnvironmentConfig,
4
3
  ALL_EDGE_SCOPES,
5
- formatScopesForEnvironment,
6
4
  EdgePopupBlockedError,
7
- EdgeStateMismatchError
5
+ formatScopesForEnvironment,
6
+ getEnvironmentConfig
8
7
  } from "@edge-markets/connect";
9
8
 
10
9
  // src/pkce.ts
@@ -288,6 +287,16 @@ var PopupManager = class {
288
287
  };
289
288
 
290
289
  // src/edge-link.ts
290
+ var hasWarnedLegacyRedirectUri = false;
291
+ function warnLegacyRedirectUriUsage(redirectUri) {
292
+ if (hasWarnedLegacyRedirectUri) {
293
+ return;
294
+ }
295
+ hasWarnedLegacyRedirectUri = true;
296
+ console.warn(
297
+ `EdgeLink: popup flows no longer require redirectUri. Received legacy redirectUri "${redirectUri}". Register your frontend origin with EDGE and remove this option when possible.`
298
+ );
299
+ }
291
300
  var EdgeLink = class {
292
301
  /**
293
302
  * Creates a new EdgeLink instance.
@@ -300,6 +309,7 @@ var EdgeLink = class {
300
309
  this.state = null;
301
310
  this.messageHandler = null;
302
311
  this.isDestroyed = false;
312
+ this.isInitializing = false;
303
313
  if (!config.clientId) {
304
314
  throw new Error("EdgeLink: clientId is required");
305
315
  }
@@ -344,7 +354,7 @@ var EdgeLink = class {
344
354
  if (this.isDestroyed) {
345
355
  throw new Error("EdgeLink: Cannot open - instance has been destroyed");
346
356
  }
347
- if (this.popup.isOpen()) {
357
+ if (this.popup.isOpen() || this.isInitializing) {
348
358
  this.popup.focus();
349
359
  return;
350
360
  }
@@ -407,6 +417,7 @@ var EdgeLink = class {
407
417
  * Initializes PKCE and navigates popup to auth URL.
408
418
  */
409
419
  async initializeAuth(scopes) {
420
+ this.isInitializing = true;
410
421
  try {
411
422
  this.pkce = await generatePKCE();
412
423
  this.state = generateState();
@@ -423,6 +434,8 @@ var EdgeLink = class {
423
434
  message: error instanceof Error ? error.message : "Failed to initialize"
424
435
  }
425
436
  });
437
+ } finally {
438
+ this.isInitializing = false;
426
439
  }
427
440
  }
428
441
  /**
@@ -444,9 +457,12 @@ var EdgeLink = class {
444
457
  url.searchParams.set("code_challenge_method", "S256");
445
458
  const formattedScopes = formatScopesForEnvironment(scopes, this.config.environment);
446
459
  url.searchParams.set("scope", formattedScopes.join(" "));
447
- const redirectUri = this.config.redirectUri || `${window.location.origin}/oauth/edge/callback`;
448
- url.searchParams.set("redirect_uri", redirectUri);
460
+ url.searchParams.set("flow", "popup");
449
461
  url.searchParams.set("origin", window.location.origin);
462
+ if (this.config.redirectUri) {
463
+ url.searchParams.set("redirect_uri", this.config.redirectUri);
464
+ warnLegacyRedirectUriUsage(this.config.redirectUri);
465
+ }
450
466
  return url.toString();
451
467
  }
452
468
  /**
@@ -454,6 +470,7 @@ var EdgeLink = class {
454
470
  */
455
471
  setupMessageListener() {
456
472
  this.messageHandler = (event) => {
473
+ if (this.isDestroyed) return;
457
474
  if (event.origin !== this.expectedOrigin) {
458
475
  return;
459
476
  }
@@ -498,7 +515,7 @@ var EdgeLink = class {
498
515
  }
499
516
  });
500
517
  this.close();
501
- throw new EdgeStateMismatchError();
518
+ return;
502
519
  }
503
520
  if (!this.pkce?.verifier) {
504
521
  this.config.onExit?.({
@@ -616,9 +633,7 @@ function useEdgeLink(config) {
616
633
  }
617
634
 
618
635
  // src/edge-transfer-verify.ts
619
- import {
620
- EdgePopupBlockedError as EdgePopupBlockedError2
621
- } from "@edge-markets/connect";
636
+ import { EdgePopupBlockedError as EdgePopupBlockedError2 } from "@edge-markets/connect";
622
637
 
623
638
  // src/iframe-manager.ts
624
639
  var DEFAULT_TITLE = "EDGE Connect Transfer Verification";
@@ -1130,9 +1145,27 @@ var EdgeTransferVerify = class {
1130
1145
  * Handles the loaded event from the verification page.
1131
1146
  *
1132
1147
  * The verification UI is ready for user interaction.
1148
+ * If geolocation data was provided, forwards it to the verification page
1149
+ * via postMessage for server-side cross-referencing.
1133
1150
  */
1134
1151
  handleLoaded(event) {
1135
1152
  this.config.onLoaded?.(event);
1153
+ if (this.config.geolocation) {
1154
+ const target = this.mode === "popup" ? this.popupManager?.getWindow?.() : this.iframeManager?.getContentWindow?.();
1155
+ if (target) {
1156
+ target.postMessage(
1157
+ {
1158
+ type: "edge:transfer-verify:geo",
1159
+ nonce: this.nonce,
1160
+ latitude: this.config.geolocation.latitude,
1161
+ longitude: this.config.geolocation.longitude,
1162
+ accuracy: this.config.geolocation.accuracy,
1163
+ timestamp: this.config.geolocation.timestamp
1164
+ },
1165
+ this.expectedOrigin
1166
+ );
1167
+ }
1168
+ }
1136
1169
  }
1137
1170
  /**
1138
1171
  * Handles user closing the popup manually (popup mode only).
@@ -1154,27 +1187,44 @@ var EdgeTransferVerify = class {
1154
1187
  }
1155
1188
  };
1156
1189
 
1190
+ // src/geolocation.ts
1191
+ async function collectGeolocation(options) {
1192
+ if (typeof navigator === "undefined" || !navigator.geolocation) {
1193
+ return null;
1194
+ }
1195
+ try {
1196
+ const position = await new Promise((resolve, reject) => {
1197
+ navigator.geolocation.getCurrentPosition(resolve, reject, {
1198
+ timeout: options?.timeout ?? 1e4,
1199
+ maximumAge: options?.maximumAge ?? 6e4,
1200
+ enableHighAccuracy: options?.enableHighAccuracy ?? false
1201
+ });
1202
+ });
1203
+ return {
1204
+ latitude: position.coords.latitude,
1205
+ longitude: position.coords.longitude,
1206
+ accuracy: position.coords.accuracy,
1207
+ timestamp: new Date(position.timestamp).toISOString()
1208
+ };
1209
+ } catch {
1210
+ return null;
1211
+ }
1212
+ }
1213
+
1157
1214
  // src/index.ts
1158
- import {
1159
- EdgePopupBlockedError as EdgePopupBlockedError3,
1160
- EdgeStateMismatchError as EdgeStateMismatchError2,
1161
- EdgeError,
1162
- isEdgeError
1163
- } from "@edge-markets/connect";
1164
- import {
1165
- EDGE_SCOPES,
1166
- ALL_EDGE_SCOPES as ALL_EDGE_SCOPES2
1167
- } from "@edge-markets/connect";
1215
+ import { EdgeError, EdgePopupBlockedError as EdgePopupBlockedError3, EdgeStateMismatchError, isEdgeError } from "@edge-markets/connect";
1216
+ import { ALL_EDGE_SCOPES as ALL_EDGE_SCOPES2, EDGE_SCOPES } from "@edge-markets/connect";
1168
1217
  export {
1169
1218
  ALL_EDGE_SCOPES2 as ALL_EDGE_SCOPES,
1170
1219
  EDGE_SCOPES,
1171
1220
  EdgeError,
1172
1221
  EdgeLink,
1173
1222
  EdgePopupBlockedError3 as EdgePopupBlockedError,
1174
- EdgeStateMismatchError2 as EdgeStateMismatchError,
1223
+ EdgeStateMismatchError,
1175
1224
  EdgeTransferVerify,
1176
1225
  IframeManager,
1177
1226
  assertCryptoAvailable,
1227
+ collectGeolocation,
1178
1228
  generatePKCE,
1179
1229
  generateState,
1180
1230
  isEdgeError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edge-markets/connect-link",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "Browser SDK for EDGE Connect popup authentication",
5
5
  "author": "EdgeBoost",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@edge-markets/connect": "1.3.0"
24
+ "@edge-markets/connect": "^1.5.1"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": ">=17.0.0"
@@ -32,7 +32,9 @@
32
32
  }
33
33
  },
34
34
  "devDependencies": {
35
+ "@types/jsdom": "^28.0.1",
35
36
  "@types/react": "^18.2.0",
37
+ "jsdom": "^29.0.2",
36
38
  "tsup": "^8.0.0",
37
39
  "typescript": "^5.3.0",
38
40
  "vitest": "^1.0.0"