@realtimexsco/live-chat 1.4.9 → 1.4.11
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/firebase-messaging-sw.js +312 -71
- package/dist/index.cjs +1029 -641
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +128 -37
- package/dist/index.d.ts +128 -37
- package/dist/index.mjs +1019 -637
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +11 -0
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* cp node_modules/@realtimexsco/live-chat/dist/firebase-messaging-sw.js public/
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
/* global firebase, importScripts, BroadcastChannel */
|
|
9
|
+
/* global firebase, importScripts, BroadcastChannel, caches */
|
|
10
10
|
|
|
11
11
|
importScripts(
|
|
12
12
|
"https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js",
|
|
@@ -17,45 +17,15 @@ importScripts(
|
|
|
17
17
|
|
|
18
18
|
var PUSH_BROADCAST_CHANNEL = "realtimex-push";
|
|
19
19
|
var NOTIFICATION_CLICK_SW_TYPE = "realtimex:notification-click";
|
|
20
|
+
var PUSH_DATA_CACHE = "realtimex-push-data-v1";
|
|
21
|
+
var TAG_PREFIX = "realtimex-";
|
|
22
|
+
|
|
23
|
+
/** In-memory fallback when Cache API or FCM click data is empty. */
|
|
24
|
+
var pendingPushDataStore = {};
|
|
20
25
|
|
|
21
26
|
var params = new URL(self.location.href).searchParams;
|
|
22
27
|
var rawConfig = params.get("config");
|
|
23
28
|
|
|
24
|
-
if (rawConfig) {
|
|
25
|
-
try {
|
|
26
|
-
var firebaseConfig = JSON.parse(rawConfig);
|
|
27
|
-
firebase.initializeApp(firebaseConfig);
|
|
28
|
-
var messaging = firebase.messaging();
|
|
29
|
-
|
|
30
|
-
messaging.onBackgroundMessage(function (payload) {
|
|
31
|
-
console.log("[realtimex-push] FCM background payload (raw):", payload);
|
|
32
|
-
console.log("[realtimex-push] FCM background data:", payload && payload.data);
|
|
33
|
-
|
|
34
|
-
var title =
|
|
35
|
-
(payload.notification && payload.notification.title) || "New message";
|
|
36
|
-
var body = (payload.notification && payload.notification.body) || "";
|
|
37
|
-
var data = payload.data || {};
|
|
38
|
-
var tag = "realtimex-" + (data.conversationId || data.messageId || "push");
|
|
39
|
-
|
|
40
|
-
// Show notification ourselves so click always carries `data` for redirect.
|
|
41
|
-
// Same tag replaces FCM auto-notification when both fire.
|
|
42
|
-
return self.registration.showNotification(title, {
|
|
43
|
-
body: body,
|
|
44
|
-
data: data,
|
|
45
|
-
tag: tag,
|
|
46
|
-
renotify: true,
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error("[realtimex] Invalid firebase config in SW URL:", error);
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
console.warn(
|
|
54
|
-
"[realtimex] firebase-messaging-sw.js registered without ?config= — " +
|
|
55
|
-
"push notifications will not be delivered",
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
29
|
function parseMaybeJson(value) {
|
|
60
30
|
if (typeof value !== "string") return value;
|
|
61
31
|
try {
|
|
@@ -65,6 +35,10 @@ function parseMaybeJson(value) {
|
|
|
65
35
|
}
|
|
66
36
|
}
|
|
67
37
|
|
|
38
|
+
function cacheRequest(path) {
|
|
39
|
+
return new Request(new URL(path, self.location.origin).toString());
|
|
40
|
+
}
|
|
41
|
+
|
|
68
42
|
function extractNotificationData(notification) {
|
|
69
43
|
var raw = (notification && notification.data) || {};
|
|
70
44
|
var fcmMsg = parseMaybeJson(raw.FCM_MSG);
|
|
@@ -99,14 +73,26 @@ function resolveConversationId(data) {
|
|
|
99
73
|
).trim();
|
|
100
74
|
}
|
|
101
75
|
|
|
102
|
-
function
|
|
76
|
+
function buildNotificationTag(data) {
|
|
77
|
+
return TAG_PREFIX + (data.conversationId || data.messageId || "push");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildTargetUrl(data) {
|
|
103
81
|
if (data && data.link) return String(data.link);
|
|
104
|
-
if (conversationId) {
|
|
105
|
-
return "/chat?conversationId=" + encodeURIComponent(conversationId);
|
|
106
|
-
}
|
|
107
82
|
return "/chat";
|
|
108
83
|
}
|
|
109
84
|
|
|
85
|
+
function cachePendingOpen(data) {
|
|
86
|
+
return caches.open(PUSH_DATA_CACHE).then(function (cache) {
|
|
87
|
+
return cache.put(
|
|
88
|
+
cacheRequest("pending-open"),
|
|
89
|
+
new Response(JSON.stringify(data), {
|
|
90
|
+
headers: { "Content-Type": "application/json" },
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
110
96
|
function isChatClient(client) {
|
|
111
97
|
try {
|
|
112
98
|
var pathname = new URL(client.url).pathname || "";
|
|
@@ -126,17 +112,238 @@ function notifyPage(message) {
|
|
|
126
112
|
}
|
|
127
113
|
}
|
|
128
114
|
|
|
129
|
-
|
|
130
|
-
|
|
115
|
+
function rememberPushData(data) {
|
|
116
|
+
var messageId = String(data.messageId || data.message_id || "").trim();
|
|
117
|
+
var conversationId = String(data.conversationId || data.conversation_id || "").trim();
|
|
131
118
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
119
|
+
if (messageId) pendingPushDataStore["msg:" + messageId] = data;
|
|
120
|
+
if (conversationId) pendingPushDataStore["conv:" + conversationId] = data;
|
|
121
|
+
|
|
122
|
+
var keys = Object.keys(pendingPushDataStore);
|
|
123
|
+
if (keys.length > 40) {
|
|
124
|
+
keys.slice(0, keys.length - 20).forEach(function (key) {
|
|
125
|
+
delete pendingPushDataStore[key];
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return cachePushData(data);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function recallPushData(data) {
|
|
133
|
+
var messageId = String(
|
|
134
|
+
data.messageId || data.message_id || data.messageID || "",
|
|
135
|
+
).trim();
|
|
136
|
+
var conversationId = String(
|
|
137
|
+
data.conversationId || data.conversation_id || "",
|
|
138
|
+
).trim();
|
|
139
|
+
|
|
140
|
+
if (messageId && pendingPushDataStore["msg:" + messageId]) {
|
|
141
|
+
return pendingPushDataStore["msg:" + messageId];
|
|
142
|
+
}
|
|
143
|
+
if (conversationId && pendingPushDataStore["conv:" + conversationId]) {
|
|
144
|
+
return pendingPushDataStore["conv:" + conversationId];
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function cachePushData(data) {
|
|
150
|
+
var messageId = String(data.messageId || data.message_id || "").trim();
|
|
151
|
+
var conversationId = String(data.conversationId || data.conversation_id || "").trim();
|
|
152
|
+
var entries = [];
|
|
153
|
+
if (messageId) entries.push("push-data/" + messageId);
|
|
154
|
+
if (conversationId) entries.push("push-data/conv/" + conversationId);
|
|
155
|
+
if (!entries.length) return Promise.resolve();
|
|
156
|
+
|
|
157
|
+
var body = JSON.stringify(data);
|
|
158
|
+
|
|
159
|
+
return caches.open(PUSH_DATA_CACHE).then(function (cache) {
|
|
160
|
+
return Promise.all(
|
|
161
|
+
entries.map(function (path) {
|
|
162
|
+
return cache.put(
|
|
163
|
+
cacheRequest(path),
|
|
164
|
+
new Response(body, {
|
|
165
|
+
headers: { "Content-Type": "application/json" },
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function loadCachedPushData(messageId) {
|
|
174
|
+
var key = String(messageId || "").trim();
|
|
175
|
+
if (!key) return Promise.resolve(null);
|
|
176
|
+
return caches
|
|
177
|
+
.open(PUSH_DATA_CACHE)
|
|
178
|
+
.then(function (cache) {
|
|
179
|
+
return cache.match(cacheRequest("push-data/" + key));
|
|
180
|
+
})
|
|
181
|
+
.then(function (res) {
|
|
182
|
+
return res ? res.json() : null;
|
|
183
|
+
})
|
|
184
|
+
.catch(function () {
|
|
185
|
+
return null;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function loadCachedPushDataByConversationId(conversationId) {
|
|
190
|
+
var key = String(conversationId || "").trim();
|
|
191
|
+
if (!key) return Promise.resolve(null);
|
|
192
|
+
return caches
|
|
193
|
+
.open(PUSH_DATA_CACHE)
|
|
194
|
+
.then(function (cache) {
|
|
195
|
+
return cache.match(cacheRequest("push-data/conv/" + key));
|
|
196
|
+
})
|
|
197
|
+
.then(function (res) {
|
|
198
|
+
return res ? res.json() : null;
|
|
199
|
+
})
|
|
200
|
+
.catch(function () {
|
|
201
|
+
return null;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function resolveFromNotificationTag(tag) {
|
|
206
|
+
if (!tag || tag.indexOf(TAG_PREFIX) !== 0) return Promise.resolve(null);
|
|
207
|
+
var id = tag.slice(TAG_PREFIX.length);
|
|
208
|
+
if (!id || id === "push") return Promise.resolve(null);
|
|
209
|
+
|
|
210
|
+
return loadCachedPushData(id).then(function (cached) {
|
|
211
|
+
if (cached) return cached;
|
|
212
|
+
return loadCachedPushDataByConversationId(id);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function resolveNotificationClickData(data, notification) {
|
|
217
|
+
var merged = Object.assign({}, data);
|
|
218
|
+
if (resolveConversationId(merged)) return Promise.resolve(merged);
|
|
219
|
+
|
|
220
|
+
var recalled = recallPushData(merged);
|
|
221
|
+
if (recalled) merged = Object.assign({}, recalled, merged);
|
|
222
|
+
if (resolveConversationId(merged)) return Promise.resolve(merged);
|
|
223
|
+
|
|
224
|
+
var messageId = String(
|
|
225
|
+
merged.messageId || merged.message_id || merged.messageID || "",
|
|
226
|
+
).trim();
|
|
227
|
+
|
|
228
|
+
return resolveFromNotificationTag(notification && notification.tag)
|
|
229
|
+
.then(function (fromTag) {
|
|
230
|
+
if (fromTag) merged = Object.assign({}, fromTag, merged);
|
|
231
|
+
if (resolveConversationId(merged)) return merged;
|
|
232
|
+
|
|
233
|
+
return loadCachedPushData(messageId).then(function (cached) {
|
|
234
|
+
if (cached) merged = Object.assign({}, cached, merged);
|
|
235
|
+
if (resolveConversationId(merged)) return merged;
|
|
236
|
+
|
|
237
|
+
return loadCachedPushDataByConversationId(
|
|
238
|
+
merged.conversationId || merged.conversation_id,
|
|
239
|
+
).then(function (byConv) {
|
|
240
|
+
if (byConv) merged = Object.assign({}, byConv, merged);
|
|
241
|
+
return merged;
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Keep only our tagged notification that carries redirect data. */
|
|
248
|
+
function pruneDuplicateNotifications(keepTag) {
|
|
249
|
+
return self.registration.getNotifications().then(function (notifications) {
|
|
250
|
+
notifications.forEach(function (n) {
|
|
251
|
+
var cid = resolveConversationId(extractNotificationData(n));
|
|
252
|
+
if (n.tag === keepTag && cid) return;
|
|
253
|
+
if (!cid || n.tag !== keepTag) n.close();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* One background OS notification with redirect `data`.
|
|
260
|
+
* Closes FCM auto duplicates that lack click data.
|
|
261
|
+
*/
|
|
262
|
+
function showSingleBackgroundNotification(title, body, data) {
|
|
263
|
+
var tag = buildNotificationTag(data);
|
|
264
|
+
|
|
265
|
+
return rememberPushData(data)
|
|
266
|
+
.then(function () {
|
|
267
|
+
return self.registration.getNotifications();
|
|
268
|
+
})
|
|
269
|
+
.then(function (notifications) {
|
|
270
|
+
notifications.forEach(function (n) {
|
|
271
|
+
n.close();
|
|
272
|
+
});
|
|
273
|
+
return self.registration.showNotification(title, {
|
|
274
|
+
body: body,
|
|
275
|
+
data: data,
|
|
276
|
+
tag: tag,
|
|
277
|
+
});
|
|
278
|
+
})
|
|
279
|
+
.then(function () {
|
|
280
|
+
var delays = [80, 250, 600, 1200];
|
|
281
|
+
return delays.reduce(function (chain, ms) {
|
|
282
|
+
return chain.then(function () {
|
|
283
|
+
return new Promise(function (resolve) {
|
|
284
|
+
setTimeout(function () {
|
|
285
|
+
pruneDuplicateNotifications(tag).then(resolve);
|
|
286
|
+
}, ms);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}, Promise.resolve());
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (rawConfig) {
|
|
294
|
+
try {
|
|
295
|
+
var firebaseConfig = JSON.parse(rawConfig);
|
|
296
|
+
firebase.initializeApp(firebaseConfig);
|
|
297
|
+
var messaging = firebase.messaging();
|
|
298
|
+
|
|
299
|
+
messaging.onBackgroundMessage(function (payload) {
|
|
300
|
+
console.log("[realtimex-push] FCM background payload (raw):", payload);
|
|
301
|
+
console.log("[realtimex-push] FCM background data:", payload && payload.data);
|
|
302
|
+
|
|
303
|
+
return clients
|
|
304
|
+
.matchAll({ type: "window", includeUncontrolled: true })
|
|
305
|
+
.then(function (windowClients) {
|
|
306
|
+
var hasVisibleClient = windowClients.some(function (client) {
|
|
307
|
+
return client.visibilityState === "visible";
|
|
308
|
+
});
|
|
309
|
+
if (hasVisibleClient) {
|
|
310
|
+
console.log(
|
|
311
|
+
"[realtimex-push] app visible — skip OS notification (use in-app toast)",
|
|
312
|
+
);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
var data = payload.data || {};
|
|
317
|
+
var title =
|
|
318
|
+
(payload.notification && payload.notification.title) ||
|
|
319
|
+
data.title ||
|
|
320
|
+
"New message";
|
|
321
|
+
var body =
|
|
322
|
+
(payload.notification && payload.notification.body) ||
|
|
323
|
+
data.body ||
|
|
324
|
+
"";
|
|
325
|
+
|
|
326
|
+
console.log(
|
|
327
|
+
"[realtimex-push] single background notification:",
|
|
328
|
+
data,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return showSingleBackgroundNotification(title, body, data);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error("[realtimex] Invalid firebase config in SW URL:", error);
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
console.warn(
|
|
339
|
+
"[realtimex] firebase-messaging-sw.js registered without ?config= — " +
|
|
340
|
+
"push notifications will not be delivered",
|
|
135
341
|
);
|
|
342
|
+
}
|
|
136
343
|
|
|
137
|
-
|
|
344
|
+
function deliverNotificationClick(data) {
|
|
138
345
|
var conversationId = resolveConversationId(data);
|
|
139
|
-
var targetUrl = buildTargetUrl(data
|
|
346
|
+
var targetUrl = buildTargetUrl(data);
|
|
140
347
|
var message = {
|
|
141
348
|
type: NOTIFICATION_CLICK_SW_TYPE,
|
|
142
349
|
conversationId: conversationId,
|
|
@@ -147,30 +354,64 @@ self.addEventListener("notificationclick", function (event) {
|
|
|
147
354
|
console.log("[realtimex-push] notificationclick conversationId:", conversationId);
|
|
148
355
|
console.log("[realtimex-push] notificationclick targetUrl:", targetUrl);
|
|
149
356
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
for (var i = 0; i < windowClients.length; i++) {
|
|
158
|
-
var client = windowClients[i];
|
|
159
|
-
if (!anyClient) anyClient = client;
|
|
160
|
-
if (!chatClient && isChatClient(client)) chatClient = client;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
var target = chatClient || anyClient;
|
|
164
|
-
if (target && "focus" in target) {
|
|
165
|
-
return target.focus().then(function () {
|
|
166
|
-
notifyPage(message);
|
|
167
|
-
if (target.postMessage) target.postMessage(message);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
357
|
+
if (!conversationId) {
|
|
358
|
+
console.warn(
|
|
359
|
+
"[realtimex-push] notificationclick missing conversationId — redirect skipped",
|
|
360
|
+
);
|
|
361
|
+
return Promise.resolve();
|
|
362
|
+
}
|
|
170
363
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
364
|
+
return clients
|
|
365
|
+
.matchAll({ type: "window", includeUncontrolled: true })
|
|
366
|
+
.then(function (windowClients) {
|
|
367
|
+
var chatClient = null;
|
|
368
|
+
var anyClient = null;
|
|
369
|
+
|
|
370
|
+
for (var i = 0; i < windowClients.length; i++) {
|
|
371
|
+
var client = windowClients[i];
|
|
372
|
+
if (!anyClient) anyClient = client;
|
|
373
|
+
if (!chatClient && isChatClient(client)) chatClient = client;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
var target = chatClient || anyClient;
|
|
377
|
+
|
|
378
|
+
if (target && "focus" in target) {
|
|
379
|
+
return target.focus().then(function () {
|
|
380
|
+
notifyPage(message);
|
|
381
|
+
if (target.postMessage) target.postMessage(message);
|
|
382
|
+
|
|
383
|
+
if (conversationId && !isChatClient(target) && clients.openWindow) {
|
|
384
|
+
return cachePendingOpen(data).then(function () {
|
|
385
|
+
return clients.openWindow(targetUrl);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return undefined;
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
notifyPage(message);
|
|
393
|
+
if (clients.openWindow) {
|
|
394
|
+
return cachePendingOpen(data).then(function () {
|
|
395
|
+
return clients.openWindow(targetUrl);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
return undefined;
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
self.addEventListener("notificationclick", function (event) {
|
|
403
|
+
event.notification.close();
|
|
404
|
+
|
|
405
|
+
console.log(
|
|
406
|
+
"[realtimex-push] notificationclick raw notification.data:",
|
|
407
|
+
event.notification && event.notification.data,
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
var rawData = extractNotificationData(event.notification);
|
|
411
|
+
|
|
412
|
+
event.waitUntil(
|
|
413
|
+
resolveNotificationClickData(rawData, event.notification).then(function (data) {
|
|
414
|
+
return deliverNotificationClick(data);
|
|
415
|
+
}),
|
|
175
416
|
);
|
|
176
417
|
});
|