@realtimexsco/live-chat 1.4.8 → 1.4.10
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 +338 -75
- package/dist/index.cjs +951 -666
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +27 -19
- package/dist/index.d.ts +27 -19
- package/dist/index.mjs +941 -662
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +11 -0
- package/package.json +1 -1
|
@@ -4,15 +4,9 @@
|
|
|
4
4
|
* /firebase-messaging-sw.js:
|
|
5
5
|
*
|
|
6
6
|
* cp node_modules/@realtimexsco/live-chat/dist/firebase-messaging-sw.js public/
|
|
7
|
-
*
|
|
8
|
-
* Do NOT edit the Firebase config into this file — the chat package passes it
|
|
9
|
-
* via the registration URL (?config=...), so the copy stays generic.
|
|
10
|
-
*
|
|
11
|
-
* If your app already has its own service worker at the root scope, merge the
|
|
12
|
-
* contents of this file into it instead (two workers cannot share a scope).
|
|
13
7
|
*/
|
|
14
8
|
|
|
15
|
-
/* global firebase, importScripts */
|
|
9
|
+
/* global firebase, importScripts, BroadcastChannel, caches */
|
|
16
10
|
|
|
17
11
|
importScripts(
|
|
18
12
|
"https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js",
|
|
@@ -21,47 +15,37 @@ importScripts(
|
|
|
21
15
|
"https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js",
|
|
22
16
|
);
|
|
23
17
|
|
|
18
|
+
var PUSH_BROADCAST_CHANNEL = "realtimex-push";
|
|
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 = {};
|
|
25
|
+
|
|
24
26
|
var params = new URL(self.location.href).searchParams;
|
|
25
27
|
var rawConfig = params.get("config");
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
function parseMaybeJson(value) {
|
|
30
|
+
if (typeof value !== "string") return value;
|
|
28
31
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Background messages that carry a `notification` payload are displayed
|
|
34
|
-
// by FCM automatically; initializing messaging here is what enables it.
|
|
35
|
-
var messaging = firebase.messaging();
|
|
36
|
-
|
|
37
|
-
// Log full backend payload when a push arrives in the background.
|
|
38
|
-
// Check this in DevTools → Application → Service Workers → console,
|
|
39
|
-
// or Chrome → chrome://serviceworker-internals
|
|
40
|
-
messaging.onBackgroundMessage(function (payload) {
|
|
41
|
-
console.log("[realtimex-push] FCM background payload (raw):", payload);
|
|
42
|
-
console.log("[realtimex-push] FCM background data:", payload && payload.data);
|
|
43
|
-
console.log(
|
|
44
|
-
"[realtimex-push] FCM background notification:",
|
|
45
|
-
payload && payload.notification,
|
|
46
|
-
);
|
|
47
|
-
});
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error("[realtimex] Invalid firebase config in SW URL:", error);
|
|
32
|
+
return JSON.parse(value);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return value;
|
|
50
35
|
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cacheRequest(path) {
|
|
39
|
+
return new Request(new URL(path, self.location.origin).toString());
|
|
56
40
|
}
|
|
57
41
|
|
|
58
42
|
function extractNotificationData(notification) {
|
|
59
43
|
var raw = (notification && notification.data) || {};
|
|
44
|
+
var fcmMsg = parseMaybeJson(raw.FCM_MSG);
|
|
60
45
|
var nested =
|
|
61
|
-
|
|
62
|
-
? raw.FCM_MSG.data
|
|
63
|
-
: null;
|
|
46
|
+
fcmMsg && fcmMsg.data && typeof fcmMsg.data === "object" ? fcmMsg.data : null;
|
|
64
47
|
var merged = {};
|
|
48
|
+
|
|
65
49
|
if (nested) {
|
|
66
50
|
for (var key in nested) {
|
|
67
51
|
if (Object.prototype.hasOwnProperty.call(nested, key)) {
|
|
@@ -69,11 +53,13 @@ function extractNotificationData(notification) {
|
|
|
69
53
|
}
|
|
70
54
|
}
|
|
71
55
|
}
|
|
56
|
+
|
|
72
57
|
for (var key2 in raw) {
|
|
73
58
|
if (Object.prototype.hasOwnProperty.call(raw, key2) && key2 !== "FCM_MSG") {
|
|
74
59
|
merged[key2] = raw[key2];
|
|
75
60
|
}
|
|
76
61
|
}
|
|
62
|
+
|
|
77
63
|
return merged;
|
|
78
64
|
}
|
|
79
65
|
|
|
@@ -84,19 +70,29 @@ function resolveConversationId(data) {
|
|
|
84
70
|
data.conversation_id ||
|
|
85
71
|
data.conversationID ||
|
|
86
72
|
"",
|
|
87
|
-
);
|
|
73
|
+
).trim();
|
|
88
74
|
}
|
|
89
75
|
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (conversationId) {
|
|
94
|
-
return "/chat?conversationId=" + encodeURIComponent(conversationId);
|
|
95
|
-
}
|
|
76
|
+
function buildNotificationTag(data) {
|
|
77
|
+
return TAG_PREFIX + (data.conversationId || data.messageId || "push");
|
|
78
|
+
}
|
|
96
79
|
|
|
80
|
+
function buildTargetUrl(data) {
|
|
81
|
+
if (data && data.link) return String(data.link);
|
|
97
82
|
return "/chat";
|
|
98
83
|
}
|
|
99
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
|
+
|
|
100
96
|
function isChatClient(client) {
|
|
101
97
|
try {
|
|
102
98
|
var pathname = new URL(client.url).pathname || "";
|
|
@@ -106,49 +102,316 @@ function isChatClient(client) {
|
|
|
106
102
|
}
|
|
107
103
|
}
|
|
108
104
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
function notifyPage(message) {
|
|
106
|
+
try {
|
|
107
|
+
var bc = new BroadcastChannel(PUSH_BROADCAST_CHANNEL);
|
|
108
|
+
bc.postMessage(message);
|
|
109
|
+
bc.close();
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// ignore
|
|
112
|
+
}
|
|
113
|
+
}
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
function rememberPushData(data) {
|
|
116
|
+
var messageId = String(data.messageId || data.message_id || "").trim();
|
|
117
|
+
var conversationId = String(data.conversationId || data.conversation_id || "").trim();
|
|
118
|
+
|
|
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",
|
|
116
341
|
);
|
|
342
|
+
}
|
|
117
343
|
|
|
118
|
-
|
|
344
|
+
function deliverNotificationClick(data) {
|
|
119
345
|
var conversationId = resolveConversationId(data);
|
|
120
|
-
var targetUrl = buildTargetUrl(data
|
|
346
|
+
var targetUrl = buildTargetUrl(data);
|
|
121
347
|
var message = {
|
|
122
|
-
type:
|
|
348
|
+
type: NOTIFICATION_CLICK_SW_TYPE,
|
|
123
349
|
conversationId: conversationId,
|
|
350
|
+
data: data,
|
|
124
351
|
};
|
|
125
352
|
|
|
126
353
|
console.log("[realtimex-push] notificationclick extracted data:", data);
|
|
127
354
|
console.log("[realtimex-push] notificationclick conversationId:", conversationId);
|
|
128
355
|
console.log("[realtimex-push] notificationclick targetUrl:", targetUrl);
|
|
129
356
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
for (var i = 0; i < windowClients.length; i++) {
|
|
138
|
-
var client = windowClients[i];
|
|
139
|
-
if (!anyClient) anyClient = client;
|
|
140
|
-
if (!chatClient && isChatClient(client)) chatClient = client;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
var target = chatClient || anyClient;
|
|
144
|
-
if (target && "focus" in target) {
|
|
145
|
-
return target.focus().then(function () {
|
|
146
|
-
if (target.postMessage) target.postMessage(message);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
357
|
+
if (!conversationId) {
|
|
358
|
+
console.warn(
|
|
359
|
+
"[realtimex-push] notificationclick missing conversationId — redirect skipped",
|
|
360
|
+
);
|
|
361
|
+
return Promise.resolve();
|
|
362
|
+
}
|
|
149
363
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
+
}),
|
|
153
416
|
);
|
|
154
417
|
});
|