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 +428 -318
- package/dist/modules/bindings.js +5 -0
- package/firebase-debug.log +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,235 +35,233 @@ npm install web-manager
|
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## 🦄 Features
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
</script>
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
console.log('Web Manager initialized!');
|
|
62
86
|
```
|
|
63
87
|
|
|
64
|
-
## 📘
|
|
65
|
-
Lets go over some example usage of the library.
|
|
88
|
+
## 📘 API Reference
|
|
66
89
|
|
|
67
|
-
###
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
195
|
+
```javascript
|
|
196
|
+
const domUtils = Manager.dom();
|
|
197
|
+
await domUtils.loadScript('https://example.com/script.js');
|
|
198
|
+
await domUtils.ready();
|
|
199
|
+
```
|
|
232
200
|
|
|
233
|
-
|
|
234
|
-
console.log('Set nested key5 ', Manager.utilities().set(object, 'nested.key5', 'val5')); // Setting a nested value
|
|
201
|
+
### Utilities
|
|
235
202
|
|
|
236
|
-
|
|
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: <script>alert("xss")</script>
|
|
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
|
-
|
|
241
|
-
Manager.utilities().clipboardCopy('I am copied to the clipboard!')
|
|
228
|
+
Access via Manager instance:
|
|
242
229
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
###
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
###
|
|
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
|
-
```
|
|
372
|
-
|
|
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
|
-
|
|
416
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
442
|
-
|
|
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
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
package/dist/modules/bindings.js
CHANGED
|
@@ -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