web-manager 4.0.15 → 4.0.17
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/index.js +5 -0
- package/dist/modules/notifications.js +138 -61
- package/dist/modules/utilities.js +41 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -311,6 +311,11 @@ class Manager {
|
|
|
311
311
|
const { getFirestore } = await import('firebase/firestore');
|
|
312
312
|
const { getMessaging } = await import('firebase/messaging');
|
|
313
313
|
|
|
314
|
+
// If we're in devmode, set the firebase config authDomain to the current host
|
|
315
|
+
// if (this.isDevelopment() && firebaseConfig) {
|
|
316
|
+
// firebaseConfig.authDomain = window.location.hostname;
|
|
317
|
+
// }
|
|
318
|
+
|
|
314
319
|
// Initialize Firebase
|
|
315
320
|
const app = initializeApp(firebaseConfig);
|
|
316
321
|
|
|
@@ -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
|
|
|
@@ -157,31 +157,6 @@ class Notifications {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
// Listen for token refresh
|
|
161
|
-
async onTokenRefresh(callback) {
|
|
162
|
-
try {
|
|
163
|
-
if (!this.isSupported()) {
|
|
164
|
-
return () => {};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const { onMessage } = await import('firebase/messaging');
|
|
168
|
-
const messaging = this.manager.firebaseMessaging;
|
|
169
|
-
|
|
170
|
-
if (!messaging) {
|
|
171
|
-
return () => {};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return onMessage(messaging, (payload) => {
|
|
175
|
-
if (payload.data?.refreshToken) {
|
|
176
|
-
callback(payload.data.token);
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
} catch (error) {
|
|
180
|
-
console.error('Token refresh listener error:', error);
|
|
181
|
-
return () => {};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
160
|
// Listen for foreground messages
|
|
186
161
|
async onMessage(callback) {
|
|
187
162
|
try {
|
|
@@ -199,30 +174,47 @@ class Notifications {
|
|
|
199
174
|
return onMessage(messaging, (payload) => {
|
|
200
175
|
console.log('Foreground message received:', payload);
|
|
201
176
|
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const clickAction = payload.data?.click_action || payload.notification.click_action;
|
|
177
|
+
// Extract notification data - handle both payload.notification and payload.data formats
|
|
178
|
+
const notificationData = payload.notification || payload.data || {};
|
|
179
|
+
const { title, body, icon, badge, image, click_action, url, tag } = notificationData;
|
|
206
180
|
|
|
181
|
+
// Determine the click URL (prioritize click_action, then url, then data.url)
|
|
182
|
+
const clickUrl = click_action || url || payload.data?.click_action || payload.data?.url;
|
|
183
|
+
|
|
184
|
+
// Show notification if we have at least a title
|
|
185
|
+
if (title) {
|
|
207
186
|
const notification = new Notification(title, {
|
|
208
|
-
body,
|
|
209
|
-
icon,
|
|
210
|
-
badge,
|
|
211
|
-
image,
|
|
212
|
-
|
|
213
|
-
|
|
187
|
+
body: body || '',
|
|
188
|
+
icon: icon || '/favicon.ico',
|
|
189
|
+
badge: badge,
|
|
190
|
+
image: image,
|
|
191
|
+
tag: tag || 'default',
|
|
192
|
+
data: { ...payload.data, clickUrl },
|
|
193
|
+
requireInteraction: true,
|
|
194
|
+
renotify: true
|
|
214
195
|
});
|
|
215
196
|
|
|
216
197
|
notification.onclick = (event) => {
|
|
217
198
|
event.preventDefault();
|
|
218
|
-
|
|
219
|
-
|
|
199
|
+
|
|
200
|
+
// Focus or open the target window
|
|
201
|
+
if (clickUrl) {
|
|
202
|
+
// Try to find an existing window/tab with this URL
|
|
203
|
+
window.focus();
|
|
204
|
+
window.open(clickUrl, '_blank');
|
|
205
|
+
} else {
|
|
206
|
+
// Just focus the current window if no URL
|
|
207
|
+
window.focus();
|
|
220
208
|
}
|
|
209
|
+
|
|
221
210
|
notification.close();
|
|
222
211
|
};
|
|
223
212
|
}
|
|
224
213
|
|
|
225
|
-
callback
|
|
214
|
+
// Call the user's callback with the full payload
|
|
215
|
+
if (callback) {
|
|
216
|
+
callback(payload);
|
|
217
|
+
}
|
|
226
218
|
});
|
|
227
219
|
} catch (error) {
|
|
228
220
|
console.error('Message listener error:', error);
|
|
@@ -230,6 +222,35 @@ class Notifications {
|
|
|
230
222
|
}
|
|
231
223
|
}
|
|
232
224
|
|
|
225
|
+
// Sync subscription when auth state changes
|
|
226
|
+
async syncSubscription() {
|
|
227
|
+
try {
|
|
228
|
+
// Check if we have a stored notification token
|
|
229
|
+
const storage = this.manager.storage();
|
|
230
|
+
const storedNotification = storage.get('notifications');
|
|
231
|
+
|
|
232
|
+
if (!storedNotification?.token) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Update the subscription in Firestore with current auth state
|
|
237
|
+
await this._saveSubscription(storedNotification.token);
|
|
238
|
+
|
|
239
|
+
// Update local storage with current user ID
|
|
240
|
+
const user = this.manager.auth().getUser();
|
|
241
|
+
storage.set('notifications', {
|
|
242
|
+
...storedNotification,
|
|
243
|
+
uid: user?.uid || null,
|
|
244
|
+
timestamp: new Date().toISOString()
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return true;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('Sync subscription error:', error);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
233
254
|
// Save subscription to Firestore
|
|
234
255
|
async _saveSubscription(token) {
|
|
235
256
|
try {
|
|
@@ -240,41 +261,97 @@ class Notifications {
|
|
|
240
261
|
return;
|
|
241
262
|
}
|
|
242
263
|
|
|
243
|
-
const { doc, setDoc,
|
|
264
|
+
const { doc, getDoc, setDoc, updateDoc, deleteDoc } = await import('firebase/firestore');
|
|
265
|
+
const { getContext } = await import('./utilities.js');
|
|
244
266
|
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
userAgent: navigator.userAgent,
|
|
249
|
-
tags: ['general'],
|
|
250
|
-
created: serverTimestamp(),
|
|
251
|
-
updated: serverTimestamp(),
|
|
252
|
-
};
|
|
267
|
+
const now = new Date();
|
|
268
|
+
const timestamp = now.toISOString();
|
|
269
|
+
const timestampUNIX = Math.floor(now.getTime() / 1000);
|
|
253
270
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
271
|
+
// Get context for client information
|
|
272
|
+
const context = getContext();
|
|
273
|
+
const clientData = context.client;
|
|
274
|
+
|
|
275
|
+
// Reference to the notification document (ID is the token)
|
|
276
|
+
const notificationRef = doc(firestore, 'notifications', token);
|
|
277
|
+
|
|
278
|
+
// Check if document already exists
|
|
279
|
+
const existingDoc = await getDoc(notificationRef);
|
|
280
|
+
const existingData = existingDoc.exists() ? existingDoc.data() : null;
|
|
258
281
|
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
{ merge: true }
|
|
264
|
-
);
|
|
282
|
+
// Determine if we need to update
|
|
283
|
+
const currentUid = user?.uid || null;
|
|
284
|
+
const existingUid = existingData?.uid || null;
|
|
285
|
+
const needsUpdate = existingUid !== currentUid;
|
|
265
286
|
|
|
266
|
-
|
|
267
|
-
|
|
287
|
+
if (!existingData) {
|
|
288
|
+
// New subscription - create the document
|
|
289
|
+
const subscriptionData = {
|
|
290
|
+
token,
|
|
291
|
+
context: {
|
|
292
|
+
client: clientData
|
|
293
|
+
},
|
|
294
|
+
tags: ['general'],
|
|
295
|
+
created: {
|
|
296
|
+
timestamp,
|
|
297
|
+
timestampUNIX
|
|
298
|
+
},
|
|
299
|
+
updated: {
|
|
300
|
+
timestamp,
|
|
301
|
+
timestampUNIX
|
|
302
|
+
},
|
|
303
|
+
uid: currentUid
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
await setDoc(notificationRef, subscriptionData);
|
|
307
|
+
|
|
308
|
+
} else if (needsUpdate) {
|
|
309
|
+
// Existing subscription needs update (userId changed)
|
|
310
|
+
const updateData = {
|
|
311
|
+
context: {
|
|
312
|
+
client: clientData
|
|
313
|
+
},
|
|
314
|
+
updated: {
|
|
315
|
+
timestamp,
|
|
316
|
+
timestampUNIX
|
|
317
|
+
},
|
|
318
|
+
uid: currentUid
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
await updateDoc(notificationRef, updateData);
|
|
322
|
+
}
|
|
323
|
+
// If no update needed, do nothing
|
|
324
|
+
|
|
325
|
+
// Update user's notification reference if authenticated
|
|
326
|
+
if (user && (!existingData || needsUpdate)) {
|
|
268
327
|
await setDoc(
|
|
269
328
|
doc(firestore, 'users', user.uid, 'notifications', token),
|
|
270
329
|
{
|
|
271
330
|
token,
|
|
272
|
-
created:
|
|
331
|
+
created: existingData?.created || {
|
|
332
|
+
timestamp,
|
|
333
|
+
timestampUNIX
|
|
334
|
+
},
|
|
335
|
+
updated: {
|
|
336
|
+
timestamp,
|
|
337
|
+
timestampUNIX
|
|
338
|
+
},
|
|
273
339
|
active: true
|
|
274
340
|
},
|
|
275
341
|
{ merge: true }
|
|
276
342
|
);
|
|
277
343
|
}
|
|
344
|
+
|
|
345
|
+
// Remove old user reference if user changed
|
|
346
|
+
if (existingUid && existingUid !== currentUid && existingUid !== null) {
|
|
347
|
+
try {
|
|
348
|
+
await deleteDoc(doc(firestore, 'users', existingUid, 'notifications', token));
|
|
349
|
+
} catch (err) {
|
|
350
|
+
// Ignore errors when cleaning up old references
|
|
351
|
+
console.log('Could not clean up old user notification reference:', err.message);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
278
355
|
} catch (error) {
|
|
279
356
|
console.error('Save subscription error:', error);
|
|
280
357
|
// 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