@ethora/chat-component 26.1.1 → 26.2.2
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 +5 -34
- package/dist/components/MainComponents/ConnectionBanner.d.ts +4 -1
- package/dist/firebase-config.d.ts +1 -0
- package/dist/firebase-messaging-sw.js +261 -156
- package/dist/helpers/authStorage.d.ts +6 -0
- package/dist/helpers/cacheScope.d.ts +8 -0
- package/dist/helpers/constants/LOCAL_STORAGE.d.ts +4 -0
- package/dist/helpers/localStorageUser.d.ts +1 -2
- package/dist/main.js +12423 -12064
- package/dist/main.js.map +1 -1
- package/dist/networking/api-requests/roomMembers.api.d.ts +1 -1
- package/dist/types/models/config.model.d.ts +11 -0
- package/dist/version.d.ts +1 -1
- package/package.json +4 -3
- package/public/firebase-messaging-sw.js +261 -156
package/README.md
CHANGED
|
@@ -116,40 +116,6 @@ npm run dev
|
|
|
116
116
|
|
|
117
117
|
Open `http://localhost:5173`.
|
|
118
118
|
|
|
119
|
-
Static reference screenshots:
|
|
120
|
-
|
|
121
|
-

|
|
122
|
-

|
|
123
|
-

|
|
124
|
-
|
|
125
|
-
### GIF placeholders (manual replacement)
|
|
126
|
-
|
|
127
|
-
Replace each path with your recorded GIF file.
|
|
128
|
-
|
|
129
|
-
1. Install + first render
|
|
130
|
-
Target file: `img/placeholder-01-install-first-render.gif`
|
|
131
|
-

|
|
132
|
-
|
|
133
|
-
2. Login/auth flow
|
|
134
|
-
Target file: `img/placeholder-02-login-auth-flow.gif`
|
|
135
|
-

|
|
136
|
-
|
|
137
|
-
3. Room switching + messaging
|
|
138
|
-
Target file: `img/placeholder-03-room-switching-messaging.gif`
|
|
139
|
-

|
|
140
|
-
|
|
141
|
-
4. Push prompt + receive flow
|
|
142
|
-
Target file: `img/placeholder-04-push-flow.gif`
|
|
143
|
-

|
|
144
|
-
|
|
145
|
-
5. Widget customization before/after
|
|
146
|
-
Target file: `img/placeholder-05-widget-customization.gif`
|
|
147
|
-

|
|
148
|
-
|
|
149
|
-
6. Mobile responsive behavior
|
|
150
|
-
Target file: `img/placeholder-06-mobile-responsive.gif`
|
|
151
|
-

