@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.cjs
CHANGED
|
@@ -37,11 +37,46 @@ var AuthonMfaRequiredError = class extends Error {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
//
|
|
41
|
-
var
|
|
40
|
+
// ../shared/dist/index.js
|
|
41
|
+
var PROVIDER_DISPLAY_NAMES = {
|
|
42
|
+
google: "Google",
|
|
43
|
+
apple: "Apple",
|
|
44
|
+
kakao: "Kakao",
|
|
45
|
+
naver: "Naver",
|
|
46
|
+
facebook: "Facebook",
|
|
47
|
+
github: "GitHub",
|
|
48
|
+
discord: "Discord",
|
|
49
|
+
x: "X",
|
|
50
|
+
line: "LINE",
|
|
51
|
+
microsoft: "Microsoft"
|
|
52
|
+
};
|
|
53
|
+
var PROVIDER_COLORS = {
|
|
54
|
+
google: { bg: "#ffffff", text: "#1f1f1f" },
|
|
55
|
+
apple: { bg: "#000000", text: "#ffffff" },
|
|
56
|
+
kakao: { bg: "#FEE500", text: "#191919" },
|
|
57
|
+
naver: { bg: "#03C75A", text: "#ffffff" },
|
|
58
|
+
facebook: { bg: "#1877F2", text: "#ffffff" },
|
|
59
|
+
github: { bg: "#24292e", text: "#ffffff" },
|
|
60
|
+
discord: { bg: "#5865F2", text: "#ffffff" },
|
|
61
|
+
x: { bg: "#000000", text: "#ffffff" },
|
|
62
|
+
line: { bg: "#06C755", text: "#ffffff" },
|
|
63
|
+
microsoft: { bg: "#ffffff", text: "#1f1f1f" }
|
|
64
|
+
};
|
|
65
|
+
var DEFAULT_BRANDING = {
|
|
66
|
+
primaryColorStart: "#7c3aed",
|
|
67
|
+
primaryColorEnd: "#4f46e5",
|
|
68
|
+
lightBg: "#ffffff",
|
|
69
|
+
lightText: "#111827",
|
|
70
|
+
darkBg: "#0f172a",
|
|
71
|
+
darkText: "#f1f5f9",
|
|
72
|
+
borderRadius: 12,
|
|
73
|
+
showEmailPassword: true,
|
|
74
|
+
showDivider: true,
|
|
75
|
+
showSecuredBy: true,
|
|
76
|
+
locale: "en"
|
|
77
|
+
};
|
|
42
78
|
|
|
43
79
|
// src/providers.ts
|
|
44
|
-
var import_shared = require("@authon/shared");
|
|
45
80
|
var PROVIDER_ICONS = {
|
|
46
81
|
google: `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>`,
|
|
47
82
|
apple: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/></svg>`,
|
|
@@ -55,10 +90,10 @@ var PROVIDER_ICONS = {
|
|
|
55
90
|
microsoft: `<svg viewBox="0 0 24 24" width="20" height="20"><rect fill="#F25022" x="1" y="1" width="10" height="10"/><rect fill="#7FBA00" x="13" y="1" width="10" height="10"/><rect fill="#00A4EF" x="1" y="13" width="10" height="10"/><rect fill="#FFB900" x="13" y="13" width="10" height="10"/></svg>`
|
|
56
91
|
};
|
|
57
92
|
function getProviderButtonConfig(provider) {
|
|
58
|
-
const colors =
|
|
93
|
+
const colors = PROVIDER_COLORS[provider];
|
|
59
94
|
return {
|
|
60
95
|
provider,
|
|
61
|
-
label: `Continue with ${
|
|
96
|
+
label: `Continue with ${PROVIDER_DISPLAY_NAMES[provider]}`,
|
|
62
97
|
bgColor: colors.bg,
|
|
63
98
|
textColor: colors.text,
|
|
64
99
|
iconSvg: PROVIDER_ICONS[provider]
|
|
@@ -66,6 +101,30 @@ function getProviderButtonConfig(provider) {
|
|
|
66
101
|
}
|
|
67
102
|
|
|
68
103
|
// src/modal.ts
|
|
104
|
+
function hexToRgba(hex, alpha) {
|
|
105
|
+
const h = hex.replace("#", "");
|
|
106
|
+
const r = parseInt(h.substring(0, 2), 16);
|
|
107
|
+
const g = parseInt(h.substring(2, 4), 16);
|
|
108
|
+
const b = parseInt(h.substring(4, 6), 16);
|
|
109
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
110
|
+
}
|
|
111
|
+
var WALLET_OPTIONS = [
|
|
112
|
+
{ id: "pexus", name: "Pexus", color: "#7c3aed" },
|
|
113
|
+
{ id: "metamask", name: "MetaMask", color: "#f6851b" },
|
|
114
|
+
{ id: "phantom", name: "Phantom", color: "#ab9ff2" }
|
|
115
|
+
];
|
|
116
|
+
function walletIconSvg(id) {
|
|
117
|
+
switch (id) {
|
|
118
|
+
case "pexus":
|
|
119
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#7c3aed"/><path d="M7 8h4v8H7V8zm6 0h4v8h-4V8z" fill="#fff"/></svg>`;
|
|
120
|
+
case "metamask":
|
|
121
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#f6851b"/><path d="M17.2 4L12 8.5l1 .7L17.2 4zM6.8 4l5.1 5.3-1-0.6L6.8 4zM16 16.2l-1.4 2.1 3 .8.8-2.9h-2.4zM5.6 16.2l.9 2.8 3-.8-1.4-2h-2.5z" fill="#fff"/></svg>`;
|
|
122
|
+
case "phantom":
|
|
123
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#ab9ff2"/><circle cx="9" cy="11" r="1.5" fill="#fff"/><circle cx="15" cy="11" r="1.5" fill="#fff"/><path d="M6 12c0-3.3 2.7-6 6-6s6 2.7 6 6v2c0 1.1-.9 2-2 2H8c-1.1 0-2-.9-2-2v-2z" stroke="#fff" stroke-width="1.5" fill="none"/></svg>`;
|
|
124
|
+
default:
|
|
125
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#666"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="12">${id[0]?.toUpperCase() ?? "W"}</text></svg>`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
69
128
|
var ModalRenderer = class {
|
|
70
129
|
shadowRoot = null;
|
|
71
130
|
hostElement = null;
|
|
@@ -73,19 +132,38 @@ var ModalRenderer = class {
|
|
|
73
132
|
mode;
|
|
74
133
|
theme;
|
|
75
134
|
branding;
|
|
135
|
+
themeObserver = null;
|
|
136
|
+
mediaQueryListener = null;
|
|
76
137
|
enabledProviders = [];
|
|
77
138
|
currentView = "signIn";
|
|
78
139
|
onProviderClick;
|
|
79
140
|
onEmailSubmit;
|
|
80
141
|
onClose;
|
|
142
|
+
onWeb3WalletSelect;
|
|
143
|
+
onPasswordlessSubmit;
|
|
144
|
+
onOtpVerify;
|
|
145
|
+
onPasskeyClick;
|
|
81
146
|
escHandler = null;
|
|
147
|
+
// Overlay state
|
|
148
|
+
currentOverlay = "none";
|
|
149
|
+
selectedWallet = "";
|
|
150
|
+
overlayEmail = "";
|
|
151
|
+
overlayError = "";
|
|
82
152
|
constructor(options) {
|
|
83
153
|
this.mode = options.mode;
|
|
84
154
|
this.theme = options.theme || "auto";
|
|
85
|
-
this.branding = { ...
|
|
155
|
+
this.branding = { ...DEFAULT_BRANDING, ...options.branding };
|
|
86
156
|
this.onProviderClick = options.onProviderClick;
|
|
87
157
|
this.onEmailSubmit = options.onEmailSubmit;
|
|
88
158
|
this.onClose = options.onClose;
|
|
159
|
+
this.onWeb3WalletSelect = options.onWeb3WalletSelect || (() => {
|
|
160
|
+
});
|
|
161
|
+
this.onPasswordlessSubmit = options.onPasswordlessSubmit || (() => {
|
|
162
|
+
});
|
|
163
|
+
this.onOtpVerify = options.onOtpVerify || (() => {
|
|
164
|
+
});
|
|
165
|
+
this.onPasskeyClick = options.onPasskeyClick || (() => {
|
|
166
|
+
});
|
|
89
167
|
if (options.mode === "embedded" && options.containerId) {
|
|
90
168
|
this.containerElement = document.getElementById(options.containerId);
|
|
91
169
|
}
|
|
@@ -94,17 +172,20 @@ var ModalRenderer = class {
|
|
|
94
172
|
this.enabledProviders = providers;
|
|
95
173
|
}
|
|
96
174
|
setBranding(branding) {
|
|
97
|
-
this.branding = { ...
|
|
175
|
+
this.branding = { ...DEFAULT_BRANDING, ...branding };
|
|
98
176
|
}
|
|
99
177
|
open(view = "signIn") {
|
|
100
178
|
if (this.shadowRoot && this.hostElement) {
|
|
179
|
+
this.hideOverlay();
|
|
101
180
|
this.switchView(view);
|
|
102
181
|
} else {
|
|
103
182
|
this.currentView = view;
|
|
183
|
+
this.currentOverlay = "none";
|
|
104
184
|
this.render(view);
|
|
105
185
|
}
|
|
106
186
|
}
|
|
107
187
|
close() {
|
|
188
|
+
this.stopThemeObserver();
|
|
108
189
|
if (this.escHandler) {
|
|
109
190
|
document.removeEventListener("keydown", this.escHandler);
|
|
110
191
|
this.escHandler = null;
|
|
@@ -117,6 +198,46 @@ var ModalRenderer = class {
|
|
|
117
198
|
if (this.containerElement) {
|
|
118
199
|
this.containerElement.innerHTML = "";
|
|
119
200
|
}
|
|
201
|
+
this.currentOverlay = "none";
|
|
202
|
+
}
|
|
203
|
+
/** Update theme at runtime without destroying form state */
|
|
204
|
+
setTheme(theme) {
|
|
205
|
+
this.theme = theme;
|
|
206
|
+
this.updateThemeCSS();
|
|
207
|
+
if (theme === "auto") {
|
|
208
|
+
this.startThemeObserver();
|
|
209
|
+
} else {
|
|
210
|
+
this.stopThemeObserver();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
updateThemeCSS() {
|
|
214
|
+
if (!this.shadowRoot) return;
|
|
215
|
+
const styleEl = this.shadowRoot.getElementById("authon-theme-style");
|
|
216
|
+
if (styleEl) {
|
|
217
|
+
styleEl.textContent = this.buildCSS();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
startThemeObserver() {
|
|
221
|
+
this.stopThemeObserver();
|
|
222
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
223
|
+
this.themeObserver = new MutationObserver(() => this.updateThemeCSS());
|
|
224
|
+
this.themeObserver.observe(document.documentElement, {
|
|
225
|
+
attributes: true,
|
|
226
|
+
attributeFilter: ["data-theme", "class"]
|
|
227
|
+
});
|
|
228
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
229
|
+
this.mediaQueryListener = () => this.updateThemeCSS();
|
|
230
|
+
mq.addEventListener("change", this.mediaQueryListener);
|
|
231
|
+
}
|
|
232
|
+
stopThemeObserver() {
|
|
233
|
+
if (this.themeObserver) {
|
|
234
|
+
this.themeObserver.disconnect();
|
|
235
|
+
this.themeObserver = null;
|
|
236
|
+
}
|
|
237
|
+
if (this.mediaQueryListener) {
|
|
238
|
+
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", this.mediaQueryListener);
|
|
239
|
+
this.mediaQueryListener = null;
|
|
240
|
+
}
|
|
120
241
|
}
|
|
121
242
|
showError(message) {
|
|
122
243
|
if (!this.shadowRoot) return;
|
|
@@ -168,6 +289,47 @@ var ModalRenderer = class {
|
|
|
168
289
|
if (!this.shadowRoot) return;
|
|
169
290
|
this.shadowRoot.getElementById("authon-loading-overlay")?.remove();
|
|
170
291
|
}
|
|
292
|
+
// ── Flow Overlay Public API ──
|
|
293
|
+
showOverlay(overlay) {
|
|
294
|
+
this.currentOverlay = overlay;
|
|
295
|
+
this.overlayError = "";
|
|
296
|
+
this.renderOverlay();
|
|
297
|
+
}
|
|
298
|
+
hideOverlay() {
|
|
299
|
+
this.currentOverlay = "none";
|
|
300
|
+
this.overlayError = "";
|
|
301
|
+
if (!this.shadowRoot) return;
|
|
302
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
303
|
+
}
|
|
304
|
+
showWeb3Success(walletId, address) {
|
|
305
|
+
this.selectedWallet = walletId;
|
|
306
|
+
this.overlayError = "";
|
|
307
|
+
const truncated = address.length > 10 ? `${address.slice(0, 6)}...${address.slice(-4)}` : address;
|
|
308
|
+
this.currentOverlay = "web3-success";
|
|
309
|
+
this.renderOverlayWithData({ truncatedAddress: truncated, walletId });
|
|
310
|
+
}
|
|
311
|
+
showPasswordlessSent() {
|
|
312
|
+
this.overlayError = "";
|
|
313
|
+
this.currentOverlay = "passwordless-sent";
|
|
314
|
+
this.renderOverlay();
|
|
315
|
+
}
|
|
316
|
+
showOtpInput(email) {
|
|
317
|
+
this.overlayEmail = email;
|
|
318
|
+
this.overlayError = "";
|
|
319
|
+
this.currentOverlay = "otp-input";
|
|
320
|
+
this.renderOverlay();
|
|
321
|
+
}
|
|
322
|
+
showPasskeySuccess() {
|
|
323
|
+
this.overlayError = "";
|
|
324
|
+
this.currentOverlay = "passkey-success";
|
|
325
|
+
this.renderOverlay();
|
|
326
|
+
}
|
|
327
|
+
showOverlayError(message) {
|
|
328
|
+
this.overlayError = message;
|
|
329
|
+
if (this.currentOverlay !== "none") {
|
|
330
|
+
this.renderOverlay();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
171
333
|
// ── Smooth view switch (no flicker) ──
|
|
172
334
|
switchView(view) {
|
|
173
335
|
if (!this.shadowRoot || view === this.currentView) return;
|
|
@@ -198,13 +360,16 @@ var ModalRenderer = class {
|
|
|
198
360
|
this.shadowRoot.innerHTML = this.buildShell(view);
|
|
199
361
|
this.attachInnerEvents(view);
|
|
200
362
|
this.attachShellEvents();
|
|
363
|
+
if (this.theme === "auto") {
|
|
364
|
+
this.startThemeObserver();
|
|
365
|
+
}
|
|
201
366
|
}
|
|
202
367
|
// ── HTML builders ──
|
|
203
368
|
/** Shell = style + backdrop + modal-container (stable across view switches) */
|
|
204
369
|
buildShell(view) {
|
|
205
370
|
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
206
371
|
return `
|
|
207
|
-
<style>${this.buildCSS()}</style>
|
|
372
|
+
<style id="authon-theme-style">${this.buildCSS()}</style>
|
|
208
373
|
${popupWrapper}
|
|
209
374
|
<div class="modal-container" role="dialog" aria-modal="true">
|
|
210
375
|
<div id="modal-inner" class="modal-inner">
|
|
@@ -239,6 +404,35 @@ var ModalRenderer = class {
|
|
|
239
404
|
${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
|
|
240
405
|
<button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
|
|
241
406
|
</form>` : "";
|
|
407
|
+
const hasMethodAbove = showProviders && this.enabledProviders.length > 0 || b.showEmailPassword !== false;
|
|
408
|
+
const hasMethodBelow = b.showWeb3 || b.showPasswordless || b.showPasskey;
|
|
409
|
+
const methodDivider = hasMethodAbove && hasMethodBelow ? `<div class="divider"><span>or</span></div>` : "";
|
|
410
|
+
const methodButtons = [];
|
|
411
|
+
if (b.showWeb3) {
|
|
412
|
+
methodButtons.push(`<button class="auth-method-btn web3-btn" id="web3-btn">
|
|
413
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
414
|
+
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4h-4z"/>
|
|
415
|
+
</svg>
|
|
416
|
+
<span>Connect Wallet</span>
|
|
417
|
+
</button>`);
|
|
418
|
+
}
|
|
419
|
+
if (b.showPasswordless) {
|
|
420
|
+
methodButtons.push(`<button class="auth-method-btn passwordless-btn" id="passwordless-btn">
|
|
421
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
422
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
|
423
|
+
</svg>
|
|
424
|
+
<span>Continue with Magic Link</span>
|
|
425
|
+
</button>`);
|
|
426
|
+
}
|
|
427
|
+
if (b.showPasskey) {
|
|
428
|
+
methodButtons.push(`<button class="auth-method-btn passkey-btn" id="passkey-btn">
|
|
429
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
430
|
+
<circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/><path d="M21.7 13.3 19 11"/><path d="m21 15-2.5-1.5"/><path d="m17 17 2.5-1.5"/><path d="M22 9v6a1 1 0 0 1-1 1h-.5"/><circle cx="18" cy="9" r="3"/>
|
|
431
|
+
</svg>
|
|
432
|
+
<span>Sign in with Passkey</span>
|
|
433
|
+
</button>`);
|
|
434
|
+
}
|
|
435
|
+
const authMethods = methodButtons.length > 0 ? `<div class="auth-methods">${methodButtons.join("")}</div>` : "";
|
|
242
436
|
const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
|
|
243
437
|
${b.termsUrl ? `<a href="${b.termsUrl}" target="_blank">Terms of Service</a>` : ""}
|
|
244
438
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
@@ -257,6 +451,8 @@ var ModalRenderer = class {
|
|
|
257
451
|
${showProviders ? `<div class="providers">${providerButtons}</div>` : ""}
|
|
258
452
|
${divider}
|
|
259
453
|
${emailForm}
|
|
454
|
+
${methodDivider}
|
|
455
|
+
${authMethods}
|
|
260
456
|
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
261
457
|
${footer}
|
|
262
458
|
${b.showSecuredBy !== false ? `<div class="secured-by">Secured by <a href="https://authon.dev" target="_blank" rel="noopener noreferrer" class="secured-link">Authon</a></div>` : ""}
|
|
@@ -265,6 +461,11 @@ var ModalRenderer = class {
|
|
|
265
461
|
isDark() {
|
|
266
462
|
if (this.theme === "dark") return true;
|
|
267
463
|
if (this.theme === "light") return false;
|
|
464
|
+
if (typeof document !== "undefined") {
|
|
465
|
+
const html = document.documentElement;
|
|
466
|
+
if (html.classList.contains("dark") || html.getAttribute("data-theme") === "dark") return true;
|
|
467
|
+
if (html.classList.contains("light") || html.getAttribute("data-theme") === "light") return false;
|
|
468
|
+
}
|
|
268
469
|
return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
269
470
|
}
|
|
270
471
|
buildCSS() {
|
|
@@ -288,6 +489,10 @@ var ModalRenderer = class {
|
|
|
288
489
|
--authon-border: ${borderColor};
|
|
289
490
|
--authon-divider: ${dividerColor};
|
|
290
491
|
--authon-input-bg: ${inputBg};
|
|
492
|
+
--authon-overlay-bg: ${hexToRgba(bg, 0.92)};
|
|
493
|
+
--authon-overlay-bg-solid: ${hexToRgba(bg, 0.97)};
|
|
494
|
+
--authon-backdrop-bg: rgba(0,0,0,${dark ? "0.7" : "0.5"});
|
|
495
|
+
--authon-shadow-opacity: ${dark ? "0.5" : "0.25"};
|
|
291
496
|
--authon-radius: ${b.borderRadius ?? 12}px;
|
|
292
497
|
--authon-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
293
498
|
font-family: var(--authon-font);
|
|
@@ -296,7 +501,7 @@ var ModalRenderer = class {
|
|
|
296
501
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
297
502
|
.backdrop {
|
|
298
503
|
position: fixed; inset: 0; z-index: 99998;
|
|
299
|
-
background:
|
|
504
|
+
background: var(--authon-backdrop-bg); backdrop-filter: blur(4px);
|
|
300
505
|
animation: fadeIn 0.2s ease;
|
|
301
506
|
}
|
|
302
507
|
.modal-container {
|
|
@@ -390,10 +595,154 @@ var ModalRenderer = class {
|
|
|
390
595
|
}
|
|
391
596
|
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
392
597
|
.secured-link:hover { text-decoration: underline; }
|
|
598
|
+
|
|
599
|
+
/* Auth method buttons */
|
|
600
|
+
.auth-methods { display: flex; flex-direction: column; gap: 8px; }
|
|
601
|
+
.auth-method-btn {
|
|
602
|
+
display: flex; align-items: center; justify-content: center; gap: 8px;
|
|
603
|
+
width: 100%; padding: 10px 16px;
|
|
604
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
605
|
+
font-size: 13px; font-weight: 500; cursor: pointer;
|
|
606
|
+
font-family: var(--authon-font); transition: opacity 0.15s, transform 0.1s;
|
|
607
|
+
}
|
|
608
|
+
.auth-method-btn:hover { opacity: 0.85; }
|
|
609
|
+
.auth-method-btn:active { transform: scale(0.98); }
|
|
610
|
+
/* Web3 -- purple */
|
|
611
|
+
.web3-btn {
|
|
612
|
+
background: ${dark ? "rgba(139,92,246,0.12)" : "rgba(139,92,246,0.08)"};
|
|
613
|
+
border: 1px solid ${dark ? "rgba(139,92,246,0.3)" : "rgba(139,92,246,0.25)"};
|
|
614
|
+
color: ${dark ? "#c4b5fd" : "#7c3aed"};
|
|
615
|
+
}
|
|
616
|
+
/* Passwordless -- cyan */
|
|
617
|
+
.passwordless-btn {
|
|
618
|
+
background: ${dark ? "rgba(6,182,212,0.12)" : "rgba(6,182,212,0.08)"};
|
|
619
|
+
border: 1px solid ${dark ? "rgba(6,182,212,0.3)" : "rgba(6,182,212,0.25)"};
|
|
620
|
+
color: ${dark ? "#67e8f9" : "#0891b2"};
|
|
621
|
+
}
|
|
622
|
+
/* Passkey -- amber */
|
|
623
|
+
.passkey-btn {
|
|
624
|
+
background: ${dark ? "rgba(245,158,11,0.12)" : "rgba(245,158,11,0.08)"};
|
|
625
|
+
border: 1px solid ${dark ? "rgba(245,158,11,0.3)" : "rgba(245,158,11,0.25)"};
|
|
626
|
+
color: ${dark ? "#fcd34d" : "#b45309"};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/* Flow overlay */
|
|
630
|
+
.flow-overlay {
|
|
631
|
+
position: absolute; inset: 0; z-index: 10;
|
|
632
|
+
background: var(--authon-overlay-bg-solid);
|
|
633
|
+
backdrop-filter: blur(2px);
|
|
634
|
+
border-radius: var(--authon-radius);
|
|
635
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
636
|
+
gap: 12px; padding: 24px;
|
|
637
|
+
animation: fadeIn 0.2s ease;
|
|
638
|
+
}
|
|
639
|
+
.flow-overlay .cancel-link {
|
|
640
|
+
font-size: 12px; color: var(--authon-dim); cursor: pointer; border: none;
|
|
641
|
+
background: none; font-family: var(--authon-font); margin-top: 4px;
|
|
642
|
+
}
|
|
643
|
+
.flow-overlay .cancel-link:hover { text-decoration: underline; }
|
|
644
|
+
.flow-overlay .overlay-title {
|
|
645
|
+
font-size: 14px; font-weight: 600; color: var(--authon-text); text-align: center;
|
|
646
|
+
}
|
|
647
|
+
.flow-overlay .overlay-subtitle {
|
|
648
|
+
font-size: 12px; color: var(--authon-muted); text-align: center;
|
|
649
|
+
}
|
|
650
|
+
.flow-overlay .overlay-error {
|
|
651
|
+
padding: 6px 12px; margin-top: 4px;
|
|
652
|
+
background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3);
|
|
653
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
654
|
+
font-size: 12px; color: #ef4444; text-align: center; width: 100%;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/* Wallet picker */
|
|
658
|
+
.wallet-picker { display: flex; flex-direction: column; gap: 8px; width: 100%; }
|
|
659
|
+
.wallet-btn {
|
|
660
|
+
display: flex; align-items: center; gap: 10px;
|
|
661
|
+
width: 100%; padding: 10px 14px;
|
|
662
|
+
background: ${dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)"};
|
|
663
|
+
border: 1px solid ${dark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};
|
|
664
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
665
|
+
font-size: 13px; font-weight: 500; color: var(--authon-text);
|
|
666
|
+
cursor: pointer; font-family: var(--authon-font);
|
|
667
|
+
transition: opacity 0.15s;
|
|
668
|
+
}
|
|
669
|
+
.wallet-btn:hover { opacity: 0.8; }
|
|
670
|
+
.wallet-btn .wallet-icon { display: flex; align-items: center; flex-shrink: 0; }
|
|
671
|
+
.wallet-btn .wallet-icon svg { border-radius: 6px; }
|
|
672
|
+
|
|
673
|
+
/* Passwordless email input in overlay */
|
|
674
|
+
.pwless-form { display: flex; flex-direction: column; gap: 10px; width: 100%; }
|
|
675
|
+
.pwless-submit {
|
|
676
|
+
width: 100%; padding: 10px;
|
|
677
|
+
background: linear-gradient(135deg, #06b6d4, #0891b2);
|
|
678
|
+
color: #fff; border: none; border-radius: calc(var(--authon-radius) * 0.5);
|
|
679
|
+
font-size: 13px; font-weight: 600; cursor: pointer;
|
|
680
|
+
font-family: var(--authon-font); transition: opacity 0.15s;
|
|
681
|
+
}
|
|
682
|
+
.pwless-submit:hover { opacity: 0.9; }
|
|
683
|
+
.pwless-submit:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
684
|
+
|
|
685
|
+
/* OTP input */
|
|
686
|
+
.otp-container { display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; }
|
|
687
|
+
.otp-inputs { display: flex; gap: 8px; justify-content: center; }
|
|
688
|
+
.otp-digit {
|
|
689
|
+
width: 40px; height: 48px; text-align: center;
|
|
690
|
+
font-size: 20px; font-weight: 600; font-family: var(--authon-font);
|
|
691
|
+
background: var(--authon-input-bg); color: var(--authon-text);
|
|
692
|
+
border: 1px solid var(--authon-border);
|
|
693
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
694
|
+
outline: none; transition: border-color 0.15s;
|
|
695
|
+
}
|
|
696
|
+
.otp-digit:focus {
|
|
697
|
+
border-color: var(--authon-primary-start);
|
|
698
|
+
box-shadow: 0 0 0 3px rgba(124,58,237,0.15);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* Success check animation */
|
|
702
|
+
.success-check {
|
|
703
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
704
|
+
display: flex; align-items: center; justify-content: center;
|
|
705
|
+
}
|
|
706
|
+
.success-check svg path {
|
|
707
|
+
stroke-dasharray: 20;
|
|
708
|
+
stroke-dashoffset: 20;
|
|
709
|
+
animation: check-draw 0.4s ease-out 0.1s forwards;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/* Spinner */
|
|
713
|
+
.flow-spinner {
|
|
714
|
+
animation: spin 0.8s linear infinite;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/* Passkey verifying icon */
|
|
718
|
+
.passkey-icon-pulse {
|
|
719
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
720
|
+
display: flex; align-items: center; justify-content: center;
|
|
721
|
+
background: rgba(245,158,11,0.15);
|
|
722
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/* Wallet connecting icon */
|
|
726
|
+
.wallet-connecting-icon {
|
|
727
|
+
width: 48px; height: 48px; border-radius: 12px;
|
|
728
|
+
display: flex; align-items: center; justify-content: center;
|
|
729
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
730
|
+
}
|
|
731
|
+
.wallet-connecting-icon svg { border-radius: 6px; }
|
|
732
|
+
|
|
733
|
+
/* Address badge */
|
|
734
|
+
.address-badge {
|
|
735
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
736
|
+
padding: 2px 10px; border-radius: 6px;
|
|
737
|
+
background: ${dark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.03)"};
|
|
738
|
+
font-size: 11px; font-family: monospace; color: var(--authon-muted);
|
|
739
|
+
}
|
|
740
|
+
.address-badge .wallet-icon-sm svg { width: 16px; height: 16px; border-radius: 4px; }
|
|
741
|
+
|
|
393
742
|
/* Loading overlay */
|
|
394
743
|
#authon-loading-overlay {
|
|
395
744
|
position: absolute; inset: 0; z-index: 10;
|
|
396
|
-
background:
|
|
745
|
+
background: var(--authon-overlay-bg);
|
|
397
746
|
backdrop-filter: blur(2px);
|
|
398
747
|
border-radius: var(--authon-radius);
|
|
399
748
|
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
|
|
@@ -425,11 +774,229 @@ var ModalRenderer = class {
|
|
|
425
774
|
@keyframes blink { 0%,80%,100% { opacity: .2; } 40% { opacity: 1; } }
|
|
426
775
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
427
776
|
@keyframes slideIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } }
|
|
777
|
+
@keyframes check-draw { to { stroke-dashoffset: 0; } }
|
|
778
|
+
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }
|
|
428
779
|
${b.customCss || ""}
|
|
429
780
|
`;
|
|
430
781
|
}
|
|
782
|
+
// ── Flow Overlay Rendering ──
|
|
783
|
+
renderOverlay() {
|
|
784
|
+
this.renderOverlayWithData({});
|
|
785
|
+
}
|
|
786
|
+
renderOverlayWithData(data) {
|
|
787
|
+
if (!this.shadowRoot) return;
|
|
788
|
+
const container = this.shadowRoot.querySelector(".modal-container");
|
|
789
|
+
if (!container) return;
|
|
790
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
791
|
+
if (this.currentOverlay === "none") return;
|
|
792
|
+
const overlay = document.createElement("div");
|
|
793
|
+
overlay.id = "flow-overlay";
|
|
794
|
+
overlay.className = "flow-overlay";
|
|
795
|
+
overlay.innerHTML = this.buildOverlayContent(data);
|
|
796
|
+
container.appendChild(overlay);
|
|
797
|
+
this.attachOverlayEvents(overlay);
|
|
798
|
+
}
|
|
799
|
+
buildOverlayContent(data) {
|
|
800
|
+
const dark = this.isDark();
|
|
801
|
+
const errorHtml = this.overlayError ? `<div class="overlay-error">${this.escapeHtml(this.overlayError)}</div>` : "";
|
|
802
|
+
switch (this.currentOverlay) {
|
|
803
|
+
case "web3-picker": {
|
|
804
|
+
const walletItems = WALLET_OPTIONS.map(
|
|
805
|
+
(w) => `<button class="wallet-btn" data-wallet="${w.id}">
|
|
806
|
+
<span class="wallet-icon">${walletIconSvg(w.id)}</span>
|
|
807
|
+
<span>${w.name}</span>
|
|
808
|
+
</button>`
|
|
809
|
+
).join("");
|
|
810
|
+
return `
|
|
811
|
+
<div class="overlay-title" style="margin-bottom: 4px;">Select Wallet</div>
|
|
812
|
+
<div class="wallet-picker">${walletItems}</div>
|
|
813
|
+
${errorHtml}
|
|
814
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
815
|
+
`;
|
|
816
|
+
}
|
|
817
|
+
case "web3-connecting": {
|
|
818
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === this.selectedWallet);
|
|
819
|
+
const walletName = wallet?.name ?? this.selectedWallet;
|
|
820
|
+
return `
|
|
821
|
+
<div class="wallet-connecting-icon">${walletIconSvg(this.selectedWallet)}</div>
|
|
822
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
823
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
824
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" opacity="0.25"/>
|
|
825
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" stroke-linecap="round"/>
|
|
826
|
+
</svg>
|
|
827
|
+
<span class="overlay-subtitle">Connecting ${this.escapeHtml(walletName)}...</span>
|
|
828
|
+
</div>
|
|
829
|
+
${errorHtml}
|
|
830
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
831
|
+
`;
|
|
832
|
+
}
|
|
833
|
+
case "web3-success": {
|
|
834
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === (data.walletId || this.selectedWallet));
|
|
835
|
+
const walletColor = wallet?.color ?? "#8b5cf6";
|
|
836
|
+
const truncAddr = data.truncatedAddress || "0x...";
|
|
837
|
+
return `
|
|
838
|
+
<div class="success-check" style="background:linear-gradient(135deg, ${walletColor}, ${walletColor})">
|
|
839
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
840
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
841
|
+
</svg>
|
|
842
|
+
</div>
|
|
843
|
+
<div class="overlay-title">Wallet Connected</div>
|
|
844
|
+
<div class="address-badge">
|
|
845
|
+
<span class="wallet-icon-sm">${walletIconSvg(data.walletId || this.selectedWallet)}</span>
|
|
846
|
+
<span>${this.escapeHtml(truncAddr)}</span>
|
|
847
|
+
</div>
|
|
848
|
+
`;
|
|
849
|
+
}
|
|
850
|
+
case "passwordless-input": {
|
|
851
|
+
return `
|
|
852
|
+
<div class="overlay-title">Enter your email</div>
|
|
853
|
+
<div class="pwless-form">
|
|
854
|
+
<input type="email" placeholder="you@example.com" class="input" id="pwless-email" autocomplete="email" />
|
|
855
|
+
<button class="pwless-submit" id="pwless-submit-btn">Send Magic Link</button>
|
|
856
|
+
</div>
|
|
857
|
+
${errorHtml}
|
|
858
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
859
|
+
`;
|
|
860
|
+
}
|
|
861
|
+
case "passwordless-sending": {
|
|
862
|
+
return `
|
|
863
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
864
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" opacity="0.25"/>
|
|
865
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" stroke-linecap="round"/>
|
|
866
|
+
</svg>
|
|
867
|
+
<span class="overlay-subtitle">Sending magic link...</span>
|
|
868
|
+
`;
|
|
869
|
+
}
|
|
870
|
+
case "passwordless-sent": {
|
|
871
|
+
return `
|
|
872
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
873
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
874
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
875
|
+
</svg>
|
|
876
|
+
</div>
|
|
877
|
+
<div class="overlay-title">Magic link sent!</div>
|
|
878
|
+
<span class="overlay-subtitle">Check your email inbox</span>
|
|
879
|
+
`;
|
|
880
|
+
}
|
|
881
|
+
case "otp-input": {
|
|
882
|
+
const digitInputs = Array.from(
|
|
883
|
+
{ length: 6 },
|
|
884
|
+
(_, i) => `<input type="text" inputmode="numeric" maxlength="1" class="otp-digit" data-idx="${i}" autocomplete="one-time-code" />`
|
|
885
|
+
).join("");
|
|
886
|
+
return `
|
|
887
|
+
<div class="otp-container">
|
|
888
|
+
<div class="overlay-title">Enter verification code</div>
|
|
889
|
+
<span class="overlay-subtitle">6-digit code sent to ${this.escapeHtml(this.overlayEmail)}</span>
|
|
890
|
+
<div class="otp-inputs">${digitInputs}</div>
|
|
891
|
+
${errorHtml}
|
|
892
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
893
|
+
</div>
|
|
894
|
+
`;
|
|
895
|
+
}
|
|
896
|
+
case "passkey-verifying": {
|
|
897
|
+
return `
|
|
898
|
+
<div class="passkey-icon-pulse">
|
|
899
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
900
|
+
<circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/>
|
|
901
|
+
<path d="M21.7 13.3 19 11"/><path d="m21 15-2.5-1.5"/><path d="m17 17 2.5-1.5"/>
|
|
902
|
+
<path d="M22 9v6a1 1 0 0 1-1 1h-.5"/><circle cx="18" cy="9" r="3"/>
|
|
903
|
+
</svg>
|
|
904
|
+
</div>
|
|
905
|
+
<span class="overlay-subtitle">Verifying identity...</span>
|
|
906
|
+
${errorHtml}
|
|
907
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
908
|
+
`;
|
|
909
|
+
}
|
|
910
|
+
case "passkey-success": {
|
|
911
|
+
return `
|
|
912
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
913
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
914
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
915
|
+
</svg>
|
|
916
|
+
</div>
|
|
917
|
+
<div class="overlay-title">Identity verified!</div>
|
|
918
|
+
`;
|
|
919
|
+
}
|
|
920
|
+
default:
|
|
921
|
+
return "";
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
attachOverlayEvents(overlay) {
|
|
925
|
+
if (!this.shadowRoot) return;
|
|
926
|
+
const cancelBtn = overlay.querySelector("#overlay-cancel");
|
|
927
|
+
if (cancelBtn) {
|
|
928
|
+
cancelBtn.addEventListener("click", () => this.hideOverlay());
|
|
929
|
+
}
|
|
930
|
+
overlay.querySelectorAll(".wallet-btn").forEach((btn) => {
|
|
931
|
+
btn.addEventListener("click", () => {
|
|
932
|
+
const walletId = btn.dataset.wallet;
|
|
933
|
+
if (walletId) {
|
|
934
|
+
this.selectedWallet = walletId;
|
|
935
|
+
this.onWeb3WalletSelect(walletId);
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
const pwlessSubmit = overlay.querySelector("#pwless-submit-btn");
|
|
940
|
+
const pwlessEmail = overlay.querySelector("#pwless-email");
|
|
941
|
+
if (pwlessSubmit && pwlessEmail) {
|
|
942
|
+
setTimeout(() => pwlessEmail.focus(), 50);
|
|
943
|
+
const submitHandler = () => {
|
|
944
|
+
const email = pwlessEmail.value.trim();
|
|
945
|
+
if (!email) return;
|
|
946
|
+
this.overlayEmail = email;
|
|
947
|
+
this.showOverlay("passwordless-sending");
|
|
948
|
+
this.onPasswordlessSubmit(email);
|
|
949
|
+
};
|
|
950
|
+
pwlessSubmit.addEventListener("click", submitHandler);
|
|
951
|
+
pwlessEmail.addEventListener("keydown", (e) => {
|
|
952
|
+
if (e.key === "Enter") {
|
|
953
|
+
e.preventDefault();
|
|
954
|
+
submitHandler();
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
const otpDigits = overlay.querySelectorAll(".otp-digit");
|
|
959
|
+
if (otpDigits.length === 6) {
|
|
960
|
+
setTimeout(() => otpDigits[0].focus(), 50);
|
|
961
|
+
otpDigits.forEach((digit, idx) => {
|
|
962
|
+
digit.addEventListener("input", () => {
|
|
963
|
+
const val = digit.value.replace(/\D/g, "");
|
|
964
|
+
digit.value = val.slice(0, 1);
|
|
965
|
+
if (val && idx < 5) {
|
|
966
|
+
otpDigits[idx + 1].focus();
|
|
967
|
+
}
|
|
968
|
+
const code = Array.from(otpDigits).map((d) => d.value).join("");
|
|
969
|
+
if (code.length === 6) {
|
|
970
|
+
this.onOtpVerify(this.overlayEmail, code);
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
digit.addEventListener("keydown", (e) => {
|
|
974
|
+
if (e.key === "Backspace" && !digit.value && idx > 0) {
|
|
975
|
+
otpDigits[idx - 1].focus();
|
|
976
|
+
otpDigits[idx - 1].value = "";
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
digit.addEventListener("paste", (e) => {
|
|
980
|
+
e.preventDefault();
|
|
981
|
+
const pasted = (e.clipboardData?.getData("text") ?? "").replace(/\D/g, "").slice(0, 6);
|
|
982
|
+
if (pasted.length === 0) return;
|
|
983
|
+
for (let i = 0; i < 6; i++) {
|
|
984
|
+
otpDigits[i].value = pasted[i] || "";
|
|
985
|
+
}
|
|
986
|
+
const lastIdx = Math.min(pasted.length, 5);
|
|
987
|
+
otpDigits[lastIdx].focus();
|
|
988
|
+
if (pasted.length === 6) {
|
|
989
|
+
this.onOtpVerify(this.overlayEmail, pasted);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
escapeHtml(str) {
|
|
996
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
997
|
+
}
|
|
431
998
|
// ── Event binding ──
|
|
432
|
-
/** Attach events to shell elements (backdrop, ESC)
|
|
999
|
+
/** Attach events to shell elements (backdrop, ESC) -- called once */
|
|
433
1000
|
attachShellEvents() {
|
|
434
1001
|
if (!this.shadowRoot) return;
|
|
435
1002
|
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
@@ -441,12 +1008,18 @@ var ModalRenderer = class {
|
|
|
441
1008
|
}
|
|
442
1009
|
if (this.mode === "popup") {
|
|
443
1010
|
this.escHandler = (e) => {
|
|
444
|
-
if (e.key === "Escape")
|
|
1011
|
+
if (e.key === "Escape") {
|
|
1012
|
+
if (this.currentOverlay !== "none") {
|
|
1013
|
+
this.hideOverlay();
|
|
1014
|
+
} else {
|
|
1015
|
+
this.onClose();
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
445
1018
|
};
|
|
446
1019
|
document.addEventListener("keydown", this.escHandler);
|
|
447
1020
|
}
|
|
448
1021
|
}
|
|
449
|
-
/** Attach events to inner content (buttons, form, switch link)
|
|
1022
|
+
/** Attach events to inner content (buttons, form, switch link) -- called on each view */
|
|
450
1023
|
attachInnerEvents(view) {
|
|
451
1024
|
if (!this.shadowRoot) return;
|
|
452
1025
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
@@ -480,6 +1053,18 @@ var ModalRenderer = class {
|
|
|
480
1053
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
481
1054
|
});
|
|
482
1055
|
}
|
|
1056
|
+
const web3Btn = this.shadowRoot.getElementById("web3-btn");
|
|
1057
|
+
if (web3Btn) {
|
|
1058
|
+
web3Btn.addEventListener("click", () => this.showOverlay("web3-picker"));
|
|
1059
|
+
}
|
|
1060
|
+
const pwlessBtn = this.shadowRoot.getElementById("passwordless-btn");
|
|
1061
|
+
if (pwlessBtn) {
|
|
1062
|
+
pwlessBtn.addEventListener("click", () => this.showOverlay("passwordless-input"));
|
|
1063
|
+
}
|
|
1064
|
+
const passkeyBtn = this.shadowRoot.getElementById("passkey-btn");
|
|
1065
|
+
if (passkeyBtn) {
|
|
1066
|
+
passkeyBtn.addEventListener("click", () => this.onPasskeyClick());
|
|
1067
|
+
}
|
|
483
1068
|
}
|
|
484
1069
|
};
|
|
485
1070
|
|
|
@@ -1008,6 +1593,10 @@ var Authon = class {
|
|
|
1008
1593
|
await this.ensureInitialized();
|
|
1009
1594
|
this.getModal().open("signUp");
|
|
1010
1595
|
}
|
|
1596
|
+
/** Update theme at runtime without destroying form state */
|
|
1597
|
+
setTheme(theme) {
|
|
1598
|
+
this.getModal().setTheme(theme);
|
|
1599
|
+
}
|
|
1011
1600
|
async signInWithOAuth(provider, options) {
|
|
1012
1601
|
await this.ensureInitialized();
|
|
1013
1602
|
await this.startOAuthFlow(provider, options);
|
|
@@ -1350,7 +1939,53 @@ var Authon = class {
|
|
|
1350
1939
|
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
1351
1940
|
});
|
|
1352
1941
|
},
|
|
1353
|
-
onClose: () => this.modal?.close()
|
|
1942
|
+
onClose: () => this.modal?.close(),
|
|
1943
|
+
onWeb3WalletSelect: async (walletId) => {
|
|
1944
|
+
const chain = walletId === "phantom" ? "solana" : "evm";
|
|
1945
|
+
try {
|
|
1946
|
+
this.modal?.showOverlay?.("web3-connecting");
|
|
1947
|
+
const address = await this.getWalletAddress(walletId);
|
|
1948
|
+
const { message } = await this.web3GetNonce(address, chain, walletId);
|
|
1949
|
+
const signature = await this.requestWalletSignature(walletId, message);
|
|
1950
|
+
await this.web3Verify(message, signature, address, chain, walletId);
|
|
1951
|
+
this.modal?.showWeb3Success(walletId, address);
|
|
1952
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
1953
|
+
} catch (err) {
|
|
1954
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1955
|
+
}
|
|
1956
|
+
},
|
|
1957
|
+
onPasswordlessSubmit: async (email) => {
|
|
1958
|
+
try {
|
|
1959
|
+
const method = this.branding?.passwordlessMethod ?? "magic_link";
|
|
1960
|
+
if (method === "email_otp" || method === "both") {
|
|
1961
|
+
await this.sendEmailOtp(email);
|
|
1962
|
+
this.modal?.showOtpInput(email);
|
|
1963
|
+
} else {
|
|
1964
|
+
await this.sendMagicLink(email);
|
|
1965
|
+
this.modal?.showPasswordlessSent();
|
|
1966
|
+
}
|
|
1967
|
+
} catch (err) {
|
|
1968
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
onOtpVerify: async (email, code) => {
|
|
1972
|
+
try {
|
|
1973
|
+
await this.verifyPasswordless({ email, code });
|
|
1974
|
+
this.modal?.close();
|
|
1975
|
+
} catch (err) {
|
|
1976
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1977
|
+
}
|
|
1978
|
+
},
|
|
1979
|
+
onPasskeyClick: async () => {
|
|
1980
|
+
try {
|
|
1981
|
+
this.modal?.showOverlay?.("passkey-verifying");
|
|
1982
|
+
await this.authenticateWithPasskey();
|
|
1983
|
+
this.modal?.showPasskeySuccess();
|
|
1984
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
1985
|
+
} catch (err) {
|
|
1986
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1354
1989
|
});
|
|
1355
1990
|
}
|
|
1356
1991
|
if (this.branding) this.modal.setBranding(this.branding);
|
|
@@ -1643,6 +2278,30 @@ var Authon = class {
|
|
|
1643
2278
|
});
|
|
1644
2279
|
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
1645
2280
|
}
|
|
2281
|
+
// ── Wallet helpers ──
|
|
2282
|
+
async getWalletAddress(walletId) {
|
|
2283
|
+
if (walletId === "phantom") {
|
|
2284
|
+
const provider2 = window.solana;
|
|
2285
|
+
if (!provider2?.isPhantom) throw new Error("Phantom wallet not detected. Please install it from phantom.app");
|
|
2286
|
+
const resp = await provider2.connect();
|
|
2287
|
+
return resp.publicKey.toString();
|
|
2288
|
+
}
|
|
2289
|
+
const provider = window.ethereum;
|
|
2290
|
+
if (!provider) throw new Error(`${walletId} wallet not detected. Please install it.`);
|
|
2291
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2292
|
+
return accounts[0];
|
|
2293
|
+
}
|
|
2294
|
+
async requestWalletSignature(walletId, message) {
|
|
2295
|
+
if (walletId === "phantom") {
|
|
2296
|
+
const provider2 = window.solana;
|
|
2297
|
+
const encoded = new TextEncoder().encode(message);
|
|
2298
|
+
const signed = await provider2.signMessage(encoded, "utf8");
|
|
2299
|
+
return Array.from(new Uint8Array(signed.signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2300
|
+
}
|
|
2301
|
+
const provider = window.ethereum;
|
|
2302
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2303
|
+
return provider.request({ method: "personal_sign", params: [message, accounts[0]] });
|
|
2304
|
+
}
|
|
1646
2305
|
// ── WebAuthn helpers ──
|
|
1647
2306
|
bufferToBase64url(buffer) {
|
|
1648
2307
|
const bytes = new Uint8Array(buffer);
|