@authon/js 0.3.0 → 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 +58 -149
- package/README.md +201 -368
- package/dist/index.cjs +673 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +669 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,11 +8,46 @@ var AuthonMfaRequiredError = class extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
|
|
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
|
+
};
|
|
13
49
|
|
|
14
50
|
// src/providers.ts
|
|
15
|
-
import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES } from "@authon/shared";
|
|
16
51
|
var PROVIDER_ICONS = {
|
|
17
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>`,
|
|
18
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>`,
|
|
@@ -37,6 +72,30 @@ function getProviderButtonConfig(provider) {
|
|
|
37
72
|
}
|
|
38
73
|
|
|
39
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
|
+
}
|
|
40
99
|
var ModalRenderer = class {
|
|
41
100
|
shadowRoot = null;
|
|
42
101
|
hostElement = null;
|
|
@@ -44,12 +103,23 @@ var ModalRenderer = class {
|
|
|
44
103
|
mode;
|
|
45
104
|
theme;
|
|
46
105
|
branding;
|
|
106
|
+
themeObserver = null;
|
|
107
|
+
mediaQueryListener = null;
|
|
47
108
|
enabledProviders = [];
|
|
48
109
|
currentView = "signIn";
|
|
49
110
|
onProviderClick;
|
|
50
111
|
onEmailSubmit;
|
|
51
112
|
onClose;
|
|
113
|
+
onWeb3WalletSelect;
|
|
114
|
+
onPasswordlessSubmit;
|
|
115
|
+
onOtpVerify;
|
|
116
|
+
onPasskeyClick;
|
|
52
117
|
escHandler = null;
|
|
118
|
+
// Overlay state
|
|
119
|
+
currentOverlay = "none";
|
|
120
|
+
selectedWallet = "";
|
|
121
|
+
overlayEmail = "";
|
|
122
|
+
overlayError = "";
|
|
53
123
|
constructor(options) {
|
|
54
124
|
this.mode = options.mode;
|
|
55
125
|
this.theme = options.theme || "auto";
|
|
@@ -57,6 +127,14 @@ var ModalRenderer = class {
|
|
|
57
127
|
this.onProviderClick = options.onProviderClick;
|
|
58
128
|
this.onEmailSubmit = options.onEmailSubmit;
|
|
59
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
|
+
});
|
|
60
138
|
if (options.mode === "embedded" && options.containerId) {
|
|
61
139
|
this.containerElement = document.getElementById(options.containerId);
|
|
62
140
|
}
|
|
@@ -69,13 +147,16 @@ var ModalRenderer = class {
|
|
|
69
147
|
}
|
|
70
148
|
open(view = "signIn") {
|
|
71
149
|
if (this.shadowRoot && this.hostElement) {
|
|
150
|
+
this.hideOverlay();
|
|
72
151
|
this.switchView(view);
|
|
73
152
|
} else {
|
|
74
153
|
this.currentView = view;
|
|
154
|
+
this.currentOverlay = "none";
|
|
75
155
|
this.render(view);
|
|
76
156
|
}
|
|
77
157
|
}
|
|
78
158
|
close() {
|
|
159
|
+
this.stopThemeObserver();
|
|
79
160
|
if (this.escHandler) {
|
|
80
161
|
document.removeEventListener("keydown", this.escHandler);
|
|
81
162
|
this.escHandler = null;
|
|
@@ -88,6 +169,46 @@ var ModalRenderer = class {
|
|
|
88
169
|
if (this.containerElement) {
|
|
89
170
|
this.containerElement.innerHTML = "";
|
|
90
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
|
+
}
|
|
91
212
|
}
|
|
92
213
|
showError(message) {
|
|
93
214
|
if (!this.shadowRoot) return;
|
|
@@ -139,6 +260,47 @@ var ModalRenderer = class {
|
|
|
139
260
|
if (!this.shadowRoot) return;
|
|
140
261
|
this.shadowRoot.getElementById("authon-loading-overlay")?.remove();
|
|
141
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
|
+
}
|
|
142
304
|
// ── Smooth view switch (no flicker) ──
|
|
143
305
|
switchView(view) {
|
|
144
306
|
if (!this.shadowRoot || view === this.currentView) return;
|
|
@@ -169,13 +331,16 @@ var ModalRenderer = class {
|
|
|
169
331
|
this.shadowRoot.innerHTML = this.buildShell(view);
|
|
170
332
|
this.attachInnerEvents(view);
|
|
171
333
|
this.attachShellEvents();
|
|
334
|
+
if (this.theme === "auto") {
|
|
335
|
+
this.startThemeObserver();
|
|
336
|
+
}
|
|
172
337
|
}
|
|
173
338
|
// ── HTML builders ──
|
|
174
339
|
/** Shell = style + backdrop + modal-container (stable across view switches) */
|
|
175
340
|
buildShell(view) {
|
|
176
341
|
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
177
342
|
return `
|
|
178
|
-
<style>${this.buildCSS()}</style>
|
|
343
|
+
<style id="authon-theme-style">${this.buildCSS()}</style>
|
|
179
344
|
${popupWrapper}
|
|
180
345
|
<div class="modal-container" role="dialog" aria-modal="true">
|
|
181
346
|
<div id="modal-inner" class="modal-inner">
|
|
@@ -210,6 +375,35 @@ var ModalRenderer = class {
|
|
|
210
375
|
${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
|
|
211
376
|
<button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
|
|
212
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>` : "";
|
|
213
407
|
const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
|
|
214
408
|
${b.termsUrl ? `<a href="${b.termsUrl}" target="_blank">Terms of Service</a>` : ""}
|
|
215
409
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
@@ -228,6 +422,8 @@ var ModalRenderer = class {
|
|
|
228
422
|
${showProviders ? `<div class="providers">${providerButtons}</div>` : ""}
|
|
229
423
|
${divider}
|
|
230
424
|
${emailForm}
|
|
425
|
+
${methodDivider}
|
|
426
|
+
${authMethods}
|
|
231
427
|
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
232
428
|
${footer}
|
|
233
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>` : ""}
|
|
@@ -236,6 +432,11 @@ var ModalRenderer = class {
|
|
|
236
432
|
isDark() {
|
|
237
433
|
if (this.theme === "dark") return true;
|
|
238
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
|
+
}
|
|
239
440
|
return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
240
441
|
}
|
|
241
442
|
buildCSS() {
|
|
@@ -259,6 +460,10 @@ var ModalRenderer = class {
|
|
|
259
460
|
--authon-border: ${borderColor};
|
|
260
461
|
--authon-divider: ${dividerColor};
|
|
261
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"};
|
|
262
467
|
--authon-radius: ${b.borderRadius ?? 12}px;
|
|
263
468
|
--authon-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
264
469
|
font-family: var(--authon-font);
|
|
@@ -267,7 +472,7 @@ var ModalRenderer = class {
|
|
|
267
472
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
268
473
|
.backdrop {
|
|
269
474
|
position: fixed; inset: 0; z-index: 99998;
|
|
270
|
-
background:
|
|
475
|
+
background: var(--authon-backdrop-bg); backdrop-filter: blur(4px);
|
|
271
476
|
animation: fadeIn 0.2s ease;
|
|
272
477
|
}
|
|
273
478
|
.modal-container {
|
|
@@ -361,10 +566,154 @@ var ModalRenderer = class {
|
|
|
361
566
|
}
|
|
362
567
|
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
363
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
|
+
|
|
364
713
|
/* Loading overlay */
|
|
365
714
|
#authon-loading-overlay {
|
|
366
715
|
position: absolute; inset: 0; z-index: 10;
|
|
367
|
-
background:
|
|
716
|
+
background: var(--authon-overlay-bg);
|
|
368
717
|
backdrop-filter: blur(2px);
|
|
369
718
|
border-radius: var(--authon-radius);
|
|
370
719
|
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
|
|
@@ -396,11 +745,229 @@ var ModalRenderer = class {
|
|
|
396
745
|
@keyframes blink { 0%,80%,100% { opacity: .2; } 40% { opacity: 1; } }
|
|
397
746
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
398
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; } }
|
|
399
750
|
${b.customCss || ""}
|
|
400
751
|
`;
|
|
401
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
|
+
}
|
|
402
969
|
// ── Event binding ──
|
|
403
|
-
/** Attach events to shell elements (backdrop, ESC)
|
|
970
|
+
/** Attach events to shell elements (backdrop, ESC) -- called once */
|
|
404
971
|
attachShellEvents() {
|
|
405
972
|
if (!this.shadowRoot) return;
|
|
406
973
|
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
@@ -412,12 +979,18 @@ var ModalRenderer = class {
|
|
|
412
979
|
}
|
|
413
980
|
if (this.mode === "popup") {
|
|
414
981
|
this.escHandler = (e) => {
|
|
415
|
-
if (e.key === "Escape")
|
|
982
|
+
if (e.key === "Escape") {
|
|
983
|
+
if (this.currentOverlay !== "none") {
|
|
984
|
+
this.hideOverlay();
|
|
985
|
+
} else {
|
|
986
|
+
this.onClose();
|
|
987
|
+
}
|
|
988
|
+
}
|
|
416
989
|
};
|
|
417
990
|
document.addEventListener("keydown", this.escHandler);
|
|
418
991
|
}
|
|
419
992
|
}
|
|
420
|
-
/** Attach events to inner content (buttons, form, switch link)
|
|
993
|
+
/** Attach events to inner content (buttons, form, switch link) -- called on each view */
|
|
421
994
|
attachInnerEvents(view) {
|
|
422
995
|
if (!this.shadowRoot) return;
|
|
423
996
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
@@ -451,6 +1024,18 @@ var ModalRenderer = class {
|
|
|
451
1024
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
452
1025
|
});
|
|
453
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
|
+
}
|
|
454
1039
|
}
|
|
455
1040
|
};
|
|
456
1041
|
|
|
@@ -979,6 +1564,10 @@ var Authon = class {
|
|
|
979
1564
|
await this.ensureInitialized();
|
|
980
1565
|
this.getModal().open("signUp");
|
|
981
1566
|
}
|
|
1567
|
+
/** Update theme at runtime without destroying form state */
|
|
1568
|
+
setTheme(theme) {
|
|
1569
|
+
this.getModal().setTheme(theme);
|
|
1570
|
+
}
|
|
982
1571
|
async signInWithOAuth(provider, options) {
|
|
983
1572
|
await this.ensureInitialized();
|
|
984
1573
|
await this.startOAuthFlow(provider, options);
|
|
@@ -1321,7 +1910,53 @@ var Authon = class {
|
|
|
1321
1910
|
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
1322
1911
|
});
|
|
1323
1912
|
},
|
|
1324
|
-
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
|
+
}
|
|
1325
1960
|
});
|
|
1326
1961
|
}
|
|
1327
1962
|
if (this.branding) this.modal.setBranding(this.branding);
|
|
@@ -1614,6 +2249,30 @@ var Authon = class {
|
|
|
1614
2249
|
});
|
|
1615
2250
|
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
1616
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
|
+
}
|
|
1617
2276
|
// ── WebAuthn helpers ──
|
|
1618
2277
|
bufferToBase64url(buffer) {
|
|
1619
2278
|
const bytes = new Uint8Array(buffer);
|