@authon/js 0.2.1 → 0.3.1

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/index.cjs CHANGED
@@ -21,15 +21,62 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Authon: () => Authon,
24
+ AuthonMfaRequiredError: () => AuthonMfaRequiredError,
25
+ generateQrSvg: () => generateQrSvg,
24
26
  getProviderButtonConfig: () => getProviderButtonConfig
25
27
  });
26
28
  module.exports = __toCommonJS(index_exports);
27
29
 
28
- // src/modal.ts
29
- var import_shared2 = require("@authon/shared");
30
+ // src/types.ts
31
+ var AuthonMfaRequiredError = class extends Error {
32
+ mfaToken;
33
+ constructor(mfaToken) {
34
+ super("MFA verification required");
35
+ this.name = "AuthonMfaRequiredError";
36
+ this.mfaToken = mfaToken;
37
+ }
38
+ };
39
+
40
+ // ../shared/dist/index.js
41
+ var PROVIDER_DISPLAY_NAMES = {
42
+ google: "Google",
43
+ apple: "Apple",
44
+ kakao: "Kakao",
45
+ naver: "Naver",
46
+ facebook: "Facebook",
47
+ github: "GitHub",
48
+ discord: "Discord",
49
+ x: "X",
50
+ line: "LINE",
51
+ microsoft: "Microsoft"
52
+ };
53
+ var PROVIDER_COLORS = {
54
+ google: { bg: "#ffffff", text: "#1f1f1f" },
55
+ apple: { bg: "#000000", text: "#ffffff" },
56
+ kakao: { bg: "#FEE500", text: "#191919" },
57
+ naver: { bg: "#03C75A", text: "#ffffff" },
58
+ facebook: { bg: "#1877F2", text: "#ffffff" },
59
+ github: { bg: "#24292e", text: "#ffffff" },
60
+ discord: { bg: "#5865F2", text: "#ffffff" },
61
+ x: { bg: "#000000", text: "#ffffff" },
62
+ line: { bg: "#06C755", text: "#ffffff" },
63
+ microsoft: { bg: "#ffffff", text: "#1f1f1f" }
64
+ };
65
+ var DEFAULT_BRANDING = {
66
+ primaryColorStart: "#7c3aed",
67
+ primaryColorEnd: "#4f46e5",
68
+ lightBg: "#ffffff",
69
+ lightText: "#111827",
70
+ darkBg: "#0f172a",
71
+ darkText: "#f1f5f9",
72
+ borderRadius: 12,
73
+ showEmailPassword: true,
74
+ showDivider: true,
75
+ showSecuredBy: true,
76
+ locale: "en"
77
+ };
30
78
 
31
79
  // src/providers.ts
