web-manager 4.0.18 → 4.0.19

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/README.md CHANGED
@@ -35,235 +35,233 @@ npm install web-manager
35
35
  ```
36
36
 
37
37
  ## 🦄 Features
38
- * Polyfill detection and implementation for Promises, Array methods, window.fetch, and more!
39
- * Dom API that acts as a super lightweight and optimized version of jQuery
40
- * Improved Localstorage API
41
- * Utility API with the most useful Lodash methods `get` and `set`
42
-
43
- ## 📚 Libraries
44
- * Firebase (Firebase app, Firestore, Auth, & Messaging)
45
- * Lazysizes to lazyload images
46
- * Sentry to report errors
47
- * [Chatsy.ai](https://chatsy.ai) AI chatbot integration
48
- * Cookieconsent to comply with GDPR
49
-
50
- ## 📘 Example Setup
51
- After installing via npm, simply paste this script before the closing `</body>` tag to initialize Web Manager.
52
- ```html
53
- <script type="text/javascript">
54
- var Manager = new (require('web-manager'));
55
- var config = {
56
- // ... your config here
38
+ * **Firebase v12 Integration**: Modern Firebase Auth, Firestore, and Cloud Messaging
39
+ * **Data Binding System**: Reactive DOM updates with `data-wm-bind` attributes
40
+ * **Storage API**: Enhanced localStorage/sessionStorage with automatic JSON serialization
41
+ * **Utilities**: Essential functions like `clipboardCopy()`, `escapeHTML()`, `getContext()`, and `showNotification()`
42
+ * **DOM Utilities**: Lightweight helpers for dynamic script loading and DOM ready detection
43
+ * **Service Worker Management**: Easy registration and messaging with service workers
44
+ * **Push Notifications**: Simplified Firebase Cloud Messaging subscription system
45
+
46
+ ## 📚 Integrated Libraries
47
+ * **Firebase v12**: Firebase App, Firestore, Auth, and Cloud Messaging
48
+ * **Sentry**: Comprehensive error tracking and session replay
49
+ * **Firebase App Check**: Optional reCAPTCHA Enterprise protection
50
+
51
+ ## 📘 Quick Start
52
+
53
+ ### Installation
54
+ ```bash
55
+ npm install web-manager
56
+ ```
57
+
58
+ ### Basic Setup
59
+ ```javascript
60
+ import Manager from 'web-manager';
61
+
62
+ // Initialize with your configuration
63
+ await Manager.initialize({
64
+ environment: 'production',
65
+ buildTime: Date.now(),
66
+ brand: {
67
+ id: 'my-app',
68
+ name: 'My Application'
69
+ },
70
+ firebase: {
71
+ app: {
72
+ enabled: true,
73
+ config: {
74
+ apiKey: 'your-api-key',
75
+ authDomain: 'your-app.firebaseapp.com',
76
+ projectId: 'your-project-id',
77
+ storageBucket: 'your-app.appspot.com',
78
+ messagingSenderId: '123456789',
79
+ appId: '1:123456789:web:abcdef'
80
+ }
81
+ }
57
82
  }
58
- Manager.init(config, function() {
59
- Manager.log('Initialized main.js');
60
- });
61
- </script>
83
+ });
84
+
85
+ console.log('Web Manager initialized!');
62
86
  ```
63
87
 
64
- ## 📘 Example Usage
65
- Lets go over some example usage of the library.
88
+ ## 📘 API Reference
66
89
 
67
- ### Kitchen Sink Config example
68
- By default, all of the libraries are enabled. But you can simply set `enabled` to `false` to disable any of them. Most of these libraries work without configuration but for some, such as Firebase, Tawk, and Sentry, you must supply the relevant IDs and API keys.
90
+ ### Configuration
69
91
 
70
- ```html
71
- <script type="text/javascript">
72
- var config = {
73
- pushNotifications: {
74
- autoRequest: 60 // How long to wait before auto ask to subscribe. 0 to disable.
75
- },
76
- serviceWorker: {
77
- path: 'firebase-messaging-sw.js' // Path to your service worker
78
- },
79
- libraries: {
80
- firebase_app: { // Config is required if enabled
81
- enabled: true,
82
- config: {
83
- apiKey: '123456',
84
- authDomain: 'xxx.firebaseapp.com',
85
- databaseURL: 'https://xxx.firebaseio.com',
86
- projectId: 'xxx',
87
- storageBucket: 'xxx.appspot.com',
88
- messagingSenderId: '123456',
89
- appId: '1:xxx'
90
- }
91
- },
92
- tawk: { // Config is required if enabled
93
- enabled: true,
94
- config: {
95
- chatId: 'xxx'
96
- }
97
- },
98
- sentry: { // Config is required if enabled
99
- enabled: true,
100
- config: {
101
- dsn: 'xxx',
102
- release: 'xxx'
103
- }
104
- },
105
- cookieconsent: { // No config required
106
- enabled: true,
107
- config: {
108
- palette: {
109
- popup: {
110
- background: '#237afc',
111
- text: '#ffffff'
112
- },
113
- button: {
114
- background: '#fff',
115
- text: '#237afc'
116
- }
117
- },
118
- theme: 'classic',
119
- position: 'bottom-left',
120
- type: '',
121
- content: {
122
- message: 'This website uses cookies to ensure you get the best experience on our website.',
123
- dismiss: 'Got it!',
124
- link: 'Learn more',
125
- href: (window.location.href + '/cookies/')
126
- }
127
- }
128
- },
129
- lazysizes: { // No config required
130
- enabled: true
131
- }
92
+ Here's a comprehensive configuration example with all available options:
93
+
94
+ ```javascript
95
+ await Manager.initialize({
96
+ // Environment and build info
97
+ environment: 'production', // 'development' or 'production'
98
+ buildTime: Date.now(),
99
+
100
+ // Brand information
101
+ brand: {
102
+ id: 'my-app',
103
+ name: 'My Application',
104
+ description: 'App description',
105
+ type: 'Organization',
106
+ images: {
107
+ brandmark: 'https://example.com/logo.png',
108
+ wordmark: 'https://example.com/wordmark.png',
109
+ combomark: 'https://example.com/combomark.png'
110
+ },
111
+ contact: {
112
+ email: 'support@example.com',
113
+ phone: '+1-555-0123'
114
+ }
115
+ },
116
+
117
+ // Firebase configuration
118
+ firebase: {
119
+ app: {
120
+ enabled: true,
121
+ config: {
122
+ apiKey: 'your-api-key',
123
+ authDomain: 'your-app.firebaseapp.com',
124
+ projectId: 'your-project-id',
125
+ storageBucket: 'your-app.appspot.com',
126
+ messagingSenderId: '123456789',
127
+ appId: '1:123456789:web:abcdef'
128
+ }
129
+ },
130
+ appCheck: {
131
+ enabled: false,
132
+ config: {
133
+ siteKey: 'your-recaptcha-site-key'
132
134
  }
133
135
  }
134
- var Manager = new (require('web-manager'));
136
+ },
137
+
138
+ // Sentry error tracking
139
+ sentry: {
140
+ enabled: true,
141
+ config: {
142
+ dsn: 'https://your-sentry-dsn',
143
+ replaysSessionSampleRate: 0.01,
144
+ replaysOnErrorSampleRate: 0.01
145
+ }
146
+ },
135
147
 
136
- Manager.init(config, function() {
137
- Manager.log('Initialized main.js');
138
- });
139
- </script>
140
- ```
148
+ // Push notifications
149
+ pushNotifications: {
150
+ enabled: true,
151
+ config: {
152
+ autoRequest: 60000 // Auto-request after 60s of first user interaction
153
+ }
154
+ },
141
155
 
142
- ### Utilizing the .dom() API
143
- The Web Manager .dom() API is like a super lightweight and efficient version of jQuery, just better!
144
- ```html
145
- <div class="el" id="el1">.el 1</div>
146
- <div class="el" id="el2">.el 1</div>
147
- <div class="el" id="el3">.el 1</div>
148
-
149
- <div class="hide-me">.hide-me</div>
150
- <div class="show-me">.show-me</div>
151
-
152
- <div id="attributes" data-foo="bar">#attributes</div>
153
-
154
- <input class="input" type="text" name="" value="Hello World!">
155
-
156
- <div class="click-me">Click counter: 0</div>
157
-
158
- <script type="text/javascript">
159
- Manager.ready(function() {
160
- console.log('--- Exploring the .dom() API ---');
161
- const el = Manager.dom().select('.el'); // Select using a standard querySelectorAll argument
162
- el.addClass('new-class'); // Add a class
163
- el.removeClass('old-class'); // Remove a class
164
- el.each(function(element, index) { // Iterate through the elements
165
- console.log('Loop number: ', index, element);
166
- Manager.dom().select(element).setInnerHTML('Element number: ' + index); // Set setInnerHTML
167
- });
168
- console.log('Get ', el.get(0));
169
- console.log('Get ', el.get(1));
170
- console.log('Exists ', el.exists());
171
- console.log('Exists (false)', Manager.dom().select('.this-doesnt-exist').exists());
172
-
173
- const el2 = Manager.dom().select('.hide-me');
174
- el2.hide(); // Hide an element
175
-
176
- const el3 = Manager.dom().select('.show-me');
177
- el2.show(); // Show an element
178
-
179
- const el4 = Manager.dom().select('#attributes');
180
- console.log('Attribute 1: ', el4.getAttribute('data-foo')); // Get an attribute
181
- el4.setAttribute('data-foo', 'baz'); // Set an attribute
182
- console.log('Attribute 2: ', el4.getAttribute('data-foo'));
183
-
184
- const el5 = Manager.dom().select('.input');
185
- console.log('Value 1: ', el5.getValue()); // Get value of an input
186
- el5.setValue('Hello again World!'); // Set a value
187
- console.log('Value 2: ', el5.getValue());
188
-
189
- var clicks = 0;
190
- const el6 = Manager.dom().select('.click-me');
191
- Manager.dom().select('body').on('click', function(event) {
192
- if (event.target.matches('.click-me')) {
193
- clicks++;
194
- el6.setInnerHTML('Click counter: ' + clicks)
195
- }
196
- });
156
+ // Service worker
157
+ serviceWorker: {
158
+ enabled: true,
159
+ config: {
160
+ path: '/service-worker.js'
161
+ }
162
+ },
197
163
 
198
- // Loading a script
199
- Manager.dom()
200
- .loadScript({src: 'https://platform.twitter.com/widgets.js', crossorigin: true}, function() {
201
- Manager.log('Loaded Twitter script.');
202
- });
164
+ // Valid redirect hosts for auth
165
+ validRedirectHosts: ['example.com', 'app.example.com']
166
+ });
167
+ ```
203
168
 
169
+ ### DOM Utilities
204
170
 
171
+ The DOM utilities provide essential functions for working with the DOM:
205
172
 
206
- });
207
- </script>
173
+ ```javascript
174
+ import { loadScript, ready } from 'web-manager';
175
+
176
+ // Wait for DOM to be ready
177
+ await ready();
178
+ console.log('DOM is ready!');
179
+
180
+ // Load an external script dynamically
181
+ await loadScript({
182
+ src: 'https://example.com/script.js',
183
+ async: true,
184
+ crossorigin: 'anonymous',
185
+ timeout: 30000,
186
+ retries: 2
187
+ });
188
+
189
+ // Or simply pass a URL string
190
+ await loadScript('https://example.com/script.js');
208
191
  ```
209
192
 
210
- ### Utilizing the .utilities() API
211
- The Web Manager .utilities() API wraps some useful functions such as getting and setting values of objects.
212
- ```html
213
- <script type="text/javascript">
214
- console.log('--- Exploring the .utilities() API ---');
215
-
216
- // .get() and .set()
217
- Manager.ready(function() {
218
- var object = {
219
- key1: 'val1',
220
- key2: 'val2',
221
- nested: {
222
- key4: 'val4'
223
- }
224
- };
225
- console.log('Root object ', Manager.utilities().get(object)); // Get whole object
226
- console.log('Get key1 ', Manager.utilities().get(object, 'key1')); // Get a key's value
227
- console.log('Get key3 ', Manager.utilities().get(object, 'key3')); // key3 doesn't exist
228
- console.log('Get key3 ', Manager.utilities().get(object, 'key3', 'key3default')); // key3 still doesn't exist, but well request a default instead
193
+ You can also access these via the Manager instance:
229
194
 
230
- console.log('Set key2 ', Manager.utilities().set(object, 'key2', 'new val2')); // Setting a value
231
- console.log('Set key3 ', Manager.utilities().set(object, 'key3', 'val3')); // Setting a value that doesn't exist won't overwrite
195
+ ```javascript
196
+ const domUtils = Manager.dom();
197
+ await domUtils.loadScript('https://example.com/script.js');
198
+ await domUtils.ready();
199
+ ```
232
200
 
233
- console.log('Get nested key4 ', Manager.utilities().get(object, 'nested.key4')); // Getting a nested value
234
- console.log('Set nested key5 ', Manager.utilities().set(object, 'nested.key5', 'val5')); // Setting a nested value
201
+ ### Utilities
235
202
 
236
- console.log('Root object (final)', Manager.utilities().get(object)); // Get whole object a final time
203
+ The utilities module provides essential helper functions:
237
204
 
238
- });
205
+ ```javascript
206
+ import { clipboardCopy, escapeHTML, getContext, showNotification } from 'web-manager';
207
+
208
+ // Copy text to clipboard
209
+ await clipboardCopy('Text to copy');
210
+
211
+ // Escape HTML to prevent XSS attacks
212
+ const safe = escapeHTML('<script>alert("xss")</script>');
213
+ // Returns: &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;
214
+
215
+ // Get client context information
216
+ const context = getContext();
217
+ // Returns: {
218
+ // client: { language, mobile, platform, userAgent, url },
219
+ // browser: { vendor }
220
+ // }
221
+
222
+ // Show Bootstrap-styled notification
223
+ showNotification('Success!', { type: 'success', timeout: 5000 });
224
+ showNotification('Error occurred', 'danger'); // Shorthand
225
+ showNotification(new Error('Something went wrong')); // Auto-detects error
226
+ ```
239
227
 
240
- // .clipboardCopy()
241
- Manager.utilities().clipboardCopy('I am copied to the clipboard!')
228
+ Access via Manager instance:
242
229
 
243
- // .escapeHTML()
244
- Manager.utilities().escapeHTML('<strong>This will will NOT render as bold!</strong>')
245
- </script>
230
+ ```javascript
231
+ const utils = Manager.utilities();
232
+ utils.clipboardCopy('Hello!');
233
+ utils.escapeHTML('<div>Test</div>');
234
+ utils.getContext();
235
+ utils.showNotification('Message', 'info');
246
236
  ```
247
237
 
248
- ### Utilizing the .storage() API
249
- The Web Manager .storage() API is a wrapper for the localStorage API that automatically checks if localStorage is supported, automatically serializing (`JSON.stringify()`) and parsing (`JSON.parse()`) the inputs and outputs allowing you to natively work with storing objects in localStorage without any extra work!
250
- ```html
251
- <script type="text/javascript">
252
- Manager.ready(function() {
253
- console.log('--- Exploring the .storage() API ---');
254
-
255
- // By default, all methods only affect the the assigned node '_manager'
256
- Manager.storage().clear(); // Clear _manager node
257
- console.log(Manager.storage().get('key1', '1')); // Get a key with a default of 1 if key doesnt exist
258
- console.log(Manager.storage().set('key1', '2')); // Set a key
259
- console.log(Manager.storage().get('key1', '1'));
260
- console.log(Manager.storage().get('', '1'));
261
- console.log(Manager.storage().set('key1.key2.key3.key4', 'inner4')); // Set a nested key
262
- console.log(Manager.storage().get('', '1'));
263
- });
264
- </script>
238
+ ### Storage API
239
+
240
+ Enhanced localStorage and sessionStorage with automatic JSON serialization and nested path support:
241
+
242
+ ```javascript
243
+ const storage = Manager.storage();
244
+
245
+ // LocalStorage operations (persists across sessions)
246
+ storage.set('user.name', 'John');
247
+ storage.set('user.preferences', { theme: 'dark', lang: 'en' });
248
+
249
+ const name = storage.get('user.name'); // 'John'
250
+ const theme = storage.get('user.preferences.theme'); // 'dark'
251
+ const all = storage.get(); // Get entire storage object
252
+
253
+ storage.remove('user.name');
254
+ storage.clear(); // Clear all data
255
+
256
+ // SessionStorage operations (cleared when browser closes)
257
+ storage.session.set('temp.data', 'value');
258
+ const tempData = storage.session.get('temp.data');
259
+ storage.session.remove('temp.data');
260
+ storage.session.clear();
265
261
  ```
266
262
 
263
+ All data is automatically serialized to JSON, so you can store objects, arrays, and primitives without manual conversion.
264
+
267
265
  ### Utilizing the Data Binding System
268
266
  Web Manager includes a powerful data binding system that automatically updates your DOM elements based on data changes. Simply add the `data-wm-bind` attribute to any element.
269
267
 
@@ -324,134 +322,246 @@ Manager.bindings().clear();
324
322
 
325
323
  Future actions like `@class` and `@style` can be easily added.
326
324
 
327
- ### Utilizing the Firebase Auth System
328
- The Firebase login system works like charm out of the box without you having to write a single line of code. All you have to do is add a few classes to your HTML elements and everything will work.
329
-
330
- #### Authentication Action Classes
331
- * `.auth-signout-btn`: Add to a button to sign out the current user (shows confirmation dialog)
332
- * `.auth-email-input`: Add to an input for the user's email
333
- * `.auth-password-input`: Add to an input for the user's password
334
- * `.auth-signin-email-btn`: Add to a button to handle the signin process
335
- * `.auth-signup-email-btn`: Add to a button to handle the signup process
336
- * `.auth-signout-all-btn`: Add to a button to handle the signout process
337
- * `.auth-email-element`: Add to any element to display the user's email
338
- * `.auth-terms-input`: Add to a checkbox to require a TOS agreement before signup occurs
339
- * `.auth-newsletter-input`: Add to a checkbox to opt-in the user to newsletters upon signup
340
- * `.auth-uid-element`: Add to any element to display the user's uid
341
- * `.auth-signedin-true-element`: Add to any element and it will be hidden if the user *is* signed in
342
- * `.auth-signedin-false-element`: Add to any element and it will be hidden if the user *is not* signed in
343
-
344
- For these, you must first call `.account().resolve()`
345
- * `.auth-apikey-element`: Add to any element and it will display the user's API key
346
- * `.auth-delete-account-btn`: Add to a button to handle the account deletion process
347
- * `.auth-delete-account-confirmation-input`: Add to a checkbox to require confirmation before deleting
348
- * `.auth-delete-account-error-message-element`: Add to any element to show any error about account deletion
349
-
350
- * `.auth-billing-subscribe-btn`: Add to any button to turn it into a subscribe button
351
- * `.auth-billing-update-btn`: Add to any button to turn it into a button to update an existing subscription
352
- * `.auth-billing-plan-id-element`: Add to any element and it will display the user's plan ID
353
- * `.auth-billing-frequency-element`: Add to any element and it will display the user's plan frequency
354
- * `.auth-billing-start-date-element`: Add to any element and it will display the user's plan start date
355
- * `.auth-billing-expiration-date-element`: Add to any element and it will display the user's plan expiration date
356
-
357
- * `.auth-created-element`: Add to any element to show the local string for account creation date
358
- * `.auth-phone-element`: Add to any element to display the user's phone
359
-
360
- * `.auth-referral-count-element`: Update this element with the user's referral count
361
- * `.auth-referral-code-element`: Update this element with the user's referral code
362
- * `.auth-referral-link-element`: Update this element with the user's referral link
363
- * `.auth-referral-social-link`: Update this element with the user's referral link for socials where `data-provider` is the social network
364
-
365
- * Future additions (not added yet)
366
- * `auth-link-provider-btn`: Initiate a link to the `data-provider` in this element
367
- * `auth-unlink-provider-btn`: Initiate an unlink to the `data-provider` in this element
368
- * `auth-signout-all-sessions-btn`: Call the server to sign out of all sessions and then log out of the current one.
325
+ ### Firebase Authentication
369
326
 
327
+ The auth module provides Firebase Authentication integration with automatic account data fetching:
370
328
 
371
- ```html
372
- <div class="auth-signedin-false-element">
373
- <form onsubmit="return false;">
374
- <fieldset>
375
- <legend>Login</legend>
376
- <label for="email">Email</label>
377
- <input id="email" class="auth-email-input" type="email" name="email" placeholder="name@example.com" required autocomplete="email">
378
- <label for="password">Password</label>
379
- <input id="password" class="auth-password-input" type="password" name="password" placeholder="******" required autocomplete="current-password">
380
- </fieldset>
381
-
382
- <button id="submit" class="auth-signin-email-btn" type="submit">Sign in</button>
383
- </form>
384
-
385
- <form onsubmit="return false;">
386
- <fieldset>
387
- <legend>Signup</legend>
388
- <label for="email">Email</label>
389
- <input id="email" class="auth-email-input" type="email" name="email" placeholder="name@example.com" required autocomplete="email">
390
- <label for="password">Password</label>
391
- <input id="password" class="auth-password-input" type="password" name="password" placeholder="******" required autocomplete="new-password">
392
- <label for="passwordConfirm">Confirm password</label>
393
- <input id="passwordConfirm" class="auth-password-input" type="password" name="passwordConfirm" placeholder="******" required autocomplete="new-password">
394
- </fieldset>
395
-
396
- <button id="submit" class="auth-signup-email-btn" type="submit">Sign up</button>
397
- </form>
398
-
399
- </div>
400
- <div class="auth-signedin-true-element" hidden>
401
- <fieldset>
402
- <legend>My Account</legend>
403
- <label for="email">Email</label>
404
- <input id="email" class="auth-email-element" type="email" name="email" placeholder="name@example.com" disabled>
405
- <label for="uid">User ID</label>
406
- <input id="uid" class="auth-uid-element" type="text" name="uid" placeholder="1234567890" disabled>
407
- <label for="password">Password</label>
408
- <input id="password" class="" type="password" name="password" placeholder="******" disabled>
409
- </fieldset>
410
-
411
- <a href="#" onclick="return false;" class="auth-signout-all-btn">Sign out</a>
412
- </div>
413
- ```
329
+ ```javascript
330
+ const auth = Manager.auth();
414
331
 
415
- ### Utilizing the Firebase Push Notification Subscription System
416
- The Firebase push notification system also works with minimal implementation on your part. Just call `Manager.This.notifications().subscribe()` and the rest is handled for you!
332
+ // Listen for auth state changes (waits for settled state)
333
+ const unsubscribe = auth.listen({ account: true }, (result) => {
334
+ console.log('User:', result.user);
335
+ console.log('Account:', result.account);
417
336
 
418
- ```html
419
- <script type="text/javascript">
420
- Manager.This.notifications().subscribe()
421
- .then(function() {
422
- Manager.log('Subscribed to push notifications!')
423
- })
424
- .catch(function(e) {
425
- Manager.log('error', 'Failed to subscribe to push notifications: ', e)
426
- })
427
- </script>
337
+ // result.user contains: uid, email, displayName, photoURL, emailVerified
338
+ // result.account contains resolved account data from Firestore
339
+ });
340
+
341
+ // Listen only once
342
+ auth.listen({ once: true }, (result) => {
343
+ console.log('Initial auth state:', result);
344
+ });
345
+
346
+ // Check if user is authenticated
347
+ if (auth.isAuthenticated()) {
348
+ const user = auth.getUser();
349
+ console.log('Current user:', user);
350
+ }
351
+
352
+ // Sign in with email and password
353
+ try {
354
+ const user = await auth.signInWithEmailAndPassword('user@example.com', 'password');
355
+ console.log('Signed in:', user);
356
+ } catch (error) {
357
+ console.error('Sign in failed:', error);
358
+ }
359
+
360
+ // Sign in with custom token
361
+ await auth.signInWithCustomToken('custom-token');
362
+
363
+ // Sign out
364
+ await auth.signOut();
365
+
366
+ // Get ID token for API calls
367
+ const idToken = await auth.getIdToken(forceRefresh = false);
428
368
  ```
429
369
 
430
- ### Utilizing the ServiceWorker API
431
- Also included is an API wrapper for some ServiceWorker functions to make development easier. You don't have to waste lines of code checking if the service worker is supported, as this is implemented by default.
370
+ #### Built-in Auth UI Classes
432
371
 
433
- ```html
434
- <script type="text/javascript">
435
- Manager.serviceWorker().postMessage({command: 'debug', args: {key: 'value'}}, function (response) {
436
- Manager.log('Callback...', response);
372
+ Add these CSS classes to HTML elements for automatic auth functionality:
373
+
374
+ * `.auth-signout-btn` - Sign out button (shows confirmation)
375
+
376
+ The auth system automatically updates DOM elements with `data-wm-bind` attributes (see Data Binding section).
377
+
378
+ ### Push Notifications
379
+
380
+ Simplified Firebase Cloud Messaging integration:
381
+
382
+ ```javascript
383
+ const notifications = Manager.notifications();
384
+
385
+ // Check if notifications are supported
386
+ if (notifications.isSupported()) {
387
+ console.log('Push notifications are supported!');
388
+ }
389
+
390
+ // Check subscription status
391
+ const isSubscribed = await notifications.isSubscribed();
392
+
393
+ // Subscribe to push notifications
394
+ try {
395
+ const result = await notifications.subscribe({
396
+ vapidKey: 'your-vapid-key' // Optional
437
397
  });
438
- </script>
398
+ console.log('Subscribed!', result.token);
399
+ } catch (error) {
400
+ console.error('Subscription failed:', error);
401
+ }
402
+
403
+ // Unsubscribe
404
+ await notifications.unsubscribe();
405
+
406
+ // Get current FCM token
407
+ const token = await notifications.getToken();
408
+
409
+ // Listen for foreground messages
410
+ const unsubscribe = await notifications.onMessage((payload) => {
411
+ console.log('Message received:', payload);
412
+ });
413
+
414
+ // Sync subscription with current auth state
415
+ await notifications.syncSubscription();
439
416
  ```
440
417
 
441
- ### Utilizing the .account() API
442
- To preserve file size and enforce optimization, the `.account()` library must be explicitly loaded.
418
+ The notification system automatically:
419
+ - Requests permission after user interaction (configurable via `pushNotifications.config.autoRequest`)
420
+ - Stores subscription info in Firestore
421
+ - Syncs with user authentication state
422
+ - Shows notifications when app is in foreground
443
423
 
444
- ```html
445
- <script type="text/javascript">
446
- Manager.account().import()
447
- .then(function (Account) {
448
- var account = new Account();
449
- account.resolve()
450
- })
451
- </script>
424
+ ### Service Worker
425
+
426
+ Manage service workers with automatic support detection:
427
+
428
+ ```javascript
429
+ const sw = Manager.serviceWorker();
430
+
431
+ // Check if service workers are supported
432
+ if (sw.isSupported()) {
433
+ console.log('Service workers are supported!');
434
+ }
435
+
436
+ // Register service worker (done automatically during initialization)
437
+ const registration = await sw.register({
438
+ path: '/service-worker.js',
439
+ scope: '/'
440
+ });
441
+
442
+ // Wait for service worker to be ready
443
+ await sw.ready();
444
+
445
+ // Get current registration
446
+ const reg = sw.getRegistration();
447
+
448
+ // Post message to service worker
449
+ try {
450
+ const response = await sw.postMessage({
451
+ command: 'cache-clear',
452
+ payload: { pattern: '*.js' }
453
+ }, { timeout: 5000 });
454
+ console.log('Response:', response);
455
+ } catch (error) {
456
+ console.error('Message failed:', error);
457
+ }
458
+
459
+ // Listen for messages from service worker
460
+ const unsubscribe = sw.onMessage('notification-click', (data, event) => {
461
+ console.log('Notification clicked:', data);
462
+ });
463
+
464
+ // Get service worker state
465
+ const state = sw.getState(); // 'none', 'installing', 'waiting', 'active'
466
+ ```
467
+
468
+ ### Firestore
469
+
470
+ Simplified Firestore wrapper with chainable queries:
471
+
472
+ ```javascript
473
+ const db = Manager.firestore();
474
+
475
+ // Document operations
476
+ await db.doc('users/user123').set({ name: 'John', age: 30 });
477
+ await db.doc('users', 'user123').update({ age: 31 });
478
+
479
+ const docSnap = await db.doc('users/user123').get();
480
+ if (docSnap.exists()) {
481
+ console.log('User data:', docSnap.data());
482
+ }
483
+
484
+ await db.doc('users/user123').delete();
485
+
486
+ // Collection queries
487
+ const snapshot = await db.collection('users').get();
488
+ snapshot.docs.forEach(doc => {
489
+ console.log(doc.id, doc.data());
490
+ });
491
+
492
+ // Query with filters
493
+ const result = await db.collection('users')
494
+ .where('age', '>=', 18)
495
+ .orderBy('age', 'desc')
496
+ .limit(10)
497
+ .get();
498
+
499
+ // Chainable queries
500
+ const adults = await db.collection('users')
501
+ .where('age', '>=', 18)
502
+ .where('active', '==', true)
503
+ .orderBy('createdAt', 'desc')
504
+ .limit(20)
505
+ .startAt(lastDoc)
506
+ .get();
452
507
  ```
453
508
 
509
+ ### Sentry Integration
510
+
511
+ Error tracking with automatic configuration:
454
512
 
513
+ ```javascript
514
+ const sentry = Manager.sentry();
515
+
516
+ // Capture an exception
517
+ try {
518
+ throw new Error('Something went wrong');
519
+ } catch (error) {
520
+ sentry.captureException(error, {
521
+ tags: { feature: 'checkout' },
522
+ extra: { orderId: '12345' }
523
+ });
524
+ }
525
+ ```
526
+
527
+ Sentry is automatically configured with:
528
+ - Environment and release tracking
529
+ - User context from auth state
530
+ - Session replay (if configured)
531
+ - Filtering for development/automated browsers
532
+
533
+ ### Manager Helper Methods
534
+
535
+ The Manager instance provides several utility methods:
536
+
537
+ ```javascript
538
+ // Check if running in development mode
539
+ if (Manager.isDevelopment()) {
540
+ console.log('Running in development');
541
+ }
542
+
543
+ // Get Firebase Functions URL
544
+ const functionsUrl = Manager.getFunctionsUrl(); // Uses config environment
545
+ const devUrl = Manager.getFunctionsUrl('development'); // http://localhost:5001/...
546
+ const prodUrl = Manager.getFunctionsUrl('production'); // https://us-central1-...
547
+
548
+ // Get API URL (derives from Firebase config)
549
+ const apiUrl = Manager.getApiUrl(); // https://api.your-domain.com
550
+
551
+ // Validate redirect URLs (for auth flows)
552
+ const isValid = Manager.isValidRedirectUrl('https://example.com/callback');
553
+
554
+ // Access Firebase instances directly
555
+ const app = Manager.firebaseApp;
556
+ const auth = Manager.firebaseAuth;
557
+ const firestore = Manager.firebaseFirestore;
558
+ const messaging = Manager.firebaseMessaging;
559
+
560
+ // Access configuration
561
+ const config = Manager.config;
562
+ console.log('Brand:', config.brand);
563
+ console.log('Environment:', config.environment);
564
+ ```
455
565
 
456
566
  ## 🗨️ Final Words
457
567
  If you are still having difficulty, we would love for you to post
@@ -105,6 +105,11 @@ class Bindings {
105
105
  // case '@class':
106
106
  // case '@style':
107
107
  }
108
+
109
+ // Add bound class to indicate element has been processed
110
+ if (!element.classList.contains('wm-bound')) {
111
+ element.classList.add('wm-bound');
112
+ }
108
113
  });
109
114
  }
110
115
 
@@ -0,0 +1,8 @@
1
+ [debug] [2025-10-20T10:38:46.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"]
2
+ [debug] [2025-10-20T10:38:46.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"]
3
+ [debug] [2025-10-20T10:38:46.421Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
4
+ [debug] [2025-10-20T10:38:46.421Z] > 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"]
5
+ [debug] [2025-10-20T10:38:46.421Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
6
+ [debug] [2025-10-20T10:38:46.422Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
7
+ [debug] [2025-10-20T10:38:46.422Z] > 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"]
8
+ [debug] [2025-10-20T10:38:46.422Z] > 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.0.18",
3
+ "version": "4.0.19",
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",