@geolonia/geonicdb-sdk 0.10.0 → 0.11.0

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/geonicdb.iife.js CHANGED
@@ -477,11 +477,12 @@ var GeonicDBModule = (() => {
477
477
  __publicField(this, "_dpopReady", null);
478
478
  __publicField(this, "_dpopNonce", null);
479
479
  /**
480
- * #1230: when true, every DPoP token mutation (login bind, refresh) is
481
- * mirrored to IndexedDB so the session survives a page reload. Toggled
482
- * by `login({ dpopPersist: true })` and `restoreSession()`.
480
+ * When true, every DPoP token mutation (login bind, refresh) is mirrored to
481
+ * IndexedDB so the session survives a page reload (#1230). Toggled by
482
+ * `login(email, password, { rememberSession: true })` and
483
+ * `restoreSession()` (#1244).
483
484
  */
484
- __publicField(this, "_dpopPersist", false);
485
+ __publicField(this, "_rememberSession", false);
485
486
  /** Email captured at login, persisted for UI display. */
486
487
  __publicField(this, "_email", null);
487
488
  __publicField(this, "_tokenPromise", null);
@@ -546,22 +547,22 @@ var GeonicDBModule = (() => {
546
547
  this._emitCacheEvent?.(name, payload);
547
548
  }
548
549
  /**
549
- * Login with email and password (Bearer JWT).
550
+ * Login with email and password.
550
551
  *
551
- * Pass `{ dpop: true }` to immediately exchange the Bearer session for a
552
- * DPoP sender-constrained session via `/auth/dpop-bind` (RFC 9449). The
553
- * returned tokens become unusable without this SDK instance's
554
- * non-extractable private key, so a token leaked from storage (e.g.
555
- * localStorage XSS) cannot be replayed from another origin.
552
+ * #1244: DPoP (RFC 9449) is transparent and always-on — there is no `dpop`
553
+ * option to set. After the Bearer login succeeds, the SDK automatically
554
+ * exchanges it for a DPoP sender-constrained session so a token leaked from
555
+ * storage (e.g. localStorage XSS) cannot be replayed from another origin.
556
+ *
557
+ * When the runtime lacks Web Crypto (`crypto.subtle.generateKey`, e.g. a
558
+ * non-HTTPS context), DPoP cannot be used and the SDK silently keeps the
559
+ * Bearer session — login still succeeds. Callers never deal with DPoP
560
+ * directly; pass `{ rememberSession: true }` only to keep the user signed in
561
+ * across reloads.
556
562
  */
557
563
  async login(email, password, options) {
558
564
  this._log("login", email);
559
- const wantsDpopPersist = options?.dpopPersist === true;
560
- if (wantsDpopPersist && !options?.dpop) {
561
- throw new AuthenticationError(
562
- "dpopPersist: true requires dpop: true. Persisting Bearer tokens is the caller's responsibility (localStorage / sessionStorage)."
563
- );
564
- }
565
+ const wantsRemember = options?.rememberSession === true;
565
566
  const headers = {
566
567
  "Content-Type": "application/json"
567
568
  };
@@ -584,14 +585,30 @@ var GeonicDBModule = (() => {
584
585
  this._tokenType = "Bearer";
585
586
  this._refreshToken = data.refreshToken;
586
587
  this._email = email;
587
- this._dpopPersist = wantsDpopPersist;
588
+ this._rememberSession = wantsRemember;
588
589
  this._invalidateAuthScopedState();
589
- if (options?.dpop) {
590
- const bound = await this._bindDpopSession();
591
- if (this._dpopPersist) {
592
- await this._persistDpopSession();
590
+ if (!wantsRemember && this._tenant) {
591
+ void clearDPoPSession(this._tenant);
592
+ }
593
+ if (this._dpopReady) await this._dpopReady;
594
+ if (this._dpopKeyPair) {
595
+ try {
596
+ const bound = await this._bindDpopSession();
597
+ if (this._rememberSession) {
598
+ await this._persistDpopSession();
599
+ }
600
+ return bound;
601
+ } catch (err) {
602
+ this._token = null;
603
+ this._tokenExpiry = 0;
604
+ this._tokenType = "Bearer";
605
+ this._refreshToken = null;
606
+ this._tokenPromise = null;
607
+ this._rememberSession = false;
608
+ this._email = null;
609
+ this._invalidateAuthScopedState();
610
+ throw err;
593
611
  }
594
- return bound;
595
612
  }
596
613
  return data;
597
614
  }
@@ -685,18 +702,19 @@ var GeonicDBModule = (() => {
685
702
  this._refreshToken = null;
686
703
  this._tokenPromise = null;
687
704
  this._email = null;
688
- if (this._dpopPersist && this._tenant) {
705
+ if (this._tenant) {
689
706
  void clearDPoPSession(this._tenant);
690
707
  }
691
- this._dpopPersist = false;
708
+ this._rememberSession = false;
692
709
  this._anonymous = this._initialAnonymous;
693
710
  this._invalidateAuthScopedState();
694
711
  }
695
712
  /**
696
- * Restore a DPoP session previously persisted via `login({ dpop: true,
697
- * dpopPersist: true })`. Returns true if a usable session was found and
698
- * loaded into the SDK, false otherwise (no entry, IndexedDB unavailable,
699
- * or the entry was for a different tenant).
713
+ * Restore a session previously persisted via
714
+ * `login(email, password, { rememberSession: true })`. Returns true if a
715
+ * usable session was found and loaded into the
716
+ * SDK, false otherwise (no entry, IndexedDB unavailable, or the entry was
717
+ * for a different tenant).
700
718
  *
701
719
  * After a successful restore the SDK behaves as if `login()` had just
702
720
  * succeeded: tokens are set, the DPoP key pair is rehydrated as a
@@ -724,19 +742,19 @@ var GeonicDBModule = (() => {
724
742
  this._dpopKeyPair = persisted.keyPair;
725
743
  this._dpopReady = Promise.resolve();
726
744
  this._dpopNonce = null;
727
- this._dpopPersist = true;
745
+ this._rememberSession = true;
728
746
  this._email = persisted.email ?? null;
729
747
  this._tokenPromise = null;
730
748
  this._invalidateAuthScopedState();
731
749
  return true;
732
750
  }
733
751
  /**
734
- * Whether this AuthManager currently has DPoP persistence enabled.
735
- * Exposed for the `GeonicDB` wrapper to advertise behavior; internal
736
- * consumers should read `_dpopPersist` directly.
752
+ * Whether this AuthManager currently keeps the session persisted across
753
+ * reloads (`rememberSession`). Exposed for the `GeonicDB` wrapper; internal
754
+ * consumers should read `_rememberSession` directly.
737
755
  */
738
- isDpopPersisted() {
739
- return this._dpopPersist;
756
+ isSessionRemembered() {
757
+ return this._rememberSession;
740
758
  }
741
759
  /**
742
760
  * Persist the current DPoP session to IndexedDB. Best-effort: a failed
@@ -746,7 +764,7 @@ var GeonicDBModule = (() => {
746
764
  * @internal
747
765
  */
748
766
  async _persistDpopSession() {
749
- if (!this._dpopPersist || !this._tenant || !this._dpopKeyPair || !this._token || !this._refreshToken) {
767
+ if (!this._rememberSession || !this._tenant || !this._dpopKeyPair || !this._token || !this._refreshToken) {
750
768
  return;
751
769
  }
752
770
  const ok = await saveDPoPSession({
@@ -829,7 +847,7 @@ var GeonicDBModule = (() => {
829
847
  expiresIn: data.expiresIn,
830
848
  refreshToken: this._refreshToken
831
849
  });
832
- if (this._dpopPersist) void this._persistDpopSession();
850
+ if (this._rememberSession) void this._persistDpopSession();
833
851
  return this._token;
834
852
  } catch (err) {
835
853
  this._tokenPromise = null;
@@ -1410,35 +1428,29 @@ var GeonicDBModule = (() => {
1410
1428
  }
1411
1429
  // --- Authentication ---
1412
1430
  /**
1413
- * Login with email and password (Bearer JWT).
1431
+ * Login with email and password.
1414
1432
  *
1415
- * Pass `{ dpop: true }` to immediately upgrade the session to a DPoP
1416
- * sender-constrained token (RFC 9449). Recommended for browser apps that
1417
- * persist tokens in storage exposed to XSS (localStorage / sessionStorage),
1418
- * since DPoP-bound tokens are unusable without this SDK instance's
1419
- * non-extractable private key.
1433
+ * The session is sender-constrained with DPoP (RFC 9449) automatically and
1434
+ * transparently — there is no DPoP option to set. A token leaked from
1435
+ * storage (e.g. localStorage XSS) is therefore unusable from another origin.
1436
+ * On runtimes without Web Crypto, the SDK silently keeps a plain Bearer
1437
+ * session and login still succeeds.
1420
1438
  *
1421
1439
  * @example
1422
1440
  * ```ts
1423
- * // Bearer (default, backward compatible)
1441
+ * // Standard login — DPoP applied transparently when supported.
1424
1442
  * await db.login('user@example.com', 'password');
1425
1443
  *
1426
- * // DPoP-bound (recommended for SPAs)
1427
- * await db.login('user@example.com', 'password', { dpop: true });
1428
- *
1429
- * // DPoP-bound + persisted across reloads (browser SPA UX)
1430
- * await db.login('user@example.com', 'password', {
1431
- * dpop: true,
1432
- * dpopPersist: true,
1433
- * });
1444
+ * // Keep the user signed in across page reloads (browser SPA UX).
1445
+ * await db.login('user@example.com', 'password', { rememberSession: true });
1434
1446
  * ```
1435
1447
  */
1436
1448
  async login(email, password, options) {
1437
1449
  return this._auth.login(email, password, options);
1438
1450
  }
1439
1451
  /**
1440
- * Rehydrate a DPoP session previously persisted via
1441
- * `login({ dpop: true, dpopPersist: true })`.
1452
+ * Rehydrate a session previously persisted via
1453
+ * `login(email, password, { rememberSession: true })`.
1442
1454
  *
1443
1455
  * Returns true if a session for this tenant was found and loaded; false
1444
1456
  * if there was no entry, IndexedDB is unavailable, or the persisted
@@ -1449,7 +1461,7 @@ var GeonicDBModule = (() => {
1449
1461
  * ```ts
1450
1462
  * const db = new GeonicDB({ baseUrl, tenant });
1451
1463
  * if (await db.restoreSession()) {
1452
- * // Tokens + DPoP private key recovered from IndexedDB — boot the app.
1464
+ * // Session (tokens + non-extractable key) recovered — boot the app.
1453
1465
  * } else {
1454
1466
  * // No session; show the login form.
1455
1467
  * }