web-manager 4.1.42 → 4.2.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/modules/auth.js +11 -0
- 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 +1 -1
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/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) {
|
|
@@ -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