@geolonia/geonicdb-sdk 0.14.0 → 0.16.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 +35 -4
- package/geonicdb.cjs +52 -7
- package/geonicdb.cjs.map +2 -2
- package/geonicdb.d.ts +61 -4
- package/geonicdb.iife.js +52 -7
- package/geonicdb.iife.js.map +2 -2
- package/geonicdb.mjs +52 -7
- package/geonicdb.mjs.map +2 -2
- package/package.json +1 -1
- package/react/index.d.ts +49 -6
package/README.md
CHANGED
|
@@ -115,6 +115,30 @@ On runtimes without Web Crypto (`subtle.generateKey`, e.g. a non-HTTPS
|
|
|
115
115
|
context), the session cannot be sender-constrained: the SDK silently keeps
|
|
116
116
|
the plain Bearer session and **login still succeeds** (`tokenType === 'Bearer'`).
|
|
117
117
|
|
|
118
|
+
#### First-login forced password change (#1532)
|
|
119
|
+
|
|
120
|
+
When an administrator issues a **temporary password** — either at creation
|
|
121
|
+
(`POST /admin/users` with `passwordResetRequired: true`) or afterwards
|
|
122
|
+
(`POST /admin/users/{userId}/reset-password`) — the user must set a new password
|
|
123
|
+
on first login. Logging in with only the temporary password throws
|
|
124
|
+
`PasswordResetRequiredError`; supply `newPassword` to set the new password and
|
|
125
|
+
receive tokens in the same call (no re-login needed):
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { PasswordResetRequiredError, TemporaryPasswordExpiredError } from '@geolonia/geonicdb-sdk';
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await db.login('user@example.com', temporaryPassword);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
if (err instanceof PasswordResetRequiredError) {
|
|
134
|
+
// Prompt the user for a new password, then complete login in one call:
|
|
135
|
+
await db.login('user@example.com', temporaryPassword, { newPassword });
|
|
136
|
+
} else if (err instanceof TemporaryPasswordExpiredError) {
|
|
137
|
+
// Ask an administrator to re-issue the temporary password.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
118
142
|
#### Keeping the user signed in across page reloads
|
|
119
143
|
|
|
120
144
|
By default a session is held in memory only — reloading the page loses it
|
|
@@ -196,6 +220,11 @@ const entities = await db.getEntities({ type: 'GeoJSON' });
|
|
|
196
220
|
|
|
197
221
|
## Entity CRUD
|
|
198
222
|
|
|
223
|
+
> **Automatic `@context` (#1583)**: the SDK sends requests as `application/ld+json`,
|
|
224
|
+
> which requires an inline JSON-LD `@context` in the body. When your body omits
|
|
225
|
+
> `@context`, the SDK injects the NGSI-LD core context automatically; a
|
|
226
|
+
> `@context` you provide is always preserved (never overwritten).
|
|
227
|
+
|
|
199
228
|
```typescript
|
|
200
229
|
// Create
|
|
201
230
|
await db.createEntity({ id: 'urn:ngsi-ld:Room:001', type: 'Room', ... });
|
|
@@ -228,9 +257,11 @@ await db.deleteEntity('urn:ngsi-ld:Room:001');
|
|
|
228
257
|
## Types / Attributes Discovery
|
|
229
258
|
|
|
230
259
|
```typescript
|
|
231
|
-
|
|
260
|
+
// getTypes()/getAttributes() は EntityTypeList/AttributeList オブジェクトを返す。
|
|
261
|
+
// 型名・属性名の一覧は typeList / attributeList から取得する。
|
|
262
|
+
const { typeList } = await db.getTypes(); // string[]
|
|
232
263
|
const roomType = await db.getType('Room');
|
|
233
|
-
const
|
|
264
|
+
const { attributeList } = await db.getAttributes(); // string[]
|
|
234
265
|
const tempAttr = await db.getAttribute('temperature');
|
|
235
266
|
```
|
|
236
267
|
|
|
@@ -459,9 +490,9 @@ handle.stop();
|
|
|
459
490
|
|
|
460
491
|
| Method | Parameters | Returns | Description |
|
|
461
492
|
|--------|-----------|---------|-------------|
|
|
462
|
-
| `getTypes()` | — | `Promise<
|
|
493
|
+
| `getTypes()` | — | `Promise<EntityTypeList>` | List all entity types (`{ id, type, typeList }`) |
|
|
463
494
|
| `getType(typeName)` | `string` | `Promise<Record<string, unknown>>` | Get details for a specific entity type |
|
|
464
|
-
| `getAttributes()` | — | `Promise<
|
|
495
|
+
| `getAttributes()` | — | `Promise<AttributeList>` | List all attributes (`{ id, type, attributeList }`) |
|
|
465
496
|
| `getAttribute(attrName)` | `string` | `Promise<Record<string, unknown>>` | Get details for a specific attribute |
|
|
466
497
|
|
|
467
498
|
### Temporal API
|
package/geonicdb.cjs
CHANGED
|
@@ -29,7 +29,9 @@ __export(index_exports, {
|
|
|
29
29
|
GeonicDBError: () => GeonicDBError,
|
|
30
30
|
NetworkError: () => NetworkError,
|
|
31
31
|
NotFoundError: () => NotFoundError,
|
|
32
|
+
PasswordResetRequiredError: () => PasswordResetRequiredError,
|
|
32
33
|
RateLimitError: () => RateLimitError,
|
|
34
|
+
TemporaryPasswordExpiredError: () => TemporaryPasswordExpiredError,
|
|
33
35
|
ValidationError: () => ValidationError,
|
|
34
36
|
buildPathWithParams: () => buildPathWithParams,
|
|
35
37
|
default: () => index_default
|
|
@@ -80,6 +82,7 @@ var RECONNECT_MAX_ATTEMPTS = 10;
|
|
|
80
82
|
var RECONNECT_BASE_MS = 1e3;
|
|
81
83
|
var RECONNECT_MAX_DELAY_MS = 3e4;
|
|
82
84
|
var SUB_PROTOCOL = "access_token";
|
|
85
|
+
var NGSI_LD_CORE_CONTEXT = "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.9.jsonld";
|
|
83
86
|
var SDK_CACHE_MAX_ENTRIES_DEFAULT = 1e3;
|
|
84
87
|
var SDK_POLL_INTERVAL_MS_DEFAULT = 5e3;
|
|
85
88
|
var DPOP_IDB_NAME = "geonicdb-sdk";
|
|
@@ -321,6 +324,20 @@ var ConflictError = class extends GeonicDBError {
|
|
|
321
324
|
this.name = "ConflictError";
|
|
322
325
|
}
|
|
323
326
|
};
|
|
327
|
+
var PasswordResetRequiredError = class extends AuthenticationError {
|
|
328
|
+
constructor(message = "Password reset required: set a new password to complete login") {
|
|
329
|
+
super(message);
|
|
330
|
+
this.name = "PasswordResetRequiredError";
|
|
331
|
+
this.statusCode = 409;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var TemporaryPasswordExpiredError = class extends AuthenticationError {
|
|
335
|
+
constructor(message = "Temporary password expired: ask an administrator to re-issue it") {
|
|
336
|
+
super(message);
|
|
337
|
+
this.name = "TemporaryPasswordExpiredError";
|
|
338
|
+
this.statusCode = 403;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
324
341
|
var ValidationError = class extends GeonicDBError {
|
|
325
342
|
constructor(message = "Validation failed") {
|
|
326
343
|
super(message, 422);
|
|
@@ -711,16 +728,24 @@ var _AuthManager = class _AuthManager {
|
|
|
711
728
|
"Content-Type": "application/json"
|
|
712
729
|
};
|
|
713
730
|
if (this._tenant) headers["NGSILD-Tenant"] = this._tenant;
|
|
731
|
+
const loginBody = { email, password };
|
|
732
|
+
if (options?.newPassword !== void 0) loginBody.newPassword = options.newPassword;
|
|
714
733
|
const res = await fetch(this._baseUrl + "/auth/login", {
|
|
715
734
|
method: "POST",
|
|
716
735
|
headers,
|
|
717
|
-
body: JSON.stringify(
|
|
736
|
+
body: JSON.stringify(loginBody)
|
|
718
737
|
});
|
|
719
738
|
if (!res.ok) {
|
|
720
739
|
const e = await res.json().catch(() => ({}));
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
)
|
|
740
|
+
const message = e.detail || e.description || "Login failed: " + res.status;
|
|
741
|
+
const code = e.error || e.type || e.title || "";
|
|
742
|
+
if (res.status === 409 && code.includes("PasswordResetRequired")) {
|
|
743
|
+
throw new PasswordResetRequiredError(message);
|
|
744
|
+
}
|
|
745
|
+
if (res.status === 403 && code.includes("TemporaryPasswordExpired")) {
|
|
746
|
+
throw new TemporaryPasswordExpiredError(message);
|
|
747
|
+
}
|
|
748
|
+
throw new AuthenticationError(message);
|
|
724
749
|
}
|
|
725
750
|
const data = await res.json();
|
|
726
751
|
this._anonymous = false;
|
|
@@ -1221,7 +1246,15 @@ var _AuthManager = class _AuthManager {
|
|
|
1221
1246
|
}
|
|
1222
1247
|
const url = this._baseUrl + path;
|
|
1223
1248
|
const isDPoP = this._tokenType === "DPoP" && !!this._dpopKeyPair && token !== null;
|
|
1224
|
-
const
|
|
1249
|
+
const isEntityWrite = path.split("?")[0].startsWith("/ngsi-ld/v1/entities");
|
|
1250
|
+
let effectiveBody = body;
|
|
1251
|
+
if (isEntityWrite && effectiveBody !== null && typeof effectiveBody === "object" && !Array.isArray(effectiveBody) && !("@context" in effectiveBody)) {
|
|
1252
|
+
effectiveBody = {
|
|
1253
|
+
...effectiveBody,
|
|
1254
|
+
"@context": NGSI_LD_CORE_CONTEXT
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
const bodyStr = effectiveBody !== void 0 ? JSON.stringify(effectiveBody) : void 0;
|
|
1225
1258
|
const doRequest = async (reqToken, dpopNonce) => {
|
|
1226
1259
|
const reqIsDPoP = this._tokenType === "DPoP" && !!this._dpopKeyPair && reqToken !== null;
|
|
1227
1260
|
const dpopProof = reqIsDPoP ? await createDPoPProof(
|
|
@@ -1934,7 +1967,13 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1934
1967
|
return { deleted: true };
|
|
1935
1968
|
}
|
|
1936
1969
|
// --- Types / Attributes Discovery ---
|
|
1937
|
-
/**
|
|
1970
|
+
/**
|
|
1971
|
+
* List all entity types.
|
|
1972
|
+
*
|
|
1973
|
+
* `details` 未指定の `GET /types` は ETSI OpenAPI v1.8.1 準拠の
|
|
1974
|
+
* `EntityTypeList` オブジェクト(`{ id, type: 'EntityTypeList', typeList }`)を返す。
|
|
1975
|
+
* 型名の一覧は `typeList` から取得する。
|
|
1976
|
+
*/
|
|
1938
1977
|
async getTypes() {
|
|
1939
1978
|
return this._jsonGet("/ngsi-ld/v1/types", "Types query failed");
|
|
1940
1979
|
}
|
|
@@ -1945,7 +1984,13 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1945
1984
|
"Type not found"
|
|
1946
1985
|
);
|
|
1947
1986
|
}
|
|
1948
|
-
/**
|
|
1987
|
+
/**
|
|
1988
|
+
* List all attributes.
|
|
1989
|
+
*
|
|
1990
|
+
* `details` 未指定の `GET /attributes` は ETSI OpenAPI v1.8.1 準拠の
|
|
1991
|
+
* `AttributeList` オブジェクト(`{ id, type: 'AttributeList', attributeList }`)を返す。
|
|
1992
|
+
* 属性名の一覧は `attributeList` から取得する。
|
|
1993
|
+
*/
|
|
1949
1994
|
async getAttributes() {
|
|
1950
1995
|
return this._jsonGet("/ngsi-ld/v1/attributes", "Attributes query failed");
|
|
1951
1996
|
}
|