@edge-markets/connect-react-native 1.0.3 → 1.3.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
@@ -305,3 +305,9 @@ Install and import `react-native-get-random-values` before any crypto operations
305
305
  MIT
306
306
 
307
307
 
308
+
309
+
310
+
311
+
312
+
313
+
package/dist/index.d.mts CHANGED
@@ -1,115 +1,10 @@
1
- import { EdgeEnvironment, EdgeLinkSuccess, EdgeLinkExit, EdgeScope } from '@edge-markets/connect';
2
- export { ALL_EDGE_SCOPES, Balance, EDGE_ENVIRONMENTS, EdgeApiError, EdgeAuthenticationError, EdgeConsentRequiredError, EdgeEnvironment, EdgeError, EdgeLinkExit, EdgeLinkSuccess, EdgeNetworkError, EdgePopupBlockedError, EdgeScope, EdgeStateMismatchError, EdgeTokenExchangeError, EdgeTokens, SCOPE_DESCRIPTIONS, Transfer, User, formatScopesForEnvironment, getEnvironmentConfig, isApiError, isAuthenticationError, isConsentRequiredError, isNetworkError } from '@edge-markets/connect';
1
+ import { EdgeLinkConfigBase, EdgeEnvironment, EdgeScope, EdgeLinkSuccess, EdgeLinkEvent, PKCEPair } from '@edge-markets/connect';
2
+ export { ALL_EDGE_SCOPES, Balance, EDGE_ENVIRONMENTS, EdgeApiError, EdgeAuthenticationError, EdgeConsentRequiredError, EdgeEnvironment, EdgeError, EdgeLinkEvent, EdgeLinkEventName, EdgeLinkExit, EdgeLinkSuccess, EdgeNetworkError, EdgePopupBlockedError, EdgeScope, EdgeStateMismatchError, EdgeTokenExchangeError, EdgeTokens, PKCEPair, SCOPE_DESCRIPTIONS, Transfer, User, formatScopesForEnvironment, getEnvironmentConfig, isApiError, isAuthenticationError, isConsentRequiredError, isNetworkError } from '@edge-markets/connect';
3
3
 