32
- var import_shared = require("@authon/shared");
33
80
  var PROVIDER_ICONS = {
34
81
  google: `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>`,
35
82
  apple: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/></svg>`,
@@ -43,10 +90,10 @@ var PROVIDER_ICONS = {
43
90
  microsoft: `<svg viewBox="0 0 24 24" width="20" height="20"><rect fill="#F25022" x="1" y="1" width="10" height="10"/><rect fill="#7FBA00" x="13" y="1" width="10" height="10"/><rect fill="#00A4EF" x="1" y="13" width="10" height="10"/><rect fill="#FFB900" x="13" y="13" width="10" height="10"/></svg>`
44
91
  };
45
92
  function getProviderButtonConfig(provider) {
46
- const colors = import_shared.PROVIDER_COLORS[provider];
93
+ const colors = PROVIDER_COLORS[provider];
47
94
  return {
48
95
  provider,
49
- label: `Continue with ${import_shared.PROVIDER_DISPLAY_NAMES[provider]}`,
96
+ label: `Continue with ${PROVIDER_DISPLAY_NAMES[provider]}`,
50
97
  bgColor: colors.bg,
51
98
  textColor: colors.text,
52
99
  iconSvg: PROVIDER_ICONS[provider]
@@ -54,6 +101,30 @@ function getProviderButtonConfig(provider) {
54
101
  }
55
102
 
56
103
  // src/modal.ts
104
+ function hexToRgba(hex, alpha) {
105
+ const h = hex.replace("#", "");
106
+ const r = parseInt(h.substring(0, 2), 16);
107
+ const g = parseInt(h.substring(2, 4), 16);
108
+ const b = parseInt(h.substring(4, 6), 16);
109
+ return `rgba(${r},${g},${b},${alpha})`;
110
+ }
111
+ var WALLET_OPTIONS = [
112
+ { id: "pexus", name: "Pexus", color: "#7c3aed" },
113
+ { id: "metamask", name: "MetaMask", color: "#f6851b" },
114
+ { id: "phantom", name: "Phantom", color: "#ab9ff2" }
115
+ ];
116
+ function walletIconSvg(id) {
117
+ switch (id) {
118
+ case "pexus":
119
+ return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#7c3aed"/><path d="M7 8h4v8H7V8zm6 0h4v8h-4V8z" fill="#fff"/></svg>`;
120
+ case "metamask":
121
+ return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#f6851b"/><path d="M17.2 4L12 8.5l1 .7L17.2 4zM6.8 4l5.1 5.3-1-0.6L6.8 4zM16 16.2l-1.4 2.1 3 .8.8-2.9h-2.4zM5.6 16.2l.9 2.8 3-.8-1.4-2h-2.5z" fill="#fff"/></svg>`;
122
+ case "phantom":
123
+ return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#ab9ff2"/><circle cx="9" cy="11" r="1.5" fill="#fff"/><circle cx="15" cy="11" r="1.5" fill="#fff"/><path d="M6 12c0-3.3 2.7-6 6-6s6 2.7 6 6v2c0 1.1-.9 2-2 2H8c-1.1 0-2-.9-2-2v-2z" stroke="#fff" stroke-width="1.5" fill="none"/></svg>`;
124
+ default:
125
+ return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#666"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="12">${id[0]?.toUpperCase() ?? "W"}</text></svg>`;
126
+ }
127
+ }
57
128
  var ModalRenderer = class {
58
129
  shadowRoot = null;
59
130
  hostElement = null;
@@ -61,19 +132,38 @@ var ModalRenderer = class {
61
132
  mode;
62
133
  theme;
63
134
  branding;
135
+ themeObserver = null;
136
+ mediaQueryListener = null;
64
137
  enabledProviders = [];
65
138
  currentView = "signIn";
66
139
  onProviderClick;
67
140
  onEmailSubmit;
68
141
  onClose;
142
+ onWeb3WalletSelect;
143
+ onPasswordlessSubmit;
144
+ onOtpVerify;
145
+ onPasskeyClick;
69
146
  escHandler = null;
147
+ // Overlay state
148
+ currentOverlay = "none";
149
+ selectedWallet = "";
150
+ overlayEmail = "";
151
+ overlayError = "";
70
152
  constructor(options) {
71
153
  this.mode = options.mode;
72
154
  this.theme = options.theme || "auto";
73
- this.branding = { ...import_shared2.DEFAULT_BRANDING, ...options.branding };
155
+ this.branding = { ...DEFAULT_BRANDING, ...options.branding };
74
156
  this.onProviderClick = options.onProviderClick;
75
157
  this.onEmailSubmit = options.onEmailSubmit;
76
158
  this.onClose = options.onClose;
159
+ this.onWeb3WalletSelect = options.onWeb3WalletSelect || (() => {
160
+ });
161
+ this.onPasswordlessSubmit = options.onPasswordlessSubmit || (() => {
162
+ });
163
+ this.onOtpVerify = options.onOtpVerify || (() => {
164
+ });
165
+ this.onPasskeyClick = options.onPasskeyClick || (() => {
166
+ });
77
167
  if (options.mode === "embedded" && options.containerId) {
78
168
  this.containerElement = document.getElementById(options.containerId);
79
169
  }
@@ -82,17 +172,20 @@ var ModalRenderer = class {
82
172
  this.enabledProviders = providers;
83
173
  }
84
174
  setBranding(branding) {
85
- this.branding = { ...import_shared2.DEFAULT_BRANDING, ...branding };
175
+ this.branding = { ...DEFAULT_BRANDING, ...branding };
86
176
  }
87
177
  open(view = "signIn") {
88
178
  if (this.shadowRoot && this.hostElement) {
179
+ this.hideOverlay();
89
180
  this.switchView(view);
90
181
  } else {
91
182
  this.currentView = view;
183
+ this.currentOverlay = "none";
92
184
  this.render(view);
93
185
  }
94
186
  }
95
187
  close() {
188
+ this.stopThemeObserver();
96
189
  if (this.escHandler) {
97
190
  document.removeEventListener("keydown", this.escHandler);
98
191
  this.escHandler = null;
@@ -105,6 +198,46 @@ var ModalRenderer = class {
105
198
  if (this.containerElement) {
106
199
  this.containerElement.innerHTML = "";
107
200
  }
201
+ this.currentOverlay = "none";
202
+ }
203
+ /** Update theme at runtime without destroying form state */
204
+ setTheme(theme) {
205
+ this.theme = theme;
206
+ this.updateThemeCSS();
207
+ if (theme === "auto") {
208
+ this.startThemeObserver();
209
+ } else {
210
+ this.stopThemeObserver();
211
+ }
212
+ }
213
+ updateThemeCSS() {
214
+ if (!this.shadowRoot) return;
215
+ const styleEl = this.shadowRoot.getElementById("authon-theme-style");
216
+ if (styleEl) {
217
+ styleEl.textContent = this.buildCSS();
218
+ }
219
+ }
220
+ startThemeObserver() {
221
+ this.stopThemeObserver();
222
+ if (typeof document === "undefined" || typeof window === "undefined") return;
223
+ this.themeObserver = new MutationObserver(() => this.updateThemeCSS());
224
+ this.themeObserver.observe(document.documentElement, {
225
+ attributes: true,
226
+ attributeFilter: ["data-theme", "class"]
227
+ });
228
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
229
+ this.mediaQueryListener = () => this.updateThemeCSS();
230
+ mq.addEventListener("change", this.mediaQueryListener);
231
+ }
232
+ stopThemeObserver() {
233
+ if (this.themeObserver) {
234
+ this.themeObserver.disconnect();
235
+ this.themeObserver = null;
236
+ }
237
+ if (this.mediaQueryListener) {
238
+ window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", this.mediaQueryListener);
239
+ this.mediaQueryListener = null;
240
+ }
108
241
  }
109
242
  showError(message) {
110
243
  if (!this.shadowRoot) return;
@@ -156,6 +289,47 @@ var ModalRenderer = class {
156
289
  if (!this.shadowRoot) return;
157
290
  this.shadowRoot.getElementById("authon-loading-overlay")?.remove();
158
291
  }
292
+ // ── Flow Overlay Public API ──
293
+ showOverlay(overlay) {
294
+ this.currentOverlay = overlay;
295
+ this.overlayError = "";
296
+ this.renderOverlay();
297
+ }
298
+ hideOverlay() {
299
+ this.currentOverlay = "none";
300
+ this.overlayError = "";
301
+ if (!this.shadowRoot) return;
302
+ this.shadowRoot.getElementById("flow-overlay")?.remove();
303
+ }
304
+ showWeb3Success(walletId, address) {
305
+ this.selectedWallet = walletId;
306
+ this.overlayError = "";
307
+ const truncated = address.length > 10 ? `${address.slice(0, 6)}...${address.slice(-4)}` : address;
308
+ this.currentOverlay = "web3-success";
309
+ this.renderOverlayWithData({ truncatedAddress: truncated, walletId });
310
+ }
311
+ showPasswordlessSent() {
312
+ this.overlayError = "";
313
+ this.currentOverlay = "passwordless-sent";
314
+ this.renderOverlay();
315
+ }
316
+ showOtpInput(email) {
317
+ this.overlayEmail = email;
318
+ this.overlayError = "";
319
+ this.currentOverlay = "otp-input";
320
+ this.renderOverlay();
321
+ }
322
+ showPasskeySuccess() {
323
+ this.overlayError = "";
324
+ this.currentOverlay = "passkey-success";
325
+ this.renderOverlay();
326
+ }
327
+ showOverlayError(message) {
328
+ this.overlayError = message;
329
+ if (this.currentOverlay !== "none") {
330
+ this.renderOverlay();
331
+ }
332
+ }
159
333
  // ── Smooth view switch (no flicker) ──
160
334
  switchView(view) {
161
335
  if (!this.shadowRoot || view === this.currentView) return;
@@ -186,13 +360,16 @@ var ModalRenderer = class {
186
360
  this.shadowRoot.innerHTML = this.buildShell(view);
187
361
  this.attachInnerEvents(view);
188
362
  this.attachShellEvents();
363
+ if (this.theme === "auto") {
364
+ this.startThemeObserver();
365
+ }
189
366
  }
190
367
  // ── HTML builders ──
191
368
  /** Shell = style + backdrop + modal-container (stable across view switches) */
192
369
  buildShell(view) {
193
370
  const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
194
371
  return `
195
- <style>${this.buildCSS()}</style>
372
+ <style id="authon-theme-style">${this.buildCSS()}</style>
196
373
  ${popupWrapper}
197
374
  <div class="modal-container" role="dialog" aria-modal="true">
198
375
  <div id="modal-inner" class="modal-inner">
@@ -227,6 +404,35 @@ var ModalRenderer = class {
227
404
  ${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
228
405
  <button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
229
406
  </form>` : "";
407
+ const hasMethodAbove = showProviders && this.enabledProviders.length > 0 || b.showEmailPassword !== false;
408
+ const hasMethodBelow = b.showWeb3 || b.showPasswordless || b.showPasskey;
409
+ const methodDivider = hasMethodAbove && hasMethodBelow ? `<div class="divider"><span>or</span></div>` : "";
410
+ const methodButtons = [];
411
+ if (b.showWeb3) {
412
+ methodButtons.push(`<button class="auth-method-btn web3-btn" id="web3-btn">
413
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
414
+ <path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4h-4z"/>
415
+ </svg>
416
+ <span>Connect Wallet</span>
417
+ </button>`);
418
+ }
419
+ if (b.showPasswordless) {
420
+ methodButtons.push(`<button class="auth-method-btn passwordless-btn" id="passwordless-btn">
421
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
422
+ <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
423
+ </svg>
424
+ <span>Continue with Magic Link</span>
425
+ </button>`);
426
+ }
427
+ if (b.showPasskey) {
428
+ methodButtons.push(`<button class="auth-method-btn passkey-btn" id="passkey-btn">
429
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
430
+ <circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/><path d="M21.7 13.3 19 11"/><path d="m21 15-2.5-1.5"/><path d="m17 17 2.5-1.5"/><path d="M22 9v6a1 1 0 0 1-1 1h-.5"/><circle cx="18" cy="9" r="3"/>
431
+ </svg>
432
+ <span>Sign in with Passkey</span>
433
+ </button>`);
434
+ }
435
+ const authMethods = methodButtons.length > 0 ? `<div class="auth-methods">${methodButtons.join("")}</div>` : "";
230
436
  const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
231
437
  ${b.termsUrl ? `<a href="${b.termsUrl}" target="_blank">Terms of Service</a>` : ""}
232
438
  ${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
@@ -245,6 +451,8 @@ var ModalRenderer = class {
245
451
  ${showProviders ? `<div class="providers">${providerButtons}</div>` : ""}
246
452
  ${divider}
247
453
  ${emailForm}
454
+ ${methodDivider}
455
+ ${authMethods}
248
456
  <p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
249
457
  ${footer}
250
458
  ${b.showSecuredBy !== false ? `<div class="secured-by">Secured by <a href="https://authon.dev" target="_blank" rel="noopener noreferrer" class="secured-link">Authon</a></div>` : ""}
@@ -253,6 +461,11 @@ var ModalRenderer = class {
253
461
  isDark() {
254
462
  if (this.theme === "dark") return true;
255
463
  if (this.theme === "light") return false;
464
+ if (typeof document !== "undefined") {
465
+ const html = document.documentElement;
466
+ if (html.classList.contains("dark") || html.getAttribute("data-theme") === "dark") return true;
467
+ if (html.classList.contains("light") || html.getAttribute("data-theme") === "light") return false;
468
+ }
256
469
  return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
257
470
  }
258
471
  buildCSS() {
@@ -276,6 +489,10 @@ var ModalRenderer = class {
276
489
  --authon-border: ${borderColor};
277
490
  --authon-divider: ${dividerColor};
278
491
  --authon-input-bg: ${inputBg};
492
+ --authon-overlay-bg: ${hexToRgba(bg, 0.92)};
493
+ --authon-overlay-bg-solid: ${hexToRgba(bg, 0.97)};
494
+ --authon-backdrop-bg: rgba(0,0,0,${dark ? "0.7" : "0.5"});
495
+ --authon-shadow-opacity: ${dark ? "0.5" : "0.25"};
279
496
  --authon-radius: ${b.borderRadius ?? 12}px;
280
497
  --authon-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
281
498
  font-family: var(--authon-font);
@@ -284,7 +501,7 @@ var ModalRenderer = class {
284
501
  * { box-sizing: border-box; margin: 0; padding: 0; }
285
502
  .backdrop {
286
503
  position: fixed; inset: 0; z-index: 99998;
287
- background: rgba(0,0,0,${dark ? "0.7" : "0.5"}); backdrop-filter: blur(4px);
504
+ background: var(--authon-backdrop-bg); backdrop-filter: blur(4px);
288
505
  animation: fadeIn 0.2s ease;
289
506
  }
290
507
  .modal-container {
@@ -378,10 +595,154 @@ var ModalRenderer = class {
378
595
  }
379
596
  .secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
380
597
  .secured-link:hover { text-decoration: underline; }
598
+
599
+ /* Auth method buttons */
600
+ .auth-methods { display: flex; flex-direction: column; gap: 8px; }
601
+ .auth-method-btn {
602
+ display: flex; align-items: center; justify-content: center; gap: 8px;
603
+ width: 100%; padding: 10px 16px;
604
+ border-radius: calc(var(--authon-radius) * 0.5);
605
+ font-size: 13px; font-weight: 500; cursor: pointer;
606
+ font-family: var(--authon-font); transition: opacity 0.15s, transform 0.1s;
607
+ }
608
+ .auth-method-btn:hover { opacity: 0.85; }
609
+ .auth-method-btn:active { transform: scale(0.98); }
610
+ /* Web3 -- purple */
611
+ .web3-btn {
612
+ background: ${dark ? "rgba(139,92,246,0.12)" : "rgba(139,92,246,0.08)"};
613
+ border: 1px solid ${dark ? "rgba(139,92,246,0.3)" : "rgba(139,92,246,0.25)"};
614
+ color: ${dark ? "#c4b5fd" : "#7c3aed"};
615
+ }
616
+ /* Passwordless -- cyan */
617
+ .passwordless-btn {
618
+ background: ${dark ? "rgba(6,182,212,0.12)" : "rgba(6,182,212,0.08)"};
619
+ border: 1px solid ${dark ? "rgba(6,182,212,0.3)" : "rgba(6,182,212,0.25)"};
620
+ color: ${dark ? "#67e8f9" : "#0891b2"};
621
+ }
622
+ /* Passkey -- amber */
623
+ .passkey-btn {
624
+ background: ${dark ? "rgba(245,158,11,0.12)" : "rgba(245,158,11,0.08)"};
625
+ border: 1px solid ${dark ? "rgba(245,158,11,0.3)" : "rgba(245,158,11,0.25)"};
626
+ color: ${dark ? "#fcd34d" : "#b45309"};
627
+ }
628
+
629
+ /* Flow overlay */
630
+ .flow-overlay {
631
+ position: absolute; inset: 0; z-index: 10;
632
+ background: var(--authon-overlay-bg-solid);
633
+ backdrop-filter: blur(2px);
634
+ border-radius: var(--authon-radius);
635
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
636
+ gap: 12px; padding: 24px;
637
+ animation: fadeIn 0.2s ease;
638
+ }
639
+ .flow-overlay .cancel-link {
640
+ font-size: 12px; color: var(--authon-dim); cursor: pointer; border: none;
641
+ background: none; font-family: var(--authon-font); margin-top: 4px;
642
+ }
643
+ .flow-overlay .cancel-link:hover { text-decoration: underline; }
644
+ .flow-overlay .overlay-title {
645
+ font-size: 14px; font-weight: 600; color: var(--authon-text); text-align: center;
646
+ }
647
+ .flow-overlay .overlay-subtitle {
648
+ font-size: 12px; color: var(--authon-muted); text-align: center;
649
+ }
650
+ .flow-overlay .overlay-error {
651
+ padding: 6px 12px; margin-top: 4px;
652
+ background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3);
653
+ border-radius: calc(var(--authon-radius) * 0.33);
654
+ font-size: 12px; color: #ef4444; text-align: center; width: 100%;
655
+ }
656
+
657
+ /* Wallet picker */
658
+ .wallet-picker { display: flex; flex-direction: column; gap: 8px; width: 100%; }
659
+ .wallet-btn {
660
+ display: flex; align-items: center; gap: 10px;
661
+ width: 100%; padding: 10px 14px;
662
+ background: ${dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)"};
663
+ border: 1px solid ${dark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};
664
+ border-radius: calc(var(--authon-radius) * 0.5);
665
+ font-size: 13px; font-weight: 500; color: var(--authon-text);
666
+ cursor: pointer; font-family: var(--authon-font);
667
+ transition: opacity 0.15s;
668
+ }
669
+ .wallet-btn:hover { opacity: 0.8; }
670
+ .wallet-btn .wallet-icon { display: flex; align-items: center; flex-shrink: 0; }
671
+ .wallet-btn .wallet-icon svg { border-radius: 6px; }
672
+
673
+ /* Passwordless email input in overlay */
674
+ .pwless-form { display: flex; flex-direction: column; gap: 10px; width: 100%; }
675
+ .pwless-submit {
676
+ width: 100%; padding: 10px;
677
+ background: linear-gradient(135deg, #06b6d4, #0891b2);
678
+ color: #fff; border: none; border-radius: calc(var(--authon-radius) * 0.5);
679
+ font-size: 13px; font-weight: 600; cursor: pointer;
680
+ font-family: var(--authon-font); transition: opacity 0.15s;
681
+ }
682
+ .pwless-submit:hover { opacity: 0.9; }
683
+ .pwless-submit:disabled { opacity: 0.6; cursor: not-allowed; }
684
+
685
+ /* OTP input */
686
+ .otp-container { display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; }
687
+ .otp-inputs { display: flex; gap: 8px; justify-content: center; }
688
+ .otp-digit {
689
+ width: 40px; height: 48px; text-align: center;
690
+ font-size: 20px; font-weight: 600; font-family: var(--authon-font);
691
+ background: var(--authon-input-bg); color: var(--authon-text);
692
+ border: 1px solid var(--authon-border);
693
+ border-radius: calc(var(--authon-radius) * 0.33);
694
+ outline: none; transition: border-color 0.15s;
695
+ }
696
+ .otp-digit:focus {
697
+ border-color: var(--authon-primary-start);
698
+ box-shadow: 0 0 0 3px rgba(124,58,237,0.15);
699
+ }
700
+
701
+ /* Success check animation */
702
+ .success-check {
703
+ width: 48px; height: 48px; border-radius: 50%;
704
+ display: flex; align-items: center; justify-content: center;
705
+ }
706
+ .success-check svg path {
707
+ stroke-dasharray: 20;
708
+ stroke-dashoffset: 20;
709
+ animation: check-draw 0.4s ease-out 0.1s forwards;
710
+ }
711
+
712
+ /* Spinner */
713
+ .flow-spinner {
714
+ animation: spin 0.8s linear infinite;
715
+ }
716
+
717
+ /* Passkey verifying icon */
718
+ .passkey-icon-pulse {
719
+ width: 48px; height: 48px; border-radius: 50%;
720
+ display: flex; align-items: center; justify-content: center;
721
+ background: rgba(245,158,11,0.15);
722
+ animation: pulse 1.5s ease-in-out infinite;
723
+ }
724
+
725
+ /* Wallet connecting icon */
726
+ .wallet-connecting-icon {
727
+ width: 48px; height: 48px; border-radius: 12px;
728
+ display: flex; align-items: center; justify-content: center;
729
+ animation: pulse 1.5s ease-in-out infinite;
730
+ }
731
+ .wallet-connecting-icon svg { border-radius: 6px; }
732
+
733
+ /* Address badge */
734
+ .address-badge {
735
+ display: inline-flex; align-items: center; gap: 6px;
736
+ padding: 2px 10px; border-radius: 6px;
737
+ background: ${dark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.03)"};
738
+ font-size: 11px; font-family: monospace; color: var(--authon-muted);
739
+ }
740
+ .address-badge .wallet-icon-sm svg { width: 16px; height: 16px; border-radius: 4px; }
741
+
381
742
  /* Loading overlay */
382
743
  #authon-loading-overlay {
383
744
  position: absolute; inset: 0; z-index: 10;
384
- background: ${dark ? "rgba(15,23,42,0.92)" : "rgba(255,255,255,0.92)"};
745
+ background: var(--authon-overlay-bg);
385
746
  backdrop-filter: blur(2px);
386
747
  border-radius: var(--authon-radius);
387
748
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
@@ -413,11 +774,229 @@ var ModalRenderer = class {
413
774
  @keyframes blink { 0%,80%,100% { opacity: .2; } 40% { opacity: 1; } }
414
775
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
415
776
  @keyframes slideIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } }
777
+ @keyframes check-draw { to { stroke-dashoffset: 0; } }
778
+ @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }
416
779
  ${b.customCss || ""}
417
780
  `;
418
781
  }
782
+ // ── Flow Overlay Rendering ──
783
+ renderOverlay() {
784
+ this.renderOverlayWithData({});
785
+ }
786
+ renderOverlayWithData(data) {
787
+ if (!this.shadowRoot) return;
788
+ const container = this.shadowRoot.querySelector(".modal-container");
789
+ if (!container) return;
790
+ this.shadowRoot.getElementById("flow-overlay")?.remove();
791
+ if (this.currentOverlay === "none") return;
792
+ const overlay = document.createElement("div");
793
+ overlay.id = "flow-overlay";
794
+ overlay.className = "flow-overlay";
795
+ overlay.innerHTML = this.buildOverlayContent(data);
796
+ container.appendChild(overlay);
797
+ this.attachOverlayEvents(overlay);
798
+ }
799
+ buildOverlayContent(data) {
800
+ const dark = this.isDark();
801
+ const errorHtml = this.overlayError ? `<div class="overlay-error">${this.escapeHtml(this.overlayError)}</div>` : "";
802
+ switch (this.currentOverlay) {
803
+ case "web3-picker": {
804
+ const walletItems = WALLET_OPTIONS.map(
805
+ (w) => `<button class="wallet-btn" data-wallet="${w.id}">
806
+ <span class="wallet-icon">${walletIconSvg(w.id)}</span>
807
+ <span>${w.name}</span>
808
+ </button>`
809
+ ).join("");
810
+ return `
811
+ <div class="overlay-title" style="margin-bottom: 4px;">Select Wallet</div>
812
+ <div class="wallet-picker">${walletItems}</div>
813
+ ${errorHtml}
814
+ <button class="cancel-link" id="overlay-cancel">Cancel</button>
815
+ `;
816
+ }
817
+ case "web3-connecting": {
818
+ const wallet = WALLET_OPTIONS.find((w) => w.id === this.selectedWallet);
819
+ const walletName = wallet?.name ?? this.selectedWallet;
820
+ return `
821
+ <div class="wallet-connecting-icon">${walletIconSvg(this.selectedWallet)}</div>
822
+ <div style="display:flex;align-items:center;gap:8px;">
823
+ <svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
824
+ <circle cx="8" cy="8" r="6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" opacity="0.25"/>
825
+ <path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" stroke-linecap="round"/>
826
+ </svg>
827
+ <span class="overlay-subtitle">Connecting ${this.escapeHtml(walletName)}...</span>
828
+ </div>
829
+ ${errorHtml}
830
+ <button class="cancel-link" id="overlay-cancel">Cancel</button>
831
+ `;
832
+ }
833
+ case "web3-success": {
834
+ const wallet = WALLET_OPTIONS.find((w) => w.id === (data.walletId || this.selectedWallet));
835
+ const walletColor = wallet?.color ?? "#8b5cf6";
836
+ const truncAddr = data.truncatedAddress || "0x...";
837
+ return `
838
+ <div class="success-check" style="background:linear-gradient(135deg, ${walletColor}, ${walletColor})">
839
+ <svg width="24" height="24" viewBox="0 0 20 20" fill="none">
840
+ <path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
841
+ </svg>
842
+ </div>
843
+ <div class="overlay-title">Wallet Connected</div>
844
+ <div class="address-badge">
845
+ <span class="wallet-icon-sm">${walletIconSvg(data.walletId || this.selectedWallet)}</span>
846
+ <span>${this.escapeHtml(truncAddr)}</span>
847
+ </div>
848
+ `;
849
+ }
850
+ case "passwordless-input": {
851
+ return `
852
+ <div class="overlay-title">Enter your email</div>
853
+ <div class="pwless-form">
854
+ <input type="email" placeholder="you@example.com" class="input" id="pwless-email" autocomplete="email" />
855
+ <button class="pwless-submit" id="pwless-submit-btn">Send Magic Link</button>
856
+ </div>
857
+ ${errorHtml}
858
+ <button class="cancel-link" id="overlay-cancel">Cancel</button>
859
+ `;
860
+ }
861
+ case "passwordless-sending": {
862
+ return `
863
+ <svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
864
+ <circle cx="8" cy="8" r="6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" opacity="0.25"/>
865
+ <path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" stroke-linecap="round"/>
866
+ </svg>
867
+ <span class="overlay-subtitle">Sending magic link...</span>
868
+ `;
869
+ }
870
+ case "passwordless-sent": {
871
+ return `
872
+ <div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
873
+ <svg width="24" height="24" viewBox="0 0 20 20" fill="none">
874
+ <path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
875
+ </svg>
876
+ </div>
877
+ <div class="overlay-title">Magic link sent!</div>
878
+ <span class="overlay-subtitle">Check your email inbox</span>
879
+ `;
880
+ }
881
+ case "otp-input": {
882
+ const digitInputs = Array.from(
883
+ { length: 6 },
884
+ (_, i) => `<input type="text" inputmode="numeric" maxlength="1" class="otp-digit" data-idx="${i}" autocomplete="one-time-code" />`
885
+ ).join("");
886
+ return `
887
+ <div class="otp-container">
888
+ <div class="overlay-title">Enter verification code</div>
889
+ <span class="overlay-subtitle">6-digit code sent to ${this.escapeHtml(this.overlayEmail)}</span>
890
+ <div class="otp-inputs">${digitInputs}</div>
891
+ ${errorHtml}
892
+ <button class="cancel-link" id="overlay-cancel">Cancel</button>
893
+ </div>
894
+ `;
895
+ }
896
+ case "passkey-verifying": {
897
+ return `
898
+ <div class="passkey-icon-pulse">
899
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
900
+ <circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/>
901
+ <path d="M21.7 13.3 19 11"/><path d="m21 15-2.5-1.5"/><path d="m17 17 2.5-1.5"/>
902
+ <path d="M22 9v6a1 1 0 0 1-1 1h-.5"/><circle cx="18" cy="9" r="3"/>
903
+ </svg>
904
+ </div>
905
+ <span class="overlay-subtitle">Verifying identity...</span>
906
+ ${errorHtml}
907
+ <button class="cancel-link" id="overlay-cancel">Cancel</button>
908
+ `;
909
+ }
910
+ case "passkey-success": {
911
+ return `
912
+ <div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
913
+ <svg width="24" height="24" viewBox="0 0 20 20" fill="none">
914
+ <path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
915
+ </svg>
916
+ </div>
917
+ <div class="overlay-title">Identity verified!</div>
918
+ `;
919
+ }
920
+ default:
921
+ return "";
922
+ }
923
+ }
924
+ attachOverlayEvents(overlay) {
925
+ if (!this.shadowRoot) return;
926
+ const cancelBtn = overlay.querySelector("#overlay-cancel");
927
+ if (cancelBtn) {
928
+ cancelBtn.addEventListener("click", () => this.hideOverlay());
929
+ }
930
+ overlay.querySelectorAll(".wallet-btn").forEach((btn) => {
931
+ btn.addEventListener("click", () => {
932
+ const walletId = btn.dataset.wallet;
933
+ if (walletId) {
934
+ this.selectedWallet = walletId;
935
+ this.onWeb3WalletSelect(walletId);
936
+ }
937
+ });
938
+ });
939
+ const pwlessSubmit = overlay.querySelector("#pwless-submit-btn");
940
+ const pwlessEmail = overlay.querySelector("#pwless-email");
941
+ if (pwlessSubmit && pwlessEmail) {
942
+ setTimeout(() => pwlessEmail.focus(), 50);
943
+ const submitHandler = () => {
944
+ const email = pwlessEmail.value.trim();
945
+ if (!email) return;
946
+ this.overlayEmail = email;
947
+ this.showOverlay("passwordless-sending");
948
+ this.onPasswordlessSubmit(email);
949
+ };
950
+ pwlessSubmit.addEventListener("click", submitHandler);
951
+ pwlessEmail.addEventListener("keydown", (e) => {
952
+ if (e.key === "Enter") {
953
+ e.preventDefault();
954
+ submitHandler();
955
+ }
956
+ });
957
+ }
958
+ const otpDigits = overlay.querySelectorAll(".otp-digit");
959
+ if (otpDigits.length === 6) {
960
+ setTimeout(() => otpDigits[0].focus(), 50);
961
+ otpDigits.forEach((digit, idx) => {
962
+ digit.addEventListener("input", () => {
963
+ const val = digit.value.replace(/\D/g, "");
964
+ digit.value = val.slice(0, 1);
965
+ if (val && idx < 5) {
966
+ otpDigits[idx + 1].focus();
967
+ }
968
+ const code = Array.from(otpDigits).map((d) => d.value).join("");
969
+ if (code.length === 6) {
970
+ this.onOtpVerify(this.overlayEmail, code);
971
+ }
972
+ });
973
+ digit.addEventListener("keydown", (e) => {
974
+ if (e.key === "Backspace" && !digit.value && idx > 0) {
975
+ otpDigits[idx - 1].focus();
976
+ otpDigits[idx - 1].value = "";
977
+ }
978
+ });
979
+ digit.addEventListener("paste", (e) => {
980
+ e.preventDefault();
981
+ const pasted = (e.clipboardData?.getData("text") ?? "").replace(/\D/g, "").slice(0, 6);
982
+ if (pasted.length === 0) return;
983
+ for (let i = 0; i < 6; i++) {
984
+ otpDigits[i].value = pasted[i] || "";
985
+ }
986
+ const lastIdx = Math.min(pasted.length, 5);
987
+ otpDigits[lastIdx].focus();
988
+ if (pasted.length === 6) {
989
+ this.onOtpVerify(this.overlayEmail, pasted);
990
+ }
991
+ });
992
+ });
993
+ }
994
+ }
995
+ escapeHtml(str) {
996
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
997
+ }
419
998
  // ── Event binding ──
420
- /** Attach events to shell elements (backdrop, ESC) called once */
999
+ /** Attach events to shell elements (backdrop, ESC) -- called once */
421
1000
  attachShellEvents() {
422
1001
  if (!this.shadowRoot) return;
423
1002
  const backdrop = this.shadowRoot.getElementById("backdrop");
@@ -429,12 +1008,18 @@ var ModalRenderer = class {
429
1008
  }
430
1009
  if (this.mode === "popup") {
431
1010
  this.escHandler = (e) => {
432
- if (e.key === "Escape") this.onClose();
1011
+ if (e.key === "Escape") {
1012
+ if (this.currentOverlay !== "none") {
1013
+ this.hideOverlay();
1014
+ } else {
1015
+ this.onClose();
1016
+ }
1017
+ }
433
1018
  };
434
1019
  document.addEventListener("keydown", this.escHandler);
435
1020
  }
436
1021
  }
437
- /** Attach events to inner content (buttons, form, switch link) called on each view */
1022
+ /** Attach events to inner content (buttons, form, switch link) -- called on each view */
438
1023
  attachInnerEvents(view) {
439
1024
  if (!this.shadowRoot) return;
440
1025
  this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
@@ -468,6 +1053,18 @@ var ModalRenderer = class {
468
1053
  this.open(view === "signIn" ? "signUp" : "signIn");
469
1054
  });
470
1055
  }
1056
+ const web3Btn = this.shadowRoot.getElementById("web3-btn");
1057
+ if (web3Btn) {
1058
+ web3Btn.addEventListener("click", () => this.showOverlay("web3-picker"));
1059
+ }
1060
+ const pwlessBtn = this.shadowRoot.getElementById("passwordless-btn");
1061
+ if (pwlessBtn) {
1062
+ pwlessBtn.addEventListener("click", () => this.showOverlay("passwordless-input"));
1063
+ }
1064
+ const passkeyBtn = this.shadowRoot.getElementById("passkey-btn");
1065
+ if (passkeyBtn) {
1066
+ passkeyBtn.addEventListener("click", () => this.onPasskeyClick());
1067
+ }
471
1068
  }
472
1069
  };
473
1070
 
@@ -497,6 +1094,9 @@ var SessionManager = class {
497
1094
  this.scheduleRefresh(tokens.expiresIn);
498
1095
  }
499
1096
  }
1097
+ updateUser(user) {
1098
+ this.user = user;
1099
+ }
500
1100
  clearSession() {
501
1101
  this.accessToken = null;
502
1102
  this.refreshToken = null;
@@ -558,6 +1158,404 @@ var SessionManager = class {
558
1158
  }
559
1159
  };
560
1160
 
1161
+ // src/qrcode.ts
1162
+ var EXP = [];
1163
+ var LOG = new Array(256).fill(0);
1164
+ (() => {
1165
+ let v = 1;
1166
+ for (let i = 0; i < 255; i++) {
1167
+ EXP[i] = v;
1168
+ LOG[v] = i;
1169
+ v <<= 1;
1170
+ if (v & 256) v ^= 285;
1171
+ }
1172
+ for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
1173
+ })();
1174
+ var gfMul = (a, b) => a && b ? EXP[LOG[a] + LOG[b]] : 0;
1175
+ function rsEncode(data, ecLen) {
1176
+ let g = [1];
1177
+ for (let i = 0; i < ecLen; i++) {
1178
+ const ng = new Array(g.length + 1).fill(0);
1179
+ for (let j = 0; j < g.length; j++) {
1180
+ ng[j] ^= gfMul(g[j], EXP[i]);
1181
+ ng[j + 1] ^= g[j];
1182
+ }
1183
+ g = ng;
1184
+ }
1185
+ const rem = new Array(ecLen).fill(0);
1186
+ for (const d of data) {
1187
+ const fb = d ^ rem[0];
1188
+ for (let j = 0; j < ecLen - 1; j++) {
1189
+ rem[j] = rem[j + 1] ^ gfMul(g[ecLen - 1 - j], fb);
1190
+ }
1191
+ rem[ecLen - 1] = gfMul(g[0], fb);
1192
+ }
1193
+ return rem;
1194
+ }
1195
+ var VER = [
1196
+ { total: 0, ec: 0, g1: 0, g1d: 0, g2: 0, g2d: 0, align: [] },
1197
+ // dummy
1198
+ { total: 26, ec: 7, g1: 1, g1d: 19, g2: 0, g2d: 0, align: [] },
1199
+ { total: 44, ec: 10, g1: 1, g1d: 34, g2: 0, g2d: 0, align: [6, 18] },
1200
+ { total: 70, ec: 15, g1: 1, g1d: 55, g2: 0, g2d: 0, align: [6, 22] },
1201
+ { total: 100, ec: 20, g1: 1, g1d: 80, g2: 0, g2d: 0, align: [6, 26] },
1202
+ { total: 134, ec: 26, g1: 1, g1d: 108, g2: 0, g2d: 0, align: [6, 30] },
1203
+ { total: 172, ec: 18, g1: 2, g1d: 68, g2: 0, g2d: 0, align: [6, 34] },
1204
+ { total: 196, ec: 20, g1: 2, g1d: 78, g2: 0, g2d: 0, align: [6, 22, 38] },
1205
+ { total: 242, ec: 24, g1: 2, g1d: 97, g2: 0, g2d: 0, align: [6, 24, 42] },
1206
+ { total: 292, ec: 30, g1: 2, g1d: 116, g2: 0, g2d: 0, align: [6, 26, 46] },
1207
+ { total: 346, ec: 18, g1: 2, g1d: 68, g2: 2, g2d: 69, align: [6, 28, 50] },
1208
+ { total: 404, ec: 20, g1: 4, g1d: 81, g2: 0, g2d: 0, align: [6, 30, 54] },
1209
+ { total: 466, ec: 24, g1: 2, g1d: 92, g2: 2, g2d: 93, align: [6, 32, 58] },
1210
+ { total: 532, ec: 26, g1: 4, g1d: 107, g2: 0, g2d: 0, align: [6, 34, 62] }
1211
+ ];
1212
+ function dataCapacity(ver) {
1213
+ const v = VER[ver];
1214
+ return v.g1 * v.g1d + v.g2 * v.g2d;
1215
+ }
1216
+ function pickVersion(byteLen) {
1217
+ for (let v = 1; v < VER.length; v++) {
1218
+ const headerBits = 4 + (v <= 9 ? 8 : 16);
1219
+ const available = dataCapacity(v) * 8 - headerBits;
1220
+ if (byteLen * 8 <= available) return v;
1221
+ }
1222
+ throw new Error(`Data too long for QR code (${byteLen} bytes)`);
1223
+ }
1224
+ function encodeData(bytes, ver) {
1225
+ const cap = dataCapacity(ver);
1226
+ const countBits = ver <= 9 ? 8 : 16;
1227
+ const bits = [];
1228
+ const push = (val, len) => {
1229
+ for (let i = len - 1; i >= 0; i--) bits.push(val >> i & 1);
1230
+ };
1231
+ push(4, 4);
1232
+ push(bytes.length, countBits);
1233
+ for (const b of bytes) push(b, 8);
1234
+ push(0, Math.min(4, cap * 8 - bits.length));
1235
+ while (bits.length % 8) bits.push(0);
1236
+ const padBytes = [236, 17];
1237
+ let pi = 0;
1238
+ while (bits.length < cap * 8) {
1239
+ push(padBytes[pi], 8);
1240
+ pi ^= 1;
1241
+ }
1242
+ const cw = [];
1243
+ for (let i = 0; i < bits.length; i += 8) {
1244
+ let byte = 0;
1245
+ for (let j = 0; j < 8; j++) byte = byte << 1 | bits[i + j];
1246
+ cw.push(byte);
1247
+ }
1248
+ return cw;
1249
+ }
1250
+ function computeCodewords(ver, dataCW) {
1251
+ const v = VER[ver];
1252
+ const blocks = [];
1253
+ const ecBlocks = [];
1254
+ let offset = 0;
1255
+ for (let i = 0; i < v.g1; i++) {
1256
+ const block = dataCW.slice(offset, offset + v.g1d);
1257
+ blocks.push(block);
1258
+ ecBlocks.push(rsEncode(block, v.ec));
1259
+ offset += v.g1d;
1260
+ }
1261
+ for (let i = 0; i < v.g2; i++) {
1262
+ const block = dataCW.slice(offset, offset + v.g2d);
1263
+ blocks.push(block);
1264
+ ecBlocks.push(rsEncode(block, v.ec));
1265
+ offset += v.g2d;
1266
+ }
1267
+ const result = [];
1268
+ const maxDataLen = Math.max(v.g1d, v.g2d || 0);
1269
+ for (let i = 0; i < maxDataLen; i++) {
1270
+ for (const block of blocks) {
1271
+ if (i < block.length) result.push(block[i]);
1272
+ }
1273
+ }
1274
+ for (let i = 0; i < v.ec; i++) {
1275
+ for (const block of ecBlocks) result.push(block[i]);
1276
+ }
1277
+ return result;
1278
+ }
1279
+ var UNSET = -1;
1280
+ var DARK = 1;
1281
+ var LIGHT = 0;
1282
+ function createMatrix(size) {
1283
+ return Array.from({ length: size }, () => new Array(size).fill(UNSET));
1284
+ }
1285
+ function setModule(m, r, c, dark) {
1286
+ if (r >= 0 && r < m.length && c >= 0 && c < m.length) m[r][c] = dark ? DARK : LIGHT;
1287
+ }
1288
+ function placeFinderPattern(m, row, col) {
1289
+ for (let r = -1; r <= 7; r++) {
1290
+ for (let c = -1; c <= 7; c++) {
1291
+ const dark = r >= 0 && r <= 6 && c >= 0 && c <= 6 && (r === 0 || r === 6 || c === 0 || c === 6 || r >= 2 && r <= 4 && c >= 2 && c <= 4);
1292
+ setModule(m, row + r, col + c, dark);
1293
+ }
1294
+ }
1295
+ }
1296
+ function placeAlignmentPattern(m, row, col) {
1297
+ for (let r = -2; r <= 2; r++) {
1298
+ for (let c = -2; c <= 2; c++) {
1299
+ const dark = Math.abs(r) === 2 || Math.abs(c) === 2 || r === 0 && c === 0;
1300
+ m[row + r][col + c] = dark ? DARK : LIGHT;
1301
+ }
1302
+ }
1303
+ }
1304
+ function isReserved(m, r, c) {
1305
+ return m[r][c] !== UNSET;
1306
+ }
1307
+ function buildMatrix(ver, codewords) {
1308
+ const size = ver * 4 + 17;
1309
+ const m = createMatrix(size);
1310
+ placeFinderPattern(m, 0, 0);
1311
+ placeFinderPattern(m, 0, size - 7);
1312
+ placeFinderPattern(m, size - 7, 0);
1313
+ for (let i = 8; i < size - 8; i++) {
1314
+ m[6][i] = i % 2 === 0 ? DARK : LIGHT;
1315
+ m[i][6] = i % 2 === 0 ? DARK : LIGHT;
1316
+ }
1317
+ const ap = VER[ver].align;
1318
+ if (ap.length > 0) {
1319
+ for (const r of ap) {
1320
+ for (const c of ap) {
1321
+ if (r <= 8 && c <= 8) continue;
1322
+ if (r <= 8 && c >= size - 8) continue;
1323
+ if (r >= size - 8 && c <= 8) continue;
1324
+ placeAlignmentPattern(m, r, c);
1325
+ }
1326
+ }
1327
+ }
1328
+ m[4 * ver + 9][8] = DARK;
1329
+ for (let i = 0; i < 9; i++) {
1330
+ if (m[8][i] === UNSET) m[8][i] = LIGHT;
1331
+ if (m[i][8] === UNSET) m[i][8] = LIGHT;
1332
+ }
1333
+ for (let i = 0; i < 8; i++) {
1334
+ if (m[8][size - 1 - i] === UNSET) m[8][size - 1 - i] = LIGHT;
1335
+ if (m[size - 1 - i][8] === UNSET) m[size - 1 - i][8] = LIGHT;
1336
+ }
1337
+ if (ver >= 7) {
1338
+ for (let i = 0; i < 6; i++) {
1339
+ for (let j = 0; j < 3; j++) {
1340
+ m[i][size - 11 + j] = LIGHT;
1341
+ m[size - 11 + j][i] = LIGHT;
1342
+ }
1343
+ }
1344
+ }
1345
+ let bitIdx = 0;
1346
+ const totalBits = codewords.length * 8;
1347
+ let upward = true;
1348
+ for (let right = size - 1; right >= 0; right -= 2) {
1349
+ if (right === 6) right = 5;
1350
+ for (let i = 0; i < size; i++) {
1351
+ const row = upward ? size - 1 - i : i;
1352
+ for (const dc of [0, -1]) {
1353
+ const col = right + dc;
1354
+ if (col < 0 || col >= size) continue;
1355
+ if (isReserved(m, row, col)) continue;
1356
+ if (bitIdx < totalBits) {
1357
+ const byteIdx = bitIdx >> 3;
1358
+ const bitPos = 7 - (bitIdx & 7);
1359
+ m[row][col] = codewords[byteIdx] >> bitPos & 1;
1360
+ bitIdx++;
1361
+ } else {
1362
+ m[row][col] = LIGHT;
1363
+ }
1364
+ }
1365
+ }
1366
+ upward = !upward;
1367
+ }
1368
+ return m;
1369
+ }
1370
+ var MASKS = [
1371
+ (r, c) => (r + c) % 2 === 0,
1372
+ (r) => r % 2 === 0,
1373
+ (_, c) => c % 3 === 0,
1374
+ (r, c) => (r + c) % 3 === 0,
1375
+ (r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,
1376
+ (r, c) => r * c % 2 + r * c % 3 === 0,
1377
+ (r, c) => (r * c % 2 + r * c % 3) % 2 === 0,
1378
+ (r, c) => ((r + c) % 2 + r * c % 3) % 2 === 0
1379
+ ];
1380
+ function applyMask(m, maskIdx, template) {
1381
+ const size = m.length;
1382
+ const result = m.map((row) => [...row]);
1383
+ const fn = MASKS[maskIdx];
1384
+ for (let r = 0; r < size; r++) {
1385
+ for (let c = 0; c < size; c++) {
1386
+ if (template[r][c] !== UNSET) continue;
1387
+ if (fn(r, c)) result[r][c] ^= 1;
1388
+ }
1389
+ }
1390
+ return result;
1391
+ }
1392
+ function penalty(m) {
1393
+ const size = m.length;
1394
+ let score = 0;
1395
+ for (let r = 0; r < size; r++) {
1396
+ let count = 1;
1397
+ for (let c = 1; c < size; c++) {
1398
+ if (m[r][c] === m[r][c - 1]) {
1399
+ count++;
1400
+ } else {
1401
+ if (count >= 5) score += count - 2;
1402
+ count = 1;
1403
+ }
1404
+ }
1405
+ if (count >= 5) score += count - 2;
1406
+ }
1407
+ for (let c = 0; c < size; c++) {
1408
+ let count = 1;
1409
+ for (let r = 1; r < size; r++) {
1410
+ if (m[r][c] === m[r - 1][c]) {
1411
+ count++;
1412
+ } else {
1413
+ if (count >= 5) score += count - 2;
1414
+ count = 1;
1415
+ }
1416
+ }
1417
+ if (count >= 5) score += count - 2;
1418
+ }
1419
+ for (let r = 0; r < size - 1; r++) {
1420
+ for (let c = 0; c < size - 1; c++) {
1421
+ const v = m[r][c];
1422
+ if (v === m[r][c + 1] && v === m[r + 1][c] && v === m[r + 1][c + 1]) score += 3;
1423
+ }
1424
+ }
1425
+ const pat1 = [1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0];
1426
+ const pat2 = [0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1];
1427
+ for (let r = 0; r < size; r++) {
1428
+ for (let c = 0; c <= size - 11; c++) {
1429
+ let match1 = true, match2 = true;
1430
+ for (let k = 0; k < 11; k++) {
1431
+ if (m[r][c + k] !== pat1[k]) match1 = false;
1432
+ if (m[r][c + k] !== pat2[k]) match2 = false;
1433
+ }
1434
+ if (match1 || match2) score += 40;
1435
+ }
1436
+ }
1437
+ for (let c = 0; c < size; c++) {
1438
+ for (let r = 0; r <= size - 11; r++) {
1439
+ let match1 = true, match2 = true;
1440
+ for (let k = 0; k < 11; k++) {
1441
+ if (m[r + k][c] !== pat1[k]) match1 = false;
1442
+ if (m[r + k][c] !== pat2[k]) match2 = false;
1443
+ }
1444
+ if (match1 || match2) score += 40;
1445
+ }
1446
+ }
1447
+ let dark = 0;
1448
+ for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (m[r][c]) dark++;
1449
+ const pct = dark * 100 / (size * size);
1450
+ const prev5 = Math.floor(pct / 5) * 5;
1451
+ const next5 = prev5 + 5;
1452
+ score += Math.min(Math.abs(prev5 - 50) / 5, Math.abs(next5 - 50) / 5) * 10;
1453
+ return score;
1454
+ }
1455
+ function bchEncode(data, gen, dataBits) {
1456
+ let d = data << 15 - dataBits;
1457
+ const genLen = Math.floor(Math.log2(gen)) + 1;
1458
+ const totalBits = dataBits + (genLen - 1);
1459
+ d = data << totalBits - dataBits;
1460
+ for (let i = dataBits - 1; i >= 0; i--) {
1461
+ if (d & 1 << i + genLen - 1) d ^= gen << i;
1462
+ }
1463
+ return data << genLen - 1 | d;
1464
+ }
1465
+ function placeFormatInfo(m, maskIdx) {
1466
+ const size = m.length;
1467
+ const data = 1 << 3 | maskIdx;
1468
+ let format = bchEncode(data, 1335, 5);
1469
+ format ^= 21522;
1470
+ const bits = [];
1471
+ for (let i = 14; i >= 0; i--) bits.push(format >> i & 1);
1472
+ const hPos = [0, 1, 2, 3, 4, 5, 7, 8, size - 8, size - 7, size - 6, size - 5, size - 4, size - 3, size - 2];
1473
+ for (let i = 0; i < 15; i++) m[8][hPos[i]] = bits[i];
1474
+ const vPos = [size - 1, size - 2, size - 3, size - 4, size - 5, size - 6, size - 7, size - 8, 7, 5, 4, 3, 2, 1, 0];
1475
+ for (let i = 0; i < 15; i++) m[vPos[i]][8] = bits[i];
1476
+ }
1477
+ function placeVersionInfo(m, ver) {
1478
+ if (ver < 7) return;
1479
+ const size = m.length;
1480
+ let info = bchEncode(ver, 7973, 6);
1481
+ for (let i = 0; i < 18; i++) {
1482
+ const bit = info >> i & 1;
1483
+ const r = Math.floor(i / 3);
1484
+ const c = size - 11 + i % 3;
1485
+ m[r][c] = bit;
1486
+ m[c][r] = bit;
1487
+ }
1488
+ }
1489
+ function generateQrSvg(text, moduleSize = 4) {
1490
+ const bytes = Array.from(new TextEncoder().encode(text));
1491
+ const ver = pickVersion(bytes.length);
1492
+ const dataCW = encodeData(bytes, ver);
1493
+ const allCW = computeCodewords(ver, dataCW);
1494
+ const size = ver * 4 + 17;
1495
+ const template = createMatrix(size);
1496
+ placeFinderPattern(template, 0, 0);
1497
+ placeFinderPattern(template, 0, size - 7);
1498
+ placeFinderPattern(template, size - 7, 0);
1499
+ for (let i = 8; i < size - 8; i++) {
1500
+ template[6][i] = LIGHT;
1501
+ template[i][6] = LIGHT;
1502
+ }
1503
+ const ap = VER[ver].align;
1504
+ for (const r of ap) {
1505
+ for (const c of ap) {
1506
+ if (r <= 8 && c <= 8) continue;
1507
+ if (r <= 8 && c >= size - 8) continue;
1508
+ if (r >= size - 8 && c <= 8) continue;
1509
+ for (let dr = -2; dr <= 2; dr++) for (let dc = -2; dc <= 2; dc++) template[r + dr][c + dc] = LIGHT;
1510
+ }
1511
+ }
1512
+ template[4 * ver + 9][8] = LIGHT;
1513
+ for (let i = 0; i < 9; i++) {
1514
+ if (template[8][i] === UNSET) template[8][i] = LIGHT;
1515
+ if (template[i][8] === UNSET) template[i][8] = LIGHT;
1516
+ }
1517
+ for (let i = 0; i < 8; i++) {
1518
+ if (template[8][size - 1 - i] === UNSET) template[8][size - 1 - i] = LIGHT;
1519
+ if (template[size - 1 - i][8] === UNSET) template[size - 1 - i][8] = LIGHT;
1520
+ }
1521
+ if (ver >= 7) {
1522
+ for (let i = 0; i < 6; i++) for (let j = 0; j < 3; j++) {
1523
+ template[i][size - 11 + j] = LIGHT;
1524
+ template[size - 11 + j][i] = LIGHT;
1525
+ }
1526
+ }
1527
+ const base = buildMatrix(ver, allCW);
1528
+ let bestMask = 0;
1529
+ let bestScore = Infinity;
1530
+ for (let mask = 0; mask < 8; mask++) {
1531
+ const masked = applyMask(base, mask, template);
1532
+ placeFormatInfo(masked, mask);
1533
+ placeVersionInfo(masked, ver);
1534
+ const s = penalty(masked);
1535
+ if (s < bestScore) {
1536
+ bestScore = s;
1537
+ bestMask = mask;
1538
+ }
1539
+ }
1540
+ const final = applyMask(base, bestMask, template);
1541
+ placeFormatInfo(final, bestMask);
1542
+ placeVersionInfo(final, ver);
1543
+ const quiet = 4;
1544
+ const total = size + quiet * 2;
1545
+ const px = total * moduleSize;
1546
+ let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${total} ${total}" width="${px}" height="${px}" shape-rendering="crispEdges">`;
1547
+ svg += `<rect width="${total}" height="${total}" fill="#fff"/>`;
1548
+ for (let r = 0; r < size; r++) {
1549
+ for (let c = 0; c < size; c++) {
1550
+ if (final[r][c] === DARK) {
1551
+ svg += `<rect x="${c + quiet}" y="${r + quiet}" width="1" height="1" fill="#000"/>`;
1552
+ }
1553
+ }
1554
+ }
1555
+ svg += "</svg>";
1556
+ return svg;
1557
+ }
1558
+
561
1559
  // src/authon.ts
562
1560
  var Authon = class {
563
1561
  publishableKey;
@@ -595,15 +1593,26 @@ var Authon = class {
595
1593
  await this.ensureInitialized();
596
1594
  this.getModal().open("signUp");
597
1595
  }
1596
+ /** Update theme at runtime without destroying form state */
1597
+ setTheme(theme) {
1598
+ this.getModal().setTheme(theme);
1599
+ }
598
1600
  async signInWithOAuth(provider, options) {
599
1601
  await this.ensureInitialized();
600
1602
  await this.startOAuthFlow(provider, options);
601
1603
  }
602
1604
  async signInWithEmail(email, password) {
603
- const tokens = await this.apiPost("/v1/auth/signin", { email, password });
604
- this.session.setSession(tokens);
605
- this.emit("signedIn", tokens.user);
606
- return tokens.user;
1605
+ const res = await this.apiPost(
1606
+ "/v1/auth/signin",
1607
+ { email, password }
1608
+ );
1609
+ if (res.mfaRequired && res.mfaToken) {
1610
+ this.emit("mfaRequired", res.mfaToken);
1611
+ throw new AuthonMfaRequiredError(res.mfaToken);
1612
+ }
1613
+ this.session.setSession(res);
1614
+ this.emit("signedIn", res.user);
1615
+ return res.user;
607
1616
  }
608
1617
  async signUpWithEmail(email, password, meta) {
609
1618
  const tokens = await this.apiPost("/v1/auth/signup", {
@@ -631,6 +1640,258 @@ var Authon = class {
631
1640
  set.add(listener);
632
1641
  return () => set.delete(listener);
633
1642
  }
1643
+ // ── MFA ──
1644
+ async setupMfa() {
1645
+ const token = this.session.getToken();
1646
+ if (!token) throw new Error("Must be signed in to setup MFA");
1647
+ const res = await this.apiPostAuth("/v1/auth/mfa/totp/setup", void 0, token);
1648
+ return { ...res, qrCodeSvg: generateQrSvg(res.qrCodeUri) };
1649
+ }
1650
+ async verifyMfaSetup(code) {
1651
+ const token = this.session.getToken();
1652
+ if (!token) throw new Error("Must be signed in to verify MFA setup");
1653
+ await this.apiPostAuth("/v1/auth/mfa/totp/verify-setup", { code }, token);
1654
+ }
1655
+ async verifyMfa(mfaToken, code) {
1656
+ const res = await this.apiPost("/v1/auth/mfa/verify", { mfaToken, code });
1657
+ this.session.setSession(res);
1658
+ this.emit("signedIn", res.user);
1659
+ return res.user;
1660
+ }
1661
+ async disableMfa(code) {
1662
+ const token = this.session.getToken();
1663
+ if (!token) throw new Error("Must be signed in to disable MFA");
1664
+ await this.apiPostAuth("/v1/auth/mfa/disable", { code }, token);
1665
+ }
1666
+ async getMfaStatus() {
1667
+ const token = this.session.getToken();
1668
+ if (!token) throw new Error("Must be signed in to get MFA status");
1669
+ const res = await fetch(`${this.config.apiUrl}/v1/auth/mfa/status`, {
1670
+ headers: {
1671
+ "x-api-key": this.publishableKey,
1672
+ Authorization: `Bearer ${token}`
1673
+ },
1674
+ credentials: "include"
1675
+ });
1676
+ if (!res.ok) throw new Error(await this.parseApiError(res, "/v1/auth/mfa/status"));
1677
+ return res.json();
1678
+ }
1679
+ async regenerateBackupCodes(code) {
1680
+ const token = this.session.getToken();
1681
+ if (!token) throw new Error("Must be signed in to regenerate backup codes");
1682
+ const res = await this.apiPostAuth(
1683
+ "/v1/auth/mfa/backup-codes/regenerate",
1684
+ { code },
1685
+ token
1686
+ );
1687
+ return res.backupCodes;
1688
+ }
1689
+ // ── Passwordless ──
1690
+ async sendMagicLink(email) {
1691
+ await this.apiPost("/v1/auth/passwordless/magic-link", { email });
1692
+ }
1693
+ async sendEmailOtp(email) {
1694
+ await this.apiPost("/v1/auth/passwordless/email-otp", { email });
1695
+ }
1696
+ async verifyPasswordless(options) {
1697
+ const res = await this.apiPost("/v1/auth/passwordless/verify", options);
1698
+ this.session.setSession(res);
1699
+ this.emit("signedIn", res.user);
1700
+ return res.user;
1701
+ }
1702
+ // ── Passkeys ──
1703
+ async registerPasskey(name) {
1704
+ const token = this.session.getToken();
1705
+ if (!token) throw new Error("Must be signed in to register a passkey");
1706
+ const options = await this.apiPostAuth(
1707
+ "/v1/auth/passkeys/register/options",
1708
+ name ? { name } : void 0,
1709
+ token
1710
+ );
1711
+ const credential = await navigator.credentials.create({
1712
+ publicKey: this.deserializeCreationOptions(options.options)
1713
+ });
1714
+ const attestation = credential.response;
1715
+ const result = await this.apiPostAuth(
1716
+ "/v1/auth/passkeys/register/verify",
1717
+ {
1718
+ id: credential.id,
1719
+ rawId: this.bufferToBase64url(credential.rawId),
1720
+ type: credential.type,
1721
+ response: {
1722
+ attestationObject: this.bufferToBase64url(attestation.attestationObject),
1723
+ clientDataJSON: this.bufferToBase64url(attestation.clientDataJSON)
1724
+ }
1725
+ },
1726
+ token
1727
+ );
1728
+ this.emit("passkeyRegistered", result);
1729
+ return result;
1730
+ }
1731
+ async authenticateWithPasskey(email) {
1732
+ const options = await this.apiPost(
1733
+ "/v1/auth/passkeys/authenticate/options",
1734
+ email ? { email } : void 0
1735
+ );
1736
+ const credential = await navigator.credentials.get({
1737
+ publicKey: this.deserializeRequestOptions(options.options)
1738
+ });
1739
+ const assertion = credential.response;
1740
+ const res = await this.apiPost("/v1/auth/passkeys/authenticate/verify", {
1741
+ id: credential.id,
1742
+ rawId: this.bufferToBase64url(credential.rawId),
1743
+ type: credential.type,
1744
+ response: {
1745
+ authenticatorData: this.bufferToBase64url(assertion.authenticatorData),
1746
+ clientDataJSON: this.bufferToBase64url(assertion.clientDataJSON),
1747
+ signature: this.bufferToBase64url(assertion.signature),
1748
+ userHandle: assertion.userHandle ? this.bufferToBase64url(assertion.userHandle) : void 0
1749
+ }
1750
+ });
1751
+ this.session.setSession(res);
1752
+ this.emit("signedIn", res.user);
1753
+ return res.user;
1754
+ }
1755
+ async listPasskeys() {
1756
+ const token = this.session.getToken();
1757
+ if (!token) throw new Error("Must be signed in to list passkeys");
1758
+ return this.apiGetAuth("/v1/auth/passkeys", token);
1759
+ }
1760
+ async renamePasskey(passkeyId, name) {
1761
+ const token = this.session.getToken();
1762
+ if (!token) throw new Error("Must be signed in to rename a passkey");
1763
+ return this.apiPatchAuth(`/v1/auth/passkeys/${passkeyId}`, { name }, token);
1764
+ }
1765
+ async revokePasskey(passkeyId) {
1766
+ const token = this.session.getToken();
1767
+ if (!token) throw new Error("Must be signed in to revoke a passkey");
1768
+ await this.apiDeleteAuth(`/v1/auth/passkeys/${passkeyId}`, token);
1769
+ }
1770
+ // ── Web3 ──
1771
+ async web3GetNonce(address, chain, walletType, chainId) {
1772
+ return this.apiPost("/v1/auth/web3/nonce", {
1773
+ address,
1774
+ chain,
1775
+ walletType,
1776
+ ...chainId != null ? { chainId } : {}
1777
+ });
1778
+ }
1779
+ async web3Verify(message, signature, address, chain, walletType) {
1780
+ const res = await this.apiPost("/v1/auth/web3/verify", {
1781
+ message,
1782
+ signature,
1783
+ address,
1784
+ chain,
1785
+ walletType
1786
+ });
1787
+ this.session.setSession(res);
1788
+ this.emit("signedIn", res.user);
1789
+ return res.user;
1790
+ }
1791
+ async listWallets() {
1792
+ const token = this.session.getToken();
1793
+ if (!token) throw new Error("Must be signed in to list wallets");
1794
+ return this.apiGetAuth("/v1/auth/web3/wallets", token);
1795
+ }
1796
+ async linkWallet(params) {
1797
+ const token = this.session.getToken();
1798
+ if (!token) throw new Error("Must be signed in to link a wallet");
1799
+ const wallet = await this.apiPostAuth("/v1/auth/web3/wallets/link", params, token);
1800
+ this.emit("web3Connected", wallet);
1801
+ return wallet;
1802
+ }
1803
+ async unlinkWallet(walletId) {
1804
+ const token = this.session.getToken();
1805
+ if (!token) throw new Error("Must be signed in to unlink a wallet");
1806
+ await this.apiDeleteAuth(`/v1/auth/web3/wallets/${walletId}`, token);
1807
+ }
1808
+ // ── User Profile ──
1809
+ async updateProfile(data) {
1810
+ const token = this.session.getToken();
1811
+ if (!token) throw new Error("Must be signed in to update profile");
1812
+ const user = await this.apiPatchAuth("/v1/auth/me", data, token);
1813
+ this.session.updateUser(user);
1814
+ return user;
1815
+ }
1816
+ // ── Session Management ──
1817
+ async listSessions() {
1818
+ const token = this.session.getToken();
1819
+ if (!token) throw new Error("Must be signed in to list sessions");
1820
+ return this.apiGetAuth("/v1/auth/me/sessions", token);
1821
+ }
1822
+ async revokeSession(sessionId) {
1823
+ const token = this.session.getToken();
1824
+ if (!token) throw new Error("Must be signed in to revoke a session");
1825
+ await this.apiDeleteAuth(`/v1/auth/me/sessions/${sessionId}`, token);
1826
+ }
1827
+ // ── Organizations ──
1828
+ organizations = {
1829
+ list: async () => {
1830
+ const token = this.session.getToken();
1831
+ if (!token) throw new Error("Must be signed in to list organizations");
1832
+ return this.apiGetAuth("/v1/auth/organizations", token);
1833
+ },
1834
+ create: async (params) => {
1835
+ const token = this.session.getToken();
1836
+ if (!token) throw new Error("Must be signed in to create an organization");
1837
+ return this.apiPostAuth("/v1/auth/organizations", params, token);
1838
+ },
1839
+ get: async (orgId) => {
1840
+ const token = this.session.getToken();
1841
+ if (!token) throw new Error("Must be signed in to get organization");
1842
+ return this.apiGetAuth(`/v1/auth/organizations/${orgId}`, token);
1843
+ },
1844
+ update: async (orgId, params) => {
1845
+ const token = this.session.getToken();
1846
+ if (!token) throw new Error("Must be signed in to update organization");
1847
+ return this.apiPatchAuth(`/v1/auth/organizations/${orgId}`, params, token);
1848
+ },
1849
+ delete: async (orgId) => {
1850
+ const token = this.session.getToken();
1851
+ if (!token) throw new Error("Must be signed in to delete organization");
1852
+ await this.apiDeleteAuth(`/v1/auth/organizations/${orgId}`, token);
1853
+ },
1854
+ getMembers: async (orgId) => {
1855
+ const token = this.session.getToken();
1856
+ if (!token) throw new Error("Must be signed in to get organization members");
1857
+ return this.apiGetAuth(`/v1/auth/organizations/${orgId}/members`, token);
1858
+ },
1859
+ invite: async (orgId, params) => {
1860
+ const token = this.session.getToken();
1861
+ if (!token) throw new Error("Must be signed in to invite a member");
1862
+ return this.apiPostAuth(`/v1/auth/organizations/${orgId}/invitations`, params, token);
1863
+ },
1864
+ getInvitations: async (orgId) => {
1865
+ const token = this.session.getToken();
1866
+ if (!token) throw new Error("Must be signed in to get invitations");
1867
+ return this.apiGetAuth(`/v1/auth/organizations/${orgId}/invitations`, token);
1868
+ },
1869
+ acceptInvitation: async (token) => {
1870
+ const authToken = this.session.getToken();
1871
+ if (!authToken) throw new Error("Must be signed in to accept an invitation");
1872
+ return this.apiPostAuth(`/v1/auth/organizations/invitations/${token}/accept`, void 0, authToken);
1873
+ },
1874
+ rejectInvitation: async (token) => {
1875
+ const authToken = this.session.getToken();
1876
+ if (!authToken) throw new Error("Must be signed in to reject an invitation");
1877
+ await this.apiPostAuth(`/v1/auth/organizations/invitations/${token}/reject`, void 0, authToken);
1878
+ },
1879
+ removeMember: async (orgId, memberId) => {
1880
+ const token = this.session.getToken();
1881
+ if (!token) throw new Error("Must be signed in to remove a member");
1882
+ await this.apiDeleteAuth(`/v1/auth/organizations/${orgId}/members/${memberId}`, token);
1883
+ },
1884
+ updateMemberRole: async (orgId, memberId, role) => {
1885
+ const token = this.session.getToken();
1886
+ if (!token) throw new Error("Must be signed in to update member role");
1887
+ return this.apiPatchAuth(`/v1/auth/organizations/${orgId}/members/${memberId}`, { role }, token);
1888
+ },
1889
+ leave: async (orgId) => {
1890
+ const token = this.session.getToken();
1891
+ if (!token) throw new Error("Must be signed in to leave organization");
1892
+ await this.apiPostAuth(`/v1/auth/organizations/${orgId}/leave`, void 0, token);
1893
+ }
1894
+ };
634
1895
  destroy() {
635
1896
  this.modal?.close();
636
1897
  this.session.destroy();
@@ -678,7 +1939,53 @@ var Authon = class {
678
1939
  this.emit("error", err instanceof Error ? err : new Error(msg));
679
1940
  });
680
1941
  },
681
- onClose: () => this.modal?.close()
1942
+ onClose: () => this.modal?.close(),
1943
+ onWeb3WalletSelect: async (walletId) => {
1944
+ const chain = walletId === "phantom" ? "solana" : "evm";
1945
+ try {
1946
+ this.modal?.showOverlay?.("web3-connecting");
1947
+ const address = await this.getWalletAddress(walletId);
1948
+ const { message } = await this.web3GetNonce(address, chain, walletId);
1949
+ const signature = await this.requestWalletSignature(walletId, message);
1950
+ await this.web3Verify(message, signature, address, chain, walletId);
1951
+ this.modal?.showWeb3Success(walletId, address);
1952
+ setTimeout(() => this.modal?.close(), 2500);
1953
+ } catch (err) {
1954
+ this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
1955
+ }
1956
+ },
1957
+ onPasswordlessSubmit: async (email) => {
1958
+ try {
1959
+ const method = this.branding?.passwordlessMethod ?? "magic_link";
1960
+ if (method === "email_otp" || method === "both") {
1961
+ await this.sendEmailOtp(email);
1962
+ this.modal?.showOtpInput(email);
1963
+ } else {
1964
+ await this.sendMagicLink(email);
1965
+ this.modal?.showPasswordlessSent();
1966
+ }
1967
+ } catch (err) {
1968
+ this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
1969
+ }
1970
+ },
1971
+ onOtpVerify: async (email, code) => {
1972
+ try {
1973
+ await this.verifyPasswordless({ email, code });
1974
+ this.modal?.close();
1975
+ } catch (err) {
1976
+ this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
1977
+ }
1978
+ },
1979
+ onPasskeyClick: async () => {
1980
+ try {
1981
+ this.modal?.showOverlay?.("passkey-verifying");
1982
+ await this.authenticateWithPasskey();
1983
+ this.modal?.showPasskeySuccess();
1984
+ setTimeout(() => this.modal?.close(), 2500);
1985
+ } catch (err) {
1986
+ this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
1987
+ }
1988
+ }
682
1989
  });
683
1990
  }
684
1991
  if (this.branding) this.modal.setBranding(this.branding);
@@ -921,6 +2228,130 @@ var Authon = class {
921
2228
  if (!res.ok) throw new Error(await this.parseApiError(res, path));
922
2229
  return res.json();
923
2230
  }
2231
+ async apiPostAuth(path, body, token) {
2232
+ const res = await fetch(`${this.config.apiUrl}${path}`, {
2233
+ method: "POST",
2234
+ headers: {
2235
+ "Content-Type": "application/json",
2236
+ "x-api-key": this.publishableKey,
2237
+ Authorization: `Bearer ${token}`
2238
+ },
2239
+ credentials: "include",
2240
+ body: body ? JSON.stringify(body) : void 0
2241
+ });
2242
+ if (!res.ok) throw new Error(await this.parseApiError(res, path));
2243
+ return res.json();
2244
+ }
2245
+ async apiGetAuth(path, token) {
2246
+ const res = await fetch(`${this.config.apiUrl}${path}`, {
2247
+ headers: {
2248
+ "x-api-key": this.publishableKey,
2249
+ Authorization: `Bearer ${token}`
2250
+ },
2251
+ credentials: "include"
2252
+ });
2253
+ if (!res.ok) throw new Error(await this.parseApiError(res, path));
2254
+ return res.json();
2255
+ }
2256
+ async apiPatchAuth(path, body, token) {
2257
+ const res = await fetch(`${this.config.apiUrl}${path}`, {
2258
+ method: "PATCH",
2259
+ headers: {
2260
+ "Content-Type": "application/json",
2261
+ "x-api-key": this.publishableKey,
2262
+ Authorization: `Bearer ${token}`
2263
+ },
2264
+ credentials: "include",
2265
+ body: body ? JSON.stringify(body) : void 0
2266
+ });
2267
+ if (!res.ok) throw new Error(await this.parseApiError(res, path));
2268
+ return res.json();
2269
+ }
2270
+ async apiDeleteAuth(path, token) {
2271
+ const res = await fetch(`${this.config.apiUrl}${path}`, {
2272
+ method: "DELETE",
2273
+ headers: {
2274
+ "x-api-key": this.publishableKey,
2275
+ Authorization: `Bearer ${token}`
2276
+ },
2277
+ credentials: "include"
2278
+ });
2279
+ if (!res.ok) throw new Error(await this.parseApiError(res, path));
2280
+ }
2281
+ // ── Wallet helpers ──
2282
+ async getWalletAddress(walletId) {
2283
+ if (walletId === "phantom") {
2284
+ const provider2 = window.solana;
2285
+ if (!provider2?.isPhantom) throw new Error("Phantom wallet not detected. Please install it from phantom.app");
2286
+ const resp = await provider2.connect();
2287
+ return resp.publicKey.toString();
2288
+ }
2289
+ const provider = window.ethereum;
2290
+ if (!provider) throw new Error(`${walletId} wallet not detected. Please install it.`);
2291
+ const accounts = await provider.request({ method: "eth_requestAccounts" });
2292
+ return accounts[0];
2293
+ }
2294
+ async requestWalletSignature(walletId, message) {
2295
+ if (walletId === "phantom") {
2296
+ const provider2 = window.solana;
2297
+ const encoded = new TextEncoder().encode(message);
2298
+ const signed = await provider2.signMessage(encoded, "utf8");
2299
+ return Array.from(new Uint8Array(signed.signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
2300
+ }
2301
+ const provider = window.ethereum;
2302
+ const accounts = await provider.request({ method: "eth_requestAccounts" });
2303
+ return provider.request({ method: "personal_sign", params: [message, accounts[0]] });
2304
+ }
2305
+ // ── WebAuthn helpers ──
2306
+ bufferToBase64url(buffer) {
2307
+ const bytes = new Uint8Array(buffer);
2308
+ let binary = "";
2309
+ for (let i = 0; i < bytes.length; i++) {
2310
+ binary += String.fromCharCode(bytes[i]);
2311
+ }
2312
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2313
+ }
2314
+ base64urlToBuffer(base64url) {
2315
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
2316
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
2317
+ const binary = atob(padded);
2318
+ const bytes = new Uint8Array(binary.length);
2319
+ for (let i = 0; i < binary.length; i++) {
2320
+ bytes[i] = binary.charCodeAt(i);
2321
+ }
2322
+ return bytes.buffer;
2323
+ }
2324
+ deserializeCreationOptions(options) {
2325
+ const opts = { ...options };
2326
+ if (typeof opts.challenge === "string") {
2327
+ opts.challenge = this.base64urlToBuffer(opts.challenge);
2328
+ }
2329
+ if (opts.user && typeof opts.user.id === "string") {
2330
+ opts.user.id = this.base64urlToBuffer(
2331
+ opts.user.id
2332
+ );
2333
+ }
2334
+ if (Array.isArray(opts.excludeCredentials)) {
2335
+ opts.excludeCredentials = opts.excludeCredentials.map((c) => ({
2336
+ ...c,
2337
+ id: typeof c.id === "string" ? this.base64urlToBuffer(c.id) : c.id
2338
+ }));
2339
+ }
2340
+ return opts;
2341
+ }
2342
+ deserializeRequestOptions(options) {
2343
+ const opts = { ...options };
2344
+ if (typeof opts.challenge === "string") {
2345
+ opts.challenge = this.base64urlToBuffer(opts.challenge);
2346
+ }
2347
+ if (Array.isArray(opts.allowCredentials)) {
2348
+ opts.allowCredentials = opts.allowCredentials.map((c) => ({
2349
+ ...c,
2350
+ id: typeof c.id === "string" ? this.base64urlToBuffer(c.id) : c.id
2351
+ }));
2352
+ }
2353
+ return opts;
2354
+ }
924
2355
  async parseApiError(res, path) {
925
2356
  try {
926
2357
  const body = await res.json();
@@ -938,6 +2369,8 @@ var Authon = class {
938
2369
  // Annotate the CommonJS export names for ESM import in node:
939
2370
  0 && (module.exports = {
940
2371
  Authon,
2372
+ AuthonMfaRequiredError,
2373
+ generateQrSvg,
941
2374
  getProviderButtonConfig
942
2375
  });
943
2376
  //# sourceMappingURL=index.cjs.map