web-manager 4.1.37 → 4.1.39
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 +9 -0
- package/CLAUDE.md +8 -2
- package/dist/modules/utilities.js +35 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
15
15
|
- `Security` in case of vulnerabilities.
|
|
16
16
|
|
|
17
17
|
---
|
|
18
|
+
## [4.1.39] - 2026-04-10
|
|
19
|
+
### Changed
|
|
20
|
+
- Converted `Utilities` methods to arrow class fields so `this` is permanently bound to the instance, allowing methods to be destructured, aliased, or passed as callbacks without losing context.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
## [4.1.38] - 2026-04-08
|
|
24
|
+
### Added
|
|
25
|
+
- Added `sanitizeURL()` utility method that validates URLs against dangerous URI schemes (javascript:, data:, etc.), allowing only http: and https: protocols.
|
|
26
|
+
|
|
18
27
|
## [4.1.37] - 2026-04-05
|
|
19
28
|
### Added
|
|
20
29
|
- Added `_resolveUsage()` to auth module that merges account usage data with plan limits from config, exposed as a top-level `usage` binding context.
|
package/CLAUDE.md
CHANGED
|
@@ -152,7 +152,8 @@ document.body.addEventListener('click', (e) => {
|
|
|
152
152
|
### Auth (`auth.js`)
|
|
153
153
|
- **Class**: `Auth`
|
|
154
154
|
- **Key Methods**: `listen(options, callback)`, `isAuthenticated()`, `getUser()`, `signInWithEmailAndPassword()`, `signOut()`, `getIdToken()`, `resolveSubscription(account?)`
|
|
155
|
-
- **Bindings**: Updates `auth
|
|
155
|
+
- **Bindings**: Updates `auth` and `usage` context on auth settle
|
|
156
|
+
- **Usage Resolution**: `_resolveUsage(state)` merges `account.usage` (Firestore) with plan limits from `config.payment.plans` to produce the `usage` bindings key (e.g., `{ credits: { monthly: 5, limit: 100 } }`)
|
|
156
157
|
|
|
157
158
|
#### resolveSubscription(account?)
|
|
158
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.
|
|
@@ -268,9 +269,14 @@ Current test coverage is minimal - focuses on configuration and storage.
|
|
|
268
269
|
|
|
269
270
|
### Modifying Configuration Defaults
|
|
270
271
|
1. Edit `_processConfiguration()` in `src/index.js`
|
|
271
|
-
2. Add to `defaults` object
|
|
272
|
+
2. Add to `defaults` object (e.g., `payment: { processors: {}, plans: [] }`)
|
|
272
273
|
3. Document in README.md Configuration section
|
|
273
274
|
|
|
275
|
+
### Payment Configuration
|
|
276
|
+
Payment config is set in `_config.yml` under `web_manager.payment` and includes:
|
|
277
|
+
- `processors`: Stripe, PayPal, etc. (publishable keys)
|
|
278
|
+
- `plans`: Array of `{ id, limits: { feature: N } }` used to resolve usage limits on the frontend
|
|
279
|
+
|
|
274
280
|
### Adding a Data Binding Action
|
|
275
281
|
1. Edit `_executeAction()` in `src/modules/bindings.js`
|
|
276
282
|
2. Add case for new action (e.g., `@class`)
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
// Methods are defined as arrow class fields so `this` is permanently bound to the instance.
|
|
2
|
+
// This means consumers can safely alias or destructure methods without losing context:
|
|
3
|
+
// const { escapeHTML } = webManager.utilities(); // ✓ works
|
|
4
|
+
// const escape = webManager.utilities().escapeHTML; // ✓ works
|
|
5
|
+
// items.map(webManager.utilities().escapeHTML); // ✓ works
|
|
6
|
+
// Safe because webManager.utilities() is a singleton — only one instance ever exists.
|
|
1
7
|
class Utilities {
|
|
2
8
|
constructor(manager) {
|
|
3
9
|
this.manager = manager;
|
|
4
10
|
}
|
|
5
11
|
|
|
6
12
|
// Copy text to clipboard
|
|
7
|
-
clipboardCopy(input) {
|
|
13
|
+
clipboardCopy = (input) => {
|
|
8
14
|
// Get the text from the input
|
|
9
15
|
const text = input && input.nodeType
|
|
10
16
|
? input.value || input.innerText || input.innerHTML
|
|
@@ -38,7 +44,7 @@ class Utilities {
|
|
|
38
44
|
|
|
39
45
|
// Escape HTML to prevent XSS
|
|
40
46
|
// Accepts a string, object, or array — walks recursively, escaping all string values
|
|
41
|
-
escapeHTML(input) {
|
|
47
|
+
escapeHTML = (input) => {
|
|
42
48
|
// Strings — escape and return
|
|
43
49
|
if (typeof input === 'string') {
|
|
44
50
|
this._shadowElement = this._shadowElement || document.createElement('p');
|
|
@@ -81,8 +87,28 @@ class Utilities {
|
|
|
81
87
|
return input;
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
// Sanitize URL to prevent javascript:, data:, and other dangerous URI schemes
|
|
91
|
+
// Returns the original URL if safe, or '' if rejected
|
|
92
|
+
sanitizeURL = (url) => {
|
|
93
|
+
if (!url || typeof url !== 'string') {
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const parsed = new URL(url, window.location.origin);
|
|
99
|
+
|
|
100
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return url;
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return '';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
84
110
|
// Show notification
|
|
85
|
-
showNotification(message, options = {}) {
|
|
111
|
+
showNotification = (message, options = {}) => {
|
|
86
112
|
// Handle different input types
|
|
87
113
|
let text = message;
|
|
88
114
|
let type = options.type || 'info';
|
|
@@ -128,7 +154,7 @@ class Utilities {
|
|
|
128
154
|
}
|
|
129
155
|
|
|
130
156
|
// Get platform (OS)
|
|
131
|
-
getPlatform() {
|
|
157
|
+
getPlatform = () => {
|
|
132
158
|
const ua = navigator.userAgent.toLowerCase();
|
|
133
159
|
const platform = (navigator.userAgentData?.platform || navigator.platform || '').toLowerCase();
|
|
134
160
|
|
|
@@ -158,7 +184,7 @@ class Utilities {
|
|
|
158
184
|
}
|
|
159
185
|
|
|
160
186
|
// Get browser name
|
|
161
|
-
getBrowser() {
|
|
187
|
+
getBrowser = () => {
|
|
162
188
|
const ua = navigator.userAgent;
|
|
163
189
|
|
|
164
190
|
// Order matters - check more specific browsers first
|
|
@@ -197,7 +223,7 @@ class Utilities {
|
|
|
197
223
|
}
|
|
198
224
|
|
|
199
225
|
// Get runtime environment
|
|
200
|
-
getRuntime() {
|
|
226
|
+
getRuntime = () => {
|
|
201
227
|
// Use config runtime if provided
|
|
202
228
|
if (this.manager?.config?.runtime) {
|
|
203
229
|
return this.manager.config.runtime;
|
|
@@ -217,7 +243,7 @@ class Utilities {
|
|
|
217
243
|
}
|
|
218
244
|
|
|
219
245
|
// Check if mobile device
|
|
220
|
-
isMobile() {
|
|
246
|
+
isMobile = () => {
|
|
221
247
|
try {
|
|
222
248
|
// Try modern API first
|
|
223
249
|
const m = navigator.userAgentData?.mobile;
|
|
@@ -237,7 +263,7 @@ class Utilities {
|
|
|
237
263
|
}
|
|
238
264
|
|
|
239
265
|
// Get device based on screen width
|
|
240
|
-
getDevice() {
|
|
266
|
+
getDevice = () => {
|
|
241
267
|
const width = window.innerWidth;
|
|
242
268
|
|
|
243
269
|
// Mobile: < 768px (Bootstrap's md breakpoint)
|
|
@@ -255,7 +281,7 @@ class Utilities {
|
|
|
255
281
|
}
|
|
256
282
|
|
|
257
283
|
// Get context information
|
|
258
|
-
getContext() {
|
|
284
|
+
getContext = () => {
|
|
259
285
|
// Return context information
|
|
260
286
|
return {
|
|
261
287
|
client: {
|
package/package.json
CHANGED