web-manager 4.0.34 → 4.0.35
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/CLAUDE.md +0 -0
- package/README.md +481 -362
- package/dist/modules/auth.js +5 -7
- package/firebase-debug.log +28 -0
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
Binary file
|
package/README.md
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="https://img.shields.io/github/package-json/v/itw-creative-works/web-manager.svg">
|
|
9
9
|
<br>
|
|
10
|
-
<img src="https://img.shields.io/david/itw-creative-works/web-manager.svg">
|
|
11
|
-
<img src="https://img.shields.io/david/dev/itw-creative-works/web-manager.svg">
|
|
12
10
|
<img src="https://img.shields.io/bundlephobia/min/web-manager.svg">
|
|
13
11
|
<img src="https://img.shields.io/codeclimate/maintainability-percentage/itw-creative-works/web-manager.svg">
|
|
14
12
|
<img src="https://img.shields.io/npm/dm/web-manager.svg">
|
|
@@ -22,40 +20,49 @@
|
|
|
22
20
|
<a href="https://itwcreativeworks.com">Site</a> | <a href="https://www.npmjs.com/package/web-manager">NPM Module</a> | <a href="https://github.com/itw-creative-works/web-manager">GitHub Repo</a>
|
|
23
21
|
<br>
|
|
24
22
|
<br>
|
|
25
|
-
<strong>Web Manager</strong> is
|
|
23
|
+
<strong>Web Manager</strong> is a modern JavaScript utility library for building web applications with Firebase integration. It provides authentication, data binding, storage management, push notifications, error tracking, and more.
|
|
26
24
|
<br>
|
|
27
25
|
<br>
|
|
28
|
-
|
|
26
|
+
Optimized for use with <a href="https://www.npmjs.com/package/webpack">webpack</a> but works standalone too.
|
|
29
27
|
</p>
|
|
30
28
|
|
|
31
|
-
##
|
|
32
|
-
|
|
29
|
+
## Table of Contents
|
|
30
|
+
- [Installation](#-installation)
|
|
31
|
+
- [Requirements](#-requirements)
|
|
32
|
+
- [Quick Start](#-quick-start)
|
|
33
|
+
- [Supported Environments](#-supported-environments)
|
|
34
|
+
- [Features](#-features)
|
|
35
|
+
- [Configuration](#-configuration)
|
|
36
|
+
- [API Reference](#-api-reference)
|
|
37
|
+
- [Manager Instance](#manager-instance)
|
|
38
|
+
- [Storage API](#storage-api)
|
|
39
|
+
- [Authentication](#authentication)
|
|
40
|
+
- [Data Binding System](#data-binding-system)
|
|
41
|
+
- [Firestore](#firestore)
|
|
42
|
+
- [Push Notifications](#push-notifications)
|
|
43
|
+
- [Service Worker](#service-worker)
|
|
44
|
+
- [Sentry Error Tracking](#sentry-error-tracking)
|
|
45
|
+
- [DOM Utilities](#dom-utilities)
|
|
46
|
+
- [Utility Functions](#utility-functions)
|
|
47
|
+
- [HTML Data Attributes](#-html-data-attributes)
|
|
48
|
+
- [Direct Module Imports](#-direct-module-imports)
|
|
49
|
+
- [Browser Support](#-browser-support)
|
|
50
|
+
- [Projects Using This Library](#-projects-using-this-library)
|
|
51
|
+
- [Support](#-support)
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
33
54
|
```shell
|
|
34
55
|
npm install web-manager
|
|
35
56
|
```
|
|
36
57
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
58
|
+
## Requirements
|
|
59
|
+
- **Node.js**: >= 12
|
|
60
|
+
- **Browser**: Modern browsers (ES6+ support, transpiled to ES5 for older browsers)
|
|
45
61
|
|
|
46
|
-
|
|
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
|
|
62
|
+
**Note**: This library does not include TypeScript definitions.
|
|
50
63
|
|
|
51
|
-
##
|
|
64
|
+
## Quick Start
|
|
52
65
|
|
|
53
|
-
### Installation
|
|
54
|
-
```bash
|
|
55
|
-
npm install web-manager
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Basic Setup
|
|
59
66
|
```javascript
|
|
60
67
|
import Manager from 'web-manager';
|
|
61
68
|
|
|
@@ -85,21 +92,46 @@ await Manager.initialize({
|
|
|
85
92
|
console.log('Web Manager initialized!');
|
|
86
93
|
```
|
|
87
94
|
|
|
88
|
-
##
|
|
95
|
+
## Supported Environments
|
|
96
|
+
|
|
97
|
+
Web Manager is designed to work in multiple environments:
|
|
89
98
|
|
|
90
|
-
|
|
99
|
+
| Environment | Support | Notes |
|
|
100
|
+
|-------------|---------|-------|
|
|
101
|
+
| **Web** | Full | Primary target, works with webpack bundlers |
|
|
102
|
+
| **Electron** | Full | Works in renderer process |
|
|
103
|
+
| **Chrome Extension** | Full | Content scripts and popup pages |
|
|
104
|
+
| **Firefox Extension** | Full | Content scripts and popup pages |
|
|
105
|
+
| **Safari Extension** | Partial | Basic functionality supported |
|
|
91
106
|
|
|
92
|
-
|
|
107
|
+
## Features
|
|
108
|
+
- **Firebase v12 Integration**: Modern Firebase Auth, Firestore, and Cloud Messaging
|
|
109
|
+
- **Data Binding System**: Reactive DOM updates with `data-wm-bind` attributes
|
|
110
|
+
- **Storage API**: Enhanced localStorage/sessionStorage with path-based access and JSON serialization
|
|
111
|
+
- **Utilities**: `clipboardCopy()`, `escapeHTML()`, `getContext()`, `showNotification()`, `getPlatform()`, `getRuntime()`, `isMobile()`, `getDeviceType()`
|
|
112
|
+
- **DOM Utilities**: Dynamic script loading with retry/timeout support
|
|
113
|
+
- **Service Worker Management**: Registration, messaging, and state tracking
|
|
114
|
+
- **Push Notifications**: Firebase Cloud Messaging with auto-subscription
|
|
115
|
+
- **Error Tracking**: Sentry integration with session replay
|
|
116
|
+
- **App Check**: Optional reCAPTCHA Enterprise protection
|
|
117
|
+
- **Version Checking**: Auto-reload when new version is deployed
|
|
118
|
+
- **HTML Data Attributes**: Automatic `data-platform`, `data-runtime`, `data-device` on `<html>`
|
|
119
|
+
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
### Full Configuration Reference
|
|
93
123
|
|
|
94
124
|
```javascript
|
|
95
125
|
await Manager.initialize({
|
|
96
|
-
// Environment
|
|
97
|
-
environment: 'production',
|
|
126
|
+
// Environment: 'development' or 'production'
|
|
127
|
+
environment: 'production',
|
|
128
|
+
|
|
129
|
+
// Build timestamp for version checking
|
|
98
130
|
buildTime: Date.now(),
|
|
99
131
|
|
|
100
132
|
// Brand information
|
|
101
133
|
brand: {
|
|
102
|
-
id: 'my-app',
|
|
134
|
+
id: 'my-app', // Used for custom protocol URLs
|
|
103
135
|
name: 'My Application',
|
|
104
136
|
description: 'App description',
|
|
105
137
|
type: 'Organization',
|
|
@@ -130,7 +162,18 @@ await Manager.initialize({
|
|
|
130
162
|
appCheck: {
|
|
131
163
|
enabled: false,
|
|
132
164
|
config: {
|
|
133
|
-
siteKey: 'your-recaptcha-site-key'
|
|
165
|
+
siteKey: 'your-recaptcha-enterprise-site-key'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Authentication settings
|
|
171
|
+
auth: {
|
|
172
|
+
enabled: true,
|
|
173
|
+
config: {
|
|
174
|
+
redirects: {
|
|
175
|
+
authenticated: '/account', // Redirect after login
|
|
176
|
+
unauthenticated: '/signup' // Redirect when not logged in
|
|
134
177
|
}
|
|
135
178
|
}
|
|
136
179
|
},
|
|
@@ -140,8 +183,9 @@ await Manager.initialize({
|
|
|
140
183
|
enabled: true,
|
|
141
184
|
config: {
|
|
142
185
|
dsn: 'https://your-sentry-dsn',
|
|
143
|
-
|
|
144
|
-
|
|
186
|
+
release: '1.0.0',
|
|
187
|
+
replaysSessionSampleRate: 0.01, // 1% of sessions
|
|
188
|
+
replaysOnErrorSampleRate: 0.01 // 1% of error sessions
|
|
145
189
|
}
|
|
146
190
|
},
|
|
147
191
|
|
|
@@ -149,7 +193,8 @@ await Manager.initialize({
|
|
|
149
193
|
pushNotifications: {
|
|
150
194
|
enabled: true,
|
|
151
195
|
config: {
|
|
152
|
-
autoRequest: 60000
|
|
196
|
+
autoRequest: 60000, // Auto-request after 60s of first click
|
|
197
|
+
vapidKey: 'your-vapid-key' // Optional VAPID key
|
|
153
198
|
}
|
|
154
199
|
},
|
|
155
200
|
|
|
@@ -161,389 +206,367 @@ await Manager.initialize({
|
|
|
161
206
|
}
|
|
162
207
|
},
|
|
163
208
|
|
|
164
|
-
//
|
|
209
|
+
// Version checking (auto-reload on new version)
|
|
210
|
+
refreshNewVersion: {
|
|
211
|
+
enabled: true,
|
|
212
|
+
config: {
|
|
213
|
+
interval: 3600000 // Check every hour (1000 * 60 * 60)
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// Valid hosts for auth redirects (security)
|
|
165
218
|
validRedirectHosts: ['example.com', 'app.example.com']
|
|
166
219
|
});
|
|
167
220
|
```
|
|
168
221
|
|
|
169
|
-
###
|
|
222
|
+
### Configuration Notes
|
|
170
223
|
|
|
171
|
-
|
|
224
|
+
- **Timeout values** can be specified as strings with math expressions: `'1000 * 60 * 60'` (evaluated safely)
|
|
225
|
+
- **Deep merge**: Your config is deep-merged with defaults, so you only need to specify what you want to change
|
|
226
|
+
- **Firebase required**: Most features require Firebase to be configured and enabled
|
|
172
227
|
|
|
173
|
-
|
|
174
|
-
import { loadScript, ready } from 'web-manager';
|
|
228
|
+
## API Reference
|
|
175
229
|
|
|
176
|
-
|
|
177
|
-
await ready();
|
|
178
|
-
console.log('DOM is ready!');
|
|
230
|
+
### Manager Instance
|
|
179
231
|
|
|
180
|
-
|
|
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');
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
You can also access these via the Manager instance:
|
|
232
|
+
The Manager is a singleton that provides access to all modules:
|
|
194
233
|
|
|
195
234
|
```javascript
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
235
|
+
import Manager from 'web-manager';
|
|
236
|
+
|
|
237
|
+
// Module getters
|
|
238
|
+
Manager.storage(); // Storage API
|
|
239
|
+
Manager.auth(); // Firebase Auth wrapper
|
|
240
|
+
Manager.bindings(); // Data binding system
|
|
241
|
+
Manager.firestore(); // Firestore wrapper
|
|
242
|
+
Manager.notifications(); // Push notifications
|
|
243
|
+
Manager.serviceWorker(); // Service worker management
|
|
244
|
+
Manager.sentry(); // Error tracking
|
|
245
|
+
Manager.dom(); // DOM utilities
|
|
246
|
+
Manager.utilities(); // Utility functions
|
|
247
|
+
|
|
248
|
+
// Helper methods
|
|
249
|
+
Manager.isDevelopment(); // Check if in development mode
|
|
250
|
+
Manager.getFunctionsUrl(); // Get Firebase Functions URL
|
|
251
|
+
Manager.getFunctionsUrl('development'); // Force development URL
|
|
252
|
+
Manager.getApiUrl(); // Get API URL (derived from authDomain)
|
|
253
|
+
Manager.isValidRedirectUrl('https://...'); // Validate redirect URL
|
|
254
|
+
|
|
255
|
+
// Firebase instances (after initialization)
|
|
256
|
+
Manager.firebaseApp; // Firebase App instance
|
|
257
|
+
Manager.firebaseAuth; // Firebase Auth instance
|
|
258
|
+
Manager.firebaseFirestore; // Firestore instance
|
|
259
|
+
Manager.firebaseMessaging; // FCM instance
|
|
260
|
+
|
|
261
|
+
// Configuration
|
|
262
|
+
Manager.config; // Access full configuration
|
|
199
263
|
```
|
|
200
264
|
|
|
201
|
-
###
|
|
265
|
+
### Storage API
|
|
202
266
|
|
|
203
|
-
|
|
267
|
+
Enhanced localStorage and sessionStorage with path-based access:
|
|
204
268
|
|
|
205
269
|
```javascript
|
|
206
|
-
|
|
270
|
+
const storage = Manager.storage();
|
|
207
271
|
|
|
208
|
-
//
|
|
209
|
-
|
|
272
|
+
// LocalStorage (persists across browser sessions)
|
|
273
|
+
storage.set('user.name', 'John');
|
|
274
|
+
storage.set('user.preferences', { theme: 'dark', lang: 'en' });
|
|
210
275
|
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
276
|
+
const name = storage.get('user.name'); // 'John'
|
|
277
|
+
const theme = storage.get('user.preferences.theme'); // 'dark'
|
|
278
|
+
const all = storage.get(); // Entire storage object
|
|
279
|
+
const fallback = storage.get('missing.path', 'default'); // 'default'
|
|
214
280
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// Returns: {
|
|
218
|
-
// client: { language, mobile, platform, userAgent, url },
|
|
219
|
-
// browser: { vendor }
|
|
220
|
-
// }
|
|
281
|
+
storage.remove('user.name');
|
|
282
|
+
storage.clear();
|
|
221
283
|
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
284
|
+
// SessionStorage (cleared when browser closes)
|
|
285
|
+
storage.session.set('temp.token', 'abc123');
|
|
286
|
+
storage.session.get('temp.token');
|
|
287
|
+
storage.session.remove('temp.token');
|
|
288
|
+
storage.session.clear();
|
|
226
289
|
```
|
|
227
290
|
|
|
228
|
-
|
|
291
|
+
**Features**:
|
|
292
|
+
- Automatic JSON serialization/deserialization
|
|
293
|
+
- Nested path access using dot notation
|
|
294
|
+
- Fallback to in-memory storage if localStorage unavailable
|
|
295
|
+
- Uses lodash `get`/`set` for reliable path access
|
|
296
|
+
|
|
297
|
+
### Authentication
|
|
298
|
+
|
|
299
|
+
Firebase Authentication wrapper with automatic account data fetching:
|
|
229
300
|
|
|
230
301
|
```javascript
|
|
231
|
-
const
|
|
232
|
-
utils.clipboardCopy('Hello!');
|
|
233
|
-
utils.escapeHTML('<div>Test</div>');
|
|
234
|
-
utils.getContext();
|
|
235
|
-
utils.showNotification('Message', 'info');
|
|
236
|
-
```
|
|
302
|
+
const auth = Manager.auth();
|
|
237
303
|
|
|
238
|
-
|
|
304
|
+
// Listen for auth state changes
|
|
305
|
+
const unsubscribe = auth.listen({ account: true }, (result) => {
|
|
306
|
+
console.log('User:', result.user); // Firebase user or null
|
|
307
|
+
console.log('Account:', result.account); // Firestore account data or null
|
|
308
|
+
});
|
|
239
309
|
|
|
240
|
-
|
|
310
|
+
// Listen once (useful for initial state)
|
|
311
|
+
auth.listen({ once: true }, (result) => {
|
|
312
|
+
console.log('Initial state:', result);
|
|
313
|
+
});
|
|
241
314
|
|
|
242
|
-
|
|
243
|
-
|
|
315
|
+
// Check authentication status
|
|
316
|
+
if (auth.isAuthenticated()) {
|
|
317
|
+
const user = auth.getUser();
|
|
318
|
+
console.log('Logged in as:', user.email);
|
|
319
|
+
}
|
|
244
320
|
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
321
|
+
// Sign in
|
|
322
|
+
try {
|
|
323
|
+
const user = await auth.signInWithEmailAndPassword('user@example.com', 'password');
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error('Sign in failed:', error.message);
|
|
326
|
+
}
|
|
248
327
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const all = storage.get(); // Get entire storage object
|
|
328
|
+
// Sign in with custom token (from backend)
|
|
329
|
+
await auth.signInWithCustomToken('custom-jwt-token');
|
|
252
330
|
|
|
253
|
-
|
|
254
|
-
|
|
331
|
+
// Get ID token for API calls
|
|
332
|
+
const idToken = await auth.getIdToken();
|
|
333
|
+
const freshToken = await auth.getIdToken(true); // Force refresh
|
|
255
334
|
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
335
|
+
// Sign out
|
|
336
|
+
await auth.signOut();
|
|
337
|
+
|
|
338
|
+
// Stop listening
|
|
339
|
+
unsubscribe();
|
|
261
340
|
```
|
|
262
341
|
|
|
263
|
-
|
|
342
|
+
**getUser() returns enhanced user object**:
|
|
343
|
+
```javascript
|
|
344
|
+
{
|
|
345
|
+
uid: 'abc123',
|
|
346
|
+
email: 'user@example.com',
|
|
347
|
+
displayName: 'John Doe', // Falls back to email or 'User'
|
|
348
|
+
photoURL: 'https://...', // Falls back to ui-avatars.com
|
|
349
|
+
emailVerified: true
|
|
350
|
+
}
|
|
351
|
+
```
|
|
264
352
|
|
|
265
|
-
|
|
266
|
-
|
|
353
|
+
**HTML Auth Classes**:
|
|
354
|
+
Add these classes to elements for automatic auth functionality:
|
|
355
|
+
- `.auth-signout-btn` - Sign out button (shows confirmation dialog)
|
|
356
|
+
|
|
357
|
+
### Data Binding System
|
|
358
|
+
|
|
359
|
+
Reactive DOM updates with `data-wm-bind` attributes:
|
|
267
360
|
|
|
268
361
|
#### Basic Text Binding
|
|
269
362
|
```html
|
|
270
|
-
<!-- Display
|
|
363
|
+
<!-- Display text content (default action) -->
|
|
271
364
|
<span data-wm-bind="auth.user.email"></span>
|
|
272
|
-
|
|
273
|
-
<!-- Display nested properties -->
|
|
274
|
-
<div data-wm-bind="auth.account.subscription.plan"></div>
|
|
365
|
+
<span data-wm-bind="@text auth.user.displayName"></span>
|
|
275
366
|
```
|
|
276
367
|
|
|
277
368
|
#### Input/Textarea Value Binding
|
|
278
369
|
```html
|
|
279
|
-
|
|
280
|
-
<input data-wm-bind="@value settings.theme" />
|
|
281
|
-
|
|
282
|
-
<!-- Set textarea value -->
|
|
370
|
+
<input data-wm-bind="@value settings.email" />
|
|
283
371
|
<textarea data-wm-bind="@value user.bio"></textarea>
|
|
284
|
-
|
|
285
|
-
<!-- Combine with other actions -->
|
|
286
|
-
<input data-wm-bind="@value auth.user.email, @attr disabled auth.user.emailVerified" />
|
|
287
372
|
```
|
|
288
373
|
|
|
289
374
|
#### Conditional Visibility
|
|
290
375
|
```html
|
|
291
|
-
<!-- Show
|
|
292
|
-
<div data-wm-bind="@show auth.user">Welcome!</div>
|
|
293
|
-
<div data-wm-bind="@show auth.user.emailVerified">Email is verified</div>
|
|
376
|
+
<!-- Show when truthy -->
|
|
377
|
+
<div data-wm-bind="@show auth.user">Welcome back!</div>
|
|
294
378
|
|
|
295
|
-
<!-- Hide
|
|
379
|
+
<!-- Hide when truthy -->
|
|
296
380
|
<div data-wm-bind="@hide auth.user">Please log in</div>
|
|
297
381
|
|
|
298
|
-
<!--
|
|
299
|
-
<div data-wm-bind="@show !auth.user">
|
|
382
|
+
<!-- Negation -->
|
|
383
|
+
<div data-wm-bind="@show !auth.user">Not logged in</div>
|
|
300
384
|
|
|
301
385
|
<!-- Comparisons -->
|
|
302
|
-
<div data-wm-bind="@show auth.account.
|
|
303
|
-
<div data-wm-bind="@hide settings.
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
#### Usage in JavaScript
|
|
307
|
-
```javascript
|
|
308
|
-
// Auth data is automatically bound when using auth().listen()
|
|
309
|
-
Manager.auth().listen({ account: true }, (result) => {
|
|
310
|
-
// auth.user and auth.account data are automatically bound to the DOM
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// Update bindings with custom data
|
|
314
|
-
Manager.bindings().update({
|
|
315
|
-
settings: { custom: 'value' },
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// Get current binding context
|
|
319
|
-
const context = Manager.bindings().getContext();
|
|
320
|
-
|
|
321
|
-
// Clear all bindings
|
|
322
|
-
Manager.bindings().clear();
|
|
386
|
+
<div data-wm-bind="@show auth.account.plan === 'premium'">Premium content</div>
|
|
387
|
+
<div data-wm-bind="@hide settings.notifications === false">Notifications on</div>
|
|
323
388
|
```
|
|
324
389
|
|
|
325
|
-
#### Attribute
|
|
390
|
+
#### Attribute Binding
|
|
326
391
|
```html
|
|
327
|
-
<!-- Set an attribute value -->
|
|
328
392
|
<img data-wm-bind="@attr src auth.user.photoURL" />
|
|
329
393
|
<a data-wm-bind="@attr href settings.profileUrl">Profile</a>
|
|
330
|
-
|
|
331
|
-
<!-- Multiple attributes on same element -->
|
|
332
|
-
<img data-wm-bind="@attr src auth.user.photoURL, @attr alt auth.user.displayName" />
|
|
394
|
+
<input data-wm-bind="@attr disabled auth.loading" />
|
|
333
395
|
```
|
|
334
396
|
|
|
335
|
-
####
|
|
336
|
-
You can combine multiple actions on a single element by separating them with commas:
|
|
337
|
-
|
|
397
|
+
#### Style Binding
|
|
338
398
|
```html
|
|
339
|
-
<!--
|
|
340
|
-
<div data-wm-bind="@
|
|
399
|
+
<!-- CSS custom properties -->
|
|
400
|
+
<div data-wm-bind="@style --rating-width ratings.percent"></div>
|
|
341
401
|
|
|
342
|
-
<!--
|
|
343
|
-
<
|
|
344
|
-
|
|
345
|
-
<!-- Multiple attributes -->
|
|
346
|
-
<a data-wm-bind="@attr href settings.url, @attr target settings.target, @text settings.linkText"></a>
|
|
402
|
+
<!-- Inline styles -->
|
|
403
|
+
<div data-wm-bind="@style background-color theme.primary"></div>
|
|
347
404
|
```
|
|
348
405
|
|
|
349
|
-
####
|
|
350
|
-
|
|
351
|
-
|
|
406
|
+
#### Multiple Actions
|
|
407
|
+
Combine actions with commas:
|
|
352
408
|
```html
|
|
353
|
-
|
|
354
|
-
<span data-wm-bind="auth.user.displayName" class="wm-binding-skeleton"></span>
|
|
355
|
-
|
|
356
|
-
<!-- Profile card with multiple skeleton loaders -->
|
|
357
|
-
<div class="profile-card">
|
|
358
|
-
<img data-wm-bind="@attr src auth.user.photoURL" class="wm-binding-skeleton">
|
|
359
|
-
<h3 data-wm-bind="auth.user.displayName" class="wm-binding-skeleton"></h3>
|
|
360
|
-
<p data-wm-bind="auth.user.email" class="wm-binding-skeleton"></p>
|
|
361
|
-
</div>
|
|
362
|
-
|
|
363
|
-
<!-- Elements without the class won't show skeleton loaders -->
|
|
364
|
-
<span data-wm-bind="settings.theme"></span>
|
|
409
|
+
<img data-wm-bind="@show auth.user, @attr src auth.user.photoURL, @attr alt auth.user.displayName" />
|
|
365
410
|
```
|
|
366
411
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
- Prevents interaction until data is loaded
|
|
371
|
-
- Fades in smoothly when data arrives
|
|
372
|
-
- Adapts to dark themes
|
|
373
|
-
- Respects `prefers-reduced-motion` accessibility settings
|
|
412
|
+
#### JavaScript API
|
|
413
|
+
```javascript
|
|
414
|
+
const bindings = Manager.bindings();
|
|
374
415
|
|
|
375
|
-
|
|
376
|
-
|
|
416
|
+
// Update context data
|
|
417
|
+
bindings.update({
|
|
418
|
+
settings: { theme: 'dark', email: 'user@example.com' },
|
|
419
|
+
custom: { value: 123 }
|
|
420
|
+
});
|
|
377
421
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
.wm-bound {
|
|
381
|
-
outline: 1px dashed rgba(0, 123, 255, 0.3);
|
|
382
|
-
}
|
|
422
|
+
// Get current context
|
|
423
|
+
const context = bindings.getContext();
|
|
383
424
|
|
|
384
|
-
|
|
385
|
-
.
|
|
386
|
-
/* Element has been successfully bound */
|
|
387
|
-
}
|
|
425
|
+
// Clear all bindings
|
|
426
|
+
bindings.clear();
|
|
388
427
|
```
|
|
389
428
|
|
|
390
|
-
####
|
|
391
|
-
Set CSS custom properties (CSS variables) or inline styles dynamically:
|
|
392
|
-
|
|
429
|
+
#### Skeleton Loaders
|
|
393
430
|
```html
|
|
394
|
-
<!--
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
<!-- Set regular style property -->
|
|
398
|
-
<div data-wm-bind="@style width user.profile.width"></div>
|
|
399
|
-
|
|
400
|
-
<!-- Multiple styles -->
|
|
401
|
-
<div data-wm-bind="@style --primary-color theme.primaryColor, @style --secondary-color theme.secondaryColor"></div>
|
|
431
|
+
<!-- Shows shimmer animation until bound -->
|
|
432
|
+
<span data-wm-bind="auth.user.name" class="wm-binding-skeleton"></span>
|
|
402
433
|
```
|
|
403
434
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
background: var(--primary-color, #007bff);
|
|
410
|
-
}
|
|
411
|
-
```
|
|
435
|
+
The skeleton automatically:
|
|
436
|
+
- Displays shimmer animation while loading
|
|
437
|
+
- Fades in smoothly when data arrives
|
|
438
|
+
- Adds `wm-bound` class when complete
|
|
439
|
+
- Respects `prefers-reduced-motion`
|
|
412
440
|
|
|
413
441
|
#### Supported Actions
|
|
414
|
-
- **`@text`** (default): Sets the text content of the element
|
|
415
|
-
- **`@value`**: Sets the value of an input or textarea element
|
|
416
|
-
- **`@show`**: Shows the element when condition is true
|
|
417
|
-
- **`@hide`**: Hides the element when condition is true
|
|
418
|
-
- **`@attr`**: Sets an attribute value (format: `@attr attributeName expression`)
|
|
419
|
-
- **`@style`**: Sets a CSS custom property or inline style (format: `@style propertyName expression`)
|
|
420
442
|
|
|
421
|
-
|
|
443
|
+
| Action | Syntax | Description |
|
|
444
|
+
|--------|--------|-------------|
|
|
445
|
+
| `@text` | `@text path` | Set text content (default) |
|
|
446
|
+
| `@value` | `@value path` | Set input/textarea value |
|
|
447
|
+
| `@show` | `@show condition` | Show element if truthy |
|
|
448
|
+
| `@hide` | `@hide condition` | Hide element if truthy |
|
|
449
|
+
| `@attr` | `@attr name path` | Set attribute value |
|
|
450
|
+
| `@style` | `@style prop path` | Set CSS property or variable |
|
|
422
451
|
|
|
423
|
-
###
|
|
452
|
+
### Firestore
|
|
424
453
|
|
|
425
|
-
|
|
454
|
+
Simplified Firestore wrapper with chainable queries:
|
|
426
455
|
|
|
427
456
|
```javascript
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
// Listen for auth state changes (waits for settled state)
|
|
431
|
-
const unsubscribe = auth.listen({ account: true }, (result) => {
|
|
432
|
-
console.log('User:', result.user);
|
|
433
|
-
console.log('Account:', result.account);
|
|
434
|
-
|
|
435
|
-
// result.user contains: uid, email, displayName, photoURL, emailVerified
|
|
436
|
-
// result.account contains resolved account data from Firestore
|
|
437
|
-
});
|
|
457
|
+
const db = Manager.firestore();
|
|
438
458
|
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
});
|
|
459
|
+
// Document operations - two syntax options
|
|
460
|
+
await db.doc('users/user123').set({ name: 'John', age: 30 });
|
|
461
|
+
await db.doc('users', 'user123').update({ age: 31 });
|
|
443
462
|
|
|
444
|
-
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
console.log('
|
|
463
|
+
const docSnap = await db.doc('users/user123').get();
|
|
464
|
+
if (docSnap.exists()) {
|
|
465
|
+
console.log('Data:', docSnap.data());
|
|
466
|
+
console.log('ID:', docSnap.id);
|
|
448
467
|
}
|
|
449
468
|
|
|
450
|
-
|
|
451
|
-
try {
|
|
452
|
-
const user = await auth.signInWithEmailAndPassword('user@example.com', 'password');
|
|
453
|
-
console.log('Signed in:', user);
|
|
454
|
-
} catch (error) {
|
|
455
|
-
console.error('Sign in failed:', error);
|
|
456
|
-
}
|
|
469
|
+
await db.doc('users/user123').delete();
|
|
457
470
|
|
|
458
|
-
//
|
|
459
|
-
await
|
|
471
|
+
// Collection queries
|
|
472
|
+
const snapshot = await db.collection('users').get();
|
|
473
|
+
console.log('Count:', snapshot.size);
|
|
474
|
+
console.log('Empty:', snapshot.empty);
|
|
475
|
+
snapshot.docs.forEach(doc => {
|
|
476
|
+
console.log(doc.id, doc.data());
|
|
477
|
+
});
|
|
460
478
|
|
|
461
|
-
//
|
|
462
|
-
await
|
|
479
|
+
// Query with filters (chainable)
|
|
480
|
+
const results = await db.collection('users')
|
|
481
|
+
.where('age', '>=', 18)
|
|
482
|
+
.where('active', '==', true)
|
|
483
|
+
.orderBy('createdAt', 'desc')
|
|
484
|
+
.limit(20)
|
|
485
|
+
.get();
|
|
463
486
|
|
|
464
|
-
//
|
|
465
|
-
const
|
|
487
|
+
// Pagination
|
|
488
|
+
const page2 = await db.collection('users')
|
|
489
|
+
.orderBy('name')
|
|
490
|
+
.startAt('M')
|
|
491
|
+
.endAt('N')
|
|
492
|
+
.get();
|
|
466
493
|
```
|
|
467
494
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
Add these CSS classes to HTML elements for automatic auth functionality:
|
|
471
|
-
|
|
472
|
-
* `.auth-signout-btn` - Sign out button (shows confirmation)
|
|
473
|
-
|
|
474
|
-
The auth system automatically updates DOM elements with `data-wm-bind` attributes (see Data Binding section).
|
|
495
|
+
**Where Operators**: `<`, `<=`, `==`, `!=`, `>=`, `>`, `array-contains`, `in`, `array-contains-any`, `not-in`
|
|
475
496
|
|
|
476
497
|
### Push Notifications
|
|
477
498
|
|
|
478
|
-
|
|
499
|
+
Firebase Cloud Messaging integration:
|
|
479
500
|
|
|
480
501
|
```javascript
|
|
481
502
|
const notifications = Manager.notifications();
|
|
482
503
|
|
|
483
|
-
// Check
|
|
504
|
+
// Check support
|
|
484
505
|
if (notifications.isSupported()) {
|
|
485
|
-
console.log('Push notifications
|
|
506
|
+
console.log('Push notifications available');
|
|
486
507
|
}
|
|
487
508
|
|
|
488
509
|
// Check subscription status
|
|
489
510
|
const isSubscribed = await notifications.isSubscribed();
|
|
490
511
|
|
|
491
|
-
// Subscribe
|
|
512
|
+
// Subscribe
|
|
492
513
|
try {
|
|
493
514
|
const result = await notifications.subscribe({
|
|
494
515
|
vapidKey: 'your-vapid-key' // Optional
|
|
495
516
|
});
|
|
496
|
-
console.log('
|
|
517
|
+
console.log('Token:', result.token);
|
|
497
518
|
} catch (error) {
|
|
498
|
-
|
|
519
|
+
if (error.message.includes('permission')) {
|
|
520
|
+
console.log('User denied permission');
|
|
521
|
+
}
|
|
499
522
|
}
|
|
500
523
|
|
|
501
524
|
// Unsubscribe
|
|
502
525
|
await notifications.unsubscribe();
|
|
503
526
|
|
|
504
|
-
// Get current
|
|
527
|
+
// Get current token
|
|
505
528
|
const token = await notifications.getToken();
|
|
506
529
|
|
|
507
530
|
// Listen for foreground messages
|
|
508
531
|
const unsubscribe = await notifications.onMessage((payload) => {
|
|
509
|
-
console.log('
|
|
532
|
+
console.log('Received:', payload);
|
|
510
533
|
});
|
|
511
534
|
|
|
512
|
-
// Sync subscription with
|
|
535
|
+
// Sync subscription with auth state
|
|
513
536
|
await notifications.syncSubscription();
|
|
514
537
|
```
|
|
515
538
|
|
|
516
|
-
|
|
517
|
-
-
|
|
518
|
-
-
|
|
539
|
+
**Features**:
|
|
540
|
+
- Stores subscription in localStorage and Firestore
|
|
541
|
+
- Tracks device context (platform, runtime, deviceType)
|
|
542
|
+
- Auto-requests after configurable delay post-click
|
|
519
543
|
- Syncs with user authentication state
|
|
520
|
-
- Shows notifications when app is in foreground
|
|
521
544
|
|
|
522
545
|
### Service Worker
|
|
523
546
|
|
|
524
|
-
|
|
547
|
+
Service worker registration and messaging:
|
|
525
548
|
|
|
526
549
|
```javascript
|
|
527
550
|
const sw = Manager.serviceWorker();
|
|
528
551
|
|
|
529
|
-
// Check
|
|
552
|
+
// Check support
|
|
530
553
|
if (sw.isSupported()) {
|
|
531
|
-
console.log('Service workers
|
|
554
|
+
console.log('Service workers available');
|
|
532
555
|
}
|
|
533
556
|
|
|
534
|
-
// Register
|
|
557
|
+
// Register (done automatically during init if enabled)
|
|
535
558
|
const registration = await sw.register({
|
|
536
559
|
path: '/service-worker.js',
|
|
537
560
|
scope: '/'
|
|
538
561
|
});
|
|
539
562
|
|
|
540
|
-
// Wait for
|
|
563
|
+
// Wait for ready state
|
|
541
564
|
await sw.ready();
|
|
542
565
|
|
|
543
|
-
// Get
|
|
566
|
+
// Get registration
|
|
544
567
|
const reg = sw.getRegistration();
|
|
545
568
|
|
|
546
|
-
// Post message
|
|
569
|
+
// Post message with response
|
|
547
570
|
try {
|
|
548
571
|
const response = await sw.postMessage({
|
|
549
572
|
command: 'cache-clear',
|
|
@@ -551,62 +574,21 @@ try {
|
|
|
551
574
|
}, { timeout: 5000 });
|
|
552
575
|
console.log('Response:', response);
|
|
553
576
|
} catch (error) {
|
|
554
|
-
console.error('
|
|
577
|
+
console.error('Timeout or error:', error);
|
|
555
578
|
}
|
|
556
579
|
|
|
557
580
|
// Listen for messages from service worker
|
|
558
581
|
const unsubscribe = sw.onMessage('notification-click', (data, event) => {
|
|
559
|
-
console.log('
|
|
582
|
+
console.log('Clicked:', data);
|
|
560
583
|
});
|
|
561
584
|
|
|
562
|
-
// Get
|
|
563
|
-
const state = sw.getState(); // 'none', 'installing', 'waiting', 'active'
|
|
585
|
+
// Get current state
|
|
586
|
+
const state = sw.getState(); // 'none', 'installing', 'waiting', 'active', 'unknown'
|
|
564
587
|
```
|
|
565
588
|
|
|
566
|
-
###
|
|
589
|
+
### Sentry Error Tracking
|
|
567
590
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
```javascript
|
|
571
|
-
const db = Manager.firestore();
|
|
572
|
-
|
|
573
|
-
// Document operations
|
|
574
|
-
await db.doc('users/user123').set({ name: 'John', age: 30 });
|
|
575
|
-
await db.doc('users', 'user123').update({ age: 31 });
|
|
576
|
-
|
|
577
|
-
const docSnap = await db.doc('users/user123').get();
|
|
578
|
-
if (docSnap.exists()) {
|
|
579
|
-
console.log('User data:', docSnap.data());
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
await db.doc('users/user123').delete();
|
|
583
|
-
|
|
584
|
-
// Collection queries
|
|
585
|
-
const snapshot = await db.collection('users').get();
|
|
586
|
-
snapshot.docs.forEach(doc => {
|
|
587
|
-
console.log(doc.id, doc.data());
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
// Query with filters
|
|
591
|
-
const result = await db.collection('users')
|
|
592
|
-
.where('age', '>=', 18)
|
|
593
|
-
.orderBy('age', 'desc')
|
|
594
|
-
.limit(10)
|
|
595
|
-
.get();
|
|
596
|
-
|
|
597
|
-
// Chainable queries
|
|
598
|
-
const adults = await db.collection('users')
|
|
599
|
-
.where('age', '>=', 18)
|
|
600
|
-
.where('active', '==', true)
|
|
601
|
-
.orderBy('createdAt', 'desc')
|
|
602
|
-
.limit(20)
|
|
603
|
-
.startAt(lastDoc)
|
|
604
|
-
.get();
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
### Sentry Integration
|
|
608
|
-
|
|
609
|
-
Error tracking with automatic configuration:
|
|
591
|
+
Automatic error tracking with Sentry:
|
|
610
592
|
|
|
611
593
|
```javascript
|
|
612
594
|
const sentry = Manager.sentry();
|
|
@@ -622,54 +604,191 @@ try {
|
|
|
622
604
|
}
|
|
623
605
|
```
|
|
624
606
|
|
|
625
|
-
|
|
626
|
-
- Environment and release tracking
|
|
627
|
-
- User context from auth state
|
|
628
|
-
- Session
|
|
629
|
-
-
|
|
607
|
+
**Automatic Features**:
|
|
608
|
+
- Environment and release tracking from config
|
|
609
|
+
- User context from auth state (uid, email)
|
|
610
|
+
- Session duration tracking
|
|
611
|
+
- Filters out Lighthouse and automated browsers (Selenium, Puppeteer)
|
|
612
|
+
- Blocks sending in development mode
|
|
613
|
+
- Dynamic import to reduce bundle size
|
|
614
|
+
|
|
615
|
+
### DOM Utilities
|
|
616
|
+
|
|
617
|
+
```javascript
|
|
618
|
+
import { loadScript, ready } from 'web-manager/modules/dom';
|
|
619
|
+
// Or: const { loadScript, ready } = Manager.dom();
|
|
620
|
+
|
|
621
|
+
// Wait for DOM ready
|
|
622
|
+
await ready();
|
|
623
|
+
|
|
624
|
+
// Load external script
|
|
625
|
+
await loadScript({
|
|
626
|
+
src: 'https://example.com/script.js',
|
|
627
|
+
async: true,
|
|
628
|
+
defer: false,
|
|
629
|
+
crossorigin: 'anonymous',
|
|
630
|
+
integrity: 'sha384-...',
|
|
631
|
+
timeout: 30000,
|
|
632
|
+
retries: 2,
|
|
633
|
+
parent: document.head,
|
|
634
|
+
attributes: { 'data-custom': 'value' }
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Simple string syntax
|
|
638
|
+
await loadScript('https://example.com/script.js');
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
**loadScript Options**:
|
|
630
642
|
|
|
631
|
-
|
|
643
|
+
| Option | Type | Default | Description |
|
|
644
|
+
|--------|------|---------|-------------|
|
|
645
|
+
| `src` | string | required | Script URL |
|
|
646
|
+
| `async` | boolean | `true` | Load asynchronously |
|
|
647
|
+
| `defer` | boolean | `false` | Defer execution |
|
|
648
|
+
| `crossorigin` | boolean/string | `false` | CORS setting |
|
|
649
|
+
| `integrity` | string | `null` | SRI hash |
|
|
650
|
+
| `timeout` | number | `60000` | Timeout in ms |
|
|
651
|
+
| `retries` | number | `0` | Retry attempts |
|
|
652
|
+
| `parent` | Element | `document.head` | Parent element |
|
|
653
|
+
| `attributes` | object | `{}` | Custom attributes |
|
|
632
654
|
|
|
633
|
-
|
|
655
|
+
### Utility Functions
|
|
634
656
|
|
|
635
657
|
```javascript
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
658
|
+
import {
|
|
659
|
+
clipboardCopy,
|
|
660
|
+
escapeHTML,
|
|
661
|
+
showNotification,
|
|
662
|
+
getPlatform,
|
|
663
|
+
getRuntime,
|
|
664
|
+
isMobile,
|
|
665
|
+
getDeviceType,
|
|
666
|
+
getContext
|
|
667
|
+
} from 'web-manager/modules/utilities';
|
|
668
|
+
// Or: const utils = Manager.utilities();
|
|
669
|
+
|
|
670
|
+
// Copy to clipboard
|
|
671
|
+
await clipboardCopy('Text to copy');
|
|
672
|
+
await clipboardCopy(document.querySelector('#input')); // From element
|
|
673
|
+
|
|
674
|
+
// Escape HTML (XSS prevention)
|
|
675
|
+
const safe = escapeHTML('<script>alert("xss")</script>');
|
|
676
|
+
// '<script>alert("xss")</script>'
|
|
677
|
+
|
|
678
|
+
// Show notification (Bootstrap-styled)
|
|
679
|
+
showNotification('Success!', { type: 'success', timeout: 5000 });
|
|
680
|
+
showNotification('Error!', 'danger');
|
|
681
|
+
showNotification(new Error('Failed'), { timeout: 0 }); // No auto-dismiss
|
|
682
|
+
|
|
683
|
+
// Platform detection
|
|
684
|
+
getPlatform(); // 'windows', 'mac', 'linux', 'ios', 'android', 'chromeos', 'unknown'
|
|
685
|
+
|
|
686
|
+
// Runtime detection
|
|
687
|
+
getRuntime(); // 'web', 'browser-extension'
|
|
688
|
+
|
|
689
|
+
// Device detection
|
|
690
|
+
isMobile(); // true/false
|
|
691
|
+
getDeviceType(); // 'mobile' (<768px), 'tablet' (768-1199px), 'desktop' (>=1200px)
|
|
692
|
+
|
|
693
|
+
// Full context
|
|
694
|
+
getContext();
|
|
695
|
+
// {
|
|
696
|
+
// client: { language, mobile, deviceType, platform, runtime, userAgent, url },
|
|
697
|
+
// browser: { vendor }
|
|
698
|
+
// }
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
**showNotification Options**:
|
|
640
702
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
703
|
+
| Option | Type | Default | Description |
|
|
704
|
+
|--------|------|---------|-------------|
|
|
705
|
+
| `type` | string | `'info'` | `'info'`, `'success'`, `'warning'`, `'danger'` |
|
|
706
|
+
| `timeout` | number | `5000` | Auto-dismiss after ms (0 = never) |
|
|
645
707
|
|
|
646
|
-
|
|
647
|
-
const apiUrl = Manager.getApiUrl(); // https://api.your-domain.com
|
|
708
|
+
## HTML Data Attributes
|
|
648
709
|
|
|
649
|
-
|
|
650
|
-
const isValid = Manager.isValidRedirectUrl('https://example.com/callback');
|
|
710
|
+
Web Manager automatically sets these attributes on the `<html>` element during initialization:
|
|
651
711
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
712
|
+
```html
|
|
713
|
+
<html data-platform="mac" data-runtime="web" data-device="desktop">
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
| Attribute | Values | Description |
|
|
717
|
+
|-----------|--------|-------------|
|
|
718
|
+
| `data-platform` | `windows`, `mac`, `linux`, `ios`, `android`, `chromeos`, `unknown` | Operating system |
|
|
719
|
+
| `data-runtime` | `web`, `browser-extension` | Runtime environment |
|
|
720
|
+
| `data-device` | `mobile`, `tablet`, `desktop` | Device type by screen width |
|
|
721
|
+
|
|
722
|
+
**CSS Usage**:
|
|
723
|
+
```css
|
|
724
|
+
/* Platform-specific styles */
|
|
725
|
+
[data-platform="ios"] .download-btn { display: none; }
|
|
726
|
+
[data-platform="windows"] .app-icon { content: url('windows-icon.png'); }
|
|
657
727
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
console.log('Environment:', config.environment);
|
|
728
|
+
/* Device-responsive styles */
|
|
729
|
+
[data-device="mobile"] .sidebar { display: none; }
|
|
730
|
+
[data-device="desktop"] .mobile-menu { display: none; }
|
|
662
731
|
```
|
|
663
732
|
|
|
664
|
-
##
|
|
665
|
-
If you are still having difficulty, we would love for you to post
|
|
666
|
-
a question to [the Web Manager issues page](https://github.com/itw-creative-works/web-manager/issues). It is much easier to answer questions that include your code and relevant files! So if you can provide them, we'd be extremely grateful (and more likely to help you find the answer!)
|
|
733
|
+
## Direct Module Imports
|
|
667
734
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
735
|
+
Import individual modules to reduce bundle size:
|
|
736
|
+
|
|
737
|
+
```javascript
|
|
738
|
+
// Storage only
|
|
739
|
+
import Storage from 'web-manager/modules/storage';
|
|
740
|
+
const storage = new Storage();
|
|
741
|
+
|
|
742
|
+
// Utilities only
|
|
743
|
+
import { clipboardCopy, escapeHTML } from 'web-manager/modules/utilities';
|
|
744
|
+
|
|
745
|
+
// DOM utilities only
|
|
746
|
+
import { loadScript, ready } from 'web-manager/modules/dom';
|
|
747
|
+
|
|
748
|
+
// Full manager (default)
|
|
749
|
+
import Manager from 'web-manager';
|
|
750
|
+
```
|
|
674
751
|
|
|
675
|
-
|
|
752
|
+
**Available Modules**:
|
|
753
|
+
- `web-manager/modules/storage` - Storage class
|
|
754
|
+
- `web-manager/modules/utilities` - Utility functions
|
|
755
|
+
- `web-manager/modules/dom` - DOM utilities
|
|
756
|
+
- `web-manager/modules/auth` - Auth class (requires Manager)
|
|
757
|
+
- `web-manager/modules/bindings` - Bindings class (requires Manager)
|
|
758
|
+
- `web-manager/modules/firestore` - Firestore class (requires Manager)
|
|
759
|
+
- `web-manager/modules/notifications` - Notifications class (requires Manager)
|
|
760
|
+
- `web-manager/modules/service-worker` - ServiceWorker class (requires Manager)
|
|
761
|
+
- `web-manager/modules/sentry` - Sentry class (requires Manager)
|
|
762
|
+
|
|
763
|
+
## Browser Support
|
|
764
|
+
|
|
765
|
+
Web Manager is transpiled to ES5 for broad browser support:
|
|
766
|
+
|
|
767
|
+
| Browser | Version | Support |
|
|
768
|
+
|---------|---------|---------|
|
|
769
|
+
| Chrome | 60+ | Full |
|
|
770
|
+
| Firefox | 55+ | Full |
|
|
771
|
+
| Safari | 11+ | Full |
|
|
772
|
+
| Edge | 79+ | Full |
|
|
773
|
+
| IE | 11 | Not supported |
|
|
774
|
+
|
|
775
|
+
**Notes**:
|
|
776
|
+
- Service Workers require HTTPS (except localhost)
|
|
777
|
+
- Push Notifications require Service Worker support
|
|
778
|
+
- Some features use modern APIs with fallbacks
|
|
779
|
+
|
|
780
|
+
## Projects Using This Library
|
|
781
|
+
|
|
782
|
+
- [Somiibo](https://somiibo.com/): A Social Media Bot with an open-source module library
|
|
783
|
+
- [JekyllUp](https://jekyllup.com/): A website devoted to sharing the best Jekyll themes
|
|
784
|
+
- [Slapform](https://slapform.com/): A backend processor for HTML forms on static sites
|
|
785
|
+
- [SoundGrail Music App](https://app.soundgrail.com/): A resource for producers, musicians, and DJs
|
|
786
|
+
- [Hammock Report](https://hammockreport.com/): An API for exploring and listing backyard products
|
|
787
|
+
|
|
788
|
+
*Want your project listed? [Open an issue](https://github.com/itw-creative-works/web-manager/issues)!*
|
|
789
|
+
|
|
790
|
+
## Support
|
|
791
|
+
|
|
792
|
+
If you're having issues or have questions:
|
|
793
|
+
- [Open an issue](https://github.com/itw-creative-works/web-manager/issues) on GitHub
|
|
794
|
+
- Include code samples and relevant files to help us help you faster
|
package/dist/modules/auth.js
CHANGED
|
@@ -123,13 +123,6 @@ class Auth {
|
|
|
123
123
|
|
|
124
124
|
// Set up listener for auth state changes
|
|
125
125
|
unsubscribe = this.onAuthStateChanged((user) => {
|
|
126
|
-
// If once option is set, unsubscribe
|
|
127
|
-
// We have to do this here because unsubscribe is only available after this call
|
|
128
|
-
if (options.once && unsubscribe) {
|
|
129
|
-
unsubscribe();
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
126
|
// Wait for settled state before first callback
|
|
134
127
|
if (!hasCalledback && !this.manager._firebaseAuthInitialized) {
|
|
135
128
|
return; // Auth state not yet determined
|
|
@@ -140,6 +133,11 @@ class Auth {
|
|
|
140
133
|
|
|
141
134
|
// Get current state and call the callback
|
|
142
135
|
getStateAndCallback(user);
|
|
136
|
+
|
|
137
|
+
// If once option is set, unsubscribe AFTER calling the callback
|
|
138
|
+
if (options.once && unsubscribe) {
|
|
139
|
+
unsubscribe();
|
|
140
|
+
}
|
|
143
141
|
});
|
|
144
142
|
|
|
145
143
|
return unsubscribe;
|
package/firebase-debug.log
CHANGED
|
@@ -658,3 +658,31 @@
|
|
|
658
658
|
[debug] [2025-12-13T03:15:51.272Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
659
659
|
[debug] [2025-12-13T03:15:51.272Z] > 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"]
|
|
660
660
|
[debug] [2025-12-13T03:15:51.272Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
661
|
+
[debug] [2025-12-13T10:11:01.828Z] > 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"]
|
|
662
|
+
[debug] [2025-12-13T10:11:01.828Z] > 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"]
|
|
663
|
+
[debug] [2025-12-13T10:11:01.831Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
664
|
+
[debug] [2025-12-13T10:11:01.832Z] > 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"]
|
|
665
|
+
[debug] [2025-12-13T10:11:01.833Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
666
|
+
[debug] [2025-12-13T10:11:01.842Z] > 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"]
|
|
667
|
+
[debug] [2025-12-13T10:11:01.843Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
668
|
+
[debug] [2025-12-13T10:11:01.831Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
669
|
+
[debug] [2025-12-13T10:11:01.831Z] > 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"]
|
|
670
|
+
[debug] [2025-12-13T10:11:01.831Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
671
|
+
[debug] [2025-12-13T10:11:01.846Z] > 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"]
|
|
672
|
+
[debug] [2025-12-13T10:11:01.847Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
673
|
+
[debug] [2025-12-13T10:11:01.865Z] > 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"]
|
|
674
|
+
[debug] [2025-12-13T10:11:01.870Z] > 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"]
|
|
675
|
+
[debug] [2025-12-13T10:11:01.866Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
676
|
+
[debug] [2025-12-13T10:11:01.867Z] > 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"]
|
|
677
|
+
[debug] [2025-12-13T10:11:01.867Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
678
|
+
[debug] [2025-12-13T10:11:01.870Z] > 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"]
|
|
679
|
+
[debug] [2025-12-13T10:11:01.870Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
680
|
+
[debug] [2025-12-13T10:11:01.871Z] > 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"]
|
|
681
|
+
[debug] [2025-12-13T10:11:01.871Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
682
|
+
[debug] [2025-12-13T10:11:01.870Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
683
|
+
[debug] [2025-12-13T10:11:01.871Z] > 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"]
|
|
684
|
+
[debug] [2025-12-13T10:11:01.871Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
685
|
+
[debug] [2025-12-13T10:11:01.873Z] > 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"]
|
|
686
|
+
[debug] [2025-12-13T10:11:01.873Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
|
687
|
+
[debug] [2025-12-13T10:11:01.874Z] > 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"]
|
|
688
|
+
[debug] [2025-12-13T10:11:01.874Z] > authorizing via signed-in user (ian.wiedenman@gmail.com)
|
package/package.json
CHANGED