@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/dist/index.d.cts
CHANGED
|
@@ -278,6 +278,11 @@ declare function mountPairingModal(state: PairingState, options: PairingModalOpt
|
|
|
278
278
|
* the provider never renders a QR for it, so
|
|
279
279
|
* nobody can turn a same-device link into a
|
|
280
280
|
* static code to relay.
|
|
281
|
+
* GET /pair/start the same-device sign-in as a NAVIGATION:
|
|
282
|
+
* the /pair parameters as a query, plus
|
|
283
|
+
* request_id (the page's own token) and
|
|
284
|
+
* origin; answered with a redirect to the
|
|
285
|
+
* pairing's universal link, inside the tap
|
|
281
286
|
* GET /pair/:id/status poll: pending | claimed |
|
|
282
287
|
* approved (with code) | denied | expired |
|
|
283
288
|
* enrolling. Over-polling cancels the request
|
|
@@ -305,7 +310,7 @@ declare function mountPairingModal(state: PairingState, options: PairingModalOpt
|
|
|
305
310
|
* auth-code mode leaves it to the RP backend.
|
|
306
311
|
*/
|
|
307
312
|
declare const WIRE_VERSION = 1;
|
|
308
|
-
declare const SDK_VERSION = "0.1.
|
|
313
|
+
declare const SDK_VERSION = "0.1.18";
|
|
309
314
|
declare const SDK_NAME = "@zoreal/oauth2-js";
|
|
310
315
|
declare const DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
311
316
|
/** Pending TTL is short. Poll gently; over-polling cancels the request. */
|
|
@@ -413,7 +418,15 @@ declare function startPairing(issuer: string, params: StartPairingParams, signal
|
|
|
413
418
|
* Throws FlowAbandonedError for the human outcomes (denied, expired,
|
|
414
419
|
* enrolment abandoned) and OAuthFlowError for protocol ones.
|
|
415
420
|
*/
|
|
416
|
-
declare function pollUntilApproved(issuer: string, requestId: string, onState?: (state: PairingState) => void, signal?: AbortSignal
|
|
421
|
+
declare function pollUntilApproved(issuer: string, requestId: string, onState?: (state: PairingState) => void, signal?: AbortSignal, options?: {
|
|
422
|
+
/**
|
|
423
|
+
* Same-device navigation only. The page starts polling while the
|
|
424
|
+
* provider is still answering the navigation that creates the pairing,
|
|
425
|
+
* so a "no such pairing" answer before this instant (epoch ms) is the
|
|
426
|
+
* pairing not existing YET, and is read as pending.
|
|
427
|
+
*/
|
|
428
|
+
tolerateUnknownUntil?: number;
|
|
429
|
+
}): Promise<string>;
|
|
417
430
|
/**
|
|
418
431
|
* The code exchange, browser-direct mode only: a public client, PKCE and no
|
|
419
432
|
* secret. What comes back can only ever be the pseudonymous tier, by
|
package/dist/index.d.ts
CHANGED
|
@@ -278,6 +278,11 @@ declare function mountPairingModal(state: PairingState, options: PairingModalOpt
|
|
|
278
278
|
* the provider never renders a QR for it, so
|
|
279
279
|
* nobody can turn a same-device link into a
|
|
280
280
|
* static code to relay.
|
|
281
|
+
* GET /pair/start the same-device sign-in as a NAVIGATION:
|
|
282
|
+
* the /pair parameters as a query, plus
|
|
283
|
+
* request_id (the page's own token) and
|
|
284
|
+
* origin; answered with a redirect to the
|
|
285
|
+
* pairing's universal link, inside the tap
|
|
281
286
|
* GET /pair/:id/status poll: pending | claimed |
|
|
282
287
|
* approved (with code) | denied | expired |
|
|
283
288
|
* enrolling. Over-polling cancels the request
|
|
@@ -305,7 +310,7 @@ declare function mountPairingModal(state: PairingState, options: PairingModalOpt
|
|
|
305
310
|
* auth-code mode leaves it to the RP backend.
|
|
306
311
|
*/
|
|
307
312
|
declare const WIRE_VERSION = 1;
|
|
308
|
-
declare const SDK_VERSION = "0.1.
|
|
313
|
+
declare const SDK_VERSION = "0.1.18";
|
|
309
314
|
declare const SDK_NAME = "@zoreal/oauth2-js";
|
|
310
315
|
declare const DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
311
316
|
/** Pending TTL is short. Poll gently; over-polling cancels the request. */
|
|
@@ -413,7 +418,15 @@ declare function startPairing(issuer: string, params: StartPairingParams, signal
|
|
|
413
418
|
* Throws FlowAbandonedError for the human outcomes (denied, expired,
|
|
414
419
|
* enrolment abandoned) and OAuthFlowError for protocol ones.
|
|
415
420
|
*/
|
|
416
|
-
declare function pollUntilApproved(issuer: string, requestId: string, onState?: (state: PairingState) => void, signal?: AbortSignal
|
|
421
|
+
declare function pollUntilApproved(issuer: string, requestId: string, onState?: (state: PairingState) => void, signal?: AbortSignal, options?: {
|
|
422
|
+
/**
|
|
423
|
+
* Same-device navigation only. The page starts polling while the
|
|
424
|
+
* provider is still answering the navigation that creates the pairing,
|
|
425
|
+
* so a "no such pairing" answer before this instant (epoch ms) is the
|
|
426
|
+
* pairing not existing YET, and is read as pending.
|
|
427
|
+
*/
|
|
428
|
+
tolerateUnknownUntil?: number;
|
|
429
|
+
}): Promise<string>;
|
|
417
430
|
/**
|
|
418
431
|
* The code exchange, browser-direct mode only: a public client, PKCE and no
|
|
419
432
|
* secret. What comes back can only ever be the pseudonymous tier, by
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ function unsafeClaims(idToken) {
|
|
|
14
14
|
|
|
15
15
|
// src/wire.ts
|
|
16
16
|
var WIRE_VERSION = 1;
|
|
17
|
-
var SDK_VERSION = "0.1.
|
|
17
|
+
var SDK_VERSION = "0.1.18";
|
|
18
18
|
var SDK_NAME = "@zoreal/oauth2-js";
|
|
19
19
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
20
20
|
var POLL_INTERVAL_MS = 2e3;
|
|
@@ -63,6 +63,20 @@ async function startPairing(issuer, params, signal) {
|
|
|
63
63
|
}
|
|
64
64
|
return body;
|
|
65
65
|
}
|
|
66
|
+
function sameDeviceStartUrl(issuer, params) {
|
|
67
|
+
const query = new URLSearchParams();
|
|
68
|
+
const all = {
|
|
69
|
+
...params,
|
|
70
|
+
code_challenge_method: "S256",
|
|
71
|
+
wire_version: WIRE_VERSION,
|
|
72
|
+
sdk: `${SDK_NAME}/${SDK_VERSION}`
|
|
73
|
+
};
|
|
74
|
+
for (const [key, value] of Object.entries(all)) {
|
|
75
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
76
|
+
query.set(key, String(value));
|
|
77
|
+
}
|
|
78
|
+
return `${issuer}/pair/start?${query.toString()}`;
|
|
79
|
+
}
|
|
66
80
|
var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
67
81
|
if (signal?.aborted) {
|
|
68
82
|
reject(new DOMException("aborted", "AbortError"));
|
|
@@ -78,12 +92,17 @@ var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
|
78
92
|
}, ms);
|
|
79
93
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
80
94
|
});
|
|
81
|
-
async function pollUntilApproved(issuer, requestId, onState, signal) {
|
|
95
|
+
async function pollUntilApproved(issuer, requestId, onState, signal, options = {}) {
|
|
82
96
|
for (; ; ) {
|
|
83
97
|
const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {
|
|
84
98
|
signal
|
|
85
99
|
});
|
|
86
100
|
const body = await parseJson(response);
|
|
101
|
+
if (response.status === 404 && (options.tolerateUnknownUntil ?? 0) > Date.now()) {
|
|
102
|
+
onState?.({ status: "pending" });
|
|
103
|
+
await sleep(POLL_INTERVAL_MS, signal);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
87
106
|
if (!response.ok) {
|
|
88
107
|
throw new OAuthFlowError(
|
|
89
108
|
body.error ?? "server_error",
|
|
@@ -1395,10 +1414,15 @@ var CSS = `
|
|
|
1395
1414
|
sides and lights two edges at once near the ends. So here the light is a
|
|
1396
1415
|
dash on an SVG outline, which moves at one speed the whole way round
|
|
1397
1416
|
whatever the shape, drawn with the well's tokens: its colour and head
|
|
1398
|
-
tint, its line width, its halo, its four second lap.
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1417
|
+
tint, its line width, its halo, its four second lap. The outline's length
|
|
1418
|
+
is measured by the component and set as --zrl-ring-len, and every dash
|
|
1419
|
+
and offset is a fraction of it, because pathLength does not scale dash
|
|
1420
|
+
values given from CSS. A stroke cannot fade along its length, so the tail
|
|
1421
|
+
is a stack of dashes sharing one head, each shorter and more opaque than
|
|
1422
|
+
the one under it, with opacities chosen so the stack composes to a
|
|
1423
|
+
straight fade from the head to nothing three tenths of the way back; the
|
|
1424
|
+
component sets each layer's length, offset and opacity. Shown only while
|
|
1425
|
+
busy. */
|
|
1402
1426
|
.${PREFIX}-ring {
|
|
1403
1427
|
position: relative;
|
|
1404
1428
|
display: inline-flex;
|
|
@@ -1417,6 +1441,7 @@ var CSS = `
|
|
|
1417
1441
|
}
|
|
1418
1442
|
.${PREFIX}-ring[data-busy="true"] > .${PREFIX}-ring-svg { opacity: 1; }
|
|
1419
1443
|
.${PREFIX}-ring-svg rect {
|
|
1444
|
+
--zrl-l: var(--zrl-ring-len, 600px);
|
|
1420
1445
|
x: 2px;
|
|
1421
1446
|
y: 2px;
|
|
1422
1447
|
width: calc(100% - 4px);
|
|
@@ -1425,23 +1450,18 @@ var CSS = `
|
|
|
1425
1450
|
stroke: var(--zrl-beam);
|
|
1426
1451
|
stroke-width: var(--zrl-beam-line);
|
|
1427
1452
|
stroke-linecap: round;
|
|
1453
|
+
stroke-dashoffset: var(--zrl-s, 0px);
|
|
1428
1454
|
animation: ${PREFIX}-dash var(--zrl-beam-time) linear infinite;
|
|
1429
1455
|
}
|
|
1430
|
-
.${PREFIX}-ring-
|
|
1431
|
-
.${PREFIX}-ring-body { stroke-dasharray: 16 84; stroke-dashoffset: -14; opacity: 0.8; }
|
|
1432
|
-
.${PREFIX}-ring-head { stroke-dasharray: 5 95; stroke-dashoffset: -25; stroke: var(--zrl-beam-head); }
|
|
1456
|
+
.${PREFIX}-ring-head { stroke: var(--zrl-beam-head); }
|
|
1433
1457
|
.${PREFIX}-ring-halo {
|
|
1434
|
-
stroke-dasharray: 30 70;
|
|
1435
|
-
stroke-dashoffset: 0;
|
|
1436
1458
|
stroke-width: calc(var(--zrl-glow-core) * 2 + var(--zrl-beam-line));
|
|
1437
|
-
opacity: calc(var(--zrl-glow-opacity) * 0.5);
|
|
1438
1459
|
filter: blur(var(--zrl-glow-blur));
|
|
1439
1460
|
}
|
|
1440
1461
|
@keyframes ${PREFIX}-dash {
|
|
1441
|
-
|
|
1462
|
+
from { stroke-dashoffset: var(--zrl-s, 0px); }
|
|
1463
|
+
to { stroke-dashoffset: calc(var(--zrl-s, 0px) - var(--zrl-l)); }
|
|
1442
1464
|
}
|
|
1443
|
-
.${PREFIX}-ring-body { --zrl-dash-start: -14; }
|
|
1444
|
-
.${PREFIX}-ring-head { --zrl-dash-start: -25; }
|
|
1445
1465
|
@media (prefers-reduced-motion: reduce) {
|
|
1446
1466
|
.${PREFIX}-ring-svg rect { animation: none; stroke-dasharray: none; opacity: 0.45; }
|
|
1447
1467
|
.${PREFIX}-ring-halo, .${PREFIX}-ring-head { display: none; }
|
|
@@ -1687,11 +1707,154 @@ async function challengeS256(verifier) {
|
|
|
1687
1707
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
1688
1708
|
return base64url(new Uint8Array(digest));
|
|
1689
1709
|
}
|
|
1710
|
+
function challengeS256Sync(verifier) {
|
|
1711
|
+
return base64url(sha256(new TextEncoder().encode(verifier)));
|
|
1712
|
+
}
|
|
1713
|
+
var K = new Uint32Array([
|
|
1714
|
+
1116352408,
|
|
1715
|
+
1899447441,
|
|
1716
|
+
3049323471,
|
|
1717
|
+
3921009573,
|
|
1718
|
+
961987163,
|
|
1719
|
+
1508970993,
|
|
1720
|
+
2453635748,
|
|
1721
|
+
2870763221,
|
|
1722
|
+
3624381080,
|
|
1723
|
+
310598401,
|
|
1724
|
+
607225278,
|
|
1725
|
+
1426881987,
|
|
1726
|
+
1925078388,
|
|
1727
|
+
2162078206,
|
|
1728
|
+
2614888103,
|
|
1729
|
+
3248222580,
|
|
1730
|
+
3835390401,
|
|
1731
|
+
4022224774,
|
|
1732
|
+
264347078,
|
|
1733
|
+
604807628,
|
|
1734
|
+
770255983,
|
|
1735
|
+
1249150122,
|
|
1736
|
+
1555081692,
|
|
1737
|
+
1996064986,
|
|
1738
|
+
2554220882,
|
|
1739
|
+
2821834349,
|
|
1740
|
+
2952996808,
|
|
1741
|
+
3210313671,
|
|
1742
|
+
3336571891,
|
|
1743
|
+
3584528711,
|
|
1744
|
+
113926993,
|
|
1745
|
+
338241895,
|
|
1746
|
+
666307205,
|
|
1747
|
+
773529912,
|
|
1748
|
+
1294757372,
|
|
1749
|
+
1396182291,
|
|
1750
|
+
1695183700,
|
|
1751
|
+
1986661051,
|
|
1752
|
+
2177026350,
|
|
1753
|
+
2456956037,
|
|
1754
|
+
2730485921,
|
|
1755
|
+
2820302411,
|
|
1756
|
+
3259730800,
|
|
1757
|
+
3345764771,
|
|
1758
|
+
3516065817,
|
|
1759
|
+
3600352804,
|
|
1760
|
+
4094571909,
|
|
1761
|
+
275423344,
|
|
1762
|
+
430227734,
|
|
1763
|
+
506948616,
|
|
1764
|
+
659060556,
|
|
1765
|
+
883997877,
|
|
1766
|
+
958139571,
|
|
1767
|
+
1322822218,
|
|
1768
|
+
1537002063,
|
|
1769
|
+
1747873779,
|
|
1770
|
+
1955562222,
|
|
1771
|
+
2024104815,
|
|
1772
|
+
2227730452,
|
|
1773
|
+
2361852424,
|
|
1774
|
+
2428436474,
|
|
1775
|
+
2756734187,
|
|
1776
|
+
3204031479,
|
|
1777
|
+
3329325298
|
|
1778
|
+
]);
|
|
1779
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
1780
|
+
function sha256(message) {
|
|
1781
|
+
const H = new Uint32Array([
|
|
1782
|
+
1779033703,
|
|
1783
|
+
3144134277,
|
|
1784
|
+
1013904242,
|
|
1785
|
+
2773480762,
|
|
1786
|
+
1359893119,
|
|
1787
|
+
2600822924,
|
|
1788
|
+
528734635,
|
|
1789
|
+
1541459225
|
|
1790
|
+
]);
|
|
1791
|
+
const length = message.length;
|
|
1792
|
+
const padded = new Uint8Array(length + 9 + 63 >> 6 << 6);
|
|
1793
|
+
padded.set(message);
|
|
1794
|
+
padded[length] = 128;
|
|
1795
|
+
const view = new DataView(padded.buffer);
|
|
1796
|
+
const bits = length * 8;
|
|
1797
|
+
view.setUint32(padded.length - 8, Math.floor(bits / 4294967296));
|
|
1798
|
+
view.setUint32(padded.length - 4, bits >>> 0);
|
|
1799
|
+
const W = new Uint32Array(64);
|
|
1800
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
1801
|
+
for (let i = 0; i < 16; i++) W[i] = view.getUint32(offset + i * 4);
|
|
1802
|
+
for (let i = 16; i < 64; i++) {
|
|
1803
|
+
const w15 = W[i - 15];
|
|
1804
|
+
const w2 = W[i - 2];
|
|
1805
|
+
const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ w15 >>> 3;
|
|
1806
|
+
const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ w2 >>> 10;
|
|
1807
|
+
W[i] = W[i - 16] + s0 + W[i - 7] + s1 >>> 0;
|
|
1808
|
+
}
|
|
1809
|
+
let [a, b, c, d, e, f, g, h] = H;
|
|
1810
|
+
for (let i = 0; i < 64; i++) {
|
|
1811
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
1812
|
+
const ch = e & f ^ ~e & g;
|
|
1813
|
+
const t1 = h + S1 + ch + K[i] + W[i] >>> 0;
|
|
1814
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
1815
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
1816
|
+
const t2 = S0 + maj >>> 0;
|
|
1817
|
+
h = g;
|
|
1818
|
+
g = f;
|
|
1819
|
+
f = e;
|
|
1820
|
+
e = d + t1 >>> 0;
|
|
1821
|
+
d = c;
|
|
1822
|
+
c = b;
|
|
1823
|
+
b = a;
|
|
1824
|
+
a = t1 + t2 >>> 0;
|
|
1825
|
+
}
|
|
1826
|
+
H[0] = H[0] + a >>> 0;
|
|
1827
|
+
H[1] = H[1] + b >>> 0;
|
|
1828
|
+
H[2] = H[2] + c >>> 0;
|
|
1829
|
+
H[3] = H[3] + d >>> 0;
|
|
1830
|
+
H[4] = H[4] + e >>> 0;
|
|
1831
|
+
H[5] = H[5] + f >>> 0;
|
|
1832
|
+
H[6] = H[6] + g >>> 0;
|
|
1833
|
+
H[7] = H[7] + h >>> 0;
|
|
1834
|
+
}
|
|
1835
|
+
const out = new Uint8Array(32);
|
|
1836
|
+
const outView = new DataView(out.buffer);
|
|
1837
|
+
for (let i = 0; i < 8; i++) outView.setUint32(i * 4, H[i]);
|
|
1838
|
+
return out;
|
|
1839
|
+
}
|
|
1690
1840
|
function generateState() {
|
|
1691
1841
|
const bytes = new Uint8Array(16);
|
|
1692
1842
|
crypto.getRandomValues(bytes);
|
|
1693
1843
|
return base64url(bytes);
|
|
1694
1844
|
}
|
|
1845
|
+
var TOKEN_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
1846
|
+
function generateRequestId() {
|
|
1847
|
+
let out = "";
|
|
1848
|
+
const bytes = new Uint8Array(64);
|
|
1849
|
+
while (out.length < 32) {
|
|
1850
|
+
crypto.getRandomValues(bytes);
|
|
1851
|
+
for (const byte of bytes) {
|
|
1852
|
+
if (byte >= 248 || out.length === 32) continue;
|
|
1853
|
+
out += TOKEN_ALPHABET[byte % 62];
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return out;
|
|
1857
|
+
}
|
|
1695
1858
|
|
|
1696
1859
|
// src/login.ts
|
|
1697
1860
|
function startLogin(options) {
|
|
@@ -1721,109 +1884,144 @@ function startLogin(options) {
|
|
|
1721
1884
|
const state = generateState();
|
|
1722
1885
|
const nonce = generateState();
|
|
1723
1886
|
try {
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1887
|
+
let code;
|
|
1888
|
+
let selectBy = "device";
|
|
1889
|
+
if (useAppLink && typeof window !== "undefined") {
|
|
1890
|
+
const requestId = generateRequestId();
|
|
1891
|
+
const startUrl = sameDeviceStartUrl(issuer, {
|
|
1727
1892
|
client_id: options.clientId,
|
|
1728
1893
|
scope: options.scope ?? "openid",
|
|
1729
1894
|
state,
|
|
1730
1895
|
nonce,
|
|
1731
|
-
code_challenge:
|
|
1896
|
+
code_challenge: challengeS256Sync(verifier),
|
|
1732
1897
|
redirect_uri: flow === "auth-code" ? options.redirect_uri : void 0,
|
|
1733
1898
|
acr_values: Array.isArray(options.acr_values) ? options.acr_values.join(" ") : options.acr_values,
|
|
1734
1899
|
max_age: options.max_age,
|
|
1735
1900
|
prompt: options.prompt,
|
|
1736
1901
|
locale: options.locale,
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
let code;
|
|
1742
|
-
let selectBy = "device";
|
|
1743
|
-
if ("code" in started) {
|
|
1744
|
-
code = started.code;
|
|
1745
|
-
selectBy = "session";
|
|
1746
|
-
} else {
|
|
1747
|
-
selectBy = useAppLink ? "app_link" : "qr";
|
|
1748
|
-
const requestId = started.request_id;
|
|
1749
|
-
const qrBase = `${issuer}/pair/${encodeURIComponent(requestId)}/qr.svg`;
|
|
1750
|
-
const animated = !useAppLink && started.display !== "legacy";
|
|
1751
|
-
const qrRefreshSeconds = !animated ? void 0 : typeof started.qr_refresh_seconds === "number" && started.qr_refresh_seconds > 0 ? started.qr_refresh_seconds : DEFAULT_QR_REFRESH_SECONDS;
|
|
1902
|
+
request_id: requestId,
|
|
1903
|
+
origin: window.location.origin
|
|
1904
|
+
});
|
|
1905
|
+
selectBy = "app_link";
|
|
1752
1906
|
surface.requestId = requestId;
|
|
1753
|
-
surface.pairUrl =
|
|
1754
|
-
surface.
|
|
1755
|
-
surface.appLink = useAppLink;
|
|
1907
|
+
surface.pairUrl = startUrl;
|
|
1908
|
+
surface.appLink = true;
|
|
1756
1909
|
const withSurface = (s) => ({
|
|
1757
1910
|
...s,
|
|
1758
|
-
pairUrl:
|
|
1759
|
-
|
|
1760
|
-
qrRefreshSeconds,
|
|
1761
|
-
appLink: useAppLink,
|
|
1911
|
+
pairUrl: startUrl,
|
|
1912
|
+
appLink: true,
|
|
1762
1913
|
intent,
|
|
1763
1914
|
cancel
|
|
1764
1915
|
});
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
options.onState?.(initial);
|
|
1768
|
-
if ((options.pairingUI ?? "modal") === "modal" && !useAppLink) {
|
|
1769
|
-
modal = mountPairingModal(initial, {
|
|
1770
|
-
onCancel: cancel,
|
|
1771
|
-
intent,
|
|
1772
|
-
locale: options.locale,
|
|
1773
|
-
theme: options.theme,
|
|
1774
|
-
timeoutMs: options.pairingTimeoutMs
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
if (useAppLink && typeof window !== "undefined") {
|
|
1778
|
-
window.location.assign(started.pair_url);
|
|
1779
|
-
}
|
|
1780
|
-
if (qrRefreshSeconds !== void 0) {
|
|
1781
|
-
const periodMs = qrRefreshSeconds * 1e3;
|
|
1782
|
-
let due = Date.now() + periodMs;
|
|
1783
|
-
let timer;
|
|
1784
|
-
let stopped = false;
|
|
1785
|
-
const emit = () => {
|
|
1786
|
-
timer = void 0;
|
|
1787
|
-
surface.qrUrl = `${qrBase}?t=${Date.now()}`;
|
|
1788
|
-
const next = withSurface(lastPolled);
|
|
1789
|
-
modal?.update(next);
|
|
1790
|
-
options.onState?.(next);
|
|
1791
|
-
if (stopped) return;
|
|
1792
|
-
due = Date.now() + periodMs;
|
|
1793
|
-
timer = setTimeout(emit, periodMs);
|
|
1794
|
-
};
|
|
1795
|
-
const onVisible = () => {
|
|
1796
|
-
if (document.visibilityState === "visible" && timer !== void 0 && Date.now() >= due) {
|
|
1797
|
-
clearTimeout(timer);
|
|
1798
|
-
emit();
|
|
1799
|
-
}
|
|
1800
|
-
};
|
|
1801
|
-
const hasDocument = typeof document !== "undefined";
|
|
1802
|
-
if (hasDocument) document.addEventListener("visibilitychange", onVisible);
|
|
1803
|
-
stopRefresh = () => {
|
|
1804
|
-
stopped = true;
|
|
1805
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
1806
|
-
timer = void 0;
|
|
1807
|
-
if (hasDocument) document.removeEventListener("visibilitychange", onVisible);
|
|
1808
|
-
stopRefresh = () => {
|
|
1809
|
-
};
|
|
1810
|
-
};
|
|
1811
|
-
timer = setTimeout(emit, Math.max(0, due - Date.now()));
|
|
1812
|
-
}
|
|
1916
|
+
options.onState?.(withSurface({ status: "pending" }));
|
|
1917
|
+
window.location.assign(startUrl);
|
|
1813
1918
|
code = await pollUntilApproved(
|
|
1814
1919
|
issuer,
|
|
1815
1920
|
requestId,
|
|
1816
|
-
(s) =>
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1921
|
+
(s) => options.onState?.(withSurface(s)),
|
|
1922
|
+
controller.signal,
|
|
1923
|
+
{ tolerateUnknownUntil: Date.now() + 15e3 }
|
|
1924
|
+
);
|
|
1925
|
+
} else {
|
|
1926
|
+
const started = await startPairing(
|
|
1927
|
+
issuer,
|
|
1928
|
+
{
|
|
1929
|
+
client_id: options.clientId,
|
|
1930
|
+
scope: options.scope ?? "openid",
|
|
1931
|
+
state,
|
|
1932
|
+
nonce,
|
|
1933
|
+
code_challenge: await challengeS256(verifier),
|
|
1934
|
+
redirect_uri: flow === "auth-code" ? options.redirect_uri : void 0,
|
|
1935
|
+
acr_values: Array.isArray(options.acr_values) ? options.acr_values.join(" ") : options.acr_values,
|
|
1936
|
+
max_age: options.max_age,
|
|
1937
|
+
prompt: options.prompt,
|
|
1938
|
+
locale: options.locale,
|
|
1939
|
+
display: "qr"
|
|
1822
1940
|
},
|
|
1823
1941
|
controller.signal
|
|
1824
1942
|
);
|
|
1943
|
+
if ("code" in started) {
|
|
1944
|
+
code = started.code;
|
|
1945
|
+
selectBy = "session";
|
|
1946
|
+
} else {
|
|
1947
|
+
selectBy = "qr";
|
|
1948
|
+
const requestId = started.request_id;
|
|
1949
|
+
const qrBase = `${issuer}/pair/${encodeURIComponent(requestId)}/qr.svg`;
|
|
1950
|
+
const animated = started.display !== "legacy";
|
|
1951
|
+
const qrRefreshSeconds = !animated ? void 0 : typeof started.qr_refresh_seconds === "number" && started.qr_refresh_seconds > 0 ? started.qr_refresh_seconds : DEFAULT_QR_REFRESH_SECONDS;
|
|
1952
|
+
surface.requestId = requestId;
|
|
1953
|
+
surface.pairUrl = started.pair_url;
|
|
1954
|
+
surface.qrUrl = qrBase;
|
|
1955
|
+
surface.appLink = false;
|
|
1956
|
+
const withSurface = (s) => ({
|
|
1957
|
+
...s,
|
|
1958
|
+
pairUrl: surface.pairUrl,
|
|
1959
|
+
qrUrl: surface.qrUrl,
|
|
1960
|
+
qrRefreshSeconds,
|
|
1961
|
+
appLink: false,
|
|
1962
|
+
intent,
|
|
1963
|
+
cancel
|
|
1964
|
+
});
|
|
1965
|
+
let lastPolled = { status: "pending", expiresIn: started.expires_in };
|
|
1966
|
+
const initial = withSurface(lastPolled);
|
|
1967
|
+
options.onState?.(initial);
|
|
1968
|
+
if ((options.pairingUI ?? "modal") === "modal") {
|
|
1969
|
+
modal = mountPairingModal(initial, {
|
|
1970
|
+
onCancel: cancel,
|
|
1971
|
+
intent,
|
|
1972
|
+
locale: options.locale,
|
|
1973
|
+
theme: options.theme,
|
|
1974
|
+
timeoutMs: options.pairingTimeoutMs
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
if (qrRefreshSeconds !== void 0) {
|
|
1978
|
+
const periodMs = qrRefreshSeconds * 1e3;
|
|
1979
|
+
let due = Date.now() + periodMs;
|
|
1980
|
+
let timer;
|
|
1981
|
+
let stopped = false;
|
|
1982
|
+
const emit = () => {
|
|
1983
|
+
timer = void 0;
|
|
1984
|
+
surface.qrUrl = `${qrBase}?t=${Date.now()}`;
|
|
1985
|
+
const next = withSurface(lastPolled);
|
|
1986
|
+
modal?.update(next);
|
|
1987
|
+
options.onState?.(next);
|
|
1988
|
+
if (stopped) return;
|
|
1989
|
+
due = Date.now() + periodMs;
|
|
1990
|
+
timer = setTimeout(emit, periodMs);
|
|
1991
|
+
};
|
|
1992
|
+
const onVisible = () => {
|
|
1993
|
+
if (document.visibilityState === "visible" && timer !== void 0 && Date.now() >= due) {
|
|
1994
|
+
clearTimeout(timer);
|
|
1995
|
+
emit();
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
const hasDocument = typeof document !== "undefined";
|
|
1999
|
+
if (hasDocument) document.addEventListener("visibilitychange", onVisible);
|
|
2000
|
+
stopRefresh = () => {
|
|
2001
|
+
stopped = true;
|
|
2002
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2003
|
+
timer = void 0;
|
|
2004
|
+
if (hasDocument) document.removeEventListener("visibilitychange", onVisible);
|
|
2005
|
+
stopRefresh = () => {
|
|
2006
|
+
};
|
|
2007
|
+
};
|
|
2008
|
+
timer = setTimeout(emit, Math.max(0, due - Date.now()));
|
|
2009
|
+
}
|
|
2010
|
+
code = await pollUntilApproved(
|
|
2011
|
+
issuer,
|
|
2012
|
+
requestId,
|
|
2013
|
+
(s) => {
|
|
2014
|
+
lastPolled = s;
|
|
2015
|
+
if (s.status !== "pending") stopRefresh();
|
|
2016
|
+
const next = withSurface(s);
|
|
2017
|
+
modal?.update(next);
|
|
2018
|
+
options.onState?.(next);
|
|
2019
|
+
},
|
|
2020
|
+
controller.signal
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
teardown();
|
|
1825
2024
|
}
|
|
1826
|
-
teardown();
|
|
1827
2025
|
if (flow === "auth-code") {
|
|
1828
2026
|
const response2 = {
|
|
1829
2027
|
code,
|