lemma-sdk 0.2.21 → 0.2.23

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/auth.d.ts CHANGED
@@ -102,6 +102,14 @@ export declare class AuthManager {
102
102
  private setState;
103
103
  private assertBrowserContext;
104
104
  private getCookie;
105
+ private getCookieDomainCandidates;
106
+ private expireCookie;
107
+ /**
108
+ * Defensive cleanup for stale SuperTokens frontend marker cookies/storage.
109
+ * This helps recover when signout/session-expiry paths leave local markers behind.
110
+ */
111
+ private clearFrontendSessionMarkers;
112
+ private applyUnauthenticatedState;
105
113
  private clearInjectedToken;
106
114
  private rawSignOutViaBackend;
107
115
  /**
package/dist/auth.js CHANGED
@@ -18,6 +18,14 @@
18
18
  import Session from "supertokens-web-js/recipe/session";
19
19
  import { ensureCookieSessionSupport } from "./supertokens.js";
20
20
  const DEFAULT_BLOCKED_REDIRECT_PATHS = ["/login", "/signup", "/auth"];
21
+ const SUPERTOKENS_FRONTEND_MARKER_KEYS = [
22
+ "sFrontToken",
23
+ "st-last-access-token-update",
24
+ "sIRTFrontend",
25
+ "sAntiCsrf",
26
+ "st-access-token",
27
+ "st-refresh-token",
28
+ ];
21
29
  const LOCALSTORAGE_TOKEN_KEY = "lemma_token";
22
30
  function readStorageToken() {
23
31
  if (typeof window === "undefined")
@@ -214,6 +222,68 @@ export class AuthManager {
214
222
  const match = document.cookie.match(new RegExp(`(?:^|; )${escaped}=([^;]*)`));
215
223
  return match ? decodeURIComponent(match[1]) : undefined;
216
224
  }
225
+ getCookieDomainCandidates() {
226
+ if (typeof window === "undefined") {
227
+ return [undefined];
228
+ }
229
+ const host = window.location.hostname;
230
+ const isIpv4 = /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host);
231
+ const isIpv6 = host.includes(":");
232
+ if (!host || host === "localhost" || isIpv4 || isIpv6) {
233
+ return [undefined];
234
+ }
235
+ const domains = new Set();
236
+ const parts = host.split(".").filter(Boolean);
237
+ for (let i = 0; i < parts.length - 1; i += 1) {
238
+ const candidate = parts.slice(i).join(".");
239
+ if (!candidate)
240
+ continue;
241
+ domains.add(candidate);
242
+ domains.add(`.${candidate}`);
243
+ }
244
+ return [undefined, ...domains];
245
+ }
246
+ expireCookie(name, domain) {
247
+ if (typeof document === "undefined")
248
+ return;
249
+ const domainPart = domain ? `;domain=${domain}` : "";
250
+ document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/${domainPart};samesite=lax`;
251
+ document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/${domainPart}`;
252
+ }
253
+ /**
254
+ * Defensive cleanup for stale SuperTokens frontend marker cookies/storage.
255
+ * This helps recover when signout/session-expiry paths leave local markers behind.
256
+ */
257
+ clearFrontendSessionMarkers() {
258
+ if (typeof window === "undefined")
259
+ return;
260
+ for (const key of SUPERTOKENS_FRONTEND_MARKER_KEYS) {
261
+ try {
262
+ window.localStorage.removeItem(key);
263
+ }
264
+ catch {
265
+ // ignore storage errors
266
+ }
267
+ try {
268
+ window.sessionStorage.removeItem(key);
269
+ }
270
+ catch {
271
+ // ignore storage errors
272
+ }
273
+ }
274
+ const domains = this.getCookieDomainCandidates();
275
+ for (const key of SUPERTOKENS_FRONTEND_MARKER_KEYS) {
276
+ for (const domain of domains) {
277
+ this.expireCookie(key, domain);
278
+ }
279
+ }
280
+ }
281
+ applyUnauthenticatedState() {
282
+ this.clearFrontendSessionMarkers();
283
+ const next = { status: "unauthenticated", user: null };
284
+ this.setState(next);
285
+ return next;
286
+ }
217
287
  clearInjectedToken() {
218
288
  this.injectedToken = null;
219
289
  clearTestingToken();
@@ -322,15 +392,11 @@ export class AuthManager {
322
392
  const response = await fetch(`${this.apiUrl}/users/me`, this.getRequestInit({ method: "GET" }));
323
393
  // Only 401 means not authenticated — 403 means authenticated but forbidden
324
394
  if (response.status === 401) {
325
- const next = { status: "unauthenticated", user: null };
326
- this.setState(next);
327
- return next;
395
+ return this.applyUnauthenticatedState();
328
396
  }
329
397
  if (!response.ok) {
330
398
  // For non-401 errors on /users/me, treat as unauthenticated (conservative)
331
- const next = { status: "unauthenticated", user: null };
332
- this.setState(next);
333
- return next;
399
+ return this.applyUnauthenticatedState();
334
400
  }
335
401
  const user = (await response.json());
336
402
  const next = { status: "authenticated", user };
@@ -338,9 +404,7 @@ export class AuthManager {
338
404
  return next;
339
405
  }
340
406
  catch {
341
- const next = { status: "unauthenticated", user: null };
342
- this.setState(next);
343
- return next;
407
+ return this.applyUnauthenticatedState();
344
408
  }
345
409
  }
346
410
  /**
@@ -348,7 +412,7 @@ export class AuthManager {
348
412
  * Does NOT redirect — call redirectToAuth() explicitly if desired.
349
413
  */
350
414
  markUnauthenticated() {
351
- this.setState({ status: "unauthenticated", user: null });
415
+ this.applyUnauthenticatedState();
352
416
  }
353
417
  /**
354
418
  * Sign out the current user session.
@@ -376,6 +440,9 @@ export class AuthManager {
376
440
  // best effort fallback only
377
441
  }
378
442
  }
443
+ // Always clear frontend markers on logout attempt, even if backend session
444
+ // cleanup is partial. This avoids stale local "EXISTS" signals.
445
+ this.clearFrontendSessionMarkers();
379
446
  const isAuthenticated = await this.isAuthenticatedViaCookie();
380
447
  if (!isAuthenticated) {
381
448
  this.markUnauthenticated();
@@ -204,6 +204,14 @@ exports.resolveSafeRedirectUri = resolveSafeRedirectUri;
204
204
  const session_1 = require("supertokens-web-js/recipe/session");
205
205
  const supertokens_js_1 = require("./supertokens.js");
206
206
  const DEFAULT_BLOCKED_REDIRECT_PATHS = ["/login", "/signup", "/auth"];
207
+ const SUPERTOKENS_FRONTEND_MARKER_KEYS = [
208
+ "sFrontToken",
209
+ "st-last-access-token-update",
210
+ "sIRTFrontend",
211
+ "sAntiCsrf",
212
+ "st-access-token",
213
+ "st-refresh-token",
214
+ ];
207
215
  const LOCALSTORAGE_TOKEN_KEY = "lemma_token";
208
216
  function readStorageToken() {
209
217
  if (typeof window === "undefined")
@@ -397,6 +405,68 @@ class AuthManager {
397
405
  const match = document.cookie.match(new RegExp(`(?:^|; )${escaped}=([^;]*)`));
398
406
  return match ? decodeURIComponent(match[1]) : undefined;
399
407
  }
408
+ getCookieDomainCandidates() {
409
+ if (typeof window === "undefined") {
410
+ return [undefined];
411
+ }
412
+ const host = window.location.hostname;
413
+ const isIpv4 = /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host);
414
+ const isIpv6 = host.includes(":");
415
+ if (!host || host === "localhost" || isIpv4 || isIpv6) {
416
+ return [undefined];
417
+ }
418
+ const domains = new Set();
419
+ const parts = host.split(".").filter(Boolean);
420
+ for (let i = 0; i < parts.length - 1; i += 1) {
421
+ const candidate = parts.slice(i).join(".");
422
+ if (!candidate)
423
+ continue;
424
+ domains.add(candidate);
425
+ domains.add(`.${candidate}`);
426
+ }
427
+ return [undefined, ...domains];
428
+ }
429
+ expireCookie(name, domain) {
430
+ if (typeof document === "undefined")
431
+ return;
432
+ const domainPart = domain ? `;domain=${domain}` : "";
433
+ document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/${domainPart};samesite=lax`;
434
+ document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/${domainPart}`;
435
+ }
436
+ /**
437
+ * Defensive cleanup for stale SuperTokens frontend marker cookies/storage.
438
+ * This helps recover when signout/session-expiry paths leave local markers behind.
439
+ */
440
+ clearFrontendSessionMarkers() {
441
+ if (typeof window === "undefined")
442
+ return;
443
+ for (const key of SUPERTOKENS_FRONTEND_MARKER_KEYS) {
444
+ try {
445
+ window.localStorage.removeItem(key);
446
+ }
447
+ catch {
448
+ // ignore storage errors
449
+ }
450
+ try {
451
+ window.sessionStorage.removeItem(key);
452
+ }
453
+ catch {
454
+ // ignore storage errors
455
+ }
456
+ }
457
+ const domains = this.getCookieDomainCandidates();
458
+ for (const key of SUPERTOKENS_FRONTEND_MARKER_KEYS) {
459
+ for (const domain of domains) {
460
+ this.expireCookie(key, domain);
461
+ }
462
+ }
463
+ }
464
+ applyUnauthenticatedState() {
465
+ this.clearFrontendSessionMarkers();
466
+ const next = { status: "unauthenticated", user: null };
467
+ this.setState(next);
468
+ return next;
469
+ }
400
470
  clearInjectedToken() {
401
471
  this.injectedToken = null;
402
472
  clearTestingToken();
@@ -505,15 +575,11 @@ class AuthManager {
505
575
  const response = await fetch(`${this.apiUrl}/users/me`, this.getRequestInit({ method: "GET" }));
506
576
  // Only 401 means not authenticated — 403 means authenticated but forbidden
507
577
  if (response.status === 401) {
508
- const next = { status: "unauthenticated", user: null };
509
- this.setState(next);
510
- return next;
578
+ return this.applyUnauthenticatedState();
511
579
  }
512
580
  if (!response.ok) {
513
581
  // For non-401 errors on /users/me, treat as unauthenticated (conservative)
514
- const next = { status: "unauthenticated", user: null };
515
- this.setState(next);
516
- return next;
582
+ return this.applyUnauthenticatedState();
517
583
  }
518
584
  const user = (await response.json());
519
585
  const next = { status: "authenticated", user };
@@ -521,9 +587,7 @@ class AuthManager {
521
587
  return next;
522
588
  }
523
589
  catch {
524
- const next = { status: "unauthenticated", user: null };
525
- this.setState(next);
526
- return next;
590
+ return this.applyUnauthenticatedState();
527
591
  }
528
592
  }
529
593
  /**
@@ -531,7 +595,7 @@ class AuthManager {
531
595
  * Does NOT redirect — call redirectToAuth() explicitly if desired.
532
596
  */
533
597
  markUnauthenticated() {
534
- this.setState({ status: "unauthenticated", user: null });
598
+ this.applyUnauthenticatedState();
535
599
  }
536
600
  /**
537
601
  * Sign out the current user session.
@@ -559,6 +623,9 @@ class AuthManager {
559
623
  // best effort fallback only
560
624
  }
561
625
  }
626
+ // Always clear frontend markers on logout attempt, even if backend session
627
+ // cleanup is partial. This avoids stale local "EXISTS" signals.
628
+ this.clearFrontendSessionMarkers();
562
629
  const isAuthenticated = await this.isAuthenticatedViaCookie();
563
630
  if (!isAuthenticated) {
564
631
  this.markUnauthenticated();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemma-sdk",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "Official TypeScript SDK for Lemma pod-scoped APIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",