@zoreal/oauth2-js 0.1.19 → 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 +233 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +232 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -211,6 +211,17 @@ interface LoginHandle<T> {
|
|
|
211
211
|
|
|
212
212
|
declare function startLogin(options: BrowserDirectLoginOptions): LoginHandle<ZorealCredentialResponse>;
|
|
213
213
|
declare function startLogin(options: AuthCodeLoginOptions): LoginHandle<ZorealCodeResponse>;
|
|
214
|
+
/**
|
|
215
|
+
* Finishes a same-device sign-in on the page the ZOREAL ID app reopened.
|
|
216
|
+
*
|
|
217
|
+
* Call it on every page load where `startLogin` can be called. It answers
|
|
218
|
+
* null at once when this page load is not a return; otherwise it takes the
|
|
219
|
+
* flow saved before the tap, polls the pairing named in the fragment, and
|
|
220
|
+
* resolves the way `startLogin` would have: an ID token in browser-direct
|
|
221
|
+
* mode, the code and the verifier in auth-code mode. The fragment is removed
|
|
222
|
+
* from the address bar as it is read.
|
|
223
|
+
*/
|
|
224
|
+
declare function resumeLogin(options: Pick<StartLoginOptions, 'clientId' | 'issuer' | 'onState'>): LoginHandle<ZorealCredentialResponse | ZorealCodeResponse> | null;
|
|
214
225
|
|
|
215
226
|
/**
|
|
216
227
|
* The pairing modal, in plain DOM.
|
|
@@ -310,7 +321,7 @@ declare function mountPairingModal(state: PairingState, options: PairingModalOpt
|
|
|
310
321
|
* auth-code mode leaves it to the RP backend.
|
|
311
322
|
*/
|
|
312
323
|
declare const WIRE_VERSION = 1;
|
|
313
|
-
declare const SDK_VERSION = "0.1.
|
|
324
|
+
declare const SDK_VERSION = "0.1.20";
|
|
314
325
|
declare const SDK_NAME = "@zoreal/oauth2-js";
|
|
315
326
|
declare const DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
316
327
|
/** Pending TTL is short. Poll gently; over-polling cancels the request. */
|
|
@@ -466,4 +477,4 @@ declare function unsafeClaims(idToken: string): Record<string, unknown>;
|
|
|
466
477
|
*/
|
|
467
478
|
declare function resolveIntent(intent: LoginIntent | undefined, scope: string | undefined, acrValues: string | readonly string[] | undefined): LoginIntent;
|
|
468
479
|
|
|
469
|
-
export { type AcrValue, type AuthCodeLoginOptions, type BrowserDirectLoginOptions, DEFAULT_ISSUER, DEFAULT_PAIRING_TIMEOUT_MS, DEFAULT_QR_REFRESH_SECONDS, type ErrorCode, FlowAbandonedError, type LoginHandle, type LoginIntent, type NonOAuthError, OAuthFlowError, POLL_INTERVAL_ENROLLING_MS, POLL_INTERVAL_MS, type PairCreated, type PairDisplay, type PairImmediate, type PairStartResponse, type PairStatusResponse, type PairingModalHandle, type PairingModalOptions, type PairingState, type PairingUI, SDK_NAME, SDK_VERSION, type SelectBy, type StartLoginOptions, type StartPairingParams, type TokenResponse, WIRE_VERSION, type ZorealCodeResponse, type ZorealCredentialResponse, type ZorealTheme, challengeS256, exchangeCode, generateState, generateVerifier, isMobileUserAgent, mountPairingModal, pollUntilApproved, resolveIntent, startLogin, startPairing, unsafeClaims };
|
|
480
|
+
export { type AcrValue, type AuthCodeLoginOptions, type BrowserDirectLoginOptions, DEFAULT_ISSUER, DEFAULT_PAIRING_TIMEOUT_MS, DEFAULT_QR_REFRESH_SECONDS, type ErrorCode, FlowAbandonedError, type LoginHandle, type LoginIntent, type NonOAuthError, OAuthFlowError, POLL_INTERVAL_ENROLLING_MS, POLL_INTERVAL_MS, type PairCreated, type PairDisplay, type PairImmediate, type PairStartResponse, type PairStatusResponse, type PairingModalHandle, type PairingModalOptions, type PairingState, type PairingUI, SDK_NAME, SDK_VERSION, type SelectBy, type StartLoginOptions, type StartPairingParams, type TokenResponse, WIRE_VERSION, type ZorealCodeResponse, type ZorealCredentialResponse, type ZorealTheme, challengeS256, exchangeCode, generateState, generateVerifier, isMobileUserAgent, mountPairingModal, pollUntilApproved, resolveIntent, resumeLogin, startLogin, startPairing, unsafeClaims };
|
package/dist/index.d.ts
CHANGED
|
@@ -211,6 +211,17 @@ interface LoginHandle<T> {
|
|
|
211
211
|
|
|
212
212
|
declare function startLogin(options: BrowserDirectLoginOptions): LoginHandle<ZorealCredentialResponse>;
|
|
213
213
|
declare function startLogin(options: AuthCodeLoginOptions): LoginHandle<ZorealCodeResponse>;
|
|
214
|
+
/**
|
|
215
|
+
* Finishes a same-device sign-in on the page the ZOREAL ID app reopened.
|
|
216
|
+
*
|
|
217
|
+
* Call it on every page load where `startLogin` can be called. It answers
|
|
218
|
+
* null at once when this page load is not a return; otherwise it takes the
|
|
219
|
+
* flow saved before the tap, polls the pairing named in the fragment, and
|
|
220
|
+
* resolves the way `startLogin` would have: an ID token in browser-direct
|
|
221
|
+
* mode, the code and the verifier in auth-code mode. The fragment is removed
|
|
222
|
+
* from the address bar as it is read.
|
|
223
|
+
*/
|
|
224
|
+
declare function resumeLogin(options: Pick<StartLoginOptions, 'clientId' | 'issuer' | 'onState'>): LoginHandle<ZorealCredentialResponse | ZorealCodeResponse> | null;
|
|
214
225
|
|
|
215
226
|
/**
|
|
216
227
|
* The pairing modal, in plain DOM.
|
|
@@ -310,7 +321,7 @@ declare function mountPairingModal(state: PairingState, options: PairingModalOpt
|
|
|
310
321
|
* auth-code mode leaves it to the RP backend.
|
|
311
322
|
*/
|
|
312
323
|
declare const WIRE_VERSION = 1;
|
|
313
|
-
declare const SDK_VERSION = "0.1.
|
|
324
|
+
declare const SDK_VERSION = "0.1.20";
|
|
314
325
|
declare const SDK_NAME = "@zoreal/oauth2-js";
|
|
315
326
|
declare const DEFAULT_ISSUER = "https://id.zoreal.com";
|
|
316
327
|
/** Pending TTL is short. Poll gently; over-polling cancels the request. */
|
|
@@ -466,4 +477,4 @@ declare function unsafeClaims(idToken: string): Record<string, unknown>;
|
|
|
466
477
|
*/
|
|
467
478
|
declare function resolveIntent(intent: LoginIntent | undefined, scope: string | undefined, acrValues: string | readonly string[] | undefined): LoginIntent;
|
|
468
479
|
|
|
469
|
-
export { type AcrValue, type AuthCodeLoginOptions, type BrowserDirectLoginOptions, DEFAULT_ISSUER, DEFAULT_PAIRING_TIMEOUT_MS, DEFAULT_QR_REFRESH_SECONDS, type ErrorCode, FlowAbandonedError, type LoginHandle, type LoginIntent, type NonOAuthError, OAuthFlowError, POLL_INTERVAL_ENROLLING_MS, POLL_INTERVAL_MS, type PairCreated, type PairDisplay, type PairImmediate, type PairStartResponse, type PairStatusResponse, type PairingModalHandle, type PairingModalOptions, type PairingState, type PairingUI, SDK_NAME, SDK_VERSION, type SelectBy, type StartLoginOptions, type StartPairingParams, type TokenResponse, WIRE_VERSION, type ZorealCodeResponse, type ZorealCredentialResponse, type ZorealTheme, challengeS256, exchangeCode, generateState, generateVerifier, isMobileUserAgent, mountPairingModal, pollUntilApproved, resolveIntent, startLogin, startPairing, unsafeClaims };
|
|
480
|
+
export { type AcrValue, type AuthCodeLoginOptions, type BrowserDirectLoginOptions, DEFAULT_ISSUER, DEFAULT_PAIRING_TIMEOUT_MS, DEFAULT_QR_REFRESH_SECONDS, type ErrorCode, FlowAbandonedError, type LoginHandle, type LoginIntent, type NonOAuthError, OAuthFlowError, POLL_INTERVAL_ENROLLING_MS, POLL_INTERVAL_MS, type PairCreated, type PairDisplay, type PairImmediate, type PairStartResponse, type PairStatusResponse, type PairingModalHandle, type PairingModalOptions, type PairingState, type PairingUI, SDK_NAME, SDK_VERSION, type SelectBy, type StartLoginOptions, type StartPairingParams, type TokenResponse, WIRE_VERSION, type ZorealCodeResponse, type ZorealCredentialResponse, type ZorealTheme, challengeS256, exchangeCode, generateState, generateVerifier, isMobileUserAgent, mountPairingModal, pollUntilApproved, resolveIntent, resumeLogin, startLogin, startPairing, unsafeClaims };
|
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;
|
|
@@ -198,6 +198,76 @@ function titleFor(t, intent) {
|
|
|
198
198
|
return t.title;
|
|
199
199
|
}
|
|
200
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
|
+
|
|
201
271
|
// src/i18n.ts
|
|
202
272
|
var en = {
|
|
203
273
|
title: "Scan to sign in",
|
|
@@ -1041,8 +1111,8 @@ function interpolate(template, time) {
|
|
|
1041
1111
|
}
|
|
1042
1112
|
|
|
1043
1113
|
// src/styles.ts
|
|
1044
|
-
var
|
|
1045
|
-
var cx = (name) => `${
|
|
1114
|
+
var PREFIX2 = "zrl";
|
|
1115
|
+
var cx = (name) => `${PREFIX2}-${name}`;
|
|
1046
1116
|
var STYLE_ELEMENT_ID = "zoreal-pairing-styles";
|
|
1047
1117
|
var LIGHT = `
|
|
1048
1118
|
--zrl-scrim: rgba(16, 18, 27, 0.45);
|
|
@@ -1106,13 +1176,13 @@ var DARK = `
|
|
|
1106
1176
|
--zrl-glow-opacity: 0.85;
|
|
1107
1177
|
`;
|
|
1108
1178
|
var CSS = `
|
|
1109
|
-
.${
|
|
1110
|
-
.${
|
|
1179
|
+
.${PREFIX2}-root { ${LIGHT} }
|
|
1180
|
+
.${PREFIX2}-root[data-theme="dark"] { ${DARK} }
|
|
1111
1181
|
@media (prefers-color-scheme: dark) {
|
|
1112
|
-
.${
|
|
1182
|
+
.${PREFIX2}-root[data-theme="auto"] { ${DARK} }
|
|
1113
1183
|
}
|
|
1114
1184
|
|
|
1115
|
-
.${
|
|
1185
|
+
.${PREFIX2}-scrim {
|
|
1116
1186
|
position: fixed;
|
|
1117
1187
|
inset: 0;
|
|
1118
1188
|
z-index: 2147483000;
|
|
@@ -1124,10 +1194,10 @@ var CSS = `
|
|
|
1124
1194
|
backdrop-filter: blur(3px);
|
|
1125
1195
|
-webkit-backdrop-filter: blur(3px);
|
|
1126
1196
|
font-family: inherit;
|
|
1127
|
-
animation: ${
|
|
1197
|
+
animation: ${PREFIX2}-fade 200ms ease-out both;
|
|
1128
1198
|
}
|
|
1129
1199
|
|
|
1130
|
-
.${
|
|
1200
|
+
.${PREFIX2}-card {
|
|
1131
1201
|
position: relative;
|
|
1132
1202
|
box-sizing: border-box;
|
|
1133
1203
|
width: 100%;
|
|
@@ -1139,14 +1209,14 @@ var CSS = `
|
|
|
1139
1209
|
outline: 1px solid var(--zrl-ring);
|
|
1140
1210
|
outline-offset: -1px;
|
|
1141
1211
|
text-align: center;
|
|
1142
|
-
animation: ${
|
|
1212
|
+
animation: ${PREFIX2}-rise 300ms cubic-bezier(0.23, 1, 0.32, 1) both;
|
|
1143
1213
|
}
|
|
1144
1214
|
|
|
1145
|
-
.${
|
|
1215
|
+
.${PREFIX2}-body { padding: 28px 24px 20px; }
|
|
1146
1216
|
|
|
1147
|
-
.${
|
|
1217
|
+
.${PREFIX2}-lockup { display: block; margin: 0 auto; color: var(--zrl-ink); }
|
|
1148
1218
|
|
|
1149
|
-
.${
|
|
1219
|
+
.${PREFIX2}-title {
|
|
1150
1220
|
margin: 18px 0 0;
|
|
1151
1221
|
font-size: 18px;
|
|
1152
1222
|
font-weight: 600;
|
|
@@ -1155,7 +1225,7 @@ var CSS = `
|
|
|
1155
1225
|
color: var(--zrl-ink);
|
|
1156
1226
|
}
|
|
1157
1227
|
|
|
1158
|
-
.${
|
|
1228
|
+
.${PREFIX2}-body-text {
|
|
1159
1229
|
margin: 6px auto 0;
|
|
1160
1230
|
max-width: 30ch;
|
|
1161
1231
|
font-size: 14px;
|
|
@@ -1163,7 +1233,7 @@ var CSS = `
|
|
|
1163
1233
|
color: var(--zrl-ink-soft);
|
|
1164
1234
|
}
|
|
1165
1235
|
|
|
1166
|
-
.${
|
|
1236
|
+
.${PREFIX2}-qr-well {
|
|
1167
1237
|
position: relative;
|
|
1168
1238
|
display: grid;
|
|
1169
1239
|
place-items: center;
|
|
@@ -1202,9 +1272,9 @@ var CSS = `
|
|
|
1202
1272
|
All three share one containing block, the well's padding box, so the two
|
|
1203
1273
|
comets ride the same path; the spent badge is a later sibling and paints
|
|
1204
1274
|
above them. */
|
|
1205
|
-
.${
|
|
1206
|
-
.${
|
|
1207
|
-
.${
|
|
1275
|
+
.${PREFIX2}-qr-beam,
|
|
1276
|
+
.${PREFIX2}-qr-beam-glow,
|
|
1277
|
+
.${PREFIX2}-qr-beam-glow-band {
|
|
1208
1278
|
position: absolute;
|
|
1209
1279
|
inset: calc(0px - var(--zrl-beam-line));
|
|
1210
1280
|
border: var(--zrl-beam-line) solid transparent;
|
|
@@ -1217,7 +1287,7 @@ var CSS = `
|
|
|
1217
1287
|
mask-clip: padding-box, border-box;
|
|
1218
1288
|
mask-composite: intersect;
|
|
1219
1289
|
}
|
|
1220
|
-
.${
|
|
1290
|
+
.${PREFIX2}-qr-beam-glow {
|
|
1221
1291
|
inset: calc(0px - var(--zrl-glow-reach));
|
|
1222
1292
|
border-width: var(--zrl-glow-reach);
|
|
1223
1293
|
border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-reach));
|
|
@@ -1225,7 +1295,7 @@ var CSS = `
|
|
|
1225
1295
|
opacity: var(--zrl-glow-opacity);
|
|
1226
1296
|
will-change: filter;
|
|
1227
1297
|
}
|
|
1228
|
-
.${
|
|
1298
|
+
.${PREFIX2}-qr-beam-glow-band {
|
|
1229
1299
|
inset: calc(0px - var(--zrl-glow-core));
|
|
1230
1300
|
border-width: var(--zrl-glow-core);
|
|
1231
1301
|
border-radius: calc(var(--zrl-radius) - 1px + var(--zrl-glow-core));
|
|
@@ -1236,8 +1306,8 @@ var CSS = `
|
|
|
1236
1306
|
border reads as the well's own line with a light passing over it; shown
|
|
1237
1307
|
once the light has stopped. Every path below ends here, which is what
|
|
1238
1308
|
makes them look the same at rest. */
|
|
1239
|
-
.${
|
|
1240
|
-
.${
|
|
1309
|
+
.${PREFIX2}-qr-beam::before,
|
|
1310
|
+
.${PREFIX2}-qr-beam-glow-band::before {
|
|
1241
1311
|
content: '';
|
|
1242
1312
|
position: absolute;
|
|
1243
1313
|
inset: -50%;
|
|
@@ -1250,8 +1320,8 @@ var CSS = `
|
|
|
1250
1320
|
whole. A transform animation runs on the compositor, so the light keeps
|
|
1251
1321
|
moving while the page is busy; animating the gradient angle instead
|
|
1252
1322
|
repaints every frame on the main thread and stutters. */
|
|
1253
|
-
.${
|
|
1254
|
-
.${
|
|
1323
|
+
.${PREFIX2}-qr-beam::after,
|
|
1324
|
+
.${PREFIX2}-qr-beam-glow-band::after {
|
|
1255
1325
|
content: '';
|
|
1256
1326
|
position: absolute;
|
|
1257
1327
|
inset: -50%;
|
|
@@ -1262,7 +1332,7 @@ var CSS = `
|
|
|
1262
1332
|
var(--zrl-beam-head) 348deg,
|
|
1263
1333
|
transparent 356deg 360deg
|
|
1264
1334
|
);
|
|
1265
|
-
animation: ${
|
|
1335
|
+
animation: ${PREFIX2}-orbit var(--zrl-beam-time) linear infinite;
|
|
1266
1336
|
will-change: transform;
|
|
1267
1337
|
transition: opacity 400ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
1268
1338
|
}
|
|
@@ -1270,15 +1340,15 @@ var CSS = `
|
|
|
1270
1340
|
/* Spent: the light stops where it is and fades, and the edge settles to the
|
|
1271
1341
|
dim glow. Paused rather than removed, so it does not jump back to its start
|
|
1272
1342
|
on the way out. */
|
|
1273
|
-
.${
|
|
1274
|
-
.${
|
|
1343
|
+
.${PREFIX2}-qr-well[data-spent="true"] .${PREFIX2}-qr-beam::after,
|
|
1344
|
+
.${PREFIX2}-qr-well[data-spent="true"] .${PREFIX2}-qr-beam-glow-band::after {
|
|
1275
1345
|
animation-play-state: paused;
|
|
1276
1346
|
opacity: 0;
|
|
1277
1347
|
}
|
|
1278
|
-
.${
|
|
1279
|
-
.${
|
|
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; }
|
|
1280
1350
|
|
|
1281
|
-
.${
|
|
1351
|
+
.${PREFIX2}-qr {
|
|
1282
1352
|
display: block;
|
|
1283
1353
|
width: 100%;
|
|
1284
1354
|
height: 100%;
|
|
@@ -1293,17 +1363,17 @@ var CSS = `
|
|
|
1293
1363
|
/* Once the code is claimed the QR is spent. Blurring it out rather than
|
|
1294
1364
|
swapping it keeps one object on screen through the state change, so the eye
|
|
1295
1365
|
reads a transformation instead of two things trading places. */
|
|
1296
|
-
.${
|
|
1366
|
+
.${PREFIX2}-qr[data-spent="true"] { opacity: 0.2; filter: var(--zrl-qr-spent-filter); transform: scale(0.96); }
|
|
1297
1367
|
|
|
1298
|
-
.${
|
|
1368
|
+
.${PREFIX2}-qr-overlay {
|
|
1299
1369
|
position: absolute;
|
|
1300
1370
|
inset: 0;
|
|
1301
1371
|
display: grid;
|
|
1302
1372
|
place-items: center;
|
|
1303
|
-
animation: ${
|
|
1373
|
+
animation: ${PREFIX2}-fade 200ms ease-out both;
|
|
1304
1374
|
}
|
|
1305
1375
|
|
|
1306
|
-
.${
|
|
1376
|
+
.${PREFIX2}-qr-badge {
|
|
1307
1377
|
display: grid;
|
|
1308
1378
|
place-items: center;
|
|
1309
1379
|
width: 56px;
|
|
@@ -1313,7 +1383,7 @@ var CSS = `
|
|
|
1313
1383
|
color: var(--zrl-accent-ink);
|
|
1314
1384
|
}
|
|
1315
1385
|
|
|
1316
|
-
.${
|
|
1386
|
+
.${PREFIX2}-status {
|
|
1317
1387
|
display: flex;
|
|
1318
1388
|
align-items: center;
|
|
1319
1389
|
justify-content: center;
|
|
@@ -1324,8 +1394,8 @@ var CSS = `
|
|
|
1324
1394
|
color: var(--zrl-ink);
|
|
1325
1395
|
}
|
|
1326
1396
|
|
|
1327
|
-
.${
|
|
1328
|
-
.${
|
|
1397
|
+
.${PREFIX2}-dot { position: relative; display: grid; place-items: center; width: 8px; height: 8px; }
|
|
1398
|
+
.${PREFIX2}-dot i {
|
|
1329
1399
|
position: absolute;
|
|
1330
1400
|
width: 8px;
|
|
1331
1401
|
height: 8px;
|
|
@@ -1333,25 +1403,25 @@ var CSS = `
|
|
|
1333
1403
|
background: var(--zrl-accent);
|
|
1334
1404
|
font-style: normal;
|
|
1335
1405
|
}
|
|
1336
|
-
.${
|
|
1406
|
+
.${PREFIX2}-dot i:first-child { animation: ${PREFIX2}-ping 1.8s cubic-bezier(0.23, 1, 0.32, 1) infinite; }
|
|
1337
1407
|
|
|
1338
|
-
.${
|
|
1408
|
+
.${PREFIX2}-timer {
|
|
1339
1409
|
margin: 4px 0 0;
|
|
1340
1410
|
font-size: 12px;
|
|
1341
1411
|
font-variant-numeric: tabular-nums;
|
|
1342
1412
|
color: var(--zrl-ink-mute);
|
|
1343
1413
|
transition: color 200ms ease-out;
|
|
1344
1414
|
}
|
|
1345
|
-
.${
|
|
1415
|
+
.${PREFIX2}-timer[data-urgent="true"] { color: var(--zrl-urgent); }
|
|
1346
1416
|
|
|
1347
|
-
.${
|
|
1417
|
+
.${PREFIX2}-help {
|
|
1348
1418
|
padding: 14px 24px;
|
|
1349
1419
|
border-top: 1px solid var(--zrl-line-soft);
|
|
1350
1420
|
background: var(--zrl-surface-sunken);
|
|
1351
1421
|
border-radius: 0;
|
|
1352
1422
|
}
|
|
1353
|
-
.${
|
|
1354
|
-
.${
|
|
1423
|
+
.${PREFIX2}-help-title { margin: 0; font-size: 12px; font-weight: 600; color: var(--zrl-ink); }
|
|
1424
|
+
.${PREFIX2}-help-body {
|
|
1355
1425
|
margin: 4px auto 0;
|
|
1356
1426
|
max-width: 34ch;
|
|
1357
1427
|
font-size: 12px;
|
|
@@ -1359,9 +1429,9 @@ var CSS = `
|
|
|
1359
1429
|
color: var(--zrl-ink-soft);
|
|
1360
1430
|
}
|
|
1361
1431
|
|
|
1362
|
-
.${
|
|
1432
|
+
.${PREFIX2}-footer { padding: 12px; border-top: 1px solid var(--zrl-line-soft); }
|
|
1363
1433
|
|
|
1364
|
-
.${
|
|
1434
|
+
.${PREFIX2}-cancel {
|
|
1365
1435
|
display: block;
|
|
1366
1436
|
width: 100%;
|
|
1367
1437
|
padding: 10px;
|
|
@@ -1375,10 +1445,10 @@ var CSS = `
|
|
|
1375
1445
|
cursor: pointer;
|
|
1376
1446
|
transition: background-color 150ms ease-out, color 150ms ease-out, transform 150ms ease-out;
|
|
1377
1447
|
}
|
|
1378
|
-
.${
|
|
1379
|
-
.${
|
|
1448
|
+
.${PREFIX2}-cancel:hover { background: var(--zrl-surface-sunken); color: var(--zrl-ink); }
|
|
1449
|
+
.${PREFIX2}-cancel:active { transform: scale(0.99); }
|
|
1380
1450
|
|
|
1381
|
-
.${
|
|
1451
|
+
.${PREFIX2}-secured {
|
|
1382
1452
|
display: flex;
|
|
1383
1453
|
align-items: center;
|
|
1384
1454
|
justify-content: center;
|
|
@@ -1390,9 +1460,9 @@ var CSS = `
|
|
|
1390
1460
|
border-radius: 6px;
|
|
1391
1461
|
transition: color 150ms ease-out;
|
|
1392
1462
|
}
|
|
1393
|
-
.${
|
|
1463
|
+
.${PREFIX2}-secured:hover { color: var(--zrl-ink); }
|
|
1394
1464
|
|
|
1395
|
-
.${
|
|
1465
|
+
.${PREFIX2}-close {
|
|
1396
1466
|
position: absolute;
|
|
1397
1467
|
top: 12px;
|
|
1398
1468
|
inset-inline-end: 12px;
|
|
@@ -1408,25 +1478,25 @@ var CSS = `
|
|
|
1408
1478
|
cursor: pointer;
|
|
1409
1479
|
transition: background-color 150ms ease-out, color 150ms ease-out, transform 150ms ease-out;
|
|
1410
1480
|
}
|
|
1411
|
-
.${
|
|
1412
|
-
.${
|
|
1481
|
+
.${PREFIX2}-close:hover { background: var(--zrl-surface-sunken); color: var(--zrl-ink); }
|
|
1482
|
+
.${PREFIX2}-close:active { transform: scale(0.95); }
|
|
1413
1483
|
|
|
1414
|
-
.${
|
|
1484
|
+
.${PREFIX2}-card :focus-visible {
|
|
1415
1485
|
outline: 2px solid var(--zrl-accent);
|
|
1416
1486
|
outline-offset: 2px;
|
|
1417
1487
|
}
|
|
1418
1488
|
|
|
1419
|
-
@keyframes ${
|
|
1420
|
-
@keyframes ${
|
|
1489
|
+
@keyframes ${PREFIX2}-fade { from { opacity: 0 } to { opacity: 1 } }
|
|
1490
|
+
@keyframes ${PREFIX2}-rise {
|
|
1421
1491
|
from { opacity: 0; transform: translateY(10px) scale(0.98) }
|
|
1422
1492
|
to { opacity: 1; transform: none }
|
|
1423
1493
|
}
|
|
1424
|
-
@keyframes ${
|
|
1494
|
+
@keyframes ${PREFIX2}-ping {
|
|
1425
1495
|
0% { transform: scale(1); opacity: 0.5 }
|
|
1426
1496
|
70%, 100% { transform: scale(2.6); opacity: 0 }
|
|
1427
1497
|
}
|
|
1428
1498
|
|
|
1429
|
-
@keyframes ${
|
|
1499
|
+
@keyframes ${PREFIX2}-orbit { to { transform: rotate(360deg) } }
|
|
1430
1500
|
/* THE BUSY RING. The light of the QR well, around any control that is
|
|
1431
1501
|
waiting on the provider: the button on a phone between the tap and the
|
|
1432
1502
|
hand-over to the app. The well's sweep is a cone from the centre, which is
|
|
@@ -1443,13 +1513,13 @@ var CSS = `
|
|
|
1443
1513
|
straight fade from the head to nothing three tenths of the way back; the
|
|
1444
1514
|
component sets each layer's length, offset and opacity. Shown only while
|
|
1445
1515
|
busy. */
|
|
1446
|
-
.${
|
|
1516
|
+
.${PREFIX2}-ring {
|
|
1447
1517
|
position: relative;
|
|
1448
1518
|
display: inline-flex;
|
|
1449
1519
|
vertical-align: middle;
|
|
1450
1520
|
--zrl-beam-time: 4s;
|
|
1451
1521
|
}
|
|
1452
|
-
.${
|
|
1522
|
+
.${PREFIX2}-ring-svg {
|
|
1453
1523
|
position: absolute;
|
|
1454
1524
|
inset: -4px;
|
|
1455
1525
|
width: calc(100% + 8px);
|
|
@@ -1459,8 +1529,8 @@ var CSS = `
|
|
|
1459
1529
|
opacity: 0;
|
|
1460
1530
|
transition: opacity 200ms ease-out;
|
|
1461
1531
|
}
|
|
1462
|
-
.${
|
|
1463
|
-
.${
|
|
1532
|
+
.${PREFIX2}-ring[data-busy="true"] > .${PREFIX2}-ring-svg { opacity: 1; }
|
|
1533
|
+
.${PREFIX2}-ring-svg rect {
|
|
1464
1534
|
--zrl-l: var(--zrl-ring-len, 600px);
|
|
1465
1535
|
x: 2px;
|
|
1466
1536
|
y: 2px;
|
|
@@ -1471,37 +1541,37 @@ var CSS = `
|
|
|
1471
1541
|
stroke-width: var(--zrl-beam-line);
|
|
1472
1542
|
stroke-linecap: round;
|
|
1473
1543
|
stroke-dashoffset: var(--zrl-s, 0px);
|
|
1474
|
-
animation: ${
|
|
1544
|
+
animation: ${PREFIX2}-dash var(--zrl-beam-time) linear infinite;
|
|
1475
1545
|
}
|
|
1476
|
-
.${
|
|
1477
|
-
.${
|
|
1546
|
+
.${PREFIX2}-ring-head { stroke: var(--zrl-beam-head); }
|
|
1547
|
+
.${PREFIX2}-ring-halo {
|
|
1478
1548
|
stroke-width: calc(var(--zrl-glow-core) * 2 + var(--zrl-beam-line));
|
|
1479
1549
|
filter: blur(var(--zrl-glow-blur));
|
|
1480
1550
|
}
|
|
1481
|
-
@keyframes ${
|
|
1551
|
+
@keyframes ${PREFIX2}-dash {
|
|
1482
1552
|
from { stroke-dashoffset: var(--zrl-s, 0px); }
|
|
1483
1553
|
to { stroke-dashoffset: calc(var(--zrl-s, 0px) - var(--zrl-l)); }
|
|
1484
1554
|
}
|
|
1485
1555
|
@media (prefers-reduced-motion: reduce) {
|
|
1486
|
-
.${
|
|
1487
|
-
.${
|
|
1556
|
+
.${PREFIX2}-ring-svg rect { animation: none; stroke-dasharray: none; opacity: 0.45; }
|
|
1557
|
+
.${PREFIX2}-ring-halo, .${PREFIX2}-ring-head { display: none; }
|
|
1488
1558
|
}
|
|
1489
1559
|
|
|
1490
1560
|
|
|
1491
1561
|
@media (prefers-reduced-motion: reduce) {
|
|
1492
|
-
.${
|
|
1493
|
-
.${
|
|
1494
|
-
.${
|
|
1495
|
-
.${
|
|
1496
|
-
.${
|
|
1497
|
-
.${
|
|
1498
|
-
.${
|
|
1499
|
-
.${
|
|
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 }
|
|
1500
1570
|
/* No travelling light; the edge keeps its dim static glow instead. */
|
|
1501
|
-
.${
|
|
1502
|
-
.${
|
|
1503
|
-
.${
|
|
1504
|
-
.${
|
|
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 }
|
|
1505
1575
|
}
|
|
1506
1576
|
`;
|
|
1507
1577
|
function ensureStyles() {
|
|
@@ -1906,8 +1976,23 @@ function startLogin(options) {
|
|
|
1906
1976
|
try {
|
|
1907
1977
|
let code;
|
|
1908
1978
|
let selectBy = "device";
|
|
1979
|
+
let returnId = null;
|
|
1909
1980
|
if (useAppLink && typeof window !== "undefined") {
|
|
1910
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;
|
|
1911
1996
|
const startUrl = sameDeviceStartUrl(issuer, {
|
|
1912
1997
|
client_id: options.clientId,
|
|
1913
1998
|
scope: options.scope ?? "openid",
|
|
@@ -1920,7 +2005,8 @@ function startLogin(options) {
|
|
|
1920
2005
|
prompt: options.prompt,
|
|
1921
2006
|
locale: options.locale,
|
|
1922
2007
|
request_id: requestId,
|
|
1923
|
-
origin: window.location.origin
|
|
2008
|
+
origin: window.location.origin,
|
|
2009
|
+
return_to: returnToUrl()
|
|
1924
2010
|
});
|
|
1925
2011
|
selectBy = "app_link";
|
|
1926
2012
|
surface.requestId = requestId;
|
|
@@ -1942,6 +2028,9 @@ function startLogin(options) {
|
|
|
1942
2028
|
controller.signal,
|
|
1943
2029
|
{ tolerateUnknownUntil: Date.now() + 15e3 }
|
|
1944
2030
|
);
|
|
2031
|
+
if (isReturnDone(requestId)) {
|
|
2032
|
+
throw new DOMException("aborted", "AbortError");
|
|
2033
|
+
}
|
|
1945
2034
|
} else {
|
|
1946
2035
|
const started = await startPairing(
|
|
1947
2036
|
issuer,
|
|
@@ -2042,6 +2131,7 @@ function startLogin(options) {
|
|
|
2042
2131
|
}
|
|
2043
2132
|
teardown();
|
|
2044
2133
|
}
|
|
2134
|
+
if (returnId) markReturnDone(returnId);
|
|
2045
2135
|
if (flow === "auth-code") {
|
|
2046
2136
|
const response2 = {
|
|
2047
2137
|
code,
|
|
@@ -2095,6 +2185,67 @@ function startLogin(options) {
|
|
|
2095
2185
|
}
|
|
2096
2186
|
};
|
|
2097
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
|
+
}
|
|
2098
2249
|
export {
|
|
2099
2250
|
DEFAULT_ISSUER,
|
|
2100
2251
|
DEFAULT_PAIRING_TIMEOUT_MS,
|
|
@@ -2114,6 +2265,7 @@ export {
|
|
|
2114
2265
|
mountPairingModal,
|
|
2115
2266
|
pollUntilApproved,
|
|
2116
2267
|
resolveIntent,
|
|
2268
|
+
resumeLogin,
|
|
2117
2269
|
startLogin,
|
|
2118
2270
|
startPairing,
|
|
2119
2271
|
unsafeClaims
|