@authon/js 0.3.5 → 0.4.1

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.cts CHANGED
@@ -110,6 +110,13 @@ declare class Authon {
110
110
  updateMemberRole: (orgId: string, memberId: string, role: string) => Promise<OrganizationMember>;
111
111
  leave: (orgId: string) => Promise<void>;
112
112
  };
113
+ /** Testing utilities — only available when initialized with a pk_test_ key */
114
+ get testing(): {
115
+ signIn(params: {
116
+ email: string;
117
+ nickname?: string;
118
+ }): Promise<AuthonUser>;
119
+ } | undefined;
113
120
  destroy(): void;
114
121
  private loadTurnstileScript;
115
122
  private emit;
package/dist/index.d.ts CHANGED
@@ -110,6 +110,13 @@ declare class Authon {
110
110
  updateMemberRole: (orgId: string, memberId: string, role: string) => Promise<OrganizationMember>;
111
111
  leave: (orgId: string) => Promise<void>;
112
112
  };
113
+ /** Testing utilities — only available when initialized with a pk_test_ key */
114
+ get testing(): {
115
+ signIn(params: {
116
+ email: string;
117
+ nickname?: string;
118
+ }): Promise<AuthonUser>;
119
+ } | undefined;
113
120
  destroy(): void;
114
121
  private loadTurnstileScript;
115
122
  private emit;
