@omega.js/client 0.1.0

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.
Files changed (72) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +874 -0
  3. package/dist/index.js +999 -0
  4. package/dist/modules/analytics.js +584 -0
  5. package/dist/modules/auth.js +469 -0
  6. package/dist/modules/bindings.js +319 -0
  7. package/dist/modules/device.js +282 -0
  8. package/dist/modules/dom.js +96 -0
  9. package/dist/modules/features.js +30 -0
  10. package/dist/modules/firestore.js +313 -0
  11. package/dist/modules/form-manager.js +1577 -0
  12. package/dist/modules/icon-core.js +226 -0
  13. package/dist/modules/icon-renderer.js +149 -0
  14. package/dist/modules/live-page.js +235 -0
  15. package/dist/modules/logger.js +36 -0
  16. package/dist/modules/motion.js +853 -0
  17. package/dist/modules/notifications.js +433 -0
  18. package/dist/modules/path-prefix.js +22 -0
  19. package/dist/modules/request.js +223 -0
  20. package/dist/modules/sentry.js +108 -0
  21. package/dist/modules/service-worker.js +237 -0
  22. package/dist/modules/storage.js +133 -0
  23. package/dist/modules/triggers.js +117 -0
  24. package/dist/modules/utilities.js +479 -0
  25. package/dist/modules/vert-document.js +354 -0
  26. package/dist/modules/verts.js +1133 -0
  27. package/dist/vendor/account/engine.js +182 -0
  28. package/dist/vendor/account/features.js +220 -0
  29. package/dist/vendor/account/index.js +53 -0
  30. package/dist/vendor/account/schema.js +272 -0
  31. package/dist/vendor/account/subscription.js +38 -0
  32. package/dist/vendor/analytics/adapters/ga4.js +26 -0
  33. package/dist/vendor/analytics/adapters/meta.js +26 -0
  34. package/dist/vendor/analytics/adapters/resolve.js +130 -0
  35. package/dist/vendor/analytics/adapters/tiktok.js +27 -0
  36. package/dist/vendor/analytics/catalog.js +908 -0
  37. package/dist/vendor/analytics/consent.js +49 -0
  38. package/dist/vendor/analytics/core.js +141 -0
  39. package/dist/vendor/analytics/identity.js +136 -0
  40. package/dist/vendor/analytics/index.js +170 -0
  41. package/dist/vendor/analytics/logger.js +40 -0
  42. package/dist/vendor/analytics/transports/browser.js +110 -0
  43. package/dist/vendor/monitoring/browser.js +207 -0
  44. package/dist/vendor/monitoring/core.js +180 -0
  45. package/dist/vendor/monitoring/logger.js +39 -0
  46. package/docs/architecture.md +59 -0
  47. package/docs/bindings.md +235 -0
  48. package/docs/build-system.md +32 -0
  49. package/docs/cdp-debugging.md +29 -0
  50. package/docs/code-patterns.md +96 -0
  51. package/docs/common-tasks.md +36 -0
  52. package/docs/dependencies.md +19 -0
  53. package/docs/index.md +159 -0
  54. package/docs/modules.md +180 -0
  55. package/docs/shared/agent-docs.md +89 -0
  56. package/docs/shared/analytics.md +612 -0
  57. package/docs/shared/brands.md +51 -0
  58. package/docs/shared/breaking-changes.md +497 -0
  59. package/docs/shared/config.md +1387 -0
  60. package/docs/shared/deploys.md +215 -0
  61. package/docs/shared/icons.md +201 -0
  62. package/docs/shared/local-dev.md +147 -0
  63. package/docs/shared/logging.md +202 -0
  64. package/docs/shared/monitoring.md +153 -0
  65. package/docs/shared/publishing.md +183 -0
  66. package/docs/shared/rulings.md +34 -0
  67. package/docs/shared/testing.md +147 -0
  68. package/docs/shared/theming.md +604 -0
  69. package/docs/shared/translation.md +291 -0
  70. package/docs/shared/updates.md +61 -0
  71. package/docs/testing.md +9 -0
  72. package/package.json +65 -0
