@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.js
CHANGED
|
@@ -8,11 +8,46 @@ var AuthonMfaRequiredError = class extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
|
|
11
|
+
// ../shared/dist/index.js
|
|
12
|
+
var PROVIDER_DISPLAY_NAMES = {
|
|
13
|
+
google: "Google",
|
|
14
|
+
apple: "Apple",
|
|
15
|
+
kakao: "Kakao",
|
|
16
|
+
naver: "Naver",
|
|
17
|
+
facebook: "Facebook",
|
|
18
|
+
github: "GitHub",
|
|
19
|
+
discord: "Discord",
|
|
20
|
+
x: "X",
|
|
21
|
+
line: "LINE",
|
|
22
|
+
microsoft: "Microsoft"
|
|
23
|
+
};
|
|
24
|
+
var PROVIDER_COLORS = {
|
|
25
|
+
google: { bg: "#ffffff", text: "#1f1f1f" },
|
|
26
|
+
apple: { bg: "#000000", text: "#ffffff" },
|
|
27
|
+
kakao: { bg: "#FEE500", text: "#191919" },
|
|
28
|
+
naver: { bg: "#03C75A", text: "#ffffff" },
|
|
29
|
+
facebook: { bg: "#1877F2", text: "#ffffff" },
|
|
30
|
+
github: { bg: "#24292e", text: "#ffffff" },
|
|
31
|
+
discord: { bg: "#5865F2", text: "#ffffff" },
|
|
32
|
+
x: { bg: "#000000", text: "#ffffff" },
|
|
33
|
+
line: { bg: "#06C755", text: "#ffffff" },
|
|
34
|
+
microsoft: { bg: "#ffffff", text: "#1f1f1f" }
|
|
35
|
+
};
|
|
36
|
+
var DEFAULT_BRANDING = {
|
|
37
|
+
primaryColorStart: "#7c3aed",
|
|
38
|
+
primaryColorEnd: "#4f46e5",
|
|
39
|
+
lightBg: "#ffffff",
|
|
40
|
+
lightText: "#111827",
|
|
41
|
+
darkBg: "#0f172a",
|
|
42
|
+
darkText: "#f1f5f9",
|
|
43
|
+
borderRadius: 12,
|
|
44
|
+
showEmailPassword: true,
|
|
45
|
+
showDivider: true,
|
|
46
|
+
showSecuredBy: true,
|
|
47
|
+
locale: "en"
|
|
48
|
+
};
|
|
13
49
|
|
|
14
50
|
// src/providers.ts
|
|
15
|
-
import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES } from "@authon/shared";
|
|
16
51
|
var PROVIDER_ICONS = {
|
|
17
52
|
google: `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>`,
|
|
18
53
|
apple: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/></svg>`,
|
|
@@ -37,6 +72,30 @@ function getProviderButtonConfig(provider) {
|
|
|
37
72
|
}
|
|
38
73
|
|
|
39
74
|
// src/modal.ts
|
|
75
|
+
function hexToRgba(hex, alpha) {
|
|
76
|
+
const h = hex.replace("#", "");
|
|
77
|
+
const r = parseInt(h.substring(0, 2), 16);
|
|
78
|
+
const g = parseInt(h.substring(2, 4), 16);
|
|
79
|
+
const b = parseInt(h.substring(4, 6), 16);
|
|
80
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
81
|
+
}
|
|
82
|
+
var WALLET_OPTIONS = [
|
|
83
|
+
{ id: "pexus", name: "Pexus", color: "#7c3aed" },
|
|
84
|
+
{ id: "metamask", name: "MetaMask", color: "#f6851b" },
|
|
85
|
+
{ id: "phantom", name: "Phantom", color: "#ab9ff2" }
|
|
86
|
+
];
|
|
87
|
+
function walletIconSvg(id) {
|
|
88
|
+
switch (id) {
|
|
89
|
+
case "pexus":
|
|
90
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#7c3aed"/><path d="M7 8h4v8H7V8zm6 0h4v8h-4V8z" fill="#fff"/></svg>`;
|
|
91
|
+
case "metamask":
|
|
92
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#f6851b"/><path d="M17.2 4L12 8.5l1 .7L17.2 4zM6.8 4l5.1 5.3-1-0.6L6.8 4zM16 16.2l-1.4 2.1 3 .8.8-2.9h-2.4zM5.6 16.2l.9 2.8 3-.8-1.4-2h-2.5z" fill="#fff"/></svg>`;
|
|
93
|
+
case "phantom":
|
|
94
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#ab9ff2"/><circle cx="9" cy="11" r="1.5" fill="#fff"/><circle cx="15" cy="11" r="1.5" fill="#fff"/><path d="M6 12c0-3.3 2.7-6 6-6s6 2.7 6 6v2c0 1.1-.9 2-2 2H8c-1.1 0-2-.9-2-2v-2z" stroke="#fff" stroke-width="1.5" fill="none"/></svg>`;
|
|
95
|
+
default:
|
|
96
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="6" fill="#666"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="12">${id[0]?.toUpperCase() ?? "W"}</text></svg>`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
40
99
|
var ModalRenderer = class {
|
|
41
100
|
shadowRoot = null;
|
|
42
101
|
hostElement = null;
|
|
@@ -44,19 +103,43 @@ var ModalRenderer = class {
|
|
|
44
103
|
mode;
|
|
45
104
|
theme;
|
|
46
105
|
branding;
|
|
106
|
+
themeObserver = null;
|
|
107
|
+
mediaQueryListener = null;
|
|
47
108
|
enabledProviders = [];
|
|
48
109
|
currentView = "signIn";
|
|
49
110
|
onProviderClick;
|
|
50
111
|
onEmailSubmit;
|
|
51
112
|
onClose;
|
|
113
|
+
onWeb3WalletSelect;
|
|
114
|
+
onPasswordlessSubmit;
|
|
115
|
+
onOtpVerify;
|
|
116
|
+
onPasskeyClick;
|
|
52
117
|
escHandler = null;
|
|
118
|
+
// Overlay state
|
|
119
|
+
currentOverlay = "none";
|
|
120
|
+
selectedWallet = "";
|
|
121
|
+
overlayEmail = "";
|
|
122
|
+
overlayError = "";
|
|
123
|
+
// Turnstile CAPTCHA
|
|
124
|
+
captchaSiteKey = "";
|
|
125
|
+
turnstileWidgetId = null;
|
|
126
|
+
turnstileToken = "";
|
|
53
127
|
constructor(options) {
|
|
54
128
|
this.mode = options.mode;
|
|
55
129
|
this.theme = options.theme || "auto";
|
|
56
130
|
this.branding = { ...DEFAULT_BRANDING, ...options.branding };
|
|
131
|
+
this.captchaSiteKey = options.captchaSiteKey || "";
|
|
57
132
|
this.onProviderClick = options.onProviderClick;
|
|
58
133
|
this.onEmailSubmit = options.onEmailSubmit;
|
|
59
134
|
this.onClose = options.onClose;
|
|
135
|
+
this.onWeb3WalletSelect = options.onWeb3WalletSelect || (() => {
|
|
136
|
+
});
|
|
137
|
+
this.onPasswordlessSubmit = options.onPasswordlessSubmit || (() => {
|
|
138
|
+
});
|
|
139
|
+
this.onOtpVerify = options.onOtpVerify || (() => {
|
|
140
|
+
});
|
|
141
|
+
this.onPasskeyClick = options.onPasskeyClick || (() => {
|
|
142
|
+
});
|
|
60
143
|
if (options.mode === "embedded" && options.containerId) {
|
|
61
144
|
this.containerElement = document.getElementById(options.containerId);
|
|
62
145
|
}
|
|
@@ -69,17 +152,25 @@ var ModalRenderer = class {
|
|
|
69
152
|
}
|
|
70
153
|
open(view = "signIn") {
|
|
71
154
|
if (this.shadowRoot && this.hostElement) {
|
|
155
|
+
this.hideOverlay();
|
|
72
156
|
this.switchView(view);
|
|
73
157
|
} else {
|
|
74
158
|
this.currentView = view;
|
|
159
|
+
this.currentOverlay = "none";
|
|
75
160
|
this.render(view);
|
|
76
161
|
}
|
|
77
162
|
}
|
|
78
163
|
close() {
|
|
164
|
+
this.stopThemeObserver();
|
|
79
165
|
if (this.escHandler) {
|
|
80
166
|
document.removeEventListener("keydown", this.escHandler);
|
|
81
167
|
this.escHandler = null;
|
|
82
168
|
}
|
|
169
|
+
if (this.turnstileWidgetId !== null) {
|
|
170
|
+
window.turnstile?.remove(this.turnstileWidgetId);
|
|
171
|
+
this.turnstileWidgetId = null;
|
|
172
|
+
this.turnstileToken = "";
|
|
173
|
+
}
|
|
83
174
|
if (this.hostElement) {
|
|
84
175
|
this.hostElement.remove();
|
|
85
176
|
this.hostElement = null;
|
|
@@ -88,6 +179,55 @@ var ModalRenderer = class {
|
|
|
88
179
|
if (this.containerElement) {
|
|
89
180
|
this.containerElement.innerHTML = "";
|
|
90
181
|
}
|
|
182
|
+
this.currentOverlay = "none";
|
|
183
|
+
}
|
|
184
|
+
getTurnstileToken() {
|
|
185
|
+
return this.turnstileToken;
|
|
186
|
+
}
|
|
187
|
+
resetTurnstile() {
|
|
188
|
+
if (this.turnstileWidgetId !== null) {
|
|
189
|
+
window.turnstile?.reset(this.turnstileWidgetId);
|
|
190
|
+
this.turnstileToken = "";
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Update theme at runtime without destroying form state */
|
|
194
|
+
setTheme(theme) {
|
|
195
|
+
this.theme = theme;
|
|
196
|
+
this.updateThemeCSS();
|
|
197
|
+
if (theme === "auto") {
|
|
198
|
+
this.startThemeObserver();
|
|
199
|
+
} else {
|
|
200
|
+
this.stopThemeObserver();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
updateThemeCSS() {
|
|
204
|
+
if (!this.shadowRoot) return;
|
|
205
|
+
const styleEl = this.shadowRoot.getElementById("authon-theme-style");
|
|
206
|
+
if (styleEl) {
|
|
207
|
+
styleEl.textContent = this.buildCSS();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
startThemeObserver() {
|
|
211
|
+
this.stopThemeObserver();
|
|
212
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
213
|
+
this.themeObserver = new MutationObserver(() => this.updateThemeCSS());
|
|
214
|
+
this.themeObserver.observe(document.documentElement, {
|
|
215
|
+
attributes: true,
|
|
216
|
+
attributeFilter: ["data-theme", "class"]
|
|
217
|
+
});
|
|
218
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
219
|
+
this.mediaQueryListener = () => this.updateThemeCSS();
|
|
220
|
+
mq.addEventListener("change", this.mediaQueryListener);
|
|
221
|
+
}
|
|
222
|
+
stopThemeObserver() {
|
|
223
|
+
if (this.themeObserver) {
|
|
224
|
+
this.themeObserver.disconnect();
|
|
225
|
+
this.themeObserver = null;
|
|
226
|
+
}
|
|
227
|
+
if (this.mediaQueryListener) {
|
|
228
|
+
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", this.mediaQueryListener);
|
|
229
|
+
this.mediaQueryListener = null;
|
|
230
|
+
}
|
|
91
231
|
}
|
|
92
232
|
showError(message) {
|
|
93
233
|
if (!this.shadowRoot) return;
|
|
@@ -139,6 +279,47 @@ var ModalRenderer = class {
|
|
|
139
279
|
if (!this.shadowRoot) return;
|
|
140
280
|
this.shadowRoot.getElementById("authon-loading-overlay")?.remove();
|
|
141
281
|
}
|
|
282
|
+
// ── Flow Overlay Public API ──
|
|
283
|
+
showOverlay(overlay) {
|
|
284
|
+
this.currentOverlay = overlay;
|
|
285
|
+
this.overlayError = "";
|
|
286
|
+
this.renderOverlay();
|
|
287
|
+
}
|
|
288
|
+
hideOverlay() {
|
|
289
|
+
this.currentOverlay = "none";
|
|
290
|
+
this.overlayError = "";
|
|
291
|
+
if (!this.shadowRoot) return;
|
|
292
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
293
|
+
}
|
|
294
|
+
showWeb3Success(walletId, address) {
|
|
295
|
+
this.selectedWallet = walletId;
|
|
296
|
+
this.overlayError = "";
|
|
297
|
+
const truncated = address.length > 10 ? `${address.slice(0, 6)}...${address.slice(-4)}` : address;
|
|
298
|
+
this.currentOverlay = "web3-success";
|
|
299
|
+
this.renderOverlayWithData({ truncatedAddress: truncated, walletId });
|
|
300
|
+
}
|
|
301
|
+
showPasswordlessSent() {
|
|
302
|
+
this.overlayError = "";
|
|
303
|
+
this.currentOverlay = "passwordless-sent";
|
|
304
|
+
this.renderOverlay();
|
|
305
|
+
}
|
|
306
|
+
showOtpInput(email) {
|
|
307
|
+
this.overlayEmail = email;
|
|
308
|
+
this.overlayError = "";
|
|
309
|
+
this.currentOverlay = "otp-input";
|
|
310
|
+
this.renderOverlay();
|
|
311
|
+
}
|
|
312
|
+
showPasskeySuccess() {
|
|
313
|
+
this.overlayError = "";
|
|
314
|
+
this.currentOverlay = "passkey-success";
|
|
315
|
+
this.renderOverlay();
|
|
316
|
+
}
|
|
317
|
+
showOverlayError(message) {
|
|
318
|
+
this.overlayError = message;
|
|
319
|
+
if (this.currentOverlay !== "none") {
|
|
320
|
+
this.renderOverlay();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
142
323
|
// ── Smooth view switch (no flicker) ──
|
|
143
324
|
switchView(view) {
|
|
144
325
|
if (!this.shadowRoot || view === this.currentView) return;
|
|
@@ -169,13 +350,16 @@ var ModalRenderer = class {
|
|
|
169
350
|
this.shadowRoot.innerHTML = this.buildShell(view);
|
|
170
351
|
this.attachInnerEvents(view);
|
|
171
352
|
this.attachShellEvents();
|
|
353
|
+
if (this.theme === "auto") {
|
|
354
|
+
this.startThemeObserver();
|
|
355
|
+
}
|
|
172
356
|
}
|
|
173
357
|
// ── HTML builders ──
|
|
174
358
|
/** Shell = style + backdrop + modal-container (stable across view switches) */
|
|
175
359
|
buildShell(view) {
|
|
176
360
|
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
177
361
|
return `
|
|
178
|
-
<style>${this.buildCSS()}</style>
|
|
362
|
+
<style id="authon-theme-style">${this.buildCSS()}</style>
|
|
179
363
|
${popupWrapper}
|
|
180
364
|
<div class="modal-container" role="dialog" aria-modal="true">
|
|
181
365
|
<div id="modal-inner" class="modal-inner">
|
|
@@ -204,12 +388,43 @@ var ModalRenderer = class {
|
|
|
204
388
|
</button>`;
|
|
205
389
|
}).join("") : "";
|
|
206
390
|
const divider = showProviders && b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
|
|
391
|
+
const captchaContainer = this.captchaSiteKey ? '<div id="turnstile-container" style="display:flex;justify-content:center;margin:4px 0"></div>' : "";
|
|
207
392
|
const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
|
|
208
393
|
<input type="email" placeholder="Email address" name="email" required class="input" autocomplete="email" />
|
|
209
394
|
<input type="password" placeholder="Password" name="password" required class="input" autocomplete="${isSignUp ? "new-password" : "current-password"}" />
|
|
210
395
|
${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
|
|
396
|
+
${captchaContainer}
|
|
211
397
|
<button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
|
|
212
398
|
</form>` : "";
|
|
399
|
+
const hasMethodAbove = showProviders && this.enabledProviders.length > 0 || b.showEmailPassword !== false;
|
|
400
|
+
const hasMethodBelow = b.showWeb3 || b.showPasswordless || b.showPasskey;
|
|
401
|
+
const methodDivider = hasMethodAbove && hasMethodBelow ? `<div class="divider"><span>or</span></div>` : "";
|
|
402
|
+
const methodButtons = [];
|
|
403
|
+
if (b.showWeb3) {
|
|
404
|
+
methodButtons.push(`<button class="auth-method-btn web3-btn" id="web3-btn">
|
|
405
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
406
|
+
<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"/>
|
|
407
|
+
</svg>
|
|
408
|
+
<span>Connect Wallet</span>
|
|
409
|
+
</button>`);
|
|
410
|
+
}
|
|
411
|
+
if (b.showPasswordless) {
|
|
412
|
+
methodButtons.push(`<button class="auth-method-btn passwordless-btn" id="passwordless-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="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"/>
|
|
415
|
+
</svg>
|
|
416
|
+
<span>Continue with Magic Link</span>
|
|
417
|
+
</button>`);
|
|
418
|
+
}
|
|
419
|
+
if (b.showPasskey) {
|
|
420
|
+
methodButtons.push(`<button class="auth-method-btn passkey-btn" id="passkey-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
|
+
<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"/>
|
|
423
|
+
</svg>
|
|
424
|
+
<span>Sign in with Passkey</span>
|
|
425
|
+
</button>`);
|
|
426
|
+
}
|
|
427
|
+
const authMethods = methodButtons.length > 0 ? `<div class="auth-methods">${methodButtons.join("")}</div>` : "";
|
|
213
428
|
const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
|
|
214
429
|
${b.termsUrl ? `<a href="${b.termsUrl}" target="_blank">Terms of Service</a>` : ""}
|
|
215
430
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
@@ -228,14 +443,60 @@ var ModalRenderer = class {
|
|
|
228
443
|
${showProviders ? `<div class="providers">${providerButtons}</div>` : ""}
|
|
229
444
|
${divider}
|
|
230
445
|
${emailForm}
|
|
446
|
+
${methodDivider}
|
|
447
|
+
${authMethods}
|
|
231
448
|
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
232
449
|
${footer}
|
|
233
450
|
${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>` : ""}
|
|
234
451
|
`;
|
|
235
452
|
}
|
|
453
|
+
renderTurnstile() {
|
|
454
|
+
if (!this.captchaSiteKey || !this.shadowRoot) return;
|
|
455
|
+
const container = this.shadowRoot.getElementById("turnstile-container");
|
|
456
|
+
if (!container) return;
|
|
457
|
+
const w = window;
|
|
458
|
+
const tryRender = () => {
|
|
459
|
+
if (!w.turnstile || !container.isConnected) return;
|
|
460
|
+
const wrapper = document.createElement("div");
|
|
461
|
+
wrapper.style.display = "flex";
|
|
462
|
+
wrapper.style.justifyContent = "center";
|
|
463
|
+
document.body.appendChild(wrapper);
|
|
464
|
+
this.turnstileWidgetId = w.turnstile.render(wrapper, {
|
|
465
|
+
sitekey: this.captchaSiteKey,
|
|
466
|
+
callback: (token) => {
|
|
467
|
+
this.turnstileToken = token;
|
|
468
|
+
},
|
|
469
|
+
"expired-callback": () => {
|
|
470
|
+
this.turnstileToken = "";
|
|
471
|
+
},
|
|
472
|
+
"error-callback": () => {
|
|
473
|
+
this.turnstileToken = "";
|
|
474
|
+
},
|
|
475
|
+
theme: this.isDark() ? "dark" : "light",
|
|
476
|
+
size: "flexible"
|
|
477
|
+
});
|
|
478
|
+
container.appendChild(wrapper);
|
|
479
|
+
};
|
|
480
|
+
if (w.turnstile) {
|
|
481
|
+
tryRender();
|
|
482
|
+
} else {
|
|
483
|
+
const interval = setInterval(() => {
|
|
484
|
+
if (w.turnstile) {
|
|
485
|
+
clearInterval(interval);
|
|
486
|
+
tryRender();
|
|
487
|
+
}
|
|
488
|
+
}, 200);
|
|
489
|
+
setTimeout(() => clearInterval(interval), 1e4);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
236
492
|
isDark() {
|
|
237
493
|
if (this.theme === "dark") return true;
|
|
238
494
|
if (this.theme === "light") return false;
|
|
495
|
+
if (typeof document !== "undefined") {
|
|
496
|
+
const html = document.documentElement;
|
|
497
|
+
if (html.classList.contains("dark") || html.getAttribute("data-theme") === "dark") return true;
|
|
498
|
+
if (html.classList.contains("light") || html.getAttribute("data-theme") === "light") return false;
|
|
499
|
+
}
|
|
239
500
|
return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
240
501
|
}
|
|
241
502
|
buildCSS() {
|
|
@@ -259,6 +520,10 @@ var ModalRenderer = class {
|
|
|
259
520
|
--authon-border: ${borderColor};
|
|
260
521
|
--authon-divider: ${dividerColor};
|
|
261
522
|
--authon-input-bg: ${inputBg};
|
|
523
|
+
--authon-overlay-bg: ${hexToRgba(bg, 0.92)};
|
|
524
|
+
--authon-overlay-bg-solid: ${hexToRgba(bg, 0.97)};
|
|
525
|
+
--authon-backdrop-bg: rgba(0,0,0,${dark ? "0.7" : "0.5"});
|
|
526
|
+
--authon-shadow-opacity: ${dark ? "0.5" : "0.25"};
|
|
262
527
|
--authon-radius: ${b.borderRadius ?? 12}px;
|
|
263
528
|
--authon-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
264
529
|
font-family: var(--authon-font);
|
|
@@ -267,7 +532,7 @@ var ModalRenderer = class {
|
|
|
267
532
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
268
533
|
.backdrop {
|
|
269
534
|
position: fixed; inset: 0; z-index: 99998;
|
|
270
|
-
background:
|
|
535
|
+
background: var(--authon-backdrop-bg); backdrop-filter: blur(4px);
|
|
271
536
|
animation: fadeIn 0.2s ease;
|
|
272
537
|
}
|
|
273
538
|
.modal-container {
|
|
@@ -361,10 +626,154 @@ var ModalRenderer = class {
|
|
|
361
626
|
}
|
|
362
627
|
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
363
628
|
.secured-link:hover { text-decoration: underline; }
|
|
629
|
+
|
|
630
|
+
/* Auth method buttons */
|
|
631
|
+
.auth-methods { display: flex; flex-direction: column; gap: 8px; }
|
|
632
|
+
.auth-method-btn {
|
|
633
|
+
display: flex; align-items: center; justify-content: center; gap: 8px;
|
|
634
|
+
width: 100%; padding: 10px 16px;
|
|
635
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
636
|
+
font-size: 13px; font-weight: 500; cursor: pointer;
|
|
637
|
+
font-family: var(--authon-font); transition: opacity 0.15s, transform 0.1s;
|
|
638
|
+
}
|
|
639
|
+
.auth-method-btn:hover { opacity: 0.85; }
|
|
640
|
+
.auth-method-btn:active { transform: scale(0.98); }
|
|
641
|
+
/* Web3 -- purple */
|
|
642
|
+
.web3-btn {
|
|
643
|
+
background: ${dark ? "rgba(139,92,246,0.12)" : "rgba(139,92,246,0.08)"};
|
|
644
|
+
border: 1px solid ${dark ? "rgba(139,92,246,0.3)" : "rgba(139,92,246,0.25)"};
|
|
645
|
+
color: ${dark ? "#c4b5fd" : "#7c3aed"};
|
|
646
|
+
}
|
|
647
|
+
/* Passwordless -- cyan */
|
|
648
|
+
.passwordless-btn {
|
|
649
|
+
background: ${dark ? "rgba(6,182,212,0.12)" : "rgba(6,182,212,0.08)"};
|
|
650
|
+
border: 1px solid ${dark ? "rgba(6,182,212,0.3)" : "rgba(6,182,212,0.25)"};
|
|
651
|
+
color: ${dark ? "#67e8f9" : "#0891b2"};
|
|
652
|
+
}
|
|
653
|
+
/* Passkey -- amber */
|
|
654
|
+
.passkey-btn {
|
|
655
|
+
background: ${dark ? "rgba(245,158,11,0.12)" : "rgba(245,158,11,0.08)"};
|
|
656
|
+
border: 1px solid ${dark ? "rgba(245,158,11,0.3)" : "rgba(245,158,11,0.25)"};
|
|
657
|
+
color: ${dark ? "#fcd34d" : "#b45309"};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/* Flow overlay */
|
|
661
|
+
.flow-overlay {
|
|
662
|
+
position: absolute; inset: 0; z-index: 10;
|
|
663
|
+
background: var(--authon-overlay-bg-solid);
|
|
664
|
+
backdrop-filter: blur(2px);
|
|
665
|
+
border-radius: var(--authon-radius);
|
|
666
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
667
|
+
gap: 12px; padding: 24px;
|
|
668
|
+
animation: fadeIn 0.2s ease;
|
|
669
|
+
}
|
|
670
|
+
.flow-overlay .cancel-link {
|
|
671
|
+
font-size: 12px; color: var(--authon-dim); cursor: pointer; border: none;
|
|
672
|
+
background: none; font-family: var(--authon-font); margin-top: 4px;
|
|
673
|
+
}
|
|
674
|
+
.flow-overlay .cancel-link:hover { text-decoration: underline; }
|
|
675
|
+
.flow-overlay .overlay-title {
|
|
676
|
+
font-size: 14px; font-weight: 600; color: var(--authon-text); text-align: center;
|
|
677
|
+
}
|
|
678
|
+
.flow-overlay .overlay-subtitle {
|
|
679
|
+
font-size: 12px; color: var(--authon-muted); text-align: center;
|
|
680
|
+
}
|
|
681
|
+
.flow-overlay .overlay-error {
|
|
682
|
+
padding: 6px 12px; margin-top: 4px;
|
|
683
|
+
background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3);
|
|
684
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
685
|
+
font-size: 12px; color: #ef4444; text-align: center; width: 100%;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/* Wallet picker */
|
|
689
|
+
.wallet-picker { display: flex; flex-direction: column; gap: 8px; width: 100%; }
|
|
690
|
+
.wallet-btn {
|
|
691
|
+
display: flex; align-items: center; gap: 10px;
|
|
692
|
+
width: 100%; padding: 10px 14px;
|
|
693
|
+
background: ${dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)"};
|
|
694
|
+
border: 1px solid ${dark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};
|
|
695
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
696
|
+
font-size: 13px; font-weight: 500; color: var(--authon-text);
|
|
697
|
+
cursor: pointer; font-family: var(--authon-font);
|
|
698
|
+
transition: opacity 0.15s;
|
|
699
|
+
}
|
|
700
|
+
.wallet-btn:hover { opacity: 0.8; }
|
|
701
|
+
.wallet-btn .wallet-icon { display: flex; align-items: center; flex-shrink: 0; }
|
|
702
|
+
.wallet-btn .wallet-icon svg { border-radius: 6px; }
|
|
703
|
+
|
|
704
|
+
/* Passwordless email input in overlay */
|
|
705
|
+
.pwless-form { display: flex; flex-direction: column; gap: 10px; width: 100%; }
|
|
706
|
+
.pwless-submit {
|
|
707
|
+
width: 100%; padding: 10px;
|
|
708
|
+
background: linear-gradient(135deg, #06b6d4, #0891b2);
|
|
709
|
+
color: #fff; border: none; border-radius: calc(var(--authon-radius) * 0.5);
|
|
710
|
+
font-size: 13px; font-weight: 600; cursor: pointer;
|
|
711
|
+
font-family: var(--authon-font); transition: opacity 0.15s;
|
|
712
|
+
}
|
|
713
|
+
.pwless-submit:hover { opacity: 0.9; }
|
|
714
|
+
.pwless-submit:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
715
|
+
|
|
716
|
+
/* OTP input */
|
|
717
|
+
.otp-container { display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; }
|
|
718
|
+
.otp-inputs { display: flex; gap: 8px; justify-content: center; }
|
|
719
|
+
.otp-digit {
|
|
720
|
+
width: 40px; height: 48px; text-align: center;
|
|
721
|
+
font-size: 20px; font-weight: 600; font-family: var(--authon-font);
|
|
722
|
+
background: var(--authon-input-bg); color: var(--authon-text);
|
|
723
|
+
border: 1px solid var(--authon-border);
|
|
724
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
725
|
+
outline: none; transition: border-color 0.15s;
|
|
726
|
+
}
|
|
727
|
+
.otp-digit:focus {
|
|
728
|
+
border-color: var(--authon-primary-start);
|
|
729
|
+
box-shadow: 0 0 0 3px rgba(124,58,237,0.15);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/* Success check animation */
|
|
733
|
+
.success-check {
|
|
734
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
735
|
+
display: flex; align-items: center; justify-content: center;
|
|
736
|
+
}
|
|
737
|
+
.success-check svg path {
|
|
738
|
+
stroke-dasharray: 20;
|
|
739
|
+
stroke-dashoffset: 20;
|
|
740
|
+
animation: check-draw 0.4s ease-out 0.1s forwards;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/* Spinner */
|
|
744
|
+
.flow-spinner {
|
|
745
|
+
animation: spin 0.8s linear infinite;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/* Passkey verifying icon */
|
|
749
|
+
.passkey-icon-pulse {
|
|
750
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
751
|
+
display: flex; align-items: center; justify-content: center;
|
|
752
|
+
background: rgba(245,158,11,0.15);
|
|
753
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/* Wallet connecting icon */
|
|
757
|
+
.wallet-connecting-icon {
|
|
758
|
+
width: 48px; height: 48px; border-radius: 12px;
|
|
759
|
+
display: flex; align-items: center; justify-content: center;
|
|
760
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
761
|
+
}
|
|
762
|
+
.wallet-connecting-icon svg { border-radius: 6px; }
|
|
763
|
+
|
|
764
|
+
/* Address badge */
|
|
765
|
+
.address-badge {
|
|
766
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
767
|
+
padding: 2px 10px; border-radius: 6px;
|
|
768
|
+
background: ${dark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.03)"};
|
|
769
|
+
font-size: 11px; font-family: monospace; color: var(--authon-muted);
|
|
770
|
+
}
|
|
771
|
+
.address-badge .wallet-icon-sm svg { width: 16px; height: 16px; border-radius: 4px; }
|
|
772
|
+
|
|
364
773
|
/* Loading overlay */
|
|
365
774
|
#authon-loading-overlay {
|
|
366
775
|
position: absolute; inset: 0; z-index: 10;
|
|
367
|
-
background:
|
|
776
|
+
background: var(--authon-overlay-bg);
|
|
368
777
|
backdrop-filter: blur(2px);
|
|
369
778
|
border-radius: var(--authon-radius);
|
|
370
779
|
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
|
|
@@ -396,11 +805,229 @@ var ModalRenderer = class {
|
|
|
396
805
|
@keyframes blink { 0%,80%,100% { opacity: .2; } 40% { opacity: 1; } }
|
|
397
806
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
398
807
|
@keyframes slideIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } }
|
|
808
|
+
@keyframes check-draw { to { stroke-dashoffset: 0; } }
|
|
809
|
+
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }
|
|
399
810
|
${b.customCss || ""}
|
|
400
811
|
`;
|
|
401
812
|
}
|
|
813
|
+
// ── Flow Overlay Rendering ──
|
|
814
|
+
renderOverlay() {
|
|
815
|
+
this.renderOverlayWithData({});
|
|
816
|
+
}
|
|
817
|
+
renderOverlayWithData(data) {
|
|
818
|
+
if (!this.shadowRoot) return;
|
|
819
|
+
const container = this.shadowRoot.querySelector(".modal-container");
|
|
820
|
+
if (!container) return;
|
|
821
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
822
|
+
if (this.currentOverlay === "none") return;
|
|
823
|
+
const overlay = document.createElement("div");
|
|
824
|
+
overlay.id = "flow-overlay";
|
|
825
|
+
overlay.className = "flow-overlay";
|
|
826
|
+
overlay.innerHTML = this.buildOverlayContent(data);
|
|
827
|
+
container.appendChild(overlay);
|
|
828
|
+
this.attachOverlayEvents(overlay);
|
|
829
|
+
}
|
|
830
|
+
buildOverlayContent(data) {
|
|
831
|
+
const dark = this.isDark();
|
|
832
|
+
const errorHtml = this.overlayError ? `<div class="overlay-error">${this.escapeHtml(this.overlayError)}</div>` : "";
|
|
833
|
+
switch (this.currentOverlay) {
|
|
834
|
+
case "web3-picker": {
|
|
835
|
+
const walletItems = WALLET_OPTIONS.map(
|
|
836
|
+
(w) => `<button class="wallet-btn" data-wallet="${w.id}">
|
|
837
|
+
<span class="wallet-icon">${walletIconSvg(w.id)}</span>
|
|
838
|
+
<span>${w.name}</span>
|
|
839
|
+
</button>`
|
|
840
|
+
).join("");
|
|
841
|
+
return `
|
|
842
|
+
<div class="overlay-title" style="margin-bottom: 4px;">Select Wallet</div>
|
|
843
|
+
<div class="wallet-picker">${walletItems}</div>
|
|
844
|
+
${errorHtml}
|
|
845
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
846
|
+
`;
|
|
847
|
+
}
|
|
848
|
+
case "web3-connecting": {
|
|
849
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === this.selectedWallet);
|
|
850
|
+
const walletName = wallet?.name ?? this.selectedWallet;
|
|
851
|
+
return `
|
|
852
|
+
<div class="wallet-connecting-icon">${walletIconSvg(this.selectedWallet)}</div>
|
|
853
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
854
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
855
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" opacity="0.25"/>
|
|
856
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" stroke-linecap="round"/>
|
|
857
|
+
</svg>
|
|
858
|
+
<span class="overlay-subtitle">Connecting ${this.escapeHtml(walletName)}...</span>
|
|
859
|
+
</div>
|
|
860
|
+
${errorHtml}
|
|
861
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
862
|
+
`;
|
|
863
|
+
}
|
|
864
|
+
case "web3-success": {
|
|
865
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === (data.walletId || this.selectedWallet));
|
|
866
|
+
const walletColor = wallet?.color ?? "#8b5cf6";
|
|
867
|
+
const truncAddr = data.truncatedAddress || "0x...";
|
|
868
|
+
return `
|
|
869
|
+
<div class="success-check" style="background:linear-gradient(135deg, ${walletColor}, ${walletColor})">
|
|
870
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
871
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
872
|
+
</svg>
|
|
873
|
+
</div>
|
|
874
|
+
<div class="overlay-title">Wallet Connected</div>
|
|
875
|
+
<div class="address-badge">
|
|
876
|
+
<span class="wallet-icon-sm">${walletIconSvg(data.walletId || this.selectedWallet)}</span>
|
|
877
|
+
<span>${this.escapeHtml(truncAddr)}</span>
|
|
878
|
+
</div>
|
|
879
|
+
`;
|
|
880
|
+
}
|
|
881
|
+
case "passwordless-input": {
|
|
882
|
+
return `
|
|
883
|
+
<div class="overlay-title">Enter your email</div>
|
|
884
|
+
<div class="pwless-form">
|
|
885
|
+
<input type="email" placeholder="you@example.com" class="input" id="pwless-email" autocomplete="email" />
|
|
886
|
+
<button class="pwless-submit" id="pwless-submit-btn">Send Magic Link</button>
|
|
887
|
+
</div>
|
|
888
|
+
${errorHtml}
|
|
889
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
890
|
+
`;
|
|
891
|
+
}
|
|
892
|
+
case "passwordless-sending": {
|
|
893
|
+
return `
|
|
894
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
895
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" opacity="0.25"/>
|
|
896
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" stroke-linecap="round"/>
|
|
897
|
+
</svg>
|
|
898
|
+
<span class="overlay-subtitle">Sending magic link...</span>
|
|
899
|
+
`;
|
|
900
|
+
}
|
|
901
|
+
case "passwordless-sent": {
|
|
902
|
+
return `
|
|
903
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
904
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
905
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
906
|
+
</svg>
|
|
907
|
+
</div>
|
|
908
|
+
<div class="overlay-title">Magic link sent!</div>
|
|
909
|
+
<span class="overlay-subtitle">Check your email inbox</span>
|
|
910
|
+
`;
|
|
911
|
+
}
|
|
912
|
+
case "otp-input": {
|
|
913
|
+
const digitInputs = Array.from(
|
|
914
|
+
{ length: 6 },
|
|
915
|
+
(_, i) => `<input type="text" inputmode="numeric" maxlength="1" class="otp-digit" data-idx="${i}" autocomplete="one-time-code" />`
|
|
916
|
+
).join("");
|
|
917
|
+
return `
|
|
918
|
+
<div class="otp-container">
|
|
919
|
+
<div class="overlay-title">Enter verification code</div>
|
|
920
|
+
<span class="overlay-subtitle">6-digit code sent to ${this.escapeHtml(this.overlayEmail)}</span>
|
|
921
|
+
<div class="otp-inputs">${digitInputs}</div>
|
|
922
|
+
${errorHtml}
|
|
923
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
924
|
+
</div>
|
|
925
|
+
`;
|
|
926
|
+
}
|
|
927
|
+
case "passkey-verifying": {
|
|
928
|
+
return `
|
|
929
|
+
<div class="passkey-icon-pulse">
|
|
930
|
+
<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">
|
|
931
|
+
<circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/>
|
|
932
|
+
<path d="M21.7 13.3 19 11"/><path d="m21 15-2.5-1.5"/><path d="m17 17 2.5-1.5"/>
|
|
933
|
+
<path d="M22 9v6a1 1 0 0 1-1 1h-.5"/><circle cx="18" cy="9" r="3"/>
|
|
934
|
+
</svg>
|
|
935
|
+
</div>
|
|
936
|
+
<span class="overlay-subtitle">Verifying identity...</span>
|
|
937
|
+
${errorHtml}
|
|
938
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
939
|
+
`;
|
|
940
|
+
}
|
|
941
|
+
case "passkey-success": {
|
|
942
|
+
return `
|
|
943
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
944
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
945
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
946
|
+
</svg>
|
|
947
|
+
</div>
|
|
948
|
+
<div class="overlay-title">Identity verified!</div>
|
|
949
|
+
`;
|
|
950
|
+
}
|
|
951
|
+
default:
|
|
952
|
+
return "";
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
attachOverlayEvents(overlay) {
|
|
956
|
+
if (!this.shadowRoot) return;
|
|
957
|
+
const cancelBtn = overlay.querySelector("#overlay-cancel");
|
|
958
|
+
if (cancelBtn) {
|
|
959
|
+
cancelBtn.addEventListener("click", () => this.hideOverlay());
|
|
960
|
+
}
|
|
961
|
+
overlay.querySelectorAll(".wallet-btn").forEach((btn) => {
|
|
962
|
+
btn.addEventListener("click", () => {
|
|
963
|
+
const walletId = btn.dataset.wallet;
|
|
964
|
+
if (walletId) {
|
|
965
|
+
this.selectedWallet = walletId;
|
|
966
|
+
this.onWeb3WalletSelect(walletId);
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
});
|
|
970
|
+
const pwlessSubmit = overlay.querySelector("#pwless-submit-btn");
|
|
971
|
+
const pwlessEmail = overlay.querySelector("#pwless-email");
|
|
972
|
+
if (pwlessSubmit && pwlessEmail) {
|
|
973
|
+
setTimeout(() => pwlessEmail.focus(), 50);
|
|
974
|
+
const submitHandler = () => {
|
|
975
|
+
const email = pwlessEmail.value.trim();
|
|
976
|
+
if (!email) return;
|
|
977
|
+
this.overlayEmail = email;
|
|
978
|
+
this.showOverlay("passwordless-sending");
|
|
979
|
+
this.onPasswordlessSubmit(email);
|
|
980
|
+
};
|
|
981
|
+
pwlessSubmit.addEventListener("click", submitHandler);
|
|
982
|
+
pwlessEmail.addEventListener("keydown", (e) => {
|
|
983
|
+
if (e.key === "Enter") {
|
|
984
|
+
e.preventDefault();
|
|
985
|
+
submitHandler();
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
const otpDigits = overlay.querySelectorAll(".otp-digit");
|
|
990
|
+
if (otpDigits.length === 6) {
|
|
991
|
+
setTimeout(() => otpDigits[0].focus(), 50);
|
|
992
|
+
otpDigits.forEach((digit, idx) => {
|
|
993
|
+
digit.addEventListener("input", () => {
|
|
994
|
+
const val = digit.value.replace(/\D/g, "");
|
|
995
|
+
digit.value = val.slice(0, 1);
|
|
996
|
+
if (val && idx < 5) {
|
|
997
|
+
otpDigits[idx + 1].focus();
|
|
998
|
+
}
|
|
999
|
+
const code = Array.from(otpDigits).map((d) => d.value).join("");
|
|
1000
|
+
if (code.length === 6) {
|
|
1001
|
+
this.onOtpVerify(this.overlayEmail, code);
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
digit.addEventListener("keydown", (e) => {
|
|
1005
|
+
if (e.key === "Backspace" && !digit.value && idx > 0) {
|
|
1006
|
+
otpDigits[idx - 1].focus();
|
|
1007
|
+
otpDigits[idx - 1].value = "";
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
digit.addEventListener("paste", (e) => {
|
|
1011
|
+
e.preventDefault();
|
|
1012
|
+
const pasted = (e.clipboardData?.getData("text") ?? "").replace(/\D/g, "").slice(0, 6);
|
|
1013
|
+
if (pasted.length === 0) return;
|
|
1014
|
+
for (let i = 0; i < 6; i++) {
|
|
1015
|
+
otpDigits[i].value = pasted[i] || "";
|
|
1016
|
+
}
|
|
1017
|
+
const lastIdx = Math.min(pasted.length, 5);
|
|
1018
|
+
otpDigits[lastIdx].focus();
|
|
1019
|
+
if (pasted.length === 6) {
|
|
1020
|
+
this.onOtpVerify(this.overlayEmail, pasted);
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
escapeHtml(str) {
|
|
1027
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1028
|
+
}
|
|
402
1029
|
// ── Event binding ──
|
|
403
|
-
/** Attach events to shell elements (backdrop, ESC)
|
|
1030
|
+
/** Attach events to shell elements (backdrop, ESC) -- called once */
|
|
404
1031
|
attachShellEvents() {
|
|
405
1032
|
if (!this.shadowRoot) return;
|
|
406
1033
|
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
@@ -412,12 +1039,18 @@ var ModalRenderer = class {
|
|
|
412
1039
|
}
|
|
413
1040
|
if (this.mode === "popup") {
|
|
414
1041
|
this.escHandler = (e) => {
|
|
415
|
-
if (e.key === "Escape")
|
|
1042
|
+
if (e.key === "Escape") {
|
|
1043
|
+
if (this.currentOverlay !== "none") {
|
|
1044
|
+
this.hideOverlay();
|
|
1045
|
+
} else {
|
|
1046
|
+
this.onClose();
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
416
1049
|
};
|
|
417
1050
|
document.addEventListener("keydown", this.escHandler);
|
|
418
1051
|
}
|
|
419
1052
|
}
|
|
420
|
-
/** Attach events to inner content (buttons, form, switch link)
|
|
1053
|
+
/** Attach events to inner content (buttons, form, switch link) -- called on each view */
|
|
421
1054
|
attachInnerEvents(view) {
|
|
422
1055
|
if (!this.shadowRoot) return;
|
|
423
1056
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
@@ -438,6 +1071,7 @@ var ModalRenderer = class {
|
|
|
438
1071
|
);
|
|
439
1072
|
});
|
|
440
1073
|
}
|
|
1074
|
+
this.renderTurnstile();
|
|
441
1075
|
const backBtn = this.shadowRoot.getElementById("back-btn");
|
|
442
1076
|
if (backBtn) {
|
|
443
1077
|
backBtn.addEventListener("click", () => {
|
|
@@ -451,6 +1085,18 @@ var ModalRenderer = class {
|
|
|
451
1085
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
452
1086
|
});
|
|
453
1087
|
}
|
|
1088
|
+
const web3Btn = this.shadowRoot.getElementById("web3-btn");
|
|
1089
|
+
if (web3Btn) {
|
|
1090
|
+
web3Btn.addEventListener("click", () => this.showOverlay("web3-picker"));
|
|
1091
|
+
}
|
|
1092
|
+
const pwlessBtn = this.shadowRoot.getElementById("passwordless-btn");
|
|
1093
|
+
if (pwlessBtn) {
|
|
1094
|
+
pwlessBtn.addEventListener("click", () => this.showOverlay("passwordless-input"));
|
|
1095
|
+
}
|
|
1096
|
+
const passkeyBtn = this.shadowRoot.getElementById("passkey-btn");
|
|
1097
|
+
if (passkeyBtn) {
|
|
1098
|
+
passkeyBtn.addEventListener("click", () => this.onPasskeyClick());
|
|
1099
|
+
}
|
|
454
1100
|
}
|
|
455
1101
|
};
|
|
456
1102
|
|
|
@@ -953,6 +1599,8 @@ var Authon = class {
|
|
|
953
1599
|
providers = [];
|
|
954
1600
|
providerFlowModes = {};
|
|
955
1601
|
initialized = false;
|
|
1602
|
+
captchaEnabled = false;
|
|
1603
|
+
turnstileSiteKey = "";
|
|
956
1604
|
constructor(publishableKey, config) {
|
|
957
1605
|
this.publishableKey = publishableKey;
|
|
958
1606
|
this.config = {
|
|
@@ -979,14 +1627,20 @@ var Authon = class {
|
|
|
979
1627
|
await this.ensureInitialized();
|
|
980
1628
|
this.getModal().open("signUp");
|
|
981
1629
|
}
|
|
1630
|
+
/** Update theme at runtime without destroying form state */
|
|
1631
|
+
setTheme(theme) {
|
|
1632
|
+
this.getModal().setTheme(theme);
|
|
1633
|
+
}
|
|
982
1634
|
async signInWithOAuth(provider, options) {
|
|
983
1635
|
await this.ensureInitialized();
|
|
984
1636
|
await this.startOAuthFlow(provider, options);
|
|
985
1637
|
}
|
|
986
|
-
async signInWithEmail(email, password) {
|
|
1638
|
+
async signInWithEmail(email, password, turnstileToken) {
|
|
1639
|
+
const body = { email, password };
|
|
1640
|
+
if (turnstileToken) body.turnstileToken = turnstileToken;
|
|
987
1641
|
const res = await this.apiPost(
|
|
988
1642
|
"/v1/auth/signin",
|
|
989
|
-
|
|
1643
|
+
body
|
|
990
1644
|
);
|
|
991
1645
|
if (res.mfaRequired && res.mfaToken) {
|
|
992
1646
|
this.emit("mfaRequired", res.mfaToken);
|
|
@@ -1280,6 +1934,14 @@ var Authon = class {
|
|
|
1280
1934
|
this.listeners.clear();
|
|
1281
1935
|
}
|
|
1282
1936
|
// ── Internal ──
|
|
1937
|
+
loadTurnstileScript() {
|
|
1938
|
+
if (typeof document === "undefined") return;
|
|
1939
|
+
if (document.querySelector('script[src*="challenges.cloudflare.com/turnstile"]')) return;
|
|
1940
|
+
const script = document.createElement("script");
|
|
1941
|
+
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
1942
|
+
script.async = true;
|
|
1943
|
+
document.head.appendChild(script);
|
|
1944
|
+
}
|
|
1283
1945
|
emit(event, ...args) {
|
|
1284
1946
|
this.listeners.get(event)?.forEach((fn) => fn(...args));
|
|
1285
1947
|
}
|
|
@@ -1291,6 +1953,11 @@ var Authon = class {
|
|
|
1291
1953
|
this.apiGet("/v1/auth/providers")
|
|
1292
1954
|
]);
|
|
1293
1955
|
this.branding = { ...branding, ...this.config.appearance };
|
|
1956
|
+
this.captchaEnabled = !!branding.captchaEnabled;
|
|
1957
|
+
this.turnstileSiteKey = branding.turnstileSiteKey || "";
|
|
1958
|
+
if (this.captchaEnabled && this.turnstileSiteKey) {
|
|
1959
|
+
this.loadTurnstileScript();
|
|
1960
|
+
}
|
|
1294
1961
|
this.providers = providersRes.providers;
|
|
1295
1962
|
this.providerFlowModes = {};
|
|
1296
1963
|
for (const provider of this.providers) {
|
|
@@ -1311,17 +1978,66 @@ var Authon = class {
|
|
|
1311
1978
|
theme: this.config.theme,
|
|
1312
1979
|
containerId: this.config.containerId,
|
|
1313
1980
|
branding: this.branding || void 0,
|
|
1981
|
+
captchaSiteKey: this.captchaEnabled ? this.turnstileSiteKey : void 0,
|
|
1314
1982
|
onProviderClick: (provider) => this.startOAuthFlow(provider),
|
|
1315
1983
|
onEmailSubmit: (email, password, isSignUp) => {
|
|
1316
1984
|
this.modal?.clearError();
|
|
1317
|
-
const
|
|
1985
|
+
const turnstileToken = this.modal?.getTurnstileToken?.() || void 0;
|
|
1986
|
+
const promise = isSignUp ? this.signUpWithEmail(email, password, { turnstileToken }) : this.signInWithEmail(email, password, turnstileToken);
|
|
1318
1987
|
promise.then(() => this.modal?.close()).catch((err) => {
|
|
1988
|
+
this.modal?.resetTurnstile?.();
|
|
1319
1989
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1320
1990
|
this.modal?.showError(msg || "Authentication failed");
|
|
1321
1991
|
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
1322
1992
|
});
|
|
1323
1993
|
},
|
|
1324
|
-
onClose: () => this.modal?.close()
|
|
1994
|
+
onClose: () => this.modal?.close(),
|
|
1995
|
+
onWeb3WalletSelect: async (walletId) => {
|
|
1996
|
+
const chain = walletId === "phantom" ? "solana" : "evm";
|
|
1997
|
+
try {
|
|
1998
|
+
this.modal?.showOverlay?.("web3-connecting");
|
|
1999
|
+
const address = await this.getWalletAddress(walletId);
|
|
2000
|
+
const { message } = await this.web3GetNonce(address, chain, walletId);
|
|
2001
|
+
const signature = await this.requestWalletSignature(walletId, message);
|
|
2002
|
+
await this.web3Verify(message, signature, address, chain, walletId);
|
|
2003
|
+
this.modal?.showWeb3Success(walletId, address);
|
|
2004
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
2005
|
+
} catch (err) {
|
|
2006
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
2007
|
+
}
|
|
2008
|
+
},
|
|
2009
|
+
onPasswordlessSubmit: async (email) => {
|
|
2010
|
+
try {
|
|
2011
|
+
const method = this.branding?.passwordlessMethod ?? "magic_link";
|
|
2012
|
+
if (method === "email_otp" || method === "both") {
|
|
2013
|
+
await this.sendEmailOtp(email);
|
|
2014
|
+
this.modal?.showOtpInput(email);
|
|
2015
|
+
} else {
|
|
2016
|
+
await this.sendMagicLink(email);
|
|
2017
|
+
this.modal?.showPasswordlessSent();
|
|
2018
|
+
}
|
|
2019
|
+
} catch (err) {
|
|
2020
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
2021
|
+
}
|
|
2022
|
+
},
|
|
2023
|
+
onOtpVerify: async (email, code) => {
|
|
2024
|
+
try {
|
|
2025
|
+
await this.verifyPasswordless({ email, code });
|
|
2026
|
+
this.modal?.close();
|
|
2027
|
+
} catch (err) {
|
|
2028
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
2029
|
+
}
|
|
2030
|
+
},
|
|
2031
|
+
onPasskeyClick: async () => {
|
|
2032
|
+
try {
|
|
2033
|
+
this.modal?.showOverlay?.("passkey-verifying");
|
|
2034
|
+
await this.authenticateWithPasskey();
|
|
2035
|
+
this.modal?.showPasskeySuccess();
|
|
2036
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
2037
|
+
} catch (err) {
|
|
2038
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1325
2041
|
});
|
|
1326
2042
|
}
|
|
1327
2043
|
if (this.branding) this.modal.setBranding(this.branding);
|
|
@@ -1614,6 +2330,30 @@ var Authon = class {
|
|
|
1614
2330
|
});
|
|
1615
2331
|
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
1616
2332
|
}
|
|
2333
|
+
// ── Wallet helpers ──
|
|
2334
|
+
async getWalletAddress(walletId) {
|
|
2335
|
+
if (walletId === "phantom") {
|
|
2336
|
+
const provider2 = window.solana;
|
|
2337
|
+
if (!provider2?.isPhantom) throw new Error("Phantom wallet not detected. Please install it from phantom.app");
|
|
2338
|
+
const resp = await provider2.connect();
|
|
2339
|
+
return resp.publicKey.toString();
|
|
2340
|
+
}
|
|
2341
|
+
const provider = window.ethereum;
|
|
2342
|
+
if (!provider) throw new Error(`${walletId} wallet not detected. Please install it.`);
|
|
2343
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2344
|
+
return accounts[0];
|
|
2345
|
+
}
|
|
2346
|
+
async requestWalletSignature(walletId, message) {
|
|
2347
|
+
if (walletId === "phantom") {
|
|
2348
|
+
const provider2 = window.solana;
|
|
2349
|
+
const encoded = new TextEncoder().encode(message);
|
|
2350
|
+
const signed = await provider2.signMessage(encoded, "utf8");
|
|
2351
|
+
return Array.from(new Uint8Array(signed.signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2352
|
+
}
|
|
2353
|
+
const provider = window.ethereum;
|
|
2354
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2355
|
+
return provider.request({ method: "personal_sign", params: [message, accounts[0]] });
|
|
2356
|
+
}
|
|
1617
2357
|
// ── WebAuthn helpers ──
|
|
1618
2358
|
bufferToBase64url(buffer) {
|
|
1619
2359
|
const bytes = new Uint8Array(buffer);
|