@zoreal/oauth2-react 0.2.15 → 0.2.17
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 +1 -1
- package/dist/index.cjs +311 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -19
- package/dist/index.d.ts +0 -19
- package/dist/index.js +314 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -251,7 +251,7 @@ What the modal does:
|
|
|
251
251
|
|
|
252
252
|
| | |
|
|
253
253
|
| --- | --- |
|
|
254
|
-
| **Mobile** | No QR and no modal. The SDK
|
|
254
|
+
| **Mobile** | No QR and no modal. The tap itself is a navigation: the SDK sends the tab to the provider's `/pair/start` with the pairing's parameters, synchronously from the click, and the provider answers with a redirect to the pairing's universal link, which the ZOREAL ID app claims while the page stays put and polls. A browser hands a link to an app only inside a navigation the person began, which is why nothing is fetched first. With no app installed the same redirect lands on the page that installs it. Call `login()` from the click handler itself: `ZorealLogin` does, disables itself and runs a light round its edge until the flow ends; a site with its own button keeps that button and draws its own busy state from the tap until `onSuccess` or `onError` fires, or wraps it in `ZorealBusyRing` to get the same light (a wrapper: pass `block` for a full-width button, keep `overflow: hidden` off its ancestors, and expect `.parent > button` selectors to stop matching). Force one or the other with `display: 'qr'` / `'link'`. |
|
|
255
255
|
| **Live status** | The copy and the title follow the pairing: waiting for a scan, then waiting for approval once the holder has claimed the code (the spent QR blurs out behind a phone glyph). |
|
|
256
256
|
| **Title** | Says what the scan is for, inferred from the request: "Scan to sign in" for `openid`, `email` and `profile.name`; "Scan to verify your identity" once a document attribute such as `zoreal.age` or `profile.birthdate` is requested; "Scan to prove you are a real human" for `openid` alone with `acr_values: 'zoreal.live'`. Override with `intent`, one of `'sign-in'`, `'identify'`, `'presence'`, when the scope does not say. |
|
|
257
257
|
| **Countdown** | Counts down to expiry, turning amber under 20s. Reads the clock each tick rather than decrementing, so a backgrounded tab comes back honest. |
|
package/dist/index.cjs
CHANGED
|
@@ -41,7 +41,7 @@ var import_react2 = require("react");
|
|
|
41
41
|
|
|
42
42
|
// src/wire.ts
|
|
43
43
|
var WIRE_VERSION = 1;
|
|
44
|
-
var SDK_VERSION = "0.2.
|
|
44
|
+
var SDK_VERSION = "0.2.17";
|
|
45
45
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
46
46
|
var POLL_INTERVAL_MS = 2e3;
|
|
47
47
|
var POLL_INTERVAL_ENROLLING_MS = 5e3;
|
|
@@ -1370,10 +1370,15 @@ var CSS = `
|
|
|
1370
1370
|
sides and lights two edges at once near the ends. So here the light is a
|
|
1371
1371
|
dash on an SVG outline, which moves at one speed the whole way round
|
|
1372
1372
|
whatever the shape, drawn with the well's tokens: its colour and head
|
|
1373
|
-
tint, its line width, its halo, its four second lap.
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1373
|
+
tint, its line width, its halo, its four second lap. The outline's length
|
|
1374
|
+
is measured by the component and set as --zrl-ring-len, and every dash
|
|
1375
|
+
and offset is a fraction of it, because pathLength does not scale dash
|
|
1376
|
+
values given from CSS. A stroke cannot fade along its length, so the tail
|
|
1377
|
+
is a stack of dashes sharing one head, each shorter and more opaque than
|
|
1378
|
+
the one under it, with opacities chosen so the stack composes to a
|
|
1379
|
+
straight fade from the head to nothing three tenths of the way back; the
|
|
1380
|
+
component sets each layer's length, offset and opacity. Shown only while
|
|
1381
|
+
busy. */
|
|
1377
1382
|
.${PREFIX}-ring {
|
|
1378
1383
|
position: relative;
|
|
1379
1384
|
display: inline-flex;
|
|
@@ -1392,6 +1397,7 @@ var CSS = `
|
|
|
1392
1397
|
}
|
|
1393
1398
|
.${PREFIX}-ring[data-busy="true"] > .${PREFIX}-ring-svg { opacity: 1; }
|
|
1394
1399
|
.${PREFIX}-ring-svg rect {
|
|
1400
|
+
--zrl-l: var(--zrl-ring-len, 600px);
|
|
1395
1401
|
x: 2px;
|
|
1396
1402
|
y: 2px;
|
|
1397
1403
|
width: calc(100% - 4px);
|
|
@@ -1400,23 +1406,18 @@ var CSS = `
|
|
|
1400
1406
|
stroke: var(--zrl-beam);
|
|
1401
1407
|
stroke-width: var(--zrl-beam-line);
|
|
1402
1408
|
stroke-linecap: round;
|
|
1409
|
+
stroke-dashoffset: var(--zrl-s, 0px);
|
|
1403
1410
|
animation: ${PREFIX}-dash var(--zrl-beam-time) linear infinite;
|
|
1404
1411
|
}
|
|
1405
|
-
.${PREFIX}-ring-
|
|
1406
|
-
.${PREFIX}-ring-body { stroke-dasharray: 16 84; stroke-dashoffset: -14; opacity: 0.8; }
|
|
1407
|
-
.${PREFIX}-ring-head { stroke-dasharray: 5 95; stroke-dashoffset: -25; stroke: var(--zrl-beam-head); }
|
|
1412
|
+
.${PREFIX}-ring-head { stroke: var(--zrl-beam-head); }
|
|
1408
1413
|
.${PREFIX}-ring-halo {
|
|
1409
|
-
stroke-dasharray: 30 70;
|
|
1410
|
-
stroke-dashoffset: 0;
|
|
1411
1414
|
stroke-width: calc(var(--zrl-glow-core) * 2 + var(--zrl-beam-line));
|
|
1412
|
-
opacity: calc(var(--zrl-glow-opacity) * 0.5);
|
|
1413
1415
|
filter: blur(var(--zrl-glow-blur));
|
|
1414
1416
|
}
|
|
1415
1417
|
@keyframes ${PREFIX}-dash {
|
|
1416
|
-
|
|
1418
|
+
from { stroke-dashoffset: var(--zrl-s, 0px); }
|
|
1419
|
+
to { stroke-dashoffset: calc(var(--zrl-s, 0px) - var(--zrl-l)); }
|
|
1417
1420
|
}
|
|
1418
|
-
.${PREFIX}-ring-body { --zrl-dash-start: -14; }
|
|
1419
|
-
.${PREFIX}-ring-head { --zrl-dash-start: -25; }
|
|
1420
1421
|
@media (prefers-reduced-motion: reduce) {
|
|
1421
1422
|
.${PREFIX}-ring-svg rect { animation: none; stroke-dasharray: none; opacity: 0.45; }
|
|
1422
1423
|
.${PREFIX}-ring-halo, .${PREFIX}-ring-head { display: none; }
|
|
@@ -1690,6 +1691,20 @@ async function startPairing(issuer, params) {
|
|
|
1690
1691
|
}
|
|
1691
1692
|
return body;
|
|
1692
1693
|
}
|
|
1694
|
+
function sameDeviceStartUrl(issuer, params) {
|
|
1695
|
+
const query = new URLSearchParams();
|
|
1696
|
+
const all = {
|
|
1697
|
+
...params,
|
|
1698
|
+
code_challenge_method: "S256",
|
|
1699
|
+
wire_version: WIRE_VERSION,
|
|
1700
|
+
sdk: `@zoreal/oauth2-react/${SDK_VERSION}`
|
|
1701
|
+
};
|
|
1702
|
+
for (const [key, value] of Object.entries(all)) {
|
|
1703
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
1704
|
+
query.set(key, String(value));
|
|
1705
|
+
}
|
|
1706
|
+
return `${issuer}/pair/start?${query.toString()}`;
|
|
1707
|
+
}
|
|
1693
1708
|
function qrRefreshSecondsOf(started) {
|
|
1694
1709
|
const seconds = started.qr_refresh_seconds;
|
|
1695
1710
|
return typeof seconds === "number" && seconds > 0 ? seconds : DEFAULT_QR_REFRESH_SECONDS;
|
|
@@ -1746,6 +1761,11 @@ async function pollUntilApproved(issuer, requestId, onState, signal, options = {
|
|
|
1746
1761
|
signal
|
|
1747
1762
|
});
|
|
1748
1763
|
const body = await parseJson(response);
|
|
1764
|
+
if (response.status === 404 && (options.tolerateUnknownUntil ?? 0) > Date.now()) {
|
|
1765
|
+
emit({ status: "pending" });
|
|
1766
|
+
await sleep(POLL_INTERVAL_MS, signal);
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1749
1769
|
if (!response.ok) {
|
|
1750
1770
|
throw new OAuthFlowError(
|
|
1751
1771
|
body.error ?? "server_error",
|
|
@@ -1827,11 +1847,154 @@ async function challengeS256(verifier) {
|
|
|
1827
1847
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
1828
1848
|
return base64url(new Uint8Array(digest));
|
|
1829
1849
|
}
|
|
1850
|
+
function challengeS256Sync(verifier) {
|
|
1851
|
+
return base64url(sha256(new TextEncoder().encode(verifier)));
|
|
1852
|
+
}
|
|
1853
|
+
var K = new Uint32Array([
|
|
1854
|
+
1116352408,
|
|
1855
|
+
1899447441,
|
|
1856
|
+
3049323471,
|
|
1857
|
+
3921009573,
|
|
1858
|
+
961987163,
|
|
1859
|
+
1508970993,
|
|
1860
|
+
2453635748,
|
|
1861
|
+
2870763221,
|
|
1862
|
+
3624381080,
|
|
1863
|
+
310598401,
|
|
1864
|
+
607225278,
|
|
1865
|
+
1426881987,
|
|
1866
|
+
1925078388,
|
|
1867
|
+
2162078206,
|
|
1868
|
+
2614888103,
|
|
1869
|
+
3248222580,
|
|
1870
|
+
3835390401,
|
|
1871
|
+
4022224774,
|
|
1872
|
+
264347078,
|
|
1873
|
+
604807628,
|
|
1874
|
+
770255983,
|
|
1875
|
+
1249150122,
|
|
1876
|
+
1555081692,
|
|
1877
|
+
1996064986,
|
|
1878
|
+
2554220882,
|
|
1879
|
+
2821834349,
|
|
1880
|
+
2952996808,
|
|
1881
|
+
3210313671,
|
|
1882
|
+
3336571891,
|
|
1883
|
+
3584528711,
|
|
1884
|
+
113926993,
|
|
1885
|
+
338241895,
|
|
1886
|
+
666307205,
|
|
1887
|
+
773529912,
|
|
1888
|
+
1294757372,
|
|
1889
|
+
1396182291,
|
|
1890
|
+
1695183700,
|
|
1891
|
+
1986661051,
|
|
1892
|
+
2177026350,
|
|
1893
|
+
2456956037,
|
|
1894
|
+
2730485921,
|
|
1895
|
+
2820302411,
|
|
1896
|
+
3259730800,
|
|
1897
|
+
3345764771,
|
|
1898
|
+
3516065817,
|
|
1899
|
+
3600352804,
|
|
1900
|
+
4094571909,
|
|
1901
|
+
275423344,
|
|
1902
|
+
430227734,
|
|
1903
|
+
506948616,
|
|
1904
|
+
659060556,
|
|
1905
|
+
883997877,
|
|
1906
|
+
958139571,
|
|
1907
|
+
1322822218,
|
|
1908
|
+
1537002063,
|
|
1909
|
+
1747873779,
|
|
1910
|
+
1955562222,
|
|
1911
|
+
2024104815,
|
|
1912
|
+
2227730452,
|
|
1913
|
+
2361852424,
|
|
1914
|
+
2428436474,
|
|
1915
|
+
2756734187,
|
|
1916
|
+
3204031479,
|
|
1917
|
+
3329325298
|
|
1918
|
+
]);
|
|
1919
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
1920
|
+
function sha256(message) {
|
|
1921
|
+
const H = new Uint32Array([
|
|
1922
|
+
1779033703,
|
|
1923
|
+
3144134277,
|
|
1924
|
+
1013904242,
|
|
1925
|
+
2773480762,
|
|
1926
|
+
1359893119,
|
|
1927
|
+
2600822924,
|
|
1928
|
+
528734635,
|
|
1929
|
+
1541459225
|
|
1930
|
+
]);
|
|
1931
|
+
const length = message.length;
|
|
1932
|
+
const padded = new Uint8Array(length + 9 + 63 >> 6 << 6);
|
|
1933
|
+
padded.set(message);
|
|
1934
|
+
padded[length] = 128;
|
|
1935
|
+
const view = new DataView(padded.buffer);
|
|
1936
|
+
const bits = length * 8;
|
|
1937
|
+
view.setUint32(padded.length - 8, Math.floor(bits / 4294967296));
|
|
1938
|
+
view.setUint32(padded.length - 4, bits >>> 0);
|
|
1939
|
+
const W = new Uint32Array(64);
|
|
1940
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
1941
|
+
for (let i = 0; i < 16; i++) W[i] = view.getUint32(offset + i * 4);
|
|
1942
|
+
for (let i = 16; i < 64; i++) {
|
|
1943
|
+
const w15 = W[i - 15];
|
|
1944
|
+
const w2 = W[i - 2];
|
|
1945
|
+
const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ w15 >>> 3;
|
|
1946
|
+
const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ w2 >>> 10;
|
|
1947
|
+
W[i] = W[i - 16] + s0 + W[i - 7] + s1 >>> 0;
|
|
1948
|
+
}
|
|
1949
|
+
let [a, b, c, d, e, f, g, h] = H;
|
|
1950
|
+
for (let i = 0; i < 64; i++) {
|
|
1951
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
1952
|
+
const ch = e & f ^ ~e & g;
|
|
1953
|
+
const t1 = h + S1 + ch + K[i] + W[i] >>> 0;
|
|
1954
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
1955
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
1956
|
+
const t2 = S0 + maj >>> 0;
|
|
1957
|
+
h = g;
|
|
1958
|
+
g = f;
|
|
1959
|
+
f = e;
|
|
1960
|
+
e = d + t1 >>> 0;
|
|
1961
|
+
d = c;
|
|
1962
|
+
c = b;
|
|
1963
|
+
b = a;
|
|
1964
|
+
a = t1 + t2 >>> 0;
|
|
1965
|
+
}
|
|
1966
|
+
H[0] = H[0] + a >>> 0;
|
|
1967
|
+
H[1] = H[1] + b >>> 0;
|
|
1968
|
+
H[2] = H[2] + c >>> 0;
|
|
1969
|
+
H[3] = H[3] + d >>> 0;
|
|
1970
|
+
H[4] = H[4] + e >>> 0;
|
|
1971
|
+
H[5] = H[5] + f >>> 0;
|
|
1972
|
+
H[6] = H[6] + g >>> 0;
|
|
1973
|
+
H[7] = H[7] + h >>> 0;
|
|
1974
|
+
}
|
|
1975
|
+
const out = new Uint8Array(32);
|
|
1976
|
+
const outView = new DataView(out.buffer);
|
|
1977
|
+
for (let i = 0; i < 8; i++) outView.setUint32(i * 4, H[i]);
|
|
1978
|
+
return out;
|
|
1979
|
+
}
|
|
1830
1980
|
function generateState() {
|
|
1831
1981
|
const bytes = new Uint8Array(16);
|
|
1832
1982
|
crypto.getRandomValues(bytes);
|
|
1833
1983
|
return base64url(bytes);
|
|
1834
1984
|
}
|
|
1985
|
+
var TOKEN_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
1986
|
+
function generateRequestId() {
|
|
1987
|
+
let out = "";
|
|
1988
|
+
const bytes = new Uint8Array(64);
|
|
1989
|
+
while (out.length < 32) {
|
|
1990
|
+
crypto.getRandomValues(bytes);
|
|
1991
|
+
for (const byte of bytes) {
|
|
1992
|
+
if (byte >= 248 || out.length === 32) continue;
|
|
1993
|
+
out += TOKEN_ALPHABET[byte % 62];
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
return out;
|
|
1997
|
+
}
|
|
1835
1998
|
|
|
1836
1999
|
// src/useZorealLogin.ts
|
|
1837
2000
|
function useZorealFlow(options) {
|
|
@@ -1864,76 +2027,119 @@ function useZorealFlow(options) {
|
|
|
1864
2027
|
const useAppLink = display === "link";
|
|
1865
2028
|
const intent = resolveIntent(opts.intent, opts.scope, opts.acr_values);
|
|
1866
2029
|
try {
|
|
1867
|
-
const started = await startPairing(issuer, {
|
|
1868
|
-
client_id: clientId,
|
|
1869
|
-
scope: opts.scope ?? "openid",
|
|
1870
|
-
state,
|
|
1871
|
-
nonce,
|
|
1872
|
-
code_challenge: await challengeS256(verifier),
|
|
1873
|
-
redirect_uri: flow === "auth-code" ? opts.redirect_uri : void 0,
|
|
1874
|
-
acr_values: Array.isArray(opts.acr_values) ? opts.acr_values.join(" ") : opts.acr_values,
|
|
1875
|
-
max_age: opts.max_age,
|
|
1876
|
-
prompt: opts.prompt,
|
|
1877
|
-
locale,
|
|
1878
|
-
display
|
|
1879
|
-
});
|
|
1880
2030
|
let code;
|
|
1881
2031
|
let selectBy = "device";
|
|
1882
|
-
if (
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2032
|
+
if (useAppLink) {
|
|
2033
|
+
const requestId = generateRequestId();
|
|
2034
|
+
const startUrl = sameDeviceStartUrl(issuer, {
|
|
2035
|
+
client_id: clientId,
|
|
2036
|
+
scope: opts.scope ?? "openid",
|
|
2037
|
+
state,
|
|
2038
|
+
nonce,
|
|
2039
|
+
code_challenge: challengeS256Sync(verifier),
|
|
2040
|
+
redirect_uri: flow === "auth-code" ? opts.redirect_uri : void 0,
|
|
2041
|
+
acr_values: Array.isArray(opts.acr_values) ? opts.acr_values.join(" ") : opts.acr_values,
|
|
2042
|
+
max_age: opts.max_age,
|
|
2043
|
+
prompt: opts.prompt,
|
|
2044
|
+
locale,
|
|
2045
|
+
request_id: requestId,
|
|
2046
|
+
origin: window.location.origin
|
|
2047
|
+
});
|
|
2048
|
+
selectBy = "app_link";
|
|
1888
2049
|
const cancel = () => {
|
|
1889
2050
|
controller.abort();
|
|
1890
2051
|
setPairing(null);
|
|
1891
|
-
publishRef.current?.(null);
|
|
1892
2052
|
};
|
|
1893
|
-
const surface = {
|
|
1894
|
-
pairUrl: started.pair_url,
|
|
1895
|
-
appLink: useAppLink,
|
|
1896
|
-
intent,
|
|
1897
|
-
cancel,
|
|
1898
|
-
// The app link has no QR and therefore no cadence to report.
|
|
1899
|
-
...useAppLink ? null : { qrRefreshSeconds }
|
|
1900
|
-
};
|
|
1901
|
-
let qrUrl = `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`;
|
|
2053
|
+
const surface = { pairUrl: startUrl, appLink: true, intent, cancel };
|
|
1902
2054
|
const active = {
|
|
1903
|
-
requestId
|
|
1904
|
-
pairUrl:
|
|
1905
|
-
qrUrl,
|
|
1906
|
-
state: { status: "pending",
|
|
1907
|
-
appLink:
|
|
2055
|
+
requestId,
|
|
2056
|
+
pairUrl: startUrl,
|
|
2057
|
+
qrUrl: "",
|
|
2058
|
+
state: { status: "pending", ...surface },
|
|
2059
|
+
appLink: true,
|
|
1908
2060
|
cancel
|
|
1909
2061
|
};
|
|
1910
2062
|
setPairing(active);
|
|
1911
|
-
if (!useAppLink) {
|
|
1912
|
-
publishRef.current?.({ state: active.state, qrUrl, intent, cancel });
|
|
1913
|
-
}
|
|
1914
2063
|
opts.onPairingStateChange?.(active.state);
|
|
1915
|
-
|
|
1916
|
-
window.location.assign(started.pair_url);
|
|
1917
|
-
}
|
|
2064
|
+
window.location.assign(startUrl);
|
|
1918
2065
|
code = await pollUntilApproved(
|
|
1919
2066
|
issuer,
|
|
1920
|
-
|
|
2067
|
+
requestId,
|
|
1921
2068
|
(s) => {
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
setPairing(
|
|
1925
|
-
(p) => p && p.requestId === started.request_id ? { ...p, qrUrl, state: enriched } : p
|
|
1926
|
-
);
|
|
1927
|
-
if (!useAppLink) {
|
|
1928
|
-
publishRef.current?.({ state: enriched, qrUrl, intent, cancel });
|
|
1929
|
-
}
|
|
2069
|
+
const enriched = { ...s, ...surface };
|
|
2070
|
+
setPairing((p) => p && p.requestId === requestId ? { ...p, state: enriched } : p);
|
|
1930
2071
|
opts.onPairingStateChange?.(enriched);
|
|
1931
2072
|
},
|
|
1932
2073
|
controller.signal,
|
|
1933
|
-
|
|
1934
|
-
// for one, so the frames are asked for only on the QR surface.
|
|
1935
|
-
{ qrRefreshSeconds: useAppLink ? void 0 : qrRefreshSeconds }
|
|
2074
|
+
{ tolerateUnknownUntil: Date.now() + 15e3 }
|
|
1936
2075
|
);
|
|
2076
|
+
} else {
|
|
2077
|
+
const started = await startPairing(issuer, {
|
|
2078
|
+
client_id: clientId,
|
|
2079
|
+
scope: opts.scope ?? "openid",
|
|
2080
|
+
state,
|
|
2081
|
+
nonce,
|
|
2082
|
+
code_challenge: await challengeS256(verifier),
|
|
2083
|
+
redirect_uri: flow === "auth-code" ? opts.redirect_uri : void 0,
|
|
2084
|
+
acr_values: Array.isArray(opts.acr_values) ? opts.acr_values.join(" ") : opts.acr_values,
|
|
2085
|
+
max_age: opts.max_age,
|
|
2086
|
+
prompt: opts.prompt,
|
|
2087
|
+
locale,
|
|
2088
|
+
display: "qr"
|
|
2089
|
+
});
|
|
2090
|
+
if ("code" in started) {
|
|
2091
|
+
code = started.code;
|
|
2092
|
+
selectBy = "session";
|
|
2093
|
+
} else {
|
|
2094
|
+
selectBy = "qr";
|
|
2095
|
+
const qrRefreshSeconds = qrRefreshSecondsOf(started);
|
|
2096
|
+
const cancel = () => {
|
|
2097
|
+
controller.abort();
|
|
2098
|
+
setPairing(null);
|
|
2099
|
+
publishRef.current?.(null);
|
|
2100
|
+
};
|
|
2101
|
+
const surface = {
|
|
2102
|
+
pairUrl: started.pair_url,
|
|
2103
|
+
appLink: false,
|
|
2104
|
+
intent,
|
|
2105
|
+
cancel,
|
|
2106
|
+
qrRefreshSeconds
|
|
2107
|
+
};
|
|
2108
|
+
let qrUrl = `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`;
|
|
2109
|
+
const active = {
|
|
2110
|
+
requestId: started.request_id,
|
|
2111
|
+
pairUrl: surface.pairUrl,
|
|
2112
|
+
qrUrl,
|
|
2113
|
+
state: { status: "pending", expiresIn: started.expires_in, qrUrl, ...surface },
|
|
2114
|
+
appLink: false,
|
|
2115
|
+
cancel
|
|
2116
|
+
};
|
|
2117
|
+
setPairing(active);
|
|
2118
|
+
if (!useAppLink) {
|
|
2119
|
+
publishRef.current?.({ state: active.state, qrUrl, intent, cancel });
|
|
2120
|
+
}
|
|
2121
|
+
opts.onPairingStateChange?.(active.state);
|
|
2122
|
+
if (useAppLink) {
|
|
2123
|
+
window.location.assign(started.pair_url);
|
|
2124
|
+
}
|
|
2125
|
+
code = await pollUntilApproved(
|
|
2126
|
+
issuer,
|
|
2127
|
+
started.request_id,
|
|
2128
|
+
(s) => {
|
|
2129
|
+
if (s.qrUrl) qrUrl = s.qrUrl;
|
|
2130
|
+
const enriched = { ...s, ...surface, qrUrl };
|
|
2131
|
+
setPairing(
|
|
2132
|
+
(p) => p && p.requestId === started.request_id ? { ...p, qrUrl, state: enriched } : p
|
|
2133
|
+
);
|
|
2134
|
+
if (!useAppLink) {
|
|
2135
|
+
publishRef.current?.({ state: enriched, qrUrl, intent, cancel });
|
|
2136
|
+
}
|
|
2137
|
+
opts.onPairingStateChange?.(enriched);
|
|
2138
|
+
},
|
|
2139
|
+
controller.signal,
|
|
2140
|
+
{ qrRefreshSeconds }
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
1937
2143
|
}
|
|
1938
2144
|
setPairing(null);
|
|
1939
2145
|
publishRef.current?.(null);
|
|
@@ -2000,6 +2206,9 @@ function useZorealLogin(options) {
|
|
|
2000
2206
|
// src/ring.tsx
|
|
2001
2207
|
var import_react4 = require("react");
|
|
2002
2208
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2209
|
+
var TAIL = 0.3;
|
|
2210
|
+
var STACK = Array.from({ length: 12 }, (_, i) => 12 - i);
|
|
2211
|
+
var HALO = [3, 2, 1];
|
|
2003
2212
|
function ZorealBusyRing({
|
|
2004
2213
|
busy,
|
|
2005
2214
|
radius = 8,
|
|
@@ -2012,11 +2221,42 @@ function ZorealBusyRing({
|
|
|
2012
2221
|
(0, import_react4.useEffect)(() => {
|
|
2013
2222
|
ensureStyles();
|
|
2014
2223
|
}, []);
|
|
2224
|
+
const wrapRef = (0, import_react4.useRef)(null);
|
|
2225
|
+
const measureRef = (0, import_react4.useRef)(null);
|
|
2226
|
+
(0, import_react4.useLayoutEffect)(() => {
|
|
2227
|
+
const wrap = wrapRef.current;
|
|
2228
|
+
const rect = measureRef.current;
|
|
2229
|
+
if (!wrap || !rect) return;
|
|
2230
|
+
const measure = () => {
|
|
2231
|
+
const length = rect.getTotalLength();
|
|
2232
|
+
if (length > 0) wrap.style.setProperty("--zrl-ring-len", `${length}px`);
|
|
2233
|
+
};
|
|
2234
|
+
measure();
|
|
2235
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
2236
|
+
const observer = new ResizeObserver(measure);
|
|
2237
|
+
observer.observe(wrap);
|
|
2238
|
+
return () => observer.disconnect();
|
|
2239
|
+
}, [radius]);
|
|
2015
2240
|
const rx = radius + 2;
|
|
2016
|
-
const layer = (name) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2241
|
+
const layer = (key, name, len, alpha, ref) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2242
|
+
"rect",
|
|
2243
|
+
{
|
|
2244
|
+
ref,
|
|
2245
|
+
className: cx(name),
|
|
2246
|
+
rx,
|
|
2247
|
+
ry: rx,
|
|
2248
|
+
style: {
|
|
2249
|
+
strokeDasharray: `calc(var(--zrl-l) * ${len}) calc(var(--zrl-l) * ${1 - len})`,
|
|
2250
|
+
"--zrl-s": `calc(var(--zrl-l) * ${-(TAIL - len)})`,
|
|
2251
|
+
opacity: alpha
|
|
2252
|
+
}
|
|
2253
|
+
},
|
|
2254
|
+
key
|
|
2255
|
+
);
|
|
2017
2256
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2018
2257
|
"span",
|
|
2019
2258
|
{
|
|
2259
|
+
ref: wrapRef,
|
|
2020
2260
|
className: className ? `${cx("root")} ${cx("ring")} ${className}` : `${cx("root")} ${cx("ring")}`,
|
|
2021
2261
|
"data-theme": theme,
|
|
2022
2262
|
"data-busy": busy,
|
|
@@ -2024,10 +2264,10 @@ function ZorealBusyRing({
|
|
|
2024
2264
|
children: [
|
|
2025
2265
|
children,
|
|
2026
2266
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { className: cx("ring-svg"), "aria-hidden": "true", children: [
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
layer("ring-head")
|
|
2267
|
+
HALO.map(
|
|
2268
|
+
(n, i) => layer(`h${n}`, "ring-halo", TAIL * n / HALO.length, `calc(var(--zrl-glow-opacity) * 0.6 / ${n})`, i === 0 ? measureRef : void 0)
|
|
2269
|
+
),
|
|
2270
|
+
STACK.map((n) => layer(`t${n}`, n <= 2 ? "ring-head" : "ring-tail", TAIL * n / STACK.length, String(1 / n)))
|
|
2031
2271
|
] })
|
|
2032
2272
|
]
|
|
2033
2273
|
}
|