@authon/js 0.1.9 → 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.
Files changed (3) hide show
  1. package/dist/index.js +104 -31
  2. package/dist/index.mjs +104 -31
  3. package/package.json +1 -1
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;
@@ -131,6 +140,37 @@ var ModalRenderer = class {
131
140
  this.containerElement.innerHTML = "";
132
141
  }
133
142
  }
143
+ showError(message) {
144
+ if (!this.shadowRoot) return;
145
+ this.clearError();
146
+ const errorEl = this.shadowRoot.getElementById("email-form");
147
+ if (errorEl) {
148
+ const errDiv = document.createElement("div");
149
+ errDiv.id = "authon-error-msg";
150
+ errDiv.className = "error-msg";
151
+ errDiv.textContent = message;
152
+ errorEl.appendChild(errDiv);
153
+ }
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
+ }
170
+ clearError() {
171
+ if (!this.shadowRoot) return;
172
+ this.shadowRoot.getElementById("authon-error-msg")?.remove();
173
+ }
134
174
  showLoading() {
135
175
  if (!this.shadowRoot) return;
136
176
  this.hideLoading();
@@ -182,8 +222,8 @@ var ModalRenderer = class {
182
222
  }).join("");
183
223
  const divider = b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
184
224
  const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
185
- <input type="email" placeholder="Email address" name="email" required class="input" />
186
- <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"}" />
187
227
  <button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
