@authon/js 0.1.0

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.md ADDED
@@ -0,0 +1,100 @@
1
+ # @authon/js
2
+
3
+ Core browser SDK for [Authon](https://authon.dev) — ShadowDOM login modal, OAuth flows, and session management.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @authon/js
9
+ # or
10
+ pnpm add @authon/js
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```ts
16
+ import { Authon } from '@authon/js';
17
+
18
+ const authon = new Authon('pk_live_...');
19
+
20
+ // Open the sign-in modal
21
+ await authon.openSignIn();
22
+
23
+ // Listen for auth events
24
+ authon.on('signedIn', (user) => {
25
+ console.log('Signed in:', user.email);
26
+ });
27
+
28
+ authon.on('signedOut', () => {
29
+ console.log('Signed out');
30
+ });
31
+
32
+ // Email/password sign-in
33
+ const user = await authon.signInWithEmail('user@example.com', 'password');
34
+
35
+ // OAuth sign-in (opens popup)
36
+ await authon.signInWithOAuth('google');
37
+
38
+ // Get current user and token
39
+ const currentUser = authon.getUser();
40
+ const token = authon.getToken();
41
+
42
+ // Sign out
43
+ await authon.signOut();
44
+ ```
45
+
46
+ ## Configuration
47
+
48
+ ```ts
49
+ const authon = new Authon('pk_live_...', {
50
+ apiUrl: 'https://api.authon.dev', // Custom API URL
51
+ mode: 'popup', // 'popup' | 'embedded'
52
+ theme: 'auto', // 'light' | 'dark' | 'auto'
53
+ locale: 'en', // Locale for the modal UI
54
+ containerId: 'auth-container', // Container element ID (embedded mode)
55
+ appearance: { // Custom branding overrides
56
+ primaryColorStart: '#7c3aed',
57
+ primaryColorEnd: '#4f46e5',
58
+ borderRadius: 12,
59
+ brandName: 'My App',
60
+ },
61
+ });
62
+ ```
63
+
64
+ ## API Reference
65
+
66
+ ### `Authon` class
67
+
68
+ | Method | Returns | Description |
69
+ |--------|---------|-------------|
70
+ | `openSignIn()` | `Promise<void>` | Open the sign-in modal |
71
+ | `openSignUp()` | `Promise<void>` | Open the sign-up modal |
72
+ | `signInWithEmail(email, password)` | `Promise<AuthonUser>` | Sign in with email/password |
73
+ | `signUpWithEmail(email, password, meta?)` | `Promise<AuthonUser>` | Register with email/password |
74
+ | `signInWithOAuth(provider)` | `Promise<void>` | Start OAuth flow in popup window |
75
+ | `signOut()` | `Promise<void>` | Sign out and clear session |
76
+ | `getUser()` | `AuthonUser \| null` | Get current user |
77
+ | `getToken()` | `string \| null` | Get current access token |
78
+ | `on(event, listener)` | `() => void` | Subscribe to events (returns unsubscribe fn) |
79
+ | `destroy()` | `void` | Clean up resources |
80
+
81
+ ### Events
82
+
83
+ | Event | Payload | Description |
84
+ |-------|---------|-------------|
85
+ | `signedIn` | `AuthonUser` | User signed in |
86
+ | `signedOut` | none | User signed out |
87
+ | `tokenRefreshed` | `string` | Access token was refreshed |
88
+ | `error` | `Error` | An error occurred |
89
+
90
+ ## ShadowDOM Modal
91
+
92
+ The login modal renders inside a ShadowRoot, preventing CSS conflicts with your application. Branding (colors, logo, border radius, custom CSS) is fetched from your Authon project settings and can be overridden via the `appearance` config.
93
+
94
+ ## Documentation
95
+
96
+ [authon.dev/docs](https://authon.dev/docs)
97
+
98
+ ## License
99
+
100
+ [MIT](../../LICENSE)
package/dist/index.cjs ADDED
@@ -0,0 +1,524 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Authon: () => Authon,
24
+ getProviderButtonConfig: () => getProviderButtonConfig
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/modal.ts
29
+ var import_shared2 = require("@authon/shared");
30
+
31
+ // src/providers.ts
32
+ var import_shared = require("@authon/shared");
33
+ var PROVIDER_ICONS = {
34
+ 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
+ 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>`,
36
+ kakao: `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="#191919" d="M12 3C6.48 3 2 6.36 2 10.43c0 2.62 1.75 4.93 4.37 6.23l-1.12 4.14c-.1.36.31.65.62.44l4.93-3.26c.39.04.79.06 1.2.06 5.52 0 10-3.36 10-7.61C22 6.36 17.52 3 12 3z"/></svg>`,
37
+ naver: `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="#fff" d="M16.27 3H7.73A4.73 4.73 0 003 7.73v8.54A4.73 4.73 0 007.73 21h8.54A4.73 4.73 0 0021 16.27V7.73A4.73 4.73 0 0016.27 3zm-1.84 12.44h-2.1l-2.86-4.15v4.15H7.38V8.56h2.1l2.86 4.15V8.56h2.09v6.88z"/></svg>`,
38
+ facebook: `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="#fff" d="M24 12.07C24 5.41 18.63 0 12 0S0 5.4 0 12.07C0 18.1 4.39 23.1 10.13 24v-8.44H7.08v-3.49h3.04V9.41c0-3.02 1.8-4.7 4.54-4.7 1.31 0 2.68.24 2.68.24v2.97h-1.5c-1.5 0-1.96.93-1.96 1.89v2.26h3.33l-.53 3.49h-2.8V24C19.62 23.1 24 18.1 24 12.07z"/></svg>`,
39
+ github: `<svg viewBox="0 0 24 24" width="20" height="20" fill="#fff"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>`,
40
+ discord: `<svg viewBox="0 0 24 24" width="20" height="20" fill="#fff"><path d="M20.32 4.37a19.8 19.8 0 00-4.89-1.52.07.07 0 00-.08.04c-.21.38-.44.87-.61 1.26a18.27 18.27 0 00-5.49 0 12.64 12.64 0 00-.62-1.26.07.07 0 00-.08-.04 19.74 19.74 0 00-4.89 1.52.07.07 0 00-.03.03C1.11 8.39.34 12.28.73 16.12a.08.08 0 00.03.06 19.9 19.9 0 005.99 3.03.08.08 0 00.08-.03c.46-.63.87-1.3 1.22-2a.08.08 0 00-.04-.11 13.1 13.1 0 01-1.87-.9.08.08 0 01-.01-.13c.13-.09.25-.19.37-.29a.07.07 0 01.08-.01c3.93 1.8 8.18 1.8 12.07 0a.07.07 0 01.08 0c.12.1.25.2.37.3a.08.08 0 01-.01.12c-.6.35-1.22.65-1.87.9a.08.08 0 00-.04.1c.36.7.77 1.37 1.22 2a.08.08 0 00.08.03 19.83 19.83 0 006-3.03.08.08 0 00.03-.05c.47-4.87-.78-9.09-3.3-12.84a.06.06 0 00-.03-.03zM8.02 13.62c-1.11 0-2.03-1.02-2.03-2.28 0-1.26.9-2.28 2.03-2.28 1.14 0 2.04 1.03 2.03 2.28 0 1.26-.9 2.28-2.03 2.28zm7.5 0c-1.11 0-2.03-1.02-2.03-2.28 0-1.26.9-2.28 2.03-2.28 1.14 0 2.04 1.03 2.03 2.28 0 1.26-.89 2.28-2.03 2.28z"/></svg>`,
41
+ x: `<svg viewBox="0 0 24 24" width="20" height="20" fill="#fff"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>`,
42
+ line: `<svg viewBox="0 0 24 24" width="20" height="20" fill="#fff"><path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63h2.386c.346 0 .627.285.627.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63.346 0 .628.285.628.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314"/></svg>`,
43
+ 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
+ };
45
+ function getProviderButtonConfig(provider) {
46
+ const colors = import_shared.PROVIDER_COLORS[provider];
47
+ return {
48
+ provider,
49
+ label: `Continue with ${import_shared.PROVIDER_DISPLAY_NAMES[provider]}`,
50
+ bgColor: colors.bg,
51
+ textColor: colors.text,
52
+ iconSvg: PROVIDER_ICONS[provider]
53
+ };
54
+ }
55
+
56
+ // src/modal.ts
57
+ var ModalRenderer = class {
58
+ shadowRoot = null;
59
+ hostElement = null;
60
+ containerElement = null;
61
+ mode;
62
+ branding;
63
+ enabledProviders = [];
64
+ onProviderClick;
65
+ onEmailSubmit;
66
+ onClose;
67
+ constructor(options) {
68
+ this.mode = options.mode;
69
+ this.branding = { ...import_shared2.DEFAULT_BRANDING, ...options.branding };
70
+ this.onProviderClick = options.onProviderClick;
71
+ this.onEmailSubmit = options.onEmailSubmit;
72
+ this.onClose = options.onClose;
73
+ if (options.mode === "embedded" && options.containerId) {
74
+ this.containerElement = document.getElementById(options.containerId);
75
+ }
76
+ }
77
+ setProviders(providers) {
78
+ this.enabledProviders = providers;
79
+ }
80
+ setBranding(branding) {
81
+ this.branding = { ...import_shared2.DEFAULT_BRANDING, ...branding };
82
+ }
83
+ open(view = "signIn") {
84
+ this.close();
85
+ this.render(view);
86
+ }
87
+ close() {
88
+ if (this.hostElement) {
89
+ this.hostElement.remove();
90
+ this.hostElement = null;
91
+ this.shadowRoot = null;
92
+ }
93
+ if (this.containerElement) {
94
+ this.containerElement.innerHTML = "";
95
+ }
96
+ }
97
+ render(view) {
98
+ const host = document.createElement("div");
99
+ host.setAttribute("data-authon-modal", "");
100
+ this.hostElement = host;
101
+ if (this.mode === "popup") {
102
+ document.body.appendChild(host);
103
+ } else if (this.containerElement) {
104
+ this.containerElement.appendChild(host);
105
+ }
106
+ this.shadowRoot = host.attachShadow({ mode: "open" });
107
+ this.shadowRoot.innerHTML = this.buildHTML(view);
108
+ this.attachEvents(view);
109
+ }
110
+ buildHTML(view) {
111
+ const b = this.branding;
112
+ const isSignUp = view === "signUp";
113
+ const title = isSignUp ? "Create your account" : "Welcome back";
114
+ const subtitle = isSignUp ? "Already have an account?" : "Don't have an account?";
115
+ const subtitleLink = isSignUp ? "Sign in" : "Sign up";
116
+ const providerButtons = this.enabledProviders.filter((p) => !b.hiddenProviders?.includes(p)).map((p) => {
117
+ const config = getProviderButtonConfig(p);
118
+ return `<button class="provider-btn" data-provider="${p}" style="background:${config.bgColor};color:${config.textColor};border:1px solid ${config.bgColor === "#ffffff" ? "#e5e7eb" : config.bgColor}">
119
+ <span class="provider-icon">${config.iconSvg}</span>
120
+ <span>${config.label}</span>
121
+ </button>`;
122
+ }).join("");
123
+ const divider = b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
124
+ const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
125
+ <input type="email" placeholder="Email address" name="email" required class="input" />
126
+ <input type="password" placeholder="Password" name="password" required class="input" />
127
+ <button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
128
+ </form>` : "";
129
+ const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
130
+ ${b.termsUrl ? `<a href="${b.termsUrl}" target="_blank">Terms of Service</a>` : ""}
131
+ ${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
132
+ ${b.privacyUrl ? `<a href="${b.privacyUrl}" target="_blank">Privacy Policy</a>` : ""}
133
+ </div>` : "";
134
+ const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
135
+ return `
136
+ <style>${this.buildCSS()}</style>
137
+ ${popupWrapper}
138
+ <div class="modal-container" role="dialog" aria-modal="true">
139
+ ${b.logoDataUrl ? `<img src="${b.logoDataUrl}" alt="Logo" class="logo" />` : ""}
140
+ <h2 class="title">${title}</h2>
141
+ ${b.brandName ? `<p class="brand-name">${b.brandName}</p>` : ""}
142
+ <div class="providers">${providerButtons}</div>
143
+ ${divider}
144
+ ${emailForm}
145
+ <p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
146
+ ${footer}
147
+ </div>
148
+ `;
149
+ }
150
+ buildCSS() {
151
+ const b = this.branding;
152
+ return `
153
+ :host {
154
+ --authon-primary-start: ${b.primaryColorStart || "#7c3aed"};
155
+ --authon-primary-end: ${b.primaryColorEnd || "#4f46e5"};
156
+ --authon-light-bg: ${b.lightBg || "#ffffff"};
157
+ --authon-light-text: ${b.lightText || "#111827"};
158
+ --authon-dark-bg: ${b.darkBg || "#0f172a"};
159
+ --authon-dark-text: ${b.darkText || "#f1f5f9"};
160
+ --authon-radius: ${b.borderRadius ?? 12}px;
161
+ --authon-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
162
+ font-family: var(--authon-font);
163
+ color: var(--authon-light-text);
164
+ }
165
+ * { box-sizing: border-box; margin: 0; padding: 0; }
166
+ .backdrop {
167
+ position: fixed; inset: 0; z-index: 99998;
168
+ background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
169
+ animation: fadeIn 0.2s ease;
170
+ }
171
+ .modal-container {
172
+ ${this.mode === "popup" ? "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 99999; max-height: 90vh; overflow-y: auto;" : ""}
173
+ background: var(--authon-light-bg);
174
+ border-radius: var(--authon-radius);
175
+ padding: 32px;
176
+ width: 400px; max-width: 100%;
177
+ ${this.mode === "popup" ? "box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25); animation: slideIn 0.3s ease;" : ""}
178
+ }
179
+ .logo { display: block; margin: 0 auto 16px; max-height: 48px; }
180
+ .title { text-align: center; font-size: 24px; font-weight: 700; margin-bottom: 8px; }
181
+ .brand-name { text-align: center; font-size: 14px; color: #6b7280; margin-bottom: 24px; }
182
+ .providers { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
183
+ .provider-btn {
184
+ display: flex; align-items: center; gap: 12px;
185
+ width: 100%; padding: 10px 16px; border-radius: calc(var(--authon-radius) * 0.67);
186
+ font-size: 14px; font-weight: 500; cursor: pointer;
187
+ transition: opacity 0.15s, transform 0.1s;
188
+ font-family: var(--authon-font);
189
+ }
190
+ .provider-btn:hover { opacity: 0.9; }
191
+ .provider-btn:active { transform: scale(0.98); }
192
+ .provider-icon { display: flex; align-items: center; flex-shrink: 0; }
193
+ .divider {
194
+ display: flex; align-items: center; gap: 12px;
195
+ margin: 16px 0; color: #9ca3af; font-size: 13px;
196
+ }
197
+ .divider::before, .divider::after {
198
+ content: ''; flex: 1; height: 1px; background: #e5e7eb;
199
+ }
200
+ .email-form { display: flex; flex-direction: column; gap: 10px; }
201
+ .input {
202
+ width: 100%; padding: 10px 14px;
203
+ border: 1px solid #d1d5db; border-radius: calc(var(--authon-radius) * 0.5);
204
+ font-size: 14px; font-family: var(--authon-font);
205
+ outline: none; transition: border-color 0.15s;
206
+ }
207
+ .input:focus { border-color: var(--authon-primary-start); box-shadow: 0 0 0 3px rgba(124,58,237,0.1); }
208
+ .submit-btn {
209
+ width: 100%; padding: 10px;
210
+ background: linear-gradient(135deg, var(--authon-primary-start), var(--authon-primary-end));
211
+ color: #fff; border: none; border-radius: calc(var(--authon-radius) * 0.5);
212
+ font-size: 14px; font-weight: 600; cursor: pointer;
213
+ font-family: var(--authon-font); transition: opacity 0.15s;
214
+ }
215
+ .submit-btn:hover { opacity: 0.9; }
216
+ .switch-view { text-align: center; margin-top: 16px; font-size: 13px; color: #6b7280; }
217
+ .switch-view a { color: var(--authon-primary-start); text-decoration: none; font-weight: 500; }
218
+ .switch-view a:hover { text-decoration: underline; }
219
+ .footer { text-align: center; margin-top: 16px; font-size: 12px; color: #9ca3af; }
220
+ .footer a { color: #9ca3af; text-decoration: none; }
221
+ .footer a:hover { text-decoration: underline; }
222
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
223
+ @keyframes slideIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } }
224
+ ${b.customCss || ""}
225
+ `;
226
+ }
227
+ attachEvents(view) {
228
+ if (!this.shadowRoot) return;
229
+ this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
230
+ btn.addEventListener("click", () => {
231
+ const provider = btn.dataset.provider;
232
+ this.onProviderClick(provider);
233
+ });
234
+ });
235
+ const form = this.shadowRoot.getElementById("email-form");
236
+ if (form) {
237
+ form.addEventListener("submit", (e) => {
238
+ e.preventDefault();
239
+ const formData = new FormData(form);
240
+ this.onEmailSubmit(
241
+ formData.get("email"),
242
+ formData.get("password"),
243
+ view === "signUp"
244
+ );
245
+ });
246
+ }
247
+ const switchLink = this.shadowRoot.getElementById("switch-link");
248
+ if (switchLink) {
249
+ switchLink.addEventListener("click", (e) => {
250
+ e.preventDefault();
251
+ this.open(view === "signIn" ? "signUp" : "signIn");
252
+ });
253
+ }
254
+ const backdrop = this.shadowRoot.getElementById("backdrop");
255
+ if (backdrop) {
256
+ backdrop.addEventListener("click", () => this.onClose());
257
+ }
258
+ if (this.mode === "popup") {
259
+ const handler = (e) => {
260
+ if (e.key === "Escape") {
261
+ this.onClose();
262
+ document.removeEventListener("keydown", handler);
263
+ }
264
+ };
265
+ document.addEventListener("keydown", handler);
266
+ }
267
+ }
268
+ };
269
+
270
+ // src/session.ts
271
+ var SessionManager = class {
272
+ accessToken = null;
273
+ user = null;
274
+ refreshTimer = null;
275
+ apiUrl;
276
+ publishableKey;
277
+ constructor(publishableKey, apiUrl) {
278
+ this.publishableKey = publishableKey;
279
+ this.apiUrl = apiUrl;
280
+ }
281
+ getToken() {
282
+ return this.accessToken;
283
+ }
284
+ getUser() {
285
+ return this.user;
286
+ }
287
+ setSession(tokens) {
288
+ this.accessToken = tokens.accessToken;
289
+ this.user = tokens.user;
290
+ this.scheduleRefresh(tokens.expiresIn);
291
+ }
292
+ clearSession() {
293
+ this.accessToken = null;
294
+ this.user = null;
295
+ if (this.refreshTimer) {
296
+ clearTimeout(this.refreshTimer);
297
+ this.refreshTimer = null;
298
+ }
299
+ }
300
+ scheduleRefresh(expiresIn) {
301
+ if (this.refreshTimer) clearTimeout(this.refreshTimer);
302
+ const refreshIn = Math.max((expiresIn - 60) * 1e3, 5e3);
303
+ this.refreshTimer = setTimeout(() => this.refresh(), refreshIn);
304
+ }
305
+ async refresh() {
306
+ try {
307
+ const res = await fetch(`${this.apiUrl}/v1/auth/token/refresh`, {
308
+ method: "POST",
309
+ headers: {
310
+ "Content-Type": "application/json",
311
+ "x-api-key": this.publishableKey
312
+ },
313
+ credentials: "include"
314
+ });
315
+ if (!res.ok) {
316
+ this.clearSession();
317
+ return null;
318
+ }
319
+ const tokens = await res.json();
320
+ this.setSession(tokens);
321
+ return tokens;
322
+ } catch {
323
+ this.clearSession();
324
+ return null;
325
+ }
326
+ }
327
+ async signOut() {
328
+ try {
329
+ await fetch(`${this.apiUrl}/v1/auth/signout`, {
330
+ method: "POST",
331
+ headers: {
332
+ "Content-Type": "application/json",
333
+ "x-api-key": this.publishableKey,
334
+ ...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
335
+ },
336
+ credentials: "include"
337
+ });
338
+ } catch {
339
+ }
340
+ this.clearSession();
341
+ }
342
+ destroy() {
343
+ this.clearSession();
344
+ }
345
+ };
346
+
347
+ // src/authon.ts
348
+ var Authon = class {
349
+ publishableKey;
350
+ config;
351
+ session;
352
+ modal = null;
353
+ listeners = /* @__PURE__ */ new Map();
354
+ branding = null;
355
+ providers = [];
356
+ initialized = false;
357
+ constructor(publishableKey, config) {
358
+ this.publishableKey = publishableKey;
359
+ this.config = {
360
+ apiUrl: config?.apiUrl || "https://api.authon.dev",
361
+ mode: config?.mode || "popup",
362
+ theme: config?.theme || "auto",
363
+ locale: config?.locale || "en",
364
+ containerId: config?.containerId,
365
+ appearance: config?.appearance
366
+ };
367
+ this.session = new SessionManager(publishableKey, this.config.apiUrl);
368
+ }
369
+ // ── Public API ──
370
+ async openSignIn() {
371
+ await this.ensureInitialized();
372
+ this.getModal().open("signIn");
373
+ }
374
+ async openSignUp() {
375
+ await this.ensureInitialized();
376
+ this.getModal().open("signUp");
377
+ }
378
+ async signInWithOAuth(provider) {
379
+ await this.ensureInitialized();
380
+ await this.startOAuthFlow(provider);
381
+ }
382
+ async signInWithEmail(email, password) {
383
+ const tokens = await this.apiPost("/v1/auth/signin", { email, password });
384
+ this.session.setSession(tokens);
385
+ this.emit("signedIn", tokens.user);
386
+ return tokens.user;
387
+ }
388
+ async signUpWithEmail(email, password, meta) {
389
+ const tokens = await this.apiPost("/v1/auth/signup", {
390
+ email,
391
+ password,
392
+ ...meta
393
+ });
394
+ this.session.setSession(tokens);
395
+ this.emit("signedIn", tokens.user);
396
+ return tokens.user;
397
+ }
398
+ async signOut() {
399
+ await this.session.signOut();
400
+ this.emit("signedOut");
401
+ }
402
+ getUser() {
403
+ return this.session.getUser();
404
+ }
405
+ getToken() {
406
+ return this.session.getToken();
407
+ }
408
+ on(event, listener) {
409
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
410
+ const set = this.listeners.get(event);
411
+ set.add(listener);
412
+ return () => set.delete(listener);
413
+ }
414
+ destroy() {
415
+ this.modal?.close();
416
+ this.session.destroy();
417
+ this.listeners.clear();
418
+ }
419
+ // ── Internal ──
420
+ emit(event, ...args) {
421
+ this.listeners.get(event)?.forEach((fn) => fn(...args));
422
+ }
423
+ async ensureInitialized() {
424
+ if (this.initialized) return;
425
+ try {
426
+ const [branding, providers] = await Promise.all([
427
+ this.apiGet("/v1/auth/branding"),
428
+ this.apiGet("/v1/auth/providers")
429
+ ]);
430
+ this.branding = { ...branding, ...this.config.appearance };
431
+ this.providers = providers.providers;
432
+ this.initialized = true;
433
+ } catch (err) {
434
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
435
+ throw err;
436
+ }
437
+ }
438
+ getModal() {
439
+ if (!this.modal) {
440
+ this.modal = new ModalRenderer({
441
+ mode: this.config.mode,
442
+ containerId: this.config.containerId,
443
+ branding: this.branding || void 0,
444
+ onProviderClick: (provider) => this.startOAuthFlow(provider),
445
+ onEmailSubmit: (email, password, isSignUp) => {
446
+ if (isSignUp) {
447
+ this.signUpWithEmail(email, password).then(() => this.modal?.close());
448
+ } else {
449
+ this.signInWithEmail(email, password).then(() => this.modal?.close());
450
+ }
451
+ },
452
+ onClose: () => this.modal?.close()
453
+ });
454
+ }
455
+ if (this.branding) this.modal.setBranding(this.branding);
456
+ this.modal.setProviders(this.providers);
457
+ return this.modal;
458
+ }
459
+ async startOAuthFlow(provider) {
460
+ try {
461
+ const { url } = await this.apiGet(
462
+ `/v1/auth/oauth/${provider}/url`
463
+ );
464
+ const width = 500;
465
+ const height = 700;
466
+ const left = window.screenX + (window.outerWidth - width) / 2;
467
+ const top = window.screenY + (window.outerHeight - height) / 2;
468
+ const popup = window.open(
469
+ url,
470
+ "authon-oauth",
471
+ `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no`
472
+ );
473
+ const handler = async (e) => {
474
+ if (e.data?.type === "authon-oauth-callback") {
475
+ window.removeEventListener("message", handler);
476
+ popup?.close();
477
+ try {
478
+ const tokens = await this.apiPost("/v1/auth/oauth/callback", {
479
+ code: e.data.code,
480
+ state: e.data.state,
481
+ codeVerifier: e.data.codeVerifier,
482
+ provider
483
+ });
484
+ this.session.setSession(tokens);
485
+ this.modal?.close();
486
+ this.emit("signedIn", tokens.user);
487
+ } catch (err) {
488
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
489
+ }
490
+ }
491
+ };
492
+ window.addEventListener("message", handler);
493
+ } catch (err) {
494
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
495
+ }
496
+ }
497
+ async apiGet(path) {
498
+ const res = await fetch(`${this.config.apiUrl}${path}`, {
499
+ headers: { "x-api-key": this.publishableKey },
500
+ credentials: "include"
501
+ });
502
+ if (!res.ok) throw new Error(`API ${path}: ${res.status}`);
503
+ return res.json();
504
+ }
505
+ async apiPost(path, body) {
506
+ const res = await fetch(`${this.config.apiUrl}${path}`, {
507
+ method: "POST",
508
+ headers: {
509
+ "Content-Type": "application/json",
510
+ "x-api-key": this.publishableKey
511
+ },
512
+ credentials: "include",
513
+ body: body ? JSON.stringify(body) : void 0
514
+ });
515
+ if (!res.ok) throw new Error(`API ${path}: ${res.status}`);
516
+ return res.json();
517
+ }
518
+ };
519
+ // Annotate the CommonJS export names for ESM import in node:
520
+ 0 && (module.exports = {
521
+ Authon,
522
+ getProviderButtonConfig
523
+ });
524
+ //# sourceMappingURL=index.cjs.map