@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 CHANGED
@@ -116,40 +116,6 @@ npm run dev
116
116
 
117
117
  Open `http://localhost:5173`.
118
118
 
119
- Static reference screenshots:
120
-
121
- ![Quick setup step 1](./img/readme01.png)
122
- ![Quick setup step 2](./img/readme02.png)
123
- ![Chat initial view](./img/readme03.png)
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
- ![GIF Placeholder: Install + first render](./img/placeholder-01-install-first-render.gif)
132
-
133
- 2. Login/auth flow
134
- Target file: `img/placeholder-02-login-auth-flow.gif`
135
- ![GIF Placeholder: Login/auth flow](./img/placeholder-02-login-auth-flow.gif)
136
-
137
- 3. Room switching + messaging
138
- Target file: `img/placeholder-03-room-switching-messaging.gif`
139
- ![GIF Placeholder: Room switching + messaging](./img/placeholder-03-room-switching-messaging.gif)
140
-
141
- 4. Push prompt + receive flow
142
- Target file: `img/placeholder-04-push-flow.gif`
143
- ![GIF Placeholder: Push notification flow](./img/placeholder-04-push-flow.gif)
144
-
145
- 5. Widget customization before/after
146
- Target file: `img/placeholder-05-widget-customization.gif`
147
- ![GIF Placeholder: Widget customization](./img/placeholder-05-widget-customization.gif)
148
-
149
- 6. Mobile responsive behavior
150
- Target file: `img/placeholder-06-mobile-responsive.gif`
151
- ![GIF Placeholder: Mobile responsive behavior](./img/placeholder-06-mobile-responsive.gif)
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
  />
@@ -1,3 +1,6 @@
1
1
  import { default as React } from 'react';
2
- declare const ConnectionBanner: React.FC;
2
+ interface ConnectionBannerProps {
3
+ message?: string;
4
+ }
5
+ declare const ConnectionBanner: React.FC<ConnectionBannerProps>;
3
6
  export default ConnectionBanner;
@@ -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
- // Only initialize if we have at least apiKey and appId
24
- if (firebaseConfig.apiKey && firebaseConfig.appId) {
25
- firebase.initializeApp(firebaseConfig);
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; // auto-cleanup after 60 s
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
- console.log('[SW] FCM message marked as handled:', id);
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
- // ── Shared Helpers ────────────────────────────────────────────
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
- return `${APP_URL}/chat?chatId=${encodeURIComponent(chatId)}`;
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, bridgePayload) {
78
+ function postBridgeToClients(windowClients, payload) {
66
79
  windowClients.forEach((client) => {
67
80
  client.postMessage({
68
81
  type: 'PUSH_FOREGROUND_BRIDGE',
69
- payload: bridgePayload,
82
+ payload,
70
83
  });
71
84
  });
72
85
  }
73
86
 
74
- function isAnyClientVisible(windowClients) {
75
- return windowClients.some((c) => c.visibilityState === 'visible');
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
- // ── 1. FCM Background Messages ───────────────────────────────
79
- // This fires for pushes delivered through Firebase Cloud Messaging
80
- // when no foreground tab is handling them via onMessage().
81
- messaging.onBackgroundMessage((payload) => {
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
- const data = payload.data || {};
85
- const notification = payload.notification || {};
86
- const messageId = payload.messageId || data.msgID || null;
149
+ const data = payload.data || {};
150
+ const notification = payload.notification || {};
151
+ const messageId = payload.messageId || data.msgID || null;
87
152
 
88
- // Mark so the generic push handler skips this one
89
- markHandledByFcm(messageId);
153
+ markHandledByFcm(messageId);
90
154
 
91
- const isSystem = isSystemPayload(data);
92
- const title =
93
- (isSystem ? 'System' : undefined) ||
94
- notification.title ||
95
- data.title ||
96
- 'New Message';
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
- const bridgePayload = {
102
- messageId,
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
- const options = {
114
- body,
115
- icon: notification.image || '/favicon.ico',
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
- workspaceId: data.workspaceId || data.workspace_id || null,
124
- },
125
- requireInteraction: false,
126
- silent: false,
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
- return clients
130
- .matchAll({ type: 'window', includeUncontrolled: true })
131
- .then((windowClients) => {
132
- postBridgeToClients(windowClients, bridgePayload);
133
-
134
- if (!isAnyClientVisible(windowClients)) {
135
- console.log(
136
- `[NotifyPolicy] source=fcm_bg action=show reason=${isSystem ? 'system' : 'background'} msgId=${messageId || ''}`
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
- // ── 2. Generic Web Push API ───────────────────────────────────
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
- console.log('[SW] Generic push event received');
223
+ // console.log('[SW] push event');
153
224
 
154
- let raw = {};
155
- try {
156
- if (event.data) {
157
- raw = event.data.json();
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 = raw.messageId || payloadData.msgID || null;
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
- console.log(`[SW] push event skipped – already handled by FCM (msgId=${messageId})`);
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: raw.icon || '/favicon.ico',
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: raw.workspaceId || payloadData.workspaceId || payloadData.workspace_id || '',
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: raw.icon || '/favicon.ico',
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: raw.workspaceId || payloadData.workspaceId || payloadData.workspace_id || null,
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 reason=${isSystem ? 'system' : 'background'} msgId=${messageId || ''}`
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
- // ── 3. Notification Click ─────────────────────────────────────
348
+ // ── Notification Click ────────────────────────────────────────
245
349
  self.addEventListener('notificationclick', (event) => {
246
- console.log('[NotifyPolicy] source=push_bg action=click_start');
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
- console.log('[NotifyPolicy] source=push_bg action=click_url', {
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
- console.log('[NotifyPolicy] source=push_bg action=clients_found', {
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 (e) {
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
- // ── 4. Push Subscription Change ───────────────────────────────
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
- console.log('[SW] Push subscription changed – re-subscribing');
318
-
435
+ // console.warn('[SW][POSSIBLE_DROP] subscription changed');
319
436
  event.waitUntil(
320
- self.registration.pushManager
321
- .subscribe({ userVisibleOnly: true })
322
- .then((subscription) => {
323
- console.log(
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
- .catch((err) => {
337
- console.error('[SW] pushsubscriptionchange – failed to re-subscribe:', err);
338
- })
442
+ });
443
+ })
339
444
  );
340
445
  });
341
446
 
342
- // ── 5. Lifecycle ──────────────────────────────────────────────
447
+ // ── Lifecycle ─────────────────────────────────────────────────
343
448
  self.addEventListener('activate', (event) => {
344
- console.log('[SW] Service worker activated');
449
+ // console.warn('[SW] activated');
345
450
  event.waitUntil(clients.claim());
346
451
  });
347
452
 
348
- self.addEventListener('install', () => {
349
- console.log('[SW] Service worker installed');
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;