@ethora/chat-component 26.2.20 → 26.2.21
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/README.md +9 -1
- package/dist/config.d.ts +8 -5
- package/dist/firebase-messaging-sw.js +351 -386
- package/dist/helpers/constants/PLATFORM_CONSTANTS.d.ts +3 -3
- package/dist/hooks/usePushNotifications.d.ts +4 -0
- package/dist/main.js +9132 -9066
- package/dist/main.js.map +1 -1
- package/dist/types/models/config.model.d.ts +2 -0
- package/dist/utils/firebasePushNotifications.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -1
- package/public/firebase-messaging-sw.js +351 -386
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/* eslint-disable no-empty
|
|
1
|
+
/* eslint-disable no-empty */
|
|
2
2
|
/* eslint-env worker */
|
|
3
|
-
/* global firebase, clients */
|
|
3
|
+
/* global firebase, clients, importScripts */
|
|
4
4
|
|
|
5
|
-
// ── Firebase SDK ──────────────────────────────────────────────
|
|
6
5
|
importScripts(
|
|
7
6
|
'https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js'
|
|
8
7
|
);
|
|
@@ -10,9 +9,30 @@ importScripts(
|
|
|
10
9
|
'https://www.gstatic.com/firebasejs/10.12.0/firebase-messaging-compat.js'
|
|
11
10
|
);
|
|
12
11
|
|
|
13
|
-
// ──
|
|
12
|
+
// ── DEBUG ─────────────────────────────────────────────────────
|
|
13
|
+
const DEBUG = false;
|
|
14
|
+
const DEDUP_TTL = 60 * 1000;
|
|
15
|
+
|
|
14
16
|
const params = new URL(self.location.href).searchParams;
|
|
17
|
+
const CUSTOM_ICON = (params.get('iconPath') || '').trim();
|
|
18
|
+
const CUSTOM_BADGE = (params.get('badgePath') || '').trim();
|
|
19
|
+
const DEFAULT_ICON = CUSTOM_ICON || '/favicon.ico';
|
|
20
|
+
const DEFAULT_BADGE = CUSTOM_BADGE || CUSTOM_ICON || '/favicon.ico';
|
|
21
|
+
|
|
22
|
+
// ── LOGGING ───────────────────────────────────────────────────
|
|
23
|
+
function log(...args) {
|
|
24
|
+
if (DEBUG) console.log('[SW]', ...args);
|
|
25
|
+
}
|
|
15
26
|
|
|
27
|
+
function warn(...args) {
|
|
28
|
+
if (DEBUG) console.warn('[SW]', ...args);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function error(...args) {
|
|
32
|
+
console.error('[SW]', ...args);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── CONFIG ────────────────────────────────────────────────────
|
|
16
36
|
const firebaseConfig = {
|
|
17
37
|
apiKey: params.get('apiKey'),
|
|
18
38
|
authDomain: params.get('authDomain'),
|
|
@@ -22,477 +42,422 @@ const firebaseConfig = {
|
|
|
22
42
|
appId: params.get('appId'),
|
|
23
43
|
};
|
|
24
44
|
|
|
25
|
-
|
|
45
|
+
const APP_URL = self.location.origin;
|
|
46
|
+
|
|
47
|
+
// ── INIT FIREBASE ─────────────────────────────────────────────
|
|
48
|
+
try {
|
|
49
|
+
if (
|
|
50
|
+
firebaseConfig.apiKey &&
|
|
51
|
+
firebaseConfig.appId &&
|
|
52
|
+
firebaseConfig.messagingSenderId &&
|
|
53
|
+
firebaseConfig.projectId
|
|
54
|
+
) {
|
|
55
|
+
if (!firebase.apps.length) {
|
|
56
|
+
firebase.initializeApp(firebaseConfig);
|
|
57
|
+
log('Firebase initialized');
|
|
58
|
+
} else {
|
|
59
|
+
log('Firebase already initialized');
|
|
60
|
+
}
|
|
26
61
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
firebaseConfig.projectId
|
|
32
|
-
) {
|
|
33
|
-
if (!firebase.apps.length) {
|
|
34
|
-
firebase.initializeApp(firebaseConfig);
|
|
62
|
+
firebase.messaging();
|
|
63
|
+
log('Firebase messaging ready (without onBackgroundMessage)');
|
|
64
|
+
} else {
|
|
65
|
+
warn('Firebase config missing', firebaseConfig);
|
|
35
66
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// console.warn('[SW] Firebase config missing → FCM disabled');
|
|
67
|
+
} catch (e) {
|
|
68
|
+
error('Firebase init error', e);
|
|
39
69
|
}
|
|
40
70
|
|
|
41
|
-
|
|
71
|
+
// ── DEDUP ─────────────────────────────────────────────────────
|
|
72
|
+
const handledPushes = new Map();
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const DEDUP_TTL_MS = 60_000;
|
|
74
|
+
function cleanupHandledPushes() {
|
|
75
|
+
const now = Date.now();
|
|
46
76
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
for (const [key, ts] of handledPushes.entries()) {
|
|
78
|
+
if (now - ts > DEDUP_TTL) {
|
|
79
|
+
handledPushes.delete(key);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
52
82
|
}
|
|
53
83
|
|
|
54
|
-
function
|
|
55
|
-
|
|
84
|
+
function rememberPush(key) {
|
|
85
|
+
if (!key) return;
|
|
86
|
+
cleanupHandledPushes();
|
|
87
|
+
handledPushes.set(key, Date.now());
|
|
88
|
+
log('DEDUP remember:', key);
|
|
56
89
|
}
|
|
57
90
|
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const explicitMessageId =
|
|
62
|
-
payload.messageId ||
|
|
63
|
-
data.msgID ||
|
|
64
|
-
data.messageId ||
|
|
65
|
-
data.message_id ||
|
|
66
|
-
'';
|
|
91
|
+
function wasHandled(key) {
|
|
92
|
+
if (!key) return false;
|
|
93
|
+
cleanupHandledPushes();
|
|
67
94
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const title = notification.title || payload.title || '';
|
|
71
|
-
const body = notification.body || payload.body || payload.message || '';
|
|
95
|
+
const hit = handledPushes.has(key);
|
|
96
|
+
if (hit) log('DEDUP hit:', key);
|
|
72
97
|
|
|
73
|
-
return
|
|
98
|
+
return hit;
|
|
74
99
|
}
|
|
75
100
|
|
|
76
|
-
// ──
|
|
77
|
-
function
|
|
78
|
-
return
|
|
101
|
+
// ── HELPERS ───────────────────────────────────────────────────
|
|
102
|
+
function isVisibleClients(clientsList) {
|
|
103
|
+
return clientsList.some((c) => {
|
|
104
|
+
try {
|
|
105
|
+
return c.visibilityState === 'visible';
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
79
110
|
}
|
|
80
111
|
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (isSystemPayload(data)) return `${APP_URL}/?fromPush=1`;
|
|
88
|
-
if (!data?.jid) return `${APP_URL}/?fromPush=1`;
|
|
112
|
+
function dumpClients(clientsList) {
|
|
113
|
+
return clientsList.map((c) => ({
|
|
114
|
+
url: c.url,
|
|
115
|
+
visibility: c.visibilityState,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
89
118
|
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
function safeJsonParse(text) {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(text);
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
92
126
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
127
|
+
function stableStringify(value) {
|
|
128
|
+
try {
|
|
129
|
+
if (value === null || value === undefined) return '';
|
|
130
|
+
if (typeof value !== 'object') return String(value);
|
|
96
131
|
|
|
97
|
-
|
|
98
|
-
}
|
|
132
|
+
if (Array.isArray(value)) {
|
|
133
|
+
return `[${value.map((v) => stableStringify(v)).join(',')}]`;
|
|
134
|
+
}
|
|
99
135
|
|
|
100
|
-
|
|
101
|
-
|
|
136
|
+
const keys = Object.keys(value).sort();
|
|
137
|
+
return `{${keys
|
|
138
|
+
.map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`)
|
|
139
|
+
.join(',')}}`;
|
|
140
|
+
} catch {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
102
143
|
}
|
|
103
144
|
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
type: 'PUSH_FOREGROUND_BRIDGE',
|
|
108
|
-
payload,
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
145
|
+
function simpleHash(input) {
|
|
146
|
+
const str = String(input || '');
|
|
147
|
+
let hash = 0;
|
|
112
148
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
sourceType: 'empty',
|
|
118
|
-
raw: {
|
|
119
|
-
title: 'New message',
|
|
120
|
-
body: '',
|
|
121
|
-
data: {},
|
|
122
|
-
},
|
|
123
|
-
};
|
|
149
|
+
for (let i = 0; i < str.length; i += 1) {
|
|
150
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
151
|
+
hash |= 0;
|
|
124
152
|
}
|
|
125
153
|
|
|
126
|
-
|
|
154
|
+
return String(hash);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeUrl(url) {
|
|
158
|
+
if (!url) return APP_URL;
|
|
127
159
|
|
|
128
160
|
try {
|
|
129
|
-
|
|
130
|
-
} catch
|
|
131
|
-
|
|
161
|
+
return new URL(url, self.location.origin).toString();
|
|
162
|
+
} catch {
|
|
163
|
+
return APP_URL;
|
|
132
164
|
}
|
|
165
|
+
}
|
|
133
166
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
sourceType: 'empty',
|
|
138
|
-
raw: {
|
|
139
|
-
title: 'New message',
|
|
140
|
-
body: '',
|
|
141
|
-
data: {},
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
}
|
|
167
|
+
function pickTitle(raw = {}, data = {}, notification = {}) {
|
|
168
|
+
return notification.title || raw.title || data.title || 'New Message';
|
|
169
|
+
}
|
|
145
170
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// console.log('[SW][JSON_PUSH]', parsed);
|
|
149
|
-
return {
|
|
150
|
-
sourceType: 'json',
|
|
151
|
-
raw: parsed,
|
|
152
|
-
};
|
|
153
|
-
} catch (err) {
|
|
154
|
-
/* console.warn('[SW][TEXT_PUSH] Non-JSON push received, processing as plain text', {
|
|
155
|
-
error: err,
|
|
156
|
-
rawText: textPayload,
|
|
157
|
-
});*/
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
sourceType: 'text',
|
|
161
|
-
raw: {
|
|
162
|
-
title: 'New message',
|
|
163
|
-
body: textPayload,
|
|
164
|
-
data: {},
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
}
|
|
171
|
+
function pickBody(raw = {}, data = {}, notification = {}) {
|
|
172
|
+
return notification.body || raw.body || data.body || 'You have a new message';
|
|
168
173
|
}
|
|
169
174
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
function pickUrl(raw = {}, data = {}, notification = {}) {
|
|
176
|
+
return normalizeUrl(
|
|
177
|
+
data.url ||
|
|
178
|
+
data.link ||
|
|
179
|
+
data.click_action ||
|
|
180
|
+
notification.click_action ||
|
|
181
|
+
raw.url ||
|
|
182
|
+
raw.link ||
|
|
183
|
+
raw.click_action ||
|
|
184
|
+
APP_URL
|
|
185
|
+
);
|
|
186
|
+
}
|
|
174
187
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const dedupeKey = buildDedupeKey({
|
|
179
|
-
messageId,
|
|
180
|
-
data,
|
|
181
|
-
notification,
|
|
182
|
-
});
|
|
188
|
+
function pickIcon(raw = {}, data = {}, notification = {}) {
|
|
189
|
+
return data.icon || notification.icon || raw.icon || DEFAULT_ICON;
|
|
190
|
+
}
|
|
183
191
|
|
|
184
|
-
|
|
185
|
-
|
|
192
|
+
function pickBadge(raw = {}, data = {}, notification = {}) {
|
|
193
|
+
return data.badge || notification.badge || raw.badge || DEFAULT_BADGE;
|
|
194
|
+
}
|
|
186
195
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
notification.title ||
|
|
191
|
-
data.title ||
|
|
192
|
-
'New Message';
|
|
196
|
+
function pickImage(raw = {}, data = {}, notification = {}) {
|
|
197
|
+
return data.image || notification.image || raw.image || undefined;
|
|
198
|
+
}
|
|
193
199
|
|
|
194
|
-
|
|
200
|
+
function extractMessageId(raw = {}, data = {}) {
|
|
201
|
+
return (
|
|
202
|
+
raw.messageId ||
|
|
203
|
+
raw.fcmMessageId ||
|
|
204
|
+
raw.fcmMessageId ||
|
|
205
|
+
raw.message_id ||
|
|
206
|
+
data.messageId ||
|
|
207
|
+
data.fcmMessageId ||
|
|
208
|
+
data.message_id ||
|
|
209
|
+
data.msgID ||
|
|
210
|
+
data['gcm.message_id'] ||
|
|
211
|
+
data['google.message_id'] ||
|
|
212
|
+
data['google.c.a.c_id'] ||
|
|
213
|
+
null
|
|
214
|
+
);
|
|
215
|
+
}
|
|
195
216
|
|
|
196
|
-
|
|
217
|
+
function hasNotificationPayload(raw = {}, notification = {}) {
|
|
218
|
+
return Boolean(
|
|
219
|
+
(notification && (notification.title || notification.body)) ||
|
|
220
|
+
(raw.notification && (raw.notification.title || raw.notification.body))
|
|
221
|
+
);
|
|
222
|
+
}
|
|
197
223
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
meta: {
|
|
213
|
-
source: 'fcm',
|
|
214
|
-
},
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const options = {
|
|
218
|
-
body,
|
|
219
|
-
icon: notification.icon || notification.image || '/favicon.ico',
|
|
220
|
-
badge: '/favicon.ico',
|
|
221
|
-
tag: data.tag || 'ethora-notification',
|
|
222
|
-
data: {
|
|
223
|
-
url,
|
|
224
|
-
roomJid: data.jid || null,
|
|
225
|
-
senderId: data.userJid || null,
|
|
226
|
-
messageId,
|
|
227
|
-
workspaceId: data.workspaceId || data.workspace_id || null,
|
|
228
|
-
},
|
|
229
|
-
requireInteraction: false,
|
|
230
|
-
silent: false,
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
return clients
|
|
234
|
-
.matchAll({ type: 'window', includeUncontrolled: true })
|
|
235
|
-
.then((windowClients) => {
|
|
236
|
-
postBridgeToClients(windowClients, bridgePayload);
|
|
237
|
-
|
|
238
|
-
if (!isAnyClientVisible(windowClients)) {
|
|
239
|
-
/* console.log(
|
|
240
|
-
`[NotifyPolicy] source=fcm_bg action=show reason=${isSystem ? 'system' : 'background'} msgId=${messageId || ''}`
|
|
241
|
-
);*/
|
|
242
|
-
return self.registration.showNotification(title, options);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/* console.log(
|
|
246
|
-
`[NotifyPolicy] source=fcm_bg action=skip reason=tab_visible msgId=${messageId || ''}`
|
|
247
|
-
);*/
|
|
248
|
-
});
|
|
224
|
+
function makeDedupKey(raw = {}, data = {}, notification = {}) {
|
|
225
|
+
const messageId = extractMessageId(raw, data);
|
|
226
|
+
if (messageId) return `id:${messageId}`;
|
|
227
|
+
|
|
228
|
+
const title = pickTitle(raw, data, notification);
|
|
229
|
+
const body = pickBody(raw, data, notification);
|
|
230
|
+
const url = pickUrl(raw, data, notification);
|
|
231
|
+
|
|
232
|
+
const canonical = stableStringify({
|
|
233
|
+
title,
|
|
234
|
+
body,
|
|
235
|
+
url,
|
|
236
|
+
data,
|
|
237
|
+
notification,
|
|
249
238
|
});
|
|
239
|
+
|
|
240
|
+
return `fp:${simpleHash(canonical)}`;
|
|
250
241
|
}
|
|
251
242
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
243
|
+
function normalizePush(event) {
|
|
244
|
+
if (!event.data) {
|
|
245
|
+
warn('EMPTY PUSH');
|
|
246
|
+
return { type: 'empty', raw: {}, text: '' };
|
|
247
|
+
}
|
|
255
248
|
|
|
256
|
-
|
|
249
|
+
let text = '';
|
|
257
250
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
251
|
+
try {
|
|
252
|
+
text = event.data.text();
|
|
253
|
+
log('PUSH TEXT', text);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
warn('TEXT READ FAIL', e);
|
|
261
256
|
}
|
|
262
257
|
|
|
263
|
-
const
|
|
264
|
-
const payloadData = raw.data || {};
|
|
265
|
-
const messageId =
|
|
266
|
-
raw.messageId ||
|
|
267
|
-
payloadData.msgID ||
|
|
268
|
-
payloadData.messageId ||
|
|
269
|
-
payloadData.message_id ||
|
|
270
|
-
null;
|
|
271
|
-
const dedupeKey = buildDedupeKey({
|
|
272
|
-
messageId,
|
|
273
|
-
data: payloadData,
|
|
274
|
-
notification,
|
|
275
|
-
title: raw.title,
|
|
276
|
-
body: raw.body || raw.message,
|
|
277
|
-
roomJid: raw.roomJid,
|
|
278
|
-
senderId: raw.senderId,
|
|
279
|
-
});
|
|
258
|
+
const json = safeJsonParse(text);
|
|
280
259
|
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
return;
|
|
260
|
+
if (json) {
|
|
261
|
+
log('PUSH JSON', json);
|
|
262
|
+
return { type: 'json', raw: json, text };
|
|
284
263
|
}
|
|
285
264
|
|
|
286
|
-
|
|
287
|
-
|
|
265
|
+
warn('NOT JSON PUSH', text);
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
type: 'text',
|
|
269
|
+
raw: {
|
|
270
|
+
title: 'New Message',
|
|
271
|
+
body: text,
|
|
272
|
+
data: {},
|
|
273
|
+
},
|
|
274
|
+
text,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function broadcastDebugMessage(payload) {
|
|
279
|
+
try {
|
|
280
|
+
const windowClients = await clients.matchAll({
|
|
281
|
+
type: 'window',
|
|
282
|
+
includeUncontrolled: true,
|
|
283
|
+
});
|
|
288
284
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
285
|
+
for (const c of windowClients) {
|
|
286
|
+
try {
|
|
287
|
+
c.postMessage({
|
|
288
|
+
type: 'PUSH_DEBUG',
|
|
289
|
+
payload,
|
|
290
|
+
});
|
|
291
|
+
} catch {}
|
|
292
|
+
}
|
|
293
|
+
} catch (e) {
|
|
294
|
+
warn('broadcastDebugMessage failed', e);
|
|
294
295
|
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── MAIN HANDLER ──────────────────────────────────────────────
|
|
299
|
+
async function handlePush(raw = {}, source = 'push') {
|
|
300
|
+
const data = raw.data || {};
|
|
301
|
+
const notification = raw.notification || {};
|
|
295
302
|
|
|
296
|
-
|
|
297
|
-
|
|
303
|
+
const title = pickTitle(raw, data, notification);
|
|
304
|
+
const body = pickBody(raw, data, notification);
|
|
305
|
+
const url = pickUrl(raw, data, notification);
|
|
306
|
+
const icon = pickIcon(raw, data, notification);
|
|
307
|
+
const badge = pickBadge(raw, data, notification);
|
|
308
|
+
const image = pickImage(raw, data, notification);
|
|
309
|
+
|
|
310
|
+
const messageId = extractMessageId(raw, data);
|
|
311
|
+
const dedupKey = makeDedupKey(raw, data, notification);
|
|
312
|
+
const browserWillAutoShow = hasNotificationPayload(raw, notification);
|
|
313
|
+
|
|
314
|
+
log('--- PUSH DECISION ---');
|
|
315
|
+
log('source:', source);
|
|
316
|
+
log('messageId:', messageId);
|
|
317
|
+
log('dedupKey:', dedupKey);
|
|
318
|
+
log('title:', title);
|
|
319
|
+
log('body:', body);
|
|
320
|
+
log('url:', url);
|
|
321
|
+
log('icon:', icon);
|
|
322
|
+
log('badge:', badge);
|
|
323
|
+
log('browserWillAutoShow:', browserWillAutoShow);
|
|
324
|
+
|
|
325
|
+
if (wasHandled(dedupKey)) {
|
|
326
|
+
log('SKIP DEDUP');
|
|
327
|
+
return;
|
|
298
328
|
}
|
|
299
329
|
|
|
300
|
-
|
|
330
|
+
rememberPush(dedupKey);
|
|
301
331
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
'Ethora Chat';
|
|
332
|
+
const windowClients = await clients.matchAll({
|
|
333
|
+
type: 'window',
|
|
334
|
+
includeUncontrolled: true,
|
|
335
|
+
});
|
|
307
336
|
|
|
308
|
-
const
|
|
309
|
-
notification.body ||
|
|
310
|
-
raw.body ||
|
|
311
|
-
raw.message ||
|
|
312
|
-
payloadData.body ||
|
|
313
|
-
'You have a new message.';
|
|
337
|
+
const visible = isVisibleClients(windowClients);
|
|
314
338
|
|
|
315
|
-
|
|
316
|
-
|
|
339
|
+
log('clients:', dumpClients(windowClients));
|
|
340
|
+
log('visibleClientExists:', visible);
|
|
317
341
|
|
|
318
|
-
|
|
319
|
-
|
|
342
|
+
await broadcastDebugMessage({
|
|
343
|
+
source,
|
|
344
|
+
raw,
|
|
345
|
+
data,
|
|
320
346
|
notification,
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
347
|
+
title,
|
|
348
|
+
body,
|
|
349
|
+
url,
|
|
350
|
+
icon,
|
|
351
|
+
badge,
|
|
352
|
+
image,
|
|
325
353
|
messageId,
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
image: icon,
|
|
330
|
-
},
|
|
331
|
-
data: {
|
|
332
|
-
...payloadData,
|
|
333
|
-
jid: raw.roomJid || payloadData.jid || '',
|
|
334
|
-
userJid: raw.senderId || payloadData.userJid || '',
|
|
335
|
-
msgID: messageId || '',
|
|
336
|
-
workspaceId:
|
|
337
|
-
raw.workspaceId ||
|
|
338
|
-
payloadData.workspaceId ||
|
|
339
|
-
payloadData.workspace_id ||
|
|
340
|
-
'',
|
|
341
|
-
},
|
|
342
|
-
meta: {
|
|
343
|
-
source: sourceType === 'json' ? 'webpush' : 'text_push',
|
|
344
|
-
},
|
|
345
|
-
};
|
|
354
|
+
dedupKey,
|
|
355
|
+
browserWillAutoShow,
|
|
356
|
+
});
|
|
346
357
|
|
|
358
|
+
if (browserWillAutoShow) {
|
|
359
|
+
log('SKIP MANUAL SHOW: browser/FCM should auto-display notification');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Data-only push: показуємо вручну
|
|
347
364
|
const options = {
|
|
348
365
|
body,
|
|
349
366
|
icon,
|
|
350
|
-
badge
|
|
351
|
-
|
|
367
|
+
badge,
|
|
368
|
+
image,
|
|
369
|
+
tag: dedupKey,
|
|
370
|
+
renotify: false,
|
|
352
371
|
data: {
|
|
353
372
|
url,
|
|
354
|
-
roomJid: raw.roomJid || payloadData.jid || null,
|
|
355
|
-
senderId: raw.senderId || payloadData.userJid || null,
|
|
356
373
|
messageId,
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
payloadData.workspaceId ||
|
|
360
|
-
payloadData.workspace_id ||
|
|
361
|
-
null,
|
|
362
|
-
sourceType,
|
|
374
|
+
dedupKey,
|
|
375
|
+
source,
|
|
363
376
|
},
|
|
364
|
-
requireInteraction: false,
|
|
365
|
-
silent: false,
|
|
366
377
|
};
|
|
367
378
|
|
|
379
|
+
if (!visible) {
|
|
380
|
+
log('SHOW NOTIFICATION', {
|
|
381
|
+
reason: 'NO_VISIBLE_CLIENT',
|
|
382
|
+
tag: options.tag,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
return self.registration.showNotification(title, options);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
log('SKIP NOTIFICATION', {
|
|
389
|
+
reason: 'VISIBLE_CLIENT',
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ── RAW PUSH ONLY ─────────────────────────────────────────────
|
|
394
|
+
self.addEventListener('push', (event) => {
|
|
395
|
+
log('RAW PUSH EVENT');
|
|
396
|
+
|
|
368
397
|
event.waitUntil(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
})
|
|
398
|
+
(async () => {
|
|
399
|
+
try {
|
|
400
|
+
const { type, raw } = normalizePush(event);
|
|
401
|
+
|
|
402
|
+
log('PARSED PUSH', {
|
|
403
|
+
type,
|
|
404
|
+
raw,
|
|
405
|
+
messageId: extractMessageId(raw, raw.data || {}),
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
return handlePush(raw, `raw_push_${type}`);
|
|
409
|
+
} catch (e) {
|
|
410
|
+
error('PUSH HANDLER ERROR', e);
|
|
411
|
+
}
|
|
412
|
+
})()
|
|
385
413
|
);
|
|
386
414
|
});
|
|
387
415
|
|
|
388
|
-
// ──
|
|
416
|
+
// ── CLICK ─────────────────────────────────────────────────────
|
|
389
417
|
self.addEventListener('notificationclick', (event) => {
|
|
390
|
-
|
|
391
|
-
event.notification.close();
|
|
418
|
+
log('NOTIFICATION CLICK', event.notification);
|
|
392
419
|
|
|
393
|
-
|
|
394
|
-
(event.notification.data && event.notification.data.url) || APP_URL;
|
|
420
|
+
event.notification.close();
|
|
395
421
|
|
|
396
|
-
|
|
397
|
-
url: targetUrl,
|
|
398
|
-
});*/
|
|
422
|
+
const url = event.notification?.data?.url || APP_URL;
|
|
399
423
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const
|
|
424
|
+
event.waitUntil(
|
|
425
|
+
(async () => {
|
|
426
|
+
const allClients = await clients.matchAll({
|
|
403
427
|
type: 'window',
|
|
404
428
|
includeUncontrolled: true,
|
|
405
429
|
});
|
|
406
430
|
|
|
407
|
-
|
|
408
|
-
count: windowClients.length,
|
|
409
|
-
});*/
|
|
410
|
-
|
|
411
|
-
const clickPayload = {
|
|
412
|
-
type: 'PUSH_NOTIFICATION_CLICK',
|
|
413
|
-
data: event.notification.data || {},
|
|
414
|
-
notification: {
|
|
415
|
-
title: event.notification.title || '',
|
|
416
|
-
body: event.notification.body || '',
|
|
417
|
-
},
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
windowClients.forEach((client) => {
|
|
421
|
-
client.postMessage(clickPayload);
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
for (const client of windowClients) {
|
|
425
|
-
if (client.url === targetUrl && 'focus' in client) {
|
|
426
|
-
/* console.log('[NotifyPolicy] source=push_bg action=focus_exact', {
|
|
427
|
-
url: client.url,
|
|
428
|
-
});*/
|
|
429
|
-
return client.focus();
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
for (const client of windowClients) {
|
|
431
|
+
for (const client of allClients) {
|
|
434
432
|
try {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return focusedClient.navigate(targetUrl);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return focusedClient;
|
|
433
|
+
if ('focus' in client) {
|
|
434
|
+
await client.focus();
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
client.postMessage({
|
|
438
|
+
type: 'OPEN_FROM_PUSH',
|
|
439
|
+
url,
|
|
440
|
+
notificationData: event.notification?.data || {},
|
|
441
|
+
});
|
|
442
|
+
} catch {}
|
|
443
|
+
|
|
444
|
+
return;
|
|
451
445
|
}
|
|
452
|
-
} catch
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (clients.openWindow) {
|
|
456
|
-
/* console.log('[NotifyPolicy] source=push_bg action=open_new_window', {
|
|
457
|
-
url: targetUrl,
|
|
458
|
-
});*/
|
|
459
|
-
return clients.openWindow(targetUrl);
|
|
446
|
+
} catch {}
|
|
460
447
|
}
|
|
461
|
-
} catch (err) {
|
|
462
|
-
console.error('[NotifyPolicy] source=push_bg action=click_error', err);
|
|
463
448
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
event.waitUntil(handleClick());
|
|
449
|
+
return clients.openWindow(url);
|
|
450
|
+
})()
|
|
451
|
+
);
|
|
471
452
|
});
|
|
472
453
|
|
|
473
|
-
// ──
|
|
474
|
-
self.addEventListener('
|
|
475
|
-
|
|
476
|
-
event.waitUntil(
|
|
477
|
-
clients
|
|
478
|
-
.matchAll({ type: 'window', includeUncontrolled: true })
|
|
479
|
-
.then((allClients) => {
|
|
480
|
-
allClients.forEach((client) => {
|
|
481
|
-
client.postMessage({
|
|
482
|
-
type: 'PUSH_SUBSCRIPTION_CHANGED',
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
})
|
|
486
|
-
);
|
|
454
|
+
// ── LIFECYCLE ─────────────────────────────────────────────────
|
|
455
|
+
self.addEventListener('install', (event) => {
|
|
456
|
+
log('SW INSTALLED');
|
|
457
|
+
event.waitUntil(self.skipWaiting());
|
|
487
458
|
});
|
|
488
459
|
|
|
489
|
-
// ── Lifecycle ─────────────────────────────────────────────────
|
|
490
460
|
self.addEventListener('activate', (event) => {
|
|
491
|
-
|
|
461
|
+
log('SW ACTIVATED');
|
|
492
462
|
event.waitUntil(clients.claim());
|
|
493
463
|
});
|
|
494
|
-
|
|
495
|
-
self.addEventListener('install', (event) => {
|
|
496
|
-
// console.log('[SW] installed');
|
|
497
|
-
event.waitUntil(self.skipWaiting());
|
|
498
|
-
});
|