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