@zoreal/oauth2-js 0.1.16 → 0.1.18
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 +294 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +294 -96
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,7 +140,7 @@ is nothing to configure and nothing to draw.
|
|
|
140
140
|
|
|
141
141
|
| | |
|
|
142
142
|
| --- | --- |
|
|
143
|
-
| **Mobile** | No QR and no modal. `startLogin`
|
|
143
|
+
| **Mobile** | No QR and no modal. The tap itself is a navigation: `startLogin` sends the tab to the provider's `/pair/start` with the pairing's parameters, synchronously, 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 `startLogin` from the click handler itself, disable the button, and show it working until the promise settles. Force one or the other with `display: 'qr'` / `'link'`. The choice is made before the pairing is created, because the provider binds the surface there: a link-mode pairing is claimed only by the app that opened that exact link, and has no QR at all. |
|
|
144
144
|
| **Live status** | Copy and title follow the pairing: waiting for a scan, then waiting for approval once the code is claimed (the spent QR blurs out behind a phone glyph). |
|
|
145
145
|
| **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. |
|
|
146
146
|
| **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
|
@@ -60,7 +60,7 @@ function unsafeClaims(idToken) {
|
|
|
60
60
|
|
|
61
61
|
// src/wire.ts
|
|
62
62
|
var WIRE_VERSION = 1;
|
|
63
|
-
var SDK_VERSION = "0.1.
|
|
63
|
+
var SDK_VERSION = "0.1.18";
|
|
64
64
|
var SDK_NAME = "@zoreal/oauth2-js";
|
|
65
65
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
66
66
|
var POLL_INTERVAL_MS = 2e3;
|
|
@@ -109,6 +109,20 @@ async function startPairing(issuer, params, signal) {
|
|
|
109
109
|
}
|
|
110
110
|
return body;
|
|
111
111
|
}
|
|
112
|
+
function sameDeviceStartUrl(issuer, params) {
|
|
113
|
+
const query = new URLSearchParams();
|
|
114
|
+
const all = {
|
|
115
|
+
...params,
|
|
116
|
+
code_challenge_method: "S256",
|
|
117
|
+
wire_version: WIRE_VERSION,
|
|
118
|
+
sdk: `${SDK_NAME}/${SDK_VERSION}`
|
|
119
|
+
};
|
|
120
|
+
for (const [key, value] of Object.entries(all)) {
|
|
121
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
122
|
+
query.set(key, String(value));
|
|
123
|
+
}
|
|
124
|
+
return `${issuer}/pair/start?${query.toString()}`;
|
|
125
|
+
}
|
|
112
126
|
var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
113
127
|
if (signal?.aborted) {
|
|
114
128
|
reject(new DOMException("aborted", "AbortError"));
|
|
@@ -124,12 +138,17 @@ var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
|
124
138
|
}, ms);
|
|
125
139
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
126
140
|
});
|
|
127
|
-
async function pollUntilApproved(issuer, requestId, onState, signal) {
|
|
141
|
+
async function pollUntilApproved(issuer, requestId, onState, signal, options = {}) {
|
|
128
142
|
for (; ; ) {
|
|
129
143
|
const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {
|
|
130
144
|
signal
|
|
131
145
|
});
|
|
132
146
|
const body = await parseJson(response);
|
|
147
|
+
if (response.status === 404 && (options.tolerateUnknownUntil ?? 0) > Date.now()) {
|
|
148
|
+
onState?.({ status: "pending" });
|
|
149
|
+
await sleep(POLL_INTERVAL_MS, signal);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
133
152
|
if (!response.ok) {
|
|
134
153
|
throw new OAuthFlowError(
|
|
135
154
|
body.error ?? "server_error",
|
|
@@ -1441,10 +1460,15 @@ var CSS = `
|
|
|
1441
1460
|
sides and lights two edges at once near the ends. So here the light is a
|
|
1442
1461
|
dash on an SVG outline, which moves at one speed the whole way round
|
|
1443
1462
|
whatever the shape, drawn with the well's tokens: its colour and head
|
|
1444
|
-
tint, its line width, its halo, its four second lap.
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1463
|
+
tint, its line width, its halo, its four second lap. The outline's length
|
|
1464
|
+
is measured by the component and set as --zrl-ring-len, and every dash
|
|
1465
|
+
and offset is a fraction of it, because pathLength does not scale dash
|
|
1466
|
+
values given from CSS. A stroke cannot fade along its length, so the tail
|
|
1467
|
+
is a stack of dashes sharing one head, each shorter and more opaque than
|
|
1468
|
+
the one under it, with opacities chosen so the stack composes to a
|
|
1469
|
+
straight fade from the head to nothing three tenths of the way back; the
|
|
1470
|
+
component sets each layer's length, offset and opacity. Shown only while
|
|
1471
|
+
busy. */
|
|
1448
1472
|
.${PREFIX}-ring {
|
|
1449
1473
|
position: relative;
|
|
1450
1474
|
display: inline-flex;
|
|
@@ -1463,6 +1487,7 @@ var CSS = `
|
|
|
1463
1487
|
}
|
|
1464
1488
|
.${PREFIX}-ring[data-busy="true"] > .${PREFIX}-ring-svg { opacity: 1; }
|
|
1465
1489
|
.${PREFIX}-ring-svg rect {
|
|
1490
|
+
--zrl-l: var(--zrl-ring-len, 600px);
|
|
1466
1491
|
x: 2px;
|
|
1467
1492
|
y: 2px;
|
|
1468
1493
|
width: calc(100% - 4px);
|
|
@@ -1471,23 +1496,18 @@ var CSS = `
|
|
|
1471
1496
|
stroke: var(--zrl-beam);
|
|
1472
1497
|
stroke-width: var(--zrl-beam-line);
|
|
1473
1498
|
stroke-linecap: round;
|
|
1499
|
+
stroke-dashoffset: var(--zrl-s, 0px);
|
|
1474
1500
|
animation: ${PREFIX}-dash var(--zrl-beam-time) linear infinite;
|
|
1475
1501
|
}
|
|
1476
|
-
.${PREFIX}-ring-
|
|
1477
|
-
.${PREFIX}-ring-body { stroke-dasharray: 16 84; stroke-dashoffset: -14; opacity: 0.8; }
|
|
1478
|
-
.${PREFIX}-ring-head { stroke-dasharray: 5 95; stroke-dashoffset: -25; stroke: var(--zrl-beam-head); }
|
|
1502
|
+
.${PREFIX}-ring-head { stroke: var(--zrl-beam-head); }
|
|
1479
1503
|
.${PREFIX}-ring-halo {
|
|
1480
|
-
stroke-dasharray: 30 70;
|
|
1481
|
-
stroke-dashoffset: 0;
|
|
1482
1504
|
stroke-width: calc(var(--zrl-glow-core) * 2 + var(--zrl-beam-line));
|
|
1483
|
-
opacity: calc(var(--zrl-glow-opacity) * 0.5);
|
|
1484
1505
|
filter: blur(var(--zrl-glow-blur));
|
|
1485
1506
|
}
|
|
1486
1507
|
@keyframes ${PREFIX}-dash {
|
|
1487
|
-
|
|
1508
|
+
from { stroke-dashoffset: var(--zrl-s, 0px); }
|
|
1509
|
+
to { stroke-dashoffset: calc(var(--zrl-s, 0px) - var(--zrl-l)); }
|
|
1488
1510
|
}
|
|
1489
|
-
.${PREFIX}-ring-body { --zrl-dash-start: -14; }
|
|
1490
|
-
.${PREFIX}-ring-head { --zrl-dash-start: -25; }
|
|
1491
1511
|
@media (prefers-reduced-motion: reduce) {
|
|
1492
1512
|
.${PREFIX}-ring-svg rect { animation: none; stroke-dasharray: none; opacity: 0.45; }
|
|
1493
1513
|
.${PREFIX}-ring-halo, .${PREFIX}-ring-head { display: none; }
|
|
@@ -1733,11 +1753,154 @@ async function challengeS256(verifier) {
|
|
|
1733
1753
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
1734
1754
|
return base64url(new Uint8Array(digest));
|
|
1735
1755
|
}
|
|
1756
|
+
function challengeS256Sync(verifier) {
|
|
1757
|
+
return base64url(sha256(new TextEncoder().encode(verifier)));
|
|
1758
|
+
}
|
|
1759
|
+
var K = new Uint32Array([
|
|
1760
|
+
1116352408,
|
|
1761
|
+
1899447441,
|
|
1762
|
+
3049323471,
|
|
1763
|
+
3921009573,
|
|
1764
|
+
961987163,
|
|
1765
|
+
1508970993,
|
|
1766
|
+
2453635748,
|
|
1767
|
+
2870763221,
|
|
1768
|
+
3624381080,
|
|
1769
|
+
310598401,
|
|
1770
|
+
607225278,
|
|
1771
|
+
1426881987,
|
|
1772
|
+
1925078388,
|
|
1773
|
+
2162078206,
|
|
1774
|
+
2614888103,
|
|
1775
|
+
3248222580,
|
|
1776
|
+
3835390401,
|
|
1777
|
+
4022224774,
|
|
1778
|
+
264347078,
|
|
1779
|
+
604807628,
|
|
1780
|
+
770255983,
|
|
1781
|
+
1249150122,
|
|
1782
|
+
1555081692,
|
|
1783
|
+
1996064986,
|
|
1784
|
+
2554220882,
|
|
1785
|
+
2821834349,
|
|
1786
|
+
2952996808,
|
|
1787
|
+
3210313671,
|
|
1788
|
+
3336571891,
|
|
1789
|
+
3584528711,
|
|
1790
|
+
113926993,
|
|
1791
|
+
338241895,
|
|
1792
|
+
666307205,
|
|
1793
|
+
773529912,
|
|
1794
|
+
1294757372,
|
|
1795
|
+
1396182291,
|
|
1796
|
+
1695183700,
|
|
1797
|
+
1986661051,
|
|
1798
|
+
2177026350,
|
|
1799
|
+
2456956037,
|
|
1800
|
+
2730485921,
|
|
1801
|
+
2820302411,
|
|
1802
|
+
3259730800,
|
|
1803
|
+
3345764771,
|
|
1804
|
+
3516065817,
|
|
1805
|
+
3600352804,
|
|
1806
|
+
4094571909,
|
|
1807
|
+
275423344,
|
|
1808
|
+
430227734,
|
|
1809
|
+
506948616,
|
|
1810
|
+
659060556,
|
|
1811
|
+
883997877,
|
|
1812
|
+
958139571,
|
|
1813
|
+
1322822218,
|
|
1814
|
+
1537002063,
|
|
1815
|
+
1747873779,
|
|
1816
|
+
1955562222,
|
|
1817
|
+
2024104815,
|
|
1818
|
+
2227730452,
|
|
1819
|
+
2361852424,
|
|
1820
|
+
2428436474,
|
|
1821
|
+
2756734187,
|
|
1822
|
+
3204031479,
|
|
1823
|
+
3329325298
|
|
1824
|
+
]);
|
|
1825
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
1826
|
+
function sha256(message) {
|
|
1827
|
+
const H = new Uint32Array([
|
|
1828
|
+
1779033703,
|
|
1829
|
+
3144134277,
|
|
1830
|
+
1013904242,
|
|
1831
|
+
2773480762,
|
|
1832
|
+
1359893119,
|
|
1833
|
+
2600822924,
|
|
1834
|
+
528734635,
|
|
1835
|
+
1541459225
|
|
1836
|
+
]);
|
|
1837
|
+
const length = message.length;
|
|
1838
|
+
const padded = new Uint8Array(length + 9 + 63 >> 6 << 6);
|
|
1839
|
+
padded.set(message);
|
|
1840
|
+
padded[length] = 128;
|
|
1841
|
+
const view = new DataView(padded.buffer);
|
|
1842
|
+
const bits = length * 8;
|
|
1843
|
+
view.setUint32(padded.length - 8, Math.floor(bits / 4294967296));
|
|
1844
|
+
view.setUint32(padded.length - 4, bits >>> 0);
|
|
1845
|
+
const W = new Uint32Array(64);
|
|
1846
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
1847
|
+
for (let i = 0; i < 16; i++) W[i] = view.getUint32(offset + i * 4);
|
|
1848
|
+
for (let i = 16; i < 64; i++) {
|
|
1849
|
+
const w15 = W[i - 15];
|
|
1850
|
+
const w2 = W[i - 2];
|
|
1851
|
+
const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ w15 >>> 3;
|
|
1852
|
+
const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ w2 >>> 10;
|
|
1853
|
+
W[i] = W[i - 16] + s0 + W[i - 7] + s1 >>> 0;
|
|
1854
|
+
}
|
|
1855
|
+
let [a, b, c, d, e, f, g, h] = H;
|
|
1856
|
+
for (let i = 0; i < 64; i++) {
|
|
1857
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
1858
|
+
const ch = e & f ^ ~e & g;
|
|
1859
|
+
const t1 = h + S1 + ch + K[i] + W[i] >>> 0;
|
|
1860
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
1861
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
1862
|
+
const t2 = S0 + maj >>> 0;
|
|
1863
|
+
h = g;
|
|
1864
|
+
g = f;
|
|
1865
|
+
f = e;
|
|
1866
|
+
e = d + t1 >>> 0;
|
|
1867
|
+
d = c;
|
|
1868
|
+
c = b;
|
|
1869
|
+
b = a;
|
|
1870
|
+
a = t1 + t2 >>> 0;
|
|
1871
|
+
}
|
|
1872
|
+
H[0] = H[0] + a >>> 0;
|
|
1873
|
+
H[1] = H[1] + b >>> 0;
|
|
1874
|
+
H[2] = H[2] + c >>> 0;
|
|
1875
|
+
H[3] = H[3] + d >>> 0;
|
|
1876
|
+
H[4] = H[4] + e >>> 0;
|
|
1877
|
+
H[5] = H[5] + f >>> 0;
|
|
1878
|
+
H[6] = H[6] + g >>> 0;
|
|
1879
|
+
H[7] = H[7] + h >>> 0;
|
|
1880
|
+
}
|
|
1881
|
+
const out = new Uint8Array(32);
|
|
1882
|
+
const outView = new DataView(out.buffer);
|
|
1883
|
+
for (let i = 0; i < 8; i++) outView.setUint32(i * 4, H[i]);
|
|
1884
|
+
return out;
|
|
1885
|
+
}
|
|
1736
1886
|
function generateState() {
|
|
1737
1887
|
const bytes = new Uint8Array(16);
|
|
1738
1888
|
crypto.getRandomValues(bytes);
|
|
1739
1889
|
return base64url(bytes);
|
|
1740
1890
|
}
|
|
1891
|
+
var TOKEN_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
1892
|
+
function generateRequestId() {
|
|
1893
|
+
let out = "";
|
|
1894
|
+
const bytes = new Uint8Array(64);
|
|
1895
|
+
while (out.length < 32) {
|
|
1896
|
+
crypto.getRandomValues(bytes);
|
|
1897
|
+
for (const byte of bytes) {
|
|
1898
|
+
if (byte >= 248 || out.length === 32) continue;
|
|
1899
|
+
out += TOKEN_ALPHABET[byte % 62];
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return out;
|
|
1903
|
+
}
|
|
1741
1904
|
|
|
1742
1905
|
// src/login.ts
|
|
1743
1906
|
function startLogin(options) {
|
|
@@ -1767,109 +1930,144 @@ function startLogin(options) {
|
|
|
1767
1930
|
const state = generateState();
|
|
1768
1931
|
const nonce = generateState();
|
|
1769
1932
|
try {
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1933
|
+
let code;
|
|
1934
|
+
let selectBy = "device";
|
|
1935
|
+
if (useAppLink && typeof window !== "undefined") {
|
|
1936
|
+
const requestId = generateRequestId();
|
|
1937
|
+
const startUrl = sameDeviceStartUrl(issuer, {
|
|
1773
1938
|
client_id: options.clientId,
|
|
1774
1939
|
scope: options.scope ?? "openid",
|
|
1775
1940
|
state,
|
|
1776
1941
|
nonce,
|
|
1777
|
-
code_challenge:
|
|
1942
|
+
code_challenge: challengeS256Sync(verifier),
|
|
1778
1943
|
redirect_uri: flow === "auth-code" ? options.redirect_uri : void 0,
|
|
1779
1944
|
acr_values: Array.isArray(options.acr_values) ? options.acr_values.join(" ") : options.acr_values,
|
|
1780
1945
|
max_age: options.max_age,
|
|
1781
1946
|
prompt: options.prompt,
|
|
1782
1947
|
locale: options.locale,
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
let code;
|
|
1788
|
-
let selectBy = "device";
|
|
1789
|
-
if ("code" in started) {
|
|
1790
|
-
code = started.code;
|
|
1791
|
-
selectBy = "session";
|
|
1792
|
-
} else {
|
|
1793
|
-
selectBy = useAppLink ? "app_link" : "qr";
|
|
1794
|
-
const requestId = started.request_id;
|
|
1795
|
-
const qrBase = `${issuer}/pair/${encodeURIComponent(requestId)}/qr.svg`;
|
|
1796
|
-
const animated = !useAppLink && started.display !== "legacy";
|
|
1797
|
-
const qrRefreshSeconds = !animated ? void 0 : typeof started.qr_refresh_seconds === "number" && started.qr_refresh_seconds > 0 ? started.qr_refresh_seconds : DEFAULT_QR_REFRESH_SECONDS;
|
|
1948
|
+
request_id: requestId,
|
|
1949
|
+
origin: window.location.origin
|
|
1950
|
+
});
|
|
1951
|
+
selectBy = "app_link";
|
|
1798
1952
|
surface.requestId = requestId;
|
|
1799
|
-
surface.pairUrl =
|
|
1800
|
-
surface.
|
|
1801
|
-
surface.appLink = useAppLink;
|
|
1953
|
+
surface.pairUrl = startUrl;
|
|
1954
|
+
surface.appLink = true;
|
|
1802
1955
|
const withSurface = (s) => ({
|
|
1803
1956
|
...s,
|
|
1804
|
-
pairUrl:
|
|
1805
|
-
|
|
1806
|
-
qrRefreshSeconds,
|
|
1807
|
-
appLink: useAppLink,
|
|
1957
|
+
pairUrl: startUrl,
|
|
1958
|
+
appLink: true,
|
|
1808
1959
|
intent,
|
|
1809
1960
|
cancel
|
|
1810
1961
|
});
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
options.onState?.(initial);
|
|
1814
|
-
if ((options.pairingUI ?? "modal") === "modal" && !useAppLink) {
|
|
1815
|
-
modal = mountPairingModal(initial, {
|
|
1816
|
-
onCancel: cancel,
|
|
1817
|
-
intent,
|
|
1818
|
-
locale: options.locale,
|
|
1819
|
-
theme: options.theme,
|
|
1820
|
-
timeoutMs: options.pairingTimeoutMs
|
|
1821
|
-
});
|
|
1822
|
-
}
|
|
1823
|
-
if (useAppLink && typeof window !== "undefined") {
|
|
1824
|
-
window.location.assign(started.pair_url);
|
|
1825
|
-
}
|
|
1826
|
-
if (qrRefreshSeconds !== void 0) {
|
|
1827
|
-
const periodMs = qrRefreshSeconds * 1e3;
|
|
1828
|
-
let due = Date.now() + periodMs;
|
|
1829
|
-
let timer;
|
|
1830
|
-
let stopped = false;
|
|
1831
|
-
const emit = () => {
|
|
1832
|
-
timer = void 0;
|
|
1833
|
-
surface.qrUrl = `${qrBase}?t=${Date.now()}`;
|
|
1834
|
-
const next = withSurface(lastPolled);
|
|
1835
|
-
modal?.update(next);
|
|
1836
|
-
options.onState?.(next);
|
|
1837
|
-
if (stopped) return;
|
|
1838
|
-
due = Date.now() + periodMs;
|
|
1839
|
-
timer = setTimeout(emit, periodMs);
|
|
1840
|
-
};
|
|
1841
|
-
const onVisible = () => {
|
|
1842
|
-
if (document.visibilityState === "visible" && timer !== void 0 && Date.now() >= due) {
|
|
1843
|
-
clearTimeout(timer);
|
|
1844
|
-
emit();
|
|
1845
|
-
}
|
|
1846
|
-
};
|
|
1847
|
-
const hasDocument = typeof document !== "undefined";
|
|
1848
|
-
if (hasDocument) document.addEventListener("visibilitychange", onVisible);
|
|
1849
|
-
stopRefresh = () => {
|
|
1850
|
-
stopped = true;
|
|
1851
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
1852
|
-
timer = void 0;
|
|
1853
|
-
if (hasDocument) document.removeEventListener("visibilitychange", onVisible);
|
|
1854
|
-
stopRefresh = () => {
|
|
1855
|
-
};
|
|
1856
|
-
};
|
|
1857
|
-
timer = setTimeout(emit, Math.max(0, due - Date.now()));
|
|
1858
|
-
}
|
|
1962
|
+
options.onState?.(withSurface({ status: "pending" }));
|
|
1963
|
+
window.location.assign(startUrl);
|
|
1859
1964
|
code = await pollUntilApproved(
|
|
1860
1965
|
issuer,
|
|
1861
1966
|
requestId,
|
|
1862
|
-
(s) =>
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1967
|
+
(s) => options.onState?.(withSurface(s)),
|
|
1968
|
+
controller.signal,
|
|
1969
|
+
{ tolerateUnknownUntil: Date.now() + 15e3 }
|
|
1970
|
+
);
|
|
1971
|
+
} else {
|
|
1972
|
+
const started = await startPairing(
|
|
1973
|
+
issuer,
|
|
1974
|
+
{
|
|
1975
|
+
client_id: options.clientId,
|
|
1976
|
+
scope: options.scope ?? "openid",
|
|
1977
|
+
state,
|
|
1978
|
+
nonce,
|
|
1979
|
+
code_challenge: await challengeS256(verifier),
|
|
1980
|
+
redirect_uri: flow === "auth-code" ? options.redirect_uri : void 0,
|
|
1981
|
+
acr_values: Array.isArray(options.acr_values) ? options.acr_values.join(" ") : options.acr_values,
|
|
1982
|
+
max_age: options.max_age,
|
|
1983
|
+
prompt: options.prompt,
|
|
1984
|
+
locale: options.locale,
|
|
1985
|
+
display: "qr"
|
|
1868
1986
|
},
|
|
1869
1987
|
controller.signal
|
|
1870
1988
|
);
|
|
1989
|
+
if ("code" in started) {
|
|
1990
|
+
code = started.code;
|
|
1991
|
+
selectBy = "session";
|
|
1992
|
+
} else {
|
|
1993
|
+
selectBy = "qr";
|
|
1994
|
+
const requestId = started.request_id;
|
|
1995
|
+
const qrBase = `${issuer}/pair/${encodeURIComponent(requestId)}/qr.svg`;
|
|
1996
|
+
const animated = started.display !== "legacy";
|
|
1997
|
+
const qrRefreshSeconds = !animated ? void 0 : typeof started.qr_refresh_seconds === "number" && started.qr_refresh_seconds > 0 ? started.qr_refresh_seconds : DEFAULT_QR_REFRESH_SECONDS;
|
|
1998
|
+
surface.requestId = requestId;
|
|
1999
|
+
surface.pairUrl = started.pair_url;
|
|
2000
|
+
surface.qrUrl = qrBase;
|
|
2001
|
+
surface.appLink = false;
|
|
2002
|
+
const withSurface = (s) => ({
|
|
2003
|
+
...s,
|
|
2004
|
+
pairUrl: surface.pairUrl,
|
|
2005
|
+
qrUrl: surface.qrUrl,
|
|
2006
|
+
qrRefreshSeconds,
|
|
2007
|
+
appLink: false,
|
|
2008
|
+
intent,
|
|
2009
|
+
cancel
|
|
2010
|
+
});
|
|
2011
|
+
let lastPolled = { status: "pending", expiresIn: started.expires_in };
|
|
2012
|
+
const initial = withSurface(lastPolled);
|
|
2013
|
+
options.onState?.(initial);
|
|
2014
|
+
if ((options.pairingUI ?? "modal") === "modal") {
|
|
2015
|
+
modal = mountPairingModal(initial, {
|
|
2016
|
+
onCancel: cancel,
|
|
2017
|
+
intent,
|
|
2018
|
+
locale: options.locale,
|
|
2019
|
+
theme: options.theme,
|
|
2020
|
+
timeoutMs: options.pairingTimeoutMs
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
if (qrRefreshSeconds !== void 0) {
|
|
2024
|
+
const periodMs = qrRefreshSeconds * 1e3;
|
|
2025
|
+
let due = Date.now() + periodMs;
|
|
2026
|
+
let timer;
|
|
2027
|
+
let stopped = false;
|
|
2028
|
+
const emit = () => {
|
|
2029
|
+
timer = void 0;
|
|
2030
|
+
surface.qrUrl = `${qrBase}?t=${Date.now()}`;
|
|
2031
|
+
const next = withSurface(lastPolled);
|
|
2032
|
+
modal?.update(next);
|
|
2033
|
+
options.onState?.(next);
|
|
2034
|
+
if (stopped) return;
|
|
2035
|
+
due = Date.now() + periodMs;
|
|
2036
|
+
timer = setTimeout(emit, periodMs);
|
|
2037
|
+
};
|
|
2038
|
+
const onVisible = () => {
|
|
2039
|
+
if (document.visibilityState === "visible" && timer !== void 0 && Date.now() >= due) {
|
|
2040
|
+
clearTimeout(timer);
|
|
2041
|
+
emit();
|
|
2042
|
+
}
|
|
2043
|
+
};
|
|
2044
|
+
const hasDocument = typeof document !== "undefined";
|
|
2045
|
+
if (hasDocument) document.addEventListener("visibilitychange", onVisible);
|
|
2046
|
+
stopRefresh = () => {
|
|
2047
|
+
stopped = true;
|
|
2048
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2049
|
+
timer = void 0;
|
|
2050
|
+
if (hasDocument) document.removeEventListener("visibilitychange", onVisible);
|
|
2051
|
+
stopRefresh = () => {
|
|
2052
|
+
};
|
|
2053
|
+
};
|
|
2054
|
+
timer = setTimeout(emit, Math.max(0, due - Date.now()));
|
|
2055
|
+
}
|
|
2056
|
+
code = await pollUntilApproved(
|
|
2057
|
+
issuer,
|
|
2058
|
+
requestId,
|
|
2059
|
+
(s) => {
|
|
2060
|
+
lastPolled = s;
|
|
2061
|
+
if (s.status !== "pending") stopRefresh();
|
|
2062
|
+
const next = withSurface(s);
|
|
2063
|
+
modal?.update(next);
|
|
2064
|
+
options.onState?.(next);
|
|
2065
|
+
},
|
|
2066
|
+
controller.signal
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
teardown();
|
|
1871
2070
|
}
|
|
1872
|
-
teardown();
|
|
1873
2071
|
if (flow === "auth-code") {
|
|
1874
2072
|
const response2 = {
|
|
1875
2073
|
code,
|