@zoreal/oauth2-react 0.2.7 → 0.2.9
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 +14 -3
- package/dist/index.cjs +135 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -5
- package/dist/index.d.ts +38 -5
- package/dist/index.js +135 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -37,8 +37,25 @@ interface PairingState {
|
|
|
37
37
|
* carrying nothing to render. Present on every callback of a QR/link flow.
|
|
38
38
|
*/
|
|
39
39
|
pairUrl?: string;
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* The provider-served SVG to show right now. Put it in an <img>; do not draw
|
|
42
|
+
* your own.
|
|
43
|
+
*
|
|
44
|
+
* IT CHANGES DURING THE PAIRING. The code on screen is one frame of a
|
|
45
|
+
* rotating sequence, and a frame the provider has moved past is refused when
|
|
46
|
+
* the phone tries to claim it, which is what stops a screenshot of the code
|
|
47
|
+
* from being relayed to someone else's browser. So render the qrUrl of the
|
|
48
|
+
* state you are handed, every time you are handed one; a UI that caches the
|
|
49
|
+
* first value shows a code that stops working seconds later.
|
|
50
|
+
*/
|
|
41
51
|
qrUrl?: string;
|
|
52
|
+
/**
|
|
53
|
+
* How often, in seconds, `qrUrl` is replaced while the pairing is pending.
|
|
54
|
+
* Informational: the SDK already emits a new state on that cadence, so a
|
|
55
|
+
* custom UI does not need a timer of its own. Absent on the app link, which
|
|
56
|
+
* has no QR.
|
|
57
|
+
*/
|
|
58
|
+
qrRefreshSeconds?: number;
|
|
42
59
|
/** True when the flow resolved to the app link (mobile) rather than a QR. */
|
|
43
60
|
appLink?: boolean;
|
|
44
61
|
/** Abandons this pairing: stops the poll. Wire it to your UI's cancel control. */
|
|
@@ -71,10 +88,13 @@ interface ZorealButtonConfiguration {
|
|
|
71
88
|
theme?: 'outline' | 'filled' | 'filled_black';
|
|
72
89
|
size?: 'large' | 'medium' | 'small';
|
|
73
90
|
/**
|
|
74
|
-
* All
|
|
75
|
-
* authenticated; there is no 'verified_human' variant.
|
|
91
|
+
* All five are neutral. The button asserts nothing about a person who has
|
|
92
|
+
* not yet authenticated; there is no 'verified_human' variant. 'verify_with'
|
|
93
|
+
* stays inside that rule: it names the act the button starts, not a state
|
|
94
|
+
* the person already holds — built for surfaces where the login IS a check
|
|
95
|
+
* (ZOREAL Meet's presence request), not a sign-in.
|
|
76
96
|
*/
|
|
77
|
-
text?: 'continue_with' | 'signin_with' | 'signup_with' | 'signin';
|
|
97
|
+
text?: 'continue_with' | 'signin_with' | 'signup_with' | 'signin' | 'verify_with';
|
|
78
98
|
shape?: 'rectangular' | 'pill' | 'square';
|
|
79
99
|
logo_alignment?: 'left' | 'center';
|
|
80
100
|
width?: string | number;
|
|
@@ -126,8 +146,21 @@ interface AuthCodeFlowOptions extends ZorealLoginRequestOptions {
|
|
|
126
146
|
redirect_uri?: string;
|
|
127
147
|
ux_mode?: 'popup' | 'redirect';
|
|
128
148
|
}
|
|
129
|
-
|
|
149
|
+
/**
|
|
150
|
+
* The button runs either flow, discriminated on `flow` exactly as
|
|
151
|
+
* useZorealLogin is: browser-direct (the default) hands onSuccess an ID
|
|
152
|
+
* token; auth-code hands it the code, PKCE verifier and nonce for YOUR
|
|
153
|
+
* backend to redeem. Added in 0.2.8 — before that the button was
|
|
154
|
+
* browser-direct only and auth-code integrations had to rebuild the button
|
|
155
|
+
* around the hook to get the same pixels.
|
|
156
|
+
*/
|
|
157
|
+
type ZorealLoginProps = ({
|
|
158
|
+
flow?: 'browser-direct';
|
|
130
159
|
onSuccess: (response: ZorealCredentialResponse) => void;
|
|
160
|
+
} | {
|
|
161
|
+
flow: 'auth-code';
|
|
162
|
+
onSuccess: (response: ZorealCodeResponse) => void;
|
|
163
|
+
}) & {
|
|
131
164
|
onError?: (error: NonOAuthError) => void;
|
|
132
165
|
containerProps?: React.ComponentPropsWithoutRef<'div'>;
|
|
133
166
|
} & ZorealLoginRequestOptions & ZorealButtonConfiguration;
|
package/dist/index.d.ts
CHANGED
|
@@ -37,8 +37,25 @@ interface PairingState {
|
|
|
37
37
|
* carrying nothing to render. Present on every callback of a QR/link flow.
|
|
38
38
|
*/
|
|
39
39
|
pairUrl?: string;
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* The provider-served SVG to show right now. Put it in an <img>; do not draw
|
|
42
|
+
* your own.
|
|
43
|
+
*
|
|
44
|
+
* IT CHANGES DURING THE PAIRING. The code on screen is one frame of a
|
|
45
|
+
* rotating sequence, and a frame the provider has moved past is refused when
|
|
46
|
+
* the phone tries to claim it, which is what stops a screenshot of the code
|
|
47
|
+
* from being relayed to someone else's browser. So render the qrUrl of the
|
|
48
|
+
* state you are handed, every time you are handed one; a UI that caches the
|
|
49
|
+
* first value shows a code that stops working seconds later.
|
|
50
|
+
*/
|
|
41
51
|
qrUrl?: string;
|
|
52
|
+
/**
|
|
53
|
+
* How often, in seconds, `qrUrl` is replaced while the pairing is pending.
|
|
54
|
+
* Informational: the SDK already emits a new state on that cadence, so a
|
|
55
|
+
* custom UI does not need a timer of its own. Absent on the app link, which
|
|
56
|
+
* has no QR.
|
|
57
|
+
*/
|
|
58
|
+
qrRefreshSeconds?: number;
|
|
42
59
|
/** True when the flow resolved to the app link (mobile) rather than a QR. */
|
|
43
60
|
appLink?: boolean;
|
|
44
61
|
/** Abandons this pairing: stops the poll. Wire it to your UI's cancel control. */
|
|
@@ -71,10 +88,13 @@ interface ZorealButtonConfiguration {
|
|
|
71
88
|
theme?: 'outline' | 'filled' | 'filled_black';
|
|
72
89
|
size?: 'large' | 'medium' | 'small';
|
|
73
90
|
/**
|
|
74
|
-
* All
|
|
75
|
-
* authenticated; there is no 'verified_human' variant.
|
|
91
|
+
* All five are neutral. The button asserts nothing about a person who has
|
|
92
|
+
* not yet authenticated; there is no 'verified_human' variant. 'verify_with'
|
|
93
|
+
* stays inside that rule: it names the act the button starts, not a state
|
|
94
|
+
* the person already holds — built for surfaces where the login IS a check
|
|
95
|
+
* (ZOREAL Meet's presence request), not a sign-in.
|
|
76
96
|
*/
|
|
77
|
-
text?: 'continue_with' | 'signin_with' | 'signup_with' | 'signin';
|
|
97
|
+
text?: 'continue_with' | 'signin_with' | 'signup_with' | 'signin' | 'verify_with';
|
|
78
98
|
shape?: 'rectangular' | 'pill' | 'square';
|
|
79
99
|
logo_alignment?: 'left' | 'center';
|
|
80
100
|
width?: string | number;
|
|
@@ -126,8 +146,21 @@ interface AuthCodeFlowOptions extends ZorealLoginRequestOptions {
|
|
|
126
146
|
redirect_uri?: string;
|
|
127
147
|
ux_mode?: 'popup' | 'redirect';
|
|
128
148
|
}
|
|
129
|
-
|
|
149
|
+
/**
|
|
150
|
+
* The button runs either flow, discriminated on `flow` exactly as
|
|
151
|
+
* useZorealLogin is: browser-direct (the default) hands onSuccess an ID
|
|
152
|
+
* token; auth-code hands it the code, PKCE verifier and nonce for YOUR
|
|
153
|
+
* backend to redeem. Added in 0.2.8 — before that the button was
|
|
154
|
+
* browser-direct only and auth-code integrations had to rebuild the button
|
|
155
|
+
* around the hook to get the same pixels.
|
|
156
|
+
*/
|
|
157
|
+
type ZorealLoginProps = ({
|
|
158
|
+
flow?: 'browser-direct';
|
|
130
159
|
onSuccess: (response: ZorealCredentialResponse) => void;
|
|
160
|
+
} | {
|
|
161
|
+
flow: 'auth-code';
|
|
162
|
+
onSuccess: (response: ZorealCodeResponse) => void;
|
|
163
|
+
}) & {
|
|
131
164
|
onError?: (error: NonOAuthError) => void;
|
|
132
165
|
containerProps?: React.ComponentPropsWithoutRef<'div'>;
|
|
133
166
|
} & ZorealLoginRequestOptions & ZorealButtonConfiguration;
|
package/dist/index.js
CHANGED
|
@@ -5,10 +5,11 @@ import { createContext, useContext, useMemo, useState as useState2 } from "react
|
|
|
5
5
|
|
|
6
6
|
// src/wire.ts
|
|
7
7
|
var WIRE_VERSION = 1;
|
|
8
|
-
var SDK_VERSION = "0.2.
|
|
8
|
+
var SDK_VERSION = "0.2.9";
|
|
9
9
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
10
10
|
var POLL_INTERVAL_MS = 2e3;
|
|
11
11
|
var POLL_INTERVAL_ENROLLING_MS = 5e3;
|
|
12
|
+
var DEFAULT_QR_REFRESH_SECONDS = 3;
|
|
12
13
|
|
|
13
14
|
// src/PairingModal.tsx
|
|
14
15
|
import { useEffect, useId, useRef, useState } from "react";
|
|
@@ -1113,6 +1114,23 @@ function PairingModal({
|
|
|
1113
1114
|
const deadlineRef = useRef(0);
|
|
1114
1115
|
const [remaining, setRemaining] = useState(Math.round(timeoutMs / 1e3));
|
|
1115
1116
|
const settled = state.status === "claimed" || state.status === "enrolling";
|
|
1117
|
+
const [frameUrl, setFrameUrl] = useState(qrUrl);
|
|
1118
|
+
useEffect(() => {
|
|
1119
|
+
if (settled || frameUrl === qrUrl) return;
|
|
1120
|
+
let abandoned = false;
|
|
1121
|
+
const next = new Image();
|
|
1122
|
+
next.onload = () => {
|
|
1123
|
+
if (!abandoned) setFrameUrl(qrUrl);
|
|
1124
|
+
};
|
|
1125
|
+
next.onerror = () => {
|
|
1126
|
+
};
|
|
1127
|
+
next.src = qrUrl;
|
|
1128
|
+
return () => {
|
|
1129
|
+
abandoned = true;
|
|
1130
|
+
next.onload = null;
|
|
1131
|
+
next.onerror = null;
|
|
1132
|
+
};
|
|
1133
|
+
}, [qrUrl, settled, frameUrl]);
|
|
1116
1134
|
const onCancelRef = useRef(onCancel);
|
|
1117
1135
|
onCancelRef.current = onCancel;
|
|
1118
1136
|
useEffect(() => {
|
|
@@ -1164,7 +1182,7 @@ function PairingModal({
|
|
|
1164
1182
|
/* @__PURE__ */ jsx3("h2", { id: titleId, className: cx("title"), children: settled ? t.titleApprove : t.title }),
|
|
1165
1183
|
/* @__PURE__ */ jsx3("p", { className: cx("body-text"), children: body }),
|
|
1166
1184
|
/* @__PURE__ */ jsxs3("div", { className: cx("qr-well"), children: [
|
|
1167
|
-
/* @__PURE__ */ jsx3("img", { className: cx("qr"), "data-spent": settled, src:
|
|
1185
|
+
/* @__PURE__ */ jsx3("img", { className: cx("qr"), "data-spent": settled, src: frameUrl, alt: t.qrAlt, width: 180, height: 180 }),
|
|
1168
1186
|
settled && /* @__PURE__ */ jsx3("span", { className: cx("qr-overlay"), children: /* @__PURE__ */ jsx3("span", { className: cx("qr-badge"), children: /* @__PURE__ */ jsx3(IconPhone, {}) }) })
|
|
1169
1187
|
] }),
|
|
1170
1188
|
/* @__PURE__ */ jsxs3("div", { className: cx("status"), children: [
|
|
@@ -1302,55 +1320,99 @@ async function startPairing(issuer, params) {
|
|
|
1302
1320
|
}
|
|
1303
1321
|
return body;
|
|
1304
1322
|
}
|
|
1323
|
+
function qrRefreshSecondsOf(started) {
|
|
1324
|
+
const seconds = started.qr_refresh_seconds;
|
|
1325
|
+
return typeof seconds === "number" && seconds > 0 ? seconds : DEFAULT_QR_REFRESH_SECONDS;
|
|
1326
|
+
}
|
|
1327
|
+
function qrFrameUrl(issuer, requestId) {
|
|
1328
|
+
return `${issuer}/pair/${encodeURIComponent(requestId)}/qr.svg?t=${Date.now()}`;
|
|
1329
|
+
}
|
|
1305
1330
|
var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
1306
1331
|
if (signal?.aborted) {
|
|
1307
1332
|
reject(new DOMException("aborted", "AbortError"));
|
|
1308
1333
|
return;
|
|
1309
1334
|
}
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
clearTimeout(t);
|
|
1335
|
+
const onAbort = () => {
|
|
1336
|
+
clearTimeout(timer);
|
|
1313
1337
|
reject(new DOMException("aborted", "AbortError"));
|
|
1314
|
-
}
|
|
1338
|
+
};
|
|
1339
|
+
const timer = setTimeout(() => {
|
|
1340
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1341
|
+
resolve();
|
|
1342
|
+
}, ms);
|
|
1343
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1315
1344
|
});
|
|
1316
|
-
async function pollUntilApproved(issuer, requestId, onState, signal) {
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1345
|
+
async function pollUntilApproved(issuer, requestId, onState, signal, options = {}) {
|
|
1346
|
+
let last = { status: "pending" };
|
|
1347
|
+
const emit = (state) => {
|
|
1348
|
+
last = state;
|
|
1349
|
+
onState?.(state);
|
|
1350
|
+
};
|
|
1351
|
+
let frames = null;
|
|
1352
|
+
const stopFrames = () => {
|
|
1353
|
+
frames?.abort();
|
|
1354
|
+
frames = null;
|
|
1355
|
+
};
|
|
1356
|
+
const startFrames = () => {
|
|
1357
|
+
const seconds = options.qrRefreshSeconds;
|
|
1358
|
+
if (frames || signal?.aborted || typeof seconds !== "number" || !(seconds > 0)) return;
|
|
1359
|
+
const period = seconds * 1e3;
|
|
1360
|
+
const controller = new AbortController();
|
|
1361
|
+
frames = controller;
|
|
1362
|
+
signal?.addEventListener("abort", stopFrames, { signal: controller.signal });
|
|
1363
|
+
void (async () => {
|
|
1364
|
+
let due = Date.now() + period;
|
|
1365
|
+
for (; ; ) {
|
|
1366
|
+
await sleep(Math.max(0, due - Date.now()), controller.signal);
|
|
1367
|
+
emit({ ...last, qrUrl: qrFrameUrl(issuer, requestId) });
|
|
1368
|
+
due = Date.now() + period;
|
|
1369
|
+
}
|
|
1370
|
+
})().catch(() => {
|
|
1332
1371
|
});
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1372
|
+
};
|
|
1373
|
+
try {
|
|
1374
|
+
for (; ; ) {
|
|
1375
|
+
const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {
|
|
1376
|
+
signal
|
|
1377
|
+
});
|
|
1378
|
+
const body = await parseJson(response);
|
|
1379
|
+
if (!response.ok) {
|
|
1380
|
+
throw new OAuthFlowError(
|
|
1381
|
+
body.error ?? "server_error",
|
|
1382
|
+
body.error_description ?? `Pairing status failed (${response.status})`
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
emit({
|
|
1386
|
+
status: body.status,
|
|
1387
|
+
expiresIn: body.expires_in,
|
|
1388
|
+
enrolmentDeadline: body.enrolment_deadline
|
|
1389
|
+
});
|
|
1390
|
+
if (body.status === "pending") startFrames();
|
|
1391
|
+
else stopFrames();
|
|
1392
|
+
switch (body.status) {
|
|
1393
|
+
case "approved":
|
|
1394
|
+
if (!body.code) {
|
|
1395
|
+
throw new OAuthFlowError("server_error", "approved with no authorization code");
|
|
1396
|
+
}
|
|
1397
|
+
return body.code;
|
|
1398
|
+
case "denied":
|
|
1399
|
+
throw new FlowAbandonedError({ type: "request_denied", description: body.error_description });
|
|
1400
|
+
case "expired":
|
|
1401
|
+
throw new FlowAbandonedError({ type: "request_expired", description: body.error_description });
|
|
1402
|
+
case "cancelled":
|
|
1403
|
+
throw new FlowAbandonedError({
|
|
1404
|
+
type: "request_expired",
|
|
1405
|
+
description: body.error_description ?? "the provider cancelled the pairing request"
|
|
1406
|
+
});
|
|
1407
|
+
case "enrolling":
|
|
1408
|
+
await sleep(POLL_INTERVAL_ENROLLING_MS, signal);
|
|
1409
|
+
break;
|
|
1410
|
+
default:
|
|
1411
|
+
await sleep(POLL_INTERVAL_MS, signal);
|
|
1412
|
+
}
|
|
1353
1413
|
}
|
|
1414
|
+
} finally {
|
|
1415
|
+
stopFrames();
|
|
1354
1416
|
}
|
|
1355
1417
|
}
|
|
1356
1418
|
async function exchangeCode(issuer, input) {
|
|
@@ -1377,6 +1439,11 @@ function isMobileUserAgent() {
|
|
|
1377
1439
|
if (typeof navigator === "undefined") return false;
|
|
1378
1440
|
return /android|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
1379
1441
|
}
|
|
1442
|
+
function resolveDisplay(display) {
|
|
1443
|
+
if (display === "link") return "link";
|
|
1444
|
+
if (display === "qr") return "qr";
|
|
1445
|
+
return isMobileUserAgent() ? "link" : "qr";
|
|
1446
|
+
}
|
|
1380
1447
|
|
|
1381
1448
|
// src/pkce.ts
|
|
1382
1449
|
var VERIFIER_BYTES = 32;
|
|
@@ -1423,6 +1490,8 @@ function useZorealFlow(options) {
|
|
|
1423
1490
|
const verifier = generateVerifier();
|
|
1424
1491
|
const state = generateState();
|
|
1425
1492
|
const nonce = generateState();
|
|
1493
|
+
const display = resolveDisplay(opts.display);
|
|
1494
|
+
const useAppLink = display === "link";
|
|
1426
1495
|
try {
|
|
1427
1496
|
const started = await startPairing(issuer, {
|
|
1428
1497
|
client_id: clientId,
|
|
@@ -1434,7 +1503,8 @@ function useZorealFlow(options) {
|
|
|
1434
1503
|
acr_values: Array.isArray(opts.acr_values) ? opts.acr_values.join(" ") : opts.acr_values,
|
|
1435
1504
|
max_age: opts.max_age,
|
|
1436
1505
|
prompt: opts.prompt,
|
|
1437
|
-
locale
|
|
1506
|
+
locale,
|
|
1507
|
+
display
|
|
1438
1508
|
});
|
|
1439
1509
|
let code;
|
|
1440
1510
|
let selectBy = "device";
|
|
@@ -1442,8 +1512,8 @@ function useZorealFlow(options) {
|
|
|
1442
1512
|
code = started.code;
|
|
1443
1513
|
selectBy = "session";
|
|
1444
1514
|
} else {
|
|
1445
|
-
const useAppLink = opts.display === "link" || opts.display !== "qr" && isMobileUserAgent();
|
|
1446
1515
|
selectBy = useAppLink ? "app_link" : "qr";
|
|
1516
|
+
const qrRefreshSeconds = qrRefreshSecondsOf(started);
|
|
1447
1517
|
const cancel = () => {
|
|
1448
1518
|
controller.abort();
|
|
1449
1519
|
setPairing(null);
|
|
@@ -1451,21 +1521,23 @@ function useZorealFlow(options) {
|
|
|
1451
1521
|
};
|
|
1452
1522
|
const surface = {
|
|
1453
1523
|
pairUrl: started.pair_url,
|
|
1454
|
-
qrUrl: `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`,
|
|
1455
1524
|
appLink: useAppLink,
|
|
1456
|
-
cancel
|
|
1525
|
+
cancel,
|
|
1526
|
+
// The app link has no QR and therefore no cadence to report.
|
|
1527
|
+
...useAppLink ? null : { qrRefreshSeconds }
|
|
1457
1528
|
};
|
|
1529
|
+
let qrUrl = `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`;
|
|
1458
1530
|
const active = {
|
|
1459
1531
|
requestId: started.request_id,
|
|
1460
1532
|
pairUrl: surface.pairUrl,
|
|
1461
|
-
qrUrl
|
|
1462
|
-
state: { status: "pending", expiresIn: started.expires_in, ...surface },
|
|
1533
|
+
qrUrl,
|
|
1534
|
+
state: { status: "pending", expiresIn: started.expires_in, qrUrl, ...surface },
|
|
1463
1535
|
appLink: useAppLink,
|
|
1464
1536
|
cancel
|
|
1465
1537
|
};
|
|
1466
1538
|
setPairing(active);
|
|
1467
1539
|
if (!useAppLink) {
|
|
1468
|
-
publishRef.current?.({ state: active.state, qrUrl
|
|
1540
|
+
publishRef.current?.({ state: active.state, qrUrl, cancel });
|
|
1469
1541
|
}
|
|
1470
1542
|
opts.onPairingStateChange?.(active.state);
|
|
1471
1543
|
if (useAppLink) {
|
|
@@ -1475,16 +1547,20 @@ function useZorealFlow(options) {
|
|
|
1475
1547
|
issuer,
|
|
1476
1548
|
started.request_id,
|
|
1477
1549
|
(s) => {
|
|
1478
|
-
|
|
1550
|
+
if (s.qrUrl) qrUrl = s.qrUrl;
|
|
1551
|
+
const enriched = { ...s, ...surface, qrUrl };
|
|
1479
1552
|
setPairing(
|
|
1480
|
-
(p) => p && p.requestId === started.request_id ? { ...p, state: enriched } : p
|
|
1553
|
+
(p) => p && p.requestId === started.request_id ? { ...p, qrUrl, state: enriched } : p
|
|
1481
1554
|
);
|
|
1482
1555
|
if (!useAppLink) {
|
|
1483
|
-
publishRef.current?.({ state: enriched, qrUrl
|
|
1556
|
+
publishRef.current?.({ state: enriched, qrUrl, cancel });
|
|
1484
1557
|
}
|
|
1485
1558
|
opts.onPairingStateChange?.(enriched);
|
|
1486
1559
|
},
|
|
1487
|
-
controller.signal
|
|
1560
|
+
controller.signal,
|
|
1561
|
+
// The app link has no QR to animate, and the provider answers 404
|
|
1562
|
+
// for one, so the frames are asked for only on the QR surface.
|
|
1563
|
+
{ qrRefreshSeconds: useAppLink ? void 0 : qrRefreshSeconds }
|
|
1488
1564
|
);
|
|
1489
1565
|
}
|
|
1490
1566
|
setPairing(null);
|
|
@@ -1555,7 +1631,8 @@ var TEXTS = {
|
|
|
1555
1631
|
continue_with: "Continue with ZOREAL",
|
|
1556
1632
|
signin_with: "Sign in with ZOREAL",
|
|
1557
1633
|
signup_with: "Sign up with ZOREAL",
|
|
1558
|
-
signin: "Sign in"
|
|
1634
|
+
signin: "Sign in",
|
|
1635
|
+
verify_with: "Verify with ZOREAL ID"
|
|
1559
1636
|
};
|
|
1560
1637
|
var SIZES = {
|
|
1561
1638
|
large: { height: 44, font: 15, pad: 20 },
|
|
@@ -1575,12 +1652,14 @@ function ZorealLogin(props) {
|
|
|
1575
1652
|
logo_alignment = "left",
|
|
1576
1653
|
width,
|
|
1577
1654
|
click_listener,
|
|
1655
|
+
flow = "browser-direct",
|
|
1578
1656
|
...request
|
|
1579
1657
|
} = props;
|
|
1580
1658
|
const { login } = useZorealFlow({
|
|
1581
1659
|
...request,
|
|
1582
|
-
flow
|
|
1583
|
-
onCredential: onSuccess,
|
|
1660
|
+
flow,
|
|
1661
|
+
onCredential: flow === "browser-direct" ? onSuccess : void 0,
|
|
1662
|
+
onCode: flow === "auth-code" ? onSuccess : void 0,
|
|
1584
1663
|
onError: (e) => onError?.({ type: "unknown", description: e.description ?? e.error }),
|
|
1585
1664
|
onNonOAuthError: (e) => onError?.(e)
|
|
1586
1665
|
});
|