@authon/js 0.1.10 → 0.1.11

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 CHANGED
@@ -100,6 +100,7 @@ var ModalRenderer = class {
100
100
  onProviderClick;
101
101
  onEmailSubmit;
102
102
  onClose;
103
+ escHandler = null;
103
104
  constructor(options) {
104
105
  this.mode = options.mode;
105
106
  this.theme = options.theme || "auto";
@@ -118,10 +119,18 @@ var ModalRenderer = class {
118
119
  this.branding = { ...DEFAULT_BRANDING, ...branding };
119
120
  }
120
121
  open(view = "signIn") {
121
- this.close();
122
- this.render(view);
122
+ if (this.shadowRoot && this.hostElement) {
123
+ this.shadowRoot.innerHTML = this.buildHTML(view);
124
+ this.attachEvents(view);
125
+ } else {
126
+ this.render(view);
127
+ }
123
128
  }
124
129
  close() {
130
+ if (this.escHandler) {
131
+ document.removeEventListener("keydown", this.escHandler);
132
+ this.escHandler = null;
133
+ }
125
134
  if (this.hostElement) {
126
135
  this.hostElement.remove();
127
136
  this.hostElement = null;
@@ -143,6 +152,21 @@ var ModalRenderer = class {
143
152
  errorEl.appendChild(errDiv);
144
153
  }
145
154
  }
155
+ showBanner(message, type = "error") {
156
+ if (!this.shadowRoot) return;
157
+ this.clearBanner();
158
+ const container = this.shadowRoot.querySelector(".modal-container");
159
+ if (!container) return;
160
+ const banner = document.createElement("div");
161
+ banner.id = "authon-banner";
162
+ banner.className = type === "warning" ? "banner-warning" : "error-msg";
163
+ banner.textContent = message;
164
+ container.insertBefore(banner, container.firstChild);
165
+ }
166
+ clearBanner() {
167
+ if (!this.shadowRoot) return;
168
+ this.shadowRoot.getElementById("authon-banner")?.remove();
169
+ }
146
170
  clearError() {
147
171
  if (!this.shadowRoot) return;
148
172
  this.shadowRoot.getElementById("authon-error-msg")?.remove();
@@ -198,8 +222,8 @@ var ModalRenderer = class {
198
222
  }).join("");
199
223
  const divider = b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
200
224
  const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
201
- <input type="email" placeholder="Email address" name="email" required class="input" />
202
- <input type="password" placeholder="Password" name="password" required class="input" />
225
+ <input type="email" placeholder="Email address" name="email" required class="input" autocomplete="email" />
226
+ <input type="password" placeholder="Password" name="password" required class="input" autocomplete="${isSignUp ? "new-password" : "current-password"}" />
203
227
  <button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
204
228
  </form>` : "";
205
229
  const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
@@ -320,6 +344,13 @@ var ModalRenderer = class {
320
344
  font-size: 13px; color: #ef4444; text-align: center;
321
345
  animation: fadeIn 0.15s ease;
322
346
  }
347
+ .banner-warning {
348
+ margin-bottom: 16px; padding: 10px 14px;
349
+ background: rgba(245,158,11,0.1); border: 1px solid rgba(245,158,11,0.3);
350
+ border-radius: calc(var(--authon-radius) * 0.33);
351
+ font-size: 13px; color: #f59e0b; text-align: center;
352
+ animation: fadeIn 0.15s ease;
353
+ }
323
354
  .switch-view { text-align: center; margin-top: 16px; font-size: 13px; color: var(--authon-muted); }
324
355
  .switch-view a { color: var(--authon-primary-start); text-decoration: none; font-weight: 500; }
325
356
  .switch-view a:hover { text-decoration: underline; }
@@ -401,14 +432,15 @@ var ModalRenderer = class {
401
432
  if (backdrop) {
402
433
  backdrop.addEventListener("click", () => this.onClose());
403
434
  }
435
+ if (this.escHandler) {
436
+ document.removeEventListener("keydown", this.escHandler);
437
+ this.escHandler = null;
438
+ }
404
439
  if (this.mode === "popup") {
405
- const handler = (e) => {
406
- if (e.key === "Escape") {
407
- this.onClose();
408
- document.removeEventListener("keydown", handler);
409
- }
440
+ this.escHandler = (e) => {
441
+ if (e.key === "Escape") this.onClose();
410
442
  };
411
- document.addEventListener("keydown", handler);
443
+ document.addEventListener("keydown", this.escHandler);
412
444
  }
413
445
  }
414
446
  };
@@ -632,39 +664,51 @@ var Authon = class {
632
664
  "authon-oauth",
633
665
  `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no`
634
666
  );
667
+ if (!popup || popup.closed) {
668
+ this.modal?.hideLoading();
669
+ this.modal?.showBanner(
670
+ "Pop-up blocked. Please allow pop-ups for this site and try again.",
671
+ "warning"
672
+ );
673
+ this.emit("error", new Error("Popup was blocked by the browser"));
674
+ return;
675
+ }
635
676
  let callbackReceived = false;
636
677
  let cleaned = false;
637
678
  const cleanup = () => {
638
679
  if (cleaned) return;
639
680
  cleaned = true;
640
681
  window.removeEventListener("message", handler);
641
- window.removeEventListener("focus", focusHandler);
682
+ if (pollTimer) clearInterval(pollTimer);
642
683
  if (maxTimer) clearTimeout(maxTimer);
643
684
  };
644
- const focusHandler = () => {
685
+ const pollTimer = setInterval(() => {
645
686
  if (callbackReceived || cleaned) return;
646
- setTimeout(() => {
647
- if (callbackReceived || cleaned) return;
648
- cleanup();
649
- this.modal?.hideLoading();
650
- }, 1500);
651
- };
652
- window.addEventListener("focus", focusHandler);
687
+ try {
688
+ if (popup.closed) {
689
+ setTimeout(() => {
690
+ if (callbackReceived || cleaned) return;
691
+ cleanup();
692
+ this.modal?.hideLoading();
693
+ }, 500);
694
+ clearInterval(pollTimer);
695
+ }
696
+ } catch {
697
+ }
698
+ }, 500);
653
699
  const maxTimer = setTimeout(() => {
654
700
  if (callbackReceived || cleaned) return;
655
701
  cleanup();
656
702
  this.modal?.hideLoading();
657
703
  }, 18e4);
658
- if (!popup) {
659
- cleanup();
660
- this.modal?.hideLoading();
661
- this.emit("error", new Error("Popup was blocked by the browser"));
662
- return;
663
- }
664
704
  const handler = async (e) => {
665
705
  if (e.data?.type !== "authon-oauth-callback") return;
666
706
  callbackReceived = true;
667
707
  cleanup();
708
+ try {
709
+ if (!popup.closed) popup.close();
710
+ } catch {
711
+ }
668
712
  try {
669
713
  const tokens = await this.apiPost("/v1/auth/oauth/callback", {
670
714
  code: e.data.code,
@@ -676,7 +720,9 @@ var Authon = class {
676
720
  this.emit("signedIn", tokens.user);
677
721
  } catch (err) {
678
722
  this.modal?.hideLoading();
679
- this.emit("error", err instanceof Error ? err : new Error(String(err)));
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));
680
726
  }
681
727
  };
682
728
  window.addEventListener("message", handler);
package/dist/index.mjs CHANGED
@@ -73,6 +73,7 @@ var ModalRenderer = class {
73
73
  onProviderClick;
74
74
  onEmailSubmit;
75
75
  onClose;
76
+ escHandler = null;
76
77
  constructor(options) {
77
78
  this.mode = options.mode;
78
79
  this.theme = options.theme || "auto";
@@ -91,10 +92,18 @@ var ModalRenderer = class {
91
92
  this.branding = { ...DEFAULT_BRANDING, ...branding };
92
93
  }
93
94
  open(view = "signIn") {
94
- this.close();
95
- this.render(view);
95
+ if (this.shadowRoot && this.hostElement) {
96
+ this.shadowRoot.innerHTML = this.buildHTML(view);
97
+ this.attachEvents(view);
98
+ } else {
99
+ this.render(view);
100
+ }
96
101
  }
97
102
  close() {
103
+ if (this.escHandler) {
104
+ document.removeEventListener("keydown", this.escHandler);
105
+ this.escHandler = null;
106
+ }
98
107
  if (this.hostElement) {
99
108
  this.hostElement.remove();
100
109
  this.hostElement = null;
@@ -116,6 +125,21 @@ var ModalRenderer = class {
116
125
  errorEl.appendChild(errDiv);
117
126
  }
118
127
  }
128
+ showBanner(message, type = "error") {
129
+ if (!this.shadowRoot) return;
130
+ this.clearBanner();
131
+ const container = this.shadowRoot.querySelector(".modal-container");
132
+ if (!container) return;
133
+ const banner = document.createElement("div");
134
+ banner.id = "authon-banner";
135
+ banner.className = type === "warning" ? "banner-warning" : "error-msg";
136
+ banner.textContent = message;
137
+ container.insertBefore(banner, container.firstChild);
138
+ }
139
+ clearBanner() {
140
+ if (!this.shadowRoot) return;
141
+ this.shadowRoot.getElementById("authon-banner")?.remove();
142
+ }
119
143
  clearError() {
120
144
  if (!this.shadowRoot) return;
121
145
  this.shadowRoot.getElementById("authon-error-msg")?.remove();
@@ -171,8 +195,8 @@ var ModalRenderer = class {
171
195
  }).join("");
172
196
  const divider = b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
173
197
  const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
174
- <input type="email" placeholder="Email address" name="email" required class="input" />
175
- <input type="password" placeholder="Password" name="password" required class="input" />
198
+ <input type="email" placeholder="Email address" name="email" required class="input" autocomplete="email" />
199
+ <input type="password" placeholder="Password" name="password" required class="input" autocomplete="${isSignUp ? "new-password" : "current-password"}" />
176
200
  <button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
177
201
  </form>` : "";
178
202
  const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
@@ -293,6 +317,13 @@ var ModalRenderer = class {
293
317
  font-size: 13px; color: #ef4444; text-align: center;
294
318
  animation: fadeIn 0.15s ease;
295
319
  }
320
+ .banner-warning {
321
+ margin-bottom: 16px; padding: 10px 14px;
322
+ background: rgba(245,158,11,0.1); border: 1px solid rgba(245,158,11,0.3);
323
+ border-radius: calc(var(--authon-radius) * 0.33);
324
+ font-size: 13px; color: #f59e0b; text-align: center;
325
+ animation: fadeIn 0.15s ease;
326
+ }
296
327
  .switch-view { text-align: center; margin-top: 16px; font-size: 13px; color: var(--authon-muted); }
297
328
  .switch-view a { color: var(--authon-primary-start); text-decoration: none; font-weight: 500; }
298
329
  .switch-view a:hover { text-decoration: underline; }
@@ -374,14 +405,15 @@ var ModalRenderer = class {
374
405
  if (backdrop) {
375
406
  backdrop.addEventListener("click", () => this.onClose());
376
407
  }
408
+ if (this.escHandler) {
409
+ document.removeEventListener("keydown", this.escHandler);
410
+ this.escHandler = null;
411
+ }
377
412
  if (this.mode === "popup") {
378
- const handler = (e) => {
379
- if (e.key === "Escape") {
380
- this.onClose();
381
- document.removeEventListener("keydown", handler);
382
- }
413
+ this.escHandler = (e) => {
414
+ if (e.key === "Escape") this.onClose();
383
415
  };
384
- document.addEventListener("keydown", handler);
416
+ document.addEventListener("keydown", this.escHandler);
385
417
  }
386
418
  }
387
419
  };
@@ -605,39 +637,51 @@ var Authon = class {
605
637
  "authon-oauth",
606
638
  `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no`
607
639
  );
640
+ if (!popup || popup.closed) {
641
+ this.modal?.hideLoading();
642
+ this.modal?.showBanner(
643
+ "Pop-up blocked. Please allow pop-ups for this site and try again.",
644
+ "warning"
645
+ );
646
+ this.emit("error", new Error("Popup was blocked by the browser"));
647
+ return;
648
+ }
608
649
  let callbackReceived = false;
609
650
  let cleaned = false;
610
651
  const cleanup = () => {
611
652
  if (cleaned) return;
612
653
  cleaned = true;
613
654
  window.removeEventListener("message", handler);
614
- window.removeEventListener("focus", focusHandler);
655
+ if (pollTimer) clearInterval(pollTimer);
615
656
  if (maxTimer) clearTimeout(maxTimer);
616
657
  };
617
- const focusHandler = () => {
658
+ const pollTimer = setInterval(() => {
618
659
  if (callbackReceived || cleaned) return;
619
- setTimeout(() => {
620
- if (callbackReceived || cleaned) return;
621
- cleanup();
622
- this.modal?.hideLoading();
623
- }, 1500);
624
- };
625
- window.addEventListener("focus", focusHandler);
660
+ try {
661
+ if (popup.closed) {
662
+ setTimeout(() => {
663
+ if (callbackReceived || cleaned) return;
664
+ cleanup();
665
+ this.modal?.hideLoading();
666
+ }, 500);
667
+ clearInterval(pollTimer);
668
+ }
669
+ } catch {
670
+ }
671
+ }, 500);
626
672
  const maxTimer = setTimeout(() => {
627
673
  if (callbackReceived || cleaned) return;
628
674
  cleanup();
629
675
  this.modal?.hideLoading();
630
676
  }, 18e4);
631
- if (!popup) {
632
- cleanup();
633
- this.modal?.hideLoading();
634
- this.emit("error", new Error("Popup was blocked by the browser"));
635
- return;
636
- }
637
677
  const handler = async (e) => {
638
678
  if (e.data?.type !== "authon-oauth-callback") return;
639
679
  callbackReceived = true;
640
680
  cleanup();
681
+ try {
682
+ if (!popup.closed) popup.close();
683
+ } catch {
684
+ }
641
685
  try {
642
686
  const tokens = await this.apiPost("/v1/auth/oauth/callback", {
643
687
  code: e.data.code,
@@ -649,7 +693,9 @@ var Authon = class {
649
693
  this.emit("signedIn", tokens.user);
650
694
  } catch (err) {
651
695
  this.modal?.hideLoading();
652
- this.emit("error", err instanceof Error ? err : new Error(String(err)));
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));
653
699
  }
654
700
  };
655
701
  window.addEventListener("message", handler);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authon/js",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Authon core SDK — ShadowDOM login modal for any app",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",