@zoreal/oauth2-js 0.1.18 → 0.1.20
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 +258 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -7
- package/dist/index.d.ts +13 -7
- package/dist/index.js +257 -85
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.20";
|
|
18
18
|
var SDK_NAME = "@zoreal/oauth2-js";
|
|
19
19
|
var DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
20
20
|
var POLL_INTERVAL_MS = 2e3;
|
|
@@ -92,11 +92,30 @@ var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
|
92
92
|
}, ms);
|
|
93
93
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
94
94
|
});
|
|
95
|
+
var SETTLE_MS = 1500;
|
|
96
|
+
var NETWORK_FAILURES_TOLERATED = 4;
|
|
95
97
|
async function pollUntilApproved(issuer, requestId, onState, signal, options = {}) {
|
|
98
|
+
const settlingUntil = options.tolerateUnknownUntil ?? 0;
|
|
99
|
+
let networkFailures = 0;
|
|
100
|
+
let last = { status: "pending" };
|
|
101
|
+
if (settlingUntil > Date.now()) await sleep(SETTLE_MS, signal);
|
|
96
102
|
for (; ; ) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
let response;
|
|
104
|
+
try {
|
|
105
|
+
response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {
|
|
106
|
+
signal
|
|
107
|
+
});
|
|
108
|
+
networkFailures = 0;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
if (e instanceof DOMException && e.name === "AbortError") throw e;
|
|
111
|
+
networkFailures += 1;
|
|
112
|
+
if (settlingUntil > Date.now() || networkFailures <= NETWORK_FAILURES_TOLERATED) {
|
|
113
|
+
onState?.(last);
|
|
114
|
+
await sleep(POLL_INTERVAL_MS, signal);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
throw e;
|
|
118
|
+
}
|
|
100
119
|
const body = await parseJson(response);
|
|
101
120
|
if (response.status === 404 && (options.tolerateUnknownUntil ?? 0) > Date.now()) {
|
|
102
121
|
onState?.({ status: "pending" });
|
|
@@ -109,11 +128,12 @@ async function pollUntilApproved(issuer, requestId, onState, signal, options = {
|
|
|
109
128
|
body.error_description ?? `Pairing status failed (${response.status})`
|
|
110
129
|
);
|
|
111
130
|
}
|
|
112
|
-
|
|
131
|
+
last = {
|
|
113
132
|
status: body.status,
|
|
114
133
|
expiresIn: body.expires_in,
|
|
115
134
|
enrolmentDeadline: body.enrolment_deadline
|
|
116
|
-
}
|
|
135
|
+
};
|
|
136
|
+
onState?.(last);
|
|
117
137
|
switch (body.status) {
|
|
118
138
|
case "approved":
|
|
119
139
|
if (!body.code) {
|
|
@@ -178,6 +198,76 @@ function titleFor(t, intent) {
|
|
|
178
198
|
return t.title;
|
|
179
199
|
}
|
|
180
200
|
|
|
201
|
+
// src/return.ts
|
|
202
|
+
var PREFIX = "zoreal:oauth2:return:";
|
|
203
|
+
var DONE = "zoreal:oauth2:done:";
|
|
204
|
+
var MAX_AGE_MS = 10 * 60 * 1e3;
|
|
205
|
+
function storage() {
|
|
206
|
+
try {
|
|
207
|
+
return typeof localStorage === "undefined" ? null : localStorage;
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function saveReturnFlow(flow) {
|
|
213
|
+
try {
|
|
214
|
+
storage()?.setItem(PREFIX + flow.requestId, JSON.stringify(flow));
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function peekReturnFlow(requestId) {
|
|
219
|
+
const store = storage();
|
|
220
|
+
if (!store) return null;
|
|
221
|
+
const raw = store.getItem(PREFIX + requestId);
|
|
222
|
+
if (!raw) return null;
|
|
223
|
+
try {
|
|
224
|
+
const flow = JSON.parse(raw);
|
|
225
|
+
if (flow.v !== 1 || flow.requestId !== requestId) return null;
|
|
226
|
+
if (Date.now() - flow.createdAt > MAX_AGE_MS) return null;
|
|
227
|
+
return flow;
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function forgetReturnFlow(requestId) {
|
|
233
|
+
try {
|
|
234
|
+
storage()?.removeItem(PREFIX + requestId);
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
if (pending === requestId) pending = null;
|
|
238
|
+
}
|
|
239
|
+
function markReturnDone(requestId) {
|
|
240
|
+
try {
|
|
241
|
+
const store = storage();
|
|
242
|
+
store?.setItem(DONE + requestId, String(Date.now()));
|
|
243
|
+
store?.removeItem(PREFIX + requestId);
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function isReturnDone(requestId) {
|
|
248
|
+
return storage()?.getItem(DONE + requestId) !== null && storage()?.getItem(DONE + requestId) !== void 0;
|
|
249
|
+
}
|
|
250
|
+
function returnToUrl() {
|
|
251
|
+
if (typeof window === "undefined") return void 0;
|
|
252
|
+
const { href } = window.location;
|
|
253
|
+
const hash = href.indexOf("#");
|
|
254
|
+
return hash === -1 ? href : href.slice(0, hash);
|
|
255
|
+
}
|
|
256
|
+
var RETURN_MARK = /(?:^|[#&])zoreal_return=([A-Za-z0-9]{32})(?:&|$)/;
|
|
257
|
+
var pending = null;
|
|
258
|
+
function pendingReturnId() {
|
|
259
|
+
if (pending) return pending;
|
|
260
|
+
if (typeof window === "undefined") return null;
|
|
261
|
+
const match = RETURN_MARK.exec(window.location.hash);
|
|
262
|
+
if (!match) return null;
|
|
263
|
+
pending = match[1];
|
|
264
|
+
try {
|
|
265
|
+
window.history.replaceState(window.history.state, "", returnToUrl());
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
return pending;
|
|
269
|
+
}
|
|
270
|
+
|
|
181
271
|
// src/i18n.ts
|
|
182
272
|
var en = {
|
|
183
273
|
title: "Scan to sign in",
|
|
@@ -1021,8 +1111,8 @@ function interpolate(template, time) {
|
|
|
1021
1111
|
}
|
|
1022
1112
|
|
|
1023
1113
|
// src/styles.ts
|
|
1024
|
-
var
|
|
1025
|
-
var cx = (name) => `${
|
|
1114
|
+
var PREFIX2 = "zrl";
|
|
1115
|
+
var cx = (name) => `${PREFIX2}-${name}`;
|
|
1026
1116
|
var STYLE_ELEMENT_ID = "zoreal-pairing-styles";
|
|
1027
1117
|
var LIGHT = `
|
|
1028
1118
|
--zrl-scrim: rgba(16, 18, 27, 0.45);
|
|
@@ -1086,13 +1176,13 @@ var DARK = `
|
|
|
1086
1176
|
--zrl-glow-opacity: 0.85;
|
|
1087
1177
|
`;
|
|
1088
1178
|
var CSS = `
|
|
1089
|
-
.${
|
|
1090
|
-
.${
|
|
1179
|
+
.${PREFIX2}-root { ${LIGHT} }
|
|
1180
|
+
.${PREFIX2}-root[data-theme="dark"] { ${DARK} }
|
|
1091
1181
|
@media (prefers-color-scheme: dark) {
|
|
1092
|
-
.${
|
|
1182
|
+
.${PREFIX2}-root[data-theme="auto"] { ${DARK} }
|
|
1093
1183
|
}
|
|
1094
1184
|
|
|
1095
|
-
.${
|
|
1185
|
+
.${PREFIX2}-scrim {
|
|
1096
1186
|
position: fixed;
|
|
1097
1187
|
inset: 0;
|
|
1098
1188
|
z-index: 2147483000;
|
|
@@ -1104,10 +1194,10 @@ var CSS = `
|
|
|
1104
1194
|
backdrop-filter: blur(3px);
|
|
1105
1195
|
-webkit-backdrop-filter: blur(3px);
|
|
1106
1196
|
font-family: inherit;
|
|
1107
|
-
animation: ${
|
|
1197
|
+
animation: ${PREFIX2}-fade 200ms ease-out both;
|
|
1108
1198
|
}
|
|
1109
1199
|
|
|
1110
|
-
.${
|
|
1200
|
+
.${PREFIX2}-card {
|
|
1111
1201
|
position: relative;
|
|
1112
1202
|
box-sizing: border-box;
|
|
1113
1203
|
width: 100%;
|
|
@@ -1119,14 +1209,14 @@ var CSS = `
|
|
|
1119
1209
|
outline: 1px solid var(--zrl-ring);
|
|
1120
1210
|
outline-offset: -1px;
|
|
1121
1211
|
text-align: center;
|
|
1122
|
-
animation: ${
|
|
1212
|
+
animation: ${PREFIX2}-rise 300ms cubic-bezier(0.23, 1, 0.32, 1) both;
|
|
1123
1213
|
}
|
|
1124
1214
|
|
|
1125
|
-
.${
|
|
1215
|
+
.${PREFIX2}-body { padding: 28px 24px 20px; }
|
|
1126
1216
|
|
|
1127
|
-
.${
|
|
1217
|
+
.${PREFIX2}-lockup { display: block; margin: 0 auto; color: var(--zrl-ink); }
|
|
1128
1218
|
|
|
1129
|
-
.${
|
|
1219
|
+
.${PREFIX2}-title {
|
|
1130
1220
|
margin: 18px 0 0;
|
|
1131
1221
|
font-size: 18px;
|
|
1132
1222
|
font-weight: 600;
|
|
@@ -1135,7 +1225,7 @@ var CSS = `
|
|
|
1135
1225
|
color: var(--zrl-ink);
|
|
1136
1226
|
}
|
|
1137
1227
|
|
|
1138
|
-
.${
|
|
1228
|
+
.${PREFIX2}-body-text {
|
|
1139
1229
|
margin: 6px auto 0;
|
|
1140
1230
|
max-width: 30ch;
|
|
1141
1231
|
font-size: 14px;
|
|
@@ -1143,7 +1233,7 @@ var CSS = `
|
|
|
1143
1233
|
color: var(--zrl-ink-soft);
|
|
1144
1234
|
}
|
|
1145
1235
|
|
|
1146
|
-
.${
|
|
1236
|
+
.${PREFIX2}-qr-well {
|
|
1147
1237
|
position: relative;
|
|
1148
1238
|
display: grid;
|
|
1149
1239
|
place-items: center;
|
|
@@ -1182,9 +1272,9 @@ var CSS = `
|
|
|
1182
1272
|
All three share one containing block, the well's padding box, so the two
|
|
1183
1273
|
comets ride the same path; the spent badge is a later sibling and paints
|
|
1184
1274
|
above them. */
|
|
1185
|
-
.${
|
|
1186
|
-
.${
|
|
1187
|
-
.${
|
|
1275
|
+
.${PREFIX2}-qr-beam,
|
|
1276
|
+
.${PREFIX2}-qr-beam-glow,
|
|
1277
|
+
.${PREFIX2}-qr-beam-glow-band {
|
|
1188
1278
|
position: absolute;
|
|
1189
1279
|
inset: calc(0px - var(--zrl-beam-line));
|
|
1190
1280
|
border: var(--zrl-beam-line) solid transparent;
|
|
@@ -1197,7 +1287,7 @@ var CSS = `
|
|
|
1197
1287
|
mask-clip: padding-box, border-box;
|
|
1198
1288
|
mask-composite: intersect;
|
|
1199
1289
|
}
|
|
1200
|
-
.${
|
|
1290
|
+
.${PREFIX2}-qr-beam-glow {
|
|
1201
1291
|
inset: calc(0px - var(--zrl-glow-reach));
|
|
1202
1292
|
border-width: var(--zrl-glow-reach);
|
|
1203
1293
|
border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-reach));
|
|
@@ -1205,7 +1295,7 @@ var CSS = `
|
|
|
1205
1295
|
opacity: var(--zrl-glow-opacity);
|
|
1206
1296
|
will-change: filter;
|
|
1207
1297
|
}
|
|
1208
|
-
.${
|
|
1298
|
+
.${PREFIX2}-qr-beam-glow-band {
|
|
1209
1299
|
inset: calc(0px - var(--zrl-glow-core));
|
|
1210
1300
|
border-width: var(--zrl-glow-core);
|
|
1211
1301
|
border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-core));
|
|
@@ -1216,8 +1306,8 @@ var CSS = `
|
|
|
1216
1306
|
border reads as the well's own line with a light passing over it; shown
|
|
1217
1307
|
once the light has stopped. Every path below ends here, which is what
|
|
1218
1308
|
makes them look the same at rest. */
|
|
1219
|
-
.${
|
|
1220
|
-
.${
|
|
1309
|
+
.${PREFIX2}-qr-beam::before,
|
|
1310
|
+
.${PREFIX2}-qr-beam-glow-band::before {
|
|
1221
1311
|
content: '';
|
|
1222
1312
|
position: absolute;
|
|
1223
1313
|
inset: -50%;
|
|
@@ -1230,8 +1320,8 @@ var CSS = `
|
|
|
1230
1320
|
whole. A transform animation runs on the compositor, so the light keeps
|
|
1231
1321
|
moving while the page is busy; animating the gradient angle instead
|
|
1232
1322
|
repaints every frame on the main thread and stutters. */
|
|
1233
|
-
.${
|
|
1234
|
-
.${
|
|
1323
|
+
.${PREFIX2}-qr-beam::after,
|
|
1324
|
+
.${PREFIX2}-qr-beam-glow-band::after {
|
|
1235
1325
|
content: '';
|
|
1236
1326
|
position: absolute;
|
|
1237
1327
|
inset: -50%;
|
|
@@ -1242,7 +1332,7 @@ var CSS = `
|
|
|
1242
1332
|
var(--zrl-beam-head) 348deg,
|
|
1243
1333
|
transparent 356deg 360deg
|
|
1244
1334
|
);
|
|
1245
|
-
animation: ${
|
|
1335
|
+
animation: ${PREFIX2}-orbit var(--zrl-beam-time) linear infinite;
|
|
1246
1336
|
will-change: transform;
|
|
1247
1337
|
transition: opacity 400ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
1248
1338
|
}
|
|
@@ -1250,15 +1340,15 @@ var CSS = `
|
|
|
1250
1340
|
/* Spent: the light stops where it is and fades, and the edge settles to the
|
|
1251
1341
|
dim glow. Paused rather than removed, so it does not jump back to its start
|
|
1252
1342
|
on the way out. */
|
|
1253
|
-
.${
|
|
1254
|
-
.${
|
|
1343
|
+
.${PREFIX2}-qr-well[data-spent="true"] .${PREFIX2}-qr-beam::after,
|
|
1344
|
+
.${PREFIX2}-qr-well[data-spent="true"] .${PREFIX2}-qr-beam-glow-band::after {
|
|
1255
1345
|
animation-play-state: paused;
|
|
1256
1346
|
opacity: 0;
|
|
1257
1347
|
}
|
|
1258
|
-
.${
|
|
1259
|
-
.${
|
|
1348
|
+
.${PREFIX2}-qr-well[data-spent="true"] .${PREFIX2}-qr-beam::before { opacity: 0.55; }
|
|
1349
|
+
.${PREFIX2}-qr-well[data-spent="true"] .${PREFIX2}-qr-beam-glow-band::before { opacity: 0.7; }
|
|
1260
1350
|
|
|
1261
|
-
.${
|
|
1351
|
+
.${PREFIX2}-qr {
|
|
1262
1352
|
display: block;
|
|
1263
1353
|
width: 100%;
|
|
1264
1354
|
height: 100%;
|
|
@@ -1273,17 +1363,17 @@ var CSS = `
|
|
|
1273
1363
|
/* Once the code is claimed the QR is spent. Blurring it out rather than
|
|
1274
1364
|
swapping it keeps one object on screen through the state change, so the eye
|
|
1275
1365
|
reads a transformation instead of two things trading places. */
|
|
1276
|
-
.${
|
|
1366
|
+
.${PREFIX2}-qr[data-spent="true"] { opacity: 0.2; filter: var(--zrl-qr-spent-filter); transform: scale(0.96); }
|
|
1277
1367
|
|
|
1278
|
-
.${
|
|
1368
|
+
.${PREFIX2}-qr-overlay {
|
|
1279
1369
|
position: absolute;
|
|
1280
1370
|
inset: 0;
|
|
1281
1371
|
display: grid;
|
|
1282
1372
|
place-items: center;
|
|
1283
|
-
animation: ${
|
|
1373
|
+
animation: ${PREFIX2}-fade 200ms ease-out both;
|
|
1284
1374
|
}
|
|
1285
1375
|
|
|
1286
|
-
.${
|
|
1376
|
+
.${PREFIX2}-qr-badge {
|
|
1287
1377
|
display: grid;
|
|
1288
1378
|
place-items: center;
|
|
1289
1379
|
width: 56px;
|
|
@@ -1293,7 +1383,7 @@ var CSS = `
|
|
|
1293
1383
|
color: var(--zrl-accent-ink);
|
|
1294
1384
|
}
|
|
1295
1385
|
|
|
1296
|
-
.${
|
|
1386
|
+
.${PREFIX2}-status {
|
|
1297
1387
|
display: flex;
|
|
1298
1388
|
align-items: center;
|
|
1299
1389
|
justify-content: center;
|
|
@@ -1304,8 +1394,8 @@ var CSS = `
|
|
|
1304
1394
|
color: var(--zrl-ink);
|
|
1305
1395
|
}
|
|
1306
1396
|
|
|
1307
|
-
.${
|
|
1308
|
-
.${
|
|
1397
|
+
.${PREFIX2}-dot { position: relative; display: grid; place-items: center; width: 8px; height: 8px; }
|
|
1398
|
+
.${PREFIX2}-dot i {
|
|
1309
1399
|
position: absolute;
|
|
1310
1400
|
width: 8px;
|
|
1311
1401
|
height: 8px;
|
|
@@ -1313,25 +1403,25 @@ var CSS = `
|
|
|
1313
1403
|
background: var(--zrl-accent);
|
|
1314
1404
|
font-style: normal;
|
|
1315
1405
|
}
|
|
1316
|
-
.${
|
|
1406
|
+
.${PREFIX2}-dot i:first-child { animation: ${PREFIX2}-ping 1.8s cubic-bezier(0.23, 1, 0.32, 1) infinite; }
|
|
1317
1407
|
|
|
1318
|
-
.${
|
|
1408
|
+
.${PREFIX2}-timer {
|
|
1319
1409
|
margin: 4px 0 0;
|
|
1320
1410
|
font-size: 12px;
|
|
1321
1411
|
font-variant-numeric: tabular-nums;
|
|
1322
1412
|
color: var(--zrl-ink-mute);
|
|
1323
1413
|
transition: color 200ms ease-out;
|
|
1324
1414
|
}
|
|
1325
|
-
.${
|
|
1415
|
+
.${PREFIX2}-timer[data-urgent="true"] { color: var(--zrl-urgent); }
|
|
1326
1416
|
|
|
1327
|
-
.${
|
|
1417
|
+
.${PREFIX2}-help {
|
|
1328
1418
|
padding: 14px 24px;
|
|
1329
1419
|
border-top: 1px solid var(--zrl-line-soft);
|
|
1330
1420
|
background: var(--zrl-surface-sunken);
|
|
1331
1421
|
border-radius: 0;
|
|
1332
1422
|
}
|
|
1333
|
-
.${
|
|
1334
|
-
.${
|
|
1423
|
+
.${PREFIX2}-help-title { margin: 0; font-size: 12px; font-weight: 600; color: var(--zrl-ink); }
|
|
1424
|
+
.${PREFIX2}-help-body {
|
|
1335
1425
|
margin: 4px auto 0;
|
|
1336
1426
|
max-width: 34ch;
|
|
1337
1427
|
font-size: 12px;
|
|
@@ -1339,9 +1429,9 @@ var CSS = `
|
|
|
1339
1429
|
color: var(--zrl-ink-soft);
|
|
1340
1430
|
}
|
|
1341
1431
|
|
|
1342
|
-
.${
|
|
1432
|
+
.${PREFIX2}-footer { padding: 12px; border-top: 1px solid var(--zrl-line-soft); }
|
|
1343
1433
|
|
|
1344
|
-
.${
|
|
1434
|
+
.${PREFIX2}-cancel {
|
|
1345
1435
|
display: block;
|
|
1346
1436
|
width: 100%;
|
|
1347
1437
|
padding: 10px;
|
|
@@ -1355,10 +1445,10 @@ var CSS = `
|
|
|
1355
1445
|
cursor: pointer;
|
|
1356
1446
|
transition: background-color 150ms ease-out, color 150ms ease-out, transform 150ms ease-out;
|
|
1357
1447
|
}
|
|
1358
|
-
.${
|
|
1359
|
-
.${
|
|
1448
|
+
.${PREFIX2}-cancel:hover { background: var(--zrl-surface-sunken); color: var(--zrl-ink); }
|
|
1449
|
+
.${PREFIX2}-cancel:active { transform: scale(0.99); }
|
|
1360
1450
|
|
|
1361
|
-
.${
|
|
1451
|
+
.${PREFIX2}-secured {
|
|
1362
1452
|
display: flex;
|
|
1363
1453
|
align-items: center;
|
|
1364
1454
|
justify-content: center;
|
|
@@ -1370,9 +1460,9 @@ var CSS = `
|
|
|
1370
1460
|
border-radius: 6px;
|
|
1371
1461
|
transition: color 150ms ease-out;
|
|
1372
1462
|
}
|
|
1373
|
-
.${
|
|
1463
|
+
.${PREFIX2}-secured:hover { color: var(--zrl-ink); }
|
|
1374
1464
|
|
|
1375
|
-
.${
|
|
1465
|
+
.${PREFIX2}-close {
|
|
1376
1466
|
position: absolute;
|
|
1377
1467
|
top: 12px;
|
|
1378
1468
|
inset-inline-end: 12px;
|
|
@@ -1388,25 +1478,25 @@ var CSS = `
|
|
|
1388
1478
|
cursor: pointer;
|
|
1389
1479
|
transition: background-color 150ms ease-out, color 150ms ease-out, transform 150ms ease-out;
|
|
1390
1480
|
}
|
|
1391
|
-
.${
|
|
1392
|
-
.${
|
|
1481
|
+
.${PREFIX2}-close:hover { background: var(--zrl-surface-sunken); color: var(--zrl-ink); }
|
|
1482
|
+
.${PREFIX2}-close:active { transform: scale(0.95); }
|
|
1393
1483
|
|
|
1394
|
-
.${
|
|
1484
|
+
.${PREFIX2}-card :focus-visible {
|
|
1395
1485
|
outline: 2px solid var(--zrl-accent);
|
|
1396
1486
|
outline-offset: 2px;
|
|
1397
1487
|
}
|
|
1398
1488
|
|
|
1399
|
-
@keyframes ${
|
|
1400
|
-
@keyframes ${
|
|
1489
|
+
@keyframes ${PREFIX2}-fade { from { opacity: 0 } to { opacity: 1 } }
|
|
1490
|
+
@keyframes ${PREFIX2}-rise {
|
|
1401
1491
|
from { opacity: 0; transform: translateY(10px) scale(0.98) }
|
|
1402
1492
|
to { opacity: 1; transform: none }
|
|
1403
1493
|
}
|
|
1404
|
-
@keyframes ${
|
|
1494
|
+
@keyframes ${PREFIX2}-ping {
|
|
1405
1495
|
0% { transform: scale(1); opacity: 0.5 }
|
|
1406
1496
|
70%, 100% { transform: scale(2.6); opacity: 0 }
|
|
1407
1497
|
}
|
|
1408
1498
|
|
|
1409
|
-
@keyframes ${
|
|
1499
|
+
@keyframes ${PREFIX2}-orbit { to { transform: rotate(360deg) } }
|
|
1410
1500
|
/* THE BUSY RING. The light of the QR well, around any control that is
|
|
1411
1501
|
waiting on the provider: the button on a phone between the tap and the
|
|
1412
1502
|
hand-over to the app. The well's sweep is a cone from the centre, which is
|
|
@@ -1423,13 +1513,13 @@ var CSS = `
|
|
|
1423
1513
|
straight fade from the head to nothing three tenths of the way back; the
|
|
1424
1514
|
component sets each layer's length, offset and opacity. Shown only while
|
|
1425
1515
|
busy. */
|
|
1426
|
-
.${
|
|
1516
|
+
.${PREFIX2}-ring {
|
|
1427
1517
|
position: relative;
|
|
1428
1518
|
display: inline-flex;
|
|
1429
1519
|
vertical-align: middle;
|
|
1430
1520
|
--zrl-beam-time: 4s;
|
|
1431
1521
|
}
|
|
1432
|
-
.${
|
|
1522
|
+
.${PREFIX2}-ring-svg {
|
|
1433
1523
|
position: absolute;
|
|
1434
1524
|
inset: -4px;
|
|
1435
1525
|
width: calc(100% + 8px);
|
|
@@ -1439,8 +1529,8 @@ var CSS = `
|
|
|
1439
1529
|
opacity: 0;
|
|
1440
1530
|
transition: opacity 200ms ease-out;
|
|
1441
1531
|
}
|
|
1442
|
-
.${
|
|
1443
|
-
.${
|
|
1532
|
+
.${PREFIX2}-ring[data-busy="true"] > .${PREFIX2}-ring-svg { opacity: 1; }
|
|
1533
|
+
.${PREFIX2}-ring-svg rect {
|
|
1444
1534
|
--zrl-l: var(--zrl-ring-len, 600px);
|
|
1445
1535
|
x: 2px;
|
|
1446
1536
|
y: 2px;
|
|
@@ -1451,37 +1541,37 @@ var CSS = `
|
|
|
1451
1541
|
stroke-width: var(--zrl-beam-line);
|
|
1452
1542
|
stroke-linecap: round;
|
|
1453
1543
|
stroke-dashoffset: var(--zrl-s, 0px);
|
|
1454
|
-
animation: ${
|
|
1544
|
+
animation: ${PREFIX2}-dash var(--zrl-beam-time) linear infinite;
|
|
1455
1545
|
}
|
|
1456
|
-
.${
|
|
1457
|
-
.${
|
|
1546
|
+
.${PREFIX2}-ring-head { stroke: var(--zrl-beam-head); }
|
|
1547
|
+
.${PREFIX2}-ring-halo {
|
|
1458
1548
|
stroke-width: calc(var(--zrl-glow-core) * 2 + var(--zrl-beam-line));
|
|
1459
1549
|
filter: blur(var(--zrl-glow-blur));
|
|
1460
1550
|
}
|
|
1461
|
-
@keyframes ${
|
|
1551
|
+
@keyframes ${PREFIX2}-dash {
|
|
1462
1552
|
from { stroke-dashoffset: var(--zrl-s, 0px); }
|
|
1463
1553
|
to { stroke-dashoffset: calc(var(--zrl-s, 0px) - var(--zrl-l)); }
|
|
1464
1554
|
}
|
|
1465
1555
|
@media (prefers-reduced-motion: reduce) {
|
|
1466
|
-
.${
|
|
1467
|
-
.${
|
|
1556
|
+
.${PREFIX2}-ring-svg rect { animation: none; stroke-dasharray: none; opacity: 0.45; }
|
|
1557
|
+
.${PREFIX2}-ring-halo, .${PREFIX2}-ring-head { display: none; }
|
|
1468
1558
|
}
|
|
1469
1559
|
|
|
1470
1560
|
|
|
1471
1561
|
@media (prefers-reduced-motion: reduce) {
|
|
1472
|
-
.${
|
|
1473
|
-
.${
|
|
1474
|
-
.${
|
|
1475
|
-
.${
|
|
1476
|
-
.${
|
|
1477
|
-
.${
|
|
1478
|
-
.${
|
|
1479
|
-
.${
|
|
1562
|
+
.${PREFIX2}-scrim,
|
|
1563
|
+
.${PREFIX2}-card,
|
|
1564
|
+
.${PREFIX2}-qr-overlay { animation: none }
|
|
1565
|
+
.${PREFIX2}-dot i:first-child { animation: none; opacity: 0.35 }
|
|
1566
|
+
.${PREFIX2}-qr,
|
|
1567
|
+
.${PREFIX2}-cancel,
|
|
1568
|
+
.${PREFIX2}-close,
|
|
1569
|
+
.${PREFIX2}-timer { transition: none }
|
|
1480
1570
|
/* No travelling light; the edge keeps its dim static glow instead. */
|
|
1481
|
-
.${
|
|
1482
|
-
.${
|
|
1483
|
-
.${
|
|
1484
|
-
.${
|
|
1571
|
+
.${PREFIX2}-qr-beam::after,
|
|
1572
|
+
.${PREFIX2}-qr-beam-glow-band::after { animation: none; opacity: 0 }
|
|
1573
|
+
.${PREFIX2}-qr-beam::before { opacity: 0.55 }
|
|
1574
|
+
.${PREFIX2}-qr-beam-glow-band::before { opacity: 0.7 }
|
|
1485
1575
|
}
|
|
1486
1576
|
`;
|
|
1487
1577
|
function ensureStyles() {
|
|
@@ -1886,8 +1976,23 @@ function startLogin(options) {
|
|
|
1886
1976
|
try {
|
|
1887
1977
|
let code;
|
|
1888
1978
|
let selectBy = "device";
|
|
1979
|
+
let returnId = null;
|
|
1889
1980
|
if (useAppLink && typeof window !== "undefined") {
|
|
1890
1981
|
const requestId = generateRequestId();
|
|
1982
|
+
saveReturnFlow({
|
|
1983
|
+
v: 1,
|
|
1984
|
+
issuer,
|
|
1985
|
+
clientId: options.clientId,
|
|
1986
|
+
flow,
|
|
1987
|
+
verifier,
|
|
1988
|
+
nonce,
|
|
1989
|
+
state,
|
|
1990
|
+
scope: options.scope ?? "openid",
|
|
1991
|
+
appState: options.app_state,
|
|
1992
|
+
requestId,
|
|
1993
|
+
createdAt: Date.now()
|
|
1994
|
+
});
|
|
1995
|
+
returnId = requestId;
|
|
1891
1996
|
const startUrl = sameDeviceStartUrl(issuer, {
|
|
1892
1997
|
client_id: options.clientId,
|
|
1893
1998
|
scope: options.scope ?? "openid",
|
|
@@ -1900,7 +2005,8 @@ function startLogin(options) {
|
|
|
1900
2005
|
prompt: options.prompt,
|
|
1901
2006
|
locale: options.locale,
|
|
1902
2007
|
request_id: requestId,
|
|
1903
|
-
origin: window.location.origin
|
|
2008
|
+
origin: window.location.origin,
|
|
2009
|
+
return_to: returnToUrl()
|
|
1904
2010
|
});
|
|
1905
2011
|
selectBy = "app_link";
|
|
1906
2012
|
surface.requestId = requestId;
|
|
@@ -1922,6 +2028,9 @@ function startLogin(options) {
|
|
|
1922
2028
|
controller.signal,
|
|
1923
2029
|
{ tolerateUnknownUntil: Date.now() + 15e3 }
|
|
1924
2030
|
);
|
|
2031
|
+
if (isReturnDone(requestId)) {
|
|
2032
|
+
throw new DOMException("aborted", "AbortError");
|
|
2033
|
+
}
|
|
1925
2034
|
} else {
|
|
1926
2035
|
const started = await startPairing(
|
|
1927
2036
|
issuer,
|
|
@@ -2022,6 +2131,7 @@ function startLogin(options) {
|
|
|
2022
2131
|
}
|
|
2023
2132
|
teardown();
|
|
2024
2133
|
}
|
|
2134
|
+
if (returnId) markReturnDone(returnId);
|
|
2025
2135
|
if (flow === "auth-code") {
|
|
2026
2136
|
const response2 = {
|
|
2027
2137
|
code,
|
|
@@ -2075,6 +2185,67 @@ function startLogin(options) {
|
|
|
2075
2185
|
}
|
|
2076
2186
|
};
|
|
2077
2187
|
}
|
|
2188
|
+
function resumeLogin(options) {
|
|
2189
|
+
const id = pendingReturnId();
|
|
2190
|
+
if (!id) return null;
|
|
2191
|
+
const saved = peekReturnFlow(id);
|
|
2192
|
+
if (!saved || saved.clientId !== options.clientId) return null;
|
|
2193
|
+
forgetReturnFlow(id);
|
|
2194
|
+
const issuer = options.issuer ?? saved.issuer;
|
|
2195
|
+
const controller = new AbortController();
|
|
2196
|
+
const promise = (async () => {
|
|
2197
|
+
const code = await pollUntilApproved(
|
|
2198
|
+
issuer,
|
|
2199
|
+
id,
|
|
2200
|
+
(s) => options.onState?.({ ...s, appLink: true }),
|
|
2201
|
+
controller.signal,
|
|
2202
|
+
{ tolerateUnknownUntil: Date.now() + 5e3 }
|
|
2203
|
+
);
|
|
2204
|
+
let response;
|
|
2205
|
+
if (saved.flow === "auth-code") {
|
|
2206
|
+
response = {
|
|
2207
|
+
code,
|
|
2208
|
+
scope: saved.scope,
|
|
2209
|
+
app_state: saved.appState,
|
|
2210
|
+
code_verifier: saved.verifier,
|
|
2211
|
+
nonce: saved.nonce
|
|
2212
|
+
};
|
|
2213
|
+
} else {
|
|
2214
|
+
const tokens = await exchangeCode(issuer, {
|
|
2215
|
+
code,
|
|
2216
|
+
code_verifier: saved.verifier,
|
|
2217
|
+
client_id: options.clientId
|
|
2218
|
+
});
|
|
2219
|
+
const claims = unsafeClaims(tokens.id_token);
|
|
2220
|
+
response = {
|
|
2221
|
+
credential: tokens.id_token,
|
|
2222
|
+
clientId: options.clientId,
|
|
2223
|
+
select_by: "app_link",
|
|
2224
|
+
acr: claims.acr ?? "zoreal.device"
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
markReturnDone(id);
|
|
2228
|
+
return response;
|
|
2229
|
+
})();
|
|
2230
|
+
promise.catch(() => {
|
|
2231
|
+
});
|
|
2232
|
+
return {
|
|
2233
|
+
promise,
|
|
2234
|
+
cancel: () => controller.abort(),
|
|
2235
|
+
get requestId() {
|
|
2236
|
+
return id;
|
|
2237
|
+
},
|
|
2238
|
+
get pairUrl() {
|
|
2239
|
+
return void 0;
|
|
2240
|
+
},
|
|
2241
|
+
get qrUrl() {
|
|
2242
|
+
return void 0;
|
|
2243
|
+
},
|
|
2244
|
+
get appLink() {
|
|
2245
|
+
return true;
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2078
2249
|
export {
|
|
2079
2250
|
DEFAULT_ISSUER,
|
|
2080
2251
|
DEFAULT_PAIRING_TIMEOUT_MS,
|
|
@@ -2094,6 +2265,7 @@ export {
|
|
|
2094
2265
|
mountPairingModal,
|
|
2095
2266
|
pollUntilApproved,
|
|
2096
2267
|
resolveIntent,
|
|
2268
|
+
resumeLogin,
|
|
2097
2269
|
startLogin,
|
|
2098
2270
|
startPairing,
|
|
2099
2271
|
unsafeClaims
|