web-manager 4.1.42 → 4.3.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.
- package/CHANGELOG.md +12 -0
- package/CLAUDE.md +52 -271
- package/dist/index.js +3 -26
- package/dist/modules/auth.js +11 -0
- package/dist/modules/notifications.js +82 -9
- package/dist/modules/utilities.js +1 -1
- package/docs/architecture.md +52 -0
- package/docs/build-system.md +32 -0
- package/docs/code-patterns.md +93 -0
- package/docs/common-tasks.md +36 -0
- package/docs/dependencies.md +19 -0
- package/docs/modules.md +80 -0
- package/docs/testing.md +9 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
14
14
|
- `Fixed` for any bug fixes.
|
|
15
15
|
- `Security` in case of vulnerabilities.
|
|
16
16
|
|
|
17
|
+
---
|
|
18
|
+
## [4.2.0] - 2026-05-21
|
|
19
|
+
### Added
|
|
20
|
+
- **Consent defaults in `DEFAULT_ACCOUNT`.** Phase A companion to `backend-manager` v5.2.0's marketing-consent system. `src/modules/auth.js` now defaults `account.consent.{legal,marketing}` to the canonical shape (`status: 'revoked'`, `grantedAt`/`revokedAt` with null leaves) so `resolveAccount()` always populates the field for legacy users whose Firestore doc predates the consent system. Without this, the account-page email-preferences toggle would read `undefined` for pre-migration users and mis-state.
|
|
21
|
+
- **`docs/<topic>.md` deep references.** New `docs/architecture.md`, `docs/build-system.md`, `docs/code-patterns.md`, `docs/common-tasks.md`, `docs/dependencies.md`, `docs/modules.md`, `docs/testing.md`. Mirrors the architectural-overview-plus-deep-references pattern used in `backend-manager` and `electron-manager`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **`CLAUDE.md` refactored to the architectural-overview pattern.** Trimmed from 301 lines to ~80; per-subsystem detail moved to the new `docs/*.md` files. Keeps Claude's loaded-context cost down on every conversation.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Toast notification width.** `src/modules/utilities.js`: notifications now have `width: calc(100% - 2rem); max-width: 500px` so longer text doesn't render as a narrow cramped strip at the top of the page.
|
|
28
|
+
|
|
17
29
|
---
|
|
18
30
|
## [4.1.42] - 2026-05-11
|
|
19
31
|
### Changed
|
package/CLAUDE.md
CHANGED
|
@@ -1,301 +1,82 @@
|
|
|
1
|
-
# Web Manager
|
|
1
|
+
# Web Manager
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
> **Note for contributors and Claude:** This file is the architectural overview — identity, top-level conventions, and a map to deep references. The **meat** (module APIs, patterns, behavior tables) lives in `docs/<topic>.md`. When extending or adding content, write it in the matching `docs/*.md` file and cross-link from here — do NOT inline it. If a topic doesn't have a doc yet, create one. Goal: keep this file under 250 lines.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Identity
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Web Manager is a modern JavaScript utility library for web applications with Firebase integration. It runs in the browser, in Electron's renderer process, and inside browser extensions (content scripts, popups, background pages). Provides:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
9
|
+
- A singleton `Manager` instance exposing authentication, reactive DOM data binding, Firestore, storage, push notifications, error tracking (Sentry), service-worker helpers, and DOM/utility functions
|
|
10
|
+
- Lazy Firebase imports to keep consumer bundles small
|
|
11
|
+
- Reactive `data-wm-bind` DOM directives wired to auth + usage state
|
|
12
|
+
- A `resolveSubscription()` helper unified with backend-manager's `User.resolveSubscription()` so subscription-state logic is identical across frontend and backend
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
### Consumed by the frontend Manager family
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
### Singleton Pattern
|
|
19
|
-
The library exports a singleton `Manager` instance. Import it directly from any file — it's always the same initialized instance:
|
|
20
|
-
```javascript
|
|
21
|
-
import webManager from 'web-manager';
|
|
22
|
-
|
|
23
|
-
// Same instance everywhere — config, auth, firestore, all ready
|
|
24
|
-
webManager.auth().listen((state) => { ... });
|
|
25
|
-
webManager.utilities().escapeHTML(untrustedText);
|
|
26
|
-
webManager.config.environment; // 'development' or 'production'
|
|
27
|
-
```
|
|
28
|
-
**Do NOT create new instances** (`new Manager()`). UJM and BXM initialize the singleton — every import gets that same object. Do NOT pass `webManager` through function params or store it in module-level variables — just import it.
|
|
29
|
-
|
|
30
|
-
### Directory Structure
|
|
31
|
-
```
|
|
32
|
-
web-manager/
|
|
33
|
-
├── src/ # Source code (ES6+)
|
|
34
|
-
│ ├── index.js # Manager class, initialization, Firebase setup
|
|
35
|
-
│ └── modules/ # Feature modules
|
|
36
|
-
│ ├── auth.js # Firebase Auth wrapper
|
|
37
|
-
│ ├── bindings.js # Reactive DOM data binding
|
|
38
|
-
│ ├── dom.js # loadScript, ready utilities
|
|
39
|
-
│ ├── firestore.js # Firestore wrapper with chainable queries
|
|
40
|
-
│ ├── notifications.js # FCM push notifications
|
|
41
|
-
│ ├── sentry.js # Error tracking integration
|
|
42
|
-
│ ├── service-worker.js # SW registration and messaging
|
|
43
|
-
│ ├── storage.js # localStorage/sessionStorage wrapper
|
|
44
|
-
│ └── utilities.js # Helper functions (clipboard, escape, etc.)
|
|
45
|
-
├── dist/ # Transpiled ES5 output (generated)
|
|
46
|
-
├── _legacy/ # Old implementation (reference only, DO NOT MODIFY)
|
|
47
|
-
└── test/ # Mocha tests
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Module Dependencies
|
|
51
|
-
```
|
|
52
|
-
Manager (index.js)
|
|
53
|
-
├── Storage (standalone, no deps)
|
|
54
|
-
├── Auth → Manager, Bindings, Storage, Firestore
|
|
55
|
-
├── Bindings → Manager
|
|
56
|
-
├── Firestore → Manager (lazy Firebase import)
|
|
57
|
-
├── Notifications → Manager, Storage, Firestore
|
|
58
|
-
├── ServiceWorker → Manager
|
|
59
|
-
├── Sentry → Manager (dynamic import)
|
|
60
|
-
├── DOM utilities (standalone)
|
|
61
|
-
└── Utilities (standalone)
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Key Patterns
|
|
16
|
+
Web Manager is the runtime singleton powering **Ultimate Jekyll Manager (UJM)**, **Browser Extension Manager (BXM)**, and **Electron Manager (EM)**. Each framework initializes the singleton once and exposes it as `manager.webManager`. Any consumer of those frameworks gets a fully-wired web-manager via `import webManager from 'web-manager'`.
|
|
65
17
|
|
|
66
|
-
|
|
67
|
-
Always use early returns instead of nested conditionals:
|
|
68
|
-
```javascript
|
|
69
|
-
// CORRECT
|
|
70
|
-
function doSomething() {
|
|
71
|
-
if (!condition) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// Long code block...
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// WRONG
|
|
78
|
-
function doSomething() {
|
|
79
|
-
if (condition) {
|
|
80
|
-
// Long code block...
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### 2. DOM Element Naming
|
|
86
|
-
Prefix DOM element variables with `$`:
|
|
87
|
-
```javascript
|
|
88
|
-
const $button = document.querySelector('.submit-btn');
|
|
89
|
-
const $input = document.getElementById('email');
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### 3. Logical Operator Formatting
|
|
93
|
-
Place operators at the START of continuation lines:
|
|
94
|
-
```javascript
|
|
95
|
-
// CORRECT
|
|
96
|
-
const result = conditionA
|
|
97
|
-
|| conditionB
|
|
98
|
-
|| conditionC;
|
|
18
|
+
## Recommended skills
|
|
99
19
|
|
|
100
|
-
|
|
101
|
-
const result = conditionA ||
|
|
102
|
-
conditionB ||
|
|
103
|
-
conditionC;
|
|
104
|
-
```
|
|
20
|
+
- **`js:patterns`** — JavaScript/Node.js conventions: file structure, JSDoc, defensive coding (`?.` usage), template literals, `package.json` conventions. Auto-loads when creating new `.js` files or touching JS module structure.
|
|
105
21
|
|
|
106
|
-
|
|
107
|
-
Prefer path syntax over collection/doc chaining:
|
|
108
|
-
```javascript
|
|
109
|
-
// PREFERRED
|
|
110
|
-
db.doc('users/userId')
|
|
22
|
+
## Quick Start
|
|
111
23
|
|
|
112
|
-
|
|
113
|
-
db.doc('users', 'userId')
|
|
114
|
-
```
|
|
24
|
+
### For Consuming Projects
|
|
115
25
|
|
|
116
|
-
|
|
117
|
-
Firebase modules are dynamically imported to reduce bundle size:
|
|
118
|
-
```javascript
|
|
119
|
-
const { initializeApp } = await import('firebase/app');
|
|
120
|
-
const { getAuth } = await import('firebase/auth');
|
|
121
|
-
```
|
|
26
|
+
Web Manager is consumed indirectly through UJM, BXM, or EM — those frameworks initialize the singleton for you. Inside any consuming code:
|
|
122
27
|
|
|
123
|
-
### 6. Configuration Deep Merge
|
|
124
|
-
User config is deep-merged with defaults in `_processConfiguration()`. Only override what you need:
|
|
125
28
|
```javascript
|
|
126
|
-
|
|
127
|
-
const defaults = {
|
|
128
|
-
environment: 'production',
|
|
129
|
-
firebase: { app: { enabled: true, config: {} } },
|
|
130
|
-
// ...
|
|
131
|
-
};
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### 7. Event Delegation
|
|
135
|
-
Auth UI uses event delegation on document body:
|
|
136
|
-
```javascript
|
|
137
|
-
document.body.addEventListener('click', (e) => {
|
|
138
|
-
if (e.target.closest('.auth-signout-btn')) {
|
|
139
|
-
// Handle signout
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Module Quick Reference
|
|
145
|
-
|
|
146
|
-
### Storage (`storage.js`)
|
|
147
|
-
- **Class**: `Storage`
|
|
148
|
-
- **Key Methods**: `get(path, default)`, `set(path, value)`, `remove(path)`, `clear()`
|
|
149
|
-
- **Session**: Same methods under `.session` namespace
|
|
150
|
-
- **Storage Key**: `_manager` in localStorage
|
|
151
|
-
|
|
152
|
-
### Auth (`auth.js`)
|
|
153
|
-
- **Class**: `Auth`
|
|
154
|
-
- **Key Methods**: `listen(options, callback)`, `isAuthenticated()`, `getUser()`, `signInWithEmailAndPassword()`, `signOut()`, `getIdToken()`, `resolveSubscription(account?)`
|
|
155
|
-
- **Bindings**: Updates `auth` and `usage` context on auth settle
|
|
156
|
-
- **Usage Resolution**: `_resolveUsage(state)` merges `account.usage` (Firestore) with product limits from `config.payment.products` (OMEGA-canonical shape — same key name in BEM, UJM, and EM) to produce the `usage` bindings key (e.g., `{ credits: { monthly: 5, limit: 100 } }`)
|
|
157
|
-
|
|
158
|
-
#### resolveSubscription(account?)
|
|
159
|
-
Derives calculated subscription fields from raw account data. Returns only fields that require derivation logic — raw data (product.id, status, trial, cancellation) lives on `account.subscription` directly.
|
|
160
|
-
|
|
161
|
-
```javascript
|
|
162
|
-
const resolved = auth.resolveSubscription(account);
|
|
163
|
-
// Returns: { plan, active, trialing, cancelling }
|
|
164
|
-
```
|
|
165
|
-
- `plan`: Effective plan ID the user has access to RIGHT NOW (`'basic'` if cancelled/suspended)
|
|
166
|
-
- `active`: User has active access (active, trialing, or cancelling — all mean the user can use the product)
|
|
167
|
-
- `trialing`: In an active trial (status `'active'` + `trial.claimed` + unexpired `trial.expires`)
|
|
168
|
-
- `cancelling`: Cancellation pending (status `'active'` + `cancellation.pending` + NOT trialing)
|
|
169
|
-
|
|
170
|
-
**Unified with BEM**: The same function exists on `User.resolveSubscription(account)` in backend-manager (`helpers/user.js`) with identical logic and return shape.
|
|
171
|
-
|
|
172
|
-
#### Auth Settler Pattern
|
|
173
|
-
Auth uses a promise-based settler (`_authReady`) that resolves once Firebase's first `onAuthStateChanged` fires — the moment auth state is guaranteed (authenticated user OR null). This eliminates race conditions.
|
|
174
|
-
|
|
175
|
-
- **`once` listeners** (`listen({ once: true }, cb)`): Wait for `_authReady`, fire once, done. No cleanup needed.
|
|
176
|
-
- **Persistent listeners** (`listen({}, cb)`): Subscribe to `_authStateCallbacks`. If auth already settled when registered, catch up via `_authReady.then()`. Otherwise, `_handleAuthStateChange` handles the initial call naturally.
|
|
177
|
-
- **`_hasProcessedStateChange`**: Ensures bindings/storage updates run only once per auth state change across all listeners.
|
|
178
|
-
- **Manager owns the promise**: `_authReady` and `_authReadyResolve` live on the Manager instance. The `onAuthStateChanged` callback in `index.js` resolves it on first fire and sets `_firebaseAuthInitialized = true`.
|
|
179
|
-
|
|
180
|
-
### Bindings (`bindings.js`)
|
|
181
|
-
- **Class**: `Bindings`
|
|
182
|
-
- **Key Methods**: `update(data)`, `getContext()`, `clear()`
|
|
183
|
-
- **HTML Attr**: `data-wm-bind`
|
|
184
|
-
- **Actions**: `@text`, `@value`, `@show`, `@hide`, `@attr`, `@style`
|
|
185
|
-
|
|
186
|
-
### Firestore (`firestore.js`)
|
|
187
|
-
- **Class**: `Firestore`
|
|
188
|
-
- **Key Methods**: `doc(path)`, `collection(path)`
|
|
189
|
-
- **Doc Methods**: `.get()`, `.set()`, `.update()`, `.delete()`
|
|
190
|
-
- **Query Methods**: `.where()`, `.orderBy()`, `.limit()`, `.startAt()`, `.endAt()`
|
|
191
|
-
|
|
192
|
-
### Notifications (`notifications.js`)
|
|
193
|
-
- **Class**: `Notifications`
|
|
194
|
-
- **Key Methods**: `isSupported()`, `isSubscribed()`, `subscribe()`, `unsubscribe()`, `getToken()`, `onMessage()`
|
|
195
|
-
- **Storage**: Saves to localStorage and Firestore
|
|
196
|
-
|
|
197
|
-
### ServiceWorker (`service-worker.js`)
|
|
198
|
-
- **Class**: `ServiceWorker`
|
|
199
|
-
- **Key Methods**: `isSupported()`, `register()`, `ready()`, `postMessage()`, `onMessage()`, `getState()`
|
|
200
|
-
|
|
201
|
-
### Sentry (`sentry.js`)
|
|
202
|
-
- **Class**: `Sentry` (named `mod` internally)
|
|
203
|
-
- **Key Methods**: `init(config)`, `captureException(error, context)`
|
|
204
|
-
- **Filtering**: Blocks dev mode, Lighthouse, Selenium/Puppeteer
|
|
205
|
-
|
|
206
|
-
### DOM (`dom.js`)
|
|
207
|
-
- **Exports**: `loadScript(options)`, `ready()`
|
|
208
|
-
- **loadScript Options**: src, async, defer, crossorigin, integrity, timeout, retries
|
|
209
|
-
|
|
210
|
-
### Utilities (`utilities.js`)
|
|
211
|
-
- **Exports**: `clipboardCopy()`, `escapeHTML()`, `showNotification()`, `getPlatform()`, `getBrowser()`, `getRuntime()`, `isMobile()`, `getDevice()`, `getContext()`
|
|
212
|
-
|
|
213
|
-
## Build System
|
|
214
|
-
|
|
215
|
-
### prepare-package
|
|
216
|
-
The library uses `prepare-package` for ES5 transpilation:
|
|
29
|
+
import webManager from 'web-manager';
|
|
217
30
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"input": "./src",
|
|
222
|
-
"output": "./dist"
|
|
223
|
-
}
|
|
224
|
-
}
|
|
31
|
+
webManager.auth().listen({ once: true }, async () => { /* auth settled */ });
|
|
32
|
+
webManager.utilities().escapeHTML(untrustedText);
|
|
33
|
+
webManager.firestore().doc('users/abc').get();
|
|
225
34
|
```
|
|
226
35
|
|
|
227
|
-
|
|
228
|
-
- `npm run prepare` - Build once
|
|
229
|
-
- `npm start` - Watch mode
|
|
230
|
-
- `npm test` - Run Mocha tests
|
|
36
|
+
### For Framework Development (This Repository)
|
|
231
37
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
"module": "src/index.js",
|
|
237
|
-
"exports": {
|
|
238
|
-
".": "./dist/index.js",
|
|
239
|
-
"./modules/*": "./dist/modules/*"
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
```
|
|
38
|
+
1. `npm install` — install Web Manager's own deps
|
|
39
|
+
2. `npm run prepare` — build once: copies `src/` → `dist/` via prepare-package (ES5 transpile)
|
|
40
|
+
3. `npm start` — watch mode (rebuild on change)
|
|
41
|
+
4. `npm test` — run Mocha tests
|
|
243
42
|
|
|
244
|
-
|
|
43
|
+
> **Important:** Web Manager is a library, not an app. There is no `npm run build` / `npm run serve` here. Consume it from inside a UJM / BXM / EM project for end-to-end behavior.
|
|
245
44
|
|
|
246
|
-
|
|
247
|
-
```bash
|
|
248
|
-
npm test
|
|
249
|
-
```
|
|
45
|
+
## Architecture
|
|
250
46
|
|
|
251
|
-
|
|
47
|
+
Web Manager exports a singleton `Manager` instance from `src/index.js`. Every `import webManager from 'web-manager'` returns the same already-initialized object — do NOT call `new Manager()`, and do NOT pass `webManager` through function params or module-level variables.
|
|
252
48
|
|
|
253
|
-
|
|
49
|
+
The singleton owns nine feature modules under `src/modules/`: `storage`, `auth`, `bindings`, `firestore`, `notifications`, `service-worker`, `sentry`, `dom`, `utilities`. Firebase modules are dynamically imported to keep the bundle small. See [docs/architecture.md](docs/architecture.md) for the directory structure and module dependency graph, and [docs/modules.md](docs/modules.md) for the API reference of each module.
|
|
254
50
|
|
|
255
|
-
|
|
256
|
-
1. Add function to `src/modules/utilities.js`
|
|
257
|
-
2. Export it: `export function myFunction() { ... }`
|
|
258
|
-
3. Update README.md with documentation
|
|
259
|
-
4. Run `npm run prepare` to build
|
|
51
|
+
## File Conventions
|
|
260
52
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
7. Run `npm run prepare`
|
|
53
|
+
- **CommonJS-friendly ES6+** in `src/`. `prepare-package` transpiles to ES5 in `dist/`.
|
|
54
|
+
- **`fs-jetpack`** over `fs` / `fs-extra` for any file operations in tests/scripts.
|
|
55
|
+
- **No TypeScript** — pure JavaScript library.
|
|
56
|
+
- **Template strings** — use backticks for string interpolation.
|
|
57
|
+
- **DO NOT modify `_legacy/`** — reference only, frozen for historical context.
|
|
58
|
+
- **No backwards compatibility** unless explicitly requested — just change to the new way.
|
|
59
|
+
- **Early-return / short-circuit** style throughout — see [docs/code-patterns.md](docs/code-patterns.md) for the full code-pattern checklist (`$`-prefixed DOM vars, operators at start of continuation lines, Firestore path syntax, dynamic imports, config deep-merge, event delegation).
|
|
269
60
|
|
|
270
|
-
|
|
271
|
-
1. Edit `_processConfiguration()` in `src/index.js`
|
|
272
|
-
2. Add to `defaults` object (e.g., `payment: { processors: {}, products: [] }`)
|
|
273
|
-
3. Document in README.md Configuration section
|
|
61
|
+
## Doc-update parity
|
|
274
62
|
|
|
275
|
-
|
|
276
|
-
Payment config shape mirrors OMEGA (the SSOT) — same key names used in BEM, UJM, and EM:
|
|
277
|
-
- `processors`: Stripe, PayPal, Chargebee, Coinbase (publishable keys / client IDs)
|
|
278
|
-
- `products`: Array of `{ id, name, type, limits: { feature: N }, prices, trial, paypal, stripe, chargebee }` — used to resolve usage limits on the frontend AND drive checkout flows
|
|
63
|
+
Whenever you make a behavioral change (new module, new method, new pattern, removed feature), update:
|
|
279
64
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
65
|
+
1. **`README.md`** — user-facing summary
|
|
66
|
+
2. **`CLAUDE.md`** (this file) — architecture overview, one paragraph or cross-link
|
|
67
|
+
3. **`docs/<topic>.md`** — the meat. If a topic doesn't have a doc yet, create one.
|
|
68
|
+
4. **`CHANGELOG.md`** — if the project keeps one
|
|
284
69
|
|
|
285
|
-
|
|
70
|
+
Don't ship behavioral changes with stale docs. Validate first, then document — write docs that describe shipped reality, not intentions.
|
|
286
71
|
|
|
287
|
-
|
|
288
|
-
|---------|---------|
|
|
289
|
-
| `firebase` (^12.x) | Auth, Firestore, Messaging |
|
|
290
|
-
| `@sentry/browser` (^10.x) | Error tracking |
|
|
291
|
-
| `lodash` (^4.x) | get/set for path-based access |
|
|
292
|
-
| `itwcw-package-analytics` | Analytics (internal) |
|
|
72
|
+
## Documentation
|
|
293
73
|
|
|
294
|
-
|
|
74
|
+
Deep references live in `docs/`. Treat docs as a first-class deliverable. **Whenever you make a behavioral change, update both this overview AND the relevant `docs/*.md` deep reference.**
|
|
295
75
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
76
|
+
- [docs/architecture.md](docs/architecture.md) — singleton pattern, directory structure, module dependency graph
|
|
77
|
+
- [docs/code-patterns.md](docs/code-patterns.md) — early returns, `$`-prefixed DOM vars, logical operator placement, Firestore path syntax, dynamic imports, config deep-merge, event delegation
|
|
78
|
+
- [docs/modules.md](docs/modules.md) — full module quick reference (Storage, Auth + `resolveSubscription` + Settler Pattern, Bindings, Firestore, Notifications, ServiceWorker, Sentry, DOM, Utilities)
|
|
79
|
+
- [docs/build-system.md](docs/build-system.md) — `prepare-package` ES5 transpile, build commands, package exports
|
|
80
|
+
- [docs/testing.md](docs/testing.md) — Mocha test setup
|
|
81
|
+
- [docs/common-tasks.md](docs/common-tasks.md) — adding a utility, adding a module, modifying config defaults, payment config (OMEGA SSOT shape), adding a binding action
|
|
82
|
+
- [docs/dependencies.md](docs/dependencies.md) — dependencies table + important notes (no TypeScript, prefer fs-jetpack, no backwards-compat requirement, etc.)
|
package/dist/index.js
CHANGED
|
@@ -133,9 +133,9 @@ class Manager {
|
|
|
133
133
|
// Set up auth event listeners (uses event delegation, no need to wait for DOM)
|
|
134
134
|
this._auth.setupEventListeners();
|
|
135
135
|
|
|
136
|
-
// Set up push
|
|
137
|
-
if (this.config.pushNotifications?.enabled
|
|
138
|
-
this.
|
|
136
|
+
// Set up push notifications
|
|
137
|
+
if (this.config.pushNotifications?.enabled) {
|
|
138
|
+
this._notifications.initialize(this.config.pushNotifications.config);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// Initialize Chatsy chat widget if enabled
|
|
@@ -568,29 +568,6 @@ class Manager {
|
|
|
568
568
|
}, this.config.refreshNewVersion.config.interval);
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
-
_setupNotificationAutoRequest() {
|
|
572
|
-
// Quit if document is not available
|
|
573
|
-
if (typeof document === 'undefined') {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Set up click listener to request notification permissions
|
|
578
|
-
const handleClick = () => {
|
|
579
|
-
// Remove listener after first click
|
|
580
|
-
document.removeEventListener('click', handleClick);
|
|
581
|
-
|
|
582
|
-
// Set timeout to request notifications
|
|
583
|
-
setTimeout(() => {
|
|
584
|
-
console.log('Auto-requesting notification permissions...');
|
|
585
|
-
this._notifications.subscribe().catch(err => {
|
|
586
|
-
console.error('Notification subscription failed:', err.message);
|
|
587
|
-
});
|
|
588
|
-
}, this.config.pushNotifications.config.autoRequest);
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
// Wait for user click
|
|
592
|
-
document.addEventListener('click', handleClick);
|
|
593
|
-
}
|
|
594
571
|
|
|
595
572
|
// async _loadPolyfillsIfNeeded() {
|
|
596
573
|
// // Check if polyfills are needed by testing for ES6 features
|
package/dist/modules/auth.js
CHANGED
|
@@ -35,6 +35,17 @@ const DEFAULT_ACCOUNT = {
|
|
|
35
35
|
affiliate: { code: null, timestamp: null, url: null, page: null },
|
|
36
36
|
utm: { tags: {}, timestamp: null, url: null, page: null },
|
|
37
37
|
},
|
|
38
|
+
consent: {
|
|
39
|
+
legal: {
|
|
40
|
+
status: 'revoked',
|
|
41
|
+
grantedAt: { timestamp: null, timestampUNIX: null, source: null, ip: null, text: null },
|
|
42
|
+
},
|
|
43
|
+
marketing: {
|
|
44
|
+
status: 'revoked',
|
|
45
|
+
grantedAt: { timestamp: null, timestampUNIX: null, source: null, ip: null, text: null },
|
|
46
|
+
revokedAt: { timestamp: null, timestampUNIX: null, source: null, ip: null, text: null },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
38
49
|
};
|
|
39
50
|
|
|
40
51
|
function resolveAccount(rawData, firebaseUser) {
|
|
@@ -4,6 +4,56 @@ class Notifications {
|
|
|
4
4
|
this._requestInProgress = false;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
initialize(config) {
|
|
8
|
+
const storage = this.manager.storage();
|
|
9
|
+
const stored = storage.get('notifications');
|
|
10
|
+
const permission = typeof Notification !== 'undefined' ? Notification.permission : 'default';
|
|
11
|
+
|
|
12
|
+
console.log('[WM:push] Page load check:', { storedSubscribed: stored?.subscribed, storedToken: stored?.token?.slice(-8), permission });
|
|
13
|
+
|
|
14
|
+
// If localStorage says subscribed but browser permission disagrees, clear it
|
|
15
|
+
if (stored?.subscribed && permission !== 'granted') {
|
|
16
|
+
console.log('[WM:push] Clearing stale subscription — permission is', permission);
|
|
17
|
+
storage.set('notifications', { subscribed: false, token: null });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Arm auto-request if not currently subscribed (including just-cleared)
|
|
21
|
+
const autoRequest = config?.autoRequest;
|
|
22
|
+
if ((!stored?.subscribed || permission !== 'granted') && autoRequest > 0) {
|
|
23
|
+
console.log('[WM:push] Arming auto-request (delay:', autoRequest + 'ms)');
|
|
24
|
+
this._setupAutoRequest(autoRequest);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Listen for foreground messages (tab is focused)
|
|
28
|
+
if (permission === 'granted') {
|
|
29
|
+
console.log('[WM:push] Setting up foreground listener...', { supported: this.isSupported(), hasMessaging: !!this.manager.firebaseMessaging });
|
|
30
|
+
this.onMessage((payload) => {
|
|
31
|
+
console.log('[WM:push] Foreground message received:', payload);
|
|
32
|
+
}).then(unsub => {
|
|
33
|
+
console.log('[WM:push] Foreground listener registered:', typeof unsub === 'function' ? 'OK' : 'FAILED (got empty fn)');
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_setupAutoRequest(delay) {
|
|
39
|
+
if (typeof document === 'undefined') {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleClick = () => {
|
|
44
|
+
document.removeEventListener('click', handleClick);
|
|
45
|
+
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
console.log('[WM:push] Auto-requesting notification permissions...');
|
|
48
|
+
this.subscribe().catch(err => {
|
|
49
|
+
console.error('[WM:push] Auto-subscription failed:', err.message);
|
|
50
|
+
});
|
|
51
|
+
}, delay);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
document.addEventListener('click', handleClick);
|
|
55
|
+
}
|
|
56
|
+
|
|
7
57
|
// Check if notifications are supported
|
|
8
58
|
isSupported() {
|
|
9
59
|
return 'Notification' in window &&
|
|
@@ -222,31 +272,54 @@ class Notifications {
|
|
|
222
272
|
}
|
|
223
273
|
}
|
|
224
274
|
|
|
225
|
-
// Sync subscription when auth state changes
|
|
275
|
+
// Sync subscription when auth state changes or on page load.
|
|
276
|
+
// Re-fetches the current FCM token — if it changed (browser rotated it,
|
|
277
|
+
// service worker was re-registered, etc.), saves the new token to Firestore
|
|
278
|
+
// and updates localStorage. Clears the subscribed state if the token is gone.
|
|
226
279
|
async syncSubscription() {
|
|
227
280
|
try {
|
|
228
|
-
// Check if we have a stored notification token
|
|
229
281
|
const storage = this.manager.storage();
|
|
230
282
|
const storedNotification = storage.get('notifications');
|
|
231
283
|
|
|
232
|
-
|
|
284
|
+
const permission = typeof Notification !== 'undefined' ? Notification.permission : 'default';
|
|
285
|
+
|
|
286
|
+
console.log('[WM:push:sync] Starting sync:', { storedSubscribed: storedNotification?.subscribed, storedToken: storedNotification?.token?.slice(-8), permission });
|
|
287
|
+
|
|
288
|
+
if (permission !== 'granted') {
|
|
289
|
+
if (storedNotification?.subscribed) {
|
|
290
|
+
console.log('[WM:push:sync] Permission not granted — clearing localStorage');
|
|
291
|
+
storage.set('notifications', { subscribed: false, token: null });
|
|
292
|
+
} else {
|
|
293
|
+
console.log('[WM:push:sync] Permission not granted and not subscribed — nothing to do');
|
|
294
|
+
}
|
|
233
295
|
return false;
|
|
234
296
|
}
|
|
235
297
|
|
|
236
|
-
//
|
|
237
|
-
await this.
|
|
298
|
+
// Permission is granted — check if there's a live token (covers localStorage cleared, new browser, etc.)
|
|
299
|
+
const currentToken = await this.getToken();
|
|
300
|
+
|
|
301
|
+
if (!currentToken) {
|
|
302
|
+
console.log('[WM:push:sync] Token fetch returned null — clearing localStorage');
|
|
303
|
+
storage.set('notifications', { subscribed: false, token: null });
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
console.log('[WM:push:sync] Token valid:', currentToken.slice(-8), storedNotification?.token ? (storedNotification.token.slice(-8) === currentToken.slice(-8) ? '(unchanged)' : '(CHANGED from ' + storedNotification.token.slice(-8) + ')') : '(recovered — localStorage was empty)');
|
|
308
|
+
|
|
309
|
+
await this._saveSubscription(currentToken);
|
|
238
310
|
|
|
239
|
-
// Update local storage with current user ID
|
|
240
311
|
const user = this.manager.auth().getUser();
|
|
241
312
|
storage.set('notifications', {
|
|
242
|
-
|
|
313
|
+
subscribed: true,
|
|
314
|
+
token: currentToken,
|
|
243
315
|
uid: user?.uid || null,
|
|
244
|
-
timestamp: new Date().toISOString()
|
|
316
|
+
timestamp: new Date().toISOString(),
|
|
245
317
|
});
|
|
246
318
|
|
|
319
|
+
console.log('[WM:push:sync] Sync complete — subscribed');
|
|
247
320
|
return true;
|
|
248
321
|
} catch (error) {
|
|
249
|
-
console.error('Sync
|
|
322
|
+
console.error('[WM:push:sync] Sync error:', error);
|
|
250
323
|
return false;
|
|
251
324
|
}
|
|
252
325
|
}
|
|
@@ -130,7 +130,7 @@ class Utilities {
|
|
|
130
130
|
|
|
131
131
|
const $notification = document.createElement('div');
|
|
132
132
|
$notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
|
|
133
|
-
$notification.style.cssText = 'z-index: 9999; top: 1rem; left: 50%; transform: translateX(-50%);';
|
|
133
|
+
$notification.style.cssText = 'z-index: 9999; top: 1rem; left: 50%; transform: translateX(-50%); width: calc(100% - 2rem); max-width: 500px;';
|
|
134
134
|
|
|
135
135
|
const $text = document.createElement('span');
|
|
136
136
|
$text.textContent = text;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Singleton Pattern
|
|
4
|
+
|
|
5
|
+
The library exports a singleton `Manager` instance. Import it directly from any file — it's always the same initialized instance:
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import webManager from 'web-manager';
|
|
9
|
+
|
|
10
|
+
// Same instance everywhere — config, auth, firestore, all ready
|
|
11
|
+
webManager.auth().listen((state) => { ... });
|
|
12
|
+
webManager.utilities().escapeHTML(untrustedText);
|
|
13
|
+
webManager.config.environment; // 'development' or 'production'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Do NOT create new instances** (`new Manager()`). UJM and BXM initialize the singleton — every import gets that same object. Do NOT pass `webManager` through function params or store it in module-level variables — just import it.
|
|
17
|
+
|
|
18
|
+
## Directory Structure
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
web-manager/
|
|
22
|
+
├── src/ # Source code (ES6+)
|
|
23
|
+
│ ├── index.js # Manager class, initialization, Firebase setup
|
|
24
|
+
│ └── modules/ # Feature modules
|
|
25
|
+
│ ├── auth.js # Firebase Auth wrapper
|
|
26
|
+
│ ├── bindings.js # Reactive DOM data binding
|
|
27
|
+
│ ├── dom.js # loadScript, ready utilities
|
|
28
|
+
│ ├── firestore.js # Firestore wrapper with chainable queries
|
|
29
|
+
│ ├── notifications.js # FCM push notifications
|
|
30
|
+
│ ├── sentry.js # Error tracking integration
|
|
31
|
+
│ ├── service-worker.js # SW registration and messaging
|
|
32
|
+
│ ├── storage.js # localStorage/sessionStorage wrapper
|
|
33
|
+
│ └── utilities.js # Helper functions (clipboard, escape, etc.)
|
|
34
|
+
├── dist/ # Transpiled ES5 output (generated)
|
|
35
|
+
├── _legacy/ # Old implementation (reference only, DO NOT MODIFY)
|
|
36
|
+
└── test/ # Mocha tests
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Module Dependencies
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Manager (index.js)
|
|
43
|
+
├── Storage (standalone, no deps)
|
|
44
|
+
├── Auth → Manager, Bindings, Storage, Firestore
|
|
45
|
+
├── Bindings → Manager
|
|
46
|
+
├── Firestore → Manager (lazy Firebase import)
|
|
47
|
+
├── Notifications → Manager, Storage, Firestore
|
|
48
|
+
├── ServiceWorker → Manager
|
|
49
|
+
├── Sentry → Manager (dynamic import)
|
|
50
|
+
├── DOM utilities (standalone)
|
|
51
|
+
└── Utilities (standalone)
|
|
52
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Build System
|
|
2
|
+
|
|
3
|
+
## prepare-package
|
|
4
|
+
|
|
5
|
+
The library uses `prepare-package` for ES5 transpilation:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"preparePackage": {
|
|
10
|
+
"input": "./src",
|
|
11
|
+
"output": "./dist"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Commands**:
|
|
17
|
+
- `npm run prepare` — Build once
|
|
18
|
+
- `npm start` — Watch mode
|
|
19
|
+
- `npm test` — Run Mocha tests
|
|
20
|
+
|
|
21
|
+
## Package Exports
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"module": "src/index.js",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./dist/index.js",
|
|
29
|
+
"./modules/*": "./dist/modules/*"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Key Patterns
|
|
2
|
+
|
|
3
|
+
## 1. Early Return (Short-Circuit)
|
|
4
|
+
|
|
5
|
+
Always use early returns instead of nested conditionals:
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// CORRECT
|
|
9
|
+
function doSomething() {
|
|
10
|
+
if (!condition) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// Long code block...
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// WRONG
|
|
17
|
+
function doSomething() {
|
|
18
|
+
if (condition) {
|
|
19
|
+
// Long code block...
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 2. DOM Element Naming
|
|
25
|
+
|
|
26
|
+
Prefix DOM element variables with `$`:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
const $button = document.querySelector('.submit-btn');
|
|
30
|
+
const $input = document.getElementById('email');
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 3. Logical Operator Formatting
|
|
34
|
+
|
|
35
|
+
Place operators at the START of continuation lines:
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// CORRECT
|
|
39
|
+
const result = conditionA
|
|
40
|
+
|| conditionB
|
|
41
|
+
|| conditionC;
|
|
42
|
+
|
|
43
|
+
// WRONG
|
|
44
|
+
const result = conditionA ||
|
|
45
|
+
conditionB ||
|
|
46
|
+
conditionC;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 4. Firestore Path Syntax
|
|
50
|
+
|
|
51
|
+
Prefer path syntax over collection/doc chaining:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
// PREFERRED
|
|
55
|
+
db.doc('users/userId')
|
|
56
|
+
|
|
57
|
+
// ALSO SUPPORTED
|
|
58
|
+
db.doc('users', 'userId')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 5. Dynamic Imports
|
|
62
|
+
|
|
63
|
+
Firebase modules are dynamically imported to reduce bundle size:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const { initializeApp } = await import('firebase/app');
|
|
67
|
+
const { getAuth } = await import('firebase/auth');
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 6. Configuration Deep Merge
|
|
71
|
+
|
|
72
|
+
User config is deep-merged with defaults in `_processConfiguration()`. Only override what you need:
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
// Defaults defined in _processConfiguration()
|
|
76
|
+
const defaults = {
|
|
77
|
+
environment: 'production',
|
|
78
|
+
firebase: { app: { enabled: true, config: {} } },
|
|
79
|
+
// ...
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 7. Event Delegation
|
|
84
|
+
|
|
85
|
+
Auth UI uses event delegation on document body:
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
document.body.addEventListener('click', (e) => {
|
|
89
|
+
if (e.target.closest('.auth-signout-btn')) {
|
|
90
|
+
// Handle signout
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Common Tasks
|
|
2
|
+
|
|
3
|
+
## Adding a New Utility Function
|
|
4
|
+
|
|
5
|
+
1. Add function to `src/modules/utilities.js`
|
|
6
|
+
2. Export it: `export function myFunction() { ... }`
|
|
7
|
+
3. Update README.md with documentation
|
|
8
|
+
4. Run `npm run prepare` to build
|
|
9
|
+
|
|
10
|
+
## Adding a New Module
|
|
11
|
+
|
|
12
|
+
1. Create `src/modules/my-module.js`
|
|
13
|
+
2. Export class: `export default class MyModule { constructor(manager) { ... } }`
|
|
14
|
+
3. Import in `src/index.js`: `import MyModule from './modules/my-module.js'`
|
|
15
|
+
4. Add to Manager constructor: `this._myModule = new MyModule(this)`
|
|
16
|
+
5. Add getter: `myModule() { return this._myModule; }`
|
|
17
|
+
6. Update README.md
|
|
18
|
+
7. Run `npm run prepare`
|
|
19
|
+
|
|
20
|
+
## Modifying Configuration Defaults
|
|
21
|
+
|
|
22
|
+
1. Edit `_processConfiguration()` in `src/index.js`
|
|
23
|
+
2. Add to `defaults` object (e.g., `payment: { processors: {}, products: [] }`)
|
|
24
|
+
3. Document in README.md Configuration section
|
|
25
|
+
|
|
26
|
+
## Payment Configuration
|
|
27
|
+
|
|
28
|
+
Payment config shape mirrors OMEGA (the SSOT) — same key names used in BEM, UJM, and EM:
|
|
29
|
+
- `processors`: Stripe, PayPal, Chargebee, Coinbase (publishable keys / client IDs)
|
|
30
|
+
- `products`: Array of `{ id, name, type, limits: { feature: N }, prices, trial, paypal, stripe, chargebee }` — used to resolve usage limits on the frontend AND drive checkout flows
|
|
31
|
+
|
|
32
|
+
## Adding a Data Binding Action
|
|
33
|
+
|
|
34
|
+
1. Edit `_executeAction()` in `src/modules/bindings.js`
|
|
35
|
+
2. Add case for new action (e.g., `@class`)
|
|
36
|
+
3. Document in README.md Data Binding section
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Dependencies & Important Notes
|
|
2
|
+
|
|
3
|
+
## Dependencies
|
|
4
|
+
|
|
5
|
+
| Package | Purpose |
|
|
6
|
+
|---------|---------|
|
|
7
|
+
| `firebase` (^12.x) | Auth, Firestore, Messaging |
|
|
8
|
+
| `@sentry/browser` (^10.x) | Error tracking |
|
|
9
|
+
| `lodash` (^4.x) | get/set for path-based access |
|
|
10
|
+
| `itwcw-package-analytics` | Analytics (internal) |
|
|
11
|
+
|
|
12
|
+
## Important Notes
|
|
13
|
+
|
|
14
|
+
1. **DO NOT MODIFY `_legacy/`** — Reference only for historical context
|
|
15
|
+
2. **Backwards compatibility is NOT required** — Just change to the new way
|
|
16
|
+
3. **Prefer `fs-jetpack`** over `fs` for any file operations in tests/scripts
|
|
17
|
+
4. **No TypeScript** — This is a pure JavaScript library
|
|
18
|
+
5. **Template strings** — Use backticks for string interpolation
|
|
19
|
+
6. **Modular design** — Keep modules focused and small
|
package/docs/modules.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Module Quick Reference
|
|
2
|
+
|
|
3
|
+
## Storage (`storage.js`)
|
|
4
|
+
|
|
5
|
+
- **Class**: `Storage`
|
|
6
|
+
- **Key Methods**: `get(path, default)`, `set(path, value)`, `remove(path)`, `clear()`
|
|
7
|
+
- **Session**: Same methods under `.session` namespace
|
|
8
|
+
- **Storage Key**: `_manager` in localStorage
|
|
9
|
+
|
|
10
|
+
## Auth (`auth.js`)
|
|
11
|
+
|
|
12
|
+
- **Class**: `Auth`
|
|
13
|
+
- **Key Methods**: `listen(options, callback)`, `isAuthenticated()`, `getUser()`, `signInWithEmailAndPassword()`, `signOut()`, `getIdToken()`, `resolveSubscription(account?)`
|
|
14
|
+
- **Bindings**: Updates `auth` and `usage` context on auth settle
|
|
15
|
+
- **Usage Resolution**: `_resolveUsage(state)` merges `account.usage` (Firestore) with product limits from `config.payment.products` (OMEGA-canonical shape — same key name in BEM, UJM, and EM) to produce the `usage` bindings key (e.g., `{ credits: { monthly: 5, limit: 100 } }`)
|
|
16
|
+
|
|
17
|
+
### resolveSubscription(account?)
|
|
18
|
+
|
|
19
|
+
Derives calculated subscription fields from raw account data. Returns only fields that require derivation logic — raw data (product.id, status, trial, cancellation) lives on `account.subscription` directly.
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const resolved = auth.resolveSubscription(account);
|
|
23
|
+
// Returns: { plan, active, trialing, cancelling }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `plan`: Effective plan ID the user has access to RIGHT NOW (`'basic'` if cancelled/suspended)
|
|
27
|
+
- `active`: User has active access (active, trialing, or cancelling — all mean the user can use the product)
|
|
28
|
+
- `trialing`: In an active trial (status `'active'` + `trial.claimed` + unexpired `trial.expires`)
|
|
29
|
+
- `cancelling`: Cancellation pending (status `'active'` + `cancellation.pending` + NOT trialing)
|
|
30
|
+
|
|
31
|
+
**Unified with BEM**: The same function exists on `User.resolveSubscription(account)` in backend-manager (`helpers/user.js`) with identical logic and return shape.
|
|
32
|
+
|
|
33
|
+
### Auth Settler Pattern
|
|
34
|
+
|
|
35
|
+
Auth uses a promise-based settler (`_authReady`) that resolves once Firebase's first `onAuthStateChanged` fires — the moment auth state is guaranteed (authenticated user OR null). This eliminates race conditions.
|
|
36
|
+
|
|
37
|
+
- **`once` listeners** (`listen({ once: true }, cb)`): Wait for `_authReady`, fire once, done. No cleanup needed.
|
|
38
|
+
- **Persistent listeners** (`listen({}, cb)`): Subscribe to `_authStateCallbacks`. If auth already settled when registered, catch up via `_authReady.then()`. Otherwise, `_handleAuthStateChange` handles the initial call naturally.
|
|
39
|
+
- **`_hasProcessedStateChange`**: Ensures bindings/storage updates run only once per auth state change across all listeners.
|
|
40
|
+
- **Manager owns the promise**: `_authReady` and `_authReadyResolve` live on the Manager instance. The `onAuthStateChanged` callback in `index.js` resolves it on first fire and sets `_firebaseAuthInitialized = true`.
|
|
41
|
+
|
|
42
|
+
## Bindings (`bindings.js`)
|
|
43
|
+
|
|
44
|
+
- **Class**: `Bindings`
|
|
45
|
+
- **Key Methods**: `update(data)`, `getContext()`, `clear()`
|
|
46
|
+
- **HTML Attr**: `data-wm-bind`
|
|
47
|
+
- **Actions**: `@text`, `@value`, `@show`, `@hide`, `@attr`, `@style`
|
|
48
|
+
|
|
49
|
+
## Firestore (`firestore.js`)
|
|
50
|
+
|
|
51
|
+
- **Class**: `Firestore`
|
|
52
|
+
- **Key Methods**: `doc(path)`, `collection(path)`
|
|
53
|
+
- **Doc Methods**: `.get()`, `.set()`, `.update()`, `.delete()`
|
|
54
|
+
- **Query Methods**: `.where()`, `.orderBy()`, `.limit()`, `.startAt()`, `.endAt()`
|
|
55
|
+
|
|
56
|
+
## Notifications (`notifications.js`)
|
|
57
|
+
|
|
58
|
+
- **Class**: `Notifications`
|
|
59
|
+
- **Key Methods**: `isSupported()`, `isSubscribed()`, `subscribe()`, `unsubscribe()`, `getToken()`, `onMessage()`
|
|
60
|
+
- **Storage**: Saves to localStorage and Firestore
|
|
61
|
+
|
|
62
|
+
## ServiceWorker (`service-worker.js`)
|
|
63
|
+
|
|
64
|
+
- **Class**: `ServiceWorker`
|
|
65
|
+
- **Key Methods**: `isSupported()`, `register()`, `ready()`, `postMessage()`, `onMessage()`, `getState()`
|
|
66
|
+
|
|
67
|
+
## Sentry (`sentry.js`)
|
|
68
|
+
|
|
69
|
+
- **Class**: `Sentry` (named `mod` internally)
|
|
70
|
+
- **Key Methods**: `init(config)`, `captureException(error, context)`
|
|
71
|
+
- **Filtering**: Blocks dev mode, Lighthouse, Selenium/Puppeteer
|
|
72
|
+
|
|
73
|
+
## DOM (`dom.js`)
|
|
74
|
+
|
|
75
|
+
- **Exports**: `loadScript(options)`, `ready()`
|
|
76
|
+
- **loadScript Options**: src, async, defer, crossorigin, integrity, timeout, retries
|
|
77
|
+
|
|
78
|
+
## Utilities (`utilities.js`)
|
|
79
|
+
|
|
80
|
+
- **Exports**: `clipboardCopy()`, `escapeHTML()`, `showNotification()`, `getPlatform()`, `getBrowser()`, `getRuntime()`, `isMobile()`, `getDevice()`, `getContext()`
|
package/docs/testing.md
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web-manager",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "Easily access important variables such as the query string, current domain, and current page in a single object.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -43,14 +43,14 @@
|
|
|
43
43
|
"@sentry/browser": "Resolved by using OVERRIDES in web-manager (lighthouse is the issue"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@sentry/browser": "^10.
|
|
46
|
+
"@sentry/browser": "^10.54.0",
|
|
47
47
|
"chatsy": "^2.0.14",
|
|
48
48
|
"firebase": "^12.13.0",
|
|
49
49
|
"itwcw-package-analytics": "^1.0.8",
|
|
50
50
|
"lodash": "^4.18.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"mocha": "^11.7.
|
|
53
|
+
"mocha": "^11.7.6",
|
|
54
54
|
"prepare-package": "^2.1.0"
|
|
55
55
|
}
|
|
56
56
|
}
|