188
228
  </form>` : "";
189
229
  const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
@@ -296,6 +336,21 @@ var ModalRenderer = class {
296
336
  font-family: var(--authon-font); transition: opacity 0.15s;
297
337
  }
298
338
  .submit-btn:hover { opacity: 0.9; }
339
+ .submit-btn:disabled { opacity: 0.6; cursor: not-allowed; }
340
+ .error-msg {
341
+ margin-top: 8px; padding: 8px 12px;
342
+ background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3);
343
+ border-radius: calc(var(--authon-radius) * 0.33);
344
+ font-size: 13px; color: #ef4444; text-align: center;
345
+ animation: fadeIn 0.15s ease;
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
+ }
299
354
  .switch-view { text-align: center; margin-top: 16px; font-size: 13px; color: var(--authon-muted); }
300
355
  .switch-view a { color: var(--authon-primary-start); text-decoration: none; font-weight: 500; }
301
356
  .switch-view a:hover { text-decoration: underline; }
@@ -377,14 +432,15 @@ var ModalRenderer = class {
377
432
  if (backdrop) {
378
433
  backdrop.addEventListener("click", () => this.onClose());
379
434
  }
435
+ if (this.escHandler) {
436
+ document.removeEventListener("keydown", this.escHandler);
437
+ this.escHandler = null;
438
+ }
380
439
  if (this.mode === "popup") {
381
- const handler = (e) => {
382
- if (e.key === "Escape") {
383
- this.onClose();
384
- document.removeEventListener("keydown", handler);
385
- }
440
+ this.escHandler = (e) => {
441
+ if (e.key === "Escape") this.onClose();
386
442
  };
387
- document.addEventListener("keydown", handler);
443
+ document.addEventListener("keydown", this.escHandler);
388
444
  }
389
445
  }
390
446
  };
@@ -576,11 +632,14 @@ var Authon = class {
576
632
  branding: this.branding || void 0,
577
633
  onProviderClick: (provider) => this.startOAuthFlow(provider),
578
634
  onEmailSubmit: (email, password, isSignUp) => {
579
- if (isSignUp) {
580
- this.signUpWithEmail(email, password).then(() => this.modal?.close());
581
- } else {
582
- this.signInWithEmail(email, password).then(() => this.modal?.close());
583
- }
635
+ this.modal?.clearError();
636
+ const promise = isSignUp ? this.signUpWithEmail(email, password) : this.signInWithEmail(email, password);
637
+ promise.then(() => this.modal?.close()).catch((err) => {
638
+ const msg = err instanceof Error ? err.message : String(err);
639
+ const friendlyMsg = msg.includes("401") ? "Invalid email or password" : msg.includes("409") ? "Email already in use" : msg.includes("400") ? "Please check your input" : msg || "Authentication failed";
640
+ this.modal?.showError(friendlyMsg);
641
+ this.emit("error", err instanceof Error ? err : new Error(msg));
642
+ });
584
643
  },
585
644
  onClose: () => this.modal?.close()
586
645
  });
@@ -605,39 +664,51 @@ var Authon = class {
605
664
  "authon-oauth",
606
665
  `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no`
607
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
+ }
608
676
  let callbackReceived = false;
609
677
  let cleaned = false;
610
678
  const cleanup = () => {
611
679
  if (cleaned) return;
612
680
  cleaned = true;
613
681
  window.removeEventListener("message", handler);
614
- window.removeEventListener("focus", focusHandler);
682
+ if (pollTimer) clearInterval(pollTimer);
615
683
  if (maxTimer) clearTimeout(maxTimer);
616
684
  };
617
- const focusHandler = () => {
685
+ const pollTimer = setInterval(() => {
618
686
  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);
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);
626
699
  const maxTimer = setTimeout(() => {
627
700
  if (callbackReceived || cleaned) return;
628
701
  cleanup();
629
702
  this.modal?.hideLoading();
630
703
  }, 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
704
  const handler = async (e) => {
638
705
  if (e.data?.type !== "authon-oauth-callback") return;
639
706
  callbackReceived = true;
640
707
  cleanup();
708
+ try {
709
+ if (!popup.closed) popup.close();
710
+ } catch {
711
+ }
641
712
  try {
642
713
  const tokens = await this.apiPost("/v1/auth/oauth/callback", {
643
714
  code: e.data.code,
@@ -649,7 +720,9 @@ var Authon = class {
649
720
  this.emit("signedIn", tokens.user);
650
721
  } catch (err) {
651
722
  this.modal?.hideLoading();
652
- 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));
653
726
  }
654
727
  };
655
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;
@@ -104,6 +113,37 @@ var ModalRenderer = class {
104
113
  this.containerElement.innerHTML = "";
105
114
  }
106
115
  }
116
+ showError(message) {
117
+ if (!this.shadowRoot) return;
118
+ this.clearError();
119
+ const errorEl = this.shadowRoot.getElementById("email-form");
120
+ if (errorEl) {
121
+ const errDiv = document.createElement("div");
122
+ errDiv.id = "authon-error-msg";
123
+ errDiv.className = "error-msg";
124
+ errDiv.textContent = message;
125
+ errorEl.appendChild(errDiv);
126
+ }
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
+ }
143
+ clearError() {
144
+ if (!this.shadowRoot) return;
145
+ this.shadowRoot.getElementById("authon-error-msg")?.remove();
146
+ }
107
147
  showLoading() {
108
148
  if (!this.shadowRoot) return;
109
149
  this.hideLoading();
@@ -155,8 +195,8 @@ var ModalRenderer = class {
155
195
  }).join("");
156
196
  const divider = b.showDivider !== false && b.showEmailPassword !== false ? `<div class="divider"><span>or</span></div>` : "";
157
197
  const emailForm = b.showEmailPassword !== false ? `<form class="email-form" id="email-form">
158
- <input type="email" placeholder="Email address" name="email" required class="input" />
159
- <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"}" />
160
200
  <button type="submit" class="submit-btn">${isSignUp ? "Sign up" : "Sign in"}</button>
161
201
  </form>` : "";
162
202
  const footer = b.termsUrl || b.privacyUrl ? `<div class="footer">
@@ -269,6 +309,21 @@ var ModalRenderer = class {
269
309
  font-family: var(--authon-font); transition: opacity 0.15s;
270
310
  }
271
311
  .submit-btn:hover { opacity: 0.9; }
312
+ .submit-btn:disabled { opacity: 0.6; cursor: not-allowed; }
313
+ .error-msg {
314
+ margin-top: 8px; padding: 8px 12px;
315
+ background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3);
316
+ border-radius: calc(var(--authon-radius) * 0.33);
317
+ font-size: 13px; color: #ef4444; text-align: center;
318
+ animation: fadeIn 0.15s ease;
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
+ }
272
327
  .switch-view { text-align: center; margin-top: 16px; font-size: 13px; color: var(--authon-muted); }
273
328
  .switch-view a { color: var(--authon-primary-start); text-decoration: none; font-weight: 500; }
