@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/README.ko.md +99 -0
- package/README.md +249 -59
- package/dist/index.cjs +1451 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -2
- package/dist/index.d.ts +82 -2
- package/dist/index.js +1445 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,53 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var AuthonMfaRequiredError = class extends Error {
|
|
3
|
+
mfaToken;
|
|
4
|
+
constructor(mfaToken) {
|
|
5
|
+
super("MFA verification required");
|
|
6
|
+
this.name = "AuthonMfaRequiredError";
|
|
7
|
+
this.mfaToken = mfaToken;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// ../shared/dist/index.js
|
|
12
|
+
var PROVIDER_DISPLAY_NAMES = {
|
|
13
|
+
google: "Google",
|
|
14
|
+
apple: "Apple",
|
|
15
|
+
kakao: "Kakao",
|
|
16
|
+
naver: "Naver",
|
|
17
|
+
facebook: "Facebook",
|
|
18
|
+
github: "GitHub",
|
|
19
|
+
discord: "Discord",
|
|
20
|
+
x: "X",
|
|
21
|
+
line: "LINE",
|
|
22
|
+
microsoft: "Microsoft"
|
|
23
|
+
};
|
|
24
|
+
var PROVIDER_COLORS = {
|
|
25
|
+
google: { bg: "#ffffff", text: "#1f1f1f" },
|
|
26
|
+
apple: { bg: "#000000", text: "#ffffff" },
|
|
27
|
+
kakao: { bg: "#FEE500", text: "#191919" },
|
|
28
|
+
naver: { bg: "#03C75A", text: "#ffffff" },
|
|
29
|
+
facebook: { bg: "#1877F2", text: "#ffffff" },
|
|
30
|
+
github: { bg: "#24292e", text: "#ffffff" },
|
|
31
|
+
discord: { bg: "#5865F2", text: "#ffffff" },
|
|
32
|
+
x: { bg: "#000000", text: "#ffffff" },
|
|
33
|
+
line: { bg: "#06C755", text: "#ffffff" },
|
|
34
|
+
microsoft: { bg: "#ffffff", text: "#1f1f1f" }
|
|
35
|
+
};
|
|
36
|
+
var DEFAULT_BRANDING = {
|
|
37
|
+
primaryColorStart: "#7c3aed",
|
|
38
|
+
primaryColorEnd: "#4f46e5",
|
|
39
|
+
lightBg: "#ffffff",
|
|
40
|
+
lightText: "#111827",
|
|
41
|
+
darkBg: "#0f172a",
|
|
42
|
+
darkText: "#f1f5f9",
|
|
43
|
+
borderRadius: 12,
|
|
44
|
+
showEmailPassword: true,
|
|
45
|
+
showDivider: true,
|
|
46
|
+
showSecuredBy: true,
|
|
47
|
+
locale: "en"
|
|
48
|
+
};
|
|
3
49
|
|
|
4
50
|
// src/providers.ts
|
|
5
|
-
import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES } from "@authon/shared";
|
|
6
51
|
var PROVIDER_ICONS = {
|
|
7
52
|
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>`,
|
|
8
53
|
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>`,
|
|
@@ -27,6 +72,30 @@ function getProviderButtonConfig(provider) {
|
|
|
27
72
|
}
|
|
28
73
|
|
|
29
74
|
// src/modal.ts
|
|
75
|
+
function hexToRgba(hex, alpha) {
|
|
76
|
+
const h = hex.replace("#", "");
|
|
77
|
+
const r = parseInt(h.substring(0, 2), 16);
|
|
78
|
+
const g = parseInt(h.substring(2, 4), 16);
|
|
79
|
+
const b = parseInt(h.substring(4, 6), 16);
|
|
80
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
81
|
+
}
|
|
82
|
+
var WALLET_OPTIONS = [
|
|
83
|
+
{ id: "pexus", name: "Pexus", color: "#7c3aed" },
|
|
84
|
+
{ id: "metamask", name: "MetaMask", color: "#f6851b" },
|
|
85
|
+
{ id: "phantom", name: "Phantom", color: "#ab9ff2" }
|
|
86
|
+
];
|
|
87
|
+
function walletIconSvg(id) {
|
|
88
|
+
switch (id) {
|
|
89
|
+
case "pexus":
|
|
90
|
+
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>`;
|
|
91
|
+
case "metamask":
|
|
92
|
+
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>`;
|
|
93
|
+
case "phantom":
|
|
94
|
+
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>`;
|
|
95
|
+
default:
|
|
96
|
+
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>`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
30
99
|
var ModalRenderer = class {
|
|
31
100
|
shadowRoot = null;
|
|
32
101
|
hostElement = null;
|
|
@@ -34,12 +103,23 @@ var ModalRenderer = class {
|
|
|
34
103
|
mode;
|
|
35
104
|
theme;
|
|
36
105
|
branding;
|
|
106
|
+
themeObserver = null;
|
|
107
|
+
mediaQueryListener = null;
|
|
37
108
|
enabledProviders = [];
|
|
38
109
|
currentView = "signIn";
|
|
39
110
|
onProviderClick;
|
|
40
111
|
onEmailSubmit;
|
|
41
112
|
onClose;
|
|
113
|
+
onWeb3WalletSelect;
|
|
114
|
+
onPasswordlessSubmit;
|
|
115
|
+
onOtpVerify;
|
|
116
|
+
onPasskeyClick;
|
|
42
117
|
escHandler = null;
|
|
118
|
+
// Overlay state
|
|
119
|
+
currentOverlay = "none";
|
|
120
|
+
selectedWallet = "";
|
|
121
|
+
overlayEmail = "";
|
|
122
|
+
overlayError = "";
|
|
43
123
|
constructor(options) {
|
|
44
124
|
this.mode = options.mode;
|
|
45
125
|
this.theme = options.theme || "auto";
|
|
@@ -47,6 +127,14 @@ var ModalRenderer = class {
|
|
|
47
127
|
this.onProviderClick = options.onProviderClick;
|
|
48
128
|
this.onEmailSubmit = options.onEmailSubmit;
|
|
49
129
|
this.onClose = options.onClose;
|
|
130
|
+
this.onWeb3WalletSelect = options.onWeb3WalletSelect || (() => {
|
|
131
|
+
});
|
|
132
|
+
this.onPasswordlessSubmit = options.onPasswordlessSubmit || (() => {
|
|
133
|
+
});
|
|
134
|
+
this.onOtpVerify = options.onOtpVerify || (() => {
|
|
135
|
+
});
|
|
136
|
+
this.onPasskeyClick = options.onPasskeyClick || (() => {
|
|
137
|
+
});
|
|
50
138
|
if (options.mode === "embedded" && options.containerId) {
|
|
51
139
|
this.containerElement = document.getElementById(options.containerId);
|
|
52
140
|
}
|
|
@@ -59,13 +147,16 @@ var ModalRenderer = class {
|
|
|
59
147
|
}
|
|
60
148
|
open(view = "signIn") {
|
|
61
149
|
if (this.shadowRoot && this.hostElement) {
|
|
150
|
+
this.hideOverlay();
|
|
62
151
|
this.switchView(view);
|
|
63
152
|
} else {
|
|
64
153
|
this.currentView = view;
|
|
154
|
+
this.currentOverlay = "none";
|
|
65
155
|
this.render(view);
|
|
66
156
|
}
|
|
67
157
|
}
|
|
68
158
|
close() {
|
|
159
|
+
this.stopThemeObserver();
|
|
69
160
|
if (this.escHandler) {
|
|
70
161
|
document.removeEventListener("keydown", this.escHandler);
|
|
71
162
|
this.escHandler = null;
|
|
@@ -78,6 +169,46 @@ var ModalRenderer = class {
|
|
|
78
169
|
if (this.containerElement) {
|
|
79
170
|
this.containerElement.innerHTML = "";
|
|
80
171
|
}
|
|
172
|
+
this.currentOverlay = "none";
|
|
173
|
+
}
|
|
174
|
+
/** Update theme at runtime without destroying form state */
|
|
175
|
+
setTheme(theme) {
|
|
176
|
+
this.theme = theme;
|
|
177
|
+
this.updateThemeCSS();
|
|
178
|
+
if (theme === "auto") {
|
|
179
|
+
this.startThemeObserver();
|
|
180
|
+
} else {
|
|
181
|
+
this.stopThemeObserver();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
updateThemeCSS() {
|
|
185
|
+
if (!this.shadowRoot) return;
|
|
186
|
+
const styleEl = this.shadowRoot.getElementById("authon-theme-style");
|
|
187
|
+
if (styleEl) {
|
|
188
|
+
styleEl.textContent = this.buildCSS();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
startThemeObserver() {
|
|
192
|
+
this.stopThemeObserver();
|
|
193
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
194
|
+
this.themeObserver = new MutationObserver(() => this.updateThemeCSS());
|
|
195
|
+
this.themeObserver.observe(document.documentElement, {
|
|
196
|
+
attributes: true,
|
|
197
|
+
attributeFilter: ["data-theme", "class"]
|
|
198
|
+
});
|
|
199
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
200
|
+
this.mediaQueryListener = () => this.updateThemeCSS();
|
|
201
|
+
mq.addEventListener("change", this.mediaQueryListener);
|
|
202
|
+
}
|
|
203
|
+
stopThemeObserver() {
|
|
204
|
+
if (this.themeObserver) {
|
|
205
|
+
this.themeObserver.disconnect();
|
|
206
|
+
this.themeObserver = null;
|
|
207
|
+
}
|
|
208
|
+
if (this.mediaQueryListener) {
|
|
209
|
+
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", this.mediaQueryListener);
|
|
210
|
+
this.mediaQueryListener = null;
|
|
211
|
+
}
|
|
81
212
|
}
|
|
82
213
|
showError(message) {
|
|
83
214
|
if (!this.shadowRoot) return;
|
|
@@ -129,6 +260,47 @@ var ModalRenderer = class {
|
|
|
129
260
|
if (!this.shadowRoot) return;
|
|
130
261
|
this.shadowRoot.getElementById("authon-loading-overlay")?.remove();
|
|
131
262
|
}
|
|
263
|
+
// ── Flow Overlay Public API ──
|
|
264
|
+
showOverlay(overlay) {
|
|
265
|
+
this.currentOverlay = overlay;
|
|
266
|
+
this.overlayError = "";
|
|
267
|
+
this.renderOverlay();
|
|
268
|
+
}
|
|
269
|
+
hideOverlay() {
|
|
270
|
+
this.currentOverlay = "none";
|
|
271
|
+
this.overlayError = "";
|
|
272
|
+
if (!this.shadowRoot) return;
|
|
273
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
274
|
+
}
|
|
275
|
+
showWeb3Success(walletId, address) {
|
|
276
|
+
this.selectedWallet = walletId;
|
|
277
|
+
this.overlayError = "";
|
|
278
|
+
const truncated = address.length > 10 ? `${address.slice(0, 6)}...${address.slice(-4)}` : address;
|
|
279
|
+
this.currentOverlay = "web3-success";
|
|
280
|
+
this.renderOverlayWithData({ truncatedAddress: truncated, walletId });
|
|
281
|
+
}
|
|
282
|
+
showPasswordlessSent() {
|
|
283
|
+
this.overlayError = "";
|
|
284
|
+
this.currentOverlay = "passwordless-sent";
|
|
285
|
+
this.renderOverlay();
|
|
286
|
+
}
|
|
287
|
+
showOtpInput(email) {
|
|
288
|
+
this.overlayEmail = email;
|
|
289
|
+
this.overlayError = "";
|
|
290
|
+
this.currentOverlay = "otp-input";
|
|
291
|
+
this.renderOverlay();
|
|
292
|
+
}
|
|
293
|
+
showPasskeySuccess() {
|
|
294
|
+
this.overlayError = "";
|
|
295
|
+
this.currentOverlay = "passkey-success";
|
|
296
|
+
this.renderOverlay();
|
|
297
|
+
}
|
|
298
|
+
showOverlayError(message) {
|
|
299
|
+
this.overlayError = message;
|
|
300
|
+
if (this.currentOverlay !== "none") {
|
|
301
|
+
this.renderOverlay();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
132
304
|
// ── Smooth view switch (no flicker) ──
|
|
133
305
|
switchView(view) {
|
|
134
306
|
if (!this.shadowRoot || view === this.currentView) return;
|
|
@@ -159,13 +331,16 @@ var ModalRenderer = class {
|
|
|
159
331
|
this.shadowRoot.innerHTML = this.buildShell(view);
|
|
160
332
|
this.attachInnerEvents(view);
|
|
161
333
|
this.attachShellEvents();
|
|
334
|
+
if (this.theme === "auto") {
|
|
335
|
+
this.startThemeObserver();
|
|
336
|
+
}
|
|
162
337
|
}
|
|
163
338
|
// ── HTML builders ──
|
|
164
339
|
/** Shell = style + backdrop + modal-container (stable across view switches) */
|
|
165
340
|
buildShell(view) {
|
|
166
341
|
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
167
342
|
return `
|
|
168
|
-
<style>${this.buildCSS()}</style>
|
|
343
|
+
<style id="authon-theme-style">${this.buildCSS()}</style>
|
|
169
344
|
${popupWrapper}
|
|
170
345
|
<div class="modal-container" role="dialog" aria-modal="true">
|
|
171
346
|
<div id="modal-inner" class="modal-inner">
|
|
@@ -200,6 +375,35 @@ var ModalRenderer = class {
|
|
|
200
375
|
${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
|
|
201
376
|
<button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
|
|
202
377
|
</form>` : "";
|
|
378
|
+
const hasMethodAbove = showProviders && this.enabledProviders.length > 0 || b.showEmailPassword !== false;
|
|
379
|
+
const hasMethodBelow = b.showWeb3 || b.showPasswordless || b.showPasskey;
|
|
380
|
+
const methodDivider = hasMethodAbove && hasMethodBelow ? `<div class="divider"><span>or</span></div>` : "";
|
|
381
|
+
const methodButtons = [];
|
|
382
|
+
if (b.showWeb3) {
|
|
383
|
+
methodButtons.push(`<button class="auth-method-btn web3-btn" id="web3-btn">
|
|
384
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
385
|
+
<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"/>
|
|
386
|
+
</svg>
|
|
387
|
+
<span>Connect Wallet</span>
|
|
388
|
+
</button>`);
|
|
389
|
+
}
|
|
390
|
+
if (b.showPasswordless) {
|
|
391
|
+
methodButtons.push(`<button class="auth-method-btn passwordless-btn" id="passwordless-btn">
|
|
392
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
393
|
+
<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"/>
|
|
394
|
+
</svg>
|
|
395
|
+
<span>Continue with Magic Link</span>
|
|
396
|
+
</button>`);
|
|
397
|
+
}
|
|
398
|
+
if (b.showPasskey) {
|
|
399
|
+
methodButtons.push(`<button class="auth-method-btn passkey-btn" id="passkey-btn">
|
|
400
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
401
|
+
<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"/>
|
|
402
|
+
</svg>
|
|
403
|
+
<span>Sign in with Passkey</span>
|
|
404
|
+
</button>`);
|
|
405
|
+
}
|
|
406
|
+
const authMethods = methodButtons.length > 0 ? `<div class="auth-methods">${methodButtons.join("")}</div>` : "";
|
|
203
407
|
const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
|
|
204
408
|
${b.termsUrl ? `<a href="${b.termsUrl}" target="_blank">Terms of Service</a>` : ""}
|
|
205
409
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
@@ -218,6 +422,8 @@ var ModalRenderer = class {
|
|
|
218
422
|
${showProviders ? `<div class="providers">${providerButtons}</div>` : ""}
|
|
219
423
|
${divider}
|
|
220
424
|
${emailForm}
|
|
425
|
+
${methodDivider}
|
|
426
|
+
${authMethods}
|
|
221
427
|
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
222
428
|
${footer}
|
|
223
429
|
${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>` : ""}
|
|
@@ -226,6 +432,11 @@ var ModalRenderer = class {
|
|
|
226
432
|
isDark() {
|
|
227
433
|
if (this.theme === "dark") return true;
|
|
228
434
|
if (this.theme === "light") return false;
|
|
435
|
+
if (typeof document !== "undefined") {
|
|
436
|
+
const html = document.documentElement;
|
|
437
|
+
if (html.classList.contains("dark") || html.getAttribute("data-theme") === "dark") return true;
|
|
438
|
+
if (html.classList.contains("light") || html.getAttribute("data-theme") === "light") return false;
|
|
439
|
+
}
|
|
229
440
|
return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
230
441
|
}
|
|
231
442
|
buildCSS() {
|
|
@@ -249,6 +460,10 @@ var ModalRenderer = class {
|
|
|
249
460
|
--authon-border: ${borderColor};
|
|
250
461
|
--authon-divider: ${dividerColor};
|
|
251
462
|
--authon-input-bg: ${inputBg};
|
|
463
|
+
--authon-overlay-bg: ${hexToRgba(bg, 0.92)};
|
|
464
|
+
--authon-overlay-bg-solid: ${hexToRgba(bg, 0.97)};
|
|
465
|
+
--authon-backdrop-bg: rgba(0,0,0,${dark ? "0.7" : "0.5"});
|
|
466
|
+
--authon-shadow-opacity: ${dark ? "0.5" : "0.25"};
|
|
252
467
|
--authon-radius: ${b.borderRadius ?? 12}px;
|
|
253
468
|
--authon-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
254
469
|
font-family: var(--authon-font);
|
|
@@ -257,7 +472,7 @@ var ModalRenderer = class {
|
|
|
257
472
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
258
473
|
.backdrop {
|
|
259
474
|
position: fixed; inset: 0; z-index: 99998;
|
|
260
|
-
background:
|
|
475
|
+
background: var(--authon-backdrop-bg); backdrop-filter: blur(4px);
|
|
261
476
|
animation: fadeIn 0.2s ease;
|
|
262
477
|
}
|
|
263
478
|
.modal-container {
|
|
@@ -351,10 +566,154 @@ var ModalRenderer = class {
|
|
|
351
566
|
}
|
|
352
567
|
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
353
568
|
.secured-link:hover { text-decoration: underline; }
|
|
569
|
+
|
|
570
|
+
/* Auth method buttons */
|
|
571
|
+
.auth-methods { display: flex; flex-direction: column; gap: 8px; }
|
|
572
|
+
.auth-method-btn {
|
|
573
|
+
display: flex; align-items: center; justify-content: center; gap: 8px;
|
|
574
|
+
width: 100%; padding: 10px 16px;
|
|
575
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
576
|
+
font-size: 13px; font-weight: 500; cursor: pointer;
|
|
577
|
+
font-family: var(--authon-font); transition: opacity 0.15s, transform 0.1s;
|
|
578
|
+
}
|
|
579
|
+
.auth-method-btn:hover { opacity: 0.85; }
|
|
580
|
+
.auth-method-btn:active { transform: scale(0.98); }
|
|
581
|
+
/* Web3 -- purple */
|
|
582
|
+
.web3-btn {
|
|
583
|
+
background: ${dark ? "rgba(139,92,246,0.12)" : "rgba(139,92,246,0.08)"};
|
|
584
|
+
border: 1px solid ${dark ? "rgba(139,92,246,0.3)" : "rgba(139,92,246,0.25)"};
|
|
585
|
+
color: ${dark ? "#c4b5fd" : "#7c3aed"};
|
|
586
|
+
}
|
|
587
|
+
/* Passwordless -- cyan */
|
|
588
|
+
.passwordless-btn {
|
|
589
|
+
background: ${dark ? "rgba(6,182,212,0.12)" : "rgba(6,182,212,0.08)"};
|
|
590
|
+
border: 1px solid ${dark ? "rgba(6,182,212,0.3)" : "rgba(6,182,212,0.25)"};
|
|
591
|
+
color: ${dark ? "#67e8f9" : "#0891b2"};
|
|
592
|
+
}
|
|
593
|
+
/* Passkey -- amber */
|
|
594
|
+
.passkey-btn {
|
|
595
|
+
background: ${dark ? "rgba(245,158,11,0.12)" : "rgba(245,158,11,0.08)"};
|
|
596
|
+
border: 1px solid ${dark ? "rgba(245,158,11,0.3)" : "rgba(245,158,11,0.25)"};
|
|
597
|
+
color: ${dark ? "#fcd34d" : "#b45309"};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/* Flow overlay */
|
|
601
|
+
.flow-overlay {
|
|
602
|
+
position: absolute; inset: 0; z-index: 10;
|
|
603
|
+
background: var(--authon-overlay-bg-solid);
|
|
604
|
+
backdrop-filter: blur(2px);
|
|
605
|
+
border-radius: var(--authon-radius);
|
|
606
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
607
|
+
gap: 12px; padding: 24px;
|
|
608
|
+
animation: fadeIn 0.2s ease;
|
|
609
|
+
}
|
|
610
|
+
.flow-overlay .cancel-link {
|
|
611
|
+
font-size: 12px; color: var(--authon-dim); cursor: pointer; border: none;
|
|
612
|
+
background: none; font-family: var(--authon-font); margin-top: 4px;
|
|
613
|
+
}
|
|
614
|
+
.flow-overlay .cancel-link:hover { text-decoration: underline; }
|
|
615
|
+
.flow-overlay .overlay-title {
|
|
616
|
+
font-size: 14px; font-weight: 600; color: var(--authon-text); text-align: center;
|
|
617
|
+
}
|
|
618
|
+
.flow-overlay .overlay-subtitle {
|
|
619
|
+
font-size: 12px; color: var(--authon-muted); text-align: center;
|
|
620
|
+
}
|
|
621
|
+
.flow-overlay .overlay-error {
|
|
622
|
+
padding: 6px 12px; margin-top: 4px;
|
|
623
|
+
background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3);
|
|
624
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
625
|
+
font-size: 12px; color: #ef4444; text-align: center; width: 100%;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* Wallet picker */
|
|
629
|
+
.wallet-picker { display: flex; flex-direction: column; gap: 8px; width: 100%; }
|
|
630
|
+
.wallet-btn {
|
|
631
|
+
display: flex; align-items: center; gap: 10px;
|
|
632
|
+
width: 100%; padding: 10px 14px;
|
|
633
|
+
background: ${dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)"};
|
|
634
|
+
border: 1px solid ${dark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};
|
|
635
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
636
|
+
font-size: 13px; font-weight: 500; color: var(--authon-text);
|
|
637
|
+
cursor: pointer; font-family: var(--authon-font);
|
|
638
|
+
transition: opacity 0.15s;
|
|
639
|
+
}
|
|
640
|
+
.wallet-btn:hover { opacity: 0.8; }
|
|
641
|
+
.wallet-btn .wallet-icon { display: flex; align-items: center; flex-shrink: 0; }
|
|
642
|
+
.wallet-btn .wallet-icon svg { border-radius: 6px; }
|
|
643
|
+
|
|
644
|
+
/* Passwordless email input in overlay */
|
|
645
|
+
.pwless-form { display: flex; flex-direction: column; gap: 10px; width: 100%; }
|
|
646
|
+
.pwless-submit {
|
|
647
|
+
width: 100%; padding: 10px;
|
|
648
|
+
background: linear-gradient(135deg, #06b6d4, #0891b2);
|
|
649
|
+
color: #fff; border: none; border-radius: calc(var(--authon-radius) * 0.5);
|
|
650
|
+
font-size: 13px; font-weight: 600; cursor: pointer;
|
|
651
|
+
font-family: var(--authon-font); transition: opacity 0.15s;
|
|
652
|
+
}
|
|
653
|
+
.pwless-submit:hover { opacity: 0.9; }
|
|
654
|
+
.pwless-submit:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
655
|
+
|
|
656
|
+
/* OTP input */
|
|
657
|
+
.otp-container { display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; }
|
|
658
|
+
.otp-inputs { display: flex; gap: 8px; justify-content: center; }
|
|
659
|
+
.otp-digit {
|
|
660
|
+
width: 40px; height: 48px; text-align: center;
|
|
661
|
+
font-size: 20px; font-weight: 600; font-family: var(--authon-font);
|
|
662
|
+
background: var(--authon-input-bg); color: var(--authon-text);
|
|
663
|
+
border: 1px solid var(--authon-border);
|
|
664
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
665
|
+
outline: none; transition: border-color 0.15s;
|
|
666
|
+
}
|
|
667
|
+
.otp-digit:focus {
|
|
668
|
+
border-color: var(--authon-primary-start);
|
|
669
|
+
box-shadow: 0 0 0 3px rgba(124,58,237,0.15);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/* Success check animation */
|
|
673
|
+
.success-check {
|
|
674
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
675
|
+
display: flex; align-items: center; justify-content: center;
|
|
676
|
+
}
|
|
677
|
+
.success-check svg path {
|
|
678
|
+
stroke-dasharray: 20;
|
|
679
|
+
stroke-dashoffset: 20;
|
|
680
|
+
animation: check-draw 0.4s ease-out 0.1s forwards;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/* Spinner */
|
|
684
|
+
.flow-spinner {
|
|
685
|
+
animation: spin 0.8s linear infinite;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/* Passkey verifying icon */
|
|
689
|
+
.passkey-icon-pulse {
|
|
690
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
691
|
+
display: flex; align-items: center; justify-content: center;
|
|
692
|
+
background: rgba(245,158,11,0.15);
|
|
693
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/* Wallet connecting icon */
|
|
697
|
+
.wallet-connecting-icon {
|
|
698
|
+
width: 48px; height: 48px; border-radius: 12px;
|
|
699
|
+
display: flex; align-items: center; justify-content: center;
|
|
700
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
701
|
+
}
|
|
702
|
+
.wallet-connecting-icon svg { border-radius: 6px; }
|
|
703
|
+
|
|
704
|
+
/* Address badge */
|
|
705
|
+
.address-badge {
|
|
706
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
707
|
+
padding: 2px 10px; border-radius: 6px;
|
|
708
|
+
background: ${dark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.03)"};
|
|
709
|
+
font-size: 11px; font-family: monospace; color: var(--authon-muted);
|
|
710
|
+
}
|
|
711
|
+
.address-badge .wallet-icon-sm svg { width: 16px; height: 16px; border-radius: 4px; }
|
|
712
|
+
|
|
354
713
|
/* Loading overlay */
|
|
355
714
|
#authon-loading-overlay {
|
|
356
715
|
position: absolute; inset: 0; z-index: 10;
|
|
357
|
-
background:
|
|
716
|
+
background: var(--authon-overlay-bg);
|
|
358
717
|
backdrop-filter: blur(2px);
|
|
359
718
|
border-radius: var(--authon-radius);
|
|
360
719
|
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
|
|
@@ -386,11 +745,229 @@ var ModalRenderer = class {
|
|
|
386
745
|
@keyframes blink { 0%,80%,100% { opacity: .2; } 40% { opacity: 1; } }
|
|
387
746
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
388
747
|
@keyframes slideIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } }
|
|
748
|
+
@keyframes check-draw { to { stroke-dashoffset: 0; } }
|
|
749
|
+
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }
|
|
389
750
|
${b.customCss || ""}
|
|
390
751
|
`;
|
|
391
752
|
}
|
|
753
|
+
// ── Flow Overlay Rendering ──
|
|
754
|
+
renderOverlay() {
|
|
755
|
+
this.renderOverlayWithData({});
|
|
756
|
+
}
|
|
757
|
+
renderOverlayWithData(data) {
|
|
758
|
+
if (!this.shadowRoot) return;
|
|
759
|
+
const container = this.shadowRoot.querySelector(".modal-container");
|
|
760
|
+
if (!container) return;
|
|
761
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
762
|
+
if (this.currentOverlay === "none") return;
|
|
763
|
+
const overlay = document.createElement("div");
|
|
764
|
+
overlay.id = "flow-overlay";
|
|
765
|
+
overlay.className = "flow-overlay";
|
|
766
|
+
overlay.innerHTML = this.buildOverlayContent(data);
|
|
767
|
+
container.appendChild(overlay);
|
|
768
|
+
this.attachOverlayEvents(overlay);
|
|
769
|
+
}
|
|
770
|
+
buildOverlayContent(data) {
|
|
771
|
+
const dark = this.isDark();
|
|
772
|
+
const errorHtml = this.overlayError ? `<div class="overlay-error">${this.escapeHtml(this.overlayError)}</div>` : "";
|
|
773
|
+
switch (this.currentOverlay) {
|
|
774
|
+
case "web3-picker": {
|
|
775
|
+
const walletItems = WALLET_OPTIONS.map(
|
|
776
|
+
(w) => `<button class="wallet-btn" data-wallet="${w.id}">
|
|
777
|
+
<span class="wallet-icon">${walletIconSvg(w.id)}</span>
|
|
778
|
+
<span>${w.name}</span>
|
|
779
|
+
</button>`
|
|
780
|
+
).join("");
|
|
781
|
+
return `
|
|
782
|
+
<div class="overlay-title" style="margin-bottom: 4px;">Select Wallet</div>
|
|
783
|
+
<div class="wallet-picker">${walletItems}</div>
|
|
784
|
+
${errorHtml}
|
|
785
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
786
|
+
`;
|
|
787
|
+
}
|
|
788
|
+
case "web3-connecting": {
|
|
789
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === this.selectedWallet);
|
|
790
|
+
const walletName = wallet?.name ?? this.selectedWallet;
|
|
791
|
+
return `
|
|
792
|
+
<div class="wallet-connecting-icon">${walletIconSvg(this.selectedWallet)}</div>
|
|
793
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
794
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
795
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" opacity="0.25"/>
|
|
796
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" stroke-linecap="round"/>
|
|
797
|
+
</svg>
|
|
798
|
+
<span class="overlay-subtitle">Connecting ${this.escapeHtml(walletName)}...</span>
|
|
799
|
+
</div>
|
|
800
|
+
${errorHtml}
|
|
801
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
802
|
+
`;
|
|
803
|
+
}
|
|
804
|
+
case "web3-success": {
|
|
805
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === (data.walletId || this.selectedWallet));
|
|
806
|
+
const walletColor = wallet?.color ?? "#8b5cf6";
|
|
807
|
+
const truncAddr = data.truncatedAddress || "0x...";
|
|
808
|
+
return `
|
|
809
|
+
<div class="success-check" style="background:linear-gradient(135deg, ${walletColor}, ${walletColor})">
|
|
810
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
811
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
812
|
+
</svg>
|
|
813
|
+
</div>
|
|
814
|
+
<div class="overlay-title">Wallet Connected</div>
|
|
815
|
+
<div class="address-badge">
|
|
816
|
+
<span class="wallet-icon-sm">${walletIconSvg(data.walletId || this.selectedWallet)}</span>
|
|
817
|
+
<span>${this.escapeHtml(truncAddr)}</span>
|
|
818
|
+
</div>
|
|
819
|
+
`;
|
|
820
|
+
}
|
|
821
|
+
case "passwordless-input": {
|
|
822
|
+
return `
|
|
823
|
+
<div class="overlay-title">Enter your email</div>
|
|
824
|
+
<div class="pwless-form">
|
|
825
|
+
<input type="email" placeholder="you@example.com" class="input" id="pwless-email" autocomplete="email" />
|
|
826
|
+
<button class="pwless-submit" id="pwless-submit-btn">Send Magic Link</button>
|
|
827
|
+
</div>
|
|
828
|
+
${errorHtml}
|
|
829
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
830
|
+
`;
|
|
831
|
+
}
|
|
832
|
+
case "passwordless-sending": {
|
|
833
|
+
return `
|
|
834
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
835
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" opacity="0.25"/>
|
|
836
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" stroke-linecap="round"/>
|
|
837
|
+
</svg>
|
|
838
|
+
<span class="overlay-subtitle">Sending magic link...</span>
|
|
839
|
+
`;
|
|
840
|
+
}
|
|
841
|
+
case "passwordless-sent": {
|
|
842
|
+
return `
|
|
843
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
844
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
845
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
846
|
+
</svg>
|
|
847
|
+
</div>
|
|
848
|
+
<div class="overlay-title">Magic link sent!</div>
|
|
849
|
+
<span class="overlay-subtitle">Check your email inbox</span>
|
|
850
|
+
`;
|
|
851
|
+
}
|
|
852
|
+
case "otp-input": {
|
|
853
|
+
const digitInputs = Array.from(
|
|
854
|
+
{ length: 6 },
|
|
855
|
+
(_, i) => `<input type="text" inputmode="numeric" maxlength="1" class="otp-digit" data-idx="${i}" autocomplete="one-time-code" />`
|
|
856
|
+
).join("");
|
|
857
|
+
return `
|
|
858
|
+
<div class="otp-container">
|
|
859
|
+
<div class="overlay-title">Enter verification code</div>
|
|
860
|
+
<span class="overlay-subtitle">6-digit code sent to ${this.escapeHtml(this.overlayEmail)}</span>
|
|
861
|
+
<div class="otp-inputs">${digitInputs}</div>
|
|
862
|
+
${errorHtml}
|
|
863
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
864
|
+
</div>
|
|
865
|
+
`;
|
|
866
|
+
}
|
|
867
|
+
case "passkey-verifying": {
|
|
868
|
+
return `
|
|
869
|
+
<div class="passkey-icon-pulse">
|
|
870
|
+
<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">
|
|
871
|
+
<circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/>
|
|
872
|
+
<path d="M21.7 13.3 19 11"/><path d="m21 15-2.5-1.5"/><path d="m17 17 2.5-1.5"/>
|
|
873
|
+
<path d="M22 9v6a1 1 0 0 1-1 1h-.5"/><circle cx="18" cy="9" r="3"/>
|
|
874
|
+
</svg>
|
|
875
|
+
</div>
|
|
876
|
+
<span class="overlay-subtitle">Verifying identity...</span>
|
|
877
|
+
${errorHtml}
|
|
878
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
879
|
+
`;
|
|
880
|
+
}
|
|
881
|
+
case "passkey-success": {
|
|
882
|
+
return `
|
|
883
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
884
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
885
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
886
|
+
</svg>
|
|
887
|
+
</div>
|
|
888
|
+
<div class="overlay-title">Identity verified!</div>
|
|
889
|
+
`;
|
|
890
|
+
}
|
|
891
|
+
default:
|
|
892
|
+
return "";
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
attachOverlayEvents(overlay) {
|
|
896
|
+
if (!this.shadowRoot) return;
|
|
897
|
+
const cancelBtn = overlay.querySelector("#overlay-cancel");
|
|
898
|
+
if (cancelBtn) {
|
|
899
|
+
cancelBtn.addEventListener("click", () => this.hideOverlay());
|
|
900
|
+
}
|
|
901
|
+
overlay.querySelectorAll(".wallet-btn").forEach((btn) => {
|
|
902
|
+
btn.addEventListener("click", () => {
|
|
903
|
+
const walletId = btn.dataset.wallet;
|
|
904
|
+
if (walletId) {
|
|
905
|
+
this.selectedWallet = walletId;
|
|
906
|
+
this.onWeb3WalletSelect(walletId);
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
});
|
|
910
|
+
const pwlessSubmit = overlay.querySelector("#pwless-submit-btn");
|
|
911
|
+
const pwlessEmail = overlay.querySelector("#pwless-email");
|
|
912
|
+
if (pwlessSubmit && pwlessEmail) {
|
|
913
|
+
setTimeout(() => pwlessEmail.focus(), 50);
|
|
914
|
+
const submitHandler = () => {
|
|
915
|
+
const email = pwlessEmail.value.trim();
|
|
916
|
+
if (!email) return;
|
|
917
|
+
this.overlayEmail = email;
|
|
918
|
+
this.showOverlay("passwordless-sending");
|
|
919
|
+
this.onPasswordlessSubmit(email);
|
|
920
|
+
};
|
|
921
|
+
pwlessSubmit.addEventListener("click", submitHandler);
|
|
922
|
+
pwlessEmail.addEventListener("keydown", (e) => {
|
|
923
|
+
if (e.key === "Enter") {
|
|
924
|
+
e.preventDefault();
|
|
925
|
+
submitHandler();
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
const otpDigits = overlay.querySelectorAll(".otp-digit");
|
|
930
|
+
if (otpDigits.length === 6) {
|
|
931
|
+
setTimeout(() => otpDigits[0].focus(), 50);
|
|
932
|
+
otpDigits.forEach((digit, idx) => {
|
|
933
|
+
digit.addEventListener("input", () => {
|
|
934
|
+
const val = digit.value.replace(/\D/g, "");
|
|
935
|
+
digit.value = val.slice(0, 1);
|
|
936
|
+
if (val && idx < 5) {
|
|
937
|
+
otpDigits[idx + 1].focus();
|
|
938
|
+
}
|
|
939
|
+
const code = Array.from(otpDigits).map((d) => d.value).join("");
|
|
940
|
+
if (code.length === 6) {
|
|
941
|
+
this.onOtpVerify(this.overlayEmail, code);
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
digit.addEventListener("keydown", (e) => {
|
|
945
|
+
if (e.key === "Backspace" && !digit.value && idx > 0) {
|
|
946
|
+
otpDigits[idx - 1].focus();
|
|
947
|
+
otpDigits[idx - 1].value = "";
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
digit.addEventListener("paste", (e) => {
|
|
951
|
+
e.preventDefault();
|
|
952
|
+
const pasted = (e.clipboardData?.getData("text") ?? "").replace(/\D/g, "").slice(0, 6);
|
|
953
|
+
if (pasted.length === 0) return;
|
|
954
|
+
for (let i = 0; i < 6; i++) {
|
|
955
|
+
otpDigits[i].value = pasted[i] || "";
|
|
956
|
+
}
|
|
957
|
+
const lastIdx = Math.min(pasted.length, 5);
|
|
958
|
+
otpDigits[lastIdx].focus();
|
|
959
|
+
if (pasted.length === 6) {
|
|
960
|
+
this.onOtpVerify(this.overlayEmail, pasted);
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
escapeHtml(str) {
|
|
967
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
968
|
+
}
|
|
392
969
|
// ── Event binding ──
|
|
393
|
-
/** Attach events to shell elements (backdrop, ESC)
|
|
970
|
+
/** Attach events to shell elements (backdrop, ESC) -- called once */
|
|
394
971
|
attachShellEvents() {
|
|
395
972
|
if (!this.shadowRoot) return;
|
|
396
973
|
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
@@ -402,12 +979,18 @@ var ModalRenderer = class {
|
|
|
402
979
|
}
|
|
403
980
|
if (this.mode === "popup") {
|
|
404
981
|
this.escHandler = (e) => {
|
|
405
|
-
if (e.key === "Escape")
|
|
982
|
+
if (e.key === "Escape") {
|
|
983
|
+
if (this.currentOverlay !== "none") {
|
|
984
|
+
this.hideOverlay();
|
|
985
|
+
} else {
|
|
986
|
+
this.onClose();
|
|
987
|
+
}
|
|
988
|
+
}
|
|
406
989
|
};
|
|
407
990
|
document.addEventListener("keydown", this.escHandler);
|
|
408
991
|
}
|
|
409
992
|
}
|
|
410
|
-
/** Attach events to inner content (buttons, form, switch link)
|
|
993
|
+
/** Attach events to inner content (buttons, form, switch link) -- called on each view */
|
|
411
994
|
attachInnerEvents(view) {
|
|
412
995
|
if (!this.shadowRoot) return;
|
|
413
996
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
@@ -441,6 +1024,18 @@ var ModalRenderer = class {
|
|
|
441
1024
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
442
1025
|
});
|
|
443
1026
|
}
|
|
1027
|
+
const web3Btn = this.shadowRoot.getElementById("web3-btn");
|
|
1028
|
+
if (web3Btn) {
|
|
1029
|
+
web3Btn.addEventListener("click", () => this.showOverlay("web3-picker"));
|
|
1030
|
+
}
|
|
1031
|
+
const pwlessBtn = this.shadowRoot.getElementById("passwordless-btn");
|
|
1032
|
+
if (pwlessBtn) {
|
|
1033
|
+
pwlessBtn.addEventListener("click", () => this.showOverlay("passwordless-input"));
|
|
1034
|
+
}
|
|
1035
|
+
const passkeyBtn = this.shadowRoot.getElementById("passkey-btn");
|
|
1036
|
+
if (passkeyBtn) {
|
|
1037
|
+
passkeyBtn.addEventListener("click", () => this.onPasskeyClick());
|
|
1038
|
+
}
|
|
444
1039
|
}
|
|
445
1040
|
};
|
|
446
1041
|
|
|
@@ -470,6 +1065,9 @@ var SessionManager = class {
|
|
|
470
1065
|
this.scheduleRefresh(tokens.expiresIn);
|
|
471
1066
|
}
|
|
472
1067
|
}
|
|
1068
|
+
updateUser(user) {
|
|
1069
|
+
this.user = user;
|
|
1070
|
+
}
|
|
473
1071
|
clearSession() {
|
|
474
1072
|
this.accessToken = null;
|
|
475
1073
|
this.refreshToken = null;
|
|
@@ -531,6 +1129,404 @@ var SessionManager = class {
|
|
|
531
1129
|
}
|
|
532
1130
|
};
|
|
533
1131
|
|
|
1132
|
+
// src/qrcode.ts
|
|
1133
|
+
var EXP = [];
|
|
1134
|
+
var LOG = new Array(256).fill(0);
|
|
1135
|
+
(() => {
|
|
1136
|
+
let v = 1;
|
|
1137
|
+
for (let i = 0; i < 255; i++) {
|
|
1138
|
+
EXP[i] = v;
|
|
1139
|
+
LOG[v] = i;
|
|
1140
|
+
v <<= 1;
|
|
1141
|
+
if (v & 256) v ^= 285;
|
|
1142
|
+
}
|
|
1143
|
+
for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
|
|
1144
|
+
})();
|
|
1145
|
+
var gfMul = (a, b) => a && b ? EXP[LOG[a] + LOG[b]] : 0;
|
|
1146
|
+
function rsEncode(data, ecLen) {
|
|
1147
|
+
let g = [1];
|
|
1148
|
+
for (let i = 0; i < ecLen; i++) {
|
|
1149
|
+
const ng = new Array(g.length + 1).fill(0);
|
|
1150
|
+
for (let j = 0; j < g.length; j++) {
|
|
1151
|
+
ng[j] ^= gfMul(g[j], EXP[i]);
|
|
1152
|
+
ng[j + 1] ^= g[j];
|
|
1153
|
+
}
|
|
1154
|
+
g = ng;
|
|
1155
|
+
}
|
|
1156
|
+
const rem = new Array(ecLen).fill(0);
|
|
1157
|
+
for (const d of data) {
|
|
1158
|
+
const fb = d ^ rem[0];
|
|
1159
|
+
for (let j = 0; j < ecLen - 1; j++) {
|
|
1160
|
+
rem[j] = rem[j + 1] ^ gfMul(g[ecLen - 1 - j], fb);
|
|
1161
|
+
}
|
|
1162
|
+
rem[ecLen - 1] = gfMul(g[0], fb);
|
|
1163
|
+
}
|
|
1164
|
+
return rem;
|
|
1165
|
+
}
|
|
1166
|
+
var VER = [
|
|
1167
|
+
{ total: 0, ec: 0, g1: 0, g1d: 0, g2: 0, g2d: 0, align: [] },
|
|
1168
|
+
// dummy
|
|
1169
|
+
{ total: 26, ec: 7, g1: 1, g1d: 19, g2: 0, g2d: 0, align: [] },
|
|
1170
|
+
{ total: 44, ec: 10, g1: 1, g1d: 34, g2: 0, g2d: 0, align: [6, 18] },
|
|
1171
|
+
{ total: 70, ec: 15, g1: 1, g1d: 55, g2: 0, g2d: 0, align: [6, 22] },
|
|
1172
|
+
{ total: 100, ec: 20, g1: 1, g1d: 80, g2: 0, g2d: 0, align: [6, 26] },
|
|
1173
|
+
{ total: 134, ec: 26, g1: 1, g1d: 108, g2: 0, g2d: 0, align: [6, 30] },
|
|
1174
|
+
{ total: 172, ec: 18, g1: 2, g1d: 68, g2: 0, g2d: 0, align: [6, 34] },
|
|
1175
|
+
{ total: 196, ec: 20, g1: 2, g1d: 78, g2: 0, g2d: 0, align: [6, 22, 38] },
|
|
1176
|
+
{ total: 242, ec: 24, g1: 2, g1d: 97, g2: 0, g2d: 0, align: [6, 24, 42] },
|
|
1177
|
+
{ total: 292, ec: 30, g1: 2, g1d: 116, g2: 0, g2d: 0, align: [6, 26, 46] },
|
|
1178
|
+
{ total: 346, ec: 18, g1: 2, g1d: 68, g2: 2, g2d: 69, align: [6, 28, 50] },
|
|
1179
|
+
{ total: 404, ec: 20, g1: 4, g1d: 81, g2: 0, g2d: 0, align: [6, 30, 54] },
|
|
1180
|
+
{ total: 466, ec: 24, g1: 2, g1d: 92, g2: 2, g2d: 93, align: [6, 32, 58] },
|
|
1181
|
+
{ total: 532, ec: 26, g1: 4, g1d: 107, g2: 0, g2d: 0, align: [6, 34, 62] }
|
|
1182
|
+
];
|
|
1183
|
+
function dataCapacity(ver) {
|
|
1184
|
+
const v = VER[ver];
|
|
1185
|
+
return v.g1 * v.g1d + v.g2 * v.g2d;
|
|
1186
|
+
}
|
|
1187
|
+
function pickVersion(byteLen) {
|
|
1188
|
+
for (let v = 1; v < VER.length; v++) {
|
|
1189
|
+
const headerBits = 4 + (v <= 9 ? 8 : 16);
|
|
1190
|
+
const available = dataCapacity(v) * 8 - headerBits;
|
|
1191
|
+
if (byteLen * 8 <= available) return v;
|
|
1192
|
+
}
|
|
1193
|
+
throw new Error(`Data too long for QR code (${byteLen} bytes)`);
|
|
1194
|
+
}
|
|
1195
|
+
function encodeData(bytes, ver) {
|
|
1196
|
+
const cap = dataCapacity(ver);
|
|
1197
|
+
const countBits = ver <= 9 ? 8 : 16;
|
|
1198
|
+
const bits = [];
|
|
1199
|
+
const push = (val, len) => {
|
|
1200
|
+
for (let i = len - 1; i >= 0; i--) bits.push(val >> i & 1);
|
|
1201
|
+
};
|
|
1202
|
+
push(4, 4);
|
|
1203
|
+
push(bytes.length, countBits);
|
|
1204
|
+
for (const b of bytes) push(b, 8);
|
|
1205
|
+
push(0, Math.min(4, cap * 8 - bits.length));
|
|
1206
|
+
while (bits.length % 8) bits.push(0);
|
|
1207
|
+
const padBytes = [236, 17];
|
|
1208
|
+
let pi = 0;
|
|
1209
|
+
while (bits.length < cap * 8) {
|
|
1210
|
+
push(padBytes[pi], 8);
|
|
1211
|
+
pi ^= 1;
|
|
1212
|
+
}
|
|
1213
|
+
const cw = [];
|
|
1214
|
+
for (let i = 0; i < bits.length; i += 8) {
|
|
1215
|
+
let byte = 0;
|
|
1216
|
+
for (let j = 0; j < 8; j++) byte = byte << 1 | bits[i + j];
|
|
1217
|
+
cw.push(byte);
|
|
1218
|
+
}
|
|
1219
|
+
return cw;
|
|
1220
|
+
}
|
|
1221
|
+
function computeCodewords(ver, dataCW) {
|
|
1222
|
+
const v = VER[ver];
|
|
1223
|
+
const blocks = [];
|
|
1224
|
+
const ecBlocks = [];
|
|
1225
|
+
let offset = 0;
|
|
1226
|
+
for (let i = 0; i < v.g1; i++) {
|
|
1227
|
+
const block = dataCW.slice(offset, offset + v.g1d);
|
|
1228
|
+
blocks.push(block);
|
|
1229
|
+
ecBlocks.push(rsEncode(block, v.ec));
|
|
1230
|
+
offset += v.g1d;
|
|
1231
|
+
}
|
|
1232
|
+
for (let i = 0; i < v.g2; i++) {
|
|
1233
|
+
const block = dataCW.slice(offset, offset + v.g2d);
|
|
1234
|
+
blocks.push(block);
|
|
1235
|
+
ecBlocks.push(rsEncode(block, v.ec));
|
|
1236
|
+
offset += v.g2d;
|
|
1237
|
+
}
|
|
1238
|
+
const result = [];
|
|
1239
|
+
const maxDataLen = Math.max(v.g1d, v.g2d || 0);
|
|
1240
|
+
for (let i = 0; i < maxDataLen; i++) {
|
|
1241
|
+
for (const block of blocks) {
|
|
1242
|
+
if (i < block.length) result.push(block[i]);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
for (let i = 0; i < v.ec; i++) {
|
|
1246
|
+
for (const block of ecBlocks) result.push(block[i]);
|
|
1247
|
+
}
|
|
1248
|
+
return result;
|
|
1249
|
+
}
|
|
1250
|
+
var UNSET = -1;
|
|
1251
|
+
var DARK = 1;
|
|
1252
|
+
var LIGHT = 0;
|
|
1253
|
+
function createMatrix(size) {
|
|
1254
|
+
return Array.from({ length: size }, () => new Array(size).fill(UNSET));
|
|
1255
|
+
}
|
|
1256
|
+
function setModule(m, r, c, dark) {
|
|
1257
|
+
if (r >= 0 && r < m.length && c >= 0 && c < m.length) m[r][c] = dark ? DARK : LIGHT;
|
|
1258
|
+
}
|
|
1259
|
+
function placeFinderPattern(m, row, col) {
|
|
1260
|
+
for (let r = -1; r <= 7; r++) {
|
|
1261
|
+
for (let c = -1; c <= 7; c++) {
|
|
1262
|
+
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);
|
|
1263
|
+
setModule(m, row + r, col + c, dark);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
function placeAlignmentPattern(m, row, col) {
|
|
1268
|
+
for (let r = -2; r <= 2; r++) {
|
|
1269
|
+
for (let c = -2; c <= 2; c++) {
|
|
1270
|
+
const dark = Math.abs(r) === 2 || Math.abs(c) === 2 || r === 0 && c === 0;
|
|
1271
|
+
m[row + r][col + c] = dark ? DARK : LIGHT;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function isReserved(m, r, c) {
|
|
1276
|
+
return m[r][c] !== UNSET;
|
|
1277
|
+
}
|
|
1278
|
+
function buildMatrix(ver, codewords) {
|
|
1279
|
+
const size = ver * 4 + 17;
|
|
1280
|
+
const m = createMatrix(size);
|
|
1281
|
+
placeFinderPattern(m, 0, 0);
|
|
1282
|
+
placeFinderPattern(m, 0, size - 7);
|
|
1283
|
+
placeFinderPattern(m, size - 7, 0);
|
|
1284
|
+
for (let i = 8; i < size - 8; i++) {
|
|
1285
|
+
m[6][i] = i % 2 === 0 ? DARK : LIGHT;
|
|
1286
|
+
m[i][6] = i % 2 === 0 ? DARK : LIGHT;
|
|
1287
|
+
}
|
|
1288
|
+
const ap = VER[ver].align;
|
|
1289
|
+
if (ap.length > 0) {
|
|
1290
|
+
for (const r of ap) {
|
|
1291
|
+
for (const c of ap) {
|
|
1292
|
+
if (r <= 8 && c <= 8) continue;
|
|
1293
|
+
if (r <= 8 && c >= size - 8) continue;
|
|
1294
|
+
if (r >= size - 8 && c <= 8) continue;
|
|
1295
|
+
placeAlignmentPattern(m, r, c);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
m[4 * ver + 9][8] = DARK;
|
|
1300
|
+
for (let i = 0; i < 9; i++) {
|
|
1301
|
+
if (m[8][i] === UNSET) m[8][i] = LIGHT;
|
|
1302
|
+
if (m[i][8] === UNSET) m[i][8] = LIGHT;
|
|
1303
|
+
}
|
|
1304
|
+
for (let i = 0; i < 8; i++) {
|
|
1305
|
+
if (m[8][size - 1 - i] === UNSET) m[8][size - 1 - i] = LIGHT;
|
|
1306
|
+
if (m[size - 1 - i][8] === UNSET) m[size - 1 - i][8] = LIGHT;
|
|
1307
|
+
}
|
|
1308
|
+
if (ver >= 7) {
|
|
1309
|
+
for (let i = 0; i < 6; i++) {
|
|
1310
|
+
for (let j = 0; j < 3; j++) {
|
|
1311
|
+
m[i][size - 11 + j] = LIGHT;
|
|
1312
|
+
m[size - 11 + j][i] = LIGHT;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
let bitIdx = 0;
|
|
1317
|
+
const totalBits = codewords.length * 8;
|
|
1318
|
+
let upward = true;
|
|
1319
|
+
for (let right = size - 1; right >= 0; right -= 2) {
|
|
1320
|
+
if (right === 6) right = 5;
|
|
1321
|
+
for (let i = 0; i < size; i++) {
|
|
1322
|
+
const row = upward ? size - 1 - i : i;
|
|
1323
|
+
for (const dc of [0, -1]) {
|
|
1324
|
+
const col = right + dc;
|
|
1325
|
+
if (col < 0 || col >= size) continue;
|
|
1326
|
+
if (isReserved(m, row, col)) continue;
|
|
1327
|
+
if (bitIdx < totalBits) {
|
|
1328
|
+
const byteIdx = bitIdx >> 3;
|
|
1329
|
+
const bitPos = 7 - (bitIdx & 7);
|
|
1330
|
+
m[row][col] = codewords[byteIdx] >> bitPos & 1;
|
|
1331
|
+
bitIdx++;
|
|
1332
|
+
} else {
|
|
1333
|
+
m[row][col] = LIGHT;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
upward = !upward;
|
|
1338
|
+
}
|
|
1339
|
+
return m;
|
|
1340
|
+
}
|
|
1341
|
+
var MASKS = [
|
|
1342
|
+
(r, c) => (r + c) % 2 === 0,
|
|
1343
|
+
(r) => r % 2 === 0,
|
|
1344
|
+
(_, c) => c % 3 === 0,
|
|
1345
|
+
(r, c) => (r + c) % 3 === 0,
|
|
1346
|
+
(r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,
|
|
1347
|
+
(r, c) => r * c % 2 + r * c % 3 === 0,
|
|
1348
|
+
(r, c) => (r * c % 2 + r * c % 3) % 2 === 0,
|
|
1349
|
+
(r, c) => ((r + c) % 2 + r * c % 3) % 2 === 0
|
|
1350
|
+
];
|
|
1351
|
+
function applyMask(m, maskIdx, template) {
|
|
1352
|
+
const size = m.length;
|
|
1353
|
+
const result = m.map((row) => [...row]);
|
|
1354
|
+
const fn = MASKS[maskIdx];
|
|
1355
|
+
for (let r = 0; r < size; r++) {
|
|
1356
|
+
for (let c = 0; c < size; c++) {
|
|
1357
|
+
if (template[r][c] !== UNSET) continue;
|
|
1358
|
+
if (fn(r, c)) result[r][c] ^= 1;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
return result;
|
|
1362
|
+
}
|
|
1363
|
+
function penalty(m) {
|
|
1364
|
+
const size = m.length;
|
|
1365
|
+
let score = 0;
|
|
1366
|
+
for (let r = 0; r < size; r++) {
|
|
1367
|
+
let count = 1;
|
|
1368
|
+
for (let c = 1; c < size; c++) {
|
|
1369
|
+
if (m[r][c] === m[r][c - 1]) {
|
|
1370
|
+
count++;
|
|
1371
|
+
} else {
|
|
1372
|
+
if (count >= 5) score += count - 2;
|
|
1373
|
+
count = 1;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
if (count >= 5) score += count - 2;
|
|
1377
|
+
}
|
|
1378
|
+
for (let c = 0; c < size; c++) {
|
|
1379
|
+
let count = 1;
|
|
1380
|
+
for (let r = 1; r < size; r++) {
|
|
1381
|
+
if (m[r][c] === m[r - 1][c]) {
|
|
1382
|
+
count++;
|
|
1383
|
+
} else {
|
|
1384
|
+
if (count >= 5) score += count - 2;
|
|
1385
|
+
count = 1;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
if (count >= 5) score += count - 2;
|
|
1389
|
+
}
|
|
1390
|
+
for (let r = 0; r < size - 1; r++) {
|
|
1391
|
+
for (let c = 0; c < size - 1; c++) {
|
|
1392
|
+
const v = m[r][c];
|
|
1393
|
+
if (v === m[r][c + 1] && v === m[r + 1][c] && v === m[r + 1][c + 1]) score += 3;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
const pat1 = [1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0];
|
|
1397
|
+
const pat2 = [0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1];
|
|
1398
|
+
for (let r = 0; r < size; r++) {
|
|
1399
|
+
for (let c = 0; c <= size - 11; c++) {
|
|
1400
|
+
let match1 = true, match2 = true;
|
|
1401
|
+
for (let k = 0; k < 11; k++) {
|
|
1402
|
+
if (m[r][c + k] !== pat1[k]) match1 = false;
|
|
1403
|
+
if (m[r][c + k] !== pat2[k]) match2 = false;
|
|
1404
|
+
}
|
|
1405
|
+
if (match1 || match2) score += 40;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
for (let c = 0; c < size; c++) {
|
|
1409
|
+
for (let r = 0; r <= size - 11; r++) {
|
|
1410
|
+
let match1 = true, match2 = true;
|
|
1411
|
+
for (let k = 0; k < 11; k++) {
|
|
1412
|
+
if (m[r + k][c] !== pat1[k]) match1 = false;
|
|
1413
|
+
if (m[r + k][c] !== pat2[k]) match2 = false;
|
|
1414
|
+
}
|
|
1415
|
+
if (match1 || match2) score += 40;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
let dark = 0;
|
|
1419
|
+
for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (m[r][c]) dark++;
|
|
1420
|
+
const pct = dark * 100 / (size * size);
|
|
1421
|
+
const prev5 = Math.floor(pct / 5) * 5;
|
|
1422
|
+
const next5 = prev5 + 5;
|
|
1423
|
+
score += Math.min(Math.abs(prev5 - 50) / 5, Math.abs(next5 - 50) / 5) * 10;
|
|
1424
|
+
return score;
|
|
1425
|
+
}
|
|
1426
|
+
function bchEncode(data, gen, dataBits) {
|
|
1427
|
+
let d = data << 15 - dataBits;
|
|
1428
|
+
const genLen = Math.floor(Math.log2(gen)) + 1;
|
|
1429
|
+
const totalBits = dataBits + (genLen - 1);
|
|
1430
|
+
d = data << totalBits - dataBits;
|
|
1431
|
+
for (let i = dataBits - 1; i >= 0; i--) {
|
|
1432
|
+
if (d & 1 << i + genLen - 1) d ^= gen << i;
|
|
1433
|
+
}
|
|
1434
|
+
return data << genLen - 1 | d;
|
|
1435
|
+
}
|
|
1436
|
+
function placeFormatInfo(m, maskIdx) {
|
|
1437
|
+
const size = m.length;
|
|
1438
|
+
const data = 1 << 3 | maskIdx;
|
|
1439
|
+
let format = bchEncode(data, 1335, 5);
|
|
1440
|
+
format ^= 21522;
|
|
1441
|
+
const bits = [];
|
|
1442
|
+
for (let i = 14; i >= 0; i--) bits.push(format >> i & 1);
|
|
1443
|
+
const hPos = [0, 1, 2, 3, 4, 5, 7, 8, size - 8, size - 7, size - 6, size - 5, size - 4, size - 3, size - 2];
|
|
1444
|
+
for (let i = 0; i < 15; i++) m[8][hPos[i]] = bits[i];
|
|
1445
|
+
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];
|
|
1446
|
+
for (let i = 0; i < 15; i++) m[vPos[i]][8] = bits[i];
|
|
1447
|
+
}
|
|
1448
|
+
function placeVersionInfo(m, ver) {
|
|
1449
|
+
if (ver < 7) return;
|
|
1450
|
+
const size = m.length;
|
|
1451
|
+
let info = bchEncode(ver, 7973, 6);
|
|
1452
|
+
for (let i = 0; i < 18; i++) {
|
|
1453
|
+
const bit = info >> i & 1;
|
|
1454
|
+
const r = Math.floor(i / 3);
|
|
1455
|
+
const c = size - 11 + i % 3;
|
|
1456
|
+
m[r][c] = bit;
|
|
1457
|
+
m[c][r] = bit;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
function generateQrSvg(text, moduleSize = 4) {
|
|
1461
|
+
const bytes = Array.from(new TextEncoder().encode(text));
|
|
1462
|
+
const ver = pickVersion(bytes.length);
|
|
1463
|
+
const dataCW = encodeData(bytes, ver);
|
|
1464
|
+
const allCW = computeCodewords(ver, dataCW);
|
|
1465
|
+
const size = ver * 4 + 17;
|
|
1466
|
+
const template = createMatrix(size);
|
|
1467
|
+
placeFinderPattern(template, 0, 0);
|
|
1468
|
+
placeFinderPattern(template, 0, size - 7);
|
|
1469
|
+
placeFinderPattern(template, size - 7, 0);
|
|
1470
|
+
for (let i = 8; i < size - 8; i++) {
|
|
1471
|
+
template[6][i] = LIGHT;
|
|
1472
|
+
template[i][6] = LIGHT;
|
|
1473
|
+
}
|
|
1474
|
+
const ap = VER[ver].align;
|
|
1475
|
+
for (const r of ap) {
|
|
1476
|
+
for (const c of ap) {
|
|
1477
|
+
if (r <= 8 && c <= 8) continue;
|
|
1478
|
+
if (r <= 8 && c >= size - 8) continue;
|
|
1479
|
+
if (r >= size - 8 && c <= 8) continue;
|
|
1480
|
+
for (let dr = -2; dr <= 2; dr++) for (let dc = -2; dc <= 2; dc++) template[r + dr][c + dc] = LIGHT;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
template[4 * ver + 9][8] = LIGHT;
|
|
1484
|
+
for (let i = 0; i < 9; i++) {
|
|
1485
|
+
if (template[8][i] === UNSET) template[8][i] = LIGHT;
|
|
1486
|
+
if (template[i][8] === UNSET) template[i][8] = LIGHT;
|
|
1487
|
+
}
|
|
1488
|
+
for (let i = 0; i < 8; i++) {
|
|
1489
|
+
if (template[8][size - 1 - i] === UNSET) template[8][size - 1 - i] = LIGHT;
|
|
1490
|
+
if (template[size - 1 - i][8] === UNSET) template[size - 1 - i][8] = LIGHT;
|
|
1491
|
+
}
|
|
1492
|
+
if (ver >= 7) {
|
|
1493
|
+
for (let i = 0; i < 6; i++) for (let j = 0; j < 3; j++) {
|
|
1494
|
+
template[i][size - 11 + j] = LIGHT;
|
|
1495
|
+
template[size - 11 + j][i] = LIGHT;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
const base = buildMatrix(ver, allCW);
|
|
1499
|
+
let bestMask = 0;
|
|
1500
|
+
let bestScore = Infinity;
|
|
1501
|
+
for (let mask = 0; mask < 8; mask++) {
|
|
1502
|
+
const masked = applyMask(base, mask, template);
|
|
1503
|
+
placeFormatInfo(masked, mask);
|
|
1504
|
+
placeVersionInfo(masked, ver);
|
|
1505
|
+
const s = penalty(masked);
|
|
1506
|
+
if (s < bestScore) {
|
|
1507
|
+
bestScore = s;
|
|
1508
|
+
bestMask = mask;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
const final = applyMask(base, bestMask, template);
|
|
1512
|
+
placeFormatInfo(final, bestMask);
|
|
1513
|
+
placeVersionInfo(final, ver);
|
|
1514
|
+
const quiet = 4;
|
|
1515
|
+
const total = size + quiet * 2;
|
|
1516
|
+
const px = total * moduleSize;
|
|
1517
|
+
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${total} ${total}" width="${px}" height="${px}" shape-rendering="crispEdges">`;
|
|
1518
|
+
svg += `<rect width="${total}" height="${total}" fill="#fff"/>`;
|
|
1519
|
+
for (let r = 0; r < size; r++) {
|
|
1520
|
+
for (let c = 0; c < size; c++) {
|
|
1521
|
+
if (final[r][c] === DARK) {
|
|
1522
|
+
svg += `<rect x="${c + quiet}" y="${r + quiet}" width="1" height="1" fill="#000"/>`;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
svg += "</svg>";
|
|
1527
|
+
return svg;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
534
1530
|
// src/authon.ts
|
|
535
1531
|
var Authon = class {
|
|
536
1532
|
publishableKey;
|
|
@@ -568,15 +1564,26 @@ var Authon = class {
|
|
|
568
1564
|
await this.ensureInitialized();
|
|
569
1565
|
this.getModal().open("signUp");
|
|
570
1566
|
}
|
|
1567
|
+
/** Update theme at runtime without destroying form state */
|
|
1568
|
+
setTheme(theme) {
|
|
1569
|
+
this.getModal().setTheme(theme);
|
|
1570
|
+
}
|
|
571
1571
|
async signInWithOAuth(provider, options) {
|
|
572
1572
|
await this.ensureInitialized();
|
|
573
1573
|
await this.startOAuthFlow(provider, options);
|
|
574
1574
|
}
|
|
575
1575
|
async signInWithEmail(email, password) {
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
1576
|
+
const res = await this.apiPost(
|
|
1577
|
+
"/v1/auth/signin",
|
|
1578
|
+
{ email, password }
|
|
1579
|
+
);
|
|
1580
|
+
if (res.mfaRequired && res.mfaToken) {
|
|
1581
|
+
this.emit("mfaRequired", res.mfaToken);
|
|
1582
|
+
throw new AuthonMfaRequiredError(res.mfaToken);
|
|
1583
|
+
}
|
|
1584
|
+
this.session.setSession(res);
|
|
1585
|
+
this.emit("signedIn", res.user);
|
|
1586
|
+
return res.user;
|
|
580
1587
|
}
|
|
581
1588
|
async signUpWithEmail(email, password, meta) {
|
|
582
1589
|
const tokens = await this.apiPost("/v1/auth/signup", {
|
|
@@ -604,6 +1611,258 @@ var Authon = class {
|
|
|
604
1611
|
set.add(listener);
|
|
605
1612
|
return () => set.delete(listener);
|
|
606
1613
|
}
|
|
1614
|
+
// ── MFA ──
|
|
1615
|
+
async setupMfa() {
|
|
1616
|
+
const token = this.session.getToken();
|
|
1617
|
+
if (!token) throw new Error("Must be signed in to setup MFA");
|
|
1618
|
+
const res = await this.apiPostAuth("/v1/auth/mfa/totp/setup", void 0, token);
|
|
1619
|
+
return { ...res, qrCodeSvg: generateQrSvg(res.qrCodeUri) };
|
|
1620
|
+
}
|
|
1621
|
+
async verifyMfaSetup(code) {
|
|
1622
|
+
const token = this.session.getToken();
|
|
1623
|
+
if (!token) throw new Error("Must be signed in to verify MFA setup");
|
|
1624
|
+
await this.apiPostAuth("/v1/auth/mfa/totp/verify-setup", { code }, token);
|
|
1625
|
+
}
|
|
1626
|
+
async verifyMfa(mfaToken, code) {
|
|
1627
|
+
const res = await this.apiPost("/v1/auth/mfa/verify", { mfaToken, code });
|
|
1628
|
+
this.session.setSession(res);
|
|
1629
|
+
this.emit("signedIn", res.user);
|
|
1630
|
+
return res.user;
|
|
1631
|
+
}
|
|
1632
|
+
async disableMfa(code) {
|
|
1633
|
+
const token = this.session.getToken();
|
|
1634
|
+
if (!token) throw new Error("Must be signed in to disable MFA");
|
|
1635
|
+
await this.apiPostAuth("/v1/auth/mfa/disable", { code }, token);
|
|
1636
|
+
}
|
|
1637
|
+
async getMfaStatus() {
|
|
1638
|
+
const token = this.session.getToken();
|
|
1639
|
+
if (!token) throw new Error("Must be signed in to get MFA status");
|
|
1640
|
+
const res = await fetch(`${this.config.apiUrl}/v1/auth/mfa/status`, {
|
|
1641
|
+
headers: {
|
|
1642
|
+
"x-api-key": this.publishableKey,
|
|
1643
|
+
Authorization: `Bearer ${token}`
|
|
1644
|
+
},
|
|
1645
|
+
credentials: "include"
|
|
1646
|
+
});
|
|
1647
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, "/v1/auth/mfa/status"));
|
|
1648
|
+
return res.json();
|
|
1649
|
+
}
|
|
1650
|
+
async regenerateBackupCodes(code) {
|
|
1651
|
+
const token = this.session.getToken();
|
|
1652
|
+
if (!token) throw new Error("Must be signed in to regenerate backup codes");
|
|
1653
|
+
const res = await this.apiPostAuth(
|
|
1654
|
+
"/v1/auth/mfa/backup-codes/regenerate",
|
|
1655
|
+
{ code },
|
|
1656
|
+
token
|
|
1657
|
+
);
|
|
1658
|
+
return res.backupCodes;
|
|
1659
|
+
}
|
|
1660
|
+
// ── Passwordless ──
|
|
1661
|
+
async sendMagicLink(email) {
|
|
1662
|
+
await this.apiPost("/v1/auth/passwordless/magic-link", { email });
|
|
1663
|
+
}
|
|
1664
|
+
async sendEmailOtp(email) {
|
|
1665
|
+
await this.apiPost("/v1/auth/passwordless/email-otp", { email });
|
|
1666
|
+
}
|
|
1667
|
+
async verifyPasswordless(options) {
|
|
1668
|
+
const res = await this.apiPost("/v1/auth/passwordless/verify", options);
|
|
1669
|
+
this.session.setSession(res);
|
|
1670
|
+
this.emit("signedIn", res.user);
|
|
1671
|
+
return res.user;
|
|
1672
|
+
}
|
|
1673
|
+
// ── Passkeys ──
|
|
1674
|
+
async registerPasskey(name) {
|
|
1675
|
+
const token = this.session.getToken();
|
|
1676
|
+
if (!token) throw new Error("Must be signed in to register a passkey");
|
|
1677
|
+
const options = await this.apiPostAuth(
|
|
1678
|
+
"/v1/auth/passkeys/register/options",
|
|
1679
|
+
name ? { name } : void 0,
|
|
1680
|
+
token
|
|
1681
|
+
);
|
|
1682
|
+
const credential = await navigator.credentials.create({
|
|
1683
|
+
publicKey: this.deserializeCreationOptions(options.options)
|
|
1684
|
+
});
|
|
1685
|
+
const attestation = credential.response;
|
|
1686
|
+
const result = await this.apiPostAuth(
|
|
1687
|
+
"/v1/auth/passkeys/register/verify",
|
|
1688
|
+
{
|
|
1689
|
+
id: credential.id,
|
|
1690
|
+
rawId: this.bufferToBase64url(credential.rawId),
|
|
1691
|
+
type: credential.type,
|
|
1692
|
+
response: {
|
|
1693
|
+
attestationObject: this.bufferToBase64url(attestation.attestationObject),
|
|
1694
|
+
clientDataJSON: this.bufferToBase64url(attestation.clientDataJSON)
|
|
1695
|
+
}
|
|
1696
|
+
},
|
|
1697
|
+
token
|
|
1698
|
+
);
|
|
1699
|
+
this.emit("passkeyRegistered", result);
|
|
1700
|
+
return result;
|
|
1701
|
+
}
|
|
1702
|
+
async authenticateWithPasskey(email) {
|
|
1703
|
+
const options = await this.apiPost(
|
|
1704
|
+
"/v1/auth/passkeys/authenticate/options",
|
|
1705
|
+
email ? { email } : void 0
|
|
1706
|
+
);
|
|
1707
|
+
const credential = await navigator.credentials.get({
|
|
1708
|
+
publicKey: this.deserializeRequestOptions(options.options)
|
|
1709
|
+
});
|
|
1710
|
+
const assertion = credential.response;
|
|
1711
|
+
const res = await this.apiPost("/v1/auth/passkeys/authenticate/verify", {
|
|
1712
|
+
id: credential.id,
|
|
1713
|
+
rawId: this.bufferToBase64url(credential.rawId),
|
|
1714
|
+
type: credential.type,
|
|
1715
|
+
response: {
|
|
1716
|
+
authenticatorData: this.bufferToBase64url(assertion.authenticatorData),
|
|
1717
|
+
clientDataJSON: this.bufferToBase64url(assertion.clientDataJSON),
|
|
1718
|
+
signature: this.bufferToBase64url(assertion.signature),
|
|
1719
|
+
userHandle: assertion.userHandle ? this.bufferToBase64url(assertion.userHandle) : void 0
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
this.session.setSession(res);
|
|
1723
|
+
this.emit("signedIn", res.user);
|
|
1724
|
+
return res.user;
|
|
1725
|
+
}
|
|
1726
|
+
async listPasskeys() {
|
|
1727
|
+
const token = this.session.getToken();
|
|
1728
|
+
if (!token) throw new Error("Must be signed in to list passkeys");
|
|
1729
|
+
return this.apiGetAuth("/v1/auth/passkeys", token);
|
|
1730
|
+
}
|
|
1731
|
+
async renamePasskey(passkeyId, name) {
|
|
1732
|
+
const token = this.session.getToken();
|
|
1733
|
+
if (!token) throw new Error("Must be signed in to rename a passkey");
|
|
1734
|
+
return this.apiPatchAuth(`/v1/auth/passkeys/${passkeyId}`, { name }, token);
|
|
1735
|
+
}
|
|
1736
|
+
async revokePasskey(passkeyId) {
|
|
1737
|
+
const token = this.session.getToken();
|
|
1738
|
+
if (!token) throw new Error("Must be signed in to revoke a passkey");
|
|
1739
|
+
await this.apiDeleteAuth(`/v1/auth/passkeys/${passkeyId}`, token);
|
|
1740
|
+
}
|
|
1741
|
+
// ── Web3 ──
|
|
1742
|
+
async web3GetNonce(address, chain, walletType, chainId) {
|
|
1743
|
+
return this.apiPost("/v1/auth/web3/nonce", {
|
|
1744
|
+
address,
|
|
1745
|
+
chain,
|
|
1746
|
+
walletType,
|
|
1747
|
+
...chainId != null ? { chainId } : {}
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
async web3Verify(message, signature, address, chain, walletType) {
|
|
1751
|
+
const res = await this.apiPost("/v1/auth/web3/verify", {
|
|
1752
|
+
message,
|
|
1753
|
+
signature,
|
|
1754
|
+
address,
|
|
1755
|
+
chain,
|
|
1756
|
+
walletType
|
|
1757
|
+
});
|
|
1758
|
+
this.session.setSession(res);
|
|
1759
|
+
this.emit("signedIn", res.user);
|
|
1760
|
+
return res.user;
|
|
1761
|
+
}
|
|
1762
|
+
async listWallets() {
|
|
1763
|
+
const token = this.session.getToken();
|
|
1764
|
+
if (!token) throw new Error("Must be signed in to list wallets");
|
|
1765
|
+
return this.apiGetAuth("/v1/auth/web3/wallets", token);
|
|
1766
|
+
}
|
|
1767
|
+
async linkWallet(params) {
|
|
1768
|
+
const token = this.session.getToken();
|
|
1769
|
+
if (!token) throw new Error("Must be signed in to link a wallet");
|
|
1770
|
+
const wallet = await this.apiPostAuth("/v1/auth/web3/wallets/link", params, token);
|
|
1771
|
+
this.emit("web3Connected", wallet);
|
|
1772
|
+
return wallet;
|
|
1773
|
+
}
|
|
1774
|
+
async unlinkWallet(walletId) {
|
|
1775
|
+
const token = this.session.getToken();
|
|
1776
|
+
if (!token) throw new Error("Must be signed in to unlink a wallet");
|
|
1777
|
+
await this.apiDeleteAuth(`/v1/auth/web3/wallets/${walletId}`, token);
|
|
1778
|
+
}
|
|
1779
|
+
// ── User Profile ──
|
|
1780
|
+
async updateProfile(data) {
|
|
1781
|
+
const token = this.session.getToken();
|
|
1782
|
+
if (!token) throw new Error("Must be signed in to update profile");
|
|
1783
|
+
const user = await this.apiPatchAuth("/v1/auth/me", data, token);
|
|
1784
|
+
this.session.updateUser(user);
|
|
1785
|
+
return user;
|
|
1786
|
+
}
|
|
1787
|
+
// ── Session Management ──
|
|
1788
|
+
async listSessions() {
|
|
1789
|
+
const token = this.session.getToken();
|
|
1790
|
+
if (!token) throw new Error("Must be signed in to list sessions");
|
|
1791
|
+
return this.apiGetAuth("/v1/auth/me/sessions", token);
|
|
1792
|
+
}
|
|
1793
|
+
async revokeSession(sessionId) {
|
|
1794
|
+
const token = this.session.getToken();
|
|
1795
|
+
if (!token) throw new Error("Must be signed in to revoke a session");
|
|
1796
|
+
await this.apiDeleteAuth(`/v1/auth/me/sessions/${sessionId}`, token);
|
|
1797
|
+
}
|
|
1798
|
+
// ── Organizations ──
|
|
1799
|
+
organizations = {
|
|
1800
|
+
list: async () => {
|
|
1801
|
+
const token = this.session.getToken();
|
|
1802
|
+
if (!token) throw new Error("Must be signed in to list organizations");
|
|
1803
|
+
return this.apiGetAuth("/v1/auth/organizations", token);
|
|
1804
|
+
},
|
|
1805
|
+
create: async (params) => {
|
|
1806
|
+
const token = this.session.getToken();
|
|
1807
|
+
if (!token) throw new Error("Must be signed in to create an organization");
|
|
1808
|
+
return this.apiPostAuth("/v1/auth/organizations", params, token);
|
|
1809
|
+
},
|
|
1810
|
+
get: async (orgId) => {
|
|
1811
|
+
const token = this.session.getToken();
|
|
1812
|
+
if (!token) throw new Error("Must be signed in to get organization");
|
|
1813
|
+
return this.apiGetAuth(`/v1/auth/organizations/${orgId}`, token);
|
|
1814
|
+
},
|
|
1815
|
+
update: async (orgId, params) => {
|
|
1816
|
+
const token = this.session.getToken();
|
|
1817
|
+
if (!token) throw new Error("Must be signed in to update organization");
|
|
1818
|
+
return this.apiPatchAuth(`/v1/auth/organizations/${orgId}`, params, token);
|
|
1819
|
+
},
|
|
1820
|
+
delete: async (orgId) => {
|
|
1821
|
+
const token = this.session.getToken();
|
|
1822
|
+
if (!token) throw new Error("Must be signed in to delete organization");
|
|
1823
|
+
await this.apiDeleteAuth(`/v1/auth/organizations/${orgId}`, token);
|
|
1824
|
+
},
|
|
1825
|
+
getMembers: async (orgId) => {
|
|
1826
|
+
const token = this.session.getToken();
|
|
1827
|
+
if (!token) throw new Error("Must be signed in to get organization members");
|
|
1828
|
+
return this.apiGetAuth(`/v1/auth/organizations/${orgId}/members`, token);
|
|
1829
|
+
},
|
|
1830
|
+
invite: async (orgId, params) => {
|
|
1831
|
+
const token = this.session.getToken();
|
|
1832
|
+
if (!token) throw new Error("Must be signed in to invite a member");
|
|
1833
|
+
return this.apiPostAuth(`/v1/auth/organizations/${orgId}/invitations`, params, token);
|
|
1834
|
+
},
|
|
1835
|
+
getInvitations: async (orgId) => {
|
|
1836
|
+
const token = this.session.getToken();
|
|
1837
|
+
if (!token) throw new Error("Must be signed in to get invitations");
|
|
1838
|
+
return this.apiGetAuth(`/v1/auth/organizations/${orgId}/invitations`, token);
|
|
1839
|
+
},
|
|
1840
|
+
acceptInvitation: async (token) => {
|
|
1841
|
+
const authToken = this.session.getToken();
|
|
1842
|
+
if (!authToken) throw new Error("Must be signed in to accept an invitation");
|
|
1843
|
+
return this.apiPostAuth(`/v1/auth/organizations/invitations/${token}/accept`, void 0, authToken);
|
|
1844
|
+
},
|
|
1845
|
+
rejectInvitation: async (token) => {
|
|
1846
|
+
const authToken = this.session.getToken();
|
|
1847
|
+
if (!authToken) throw new Error("Must be signed in to reject an invitation");
|
|
1848
|
+
await this.apiPostAuth(`/v1/auth/organizations/invitations/${token}/reject`, void 0, authToken);
|
|
1849
|
+
},
|
|
1850
|
+
removeMember: async (orgId, memberId) => {
|
|
1851
|
+
const token = this.session.getToken();
|
|
1852
|
+
if (!token) throw new Error("Must be signed in to remove a member");
|
|
1853
|
+
await this.apiDeleteAuth(`/v1/auth/organizations/${orgId}/members/${memberId}`, token);
|
|
1854
|
+
},
|
|
1855
|
+
updateMemberRole: async (orgId, memberId, role) => {
|
|
1856
|
+
const token = this.session.getToken();
|
|
1857
|
+
if (!token) throw new Error("Must be signed in to update member role");
|
|
1858
|
+
return this.apiPatchAuth(`/v1/auth/organizations/${orgId}/members/${memberId}`, { role }, token);
|
|
1859
|
+
},
|
|
1860
|
+
leave: async (orgId) => {
|
|
1861
|
+
const token = this.session.getToken();
|
|
1862
|
+
if (!token) throw new Error("Must be signed in to leave organization");
|
|
1863
|
+
await this.apiPostAuth(`/v1/auth/organizations/${orgId}/leave`, void 0, token);
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
607
1866
|
destroy() {
|
|
608
1867
|
this.modal?.close();
|
|
609
1868
|
this.session.destroy();
|
|
@@ -651,7 +1910,53 @@ var Authon = class {
|
|
|
651
1910
|
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
652
1911
|
});
|
|
653
1912
|
},
|
|
654
|
-
onClose: () => this.modal?.close()
|
|
1913
|
+
onClose: () => this.modal?.close(),
|
|
1914
|
+
onWeb3WalletSelect: async (walletId) => {
|
|
1915
|
+
const chain = walletId === "phantom" ? "solana" : "evm";
|
|
1916
|
+
try {
|
|
1917
|
+
this.modal?.showOverlay?.("web3-connecting");
|
|
1918
|
+
const address = await this.getWalletAddress(walletId);
|
|
1919
|
+
const { message } = await this.web3GetNonce(address, chain, walletId);
|
|
1920
|
+
const signature = await this.requestWalletSignature(walletId, message);
|
|
1921
|
+
await this.web3Verify(message, signature, address, chain, walletId);
|
|
1922
|
+
this.modal?.showWeb3Success(walletId, address);
|
|
1923
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
1924
|
+
} catch (err) {
|
|
1925
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1926
|
+
}
|
|
1927
|
+
},
|
|
1928
|
+
onPasswordlessSubmit: async (email) => {
|
|
1929
|
+
try {
|
|
1930
|
+
const method = this.branding?.passwordlessMethod ?? "magic_link";
|
|
1931
|
+
if (method === "email_otp" || method === "both") {
|
|
1932
|
+
await this.sendEmailOtp(email);
|
|
1933
|
+
this.modal?.showOtpInput(email);
|
|
1934
|
+
} else {
|
|
1935
|
+
await this.sendMagicLink(email);
|
|
1936
|
+
this.modal?.showPasswordlessSent();
|
|
1937
|
+
}
|
|
1938
|
+
} catch (err) {
|
|
1939
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1940
|
+
}
|
|
1941
|
+
},
|
|
1942
|
+
onOtpVerify: async (email, code) => {
|
|
1943
|
+
try {
|
|
1944
|
+
await this.verifyPasswordless({ email, code });
|
|
1945
|
+
this.modal?.close();
|
|
1946
|
+
} catch (err) {
|
|
1947
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1948
|
+
}
|
|
1949
|
+
},
|
|
1950
|
+
onPasskeyClick: async () => {
|
|
1951
|
+
try {
|
|
1952
|
+
this.modal?.showOverlay?.("passkey-verifying");
|
|
1953
|
+
await this.authenticateWithPasskey();
|
|
1954
|
+
this.modal?.showPasskeySuccess();
|
|
1955
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
1956
|
+
} catch (err) {
|
|
1957
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
655
1960
|
});
|
|
656
1961
|
}
|
|
657
1962
|
if (this.branding) this.modal.setBranding(this.branding);
|
|
@@ -894,6 +2199,130 @@ var Authon = class {
|
|
|
894
2199
|
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
895
2200
|
return res.json();
|
|
896
2201
|
}
|
|
2202
|
+
async apiPostAuth(path, body, token) {
|
|
2203
|
+
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
2204
|
+
method: "POST",
|
|
2205
|
+
headers: {
|
|
2206
|
+
"Content-Type": "application/json",
|
|
2207
|
+
"x-api-key": this.publishableKey,
|
|
2208
|
+
Authorization: `Bearer ${token}`
|
|
2209
|
+
},
|
|
2210
|
+
credentials: "include",
|
|
2211
|
+
body: body ? JSON.stringify(body) : void 0
|
|
2212
|
+
});
|
|
2213
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
2214
|
+
return res.json();
|
|
2215
|
+
}
|
|
2216
|
+
async apiGetAuth(path, token) {
|
|
2217
|
+
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
2218
|
+
headers: {
|
|
2219
|
+
"x-api-key": this.publishableKey,
|
|
2220
|
+
Authorization: `Bearer ${token}`
|
|
2221
|
+
},
|
|
2222
|
+
credentials: "include"
|
|
2223
|
+
});
|
|
2224
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
2225
|
+
return res.json();
|
|
2226
|
+
}
|
|
2227
|
+
async apiPatchAuth(path, body, token) {
|
|
2228
|
+
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
2229
|
+
method: "PATCH",
|
|
2230
|
+
headers: {
|
|
2231
|
+
"Content-Type": "application/json",
|
|
2232
|
+
"x-api-key": this.publishableKey,
|
|
2233
|
+
Authorization: `Bearer ${token}`
|
|
2234
|
+
},
|
|
2235
|
+
credentials: "include",
|
|
2236
|
+
body: body ? JSON.stringify(body) : void 0
|
|
2237
|
+
});
|
|
2238
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
2239
|
+
return res.json();
|
|
2240
|
+
}
|
|
2241
|
+
async apiDeleteAuth(path, token) {
|
|
2242
|
+
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
2243
|
+
method: "DELETE",
|
|
2244
|
+
headers: {
|
|
2245
|
+
"x-api-key": this.publishableKey,
|
|
2246
|
+
Authorization: `Bearer ${token}`
|
|
2247
|
+
},
|
|
2248
|
+
credentials: "include"
|
|
2249
|
+
});
|
|
2250
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
2251
|
+
}
|
|
2252
|
+
// ── Wallet helpers ──
|
|
2253
|
+
async getWalletAddress(walletId) {
|
|
2254
|
+
if (walletId === "phantom") {
|
|
2255
|
+
const provider2 = window.solana;
|
|
2256
|
+
if (!provider2?.isPhantom) throw new Error("Phantom wallet not detected. Please install it from phantom.app");
|
|
2257
|
+
const resp = await provider2.connect();
|
|
2258
|
+
return resp.publicKey.toString();
|
|
2259
|
+
}
|
|
2260
|
+
const provider = window.ethereum;
|
|
2261
|
+
if (!provider) throw new Error(`${walletId} wallet not detected. Please install it.`);
|
|
2262
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2263
|
+
return accounts[0];
|
|
2264
|
+
}
|
|
2265
|
+
async requestWalletSignature(walletId, message) {
|
|
2266
|
+
if (walletId === "phantom") {
|
|
2267
|
+
const provider2 = window.solana;
|
|
2268
|
+
const encoded = new TextEncoder().encode(message);
|
|
2269
|
+
const signed = await provider2.signMessage(encoded, "utf8");
|
|
2270
|
+
return Array.from(new Uint8Array(signed.signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2271
|
+
}
|
|
2272
|
+
const provider = window.ethereum;
|
|
2273
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2274
|
+
return provider.request({ method: "personal_sign", params: [message, accounts[0]] });
|
|
2275
|
+
}
|
|
2276
|
+
// ── WebAuthn helpers ──
|
|
2277
|
+
bufferToBase64url(buffer) {
|
|
2278
|
+
const bytes = new Uint8Array(buffer);
|
|
2279
|
+
let binary = "";
|
|
2280
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2281
|
+
binary += String.fromCharCode(bytes[i]);
|
|
2282
|
+
}
|
|
2283
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
2284
|
+
}
|
|
2285
|
+
base64urlToBuffer(base64url) {
|
|
2286
|
+
const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
2287
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
2288
|
+
const binary = atob(padded);
|
|
2289
|
+
const bytes = new Uint8Array(binary.length);
|
|
2290
|
+
for (let i = 0; i < binary.length; i++) {
|
|
2291
|
+
bytes[i] = binary.charCodeAt(i);
|
|
2292
|
+
}
|
|
2293
|
+
return bytes.buffer;
|
|
2294
|
+
}
|
|
2295
|
+
deserializeCreationOptions(options) {
|
|
2296
|
+
const opts = { ...options };
|
|
2297
|
+
if (typeof opts.challenge === "string") {
|
|
2298
|
+
opts.challenge = this.base64urlToBuffer(opts.challenge);
|
|
2299
|
+
}
|
|
2300
|
+
if (opts.user && typeof opts.user.id === "string") {
|
|
2301
|
+
opts.user.id = this.base64urlToBuffer(
|
|
2302
|
+
opts.user.id
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2305
|
+
if (Array.isArray(opts.excludeCredentials)) {
|
|
2306
|
+
opts.excludeCredentials = opts.excludeCredentials.map((c) => ({
|
|
2307
|
+
...c,
|
|
2308
|
+
id: typeof c.id === "string" ? this.base64urlToBuffer(c.id) : c.id
|
|
2309
|
+
}));
|
|
2310
|
+
}
|
|
2311
|
+
return opts;
|
|
2312
|
+
}
|
|
2313
|
+
deserializeRequestOptions(options) {
|
|
2314
|
+
const opts = { ...options };
|
|
2315
|
+
if (typeof opts.challenge === "string") {
|
|
2316
|
+
opts.challenge = this.base64urlToBuffer(opts.challenge);
|
|
2317
|
+
}
|
|
2318
|
+
if (Array.isArray(opts.allowCredentials)) {
|
|
2319
|
+
opts.allowCredentials = opts.allowCredentials.map((c) => ({
|
|
2320
|
+
...c,
|
|
2321
|
+
id: typeof c.id === "string" ? this.base64urlToBuffer(c.id) : c.id
|
|
2322
|
+
}));
|
|
2323
|
+
}
|
|
2324
|
+
return opts;
|
|
2325
|
+
}
|
|
897
2326
|
async parseApiError(res, path) {
|
|
898
2327
|
try {
|
|
899
2328
|
const body = await res.json();
|
|
@@ -910,6 +2339,8 @@ var Authon = class {
|
|
|
910
2339
|
};
|
|
911
2340
|
export {
|
|
912
2341
|
Authon,
|
|
2342
|
+
AuthonMfaRequiredError,
|
|
2343
|
+
generateQrSvg,
|
|
913
2344
|
getProviderButtonConfig
|
|
914
2345
|
};
|
|
915
2346
|
//# sourceMappingURL=index.js.map
|