@edge-markets/connect-react-native 1.0.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.mjs ADDED
@@ -0,0 +1,686 @@
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
+ // src/index.ts
9
+ import {
10
+ getEnvironmentConfig as getEnvironmentConfig2,
11
+ EDGE_ENVIRONMENTS,
12
+ ALL_EDGE_SCOPES as ALL_EDGE_SCOPES2,
13
+ SCOPE_DESCRIPTIONS,
14
+ formatScopesForEnvironment as formatScopesForEnvironment2,
15
+ EdgeError,
16
+ EdgeAuthenticationError,
17
+ EdgeConsentRequiredError,
18
+ EdgeTokenExchangeError,
19
+ EdgeApiError,
20
+ EdgeNetworkError,
21
+ EdgePopupBlockedError,
22
+ EdgeStateMismatchError,
23
+ isAuthenticationError,
24
+ isConsentRequiredError,
25
+ isApiError,
26
+ isNetworkError
27
+ } from "@edge-markets/connect";
28
+
29
+ // src/edge-link.ts
30
+ import { Linking, Platform } from "react-native";
31
+ import {
32
+ getEnvironmentConfig,
33
+ ALL_EDGE_SCOPES,
34
+ formatScopesForEnvironment
35
+ } from "@edge-markets/connect";
36
+
37
+ // src/pkce.ts
38
+ function hasNativeCrypto() {
39
+ return typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function" && typeof globalThis.crypto.subtle !== "undefined";
40
+ }
41
+ async function sha256Fallback(message) {
42
+ try {
43
+ const ExpoCrypto = __require("expo-crypto");
44
+ const hash = await ExpoCrypto.digestStringAsync(
45
+ ExpoCrypto.CryptoDigestAlgorithm.SHA256,
46
+ message,
47
+ { encoding: ExpoCrypto.CryptoEncoding.BASE64 }
48
+ );
49
+ const binary = atob(hash);
50
+ const bytes = new Uint8Array(binary.length);
51
+ for (let i = 0; i < binary.length; i++) {
52
+ bytes[i] = binary.charCodeAt(i);
53
+ }
54
+ return bytes.buffer;
55
+ } catch {
56
+ }
57
+ try {
58
+ const { createHash } = __require("react-native-quick-crypto");
59
+ const hash = createHash("sha256").update(message).digest();
60
+ return hash.buffer;
61
+ } catch {
62
+ }
63
+ return pureJsSha256(message);
64
+ }
65
+ function pureJsSha256(message) {
66
+ const K = new Uint32Array([
67
+ 1116352408,
68
+ 1899447441,
69
+ 3049323471,
70
+ 3921009573,
71
+ 961987163,
72
+ 1508970993,
73
+ 2453635748,
74
+ 2870763221,
75
+ 3624381080,
76
+ 310598401,
77
+ 607225278,
78
+ 1426881987,
79
+ 1925078388,
80
+ 2162078206,
81
+ 2614888103,
82
+ 3248222580,
83
+ 3835390401,
84
+ 4022224774,
85
+ 264347078,
86
+ 604807628,
87
+ 770255983,
88
+ 1249150122,
89
+ 1555081692,
90
+ 1996064986,
91
+ 2554220882,
92
+ 2821834349,
93
+ 2952996808,
94
+ 3210313671,
95
+ 3336571891,
96
+ 3584528711,
97
+ 113926993,
98
+ 338241895,
99
+ 666307205,
100
+ 773529912,
101
+ 1294757372,
102
+ 1396182291,
103
+ 1695183700,
104
+ 1986661051,
105
+ 2177026350,
106
+ 2456956037,
107
+ 2730485921,
108
+ 2820302411,
109
+ 3259730800,
110
+ 3345764771,
111
+ 3516065817,
112
+ 3600352804,
113
+ 4094571909,
114
+ 275423344,
115
+ 430227734,
116
+ 506948616,
117
+ 659060556,
118
+ 883997877,
119
+ 958139571,
120
+ 1322822218,
121
+ 1537002063,
122
+ 1747873779,
123
+ 1955562222,
124
+ 2024104815,
125
+ 2227730452,
126
+ 2361852424,
127
+ 2428436474,
128
+ 2756734187,
129
+ 3204031479,
130
+ 3329325298
131
+ ]);
132
+ const H = new Uint32Array([
133
+ 1779033703,
134
+ 3144134277,
135
+ 1013904242,
136
+ 2773480762,
137
+ 1359893119,
138
+ 2600822924,
139
+ 528734635,
140
+ 1541459225
141
+ ]);
142
+ const encoder = new TextEncoder();
143
+ const data = encoder.encode(message);
144
+ const bitLen = data.length * 8;
145
+ const padLen = (data.length + 8 >> 6) + 1 << 6;
146
+ const padded = new Uint8Array(padLen);
147
+ padded.set(data);
148
+ padded[data.length] = 128;
149
+ const view = new DataView(padded.buffer);
150
+ view.setUint32(padLen - 4, bitLen, false);
151
+ const W = new Uint32Array(64);
152
+ for (let i = 0; i < padLen; i += 64) {
153
+ for (let j = 0; j < 16; j++) {
154
+ W[j] = view.getUint32(i + j * 4, false);
155
+ }
156
+ for (let j = 16; j < 64; j++) {
157
+ const s0 = rotr(W[j - 15], 7) ^ rotr(W[j - 15], 18) ^ W[j - 15] >>> 3;
158
+ const s1 = rotr(W[j - 2], 17) ^ rotr(W[j - 2], 19) ^ W[j - 2] >>> 10;
159
+ W[j] = W[j - 16] + s0 + W[j - 7] + s1 >>> 0;
160
+ }
161
+ let [a, b, c, d, e, f, g, h] = H;
162
+ for (let j = 0; j < 64; j++) {
163
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
164
+ const ch = e & f ^ ~e & g;
165
+ const temp1 = h + S1 + ch + K[j] + W[j] >>> 0;
166
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
167
+ const maj = a & b ^ a & c ^ b & c;
168
+ const temp2 = S0 + maj >>> 0;
169
+ h = g;
170
+ g = f;
171
+ f = e;
172
+ e = d + temp1 >>> 0;
173
+ d = c;
174
+ c = b;
175
+ b = a;
176
+ a = temp1 + temp2 >>> 0;
177
+ }
178
+ H[0] = H[0] + a >>> 0;
179
+ H[1] = H[1] + b >>> 0;
180
+ H[2] = H[2] + c >>> 0;
181
+ H[3] = H[3] + d >>> 0;
182
+ H[4] = H[4] + e >>> 0;
183
+ H[5] = H[5] + f >>> 0;
184
+ H[6] = H[6] + g >>> 0;
185
+ H[7] = H[7] + h >>> 0;
186
+ }
187
+ const result = new ArrayBuffer(32);
188
+ const resultView = new DataView(result);
189
+ for (let i = 0; i < 8; i++) {
190
+ resultView.setUint32(i * 4, H[i], false);
191
+ }
192
+ return result;
193
+ function rotr(x, n) {
194
+ return (x >>> n | x << 32 - n) >>> 0;
195
+ }
196
+ }
197
+ function getRandomBytes(byteLength) {
198
+ const array = new Uint8Array(byteLength);
199
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
200
+ globalThis.crypto.getRandomValues(array);
201
+ return array;
202
+ }
203
+ try {
204
+ const ExpoRandom = __require("expo-random");
205
+ return ExpoRandom.getRandomBytes(byteLength);
206
+ } catch {
207
+ }
208
+ console.warn(
209
+ "[EdgeLink] Using Math.random fallback - NOT CRYPTOGRAPHICALLY SECURE!\nInstall react-native-get-random-values or expo-random for production."
210
+ );
211
+ for (let i = 0; i < byteLength; i++) {
212
+ array[i] = Math.floor(Math.random() * 256);
213
+ }
214
+ return array;
215
+ }
216
+ function generateRandomHex(byteLength) {
217
+ const bytes = getRandomBytes(byteLength);
218
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
219
+ }
220
+ async function sha256(plain) {
221
+ if (hasNativeCrypto()) {
222
+ const encoder = new TextEncoder();
223
+ const data = encoder.encode(plain);
224
+ return globalThis.crypto.subtle.digest("SHA-256", data);
225
+ }
226
+ return sha256Fallback(plain);
227
+ }
228
+ function base64UrlEncode(buffer) {
229
+ const bytes = new Uint8Array(buffer);
230
+ let binary = "";
231
+ bytes.forEach((byte) => {
232
+ binary += String.fromCharCode(byte);
233
+ });
234
+ let base64;
235
+ if (typeof btoa === "function") {
236
+ base64 = btoa(binary);
237
+ } else {
238
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
239
+ base64 = "";
240
+ let i = 0;
241
+ while (i < binary.length) {
242
+ const a = binary.charCodeAt(i++);
243
+ const b = binary.charCodeAt(i++);
244
+ const c = binary.charCodeAt(i++);
245
+ base64 += chars[a >> 2] + chars[(a & 3) << 4 | b >> 4] + (isNaN(b) ? "=" : chars[(b & 15) << 2 | c >> 6]) + (isNaN(c) ? "=" : chars[c & 63]);
246
+ }
247
+ }
248
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
249
+ }
250
+ async function generatePKCE() {
251
+ const verifier = generateRandomHex(64);
252
+ const hash = await sha256(verifier);
253
+ const challenge = base64UrlEncode(hash);
254
+ return { verifier, challenge };
255
+ }
256
+ function generateState() {
257
+ return generateRandomHex(32);
258
+ }
259
+ function isSecureCryptoAvailable() {
260
+ if (typeof globalThis.crypto?.getRandomValues === "function") {
261
+ return true;
262
+ }
263
+ try {
264
+ __require("expo-random");
265
+ return true;
266
+ } catch {
267
+ return false;
268
+ }
269
+ }
270
+
271
+ // src/edge-link.ts
272
+ async function openInAppBrowser(url, useExternal = false) {
273
+ if (useExternal) {
274
+ await Linking.openURL(url);
275
+ return;
276
+ }
277
+ try {
278
+ const InAppBrowser = __require("react-native-inappbrowser-reborn").default;
279
+ if (await InAppBrowser.isAvailable()) {
280
+ await InAppBrowser.openAuth(url, "", {
281
+ // iOS options
282
+ dismissButtonStyle: "cancel",
283
+ preferredBarTintColor: "#1a1a2e",
284
+ preferredControlTintColor: "#00d4aa",
285
+ readerMode: false,
286
+ animated: true,
287
+ modalEnabled: true,
288
+ enableBarCollapsing: false,
289
+ // Android options
290
+ showTitle: true,
291
+ toolbarColor: "#1a1a2e",
292
+ secondaryToolbarColor: "#16213e",
293
+ navigationBarColor: "#1a1a2e",
294
+ navigationBarDividerColor: "#16213e",
295
+ enableUrlBarHiding: true,
296
+ enableDefaultShare: false,
297
+ forceCloseOnRedirection: false,
298
+ animations: {
299
+ startEnter: "slide_in_right",
300
+ startExit: "slide_out_left",
301
+ endEnter: "slide_in_left",
302
+ endExit: "slide_out_right"
303
+ }
304
+ });
305
+ return;
306
+ }
307
+ } catch {
308
+ }
309
+ try {
310
+ const WebBrowser = __require("expo-web-browser");
311
+ await WebBrowser.openAuthSessionAsync(url, "");
312
+ return;
313
+ } catch {
314
+ }
315
+ console.warn("[EdgeLink] No in-app browser library found. Using external browser.");
316
+ await Linking.openURL(url);
317
+ }
318
+ async function closeInAppBrowser() {
319
+ try {
320
+ const InAppBrowser = __require("react-native-inappbrowser-reborn").default;
321
+ InAppBrowser.close();
322
+ } catch {
323
+ }
324
+ try {
325
+ const WebBrowser = __require("expo-web-browser");
326
+ WebBrowser.dismissBrowser();
327
+ } catch {
328
+ }
329
+ }
330
+ var EdgeLink = class {
331
+ constructor(config) {
332
+ this.pkce = null;
333
+ this.state = null;
334
+ this.linkListener = null;
335
+ this.isOpen = false;
336
+ this.isDestroyed = false;
337
+ if (!config.clientId) {
338
+ throw new Error("EdgeLink: clientId is required");
339
+ }
340
+ if (!config.environment) {
341
+ throw new Error("EdgeLink: environment is required");
342
+ }
343
+ if (!config.redirectUri) {
344
+ throw new Error("EdgeLink: redirectUri is required for React Native");
345
+ }
346
+ if (!config.onSuccess) {
347
+ throw new Error("EdgeLink: onSuccess callback is required");
348
+ }
349
+ this.config = config;
350
+ }
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
+ async open() {
359
+ if (this.isDestroyed) {
360
+ throw new Error("EdgeLink: Cannot open - instance has been destroyed");
361
+ }
362
+ if (this.isOpen) {
363
+ console.warn("[EdgeLink] Already open");
364
+ return;
365
+ }
366
+ this.emitEvent("OPEN");
367
+ this.isOpen = true;
368
+ try {
369
+ this.pkce = await generatePKCE();
370
+ this.state = generateState();
371
+ this.setupLinkListener();
372
+ const url = this.buildLinkUrl();
373
+ this.emitEvent("HANDOFF", { url });
374
+ await openInAppBrowser(url, this.config.useExternalBrowser);
375
+ } catch (error) {
376
+ this.cleanup();
377
+ this.emitEvent("ERROR", { error });
378
+ this.config.onExit?.({
379
+ reason: "error",
380
+ error: {
381
+ code: "open_failed",
382
+ message: error instanceof Error ? error.message : "Failed to open browser"
383
+ }
384
+ });
385
+ }
386
+ }
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
+ handleDeepLink(url) {
397
+ if (!this.isOpen || !url.startsWith(this.config.redirectUri)) {
398
+ return false;
399
+ }
400
+ this.processCallback(url);
401
+ return true;
402
+ }
403
+ /**
404
+ * Closes the EdgeLink flow.
405
+ */
406
+ async close() {
407
+ if (!this.isOpen) return;
408
+ await closeInAppBrowser();
409
+ this.cleanup();
410
+ this.emitEvent("CLOSE", { reason: "programmatic" });
411
+ this.config.onExit?.({
412
+ reason: "user_closed"
413
+ });
414
+ }
415
+ /**
416
+ * Destroys the EdgeLink instance.
417
+ */
418
+ destroy() {
419
+ this.isDestroyed = true;
420
+ this.cleanup();
421
+ }
422
+ // ===========================================================================
423
+ // PRIVATE METHODS
424
+ // ===========================================================================
425
+ buildLinkUrl() {
426
+ const envConfig = getEnvironmentConfig(this.config.environment);
427
+ const baseUrl = this.config.linkUrl || `${envConfig.userClientUrl}/oauth/link`;
428
+ const url = new URL(baseUrl);
429
+ const scopes = this.config.scopes || ALL_EDGE_SCOPES;
430
+ url.searchParams.set("client_id", this.config.clientId);
431
+ url.searchParams.set("state", this.state);
432
+ url.searchParams.set("code_challenge", this.pkce.challenge);
433
+ url.searchParams.set("code_challenge_method", "S256");
434
+ url.searchParams.set("redirect_uri", this.config.redirectUri);
435
+ const formattedScopes = formatScopesForEnvironment(scopes, this.config.environment);
436
+ url.searchParams.set("scope", formattedScopes.join(" "));
437
+ url.searchParams.set("platform", Platform.OS);
438
+ url.searchParams.set("flow", "redirect");
439
+ return url.toString();
440
+ }
441
+ setupLinkListener() {
442
+ this.removeLinkListener();
443
+ this.linkListener = Linking.addEventListener("url", ({ url }) => {
444
+ if (url.startsWith(this.config.redirectUri)) {
445
+ this.processCallback(url);
446
+ }
447
+ });
448
+ Linking.getInitialURL().then((url) => {
449
+ if (url && url.startsWith(this.config.redirectUri) && this.isOpen) {
450
+ this.processCallback(url);
451
+ }
452
+ });
453
+ }
454
+ removeLinkListener() {
455
+ if (this.linkListener) {
456
+ this.linkListener.remove();
457
+ this.linkListener = null;
458
+ }
459
+ }
460
+ processCallback(url) {
461
+ this.emitEvent("REDIRECT", { url });
462
+ try {
463
+ const parsed = new URL(url);
464
+ const code = parsed.searchParams.get("code");
465
+ const returnedState = parsed.searchParams.get("state");
466
+ const error = parsed.searchParams.get("error");
467
+ const errorDescription = parsed.searchParams.get("error_description");
468
+ closeInAppBrowser();
469
+ if (error) {
470
+ this.cleanup();
471
+ this.config.onExit?.({
472
+ reason: "error",
473
+ error: {
474
+ code: error,
475
+ message: errorDescription || "Authorization error"
476
+ }
477
+ });
478
+ return;
479
+ }
480
+ if (returnedState !== this.state) {
481
+ this.cleanup();
482
+ this.config.onExit?.({
483
+ reason: "error",
484
+ error: {
485
+ code: "state_mismatch",
486
+ message: "Security error: state parameter mismatch. Please try again."
487
+ }
488
+ });
489
+ return;
490
+ }
491
+ if (!code) {
492
+ this.cleanup();
493
+ this.config.onExit?.({
494
+ reason: "error",
495
+ error: {
496
+ code: "no_code",
497
+ message: "No authorization code received"
498
+ }
499
+ });
500
+ return;
501
+ }
502
+ const codeVerifier = this.pkce.verifier;
503
+ this.cleanup();
504
+ this.emitEvent("SUCCESS");
505
+ this.config.onSuccess({
506
+ code,
507
+ codeVerifier,
508
+ state: returnedState
509
+ });
510
+ } catch (error) {
511
+ this.cleanup();
512
+ this.config.onExit?.({
513
+ reason: "error",
514
+ error: {
515
+ code: "parse_error",
516
+ message: error instanceof Error ? error.message : "Failed to parse callback URL"
517
+ }
518
+ });
519
+ }
520
+ }
521
+ cleanup() {
522
+ this.removeLinkListener();
523
+ this.pkce = null;
524
+ this.state = null;
525
+ this.isOpen = false;
526
+ }
527
+ emitEvent(eventName, metadata) {
528
+ this.config.onEvent?.({
529
+ eventName,
530
+ timestamp: Date.now(),
531
+ metadata
532
+ });
533
+ }
534
+ };
535
+
536
+ // src/hooks.ts
537
+ import { useCallback, useEffect, useRef, useState } from "react";
538
+ function useEdgeLink(config) {
539
+ const [isOpen, setIsOpen] = useState(false);
540
+ const [isSuccess, setIsSuccess] = useState(false);
541
+ const [isError, setIsError] = useState(false);
542
+ const [result, setResult] = useState(null);
543
+ const [error, setError] = useState(null);
544
+ const linkRef = useRef(null);
545
+ useEffect(() => {
546
+ const linkConfig = {
547
+ clientId: config.clientId,
548
+ environment: config.environment,
549
+ redirectUri: config.redirectUri,
550
+ scopes: config.scopes,
551
+ linkUrl: config.linkUrl,
552
+ useExternalBrowser: config.useExternalBrowser,
553
+ onSuccess: (successResult) => {
554
+ setIsOpen(false);
555
+ setIsSuccess(true);
556
+ setIsError(false);
557
+ setResult(successResult);
558
+ setError(null);
559
+ },
560
+ onExit: (metadata) => {
561
+ setIsOpen(false);
562
+ if (metadata.error) {
563
+ setIsError(true);
564
+ setError(metadata.error);
565
+ }
566
+ }
567
+ };
568
+ linkRef.current = new EdgeLink(linkConfig);
569
+ return () => {
570
+ linkRef.current?.destroy();
571
+ linkRef.current = null;
572
+ };
573
+ }, [
574
+ config.clientId,
575
+ config.environment,
576
+ config.redirectUri,
577
+ config.scopes,
578
+ config.linkUrl,
579
+ config.useExternalBrowser
580
+ ]);
581
+ const open = useCallback(async () => {
582
+ if (!linkRef.current) return;
583
+ setIsOpen(true);
584
+ setIsSuccess(false);
585
+ setIsError(false);
586
+ setError(null);
587
+ await linkRef.current.open();
588
+ }, []);
589
+ const close = useCallback(async () => {
590
+ if (!linkRef.current) return;
591
+ await linkRef.current.close();
592
+ setIsOpen(false);
593
+ }, []);
594
+ const reset = useCallback(() => {
595
+ setIsSuccess(false);
596
+ setIsError(false);
597
+ setResult(null);
598
+ setError(null);
599
+ }, []);
600
+ return {
601
+ open,
602
+ close,
603
+ isOpen,
604
+ isSuccess,
605
+ isError,
606
+ result,
607
+ error,
608
+ reset
609
+ };
610
+ }
611
+ function useEdgeLinkHandler(config) {
612
+ const handleUrl = useCallback(
613
+ (url) => {
614
+ if (!url.startsWith(config.redirectUri)) {
615
+ return false;
616
+ }
617
+ try {
618
+ const parsed = new URL(url);
619
+ const code = parsed.searchParams.get("code");
620
+ const state = parsed.searchParams.get("state");
621
+ const error = parsed.searchParams.get("error");
622
+ const errorDescription = parsed.searchParams.get("error_description");
623
+ const codeVerifier = parsed.searchParams.get("code_verifier");
624
+ if (error) {
625
+ config.onError?.({
626
+ code: error,
627
+ message: errorDescription || "Authorization error"
628
+ });
629
+ return true;
630
+ }
631
+ if (code && state) {
632
+ config.onSuccess({
633
+ code,
634
+ codeVerifier: codeVerifier || "",
635
+ // Might be empty if not passed in URL
636
+ state
637
+ });
638
+ return true;
639
+ }
640
+ config.onError?.({
641
+ code: "invalid_callback",
642
+ message: "Missing code or state in callback URL"
643
+ });
644
+ return true;
645
+ } catch (e) {
646
+ config.onError?.({
647
+ code: "parse_error",
648
+ message: "Failed to parse callback URL"
649
+ });
650
+ return true;
651
+ }
652
+ },
653
+ [config]
654
+ );
655
+ return { handleUrl };
656
+ }
657
+ function useEdgeLinkEvents(onEvent, deps = []) {
658
+ useEffect(() => {
659
+ }, [onEvent, ...deps]);
660
+ }
661
+ export {
662
+ ALL_EDGE_SCOPES2 as ALL_EDGE_SCOPES,
663
+ EDGE_ENVIRONMENTS,
664
+ EdgeApiError,
665
+ EdgeAuthenticationError,
666
+ EdgeConsentRequiredError,
667
+ EdgeError,
668
+ EdgeLink,
669
+ EdgeNetworkError,
670
+ EdgePopupBlockedError,
671
+ EdgeStateMismatchError,
672
+ EdgeTokenExchangeError,
673
+ SCOPE_DESCRIPTIONS,
674
+ formatScopesForEnvironment2 as formatScopesForEnvironment,
675
+ generatePKCE,
676
+ generateState,
677
+ getEnvironmentConfig2 as getEnvironmentConfig,
678
+ isApiError,
679
+ isAuthenticationError,
680
+ isConsentRequiredError,
681
+ isNetworkError,
682
+ isSecureCryptoAvailable,
683
+ useEdgeLink,
684
+ useEdgeLinkEvents,
685
+ useEdgeLinkHandler
686
+ };