@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.cjs CHANGED
@@ -129,6 +129,7 @@ var ModalRenderer = class {
129
129
  shadowRoot = null;
130
130
  hostElement = null;
131
131
  containerElement = null;
132
+ containerId = null;
132
133
  mode;
133
134
  theme;
134
135
  branding;
@@ -154,11 +155,16 @@ var ModalRenderer = class {
154
155
  turnstileWidgetId = null;
155
156
  turnstileToken = "";
156
157
  turnstileWrapper = null;
158
+ // Dev Teleport (test mode)
159
+ isTestMode = false;
160
+ onDevTeleport = null;
157
161
  constructor(options) {
158
162
  this.mode = options.mode;
159
163
  this.theme = options.theme || "auto";
160
164
  this.branding = { ...DEFAULT_BRANDING, ...options.branding };
161
165
  this.captchaSiteKey = options.captchaSiteKey || "";
166
+ this.isTestMode = options.isTestMode || false;
167
+ this.onDevTeleport = options.onDevTeleport || null;
162
168
  this.onProviderClick = options.onProviderClick;
163
169
  this.onEmailSubmit = options.onEmailSubmit;
164
170
  this.onClose = options.onClose;
@@ -171,9 +177,20 @@ var ModalRenderer = class {
171
177
  this.onPasskeyClick = options.onPasskeyClick || (() => {
172
178
  });
173
179
  if (options.mode === "embedded" && options.containerId) {
174
- this.containerElement = document.getElementById(options.containerId);
180
+ this.containerId = options.containerId;
175
181
  }
176
182
  }
183
+ resolveContainerElement() {
184
+ if (this.mode !== "embedded" || !this.containerId) return null;
185
+ const next = document.getElementById(this.containerId);
186
+ if (this.containerElement !== next) {
187
+ this.hostElement?.remove();
188
+ this.hostElement = null;
189
+ this.shadowRoot = null;
190
+ }
191
+ this.containerElement = next;
192
+ return next;
193
+ }
177
194
  setProviders(providers) {
178
195
  this.enabledProviders = providers;
179
196
  }
@@ -181,6 +198,11 @@ var ModalRenderer = class {
181
198
  this.branding = { ...DEFAULT_BRANDING, ...branding };
182
199
  }
183
200
  open(view = "signIn") {
201
+ this.resolveContainerElement();
202
+ if (this.hostElement && !this.hostElement.isConnected) {
203
+ this.hostElement = null;
204
+ this.shadowRoot = null;
205
+ }
184
206
  if (this.shadowRoot && this.hostElement) {
185
207
  this.hideOverlay();
186
208
  this.switchView(view);
@@ -210,8 +232,9 @@ var ModalRenderer = class {
210
232
  this.hostElement = null;
211
233
  this.shadowRoot = null;
212
234
  }
213
- if (this.containerElement) {
214
- this.containerElement.innerHTML = "";
235
+ const liveContainer = this.resolveContainerElement();
236
+ if (liveContainer) {
237
+ liveContainer.replaceChildren();
215
238
  }
216
239
  this.currentOverlay = "none";
217
240
  }
@@ -377,8 +400,14 @@ var ModalRenderer = class {
377
400
  this.hostElement = host;
378
401
  if (this.mode === "popup") {
379
402
  document.body.appendChild(host);
380
- } else if (this.containerElement) {
381
- this.containerElement.appendChild(host);
403
+ } else {
404
+ const container = this.resolveContainerElement();
405
+ if (!container) {
406
+ this.hostElement = null;
407
+ throw new Error(`Authon container "#${this.containerId}" not found`);
408
+ }
409
+ container.replaceChildren();
410
+ container.appendChild(host);
382
411
  }
383
412
  this.shadowRoot = host.attachShadow({ mode: "open" });
384
413
  this.shadowRoot.innerHTML = this.buildShell(view);
@@ -479,6 +508,16 @@ var ModalRenderer = class {
479
508
  ${authMethods}
480
509
  <p class="switch-view">${subtitle} <a href="#" id="switch-link">${subtitleLink}</a></p>
481
510
  ${footer}
511
+ ${this.isTestMode ? `<div class="dev-teleport">
512
+ <div class="dev-teleport-label">
513
+ <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>
514
+ Dev Teleport
515
+ </div>
516
+ <div class="dev-teleport-row">
517
+ <input type="email" placeholder="test@example.com" id="dev-teleport-email" class="dev-teleport-input" value="dev@test.com" />
518
+ <button type="button" id="dev-teleport-btn" class="dev-teleport-btn">Go</button>
519
+ </div>
520
+ </div>` : ""}
482
521
  ${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>` : ""}
483
522
  `;
484
523
  }
@@ -655,6 +694,34 @@ var ModalRenderer = class {
655
694
  .secured-link { font-weight: 600; color: var(--authon-muted); text-decoration: none; }
656
695
  .secured-link:hover { text-decoration: underline; }
657
696
 
697
+ /* Dev Teleport */
698
+ .dev-teleport {
699
+ margin-top: 12px; padding: 10px;
700
+ border-radius: calc(var(--authon-radius) * 0.5);
701
+ background: rgba(251,191,36,0.06);
702
+ border: 1px dashed rgba(251,191,36,0.25);
703
+ }
704
+ .dev-teleport-label {
705
+ display: flex; align-items: center; gap: 4px;
706
+ font-size: 10px; font-weight: 600; color: #fbbf24;
707
+ margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;
708
+ }
709
+ .dev-teleport-row { display: flex; gap: 6px; }
710
+ .dev-teleport-input {
711
+ flex: 1; padding: 6px 10px; font-size: 12px;
712
+ border-radius: calc(var(--authon-radius) * 0.4);
713
+ background: rgba(0,0,0,0.2); border: 1px solid rgba(251,191,36,0.2);
714
+ color: #fbbf24; outline: none; font-family: ui-monospace, monospace;
715
+ }
716
+ .dev-teleport-input:focus { border-color: rgba(251,191,36,0.5); }
717
+ .dev-teleport-btn {
718
+ padding: 6px 14px; font-size: 11px; font-weight: 700;
719
+ border-radius: calc(var(--authon-radius) * 0.4);
720
+ background: rgba(251,191,36,0.15); border: 1px solid rgba(251,191,36,0.3);
721
+ color: #fbbf24; cursor: pointer;
722
+ }
723
+ .dev-teleport-btn:hover { background: rgba(251,191,36,0.25); }
724
+
658
725
  /* Auth method buttons */
659
726
  .auth-methods { display: flex; flex-direction: column; gap: 8px; }
660
727
  .auth-method-btn {
@@ -1100,6 +1167,21 @@ var ModalRenderer = class {
1100
1167
  });
1101
1168
  }
1102
1169
  this.renderTurnstile();
1170
+ const devTeleportBtn = this.shadowRoot.getElementById("dev-teleport-btn");
1171
+ const devTeleportEmail = this.shadowRoot.getElementById("dev-teleport-email");
1172
+ if (devTeleportBtn && devTeleportEmail && this.onDevTeleport) {
1173
+ const handler = this.onDevTeleport;
1174
+ devTeleportBtn.addEventListener("click", () => {
1175
+ const email = devTeleportEmail.value.trim();
1176
+ if (email) handler(email);
1177
+ });
1178
+ devTeleportEmail.addEventListener("keydown", (e) => {
1179
+ if (e.key === "Enter") {
1180
+ const email = devTeleportEmail.value.trim();
1181
+ if (email) handler(email);
1182
+ }
1183
+ });
1184
+ }
1103
1185
  const backBtn = this.shadowRoot.getElementById("back-btn");
1104
1186
  if (backBtn) {
1105
1187
  backBtn.addEventListener("click", () => {
@@ -1136,9 +1218,42 @@ var SessionManager = class {
1136
1218
  refreshTimer = null;
1137
1219
  apiUrl;
1138
1220
  publishableKey;
1221
+ storageKey;
1139
1222
  constructor(publishableKey, apiUrl) {
1140
1223
  this.publishableKey = publishableKey;
1141
1224
  this.apiUrl = apiUrl;
1225
+ this.storageKey = `authon_session_${publishableKey.slice(0, 16)}`;
1226
+ this.restoreFromStorage();
1227
+ }
1228
+ restoreFromStorage() {
1229
+ if (typeof window === "undefined") return;
1230
+ try {
1231
+ const stored = localStorage.getItem(this.storageKey);
1232
+ if (!stored) return;
1233
+ const data = JSON.parse(stored);
1234
+ if (data.accessToken && data.refreshToken && data.user) {
1235
+ this.accessToken = data.accessToken;
1236
+ this.refreshToken = data.refreshToken;
1237
+ this.user = data.user;
1238
+ this.scheduleRefresh(5);
1239
+ }
1240
+ } catch {
1241
+ }
1242
+ }
1243
+ persistToStorage() {
1244
+ if (typeof window === "undefined") return;
1245
+ try {
1246
+ if (this.accessToken && this.refreshToken && this.user) {
1247
+ localStorage.setItem(this.storageKey, JSON.stringify({
1248
+ accessToken: this.accessToken,
1249
+ refreshToken: this.refreshToken,
1250
+ user: this.user
1251
+ }));
1252
+ } else {
1253
+ localStorage.removeItem(this.storageKey);
1254
+ }
1255
+ } catch {
1256
+ }
1142
1257
  }
1143
1258
  getToken() {
1144
1259
  return this.accessToken;
@@ -1150,6 +1265,7 @@ var SessionManager = class {
1150
1265
  this.accessToken = tokens.accessToken;
1151
1266
  this.refreshToken = tokens.refreshToken;
1152
1267
  this.user = tokens.user;
1268
+ this.persistToStorage();
1153
1269
  if (tokens.expiresIn && tokens.expiresIn > 0) {
1154
1270
  this.scheduleRefresh(tokens.expiresIn);
1155
1271
  }
@@ -1161,6 +1277,7 @@ var SessionManager = class {
1161
1277
  this.accessToken = null;
1162
1278
  this.refreshToken = null;
1163
1279
  this.user = null;
1280
+ this.persistToStorage();
1164
1281
  if (this.refreshTimer) {
1165
1282
  clearTimeout(this.refreshTimer);
1166
1283
  this.refreshTimer = null;
@@ -1956,6 +2073,18 @@ var Authon = class {
1956
2073
  await this.apiPostAuth(`/v1/auth/organizations/${orgId}/leave`, void 0, token);
1957
2074
  }
1958
2075
  };
2076
+ /** Testing utilities — only available when initialized with a pk_test_ key */
2077
+ get testing() {
2078
+ if (!this.publishableKey.startsWith("pk_test_")) return void 0;
2079
+ return {
2080
+ signIn: async (params) => {
2081
+ const res = await this.apiPost("/v1/auth/testing/token", params);
2082
+ this.session.setSession(res);
2083
+ this.emit("signedIn", res.user);
2084
+ return res.user;
2085
+ }
2086
+ };
2087
+ }
1959
2088
  destroy() {
1960
2089
  this.modal?.close();
1961
2090
  this.session.destroy();
@@ -2007,6 +2136,17 @@ var Authon = class {
2007
2136
  containerId: this.config.containerId,
2008
2137
  branding: this.branding || void 0,
2009
2138
  captchaSiteKey: this.captchaEnabled ? this.turnstileSiteKey : void 0,
2139
+ isTestMode: this.publishableKey.startsWith("pk_test_"),
2140
+ onDevTeleport: this.publishableKey.startsWith("pk_test_") ? async (email) => {
2141
+ this.modal?.clearError();
2142
+ try {
2143
+ await this.testing.signIn({ email });
2144
+ this.modal?.close();
2145
+ } catch (err) {
2146
+ const msg = err instanceof Error ? err.message : String(err);
2147
+ this.modal?.showError(msg || "Dev teleport failed");
2148
+ }
2149
+ } : void 0,
2010
2150
  onProviderClick: (provider) => this.startOAuthFlow(provider),
2011
2151
  onEmailSubmit: (email, password, isSignUp) => {
2012
2152
  this.modal?.clearError();