package/dist/index.js CHANGED
@@ -100,6 +100,7 @@ var ModalRenderer = class {
100
100
  shadowRoot = null;
101
101
  hostElement = null;
102
102
  containerElement = null;
103
+ containerId = null;
103
104
  mode;
104
105
  theme;
105
106
  branding;
@@ -125,11 +126,16 @@ var ModalRenderer = class {
125
126
  turnstileWidgetId = null;
126
127
  turnstileToken = "";
127
128
  turnstileWrapper = null;
129
+ // Dev Teleport (test mode)
130
+ isTestMode = false;
131
+ onDevTeleport = null;
128
132
  constructor(options) {
129
133
  this.mode = options.mode;
130
134
  this.theme = options.theme || "auto";
131
135
  this.branding = { ...DEFAULT_BRANDING, ...options.branding };
132
136
  this.captchaSiteKey = options.captchaSiteKey || "";
137
+ this.isTestMode = options.isTestMode || false;
138
+ this.onDevTeleport = options.onDevTeleport || null;
133
139
  this.onProviderClick = options.onProviderClick;
134
140
  this.onEmailSubmit = options.onEmailSubmit;
135
141
  this.onClose = options.onClose;
@@ -142,9 +148,20 @@ var ModalRenderer = class {
142
148
  this.onPasskeyClick = options.onPasskeyClick || (() => {
143
149
  });
144
150
  if (options.mode === "embedded" && options.containerId) {
145
- this.containerElement = document.getElementById(options.containerId);
151
+ this.containerId = options.containerId;
146
152
  }
147
153
  }
154
+ resolveContainerElement() {
155
+ if (this.mode !== "embedded" || !this.containerId) return null;
156
+ const next = document.getElementById(this.containerId);
157
+ if (this.containerElement !== next) {
158
+ this.hostElement?.remove();
159
+ this.hostElement = null;
160
+ this.shadowRoot = null;
161
+ }
162
+ this.containerElement = next;
163
+ return next;
164
+ }
148
165
  setProviders(providers) {
149
166
  this.enabledProviders = providers;
150
167
  }
@@ -152,6 +169,11 @@ var ModalRenderer = class {
152
169
  this.branding = { ...DEFAULT_BRANDING, ...branding };
153
170
  }
154
171
  open(view = "signIn") {
172
+ this.resolveContainerElement();
173
+ if (this.hostElement && !this.hostElement.isConnected) {
174
+ this.hostElement = null;
175
+ this.shadowRoot = null;
176
+ }
155
177
  if (this.shadowRoot && this.hostElement) {
156
178
  this.hideOverlay();
157
179
  this.switchView(view);
@@ -181,8 +203,9 @@ var ModalRenderer = class {
181
203
  this.hostElement = null;
182
204
  this.shadowRoot = null;
183
205
  }
184
- if (this.containerElement) {
185
- this.containerElement.innerHTML = "";
206
+ const liveContainer = this.resolveContainerElement();
207
+ if (liveContainer) {
208
+ liveContainer.replaceChildren();
186
209
  }
187
210
  this.currentOverlay = "none";
188
211
  }
@@ -348,8 +371,14 @@ var ModalRenderer = class {
348
371
  this.hostElement = host;
349
372
  if (this.mode === "popup") {
350
373
  document.body.appendChild(host);
351
- } else if (this.containerElement) {
352
- this.containerElement.appendChild(host);
374
+ } else {
375
+ const container = this.resolveContainerElement();
376
+ if (!container) {
377
+ this.hostElement = null;
378
+ throw new Error(`Authon container "#${this.containerId}" not found`);
379
+ }
380
+ container.replaceChildren();
381
+ container.appendChild(host);
353
382
  }
354
383
  this.shadowRoot = host.attachShadow({ mode: "open" });
355
384
  this.shadowRoot.innerHTML = this.buildShell(view);
@@ -450,6 +479,16 @@ var ModalRenderer = class {
450
479
  ${authMethods}
451
480
  <p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
452
481
  ${footer}
482
+ ${this.isTestMode ? `<div class="dev-teleport">
483
+ <div class="dev-teleport-label">
484
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
485
+ Dev Teleport
486
+ </div>
487
+ <div class="dev-teleport-row">
488
+ <input type="email" placeholder="test@example.com" id="dev-teleport-email" class="dev-teleport-input" value="dev@test.com" />
489
+ <button type="button" id="dev-teleport-btn" class="dev-teleport-btn">Go</button>
490
+ </div>
491
+ </div>` : ""}
453
492
  ${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>` : ""}
454
493
  `;
455
494
  }
@@ -626,6 +665,34 @@ var ModalRenderer = class {
626
665
  .secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
627
666
  .secured-link:hover { text-decoration: underline; }
628
667
 
668
+ /* Dev Teleport */
669
+ .dev-teleport {
670
+ margin-top: 12px; padding: 10px;
671
+ border-radius: calc(var(--authon-radius) * 0.5);
672
+ background: rgba(251,191,36,0.06);
673
+ border: 1px dashed rgba(251,191,36,0.25);
674
+ }
675
+ .dev-teleport-label {
676
+ display: flex; align-items: center; gap: 4px;
677
+ font-size: 10px; font-weight: 600; color: #fbbf24;
678
+ margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;
679
+ }
680
+ .dev-teleport-row { display: flex; gap: 6px; }
681
+ .dev-teleport-input {
682
+ flex: 1; padding: 6px 10px; font-size: 12px;
683
+ border-radius: calc(var(--authon-radius) * 0.4);
684
+ background: rgba(0,0,0,0.2); border: 1px solid rgba(251,191,36,0.2);
685
+ color: #fbbf24; outline: none; font-family: ui-monospace, monospace;
686
+ }
687
+ .dev-teleport-input:focus { border-color: rgba(251,191,36,0.5); }
688
+ .dev-teleport-btn {
689
+ padding: 6px 14px; font-size: 11px; font-weight: 700;
690
+ border-radius: calc(var(--authon-radius) * 0.4);
691
+ background: rgba(251,191,36,0.15); border: 1px solid rgba(251,191,36,0.3);
692
+ color: #fbbf24; cursor: pointer;
693
+ }
694
+ .dev-teleport-btn:hover { background: rgba(251,191,36,0.25); }
695
+
629
696
  /* Auth method buttons */
630
697
  .auth-methods { display: flex; flex-direction: column; gap: 8px; }
631
698
  .auth-method-btn {
@@ -1071,6 +1138,21 @@ var ModalRenderer = class {
1071
1138
  });
1072
1139
  }
1073
1140
  this.renderTurnstile();
1141
+ const devTeleportBtn = this.shadowRoot.getElementById("dev-teleport-btn");
1142
+ const devTeleportEmail = this.shadowRoot.getElementById("dev-teleport-email");
1143
+ if (devTeleportBtn && devTeleportEmail && this.onDevTeleport) {
1144
+ const handler = this.onDevTeleport;
1145
+ devTeleportBtn.addEventListener("click", () => {
1146
+ const email = devTeleportEmail.value.trim();
1147
+ if (email) handler(email);
1148
+ });
1149
+ devTeleportEmail.addEventListener("keydown", (e) => {
1150
+ if (e.key === "Enter") {
1151
+ const email = devTeleportEmail.value.trim();
1152
+ if (email) handler(email);
1153
+ }
1154
+ });
1155
+ }
1074
1156
  const backBtn = this.shadowRoot.getElementById("back-btn");
1075
1157
  if (backBtn) {
1076
1158
  backBtn.addEventListener("click", () => {
@@ -1107,9 +1189,42 @@ var SessionManager = class {
1107
1189
  refreshTimer = null;
1108
1190
  apiUrl;
1109
1191
  publishableKey;
1192
+ storageKey;
1110
1193
  constructor(publishableKey, apiUrl) {
1111
1194
  this.publishableKey = publishableKey;
1112
1195
  this.apiUrl = apiUrl;
1196
+ this.storageKey = `authon_session_${publishableKey.slice(0, 16)}`;
1197
+ this.restoreFromStorage();
1198
+ }
1199
+ restoreFromStorage() {
1200
+ if (typeof window === "undefined") return;
1201
+ try {
1202
+ const stored = localStorage.getItem(this.storageKey);
1203
+ if (!stored) return;
1204
+ const data = JSON.parse(stored);
1205
+ if (data.accessToken && data.refreshToken && data.user) {
1206
+ this.accessToken = data.accessToken;
1207
+ this.refreshToken = data.refreshToken;
1208
+ this.user = data.user;
1209
+ this.scheduleRefresh(5);
1210
+ }
1211
+ } catch {
1212
+ }
1213
+ }
1214
+ persistToStorage() {
1215
+ if (typeof window === "undefined") return;
1216
+ try {
1217
+ if (this.accessToken && this.refreshToken && this.user) {
1218
+ localStorage.setItem(this.storageKey, JSON.stringify({
1219
+ accessToken: this.accessToken,
1220
+ refreshToken: this.refreshToken,
1221
+ user: this.user
1222
+ }));
1223
+ } else {
1224
+ localStorage.removeItem(this.storageKey);
1225
+ }
1226
+ } catch {
1227
+ }
1113
1228
  }
1114
1229
  getToken() {
1115
1230
  return this.accessToken;
@@ -1121,6 +1236,7 @@ var SessionManager = class {
1121
1236
  this.accessToken = tokens.accessToken;
1122
1237
  this.refreshToken = tokens.refreshToken;
1123
1238
  this.user = tokens.user;
1239
+ this.persistToStorage();
1124
1240
  if (tokens.expiresIn && tokens.expiresIn > 0) {
1125
1241
  this.scheduleRefresh(tokens.expiresIn);
1126
1242
  }
@@ -1132,6 +1248,7 @@ var SessionManager = class {
1132
1248
  this.accessToken = null;
1133
1249
  this.refreshToken = null;
1134
1250
  this.user = null;
1251
+ this.persistToStorage();
1135
1252
  if (this.refreshTimer) {
1136
1253
  clearTimeout(this.refreshTimer);
1137
1254
  this.refreshTimer = null;
@@ -1927,6 +2044,18 @@ var Authon = class {
1927
2044
  await this.apiPostAuth(`/v1/auth/organizations/${orgId}/leave`, void 0, token);
1928
2045
  }
1929
2046
  };
2047
+ /** Testing utilities — only available when initialized with a pk_test_ key */
2048
+ get testing() {
2049
+ if (!this.publishableKey.startsWith("pk_test_")) return void 0;
2050
+ return {
2051
+ signIn: async (params) => {
2052
+ const res = await this.apiPost("/v1/auth/testing/token", params);
2053
+ this.session.setSession(res);
2054
+ this.emit("signedIn", res.user);
2055
+ return res.user;
2056
+ }
2057
+ };
2058
+ }
1930
2059
  destroy() {
1931
2060
  this.modal?.close();
1932
2061
  this.session.destroy();
@@ -1978,6 +2107,17 @@ var Authon = class {
1978
2107
  containerId: this.config.containerId,
1979
2108
  branding: this.branding || void 0,
1980
2109
  captchaSiteKey: this.captchaEnabled ? this.turnstileSiteKey : void 0,
2110
+ isTestMode: this.publishableKey.startsWith("pk_test_"),
2111
+ onDevTeleport: this.publishableKey.startsWith("pk_test_") ? async (email) => {
2112
+ this.modal?.clearError();
2113
+ try {
2114
+ await this.testing.signIn({ email });
2115
+ this.modal?.close();
2116
+ } catch (err) {
2117
+ const msg = err instanceof Error ? err.message : String(err);
2118
+ this.modal?.showError(msg || "Dev teleport failed");
2119
+ }
2120
+ } : void 0,
1981
2121
  onProviderClick: (provider) => this.startOAuthFlow(provider),
1982
2122
  onEmailSubmit: (email, password, isSignUp) => {
1983
2123
  this.modal?.clearError();