@authon/js 0.1.11 → 0.1.12
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/dist/index.js +135 -76
- package/dist/index.mjs +135 -76
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -97,6 +97,7 @@ var ModalRenderer = class {
|
|
|
97
97
|
theme;
|
|
98
98
|
branding;
|
|
99
99
|
enabledProviders = [];
|
|
100
|
+
currentView = "signIn";
|
|
100
101
|
onProviderClick;
|
|
101
102
|
onEmailSubmit;
|
|
102
103
|
onClose;
|
|
@@ -120,9 +121,9 @@ var ModalRenderer = class {
|
|
|
120
121
|
}
|
|
121
122
|
open(view = "signIn") {
|
|
122
123
|
if (this.shadowRoot && this.hostElement) {
|
|
123
|
-
this.
|
|
124
|
-
this.attachEvents(view);
|
|
124
|
+
this.switchView(view);
|
|
125
125
|
} else {
|
|
126
|
+
this.currentView = view;
|
|
126
127
|
this.render(view);
|
|
127
128
|
}
|
|
128
129
|
}
|
|
@@ -155,13 +156,13 @@ var ModalRenderer = class {
|
|
|
155
156
|
showBanner(message, type = "error") {
|
|
156
157
|
if (!this.shadowRoot) return;
|
|
157
158
|
this.clearBanner();
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
159
|
+
const inner = this.shadowRoot.getElementById("modal-inner");
|
|
160
|
+
if (!inner) return;
|
|
160
161
|
const banner = document.createElement("div");
|
|
161
162
|
banner.id = "authon-banner";
|
|
162
163
|
banner.className = type === "warning" ? "banner-warning" : "error-msg";
|
|
163
164
|
banner.textContent = message;
|
|
164
|
-
|
|
165
|
+
inner.insertBefore(banner, inner.firstChild);
|
|
165
166
|
}
|
|
166
167
|
clearBanner() {
|
|
167
168
|
if (!this.shadowRoot) return;
|
|
@@ -190,6 +191,24 @@ var ModalRenderer = class {
|
|
|
190
191
|
if (!this.shadowRoot) return;
|
|
191
192
|
this.shadowRoot.getElementById("authon-loading-overlay")?.remove();
|
|
192
193
|
}
|
|
194
|
+
// ── Smooth view switch (no flicker) ──
|
|
195
|
+
switchView(view) {
|
|
196
|
+
if (!this.shadowRoot || view === this.currentView) return;
|
|
197
|
+
this.currentView = view;
|
|
198
|
+
const isSignUp = view === "signUp";
|
|
199
|
+
const inner = this.shadowRoot.getElementById("modal-inner");
|
|
200
|
+
if (!inner) return;
|
|
201
|
+
inner.style.opacity = "0";
|
|
202
|
+
inner.style.transform = "translateY(-4px)";
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
inner.innerHTML = this.buildInnerContent(view);
|
|
205
|
+
this.attachInnerEvents(view);
|
|
206
|
+
void inner.offsetHeight;
|
|
207
|
+
inner.style.opacity = "1";
|
|
208
|
+
inner.style.transform = "translateY(0)";
|
|
209
|
+
}, 140);
|
|
210
|
+
}
|
|
211
|
+
// ── Render ──
|
|
193
212
|
render(view) {
|
|
194
213
|
const host = document.createElement("div");
|
|
195
214
|
host.setAttribute("data-authon-modal", "");
|
|
@@ -200,10 +219,26 @@ var ModalRenderer = class {
|
|
|
200
219
|
this.containerElement.appendChild(host);
|
|
201
220
|
}
|
|
202
221
|
this.shadowRoot = host.attachShadow({ mode: "open" });
|
|
203
|
-
this.shadowRoot.innerHTML = this.
|
|
204
|
-
this.
|
|
222
|
+
this.shadowRoot.innerHTML = this.buildShell(view);
|
|
223
|
+
this.attachInnerEvents(view);
|
|
224
|
+
this.attachShellEvents();
|
|
225
|
+
}
|
|
226
|
+
// ── HTML builders ──
|
|
227
|
+
/** Shell = style + backdrop + modal-container (stable across view switches) */
|
|
228
|
+
buildShell(view) {
|
|
229
|
+
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
230
|
+
return `
|
|
231
|
+
<style>${this.buildCSS()}</style>
|
|
232
|
+
${popupWrapper}
|
|
233
|
+
<div class="modal-container" role="dialog" aria-modal="true">
|
|
234
|
+
<div id="modal-inner" class="modal-inner">
|
|
235
|
+
${this.buildInnerContent(view)}
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
`;
|
|
205
239
|
}
|
|
206
|
-
|
|
240
|
+
/** Inner content = everything inside the modal that changes per view */
|
|
241
|
+
buildInnerContent(view) {
|
|
207
242
|
const b = this.branding;
|
|
208
243
|
const isSignUp = view === "signUp";
|
|
209
244
|
const title = isSignUp ? "Create your account" : "Welcome back";
|
|
@@ -231,21 +266,16 @@ var ModalRenderer = class {
|
|
|
231
266
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
232
267
|
${b.privacyUrl ? `<a href="${b.privacyUrl}" target="_blank">Privacy Policy</a>` : ""}
|
|
233
268
|
</div>` : "";
|
|
234
|
-
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
235
269
|
return `
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
246
|
-
${footer}
|
|
247
|
-
${b.showSecuredBy !== false ? `<div class="secured-by">Secured by <span class="secured-brand">Authon</span></div>` : ""}
|
|
248
|
-
</div>
|
|
270
|
+
${b.logoDataUrl ? `<img src="${b.logoDataUrl}" alt="Logo" class="logo" />` : ""}
|
|
271
|
+
<h2 class="title">${title}</h2>
|
|
272
|
+
${b.brandName ? `<p class="brand-name">${b.brandName}</p>` : ""}
|
|
273
|
+
<div class="providers">${providerButtons}</div>
|
|
274
|
+
${divider}
|
|
275
|
+
${emailForm}
|
|
276
|
+
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
277
|
+
${footer}
|
|
278
|
+
${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>` : ""}
|
|
249
279
|
`;
|
|
250
280
|
}
|
|
251
281
|
isDark() {
|
|
@@ -296,6 +326,9 @@ var ModalRenderer = class {
|
|
|
296
326
|
position: ${this.mode === "popup" ? "fixed" : "relative"};
|
|
297
327
|
${this.mode === "popup" ? `box-shadow: 0 25px 50px -12px rgba(0,0,0,${dark ? "0.5" : "0.25"}); animation: slideIn 0.3s ease;` : ""}
|
|
298
328
|
}
|
|
329
|
+
.modal-inner {
|
|
330
|
+
transition: opacity 0.14s ease, transform 0.14s ease;
|
|
331
|
+
}
|
|
299
332
|
.logo { display: block; margin: 0 auto 16px; max-height: 48px; }
|
|
300
333
|
.title { text-align: center; font-size: 24px; font-weight: 700; margin-bottom: 8px; color: var(--authon-text); }
|
|
301
334
|
.brand-name { text-align: center; font-size: 14px; color: var(--authon-muted); margin-bottom: 24px; }
|
|
@@ -354,15 +387,15 @@ var ModalRenderer = class {
|
|
|
354
387
|
.switch-view { text-align: center; margin-top: 16px; font-size: 13px; color: var(--authon-muted); }
|
|
355
388
|
.switch-view a { color: var(--authon-primary-start); text-decoration: none; font-weight: 500; }
|
|
356
389
|
.switch-view a:hover { text-decoration: underline; }
|
|
357
|
-
.footer { text-align: center; margin-top:
|
|
390
|
+
.footer { text-align: center; margin-top: 12px; font-size: 12px; color: var(--authon-dim); }
|
|
358
391
|
.footer a { color: var(--authon-dim); text-decoration: none; }
|
|
359
392
|
.footer a:hover { text-decoration: underline; }
|
|
360
393
|
.secured-by {
|
|
361
|
-
text-align: center; margin-top:
|
|
362
|
-
border-top: 1px solid var(--authon-divider);
|
|
394
|
+
text-align: center; margin-top: 16px;
|
|
363
395
|
font-size: 11px; color: var(--authon-dim);
|
|
364
396
|
}
|
|
365
|
-
.secured-
|
|
397
|
+
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
398
|
+
.secured-link:hover { text-decoration: underline; }
|
|
366
399
|
/* Loading overlay */
|
|
367
400
|
#authon-loading-overlay {
|
|
368
401
|
position: absolute; inset: 0; z-index: 10;
|
|
@@ -401,7 +434,26 @@ var ModalRenderer = class {
|
|
|
401
434
|
${b.customCss || ""}
|
|
402
435
|
`;
|
|
403
436
|
}
|
|
404
|
-
|
|
437
|
+
// ── Event binding ──
|
|
438
|
+
/** Attach events to shell elements (backdrop, ESC) — called once */
|
|
439
|
+
attachShellEvents() {
|
|
440
|
+
if (!this.shadowRoot) return;
|
|
441
|
+
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
442
|
+
if (backdrop) {
|
|
443
|
+
backdrop.addEventListener("click", () => this.onClose());
|
|
444
|
+
}
|
|
445
|
+
if (this.escHandler) {
|
|
446
|
+
document.removeEventListener("keydown", this.escHandler);
|
|
447
|
+
}
|
|
448
|
+
if (this.mode === "popup") {
|
|
449
|
+
this.escHandler = (e) => {
|
|
450
|
+
if (e.key === "Escape") this.onClose();
|
|
451
|
+
};
|
|
452
|
+
document.addEventListener("keydown", this.escHandler);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/** Attach events to inner content (buttons, form, switch link) — called on each view */
|
|
456
|
+
attachInnerEvents(view) {
|
|
405
457
|
if (!this.shadowRoot) return;
|
|
406
458
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
407
459
|
btn.addEventListener("click", () => {
|
|
@@ -428,20 +480,6 @@ var ModalRenderer = class {
|
|
|
428
480
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
429
481
|
});
|
|
430
482
|
}
|
|
431
|
-
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
432
|
-
if (backdrop) {
|
|
433
|
-
backdrop.addEventListener("click", () => this.onClose());
|
|
434
|
-
}
|
|
435
|
-
if (this.escHandler) {
|
|
436
|
-
document.removeEventListener("keydown", this.escHandler);
|
|
437
|
-
this.escHandler = null;
|
|
438
|
-
}
|
|
439
|
-
if (this.mode === "popup") {
|
|
440
|
-
this.escHandler = (e) => {
|
|
441
|
-
if (e.key === "Escape") this.onClose();
|
|
442
|
-
};
|
|
443
|
-
document.addEventListener("keydown", this.escHandler);
|
|
444
|
-
}
|
|
445
483
|
}
|
|
446
484
|
};
|
|
447
485
|
|
|
@@ -651,7 +689,7 @@ var Authon = class {
|
|
|
651
689
|
async startOAuthFlow(provider) {
|
|
652
690
|
try {
|
|
653
691
|
const redirectUri = `${this.config.apiUrl}/v1/auth/oauth/redirect`;
|
|
654
|
-
const { url } = await this.apiGet(
|
|
692
|
+
const { url, state } = await this.apiGet(
|
|
655
693
|
`/v1/auth/oauth/${provider}/url?redirectUri=${encodeURIComponent(redirectUri)}`
|
|
656
694
|
);
|
|
657
695
|
this.modal?.showLoading();
|
|
@@ -673,59 +711,80 @@ var Authon = class {
|
|
|
673
711
|
this.emit("error", new Error("Popup was blocked by the browser"));
|
|
674
712
|
return;
|
|
675
713
|
}
|
|
676
|
-
let
|
|
714
|
+
let resolved = false;
|
|
677
715
|
let cleaned = false;
|
|
716
|
+
const resolve = (tokens) => {
|
|
717
|
+
if (resolved) return;
|
|
718
|
+
resolved = true;
|
|
719
|
+
cleanup();
|
|
720
|
+
try {
|
|
721
|
+
if (!popup.closed) popup.close();
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
724
|
+
this.session.setSession(tokens);
|
|
725
|
+
this.modal?.close();
|
|
726
|
+
this.emit("signedIn", tokens.user);
|
|
727
|
+
};
|
|
728
|
+
const handleError = (msg) => {
|
|
729
|
+
if (resolved) return;
|
|
730
|
+
cleanup();
|
|
731
|
+
this.modal?.hideLoading();
|
|
732
|
+
this.modal?.showError(msg);
|
|
733
|
+
this.emit("error", new Error(msg));
|
|
734
|
+
};
|
|
678
735
|
const cleanup = () => {
|
|
679
736
|
if (cleaned) return;
|
|
680
737
|
cleaned = true;
|
|
681
|
-
window.removeEventListener("message",
|
|
682
|
-
if (
|
|
738
|
+
window.removeEventListener("message", messageHandler);
|
|
739
|
+
if (apiPollTimer) clearInterval(apiPollTimer);
|
|
740
|
+
if (closePollTimer) clearInterval(closePollTimer);
|
|
683
741
|
if (maxTimer) clearTimeout(maxTimer);
|
|
684
742
|
};
|
|
685
|
-
const
|
|
686
|
-
if (
|
|
743
|
+
const messageHandler = (e) => {
|
|
744
|
+
if (e.data?.type !== "authon-oauth-callback") return;
|
|
745
|
+
if (e.data.tokens) {
|
|
746
|
+
resolve(e.data.tokens);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
window.addEventListener("message", messageHandler);
|
|
750
|
+
const apiPollTimer = setInterval(async () => {
|
|
751
|
+
if (resolved || cleaned) return;
|
|
752
|
+
try {
|
|
753
|
+
const result = await this.apiGet(
|
|
754
|
+
`/v1/auth/oauth/poll?state=${encodeURIComponent(state)}`
|
|
755
|
+
);
|
|
756
|
+
if (result.status === "completed" && result.accessToken) {
|
|
757
|
+
resolve({
|
|
758
|
+
accessToken: result.accessToken,
|
|
759
|
+
refreshToken: result.refreshToken,
|
|
760
|
+
expiresIn: result.expiresIn,
|
|
761
|
+
user: result.user
|
|
762
|
+
});
|
|
763
|
+
} else if (result.status === "error") {
|
|
764
|
+
handleError(result.message || "Authentication failed");
|
|
765
|
+
}
|
|
766
|
+
} catch {
|
|
767
|
+
}
|
|
768
|
+
}, 1500);
|
|
769
|
+
const closePollTimer = setInterval(() => {
|
|
770
|
+
if (resolved || cleaned) return;
|
|
687
771
|
try {
|
|
688
772
|
if (popup.closed) {
|
|
773
|
+
clearInterval(closePollTimer);
|
|
689
774
|
setTimeout(() => {
|
|
690
|
-
if (
|
|
775
|
+
if (resolved || cleaned) return;
|
|
691
776
|
cleanup();
|
|
692
777
|
this.modal?.hideLoading();
|
|
693
|
-
},
|
|
694
|
-
clearInterval(pollTimer);
|
|
778
|
+
}, 3e3);
|
|
695
779
|
}
|
|
696
780
|
} catch {
|
|
697
781
|
}
|
|
698
782
|
}, 500);
|
|
699
783
|
const maxTimer = setTimeout(() => {
|
|
700
|
-
if (
|
|
784
|
+
if (resolved || cleaned) return;
|
|
701
785
|
cleanup();
|
|
702
786
|
this.modal?.hideLoading();
|
|
703
787
|
}, 18e4);
|
|
704
|
-
const handler = async (e) => {
|
|
705
|
-
if (e.data?.type !== "authon-oauth-callback") return;
|
|
706
|
-
callbackReceived = true;
|
|
707
|
-
cleanup();
|
|
708
|
-
try {
|
|
709
|
-
if (!popup.closed) popup.close();
|
|
710
|
-
} catch {
|
|
711
|
-
}
|
|
712
|
-
try {
|
|
713
|
-
const tokens = await this.apiPost("/v1/auth/oauth/callback", {
|
|
714
|
-
code: e.data.code,
|
|
715
|
-
state: e.data.state,
|
|
716
|
-
provider
|
|
717
|
-
});
|
|
718
|
-
this.session.setSession(tokens);
|
|
719
|
-
this.modal?.close();
|
|
720
|
-
this.emit("signedIn", tokens.user);
|
|
721
|
-
} catch (err) {
|
|
722
|
-
this.modal?.hideLoading();
|
|
723
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
724
|
-
this.modal?.showError(msg.includes("500") ? "Authentication failed. Please try again." : msg);
|
|
725
|
-
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
726
|
-
}
|
|
727
|
-
};
|
|
728
|
-
window.addEventListener("message", handler);
|
|
729
788
|
} catch (err) {
|
|
730
789
|
this.modal?.hideLoading();
|
|
731
790
|
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
package/dist/index.mjs
CHANGED
|
@@ -70,6 +70,7 @@ var ModalRenderer = class {
|
|
|
70
70
|
theme;
|
|
71
71
|
branding;
|
|
72
72
|
enabledProviders = [];
|
|
73
|
+
currentView = "signIn";
|
|
73
74
|
onProviderClick;
|
|
74
75
|
onEmailSubmit;
|
|
75
76
|
onClose;
|
|
@@ -93,9 +94,9 @@ var ModalRenderer = class {
|
|
|
93
94
|
}
|
|
94
95
|
open(view = "signIn") {
|
|
95
96
|
if (this.shadowRoot && this.hostElement) {
|
|
96
|
-
this.
|
|
97
|
-
this.attachEvents(view);
|
|
97
|
+
this.switchView(view);
|
|
98
98
|
} else {
|
|
99
|
+
this.currentView = view;
|
|
99
100
|
this.render(view);
|
|
100
101
|
}
|
|
101
102
|
}
|
|
@@ -128,13 +129,13 @@ var ModalRenderer = class {
|
|
|
128
129
|
showBanner(message, type = "error") {
|
|
129
130
|
if (!this.shadowRoot) return;
|
|
130
131
|
this.clearBanner();
|
|
131
|
-
const
|
|
132
|
-
if (!
|
|
132
|
+
const inner = this.shadowRoot.getElementById("modal-inner");
|
|
133
|
+
if (!inner) return;
|
|
133
134
|
const banner = document.createElement("div");
|
|
134
135
|
banner.id = "authon-banner";
|
|
135
136
|
banner.className = type === "warning" ? "banner-warning" : "error-msg";
|
|
136
137
|
banner.textContent = message;
|
|
137
|
-
|
|
138
|
+
inner.insertBefore(banner, inner.firstChild);
|
|
138
139
|
}
|
|
139
140
|
clearBanner() {
|
|
140
141
|
if (!this.shadowRoot) return;
|
|
@@ -163,6 +164,24 @@ var ModalRenderer = class {
|
|
|
163
164
|
if (!this.shadowRoot) return;
|
|
164
165
|
this.shadowRoot.getElementById("authon-loading-overlay")?.remove();
|
|
165
166
|
}
|
|
167
|
+
// ── Smooth view switch (no flicker) ──
|
|
168
|
+
switchView(view) {
|
|
169
|
+
if (!this.shadowRoot || view === this.currentView) return;
|
|
170
|
+
this.currentView = view;
|
|
171
|
+
const isSignUp = view === "signUp";
|
|
172
|
+
const inner = this.shadowRoot.getElementById("modal-inner");
|
|
173
|
+
if (!inner) return;
|
|
174
|
+
inner.style.opacity = "0";
|
|
175
|
+
inner.style.transform = "translateY(-4px)";
|
|
176
|
+
setTimeout(() => {
|
|
177
|
+
inner.innerHTML = this.buildInnerContent(view);
|
|
178
|
+
this.attachInnerEvents(view);
|
|
179
|
+
void inner.offsetHeight;
|
|
180
|
+
inner.style.opacity = "1";
|
|
181
|
+
inner.style.transform = "translateY(0)";
|
|
182
|
+
}, 140);
|
|
183
|
+
}
|
|
184
|
+
// ── Render ──
|
|
166
185
|
render(view) {
|
|
167
186
|
const host = document.createElement("div");
|
|
168
187
|
host.setAttribute("data-authon-modal", "");
|
|
@@ -173,10 +192,26 @@ var ModalRenderer = class {
|
|
|
173
192
|
this.containerElement.appendChild(host);
|
|
174
193
|
}
|
|
175
194
|
this.shadowRoot = host.attachShadow({ mode: "open" });
|
|
176
|
-
this.shadowRoot.innerHTML = this.
|
|
177
|
-
this.
|
|
195
|
+
this.shadowRoot.innerHTML = this.buildShell(view);
|
|
196
|
+
this.attachInnerEvents(view);
|
|
197
|
+
this.attachShellEvents();
|
|
198
|
+
}
|
|
199
|
+
// ── HTML builders ──
|
|
200
|
+
/** Shell = style + backdrop + modal-container (stable across view switches) */
|
|
201
|
+
buildShell(view) {
|
|
202
|
+
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
203
|
+
return `
|
|
204
|
+
<style>${this.buildCSS()}</style>
|
|
205
|
+
${popupWrapper}
|
|
206
|
+
<div class="modal-container" role="dialog" aria-modal="true">
|
|
207
|
+
<div id="modal-inner" class="modal-inner">
|
|
208
|
+
${this.buildInnerContent(view)}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
`;
|
|
178
212
|
}
|
|
179
|
-
|
|
213
|
+
/** Inner content = everything inside the modal that changes per view */
|
|
214
|
+
buildInnerContent(view) {
|
|
180
215
|
const b = this.branding;
|
|
181
216
|
const isSignUp = view === "signUp";
|
|
182
217
|
const title = isSignUp ? "Create your account" : "Welcome back";
|
|
@@ -204,21 +239,16 @@ var ModalRenderer = class {
|
|
|
204
239
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
205
240
|
${b.privacyUrl ? `<a href="${b.privacyUrl}" target="_blank">Privacy Policy</a>` : ""}
|
|
206
241
|
</div>` : "";
|
|
207
|
-
const popupWrapper = this.mode === "popup" ? `<div class="backdrop" id="backdrop"></div>` : "";
|
|
208
242
|
return `
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
219
|
-
${footer}
|
|
220
|
-
${b.showSecuredBy !== false ? `<div class="secured-by">Secured by <span class="secured-brand">Authon</span></div>` : ""}
|
|
221
|
-
</div>
|
|
243
|
+
${b.logoDataUrl ? `<img src="${b.logoDataUrl}" alt="Logo" class="logo" />` : ""}
|
|
244
|
+
<h2 class="title">${title}</h2>
|
|
245
|
+
${b.brandName ? `<p class="brand-name">${b.brandName}</p>` : ""}
|
|
246
|
+
<div class="providers">${providerButtons}</div>
|
|
247
|
+
${divider}
|
|
248
|
+
${emailForm}
|
|
249
|
+
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
250
|
+
${footer}
|
|
251
|
+
${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>` : ""}
|
|
222
252
|
`;
|
|
223
253
|
}
|
|
224
254
|
isDark() {
|
|
@@ -269,6 +299,9 @@ var ModalRenderer = class {
|
|
|
269
299
|
position: ${this.mode === "popup" ? "fixed" : "relative"};
|
|
270
300
|
${this.mode === "popup" ? `box-shadow: 0 25px 50px -12px rgba(0,0,0,${dark ? "0.5" : "0.25"}); animation: slideIn 0.3s ease;` : ""}
|
|
271
301
|
}
|
|
302
|
+
.modal-inner {
|
|
303
|
+
transition: opacity 0.14s ease, transform 0.14s ease;
|
|
304
|
+
}
|
|
272
305
|
.logo { display: block; margin: 0 auto 16px; max-height: 48px; }
|
|
273
306
|
.title { text-align: center; font-size: 24px; font-weight: 700; margin-bottom: 8px; color: var(--authon-text); }
|
|
274
307
|
.brand-name { text-align: center; font-size: 14px; color: var(--authon-muted); margin-bottom: 24px; }
|
|
@@ -327,15 +360,15 @@ var ModalRenderer = class {
|
|
|
327
360
|
.switch-view { text-align: center; margin-top: 16px; font-size: 13px; color: var(--authon-muted); }
|
|
328
361
|
.switch-view a { color: var(--authon-primary-start); text-decoration: none; font-weight: 500; }
|
|
329
362
|
.switch-view a:hover { text-decoration: underline; }
|
|
330
|
-
.footer { text-align: center; margin-top:
|
|
363
|
+
.footer { text-align: center; margin-top: 12px; font-size: 12px; color: var(--authon-dim); }
|
|
331
364
|
.footer a { color: var(--authon-dim); text-decoration: none; }
|
|
332
365
|
.footer a:hover { text-decoration: underline; }
|
|
333
366
|
.secured-by {
|
|
334
|
-
text-align: center; margin-top:
|
|
335
|
-
border-top: 1px solid var(--authon-divider);
|
|
367
|
+
text-align: center; margin-top: 16px;
|
|
336
368
|
font-size: 11px; color: var(--authon-dim);
|
|
337
369
|
}
|
|
338
|
-
.secured-
|
|
370
|
+
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
371
|
+
.secured-link:hover { text-decoration: underline; }
|
|
339
372
|
/* Loading overlay */
|
|
340
373
|
#authon-loading-overlay {
|
|
341
374
|
position: absolute; inset: 0; z-index: 10;
|
|
@@ -374,7 +407,26 @@ var ModalRenderer = class {
|
|
|
374
407
|
${b.customCss || ""}
|
|
375
408
|
`;
|
|
376
409
|
}
|
|
377
|
-
|
|
410
|
+
// ── Event binding ──
|
|
411
|
+
/** Attach events to shell elements (backdrop, ESC) — called once */
|
|
412
|
+
attachShellEvents() {
|
|
413
|
+
if (!this.shadowRoot) return;
|
|
414
|
+
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
415
|
+
if (backdrop) {
|
|
416
|
+
backdrop.addEventListener("click", () => this.onClose());
|
|
417
|
+
}
|
|
418
|
+
if (this.escHandler) {
|
|
419
|
+
document.removeEventListener("keydown", this.escHandler);
|
|
420
|
+
}
|
|
421
|
+
if (this.mode === "popup") {
|
|
422
|
+
this.escHandler = (e) => {
|
|
423
|
+
if (e.key === "Escape") this.onClose();
|
|
424
|
+
};
|
|
425
|
+
document.addEventListener("keydown", this.escHandler);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/** Attach events to inner content (buttons, form, switch link) — called on each view */
|
|
429
|
+
attachInnerEvents(view) {
|
|
378
430
|
if (!this.shadowRoot) return;
|
|
379
431
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
380
432
|
btn.addEventListener("click", () => {
|
|
@@ -401,20 +453,6 @@ var ModalRenderer = class {
|
|
|
401
453
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
402
454
|
});
|
|
403
455
|
}
|
|
404
|
-
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
405
|
-
if (backdrop) {
|
|
406
|
-
backdrop.addEventListener("click", () => this.onClose());
|
|
407
|
-
}
|
|
408
|
-
if (this.escHandler) {
|
|
409
|
-
document.removeEventListener("keydown", this.escHandler);
|
|
410
|
-
this.escHandler = null;
|
|
411
|
-
}
|
|
412
|
-
if (this.mode === "popup") {
|
|
413
|
-
this.escHandler = (e) => {
|
|
414
|
-
if (e.key === "Escape") this.onClose();
|
|
415
|
-
};
|
|
416
|
-
document.addEventListener("keydown", this.escHandler);
|
|
417
|
-
}
|
|
418
456
|
}
|
|
419
457
|
};
|
|
420
458
|
|
|
@@ -624,7 +662,7 @@ var Authon = class {
|
|
|
624
662
|
async startOAuthFlow(provider) {
|
|
625
663
|
try {
|
|
626
664
|
const redirectUri = `${this.config.apiUrl}/v1/auth/oauth/redirect`;
|
|
627
|
-
const { url } = await this.apiGet(
|
|
665
|
+
const { url, state } = await this.apiGet(
|
|
628
666
|
`/v1/auth/oauth/${provider}/url?redirectUri=${encodeURIComponent(redirectUri)}`
|
|
629
667
|
);
|
|
630
668
|
this.modal?.showLoading();
|
|
@@ -646,59 +684,80 @@ var Authon = class {
|
|
|
646
684
|
this.emit("error", new Error("Popup was blocked by the browser"));
|
|
647
685
|
return;
|
|
648
686
|
}
|
|
649
|
-
let
|
|
687
|
+
let resolved = false;
|
|
650
688
|
let cleaned = false;
|
|
689
|
+
const resolve = (tokens) => {
|
|
690
|
+
if (resolved) return;
|
|
691
|
+
resolved = true;
|
|
692
|
+
cleanup();
|
|
693
|
+
try {
|
|
694
|
+
if (!popup.closed) popup.close();
|
|
695
|
+
} catch {
|
|
696
|
+
}
|
|
697
|
+
this.session.setSession(tokens);
|
|
698
|
+
this.modal?.close();
|
|
699
|
+
this.emit("signedIn", tokens.user);
|
|
700
|
+
};
|
|
701
|
+
const handleError = (msg) => {
|
|
702
|
+
if (resolved) return;
|
|
703
|
+
cleanup();
|
|
704
|
+
this.modal?.hideLoading();
|
|
705
|
+
this.modal?.showError(msg);
|
|
706
|
+
this.emit("error", new Error(msg));
|
|
707
|
+
};
|
|
651
708
|
const cleanup = () => {
|
|
652
709
|
if (cleaned) return;
|
|
653
710
|
cleaned = true;
|
|
654
|
-
window.removeEventListener("message",
|
|
655
|
-
if (
|
|
711
|
+
window.removeEventListener("message", messageHandler);
|
|
712
|
+
if (apiPollTimer) clearInterval(apiPollTimer);
|
|
713
|
+
if (closePollTimer) clearInterval(closePollTimer);
|
|
656
714
|
if (maxTimer) clearTimeout(maxTimer);
|
|
657
715
|
};
|
|
658
|
-
const
|
|
659
|
-
if (
|
|
716
|
+
const messageHandler = (e) => {
|
|
717
|
+
if (e.data?.type !== "authon-oauth-callback") return;
|
|
718
|
+
if (e.data.tokens) {
|
|
719
|
+
resolve(e.data.tokens);
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
window.addEventListener("message", messageHandler);
|
|
723
|
+
const apiPollTimer = setInterval(async () => {
|
|
724
|
+
if (resolved || cleaned) return;
|
|
725
|
+
try {
|
|
726
|
+
const result = await this.apiGet(
|
|
727
|
+
`/v1/auth/oauth/poll?state=${encodeURIComponent(state)}`
|
|
728
|
+
);
|
|
729
|
+
if (result.status === "completed" && result.accessToken) {
|
|
730
|
+
resolve({
|
|
731
|
+
accessToken: result.accessToken,
|
|
732
|
+
refreshToken: result.refreshToken,
|
|
733
|
+
expiresIn: result.expiresIn,
|
|
734
|
+
user: result.user
|
|
735
|
+
});
|
|
736
|
+
} else if (result.status === "error") {
|
|
737
|
+
handleError(result.message || "Authentication failed");
|
|
738
|
+
}
|
|
739
|
+
} catch {
|
|
740
|
+
}
|
|
741
|
+
}, 1500);
|
|
742
|
+
const closePollTimer = setInterval(() => {
|
|
743
|
+
if (resolved || cleaned) return;
|
|
660
744
|
try {
|
|
661
745
|
if (popup.closed) {
|
|
746
|
+
clearInterval(closePollTimer);
|
|
662
747
|
setTimeout(() => {
|
|
663
|
-
if (
|
|
748
|
+
if (resolved || cleaned) return;
|
|
664
749
|
cleanup();
|
|
665
750
|
this.modal?.hideLoading();
|
|
666
|
-
},
|
|
667
|
-
clearInterval(pollTimer);
|
|
751
|
+
}, 3e3);
|
|
668
752
|
}
|
|
669
753
|
} catch {
|
|
670
754
|
}
|
|
671
755
|
}, 500);
|
|
672
756
|
const maxTimer = setTimeout(() => {
|
|
673
|
-
if (
|
|
757
|
+
if (resolved || cleaned) return;
|
|
674
758
|
cleanup();
|
|
675
759
|
this.modal?.hideLoading();
|
|
676
760
|
}, 18e4);
|
|
677
|
-
const handler = async (e) => {
|
|
678
|
-
if (e.data?.type !== "authon-oauth-callback") return;
|
|
679
|
-
callbackReceived = true;
|
|
680
|
-
cleanup();
|
|
681
|
-
try {
|
|
682
|
-
if (!popup.closed) popup.close();
|
|
683
|
-
} catch {
|
|
684
|
-
}
|
|
685
|
-
try {
|
|
686
|
-
const tokens = await this.apiPost("/v1/auth/oauth/callback", {
|
|
687
|
-
code: e.data.code,
|
|
688
|
-
state: e.data.state,
|
|
689
|
-
provider
|
|
690
|
-
});
|
|
691
|
-
this.session.setSession(tokens);
|
|
692
|
-
this.modal?.close();
|
|
693
|
-
this.emit("signedIn", tokens.user);
|
|
694
|
-
} catch (err) {
|
|
695
|
-
this.modal?.hideLoading();
|
|
696
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
697
|
-
this.modal?.showError(msg.includes("500") ? "Authentication failed. Please try again." : msg);
|
|
698
|
-
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
699
|
-
}
|
|
700
|
-
};
|
|
701
|
-
window.addEventListener("message", handler);
|
|
702
761
|
} catch (err) {
|
|
703
762
|
this.modal?.hideLoading();
|
|
704
763
|
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|