@auth-link/web 2.0.0 → 3.0.0

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/dist/vue.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/vue.ts
2
- import { defineComponent, normalizeClass, onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
2
+ import { defineComponent, onBeforeUnmount, onMounted, watch } from "vue";
3
3
 
4
4
  // node_modules/qrcode-generator/dist/qrcode.mjs
5
5
  var qrcode = function(typeNumber, errorCorrectionLevel) {
@@ -1636,195 +1636,36 @@ var qrcode_default = qrcode;
1636
1636
  var stringToBytes = qrcode.stringToBytes;
1637
1637
 
1638
1638
  // src/core.ts
1639
- var token = /^[A-Za-z0-9_-]{43}$/;
1640
1639
  var AuthLinkError = class extends Error {
1640
+ code;
1641
1641
  constructor(code) {
1642
1642
  super(code);
1643
- this.code = code;
1644
1643
  this.name = "AuthLinkError";
1644
+ this.code = code;
1645
1645
  }
1646
- code;
1647
1646
  };
1648
- function fail(code) {
1649
- throw new AuthLinkError(code);
1647
+ function deadline(value) {
1648
+ const at = typeof value === "string" ? Date.parse(value) : NaN;
1649
+ if (!Number.isFinite(at) || at <= Date.now()) throw new AuthLinkError("INVALID_EXPIRY");
1650
+ return at;
1650
1651
  }
1651
- function object(value) {
1652
- if (!value || typeof value !== "object" || Array.isArray(value)) fail("INVALID_RESPONSE");
1653
- return value;
1654
- }
1655
- function validateDisplayCode(value) {
1656
- const m = object(value);
1657
- const span = Date.parse(String(m.validUntil)) - Date.parse(String(m.serverTime));
1658
- if (typeof m.value !== "string" || !/^\d{6}$/.test(m.value) || m.periodSeconds !== 30 || !Number.isFinite(span) || span <= 0 || span > 3e4 || "seed" in m || "secret" in m || "key" in m) fail("INVALID_DISPLAY_CODE");
1659
- return { value: m.value, serverTime: String(m.serverTime), validUntil: String(m.validUntil), periodSeconds: 30 };
1660
- }
1661
- function validateTicket(value, expectedMode, trustedOrigin) {
1662
- const m = object(value);
1663
- if ("pairing" in m || "seed" in m || "apiKey" in m) fail("SERVER_SECRET_FORBIDDEN");
1664
- if (typeof m.requestId !== "string" || !token.test(m.requestId) || !["bind", "confirm"].includes(String(m.purpose)) || expectedMode && m.purpose !== expectedMode || typeof m.qr !== "string" || m.qr.length > 4096 || typeof m.pairingCode !== "string" || !/^\d{6}$/.test(m.pairingCode)) fail("INVALID_TICKET");
1665
- let qr;
1652
+ function bindingImage(ticket, expectedOrigin) {
1653
+ if (typeof ticket.qr !== "string" || ticket.qr.length > 4096) throw new AuthLinkError("INVALID_QR");
1654
+ if (deadline(ticket.expiresAt) > Date.now() + 36e5) throw new AuthLinkError("INVALID_EXPIRY");
1655
+ let payload, origin;
1666
1656
  try {
1667
- qr = object(JSON.parse(m.qr));
1657
+ payload = JSON.parse(ticket.qr);
1658
+ origin = new URL(String(payload.origin));
1668
1659
  } catch {
1669
- return fail("INVALID_QR");
1670
- }
1671
- let origin;
1672
- try {
1673
- origin = new URL(String(qr.origin));
1674
- } catch {
1675
- return fail("INVALID_QR");
1676
- }
1677
- if (qr.v !== 1 || qr.requestId !== m.requestId || typeof qr.token !== "string" || !token.test(qr.token) || origin.protocol !== "https:" || origin.origin !== qr.origin || origin.origin.length > 512 || origin.username || origin.password || trustedOrigin && origin.origin !== trustedOrigin) fail("UNTRUSTED_QR");
1678
- const displayCode = m.displayCode === void 0 ? void 0 : validateDisplayCode(m.displayCode);
1679
- const expiry = Date.parse(String(m.expiresAt));
1680
- const now = displayCode ? Date.parse(displayCode.serverTime) : Date.now();
1681
- if (!Number.isFinite(expiry) || expiry > now + 36e4) fail("INVALID_EXPIRY");
1682
- return {
1683
- requestId: m.requestId,
1684
- purpose: m.purpose,
1685
- qr: m.qr,
1686
- pairingCode: m.pairingCode,
1687
- expiresAt: String(m.expiresAt),
1688
- ...displayCode ? { displayCode } : {}
1689
- };
1690
- }
1691
- function qrImage(ticket) {
1692
- const value = validateTicket(ticket);
1693
- const payload = JSON.parse(value.qr);
1694
- const qr = qrcode_default(0, "M");
1695
- qr.addData(JSON.stringify({ v: 1, origin: payload.origin, requestId: payload.requestId, token: payload.token }), "Byte");
1696
- qr.make();
1697
- return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(qr.createSvgTag({ cellSize: 4, margin: 16, scalable: true }))}`;
1698
- }
1699
- function initialSnapshot() {
1700
- return { status: "idle", code: null, secondsRemaining: 0, codeSecondsRemaining: 0, error: null };
1701
- }
1702
- function abortable(promise, signal) {
1703
- signal.throwIfAborted();
1704
- let cancel;
1705
- const abort = new Promise((_, reject) => {
1706
- cancel = () => reject(signal.reason);
1707
- signal.addEventListener("abort", cancel, { once: true });
1708
- });
1709
- return Promise.race([promise, abort]).finally(() => signal.removeEventListener("abort", cancel));
1710
- }
1711
- async function observeRequest(input, options) {
1712
- const { client, signal, onUpdate } = options;
1713
- let ticket;
1714
- try {
1715
- ticket = validateTicket(input, options.expectedMode);
1716
- } catch (error) {
1717
- if (!signal.aborted) options.onError?.(error);
1718
- return;
1719
- }
1720
- const expiry = Date.parse(ticket.expiresAt);
1721
- const deadline = performance.now() + Math.max(0, expiry - Date.now());
1722
- let ended = false, busy = false;
1723
- let done;
1724
- const finished = new Promise((resolve) => {
1725
- done = resolve;
1726
- });
1727
- const controller = new AbortController();
1728
- const cancel = () => {
1729
- ended = true;
1730
- controller.abort();
1731
- done();
1732
- };
1733
- signal.addEventListener("abort", cancel, { once: true });
1734
- let snapshot = initialSnapshot();
1735
- const remaining = () => Math.max(0, (deadline - performance.now()) / 1e3);
1736
- const tick = () => {
1737
- if (ended || signal.aborted) return;
1738
- if (remaining() <= 0) {
1739
- ended = true;
1740
- controller.abort();
1741
- onUpdate({ ...snapshot, status: "expired", code: null, submitting: false });
1742
- void Promise.resolve().then(() => options.onExpired?.()).catch(() => options.onError?.(new AuthLinkError("CALLBACK_FAILED")));
1743
- done();
1744
- return;
1745
- }
1746
- snapshot = {
1747
- ...snapshot,
1748
- status: "pending",
1749
- code: ticket.pairingCode,
1750
- secondsRemaining: Math.ceil(remaining()),
1751
- codeSecondsRemaining: Math.ceil(remaining()),
1752
- submitting: busy
1753
- };
1754
- onUpdate(snapshot);
1755
- };
1756
- const submit = async (confirmationCode) => {
1757
- if (ended || signal.aborted || busy) return;
1758
- if (!/^[0-9]{6}$/.test(confirmationCode)) {
1759
- snapshot = { ...snapshot, error: new AuthLinkError("CONFIRMATION_CODE_REQUIRED") };
1760
- tick();
1761
- return;
1762
- }
1763
- if (remaining() <= 0) {
1764
- tick();
1765
- return;
1766
- }
1767
- busy = true;
1768
- snapshot = { ...snapshot, error: null };
1769
- tick();
1770
- try {
1771
- const value = object(await abortable(client.result(ticket, controller.signal, confirmationCode), controller.signal));
1772
- if (ended || signal.aborted) return;
1773
- if (value.requestId !== ticket.requestId) fail("REQUEST_MISMATCH");
1774
- if (value.status === "confirmed") {
1775
- if (!token.test(String(value.verificationId)) || value.authenticationPolicy !== "device-presence.v1" || value.expiresAt !== ticket.expiresAt || value.purpose !== ticket.purpose || remaining() <= 0 || "action" in value || "transactionHash" in value) fail("INVALID_CONFIRMATION");
1776
- ended = true;
1777
- onUpdate({ ...snapshot, status: "confirmed", code: null, submitting: false });
1778
- try {
1779
- await options.onConfirmed?.(value);
1780
- } catch {
1781
- options.onError?.(new AuthLinkError("CALLBACK_FAILED"));
1782
- }
1783
- done();
1784
- } else if (value.status === "rejected" || value.status === "expired") {
1785
- ended = true;
1786
- onUpdate({ ...snapshot, status: value.status, code: null, submitting: false });
1787
- try {
1788
- if (value.status === "rejected") await options.onRejected?.({ status: "rejected", code: "REJECTED", requestId: ticket.requestId, purpose: ticket.purpose });
1789
- else await options.onExpired?.();
1790
- } catch {
1791
- options.onError?.(new AuthLinkError("CALLBACK_FAILED"));
1792
- } finally {
1793
- done();
1794
- }
1795
- } else {
1796
- fail("CONFIRMATION_NOT_READY");
1797
- }
1798
- } catch (error) {
1799
- if (!ended && !signal.aborted) {
1800
- const raw = error instanceof Error ? error : new AuthLinkError("NETWORK_ERROR");
1801
- const code = raw.message.replace(/^AUTHLINK_/, "");
1802
- const failure = ["CONFIRMATION_CODE_LOCKED", "CONFIRMATION_CODE_INVALID", "APPROVAL_REQUIRED", "REQUEST_EXPIRED"].includes(code) ? new AuthLinkError(code) : raw;
1803
- snapshot = { ...snapshot, error: failure };
1804
- if (code === "CONFIRMATION_CODE_LOCKED" || code === "REQUEST_EXPIRED") {
1805
- ended = true;
1806
- onUpdate({ ...snapshot, code: null, submitting: false, status: code === "REQUEST_EXPIRED" ? "expired" : "error" });
1807
- if (code === "REQUEST_EXPIRED") void Promise.resolve().then(() => options.onExpired?.()).catch(() => options.onError?.(new AuthLinkError("CALLBACK_FAILED")));
1808
- else options.onError?.(failure);
1809
- done();
1810
- } else options.onError?.(failure);
1811
- }
1812
- } finally {
1813
- busy = false;
1814
- tick();
1815
- }
1816
- };
1817
- tick();
1818
- options.onReady?.(submit);
1819
- const timer = setInterval(tick, 1e3);
1820
- if (signal.aborted) cancel();
1821
- try {
1822
- await finished;
1823
- } finally {
1824
- clearInterval(timer);
1825
- controller.abort();
1826
- signal.removeEventListener("abort", cancel);
1660
+ throw new AuthLinkError("INVALID_QR");
1827
1661
  }
1662
+ const token = /^[A-Za-z0-9_-]{43}$/;
1663
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(origin.hostname);
1664
+ if (payload.v !== 1 || typeof payload.requestId !== "string" || !token.test(payload.requestId) || typeof payload.token !== "string" || !token.test(payload.token) || origin.origin !== payload.origin || origin.protocol !== "https:" && !(origin.protocol === "http:" && loopback) || expectedOrigin !== void 0 && expectedOrigin !== origin.origin) throw new AuthLinkError("INVALID_QR");
1665
+ const image = qrcode_default(0, "M");
1666
+ image.addData(JSON.stringify({ v: 1, origin: origin.origin, requestId: payload.requestId, token: payload.token }), "Byte");
1667
+ image.make();
1668
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(image.createSvgTag({ cellSize: 4, margin: 16, scalable: true }))}`;
1828
1669
  }
1829
1670
 
1830
1671
  // src/locales/en.json
@@ -2256,26 +2097,20 @@ function getMessages(locale) {
2256
2097
 
2257
2098
  // src/view.ts
2258
2099
  var node = (tag, props, ...children) => ({ tag, props, children });
2100
+ var digits = (value) => value.replace(/[^0-9]/g, "");
2259
2101
  function widgetView(options) {
2260
- const { snapshot: s, mode, retry, refresh } = options;
2261
- const locale = resolveLocale(options.locale);
2262
- const l = getMessages(locale);
2263
- const pending = s.status === "pending", hasQr = pending && (mode === "bind" || Boolean(options.showQr)) && Boolean(options.qr);
2264
- const status = { idle: l.loading, pending: options.passcodeMode ? l.otpWaiting : l.waiting, confirmed: l.confirmed, rejected: l.rejected, expired: l.expired, error: l.error }[s.status];
2265
- const expiring = pending && s.secondsRemaining <= 30;
2266
- const expiryTime = `00:${String(Math.max(0, Math.ceil(s.secondsRemaining))).padStart(2, "0")}`;
2267
- const statusNode = () => node(
2268
- "p",
2269
- {
2270
- class: "authlink-status",
2271
- role: s.status === "error" ? "alert" : "status",
2272
- "data-expiring": String(expiring)
2273
- },
2274
- node("span", { class: "authlink-status-dot", "aria-hidden": "true" }),
2275
- expiring ? l.expiring : status,
2276
- ...expiring ? [node("span", { class: "authlink-expiry", "aria-hidden": "true" }, ` \xB7 ${expiryTime}`)] : []
2277
- );
2102
+ const { snapshot: s, mode } = options, locale = resolveLocale(options.locale), l = getMessages(locale);
2103
+ const pending = s.status === "pending", hasQr = pending && mode === "bind" && Boolean(options.qr);
2104
+ const expiring = pending && s.secondsRemaining !== void 0 && s.secondsRemaining <= 30;
2105
+ const status = s.status === "pending" ? l.otpWaiting : l[s.status];
2106
+ const remaining = s.secondsRemaining ?? 0;
2107
+ const expiryTime = `${String(Math.floor(remaining / 60)).padStart(2, "0")}:${String(remaining % 60).padStart(2, "0")}`;
2278
2108
  const children = [node("h3", { class: "authlink-title" }, l[mode])];
2109
+ const errorNode = s.error ? node(
2110
+ "p",
2111
+ { class: "authlink-input-error", role: "alert" },
2112
+ ["PASSCODE_LOCKED", "RATE_LIMITED", "AUTHLINK_PASSCODE_LOCKED"].includes(s.error.message) ? l.codeLocked : l.otpError
2113
+ ) : void 0;
2279
2114
  if (hasQr) children.push(node("p", { class: "authlink-subtitle" }, l.scan));
2280
2115
  if (pending) {
2281
2116
  const qr = hasQr ? [node(
@@ -2285,18 +2120,18 @@ function widgetView(options) {
2285
2120
  node("figcaption", { class: "authlink-qr-caption" }, l.scan)
2286
2121
  )] : [];
2287
2122
  const detail = [
2288
- node("p", { class: "authlink-help" }, options.passcodeMode ? l.otpHelp : l.help),
2123
+ node("p", { class: "authlink-help" }, l.otpHelp),
2289
2124
  node(
2290
2125
  "form",
2291
2126
  { class: "authlink-code-form", onSubmit: (event) => {
2292
2127
  event.preventDefault();
2293
2128
  const input = event.currentTarget.querySelector("input");
2294
- if (input) void options.submitCode?.(input.value);
2129
+ if (input) void options.submitCode(input.value);
2295
2130
  } },
2296
- node("label", { for: "authlink-confirmation-code" }, options.passcodeMode ? l.otpCode : l.confirmationCode),
2131
+ node("label", { for: "authlink-passcode" }, l.otpCode),
2297
2132
  node("input", {
2298
- id: "authlink-confirmation-code",
2299
- name: options.passcodeMode ? "passcode" : "confirmationCode",
2133
+ id: "authlink-passcode",
2134
+ name: "passcode",
2300
2135
  type: "text",
2301
2136
  inputmode: "numeric",
2302
2137
  autocomplete: "one-time-code",
@@ -2304,262 +2139,74 @@ function widgetView(options) {
2304
2139
  maxlength: 6,
2305
2140
  minlength: 6,
2306
2141
  required: true,
2307
- "data-authlink-focus": "confirmation-code",
2308
- value: options.inputCode ?? "",
2309
- onInput: (event) => options.setCode?.(event.target.value)
2142
+ ...s.submitting ? { disabled: true } : {},
2143
+ "data-authlink-focus": "passcode",
2144
+ value: options.inputCode,
2145
+ onInput: (event) => {
2146
+ const input = event.target, original = input.value;
2147
+ const start = input.selectionStart ?? original.length, end = input.selectionEnd ?? start;
2148
+ const code = digits(original).slice(0, 6);
2149
+ if (original !== code) {
2150
+ input.value = code;
2151
+ input.setSelectionRange(Math.min(6, digits(original.slice(0, start)).length), Math.min(6, digits(original.slice(0, end)).length));
2152
+ }
2153
+ options.setCode(code);
2154
+ },
2155
+ onPaste: (event) => {
2156
+ if (!event.clipboardData) return;
2157
+ event.preventDefault();
2158
+ const input = event.target, pasted = digits(event.clipboardData.getData("text"));
2159
+ if (!pasted) return;
2160
+ const start = input.selectionStart ?? input.value.length, end = input.selectionEnd ?? start;
2161
+ const before = input.value.slice(0, start), after = input.value.slice(end);
2162
+ const insert = pasted.slice(0, Math.max(0, 6 - before.length - after.length));
2163
+ input.value = before + insert + after;
2164
+ input.setSelectionRange(before.length + insert.length, before.length + insert.length);
2165
+ options.setCode(input.value);
2166
+ }
2310
2167
  }),
2311
- node(
2312
- "button",
2313
- { type: "submit", class: "authlink-button", ...s.submitting ? { disabled: true } : {} },
2314
- s.submitting ? l.loading : l.submitCode
2315
- )
2316
- ),
2317
- ...s.error ? [node("p", { class: "authlink-input-error", role: "alert" }, options.passcodeMode ? s.error.message === "OTP_NOT_CONFIGURED" ? l.otpSetup : l.otpError : l.codeError)] : []
2168
+ node("button", { type: "submit", class: "authlink-button", ...s.submitting ? { disabled: true } : {} }, s.submitting ? l.loading : l.submitCode)
2169
+ )
2318
2170
  ];
2319
- children.push(node("div", { class: "authlink-body" }, ...qr, node("div", { class: "authlink-details" }, ...detail)));
2320
- children.push(node("div", { class: "authlink-footer" }, statusNode()));
2321
- } else {
2322
- children.push(statusNode());
2323
- if (s.error?.message === "CONFIRMATION_CODE_LOCKED") children.push(node("p", { class: "authlink-input-error", role: "alert" }, l.codeLocked));
2324
- if (s.status === "error" && s.error?.message !== "CONFIRMATION_CODE_LOCKED") children.push(node("button", {
2325
- type: "button",
2326
- class: "authlink-button",
2327
- "data-authlink-focus": "retry",
2328
- onClick: retry
2329
- }, l.retry));
2330
- if (refresh && ["expired", "rejected", "error"].includes(s.status)) children.push(node("button", {
2331
- type: "button",
2332
- class: "authlink-button",
2333
- "data-authlink-focus": "refresh",
2334
- onClick: refresh
2335
- }, l.refresh));
2171
+ children.push(node("div", { class: "authlink-body" }, ...qr, node("div", { class: "authlink-details" }, ...detail, ...errorNode ? [errorNode] : [])));
2336
2172
  }
2337
- return node("section", {
2338
- lang: locale,
2339
- class: `authlink-widget ${options.className ?? ""}`.trim(),
2340
- "data-status": s.status,
2341
- "data-has-qr": String(hasQr),
2342
- "aria-label": l[mode]
2343
- }, ...children);
2173
+ if (!pending && errorNode) children.push(errorNode);
2174
+ children.push(node("div", { class: "authlink-footer" }, node(
2175
+ "p",
2176
+ { class: "authlink-status", role: "status", "data-expiring": String(expiring) },
2177
+ node("span", { class: "authlink-status-dot", "aria-hidden": "true" }),
2178
+ expiring ? l.expiring : status,
2179
+ ...expiring ? [node("span", { class: "authlink-expiry", "aria-hidden": "true" }, ` \xB7 ${expiryTime}`)] : []
2180
+ )));
2181
+ if (s.status === "expired" && options.refresh) children.push(node("button", {
2182
+ type: "button",
2183
+ class: "authlink-button",
2184
+ ...s.submitting ? { disabled: true } : {},
2185
+ onClick: options.refresh
2186
+ }, s.submitting ? l.loading : l.refresh));
2187
+ return node("section", { lang: locale, class: "authlink-widget", "data-status": s.status, "data-has-qr": String(hasQr), "aria-label": l[mode] }, ...children);
2344
2188
  }
2345
2189
 
2346
2190
  // src/modal.ts
2347
- function element(value, svg = false) {
2191
+ function element(value) {
2348
2192
  if (typeof value === "string") return document.createTextNode(value);
2349
- svg ||= value.tag === "svg";
2350
- const result = svg ? document.createElementNS("http://www.w3.org/2000/svg", value.tag) : document.createElement(value.tag);
2193
+ const result = document.createElement(value.tag);
2351
2194
  for (const [key, v] of Object.entries(value.props)) {
2352
2195
  if (key.startsWith("on") && typeof v === "function") result.addEventListener(key.slice(2).toLowerCase(), v);
2353
2196
  else if (v !== void 0 && v !== null) result.setAttribute(key, String(v));
2354
2197
  }
2355
- result.append(...value.children.map((child) => element(child, svg)));
2198
+ result.append(...value.children.map(element));
2356
2199
  return result;
2357
2200
  }
2358
- function openAuthLink(options) {
2201
+ function open(options, mode) {
2359
2202
  if (typeof document === "undefined" || !document.body) throw new AuthLinkError("BROWSER_REQUIRED");
2360
- const mode = options.mode ?? options.ticket.purpose;
2361
- const ticket = validateTicket(options.ticket, mode);
2203
+ if (typeof options.submit !== "function") throw new AuthLinkError("SUBMIT_REQUIRED");
2204
+ const binding = mode === "bind" ? options : void 0;
2205
+ let image = binding ? bindingImage(binding, binding.authLinkOrigin) : void 0;
2206
+ let expires = options.expiresAt === void 0 ? void 0 : deadline(options.expiresAt);
2362
2207
  const dialog = document.createElement("dialog");
2363
2208
  if (typeof dialog.showModal !== "function") throw new AuthLinkError("DIALOG_UNSUPPORTED");
2364
2209
  const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2365
- dialog.className = `authlink-dialog ${options.className ?? ""}`.trim();
2366
- dialog.setAttribute("aria-modal", "true");
2367
- const header = document.createElement("div");
2368
- header.className = "authlink-header";
2369
- const brand = document.createElement("span");
2370
- brand.className = "authlink-brand";
2371
- brand.textContent = "AuthLink";
2372
- brand.tabIndex = -1;
2373
- const closeButton = document.createElement("button");
2374
- closeButton.type = "button";
2375
- closeButton.className = "authlink-close";
2376
- const closeIcon = document.createElement("span");
2377
- closeIcon.className = "authlink-close-icon";
2378
- closeIcon.setAttribute("aria-hidden", "true");
2379
- closeButton.append(closeIcon);
2380
- const content = document.createElement("div");
2381
- content.className = "authlink-modal-content";
2382
- header.append(brand, closeButton);
2383
- dialog.append(header, content);
2384
- let locale = options.locale, snapshot = initialSnapshot(), closed = false;
2385
- let abort;
2386
- let inputCode = "";
2387
- let submitCode;
2388
- const image = qrImage(ticket);
2389
- let progressFill, progressFrame, progressDeadline = 0;
2390
- const motion = window.matchMedia?.("(prefers-reduced-motion: reduce)");
2391
- const stopProgress = () => {
2392
- if (progressFrame !== void 0) window.cancelAnimationFrame(progressFrame);
2393
- progressFrame = void 0;
2394
- };
2395
- const drawProgress = () => {
2396
- stopProgress();
2397
- if (closed || !progressFill?.isConnected) return;
2398
- const remaining = Math.max(0, progressDeadline - performance.now());
2399
- progressFill.style.transform = `scaleX(${Math.min(1, remaining / 3e4)})`;
2400
- if (remaining > 0 && !motion?.matches && typeof window.requestAnimationFrame === "function") {
2401
- progressFrame = window.requestAnimationFrame(drawProgress);
2402
- }
2403
- };
2404
- const syncProgress = (remainingMs) => {
2405
- progressDeadline = performance.now() + remainingMs;
2406
- drawProgress();
2407
- };
2408
- motion?.addEventListener?.("change", drawProgress);
2409
- const render = () => {
2410
- if (closed) return;
2411
- const language = resolveLocale(locale), messages2 = getMessages(language);
2412
- dialog.lang = language;
2413
- dialog.setAttribute("aria-label", messages2[mode]);
2414
- dialog.dataset.mode = mode;
2415
- dialog.dataset.hasQr = String(snapshot.status === "pending" && (mode === "bind" || Boolean(options.showQr)));
2416
- closeButton.setAttribute("aria-label", messages2.close);
2417
- const focused = document.activeElement;
2418
- const focusKey = focused instanceof HTMLElement && content.contains(focused) ? focused.dataset.authlinkFocus : void 0;
2419
- const next = element(widgetView({
2420
- mode,
2421
- locale,
2422
- snapshot,
2423
- qr: image,
2424
- showQr: options.showQr,
2425
- inputCode,
2426
- setCode: (code) => {
2427
- inputCode = code;
2428
- },
2429
- submitCode: (code) => submitCode?.(code) ?? Promise.resolve(),
2430
- retry,
2431
- refresh: options.onRefresh
2432
- }));
2433
- const oldCode = content.querySelector(".authlink-code");
2434
- const nextCode = next.querySelector(".authlink-code");
2435
- const codeChanged = Boolean(oldCode && nextCode && snapshot.code && oldCode.textContent !== nextCode.textContent);
2436
- if (oldCode && nextCode && oldCode.textContent === nextCode.textContent) {
2437
- oldCode.setAttribute("aria-label", nextCode.getAttribute("aria-label"));
2438
- nextCode.replaceWith(oldCode);
2439
- }
2440
- const oldProgress = content.querySelector(".authlink-progress");
2441
- const nextProgress = next.querySelector(".authlink-progress");
2442
- if (oldProgress && nextProgress) {
2443
- oldProgress.setAttribute("aria-valuenow", nextProgress.getAttribute("aria-valuenow"));
2444
- oldProgress.setAttribute("aria-label", nextProgress.getAttribute("aria-label"));
2445
- nextProgress.replaceWith(oldProgress);
2446
- }
2447
- const oldInput = content.querySelector("input[name=confirmationCode]");
2448
- const nextInput = next.querySelector("input[name=confirmationCode]");
2449
- if (oldInput && nextInput) nextInput.replaceWith(oldInput);
2450
- content.replaceChildren(next);
2451
- progressFill = content.querySelector(".authlink-progress-fill") ?? void 0;
2452
- drawProgress();
2453
- if (codeChanged && !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
2454
- nextCode?.animate?.([{ opacity: 0.5 }, { opacity: 1 }], { duration: 180, easing: "ease-out" });
2455
- }
2456
- if (focusKey) (Array.from(content.querySelectorAll("[data-authlink-focus]")).find((item) => item.dataset.authlinkFocus === focusKey) ?? brand).focus({ preventScroll: true });
2457
- };
2458
- const dispose = (notify) => {
2459
- if (closed) return;
2460
- closed = true;
2461
- abort?.abort();
2462
- stopProgress();
2463
- progressFill = void 0;
2464
- motion?.removeEventListener?.("change", drawProgress);
2465
- if (dialog.open) dialog.close();
2466
- dialog.remove();
2467
- if (!document.querySelector("dialog.authlink-dialog[open]")) {
2468
- if (document.body.style.overflow === "hidden") document.body.style.overflow = document.body.dataset.authlinkOverflow ?? "";
2469
- delete document.body.dataset.authlinkOverflow;
2470
- }
2471
- if (previousFocus?.isConnected) previousFocus.focus();
2472
- if (notify) options.onClose?.();
2473
- };
2474
- const delivered = /* @__PURE__ */ new Set();
2475
- const once = (status, callback) => {
2476
- if (delivered.has(status)) return;
2477
- delivered.add(status);
2478
- return callback();
2479
- };
2480
- const retry = () => {
2481
- if (closed) return;
2482
- abort?.abort();
2483
- abort = new AbortController();
2484
- snapshot = initialSnapshot();
2485
- render();
2486
- void observeRequest(ticket, {
2487
- client: options.client,
2488
- intervalMs: options.intervalMs,
2489
- expectedMode: mode,
2490
- signal: abort.signal,
2491
- onReady: (submit) => {
2492
- submitCode = submit;
2493
- },
2494
- onCodeTiming: syncProgress,
2495
- onUpdate: (value) => {
2496
- snapshot = value;
2497
- if (value.status !== "rejected") render();
2498
- },
2499
- onConfirmed: (value) => once("confirmed", () => {
2500
- dispose(false);
2501
- try {
2502
- return Promise.resolve(options.onConfirmed?.(value)).catch(() => options.onError?.(new AuthLinkError("CALLBACK_FAILED")));
2503
- } catch {
2504
- options.onError?.(new AuthLinkError("CALLBACK_FAILED"));
2505
- }
2506
- }),
2507
- onRejected: (value) => once("rejected", () => {
2508
- dispose(false);
2509
- try {
2510
- return Promise.resolve(options.onRejected?.(value)).catch(() => options.onError?.(new AuthLinkError("CALLBACK_FAILED")));
2511
- } catch {
2512
- options.onError?.(new AuthLinkError("CALLBACK_FAILED"));
2513
- }
2514
- }),
2515
- onExpired: () => once("expired", () => options.onExpired?.()),
2516
- onError: options.onError
2517
- });
2518
- };
2519
- closeButton.addEventListener("click", () => dispose(true));
2520
- dialog.addEventListener("cancel", (event) => {
2521
- event.preventDefault();
2522
- dispose(true);
2523
- });
2524
- dialog.addEventListener("close", () => dispose(true));
2525
- render();
2526
- document.body.append(dialog);
2527
- try {
2528
- dialog.showModal();
2529
- } catch (error) {
2530
- dialog.remove();
2531
- throw error;
2532
- }
2533
- brand.focus({ preventScroll: true });
2534
- if (document.body.dataset.authlinkOverflow === void 0) document.body.dataset.authlinkOverflow = document.body.style.overflow;
2535
- document.body.style.overflow = "hidden";
2536
- retry();
2537
- return {
2538
- close: () => dispose(true),
2539
- destroy: () => dispose(false),
2540
- setLocale: (value) => {
2541
- locale = value;
2542
- render();
2543
- },
2544
- retry
2545
- };
2546
- }
2547
-
2548
- // src/passcode.ts
2549
- function validateOTPResult(value) {
2550
- if (!value || typeof value !== "object") throw new AuthLinkError("INVALID_RESPONSE");
2551
- const result = value;
2552
- const expiry = typeof result.expiresAt === "string" ? Date.parse(result.expiresAt) : NaN;
2553
- if (result.status !== "confirmed" || result.authenticationPolicy !== "device-totp.v1" || typeof result.verificationId !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(result.verificationId) || !Number.isFinite(expiry) || expiry <= Date.now() || expiry > Date.now() + 9e4 || "requestId" in result || "state" in result || "businessId" in result) throw new AuthLinkError("INVALID_CONFIRMATION");
2554
- return result;
2555
- }
2556
- function openPasscode(options) {
2557
- if (typeof document === "undefined" || !document.body) throw new AuthLinkError("BROWSER_REQUIRED");
2558
- const deadline = Date.parse(options.expiresAt);
2559
- if (!Number.isFinite(deadline) || deadline <= Date.now() || deadline > Date.now() + 3e5) throw new AuthLinkError("INVALID_EXPIRY");
2560
- const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2561
- const dialog = document.createElement("dialog");
2562
- if (typeof dialog.showModal !== "function") throw new AuthLinkError("DIALOG_UNSUPPORTED");
2563
2210
  dialog.className = "authlink-dialog";
2564
2211
  dialog.setAttribute("aria-modal", "true");
2565
2212
  const header = document.createElement("div");
@@ -2578,91 +2225,143 @@ function openPasscode(options) {
2578
2225
  content.className = "authlink-modal-content";
2579
2226
  header.append(brand, close);
2580
2227
  dialog.append(header, content);
2581
- let locale = options.locale, closed = false, busy = false, code = "", error;
2582
- const mode = options.bindingQr ? "bind" : "confirm";
2583
- let image;
2584
- if (options.bindingQr) {
2585
- let payload;
2228
+ let locale = options.locale, closed = false, busy = false, code = "", error = null;
2229
+ let abort, timer, expiredDelivered = false;
2230
+ const notifyError = (cause) => {
2231
+ const value = cause instanceof Error ? cause : new AuthLinkError("VERIFICATION_FAILED");
2586
2232
  try {
2587
- payload = JSON.parse(options.bindingQr);
2233
+ options.onError?.(value);
2588
2234
  } catch {
2589
- throw new AuthLinkError("INVALID_TICKET");
2590
2235
  }
2591
- image = qrImage(validateTicket({ requestId: payload.requestId, purpose: "bind", qr: options.bindingQr, pairingCode: "000000", expiresAt: options.expiresAt }, "bind", options.authLinkOrigin));
2592
- }
2593
- const abort = new AbortController();
2594
- let timer;
2236
+ return value;
2237
+ };
2238
+ const callback = (fn) => {
2239
+ try {
2240
+ void Promise.resolve(fn?.()).catch(() => notifyError(new AuthLinkError("CALLBACK_FAILED")));
2241
+ } catch {
2242
+ notifyError(new AuthLinkError("CALLBACK_FAILED"));
2243
+ }
2244
+ };
2595
2245
  const dispose = (notify) => {
2596
2246
  if (closed) return;
2597
2247
  closed = true;
2598
- abort.abort();
2248
+ abort?.abort();
2599
2249
  clearInterval(timer);
2250
+ if (dialog.open) dialog.close();
2600
2251
  dialog.remove();
2601
- previousFocus?.focus();
2602
- if (notify) options.onClose?.();
2252
+ if (previousFocus?.isConnected) previousFocus.focus();
2253
+ if (notify) callback(options.onClose);
2603
2254
  };
2255
+ const expired = () => expires !== void 0 && Date.now() >= expires;
2604
2256
  const render = () => {
2605
2257
  if (closed) return;
2606
2258
  const l = getMessages(locale);
2607
2259
  dialog.lang = resolveLocale(locale);
2608
2260
  dialog.dataset.mode = mode;
2609
- dialog.dataset.hasQr = String(Boolean(image));
2261
+ dialog.dataset.hasQr = String(Boolean(image) && !expired());
2610
2262
  dialog.setAttribute("aria-label", l[mode]);
2611
2263
  close.setAttribute("aria-label", l.close);
2612
- const focused = content.contains(document.activeElement);
2613
- const remaining = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3));
2614
- const snapshot = { status: remaining ? "pending" : "expired", code: null, secondsRemaining: remaining, codeSecondsRemaining: remaining, submitting: busy, error: error ?? null };
2615
- content.replaceChildren(element(widgetView({
2264
+ const focused = document.activeElement;
2265
+ const focusKey = focused instanceof HTMLElement && content.contains(focused) ? focused.dataset.authlinkFocus : void 0;
2266
+ const oldInput = content.querySelector("input");
2267
+ const next = element(widgetView({
2616
2268
  mode,
2617
2269
  locale,
2618
- passcodeMode: true,
2619
- snapshot,
2270
+ snapshot: {
2271
+ status: expired() ? "expired" : "pending",
2272
+ submitting: busy,
2273
+ error,
2274
+ secondsRemaining: expires === void 0 ? void 0 : Math.max(0, Math.ceil((expires - Date.now()) / 1e3))
2275
+ },
2620
2276
  qr: image,
2621
- showQr: Boolean(image),
2622
2277
  inputCode: code,
2623
2278
  setCode: (value) => {
2624
2279
  code = value;
2625
2280
  },
2626
2281
  submitCode: submit,
2627
- retry: () => {
2628
- error = void 0;
2629
- render();
2630
- }
2631
- })));
2632
- if (focused) content.querySelector("input")?.focus();
2282
+ refresh: binding?.onRefresh ? refresh : void 0
2283
+ }));
2284
+ const nextInput = next.querySelector("input");
2285
+ if (oldInput && nextInput) {
2286
+ oldInput.disabled = busy;
2287
+ if (oldInput.value !== code) oldInput.value = code;
2288
+ nextInput.replaceWith(oldInput);
2289
+ }
2290
+ content.replaceChildren(next);
2291
+ if (focusKey) content.querySelector(`[data-authlink-focus="${focusKey}"]`)?.focus({ preventScroll: true });
2633
2292
  };
2634
- const submit = async (value) => {
2635
- if (closed || busy) return;
2636
- if (Date.now() >= deadline) {
2293
+ const tick = () => {
2294
+ if (closed) return;
2295
+ if (expired() && !expiredDelivered) {
2296
+ expiredDelivered = true;
2297
+ abort?.abort();
2298
+ busy = false;
2637
2299
  render();
2300
+ callback(options.onExpired);
2301
+ } else if (expires !== void 0) render();
2302
+ };
2303
+ const startTimer = () => {
2304
+ clearInterval(timer);
2305
+ if (expires !== void 0) timer = setInterval(tick, 1e3);
2306
+ };
2307
+ const submit = async (value) => {
2308
+ if (closed || busy || expired()) {
2309
+ tick();
2638
2310
  return;
2639
2311
  }
2640
2312
  if (!/^[0-9]{6}$/.test(value)) {
2641
- error = new AuthLinkError("PASSCODE_INVALID");
2313
+ error = notifyError(new AuthLinkError("PASSCODE_INVALID"));
2642
2314
  render();
2643
2315
  return;
2644
2316
  }
2645
2317
  code = value;
2646
2318
  busy = true;
2647
- error = void 0;
2319
+ error = null;
2320
+ const current = new AbortController();
2321
+ abort = current;
2648
2322
  render();
2649
2323
  try {
2650
- const result = validateOTPResult(await options.client.verify(value, abort.signal));
2651
- if (closed || abort.signal.aborted || Date.now() >= deadline) return;
2652
- try {
2653
- await options.onVerified?.(result);
2654
- } catch {
2655
- options.onError?.(new AuthLinkError("CALLBACK_FAILED"));
2656
- }
2324
+ const pending = options.submit(value, { signal: current.signal });
2325
+ if (!pending || typeof pending.then !== "function") throw new AuthLinkError("SUBMIT_MUST_RETURN_PROMISE");
2326
+ await pending;
2327
+ if (closed || current.signal.aborted || abort !== current) return;
2657
2328
  dispose(false);
2329
+ callback(options.onSuccess);
2658
2330
  } catch (cause) {
2659
- if (!closed && !abort.signal.aborted) {
2660
- error = cause instanceof Error ? cause : new AuthLinkError("NETWORK_ERROR");
2661
- options.onError?.(error);
2331
+ if (!closed && !current.signal.aborted && abort === current) error = notifyError(cause);
2332
+ } finally {
2333
+ if (!closed && abort === current) {
2334
+ busy = false;
2335
+ render();
2662
2336
  }
2337
+ }
2338
+ };
2339
+ const refresh = async () => {
2340
+ if (closed || busy || !binding?.onRefresh || !expired()) return;
2341
+ busy = true;
2342
+ error = null;
2343
+ expiredDelivered = true;
2344
+ clearInterval(timer);
2345
+ const current = new AbortController();
2346
+ abort?.abort();
2347
+ abort = current;
2348
+ render();
2349
+ try {
2350
+ const ticket = await binding.onRefresh({ signal: current.signal });
2351
+ if (closed || current.signal.aborted || abort !== current) return;
2352
+ const nextImage = bindingImage(ticket, binding.authLinkOrigin), nextDeadline = deadline(ticket.expiresAt);
2353
+ image = nextImage;
2354
+ expires = nextDeadline;
2355
+ code = "";
2356
+ expiredDelivered = false;
2357
+ startTimer();
2358
+ } catch (cause) {
2359
+ if (!closed && !current.signal.aborted && abort === current) error = notifyError(cause);
2663
2360
  } finally {
2664
- busy = false;
2665
- render();
2361
+ if (!closed && abort === current) {
2362
+ busy = false;
2363
+ render();
2364
+ }
2666
2365
  }
2667
2366
  };
2668
2367
  close.addEventListener("click", () => dispose(true));
@@ -2670,18 +2369,17 @@ function openPasscode(options) {
2670
2369
  event.preventDefault();
2671
2370
  dispose(true);
2672
2371
  });
2372
+ dialog.addEventListener("close", () => dispose(true));
2673
2373
  document.body.append(dialog);
2674
2374
  render();
2675
- dialog.showModal();
2375
+ try {
2376
+ dialog.showModal();
2377
+ } catch (cause) {
2378
+ dispose(false);
2379
+ throw cause;
2380
+ }
2676
2381
  content.querySelector("input")?.focus();
2677
- timer = setInterval(() => {
2678
- if (closed) return;
2679
- if (Date.now() >= deadline) {
2680
- render();
2681
- clearInterval(timer);
2682
- void Promise.resolve(options.onExpired?.()).catch(() => options.onError?.(new AuthLinkError("CALLBACK_FAILED")));
2683
- }
2684
- }, 1e3);
2382
+ startTimer();
2685
2383
  return {
2686
2384
  close: () => dispose(true),
2687
2385
  destroy: () => dispose(false),
@@ -2690,149 +2388,58 @@ function openPasscode(options) {
2690
2388
  render();
2691
2389
  },
2692
2390
  retry: () => {
2693
- error = void 0;
2694
- render();
2391
+ if (!closed && !busy) {
2392
+ error = null;
2393
+ tick();
2394
+ render();
2395
+ }
2695
2396
  }
2696
2397
  };
2697
2398
  }
2399
+ var openVerify = (options) => open(options, "confirm");
2400
+ var openBind = (options) => open(options, "bind");
2698
2401
 
2699
2402
  // src/vue.ts
2700
- var AuthLinkPasscode = defineComponent({
2701
- name: "AuthLinkPasscode",
2702
- props: {
2703
- client: { type: Object, required: true },
2704
- expiresAt: { type: String, required: true },
2705
- bindingQr: String,
2706
- authLinkOrigin: String,
2707
- locale: String
2708
- },
2709
- emits: ["verified", "expired", "error", "close"],
2710
- setup(props, { emit }) {
2711
- let modal, mounted = false;
2712
- const open = () => {
2713
- if (!mounted) return;
2714
- modal?.destroy();
2715
- modal = openPasscode({
2716
- ...props,
2717
- onVerified: (value) => emit("verified", value),
2718
- onExpired: () => emit("expired"),
2719
- onError: (value) => emit("error", value),
2720
- onClose: () => emit("close")
2721
- });
2722
- };
2723
- onMounted(() => {
2724
- mounted = true;
2725
- open();
2726
- });
2727
- watch([() => props.client, () => props.expiresAt, () => props.bindingQr, () => props.authLinkOrigin], open);
2728
- watch(() => props.locale, (value) => modal?.setLocale(value));
2729
- onBeforeUnmount(() => {
2730
- mounted = false;
2731
- modal?.destroy();
2732
- });
2733
- return () => null;
2734
- }
2735
- });
2736
- function useAuthLink(getOptions) {
2737
- const snapshot = shallowRef(initialSnapshot());
2738
- let submit;
2739
- const delivered = /* @__PURE__ */ new Set();
2740
- let mounted = false, abort;
2741
- const retry = () => {
2742
- if (!mounted) return;
2743
- abort?.abort();
2744
- abort = new AbortController();
2745
- const options = getOptions();
2746
- snapshot.value = initialSnapshot();
2747
- const once = (status, callback) => {
2748
- const key = `${options.ticket.requestId}:${status}`;
2749
- if (delivered.has(key)) return;
2750
- delivered.add(key);
2751
- return callback();
2752
- };
2753
- void observeRequest(options.ticket, {
2754
- client: options.client,
2755
- intervalMs: options.intervalMs,
2756
- expectedMode: options.mode,
2757
- signal: abort.signal,
2758
- onReady: (fn) => {
2759
- submit = fn;
2760
- },
2761
- onUpdate: (value) => {
2762
- snapshot.value = value;
2763
- },
2764
- onConfirmed: (value) => once("confirmed", () => getOptions().onConfirmed?.(value)),
2765
- onRejected: (result) => once("rejected", () => getOptions().onRejected?.(result)),
2766
- onExpired: () => once("expired", () => getOptions().onExpired?.()),
2767
- onError: (error) => getOptions().onError?.(error)
2768
- });
2769
- };
2770
- onMounted(() => {
2771
- mounted = true;
2772
- retry();
2773
- });
2774
- watch([
2775
- () => getOptions().ticket,
2776
- () => getOptions().client,
2777
- () => getOptions().intervalMs,
2778
- () => getOptions().mode
2779
- ], retry);
2780
- onBeforeUnmount(() => {
2781
- mounted = false;
2782
- abort?.abort();
2783
- });
2784
- return { snapshot, retry, submitCode: (code) => submit?.(code) ?? Promise.resolve() };
2785
- }
2786
- function widget(mode) {
2403
+ function prompt(bind) {
2787
2404
  return defineComponent({
2788
- name: mode === "bind" ? "AuthLinkBind" : "AuthLinkConfirm",
2405
+ name: bind ? "AuthLinkBind" : "AuthLinkVerify",
2789
2406
  inheritAttrs: false,
2790
2407
  props: {
2791
- ticket: { type: Object, required: true },
2792
- client: { type: Object, required: true },
2793
- locale: { type: String, default: defaultLocale },
2794
- intervalMs: Number,
2795
- showQr: Boolean,
2796
- className: String,
2408
+ submit: { type: Function, required: true },
2409
+ expiresAt: { type: String, required: bind },
2410
+ qr: { type: String, required: bind },
2411
+ authLinkOrigin: String,
2412
+ locale: String,
2797
2413
  onRefresh: Function
2798
2414
  },
2799
- emits: ["confirmed", "rejected", "expired", "error", "close"],
2800
- setup(props, { emit, attrs }) {
2415
+ emits: ["success", "close", "error", "expired"],
2416
+ setup(props, { emit }) {
2801
2417
  let modal, mounted = false;
2802
- const delivered = /* @__PURE__ */ new Set();
2803
- const once = (status, callback) => {
2804
- const key = `${props.ticket.requestId}:${status}`;
2805
- if (delivered.has(key)) return;
2806
- delivered.add(key);
2807
- callback();
2808
- };
2809
- const open = () => {
2418
+ const open2 = () => {
2810
2419
  if (!mounted) return;
2811
2420
  modal?.destroy();
2812
- modal = openAuthLink({
2813
- ...props,
2814
- mode,
2815
- className: normalizeClass([props.className, attrs.class]),
2816
- onRefresh: props.onRefresh ? () => props.onRefresh?.() : void 0,
2817
- onConfirmed: (value) => once("confirmed", () => emit("confirmed", value)),
2818
- onRejected: (result) => once("rejected", () => emit("rejected", result)),
2819
- onExpired: () => once("expired", () => emit("expired")),
2421
+ const common = {
2422
+ locale: props.locale,
2423
+ expiresAt: props.expiresAt,
2424
+ submit: (code, context) => props.submit(code, context),
2425
+ onSuccess: () => emit("success"),
2426
+ onClose: () => emit("close"),
2820
2427
  onError: (error) => emit("error", error),
2821
- onClose: () => emit("close")
2822
- });
2428
+ onExpired: () => emit("expired")
2429
+ };
2430
+ modal = bind ? openBind({
2431
+ ...common,
2432
+ qr: props.qr,
2433
+ expiresAt: props.expiresAt,
2434
+ authLinkOrigin: props.authLinkOrigin,
2435
+ onRefresh: props.onRefresh ? (context) => props.onRefresh(context) : void 0
2436
+ }) : openVerify(common);
2823
2437
  };
2824
2438
  onMounted(() => {
2825
2439
  mounted = true;
2826
- open();
2440
+ open2();
2827
2441
  });
2828
- watch([
2829
- () => props.ticket,
2830
- () => props.client,
2831
- () => props.intervalMs,
2832
- () => props.showQr,
2833
- () => props.className,
2834
- () => Boolean(props.onRefresh)
2835
- ], open);
2442
+ watch([() => props.expiresAt, () => props.qr, () => props.authLinkOrigin, () => Boolean(props.onRefresh)], open2);
2836
2443
  watch(() => props.locale, (value) => modal?.setLocale(value));
2837
2444
  onBeforeUnmount(() => {
2838
2445
  mounted = false;
@@ -2842,12 +2449,10 @@ function widget(mode) {
2842
2449
  }
2843
2450
  });
2844
2451
  }
2845
- var AuthLinkBind = widget("bind");
2846
- var AuthLinkConfirm = widget("confirm");
2452
+ var AuthLinkVerify = prompt(false);
2453
+ var AuthLinkBind = prompt(true);
2847
2454
  export {
2848
2455
  AuthLinkBind,
2849
- AuthLinkConfirm,
2850
- AuthLinkPasscode,
2851
- useAuthLink
2456
+ AuthLinkVerify
2852
2457
  };
2853
2458
  //# sourceMappingURL=vue.js.map