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 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 (!('Notification' in window)) {
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
- // Show notification if app is in foreground
203
- if (payload.notification) {
204
- const { title, body, icon, badge, image } = payload.notification;
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
- data: payload.data,
213
- requireInteraction: false,
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
- if (clickAction) {
219
- window.open(clickAction, '_blank');
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(payload);
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, serverTimestamp } = await import('firebase/firestore');
264
+ const { doc, getDoc, setDoc, updateDoc, deleteDoc } = await import('firebase/firestore');
265
+ const { getContext } = await import('./utilities.js');
244
266
 
245
- const subscriptionData = {
246
- token,
247
- platform: 'web',
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
- if (user) {
255
- subscriptionData.userId = user.uid;
256
- subscriptionData.userEmail = user.email;
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
- // Save to notifications collection
260
- await setDoc(
261
- doc(firestore, 'notifications', token),
262
- subscriptionData,
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
- // If user is authenticated, also save reference in user document
267
- if (user) {
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: serverTimestamp(),
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
- width: window.screen?.width,
132
- height: window.screen?.height,
133
- availWidth: window.screen?.availWidth,
134
- availHeight: window.screen?.availHeight,
135
- colorDepth: window.screen?.colorDepth,
136
- pixelRatio: window.devicePixelRatio || 1,
137
- },
138
- viewport: {
139
- width: window.innerWidth,
140
- height: window.innerHeight,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-manager",
3
- "version": "4.0.15",
3
+ "version": "4.0.17",
4
4
  "description": "Easily access important variables such as the query string, current domain, and current page in a single object.",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.js",