4
- /**
5
- * EdgeLink for React Native - Plaid-Style Authentication Flow
6
- *
7
- * Unlike the browser SDK which uses popups, the React Native SDK uses:
8
- * - In-App Browser (SFSafariViewController / Chrome Custom Tabs)
9
- * - Deep Links for OAuth callback
10
- * - Secure storage for PKCE state
11
- *
12
- * This provides a native, secure authentication experience similar to Plaid Link.
13
- *
14
- * @module @edge-markets/connect-react-native
15
- *
16
- * @example
17
- * ```typescript
18
- * import { EdgeLink } from '@edge-markets/connect-react-native'
19
- *
20
- * const link = new EdgeLink({
21
- * clientId: 'your-client-id',
22
- * environment: 'staging',
23
- * redirectUri: 'myapp://oauth/callback',
24
- * onSuccess: async (result) => {
25
- * await sendToBackend(result.code, result.codeVerifier)
26
- * },
27
- * onExit: (metadata) => {
28
- * console.log('User exited:', metadata.reason)
29
- * },
30
- * })
31
- *
32
- * // Open from a button press
33
- * <Button onPress={() => link.open()} title="Connect EdgeBoost" />
34
- * ```
35
- */
36
-
37
- /**
38
- * Configuration for EdgeLink React Native.
39
- */
40
- interface EdgeLinkConfig {
41
- /**
42
- * Your OAuth client ID from the EdgeBoost partner portal.
43
- */
44
- clientId: string;
45
- /**
46
- * Environment to connect to.
47
- */
48
- environment: EdgeEnvironment;
49
- /**
50
- * Deep link URI for OAuth callback.
51
- *
52
- * This must be registered in your app's URL schemes (iOS) and
53
- * intent filters (Android), and in your EdgeBoost OAuth client settings.
54
- *
55
- * @example
56
- * - Custom scheme: `myapp://oauth/callback`
57
- * - Universal link: `https://myapp.com/oauth/callback`
58
- */
4
+ interface EdgeLinkConfig extends EdgeLinkConfigBase {
59
5
  redirectUri: string;
60
- /**
61
- * Called when user successfully authenticates and grants consent.
62
- */
63
- onSuccess: (result: EdgeLinkSuccess) => void;
64
- /**
65
- * Called when user exits the flow.
66
- */
67
- onExit?: (metadata: EdgeLinkExit) => void;
68
- /**
69
- * Called for various events during the flow.
70
- */
71
- onEvent?: (event: EdgeLinkEvent) => void;
72
- /**
73
- * OAuth scopes to request.
74
- * @default All available scopes
75
- */
76
- scopes?: EdgeScope[];
77
- /**
78
- * Custom URL for the Link page (development only).
79
- */
80
- linkUrl?: string;
81
- /**
82
- * Use external browser instead of in-app browser.
83
- * @default false
84
- */
85
6
  useExternalBrowser?: boolean;
86
7
  }
87
- /**
88
- * Event emitted during the Link flow.
89
- */
90
- interface EdgeLinkEvent {
91
- eventName: EdgeLinkEventName;
92
- timestamp: number;
93
- metadata?: Record<string, unknown>;
94
- }
95
- type EdgeLinkEventName = 'OPEN' | 'CLOSE' | 'HANDOFF' | 'SUCCESS' | 'ERROR' | 'REDIRECT';
96
- /**
97
- * EdgeLink for React Native - handles OAuth flow with in-app browser.
98
- *
99
- * @example
100
- * ```typescript
101
- * const link = new EdgeLink({
102
- * clientId: 'your-client-id',
103
- * environment: 'staging',
104
- * redirectUri: 'myapp://oauth/callback',
105
- * onSuccess: handleSuccess,
106
- * onExit: handleExit,
107
- * })
108
- *
109
- * // Later, in response to user action
110
- * await link.open()
111
- * ```
112
- */
113
8
  declare class EdgeLink {
114
9
  private readonly config;
115
10
  private pkce;
@@ -118,31 +13,9 @@ declare class EdgeLink {
118
13
  private isOpen;
119
14
  private isDestroyed;
120
15
  constructor(config: EdgeLinkConfig);
121
- /**
122
- * Opens the EdgeLink authentication flow.
123
- *
124
- * This launches an in-app browser with the EdgeBoost login/consent page.
125
- * When complete, the browser redirects to your redirectUri and the
126
- * onSuccess/onExit callback is called.
127
- */
128
16
  open(): Promise<void>;
129
- /**
130
- * Manually handles a deep link URL.
131
- *
132
- * Use this if you're handling deep links yourself instead of relying
133
- * on the automatic listener.
134
- *
135
- * @param url - The deep link URL received
136
- * @returns true if the URL was handled, false otherwise
137
- */
138
17
  handleDeepLink(url: string): boolean;
139
- /**
140
- * Closes the EdgeLink flow.
141
- */
142
18
  close(): Promise<void>;
143
- /**
144
- * Destroys the EdgeLink instance.
145
- */
146
19
  destroy(): void;
147
20
  private buildLinkUrl;
148
21
  private setupLinkListener;
@@ -269,6 +142,8 @@ declare function useEdgeLink(config: UseEdgeLinkConfig): UseEdgeLinkReturn;
269
142
  interface UseEdgeLinkHandlerConfig {
270
143
  /** Your redirect URI prefix */
271
144
  redirectUri: string;
145
+ /** PKCE code verifier from the EdgeLink instance that initiated the flow */
146
+ codeVerifier: string;
272
147
  /** Called on successful auth */
273
148
  onSuccess: (result: EdgeLinkSuccess) => void;
274
149
  /** Called on error or user exit */
@@ -302,54 +177,8 @@ declare function useEdgeLinkHandler(config: UseEdgeLinkHandlerConfig): UseEdgeLi
302
177
  */
303
178
  declare function useEdgeLinkEvents(onEvent: (event: EdgeLinkEvent) => void, deps?: React.DependencyList): void;
304
179
 
305
- /**
306
- * PKCE (Proof Key for Code Exchange) Utilities for React Native
307
- *
308
- * React Native doesn't have Web Crypto API natively, so we use a
309
- * compatible implementation that works across platforms.
310
- *
311
- * For production apps, consider installing:
312
- * - `react-native-get-random-values` (polyfill for crypto.getRandomValues)
313
- * - `expo-crypto` (if using Expo)
314
- *
315
- * @module @edge-markets/connect-react-native/pkce
316
- */
317
- /**
318
- * A PKCE code verifier and challenge pair.
319
- */
320
- interface PKCEPair {
321
- /** High-entropy random string (43-128 characters). Keep secret until token exchange. */
322
- verifier: string;
323
- /** SHA-256 hash of verifier, base64url encoded. Include in authorization URL. */
324
- challenge: string;
325
- }
326
- /**
327
- * Generates a PKCE code verifier and challenge pair.
328
- *
329
- * @example
330
- * ```typescript
331
- * const pkce = await generatePKCE()
332
- *
333
- * // Include challenge in authorization URL
334
- * const authUrl = `https://auth.example.com/authorize?code_challenge=${pkce.challenge}&code_challenge_method=S256`
335
- *
336
- * // Later, include verifier in token exchange
337
- * const tokens = await exchangeCode(code, pkce.verifier)
338
- * ```
339
- */
340
180
  declare function generatePKCE(): Promise<PKCEPair>;
341
- /**
342
- * Generates a random state parameter for CSRF protection.
343
- *
344
- * @returns 64-character hex string (32 bytes of entropy)
345
- */
346
- declare function generateState(): string;
347
- /**
348
- * Checks if secure crypto is available.
349
- *
350
- * Returns false if only Math.random fallback is being used.
351
- * Use this to warn users in development.
352
- */
353
- declare function isSecureCryptoAvailable(): boolean;
181
+ declare function generateState(): Promise<string>;
182
+ declare function isSecureCryptoAvailable(): Promise<boolean>;
354
183
 
355
- export { EdgeLink, type EdgeLinkConfig, type EdgeLinkEvent, type EdgeLinkEventName, type PKCEPair, type UseEdgeLinkConfig, type UseEdgeLinkHandlerConfig, type UseEdgeLinkHandlerReturn, type UseEdgeLinkReturn, generatePKCE, generateState, isSecureCryptoAvailable, useEdgeLink, useEdgeLinkEvents, useEdgeLinkHandler };
184
+ export { EdgeLink, type EdgeLinkConfig, type UseEdgeLinkConfig, type UseEdgeLinkHandlerConfig, type UseEdgeLinkHandlerReturn, type UseEdgeLinkReturn, generatePKCE, generateState, isSecureCryptoAvailable, useEdgeLink, useEdgeLinkEvents, useEdgeLinkHandler };
package/dist/index.d.ts CHANGED
@@ -1,115 +1,10 @@
1
- import { EdgeEnvironment, EdgeLinkSuccess, EdgeLinkExit, EdgeScope } from '@edge-markets/connect';
2
- export { ALL_EDGE_SCOPES, Balance, EDGE_ENVIRONMENTS, EdgeApiError, EdgeAuthenticationError, EdgeConsentRequiredError, EdgeEnvironment, EdgeError, EdgeLinkExit, EdgeLinkSuccess, EdgeNetworkError, EdgePopupBlockedError, EdgeScope, EdgeStateMismatchError, EdgeTokenExchangeError, EdgeTokens, SCOPE_DESCRIPTIONS, Transfer, User, formatScopesForEnvironment, getEnvironmentConfig, isApiError, isAuthenticationError, isConsentRequiredError, isNetworkError } from '@edge-markets/connect';
1
+ import { EdgeLinkConfigBase, EdgeEnvironment, EdgeScope, EdgeLinkSuccess, EdgeLinkEvent, PKCEPair } from '@edge-markets/connect';
2
+ export { ALL_EDGE_SCOPES, Balance, EDGE_ENVIRONMENTS, EdgeApiError, EdgeAuthenticationError, EdgeConsentRequiredError, EdgeEnvironment, EdgeError, EdgeLinkEvent, EdgeLinkEventName, EdgeLinkExit, EdgeLinkSuccess, EdgeNetworkError, EdgePopupBlockedError, EdgeScope, EdgeStateMismatchError, EdgeTokenExchangeError, EdgeTokens, PKCEPair, SCOPE_DESCRIPTIONS, Transfer, User, formatScopesForEnvironment, getEnvironmentConfig, isApiError, isAuthenticationError, isConsentRequiredError, isNetworkError } from '@edge-markets/connect';
3
3
 
4
- /**
5
- * EdgeLink for React Native - Plaid-Style Authentication Flow
6
- *
7
- * Unlike the browser SDK which uses popups, the React Native SDK uses:
8
- * - In-App Browser (SFSafariViewController / Chrome Custom Tabs)
9
- * - Deep Links for OAuth callback
10
- * - Secure storage for PKCE state
11
- *
12
- * This provides a native, secure authentication experience similar to Plaid Link.
13
- *
14
- * @module @edge-markets/connect-react-native
15
- *
16
- * @example
17
- * ```typescript
18
- * import { EdgeLink } from '@edge-markets/connect-react-native'
19
- *
20
- * const link = new EdgeLink({
21
- * clientId: 'your-client-id',
22
- * environment: 'staging',
23
- * redirectUri: 'myapp://oauth/callback',
24
- * onSuccess: async (result) => {
25
- * await sendToBackend(result.code, result.codeVerifier)
26
- * },
27
- * onExit: (metadata) => {
28
- * console.log('User exited:', metadata.reason)
29
- * },
30
- * })
31
- *
32
- * // Open from a button press
33
- * <Button onPress={() => link.open()} title="Connect EdgeBoost" />
34
- * ```
35
- */
36
-
37
- /**
38
- * Configuration for EdgeLink React Native.
39
- */
40
- interface EdgeLinkConfig {
41
- /**
42
- * Your OAuth client ID from the EdgeBoost partner portal.
43
- */
44
- clientId: string;
45
- /**
46
- * Environment to connect to.
47
- */
48
- environment: EdgeEnvironment;
49
- /**
50
- * Deep link URI for OAuth callback.
51
- *
52
- * This must be registered in your app's URL schemes (iOS) and
53
- * intent filters (Android), and in your EdgeBoost OAuth client settings.
54
- *
55
- * @example
56
- * - Custom scheme: `myapp://oauth/callback`
57
- * - Universal link: `https://myapp.com/oauth/callback`
58
- */
4
+ interface EdgeLinkConfig extends EdgeLinkConfigBase {
59
5
  redirectUri: string;
60
- /**
61
- * Called when user successfully authenticates and grants consent.
62
- */
63
- onSuccess: (result: EdgeLinkSuccess) => void;
64
- /**
65
- * Called when user exits the flow.
66
- */
67
- onExit?: (metadata: EdgeLinkExit) => void;
68
- /**
69
- * Called for various events during the flow.
70
- */
71
- onEvent?: (event: EdgeLinkEvent) => void;
72
- /**
73
- * OAuth scopes to request.
74
- * @default All available scopes
75
- */
76
- scopes?: EdgeScope[];
77
- /**
78
- * Custom URL for the Link page (development only).
79
- */
80
- linkUrl?: string;
81
- /**
82
- * Use external browser instead of in-app browser.
83
- * @default false
84
- */
85
6
  useExternalBrowser?: boolean;
86
7
  }
87
- /**
88
- * Event emitted during the Link flow.
89
- */
90
- interface EdgeLinkEvent {
91
- eventName: EdgeLinkEventName;
92
- timestamp: number;
93
- metadata?: Record<string, unknown>;
94
- }
95
- type EdgeLinkEventName = 'OPEN' | 'CLOSE' | 'HANDOFF' | 'SUCCESS' | 'ERROR' | 'REDIRECT';
96
- /**
97
- * EdgeLink for React Native - handles OAuth flow with in-app browser.
98
- *
99
- * @example
100
- * ```typescript
101
- * const link = new EdgeLink({
102
- * clientId: 'your-client-id',
103
- * environment: 'staging',
104
- * redirectUri: 'myapp://oauth/callback',
105
- * onSuccess: handleSuccess,
106
- * onExit: handleExit,
107
- * })
108
- *
109
- * // Later, in response to user action
110
- * await link.open()
111
- * ```
112
- */
113
8
  declare class EdgeLink {
114
9
  private readonly config;
115
10
  private pkce;
@@ -118,31 +13,9 @@ declare class EdgeLink {
118
13
  private isOpen;
119
14
  private isDestroyed;
120
15
  constructor(config: EdgeLinkConfig);
121
- /**
122
- * Opens the EdgeLink authentication flow.
123
- *
124
- * This launches an in-app browser with the EdgeBoost login/consent page.
125
- * When complete, the browser redirects to your redirectUri and the
126
- * onSuccess/onExit callback is called.
127
- */
128
16
  open(): Promise<void>;
129
- /**
130
- * Manually handles a deep link URL.
131
- *
132
- * Use this if you're handling deep links yourself instead of relying
133
- * on the automatic listener.
134
- *
135
- * @param url - The deep link URL received
136
- * @returns true if the URL was handled, false otherwise
137
- */
138
17
  handleDeepLink(url: string): boolean;
139
- /**
140
- * Closes the EdgeLink flow.
141
- */
142
18
  close(): Promise<void>;
143
- /**
144
- * Destroys the EdgeLink instance.
145
- */
146
19
  destroy(): void;
147
20
  private buildLinkUrl;
148
21
  private setupLinkListener;
@@ -269,6 +142,8 @@ declare function useEdgeLink(config: UseEdgeLinkConfig): UseEdgeLinkReturn;
269
142
  interface UseEdgeLinkHandlerConfig {
270
143
  /** Your redirect URI prefix */
271
144
  redirectUri: string;
145
+ /** PKCE code verifier from the EdgeLink instance that initiated the flow */
146
+ codeVerifier: string;
272
147
  /** Called on successful auth */
273
148
  onSuccess: (result: EdgeLinkSuccess) => void;
274
149
  /** Called on error or user exit */
@@ -302,54 +177,8 @@ declare function useEdgeLinkHandler(config: UseEdgeLinkHandlerConfig): UseEdgeLi
302
177
  */
303
178
  declare function useEdgeLinkEvents(onEvent: (event: EdgeLinkEvent) => void, deps?: React.DependencyList): void;
304
179
 
305
- /**
306
- * PKCE (Proof Key for Code Exchange) Utilities for React Native
307
- *
308
- * React Native doesn't have Web Crypto API natively, so we use a
309
- * compatible implementation that works across platforms.
310
- *
311
- * For production apps, consider installing:
312
- * - `react-native-get-random-values` (polyfill for crypto.getRandomValues)
313
- * - `expo-crypto` (if using Expo)
314
- *
315
- * @module @edge-markets/connect-react-native/pkce
316
- */
317
- /**
318
- * A PKCE code verifier and challenge pair.
319
- */
320
- interface PKCEPair {
321
- /** High-entropy random string (43-128 characters). Keep secret until token exchange. */
322
- verifier: string;
323
- /** SHA-256 hash of verifier, base64url encoded. Include in authorization URL. */
324
- challenge: string;
325
- }
326
- /**
327
- * Generates a PKCE code verifier and challenge pair.
328
- *
329
- * @example
330
- * ```typescript
331
- * const pkce = await generatePKCE()
332
- *
333
- * // Include challenge in authorization URL
334
- * const authUrl = `https://auth.example.com/authorize?code_challenge=${pkce.challenge}&code_challenge_method=S256`
335
- *
336
- * // Later, include verifier in token exchange
337
- * const tokens = await exchangeCode(code, pkce.verifier)
338
- * ```
339
- */
340
180
  declare function generatePKCE(): Promise<PKCEPair>;
341
- /**
342
- * Generates a random state parameter for CSRF protection.
343
- *
344
- * @returns 64-character hex string (32 bytes of entropy)
345
- */
346
- declare function generateState(): string;
347
- /**
348
- * Checks if secure crypto is available.
349
- *
350
- * Returns false if only Math.random fallback is being used.
351
- * Use this to warn users in development.
352
- */
353
- declare function isSecureCryptoAvailable(): boolean;
181
+ declare function generateState(): Promise<string>;
182
+ declare function isSecureCryptoAvailable(): Promise<boolean>;
354
183
 
355
- export { EdgeLink, type EdgeLinkConfig, type EdgeLinkEvent, type EdgeLinkEventName, type PKCEPair, type UseEdgeLinkConfig, type UseEdgeLinkHandlerConfig, type UseEdgeLinkHandlerReturn, type UseEdgeLinkReturn, generatePKCE, generateState, isSecureCryptoAvailable, useEdgeLink, useEdgeLinkEvents, useEdgeLinkHandler };
184
+ export { EdgeLink, type EdgeLinkConfig, type UseEdgeLinkConfig, type UseEdgeLinkHandlerConfig, type UseEdgeLinkHandlerReturn, type UseEdgeLinkReturn, generatePKCE, generateState, isSecureCryptoAvailable, useEdgeLink, useEdgeLinkEvents, useEdgeLinkHandler };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -58,7 +68,7 @@ function hasNativeCrypto() {
58
68
  }
59
69
  async function sha256Fallback(message) {
60
70
  try {
61
- const ExpoCrypto = require("expo-crypto");
71
+ const ExpoCrypto = await import("expo-crypto");
62
72
  const hash = await ExpoCrypto.digestStringAsync(
63
73
  ExpoCrypto.CryptoDigestAlgorithm.SHA256,
64
74
  message,
@@ -73,8 +83,8 @@ async function sha256Fallback(message) {
73
83
  } catch {
74
84
  }
75
85
  try {
76
- const { createHash } = require("react-native-quick-crypto");
77
- const hash = createHash("sha256").update(message).digest();
86
+ const quickCrypto = await import("react-native-quick-crypto");
87
+ const hash = quickCrypto.createHash("sha256").update(message).digest();
78
88
  return hash.buffer;
79
89
  } catch {
80
90
  }
@@ -212,27 +222,23 @@ function pureJsSha256(message) {
212
222
  return (x >>> n | x << 32 - n) >>> 0;
213
223
  }
214
224
  }
215
- function getRandomBytes(byteLength) {
225
+ async function getRandomBytes(byteLength) {
216
226
  const array = new Uint8Array(byteLength);
217
227
  if (typeof globalThis.crypto?.getRandomValues === "function") {
218
228
  globalThis.crypto.getRandomValues(array);
219
229
  return array;
220
230
  }
221
231
  try {
222
- const ExpoRandom = require("expo-random");
232
+ const ExpoRandom = await import("expo-random");
223
233
  return ExpoRandom.getRandomBytes(byteLength);
224
234
  } catch {
225
235
  }
226
- console.warn(
227
- "[EdgeLink] Using Math.random fallback - NOT CRYPTOGRAPHICALLY SECURE!\nInstall react-native-get-random-values or expo-random for production."
236
+ throw new Error(
237
+ "[EdgeLink] No cryptographically secure random source available. Install react-native-get-random-values (and import it before EdgeLink) or expo-random."
228
238
  );
229
- for (let i = 0; i < byteLength; i++) {
230
- array[i] = Math.floor(Math.random() * 256);
231
- }
232
- return array;
233
239
  }
234
- function generateRandomHex(byteLength) {
235
- const bytes = getRandomBytes(byteLength);
240
+ async function generateRandomHex(byteLength) {
241
+ const bytes = await getRandomBytes(byteLength);
236
242
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
237
243
  }
238
244
  async function sha256(plain) {
@@ -266,20 +272,21 @@ function base64UrlEncode(buffer) {
266
272
  return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
267
273
  }
268
274
  async function generatePKCE() {
269
- const verifier = generateRandomHex(64);
275
+ const verifier = await generateRandomHex(64);
270
276
  const hash = await sha256(verifier);
271
277
  const challenge = base64UrlEncode(hash);
272
278
  return { verifier, challenge };
273
279
  }
274
- function generateState() {
275
- return generateRandomHex(32);
280
+ async function generateState() {
281
+ const bytes = await getRandomBytes(32);
282
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
276
283
  }
277
- function isSecureCryptoAvailable() {
284
+ async function isSecureCryptoAvailable() {
278
285
  if (typeof globalThis.crypto?.getRandomValues === "function") {
279
286
  return true;
280
287
  }
281
288
  try {
282
- require("expo-random");
289
+ await import("expo-random");
283
290
  return true;
284
291
  } catch {
285
292
  return false;
@@ -293,10 +300,10 @@ async function openInAppBrowser(url, useExternal = false) {
293
300
  return;
294
301
  }
295
302
  try {
296
- const InAppBrowser = require("react-native-inappbrowser-reborn").default;
303
+ const module2 = await import("react-native-inappbrowser-reborn");
304
+ const InAppBrowser = module2.default;
297
305
  if (await InAppBrowser.isAvailable()) {
298
306
  await InAppBrowser.openAuth(url, "", {
299
- // iOS options
300
307
  dismissButtonStyle: "cancel",
301
308
  preferredBarTintColor: "#1a1a2e",
302
309
  preferredControlTintColor: "#00d4aa",
@@ -304,7 +311,6 @@ async function openInAppBrowser(url, useExternal = false) {
304
311
  animated: true,
305
312
  modalEnabled: true,
306
313
  enableBarCollapsing: false,
307
- // Android options
308
314
  showTitle: true,
309
315
  toolbarColor: "#1a1a2e",
310
316
  secondaryToolbarColor: "#16213e",
@@ -325,7 +331,7 @@ async function openInAppBrowser(url, useExternal = false) {
325
331
  } catch {
326
332
  }
327
333
  try {
328
- const WebBrowser = require("expo-web-browser");
334
+ const WebBrowser = await import("expo-web-browser");
329
335
  await WebBrowser.openAuthSessionAsync(url, "");
330
336
  return;
331
337
  } catch {
@@ -335,12 +341,12 @@ async function openInAppBrowser(url, useExternal = false) {
335
341
  }
336
342
  async function closeInAppBrowser() {
337
343
  try {
338
- const InAppBrowser = require("react-native-inappbrowser-reborn").default;
339
- InAppBrowser.close();
344
+ const module2 = await import("react-native-inappbrowser-reborn");
345
+ module2.default.close();
340
346
  } catch {
341
347
  }
342
348
  try {
343
- const WebBrowser = require("expo-web-browser");
349
+ const WebBrowser = await import("expo-web-browser");
344
350
  WebBrowser.dismissBrowser();
345
351
  } catch {
346
352
  }
@@ -366,13 +372,6 @@ var EdgeLink = class {
366
372
  }
367
373
  this.config = config;
368
374
  }
369
- /**
370
- * Opens the EdgeLink authentication flow.
371
- *
372
- * This launches an in-app browser with the EdgeBoost login/consent page.
373
- * When complete, the browser redirects to your redirectUri and the
374
- * onSuccess/onExit callback is called.
375
- */
376
375
  async open() {
377
376
  if (this.isDestroyed) {
378
377
  throw new Error("EdgeLink: Cannot open - instance has been destroyed");
@@ -385,7 +384,7 @@ var EdgeLink = class {
385
384
  this.isOpen = true;
386
385
  try {
387
386
  this.pkce = await generatePKCE();
388
- this.state = generateState();
387
+ this.state = await generateState();
389
388
  this.setupLinkListener();
390
389
  const url = this.buildLinkUrl();
391
390
  this.emitEvent("HANDOFF", { url });
@@ -402,15 +401,6 @@ var EdgeLink = class {
402
401
  });
403
402
  }
404
403
  }
405
- /**
406
- * Manually handles a deep link URL.
407
- *
408
- * Use this if you're handling deep links yourself instead of relying
409
- * on the automatic listener.
410
- *
411
- * @param url - The deep link URL received
412
- * @returns true if the URL was handled, false otherwise
413
- */
414
404
  handleDeepLink(url) {
415
405
  if (!this.isOpen || !url.startsWith(this.config.redirectUri)) {
416
406
  return false;
@@ -418,9 +408,6 @@ var EdgeLink = class {
418
408
  this.processCallback(url);
419
409
  return true;
420
410
  }
421
- /**
422
- * Closes the EdgeLink flow.
423
- */
424
411
  async close() {
425
412
  if (!this.isOpen) return;
426
413
  await closeInAppBrowser();
@@ -430,16 +417,10 @@ var EdgeLink = class {
430
417
  reason: "user_closed"
431
418
  });
432
419
  }
433
- /**
434
- * Destroys the EdgeLink instance.
435
- */
436
420
  destroy() {
437
421
  this.isDestroyed = true;
438
422
  this.cleanup();
439
423
  }
440
- // ===========================================================================
441
- // PRIVATE METHODS
442
- // ===========================================================================
443
424
  buildLinkUrl() {
444
425
  const envConfig = (0, import_connect.getEnvironmentConfig)(this.config.environment);
445
426
  const baseUrl = this.config.linkUrl || `${envConfig.userClientUrl}/oauth/link`;
@@ -638,7 +619,6 @@ function useEdgeLinkHandler(config) {
638
619
  const state = parsed.searchParams.get("state");
639
620
  const error = parsed.searchParams.get("error");
640
621
  const errorDescription = parsed.searchParams.get("error_description");
641
- const codeVerifier = parsed.searchParams.get("code_verifier");
642
622
  if (error) {
643
623
  config.onError?.({
644
624
  code: error,
@@ -649,8 +629,7 @@ function useEdgeLinkHandler(config) {
649
629
  if (code && state) {
650
630
  config.onSuccess({
651
631
  code,
652
- codeVerifier: codeVerifier || "",
653
- // Might be empty if not passed in URL
632
+ codeVerifier: config.codeVerifier,
654
633
  state
655
634
  });
656
635
  return true;
package/dist/index.mjs CHANGED
@@ -1,10 +1,3 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
1
  // src/index.ts
9
2
  import {
10
3
  getEnvironmentConfig as getEnvironmentConfig2,
@@ -40,7 +33,7 @@ function hasNativeCrypto() {
40
33
  }
41
34
  async function sha256Fallback(message) {
42
35
  try {
43
- const ExpoCrypto = __require("expo-crypto");
36
+ const ExpoCrypto = await import("expo-crypto");
44
37
  const hash = await ExpoCrypto.digestStringAsync(
45
38
  ExpoCrypto.CryptoDigestAlgorithm.SHA256,
46
39
  message,
@@ -55,8 +48,8 @@ async function sha256Fallback(message) {
55
48
  } catch {
56
49
  }
57
50
  try {
58
- const { createHash } = __require("react-native-quick-crypto");
59
- const hash = createHash("sha256").update(message).digest();
51
+ const quickCrypto = await import("react-native-quick-crypto");
52
+ const hash = quickCrypto.createHash("sha256").update(message).digest();
60
53
  return hash.buffer;
61
54
  } catch {
62
55
  }
@@ -194,27 +187,23 @@ function pureJsSha256(message) {
194
187
  return (x >>> n | x << 32 - n) >>> 0;
195
188
  }
196
189
  }
197
- function getRandomBytes(byteLength) {
190
+ async function getRandomBytes(byteLength) {
198
191
  const array = new Uint8Array(byteLength);
199
192
  if (typeof globalThis.crypto?.getRandomValues === "function") {
200
193
  globalThis.crypto.getRandomValues(array);
201
194
  return array;
202
195
  }
203
196
  try {
204
- const ExpoRandom = __require("expo-random");
197
+ const ExpoRandom = await import("expo-random");
205
198
  return ExpoRandom.getRandomBytes(byteLength);
206
199
  } catch {
207
200
  }
208
- console.warn(
209
- "[EdgeLink] Using Math.random fallback - NOT CRYPTOGRAPHICALLY SECURE!\nInstall react-native-get-random-values or expo-random for production."
201
+ throw new Error(
202
+ "[EdgeLink] No cryptographically secure random source available. Install react-native-get-random-values (and import it before EdgeLink) or expo-random."
210
203
  );
211
- for (let i = 0; i < byteLength; i++) {
212
- array[i] = Math.floor(Math.random() * 256);
213
- }
214
- return array;
215
204
  }
216
- function generateRandomHex(byteLength) {
217
- const bytes = getRandomBytes(byteLength);
205
+ async function generateRandomHex(byteLength) {
206
+ const bytes = await getRandomBytes(byteLength);
218
207
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
219
208
  }
220
209
  async function sha256(plain) {
@@ -248,20 +237,21 @@ function base64UrlEncode(buffer) {
248
237
  return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
249
238
  }
250
239
  async function generatePKCE() {
251
- const verifier = generateRandomHex(64);
240
+ const verifier = await generateRandomHex(64);
252
241
  const hash = await sha256(verifier);
253
242
  const challenge = base64UrlEncode(hash);
254
243
  return { verifier, challenge };
255
244
  }
256
- function generateState() {
257
- return generateRandomHex(32);
245
+ async function generateState() {
246
+ const bytes = await getRandomBytes(32);
247
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
258
248
  }
259
- function isSecureCryptoAvailable() {
249
+ async function isSecureCryptoAvailable() {
260
250
  if (typeof globalThis.crypto?.getRandomValues === "function") {
261
251
  return true;
262
252
  }
263
253
  try {
264
- __require("expo-random");
254
+ await import("expo-random");
265
255
  return true;
266
256
  } catch {
267
257
  return false;
@@ -275,10 +265,10 @@ async function openInAppBrowser(url, useExternal = false) {
275
265
  return;
276
266
  }
277
267
  try {
278
- const InAppBrowser = __require("react-native-inappbrowser-reborn").default;
268
+ const module = await import("react-native-inappbrowser-reborn");
269
+ const InAppBrowser = module.default;
279
270
  if (await InAppBrowser.isAvailable()) {
280
271
  await InAppBrowser.openAuth(url, "", {
281
- // iOS options
282
272
  dismissButtonStyle: "cancel",
283
273
  preferredBarTintColor: "#1a1a2e",
284
274
  preferredControlTintColor: "#00d4aa",
@@ -286,7 +276,6 @@ async function openInAppBrowser(url, useExternal = false) {
286
276
  animated: true,
287
277
  modalEnabled: true,
288
278
  enableBarCollapsing: false,
289
- // Android options
290
279
  showTitle: true,
291
280
  toolbarColor: "#1a1a2e",
292
281
  secondaryToolbarColor: "#16213e",
@@ -307,7 +296,7 @@ async function openInAppBrowser(url, useExternal = false) {
307
296
  } catch {
308
297
  }
309
298
  try {
310
- const WebBrowser = __require("expo-web-browser");
299
+ const WebBrowser = await import("expo-web-browser");
311
300
  await WebBrowser.openAuthSessionAsync(url, "");
312
301
  return;
313
302
  } catch {
@@ -317,12 +306,12 @@ async function openInAppBrowser(url, useExternal = false) {
317
306
  }
318
307
  async function closeInAppBrowser() {
319
308
  try {
320
- const InAppBrowser = __require("react-native-inappbrowser-reborn").default;
321
- InAppBrowser.close();
309
+ const module = await import("react-native-inappbrowser-reborn");
310
+ module.default.close();
322
311
  } catch {
323
312
  }
324
313
  try {
325
- const WebBrowser = __require("expo-web-browser");
314
+ const WebBrowser = await import("expo-web-browser");
326
315
  WebBrowser.dismissBrowser();
327
316
  } catch {
328
317
  }
@@ -348,13 +337,6 @@ var EdgeLink = class {
348
337
  }
349
338
  this.config = config;
350
339
  }
351
- /**
352
- * Opens the EdgeLink authentication flow.
353
- *
354
- * This launches an in-app browser with the EdgeBoost login/consent page.
355
- * When complete, the browser redirects to your redirectUri and the
356
- * onSuccess/onExit callback is called.
357
- */
358
340
  async open() {
359
341
  if (this.isDestroyed) {
360
342
  throw new Error("EdgeLink: Cannot open - instance has been destroyed");
@@ -367,7 +349,7 @@ var EdgeLink = class {
367
349
  this.isOpen = true;
368
350
  try {
369
351
  this.pkce = await generatePKCE();
370
- this.state = generateState();
352
+ this.state = await generateState();
371
353
  this.setupLinkListener();
372
354
  const url = this.buildLinkUrl();
373
355
  this.emitEvent("HANDOFF", { url });
@@ -384,15 +366,6 @@ var EdgeLink = class {
384
366
  });
385
367
  }
386
368
  }
387
- /**
388
- * Manually handles a deep link URL.
389
- *
390
- * Use this if you're handling deep links yourself instead of relying
391
- * on the automatic listener.
392
- *
393
- * @param url - The deep link URL received
394
- * @returns true if the URL was handled, false otherwise
395
- */
396
369
  handleDeepLink(url) {
397
370
  if (!this.isOpen || !url.startsWith(this.config.redirectUri)) {
398
371
  return false;
@@ -400,9 +373,6 @@ var EdgeLink = class {
400
373
  this.processCallback(url);
401
374
  return true;
402
375
  }
403
- /**
404
- * Closes the EdgeLink flow.
405
- */
406
376
  async close() {
407
377
  if (!this.isOpen) return;
408
378
  await closeInAppBrowser();
@@ -412,16 +382,10 @@ var EdgeLink = class {
412
382
  reason: "user_closed"
413
383
  });
414
384
  }
415
- /**
416
- * Destroys the EdgeLink instance.
417
- */
418
385
  destroy() {
419
386
  this.isDestroyed = true;
420
387
  this.cleanup();
421
388
  }
422
- // ===========================================================================
423
- // PRIVATE METHODS
424
- // ===========================================================================
425
389
  buildLinkUrl() {
426
390
  const envConfig = getEnvironmentConfig(this.config.environment);
427
391
  const baseUrl = this.config.linkUrl || `${envConfig.userClientUrl}/oauth/link`;
@@ -620,7 +584,6 @@ function useEdgeLinkHandler(config) {
620
584
  const state = parsed.searchParams.get("state");
621
585
  const error = parsed.searchParams.get("error");
622
586
  const errorDescription = parsed.searchParams.get("error_description");
623
- const codeVerifier = parsed.searchParams.get("code_verifier");
624
587
  if (error) {
625
588
  config.onError?.({
626
589
  code: error,
@@ -631,8 +594,7 @@ function useEdgeLinkHandler(config) {
631
594
  if (code && state) {
632
595
  config.onSuccess({
633
596
  code,
634
- codeVerifier: codeVerifier || "",
635
- // Might be empty if not passed in URL
597
+ codeVerifier: config.codeVerifier,
636
598
  state
637
599
  });
638
600
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edge-markets/connect-react-native",
3
- "version": "1.0.3",
3
+ "version": "1.3.0",
4
4
  "description": "React Native SDK for EDGE Connect authentication flow for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -13,11 +13,6 @@
13
13
  }
14
14
  },
15
15
  "sideEffects": false,
16
- "scripts": {
17
- "build": "tsup src/index.ts --format cjs,esm --dts --clean",
18
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
19
- "typecheck": "tsc --noEmit"
20
- },
21
16
  "files": [
22
17
  "dist",
23
18
  "README.md"
@@ -35,7 +30,7 @@
35
30
  "author": "EdgeBoost",
36
31
  "license": "MIT",
37
32
  "dependencies": {
38
- "@edge-markets/connect": "^1.0.0"
33
+ "@edge-markets/connect": "1.3.0"
39
34
  },
40
35
  "peerDependencies": {
41
36
  "react": ">=18.0.0",
@@ -57,6 +52,10 @@
57
52
  "type": "git",
58
53
  "url": "https://github.com/edgeboost/edge-connect-sdk.git",
59
54
  "directory": "packages/react-native"
55
+ },
56
+ "scripts": {
57
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
58
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
59
+ "typecheck": "tsc --noEmit"
60
60
  }
61
- }
62
-
61
+ }