@authon/js 0.3.0 → 0.3.2
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 +757 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +753 -13
- 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,43 @@ 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 = "";
|
|
152
|
+
// Turnstile CAPTCHA
|
|
153
|
+
captchaSiteKey = "";
|
|
154
|
+
turnstileWidgetId = null;
|
|
155
|
+
turnstileToken = "";
|
|
82
156
|
constructor(options) {
|
|
83
157
|
this.mode = options.mode;
|
|
84
158
|
this.theme = options.theme || "auto";
|
|
85
|
-
this.branding = { ...
|
|
159
|
+
this.branding = { ...DEFAULT_BRANDING, ...options.branding };
|
|
160
|
+
this.captchaSiteKey = options.captchaSiteKey || "";
|
|
86
161
|
this.onProviderClick = options.onProviderClick;
|
|
87
162
|
this.onEmailSubmit = options.onEmailSubmit;
|
|
88
163
|
this.onClose = options.onClose;
|
|
164
|
+
this.onWeb3WalletSelect = options.onWeb3WalletSelect || (() => {
|
|
165
|
+
});
|
|
166
|
+
this.onPasswordlessSubmit = options.onPasswordlessSubmit || (() => {
|
|
167
|
+
});
|
|
168
|
+
this.onOtpVerify = options.onOtpVerify || (() => {
|
|
169
|
+
});
|
|
170
|
+
this.onPasskeyClick = options.onPasskeyClick || (() => {
|
|
171
|
+
});
|
|
89
172
|
if (options.mode === "embedded" && options.containerId) {
|
|
90
173
|
this.containerElement = document.getElementById(options.containerId);
|
|
91
174
|
}
|
|
@@ -94,21 +177,29 @@ var ModalRenderer = class {
|
|
|
94
177
|
this.enabledProviders = providers;
|
|
95
178
|
}
|
|
96
179
|
setBranding(branding) {
|
|
97
|
-
this.branding = { ...
|
|
180
|
+
this.branding = { ...DEFAULT_BRANDING, ...branding };
|
|
98
181
|
}
|
|
99
182
|
open(view = "signIn") {
|
|
100
183
|
if (this.shadowRoot && this.hostElement) {
|
|
184
|
+
this.hideOverlay();
|
|
101
185
|
this.switchView(view);
|
|
102
186
|
} else {
|
|
103
187
|
this.currentView = view;
|
|
188
|
+
this.currentOverlay = "none";
|
|
104
189
|
this.render(view);
|
|
105
190
|
}
|
|
106
191
|
}
|
|
107
192
|
close() {
|
|
193
|
+
this.stopThemeObserver();
|
|
108
194
|
if (this.escHandler) {
|
|
109
195
|
document.removeEventListener("keydown", this.escHandler);
|
|
110
196
|
this.escHandler = null;
|
|
111
197
|
}
|
|
198
|
+
if (this.turnstileWidgetId !== null) {
|
|
199
|
+
window.turnstile?.remove(this.turnstileWidgetId);
|
|
200
|
+
this.turnstileWidgetId = null;
|
|
201
|
+
this.turnstileToken = "";
|
|
202
|
+
}
|
|
112
203
|
if (this.hostElement) {
|
|
113
204
|
this.hostElement.remove();
|
|
114
205
|
this.hostElement = null;
|
|
@@ -117,6 +208,55 @@ var ModalRenderer = class {
|
|
|
117
208
|
if (this.containerElement) {
|
|
118
209
|
this.containerElement.innerHTML = "";
|
|
119
210
|
}
|
|
211
|
+
this.currentOverlay = "none";
|
|
212
|
+
}
|
|
213
|
+
getTurnstileToken() {
|
|
214
|
+
return this.turnstileToken;
|
|
215
|
+
}
|
|
216
|
+
resetTurnstile() {
|
|
217
|
+
if (this.turnstileWidgetId !== null) {
|
|
218
|
+
window.turnstile?.reset(this.turnstileWidgetId);
|
|
219
|
+
this.turnstileToken = "";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** Update theme at runtime without destroying form state */
|
|
223
|
+
setTheme(theme) {
|
|
224
|
+
this.theme = theme;
|
|
225
|
+
this.updateThemeCSS();
|
|
226
|
+
if (theme === "auto") {
|
|
227
|
+
this.startThemeObserver();
|
|
228
|
+
} else {
|
|
229
|
+
this.stopThemeObserver();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
updateThemeCSS() {
|
|
233
|
+
if (!this.shadowRoot) return;
|
|
234
|
+
const styleEl = this.shadowRoot.getElementById("authon-theme-style");
|
|
235
|
+
if (styleEl) {
|
|
236
|
+
styleEl.textContent = this.buildCSS();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
startThemeObserver() {
|
|
240
|
+
this.stopThemeObserver();
|
|
241
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
242
|
+
this.themeObserver = new MutationObserver(() => this.updateThemeCSS());
|
|
243
|
+
this.themeObserver.observe(document.documentElement, {
|
|
244
|
+
attributes: true,
|
|
245
|
+
attributeFilter: ["data-theme", "class"]
|
|
246
|
+
});
|
|
247
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
248
|
+
this.mediaQueryListener = () => this.updateThemeCSS();
|
|
249
|
+
mq.addEventListener("change", this.mediaQueryListener);
|
|
250
|
+
}
|
|
251
|
+
stopThemeObserver() {
|
|
252
|
+
if (this.themeObserver) {
|
|
253
|
+
this.themeObserver.disconnect();
|
|
254
|
+
this.themeObserver = null;
|
|
255
|
+
}
|
|
256
|
+
if (this.mediaQueryListener) {
|
|
257
|
+
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", this.mediaQueryListener);
|
|
258
|
+
this.mediaQueryListener = null;
|
|
259
|
+
}
|
|
120
260
|
}
|
|
121
261
|
showError(message) {
|
|
122
262
|
if (!this.shadowRoot) return;
|
|
@@ -168,6 +308,47 @@ var ModalRenderer = class {
|
|
|
168
308
|
if (!this.shadowRoot) return;
|
|
169
309
|
this.shadowRoot.getElementById("authon-loading-overlay")?.remove();
|
|
170
310
|
}
|
|
311
|
+
// ── Flow Overlay Public API ──
|
|
312
|
+
showOverlay(overlay) {
|
|
313
|
+
this.currentOverlay = overlay;
|
|
314
|
+
this.overlayError = "";
|
|
315
|
+
this.renderOverlay();
|
|
316
|
+
}
|
|
317
|
+
hideOverlay() {
|
|
318
|
+
this.currentOverlay = "none";
|
|
319
|
+
this.overlayError = "";
|
|
320
|
+
if (!this.shadowRoot) return;
|
|
321
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
322
|
+
}
|
|
323
|
+
showWeb3Success(walletId, address) {
|
|
324
|
+
this.selectedWallet = walletId;
|
|
325
|
+
this.overlayError = "";
|
|
326
|
+
const truncated = address.length > 10 ? `${address.slice(0, 6)}...${address.slice(-4)}` : address;
|
|
327
|
+
this.currentOverlay = "web3-success";
|
|
328
|
+
this.renderOverlayWithData({ truncatedAddress: truncated, walletId });
|
|
329
|
+
}
|
|
330
|
+
showPasswordlessSent() {
|
|
331
|
+
this.overlayError = "";
|
|
332
|
+
this.currentOverlay = "passwordless-sent";
|
|
333
|
+
this.renderOverlay();
|
|
334
|
+
}
|
|
335
|
+
showOtpInput(email) {
|
|
336
|
+
this.overlayEmail = email;
|
|
337
|
+
this.overlayError = "";
|
|
338
|
+
this.currentOverlay = "otp-input";
|
|
339
|
+
this.renderOverlay();
|
|
340
|
+
}
|
|
341
|
+
showPasskeySuccess() {
|
|
342
|
+
this.overlayError = "";
|
|
343
|
+
this.currentOverlay = "passkey-success";
|
|
344
|
+
this.renderOverlay();
|
|
345
|
+
}
|
|
346
|
+
showOverlayError(message) {
|
|
347
|
+
this.overlayError = message;
|
|
348
|
+
if (this.currentOverlay !== "none") {
|
|
349
|
+
this.renderOverlay();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
171
352
|
// ── Smooth view switch (no flicker) ──
|
|
172
353
|
switchView(view) {
|
|
173
354
|
if (!this.shadowRoot || view === this.currentView) return;
|
|
@@ -198,13 +379,16 @@ var ModalRenderer = class {
|
|
|
198
379
|
this.shadowRoot.innerHTML = this.buildShell(view);
|
|
199
380
|
this.attachInnerEvents(view);
|
|
200
381
|
this.attachShellEvents();
|
|
382
|
+
if (this.theme === "auto") {
|
|
383
|
+
this.startThemeObserver();
|
|
384
|
+
}
|
|
201
385
|
}
|
|
202
386
|
// ── HTML builders ──
|
|
203
387
|
/** Shell = style + backdrop + modal-container (stable across view switches) */
|
|
204
388
|
buildShell(view) {
|
|
205
389
|
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
206
390
|
return `
|
|
207
|
-
<style>${this.buildCSS()}</style>
|
|
391
|
+
<style id="authon-theme-style">${this.buildCSS()}</style>
|
|
208
392
|
${popupWrapper}
|
|
209
393
|
<div class="modal-container" role="dialog" aria-modal="true">
|
|
210
394
|
<div id="modal-inner" class="modal-inner">
|
|
@@ -233,12 +417,43 @@ var ModalRenderer = class {
|
|
|
233
417
|
</button>`;
|
|
234
418
|
}).join("") : "";
|
|
235
419
|
const divider = showProviders && b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
|
|
420
|
+
const captchaContainer = this.captchaSiteKey ? '<div id="turnstile-container" style="display:flex;justify-content:center;margin:4px 0"></div>' : "";
|
|
236
421
|
const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
|
|
237
422
|
<input type="email" placeholder="Email address" name="email" required class="input" autocomplete="email" />
|
|
238
423
|
<input type="password" placeholder="Password" name="password" required class="input" autocomplete="${isSignUp ? "new-password" : "current-password"}" />
|
|
239
424
|
${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
|
|
425
|
+
${captchaContainer}
|
|
240
426
|
<button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
|
|
241
427
|
</form>` : "";
|
|
428
|
+
const hasMethodAbove = showProviders && this.enabledProviders.length > 0 || b.showEmailPassword !== false;
|
|
429
|
+
const hasMethodBelow = b.showWeb3 || b.showPasswordless || b.showPasskey;
|
|
430
|
+
const methodDivider = hasMethodAbove && hasMethodBelow ? `<div class="divider"><span>or</span></div>` : "";
|
|
431
|
+
const methodButtons = [];
|
|
432
|
+
if (b.showWeb3) {
|
|
433
|
+
methodButtons.push(`<button class="auth-method-btn web3-btn" id="web3-btn">
|
|
434
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
435
|
+
<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"/>
|
|
436
|
+
</svg>
|
|
437
|
+
<span>Connect Wallet</span>
|
|
438
|
+
</button>`);
|
|
439
|
+
}
|
|
440
|
+
if (b.showPasswordless) {
|
|
441
|
+
methodButtons.push(`<button class="auth-method-btn passwordless-btn" id="passwordless-btn">
|
|
442
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
443
|
+
<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"/>
|
|
444
|
+
</svg>
|
|
445
|
+
<span>Continue with Magic Link</span>
|
|
446
|
+
</button>`);
|
|
447
|
+
}
|
|
448
|
+
if (b.showPasskey) {
|
|
449
|
+
methodButtons.push(`<button class="auth-method-btn passkey-btn" id="passkey-btn">
|
|
450
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
451
|
+
<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"/>
|
|
452
|
+
</svg>
|
|
453
|
+
<span>Sign in with Passkey</span>
|
|
454
|
+
</button>`);
|
|
455
|
+
}
|
|
456
|
+
const authMethods = methodButtons.length > 0 ? `<div class="auth-methods">${methodButtons.join("")}</div>` : "";
|
|
242
457
|
const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
|
|
243
458
|
${b.termsUrl ? `<a href="${b.termsUrl}" target="_blank">Terms of Service</a>` : ""}
|
|
244
459
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
@@ -257,14 +472,60 @@ var ModalRenderer = class {
|
|
|
257
472
|
${showProviders ? `<div class="providers">${providerButtons}</div>` : ""}
|
|
258
473
|
${divider}
|
|
259
474
|
${emailForm}
|
|
475
|
+
${methodDivider}
|
|
476
|
+
${authMethods}
|
|
260
477
|
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
261
478
|
${footer}
|
|
262
479
|
${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>` : ""}
|
|
263
480
|
`;
|
|
264
481
|
}
|
|
482
|
+
renderTurnstile() {
|
|
483
|
+
if (!this.captchaSiteKey || !this.shadowRoot) return;
|
|
484
|
+
const container = this.shadowRoot.getElementById("turnstile-container");
|
|
485
|
+
if (!container) return;
|
|
486
|
+
const w = window;
|
|
487
|
+
const tryRender = () => {
|
|
488
|
+
if (!w.turnstile || !container.isConnected) return;
|
|
489
|
+
const wrapper = document.createElement("div");
|
|
490
|
+
wrapper.style.display = "flex";
|
|
491
|
+
wrapper.style.justifyContent = "center";
|
|
492
|
+
document.body.appendChild(wrapper);
|
|
493
|
+
this.turnstileWidgetId = w.turnstile.render(wrapper, {
|
|
494
|
+
sitekey: this.captchaSiteKey,
|
|
495
|
+
callback: (token) => {
|
|
496
|
+
this.turnstileToken = token;
|
|
497
|
+
},
|
|
498
|
+
"expired-callback": () => {
|
|
499
|
+
this.turnstileToken = "";
|
|
500
|
+
},
|
|
501
|
+
"error-callback": () => {
|
|
502
|
+
this.turnstileToken = "";
|
|
503
|
+
},
|
|
504
|
+
theme: this.isDark() ? "dark" : "light",
|
|
505
|
+
size: "flexible"
|
|
506
|
+
});
|
|
507
|
+
container.appendChild(wrapper);
|
|
508
|
+
};
|
|
509
|
+
if (w.turnstile) {
|
|
510
|
+
tryRender();
|
|
511
|
+
} else {
|
|
512
|
+
const interval = setInterval(() => {
|
|
513
|
+
if (w.turnstile) {
|
|
514
|
+
clearInterval(interval);
|
|
515
|
+
tryRender();
|
|
516
|
+
}
|
|
517
|
+
}, 200);
|
|
518
|
+
setTimeout(() => clearInterval(interval), 1e4);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
265
521
|
isDark() {
|
|
266
522
|
if (this.theme === "dark") return true;
|
|
267
523
|
if (this.theme === "light") return false;
|
|
524
|
+
if (typeof document !== "undefined") {
|
|
525
|
+
const html = document.documentElement;
|
|
526
|
+
if (html.classList.contains("dark") || html.getAttribute("data-theme") === "dark") return true;
|
|
527
|
+
if (html.classList.contains("light") || html.getAttribute("data-theme") === "light") return false;
|
|
528
|
+
}
|
|
268
529
|
return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
269
530
|
}
|
|
270
531
|
buildCSS() {
|
|
@@ -288,6 +549,10 @@ var ModalRenderer = class {
|
|
|
288
549
|
--authon-border: ${borderColor};
|
|
289
550
|
--authon-divider: ${dividerColor};
|
|
290
551
|
--authon-input-bg: ${inputBg};
|
|
552
|
+
--authon-overlay-bg: ${hexToRgba(bg, 0.92)};
|
|
553
|
+
--authon-overlay-bg-solid: ${hexToRgba(bg, 0.97)};
|
|
554
|
+
--authon-backdrop-bg: rgba(0,0,0,${dark ? "0.7" : "0.5"});
|
|
555
|
+
--authon-shadow-opacity: ${dark ? "0.5" : "0.25"};
|
|
291
556
|
--authon-radius: ${b.borderRadius ?? 12}px;
|
|
292
557
|
--authon-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
293
558
|
font-family: var(--authon-font);
|
|
@@ -296,7 +561,7 @@ var ModalRenderer = class {
|
|
|
296
561
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
297
562
|
.backdrop {
|
|
298
563
|
position: fixed; inset: 0; z-index: 99998;
|
|
299
|
-
background:
|
|
564
|
+
background: var(--authon-backdrop-bg); backdrop-filter: blur(4px);
|
|
300
565
|
animation: fadeIn 0.2s ease;
|
|
301
566
|
}
|
|
302
567
|
.modal-container {
|
|
@@ -390,10 +655,154 @@ var ModalRenderer = class {
|
|
|
390
655
|
}
|
|
391
656
|
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
392
657
|
.secured-link:hover { text-decoration: underline; }
|
|
658
|
+
|
|
659
|
+
/* Auth method buttons */
|
|
660
|
+
.auth-methods { display: flex; flex-direction: column; gap: 8px; }
|
|
661
|
+
.auth-method-btn {
|
|
662
|
+
display: flex; align-items: center; justify-content: center; gap: 8px;
|
|
663
|
+
width: 100%; padding: 10px 16px;
|
|
664
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
665
|
+
font-size: 13px; font-weight: 500; cursor: pointer;
|
|
666
|
+
font-family: var(--authon-font); transition: opacity 0.15s, transform 0.1s;
|
|
667
|
+
}
|
|
668
|
+
.auth-method-btn:hover { opacity: 0.85; }
|
|
669
|
+
.auth-method-btn:active { transform: scale(0.98); }
|
|
670
|
+
/* Web3 -- purple */
|
|
671
|
+
.web3-btn {
|
|
672
|
+
background: ${dark ? "rgba(139,92,246,0.12)" : "rgba(139,92,246,0.08)"};
|
|
673
|
+
border: 1px solid ${dark ? "rgba(139,92,246,0.3)" : "rgba(139,92,246,0.25)"};
|
|
674
|
+
color: ${dark ? "#c4b5fd" : "#7c3aed"};
|
|
675
|
+
}
|
|
676
|
+
/* Passwordless -- cyan */
|
|
677
|
+
.passwordless-btn {
|
|
678
|
+
background: ${dark ? "rgba(6,182,212,0.12)" : "rgba(6,182,212,0.08)"};
|
|
679
|
+
border: 1px solid ${dark ? "rgba(6,182,212,0.3)" : "rgba(6,182,212,0.25)"};
|
|
680
|
+
color: ${dark ? "#67e8f9" : "#0891b2"};
|
|
681
|
+
}
|
|
682
|
+
/* Passkey -- amber */
|
|
683
|
+
.passkey-btn {
|
|
684
|
+
background: ${dark ? "rgba(245,158,11,0.12)" : "rgba(245,158,11,0.08)"};
|
|
685
|
+
border: 1px solid ${dark ? "rgba(245,158,11,0.3)" : "rgba(245,158,11,0.25)"};
|
|
686
|
+
color: ${dark ? "#fcd34d" : "#b45309"};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* Flow overlay */
|
|
690
|
+
.flow-overlay {
|
|
691
|
+
position: absolute; inset: 0; z-index: 10;
|
|
692
|
+
background: var(--authon-overlay-bg-solid);
|
|
693
|
+
backdrop-filter: blur(2px);
|
|
694
|
+
border-radius: var(--authon-radius);
|
|
695
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
696
|
+
gap: 12px; padding: 24px;
|
|
697
|
+
animation: fadeIn 0.2s ease;
|
|
698
|
+
}
|
|
699
|
+
.flow-overlay .cancel-link {
|
|
700
|
+
font-size: 12px; color: var(--authon-dim); cursor: pointer; border: none;
|
|
701
|
+
background: none; font-family: var(--authon-font); margin-top: 4px;
|
|
702
|
+
}
|
|
703
|
+
.flow-overlay .cancel-link:hover { text-decoration: underline; }
|
|
704
|
+
.flow-overlay .overlay-title {
|
|
705
|
+
font-size: 14px; font-weight: 600; color: var(--authon-text); text-align: center;
|
|
706
|
+
}
|
|
707
|
+
.flow-overlay .overlay-subtitle {
|
|
708
|
+
font-size: 12px; color: var(--authon-muted); text-align: center;
|
|
709
|
+
}
|
|
710
|
+
.flow-overlay .overlay-error {
|
|
711
|
+
padding: 6px 12px; margin-top: 4px;
|
|
712
|
+
background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3);
|
|
713
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
714
|
+
font-size: 12px; color: #ef4444; text-align: center; width: 100%;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/* Wallet picker */
|
|
718
|
+
.wallet-picker { display: flex; flex-direction: column; gap: 8px; width: 100%; }
|
|
719
|
+
.wallet-btn {
|
|
720
|
+
display: flex; align-items: center; gap: 10px;
|
|
721
|
+
width: 100%; padding: 10px 14px;
|
|
722
|
+
background: ${dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)"};
|
|
723
|
+
border: 1px solid ${dark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};
|
|
724
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
725
|
+
font-size: 13px; font-weight: 500; color: var(--authon-text);
|
|
726
|
+
cursor: pointer; font-family: var(--authon-font);
|
|
727
|
+
transition: opacity 0.15s;
|
|
728
|
+
}
|
|
729
|
+
.wallet-btn:hover { opacity: 0.8; }
|
|
730
|
+
.wallet-btn .wallet-icon { display: flex; align-items: center; flex-shrink: 0; }
|
|
731
|
+
.wallet-btn .wallet-icon svg { border-radius: 6px; }
|
|
732
|
+
|
|
733
|
+
/* Passwordless email input in overlay */
|
|
734
|
+
.pwless-form { display: flex; flex-direction: column; gap: 10px; width: 100%; }
|
|
735
|
+
.pwless-submit {
|
|
736
|
+
width: 100%; padding: 10px;
|
|
737
|
+
background: linear-gradient(135deg, #06b6d4, #0891b2);
|
|
738
|
+
color: #fff; border: none; border-radius: calc(var(--authon-radius) * 0.5);
|
|
739
|
+
font-size: 13px; font-weight: 600; cursor: pointer;
|
|
740
|
+
font-family: var(--authon-font); transition: opacity 0.15s;
|
|
741
|
+
}
|
|
742
|
+
.pwless-submit:hover { opacity: 0.9; }
|
|
743
|
+
.pwless-submit:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
744
|
+
|
|
745
|
+
/* OTP input */
|
|
746
|
+
.otp-container { display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; }
|
|
747
|
+
.otp-inputs { display: flex; gap: 8px; justify-content: center; }
|
|
748
|
+
.otp-digit {
|
|
749
|
+
width: 40px; height: 48px; text-align: center;
|
|
750
|
+
font-size: 20px; font-weight: 600; font-family: var(--authon-font);
|
|
751
|
+
background: var(--authon-input-bg); color: var(--authon-text);
|
|
752
|
+
border: 1px solid var(--authon-border);
|
|
753
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
754
|
+
outline: none; transition: border-color 0.15s;
|
|
755
|
+
}
|
|
756
|
+
.otp-digit:focus {
|
|
757
|
+
border-color: var(--authon-primary-start);
|
|
758
|
+
box-shadow: 0 0 0 3px rgba(124,58,237,0.15);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* Success check animation */
|
|
762
|
+
.success-check {
|
|
763
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
764
|
+
display: flex; align-items: center; justify-content: center;
|
|
765
|
+
}
|
|
766
|
+
.success-check svg path {
|
|
767
|
+
stroke-dasharray: 20;
|
|
768
|
+
stroke-dashoffset: 20;
|
|
769
|
+
animation: check-draw 0.4s ease-out 0.1s forwards;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/* Spinner */
|
|
773
|
+
.flow-spinner {
|
|
774
|
+
animation: spin 0.8s linear infinite;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/* Passkey verifying icon */
|
|
778
|
+
.passkey-icon-pulse {
|
|
779
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
780
|
+
display: flex; align-items: center; justify-content: center;
|
|
781
|
+
background: rgba(245,158,11,0.15);
|
|
782
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/* Wallet connecting icon */
|
|
786
|
+
.wallet-connecting-icon {
|
|
787
|
+
width: 48px; height: 48px; border-radius: 12px;
|
|
788
|
+
display: flex; align-items: center; justify-content: center;
|
|
789
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
790
|
+
}
|
|
791
|
+
.wallet-connecting-icon svg { border-radius: 6px; }
|
|
792
|
+
|
|
793
|
+
/* Address badge */
|
|
794
|
+
.address-badge {
|
|
795
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
796
|
+
padding: 2px 10px; border-radius: 6px;
|
|
797
|
+
background: ${dark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.03)"};
|
|
798
|
+
font-size: 11px; font-family: monospace; color: var(--authon-muted);
|
|
799
|
+
}
|
|
800
|
+
.address-badge .wallet-icon-sm svg { width: 16px; height: 16px; border-radius: 4px; }
|
|
801
|
+
|
|
393
802
|
/* Loading overlay */
|
|
394
803
|
#authon-loading-overlay {
|
|
395
804
|
position: absolute; inset: 0; z-index: 10;
|
|
396
|
-
background:
|
|
805
|
+
background: var(--authon-overlay-bg);
|
|
397
806
|
backdrop-filter: blur(2px);
|
|
398
807
|
border-radius: var(--authon-radius);
|
|
399
808
|
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
|
|
@@ -425,11 +834,229 @@ var ModalRenderer = class {
|
|
|
425
834
|
@keyframes blink { 0%,80%,100% { opacity: .2; } 40% { opacity: 1; } }
|
|
426
835
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
427
836
|
@keyframes slideIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } }
|
|
837
|
+
@keyframes check-draw { to { stroke-dashoffset: 0; } }
|
|
838
|
+
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }
|
|
428
839
|
${b.customCss || ""}
|
|
429
840
|
`;
|
|
430
841
|
}
|
|
842
|
+
// ── Flow Overlay Rendering ──
|
|
843
|
+
renderOverlay() {
|
|
844
|
+
this.renderOverlayWithData({});
|
|
845
|
+
}
|
|
846
|
+
renderOverlayWithData(data) {
|
|
847
|
+
if (!this.shadowRoot) return;
|
|
848
|
+
const container = this.shadowRoot.querySelector(".modal-container");
|
|
849
|
+
if (!container) return;
|
|
850
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
851
|
+
if (this.currentOverlay === "none") return;
|
|
852
|
+
const overlay = document.createElement("div");
|
|
853
|
+
overlay.id = "flow-overlay";
|
|
854
|
+
overlay.className = "flow-overlay";
|
|
855
|
+
overlay.innerHTML = this.buildOverlayContent(data);
|
|
856
|
+
container.appendChild(overlay);
|
|
857
|
+
this.attachOverlayEvents(overlay);
|
|
858
|
+
}
|
|
859
|
+
buildOverlayContent(data) {
|
|
860
|
+
const dark = this.isDark();
|
|
861
|
+
const errorHtml = this.overlayError ? `<div class="overlay-error">${this.escapeHtml(this.overlayError)}</div>` : "";
|
|
862
|
+
switch (this.currentOverlay) {
|
|
863
|
+
case "web3-picker": {
|
|
864
|
+
const walletItems = WALLET_OPTIONS.map(
|
|
865
|
+
(w) => `<button class="wallet-btn" data-wallet="${w.id}">
|
|
866
|
+
<span class="wallet-icon">${walletIconSvg(w.id)}</span>
|
|
867
|
+
<span>${w.name}</span>
|
|
868
|
+
</button>`
|
|
869
|
+
).join("");
|
|
870
|
+
return `
|
|
871
|
+
<div class="overlay-title" style="margin-bottom: 4px;">Select Wallet</div>
|
|
872
|
+
<div class="wallet-picker">${walletItems}</div>
|
|
873
|
+
${errorHtml}
|
|
874
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
875
|
+
`;
|
|
876
|
+
}
|
|
877
|
+
case "web3-connecting": {
|
|
878
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === this.selectedWallet);
|
|
879
|
+
const walletName = wallet?.name ?? this.selectedWallet;
|
|
880
|
+
return `
|
|
881
|
+
<div class="wallet-connecting-icon">${walletIconSvg(this.selectedWallet)}</div>
|
|
882
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
883
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
884
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" opacity="0.25"/>
|
|
885
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" stroke-linecap="round"/>
|
|
886
|
+
</svg>
|
|
887
|
+
<span class="overlay-subtitle">Connecting ${this.escapeHtml(walletName)}...</span>
|
|
888
|
+
</div>
|
|
889
|
+
${errorHtml}
|
|
890
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
891
|
+
`;
|
|
892
|
+
}
|
|
893
|
+
case "web3-success": {
|
|
894
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === (data.walletId || this.selectedWallet));
|
|
895
|
+
const walletColor = wallet?.color ?? "#8b5cf6";
|
|
896
|
+
const truncAddr = data.truncatedAddress || "0x...";
|
|
897
|
+
return `
|
|
898
|
+
<div class="success-check" style="background:linear-gradient(135deg, ${walletColor}, ${walletColor})">
|
|
899
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
900
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
901
|
+
</svg>
|
|
902
|
+
</div>
|
|
903
|
+
<div class="overlay-title">Wallet Connected</div>
|
|
904
|
+
<div class="address-badge">
|
|
905
|
+
<span class="wallet-icon-sm">${walletIconSvg(data.walletId || this.selectedWallet)}</span>
|
|
906
|
+
<span>${this.escapeHtml(truncAddr)}</span>
|
|
907
|
+
</div>
|
|
908
|
+
`;
|
|
909
|
+
}
|
|
910
|
+
case "passwordless-input": {
|
|
911
|
+
return `
|
|
912
|
+
<div class="overlay-title">Enter your email</div>
|
|
913
|
+
<div class="pwless-form">
|
|
914
|
+
<input type="email" placeholder="you@example.com" class="input" id="pwless-email" autocomplete="email" />
|
|
915
|
+
<button class="pwless-submit" id="pwless-submit-btn">Send Magic Link</button>
|
|
916
|
+
</div>
|
|
917
|
+
${errorHtml}
|
|
918
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
919
|
+
`;
|
|
920
|
+
}
|
|
921
|
+
case "passwordless-sending": {
|
|
922
|
+
return `
|
|
923
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
924
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" opacity="0.25"/>
|
|
925
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" stroke-linecap="round"/>
|
|
926
|
+
</svg>
|
|
927
|
+
<span class="overlay-subtitle">Sending magic link...</span>
|
|
928
|
+
`;
|
|
929
|
+
}
|
|
930
|
+
case "passwordless-sent": {
|
|
931
|
+
return `
|
|
932
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
933
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
934
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
935
|
+
</svg>
|
|
936
|
+
</div>
|
|
937
|
+
<div class="overlay-title">Magic link sent!</div>
|
|
938
|
+
<span class="overlay-subtitle">Check your email inbox</span>
|
|
939
|
+
`;
|
|
940
|
+
}
|
|
941
|
+
case "otp-input": {
|
|
942
|
+
const digitInputs = Array.from(
|
|
943
|
+
{ length: 6 },
|
|
944
|
+
(_, i) => `<input type="text" inputmode="numeric" maxlength="1" class="otp-digit" data-idx="${i}" autocomplete="one-time-code" />`
|
|
945
|
+
).join("");
|
|
946
|
+
return `
|
|
947
|
+
<div class="otp-container">
|
|
948
|
+
<div class="overlay-title">Enter verification code</div>
|
|
949
|
+
<span class="overlay-subtitle">6-digit code sent to ${this.escapeHtml(this.overlayEmail)}</span>
|
|
950
|
+
<div class="otp-inputs">${digitInputs}</div>
|
|
951
|
+
${errorHtml}
|
|
952
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
953
|
+
</div>
|
|
954
|
+
`;
|
|
955
|
+
}
|
|
956
|
+
case "passkey-verifying": {
|
|
957
|
+
return `
|
|
958
|
+
<div class="passkey-icon-pulse">
|
|
959
|
+
<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">
|
|
960
|
+
<circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/>
|
|
961
|
+
<path d="M21.7 13.3 19 11"/><path d="m21 15-2.5-1.5"/><path d="m17 17 2.5-1.5"/>
|
|
962
|
+
<path d="M22 9v6a1 1 0 0 1-1 1h-.5"/><circle cx="18" cy="9" r="3"/>
|
|
963
|
+
</svg>
|
|
964
|
+
</div>
|
|
965
|
+
<span class="overlay-subtitle">Verifying identity...</span>
|
|
966
|
+
${errorHtml}
|
|
967
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
968
|
+
`;
|
|
969
|
+
}
|
|
970
|
+
case "passkey-success": {
|
|
971
|
+
return `
|
|
972
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
973
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
974
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
975
|
+
</svg>
|
|
976
|
+
</div>
|
|
977
|
+
<div class="overlay-title">Identity verified!</div>
|
|
978
|
+
`;
|
|
979
|
+
}
|
|
980
|
+
default:
|
|
981
|
+
return "";
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
attachOverlayEvents(overlay) {
|
|
985
|
+
if (!this.shadowRoot) return;
|
|
986
|
+
const cancelBtn = overlay.querySelector("#overlay-cancel");
|
|
987
|
+
if (cancelBtn) {
|
|
988
|
+
cancelBtn.addEventListener("click", () => this.hideOverlay());
|
|
989
|
+
}
|
|
990
|
+
overlay.querySelectorAll(".wallet-btn").forEach((btn) => {
|
|
991
|
+
btn.addEventListener("click", () => {
|
|
992
|
+
const walletId = btn.dataset.wallet;
|
|
993
|
+
if (walletId) {
|
|
994
|
+
this.selectedWallet = walletId;
|
|
995
|
+
this.onWeb3WalletSelect(walletId);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
});
|
|
999
|
+
const pwlessSubmit = overlay.querySelector("#pwless-submit-btn");
|
|
1000
|
+
const pwlessEmail = overlay.querySelector("#pwless-email");
|
|
1001
|
+
if (pwlessSubmit && pwlessEmail) {
|
|
1002
|
+
setTimeout(() => pwlessEmail.focus(), 50);
|
|
1003
|
+
const submitHandler = () => {
|
|
1004
|
+
const email = pwlessEmail.value.trim();
|
|
1005
|
+
if (!email) return;
|
|
1006
|
+
this.overlayEmail = email;
|
|
1007
|
+
this.showOverlay("passwordless-sending");
|
|
1008
|
+
this.onPasswordlessSubmit(email);
|
|
1009
|
+
};
|
|
1010
|
+
pwlessSubmit.addEventListener("click", submitHandler);
|
|
1011
|
+
pwlessEmail.addEventListener("keydown", (e) => {
|
|
1012
|
+
if (e.key === "Enter") {
|
|
1013
|
+
e.preventDefault();
|
|
1014
|
+
submitHandler();
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
const otpDigits = overlay.querySelectorAll(".otp-digit");
|
|
1019
|
+
if (otpDigits.length === 6) {
|
|
1020
|
+
setTimeout(() => otpDigits[0].focus(), 50);
|
|
1021
|
+
otpDigits.forEach((digit, idx) => {
|
|
1022
|
+
digit.addEventListener("input", () => {
|
|
1023
|
+
const val = digit.value.replace(/\D/g, "");
|
|
1024
|
+
digit.value = val.slice(0, 1);
|
|
1025
|
+
if (val && idx < 5) {
|
|
1026
|
+
otpDigits[idx + 1].focus();
|
|
1027
|
+
}
|
|
1028
|
+
const code = Array.from(otpDigits).map((d) => d.value).join("");
|
|
1029
|
+
if (code.length === 6) {
|
|
1030
|
+
this.onOtpVerify(this.overlayEmail, code);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
digit.addEventListener("keydown", (e) => {
|
|
1034
|
+
if (e.key === "Backspace" && !digit.value && idx > 0) {
|
|
1035
|
+
otpDigits[idx - 1].focus();
|
|
1036
|
+
otpDigits[idx - 1].value = "";
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
digit.addEventListener("paste", (e) => {
|
|
1040
|
+
e.preventDefault();
|
|
1041
|
+
const pasted = (e.clipboardData?.getData("text") ?? "").replace(/\D/g, "").slice(0, 6);
|
|
1042
|
+
if (pasted.length === 0) return;
|
|
1043
|
+
for (let i = 0; i < 6; i++) {
|
|
1044
|
+
otpDigits[i].value = pasted[i] || "";
|
|
1045
|
+
}
|
|
1046
|
+
const lastIdx = Math.min(pasted.length, 5);
|
|
1047
|
+
otpDigits[lastIdx].focus();
|
|
1048
|
+
if (pasted.length === 6) {
|
|
1049
|
+
this.onOtpVerify(this.overlayEmail, pasted);
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
escapeHtml(str) {
|
|
1056
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1057
|
+
}
|
|
431
1058
|
// ── Event binding ──
|
|
432
|
-
/** Attach events to shell elements (backdrop, ESC)
|
|
1059
|
+
/** Attach events to shell elements (backdrop, ESC) -- called once */
|
|
433
1060
|
attachShellEvents() {
|
|
434
1061
|
if (!this.shadowRoot) return;
|
|
435
1062
|
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
@@ -441,12 +1068,18 @@ var ModalRenderer = class {
|
|
|
441
1068
|
}
|
|
442
1069
|
if (this.mode === "popup") {
|
|
443
1070
|
this.escHandler = (e) => {
|
|
444
|
-
if (e.key === "Escape")
|
|
1071
|
+
if (e.key === "Escape") {
|
|
1072
|
+
if (this.currentOverlay !== "none") {
|
|
1073
|
+
this.hideOverlay();
|
|
1074
|
+
} else {
|
|
1075
|
+
this.onClose();
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
445
1078
|
};
|
|
446
1079
|
document.addEventListener("keydown", this.escHandler);
|
|
447
1080
|
}
|
|
448
1081
|
}
|
|
449
|
-
/** Attach events to inner content (buttons, form, switch link)
|
|
1082
|
+
/** Attach events to inner content (buttons, form, switch link) -- called on each view */
|
|
450
1083
|
attachInnerEvents(view) {
|
|
451
1084
|
if (!this.shadowRoot) return;
|
|
452
1085
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
@@ -467,6 +1100,7 @@ var ModalRenderer = class {
|
|
|
467
1100
|
);
|
|
468
1101
|
});
|
|
469
1102
|
}
|
|
1103
|
+
this.renderTurnstile();
|
|
470
1104
|
const backBtn = this.shadowRoot.getElementById("back-btn");
|
|
471
1105
|
if (backBtn) {
|
|
472
1106
|
backBtn.addEventListener("click", () => {
|
|
@@ -480,6 +1114,18 @@ var ModalRenderer = class {
|
|
|
480
1114
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
481
1115
|
});
|
|
482
1116
|
}
|
|
1117
|
+
const web3Btn = this.shadowRoot.getElementById("web3-btn");
|
|
1118
|
+
if (web3Btn) {
|
|
1119
|
+
web3Btn.addEventListener("click", () => this.showOverlay("web3-picker"));
|
|
1120
|
+
}
|
|
1121
|
+
const pwlessBtn = this.shadowRoot.getElementById("passwordless-btn");
|
|
1122
|
+
if (pwlessBtn) {
|
|
1123
|
+
pwlessBtn.addEventListener("click", () => this.showOverlay("passwordless-input"));
|
|
1124
|
+
}
|
|
1125
|
+
const passkeyBtn = this.shadowRoot.getElementById("passkey-btn");
|
|
1126
|
+
if (passkeyBtn) {
|
|
1127
|
+
passkeyBtn.addEventListener("click", () => this.onPasskeyClick());
|
|
1128
|
+
}
|
|
483
1129
|
}
|
|
484
1130
|
};
|
|
485
1131
|
|
|
@@ -982,6 +1628,8 @@ var Authon = class {
|
|
|
982
1628
|
providers = [];
|
|
983
1629
|
providerFlowModes = {};
|
|
984
1630
|
initialized = false;
|
|
1631
|
+
captchaEnabled = false;
|
|
1632
|
+
turnstileSiteKey = "";
|
|
985
1633
|
constructor(publishableKey, config) {
|
|
986
1634
|
this.publishableKey = publishableKey;
|
|
987
1635
|
this.config = {
|
|
@@ -1008,14 +1656,20 @@ var Authon = class {
|
|
|
1008
1656
|
await this.ensureInitialized();
|
|
1009
1657
|
this.getModal().open("signUp");
|
|
1010
1658
|
}
|
|
1659
|
+
/** Update theme at runtime without destroying form state */
|
|
1660
|
+
setTheme(theme) {
|
|
1661
|
+
this.getModal().setTheme(theme);
|
|
1662
|
+
}
|
|
1011
1663
|
async signInWithOAuth(provider, options) {
|
|
1012
1664
|
await this.ensureInitialized();
|
|
1013
1665
|
await this.startOAuthFlow(provider, options);
|
|
1014
1666
|
}
|
|
1015
|
-
async signInWithEmail(email, password) {
|
|
1667
|
+
async signInWithEmail(email, password, turnstileToken) {
|
|
1668
|
+
const body = { email, password };
|
|
1669
|
+
if (turnstileToken) body.turnstileToken = turnstileToken;
|
|
1016
1670
|
const res = await this.apiPost(
|
|
1017
1671
|
"/v1/auth/signin",
|
|
1018
|
-
|
|
1672
|
+
body
|
|
1019
1673
|
);
|
|
1020
1674
|
if (res.mfaRequired && res.mfaToken) {
|
|
1021
1675
|
this.emit("mfaRequired", res.mfaToken);
|
|
@@ -1309,6 +1963,14 @@ var Authon = class {
|
|
|
1309
1963
|
this.listeners.clear();
|
|
1310
1964
|
}
|
|
1311
1965
|
// ── Internal ──
|
|
1966
|
+
loadTurnstileScript() {
|
|
1967
|
+
if (typeof document === "undefined") return;
|
|
1968
|
+
if (document.querySelector('script[src*="challenges.cloudflare.com/turnstile"]')) return;
|
|
1969
|
+
const script = document.createElement("script");
|
|
1970
|
+
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
1971
|
+
script.async = true;
|
|
1972
|
+
document.head.appendChild(script);
|
|
1973
|
+
}
|
|
1312
1974
|
emit(event, ...args) {
|
|
1313
1975
|
this.listeners.get(event)?.forEach((fn) => fn(...args));
|
|
1314
1976
|
}
|
|
@@ -1320,6 +1982,11 @@ var Authon = class {
|
|
|
1320
1982
|
this.apiGet("/v1/auth/providers")
|
|
1321
1983
|
]);
|
|
1322
1984
|
this.branding = { ...branding, ...this.config.appearance };
|
|
1985
|
+
this.captchaEnabled = !!branding.captchaEnabled;
|
|
1986
|
+
this.turnstileSiteKey = branding.turnstileSiteKey || "";
|
|
1987
|
+
if (this.captchaEnabled && this.turnstileSiteKey) {
|
|
1988
|
+
this.loadTurnstileScript();
|
|
1989
|
+
}
|
|
1323
1990
|
this.providers = providersRes.providers;
|
|
1324
1991
|
this.providerFlowModes = {};
|
|
1325
1992
|
for (const provider of this.providers) {
|
|
@@ -1340,17 +2007,66 @@ var Authon = class {
|
|
|
1340
2007
|
theme: this.config.theme,
|
|
1341
2008
|
containerId: this.config.containerId,
|
|
1342
2009
|
branding: this.branding || void 0,
|
|
2010
|
+
captchaSiteKey: this.captchaEnabled ? this.turnstileSiteKey : void 0,
|
|
1343
2011
|
onProviderClick: (provider) => this.startOAuthFlow(provider),
|
|
1344
2012
|
onEmailSubmit: (email, password, isSignUp) => {
|
|
1345
2013
|
this.modal?.clearError();
|
|
1346
|
-
const
|
|
2014
|
+
const turnstileToken = this.modal?.getTurnstileToken?.() || void 0;
|
|
2015
|
+
const promise = isSignUp ? this.signUpWithEmail(email, password, { turnstileToken }) : this.signInWithEmail(email, password, turnstileToken);
|
|
1347
2016
|
promise.then(() => this.modal?.close()).catch((err) => {
|
|
2017
|
+
this.modal?.resetTurnstile?.();
|
|
1348
2018
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1349
2019
|
this.modal?.showError(msg || "Authentication failed");
|
|
1350
2020
|
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
1351
2021
|
});
|
|
1352
2022
|
},
|
|
1353
|
-
onClose: () => this.modal?.close()
|
|
2023
|
+
onClose: () => this.modal?.close(),
|
|
2024
|
+
onWeb3WalletSelect: async (walletId) => {
|
|
2025
|
+
const chain = walletId === "phantom" ? "solana" : "evm";
|
|
2026
|
+
try {
|
|
2027
|
+
this.modal?.showOverlay?.("web3-connecting");
|
|
2028
|
+
const address = await this.getWalletAddress(walletId);
|
|
2029
|
+
const { message } = await this.web3GetNonce(address, chain, walletId);
|
|
2030
|
+
const signature = await this.requestWalletSignature(walletId, message);
|
|
2031
|
+
await this.web3Verify(message, signature, address, chain, walletId);
|
|
2032
|
+
this.modal?.showWeb3Success(walletId, address);
|
|
2033
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
2034
|
+
} catch (err) {
|
|
2035
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
2036
|
+
}
|
|
2037
|
+
},
|
|
2038
|
+
onPasswordlessSubmit: async (email) => {
|
|
2039
|
+
try {
|
|
2040
|
+
const method = this.branding?.passwordlessMethod ?? "magic_link";
|
|
2041
|
+
if (method === "email_otp" || method === "both") {
|
|
2042
|
+
await this.sendEmailOtp(email);
|
|
2043
|
+
this.modal?.showOtpInput(email);
|
|
2044
|
+
} else {
|
|
2045
|
+
await this.sendMagicLink(email);
|
|
2046
|
+
this.modal?.showPasswordlessSent();
|
|
2047
|
+
}
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
2050
|
+
}
|
|
2051
|
+
},
|
|
2052
|
+
onOtpVerify: async (email, code) => {
|
|
2053
|
+
try {
|
|
2054
|
+
await this.verifyPasswordless({ email, code });
|
|
2055
|
+
this.modal?.close();
|
|
2056
|
+
} catch (err) {
|
|
2057
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
2058
|
+
}
|
|
2059
|
+
},
|
|
2060
|
+
onPasskeyClick: async () => {
|
|
2061
|
+
try {
|
|
2062
|
+
this.modal?.showOverlay?.("passkey-verifying");
|
|
2063
|
+
await this.authenticateWithPasskey();
|
|
2064
|
+
this.modal?.showPasskeySuccess();
|
|
2065
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
2066
|
+
} catch (err) {
|
|
2067
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
1354
2070
|
});
|
|
1355
2071
|
}
|
|
1356
2072
|
if (this.branding) this.modal.setBranding(this.branding);
|
|
@@ -1643,6 +2359,30 @@ var Authon = class {
|
|
|
1643
2359
|
});
|
|
1644
2360
|
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
1645
2361
|
}
|
|
2362
|
+
// ── Wallet helpers ──
|
|
2363
|
+
async getWalletAddress(walletId) {
|
|
2364
|
+
if (walletId === "phantom") {
|
|
2365
|
+
const provider2 = window.solana;
|
|
2366
|
+
if (!provider2?.isPhantom) throw new Error("Phantom wallet not detected. Please install it from phantom.app");
|
|
2367
|
+
const resp = await provider2.connect();
|
|
2368
|
+
return resp.publicKey.toString();
|
|
2369
|
+
}
|
|
2370
|
+
const provider = window.ethereum;
|
|
2371
|
+
if (!provider) throw new Error(`${walletId} wallet not detected. Please install it.`);
|
|
2372
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2373
|
+
return accounts[0];
|
|
2374
|
+
}
|
|
2375
|
+
async requestWalletSignature(walletId, message) {
|
|
2376
|
+
if (walletId === "phantom") {
|
|
2377
|
+
const provider2 = window.solana;
|
|
2378
|
+
const encoded = new TextEncoder().encode(message);
|
|
2379
|
+
const signed = await provider2.signMessage(encoded, "utf8");
|
|
2380
|
+
return Array.from(new Uint8Array(signed.signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2381
|
+
}
|
|
2382
|
+
const provider = window.ethereum;
|
|
2383
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2384
|
+
return provider.request({ method: "personal_sign", params: [message, accounts[0]] });
|
|
2385
|
+
}
|
|
1646
2386
|
// ── WebAuthn helpers ──
|
|
1647
2387
|
bufferToBase64url(buffer) {
|
|
1648
2388
|
const bytes = new Uint8Array(buffer);
|