@geolonia/geonicdb-sdk 0.19.2 → 0.19.3
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 +36 -20
- package/geonicdb.cjs +264 -7
- package/geonicdb.cjs.map +2 -2
- package/geonicdb.d.ts +131 -16
- package/geonicdb.iife.js +264 -7
- package/geonicdb.iife.js.map +2 -2
- package/geonicdb.mjs +264 -7
- package/geonicdb.mjs.map +2 -2
- package/package.json +1 -1
- package/react/index.d.ts +109 -7
package/README.md
CHANGED
|
@@ -374,32 +374,43 @@ await db.connect();
|
|
|
374
374
|
| `cacheMiss` | `CacheEvent` | Cache miss — fresh response fetched from origin |
|
|
375
375
|
| `cacheInvalidated` | `CacheEvent` | Cache entry dropped via explicit `clearCache()`. WebSocket entity events do **not** trigger cache invalidation — see "Client-side Cache & Polling" |
|
|
376
376
|
|
|
377
|
-
## Web Push Notifications (#3014 / #3060)
|
|
377
|
+
## Web Push Notifications (#3014 / #3060 / #3092)
|
|
378
378
|
|
|
379
379
|
GeonicDB supports browser Web Push (VAPID) as a third NGSI subscription notification protocol
|
|
380
380
|
(alongside HTTP webhook and MQTT — see [SUBSCRIPTIONS.md](https://github.com/geolonia/geonicdb/blob/main/docs/SUBSCRIPTIONS.md#web-push-3014)
|
|
381
|
-
for the full API contract).
|
|
382
|
-
|
|
381
|
+
for the full API contract). Prefer the idempotent helper that covers both the browser Push API
|
|
382
|
+
and the GeonicDB subscription create:
|
|
383
383
|
|
|
384
384
|
```javascript
|
|
385
|
-
|
|
386
|
-
await navigator.serviceWorker.register('/sw.js');
|
|
387
|
-
|
|
388
|
-
// 2. Subscribe — VAPID public key is auto-fetched from the deployment's discovery endpoint.
|
|
389
|
-
const subscription = await db.subscribeWebPush();
|
|
385
|
+
import GeonicDB, { QuotaExceededError } from '@geolonia/geonicdb-sdk';
|
|
390
386
|
|
|
391
|
-
|
|
392
|
-
import { toNgsiv2WebPushNotification } from '@geolonia/geonicdb-sdk';
|
|
387
|
+
await navigator.serviceWorker.register('/sw.js');
|
|
393
388
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
389
|
+
try {
|
|
390
|
+
const { subscriptionId, created } = await db.registerWebPushSubscription({
|
|
391
|
+
protocol: 'ngsiv2', // required — no default ('ngsi-ld' also supported)
|
|
392
|
+
subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },
|
|
393
|
+
ttl: 86400,
|
|
394
|
+
});
|
|
395
|
+
} catch (err) {
|
|
396
|
+
if (err instanceof QuotaExceededError) {
|
|
397
|
+
// 507 — subscription quota grace ceiling; retry later
|
|
398
|
+
}
|
|
399
|
+
throw err;
|
|
400
|
+
}
|
|
399
401
|
```
|
|
400
402
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
+
Calling `registerWebPushSubscription()` again reuses the id stored in `localStorage` when the
|
|
404
|
+
broker still has that subscription (`GET` → 200) and does not `POST`. After a VAPID key rotation
|
|
405
|
+
that changes the push endpoint, it `PATCH`es `notification.webpush` / `endpoint.webpush` instead
|
|
406
|
+
of creating a new subscription. The default key is `baseUrl` + `tenant` + `protocol`; for shared
|
|
407
|
+
browsers or multiple distinct filters, pass `storageKey` that still includes that scope (e.g.
|
|
408
|
+
append `:${userId}:${filterId}` to `buildWebPushSubscriptionStorageKey(...)`). Concurrent calls with the same
|
|
409
|
+
key share one in-flight Promise. HTTP 507 (`QuotaExceededError`) requires deleting unused
|
|
410
|
+
subscriptions or changing the plan — waiting alone will not clear the quota.
|
|
411
|
+
|
|
412
|
+
Low-level helpers remain available: `subscribeWebPush()` + `toNgsiv2WebPushNotification()` /
|
|
413
|
+
`toNgsiLdWebPushEndpoint()` + `request('POST', ...)`.
|
|
403
414
|
|
|
404
415
|
`sw.js` receives the raw NGSI notification body in the `push` event:
|
|
405
416
|
|
|
@@ -414,13 +425,15 @@ self.addEventListener('push', (event) => {
|
|
|
414
425
|
Calling `subscribeWebPush()` again on an already-subscribed registration returns the existing
|
|
415
426
|
`PushSubscription` as-is if it still matches the current VAPID public key. If the deployment
|
|
416
427
|
rotated its VAPID keys, the stale subscription is unsubscribed and replaced with a fresh one
|
|
417
|
-
signed by the new key. Call `await db.unsubscribeWebPush()` to unsubscribe.
|
|
428
|
+
signed by the new key. Call `await db.unsubscribeWebPush()` to unsubscribe the browser side.
|
|
418
429
|
|
|
419
430
|
| Method | Returns | Description |
|
|
420
431
|
|--------|---------|--------------|
|
|
421
432
|
| `getWebPushVapidKey()` | `Promise<string>` | Fetch the deployment's VAPID public key (unauthenticated discovery endpoint) |
|
|
422
|
-
| `subscribeWebPush(options?)` | `Promise<PushSubscription>` | Subscribe via `PushManager.subscribe()`. Browser-only —
|
|
433
|
+
| `subscribeWebPush(options?)` | `Promise<PushSubscription>` | Subscribe via `PushManager.subscribe()`. Browser-only — does not create a GeonicDB subscription |
|
|
434
|
+
| `registerWebPushSubscription(options)` | `Promise<{ subscriptionId, created }>` | Idempotent browser + GeonicDB registration (#3092). `protocol` required. 507 → `QuotaExceededError` |
|
|
423
435
|
| `unsubscribeWebPush(registration?)` | `Promise<boolean>` | Unsubscribe; `false` if there was no active subscription |
|
|
436
|
+
| `buildWebPushSubscriptionStorageKey(baseUrl, tenant, protocol)` | `string` | Default `localStorage` key used for idempotency |
|
|
424
437
|
|
|
425
438
|
## Client-side Cache & Polling
|
|
426
439
|
|
|
@@ -692,8 +705,10 @@ handle.stop();
|
|
|
692
705
|
| Method | Parameters | Returns | Description |
|
|
693
706
|
|--------|-----------|---------|-------------|
|
|
694
707
|
| `getWebPushVapidKey()` | — | `Promise<string>` | Fetch the deployment's VAPID public key |
|
|
695
|
-
| `subscribeWebPush(options?)` | `WebPushSubscribeOptions` | `Promise<PushSubscription>` | Subscribe via `PushManager.subscribe()` |
|
|
708
|
+
| `subscribeWebPush(options?)` | `WebPushSubscribeOptions` | `Promise<PushSubscription>` | Subscribe via `PushManager.subscribe()` (browser only; no GeonicDB registration) |
|
|
709
|
+
| `registerWebPushSubscription(options)` | `RegisterWebPushSubscriptionOptions` | `Promise<RegisterWebPushSubscriptionResult>` | Idempotent browser + GeonicDB registration (#3092). `protocol` required |
|
|
696
710
|
| `unsubscribeWebPush(registration?)` | `ServiceWorkerRegistration` | `Promise<boolean>` | Unsubscribe from Web Push |
|
|
711
|
+
| `buildWebPushSubscriptionStorageKey(...)` | `baseUrl, tenant, protocol` | `string` | Default localStorage key for idempotency |
|
|
697
712
|
|
|
698
713
|
#### `WebPushSubscribeOptions`
|
|
699
714
|
|
|
@@ -745,6 +760,7 @@ try {
|
|
|
745
760
|
| `ConflictError` | 409 | Entity already exists |
|
|
746
761
|
| `ValidationError` | 422 | Bad request payload |
|
|
747
762
|
| `RateLimitError` | 429 | Rate limited (`retryAfter` property) |
|
|
763
|
+
| `QuotaExceededError` | 507 | Storage / subscription quota exceeded (beyond plan grace ceiling) |
|
|
748
764
|
| `NetworkError` | — | Fetch failure, DNS error, timeout |
|
|
749
765
|
|
|
750
766
|
### Which request failed (`request`)
|
package/geonicdb.cjs
CHANGED
|
@@ -30,10 +30,12 @@ __export(index_exports, {
|
|
|
30
30
|
NetworkError: () => NetworkError,
|
|
31
31
|
NotFoundError: () => NotFoundError,
|
|
32
32
|
PasswordResetRequiredError: () => PasswordResetRequiredError,
|
|
33
|
+
QuotaExceededError: () => QuotaExceededError,
|
|
33
34
|
RateLimitError: () => RateLimitError,
|
|
34
35
|
TemporaryPasswordExpiredError: () => TemporaryPasswordExpiredError,
|
|
35
36
|
ValidationError: () => ValidationError,
|
|
36
37
|
buildPathWithParams: () => buildPathWithParams,
|
|
38
|
+
buildWebPushSubscriptionStorageKey: () => buildWebPushSubscriptionStorageKey,
|
|
37
39
|
default: () => index_default,
|
|
38
40
|
toNgsiLdWebPushEndpoint: () => toNgsiLdWebPushEndpoint,
|
|
39
41
|
toNgsiv2WebPushNotification: () => toNgsiv2WebPushNotification,
|
|
@@ -95,6 +97,7 @@ var SDK_CACHE_IDB_NAME = "geonicdb-sdk-cache";
|
|
|
95
97
|
var SDK_CACHE_IDB_STORE = "responses";
|
|
96
98
|
var SDK_CACHE_IDB_VERSION = 1;
|
|
97
99
|
var WEBPUSH_VAPID_KEY_PATH = "/.well-known/webpush-vapid-key";
|
|
100
|
+
var WEBPUSH_SUBSCRIPTION_ID_STORAGE_PREFIX = "geonicdb:webpush:subscriptionId";
|
|
98
101
|
|
|
99
102
|
// src/sdk/dpop.ts
|
|
100
103
|
var dpopSupported = typeof crypto !== "undefined" && !!crypto.subtle && !!crypto.subtle.generateKey;
|
|
@@ -362,6 +365,12 @@ var RateLimitError = class extends GeonicDBError {
|
|
|
362
365
|
this.retryAfter = retryAfter;
|
|
363
366
|
}
|
|
364
367
|
};
|
|
368
|
+
var QuotaExceededError = class extends GeonicDBError {
|
|
369
|
+
constructor(message = "Quota exceeded") {
|
|
370
|
+
super(message, 507);
|
|
371
|
+
this.name = "QuotaExceededError";
|
|
372
|
+
}
|
|
373
|
+
};
|
|
365
374
|
var NetworkError = class extends GeonicDBError {
|
|
366
375
|
constructor(message = "Network error") {
|
|
367
376
|
super(message, 0);
|
|
@@ -383,6 +392,8 @@ function createErrorFromResponse(status, body, fallbackMessage) {
|
|
|
383
392
|
return new ValidationError(message);
|
|
384
393
|
case 429:
|
|
385
394
|
return new RateLimitError(message);
|
|
395
|
+
case 507:
|
|
396
|
+
return new QuotaExceededError(message);
|
|
386
397
|
default:
|
|
387
398
|
return new GeonicDBError(message, status);
|
|
388
399
|
}
|
|
@@ -648,6 +659,10 @@ var _AuthManager = class _AuthManager {
|
|
|
648
659
|
get baseUrl() {
|
|
649
660
|
return this._baseUrl;
|
|
650
661
|
}
|
|
662
|
+
/** Fiware-Service / NGSILD-Tenant value configured at construction time. */
|
|
663
|
+
get tenant() {
|
|
664
|
+
return this._tenant;
|
|
665
|
+
}
|
|
651
666
|
/**
|
|
652
667
|
* True when running unauthenticated.
|
|
653
668
|
*
|
|
@@ -1737,6 +1752,9 @@ function createCachePersistence() {
|
|
|
1737
1752
|
}
|
|
1738
1753
|
|
|
1739
1754
|
// src/sdk/webpush.ts
|
|
1755
|
+
function buildWebPushSubscriptionStorageKey(baseUrl, tenant, protocol) {
|
|
1756
|
+
return WEBPUSH_SUBSCRIPTION_ID_STORAGE_PREFIX + ":" + encodeURIComponent(baseUrl) + ":" + encodeURIComponent(tenant) + ":" + protocol;
|
|
1757
|
+
}
|
|
1740
1758
|
function arrayBufferToBase64Url(buf) {
|
|
1741
1759
|
const bytes = new Uint8Array(buf);
|
|
1742
1760
|
let binary = "";
|
|
@@ -1797,6 +1815,12 @@ var GeonicDB = class extends EventEmitter {
|
|
|
1797
1815
|
super();
|
|
1798
1816
|
__publicField(this, "_auth");
|
|
1799
1817
|
__publicField(this, "_ws");
|
|
1818
|
+
/**
|
|
1819
|
+
* In-flight `registerWebPushSubscription()` calls keyed by storageKey (#3092).
|
|
1820
|
+
* Concurrent callers with the same key share one Promise so a race cannot
|
|
1821
|
+
* POST two subscriptions before the first id is persisted.
|
|
1822
|
+
*/
|
|
1823
|
+
__publicField(this, "_webPushRegisterInflight", /* @__PURE__ */ new Map());
|
|
1800
1824
|
/**
|
|
1801
1825
|
* Legacy callback for token refresh events.
|
|
1802
1826
|
* Prefer `db.on('tokenRefresh', callback)` for new code.
|
|
@@ -2269,9 +2293,13 @@ var GeonicDB = class extends EventEmitter {
|
|
|
2269
2293
|
* — otherwise (e.g. after the deployment rotated its VAPID keys) it
|
|
2270
2294
|
* unsubscribes the stale one and re-subscribes with the current key.
|
|
2271
2295
|
* Fetches the deployment's VAPID public key (unless `applicationServerKey`
|
|
2272
|
-
* is given) and calls `PushManager.subscribe()`.
|
|
2273
|
-
*
|
|
2274
|
-
* the
|
|
2296
|
+
* is given) and calls `PushManager.subscribe()`.
|
|
2297
|
+
*
|
|
2298
|
+
* This method only covers the browser Push API. To also create (or reuse)
|
|
2299
|
+
* the GeonicDB subscription idempotently, prefer
|
|
2300
|
+
* `registerWebPushSubscription()` (#3092). For a manual flow, convert the
|
|
2301
|
+
* result with `toNgsiv2WebPushNotification()` / `toNgsiLdWebPushEndpoint()`
|
|
2302
|
+
* and `request('POST', ...)`.
|
|
2275
2303
|
*
|
|
2276
2304
|
* Browser-only — throws in environments without Service Worker + Push API
|
|
2277
2305
|
* support (e.g. Node.js).
|
|
@@ -2279,11 +2307,9 @@ var GeonicDB = class extends EventEmitter {
|
|
|
2279
2307
|
* @example
|
|
2280
2308
|
* ```ts
|
|
2281
2309
|
* await navigator.serviceWorker.register('/sw.js');
|
|
2282
|
-
* const
|
|
2283
|
-
*
|
|
2284
|
-
* description: 'Web Push notification',
|
|
2310
|
+
* const { subscriptionId, created } = await db.registerWebPushSubscription({
|
|
2311
|
+
* protocol: 'ngsiv2',
|
|
2285
2312
|
* subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },
|
|
2286
|
-
* notification: { webpush: toNgsiv2WebPushNotification(subscription) },
|
|
2287
2313
|
* });
|
|
2288
2314
|
* ```
|
|
2289
2315
|
*/
|
|
@@ -2303,10 +2329,121 @@ var GeonicDB = class extends EventEmitter {
|
|
|
2303
2329
|
}
|
|
2304
2330
|
return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey });
|
|
2305
2331
|
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Idempotently register a GeonicDB subscription that delivers via Web Push (#3092).
|
|
2334
|
+
*
|
|
2335
|
+
* 1. Calls `subscribeWebPush()` for the browser PushSubscription.
|
|
2336
|
+
* 2. Looks up a previously saved subscription id in `localStorage`.
|
|
2337
|
+
* 3. If that id still exists on the broker (`GET` → 200), returns it without
|
|
2338
|
+
* `POST`ing. When the push endpoint changed (e.g. VAPID key rotation),
|
|
2339
|
+
* `PATCH`es only `notification.webpush` / `endpoint.webpush` so a new
|
|
2340
|
+
* subscription is not created (avoids burning the subscription quota).
|
|
2341
|
+
* 4. Otherwise `POST`s a new subscription and persists its id.
|
|
2342
|
+
*
|
|
2343
|
+
* `protocol` is required — there is no default. A 507 from the broker is
|
|
2344
|
+
* thrown as `QuotaExceededError`.
|
|
2345
|
+
*
|
|
2346
|
+
* @example
|
|
2347
|
+
* ```ts
|
|
2348
|
+
* import { QuotaExceededError } from '@geolonia/geonicdb-sdk';
|
|
2349
|
+
*
|
|
2350
|
+
* try {
|
|
2351
|
+
* const { subscriptionId, created } = await db.registerWebPushSubscription({
|
|
2352
|
+
* protocol: 'ngsiv2',
|
|
2353
|
+
* subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },
|
|
2354
|
+
* });
|
|
2355
|
+
* } catch (err) {
|
|
2356
|
+
* if (err instanceof QuotaExceededError) {
|
|
2357
|
+
* // plan grace ceiling — delete unused subscriptions or change plan, then retry
|
|
2358
|
+
* }
|
|
2359
|
+
* throw err;
|
|
2360
|
+
* }
|
|
2361
|
+
* ```
|
|
2362
|
+
*/
|
|
2363
|
+
async registerWebPushSubscription(options) {
|
|
2364
|
+
if (!options || options.protocol !== "ngsiv2" && options.protocol !== "ngsi-ld") {
|
|
2365
|
+
throw new GeonicDBError(
|
|
2366
|
+
"registerWebPushSubscription() requires protocol: 'ngsiv2' | 'ngsi-ld'"
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
if (options.protocol === "ngsiv2" && !options.subject?.entities?.length) {
|
|
2370
|
+
throw new GeonicDBError(
|
|
2371
|
+
"registerWebPushSubscription({ protocol: 'ngsiv2' }) requires subject.entities"
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
if (options.protocol === "ngsi-ld" && !options.entities?.length) {
|
|
2375
|
+
throw new GeonicDBError(
|
|
2376
|
+
"registerWebPushSubscription({ protocol: 'ngsi-ld' }) requires entities"
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
const storageKey = options.storageKey ?? buildWebPushSubscriptionStorageKey(
|
|
2380
|
+
this._auth.baseUrl,
|
|
2381
|
+
this._auth.tenant,
|
|
2382
|
+
options.protocol
|
|
2383
|
+
);
|
|
2384
|
+
assertWebPushSubscriptionStorageWritable(storageKey);
|
|
2385
|
+
const inflight = this._webPushRegisterInflight.get(storageKey);
|
|
2386
|
+
if (inflight) return inflight;
|
|
2387
|
+
const run = this._registerWebPushSubscriptionBody(options, storageKey).finally(() => {
|
|
2388
|
+
if (this._webPushRegisterInflight.get(storageKey) === run) {
|
|
2389
|
+
this._webPushRegisterInflight.delete(storageKey);
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
this._webPushRegisterInflight.set(storageKey, run);
|
|
2393
|
+
return run;
|
|
2394
|
+
}
|
|
2395
|
+
async _registerWebPushSubscriptionBody(options, storageKey) {
|
|
2396
|
+
const pushSubscription = await this.subscribeWebPush({
|
|
2397
|
+
applicationServerKey: options.applicationServerKey,
|
|
2398
|
+
serviceWorkerRegistration: options.serviceWorkerRegistration
|
|
2399
|
+
});
|
|
2400
|
+
const delivery = {
|
|
2401
|
+
...options.ttl !== void 0 && { ttl: options.ttl },
|
|
2402
|
+
...options.urgency !== void 0 && { urgency: options.urgency },
|
|
2403
|
+
...options.topic !== void 0 && { topic: options.topic }
|
|
2404
|
+
};
|
|
2405
|
+
const savedId = readWebPushSubscriptionId(storageKey);
|
|
2406
|
+
if (savedId) {
|
|
2407
|
+
const collectionPath = webPushSubscriptionCollectionPath(options.protocol);
|
|
2408
|
+
const itemPath = `${collectionPath}/${encodeURIComponent(savedId)}`;
|
|
2409
|
+
try {
|
|
2410
|
+
const existing = await this.request("GET", itemPath);
|
|
2411
|
+
const storedEndpoint = readStoredWebPushEndpoint(options.protocol, existing);
|
|
2412
|
+
if (storedEndpoint && storedEndpoint !== pushSubscription.endpoint) {
|
|
2413
|
+
await this.request(
|
|
2414
|
+
"PATCH",
|
|
2415
|
+
itemPath,
|
|
2416
|
+
buildWebPushPatchBody(options.protocol, pushSubscription, delivery)
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
return { subscriptionId: savedId, created: false };
|
|
2420
|
+
} catch (err) {
|
|
2421
|
+
if (!(err instanceof NotFoundError)) throw err;
|
|
2422
|
+
clearWebPushSubscriptionId(storageKey);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
const createPath = webPushSubscriptionCollectionPath(options.protocol);
|
|
2426
|
+
const body = buildWebPushCreateBody(options, pushSubscription, delivery);
|
|
2427
|
+
const res = await this.requestRaw("POST", createPath, body);
|
|
2428
|
+
if (!res.ok) {
|
|
2429
|
+
const e = await res.json().catch(() => ({}));
|
|
2430
|
+
throw createErrorFromResponse(res.status, e, "Request failed: " + res.status);
|
|
2431
|
+
}
|
|
2432
|
+
const subscriptionId = subscriptionIdFromLocation(
|
|
2433
|
+
res.headers.get("Location"),
|
|
2434
|
+
options.protocol
|
|
2435
|
+
);
|
|
2436
|
+
writeWebPushSubscriptionId(storageKey, subscriptionId);
|
|
2437
|
+
return { subscriptionId, created: true };
|
|
2438
|
+
}
|
|
2306
2439
|
/**
|
|
2307
2440
|
* Unsubscribe the current browser from Web Push notifications, if
|
|
2308
2441
|
* subscribed. Returns `false` (no-op) when there is no active
|
|
2309
2442
|
* subscription or the environment has no Service Worker support.
|
|
2443
|
+
*
|
|
2444
|
+
* Does not delete the GeonicDB subscription document — clear the
|
|
2445
|
+
* persisted id (see `buildWebPushSubscriptionStorageKey`) and
|
|
2446
|
+
* `DELETE` the subscription separately if needed.
|
|
2310
2447
|
*/
|
|
2311
2448
|
async unsubscribeWebPush(registration) {
|
|
2312
2449
|
if (typeof navigator === "undefined" || !navigator.serviceWorker) return false;
|
|
@@ -2345,4 +2482,124 @@ function buildPathWithParams(path, params) {
|
|
|
2345
2482
|
const qs = sp.toString();
|
|
2346
2483
|
return qs ? `${path}?${qs}` : path;
|
|
2347
2484
|
}
|
|
2485
|
+
function webPushSubscriptionCollectionPath(protocol) {
|
|
2486
|
+
return protocol === "ngsiv2" ? "/v2/subscriptions" : "/ngsi-ld/v1/subscriptions";
|
|
2487
|
+
}
|
|
2488
|
+
function subscriptionIdFromLocation(location, protocol) {
|
|
2489
|
+
if (!location) {
|
|
2490
|
+
throw new GeonicDBError("Missing Location header after Web Push subscription create");
|
|
2491
|
+
}
|
|
2492
|
+
const marker = protocol === "ngsiv2" ? "/v2/subscriptions/" : "/ngsi-ld/v1/subscriptions/";
|
|
2493
|
+
const idx = location.indexOf(marker);
|
|
2494
|
+
const raw = idx >= 0 ? location.slice(idx + marker.length) : location.split("/").pop() || "";
|
|
2495
|
+
const id = decodeURIComponent(raw.split("?")[0].split("#")[0]);
|
|
2496
|
+
if (!id) {
|
|
2497
|
+
throw new GeonicDBError("Could not parse subscription id from Location: " + location);
|
|
2498
|
+
}
|
|
2499
|
+
return id;
|
|
2500
|
+
}
|
|
2501
|
+
function readStoredWebPushEndpoint(protocol, body) {
|
|
2502
|
+
const notification = body.notification;
|
|
2503
|
+
if (!notification) return void 0;
|
|
2504
|
+
if (protocol === "ngsiv2") {
|
|
2505
|
+
const webpush = notification.webpush;
|
|
2506
|
+
return webpush?.url;
|
|
2507
|
+
}
|
|
2508
|
+
const endpoint = notification.endpoint;
|
|
2509
|
+
return endpoint?.uri;
|
|
2510
|
+
}
|
|
2511
|
+
function buildWebPushCreateBody(options, pushSubscription, delivery) {
|
|
2512
|
+
const description = options.description ?? "Web Push notification";
|
|
2513
|
+
if (options.protocol === "ngsiv2") {
|
|
2514
|
+
return {
|
|
2515
|
+
description,
|
|
2516
|
+
subject: options.subject,
|
|
2517
|
+
notification: {
|
|
2518
|
+
webpush: toNgsiv2WebPushNotification(pushSubscription, delivery)
|
|
2519
|
+
}
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
const { uri, webpush } = toNgsiLdWebPushEndpoint(pushSubscription, delivery);
|
|
2523
|
+
return {
|
|
2524
|
+
type: "Subscription",
|
|
2525
|
+
description,
|
|
2526
|
+
entities: options.entities,
|
|
2527
|
+
...options.watchedAttributes && { watchedAttributes: options.watchedAttributes },
|
|
2528
|
+
...options.q && { q: options.q },
|
|
2529
|
+
notification: {
|
|
2530
|
+
endpoint: { uri, accept: "application/json", protocol: "webpush", webpush }
|
|
2531
|
+
}
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
function buildWebPushPatchBody(protocol, pushSubscription, delivery) {
|
|
2535
|
+
if (protocol === "ngsiv2") {
|
|
2536
|
+
return {
|
|
2537
|
+
notification: {
|
|
2538
|
+
webpush: toNgsiv2WebPushNotification(pushSubscription, delivery)
|
|
2539
|
+
}
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
const { uri, webpush } = toNgsiLdWebPushEndpoint(pushSubscription, delivery);
|
|
2543
|
+
return {
|
|
2544
|
+
notification: {
|
|
2545
|
+
endpoint: { uri, accept: "application/json", protocol: "webpush", webpush }
|
|
2546
|
+
}
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
function readWebPushSubscriptionId(storageKey) {
|
|
2550
|
+
try {
|
|
2551
|
+
if (typeof localStorage === "undefined") return null;
|
|
2552
|
+
return localStorage.getItem(storageKey);
|
|
2553
|
+
} catch {
|
|
2554
|
+
return null;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
function writeWebPushSubscriptionId(storageKey, subscriptionId) {
|
|
2558
|
+
try {
|
|
2559
|
+
if (typeof localStorage === "undefined") {
|
|
2560
|
+
throw new GeonicDBError(
|
|
2561
|
+
"registerWebPushSubscription() requires localStorage for idempotent registration"
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
localStorage.setItem(storageKey, subscriptionId);
|
|
2565
|
+
} catch (err) {
|
|
2566
|
+
if (err instanceof GeonicDBError) throw err;
|
|
2567
|
+
throw new GeonicDBError(
|
|
2568
|
+
"registerWebPushSubscription() could not persist subscription id to localStorage"
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
function assertWebPushSubscriptionStorageWritable(storageKey) {
|
|
2573
|
+
const probeKey = storageKey + "::__writable__";
|
|
2574
|
+
try {
|
|
2575
|
+
if (typeof localStorage === "undefined") {
|
|
2576
|
+
throw new GeonicDBError(
|
|
2577
|
+
"registerWebPushSubscription() requires localStorage for idempotent registration"
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2580
|
+
localStorage.setItem(probeKey, "1");
|
|
2581
|
+
if (localStorage.getItem(probeKey) !== "1") {
|
|
2582
|
+
throw new GeonicDBError(
|
|
2583
|
+
"registerWebPushSubscription() could not persist subscription id to localStorage"
|
|
2584
|
+
);
|
|
2585
|
+
}
|
|
2586
|
+
localStorage.removeItem(probeKey);
|
|
2587
|
+
} catch (err) {
|
|
2588
|
+
try {
|
|
2589
|
+
if (typeof localStorage !== "undefined") localStorage.removeItem(probeKey);
|
|
2590
|
+
} catch {
|
|
2591
|
+
}
|
|
2592
|
+
if (err instanceof GeonicDBError) throw err;
|
|
2593
|
+
throw new GeonicDBError(
|
|
2594
|
+
"registerWebPushSubscription() could not persist subscription id to localStorage"
|
|
2595
|
+
);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
function clearWebPushSubscriptionId(storageKey) {
|
|
2599
|
+
try {
|
|
2600
|
+
if (typeof localStorage === "undefined") return;
|
|
2601
|
+
localStorage.removeItem(storageKey);
|
|
2602
|
+
} catch {
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2348
2605
|
//# sourceMappingURL=geonicdb.cjs.map
|