@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/README.md +30 -44
- package/geonicdb.cjs +66 -54
- package/geonicdb.cjs.map +2 -2
- package/geonicdb.d.ts +502 -29
- package/geonicdb.iife.js +66 -54
- package/geonicdb.iife.js.map +2 -2
- package/geonicdb.mjs +66 -54
- package/geonicdb.mjs.map +2 -2
- package/package.json +1 -1
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
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
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, "
|
|
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
|
|
550
|
+
* Login with email and password.
|
|
550
551
|
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
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
|
|
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.
|
|
588
|
+
this._rememberSession = wantsRemember;
|
|
588
589
|
this._invalidateAuthScopedState();
|
|
589
|
-
if (
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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.
|
|
705
|
+
if (this._tenant) {
|
|
689
706
|
void clearDPoPSession(this._tenant);
|
|
690
707
|
}
|
|
691
|
-
this.
|
|
708
|
+
this._rememberSession = false;
|
|
692
709
|
this._anonymous = this._initialAnonymous;
|
|
693
710
|
this._invalidateAuthScopedState();
|
|
694
711
|
}
|
|
695
712
|
/**
|
|
696
|
-
* Restore a
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
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.
|
|
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
|
|
735
|
-
* Exposed for the `GeonicDB` wrapper
|
|
736
|
-
* consumers should read `
|
|
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
|
-
|
|
739
|
-
return this.
|
|
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.
|
|
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.
|
|
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
|
|
1431
|
+
* Login with email and password.
|
|
1414
1432
|
*
|
|
1415
|
-
*
|
|
1416
|
-
*
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
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
|
-
* //
|
|
1441
|
+
* // Standard login — DPoP applied transparently when supported.
|
|
1424
1442
|
* await db.login('user@example.com', 'password');
|
|
1425
1443
|
*
|
|
1426
|
-
* //
|
|
1427
|
-
* await db.login('user@example.com', 'password', {
|
|
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
|
|
1441
|
-
* `login(
|
|
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
|
-
* //
|
|
1464
|
+
* // Session (tokens + non-extractable key) recovered — boot the app.
|
|
1453
1465
|
* } else {
|
|
1454
1466
|
* // No session; show the login form.
|
|
1455
1467
|
* }
|