web-manager 4.0.14 → 4.0.16
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/modules/notifications.js +151 -49
- package/dist/modules/utilities.js +41 -19
- package/package.json +1 -1
@@ -6,8 +6,8 @@ class Notifications {
|
|
6
6
|
|
7
7
|
// Check if notifications are supported
|
8
8
|
isSupported() {
|
9
|
-
return 'Notification' in window &&
|
10
|
-
'serviceWorker' in navigator &&
|
9
|
+
return 'Notification' in window &&
|
10
|
+
'serviceWorker' in navigator &&
|
11
11
|
this.manager.firebaseMessaging !== undefined;
|
12
12
|
}
|
13
13
|
|
@@ -79,7 +79,7 @@ class Notifications {
|
|
79
79
|
storage.set('notifications', {
|
80
80
|
subscribed: true,
|
81
81
|
token: token,
|
82
|
-
|
82
|
+
timestamp: new Date().toISOString(),
|
83
83
|
uid: this.manager.auth().getUser()?.uid || null
|
84
84
|
});
|
85
85
|
|
@@ -102,7 +102,7 @@ class Notifications {
|
|
102
102
|
|
103
103
|
const { deleteToken } = await import('firebase/messaging');
|
104
104
|
const messaging = this.manager.firebaseMessaging;
|
105
|
-
|
105
|
+
|
106
106
|
if (messaging) {
|
107
107
|
await deleteToken(messaging);
|
108
108
|
}
|
@@ -121,7 +121,7 @@ class Notifications {
|
|
121
121
|
// Request permission (without subscribing)
|
122
122
|
async requestPermission() {
|
123
123
|
try {
|
124
|
-
if (!(
|
124
|
+
if (!this.isSupported()) {
|
125
125
|
throw new Error('Notifications not supported');
|
126
126
|
}
|
127
127
|
|
@@ -147,7 +147,7 @@ class Notifications {
|
|
147
147
|
|
148
148
|
const { getToken } = await import('firebase/messaging');
|
149
149
|
const swRegistration = this.manager.state.serviceWorker;
|
150
|
-
|
150
|
+
|
151
151
|
return await getToken(messaging, {
|
152
152
|
serviceWorkerRegistration: swRegistration
|
153
153
|
});
|
@@ -166,7 +166,7 @@ class Notifications {
|
|
166
166
|
|
167
167
|
const { onMessage } = await import('firebase/messaging');
|
168
168
|
const messaging = this.manager.firebaseMessaging;
|
169
|
-
|
169
|
+
|
170
170
|
if (!messaging) {
|
171
171
|
return () => {};
|
172
172
|
}
|
@@ -191,38 +191,55 @@ class Notifications {
|
|
191
191
|
|
192
192
|
const { onMessage } = await import('firebase/messaging');
|
193
193
|
const messaging = this.manager.firebaseMessaging;
|
194
|
-
|
194
|
+
|
195
195
|
if (!messaging) {
|
196
196
|
return () => {};
|
197
197
|
}
|
198
198
|
|
199
199
|
return onMessage(messaging, (payload) => {
|
200
200
|
console.log('Foreground message received:', payload);
|
201
|
-
|
202
|
-
//
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
201
|
+
|
202
|
+
// Extract notification data - handle both payload.notification and payload.data formats
|
203
|
+
const notificationData = payload.notification || payload.data || {};
|
204
|
+
const { title, body, icon, badge, image, click_action, url, tag } = notificationData;
|
205
|
+
|
206
|
+
// Determine the click URL (prioritize click_action, then url, then data.url)
|
207
|
+
const clickUrl = click_action || url || payload.data?.click_action || payload.data?.url;
|
208
|
+
|
209
|
+
// Show notification if we have at least a title
|
210
|
+
if (title) {
|
207
211
|
const notification = new Notification(title, {
|
208
|
-
body,
|
209
|
-
icon,
|
210
|
-
badge,
|
211
|
-
image,
|
212
|
-
|
213
|
-
|
212
|
+
body: body || '',
|
213
|
+
icon: icon || '/favicon.ico',
|
214
|
+
badge: badge,
|
215
|
+
image: image,
|
216
|
+
tag: tag || 'default',
|
217
|
+
data: { ...payload.data, clickUrl },
|
218
|
+
requireInteraction: true,
|
219
|
+
renotify: true
|
214
220
|
});
|
215
221
|
|
216
222
|
notification.onclick = (event) => {
|
217
223
|
event.preventDefault();
|
218
|
-
|
219
|
-
|
224
|
+
|
225
|
+
// Focus or open the target window
|
226
|
+
if (clickUrl) {
|
227
|
+
// Try to find an existing window/tab with this URL
|
228
|
+
window.focus();
|
229
|
+
window.open(clickUrl, '_blank');
|
230
|
+
} else {
|
231
|
+
// Just focus the current window if no URL
|
232
|
+
window.focus();
|
220
233
|
}
|
234
|
+
|
221
235
|
notification.close();
|
222
236
|
};
|
223
237
|
}
|
224
238
|
|
225
|
-
callback
|
239
|
+
// Call the user's callback with the full payload
|
240
|
+
if (callback) {
|
241
|
+
callback(payload);
|
242
|
+
}
|
226
243
|
});
|
227
244
|
} catch (error) {
|
228
245
|
console.error('Message listener error:', error);
|
@@ -230,51 +247,136 @@ class Notifications {
|
|
230
247
|
}
|
231
248
|
}
|
232
249
|
|
250
|
+
// Sync subscription when auth state changes
|
251
|
+
async syncSubscription() {
|
252
|
+
try {
|
253
|
+
// Check if we have a stored notification token
|
254
|
+
const storage = this.manager.storage();
|
255
|
+
const storedNotification = storage.get('notifications');
|
256
|
+
|
257
|
+
if (!storedNotification?.token) {
|
258
|
+
return false;
|
259
|
+
}
|
260
|
+
|
261
|
+
// Update the subscription in Firestore with current auth state
|
262
|
+
await this._saveSubscription(storedNotification.token);
|
263
|
+
|
264
|
+
// Update local storage with current user ID
|
265
|
+
const user = this.manager.auth().getUser();
|
266
|
+
storage.set('notifications', {
|
267
|
+
...storedNotification,
|
268
|
+
uid: user?.uid || null,
|
269
|
+
timestamp: new Date().toISOString()
|
270
|
+
});
|
271
|
+
|
272
|
+
return true;
|
273
|
+
} catch (error) {
|
274
|
+
console.error('Sync subscription error:', error);
|
275
|
+
return false;
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
233
279
|
// Save subscription to Firestore
|
234
280
|
async _saveSubscription(token) {
|
235
281
|
try {
|
236
282
|
const firestore = this.manager.firebaseFirestore;
|
237
283
|
const user = this.manager.auth().getUser();
|
238
|
-
|
284
|
+
|
239
285
|
if (!firestore || !token) {
|
240
286
|
return;
|
241
287
|
}
|
242
288
|
|
243
|
-
const { doc, setDoc,
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
289
|
+
const { doc, getDoc, setDoc, updateDoc, deleteDoc } = await import('firebase/firestore');
|
290
|
+
const { getContext } = await import('./utilities.js');
|
291
|
+
|
292
|
+
const now = new Date();
|
293
|
+
const timestamp = now.toISOString();
|
294
|
+
const timestampUNIX = Math.floor(now.getTime() / 1000);
|
295
|
+
|
296
|
+
// Get context for client information
|
297
|
+
const context = getContext();
|
298
|
+
const clientData = context.client;
|
299
|
+
|
300
|
+
// Reference to the notification document (ID is the token)
|
301
|
+
const notificationRef = doc(firestore, 'notifications', token);
|
302
|
+
|
303
|
+
// Check if document already exists
|
304
|
+
const existingDoc = await getDoc(notificationRef);
|
305
|
+
const existingData = existingDoc.exists() ? existingDoc.data() : null;
|
306
|
+
|
307
|
+
// Determine if we need to update
|
308
|
+
const currentUid = user?.uid || null;
|
309
|
+
const existingUid = existingData?.uid || null;
|
310
|
+
const needsUpdate = existingUid !== currentUid;
|
311
|
+
|
312
|
+
if (!existingData) {
|
313
|
+
// New subscription - create the document
|
314
|
+
const subscriptionData = {
|
315
|
+
token,
|
316
|
+
context: {
|
317
|
+
client: clientData
|
318
|
+
},
|
319
|
+
tags: ['general'],
|
320
|
+
created: {
|
321
|
+
timestamp,
|
322
|
+
timestampUNIX
|
323
|
+
},
|
324
|
+
updated: {
|
325
|
+
timestamp,
|
326
|
+
timestampUNIX
|
327
|
+
},
|
328
|
+
uid: currentUid
|
329
|
+
};
|
330
|
+
|
331
|
+
await setDoc(notificationRef, subscriptionData);
|
332
|
+
|
333
|
+
} else if (needsUpdate) {
|
334
|
+
// Existing subscription needs update (userId changed)
|
335
|
+
const updateData = {
|
336
|
+
context: {
|
337
|
+
client: clientData
|
338
|
+
},
|
339
|
+
updated: {
|
340
|
+
timestamp,
|
341
|
+
timestampUNIX
|
342
|
+
},
|
343
|
+
uid: currentUid
|
344
|
+
};
|
258
345
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
subscriptionData,
|
263
|
-
{ merge: true }
|
264
|
-
);
|
346
|
+
await updateDoc(notificationRef, updateData);
|
347
|
+
}
|
348
|
+
// If no update needed, do nothing
|
265
349
|
|
266
|
-
//
|
267
|
-
if (user) {
|
350
|
+
// Update user's notification reference if authenticated
|
351
|
+
if (user && (!existingData || needsUpdate)) {
|
268
352
|
await setDoc(
|
269
353
|
doc(firestore, 'users', user.uid, 'notifications', token),
|
270
354
|
{
|
271
355
|
token,
|
272
|
-
created:
|
356
|
+
created: existingData?.created || {
|
357
|
+
timestamp,
|
358
|
+
timestampUNIX
|
359
|
+
},
|
360
|
+
updated: {
|
361
|
+
timestamp,
|
362
|
+
timestampUNIX
|
363
|
+
},
|
273
364
|
active: true
|
274
365
|
},
|
275
366
|
{ merge: true }
|
276
367
|
);
|
277
368
|
}
|
369
|
+
|
370
|
+
// Remove old user reference if user changed
|
371
|
+
if (existingUid && existingUid !== currentUid && existingUid !== null) {
|
372
|
+
try {
|
373
|
+
await deleteDoc(doc(firestore, 'users', existingUid, 'notifications', token));
|
374
|
+
} catch (err) {
|
375
|
+
// Ignore errors when cleaning up old references
|
376
|
+
console.log('Could not clean up old user notification reference:', err.message);
|
377
|
+
}
|
378
|
+
}
|
379
|
+
|
278
380
|
} catch (error) {
|
279
381
|
console.error('Save subscription error:', error);
|
280
382
|
// Don't throw - this is not critical for the subscription process
|
@@ -282,4 +384,4 @@ class Notifications {
|
|
282
384
|
}
|
283
385
|
}
|
284
386
|
|
285
|
-
export default Notifications;
|
387
|
+
export default Notifications;
|
@@ -60,22 +60,22 @@ export function showNotification(message, options = {}) {
|
|
60
60
|
// Handle different input types
|
61
61
|
let text = message;
|
62
62
|
let type = options.type || 'info';
|
63
|
-
|
63
|
+
|
64
64
|
// If message is an Error object, extract message and default to danger
|
65
65
|
if (message instanceof Error) {
|
66
66
|
text = message.message;
|
67
67
|
type = options.type || 'danger';
|
68
68
|
}
|
69
|
-
|
69
|
+
|
70
70
|
// Handle string as second parameter for backwards compatibility
|
71
71
|
if (typeof options === 'string') {
|
72
72
|
options = { type: options };
|
73
73
|
type = options.type;
|
74
74
|
}
|
75
|
-
|
75
|
+
|
76
76
|
// Extract options
|
77
77
|
const timeout = options.timeout !== undefined ? options.timeout : 5000;
|
78
|
-
|
78
|
+
|
79
79
|
const $notification = document.createElement('div');
|
80
80
|
$notification.className = `alert alert-${type} alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-5`;
|
81
81
|
$notification.style.zIndex = '9999';
|
@@ -116,28 +116,50 @@ export function getContext() {
|
|
116
116
|
}
|
117
117
|
}
|
118
118
|
|
119
|
+
// Get platform
|
120
|
+
function getPlatform() {
|
121
|
+
const platform = (navigator.userAgentData?.platform || navigator.platform || 'unknown').toLowerCase();
|
122
|
+
|
123
|
+
if (/iphone|ipad|ipod/.test(platform)) {
|
124
|
+
return 'ios';
|
125
|
+
} else if (/android/.test(platform)) {
|
126
|
+
return 'android';
|
127
|
+
} else if (/win/.test(platform)) {
|
128
|
+
return 'windows';
|
129
|
+
} else if (/mac/.test(platform)) {
|
130
|
+
return 'macos';
|
131
|
+
} else if (/linux/.test(platform)) {
|
132
|
+
return 'linux';
|
133
|
+
} else if (/cros/.test(platform)) {
|
134
|
+
return 'chromeos';
|
135
|
+
} else {
|
136
|
+
return 'unknown';
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
119
140
|
// Return context information
|
120
141
|
return {
|
121
142
|
client: {
|
143
|
+
language: navigator.language,
|
122
144
|
mobile: isMobile(),
|
145
|
+
platform: getPlatform(),
|
146
|
+
userAgent: navigator.userAgent,
|
147
|
+
url: window.location.href,
|
123
148
|
},
|
124
149
|
browser: {
|
125
|
-
userAgent: navigator.userAgent,
|
126
|
-
language: navigator.language,
|
127
|
-
platform: navigator.platform,
|
128
150
|
vendor: navigator.vendor,
|
129
151
|
},
|
130
|
-
screen: {
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
},
|
138
|
-
viewport: {
|
139
|
-
|
140
|
-
|
141
|
-
}
|
152
|
+
// screen: {
|
153
|
+
// width: window.screen?.width,
|
154
|
+
// height: window.screen?.height,
|
155
|
+
// availWidth: window.screen?.availWidth,
|
156
|
+
// availHeight: window.screen?.availHeight,
|
157
|
+
// colorDepth: window.screen?.colorDepth,
|
158
|
+
// pixelRatio: window.devicePixelRatio || 1,
|
159
|
+
// },
|
160
|
+
// viewport: {
|
161
|
+
// width: window.innerWidth,
|
162
|
+
// height: window.innerHeight,
|
163
|
+
// }
|
142
164
|
};
|
143
165
|
}
|
package/package.json
CHANGED