@rivium/push-web 0.1.7 → 0.1.8

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/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export declare const SDK_NAME = "web";
2
- export declare const SDK_VERSION = "0.1.7";
2
+ export declare const SDK_VERSION = "0.1.8";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rivium/push-web",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Web Push SDK for browsers - Firebase alternative that works everywhere",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/service-worker.js CHANGED
@@ -6,30 +6,100 @@
6
6
  */
7
7
 
8
8
  // Cache name for offline support
9
- const CACHE_NAME = 'rivium-push-v0.1.5';
9
+ const CACHE_NAME = 'rivium-push-v0.1.8';
10
10
 
11
11
  /**
12
- * Config passed by the SDK on the registration URL.
12
+ * Where the worker gets its API key and device id.
13
13
  *
14
- * A service worker is terminated between pushes, so anything kept in memory is
15
- * gone by the time the next one arrives. The script URL is persisted by the
16
- * browser, so reading config from its query string works on every wake-up
17
- * without needing IndexedDB.
14
+ * A service worker is terminated between pushes, so nothing can be kept in
15
+ * memory. Two durable places hold the config, and both are read:
16
+ *
17
+ * 1. The registration URL's query string. The browser persists the script
18
+ * URL, so this is available immediately on every wake-up.
19
+ * 2. IndexedDB (`rivium-push` / `config`), written by the SDK on every load.
20
+ * This is the fallback for a worker registered WITHOUT the query string -
21
+ * an app (or another library, or a hot reload in development) that calls
22
+ * `navigator.serviceWorker.register('/rivium-push-sw.js')` itself. Such a
23
+ * worker still shows notifications, but used to silently stop confirming
24
+ * delivery and stop re-registering a changed subscription.
25
+ *
26
+ * The URL wins field by field, so an explicit registration still decides.
18
27
  */
19
- const RIVIUM_CONFIG = (() => {
28
+ const RIVIUM_DB_NAME = 'rivium-push';
29
+ const RIVIUM_DB_STORE = 'config';
30
+ const RIVIUM_DB_KEY = 'config';
31
+ const RIVIUM_DEFAULT_SERVER_URL = 'https://push-api.rivium.co';
32
+
33
+ function riviumConfigFromUrl() {
20
34
  try {
21
35
  const params = new URL(self.location.href).searchParams;
22
36
  return {
23
37
  apiKey: params.get('riviumApiKey') || null,
24
- serverUrl: params.get('riviumServerUrl') || 'https://push-api.rivium.co',
38
+ serverUrl: params.get('riviumServerUrl') || null,
25
39
  deviceId: params.get('riviumDeviceId') || null,
26
40
  // Added in 0.1.5. SDKs older than 0.1.5 don't pass it.
27
41
  sdkVersion: params.get('riviumSdkVersion') || null,
28
42
  };
29
43
  } catch (e) {
30
- return { apiKey: null, serverUrl: 'https://push-api.rivium.co', deviceId: null, sdkVersion: null };
44
+ return { apiKey: null, serverUrl: null, deviceId: null, sdkVersion: null };
45
+ }
46
+ }
47
+
48
+ /** The SDK's last known config, or null. Never rejects: storage may be unavailable. */
49
+ function riviumConfigFromStorage() {
50
+ return new Promise((resolve) => {
51
+ let request;
52
+ try {
53
+ request = indexedDB.open(RIVIUM_DB_NAME, 1);
54
+ } catch (e) {
55
+ return resolve(null);
56
+ }
57
+ // The SDK creates the store; a worker that gets here first must not leave
58
+ // an empty database behind with a half-made schema.
59
+ request.onupgradeneeded = () => {
60
+ try {
61
+ request.result.createObjectStore(RIVIUM_DB_STORE);
62
+ } catch (e) {
63
+ /* already there */
64
+ }
65
+ };
66
+ request.onerror = () => resolve(null);
67
+ request.onsuccess = () => {
68
+ const db = request.result;
69
+ try {
70
+ const get = db.transaction(RIVIUM_DB_STORE, 'readonly').objectStore(RIVIUM_DB_STORE).get(RIVIUM_DB_KEY);
71
+ get.onsuccess = () => {
72
+ resolve(get.result || null);
73
+ db.close();
74
+ };
75
+ get.onerror = () => {
76
+ resolve(null);
77
+ db.close();
78
+ };
79
+ } catch (e) {
80
+ resolve(null);
81
+ db.close();
82
+ }
83
+ };
84
+ });
85
+ }
86
+
87
+ let riviumConfigPromise = null;
88
+
89
+ /** Resolved config for this wake-up. Storage is read only when the URL is short of something. */
90
+ function riviumConfig() {
91
+ const fromUrl = riviumConfigFromUrl();
92
+ if (fromUrl.apiKey && fromUrl.deviceId) {
93
+ return Promise.resolve({ ...fromUrl, serverUrl: fromUrl.serverUrl || RIVIUM_DEFAULT_SERVER_URL });
31
94
  }
32
- })();
95
+ riviumConfigPromise = riviumConfigPromise || riviumConfigFromStorage();
96
+ return riviumConfigPromise.then((stored) => ({
97
+ apiKey: fromUrl.apiKey || (stored && stored.apiKey) || null,
98
+ serverUrl: fromUrl.serverUrl || (stored && stored.serverUrl) || RIVIUM_DEFAULT_SERVER_URL,
99
+ deviceId: fromUrl.deviceId || (stored && stored.deviceId) || null,
100
+ sdkVersion: fromUrl.sdkVersion || (stored && stored.sdkVersion) || null,
101
+ }));
102
+ }
33
103
 
