@rivium/push-web 0.1.3 → 0.1.4
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/index.d.ts +9 -0
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/service-worker.js +191 -5
package/package.json
CHANGED
package/service-worker.js
CHANGED
|
@@ -6,7 +6,73 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Cache name for offline support
|
|
9
|
-
const CACHE_NAME = 'rivium-push-
|
|
9
|
+
const CACHE_NAME = 'rivium-push-v0.1.4';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Config passed by the SDK on the registration URL.
|
|
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.
|
|
18
|
+
*/
|
|
19
|
+
const RIVIUM_CONFIG = (() => {
|
|
20
|
+
try {
|
|
21
|
+
const params = new URL(self.location.href).searchParams;
|
|
22
|
+
return {
|
|
23
|
+
apiKey: params.get('riviumApiKey') || null,
|
|
24
|
+
serverUrl: params.get('riviumServerUrl') || 'https://push-api.rivium.co',
|
|
25
|
+
deviceId: params.get('riviumDeviceId') || null,
|
|
26
|
+
};
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return { apiKey: null, serverUrl: 'https://push-api.rivium.co', deviceId: null };
|
|
29
|
+
}
|
|
30
|
+
})();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Confirm delivery to Rivium Push.
|
|
34
|
+
*
|
|
35
|
+
* Web Push, APNs and FCM all report only that the push service *accepted* a
|
|
36
|
+
* notification — none of them confirm it reached the device. This ack is the
|
|
37
|
+
* only signal that it actually arrived, so the dashboard can distinguish
|
|
38
|
+
* "accepted" from "delivered".
|
|
39
|
+
*
|
|
40
|
+
* Best-effort: a failure here must never stop the notification being shown.
|
|
41
|
+
*/
|
|
42
|
+
function riviumReportDelivered(messageId) {
|
|
43
|
+
if (!messageId || !RIVIUM_CONFIG.apiKey || !RIVIUM_CONFIG.deviceId) {
|
|
44
|
+
console.warn('[RiviumPush SW] Delivery ack skipped — missing messageId or SDK config', {
|
|
45
|
+
messageId: messageId || null,
|
|
46
|
+
hasApiKey: !!RIVIUM_CONFIG.apiKey,
|
|
47
|
+
hasDeviceId: !!RIVIUM_CONFIG.deviceId,
|
|
48
|
+
});
|
|
49
|
+
return Promise.resolve();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// No `keepalive`: event.waitUntil already holds the worker open, and
|
|
53
|
+
// keepalive is unreliable for fetches issued from a service worker.
|
|
54
|
+
return fetch(`${RIVIUM_CONFIG.serverUrl}/receipts/delivered`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
'x-api-key': RIVIUM_CONFIG.apiKey,
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify({ messageId, deviceId: RIVIUM_CONFIG.deviceId }),
|
|
61
|
+
})
|
|
62
|
+
.then((res) => {
|
|
63
|
+
// A non-2xx is a failure — resolving on it would hide the problem the
|
|
64
|
+
// same way the server used to hide an ack for a receipt that did not
|
|
65
|
+
// exist yet.
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
console.warn('[RiviumPush SW] Delivery ack rejected', res.status);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
console.log('[RiviumPush SW] Delivery confirmed', messageId);
|
|
71
|
+
})
|
|
72
|
+
.catch((err) => {
|
|
73
|
+
console.warn('[RiviumPush SW] Delivery ack failed:', err && err.message);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
10
76
|
|
|
11
77
|
// Install event
|
|
12
78
|
self.addEventListener('install', (event) => {
|
|
@@ -50,10 +116,33 @@ self.addEventListener('push', (event) => {
|
|
|
50
116
|
|
|
51
117
|
const options = {
|
|
52
118
|
body: localizedBody || data.body,
|
|
53
|
-
|
|
54
|
-
|
|
119
|
+
// Icons must resolve to a real file. Android Chrome silently drops a
|
|
120
|
+
// notification when the icon 404s (desktop is more forgiving), so keep
|
|
121
|
+
// /icon.png and /badge.png present in your public directory, or always
|
|
122
|
+
// send iconUrl from the backend.
|
|
123
|
+
icon: data.iconUrl || data.icon || '/icon.png',
|
|
124
|
+
// `badge` is the small monochrome icon Android shows in the status bar.
|
|
125
|
+
badge: data.badgeIcon || data.iconUrl || data.icon || '/badge.png',
|
|
55
126
|
image: data.imageUrl || data.image,
|
|
56
|
-
|
|
127
|
+
// Per-message uniqueness by default. A fixed tag collapses every push
|
|
128
|
+
// into one slot in the Android shade — new notifications silently replace
|
|
129
|
+
// older ones and the user never sees the second, third, … arrive. The
|
|
130
|
+
// backend still opts into grouping by sending an explicit tag or
|
|
131
|
+
// collapseKey (e.g. to fold "N liked your post" into one).
|
|
132
|
+
tag:
|
|
133
|
+
data.tag ||
|
|
134
|
+
data.collapseKey ||
|
|
135
|
+
data.threadId ||
|
|
136
|
+
data.messageId ||
|
|
137
|
+
`rivium-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
138
|
+
// renotify forces Android Chrome to actually surface the notification
|
|
139
|
+
// while Chrome is backgrounded but not closed. Without it Chrome treats
|
|
140
|
+
// the origin as "about to be focused" and swallows showNotification — the
|
|
141
|
+
// worker logs success and the shade stays empty. Harmless elsewhere.
|
|
142
|
+
renotify: true,
|
|
143
|
+
// A second signal to Android that this is a fresh user-visible event.
|
|
144
|
+
// Ignored on desktop.
|
|
145
|
+
vibrate: [200, 100, 200],
|
|
57
146
|
data: {
|
|
58
147
|
...data.data,
|
|
59
148
|
deepLink: data.deepLink,
|
|
@@ -78,8 +167,105 @@ self.addEventListener('push', (event) => {
|
|
|
78
167
|
// Show notification with potentially localized title
|
|
79
168
|
const title = localizedTitle || data.title;
|
|
80
169
|
|
|
170
|
+
event.waitUntil((async () => {
|
|
171
|
+
// Mirror the payload to any visible page. On mobile the app is usually
|
|
172
|
+
// backgrounded or the screen is locked, so the page-side connection is
|
|
173
|
+
// asleep and only the worker sees the push. Pages should dedupe by
|
|
174
|
+
// messageId, since a foreground client may receive it both ways.
|
|
175
|
+
try {
|
|
176
|
+
const clientsList = await self.clients.matchAll({
|
|
177
|
+
type: 'window',
|
|
178
|
+
includeUncontrolled: true,
|
|
179
|
+
});
|
|
180
|
+
for (const client of clientsList) {
|
|
181
|
+
client.postMessage({
|
|
182
|
+
type: 'rivium-push-message',
|
|
183
|
+
message: {
|
|
184
|
+
title,
|
|
185
|
+
body: localizedBody || data.body,
|
|
186
|
+
data: data.data || {},
|
|
187
|
+
deepLink: data.deepLink,
|
|
188
|
+
messageId: data.messageId,
|
|
189
|
+
campaignId: data.campaignId,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.warn('[RiviumPush SW] client postMessage failed:', err);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Always show the OS notification: Chrome's user-visible contract revokes
|
|
198
|
+
// subscriptions that push silently. Wrapped so an Android quirk that would
|
|
199
|
+
// otherwise fail silently surfaces here; the retry drops the optional
|
|
200
|
+
// fields (icon/badge/image/actions) that are the usual cause.
|
|
201
|
+
try {
|
|
202
|
+
await self.registration.showNotification(title, options);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.warn('[RiviumPush SW] showNotification failed, retrying minimal', err);
|
|
205
|
+
try {
|
|
206
|
+
await self.registration.showNotification(title, {
|
|
207
|
+
body: options.body,
|
|
208
|
+
tag: options.tag,
|
|
209
|
+
data: options.data,
|
|
210
|
+
});
|
|
211
|
+
} catch (err2) {
|
|
212
|
+
console.error('[RiviumPush SW] showNotification (minimal) failed too', err2);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Confirm arrival so the dashboard can show `delivered`, not only `sent`.
|
|
217
|
+
await riviumReportDelivered(data.messageId);
|
|
218
|
+
})());
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The browser can rotate a push subscription at any time — after a long idle
|
|
223
|
+
* period, a browser update, or when it decides the old endpoint is stale.
|
|
224
|
+
* Without this handler the old endpoint stays registered server-side, keeps
|
|
225
|
+
* returning 410 Gone, and the user silently stops receiving notifications.
|
|
226
|
+
*
|
|
227
|
+
* Re-subscribe with the same VAPID key and register the new endpoint.
|
|
228
|
+
*/
|
|
229
|
+
self.addEventListener('pushsubscriptionchange', (event) => {
|
|
230
|
+
console.log('[RiviumPush SW] Push subscription changed, re-subscribing');
|
|
231
|
+
|
|
232
|
+
const applicationServerKey =
|
|
233
|
+
(event.oldSubscription &&
|
|
234
|
+
event.oldSubscription.options &&
|
|
235
|
+
event.oldSubscription.options.applicationServerKey) ||
|
|
236
|
+
null;
|
|
237
|
+
|
|
238
|
+
if (!applicationServerKey || !RIVIUM_CONFIG.apiKey || !RIVIUM_CONFIG.deviceId) {
|
|
239
|
+
console.warn(
|
|
240
|
+
'[RiviumPush SW] Cannot re-subscribe: missing VAPID key or SDK config',
|
|
241
|
+
);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
81
245
|
event.waitUntil(
|
|
82
|
-
self.registration.
|
|
246
|
+
self.registration.pushManager
|
|
247
|
+
.subscribe({ userVisibleOnly: true, applicationServerKey })
|
|
248
|
+
.then((subscription) =>
|
|
249
|
+
fetch(`${RIVIUM_CONFIG.serverUrl}/devices/register`, {
|
|
250
|
+
method: 'POST',
|
|
251
|
+
headers: {
|
|
252
|
+
'Content-Type': 'application/json',
|
|
253
|
+
'x-api-key': RIVIUM_CONFIG.apiKey,
|
|
254
|
+
},
|
|
255
|
+
body: JSON.stringify({
|
|
256
|
+
deviceId: RIVIUM_CONFIG.deviceId,
|
|
257
|
+
platform: 'web',
|
|
258
|
+
appIdentifier: self.location.origin,
|
|
259
|
+
webPushSubscription: subscription.toJSON(),
|
|
260
|
+
}),
|
|
261
|
+
}),
|
|
262
|
+
)
|
|
263
|
+
.then(() => {
|
|
264
|
+
console.log('[RiviumPush SW] Re-subscribed and re-registered');
|
|
265
|
+
})
|
|
266
|
+
.catch((err) => {
|
|
267
|
+
console.error('[RiviumPush SW] Re-subscribe failed:', err && err.message);
|
|
268
|
+
}),
|
|
83
269
|
);
|
|
84
270
|
});
|
|
85
271
|
|