web-manager 4.0.15 → 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 +138 -36
- package/dist/modules/utilities.js +41 -19
- package/package.json +1 -1
@@ -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
|
|
@@ -199,30 +199,47 @@ class Notifications {
|
|
199
199
|
return onMessage(messaging, (payload) => {
|
200
200
|
console.log('Foreground message received:', payload);
|
201
201
|
|
202
|
-
//
|
203
|
-
|
204
|
-
|
205
|
-
|
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;
|
206
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,6 +247,35 @@ 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 {
|
@@ -240,41 +286,97 @@ class Notifications {
|
|
240
286
|
return;
|
241
287
|
}
|
242
288
|
|
243
|
-
const { doc, setDoc,
|
289
|
+
const { doc, getDoc, setDoc, updateDoc, deleteDoc } = await import('firebase/firestore');
|
290
|
+
const { getContext } = await import('./utilities.js');
|
244
291
|
|
245
|
-
const
|
246
|
-
|
247
|
-
|
248
|
-
userAgent: navigator.userAgent,
|
249
|
-
tags: ['general'],
|
250
|
-
created: serverTimestamp(),
|
251
|
-
updated: serverTimestamp(),
|
252
|
-
};
|
292
|
+
const now = new Date();
|
293
|
+
const timestamp = now.toISOString();
|
294
|
+
const timestampUNIX = Math.floor(now.getTime() / 1000);
|
253
295
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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
|
+
};
|
258
330
|
|
259
|
-
|
260
|
-
await setDoc(
|
261
|
-
doc(firestore, 'notifications', token),
|
262
|
-
subscriptionData,
|
263
|
-
{ merge: true }
|
264
|
-
);
|
331
|
+
await setDoc(notificationRef, subscriptionData);
|
265
332
|
|
266
|
-
|
267
|
-
|
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
|
+
};
|
345
|
+
|
346
|
+
await updateDoc(notificationRef, updateData);
|
347
|
+
}
|
348
|
+
// If no update needed, do nothing
|
349
|
+
|
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
|
@@ -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