@geolonia/geonicdb-sdk 0.10.1 → 0.11.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/geonicdb.d.ts CHANGED
@@ -121,6 +121,16 @@ export interface GetEntitiesParams {
121
121
  count?: boolean;
122
122
  /** GeoProperty key to use (default `location`) */
123
123
  geoproperty?: string;
124
+ /**
125
+ * NGSI-LD representation options (`options` query param)。カンマ区切りで複数指定可。
126
+ * - `sysAttrs` — システム属性 (`createdAt` / `modifiedAt`) を含める
127
+ * - `keyValues` — 簡易表現 (属性を値だけに圧縮)
128
+ * - `concise` — concise 表現
129
+ * - `normalized` — 正規化表現 (デフォルト)
130
+ *
131
+ * 例: `'sysAttrs'`, `'keyValues,sysAttrs'`。
132
+ */
133
+ options?: string;
124
134
  }
125
135
  /** Parameters for counting entities. */
126
136
  export interface CountEntitiesParams {
@@ -171,37 +181,20 @@ export interface ReconnectingEvent {
171
181
  /** Options for `db.login(...)`. */
172
182
  export interface LoginOptions {
173
183
  /**
174
- * If true, immediately exchange the Bearer session for a DPoP-bound
175
- * sender-constrained session via `POST /auth/dpop-bind` (RFC 9449).
176
- *
177
- * Recommended for browser apps that persist tokens in storage that may be
178
- * exposed to XSS (localStorage, sessionStorage). DPoP-bound tokens are
179
- * unusable without the client's non-extractable private key, so a stolen
180
- * token cannot be replayed from another origin.
181
- *
182
- * Throws `AuthenticationError` if DPoP is not supported in the runtime
183
- * (no Web Crypto subtle.generateKey).
184
- *
185
- * Default: false (Bearer remains).
186
- */
187
- dpop?: boolean;
188
- /**
189
- * If true, persist the DPoP session (non-extractable private key + tokens)
190
- * to IndexedDB so the session survives a page reload. Implies `dpop: true`
191
- * — passing `dpopPersist: true` without `dpop: true` throws.
192
- *
193
- * The private key is stored as a `CryptoKey` via the structured clone
194
- * algorithm; `extractable: false` is preserved, so the key cannot be read
195
- * back as JWK / PEM even with full DOM access. A token leaked from
196
- * localStorage remains unusable without this SDK origin's IndexedDB.
184
+ * If true, keep the user signed in across page reloads by persisting the
185
+ * session to IndexedDB. After a reload, call `db.restoreSession()` to
186
+ * rehydrate the session; `db.logout()` clears the persisted entry.
197
187
  *
198
- * After a reload, call `db.restoreSession()` to rehydrate. Use `db.logout()`
199
- * to clear the persisted entry.
188
+ * The session is bound to a non-extractable key (DPoP, RFC 9449) stored via
189
+ * the structured clone algorithm — `extractable: false` is preserved, so the
190
+ * key cannot be read back as JWK / PEM even with full DOM access. A token
191
+ * leaked from localStorage stays unusable without this origin's IndexedDB.
200
192
  *
201
193
  * Default: false. Opt-out by design: shared-device flows should not enable
202
- * this.
194
+ * this. Has no effect when the runtime lacks Web Crypto — the session cannot
195
+ * be sender-constrained, so it is not persisted.
203
196
  */
204
- dpopPersist?: boolean;
197
+ rememberSession?: boolean;
205
198
  }
206
199
  /** Response from the login endpoint. */
207
200
  export interface LoginResponse {
@@ -271,6 +264,15 @@ export interface GetTemporalEntitiesParams {
271
264
  limit?: number;
272
265
  /** Pagination offset */
273
266
  offset?: number;
267
+ /**
268
+ * NGSI-LD representation options (`options` query param)。カンマ区切りで複数指定可。
269
+ * - `sysAttrs` — システム属性 (`createdAt` / `modifiedAt`) を含める
270
+ * - `temporalValues` — 時系列を簡易配列表現で返す
271
+ * - `aggregatedValues` — 集約表現で返す
272
+ *
273
+ * 例: `'sysAttrs'`, `'temporalValues'`。
274
+ */
275
+ options?: string;
274
276
  }
275
277
  export type Listener<T> = (data: T) => void;
276
278
  declare class EventEmitter {
@@ -364,33 +366,27 @@ export declare class GeonicDB extends EventEmitter {
364
366
  */
365
367
  clearCache(): void;
366
368
  /**
367
- * Login with email and password (Bearer JWT).
369
+ * Login with email and password.
368
370
  *
369
- * Pass `{ dpop: true }` to immediately upgrade the session to a DPoP
370
- * sender-constrained token (RFC 9449). Recommended for browser apps that
371
- * persist tokens in storage exposed to XSS (localStorage / sessionStorage),
372
- * since DPoP-bound tokens are unusable without this SDK instance's
373
- * non-extractable private key.
371
+ * The session is sender-constrained with DPoP (RFC 9449) automatically and
372
+ * transparently — there is no DPoP option to set. A token leaked from
373
+ * storage (e.g. localStorage XSS) is therefore unusable from another origin.
374
+ * On runtimes without Web Crypto, the SDK silently keeps a plain Bearer
375
+ * session and login still succeeds.
374
376
  *
375
377
  * @example
376
378
  * ```ts
377
- * // Bearer (default, backward compatible)
379
+ * // Standard login — DPoP applied transparently when supported.
378
380
  * await db.login('user@example.com', 'password');
379
381
  *
380
- * // DPoP-bound (recommended for SPAs)
381
- * await db.login('user@example.com', 'password', { dpop: true });
382
- *
383
- * // DPoP-bound + persisted across reloads (browser SPA UX)
384
- * await db.login('user@example.com', 'password', {
385
- * dpop: true,
386
- * dpopPersist: true,
387
- * });
382
+ * // Keep the user signed in across page reloads (browser SPA UX).
383
+ * await db.login('user@example.com', 'password', { rememberSession: true });
388
384
  * ```
389
385
  */
390
386
  login(email: string, password: string, options?: LoginOptions): Promise<LoginResponse>;
391
387
  /**
392
- * Rehydrate a DPoP session previously persisted via
393
- * `login({ dpop: true, dpopPersist: true })`.
388
+ * Rehydrate a session previously persisted via
389
+ * `login(email, password, { rememberSession: true })`.
394
390
  *
395
391
  * Returns true if a session for this tenant was found and loaded; false
396
392
  * if there was no entry, IndexedDB is unavailable, or the persisted
@@ -401,7 +397,7 @@ export declare class GeonicDB extends EventEmitter {
401
397
  * ```ts
402
398
  * const db = new GeonicDB({ baseUrl, tenant });
403
399
  * if (await db.restoreSession()) {
404
- * // Tokens + DPoP private key recovered from IndexedDB — boot the app.
400
+ * // Session (tokens + non-extractable key) recovered — boot the app.
405
401
  * } else {
406
402
  * // No session; show the login form.
407
403
  * }
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
  * }