web-manager 4.1.1 → 4.1.3

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/CHANGELOG.md CHANGED
@@ -14,6 +14,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ ---
18
+ ## [4.1.1] - 2025-12-17
19
+ ### Added
20
+ - Added `getBrowser()` utility to detect browser type (chrome, firefox, safari, edge, opera, brave).
21
+ - Added `data-browser` HTML attribute set during initialization.
22
+ - Added `browser` and `vendor` fields to `getContext().client`.
23
+ - Added `geolocation` object to `getContext()` with placeholder fields (ip, country, region, city, latitude, longitude).
24
+
25
+ ### Changed
26
+ - Moved `vendor` from `browser` object to `client` object in `getContext()`.
27
+ - Replaced `browser` object with `geolocation` object in `getContext()` return structure.
28
+
17
29
  ---
18
30
  ## [4.1.0] - 2025-12-16
19
31
  ### Added
package/CLAUDE.md CHANGED
@@ -181,7 +181,7 @@ document.body.addEventListener('click', (e) => {
181
181
  - **loadScript Options**: src, async, defer, crossorigin, integrity, timeout, retries
182
182
 
183
183
  ### Utilities (`utilities.js`)
184
- - **Exports**: `clipboardCopy()`, `escapeHTML()`, `showNotification()`, `getPlatform()`, `getRuntime()`, `isMobile()`, `getDeviceType()`, `getContext()`
184
+ - **Exports**: `clipboardCopy()`, `escapeHTML()`, `showNotification()`, `getPlatform()`, `getBrowser()`, `getRuntime()`, `isMobile()`, `getDeviceType()`, `getContext()`
185
185
 
186
186
  ## Build System
187
187
 
package/README.md CHANGED
@@ -108,14 +108,14 @@ Web Manager is designed to work in multiple environments:
108
108
  - **Firebase v12 Integration**: Modern Firebase Auth, Firestore, and Cloud Messaging
109
109
  - **Data Binding System**: Reactive DOM updates with `data-wm-bind` attributes
110
110
  - **Storage API**: Enhanced localStorage/sessionStorage with path-based access and JSON serialization
111
- - **Utilities**: `clipboardCopy()`, `escapeHTML()`, `getContext()`, `showNotification()`, `getPlatform()`, `getRuntime()`, `isMobile()`, `getDeviceType()`
111
+ - **Utilities**: `clipboardCopy()`, `escapeHTML()`, `getContext()`, `showNotification()`, `getPlatform()`, `getBrowser()`, `getRuntime()`, `isMobile()`, `getDeviceType()`
112
112
  - **DOM Utilities**: Dynamic script loading with retry/timeout support
113
113
  - **Service Worker Management**: Registration, messaging, and state tracking
114
114
  - **Push Notifications**: Firebase Cloud Messaging with auto-subscription
115
115
  - **Error Tracking**: Sentry integration with session replay
116
116
  - **App Check**: Optional reCAPTCHA Enterprise protection
117
117
  - **Version Checking**: Auto-reload when new version is deployed
118
- - **HTML Data Attributes**: Automatic `data-platform`, `data-runtime`, `data-device` on `<html>`
118
+ - **HTML Data Attributes**: Automatic `data-platform`, `data-browser`, `data-runtime`, `data-device` on `<html>`
119
119
 
120
120
  ## Configuration
121
121
 
@@ -354,6 +354,35 @@ unsubscribe();
354
354
  Add these classes to elements for automatic auth functionality:
355
355
  - `.auth-signout-btn` - Sign out button (shows confirmation dialog)
356
356
 
357
+ **⚠️ Auth State Timing**:
358
+
359
+ On fresh page loads, Firebase Auth needs time to restore the user session from IndexedDB/localStorage. Methods like `auth.isAuthenticated()`, `auth.getUser()`, and `auth.getIdToken()` may return `null`/`false` if called before auth state is determined.
360
+
361
+ **Problem:**
362
+ ```javascript
363
+ // ❌ May fail on page load - auth state not yet determined
364
+ await Manager.dom().ready();
365
+ const token = await auth.getIdToken(); // Could throw if currentUser is null
366
+ ```
367
+
368
+ **Solution:** Use `auth.listen({ once: true })` to wait for auth state:
369
+ ```javascript
370
+ // ✅ Wait for auth state to be determined first
371
+ auth.listen({ once: true }, async (result) => {
372
+ if (result.user) {
373
+ const token = await auth.getIdToken(); // Safe - user is authenticated
374
+ }
375
+ });
376
+ ```
377
+
378
+ **When this matters:**
379
+ - Pages making authenticated API calls immediately on load
380
+ - OAuth callback pages
381
+ - Deep links requiring authentication
382
+
383
+ **When NOT needed:**
384
+ - User-triggered actions (button clicks) - auth state is always determined by then
385
+
357
386
  ### Data Binding System
358
387
 
359
388
  Reactive DOM updates with `data-wm-bind` attributes:
@@ -660,6 +689,7 @@ import {
660
689
  escapeHTML,
661
690
  showNotification,
662
691
  getPlatform,
692
+ getBrowser,
663
693
  getRuntime,
664
694
  isMobile,
665
695
  getDeviceType,
@@ -683,6 +713,9 @@ showNotification(new Error('Failed'), { timeout: 0 }); // No auto-dismiss
683
713
  // Platform detection
684
714
  getPlatform(); // 'windows', 'mac', 'linux', 'ios', 'android', 'chromeos', 'unknown'
685
715
 
716
+ // Browser detection
717
+ getBrowser(); // 'chrome', 'firefox', 'safari', 'edge', 'opera', 'brave', null
718
+
686
719
  // Runtime detection
687
720
  getRuntime(); // 'web', 'browser-extension'
688
721
 
@@ -693,8 +726,8 @@ getDeviceType(); // 'mobile' (<768px), 'tablet' (768-1199px), 'desktop' (>=1200p
693
726
  // Full context
694
727
  getContext();
695
728
  // {
696
- // client: { language, mobile, deviceType, platform, runtime, userAgent, url },
697
- // browser: { vendor }
729
+ // client: { language, mobile, deviceType, platform, browser, vendor, runtime, userAgent, url },
730
+ // geolocation: { ip, country, region, city, latitude, longitude }
698
731
  // }
699
732
  ```
700
733
 
@@ -710,12 +743,13 @@ getContext();
710
743
  Web Manager automatically sets these attributes on the `<html>` element during initialization:
711
744
 
712
745
  ```html
713
- <html data-platform="mac" data-runtime="web" data-device="desktop">
746
+ <html data-platform="mac" data-browser="chrome" data-runtime="web" data-device="desktop">
714
747
  ```
715
748
 
716
749
  | Attribute | Values | Description |
717
750
  |-----------|--------|-------------|
718
751
  | `data-platform` | `windows`, `mac`, `linux`, `ios`, `android`, `chromeos`, `unknown` | Operating system |
752
+ | `data-browser` | `chrome`, `firefox`, `safari`, `edge`, `opera`, `brave` | Browser name |
719
753
  | `data-runtime` | `web`, `browser-extension` | Runtime environment |
720
754
  | `data-device` | `mobile`, `tablet`, `desktop` | Device type by screen width |
721
755
 
@@ -725,6 +759,10 @@ Web Manager automatically sets these attributes on the `<html>` element during i
725
759
  [data-platform="ios"] .download-btn { display: none; }
726
760
  [data-platform="windows"] .app-icon { content: url('windows-icon.png'); }
727
761
 
762
+ /* Browser-specific styles */
763
+ [data-browser="safari"] .webkit-fix { -webkit-transform: translateZ(0); }
764
+ [data-browser="firefox"] .gecko-fix { overflow: hidden; }
765
+
728
766
  /* Device-responsive styles */
729
767
  [data-device="mobile"] .sidebar { display: none; }
730
768
  [data-device="desktop"] .mobile-menu { display: none; }
@@ -261,14 +261,12 @@ class Notifications {
261
261
  return;
262
262
  }
263
263
 
264
- const { getContext } = await import('./utilities.js');
265
-
266
264
  const now = new Date();
267
265
  const timestamp = now.toISOString();
268
266
  const timestampUNIX = Math.floor(now.getTime() / 1000);
269
267
 
270
268
  // Get context for client information
271
- const context = getContext();
269
+ const context = this.manager.utilities().getContext();
272
270
  const clientData = context.client;
273
271
 
274
272
  // Reference to the notification document (ID is the token)
@@ -283,41 +281,33 @@ class Notifications {
283
281
  const existingUid = existingData?.uid || null;
284
282
  const needsUpdate = existingUid !== currentUid;
285
283
 
284
+ // Common data for both create and update
285
+ const baseData = {
286
+ context: {
287
+ client: clientData
288
+ },
289
+ updated: {
290
+ timestamp,
291
+ timestampUNIX
292
+ },
293
+ uid: currentUid
294
+ };
295
+
296
+ // Create or update the document as needed
286
297
  if (!existingData) {
287
298
  // New subscription - create the document
288
- const subscriptionData = {
299
+ await notificationDoc.set({
300
+ ...baseData,
289
301
  token,
290
- context: {
291
- client: clientData
292
- },
293
302
  tags: ['general'],
294
303
  created: {
295
304
  timestamp,
296
305
  timestampUNIX
297
306
  },
298
- updated: {
299
- timestamp,
300
- timestampUNIX
301
- },
302
- uid: currentUid
303
- };
304
-
305
- await notificationDoc.set(subscriptionData);
306
-
307
+ });
307
308
  } else if (needsUpdate) {
308
309
  // Existing subscription needs update (userId changed)
309
- const updateData = {
310
- context: {
311
- client: clientData
312
- },
313
- updated: {
314
- timestamp,
315
- timestampUNIX
316
- },
317
- uid: currentUid
318
- };
319
-
320
- await notificationDoc.update(updateData);
310
+ await notificationDoc.update(baseData);
321
311
  }
322
312
  // If no update needed, do nothing
323
313
 
@@ -81,8 +81,8 @@ class Utilities {
81
81
  const timeout = options.timeout !== undefined ? options.timeout : 5000;
82
82
 
83
83
  const $notification = document.createElement('div');
84
- $notification.className = `alert alert-${type} alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-5`;
85
- $notification.style.zIndex = '9999';
84
+ $notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
85
+ $notification.style.cssText = 'z-index: 9999; top: 1rem; left: 50%; transform: translateX(-50%);';
86
86
  $notification.innerHTML = `
87
87
  ${text}
88
88
  <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
@@ -137,22 +137,27 @@ class Utilities {
137
137
  if (/edg/i.test(ua)) {
138
138
  return 'edge';
139
139
  }
140
+
140
141
  // Opera before Chrome (Opera includes "Chrome" in UA)
141
142
  if (/opera|opr/i.test(ua)) {
142
143
  return 'opera';
143
144
  }
145
+
144
146
  // Brave before Chrome (Brave includes "Chrome" in UA)
145
147
  if (navigator.brave || /brave/i.test(ua)) {
146
148
  return 'brave';
147
149
  }
150
+
148
151
  // Chrome (including Chromium-based browsers)
149
152
  if (/chrome|chromium|crios/i.test(ua)) {
150
153
  return 'chrome';
151
154
  }
155
+
152
156
  // Firefox
153
157
  if (/firefox|fxios/i.test(ua)) {
154
158
  return 'firefox';
155
159
  }
160
+
156
161
  // Safari last (most browsers include "Safari" in UA)
157
162
  if (/safari/i.test(ua)) {
158
163
  return 'safari';
@@ -798,3 +798,31 @@
798
798
  [debug] [2025-12-17T01:04:07.263Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
799
799
  [debug] [2025-12-17T01:04:07.264Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
800
800
  [debug] [2025-12-17T01:04:07.264Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
801
+ [debug] [2025-12-17T22:53:29.414Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
802
+ [debug] [2025-12-17T22:53:29.418Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
803
+ [debug] [2025-12-17T22:53:29.417Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
804
+ [debug] [2025-12-17T22:53:29.417Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
805
+ [debug] [2025-12-17T22:53:29.417Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
806
+ [debug] [2025-12-17T22:53:29.426Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
807
+ [debug] [2025-12-17T22:53:29.427Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
808
+ [debug] [2025-12-17T22:53:29.420Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
809
+ [debug] [2025-12-17T22:53:29.420Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
810
+ [debug] [2025-12-17T22:53:29.420Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
811
+ [debug] [2025-12-17T22:53:29.432Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
812
+ [debug] [2025-12-17T22:53:29.432Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
813
+ [debug] [2025-12-17T22:53:29.445Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
814
+ [debug] [2025-12-17T22:53:29.445Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
815
+ [debug] [2025-12-17T22:53:29.446Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
816
+ [debug] [2025-12-17T22:53:29.446Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
817
+ [debug] [2025-12-17T22:53:29.448Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
818
+ [debug] [2025-12-17T22:53:29.448Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
819
+ [debug] [2025-12-17T22:53:29.448Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
820
+ [debug] [2025-12-17T22:53:29.448Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
821
+ [debug] [2025-12-17T22:53:29.452Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
822
+ [debug] [2025-12-17T22:53:29.452Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
823
+ [debug] [2025-12-17T22:53:29.453Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
824
+ [debug] [2025-12-17T22:53:29.453Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
825
+ [debug] [2025-12-17T22:53:29.455Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
826
+ [debug] [2025-12-17T22:53:29.455Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
827
+ [debug] [2025-12-17T22:53:29.456Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
828
+ [debug] [2025-12-17T22:53:29.456Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-manager",
3
- "version": "4.1.1",
3
+ "version": "4.1.3",
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",