@realtimexsco/live-chat 1.5.4 → 1.5.6

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.
@@ -17,12 +17,28 @@ importScripts(
17
17
 
18
18
  var PUSH_BROADCAST_CHANNEL = "realtimex-push";
19
19
  var NOTIFICATION_CLICK_SW_TYPE = "realtimex:notification-click";
20
+ var CHAT_HEARTBEAT_SW_TYPE = "realtimex:chat-heartbeat";
20
21
  var PUSH_DATA_CACHE = "realtimex-push-data-v1";
21
22
  var TAG_PREFIX = "realtimex-";
22
23
 
23
24
  /** In-memory fallback when Cache API or FCM click data is empty. */
24
25
  var pendingPushDataStore = {};
25
26
 
27
+ // Last time any tab told us its ForegroundPushBridge is mounted and can
28
+ // show its own in-app toast (see CHAT_HEARTBEAT_SW_TYPE above). A visible
29
+ // tab is not the same thing as a tab with the chat UI actually mounted —
30
+ // the host app can be open and focused on a completely unrelated route —
31
+ // so "tab visible" alone isn't safe grounds to suppress the OS
32
+ // notification for a background push.
33
+ var lastChatHeartbeatAt = 0;
34
+ var CHAT_HEARTBEAT_STALE_MS = 30000;
35
+
36
+ self.addEventListener("message", function (event) {
37
+ if (event.data && event.data.type === CHAT_HEARTBEAT_SW_TYPE) {
38
+ lastChatHeartbeatAt = Date.now();
39
+ }
40
+ });
41
+
26
42
  var params = new URL(self.location.href).searchParams;
27
43
  var rawConfig = params.get("config");
28
44
 
@@ -93,15 +109,6 @@ function cachePendingOpen(data) {
93
109
  });
94
110
  }
95
111
 
96
- function isChatClient(client) {
97
- try {
98
- var pathname = new URL(client.url).pathname || "";
99
- return pathname === "/chat" || pathname.indexOf("/chat/") === 0;
100
- } catch (e) {
101
- return false;
102
- }
103
- }
104
-
105
112
  function notifyPage(message) {
106
113
  try {
107
114
  var bc = new BroadcastChannel(PUSH_BROADCAST_CHANNEL);
@@ -309,14 +316,21 @@ if (rawConfig) {
309
316
  var hasVisibleClient = windowClients.some(function (client) {
310
317
  return client.visibilityState === "visible";
311
318
  });
312
- // Regular messages: a visible tab is assumed to show its own in-app
313
- // toast, so skip the OS notification. Calls skip that assumption —
314
- // the in-app ringing dialog only exists while the user is actually
315
- // on the chat screen (ChatMain mounted), so a visible-but-elsewhere
316
- // tab still needs the OS notification or the call goes unnoticed.
317
- if (hasVisibleClient && !isCall) {
319
+ var chatBridgeActive =
320
+ Date.now() - lastChatHeartbeatAt < CHAT_HEARTBEAT_STALE_MS;
321
+ // Regular messages: only skip the OS notification when a tab is
322
+ // both visible AND actually has the chat UI mounted (recent
323
+ // heartbeat) that's what makes the in-app toast path real. A
324
+ // visible tab elsewhere in the host app (chat widget not mounted,
325
+ // e.g. a different route) can't render that toast, so it still
326
+ // needs the OS notification for DMs and group messages alike, same
327
+ // as a backgrounded/closed tab. Calls skip this assumption
328
+ // entirely — the in-app ringing dialog only exists while the user
329
+ // is actually on the chat screen, so a visible-but-elsewhere tab
330
+ // still needs the OS notification or the call goes unnoticed.
331
+ if (hasVisibleClient && chatBridgeActive && !isCall) {
318
332
  console.log(
319
- "[realtimex-push] app visible — skip OS notification (use in-app toast)",
333
+ "[realtimex-push] chat UI active in a visible tab — skip OS notification (use in-app toast)",
320
334
  );
321
335
  return;
322
336
  }
@@ -373,27 +387,18 @@ function deliverNotificationClick(data) {
373
387
  return clients
374
388
  .matchAll({ type: "window", includeUncontrolled: true })
375
389
  .then(function (windowClients) {
376
- var chatClient = null;
377
- var anyClient = null;
378
-
379
- for (var i = 0; i < windowClients.length; i++) {
380
- var client = windowClients[i];
381
- if (!anyClient) anyClient = client;
382
- if (!chatClient && isChatClient(client)) chatClient = client;
383
- }
384
-
385
- var target = chatClient || anyClient;
390
+ // Any open tab of this app already gets `message` via
391
+ // notifyPage()'s BroadcastChannel below, regardless of which route
392
+ // it's on — the chat widget can be mounted anywhere in a host app,
393
+ // there's no fixed "/chat" page to require a match against. Focus
394
+ // whichever tab is already open instead of guessing its route; only
395
+ // open a brand new window when there's genuinely no tab open at all.
396
+ var target = windowClients[0];
386
397
 
387
398
  if (target && "focus" in target) {
388
399
  return target.focus().then(function () {
389
400
  notifyPage(message);
390
401
  if (target.postMessage) target.postMessage(message);
391
-
392
- if (conversationId && !isChatClient(target) && clients.openWindow) {
393
- return cachePendingOpen(data).then(function () {
394
- return clients.openWindow(targetUrl);
395
- });
396
- }
397
402
  return undefined;
398
403
  });
399
404
  }