@authon/js 0.1.11 → 0.1.13
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.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +181 -86
- package/dist/index.mjs +181 -86
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
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,30 +219,48 @@ 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";
|
|
210
245
|
const subtitle = isSignUp ? "Already have an account?" : "Don't have an account?";
|
|
211
246
|
const subtitleLink = isSignUp ? "Sign in" : "Sign up";
|
|
212
247
|
const dark = this.isDark();
|
|
213
|
-
const
|
|
248
|
+
const showProviders = !isSignUp;
|
|
249
|
+
const providerButtons = showProviders ? this.enabledProviders.filter((p) => !b.hiddenProviders?.includes(p)).map((p) => {
|
|
214
250
|
const config = getProviderButtonConfig(p);
|
|
215
251
|
const isWhiteBg = config.bgColor === "#ffffff";
|
|
216
252
|
const btnBg = dark && isWhiteBg ? "#f8fafc" : config.bgColor;
|
|
217
253
|
const btnBorder = isWhiteBg ? dark ? "#475569" : "#e5e7eb" : config.bgColor;
|
|
218
254
|
return `<button class="provider-btn" data-provider="${p}" style="background:${btnBg};color:${config.textColor};border:1px solid ${btnBorder}">
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}).join("");
|
|
223
|
-
const divider = b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
|
|
255
|
+
<span class="provider-icon">${config.iconSvg}</span>
|
|
256
|
+
<span>${config.label}</span>
|
|
257
|
+
</button>`;
|
|
258
|
+
}).join("") : "";
|
|
259
|
+
const divider = showProviders && b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
|
|
224
260
|
const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
|
|
225
261
|
<input type="email" placeholder="Email address" name="email" required class="input" autocomplete="email" />
|
|
226
262
|
<input type="password" placeholder="Password" name="password" required class="input" autocomplete="${isSignUp ? "new-password" : "current-password"}" />
|
|
263
|
+
${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
|
|
227
264
|
<button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
|
|
228
265
|
</form>` : "";
|
|
229
266
|
const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
|
|
@@ -231,21 +268,22 @@ var ModalRenderer = class {
|
|
|
231
268
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
232
269
|
${b.privacyUrl ? `<a href="${b.privacyUrl}" target="_blank">Privacy Policy</a>` : ""}
|
|
233
270
|
</div>` : "";
|
|
234
|
-
const
|
|
271
|
+
const titleHtml = isSignUp ? `<div class="title-row">
|
|
272
|
+
<button class="back-btn" id="back-btn" type="button" aria-label="Back to sign in">
|
|
273
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5"/><path d="m12 19-7-7 7-7"/></svg>
|
|
274
|
+
</button>
|
|
275
|
+
<h2 class="title">${title}</h2>
|
|
276
|
+
</div>` : `<h2 class="title">${title}</h2>`;
|
|
235
277
|
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>
|
|
278
|
+
${b.logoDataUrl ? `<img src="${b.logoDataUrl}" alt="Logo" class="logo" />` : ""}
|
|
279
|
+
${titleHtml}
|
|
280
|
+
${b.brandName ? `<p class="brand-name">${b.brandName}</p>` : ""}
|
|
281
|
+
${showProviders ? `<div class="providers">${providerButtons}</div>` : ""}
|
|
282
|
+
${divider}
|
|
283
|
+
${emailForm}
|
|
284
|
+
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
285
|
+
${footer}
|
|
286
|
+
${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
287
|
`;
|
|
250
288
|
}
|
|
251
289
|
isDark() {
|
|
@@ -296,7 +334,20 @@ var ModalRenderer = class {
|
|
|
296
334
|
position: ${this.mode === "popup" ? "fixed" : "relative"};
|
|
297
335
|
${this.mode === "popup" ? `box-shadow: 0 25px 50px -12px rgba(0,0,0,${dark ? "0.5" : "0.25"}); animation: slideIn 0.3s ease;` : ""}
|
|
298
336
|
}
|
|
337
|
+
.modal-inner {
|
|
338
|
+
transition: opacity 0.14s ease, transform 0.14s ease;
|
|
339
|
+
}
|
|
299
340
|
.logo { display: block; margin: 0 auto 16px; max-height: 48px; }
|
|
341
|
+
.title-row { display: flex; align-items: center; position: relative; margin-bottom: 8px; }
|
|
342
|
+
.title-row .title { flex: 1; margin-bottom: 0; }
|
|
343
|
+
.back-btn {
|
|
344
|
+
position: absolute; left: 0; top: 50%; transform: translateY(-50%);
|
|
345
|
+
background: none; border: none; color: var(--authon-muted);
|
|
346
|
+
cursor: pointer; padding: 4px; border-radius: 6px; display: flex; align-items: center; justify-content: center;
|
|
347
|
+
transition: color 0.15s, background 0.15s;
|
|
348
|
+
}
|
|
349
|
+
.back-btn:hover { color: var(--authon-text); background: var(--authon-divider); }
|
|
350
|
+
.password-hint { font-size: 11px; color: var(--authon-dim); margin: -4px 0 2px; }
|
|
300
351
|
.title { text-align: center; font-size: 24px; font-weight: 700; margin-bottom: 8px; color: var(--authon-text); }
|
|
301
352
|
.brand-name { text-align: center; font-size: 14px; color: var(--authon-muted); margin-bottom: 24px; }
|
|
302
353
|
.providers { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
|
|
@@ -354,15 +405,15 @@ var ModalRenderer = class {
|
|
|
354
405
|
.switch-view { text-align: center; margin-top: 16px; font-size: 13px; color: var(--authon-muted); }
|
|
355
406
|
.switch-view a { color: var(--authon-primary-start); text-decoration: none; font-weight: 500; }
|
|
356
407
|
.switch-view a:hover { text-decoration: underline; }
|
|
357
|
-
.footer { text-align: center; margin-top:
|
|
408
|
+
.footer { text-align: center; margin-top: 12px; font-size: 12px; color: var(--authon-dim); }
|
|
358
409
|
.footer a { color: var(--authon-dim); text-decoration: none; }
|
|
359
410
|
.footer a:hover { text-decoration: underline; }
|
|
360
411
|
.secured-by {
|
|
361
|
-
text-align: center; margin-top:
|
|
362
|
-
border-top: 1px solid var(--authon-divider);
|
|
412
|
+
text-align: center; margin-top: 16px;
|
|
363
413
|
font-size: 11px; color: var(--authon-dim);
|
|
364
414
|
}
|
|
365
|
-
.secured-
|
|
415
|
+
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
416
|
+
.secured-link:hover { text-decoration: underline; }
|
|
366
417
|
/* Loading overlay */
|
|
367
418
|
#authon-loading-overlay {
|
|
368
419
|
position: absolute; inset: 0; z-index: 10;
|
|
@@ -401,7 +452,26 @@ var ModalRenderer = class {
|
|
|
401
452
|
${b.customCss || ""}
|
|
402
453
|
`;
|
|
403
454
|
}
|
|
404
|
-
|
|
455
|
+
// ── Event binding ──
|
|
456
|
+
/** Attach events to shell elements (backdrop, ESC) — called once */
|
|
457
|
+
attachShellEvents() {
|
|
458
|
+
if (!this.shadowRoot) return;
|
|
459
|
+
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
460
|
+
if (backdrop) {
|
|
461
|
+
backdrop.addEventListener("click", () => this.onClose());
|
|
462
|
+
}
|
|
463
|
+
if (this.escHandler) {
|
|
464
|
+
document.removeEventListener("keydown", this.escHandler);
|
|
465
|
+
}
|
|
466
|
+
if (this.mode === "popup") {
|
|
467
|
+
this.escHandler = (e) => {
|
|
468
|
+
if (e.key === "Escape") this.onClose();
|
|
469
|
+
};
|
|
470
|
+
document.addEventListener("keydown", this.escHandler);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/** Attach events to inner content (buttons, form, switch link) — called on each view */
|
|
474
|
+
attachInnerEvents(view) {
|
|
405
475
|
if (!this.shadowRoot) return;
|
|
406
476
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
407
477
|
btn.addEventListener("click", () => {
|
|
@@ -421,6 +491,12 @@ var ModalRenderer = class {
|
|
|
421
491
|
);
|
|
422
492
|
});
|
|
423
493
|
}
|
|
494
|
+
const backBtn = this.shadowRoot.getElementById("back-btn");
|
|
495
|
+
if (backBtn) {
|
|
496
|
+
backBtn.addEventListener("click", () => {
|
|
497
|
+
this.open("signIn");
|
|
498
|
+
});
|
|
499
|
+
}
|
|
424
500
|
const switchLink = this.shadowRoot.getElementById("switch-link");
|
|
425
501
|
if (switchLink) {
|
|
426
502
|
switchLink.addEventListener("click", (e) => {
|
|
@@ -428,20 +504,6 @@ var ModalRenderer = class {
|
|
|
428
504
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
429
505
|
});
|
|
430
506
|
}
|
|
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
507
|
}
|
|
446
508
|
};
|
|
447
509
|
|
|
@@ -636,8 +698,7 @@ var Authon = class {
|
|
|
636
698
|
const promise = isSignUp ? this.signUpWithEmail(email, password) : this.signInWithEmail(email, password);
|
|
637
699
|
promise.then(() => this.modal?.close()).catch((err) => {
|
|
638
700
|
const msg = err instanceof Error ? err.message : String(err);
|
|
639
|
-
|
|
640
|
-
this.modal?.showError(friendlyMsg);
|
|
701
|
+
this.modal?.showError(msg || "Authentication failed");
|
|
641
702
|
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
642
703
|
});
|
|
643
704
|
},
|
|
@@ -651,7 +712,7 @@ var Authon = class {
|
|
|
651
712
|
async startOAuthFlow(provider) {
|
|
652
713
|
try {
|
|
653
714
|
const redirectUri = `${this.config.apiUrl}/v1/auth/oauth/redirect`;
|
|
654
|
-
const { url } = await this.apiGet(
|
|
715
|
+
const { url, state } = await this.apiGet(
|
|
655
716
|
`/v1/auth/oauth/${provider}/url?redirectUri=${encodeURIComponent(redirectUri)}`
|
|
656
717
|
);
|
|
657
718
|
this.modal?.showLoading();
|
|
@@ -673,59 +734,80 @@ var Authon = class {
|
|
|
673
734
|
this.emit("error", new Error("Popup was blocked by the browser"));
|
|
674
735
|
return;
|
|
675
736
|
}
|
|
676
|
-
let
|
|
737
|
+
let resolved = false;
|
|
677
738
|
let cleaned = false;
|
|
739
|
+
const resolve = (tokens) => {
|
|
740
|
+
if (resolved) return;
|
|
741
|
+
resolved = true;
|
|
742
|
+
cleanup();
|
|
743
|
+
try {
|
|
744
|
+
if (!popup.closed) popup.close();
|
|
745
|
+
} catch {
|
|
746
|
+
}
|
|
747
|
+
this.session.setSession(tokens);
|
|
748
|
+
this.modal?.close();
|
|
749
|
+
this.emit("signedIn", tokens.user);
|
|
750
|
+
};
|
|
751
|
+
const handleError = (msg) => {
|
|
752
|
+
if (resolved) return;
|
|
753
|
+
cleanup();
|
|
754
|
+
this.modal?.hideLoading();
|
|
755
|
+
this.modal?.showError(msg);
|
|
756
|
+
this.emit("error", new Error(msg));
|
|
757
|
+
};
|
|
678
758
|
const cleanup = () => {
|
|
679
759
|
if (cleaned) return;
|
|
680
760
|
cleaned = true;
|
|
681
|
-
window.removeEventListener("message",
|
|
682
|
-
if (
|
|
761
|
+
window.removeEventListener("message", messageHandler);
|
|
762
|
+
if (apiPollTimer) clearInterval(apiPollTimer);
|
|
763
|
+
if (closePollTimer) clearInterval(closePollTimer);
|
|
683
764
|
if (maxTimer) clearTimeout(maxTimer);
|
|
684
765
|
};
|
|
685
|
-
const
|
|
686
|
-
if (
|
|
766
|
+
const messageHandler = (e) => {
|
|
767
|
+
if (e.data?.type !== "authon-oauth-callback") return;
|
|
768
|
+
if (e.data.tokens) {
|
|
769
|
+
resolve(e.data.tokens);
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
window.addEventListener("message", messageHandler);
|
|
773
|
+
const apiPollTimer = setInterval(async () => {
|
|
774
|
+
if (resolved || cleaned) return;
|
|
775
|
+
try {
|
|
776
|
+
const result = await this.apiGet(
|
|
777
|
+
`/v1/auth/oauth/poll?state=${encodeURIComponent(state)}`
|
|
778
|
+
);
|
|
779
|
+
if (result.status === "completed" && result.accessToken) {
|
|
780
|
+
resolve({
|
|
781
|
+
accessToken: result.accessToken,
|
|
782
|
+
refreshToken: result.refreshToken,
|
|
783
|
+
expiresIn: result.expiresIn,
|
|
784
|
+
user: result.user
|
|
785
|
+
});
|
|
786
|
+
} else if (result.status === "error") {
|
|
787
|
+
handleError(result.message || "Authentication failed");
|
|
788
|
+
}
|
|
789
|
+
} catch {
|
|
790
|
+
}
|
|
791
|
+
}, 1500);
|
|
792
|
+
const closePollTimer = setInterval(() => {
|
|
793
|
+
if (resolved || cleaned) return;
|
|
687
794
|
try {
|
|
688
795
|
if (popup.closed) {
|
|
796
|
+
clearInterval(closePollTimer);
|
|
689
797
|
setTimeout(() => {
|
|
690
|
-
if (
|
|
798
|
+
if (resolved || cleaned) return;
|
|
691
799
|
cleanup();
|
|
692
800
|
this.modal?.hideLoading();
|
|
693
|
-
},
|
|
694
|
-
clearInterval(pollTimer);
|
|
801
|
+
}, 3e3);
|
|
695
802
|
}
|
|
696
803
|
} catch {
|
|
697
804
|
}
|
|
698
805
|
}, 500);
|
|
699
806
|
const maxTimer = setTimeout(() => {
|
|
700
|
-
if (
|
|
807
|
+
if (resolved || cleaned) return;
|
|
701
808
|
cleanup();
|
|
702
809
|
this.modal?.hideLoading();
|
|
703
810
|
}, 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
811
|
} catch (err) {
|
|
730
812
|
this.modal?.hideLoading();
|
|
731
813
|
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
@@ -736,7 +818,7 @@ var Authon = class {
|
|
|
736
818
|
headers: { "x-api-key": this.publishableKey },
|
|
737
819
|
credentials: "include"
|
|
738
820
|
});
|
|
739
|
-
if (!res.ok) throw new Error(
|
|
821
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
740
822
|
return res.json();
|
|
741
823
|
}
|
|
742
824
|
async apiPost(path, body) {
|
|
@@ -749,9 +831,22 @@ var Authon = class {
|
|
|
749
831
|
credentials: "include",
|
|
750
832
|
body: body ? JSON.stringify(body) : void 0
|
|
751
833
|
});
|
|
752
|
-
if (!res.ok) throw new Error(
|
|
834
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
753
835
|
return res.json();
|
|
754
836
|
}
|
|
837
|
+
async parseApiError(res, path) {
|
|
838
|
+
try {
|
|
839
|
+
const body = await res.json();
|
|
840
|
+
if (Array.isArray(body.message) && body.message.length > 0) {
|
|
841
|
+
return body.message[0];
|
|
842
|
+
}
|
|
843
|
+
if (typeof body.message === "string" && body.message !== "Bad Request") {
|
|
844
|
+
return body.message;
|
|
845
|
+
}
|
|
846
|
+
} catch {
|
|
847
|
+
}
|
|
848
|
+
return `API ${path}: ${res.status}`;
|
|
849
|
+
}
|
|
755
850
|
};
|
|
756
851
|
// Annotate the CommonJS export names for ESM import in node:
|
|
757
852
|
0 && (module.exports = {
|
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,30 +192,48 @@ 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";
|
|
183
218
|
const subtitle = isSignUp ? "Already have an account?" : "Don't have an account?";
|
|
184
219
|
const subtitleLink = isSignUp ? "Sign in" : "Sign up";
|
|
185
220
|
const dark = this.isDark();
|
|
186
|
-
const
|
|
221
|
+
const showProviders = !isSignUp;
|
|
222
|
+
const providerButtons = showProviders ? this.enabledProviders.filter((p) => !b.hiddenProviders?.includes(p)).map((p) => {
|
|
187
223
|
const config = getProviderButtonConfig(p);
|
|
188
224
|
const isWhiteBg = config.bgColor === "#ffffff";
|
|
189
225
|
const btnBg = dark && isWhiteBg ? "#f8fafc" : config.bgColor;
|
|
190
226
|
const btnBorder = isWhiteBg ? dark ? "#475569" : "#e5e7eb" : config.bgColor;
|
|
191
227
|
return `<button class="provider-btn" data-provider="${p}" style="background:${btnBg};color:${config.textColor};border:1px solid ${btnBorder}">
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}).join("");
|
|
196
|
-
const divider = b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
|
|
228
|
+
<span class="provider-icon">${config.iconSvg}</span>
|
|
229
|
+
<span>${config.label}</span>
|
|
230
|
+
</button>`;
|
|
231
|
+
}).join("") : "";
|
|
232
|
+
const divider = showProviders && b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
|
|
197
233
|
const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
|
|
198
234
|
<input type="email" placeholder="Email address" name="email" required class="input" autocomplete="email" />
|
|
199
235
|
<input type="password" placeholder="Password" name="password" required class="input" autocomplete="${isSignUp ? "new-password" : "current-password"}" />
|
|
236
|
+
${isSignUp ? '<p class="password-hint">Must contain uppercase, lowercase, and a number (min 8 chars)</p>' : ""}
|
|
200
237
|
<button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
|
|
201
238
|
</form>` : "";
|
|
202
239
|
const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
|
|
@@ -204,21 +241,22 @@ var ModalRenderer = class {
|
|
|
204
241
|
${b.termsUrl && b.privacyUrl ? " \xB7 " : ""}
|
|
205
242
|
${b.privacyUrl ? `<a href="${b.privacyUrl}" target="_blank">Privacy Policy</a>` : ""}
|
|
206
243
|
</div>` : "";
|
|
207
|
-
const
|
|
244
|
+
const titleHtml = isSignUp ? `<div class="title-row">
|
|
245
|
+
<button class="back-btn" id="back-btn" type="button" aria-label="Back to sign in">
|
|
246
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5"/><path d="m12 19-7-7 7-7"/></svg>
|
|
247
|
+
</button>
|
|
248
|
+
<h2 class="title">${title}</h2>
|
|
249
|
+
</div>` : `<h2 class="title">${title}</h2>`;
|
|
208
250
|
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>
|
|
251
|
+
${b.logoDataUrl ? `<img src="${b.logoDataUrl}" alt="Logo" class="logo" />` : ""}
|
|
252
|
+
${titleHtml}
|
|
253
|
+
${b.brandName ? `<p class="brand-name">${b.brandName}</p>` : ""}
|
|
254
|
+
${showProviders ? `<div class="providers">${providerButtons}</div>` : ""}
|
|
255
|
+
${divider}
|
|
256
|
+
${emailForm}
|
|
257
|
+
<p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
|
|
258
|
+
${footer}
|
|
259
|
+
${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
260
|
`;
|
|
223
261
|
}
|
|
224
262
|
isDark() {
|
|
@@ -269,7 +307,20 @@ var ModalRenderer = class {
|
|
|
269
307
|
position: ${this.mode === "popup" ? "fixed" : "relative"};
|
|
270
308
|
${this.mode === "popup" ? `box-shadow: 0 25px 50px -12px rgba(0,0,0,${dark ? "0.5" : "0.25"}); animation: slideIn 0.3s ease;` : ""}
|
|
271
309
|
}
|
|
310
|
+
.modal-inner {
|
|
311
|
+
transition: opacity 0.14s ease, transform 0.14s ease;
|
|
312
|
+
}
|
|
272
313
|
.logo { display: block; margin: 0 auto 16px; max-height: 48px; }
|
|
314
|
+
.title-row { display: flex; align-items: center; position: relative; margin-bottom: 8px; }
|
|
315
|
+
.title-row .title { flex: 1; margin-bottom: 0; }
|
|
316
|
+
.back-btn {
|
|
317
|
+
position: absolute; left: 0; top: 50%; transform: translateY(-50%);
|
|
318
|
+
background: none; border: none; color: var(--authon-muted);
|
|
319
|
+
cursor: pointer; padding: 4px; border-radius: 6px; display: flex; align-items: center; justify-content: center;
|
|
320
|
+
transition: color 0.15s, background 0.15s;
|
|
321
|
+
}
|
|
322
|
+
.back-btn:hover { color: var(--authon-text); background: var(--authon-divider); }
|
|
323
|
+
.password-hint { font-size: 11px; color: var(--authon-dim); margin: -4px 0 2px; }
|
|
273
324
|
.title { text-align: center; font-size: 24px; font-weight: 700; margin-bottom: 8px; color: var(--authon-text); }
|
|
274
325
|
.brand-name { text-align: center; font-size: 14px; color: var(--authon-muted); margin-bottom: 24px; }
|
|
275
326
|
.providers { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
|
|
@@ -327,15 +378,15 @@ var ModalRenderer = class {
|
|
|
327
378
|
.switch-view { text-align: center; margin-top: 16px; font-size: 13px; color: var(--authon-muted); }
|
|
328
379
|
.switch-view a { color: var(--authon-primary-start); text-decoration: none; font-weight: 500; }
|
|
329
380
|
.switch-view a:hover { text-decoration: underline; }
|
|
330
|
-
.footer { text-align: center; margin-top:
|
|
381
|
+
.footer { text-align: center; margin-top: 12px; font-size: 12px; color: var(--authon-dim); }
|
|
331
382
|
.footer a { color: var(--authon-dim); text-decoration: none; }
|
|
332
383
|
.footer a:hover { text-decoration: underline; }
|
|
333
384
|
.secured-by {
|
|
334
|
-
text-align: center; margin-top:
|
|
335
|
-
border-top: 1px solid var(--authon-divider);
|
|
385
|
+
text-align: center; margin-top: 16px;
|
|
336
386
|
font-size: 11px; color: var(--authon-dim);
|
|
337
387
|
}
|
|
338
|
-
.secured-
|
|
388
|
+
.secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
|
|
389
|
+
.secured-link:hover { text-decoration: underline; }
|
|
339
390
|
/* Loading overlay */
|
|
340
391
|
#authon-loading-overlay {
|
|
341
392
|
position: absolute; inset: 0; z-index: 10;
|
|
@@ -374,7 +425,26 @@ var ModalRenderer = class {
|
|
|
374
425
|
${b.customCss || ""}
|
|
375
426
|
`;
|
|
376
427
|
}
|
|
377
|
-
|
|
428
|
+
// ── Event binding ──
|
|
429
|
+
/** Attach events to shell elements (backdrop, ESC) — called once */
|
|
430
|
+
attachShellEvents() {
|
|
431
|
+
if (!this.shadowRoot) return;
|
|
432
|
+
const backdrop = this.shadowRoot.getElementById("backdrop");
|
|
433
|
+
if (backdrop) {
|
|
434
|
+
backdrop.addEventListener("click", () => this.onClose());
|
|
435
|
+
}
|
|
436
|
+
if (this.escHandler) {
|
|
437
|
+
document.removeEventListener("keydown", this.escHandler);
|
|
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
|
+
}
|
|
446
|
+
/** Attach events to inner content (buttons, form, switch link) — called on each view */
|
|
447
|
+
attachInnerEvents(view) {
|
|
378
448
|
if (!this.shadowRoot) return;
|
|
379
449
|
this.shadowRoot.querySelectorAll(".provider-btn").forEach((btn) => {
|
|
380
450
|
btn.addEventListener("click", () => {
|
|
@@ -394,6 +464,12 @@ var ModalRenderer = class {
|
|
|
394
464
|
);
|
|
395
465
|
});
|
|
396
466
|
}
|
|
467
|
+
const backBtn = this.shadowRoot.getElementById("back-btn");
|
|
468
|
+
if (backBtn) {
|
|
469
|
+
backBtn.addEventListener("click", () => {
|
|
470
|
+
this.open("signIn");
|
|
471
|
+
});
|
|
472
|
+
}
|
|
397
473
|
const switchLink = this.shadowRoot.getElementById("switch-link");
|
|
398
474
|
if (switchLink) {
|
|
399
475
|
switchLink.addEventListener("click", (e) => {
|
|
@@ -401,20 +477,6 @@ var ModalRenderer = class {
|
|
|
401
477
|
this.open(view === "signIn" ? "signUp" : "signIn");
|
|
402
478
|
});
|
|
403
479
|
}
|
|
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
480
|
}
|
|
419
481
|
};
|
|
420
482
|
|
|
@@ -609,8 +671,7 @@ var Authon = class {
|
|
|
609
671
|
const promise = isSignUp ? this.signUpWithEmail(email, password) : this.signInWithEmail(email, password);
|
|
610
672
|
promise.then(() => this.modal?.close()).catch((err) => {
|
|
611
673
|
const msg = err instanceof Error ? err.message : String(err);
|
|
612
|
-
|
|
613
|
-
this.modal?.showError(friendlyMsg);
|
|
674
|
+
this.modal?.showError(msg || "Authentication failed");
|
|
614
675
|
this.emit("error", err instanceof Error ? err : new Error(msg));
|
|
615
676
|
});
|
|
616
677
|
},
|
|
@@ -624,7 +685,7 @@ var Authon = class {
|
|
|
624
685
|
async startOAuthFlow(provider) {
|
|
625
686
|
try {
|
|
626
687
|
const redirectUri = `${this.config.apiUrl}/v1/auth/oauth/redirect`;
|
|
627
|
-
const { url } = await this.apiGet(
|
|
688
|
+
const { url, state } = await this.apiGet(
|
|
628
689
|
`/v1/auth/oauth/${provider}/url?redirectUri=${encodeURIComponent(redirectUri)}`
|
|
629
690
|
);
|
|
630
691
|
this.modal?.showLoading();
|
|
@@ -646,59 +707,80 @@ var Authon = class {
|
|
|
646
707
|
this.emit("error", new Error("Popup was blocked by the browser"));
|
|
647
708
|
return;
|
|
648
709
|
}
|
|
649
|
-
let
|
|
710
|
+
let resolved = false;
|
|
650
711
|
let cleaned = false;
|
|
712
|
+
const resolve = (tokens) => {
|
|
713
|
+
if (resolved) return;
|
|
714
|
+
resolved = true;
|
|
715
|
+
cleanup();
|
|
716
|
+
try {
|
|
717
|
+
if (!popup.closed) popup.close();
|
|
718
|
+
} catch {
|
|
719
|
+
}
|
|
720
|
+
this.session.setSession(tokens);
|
|
721
|
+
this.modal?.close();
|
|
722
|
+
this.emit("signedIn", tokens.user);
|
|
723
|
+
};
|
|
724
|
+
const handleError = (msg) => {
|
|
725
|
+
if (resolved) return;
|
|
726
|
+
cleanup();
|
|
727
|
+
this.modal?.hideLoading();
|
|
728
|
+
this.modal?.showError(msg);
|
|
729
|
+
this.emit("error", new Error(msg));
|
|
730
|
+
};
|
|
651
731
|
const cleanup = () => {
|
|
652
732
|
if (cleaned) return;
|
|
653
733
|
cleaned = true;
|
|
654
|
-
window.removeEventListener("message",
|
|
655
|
-
if (
|
|
734
|
+
window.removeEventListener("message", messageHandler);
|
|
735
|
+
if (apiPollTimer) clearInterval(apiPollTimer);
|
|
736
|
+
if (closePollTimer) clearInterval(closePollTimer);
|
|
656
737
|
if (maxTimer) clearTimeout(maxTimer);
|
|
657
738
|
};
|
|
658
|
-
const
|
|
659
|
-
if (
|
|
739
|
+
const messageHandler = (e) => {
|
|
740
|
+
if (e.data?.type !== "authon-oauth-callback") return;
|
|
741
|
+
if (e.data.tokens) {
|
|
742
|
+
resolve(e.data.tokens);
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
window.addEventListener("message", messageHandler);
|
|
746
|
+
const apiPollTimer = setInterval(async () => {
|
|
747
|
+
if (resolved || cleaned) return;
|
|
748
|
+
try {
|
|
749
|
+
const result = await this.apiGet(
|
|
750
|
+
`/v1/auth/oauth/poll?state=${encodeURIComponent(state)}`
|
|
751
|
+
);
|
|
752
|
+
if (result.status === "completed" && result.accessToken) {
|
|
753
|
+
resolve({
|
|
754
|
+
accessToken: result.accessToken,
|
|
755
|
+
refreshToken: result.refreshToken,
|
|
756
|
+
expiresIn: result.expiresIn,
|
|
757
|
+
user: result.user
|
|
758
|
+
});
|
|
759
|
+
} else if (result.status === "error") {
|
|
760
|
+
handleError(result.message || "Authentication failed");
|
|
761
|
+
}
|
|
762
|
+
} catch {
|
|
763
|
+
}
|
|
764
|
+
}, 1500);
|
|
765
|
+
const closePollTimer = setInterval(() => {
|
|
766
|
+
if (resolved || cleaned) return;
|
|
660
767
|
try {
|
|
661
768
|
if (popup.closed) {
|
|
769
|
+
clearInterval(closePollTimer);
|
|
662
770
|
setTimeout(() => {
|
|
663
|
-
if (
|
|
771
|
+
if (resolved || cleaned) return;
|
|
664
772
|
cleanup();
|
|
665
773
|
this.modal?.hideLoading();
|
|
666
|
-
},
|
|
667
|
-
clearInterval(pollTimer);
|
|
774
|
+
}, 3e3);
|
|
668
775
|
}
|
|
669
776
|
} catch {
|
|
670
777
|
}
|
|
671
778
|
}, 500);
|
|
672
779
|
const maxTimer = setTimeout(() => {
|
|
673
|
-
if (
|
|
780
|
+
if (resolved || cleaned) return;
|
|
674
781
|
cleanup();
|
|
675
782
|
this.modal?.hideLoading();
|
|
676
783
|
}, 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
784
|
} catch (err) {
|
|
703
785
|
this.modal?.hideLoading();
|
|
704
786
|
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
@@ -709,7 +791,7 @@ var Authon = class {
|
|
|
709
791
|
headers: { "x-api-key": this.publishableKey },
|
|
710
792
|
credentials: "include"
|
|
711
793
|
});
|
|
712
|
-
if (!res.ok) throw new Error(
|
|
794
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
713
795
|
return res.json();
|
|
714
796
|
}
|
|
715
797
|
async apiPost(path, body) {
|
|
@@ -722,9 +804,22 @@ var Authon = class {
|
|
|
722
804
|
credentials: "include",
|
|
723
805
|
body: body ? JSON.stringify(body) : void 0
|
|
724
806
|
});
|
|
725
|
-
if (!res.ok) throw new Error(
|
|
807
|
+
if (!res.ok) throw new Error(await this.parseApiError(res, path));
|
|
726
808
|
return res.json();
|
|
727
809
|
}
|
|
810
|
+
async parseApiError(res, path) {
|
|
811
|
+
try {
|
|
812
|
+
const body = await res.json();
|
|
813
|
+
if (Array.isArray(body.message) && body.message.length > 0) {
|
|
814
|
+
return body.message[0];
|
|
815
|
+
}
|
|
816
|
+
if (typeof body.message === "string" && body.message !== "Bad Request") {
|
|
817
|
+
return body.message;
|
|
818
|
+
}
|
|
819
|
+
} catch {
|
|
820
|
+
}
|
|
821
|
+
return `API ${path}: ${res.status}`;
|
|
822
|
+
}
|
|
728
823
|
};
|
|
729
824
|
export {
|
|
730
825
|
Authon,
|