@authon/js 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +99 -0
- package/README.md +249 -59
- package/dist/index.cjs +1451 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -2
- package/dist/index.d.ts +82 -2
- package/dist/index.js +1445 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -21,15 +21,62 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Authon: () => Authon,
|
|
24
|
+
AuthonMfaRequiredError: () => AuthonMfaRequiredError,
|
|
25
|
+
generateQrSvg: () => generateQrSvg,
|
|
24
26
|
getProviderButtonConfig: () => getProviderButtonConfig
|
|
25
27
|
});
|
|
26
28
|
module.exports = __toCommonJS(index_exports);
|
|
27
29
|
|
|
28
|
-
// src/
|
|
29
|
-
var
|
|
30
|
+
// src/types.ts
|
|
31
|
+
var AuthonMfaRequiredError = class extends Error {
|
|
32
|
+
mfaToken;
|
|
33
|
+
constructor(mfaToken) {
|
|
34
|
+
super("MFA verification required");
|
|
35
|
+
this.name = "AuthonMfaRequiredError";
|
|
36
|
+
this.mfaToken = mfaToken;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
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
|
+
};
|
|
30
78
|
|
|
31
79
|
// src/providers.ts
|
|
32
|
-
var import_shared = require("@authon/shared");
|
|
33
80
|
var PROVIDER_ICONS = {
|
|
34
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>`,
|
|
35
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>`,
|
|
@@ -43,10 +90,10 @@ var PROVIDER_ICONS = {
|
|
|
43
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>`
|
|
44
91
|
};
|
|
45
92
|
function getProviderButtonConfig(provider) {
|
|
46
|
-
const colors =
|
|
93
|
+
const colors = PROVIDER_COLORS[provider];
|
|
47
94
|
return {
|
|
48
95
|
provider,
|
|
49
|
-
label: `Continue with ${
|
|
96
|
+
label: `Continue with ${PROVIDER_DISPLAY_NAMES[provider]}`,
|
|
50
97
|
bgColor: colors.bg,
|
|
51
98
|
textColor: colors.text,
|
|
52
99
|
iconSvg: PROVIDER_ICONS[provider]
|
|
@@ -54,6 +101,30 @@ function getProviderButtonConfig(provider) {
|
|
|
54
101
|
}
|
|
55
102
|
|
|
56
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
|
+
}
|
|
57
128
|
var ModalRenderer = class {
|
|
58
129
|
shadowRoot = null;
|
|
59
130
|
hostElement = null;
|
|
@@ -61,19 +132,38 @@ var ModalRenderer = class {
|
|
|
61
132
|
mode;
|
|
62
133
|
theme;
|
|
63
134
|
branding;
|
|
135
|
+
themeObserver = null;
|
|
136
|
+
mediaQueryListener = null;
|
|
64
137
|
enabledProviders = [];
|
|
65
138
|
currentView = "signIn";
|
|
66
139
|
onProviderClick;
|
|
67
140
|
onEmailSubmit;
|
|
68
141
|
onClose;
|
|
142
|
+
onWeb3WalletSelect;
|
|
143
|
+
onPasswordlessSubmit;
|
|
144
|
+
onOtpVerify;
|
|
145
|
+
onPasskeyClick;
|
|
69
146
|
escHandler = null;
|
|
147
|
+
// Overlay state
|
|
148
|
+
currentOverlay = "none";
|
|
149
|
+
selectedWallet = "";
|
|
150
|
+
overlayEmail = "";
|
|
151
|
+
overlayError = "";
|
|
70
152
|
constructor(options) {
|
|
71
153
|
this.mode = options.mode;
|
|
72
154
|
this.theme = options.theme || "auto";
|
|
73
|
-
this.branding = { ...
|
|
155
|
+
this.branding = { ...DEFAULT_BRANDING, ...options.branding };
|
|
74
156
|
this.onProviderClick = options.onProviderClick;
|
|
75
157
|
this.onEmailSubmit = options.onEmailSubmit;
|
|
76
158
|
this.onClose = options.onClose;
|
|
159
|
+
this.onWeb3WalletSelect = options.onWeb3WalletSelect || (() => {
|
|
160
|
+
});
|
|
161
|
+
this.onPasswordlessSubmit = options.onPasswordlessSubmit || (() => {
|
|
162
|
+
});
|
|
163
|
+
this.onOtpVerify = options.onOtpVerify || (() => {
|
|
164
|
+
});
|
|
165
|
+
this.onPasskeyClick = options.onPasskeyClick || (() => {
|
|
166
|
+
});
|
|
77
167
|
if (options.mode === "embedded" && options.containerId) {
|
|
78
168
|
this.containerElement = document.getElementById(options.containerId);
|
|
79
169
|
}
|
|
@@ -82,17 +172,20 @@ var ModalRenderer = class {
|
|
|
82
172
|
this.enabledProviders = providers;
|
|
83
173
|
}
|
|
84
174
|
setBranding(branding) {
|
|
85
|
-
this.branding = { ...
|
|
175
|
+
this.branding = { ...DEFAULT_BRANDING, ...branding };
|
|
86
176
|
}
|
|
87
177
|
open(view = "signIn") {
|
|
88
178
|
if (this.shadowRoot && this.hostElement) {
|
|
179
|
+
this.hideOverlay();
|
|
89
180
|
this.switchView(view);
|
|
90
181
|
} else {
|
|
91
182
|
this.currentView = view;
|
|
183
|
+
this.currentOverlay = "none";
|
|
92
184
|
this.render(view);
|
|
93
185
|
}
|
|
94
186
|
}
|
|
95
187
|
close() {
|
|
188
|
+
this.stopThemeObserver();
|
|
96
189
|
if (this.escHandler) {
|
|
97
190
|
document.removeEventListener("keydown", this.escHandler);
|
|
98
191
|
this.escHandler = null;
|
|
@@ -105,6 +198,46 @@ var ModalRenderer = class {
|
|
|
105
198
|
if (this.containerElement) {
|
|
106
199
|
this.containerElement.innerHTML = "";
|
|
107
200
|
}
|
|
201
|
+
this.currentOverlay = "none";
|
|
202
|
+
}
|
|
203
|
+
/** Update theme at runtime without destroying form state */
|
|
204
|
+
setTheme(theme) {
|
|
205
|
+
this.theme = theme;
|
|
206
|
+
this.updateThemeCSS();
|
|
207
|
+
if (theme === "auto") {
|
|
208
|
+
this.startThemeObserver();
|
|
209
|
+
} else {
|
|
210
|
+
this.stopThemeObserver();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
updateThemeCSS() {
|
|
214
|
+
if (!this.shadowRoot) return;
|
|
215
|
+
const styleEl = this.shadowRoot.getElementById("authon-theme-style");
|
|
216
|
+
if (styleEl) {
|
|
217
|
+
styleEl.textContent = this.buildCSS();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
startThemeObserver() {
|
|
221
|
+
this.stopThemeObserver();
|
|
222
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
223
|
+
this.themeObserver = new MutationObserver(() => this.updateThemeCSS());
|
|
224
|
+
this.themeObserver.observe(document.documentElement, {
|
|
225
|
+
attributes: true,
|
|
226
|
+
attributeFilter: ["data-theme", "class"]
|
|
227
|
+
});
|
|
228
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
229
|
+
this.mediaQueryListener = () => this.updateThemeCSS();
|
|
230
|
+
mq.addEventListener("change", this.mediaQueryListener);
|
|
231
|
+
}
|
|
232
|
+
stopThemeObserver() {
|
|
233
|
+
if (this.themeObserver) {
|
|
234
|
+
this.themeObserver.disconnect();
|
|
235
|
+
this.themeObserver = null;
|
|
236
|
+
}
|
|
237
|
+
if (this.mediaQueryListener) {
|
|
238
|
+
window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", this.mediaQueryListener);
|
|
239
|
+
this.mediaQueryListener = null;
|
|
240
|
+
}
|
|
108
241
|
}
|
|
109
242
|
showError(message) {
|
|
110
243
|
if (!this.shadowRoot) return;
|
|
@@ -156,6 +289,47 @@ var ModalRenderer = class {
|
|
|
156
289
|
if (!this.shadowRoot) return;
|
|
157
290
|
this.shadowRoot.getElementById("authon-loading-overlay")?.remove();
|
|
158
291
|
}
|
|
292
|
+
// ── Flow Overlay Public API ──
|
|
293
|
+
showOverlay(overlay) {
|
|
294
|
+
this.currentOverlay = overlay;
|
|
295
|
+
this.overlayError = "";
|
|
296
|
+
this.renderOverlay();
|
|
297
|
+
}
|
|
298
|
+
hideOverlay() {
|
|
299
|
+
this.currentOverlay = "none";
|
|
300
|
+
this.overlayError = "";
|
|
301
|
+
if (!this.shadowRoot) return;
|
|
302
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
303
|
+
}
|
|
304
|
+
showWeb3Success(walletId, address) {
|
|
305
|
+
this.selectedWallet = walletId;
|
|
306
|
+
this.overlayError = "";
|
|
307
|
+
const truncated = address.length > 10 ? `${address.slice(0, 6)}...${address.slice(-4)}` : address;
|
|
308
|
+
this.currentOverlay = "web3-success";
|
|
309
|
+
this.renderOverlayWithData({ truncatedAddress: truncated, walletId });
|
|
310
|
+
}
|
|
311
|
+
showPasswordlessSent() {
|
|
312
|
+
this.overlayError = "";
|
|
313
|
+
this.currentOverlay = "passwordless-sent";
|
|
314
|
+
this.renderOverlay();
|
|
315
|
+
}
|
|
316
|
+
showOtpInput(email) {
|
|
317
|
+
this.overlayEmail = email;
|
|
318
|
+
this.overlayError = "";
|
|
319
|
+
this.currentOverlay = "otp-input";
|
|
320
|
+
this.renderOverlay();
|
|
321
|
+
}
|
|
322
|
+
showPasskeySuccess() {
|
|
323
|
+
this.overlayError = "";
|
|
324
|
+
this.currentOverlay = "passkey-success";
|
|
325
|
+
this.renderOverlay();
|
|
326
|
+
}
|
|
327
|
+
showOverlayError(message) {
|
|
328
|
+
this.overlayError = message;
|
|
329
|
+
if (this.currentOverlay !== "none") {
|
|
330
|
+
this.renderOverlay();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
159
333
|
// ── Smooth view switch (no flicker) ──
|
|
160
334
|
switchView(view) {
|
|
161
335
|
if (!this.shadowRoot || view === this.currentView) return;
|
|
@@ -186,13 +360,16 @@ var ModalRenderer = class {
|
|
|
186
360
|
this.shadowRoot.innerHTML = this.buildShell(view);
|
|
187
361
|
this.attachInnerEvents(view);
|
|
188
362
|
this.attachShellEvents();
|
|
363
|
+
if (this.theme === "auto") {
|
|
364
|
+
this.startThemeObserver();
|
|
365
|
+
}
|
|
189
366
|
}
|
|
190
367
|
// ── HTML builders ──
|
|
191
368
|
/** Shell = style + backdrop + modal-container (stable across view switches) */
|
|
192
369
|
buildShell(view) {
|
|
193
370
|
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
194
371
|
return `
|
|
195
|
-
<style>${this.buildCSS()}</style>
|
|
372
|
+
<style id="authon-theme-style">${this.buildCSS()}</style>
|
|
196
373
|
${popupWrapper}
|
|
197
374
|
<div class="modal-container" role="dialog" aria-modal="true">
|
|
198
375
|
<div id="modal-inner" class="modal-inner">
|
|
@@ -227,6 +404,35 @@ var ModalRenderer = class {
|
|
|
227
404
|
${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
|
|
228
405
|
<button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
|
|
229
406
|
</form>` : "";
|
|
407
|
+
const hasMethodAbove = showProviders && this.enabledProviders.length > 0 || b.showEmailPassword !== false;
|
|
408
|
+
const hasMethodBelow = b.showWeb3 || b.showPasswordless || b.showPasskey;
|
|
409
|
+
const methodDivider = hasMethodAbove && hasMethodBelow ? `<div class="divider"><span>or</span></div>` : "";
|
|
410
|
+
const methodButtons = [];
|
|
411
|
+
if (b.showWeb3) {
|
|
412
|
+
methodButtons.push(`<button class="auth-method-btn web3-btn" id="web3-btn">
|
|
413
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
414
|
+
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4h-4z"/>
|
|
415
|
+
</svg>
|
|
416
|
+
<span>Connect Wallet</span>
|
|
417
|
+
</button>`);
|
|
418
|
+
}
|
|
419
|
+
if (b.showPasswordless) {
|
|
420
|
+
methodButtons.push(`<button class="auth-method-btn passwordless-btn" id="passwordless-btn">
|
|
421
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
422
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
|
423
|
+
</svg>
|
|
424
|
+
<span>Continue with Magic Link</span>
|
|
425
|
+
</button>`);
|
|
426
|
+
}
|
|
427
|
+
if (b.showPasskey) {
|
|
428
|
+
methodButtons.push(`<button class="auth-method-btn passkey-btn" id="passkey-btn">
|
|
429
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
430
|
+
<circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/><path d="M21.7 13.3 19 11"/><path d="m21 15-2.5-1.5"/><path d="m17 17 2.5-1.5"/><path d="M22 9v6a1 1 0 0 1-1 1h-.5"/><circle cx="18" cy="9" r="3"/>
|
|
431
|
+
</svg>
|
|
432
|
+
<span>Sign in with Passkey</span>
|
|
433
|
+
</button>`);
|
|
434
|
+
}
|
|
435
|
+
const authMethods = methodButtons.length > 0 ? `<div class="auth-methods">${methodButtons.join("")}</div>` : "";
|
|
230
436
|
const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
|
|
231
437
|
${b.termsUrl ? `<a href="${b.termsUrl}" target="_blank">Terms of Service</a>` : ""}
|
|
232
438
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
@@ -245,6 +451,8 @@ var ModalRenderer = class {
|
|
|
245
451
|
${showProviders ? `<div class="providers">${providerButtons}</div>` : ""}
|
|
246
452
|
${divider}
|
|
247
453
|
${emailForm}
|
|
454
|
+
${methodDivider}
|
|
455
|
+
${authMethods}
|
|
248
456
|
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
249
457
|
${footer}
|
|
250
458
|
${b.showSecuredBy !== false ? `<div class="secured-by">Secured by <a href="https://authon.dev" target="_blank" rel="noopener noreferrer" class="secured-link">Authon</a></div>` : ""}
|
|
@@ -253,6 +461,11 @@ var ModalRenderer = class {
|
|
|
253
461
|
isDark() {
|
|
254
462
|
if (this.theme === "dark") return true;
|
|
255
463
|
if (this.theme === "light") return false;
|
|
464
|
+
if (typeof document !== "undefined") {
|
|
465
|
+
const html = document.documentElement;
|
|
466
|
+
if (html.classList.contains("dark") || html.getAttribute("data-theme") === "dark") return true;
|
|
467
|
+
if (html.classList.contains("light") || html.getAttribute("data-theme") === "light") return false;
|
|
468
|
+
}
|
|
256
469
|
return typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
257
470
|
}
|
|
258
471
|
buildCSS() {
|
|
@@ -276,6 +489,10 @@ var ModalRenderer = class {
|
|
|
276
489
|
--authon-border: ${borderColor};
|
|
277
490
|
--authon-divider: ${dividerColor};
|
|
278
491
|
--authon-input-bg: ${inputBg};
|
|
492
|
+
--authon-overlay-bg: ${hexToRgba(bg, 0.92)};
|
|
493
|
+
--authon-overlay-bg-solid: ${hexToRgba(bg, 0.97)};
|
|
494
|
+
--authon-backdrop-bg: rgba(0,0,0,${dark ? "0.7" : "0.5"});
|
|
495
|
+
--authon-shadow-opacity: ${dark ? "0.5" : "0.25"};
|
|
279
496
|
--authon-radius: ${b.borderRadius ?? 12}px;
|
|
280
497
|
--authon-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
281
498
|
font-family: var(--authon-font);
|
|
@@ -284,7 +501,7 @@ var ModalRenderer = class {
|
|
|
284
501
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
285
502
|
.backdrop {
|
|
286
503
|
position: fixed; inset: 0; z-index: 99998;
|
|
287
|
-
background:
|
|
504
|
+
background: var(--authon-backdrop-bg); backdrop-filter: blur(4px);
|
|
288
505
|
animation: fadeIn 0.2s ease;
|
|
289
506
|
}
|
|
290
507
|
.modal-container {
|
|
@@ -378,10 +595,154 @@ var ModalRenderer = class {
|
|
|
378
595
|
}
|
|
379
596
|
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
380
597
|
.secured-link:hover { text-decoration: underline; }
|
|
598
|
+
|
|
599
|
+
/* Auth method buttons */
|
|
600
|
+
.auth-methods { display: flex; flex-direction: column; gap: 8px; }
|
|
601
|
+
.auth-method-btn {
|
|
602
|
+
display: flex; align-items: center; justify-content: center; gap: 8px;
|
|
603
|
+
width: 100%; padding: 10px 16px;
|
|
604
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
605
|
+
font-size: 13px; font-weight: 500; cursor: pointer;
|
|
606
|
+
font-family: var(--authon-font); transition: opacity 0.15s, transform 0.1s;
|
|
607
|
+
}
|
|
608
|
+
.auth-method-btn:hover { opacity: 0.85; }
|
|
609
|
+
.auth-method-btn:active { transform: scale(0.98); }
|
|
610
|
+
/* Web3 -- purple */
|
|
611
|
+
.web3-btn {
|
|
612
|
+
background: ${dark ? "rgba(139,92,246,0.12)" : "rgba(139,92,246,0.08)"};
|
|
613
|
+
border: 1px solid ${dark ? "rgba(139,92,246,0.3)" : "rgba(139,92,246,0.25)"};
|
|
614
|
+
color: ${dark ? "#c4b5fd" : "#7c3aed"};
|
|
615
|
+
}
|
|
616
|
+
/* Passwordless -- cyan */
|
|
617
|
+
.passwordless-btn {
|
|
618
|
+
background: ${dark ? "rgba(6,182,212,0.12)" : "rgba(6,182,212,0.08)"};
|
|
619
|
+
border: 1px solid ${dark ? "rgba(6,182,212,0.3)" : "rgba(6,182,212,0.25)"};
|
|
620
|
+
color: ${dark ? "#67e8f9" : "#0891b2"};
|
|
621
|
+
}
|
|
622
|
+
/* Passkey -- amber */
|
|
623
|
+
.passkey-btn {
|
|
624
|
+
background: ${dark ? "rgba(245,158,11,0.12)" : "rgba(245,158,11,0.08)"};
|
|
625
|
+
border: 1px solid ${dark ? "rgba(245,158,11,0.3)" : "rgba(245,158,11,0.25)"};
|
|
626
|
+
color: ${dark ? "#fcd34d" : "#b45309"};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/* Flow overlay */
|
|
630
|
+
.flow-overlay {
|
|
631
|
+
position: absolute; inset: 0; z-index: 10;
|
|
632
|
+
background: var(--authon-overlay-bg-solid);
|
|
633
|
+
backdrop-filter: blur(2px);
|
|
634
|
+
border-radius: var(--authon-radius);
|
|
635
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
636
|
+
gap: 12px; padding: 24px;
|
|
637
|
+
animation: fadeIn 0.2s ease;
|
|
638
|
+
}
|
|
639
|
+
.flow-overlay .cancel-link {
|
|
640
|
+
font-size: 12px; color: var(--authon-dim); cursor: pointer; border: none;
|
|
641
|
+
background: none; font-family: var(--authon-font); margin-top: 4px;
|
|
642
|
+
}
|
|
643
|
+
.flow-overlay .cancel-link:hover { text-decoration: underline; }
|
|
644
|
+
.flow-overlay .overlay-title {
|
|
645
|
+
font-size: 14px; font-weight: 600; color: var(--authon-text); text-align: center;
|
|
646
|
+
}
|
|
647
|
+
.flow-overlay .overlay-subtitle {
|
|
648
|
+
font-size: 12px; color: var(--authon-muted); text-align: center;
|
|
649
|
+
}
|
|
650
|
+
.flow-overlay .overlay-error {
|
|
651
|
+
padding: 6px 12px; margin-top: 4px;
|
|
652
|
+
background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3);
|
|
653
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
654
|
+
font-size: 12px; color: #ef4444; text-align: center; width: 100%;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/* Wallet picker */
|
|
658
|
+
.wallet-picker { display: flex; flex-direction: column; gap: 8px; width: 100%; }
|
|
659
|
+
.wallet-btn {
|
|
660
|
+
display: flex; align-items: center; gap: 10px;
|
|
661
|
+
width: 100%; padding: 10px 14px;
|
|
662
|
+
background: ${dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)"};
|
|
663
|
+
border: 1px solid ${dark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.08)"};
|
|
664
|
+
border-radius: calc(var(--authon-radius) * 0.5);
|
|
665
|
+
font-size: 13px; font-weight: 500; color: var(--authon-text);
|
|
666
|
+
cursor: pointer; font-family: var(--authon-font);
|
|
667
|
+
transition: opacity 0.15s;
|
|
668
|
+
}
|
|
669
|
+
.wallet-btn:hover { opacity: 0.8; }
|
|
670
|
+
.wallet-btn .wallet-icon { display: flex; align-items: center; flex-shrink: 0; }
|
|
671
|
+
.wallet-btn .wallet-icon svg { border-radius: 6px; }
|
|
672
|
+
|
|
673
|
+
/* Passwordless email input in overlay */
|
|
674
|
+
.pwless-form { display: flex; flex-direction: column; gap: 10px; width: 100%; }
|
|
675
|
+
.pwless-submit {
|
|
676
|
+
width: 100%; padding: 10px;
|
|
677
|
+
background: linear-gradient(135deg, #06b6d4, #0891b2);
|
|
678
|
+
color: #fff; border: none; border-radius: calc(var(--authon-radius) * 0.5);
|
|
679
|
+
font-size: 13px; font-weight: 600; cursor: pointer;
|
|
680
|
+
font-family: var(--authon-font); transition: opacity 0.15s;
|
|
681
|
+
}
|
|
682
|
+
.pwless-submit:hover { opacity: 0.9; }
|
|
683
|
+
.pwless-submit:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
684
|
+
|
|
685
|
+
/* OTP input */
|
|
686
|
+
.otp-container { display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; }
|
|
687
|
+
.otp-inputs { display: flex; gap: 8px; justify-content: center; }
|
|
688
|
+
.otp-digit {
|
|
689
|
+
width: 40px; height: 48px; text-align: center;
|
|
690
|
+
font-size: 20px; font-weight: 600; font-family: var(--authon-font);
|
|
691
|
+
background: var(--authon-input-bg); color: var(--authon-text);
|
|
692
|
+
border: 1px solid var(--authon-border);
|
|
693
|
+
border-radius: calc(var(--authon-radius) * 0.33);
|
|
694
|
+
outline: none; transition: border-color 0.15s;
|
|
695
|
+
}
|
|
696
|
+
.otp-digit:focus {
|
|
697
|
+
border-color: var(--authon-primary-start);
|
|
698
|
+
box-shadow: 0 0 0 3px rgba(124,58,237,0.15);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* Success check animation */
|
|
702
|
+
.success-check {
|
|
703
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
704
|
+
display: flex; align-items: center; justify-content: center;
|
|
705
|
+
}
|
|
706
|
+
.success-check svg path {
|
|
707
|
+
stroke-dasharray: 20;
|
|
708
|
+
stroke-dashoffset: 20;
|
|
709
|
+
animation: check-draw 0.4s ease-out 0.1s forwards;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/* Spinner */
|
|
713
|
+
.flow-spinner {
|
|
714
|
+
animation: spin 0.8s linear infinite;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/* Passkey verifying icon */
|
|
718
|
+
.passkey-icon-pulse {
|
|
719
|
+
width: 48px; height: 48px; border-radius: 50%;
|
|
720
|
+
display: flex; align-items: center; justify-content: center;
|
|
721
|
+
background: rgba(245,158,11,0.15);
|
|
722
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/* Wallet connecting icon */
|
|
726
|
+
.wallet-connecting-icon {
|
|
727
|
+
width: 48px; height: 48px; border-radius: 12px;
|
|
728
|
+
display: flex; align-items: center; justify-content: center;
|
|
729
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
730
|
+
}
|
|
731
|
+
.wallet-connecting-icon svg { border-radius: 6px; }
|
|
732
|
+
|
|
733
|
+
/* Address badge */
|
|
734
|
+
.address-badge {
|
|
735
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
736
|
+
padding: 2px 10px; border-radius: 6px;
|
|
737
|
+
background: ${dark ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.03)"};
|
|
738
|
+
font-size: 11px; font-family: monospace; color: var(--authon-muted);
|
|
739
|
+
}
|
|
740
|
+
.address-badge .wallet-icon-sm svg { width: 16px; height: 16px; border-radius: 4px; }
|
|
741
|
+
|
|
381
742
|
/* Loading overlay */
|
|
382
743
|
#authon-loading-overlay {
|
|
383
744
|
position: absolute; inset: 0; z-index: 10;
|
|
384
|
-
background:
|
|
745
|
+
background: var(--authon-overlay-bg);
|
|
385
746
|
backdrop-filter: blur(2px);
|
|
386
747
|
border-radius: var(--authon-radius);
|
|
387
748
|
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
|
|
@@ -413,11 +774,229 @@ var ModalRenderer = class {
|
|
|
413
774
|
@keyframes blink { 0%,80%,100% { opacity: .2; } 40% { opacity: 1; } }
|
|
414
775
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
415
776
|
@keyframes slideIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } }
|
|
777
|
+
@keyframes check-draw { to { stroke-dashoffset: 0; } }
|
|
778
|
+
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }
|
|
416
779
|
${b.customCss || ""}
|
|
417
780
|
`;
|
|
418
781
|
}
|
|
782
|
+
// ── Flow Overlay Rendering ──
|
|
783
|
+
renderOverlay() {
|
|
784
|
+
this.renderOverlayWithData({});
|
|
785
|
+
}
|
|
786
|
+
renderOverlayWithData(data) {
|
|
787
|
+
if (!this.shadowRoot) return;
|
|
788
|
+
const container = this.shadowRoot.querySelector(".modal-container");
|
|
789
|
+
if (!container) return;
|
|
790
|
+
this.shadowRoot.getElementById("flow-overlay")?.remove();
|
|
791
|
+
if (this.currentOverlay === "none") return;
|
|
792
|
+
const overlay = document.createElement("div");
|
|
793
|
+
overlay.id = "flow-overlay";
|
|
794
|
+
overlay.className = "flow-overlay";
|
|
795
|
+
overlay.innerHTML = this.buildOverlayContent(data);
|
|
796
|
+
container.appendChild(overlay);
|
|
797
|
+
this.attachOverlayEvents(overlay);
|
|
798
|
+
}
|
|
799
|
+
buildOverlayContent(data) {
|
|
800
|
+
const dark = this.isDark();
|
|
801
|
+
const errorHtml = this.overlayError ? `<div class="overlay-error">${this.escapeHtml(this.overlayError)}</div>` : "";
|
|
802
|
+
switch (this.currentOverlay) {
|
|
803
|
+
case "web3-picker": {
|
|
804
|
+
const walletItems = WALLET_OPTIONS.map(
|
|
805
|
+
(w) => `<button class="wallet-btn" data-wallet="${w.id}">
|
|
806
|
+
<span class="wallet-icon">${walletIconSvg(w.id)}</span>
|
|
807
|
+
<span>${w.name}</span>
|
|
808
|
+
</button>`
|
|
809
|
+
).join("");
|
|
810
|
+
return `
|
|
811
|
+
<div class="overlay-title" style="margin-bottom: 4px;">Select Wallet</div>
|
|
812
|
+
<div class="wallet-picker">${walletItems}</div>
|
|
813
|
+
${errorHtml}
|
|
814
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
815
|
+
`;
|
|
816
|
+
}
|
|
817
|
+
case "web3-connecting": {
|
|
818
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === this.selectedWallet);
|
|
819
|
+
const walletName = wallet?.name ?? this.selectedWallet;
|
|
820
|
+
return `
|
|
821
|
+
<div class="wallet-connecting-icon">${walletIconSvg(this.selectedWallet)}</div>
|
|
822
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
823
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
824
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" opacity="0.25"/>
|
|
825
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="${wallet?.color ?? "#7c3aed"}" stroke-width="2" stroke-linecap="round"/>
|
|
826
|
+
</svg>
|
|
827
|
+
<span class="overlay-subtitle">Connecting ${this.escapeHtml(walletName)}...</span>
|
|
828
|
+
</div>
|
|
829
|
+
${errorHtml}
|
|
830
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
831
|
+
`;
|
|
832
|
+
}
|
|
833
|
+
case "web3-success": {
|
|
834
|
+
const wallet = WALLET_OPTIONS.find((w) => w.id === (data.walletId || this.selectedWallet));
|
|
835
|
+
const walletColor = wallet?.color ?? "#8b5cf6";
|
|
836
|
+
const truncAddr = data.truncatedAddress || "0x...";
|
|
837
|
+
return `
|
|
838
|
+
<div class="success-check" style="background:linear-gradient(135deg, ${walletColor}, ${walletColor})">
|
|
839
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
840
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
841
|
+
</svg>
|
|
842
|
+
</div>
|
|
843
|
+
<div class="overlay-title">Wallet Connected</div>
|
|
844
|
+
<div class="address-badge">
|
|
845
|
+
<span class="wallet-icon-sm">${walletIconSvg(data.walletId || this.selectedWallet)}</span>
|
|
846
|
+
<span>${this.escapeHtml(truncAddr)}</span>
|
|
847
|
+
</div>
|
|
848
|
+
`;
|
|
849
|
+
}
|
|
850
|
+
case "passwordless-input": {
|
|
851
|
+
return `
|
|
852
|
+
<div class="overlay-title">Enter your email</div>
|
|
853
|
+
<div class="pwless-form">
|
|
854
|
+
<input type="email" placeholder="you@example.com" class="input" id="pwless-email" autocomplete="email" />
|
|
855
|
+
<button class="pwless-submit" id="pwless-submit-btn">Send Magic Link</button>
|
|
856
|
+
</div>
|
|
857
|
+
${errorHtml}
|
|
858
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
859
|
+
`;
|
|
860
|
+
}
|
|
861
|
+
case "passwordless-sending": {
|
|
862
|
+
return `
|
|
863
|
+
<svg class="flow-spinner" width="16" height="16" viewBox="0 0 16 16">
|
|
864
|
+
<circle cx="8" cy="8" r="6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" opacity="0.25"/>
|
|
865
|
+
<path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" stroke-linecap="round"/>
|
|
866
|
+
</svg>
|
|
867
|
+
<span class="overlay-subtitle">Sending magic link...</span>
|
|
868
|
+
`;
|
|
869
|
+
}
|
|
870
|
+
case "passwordless-sent": {
|
|
871
|
+
return `
|
|
872
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
873
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
874
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
875
|
+
</svg>
|
|
876
|
+
</div>
|
|
877
|
+
<div class="overlay-title">Magic link sent!</div>
|
|
878
|
+
<span class="overlay-subtitle">Check your email inbox</span>
|
|
879
|
+
`;
|
|
880
|
+
}
|
|
881
|
+
case "otp-input": {
|
|
882
|
+
const digitInputs = Array.from(
|
|
883
|
+
{ length: 6 },
|
|
884
|
+
(_, i) => `<input type="text" inputmode="numeric" maxlength="1" class="otp-digit" data-idx="${i}" autocomplete="one-time-code" />`
|
|
885
|
+
).join("");
|
|
886
|
+
return `
|
|
887
|
+
<div class="otp-container">
|
|
888
|
+
<div class="overlay-title">Enter verification code</div>
|
|
889
|
+
<span class="overlay-subtitle">6-digit code sent to ${this.escapeHtml(this.overlayEmail)}</span>
|
|
890
|
+
<div class="otp-inputs">${digitInputs}</div>
|
|
891
|
+
${errorHtml}
|
|
892
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
893
|
+
</div>
|
|
894
|
+
`;
|
|
895
|
+
}
|
|
896
|
+
case "passkey-verifying": {
|
|
897
|
+
return `
|
|
898
|
+
<div class="passkey-icon-pulse">
|
|
899
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--authon-primary-start, #7c3aed)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
900
|
+
<circle cx="10" cy="7" r="4"/><path d="M10.3 15H7a4 4 0 0 0-4 4v2"/>
|
|
901
|
+
<path d="M21.7 13.3 19 11"/><path d="m21 15-2.5-1.5"/><path d="m17 17 2.5-1.5"/>
|
|
902
|
+
<path d="M22 9v6a1 1 0 0 1-1 1h-.5"/><circle cx="18" cy="9" r="3"/>
|
|
903
|
+
</svg>
|
|
904
|
+
</div>
|
|
905
|
+
<span class="overlay-subtitle">Verifying identity...</span>
|
|
906
|
+
${errorHtml}
|
|
907
|
+
<button class="cancel-link" id="overlay-cancel">Cancel</button>
|
|
908
|
+
`;
|
|
909
|
+
}
|
|
910
|
+
case "passkey-success": {
|
|
911
|
+
return `
|
|
912
|
+
<div class="success-check" style="background:linear-gradient(135deg, var(--authon-primary-start, #7c3aed), var(--authon-primary-end, #4f46e5))">
|
|
913
|
+
<svg width="24" height="24" viewBox="0 0 20 20" fill="none">
|
|
914
|
+
<path d="M5 10l3.5 3.5L15 7" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
915
|
+
</svg>
|
|
916
|
+
</div>
|
|
917
|
+
<div class="overlay-title">Identity verified!</div>
|
|
918
|
+
`;
|
|
919
|
+
}
|
|
920
|
+
default:
|
|
921
|
+
return "";
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
attachOverlayEvents(overlay) {
|
|
925
|
+
if (!this.shadowRoot) return;
|
|
926
|
+
const cancelBtn = overlay.querySelector("#overlay-cancel");
|
|
927
|
+
if (cancelBtn) {
|
|
928
|
+
cancelBtn.addEventListener("click", () => this.hideOverlay());
|
|
929
|
+
}
|
|
930
|
+
overlay.querySelectorAll(".wallet-btn").forEach((btn) => {
|
|
931
|
+
btn.addEventListener("click", () => {
|
|
932
|
+
const walletId = btn.dataset.wallet;
|
|
933
|
+
if (walletId) {
|
|
934
|
+
this.selectedWallet = walletId;
|
|
935
|
+
this.onWeb3WalletSelect(walletId);
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
const pwlessSubmit = overlay.querySelector("#pwless-submit-btn");
|
|
940
|
+
const pwlessEmail = overlay.querySelector("#pwless-email");
|
|
941
|
+
if (pwlessSubmit && pwlessEmail) {
|
|
942
|
+
setTimeout(() => pwlessEmail.focus(), 50);
|
|
943
|
+
const submitHandler = () => {
|
|
944
|
+
const email = pwlessEmail.value.trim();
|
|
945
|
+
if (!email) return;
|
|
946
|
+
this.overlayEmail = email;
|
|
947
|
+
this.showOverlay("passwordless-sending");
|
|
948
|
+
this.onPasswordlessSubmit(email);
|
|
949
|
+
};
|
|
950
|
+
pwlessSubmit.addEventListener("click", submitHandler);
|
|
951
|
+
pwlessEmail.addEventListener("keydown", (e) => {
|
|
952
|
+
if (e.key === "Enter") {
|
|
953
|
+
e.preventDefault();
|
|
954
|
+
submitHandler();
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
const otpDigits = overlay.querySelectorAll(".otp-digit");
|
|
959
|
+
if (otpDigits.length === 6) {
|
|
960
|
+
setTimeout(() => otpDigits[0].focus(), 50);
|
|
961
|
+
otpDigits.forEach((digit, idx) => {
|
|
962
|
+
digit.addEventListener("input", () => {
|
|
963
|
+
const val = digit.value.replace(/\D/g, "");
|
|
964
|
+
digit.value = val.slice(0, 1);
|
|
965
|
+
if (val && idx < 5) {
|
|
966
|
+
otpDigits[idx + 1].focus();
|
|
967
|
+
}
|
|
968
|
+
const code = Array.from(otpDigits).map((d) => d.value).join("");
|
|
969
|
+
if (code.length === 6) {
|
|
970
|
+
this.onOtpVerify(this.overlayEmail, code);
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
digit.addEventListener("keydown", (e) => {
|
|
974
|
+
if (e.key === "Backspace" && !digit.value && idx > 0) {
|
|
975
|
+
otpDigits[idx - 1].focus();
|
|
976
|
+
otpDigits[idx - 1].value = "";
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
digit.addEventListener("paste", (e) => {
|
|
980
|
+
e.preventDefault();
|
|
981
|
+
const pasted = (e.clipboardData?.getData("text") ?? "").replace(/\D/g, "").slice(0, 6);
|
|
982
|
+
if (pasted.length === 0) return;
|
|
983
|
+
for (let i = 0; i < 6; i++) {
|
|
984
|
+
otpDigits[i].value = pasted[i] || "";
|
|
985
|
+
}
|
|
986
|
+
const lastIdx = Math.min(pasted.length, 5);
|
|
987
|
+
otpDigits[lastIdx].focus();
|
|
988
|
+
if (pasted.length === 6) {
|
|
989
|
+
this.onOtpVerify(this.overlayEmail, pasted);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
escapeHtml(str) {
|
|
996
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
997
|
+
}
|
|
419
998
|
// ── Event binding ──
|
|
420
|
-
/** Attach events to shell elements (backdrop, ESC)
|
|
999
|
+
/** Attach events to shell elements (backdrop, ESC) -- called once */
|
|
421
1000
|
attachShellEvents() {
|
|
422
1001
|
if (!this.shadowRoot) return;
|
|
423
1002
|
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
@@ -429,12 +1008,18 @@ var ModalRenderer = class {
|
|
|
429
1008
|
}
|
|
430
1009
|
if (this.mode === "popup") {
|
|
431
1010
|
this.escHandler = (e) => {
|
|
432
|
-
if (e.key === "Escape")
|
|
1011
|
+
if (e.key === "Escape") {
|
|
1012
|
+
if (this.currentOverlay !== "none") {
|
|
1013
|
+
this.hideOverlay();
|
|
1014
|
+
} else {
|
|
1015
|
+
this.onClose();
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
433
1018
|
};
|
|
434
1019
|
document.addEventListener("keydown", this.escHandler);
|
|
435
1020
|
}
|
|
436
1021
|
}
|
|
437
|
-
/** Attach events to inner content (buttons, form, switch link)
|
|
1022
|
+
/** Attach events to inner content (buttons, form, switch link) -- called on each view */
|
|
438
1023
|
attachInnerEvents(view) {
|
|
439
1024
|
if (!this.shadowRoot) return;
|
|
440
1025
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
@@ -468,6 +1053,18 @@ var ModalRenderer = class {
|
|
|
468
1053
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
469
1054
|
});
|
|
470
1055
|
}
|
|
1056
|
+
const web3Btn = this.shadowRoot.getElementById("web3-btn");
|
|
1057
|
+
if (web3Btn) {
|
|
1058
|
+
web3Btn.addEventListener("click", () => this.showOverlay("web3-picker"));
|
|
1059
|
+
}
|
|
1060
|
+
const pwlessBtn = this.shadowRoot.getElementById("passwordless-btn");
|
|
1061
|
+
if (pwlessBtn) {
|
|
1062
|
+
pwlessBtn.addEventListener("click", () => this.showOverlay("passwordless-input"));
|
|
1063
|
+
}
|
|
1064
|
+
const passkeyBtn = this.shadowRoot.getElementById("passkey-btn");
|
|
1065
|
+
if (passkeyBtn) {
|
|
1066
|
+
passkeyBtn.addEventListener("click", () => this.onPasskeyClick());
|
|
1067
|
+
}
|
|
471
1068
|
}
|
|
472
1069
|
};
|
|
473
1070
|
|
|
@@ -497,6 +1094,9 @@ var SessionManager = class {
|
|
|
497
1094
|
this.scheduleRefresh(tokens.expiresIn);
|
|
498
1095
|
}
|
|
499
1096
|
}
|
|
1097
|
+
updateUser(user) {
|
|
1098
|
+
this.user = user;
|
|
1099
|
+
}
|
|
500
1100
|
clearSession() {
|
|
501
1101
|
this.accessToken = null;
|
|
502
1102
|
this.refreshToken = null;
|
|
@@ -558,6 +1158,404 @@ var SessionManager = class {
|
|
|
558
1158
|
}
|
|
559
1159
|
};
|
|
560
1160
|
|
|
1161
|
+
// src/qrcode.ts
|
|
1162
|
+
var EXP = [];
|
|
1163
|
+
var LOG = new Array(256).fill(0);
|
|
1164
|
+
(() => {
|
|
1165
|
+
let v = 1;
|
|
1166
|
+
for (let i = 0; i < 255; i++) {
|
|
1167
|
+
EXP[i] = v;
|
|
1168
|
+
LOG[v] = i;
|
|
1169
|
+
v <<= 1;
|
|
1170
|
+
if (v & 256) v ^= 285;
|
|
1171
|
+
}
|
|
1172
|
+
for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
|
|
1173
|
+
})();
|
|
1174
|
+
var gfMul = (a, b) => a && b ? EXP[LOG[a] + LOG[b]] : 0;
|
|
1175
|
+
function rsEncode(data, ecLen) {
|
|
1176
|
+
let g = [1];
|
|
1177
|
+
for (let i = 0; i < ecLen; i++) {
|
|
1178
|
+
const ng = new Array(g.length + 1).fill(0);
|
|
1179
|
+
for (let j = 0; j < g.length; j++) {
|
|
1180
|
+
ng[j] ^= gfMul(g[j], EXP[i]);
|
|
1181
|
+
ng[j + 1] ^= g[j];
|
|
1182
|
+
}
|
|
1183
|
+
g = ng;
|
|
1184
|
+
}
|
|
1185
|
+
const rem = new Array(ecLen).fill(0);
|
|
1186
|
+
for (const d of data) {
|
|
1187
|
+
const fb = d ^ rem[0];
|
|
1188
|
+
for (let j = 0; j < ecLen - 1; j++) {
|
|
1189
|
+
rem[j] = rem[j + 1] ^ gfMul(g[ecLen - 1 - j], fb);
|
|
1190
|
+
}
|
|
1191
|
+
rem[ecLen - 1] = gfMul(g[0], fb);
|
|
1192
|
+
}
|
|
1193
|
+
return rem;
|
|
1194
|
+
}
|
|
1195
|
+
var VER = [
|
|
1196
|
+
{ total: 0, ec: 0, g1: 0, g1d: 0, g2: 0, g2d: 0, align: [] },
|
|
1197
|
+
// dummy
|
|
1198
|
+
{ total: 26, ec: 7, g1: 1, g1d: 19, g2: 0, g2d: 0, align: [] },
|
|
1199
|
+
{ total: 44, ec: 10, g1: 1, g1d: 34, g2: 0, g2d: 0, align: [6, 18] },
|
|
1200
|
+
{ total: 70, ec: 15, g1: 1, g1d: 55, g2: 0, g2d: 0, align: [6, 22] },
|
|
1201
|
+
{ total: 100, ec: 20, g1: 1, g1d: 80, g2: 0, g2d: 0, align: [6, 26] },
|
|
1202
|
+
{ total: 134, ec: 26, g1: 1, g1d: 108, g2: 0, g2d: 0, align: [6, 30] },
|
|
1203
|
+
{ total: 172, ec: 18, g1: 2, g1d: 68, g2: 0, g2d: 0, align: [6, 34] },
|
|
1204
|
+
{ total: 196, ec: 20, g1: 2, g1d: 78, g2: 0, g2d: 0, align: [6, 22, 38] },
|
|
1205
|
+
{ total: 242, ec: 24, g1: 2, g1d: 97, g2: 0, g2d: 0, align: [6, 24, 42] },
|
|
1206
|
+
{ total: 292, ec: 30, g1: 2, g1d: 116, g2: 0, g2d: 0, align: [6, 26, 46] },
|
|
1207
|
+
{ total: 346, ec: 18, g1: 2, g1d: 68, g2: 2, g2d: 69, align: [6, 28, 50] },
|
|
1208
|
+
{ total: 404, ec: 20, g1: 4, g1d: 81, g2: 0, g2d: 0, align: [6, 30, 54] },
|
|
1209
|
+
{ total: 466, ec: 24, g1: 2, g1d: 92, g2: 2, g2d: 93, align: [6, 32, 58] },
|
|
1210
|
+
{ total: 532, ec: 26, g1: 4, g1d: 107, g2: 0, g2d: 0, align: [6, 34, 62] }
|
|
1211
|
+
];
|
|
1212
|
+
function dataCapacity(ver) {
|
|
1213
|
+
const v = VER[ver];
|
|
1214
|
+
return v.g1 * v.g1d + v.g2 * v.g2d;
|
|
1215
|
+
}
|
|
1216
|
+
function pickVersion(byteLen) {
|
|
1217
|
+
for (let v = 1; v < VER.length; v++) {
|
|
1218
|
+
const headerBits = 4 + (v <= 9 ? 8 : 16);
|
|
1219
|
+
const available = dataCapacity(v) * 8 - headerBits;
|
|
1220
|
+
if (byteLen * 8 <= available) return v;
|
|
1221
|
+
}
|
|
1222
|
+
throw new Error(`Data too long for QR code (${byteLen} bytes)`);
|
|
1223
|
+
}
|
|
1224
|
+
function encodeData(bytes, ver) {
|
|
1225
|
+
const cap = dataCapacity(ver);
|
|
1226
|
+
const countBits = ver <= 9 ? 8 : 16;
|
|
1227
|
+
const bits = [];
|
|
1228
|
+
const push = (val, len) => {
|
|
1229
|
+
for (let i = len - 1; i >= 0; i--) bits.push(val >> i & 1);
|
|
1230
|
+
};
|
|
1231
|
+
push(4, 4);
|
|
1232
|
+
push(bytes.length, countBits);
|
|
1233
|
+
for (const b of bytes) push(b, 8);
|
|
1234
|
+
push(0, Math.min(4, cap * 8 - bits.length));
|
|
1235
|
+
while (bits.length % 8) bits.push(0);
|
|
1236
|
+
const padBytes = [236, 17];
|
|
1237
|
+
let pi = 0;
|
|
1238
|
+
while (bits.length < cap * 8) {
|
|
1239
|
+
push(padBytes[pi], 8);
|
|
1240
|
+
pi ^= 1;
|
|
1241
|
+
}
|
|
1242
|
+
const cw = [];
|
|
1243
|
+
for (let i = 0; i < bits.length; i += 8) {
|
|
1244
|
+
let byte = 0;
|
|
1245
|
+
for (let j = 0; j < 8; j++) byte = byte << 1 | bits[i + j];
|
|
1246
|
+
cw.push(byte);
|
|
1247
|
+
}
|
|
1248
|
+
return cw;
|
|
1249
|
+
}
|
|
1250
|
+
function computeCodewords(ver, dataCW) {
|
|
1251
|
+
const v = VER[ver];
|
|
1252
|
+
const blocks = [];
|
|
1253
|
+
const ecBlocks = [];
|
|
1254
|
+
let offset = 0;
|
|
1255
|
+
for (let i = 0; i < v.g1; i++) {
|
|
1256
|
+
const block = dataCW.slice(offset, offset + v.g1d);
|
|
1257
|
+
blocks.push(block);
|
|
1258
|
+
ecBlocks.push(rsEncode(block, v.ec));
|
|
1259
|
+
offset += v.g1d;
|
|
1260
|
+
}
|
|
1261
|
+
for (let i = 0; i < v.g2; i++) {
|
|
1262
|
+
const block = dataCW.slice(offset, offset + v.g2d);
|
|
1263
|
+
blocks.push(block);
|
|
1264
|
+
ecBlocks.push(rsEncode(block, v.ec));
|
|
1265
|
+
offset += v.g2d;
|
|
1266
|
+
}
|
|
1267
|
+
const result = [];
|
|
1268
|
+
const maxDataLen = Math.max(v.g1d, v.g2d || 0);
|
|
1269
|
+
for (let i = 0; i < maxDataLen; i++) {
|
|
1270
|
+
for (const block of blocks) {
|
|
1271
|
+
if (i < block.length) result.push(block[i]);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
for (let i = 0; i < v.ec; i++) {
|
|
1275
|
+
for (const block of ecBlocks) result.push(block[i]);
|
|
1276
|
+
}
|
|
1277
|
+
return result;
|
|
1278
|
+
}
|
|
1279
|
+
var UNSET = -1;
|
|
1280
|
+
var DARK = 1;
|
|
1281
|
+
var LIGHT = 0;
|
|
1282
|
+
function createMatrix(size) {
|
|
1283
|
+
return Array.from({ length: size }, () => new Array(size).fill(UNSET));
|
|
1284
|
+
}
|
|
1285
|
+
function setModule(m, r, c, dark) {
|
|
1286
|
+
if (r >= 0 && r < m.length && c >= 0 && c < m.length) m[r][c] = dark ? DARK : LIGHT;
|
|
1287
|
+
}
|
|
1288
|
+
function placeFinderPattern(m, row, col) {
|
|
1289
|
+
for (let r = -1; r <= 7; r++) {
|
|
1290
|
+
for (let c = -1; c <= 7; c++) {
|
|
1291
|
+
const dark = r >= 0 && r <= 6 && c >= 0 && c <= 6 && (r === 0 || r === 6 || c === 0 || c === 6 || r >= 2 && r <= 4 && c >= 2 && c <= 4);
|
|
1292
|
+
setModule(m, row + r, col + c, dark);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
function placeAlignmentPattern(m, row, col) {
|
|
1297
|
+
for (let r = -2; r <= 2; r++) {
|
|
1298
|
+
for (let c = -2; c <= 2; c++) {
|
|
1299
|
+
const dark = Math.abs(r) === 2 || Math.abs(c) === 2 || r === 0 && c === 0;
|
|
1300
|
+
m[row + r][col + c] = dark ? DARK : LIGHT;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
function isReserved(m, r, c) {
|
|
1305
|
+
return m[r][c] !== UNSET;
|
|
1306
|
+
}
|
|
1307
|
+
function buildMatrix(ver, codewords) {
|
|
1308
|
+
const size = ver * 4 + 17;
|
|
1309
|
+
const m = createMatrix(size);
|
|
1310
|
+
placeFinderPattern(m, 0, 0);
|
|
1311
|
+
placeFinderPattern(m, 0, size - 7);
|
|
1312
|
+
placeFinderPattern(m, size - 7, 0);
|
|
1313
|
+
for (let i = 8; i < size - 8; i++) {
|
|
1314
|
+
m[6][i] = i % 2 === 0 ? DARK : LIGHT;
|
|
1315
|
+
m[i][6] = i % 2 === 0 ? DARK : LIGHT;
|
|
1316
|
+
}
|
|
1317
|
+
const ap = VER[ver].align;
|
|
1318
|
+
if (ap.length > 0) {
|
|
1319
|
+
for (const r of ap) {
|
|
1320
|
+
for (const c of ap) {
|
|
1321
|
+
if (r <= 8 && c <= 8) continue;
|
|
1322
|
+
if (r <= 8 && c >= size - 8) continue;
|
|
1323
|
+
if (r >= size - 8 && c <= 8) continue;
|
|
1324
|
+
placeAlignmentPattern(m, r, c);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
m[4 * ver + 9][8] = DARK;
|
|
1329
|
+
for (let i = 0; i < 9; i++) {
|
|
1330
|
+
if (m[8][i] === UNSET) m[8][i] = LIGHT;
|
|
1331
|
+
if (m[i][8] === UNSET) m[i][8] = LIGHT;
|
|
1332
|
+
}
|
|
1333
|
+
for (let i = 0; i < 8; i++) {
|
|
1334
|
+
if (m[8][size - 1 - i] === UNSET) m[8][size - 1 - i] = LIGHT;
|
|
1335
|
+
if (m[size - 1 - i][8] === UNSET) m[size - 1 - i][8] = LIGHT;
|
|
1336
|
+
}
|
|
1337
|
+
if (ver >= 7) {
|
|
1338
|
+
for (let i = 0; i < 6; i++) {
|
|
1339
|
+
for (let j = 0; j < 3; j++) {
|
|
1340
|
+
m[i][size - 11 + j] = LIGHT;
|
|
1341
|
+
m[size - 11 + j][i] = LIGHT;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
let bitIdx = 0;
|
|
1346
|
+
const totalBits = codewords.length * 8;
|
|
1347
|
+
let upward = true;
|
|
1348
|
+
for (let right = size - 1; right >= 0; right -= 2) {
|
|
1349
|
+
if (right === 6) right = 5;
|
|
1350
|
+
for (let i = 0; i < size; i++) {
|
|
1351
|
+
const row = upward ? size - 1 - i : i;
|
|
1352
|
+
for (const dc of [0, -1]) {
|
|
1353
|
+
const col = right + dc;
|
|
1354
|
+
if (col < 0 || col >= size) continue;
|
|
1355
|
+
if (isReserved(m, row, col)) continue;
|
|
1356
|
+
if (bitIdx < totalBits) {
|
|
1357
|
+
const byteIdx = bitIdx >> 3;
|
|
1358
|
+
const bitPos = 7 - (bitIdx & 7);
|
|
1359
|
+
m[row][col] = codewords[byteIdx] >> bitPos & 1;
|
|
1360
|
+
bitIdx++;
|
|
1361
|
+
} else {
|
|
1362
|
+
m[row][col] = LIGHT;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
upward = !upward;
|
|
1367
|
+
}
|
|
1368
|
+
return m;
|
|
1369
|
+
}
|
|
1370
|
+
var MASKS = [
|
|
1371
|
+
(r, c) => (r + c) % 2 === 0,
|
|
1372
|
+
(r) => r % 2 === 0,
|
|
1373
|
+
(_, c) => c % 3 === 0,
|
|
1374
|
+
(r, c) => (r + c) % 3 === 0,
|
|
1375
|
+
(r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,
|
|
1376
|
+
(r, c) => r * c % 2 + r * c % 3 === 0,
|
|
1377
|
+
(r, c) => (r * c % 2 + r * c % 3) % 2 === 0,
|
|
1378
|
+
(r, c) => ((r + c) % 2 + r * c % 3) % 2 === 0
|
|
1379
|
+
];
|
|
1380
|
+
function applyMask(m, maskIdx, template) {
|
|
1381
|
+
const size = m.length;
|
|
1382
|
+
const result = m.map((row) => [...row]);
|
|
1383
|
+
const fn = MASKS[maskIdx];
|
|
1384
|
+
for (let r = 0; r < size; r++) {
|
|
1385
|
+
for (let c = 0; c < size; c++) {
|
|
1386
|
+
if (template[r][c] !== UNSET) continue;
|
|
1387
|
+
if (fn(r, c)) result[r][c] ^= 1;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return result;
|
|
1391
|
+
}
|
|
1392
|
+
function penalty(m) {
|
|
1393
|
+
const size = m.length;
|
|
1394
|
+
let score = 0;
|
|
1395
|
+
for (let r = 0; r < size; r++) {
|
|
1396
|
+
let count = 1;
|
|
1397
|
+
for (let c = 1; c < size; c++) {
|
|
1398
|
+
if (m[r][c] === m[r][c - 1]) {
|
|
1399
|
+
count++;
|
|
1400
|
+
} else {
|
|
1401
|
+
if (count >= 5) score += count - 2;
|
|
1402
|
+
count = 1;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (count >= 5) score += count - 2;
|
|
1406
|
+
}
|
|
1407
|
+
for (let c = 0; c < size; c++) {
|
|
1408
|
+
let count = 1;
|
|
1409
|
+
for (let r = 1; r < size; r++) {
|
|
1410
|
+
if (m[r][c] === m[r - 1][c]) {
|
|
1411
|
+
count++;
|
|
1412
|
+
} else {
|
|
1413
|
+
if (count >= 5) score += count - 2;
|
|
1414
|
+
count = 1;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (count >= 5) score += count - 2;
|
|
1418
|
+
}
|
|
1419
|
+
for (let r = 0; r < size - 1; r++) {
|
|
1420
|
+
for (let c = 0; c < size - 1; c++) {
|
|
1421
|
+
const v = m[r][c];
|
|
1422
|
+
if (v === m[r][c + 1] && v === m[r + 1][c] && v === m[r + 1][c + 1]) score += 3;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
const pat1 = [1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0];
|
|
1426
|
+
const pat2 = [0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1];
|
|
1427
|
+
for (let r = 0; r < size; r++) {
|
|
1428
|
+
for (let c = 0; c <= size - 11; c++) {
|
|
1429
|
+
let match1 = true, match2 = true;
|
|
1430
|
+
for (let k = 0; k < 11; k++) {
|
|
1431
|
+
if (m[r][c + k] !== pat1[k]) match1 = false;
|
|
1432
|
+
if (m[r][c + k] !== pat2[k]) match2 = false;
|
|
1433
|
+
}
|
|
1434
|
+
if (match1 || match2) score += 40;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
for (let c = 0; c < size; c++) {
|
|
1438
|
+
for (let r = 0; r <= size - 11; r++) {
|
|
1439
|
+
let match1 = true, match2 = true;
|
|
1440
|
+
for (let k = 0; k < 11; k++) {
|
|
1441
|
+
if (m[r + k][c] !== pat1[k]) match1 = false;
|
|
1442
|
+
if (m[r + k][c] !== pat2[k]) match2 = false;
|
|
1443
|
+
}
|
|
1444
|
+
if (match1 || match2) score += 40;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
let dark = 0;
|
|
1448
|
+
for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (m[r][c]) dark++;
|
|
1449
|
+
const pct = dark * 100 / (size * size);
|
|
1450
|
+
const prev5 = Math.floor(pct / 5) * 5;
|
|
1451
|
+
const next5 = prev5 + 5;
|
|
1452
|
+
score += Math.min(Math.abs(prev5 - 50) / 5, Math.abs(next5 - 50) / 5) * 10;
|
|
1453
|
+
return score;
|
|
1454
|
+
}
|
|
1455
|
+
function bchEncode(data, gen, dataBits) {
|
|
1456
|
+
let d = data << 15 - dataBits;
|
|
1457
|
+
const genLen = Math.floor(Math.log2(gen)) + 1;
|
|
1458
|
+
const totalBits = dataBits + (genLen - 1);
|
|
1459
|
+
d = data << totalBits - dataBits;
|
|
1460
|
+
for (let i = dataBits - 1; i >= 0; i--) {
|
|
1461
|
+
if (d & 1 << i + genLen - 1) d ^= gen << i;
|
|
1462
|
+
}
|
|
1463
|
+
return data << genLen - 1 | d;
|
|
1464
|
+
}
|
|
1465
|
+
function placeFormatInfo(m, maskIdx) {
|
|
1466
|
+
const size = m.length;
|
|
1467
|
+
const data = 1 << 3 | maskIdx;
|
|
1468
|
+
let format = bchEncode(data, 1335, 5);
|
|
1469
|
+
format ^= 21522;
|
|
1470
|
+
const bits = [];
|
|
1471
|
+
for (let i = 14; i >= 0; i--) bits.push(format >> i & 1);
|
|
1472
|
+
const hPos = [0, 1, 2, 3, 4, 5, 7, 8, size - 8, size - 7, size - 6, size - 5, size - 4, size - 3, size - 2];
|
|
1473
|
+
for (let i = 0; i < 15; i++) m[8][hPos[i]] = bits[i];
|
|
1474
|
+
const vPos = [size - 1, size - 2, size - 3, size - 4, size - 5, size - 6, size - 7, size - 8, 7, 5, 4, 3, 2, 1, 0];
|
|
1475
|
+
for (let i = 0; i < 15; i++) m[vPos[i]][8] = bits[i];
|
|
1476
|
+
}
|
|
1477
|
+
function placeVersionInfo(m, ver) {
|
|
1478
|
+
if (ver < 7) return;
|
|
1479
|
+
const size = m.length;
|
|
1480
|
+
let info = bchEncode(ver, 7973, 6);
|
|
1481
|
+
for (let i = 0; i < 18; i++) {
|
|
1482
|
+
const bit = info >> i & 1;
|
|
1483
|
+
const r = Math.floor(i / 3);
|
|
1484
|
+
const c = size - 11 + i % 3;
|
|
1485
|
+
m[r][c] = bit;
|
|
1486
|
+
m[c][r] = bit;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function generateQrSvg(text, moduleSize = 4) {
|
|
1490
|
+
const bytes = Array.from(new TextEncoder().encode(text));
|
|
1491
|
+
const ver = pickVersion(bytes.length);
|
|
1492
|
+
const dataCW = encodeData(bytes, ver);
|
|
1493
|
+
const allCW = computeCodewords(ver, dataCW);
|
|
1494
|
+
const size = ver * 4 + 17;
|
|
1495
|
+
const template = createMatrix(size);
|
|
1496
|
+
placeFinderPattern(template, 0, 0);
|
|
1497
|
+
placeFinderPattern(template, 0, size - 7);
|
|
1498
|
+
placeFinderPattern(template, size - 7, 0);
|
|
1499
|
+
for (let i = 8; i < size - 8; i++) {
|
|
1500
|
+
template[6][i] = LIGHT;
|
|
1501
|
+
template[i][6] = LIGHT;
|
|
1502
|
+
}
|
|
1503
|
+
const ap = VER[ver].align;
|
|
1504
|
+
for (const r of ap) {
|
|
1505
|
+
for (const c of ap) {
|
|
1506
|
+
if (r <= 8 && c <= 8) continue;
|
|
1507
|
+
if (r <= 8 && c >= size - 8) continue;
|
|
1508
|
+
if (r >= size - 8 && c <= 8) continue;
|
|
1509
|
+
for (let dr = -2; dr <= 2; dr++) for (let dc = -2; dc <= 2; dc++) template[r + dr][c + dc] = LIGHT;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
template[4 * ver + 9][8] = LIGHT;
|
|
1513
|
+
for (let i = 0; i < 9; i++) {
|
|
1514
|
+
if (template[8][i] === UNSET) template[8][i] = LIGHT;
|
|
1515
|
+
if (template[i][8] === UNSET) template[i][8] = LIGHT;
|
|
1516
|
+
}
|
|
1517
|
+
for (let i = 0; i < 8; i++) {
|
|
1518
|
+
if (template[8][size - 1 - i] === UNSET) template[8][size - 1 - i] = LIGHT;
|
|
1519
|
+
if (template[size - 1 - i][8] === UNSET) template[size - 1 - i][8] = LIGHT;
|
|
1520
|
+
}
|
|
1521
|
+
if (ver >= 7) {
|
|
1522
|
+
for (let i = 0; i < 6; i++) for (let j = 0; j < 3; j++) {
|
|
1523
|
+
template[i][size - 11 + j] = LIGHT;
|
|
1524
|
+
template[size - 11 + j][i] = LIGHT;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
const base = buildMatrix(ver, allCW);
|
|
1528
|
+
let bestMask = 0;
|
|
1529
|
+
let bestScore = Infinity;
|
|
1530
|
+
for (let mask = 0; mask < 8; mask++) {
|
|
1531
|
+
const masked = applyMask(base, mask, template);
|
|
1532
|
+
placeFormatInfo(masked, mask);
|
|
1533
|
+
placeVersionInfo(masked, ver);
|
|
1534
|
+
const s = penalty(masked);
|
|
1535
|
+
if (s < bestScore) {
|
|
1536
|
+
bestScore = s;
|
|
1537
|
+
bestMask = mask;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
const final = applyMask(base, bestMask, template);
|
|
1541
|
+
placeFormatInfo(final, bestMask);
|
|
1542
|
+
placeVersionInfo(final, ver);
|
|
1543
|
+
const quiet = 4;
|
|
1544
|
+
const total = size + quiet * 2;
|
|
1545
|
+
const px = total * moduleSize;
|
|
1546
|
+
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${total} ${total}" width="${px}" height="${px}" shape-rendering="crispEdges">`;
|
|
1547
|
+
svg += `<rect width="${total}" height="${total}" fill="#fff"/>`;
|
|
1548
|
+
for (let r = 0; r < size; r++) {
|
|
1549
|
+
for (let c = 0; c < size; c++) {
|
|
1550
|
+
if (final[r][c] === DARK) {
|
|
1551
|
+
svg += `<rect x="${c + quiet}" y="${r + quiet}" width="1" height="1" fill="#000"/>`;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
svg += "</svg>";
|
|
1556
|
+
return svg;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
561
1559
|
// src/authon.ts
|
|
562
1560
|
var Authon = class {
|
|
563
1561
|
publishableKey;
|
|
@@ -595,15 +1593,26 @@ var Authon = class {
|
|
|
595
1593
|
await this.ensureInitialized();
|
|
596
1594
|
this.getModal().open("signUp");
|
|
597
1595
|
}
|
|
1596
|
+
/** Update theme at runtime without destroying form state */
|
|
1597
|
+
setTheme(theme) {
|
|
1598
|
+
this.getModal().setTheme(theme);
|
|
1599
|
+
}
|
|
598
1600
|
async signInWithOAuth(provider, options) {
|
|
599
1601
|
await this.ensureInitialized();
|
|
600
1602
|
await this.startOAuthFlow(provider, options);
|
|
601
1603
|
}
|
|
602
1604
|
async signInWithEmail(email, password) {
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
1605
|
+
const res = await this.apiPost(
|
|
1606
|
+
"/v1/auth/signin",
|
|
1607
|
+
{ email, password }
|
|
1608
|
+
);
|
|
1609
|
+
if (res.mfaRequired && res.mfaToken) {
|
|
1610
|
+
this.emit("mfaRequired", res.mfaToken);
|
|
1611
|
+
throw new AuthonMfaRequiredError(res.mfaToken);
|
|
1612
|
+
}
|
|
1613
|
+
this.session.setSession(res);
|
|
1614
|
+
this.emit("signedIn", res.user);
|
|
1615
|
+
return res.user;
|
|
607
1616
|
}
|
|
608
1617
|
async signUpWithEmail(email, password, meta) {
|
|
609
1618
|
const tokens = await this.apiPost("/v1/auth/signup", {
|
|
@@ -631,6 +1640,258 @@ var Authon = class {
|
|
|
631
1640
|
set.add(listener);
|
|
632
1641
|
return () => set.delete(listener);
|
|
633
1642
|
}
|
|
1643
|
+
// ── MFA ──
|
|
1644
|
+
async setupMfa() {
|
|
1645
|
+
const token = this.session.getToken();
|
|
1646
|
+
if (!token) throw new Error("Must be signed in to setup MFA");
|
|
1647
|
+
const res = await this.apiPostAuth("/v1/auth/mfa/totp/setup", void 0, token);
|
|
1648
|
+
return { ...res, qrCodeSvg: generateQrSvg(res.qrCodeUri) };
|
|
1649
|
+
}
|
|
1650
|
+
async verifyMfaSetup(code) {
|
|
1651
|
+
const token = this.session.getToken();
|
|
1652
|
+
if (!token) throw new Error("Must be signed in to verify MFA setup");
|
|
1653
|
+
await this.apiPostAuth("/v1/auth/mfa/totp/verify-setup", { code }, token);
|
|
1654
|
+
}
|
|
1655
|
+
async verifyMfa(mfaToken, code) {
|
|
1656
|
+
const res = await this.apiPost("/v1/auth/mfa/verify", { mfaToken, code });
|
|
1657
|
+
this.session.setSession(res);
|
|
1658
|
+
this.emit("signedIn", res.user);
|
|
1659
|
+
return res.user;
|
|
1660
|
+
}
|
|
1661
|
+
async disableMfa(code) {
|
|
1662
|
+
const token = this.session.getToken();
|
|
1663
|
+
if (!token) throw new Error("Must be signed in to disable MFA");
|
|
1664
|
+
await this.apiPostAuth("/v1/auth/mfa/disable", { code }, token);
|
|
1665
|
+
}
|
|
1666
|
+
async getMfaStatus() {
|
|
1667
|
+
const token = this.session.getToken();
|
|
1668
|
+
if (!token) throw new Error("Must be signed in to get MFA status");
|
|
1669
|
+
const res = await fetch(`${this.config.apiUrl}/v1/auth/mfa/status`, {
|
|
1670
|
+
headers: {
|
|
1671
|
+
"x-api-key": this.publishableKey,
|
|
1672
|
+
Authorization: `Bearer ${token}`
|
|
1673
|
+
},
|
|
1674
|
+
credentials: "include"
|
|
1675
|
+
});
|
|
1676
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, "/v1/auth/mfa/status"));
|
|
1677
|
+
return res.json();
|
|
1678
|
+
}
|
|
1679
|
+
async regenerateBackupCodes(code) {
|
|
1680
|
+
const token = this.session.getToken();
|
|
1681
|
+
if (!token) throw new Error("Must be signed in to regenerate backup codes");
|
|
1682
|
+
const res = await this.apiPostAuth(
|
|
1683
|
+
"/v1/auth/mfa/backup-codes/regenerate",
|
|
1684
|
+
{ code },
|
|
1685
|
+
token
|
|
1686
|
+
);
|
|
1687
|
+
return res.backupCodes;
|
|
1688
|
+
}
|
|
1689
|
+
// ── Passwordless ──
|
|
1690
|
+
async sendMagicLink(email) {
|
|
1691
|
+
await this.apiPost("/v1/auth/passwordless/magic-link", { email });
|
|
1692
|
+
}
|
|
1693
|
+
async sendEmailOtp(email) {
|
|
1694
|
+
await this.apiPost("/v1/auth/passwordless/email-otp", { email });
|
|
1695
|
+
}
|
|
1696
|
+
async verifyPasswordless(options) {
|
|
1697
|
+
const res = await this.apiPost("/v1/auth/passwordless/verify", options);
|
|
1698
|
+
this.session.setSession(res);
|
|
1699
|
+
this.emit("signedIn", res.user);
|
|
1700
|
+
return res.user;
|
|
1701
|
+
}
|
|
1702
|
+
// ── Passkeys ──
|
|
1703
|
+
async registerPasskey(name) {
|
|
1704
|
+
const token = this.session.getToken();
|
|
1705
|
+
if (!token) throw new Error("Must be signed in to register a passkey");
|
|
1706
|
+
const options = await this.apiPostAuth(
|
|
1707
|
+
"/v1/auth/passkeys/register/options",
|
|
1708
|
+
name ? { name } : void 0,
|
|
1709
|
+
token
|
|
1710
|
+
);
|
|
1711
|
+
const credential = await navigator.credentials.create({
|
|
1712
|
+
publicKey: this.deserializeCreationOptions(options.options)
|
|
1713
|
+
});
|
|
1714
|
+
const attestation = credential.response;
|
|
1715
|
+
const result = await this.apiPostAuth(
|
|
1716
|
+
"/v1/auth/passkeys/register/verify",
|
|
1717
|
+
{
|
|
1718
|
+
id: credential.id,
|
|
1719
|
+
rawId: this.bufferToBase64url(credential.rawId),
|
|
1720
|
+
type: credential.type,
|
|
1721
|
+
response: {
|
|
1722
|
+
attestationObject: this.bufferToBase64url(attestation.attestationObject),
|
|
1723
|
+
clientDataJSON: this.bufferToBase64url(attestation.clientDataJSON)
|
|
1724
|
+
}
|
|
1725
|
+
},
|
|
1726
|
+
token
|
|
1727
|
+
);
|
|
1728
|
+
this.emit("passkeyRegistered", result);
|
|
1729
|
+
return result;
|
|
1730
|
+
}
|
|
1731
|
+
async authenticateWithPasskey(email) {
|
|
1732
|
+
const options = await this.apiPost(
|
|
1733
|
+
"/v1/auth/passkeys/authenticate/options",
|
|
1734
|
+
email ? { email } : void 0
|
|
1735
|
+
);
|
|
1736
|
+
const credential = await navigator.credentials.get({
|
|
1737
|
+
publicKey: this.deserializeRequestOptions(options.options)
|
|
1738
|
+
});
|
|
1739
|
+
const assertion = credential.response;
|
|
1740
|
+
const res = await this.apiPost("/v1/auth/passkeys/authenticate/verify", {
|
|
1741
|
+
id: credential.id,
|
|
1742
|
+
rawId: this.bufferToBase64url(credential.rawId),
|
|
1743
|
+
type: credential.type,
|
|
1744
|
+
response: {
|
|
1745
|
+
authenticatorData: this.bufferToBase64url(assertion.authenticatorData),
|
|
1746
|
+
clientDataJSON: this.bufferToBase64url(assertion.clientDataJSON),
|
|
1747
|
+
signature: this.bufferToBase64url(assertion.signature),
|
|
1748
|
+
userHandle: assertion.userHandle ? this.bufferToBase64url(assertion.userHandle) : void 0
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
this.session.setSession(res);
|
|
1752
|
+
this.emit("signedIn", res.user);
|
|
1753
|
+
return res.user;
|
|
1754
|
+
}
|
|
1755
|
+
async listPasskeys() {
|
|
1756
|
+
const token = this.session.getToken();
|
|
1757
|
+
if (!token) throw new Error("Must be signed in to list passkeys");
|
|
1758
|
+
return this.apiGetAuth("/v1/auth/passkeys", token);
|
|
1759
|
+
}
|
|
1760
|
+
async renamePasskey(passkeyId, name) {
|
|
1761
|
+
const token = this.session.getToken();
|
|
1762
|
+
if (!token) throw new Error("Must be signed in to rename a passkey");
|
|
1763
|
+
return this.apiPatchAuth(`/v1/auth/passkeys/${passkeyId}`, { name }, token);
|
|
1764
|
+
}
|
|
1765
|
+
async revokePasskey(passkeyId) {
|
|
1766
|
+
const token = this.session.getToken();
|
|
1767
|
+
if (!token) throw new Error("Must be signed in to revoke a passkey");
|
|
1768
|
+
await this.apiDeleteAuth(`/v1/auth/passkeys/${passkeyId}`, token);
|
|
1769
|
+
}
|
|
1770
|
+
// ── Web3 ──
|
|
1771
|
+
async web3GetNonce(address, chain, walletType, chainId) {
|
|
1772
|
+
return this.apiPost("/v1/auth/web3/nonce", {
|
|
1773
|
+
address,
|
|
1774
|
+
chain,
|
|
1775
|
+
walletType,
|
|
1776
|
+
...chainId != null ? { chainId } : {}
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
async web3Verify(message, signature, address, chain, walletType) {
|
|
1780
|
+
const res = await this.apiPost("/v1/auth/web3/verify", {
|
|
1781
|
+
message,
|
|
1782
|
+
signature,
|
|
1783
|
+
address,
|
|
1784
|
+
chain,
|
|
1785
|
+
walletType
|
|
1786
|
+
});
|
|
1787
|
+
this.session.setSession(res);
|
|
1788
|
+
this.emit("signedIn", res.user);
|
|
1789
|
+
return res.user;
|
|
1790
|
+
}
|
|
1791
|
+
async listWallets() {
|
|
1792
|
+
const token = this.session.getToken();
|
|
1793
|
+
if (!token) throw new Error("Must be signed in to list wallets");
|
|
1794
|
+
return this.apiGetAuth("/v1/auth/web3/wallets", token);
|
|
1795
|
+
}
|
|
1796
|
+
async linkWallet(params) {
|
|
1797
|
+
const token = this.session.getToken();
|
|
1798
|
+
if (!token) throw new Error("Must be signed in to link a wallet");
|
|
1799
|
+
const wallet = await this.apiPostAuth("/v1/auth/web3/wallets/link", params, token);
|
|
1800
|
+
this.emit("web3Connected", wallet);
|
|
1801
|
+
return wallet;
|
|
1802
|
+
}
|
|
1803
|
+
async unlinkWallet(walletId) {
|
|
1804
|
+
const token = this.session.getToken();
|
|
1805
|
+
if (!token) throw new Error("Must be signed in to unlink a wallet");
|
|
1806
|
+
await this.apiDeleteAuth(`/v1/auth/web3/wallets/${walletId}`, token);
|
|
1807
|
+
}
|
|
1808
|
+
// ── User Profile ──
|
|
1809
|
+
async updateProfile(data) {
|
|
1810
|
+
const token = this.session.getToken();
|
|
1811
|
+
if (!token) throw new Error("Must be signed in to update profile");
|
|
1812
|
+
const user = await this.apiPatchAuth("/v1/auth/me", data, token);
|
|
1813
|
+
this.session.updateUser(user);
|
|
1814
|
+
return user;
|
|
1815
|
+
}
|
|
1816
|
+
// ── Session Management ──
|
|
1817
|
+
async listSessions() {
|
|
1818
|
+
const token = this.session.getToken();
|
|
1819
|
+
if (!token) throw new Error("Must be signed in to list sessions");
|
|
1820
|
+
return this.apiGetAuth("/v1/auth/me/sessions", token);
|
|
1821
|
+
}
|
|
1822
|
+
async revokeSession(sessionId) {
|
|
1823
|
+
const token = this.session.getToken();
|
|
1824
|
+
if (!token) throw new Error("Must be signed in to revoke a session");
|
|
1825
|
+
await this.apiDeleteAuth(`/v1/auth/me/sessions/${sessionId}`, token);
|
|
1826
|
+
}
|
|
1827
|
+
// ── Organizations ──
|
|
1828
|
+
organizations = {
|
|
1829
|
+
list: async () => {
|
|
1830
|
+
const token = this.session.getToken();
|
|
1831
|
+
if (!token) throw new Error("Must be signed in to list organizations");
|
|
1832
|
+
return this.apiGetAuth("/v1/auth/organizations", token);
|
|
1833
|
+
},
|
|
1834
|
+
create: async (params) => {
|
|
1835
|
+
const token = this.session.getToken();
|
|
1836
|
+
if (!token) throw new Error("Must be signed in to create an organization");
|
|
1837
|
+
return this.apiPostAuth("/v1/auth/organizations", params, token);
|
|
1838
|
+
},
|
|
1839
|
+
get: async (orgId) => {
|
|
1840
|
+
const token = this.session.getToken();
|
|
1841
|
+
if (!token) throw new Error("Must be signed in to get organization");
|
|
1842
|
+
return this.apiGetAuth(`/v1/auth/organizations/${orgId}`, token);
|
|
1843
|
+
},
|
|
1844
|
+
update: async (orgId, params) => {
|
|
1845
|
+
const token = this.session.getToken();
|
|
1846
|
+
if (!token) throw new Error("Must be signed in to update organization");
|
|
1847
|
+
return this.apiPatchAuth(`/v1/auth/organizations/${orgId}`, params, token);
|
|
1848
|
+
},
|
|
1849
|
+
delete: async (orgId) => {
|
|
1850
|
+
const token = this.session.getToken();
|
|
1851
|
+
if (!token) throw new Error("Must be signed in to delete organization");
|
|
1852
|
+
await this.apiDeleteAuth(`/v1/auth/organizations/${orgId}`, token);
|
|
1853
|
+
},
|
|
1854
|
+
getMembers: async (orgId) => {
|
|
1855
|
+
const token = this.session.getToken();
|
|
1856
|
+
if (!token) throw new Error("Must be signed in to get organization members");
|
|
1857
|
+
return this.apiGetAuth(`/v1/auth/organizations/${orgId}/members`, token);
|
|
1858
|
+
},
|
|
1859
|
+
invite: async (orgId, params) => {
|
|
1860
|
+
const token = this.session.getToken();
|
|
1861
|
+
if (!token) throw new Error("Must be signed in to invite a member");
|
|
1862
|
+
return this.apiPostAuth(`/v1/auth/organizations/${orgId}/invitations`, params, token);
|
|
1863
|
+
},
|
|
1864
|
+
getInvitations: async (orgId) => {
|
|
1865
|
+
const token = this.session.getToken();
|
|
1866
|
+
if (!token) throw new Error("Must be signed in to get invitations");
|
|
1867
|
+
return this.apiGetAuth(`/v1/auth/organizations/${orgId}/invitations`, token);
|
|
1868
|
+
},
|
|
1869
|
+
acceptInvitation: async (token) => {
|
|
1870
|
+
const authToken = this.session.getToken();
|
|
1871
|
+
if (!authToken) throw new Error("Must be signed in to accept an invitation");
|
|
1872
|
+
return this.apiPostAuth(`/v1/auth/organizations/invitations/${token}/accept`, void 0, authToken);
|
|
1873
|
+
},
|
|
1874
|
+
rejectInvitation: async (token) => {
|
|
1875
|
+
const authToken = this.session.getToken();
|
|
1876
|
+
if (!authToken) throw new Error("Must be signed in to reject an invitation");
|
|
1877
|
+
await this.apiPostAuth(`/v1/auth/organizations/invitations/${token}/reject`, void 0, authToken);
|
|
1878
|
+
},
|
|
1879
|
+
removeMember: async (orgId, memberId) => {
|
|
1880
|
+
const token = this.session.getToken();
|
|
1881
|
+
if (!token) throw new Error("Must be signed in to remove a member");
|
|
1882
|
+
await this.apiDeleteAuth(`/v1/auth/organizations/${orgId}/members/${memberId}`, token);
|
|
1883
|
+
},
|
|
1884
|
+
updateMemberRole: async (orgId, memberId, role) => {
|
|
1885
|
+
const token = this.session.getToken();
|
|
1886
|
+
if (!token) throw new Error("Must be signed in to update member role");
|
|
1887
|
+
return this.apiPatchAuth(`/v1/auth/organizations/${orgId}/members/${memberId}`, { role }, token);
|
|
1888
|
+
},
|
|
1889
|
+
leave: async (orgId) => {
|
|
1890
|
+
const token = this.session.getToken();
|
|
1891
|
+
if (!token) throw new Error("Must be signed in to leave organization");
|
|
1892
|
+
await this.apiPostAuth(`/v1/auth/organizations/${orgId}/leave`, void 0, token);
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
634
1895
|
destroy() {
|
|
635
1896
|
this.modal?.close();
|
|
636
1897
|
this.session.destroy();
|
|
@@ -678,7 +1939,53 @@ var Authon = class {
|
|
|
678
1939
|
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
679
1940
|
});
|
|
680
1941
|
},
|
|
681
|
-
onClose: () => this.modal?.close()
|
|
1942
|
+
onClose: () => this.modal?.close(),
|
|
1943
|
+
onWeb3WalletSelect: async (walletId) => {
|
|
1944
|
+
const chain = walletId === "phantom" ? "solana" : "evm";
|
|
1945
|
+
try {
|
|
1946
|
+
this.modal?.showOverlay?.("web3-connecting");
|
|
1947
|
+
const address = await this.getWalletAddress(walletId);
|
|
1948
|
+
const { message } = await this.web3GetNonce(address, chain, walletId);
|
|
1949
|
+
const signature = await this.requestWalletSignature(walletId, message);
|
|
1950
|
+
await this.web3Verify(message, signature, address, chain, walletId);
|
|
1951
|
+
this.modal?.showWeb3Success(walletId, address);
|
|
1952
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
1953
|
+
} catch (err) {
|
|
1954
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1955
|
+
}
|
|
1956
|
+
},
|
|
1957
|
+
onPasswordlessSubmit: async (email) => {
|
|
1958
|
+
try {
|
|
1959
|
+
const method = this.branding?.passwordlessMethod ?? "magic_link";
|
|
1960
|
+
if (method === "email_otp" || method === "both") {
|
|
1961
|
+
await this.sendEmailOtp(email);
|
|
1962
|
+
this.modal?.showOtpInput(email);
|
|
1963
|
+
} else {
|
|
1964
|
+
await this.sendMagicLink(email);
|
|
1965
|
+
this.modal?.showPasswordlessSent();
|
|
1966
|
+
}
|
|
1967
|
+
} catch (err) {
|
|
1968
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
onOtpVerify: async (email, code) => {
|
|
1972
|
+
try {
|
|
1973
|
+
await this.verifyPasswordless({ email, code });
|
|
1974
|
+
this.modal?.close();
|
|
1975
|
+
} catch (err) {
|
|
1976
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1977
|
+
}
|
|
1978
|
+
},
|
|
1979
|
+
onPasskeyClick: async () => {
|
|
1980
|
+
try {
|
|
1981
|
+
this.modal?.showOverlay?.("passkey-verifying");
|
|
1982
|
+
await this.authenticateWithPasskey();
|
|
1983
|
+
this.modal?.showPasskeySuccess();
|
|
1984
|
+
setTimeout(() => this.modal?.close(), 2500);
|
|
1985
|
+
} catch (err) {
|
|
1986
|
+
this.modal?.showOverlayError(err instanceof Error ? err.message : String(err));
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
682
1989
|
});
|
|
683
1990
|
}
|
|
684
1991
|
if (this.branding) this.modal.setBranding(this.branding);
|
|
@@ -921,6 +2228,130 @@ var Authon = class {
|
|
|
921
2228
|
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
922
2229
|
return res.json();
|
|
923
2230
|
}
|
|
2231
|
+
async apiPostAuth(path, body, token) {
|
|
2232
|
+
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
2233
|
+
method: "POST",
|
|
2234
|
+
headers: {
|
|
2235
|
+
"Content-Type": "application/json",
|
|
2236
|
+
"x-api-key": this.publishableKey,
|
|
2237
|
+
Authorization: `Bearer ${token}`
|
|
2238
|
+
},
|
|
2239
|
+
credentials: "include",
|
|
2240
|
+
body: body ? JSON.stringify(body) : void 0
|
|
2241
|
+
});
|
|
2242
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
2243
|
+
return res.json();
|
|
2244
|
+
}
|
|
2245
|
+
async apiGetAuth(path, token) {
|
|
2246
|
+
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
2247
|
+
headers: {
|
|
2248
|
+
"x-api-key": this.publishableKey,
|
|
2249
|
+
Authorization: `Bearer ${token}`
|
|
2250
|
+
},
|
|
2251
|
+
credentials: "include"
|
|
2252
|
+
});
|
|
2253
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
2254
|
+
return res.json();
|
|
2255
|
+
}
|
|
2256
|
+
async apiPatchAuth(path, body, token) {
|
|
2257
|
+
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
2258
|
+
method: "PATCH",
|
|
2259
|
+
headers: {
|
|
2260
|
+
"Content-Type": "application/json",
|
|
2261
|
+
"x-api-key": this.publishableKey,
|
|
2262
|
+
Authorization: `Bearer ${token}`
|
|
2263
|
+
},
|
|
2264
|
+
credentials: "include",
|
|
2265
|
+
body: body ? JSON.stringify(body) : void 0
|
|
2266
|
+
});
|
|
2267
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
2268
|
+
return res.json();
|
|
2269
|
+
}
|
|
2270
|
+
async apiDeleteAuth(path, token) {
|
|
2271
|
+
const res = await fetch(`${this.config.apiUrl}${path}`, {
|
|
2272
|
+
method: "DELETE",
|
|
2273
|
+
headers: {
|
|
2274
|
+
"x-api-key": this.publishableKey,
|
|
2275
|
+
Authorization: `Bearer ${token}`
|
|
2276
|
+
},
|
|
2277
|
+
credentials: "include"
|
|
2278
|
+
});
|
|
2279
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
2280
|
+
}
|
|
2281
|
+
// ── Wallet helpers ──
|
|
2282
|
+
async getWalletAddress(walletId) {
|
|
2283
|
+
if (walletId === "phantom") {
|
|
2284
|
+
const provider2 = window.solana;
|
|
2285
|
+
if (!provider2?.isPhantom) throw new Error("Phantom wallet not detected. Please install it from phantom.app");
|
|
2286
|
+
const resp = await provider2.connect();
|
|
2287
|
+
return resp.publicKey.toString();
|
|
2288
|
+
}
|
|
2289
|
+
const provider = window.ethereum;
|
|
2290
|
+
if (!provider) throw new Error(`${walletId} wallet not detected. Please install it.`);
|
|
2291
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2292
|
+
return accounts[0];
|
|
2293
|
+
}
|
|
2294
|
+
async requestWalletSignature(walletId, message) {
|
|
2295
|
+
if (walletId === "phantom") {
|
|
2296
|
+
const provider2 = window.solana;
|
|
2297
|
+
const encoded = new TextEncoder().encode(message);
|
|
2298
|
+
const signed = await provider2.signMessage(encoded, "utf8");
|
|
2299
|
+
return Array.from(new Uint8Array(signed.signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2300
|
+
}
|
|
2301
|
+
const provider = window.ethereum;
|
|
2302
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
2303
|
+
return provider.request({ method: "personal_sign", params: [message, accounts[0]] });
|
|
2304
|
+
}
|
|
2305
|
+
// ── WebAuthn helpers ──
|
|
2306
|
+
bufferToBase64url(buffer) {
|
|
2307
|
+
const bytes = new Uint8Array(buffer);
|
|
2308
|
+
let binary = "";
|
|
2309
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2310
|
+
binary += String.fromCharCode(bytes[i]);
|
|
2311
|
+
}
|
|
2312
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
2313
|
+
}
|
|
2314
|
+
base64urlToBuffer(base64url) {
|
|
2315
|
+
const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
2316
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
2317
|
+
const binary = atob(padded);
|
|
2318
|
+
const bytes = new Uint8Array(binary.length);
|
|
2319
|
+
for (let i = 0; i < binary.length; i++) {
|
|
2320
|
+
bytes[i] = binary.charCodeAt(i);
|
|
2321
|
+
}
|
|
2322
|
+
return bytes.buffer;
|
|
2323
|
+
}
|
|
2324
|
+
deserializeCreationOptions(options) {
|
|
2325
|
+
const opts = { ...options };
|
|
2326
|
+
if (typeof opts.challenge === "string") {
|
|
2327
|
+
opts.challenge = this.base64urlToBuffer(opts.challenge);
|
|
2328
|
+
}
|
|
2329
|
+
if (opts.user && typeof opts.user.id === "string") {
|
|
2330
|
+
opts.user.id = this.base64urlToBuffer(
|
|
2331
|
+
opts.user.id
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
if (Array.isArray(opts.excludeCredentials)) {
|
|
2335
|
+
opts.excludeCredentials = opts.excludeCredentials.map((c) => ({
|
|
2336
|
+
...c,
|
|
2337
|
+
id: typeof c.id === "string" ? this.base64urlToBuffer(c.id) : c.id
|
|
2338
|
+
}));
|
|
2339
|
+
}
|
|
2340
|
+
return opts;
|
|
2341
|
+
}
|
|
2342
|
+
deserializeRequestOptions(options) {
|
|
2343
|
+
const opts = { ...options };
|
|
2344
|
+
if (typeof opts.challenge === "string") {
|
|
2345
|
+
opts.challenge = this.base64urlToBuffer(opts.challenge);
|
|
2346
|
+
}
|
|
2347
|
+
if (Array.isArray(opts.allowCredentials)) {
|
|
2348
|
+
opts.allowCredentials = opts.allowCredentials.map((c) => ({
|
|
2349
|
+
...c,
|
|
2350
|
+
id: typeof c.id === "string" ? this.base64urlToBuffer(c.id) : c.id
|
|
2351
|
+
}));
|
|
2352
|
+
}
|
|
2353
|
+
return opts;
|
|
2354
|
+
}
|
|
924
2355
|
async parseApiError(res, path) {
|
|
925
2356
|
try {
|
|
926
2357
|
const body = await res.json();
|
|
@@ -938,6 +2369,8 @@ var Authon = class {
|
|
|
938
2369
|
// Annotate the CommonJS export names for ESM import in node:
|
|
939
2370
|
0 && (module.exports = {
|
|
940
2371
|
Authon,
|
|
2372
|
+
AuthonMfaRequiredError,
|
|
2373
|
+
generateQrSvg,
|
|
941
2374
|
getProviderButtonConfig
|
|
942
2375
|
});
|
|
943
2376
|
//# sourceMappingURL=index.cjs.map
|