|
|
152
|
-
|
|
153
119
|
## Integration Modes
|
|
154
120
|
|
|
155
121
|
All modes below assume `XmppProvider` wraps `Chat`.
|
|
@@ -396,6 +362,7 @@ Below is a grouped reference for all `config` options.
|
|
|
396
362
|
| `pushNotifications.serviceWorkerPath` | `string` | Service worker path, default `/firebase-messaging-sw.js`. |
|
|
397
363
|
| `pushNotifications.serviceWorkerScope` | `string` | Service worker scope, default `/`. |
|
|
398
364
|
| `pushNotifications.softAsk` | `boolean` | Do not immediately trigger browser permission prompt. |
|
|
365
|
+
| `pushNotifications.onClick` | `(params) => void \| Promise<void>` | Callback invoked when the user clicks an OS push notification (including cold start via URL marker). |
|
|
399
366
|
|
|
400
367
|
## Custom Widgets and Overrides
|
|
401
368
|
|
|
@@ -481,6 +448,10 @@ npx @ethora/chat-component ethora-chat
|
|
|
481
448
|
serviceWorkerPath: '/firebase-messaging-sw.js',
|
|
482
449
|
serviceWorkerScope: '/',
|
|
483
450
|
softAsk: false,
|
|
451
|
+
onClick: async ({ roomJID, messageId, url, data }) => {
|
|
452
|
+
// Your app-level routing/analytics can live here.
|
|
453
|
+
console.log('Push clicked:', { roomJID, messageId, url, data });
|
|
454
|
+
},
|
|
484
455
|
},
|
|
485
456
|
}}
|
|
486
457
|
/>
|
|
@@ -8,6 +8,7 @@ export declare const defaultConfig: {
|
|
|
8
8
|
messagingSenderId: any;
|
|
9
9
|
appId: any;
|
|
10
10
|
};
|
|
11
|
+
export declare const getFirebaseApp: (customConfig?: any) => FirebaseApp | null;
|
|
11
12
|
export declare const getFirebaseMessaging: (customConfig?: any) => Messaging | null;
|
|
12
13
|
export declare const app: FirebaseApp;
|
|
13
14
|
export declare const messaging: Messaging;
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
/* ============================================================
|
|
2
|
-
* Unified Service Worker
|
|
3
|
-
* Handles BOTH Firebase Cloud Messaging (FCM) background pushes
|
|
4
|
-
* AND generic Web Push API events, so all push notifications
|
|
5
|
-
* are captured regardless of origin.
|
|
6
|
-
* ============================================================ */
|
|
7
|
-
|
|
8
1
|
// ── Firebase SDK ──────────────────────────────────────────────
|
|
9
2
|
importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js');
|
|
10
3
|
importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-messaging-compat.js');
|
|
11
4
|
|
|
12
5
|
// ── Dynamic Configuration ─────────────────────────────────────
|
|
13
|
-
const params = new URL(self.location).searchParams;
|
|
6
|
+
const params = new URL(self.location.href).searchParams;
|
|
7
|
+
|
|
14
8
|
const firebaseConfig = {
|
|
15
9
|
apiKey: params.get('apiKey'),
|
|
16
10
|
authDomain: params.get('authDomain'),
|
|
@@ -20,24 +14,32 @@ const firebaseConfig = {
|
|
|
20
14
|
appId: params.get('appId'),
|
|
21
15
|
};
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
let messaging = null;
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
firebaseConfig.apiKey &&
|
|
21
|
+
firebaseConfig.appId &&
|
|
22
|
+
firebaseConfig.messagingSenderId &&
|
|
23
|
+
firebaseConfig.projectId
|
|
24
|
+
) {
|
|
25
|
+
if (!firebase.apps.length) {
|
|
26
|
+
firebase.initializeApp(firebaseConfig);
|
|
27
|
+
}
|
|
28
|
+
messaging = firebase.messaging();
|
|
29
|
+
} else {
|
|
30
|
+
// console.warn('[SW] Firebase config missing → FCM disabled');
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
const messaging = firebase.messaging();
|
|
29
33
|
const APP_URL = self.location.origin;
|
|
30
34
|
|
|
31
35
|
// ── Deduplication ─────────────────────────────────────────────
|
|
32
|
-
// Track message IDs handled by FCM so the generic `push` listener
|
|
33
|
-
// can skip them and avoid duplicate notifications.
|
|
34
36
|
const _handledByFcm = new Set();
|
|
35
|
-
const DEDUP_TTL_MS = 60_000;
|
|
37
|
+
const DEDUP_TTL_MS = 60_000;
|
|
36
38
|
|
|
37
39
|
function markHandledByFcm(id) {
|
|
38
40
|
if (!id) return;
|
|
39
41
|
_handledByFcm.add(id);
|
|
40
|
-
|
|
42
|
+
// console.log('[SW] FCM handled:', id);
|
|
41
43
|
setTimeout(() => _handledByFcm.delete(id), DEDUP_TTL_MS);
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -45,7 +47,7 @@ function wasHandledByFcm(id) {
|
|
|
45
47
|
return id ? _handledByFcm.has(id) : false;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
// ──
|
|
50
|
+
// ── Helpers ───────────────────────────────────────────────────
|
|
49
51
|
function isSystemPayload(data) {
|
|
50
52
|
return !data?.msgID && !data?.jid && !!data?.userJid;
|
|
51
53
|
}
|
|
@@ -56,133 +58,223 @@ function buildTargetUrl(data, notification, fallbackUrl) {
|
|
|
56
58
|
const title = notification?.title || data?.title || '';
|
|
57
59
|
if (title.startsWith('http')) return title;
|
|
58
60
|
|
|
59
|
-
if (isSystemPayload(data)) return APP_URL
|
|
60
|
-
if (!data?.jid) return APP_URL
|
|
61
|
+
if (isSystemPayload(data)) return `${APP_URL}/?fromPush=1`;
|
|
62
|
+
if (!data?.jid) return `${APP_URL}/?fromPush=1`;
|
|
63
|
+
|
|
61
64
|
const chatId = String(data.jid).split('@')[0];
|
|
62
|
-
|
|
65
|
+
const messageId = data.msgID || data.messageId || data.message_id || '';
|
|
66
|
+
|
|
67
|
+
const params = new URLSearchParams({ chatId });
|
|
68
|
+
if (messageId) params.set('messageId', messageId);
|
|
69
|
+
params.set('fromPush', '1');
|
|
70
|
+
|
|
71
|
+
return `${APP_URL}/chat?${params.toString()}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isAnyClientVisible(windowClients) {
|
|
75
|
+
return windowClients.some((c) => c.visibilityState === 'visible');
|
|
63
76
|
}
|
|
64
77
|
|
|
65
|
-
function postBridgeToClients(windowClients,
|
|
78
|
+
function postBridgeToClients(windowClients, payload) {
|
|
66
79
|
windowClients.forEach((client) => {
|
|
67
80
|
client.postMessage({
|
|
68
81
|
type: 'PUSH_FOREGROUND_BRIDGE',
|
|
69
|
-
payload
|
|
82
|
+
payload,
|
|
70
83
|
});
|
|
71
84
|
});
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
function
|
|
75
|
-
|
|
87
|
+
function normalizeRawPushPayload(event) {
|
|
88
|
+
if (!event.data) {
|
|
89
|
+
// console.warn('[SW][EMPTY_PUSH] Push without payload');
|
|
90
|
+
return {
|
|
91
|
+
sourceType: 'empty',
|
|
92
|
+
raw: {
|
|
93
|
+
title: 'New message',
|
|
94
|
+
body: '',
|
|
95
|
+
data: {},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let textPayload = '';
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
textPayload = event.data.text();
|
|
104
|
+
} catch (err) {
|
|
105
|
+
// console.warn('[SW][PUSH_TEXT_READ_FAILED]', err);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!textPayload) {
|
|
109
|
+
// console.warn('[SW][EMPTY_PUSH_TEXT] Push payload text is empty');
|
|
110
|
+
return {
|
|
111
|
+
sourceType: 'empty',
|
|
112
|
+
raw: {
|
|
113
|
+
title: 'New message',
|
|
114
|
+
body: '',
|
|
115
|
+
data: {},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(textPayload);
|
|
122
|
+
// console.log('[SW][JSON_PUSH]', parsed);
|
|
123
|
+
return {
|
|
124
|
+
sourceType: 'json',
|
|
125
|
+
raw: parsed,
|
|
126
|
+
};
|
|
127
|
+
} catch (err) {
|
|
128
|
+
/* console.warn('[SW][TEXT_PUSH] Non-JSON push received, processing as plain text', {
|
|
129
|
+
error: err,
|
|
130
|
+
rawText: textPayload,
|
|
131
|
+
});*/
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
sourceType: 'text',
|
|
135
|
+
raw: {
|
|
136
|
+
title: 'New message',
|
|
137
|
+
body: textPayload,
|
|
138
|
+
data: {},
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
76
142
|
}
|
|
77
143
|
|
|
78
|
-
// ──
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
console.log('[SW] FCM onBackgroundMessage received', payload);
|
|
144
|
+
// ── FCM ───────────────────────────────────────────────────────
|
|
145
|
+
if (messaging) {
|
|
146
|
+
messaging.onBackgroundMessage((payload) => {
|
|
147
|
+
// console.log('[SW][FCM_PUSH]', payload);
|
|
83
148
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
149
|
+
const data = payload.data || {};
|
|
150
|
+
const notification = payload.notification || {};
|
|
151
|
+
const messageId = payload.messageId || data.msgID || null;
|
|
87
152
|
|
|
88
|
-
|
|
89
|
-
markHandledByFcm(messageId);
|
|
153
|
+
markHandledByFcm(messageId);
|
|
90
154
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const body =
|
|
98
|
-
notification.body || data.body || 'You have a new message.';
|
|
99
|
-
const url = buildTargetUrl(data, notification, data.url);
|
|
155
|
+
const isSystem = isSystemPayload(data);
|
|
156
|
+
const title =
|
|
157
|
+
(isSystem ? 'System' : undefined) ||
|
|
158
|
+
notification.title ||
|
|
159
|
+
data.title ||
|
|
160
|
+
'New Message';
|
|
100
161
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
notification: { title, body, image: notification.image || '/favicon.ico' },
|
|
104
|
-
data: {
|
|
105
|
-
...data,
|
|
106
|
-
jid: data.jid || '',
|
|
107
|
-
userJid: data.userJid || '',
|
|
108
|
-
msgID: data.msgID || '',
|
|
109
|
-
workspaceId: data.workspaceId || data.workspace_id || '',
|
|
110
|
-
},
|
|
111
|
-
};
|
|
162
|
+
const body =
|
|
163
|
+
notification.body || data.body || 'You have a new message.';
|
|
112
164
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
badge: '/favicon.ico',
|
|
117
|
-
tag: data.tag || 'ethora-notification',
|
|
118
|
-
data: {
|
|
119
|
-
url,
|
|
120
|
-
roomJid: data.jid || null,
|
|
121
|
-
senderId: data.userJid || null,
|
|
165
|
+
const url = buildTargetUrl(data, notification, data.url);
|
|
166
|
+
|
|
167
|
+
const bridgePayload = {
|
|
122
168
|
messageId,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
169
|
+
notification: {
|
|
170
|
+
title,
|
|
171
|
+
body,
|
|
172
|
+
image: notification.image || notification.icon || '/favicon.ico',
|
|
173
|
+
},
|
|
174
|
+
data: {
|
|
175
|
+
...data,
|
|
176
|
+
jid: data.jid || '',
|
|
177
|
+
userJid: data.userJid || '',
|
|
178
|
+
msgID: data.msgID || '',
|
|
179
|
+
workspaceId: data.workspaceId || data.workspace_id || '',
|
|
180
|
+
},
|
|
181
|
+
meta: {
|
|
182
|
+
source: 'fcm',
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const options = {
|
|
187
|
+
body,
|
|
188
|
+
icon: notification.icon || notification.image || '/favicon.ico',
|
|
189
|
+
badge: '/favicon.ico',
|
|
190
|
+
tag: data.tag || 'ethora-notification',
|
|
191
|
+
data: {
|
|
192
|
+
url,
|
|
193
|
+
roomJid: data.jid || null,
|
|
194
|
+
senderId: data.userJid || null,
|
|
195
|
+
messageId,
|
|
196
|
+
workspaceId: data.workspaceId || data.workspace_id || null,
|
|
197
|
+
},
|
|
198
|
+
requireInteraction: false,
|
|
199
|
+
silent: false,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return clients
|
|
203
|
+
.matchAll({ type: 'window', includeUncontrolled: true })
|
|
204
|
+
.then((windowClients) => {
|
|
205
|
+
postBridgeToClients(windowClients, bridgePayload);
|
|
128
206
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
);
|
|
138
|
-
return self.registration.showNotification(title, options);
|
|
139
|
-
} else {
|
|
140
|
-
console.log(
|
|
207
|
+
if (!isAnyClientVisible(windowClients)) {
|
|
208
|
+
/* console.log(
|
|
209
|
+
`[NotifyPolicy] source=fcm_bg action=show reason=${isSystem ? 'system' : 'background'} msgId=${messageId || ''}`
|
|
210
|
+
);*/
|
|
211
|
+
return self.registration.showNotification(title, options);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* console.log(
|
|
141
215
|
`[NotifyPolicy] source=fcm_bg action=skip reason=tab_visible msgId=${messageId || ''}`
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
}
|
|
216
|
+
);*/
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
146
220
|
|
|
147
|
-
// ──
|
|
148
|
-
// This fires for ANY push delivered via the Web Push protocol,
|
|
149
|
-
// including direct backend pushes for XMPP messages that are
|
|
150
|
-
// NOT routed through FCM.
|
|
221
|
+
// ── PUSH (WebPush / text / fallback) ──────────────────────────
|
|
151
222
|
self.addEventListener('push', (event) => {
|
|
152
|
-
|
|
223
|
+
// console.log('[SW] push event');
|
|
153
224
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} catch (err) {
|
|
160
|
-
console.warn('[SW] Failed to parse push payload as JSON:', err);
|
|
161
|
-
raw = { title: 'New message', body: event.data ? event.data.text() : '' };
|
|
225
|
+
const { sourceType, raw } = normalizeRawPushPayload(event);
|
|
226
|
+
|
|
227
|
+
if (!raw || typeof raw !== 'object') {
|
|
228
|
+
console.error('[SW][BROKEN_PUSH]', raw);
|
|
229
|
+
return;
|
|
162
230
|
}
|
|
163
231
|
|
|
164
232
|
const notification = raw.notification || {};
|
|
165
233
|
const payloadData = raw.data || {};
|
|
166
|
-
const messageId =
|
|
234
|
+
const messageId =
|
|
235
|
+
raw.messageId ||
|
|
236
|
+
payloadData.msgID ||
|
|
237
|
+
payloadData.messageId ||
|
|
238
|
+
payloadData.message_id ||
|
|
239
|
+
null;
|
|
167
240
|
|
|
168
|
-
// Skip if FCM already handled this exact message
|
|
169
241
|
if (wasHandledByFcm(messageId)) {
|
|
170
|
-
|
|
242
|
+
// console.log(`[SW] push skipped – already handled by FCM (msgId=${messageId})`);
|
|
171
243
|
return;
|
|
172
244
|
}
|
|
173
245
|
|
|
246
|
+
if (sourceType !== 'json') {
|
|
247
|
+
/* console.log('[SW][NON_JSON_PUSH_PROCESSED]', {
|
|
248
|
+
sourceType,
|
|
249
|
+
raw,
|
|
250
|
+
});*/
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!raw.from && !raw.fcmOptions) {
|
|
254
|
+
// console.log('[SW][NON_FCM_PUSH]', raw);
|
|
255
|
+
}
|
|
256
|
+
|
|
174
257
|
const isSystem = isSystemPayload(payloadData);
|
|
258
|
+
|
|
175
259
|
const title =
|
|
176
260
|
(isSystem ? 'System' : undefined) ||
|
|
177
261
|
notification.title ||
|
|
178
262
|
raw.title ||
|
|
179
263
|
'Ethora Chat';
|
|
264
|
+
|
|
180
265
|
const body =
|
|
181
266
|
notification.body ||
|
|
182
267
|
raw.body ||
|
|
183
268
|
raw.message ||
|
|
184
269
|
payloadData.body ||
|
|
185
270
|
'You have a new message.';
|
|
271
|
+
|
|
272
|
+
const icon =
|
|
273
|
+
raw.icon ||
|
|
274
|
+
notification.icon ||
|
|
275
|
+
notification.image ||
|
|
276
|
+
'/favicon.ico';
|
|
277
|
+
|
|
186
278
|
const url = buildTargetUrl(
|
|
187
279
|
payloadData,
|
|
188
280
|
notification,
|
|
@@ -194,20 +286,27 @@ self.addEventListener('push', (event) => {
|
|
|
194
286
|
notification: {
|
|
195
287
|
title,
|
|
196
288
|
body,
|
|
197
|
-
image:
|
|
289
|
+
image: icon,
|
|
198
290
|
},
|
|
199
291
|
data: {
|
|
200
292
|
...payloadData,
|
|
201
293
|
jid: raw.roomJid || payloadData.jid || '',
|
|
202
294
|
userJid: raw.senderId || payloadData.userJid || '',
|
|
203
295
|
msgID: messageId || '',
|
|
204
|
-
workspaceId:
|
|
296
|
+
workspaceId:
|
|
297
|
+
raw.workspaceId ||
|
|
298
|
+
payloadData.workspaceId ||
|
|
299
|
+
payloadData.workspace_id ||
|
|
300
|
+
'',
|
|
301
|
+
},
|
|
302
|
+
meta: {
|
|
303
|
+
source: sourceType === 'json' ? 'webpush' : 'text_push',
|
|
205
304
|
},
|
|
206
305
|
};
|
|
207
306
|
|
|
208
307
|
const options = {
|
|
209
308
|
body,
|
|
210
|
-
icon
|
|
309
|
+
icon,
|
|
211
310
|
badge: raw.badge || '/favicon.ico',
|
|
212
311
|
tag: raw.tag || 'ethora-notification',
|
|
213
312
|
data: {
|
|
@@ -215,7 +314,12 @@ self.addEventListener('push', (event) => {
|
|
|
215
314
|
roomJid: raw.roomJid || payloadData.jid || null,
|
|
216
315
|
senderId: raw.senderId || payloadData.userJid || null,
|
|
217
316
|
messageId,
|
|
218
|
-
workspaceId:
|
|
317
|
+
workspaceId:
|
|
318
|
+
raw.workspaceId ||
|
|
319
|
+
payloadData.workspaceId ||
|
|
320
|
+
payloadData.workspace_id ||
|
|
321
|
+
null,
|
|
322
|
+
sourceType,
|
|
219
323
|
},
|
|
220
324
|
requireInteraction: false,
|
|
221
325
|
silent: false,
|
|
@@ -228,29 +332,30 @@ self.addEventListener('push', (event) => {
|
|
|
228
332
|
postBridgeToClients(windowClients, bridgePayload);
|
|
229
333
|
|
|
230
334
|
if (!isAnyClientVisible(windowClients)) {
|
|
231
|
-
console.log(
|
|
232
|
-
`[NotifyPolicy] source=push_bg action=show
|
|
233
|
-
)
|
|
335
|
+
/* console.log(
|
|
336
|
+
`[NotifyPolicy] source=push_bg action=show type=${sourceType} msgId=${messageId || ''}`
|
|
337
|
+
);*/
|
|
234
338
|
return self.registration.showNotification(title, options);
|
|
235
|
-
} else {
|
|
236
|
-
console.log(
|
|
237
|
-
`[NotifyPolicy] source=push_bg action=skip reason=tab_visible msgId=${messageId || ''}`
|
|
238
|
-
);
|
|
239
339
|
}
|
|
340
|
+
|
|
341
|
+
/* console.log(
|
|
342
|
+
`[NotifyPolicy] source=push_bg action=skip reason=tab_visible type=${sourceType} msgId=${messageId || ''}`
|
|
343
|
+
);*/
|
|
240
344
|
})
|
|
241
345
|
);
|
|
242
346
|
});
|
|
243
347
|
|
|
244
|
-
// ──
|
|
348
|
+
// ── Notification Click ────────────────────────────────────────
|
|
245
349
|
self.addEventListener('notificationclick', (event) => {
|
|
246
|
-
|
|
350
|
+
// console.log('[NotifyPolicy] source=push_bg action=click_start');
|
|
247
351
|
event.notification.close();
|
|
248
352
|
|
|
249
353
|
const targetUrl =
|
|
250
354
|
(event.notification.data && event.notification.data.url) || APP_URL;
|
|
251
|
-
|
|
355
|
+
|
|
356
|
+
/* console.log('[NotifyPolicy] source=push_bg action=click_url', {
|
|
252
357
|
url: targetUrl,
|
|
253
|
-
})
|
|
358
|
+
});*/
|
|
254
359
|
|
|
255
360
|
async function handleClick() {
|
|
256
361
|
try {
|
|
@@ -258,49 +363,64 @@ self.addEventListener('notificationclick', (event) => {
|
|
|
258
363
|
type: 'window',
|
|
259
364
|
includeUncontrolled: true,
|
|
260
365
|
});
|
|
261
|
-
|
|
366
|
+
|
|
367
|
+
/* console.log('[NotifyPolicy] source=push_bg action=clients_found', {
|
|
262
368
|
count: windowClients.length,
|
|
369
|
+
});*/
|
|
370
|
+
|
|
371
|
+
const clickPayload = {
|
|
372
|
+
type: 'PUSH_NOTIFICATION_CLICK',
|
|
373
|
+
data: event.notification.data || {},
|
|
374
|
+
notification: {
|
|
375
|
+
title: event.notification.title || '',
|
|
376
|
+
body: event.notification.body || '',
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
windowClients.forEach((client) => {
|
|
381
|
+
client.postMessage(clickPayload);
|
|
263
382
|
});
|
|
264
383
|
|
|
265
|
-
// 1. Try to find an exactly matching URL
|
|
266
384
|
for (const client of windowClients) {
|
|
267
385
|
if (client.url === targetUrl && 'focus' in client) {
|
|
268
|
-
console.log('[NotifyPolicy] source=push_bg action=focus_exact', {
|
|
386
|
+
/* console.log('[NotifyPolicy] source=push_bg action=focus_exact', {
|
|
269
387
|
url: client.url,
|
|
270
|
-
})
|
|
388
|
+
});*/
|
|
271
389
|
return client.focus();
|
|
272
390
|
}
|
|
273
391
|
}
|
|
274
392
|
|
|
275
|
-
// 2. Try to find any client on our origin, focus it, and navigate
|
|
276
393
|
for (const client of windowClients) {
|
|
277
394
|
try {
|
|
278
395
|
const clientOrigin = new URL(client.url).origin;
|
|
279
396
|
const swOrigin = self.location.origin;
|
|
397
|
+
|
|
280
398
|
if (clientOrigin === swOrigin && 'focus' in client) {
|
|
281
|
-
console.log(
|
|
399
|
+
/* console.log(
|
|
282
400
|
'[NotifyPolicy] source=push_bg action=focus_and_navigate',
|
|
283
401
|
{ from: client.url, to: targetUrl }
|
|
284
|
-
)
|
|
402
|
+
);*/
|
|
403
|
+
|
|
285
404
|
const focusedClient = await client.focus();
|
|
405
|
+
|
|
286
406
|
if (focusedClient && 'navigate' in focusedClient) {
|
|
287
407
|
return focusedClient.navigate(targetUrl);
|
|
288
408
|
}
|
|
409
|
+
|
|
410
|
+
return focusedClient;
|
|
289
411
|
}
|
|
290
|
-
} catch (
|
|
291
|
-
// Ignore invalid URLs
|
|
292
|
-
}
|
|
412
|
+
} catch (_) {}
|
|
293
413
|
}
|
|
294
414
|
|
|
295
|
-
// 3. Fallback: Open a new window
|
|
296
415
|
if (clients.openWindow) {
|
|
297
|
-
console.log('[NotifyPolicy] source=push_bg action=open_new_window', {
|
|
416
|
+
/* console.log('[NotifyPolicy] source=push_bg action=open_new_window', {
|
|
298
417
|
url: targetUrl,
|
|
299
|
-
})
|
|
418
|
+
});*/
|
|
300
419
|
return clients.openWindow(targetUrl);
|
|
301
420
|
}
|
|
302
421
|
} catch (err) {
|
|
303
422
|
console.error('[NotifyPolicy] source=push_bg action=click_error', err);
|
|
423
|
+
|
|
304
424
|
if (clients.openWindow) {
|
|
305
425
|
return clients.openWindow(targetUrl);
|
|
306
426
|
}
|
|
@@ -310,42 +430,27 @@ self.addEventListener('notificationclick', (event) => {
|
|
|
310
430
|
event.waitUntil(handleClick());
|
|
311
431
|
});
|
|
312
432
|
|
|
313
|
-
// ──
|
|
314
|
-
// Fired when the browser automatically re-creates a subscription
|
|
315
|
-
// (e.g. after expiry). Notify clients so they can re-register.
|
|
433
|
+
// ── Debug / Subscription ──────────────────────────────────────
|
|
316
434
|
self.addEventListener('pushsubscriptionchange', (event) => {
|
|
317
|
-
|
|
318
|
-
|
|
435
|
+
// console.warn('[SW][POSSIBLE_DROP] subscription changed');
|
|
319
436
|
event.waitUntil(
|
|
320
|
-
|
|
321
|
-
.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
'[SW] New subscription obtained:',
|
|
325
|
-
subscription.endpoint
|
|
326
|
-
);
|
|
327
|
-
return clients.matchAll().then((allClients) => {
|
|
328
|
-
allClients.forEach((client) => {
|
|
329
|
-
client.postMessage({
|
|
330
|
-
type: 'PUSH_SUBSCRIPTION_CHANGED',
|
|
331
|
-
subscription: subscription.toJSON(),
|
|
332
|
-
});
|
|
333
|
-
});
|
|
437
|
+
clients.matchAll({ type: 'window', includeUncontrolled: true }).then((allClients) => {
|
|
438
|
+
allClients.forEach((client) => {
|
|
439
|
+
client.postMessage({
|
|
440
|
+
type: 'PUSH_SUBSCRIPTION_CHANGED',
|
|
334
441
|
});
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
console.error('[SW] pushsubscriptionchange – failed to re-subscribe:', err);
|
|
338
|
-
})
|
|
442
|
+
});
|
|
443
|
+
})
|
|
339
444
|
);
|
|
340
445
|
});
|
|
341
446
|
|
|
342
|
-
// ──
|
|
447
|
+
// ── Lifecycle ─────────────────────────────────────────────────
|
|
343
448
|
self.addEventListener('activate', (event) => {
|
|
344
|
-
|
|
449
|
+
// console.warn('[SW] activated');
|
|
345
450
|
event.waitUntil(clients.claim());
|
|
346
451
|
});
|
|
347
452
|
|
|
348
|
-
self.addEventListener('install', () => {
|
|
349
|
-
|
|
350
|
-
self.skipWaiting();
|
|
453
|
+
self.addEventListener('install', (event) => {
|
|
454
|
+
// console.log('[SW] installed');
|
|
455
|
+
event.waitUntil(self.skipWaiting());
|
|
351
456
|
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { User } from '../types/types';
|
|
2
|
+
export declare const sanitizeUserForPersistentStorage: (user?: User | null) => User | null;
|
|
3
|
+
export declare const hasStoredSensitiveSession: (user?: Partial<User> | null) => boolean;
|
|
4
|
+
export declare const persistUserSession: (user?: User | null) => void;
|
|
5
|
+
export declare const getStoredUser: (expectedAppId?: string) => User | null;
|
|
6
|
+
export declare const clearStoredUser: () => void;
|