@geolonia/geonicdb-sdk 0.18.1 → 0.19.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/README.md +63 -0
- package/geonicdb.cjs +142 -1
- package/geonicdb.cjs.map +3 -3
- package/geonicdb.d.ts +142 -0
- package/geonicdb.iife.js +142 -1
- package/geonicdb.iife.js.map +3 -3
- package/geonicdb.mjs +142 -1
- package/geonicdb.mjs.map +3 -3
- package/package.json +2 -2
- package/react/index.d.ts +58 -0
package/geonicdb.mjs
CHANGED
|
@@ -55,6 +55,7 @@ var DPOP_IDB_VERSION = 1;
|
|
|
55
55
|
var SDK_CACHE_IDB_NAME = "geonicdb-sdk-cache";
|
|
56
56
|
var SDK_CACHE_IDB_STORE = "responses";
|
|
57
57
|
var SDK_CACHE_IDB_VERSION = 1;
|
|
58
|
+
var WEBPUSH_VAPID_KEY_PATH = "/.well-known/webpush-vapid-key";
|
|
58
59
|
|
|
59
60
|
// src/sdk/dpop.ts
|
|
60
61
|
var dpopSupported = typeof crypto !== "undefined" && !!crypto.subtle && !!crypto.subtle.generateKey;
|
|
@@ -604,6 +605,10 @@ var _AuthManager = class _AuthManager {
|
|
|
604
605
|
});
|
|
605
606
|
}
|
|
606
607
|
}
|
|
608
|
+
/** The base URL requests are sent against (trailing slash already stripped). */
|
|
609
|
+
get baseUrl() {
|
|
610
|
+
return this._baseUrl;
|
|
611
|
+
}
|
|
607
612
|
/**
|
|
608
613
|
* True when running unauthenticated.
|
|
609
614
|
*
|
|
@@ -1692,6 +1697,61 @@ function createCachePersistence() {
|
|
|
1692
1697
|
return cachePersistenceSupported ? new IdbCachePersistence() : null;
|
|
1693
1698
|
}
|
|
1694
1699
|
|
|
1700
|
+
// src/sdk/webpush.ts
|
|
1701
|
+
function arrayBufferToBase64Url(buf) {
|
|
1702
|
+
const bytes = new Uint8Array(buf);
|
|
1703
|
+
let binary = "";
|
|
1704
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1705
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1706
|
+
}
|
|
1707
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1708
|
+
}
|
|
1709
|
+
function extractKeys(subscription) {
|
|
1710
|
+
const endpoint = subscription.endpoint;
|
|
1711
|
+
let p256dh = subscription.keys?.p256dh;
|
|
1712
|
+
let auth = subscription.keys?.auth;
|
|
1713
|
+
if ((!p256dh || !auth) && typeof subscription.getKey === "function") {
|
|
1714
|
+
const p256dhBuf = subscription.getKey("p256dh");
|
|
1715
|
+
const authBuf = subscription.getKey("auth");
|
|
1716
|
+
if (!p256dh && p256dhBuf) p256dh = arrayBufferToBase64Url(p256dhBuf);
|
|
1717
|
+
if (!auth && authBuf) auth = arrayBufferToBase64Url(authBuf);
|
|
1718
|
+
}
|
|
1719
|
+
if (!endpoint || !p256dh || !auth) {
|
|
1720
|
+
throw new Error(
|
|
1721
|
+
"Invalid PushSubscription: endpoint, keys.p256dh, and keys.auth are all required"
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
return { endpoint, keys: { p256dh, auth } };
|
|
1725
|
+
}
|
|
1726
|
+
function toNgsiv2WebPushNotification(subscription, options) {
|
|
1727
|
+
const { endpoint, keys } = extractKeys(subscription);
|
|
1728
|
+
return { url: endpoint, keys, ...options };
|
|
1729
|
+
}
|
|
1730
|
+
function toNgsiLdWebPushEndpoint(subscription, options) {
|
|
1731
|
+
const { endpoint, keys } = extractKeys(subscription);
|
|
1732
|
+
return { uri: endpoint, webpush: { keys, ...options } };
|
|
1733
|
+
}
|
|
1734
|
+
function urlBase64ToUint8Array(base64Url) {
|
|
1735
|
+
const padding = "=".repeat((4 - base64Url.length % 4) % 4);
|
|
1736
|
+
const base64 = (base64Url + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
1737
|
+
const raw = atob(base64);
|
|
1738
|
+
const output = new Uint8Array(raw.length);
|
|
1739
|
+
for (let i = 0; i < raw.length; i++) {
|
|
1740
|
+
output[i] = raw.charCodeAt(i);
|
|
1741
|
+
}
|
|
1742
|
+
return output;
|
|
1743
|
+
}
|
|
1744
|
+
function matchesApplicationServerKey(subscription, desiredKey) {
|
|
1745
|
+
const existingKey = subscription.options?.applicationServerKey;
|
|
1746
|
+
if (!existingKey) return true;
|
|
1747
|
+
const existingBytes = new Uint8Array(existingKey);
|
|
1748
|
+
if (existingBytes.length !== desiredKey.length) return false;
|
|
1749
|
+
for (let i = 0; i < existingBytes.length; i++) {
|
|
1750
|
+
if (existingBytes[i] !== desiredKey[i]) return false;
|
|
1751
|
+
}
|
|
1752
|
+
return true;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1695
1755
|
// src/sdk/index.ts
|
|
1696
1756
|
var GeonicDB = class extends EventEmitter {
|
|
1697
1757
|
constructor(options) {
|
|
@@ -2138,6 +2198,84 @@ var GeonicDB = class extends EventEmitter {
|
|
|
2138
2198
|
subscribe(options) {
|
|
2139
2199
|
this._ws.subscribe(options);
|
|
2140
2200
|
}
|
|
2201
|
+
// --- Web Push (#3014 / #3060) ---
|
|
2202
|
+
/**
|
|
2203
|
+
* Fetch the deployment's VAPID public key for Web Push subscriptions.
|
|
2204
|
+
*
|
|
2205
|
+
* Hits the unauthenticated discovery endpoint
|
|
2206
|
+
* `GET /.well-known/webpush-vapid-key` directly (not via `request()`) —
|
|
2207
|
+
* it needs no login/API key and predates any session setup. Throws
|
|
2208
|
+
* `GeonicDBError` with `statusCode: 503` if the deployment has no VAPID
|
|
2209
|
+
* keys configured (503 has no dedicated error class — see
|
|
2210
|
+
* `createErrorFromResponse()` in `./errors`).
|
|
2211
|
+
*/
|
|
2212
|
+
async getWebPushVapidKey() {
|
|
2213
|
+
const res = await fetch(this._auth.baseUrl + WEBPUSH_VAPID_KEY_PATH);
|
|
2214
|
+
if (!res.ok) {
|
|
2215
|
+
const e = await res.json().catch(() => ({}));
|
|
2216
|
+
throw createErrorFromResponse(
|
|
2217
|
+
res.status,
|
|
2218
|
+
e,
|
|
2219
|
+
"Failed to fetch Web Push VAPID key: " + res.status
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
const body = await res.json();
|
|
2223
|
+
return body.publicKey;
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Subscribe the current browser to Web Push notifications (#3014).
|
|
2227
|
+
*
|
|
2228
|
+
* Reuses an existing subscription on the service worker registration if
|
|
2229
|
+
* one is already active *and* still matches the resolved VAPID public key
|
|
2230
|
+
* — otherwise (e.g. after the deployment rotated its VAPID keys) it
|
|
2231
|
+
* unsubscribes the stale one and re-subscribes with the current key.
|
|
2232
|
+
* Fetches the deployment's VAPID public key (unless `applicationServerKey`
|
|
2233
|
+
* is given) and calls `PushManager.subscribe()`. Convert the result with
|
|
2234
|
+
* `toNgsiv2WebPushNotification()` / `toNgsiLdWebPushEndpoint()` and create
|
|
2235
|
+
* the NGSI subscription with `request('POST', ...)`.
|
|
2236
|
+
*
|
|
2237
|
+
* Browser-only — throws in environments without Service Worker + Push API
|
|
2238
|
+
* support (e.g. Node.js).
|
|
2239
|
+
*
|
|
2240
|
+
* @example
|
|
2241
|
+
* ```ts
|
|
2242
|
+
* await navigator.serviceWorker.register('/sw.js');
|
|
2243
|
+
* const subscription = await db.subscribeWebPush();
|
|
2244
|
+
* await db.request('POST', '/v2/subscriptions', {
|
|
2245
|
+
* description: 'Web Push notification',
|
|
2246
|
+
* subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },
|
|
2247
|
+
* notification: { webpush: toNgsiv2WebPushNotification(subscription) },
|
|
2248
|
+
* });
|
|
2249
|
+
* ```
|
|
2250
|
+
*/
|
|
2251
|
+
async subscribeWebPush(options) {
|
|
2252
|
+
if (typeof navigator === "undefined" || !navigator.serviceWorker || typeof window === "undefined" || !window.PushManager) {
|
|
2253
|
+
throw new Error(
|
|
2254
|
+
"subscribeWebPush() requires a browser with Service Worker + Push API support"
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
const registration = options?.serviceWorkerRegistration ?? await navigator.serviceWorker.ready;
|
|
2258
|
+
const publicKey = options?.applicationServerKey ?? await this.getWebPushVapidKey();
|
|
2259
|
+
const applicationServerKey = urlBase64ToUint8Array(publicKey);
|
|
2260
|
+
const existing = await registration.pushManager.getSubscription();
|
|
2261
|
+
if (existing) {
|
|
2262
|
+
if (matchesApplicationServerKey(existing, applicationServerKey)) return existing;
|
|
2263
|
+
await existing.unsubscribe();
|
|
2264
|
+
}
|
|
2265
|
+
return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey });
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Unsubscribe the current browser from Web Push notifications, if
|
|
2269
|
+
* subscribed. Returns `false` (no-op) when there is no active
|
|
2270
|
+
* subscription or the environment has no Service Worker support.
|
|
2271
|
+
*/
|
|
2272
|
+
async unsubscribeWebPush(registration) {
|
|
2273
|
+
if (typeof navigator === "undefined" || !navigator.serviceWorker) return false;
|
|
2274
|
+
const reg = registration ?? await navigator.serviceWorker.ready;
|
|
2275
|
+
const existing = await reg.pushManager.getSubscription();
|
|
2276
|
+
if (!existing) return false;
|
|
2277
|
+
return existing.unsubscribe();
|
|
2278
|
+
}
|
|
2141
2279
|
};
|
|
2142
2280
|
var index_default = GeonicDB;
|
|
2143
2281
|
if (typeof window !== "undefined") {
|
|
@@ -2181,6 +2319,9 @@ export {
|
|
|
2181
2319
|
TemporaryPasswordExpiredError,
|
|
2182
2320
|
ValidationError,
|
|
2183
2321
|
buildPathWithParams,
|
|
2184
|
-
index_default as default
|
|
2322
|
+
index_default as default,
|
|
2323
|
+
toNgsiLdWebPushEndpoint,
|
|
2324
|
+
toNgsiv2WebPushNotification,
|
|
2325
|
+
urlBase64ToUint8Array
|
|
2185
2326
|
};
|
|
2186
2327
|
//# sourceMappingURL=geonicdb.mjs.map
|