package/README.md ADDED
@@ -0,0 +1,874 @@
1
+ <p align="center">
2
+ <a href="https://itwcreativeworks.com">
3
+ <img src="https://cdn.itwcreativeworks.com/assets/itw-creative-works/images/logo/itw-creative-works-brandmark-black-x.svg" width="100px">
4
+ </a>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <strong>OMEGA Client</strong> — the shared frontend runtime (auth, data binding, storage, push notifications, error tracking) embedded by
9
+ <a href="../web/">@omega.js/web</a>,
10
+ <a href="../desktop/">@omega.js/desktop</a>, and
11
+ <a href="../extension/">@omega.js/extension</a>.
12
+ </p>
13
+
14
+ ## Table of Contents
15
+ - [Installation](#-installation)
16
+ - [Requirements](#-requirements)
17
+ - [Quick Start](#-quick-start)
18
+ - [Supported Environments](#-supported-environments)
19
+ - [Features](#-features)
20
+ - [Configuration](#-configuration)
21
+ - [API Reference](#-api-reference)
22
+ - [Manager Instance](#manager-instance)
23
+ - [Storage API](#storage-api)
24
+ - [Authentication](#authentication)
25
+ - [Data Binding System](#data-binding-system)
26
+ - [Firestore](#firestore)
27
+ - [Push Notifications](#push-notifications)
28
+ - [Service Worker](#service-worker)
29
+ - [Sentry Error Tracking](#sentry-error-tracking)
30
+ - [DOM Utilities](#dom-utilities)
31
+ - [Utility Functions](#utility-functions)
32
+ - [HTML Data Attributes](#-html-data-attributes)
33
+ - [Direct Module Imports](#-direct-module-imports)
34
+ - [Browser Support](#-browser-support)
35
+ - [Projects Using This Library](#-projects-using-this-library)
36
+ - [Support](#-support)
37
+
38
+ ## Installation
39
+ ```shell
40
+ npm install @omega.js/client
41
+ ```
42
+
43
+ ## Requirements
44
+ - **Node.js**: >= 12
45
+ - **Browser**: Modern browsers (ES6+ support, transpiled to ES5 for older browsers)
46
+
47
+ **Note**: This library does not include TypeScript definitions.
48
+
49
+ ## Quick Start
50
+
51
+ ```javascript
52
+ import Manager from '@omega.js/client';
53
+
54
+ // Initialize with your configuration
55
+ await Manager.initialize({
56
+ environment: 'production',
57
+ buildTime: Date.now(),
58
+ brand: {
59
+ id: 'my-app',
60
+ name: 'My Application'
61
+ },
62
+ firebase: {
63
+ app: {
64
+ enabled: true,
65
+ config: {
66
+ apiKey: 'your-api-key',
67
+ authDomain: 'your-app.com', // brand host — the site self-hosts /__/auth/*
68
+ projectId: 'your-project-id',
69
+ storageBucket: 'your-app.appspot.com',
70
+ messagingSenderId: '123456789',
71
+ appId: '1:123456789:web:abcdef'
72
+ }
73
+ }
74
+ }
75
+ });
76
+
77
+ console.log('OMEGA Client initialized!');
78
+ ```
79
+
80
+ ## Supported Environments
81
+
82
+ Web Manager is designed to work in multiple environments:
83
+
84
+ | Environment | Support | Notes |
85
+ |-------------|---------|-------|
86
+ | **Web** | Full | Primary target, works with webpack bundlers |
87
+ | **Electron** | Full | Works in renderer process |
88
+ | **Chrome Extension** | Full | Content scripts and popup pages |
89
+ | **Firefox Extension** | Full | Content scripts and popup pages |
90
+ | **Safari Extension** | Partial | Basic functionality supported |
91
+
92
+ ## Features
93
+ - **Firebase v12 Integration**: Modern Firebase Auth, Firestore, and Cloud Messaging
94
+ - **Data Binding System**: Reactive DOM updates with `data-omega-bind` attributes
95
+ - **Storage API**: Enhanced localStorage/sessionStorage with path-based access and JSON serialization
96
+ - **Utilities**: `clipboardCopy()`, `escapeHTML()`, `sanitizeURL()`, `renderMarkdown()`, `getContext()`, `showNotification()`, `getPlatform()`, `getBrowser()`, `getRuntime()`, `isMobile()`, `getDevice()`
97
+ - **DOM Utilities**: Dynamic script loading with retry/timeout support
98
+ - **Service Worker Management**: Registration, messaging, and state tracking
99
+ - **Push Notifications**: Firebase Cloud Messaging with auto-subscription
100
+ - **Error Tracking**: Sentry integration with session replay
101
+ - **App Check**: Optional reCAPTCHA Enterprise protection
102
+ - **Version Checking**: Auto-reload when new version is deployed
103
+ - **HTML Data Attributes**: Automatic `data-platform`, `data-browser`, `data-runtime`, `data-device` on `<html>`
104
+
105
+ ## Configuration
106
+
107
+ > **Dev mode = local Firebase, zero flags.** When `environment` is `'development'`, the client auto-connects the REAL Auth + Firestore SDKs to the local emulator suite — never live Firebase. `omega dev` injects that environment automatically; production builds never connect. There is deliberately no live-Firebase opt-out for dev — build production locally if you truly need live.
108
+ >
109
+ > **Dev ports (N7):** the client resolves the port map with precedence `window.__OMEGA_DEV_PORTS__` (runtime channel — set by drivers like the devkit e2e harness after the page was built) → `config.dev.ports` (baked into the chrome by `omega dev`) → classic defaults (auth `:9099`, firestore `:8080`, functions `:5001`, hosting `:5002`). The emulator connects, `getFunctionsUrl()`, and `getApiUrl()` all read it, so bumped ports (a second brand's concurrent stack) reach the browser. Dev `getApiUrl()`: mapped `hosting` → plain `http://127.0.0.1:<port>` (the hosting emulator speaks http), mapped `https` → `mgr serve`'s mkcert proxy, no map → the classic `https://localhost:5002` serve assumption.
110
+
111
+ ### Full Configuration Reference
112
+
113
+ ```javascript
114
+ await Manager.initialize({
115
+ // Environment: 'development' or 'production'
116
+ environment: 'production',
117
+
118
+ // Build timestamp for version checking
119
+ buildTime: Date.now(),
120
+
121
+ // Brand information
122
+ brand: {
123
+ id: 'my-app', // Used for custom protocol URLs
124
+ name: 'My Application',
125
+ description: 'App description',
126
+ type: 'Organization',
127
+ images: {
128
+ brandmark: 'https://example.com/logo.png',
129
+ wordmark: 'https://example.com/wordmark.png',
130
+ combomark: 'https://example.com/combomark.png'
131
+ },
132
+ contact: {
133
+ email: 'support@example.com',
134
+ phone: '+1-555-0123'
135
+ }
136
+ },
137
+
138
+ // Firebase configuration
139
+ firebase: {
140
+ app: {
141
+ enabled: true,
142
+ config: {
143
+ apiKey: 'your-api-key',
144
+ authDomain: 'your-app.com', // brand host — the site self-hosts /__/auth/*
145
+ projectId: 'your-project-id',
146
+ storageBucket: 'your-app.appspot.com',
147
+ messagingSenderId: '123456789',
148
+ appId: '1:123456789:web:abcdef'
149
+ }
150
+ },
151
+ appCheck: {
152
+ enabled: false,
153
+ config: {
154
+ siteKey: 'your-recaptcha-enterprise-site-key'
155
+ }
156
+ }
157
+ },
158
+
159
+ // Authentication settings
160
+ auth: {
161
+ enabled: true,
162
+ config: {
163
+ redirects: {
164
+ authenticated: '/account', // Redirect after login
165
+ unauthenticated: '/signup' // Redirect when not logged in
166
+ }
167
+ }
168
+ },
169
+
170
+ // Sentry error tracking
171
+ sentry: {
172
+ enabled: true,
173
+ config: {
174
+ dsn: 'https://your-sentry-dsn',
175
+ release: '1.0.0',
176
+ replaysSessionSampleRate: 0.01, // 1% of sessions
177
+ replaysOnErrorSampleRate: 0.01 // 1% of error sessions
178
+ }
179
+ },
180
+
181
+ // Push notifications
182
+ pushNotifications: {
183
+ enabled: true,
184
+ config: {
185
+ autoRequest: 60000, // Auto-request after 60s of first click
186
+ vapidKey: 'your-vapid-key' // Optional VAPID key
187
+ }
188
+ },
189
+
190
+ // Service worker
191
+ serviceWorker: {
192
+ enabled: true,
193
+ config: {
194
+ path: '/service-worker.js'
195
+ }
196
+ },
197
+
198
+ // Version checking (auto-reload on new version)
199
+ refreshNewVersion: {
200
+ enabled: true,
201
+ config: {
202
+ interval: 3600000 // Check every hour (1000 * 60 * 60)
203
+ }
204
+ },
205
+
206
+ // Valid hosts for auth redirects (security)
207
+ validRedirectHosts: ['example.com', 'app.example.com']
208
+ });
209
+ ```
210
+
211
+ ### Configuration Notes
212
+
213
+ - **Timeout values** can be specified as strings with math expressions: `'1000 * 60 * 60'` (evaluated safely)
214
+ - **Deep merge**: Your config is deep-merged with defaults, so you only need to specify what you want to change
215
+ - **Firebase required**: Most features require Firebase to be configured and enabled
216
+
217
+ ## API Reference
218
+
219
+ ### Manager Instance
220
+
221
+ The Manager is a singleton that provides access to all modules:
222
+
223
+ ```javascript
224
+ import Manager from '@omega.js/client';
225
+
226
+ // Module getters
227
+ Manager.storage(); // Storage API
228
+ Manager.auth(); // Firebase Auth wrapper
229
+ Manager.bindings(); // Data binding system
230
+ Manager.firestore(); // Firestore wrapper
231
+ Manager.notifications(); // Push notifications
232
+ Manager.serviceWorker(); // Service worker management
233
+ Manager.sentry(); // Error tracking
234
+ Manager.dom(); // DOM utilities
235
+ Manager.utilities(); // Utility functions
236
+ Manager.verts(); // Verts (provider ladder + in-house fallback units)
237
+
238
+ // Helper methods
239
+ Manager.isDevelopment(); // Check if in development mode
240
+ Manager.getFunctionsUrl(); // Get Firebase Functions URL
241
+ Manager.getFunctionsUrl('development'); // Force development URL
242
+ Manager.getApiUrl(); // Get API URL (api.<brand.url host>)
243
+ Manager.isValidRedirectUrl('https://...'); // Validate redirect URL
244
+
245
+ // Firebase instances (after initialization)
246
+ Manager.firebaseApp; // Firebase App instance
247
+ Manager.firebaseAuth; // Firebase Auth instance
248
+ Manager.firebaseFirestore; // Firestore instance
249
+ Manager.firebaseMessaging; // FCM instance
250
+
251
+ // Configuration
252
+ Manager.config; // Access full configuration
253
+ ```
254
+
255
+ ### Storage API
256
+
257
+ Enhanced localStorage and sessionStorage with path-based access:
258
+
259
+ ```javascript
260
+ const storage = Manager.storage();
261
+
262
+ // LocalStorage (persists across browser sessions)
263
+ storage.set('user.name', 'John');
264
+ storage.set('user.preferences', { theme: 'dark', lang: 'en' });
265
+
266
+ const name = storage.get('user.name'); // 'John'
267
+ const theme = storage.get('user.preferences.theme'); // 'dark'
268
+ const all = storage.get(); // Entire storage object
269
+ const fallback = storage.get('missing.path', 'default'); // 'default'
270
+
271
+ storage.remove('user.name');
272
+ storage.clear();
273
+
274
+ // SessionStorage (cleared when browser closes)
275
+ storage.session.set('temp.token', 'abc123');
276
+ storage.session.get('temp.token');
277
+ storage.session.remove('temp.token');
278
+ storage.session.clear();
279
+ ```
280
+
281
+ **Features**:
282
+ - Automatic JSON serialization/deserialization
283
+ - Nested path access using dot notation
284
+ - Fallback to in-memory storage if localStorage unavailable
285
+ - Uses lodash `get`/`set` for reliable path access
286
+
287
+ ### Authentication
288
+
289
+ Firebase Authentication wrapper with a promise-based auth settler:
290
+
291
+ ```javascript
292
+ const auth = Manager.auth();
293
+
294
+ // Listen once — waits for auth to settle, fires exactly once
295
+ auth.listen({ once: true }, (state) => {
296
+ if (state.user) {
297
+ console.log('Logged in:', state.user.email);
298
+ console.log('Account:', state.account);
299
+ } else {
300
+ console.log('Not logged in');
301
+ }
302
+ });
303
+
304
+ // Persistent listener — fires on initial settle + every future auth change
305
+ const unsubscribe = auth.listen({}, (state) => {
306
+ console.log('Auth changed:', state.user?.email || 'signed out');
307
+ });
308
+
309
+ // Check authentication status
310
+ if (auth.isAuthenticated()) {
311
+ const user = auth.getUser();
312
+ console.log('Logged in as:', user.email);
313
+ }
314
+
315
+ // Sign in
316
+ await auth.signInWithEmailAndPassword('user@example.com', 'password');
317
+
318
+ // Sign in with custom token (from backend)
319
+ await auth.signInWithCustomToken('custom-jwt-token');
320
+
321
+ // Get ID token for API calls
322
+ const idToken = await auth.getIdToken();
323
+ const freshToken = await auth.getIdToken(true); // Force refresh
324
+
325
+ // Sign out
326
+ await auth.signOut();
327
+
328
+ // Stop persistent listener
329
+ unsubscribe();
330
+ ```
331
+
332
+ **Auth Settler Design**:
333
+
334
+ On page load, Firebase Auth takes time to restore the user session. The auth settler (`Manager._authReady`) is a promise that resolves once Firebase determines the auth state (user or null). All `listen()` callbacks wait for this settler before firing — consumers never see an intermediate/unknown state.
335
+
336
+ - `{ once: true }` — Waits for the settler promise, calls the callback once, done. No cleanup needed.
337
+ - `{}` (persistent) — Gets the initial settled state via `_handleAuthStateChange`, then fires again on every future sign-in/sign-out.
338
+
339
+ **getUser() returns enhanced user object**:
340
+ ```javascript
341
+ {
342
+ uid: 'abc123',
343
+ email: 'user@example.com',
344
+ displayName: 'John Doe', // Falls back to email or 'User'
345
+ photoURL: 'https://...', // Falls back to ui-avatars.com
346
+ emailVerified: true
347
+ }
348
+ ```
349
+
350
+ **HTML Auth Classes**:
351
+ - `.omega-signout` - Sign out button (shows confirmation dialog)
352
+
353
+ **Resolve Subscription State**:
354
+
355
+ Derives calculated subscription fields from raw account data. Returns only fields that require logic — raw data is on `account.subscription` directly.
356
+
357
+ ```javascript
358
+ const resolved = auth.resolveSubscription(account);
359
+ // Or without an argument (falls back to stored auth state):
360
+ const resolved = auth.resolveSubscription();
361
+ ```
362
+
363
+ When called without an argument, reads the account from `localStorage` (the last auth state saved by `listen()`). Pass an explicit account when you have one to avoid stale data.
364
+
365
+ Returns:
366
+ ```javascript
367
+ {
368
+ plan: 'basic', // Effective plan ID right now ('basic' if cancelled/suspended)
369
+ active: true, // Has active access (active, trialing, or cancelling)
370
+ trialing: false, // In an active trial (status 'active' + unexpired trial)
371
+ cancelling: false, // Cancellation pending (status 'active' + cancellation.pending)
372
+ }
373
+ ```
374
+
375
+ Usage:
376
+ ```javascript
377
+ auth.listen({ once: true }, (state) => {
378
+ const resolved = auth.resolveSubscription(state.account);
379
+
380
+ if (!resolved.active) {
381
+ // User is on free plan or subscription ended
382
+ }
383
+
384
+ if (resolved.trialing) {
385
+ // Show trial banner
386
+ }
387
+
388
+ if (resolved.cancelling) {
389
+ // Show "your plan will cancel at end of period" notice
390
+ }
391
+
392
+ // Use resolved.plan for effective plan ID
393
+ const product = products.find(p => p.id === resolved.plan);
394
+ });
395
+ ```
396
+
397
+ **⚠️ Auth State Timing**:
398
+
399
+ Methods like `auth.isAuthenticated()`, `auth.getUser()`, and `auth.getIdToken()` read the current state directly — they may return `null` before auth settles.
400
+
401
+ ```javascript
402
+ // ❌ May fail on page load - auth state not yet determined
403
+ const token = await auth.getIdToken();
404
+
405
+ // ✅ Wait for auth to settle first
406
+ auth.listen({ once: true }, async (state) => {
407
+ if (state.user) {
408
+ const token = await auth.getIdToken(); // Safe
409
+ }
410
+ });
411
+ ```
412
+
413
+ ### Data Binding System
414
+
415
+ Reactive DOM updates with `data-omega-bind` attributes:
416
+
417
+ #### Basic Text Binding
418
+ ```html
419
+ <!-- Display text content (default action) -->
420
+ <span data-omega-bind="auth.user.email"></span>
421
+ <span data-omega-bind="@text auth.user.displayName"></span>
422
+ ```
423
+
424
+ #### Input/Textarea Value Binding
425
+ ```html
426
+ <input data-omega-bind="@value settings.email" />
427
+ <textarea data-omega-bind="@value user.bio"></textarea>
428
+ ```
429
+
430
+ #### Conditional Visibility
431
+ ```html
432
+ <!-- Show when truthy -->
433
+ <div data-omega-bind="@show auth.user">Welcome back!</div>
434
+
435
+ <!-- Hide when truthy -->
436
+ <div data-omega-bind="@hide auth.user">Please log in</div>
437
+
438
+ <!-- Negation -->
439
+ <div data-omega-bind="@show !auth.user">Not logged in</div>
440
+
441
+ <!-- Comparisons -->
442
+ <div data-omega-bind="@show auth.account.plan === 'premium'">Premium content</div>
443
+ <div data-omega-bind="@hide settings.notifications === false">Notifications on</div>
444
+ ```
445
+
446
+ #### Attribute Binding
447
+ ```html
448
+ <img data-omega-bind="@attr src auth.user.photoURL" />
449
+ <a data-omega-bind="@attr href settings.profileUrl">Profile</a>
450
+ <input data-omega-bind="@attr disabled auth.loading" />
451
+ ```
452
+
453
+ #### Style Binding
454
+ ```html
455
+ <!-- CSS custom properties -->
456
+ <div data-omega-bind="@style --rating-width ratings.percent"></div>
457
+
458
+ <!-- Inline styles -->
459
+ <div data-omega-bind="@style background-color theme.primary"></div>
460
+ ```
461
+
462
+ #### Multiple Actions
463
+ Combine actions with commas:
464
+ ```html
465
+ <img data-omega-bind="@show auth.user, @attr src auth.user.photoURL, @attr alt auth.user.displayName" />
466
+ ```
467
+
468
+ #### JavaScript API
469
+ ```javascript
470
+ const bindings = Manager.bindings();
471
+
472
+ // Update context data
473
+ bindings.update({
474
+ settings: { theme: 'dark', email: 'user@example.com' },
475
+ custom: { value: 123 }
476
+ });
477
+
478
+ // Get current context
479
+ const context = bindings.getContext();
480
+
481
+ // Clear all bindings
482
+ bindings.clear();
483
+ ```
484
+
485
+ #### Skeleton Loaders
486
+ ```html
487
+ <!-- Shows shimmer animation until bound -->
488
+ <span data-omega-bind="auth.user.name" class="omega-binding-skeleton"></span>
489
+ ```
490
+
491
+ The skeleton automatically:
492
+ - Displays shimmer animation while loading
493
+ - Fades in smoothly when data arrives
494
+ - Adds `omega-bound` class when complete
495
+ - Respects `prefers-reduced-motion`
496
+
497
+ #### Supported Actions
498
+
499
+ | Action | Syntax | Description |
500
+ |--------|--------|-------------|
501
+ | `@text` | `@text path` | Set text content (default) |
502
+ | `@value` | `@value path` | Set input/textarea value |
503
+ | `@show` | `@show condition` | Show element if truthy |
504
+ | `@hide` | `@hide condition` | Hide element if truthy |
505
+ | `@attr` | `@attr name path` | Set attribute value |
506
+ | `@style` | `@style prop path` | Set CSS property or variable |
507
+
508
+ ### Firestore
509
+
510
+ Simplified Firestore wrapper with chainable queries:
511
+
512
+ ```javascript
513
+ const db = Manager.firestore();
514
+
515
+ // Document operations - two syntax options
516
+ await db.doc('users/user123').set({ name: 'John', age: 30 });
517
+ await db.doc('users', 'user123').update({ age: 31 });
518
+
519
+ const docSnap = await db.doc('users/user123').get();
520
+ if (docSnap.exists()) {
521
+ console.log('Data:', docSnap.data());
522
+ console.log('ID:', docSnap.id);
523
+ }
524
+
525
+ await db.doc('users/user123').delete();
526
+
527
+ // Collection queries
528
+ const snapshot = await db.collection('users').get();
529
+ console.log('Count:', snapshot.size);
530
+ console.log('Empty:', snapshot.empty);
531
+ snapshot.docs.forEach(doc => {
532
+ console.log(doc.id, doc.data());
533
+ });
534
+
535
+ // Query with filters (chainable)
536
+ const results = await db.collection('users')
537
+ .where('age', '>=', 18)
538
+ .where('active', '==', true)
539
+ .orderBy('createdAt', 'desc')
540
+ .limit(20)
541
+ .get();
542
+
543
+ // Pagination
544
+ const page2 = await db.collection('users')
545
+ .orderBy('name')
546
+ .startAt('M')
547
+ .endAt('N')
548
+ .get();
549
+ ```
550
+
551
+ **Where Operators**: `<`, `<=`, `==`, `!=`, `>=`, `>`, `array-contains`, `in`, `array-contains-any`, `not-in`
552
+
553
+ ### Push Notifications
554
+
555
+ Firebase Cloud Messaging integration:
556
+
557
+ ```javascript
558
+ const notifications = Manager.notifications();
559
+
560
+ // Check support
561
+ if (notifications.isSupported()) {
562
+ console.log('Push notifications available');
563
+ }
564
+
565
+ // Check subscription status
566
+ const isSubscribed = await notifications.isSubscribed();
567
+
568
+ // Subscribe
569
+ try {
570
+ const result = await notifications.subscribe({
571
+ vapidKey: 'your-vapid-key' // Optional
572
+ });
573
+ console.log('Token:', result.token);
574
+ } catch (error) {
575
+ if (error.message.includes('permission')) {
576
+ console.log('User denied permission');
577
+ }
578
+ }
579
+
580
+ // Unsubscribe
581
+ await notifications.unsubscribe();
582
+
583
+ // Get current token
584
+ const token = await notifications.getToken();
585
+
586
+ // Listen for foreground messages
587
+ const unsubscribe = await notifications.onMessage((payload) => {
588
+ console.log('Received:', payload);
589
+ });
590
+
591
+ // Sync subscription with auth state
592
+ await notifications.syncSubscription();
593
+ ```
594
+
595
+ **Features**:
596
+ - Stores subscription in localStorage and Firestore
597
+ - Tracks device context (platform, runtime, device)
598
+ - Auto-requests after configurable delay post-click
599
+ - Syncs with user authentication state
600
+
601
+ ### Service Worker
602
+
603
+ Service worker registration and messaging:
604
+
605
+ ```javascript
606
+ const sw = Manager.serviceWorker();
607
+
608
+ // Check support
609
+ if (sw.isSupported()) {
610
+ console.log('Service workers available');
611
+ }
612
+
613
+ // Register (done automatically during init if enabled)
614
+ const registration = await sw.register({
615
+ path: '/service-worker.js',
616
+ scope: '/'
617
+ });
618
+
619
+ // Wait for ready state
620
+ await sw.ready();
621
+
622
+ // Get registration
623
+ const reg = sw.getRegistration();
624
+
625
+ // Post message with response
626
+ try {
627
+ const response = await sw.postMessage({
628
+ command: 'cache-clear',
629
+ payload: { pattern: '*.js' }
630
+ }, { timeout: 5000 });
631
+ console.log('Response:', response);
632
+ } catch (error) {
633
+ console.error('Timeout or error:', error);
634
+ }
635
+
636
+ // Listen for messages from service worker
637
+ const unsubscribe = sw.onMessage('notification-click', (data, event) => {
638
+ console.log('Clicked:', data);
639
+ });
640
+
641
+ // Get current state
642
+ const state = sw.getState(); // 'none', 'installing', 'waiting', 'active', 'unknown'
643
+ ```
644
+
645
+ ### Sentry Error Tracking
646
+
647
+ Automatic error tracking with Sentry:
648
+
649
+ ```javascript
650
+ const sentry = Manager.sentry();
651
+
652
+ // Capture an exception
653
+ try {
654
+ throw new Error('Something went wrong');
655
+ } catch (error) {
656
+ sentry.captureException(error, {
657
+ tags: { feature: 'checkout' },
658
+ extra: { orderId: '12345' }
659
+ });
660
+ }
661
+ ```
662
+
663
+ **Automatic Features**:
664
+ - Environment and release tracking from config
665
+ - User context from auth state (uid, email)
666
+ - Session duration tracking
667
+ - Filters out Lighthouse and automated browsers (Selenium, Puppeteer)
668
+ - Blocks sending in development mode
669
+ - Dynamic import to reduce bundle size
670
+
671
+ ### DOM Utilities
672
+
673
+ ```javascript
674
+ import { loadScript, ready } from '@omega.js/client/modules/dom';
675
+ // Or: const { loadScript, ready } = Manager.dom();
676
+
677
+ // Wait for DOM ready
678
+ await ready();
679
+
680
+ // Load external script
681
+ await loadScript({
682
+ src: 'https://example.com/script.js',
683
+ async: true,
684
+ defer: false,
685
+ crossorigin: 'anonymous',
686
+ integrity: 'sha384-...',
687
+ timeout: 30000,
688
+ retries: 2,
689
+ parent: document.head,
690
+ attributes: { 'data-custom': 'value' }
691
+ });
692
+
693
+ // Simple string syntax
694
+ await loadScript('https://example.com/script.js');
695
+ ```
696
+
697
+ **loadScript Options**:
698
+
699
+ | Option | Type | Default | Description |
700
+ |--------|------|---------|-------------|
701
+ | `src` | string | required | Script URL |
702
+ | `async` | boolean | `true` | Load asynchronously |
703
+ | `defer` | boolean | `false` | Defer execution |
704
+ | `crossorigin` | boolean/string | `false` | CORS setting |
705
+ | `integrity` | string | `null` | SRI hash |
706
+ | `timeout` | number | `60000` | Timeout in ms |
707
+ | `retries` | number | `0` | Retry attempts |
708
+ | `parent` | Element | `document.head` | Parent element |
709
+ | `attributes` | object | `{}` | Custom attributes |
710
+
711
+ ### Utility Functions
712
+
713
+ ```javascript
714
+ import {
715
+ clipboardCopy,
716
+ escapeHTML,
717
+ sanitizeURL,
718
+ renderMarkdown,
719
+ showNotification,
720
+ getPlatform,
721
+ getBrowser,
722
+ getRuntime,
723
+ isMobile,
724
+ getDevice,
725
+ getContext
726
+ } from '@omega.js/client/modules/utilities';
727
+ // Or: const utils = Manager.utilities();
728
+
729
+ // Copy to clipboard (rejects when the clipboard refuses — catch it)
730
+ await clipboardCopy('Text to copy');
731
+ await clipboardCopy(document.querySelector('#input')); // From element
732
+
733
+ // Escape HTML (XSS prevention)
734
+ const safe = escapeHTML('<script>alert("xss")</script>');
735
+ // '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
736
+
737
+ // Sanitize a URL (returns '' for javascript:, data:, and every non-http(s) scheme)
738
+ sanitizeURL('https://example.com/a'); // 'https://example.com/a'
739
+ sanitizeURL('javascript:alert(1)'); // ''
740
+
741
+ // Render untrusted text as safe markup — escape-first mini-markdown
742
+ // (headings, fenced + inline code, lists, bold/italic, http(s) links only)
743
+ renderMarkdown('## Spec\n\nOne **bold** and `code`.');
744
+ // '<h5 class="h6 mt-3 mb-2">Spec</h5><p>One <strong>bold</strong> and <code>code</code>.</p>'
745
+
746
+ // Show notification (Bootstrap-styled)
747
+ showNotification('Success!', { type: 'success', timeout: 5000 });
748
+ showNotification('Error!', 'danger');
749
+ showNotification(new Error('Failed'), { timeout: 0 }); // No auto-dismiss
750
+
751
+ // Platform detection
752
+ getPlatform(); // 'windows', 'mac', 'linux', 'ios', 'android', 'chromeos', 'unknown'
753
+
754
+ // Browser detection
755
+ getBrowser(); // 'chrome', 'firefox', 'safari', 'edge', 'opera', 'brave', null
756
+
757
+ // Runtime detection
758
+ getRuntime(); // 'web', 'browser-extension'
759
+
760
+ // Device detection
761
+ isMobile(); // true/false
762
+ getDevice(); // 'mobile' (<768px), 'tablet' (768-1199px), 'desktop' (>=1200px)
763
+
764
+ // Full context
765
+ getContext();
766
+ // {
767
+ // client: { language, mobile, device, platform, browser, vendor, runtime, userAgent, url },
768
+ // geolocation: { ip, country, region, city, latitude, longitude }
769
+ // }
770
+ ```
771
+
772
+ **showNotification Options**:
773
+
774
+ | Option | Type | Default | Description |
775
+ |--------|------|---------|-------------|
776
+ | `type` | string | `'info'` | `'info'`, `'success'`, `'warning'`, `'danger'` |
777
+ | `timeout` | number | `5000` | Auto-dismiss after ms (0 = never) |
778
+
779
+ ## HTML Data Attributes
780
+
781
+ Web Manager automatically sets these attributes on the `<html>` element during initialization:
782
+
783
+ ```html
784
+ <html data-platform="mac" data-browser="chrome" data-runtime="web" data-device="desktop">
785
+ ```
786
+
787
+ | Attribute | Values | Description |
788
+ |-----------|--------|-------------|
789
+ | `data-platform` | `windows`, `mac`, `linux`, `ios`, `android`, `chromeos`, `unknown` | Operating system |
790
+ | `data-browser` | `chrome`, `firefox`, `safari`, `edge`, `opera`, `brave` | Browser name |
791
+ | `data-runtime` | `web`, `browser-extension` | Runtime environment |
792
+ | `data-device` | `mobile`, `tablet`, `desktop` | Device type by screen width |
793
+
794
+ **CSS Usage**:
795
+ ```css
796
+ /* Platform-specific styles */
797
+ [data-platform="ios"] .download-btn { display: none; }
798
+ [data-platform="windows"] .app-icon { content: url('windows-icon.png'); }
799
+
800
+ /* Browser-specific styles */
801
+ [data-browser="safari"] .webkit-fix { -webkit-transform: translateZ(0); }
802
+ [data-browser="firefox"] .gecko-fix { overflow: hidden; }
803
+
804
+ /* Device-responsive styles */
805
+ [data-device="mobile"] .sidebar { display: none; }
806
+ [data-device="desktop"] .mobile-menu { display: none; }
807
+ ```
808
+
809
+ ## Direct Module Imports
810
+
811
+ Import individual modules to reduce bundle size:
812
+
813
+ ```javascript
814
+ // Storage only
815
+ import Storage from '@omega.js/client/modules/storage';
816
+ const storage = new Storage();
817
+
818
+ // Utilities only
819
+ import { clipboardCopy, escapeHTML } from '@omega.js/client/modules/utilities';
820
+
821
+ // DOM utilities only
822
+ import { loadScript, ready } from '@omega.js/client/modules/dom';
823
+
824
+ // Full manager (default)
825
+ import Manager from '@omega.js/client';
826
+ ```
827
+
828
+ **Available Modules**:
829
+ - `@omega.js/client/modules/storage` - Storage class
830
+ - `@omega.js/client/modules/utilities` - Utility functions
831
+ - `@omega.js/client/modules/dom` - DOM utilities
832
+ - `@omega.js/client/modules/auth` - Auth class (requires Manager)
833
+ - `@omega.js/client/modules/bindings` - Bindings class (requires Manager)
834
+ - `@omega.js/client/modules/firestore` - Firestore class (requires Manager)
835
+ - `@omega.js/client/modules/notifications` - Notifications class (requires Manager)
836
+ - `@omega.js/client/modules/service-worker` - ServiceWorker class (requires Manager)
837
+ - `@omega.js/client/modules/sentry` - Sentry class (requires Manager)
838
+
839
+ ## Browser Support
840
+
841
+ Web Manager is transpiled to ES5 for broad browser support:
842
+
843
+ | Browser | Version | Support |
844
+ |---------|---------|---------|
845
+ | Chrome | 60+ | Full |
846
+ | Firefox | 55+ | Full |
847
+ | Safari | 11+ | Full |
848
+ | Edge | 79+ | Full |
849
+ | IE | 11 | Not supported |
850
+
851
+ **Notes**:
852
+ - Service Workers require HTTPS (except localhost)
853
+ - Push Notifications require Service Worker support
854
+ - Some features use modern APIs with fallbacks
855
+
856
+ ## Projects Using This Library
857
+
858
+ - [Somiibo](https://somiibo.com/): A Social Media Bot with an open-source module library
859
+ - [JekyllUp](https://jekyllup.com/): A website devoted to sharing the best Jekyll themes
860
+ - [Slapform](https://slapform.com/): A backend provider for HTML forms on static sites
861
+ - [SoundGrail Music App](https://app.soundgrail.com/): A resource for producers, musicians, and DJs
862
+ - [Hammock Report](https://hammockreport.com/): An API for exploring and listing backyard products
863
+
864
+ *Want your project listed? [Open an issue](https://github.com/itw-creative-works/web-manager/issues)!*
865
+
866
+ ## Support
867
+
868
+ If you're having issues or have questions:
869
+ - [Open an issue](https://github.com/itw-creative-works/web-manager/issues) on GitHub
870
+ - Include code samples and relevant files to help us help you faster
871
+
872
+ ## License
873
+
874
+ [Elastic License 2.0](LICENSE). The source is free to use and modify; a license key unlocks payments in production deploys and removes the attribution (local dev and test payments are always free); and you may not offer `@omega.js/client` to third parties as a hosted or managed service.