274
329
  .switch-view a:hover { text-decoration: underline; }
@@ -350,14 +405,15 @@ var ModalRenderer = class {
350
405
  if (backdrop) {
351
406
  backdrop.addEventListener("click", () => this.onClose());
352
407
  }
408
+ if (this.escHandler) {
409
+ document.removeEventListener("keydown", this.escHandler);
410
+ this.escHandler = null;
411
+ }
353
412
  if (this.mode === "popup") {
354
- const handler = (e) => {
355
- if (e.key === "Escape") {
356
- this.onClose();
357
- document.removeEventListener("keydown", handler);
358
- }
413
+ this.escHandler = (e) => {
414
+ if (e.key === "Escape") this.onClose();
359
415
  };
360
- document.addEventListener("keydown", handler);
416
+ document.addEventListener("keydown", this.escHandler);
361
417
  }
362
418
  }
363
419
  };
@@ -549,11 +605,14 @@ var Authon = class {
549
605
  branding: this.branding || void 0,
550
606
  onProviderClick: (provider) => this.startOAuthFlow(provider),
551
607
  onEmailSubmit: (email, password, isSignUp) => {
552
- if (isSignUp) {
553
- this.signUpWithEmail(email, password).then(() => this.modal?.close());
554
- } else {
555
- this.signInWithEmail(email, password).then(() => this.modal?.close());
556
- }
608
+ this.modal?.clearError();
609
+ const promise = isSignUp ? this.signUpWithEmail(email, password) : this.signInWithEmail(email, password);
610
+ promise.then(() => this.modal?.close()).catch((err) => {
611
+ const msg = err instanceof Error ? err.message : String(err);
612
+ const friendlyMsg = msg.includes("401") ? "Invalid email or password" : msg.includes("409") ? "Email already in use" : msg.includes("400") ? "Please check your input" : msg || "Authentication failed";
613
+ this.modal?.showError(friendlyMsg);
614
+ this.emit("error", err instanceof Error ? err : new Error(msg));
615
+ });
557
616
  },
558
617
  onClose: () => this.modal?.close()
559
618
  });
@@ -578,39 +637,51 @@ var Authon = class {
578
637
  "authon-oauth",
579
638
  `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no`
580
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
+ }
581
649
  let callbackReceived = false;
582
650
  let cleaned = false;
583
651
  const cleanup = () => {
584
652
  if (cleaned) return;
585
653
  cleaned = true;
586
654
  window.removeEventListener("message", handler);
587
- window.removeEventListener("focus", focusHandler);
655
+ if (pollTimer) clearInterval(pollTimer);
588
656
  if (maxTimer) clearTimeout(maxTimer);
589
657
  };
590
- const focusHandler = () => {
658
+ const pollTimer = setInterval(() => {
591
659
  if (callbackReceived || cleaned) return;
592
- setTimeout(() => {
593
- if (callbackReceived || cleaned) return;
594
- cleanup();
595
- this.modal?.hideLoading();
596
- }, 1500);
597
- };
598
- 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);
599
672
  const maxTimer = setTimeout(() => {
600
673
  if (callbackReceived || cleaned) return;
601
674
  cleanup();
602
675
  this.modal?.hideLoading();
603
676
  }, 18e4);
604
- if (!popup) {
605
- cleanup();
606
- this.modal?.hideLoading();
607
- this.emit("error", new Error("Popup was blocked by the browser"));
608
- return;
609
- }
610
677
  const handler = async (e) => {
611
678
  if (e.data?.type !== "authon-oauth-callback") return;
612
679
  callbackReceived = true;
613
680
  cleanup();
681
+ try {
682
+ if (!popup.closed) popup.close();
683
+ } catch {
684
+ }
614
685
  try {
615
686
  const tokens = await this.apiPost("/v1/auth/oauth/callback", {
616
687
  code: e.data.code,
@@ -622,7 +693,9 @@ var Authon = class {
622
693
  this.emit("signedIn", tokens.user);
623
694
  } catch (err) {
624
695
  this.modal?.hideLoading();
625
- 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));
626
699
  }
627
700
  };
628
701
  window.addEventListener("message", handler);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authon/js",
3
- "version": "0.1.9",
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",