34
104
  /**
35
105
  * Confirm delivery to Rivium Push.
@@ -42,38 +112,47 @@ const RIVIUM_CONFIG = (() => {
42
112
  * Best-effort: a failure here must never stop the notification being shown.
43
113
  */
44
114
  function riviumReportDelivered(messageId) {
45
- if (!messageId || !RIVIUM_CONFIG.apiKey || !RIVIUM_CONFIG.deviceId) {
46
- console.warn('[RiviumPush SW] Delivery ack skipped missing messageId or SDK config', {
47
- messageId: messageId || null,
48
- hasApiKey: !!RIVIUM_CONFIG.apiKey,
49
- hasDeviceId: !!RIVIUM_CONFIG.deviceId,
50
- });
115
+ if (!messageId) {
116
+ console.warn('[RiviumPush SW] Delivery ack skipped - the push carried no message id');
51
117
  return Promise.resolve();
52
118
  }
53
119
 
54
- // No `keepalive`: event.waitUntil already holds the worker open, and
55
- // keepalive is unreliable for fetches issued from a service worker.
56
- return fetch(`${RIVIUM_CONFIG.serverUrl}/receipts/delivered`, {
57
- method: 'POST',
58
- headers: {
59
- 'Content-Type': 'application/json',
60
- 'x-api-key': RIVIUM_CONFIG.apiKey,
61
- },
62
- body: JSON.stringify({ messageId, deviceId: RIVIUM_CONFIG.deviceId }),
63
- })
64
- .then((res) => {
65
- // A non-2xx is a failure — resolving on it would hide the problem the
66
- // same way the server used to hide an ack for a receipt that did not
67
- // exist yet.
68
- if (!res.ok) {
69
- console.warn('[RiviumPush SW] Delivery ack rejected', res.status);
70
- return;
71
- }
72
- console.log('[RiviumPush SW] Delivery confirmed', messageId);
120
+ return riviumConfig().then((config) => {
121
+ if (!config.apiKey || !config.deviceId) {
122
+ console.warn(
123
+ '[RiviumPush SW] Delivery ack skipped - no SDK config in the worker URL or in storage: ' +
124
+ (!config.apiKey ? 'apiKey' : '') +
125
+ (!config.apiKey && !config.deviceId ? ' and ' : '') +
126
+ (!config.deviceId ? 'deviceId' : '') +
127
+ ' missing. Register the worker through the SDK (init/register) so it can confirm delivery.',
128
+ );
129
+ return;
130
+ }
131
+
132
+ // No `keepalive`: event.waitUntil already holds the worker open, and
133
+ // keepalive is unreliable for fetches issued from a service worker.
134
+ return fetch(`${config.serverUrl}/receipts/delivered`, {
135
+ method: 'POST',
136
+ headers: {
137
+ 'Content-Type': 'application/json',
138
+ 'x-api-key': config.apiKey,
139
+ },
140
+ body: JSON.stringify({ messageId, deviceId: config.deviceId }),
73
141
  })
74
- .catch((err) => {
75
- console.warn('[RiviumPush SW] Delivery ack failed:', err && err.message);
76
- });
142
+ .then((res) => {
143
+ // A non-2xx is a failure - resolving on it would hide the problem the
144
+ // same way the server used to hide an ack for a receipt that did not
145
+ // exist yet.
146
+ if (!res.ok) {
147
+ console.warn('[RiviumPush SW] Delivery ack rejected', res.status);
148
+ return;
149
+ }
150
+ console.log('[RiviumPush SW] Delivery confirmed', messageId);
151
+ })
152
+ .catch((err) => {
153
+ console.warn('[RiviumPush SW] Delivery ack failed:', err && err.message);
154
+ });
155
+ });
77
156
  }
78
157
 
79
158
  // Install event
@@ -237,36 +316,43 @@ self.addEventListener('pushsubscriptionchange', (event) => {
237
316
  event.oldSubscription.options.applicationServerKey) ||
238
317
  null;
239
318
 
240
- if (!applicationServerKey || !RIVIUM_CONFIG.apiKey || !RIVIUM_CONFIG.deviceId) {
241
- console.warn(
242
- '[RiviumPush SW] Cannot re-subscribe: missing VAPID key or SDK config',
243
- );
319
+ if (!applicationServerKey) {
320
+ console.warn('[RiviumPush SW] Cannot re-subscribe: the old subscription carried no VAPID key');
244
321
  return;
245
322
  }
246
323
 
247
324
  event.waitUntil(
248
- self.registration.pushManager
249
- .subscribe({ userVisibleOnly: true, applicationServerKey })
250
- .then((subscription) =>
251
- fetch(`${RIVIUM_CONFIG.serverUrl}/devices/register`, {
252
- method: 'POST',
253
- headers: {
254
- 'Content-Type': 'application/json',
255
- 'x-api-key': RIVIUM_CONFIG.apiKey,
256
- },
257
- body: JSON.stringify({
258
- deviceId: RIVIUM_CONFIG.deviceId,
259
- platform: 'web',
260
- appIdentifier: self.location.origin,
261
- webPushSubscription: subscription.toJSON(),
262
- // 0.1.5: SDK identity (body, not header, to avoid a CORS preflight).
263
- sdkName: 'web',
264
- ...(RIVIUM_CONFIG.sdkVersion ? { sdkVersion: RIVIUM_CONFIG.sdkVersion } : {}),
265
- }),
266
- }),
267
- )
268
- .then(() => {
269
- console.log('[RiviumPush SW] Re-subscribed and re-registered');
325
+ riviumConfig()
326
+ .then((config) => {
327
+ if (!config.apiKey || !config.deviceId) {
328
+ console.warn(
329
+ '[RiviumPush SW] Cannot re-subscribe: no SDK config in the worker URL or in storage',
330
+ );
331
+ return;
332
+ }
333
+ return self.registration.pushManager
334
+ .subscribe({ userVisibleOnly: true, applicationServerKey })
335
+ .then((subscription) =>
336
+ fetch(`${config.serverUrl}/devices/register`, {
337
+ method: 'POST',
338
+ headers: {
339
+ 'Content-Type': 'application/json',
340
+ 'x-api-key': config.apiKey,
341
+ },
342
+ body: JSON.stringify({
343
+ deviceId: config.deviceId,
344
+ platform: 'web',
345
+ appIdentifier: self.location.origin,
346
+ webPushSubscription: subscription.toJSON(),
347
+ // 0.1.5: SDK identity (body, not header, to avoid a CORS preflight).
348
+ sdkName: 'web',
349
+ ...(config.sdkVersion ? { sdkVersion: config.sdkVersion } : {}),
350
+ }),
351
+ }),
352
+ )
353
+ .then(() => {
354
+ console.log('[RiviumPush SW] Re-subscribed and re-registered');
355
+ });
270
356
  })
271
357
  .catch((err) => {
272
358
  console.error('[RiviumPush SW] Re-subscribe failed:', err && err.message);