web-manager 4.0.0 → 4.0.2
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 +4 -0
- package/README.md +7 -7
- package/_legacy/service-worker copy.js +347 -0
- package/dist/index.js +1 -1
- package/dist/modules/auth.js +63 -15
- package/dist/modules/bindings.js +4 -4
- package/dist/modules/dom.js +8 -14
- package/dist/modules/service-worker.js +39 -31
- package/package.json +4 -5
package/CHANGELOG.md
CHANGED
@@ -15,6 +15,10 @@ 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.0.0] - 2025-09-11
|
19
|
+
### BREAKING
|
20
|
+
- Updated to ITW 3.0 standard.
|
21
|
+
|
18
22
|
## [3.2.74] - 2025-07-17
|
19
23
|
### Added
|
20
24
|
- Now looks for `build.json` in the `/@output/build/` directory to ensure it works with Vite's output structure.
|
package/README.md
CHANGED
@@ -270,10 +270,10 @@ Web Manager includes a powerful data binding system that automatically updates y
|
|
270
270
|
#### Basic Text Binding
|
271
271
|
```html
|
272
272
|
<!-- Display user email -->
|
273
|
-
<span data-wm-bind="user.email"></span>
|
273
|
+
<span data-wm-bind="auth.user.email"></span>
|
274
274
|
|
275
275
|
<!-- Display nested properties -->
|
276
|
-
<div data-wm-bind="account.subscription.plan"></div>
|
276
|
+
<div data-wm-bind="auth.account.subscription.plan"></div>
|
277
277
|
|
278
278
|
<!-- Works with inputs too -->
|
279
279
|
<input data-wm-bind="settings.theme" />
|
@@ -282,14 +282,14 @@ Web Manager includes a powerful data binding system that automatically updates y
|
|
282
282
|
#### Conditional Visibility
|
283
283
|
```html
|
284
284
|
<!-- Show element when condition is true -->
|
285
|
-
<div data-wm-bind="@show user">Welcome!</div>
|
286
|
-
<div data-wm-bind="@show user.emailVerified">Email is verified</div>
|
285
|
+
<div data-wm-bind="@show auth.user">Welcome!</div>
|
286
|
+
<div data-wm-bind="@show auth.user.emailVerified">Email is verified</div>
|
287
287
|
|
288
288
|
<!-- Hide element when condition is true -->
|
289
|
-
<div data-wm-bind="@hide user">Please log in</div>
|
289
|
+
<div data-wm-bind="@hide auth.user">Please log in</div>
|
290
290
|
|
291
291
|
<!-- Comparisons -->
|
292
|
-
<div data-wm-bind="@show subscription.plan === 'premium'">Premium features</div>
|
292
|
+
<div data-wm-bind="@show auth.account.subscription.plan === 'premium'">Premium features</div>
|
293
293
|
<div data-wm-bind="@hide settings.notifications === false">Notifications enabled</div>
|
294
294
|
```
|
295
295
|
|
@@ -297,7 +297,7 @@ Web Manager includes a powerful data binding system that automatically updates y
|
|
297
297
|
```javascript
|
298
298
|
// Auth data is automatically bound when using auth().listen()
|
299
299
|
Manager.auth().listen({ account: true }, (result) => {
|
300
|
-
// user and account data are automatically bound to the DOM
|
300
|
+
// auth.user and auth.account data are automatically bound to the DOM
|
301
301
|
});
|
302
302
|
|
303
303
|
// Update bindings with custom data
|
@@ -0,0 +1,347 @@
|
|
1
|
+
class ServiceWorker {
|
2
|
+
constructor(manager) {
|
3
|
+
this.manager = manager;
|
4
|
+
this._registration = null;
|
5
|
+
this._updateCallbacks = [];
|
6
|
+
this._messageHandlers = new Map();
|
7
|
+
}
|
8
|
+
|
9
|
+
// Check if service workers are supported
|
10
|
+
isSupported() {
|
11
|
+
return 'serviceWorker' in navigator;
|
12
|
+
}
|
13
|
+
|
14
|
+
// Register service worker
|
15
|
+
async register(options = {}) {
|
16
|
+
try {
|
17
|
+
if (!this.isSupported()) {
|
18
|
+
console.warn('Service Workers are not supported');
|
19
|
+
return null;
|
20
|
+
}
|
21
|
+
|
22
|
+
const swPath = options.path || this.manager.config.serviceWorker?.config?.path || '/service-worker.js';
|
23
|
+
const scope = options.scope || '/';
|
24
|
+
|
25
|
+
// Build config object to pass to service worker
|
26
|
+
const config = {
|
27
|
+
app: this.manager.config.brand?.id,
|
28
|
+
environment: this.manager.config.environment,
|
29
|
+
buildTime: this.manager.config.buildTime,
|
30
|
+
firebase: this.manager.config.firebase?.app?.config || null
|
31
|
+
};
|
32
|
+
|
33
|
+
// Get service worker URL with just cache breaker
|
34
|
+
const cacheBreaker = config.buildTime || Date.now();
|
35
|
+
const swUrl = `${swPath}?cb=${cacheBreaker}`;
|
36
|
+
|
37
|
+
// Get existing registrations
|
38
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
39
|
+
|
40
|
+
// Check if service worker is already registered for this scope
|
41
|
+
let registration = registrations.find(reg =>
|
42
|
+
reg.scope === new URL(scope, window.location.href).href
|
43
|
+
);
|
44
|
+
|
45
|
+
// Always register/re-register to ensure we have the correct URL with config
|
46
|
+
console.log('----WM SW 1');
|
47
|
+
|
48
|
+
if (registration) {
|
49
|
+
console.log('----WM SW 2');
|
50
|
+
console.log('Unregistering existing service worker to update config');
|
51
|
+
await registration.unregister();
|
52
|
+
// Wait a bit for unregistration to complete
|
53
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
54
|
+
}
|
55
|
+
console.log('----WM SW 3');
|
56
|
+
|
57
|
+
console.log('Registering service worker with cache breaker');
|
58
|
+
registration = await navigator.serviceWorker.register(swUrl, {
|
59
|
+
scope,
|
60
|
+
updateViaCache: options.updateViaCache || 'imports'
|
61
|
+
});
|
62
|
+
|
63
|
+
// Wait for the service worker to be ready
|
64
|
+
await navigator.serviceWorker.ready;
|
65
|
+
|
66
|
+
// Send the full config via postMessage after registration
|
67
|
+
console.log('----WM SW 4');
|
68
|
+
if (registration.active) {
|
69
|
+
console.log('----WM SW 5');
|
70
|
+
try {
|
71
|
+
this.postMessage({
|
72
|
+
command: 'update-config',
|
73
|
+
payload: config
|
74
|
+
});
|
75
|
+
} catch (error) {
|
76
|
+
console.warn('Could not send config to service worker:', error);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
this._registration = registration;
|
81
|
+
this.manager.state.serviceWorker = registration;
|
82
|
+
console.log('----WM SW 6');
|
83
|
+
|
84
|
+
// Set up update handlers
|
85
|
+
this._setupUpdateHandlers(registration);
|
86
|
+
console.log('----WM SW 7');
|
87
|
+
|
88
|
+
// Check for updates
|
89
|
+
if (options.checkForUpdate !== false) {
|
90
|
+
registration.update();
|
91
|
+
}
|
92
|
+
console.log('----WM SW 8');
|
93
|
+
|
94
|
+
// Set up message channel
|
95
|
+
if (registration.active) {
|
96
|
+
this._setupMessageChannel();
|
97
|
+
}
|
98
|
+
console.log('----WM SW 9');
|
99
|
+
|
100
|
+
return registration;
|
101
|
+
} catch (error) {
|
102
|
+
console.error('Service Worker registration failed:', error);
|
103
|
+
throw error;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
// Unregister service worker
|
108
|
+
async unregister() {
|
109
|
+
try {
|
110
|
+
if (!this._registration) {
|
111
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
112
|
+
for (const registration of registrations) {
|
113
|
+
await registration.unregister();
|
114
|
+
}
|
115
|
+
} else {
|
116
|
+
await this._registration.unregister();
|
117
|
+
}
|
118
|
+
|
119
|
+
this._registration = null;
|
120
|
+
this.manager.state.serviceWorker = null;
|
121
|
+
|
122
|
+
return true;
|
123
|
+
} catch (error) {
|
124
|
+
console.error('Service Worker unregistration failed:', error);
|
125
|
+
return false;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
// Get current registration
|
130
|
+
getRegistration() {
|
131
|
+
return this._registration;
|
132
|
+
}
|
133
|
+
|
134
|
+
// Check for updates
|
135
|
+
async update() {
|
136
|
+
try {
|
137
|
+
if (!this._registration) {
|
138
|
+
throw new Error('No service worker registered');
|
139
|
+
}
|
140
|
+
|
141
|
+
await this._registration.update();
|
142
|
+
return true;
|
143
|
+
} catch (error) {
|
144
|
+
console.error('Service Worker update failed:', error);
|
145
|
+
return false;
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
// Post message to service worker
|
150
|
+
postMessage(message, options = {}) {
|
151
|
+
return new Promise((resolve, reject) => {
|
152
|
+
if (!this.isSupported()) {
|
153
|
+
return reject(new Error('Service Workers not supported'));
|
154
|
+
}
|
155
|
+
|
156
|
+
const controller = this._registration?.active || navigator.serviceWorker.controller;
|
157
|
+
|
158
|
+
if (!controller) {
|
159
|
+
return reject(new Error('No active service worker'));
|
160
|
+
}
|
161
|
+
|
162
|
+
const messageChannel = new MessageChannel();
|
163
|
+
const timeout = options.timeout || 5000;
|
164
|
+
let timeoutId;
|
165
|
+
|
166
|
+
// Set up timeout
|
167
|
+
if (timeout > 0) {
|
168
|
+
timeoutId = setTimeout(() => {
|
169
|
+
messageChannel.port1.close();
|
170
|
+
reject(new Error('Service worker message timeout'));
|
171
|
+
}, timeout);
|
172
|
+
}
|
173
|
+
|
174
|
+
// Listen for response
|
175
|
+
messageChannel.port1.onmessage = (event) => {
|
176
|
+
clearTimeout(timeoutId);
|
177
|
+
|
178
|
+
if (event.data.error) {
|
179
|
+
reject(new Error(event.data.error));
|
180
|
+
} else {
|
181
|
+
resolve(event.data);
|
182
|
+
}
|
183
|
+
};
|
184
|
+
|
185
|
+
// Send message
|
186
|
+
controller.postMessage(message, [messageChannel.port2]);
|
187
|
+
});
|
188
|
+
}
|
189
|
+
|
190
|
+
// Listen for messages from service worker
|
191
|
+
onMessage(type, handler) {
|
192
|
+
if (!this.isSupported()) {
|
193
|
+
return () => {};
|
194
|
+
}
|
195
|
+
|
196
|
+
// Store handler
|
197
|
+
if (!this._messageHandlers.has(type)) {
|
198
|
+
this._messageHandlers.set(type, new Set());
|
199
|
+
}
|
200
|
+
this._messageHandlers.get(type).add(handler);
|
201
|
+
|
202
|
+
// Set up global message listener if not already done
|
203
|
+
if (this._messageHandlers.size === 1) {
|
204
|
+
navigator.serviceWorker.addEventListener('message', this._handleMessage.bind(this));
|
205
|
+
}
|
206
|
+
|
207
|
+
// Return unsubscribe function
|
208
|
+
return () => {
|
209
|
+
const handlers = this._messageHandlers.get(type);
|
210
|
+
if (handlers) {
|
211
|
+
handlers.delete(handler);
|
212
|
+
if (handlers.size === 0) {
|
213
|
+
this._messageHandlers.delete(type);
|
214
|
+
}
|
215
|
+
}
|
216
|
+
};
|
217
|
+
}
|
218
|
+
|
219
|
+
// Skip waiting and activate new service worker
|
220
|
+
async skipWaiting() {
|
221
|
+
try {
|
222
|
+
if (!this._registration?.waiting) {
|
223
|
+
throw new Error('No service worker waiting');
|
224
|
+
}
|
225
|
+
|
226
|
+
// Post message to skip waiting
|
227
|
+
await this.postMessage({ action: 'skipWaiting' });
|
228
|
+
|
229
|
+
// Reload page after activation
|
230
|
+
let refreshing = false;
|
231
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
232
|
+
if (!refreshing) {
|
233
|
+
refreshing = true;
|
234
|
+
window.location.reload();
|
235
|
+
}
|
236
|
+
});
|
237
|
+
|
238
|
+
return true;
|
239
|
+
} catch (error) {
|
240
|
+
console.error('Skip waiting failed:', error);
|
241
|
+
return false;
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
// Listen for update events
|
246
|
+
onUpdateFound(callback) {
|
247
|
+
this._updateCallbacks.push(callback);
|
248
|
+
|
249
|
+
return () => {
|
250
|
+
const index = this._updateCallbacks.indexOf(callback);
|
251
|
+
if (index > -1) {
|
252
|
+
this._updateCallbacks.splice(index, 1);
|
253
|
+
}
|
254
|
+
};
|
255
|
+
}
|
256
|
+
|
257
|
+
// Get service worker state
|
258
|
+
getState() {
|
259
|
+
if (!this._registration) {
|
260
|
+
return 'none';
|
261
|
+
}
|
262
|
+
|
263
|
+
if (this._registration.installing) {
|
264
|
+
return 'installing';
|
265
|
+
} else if (this._registration.waiting) {
|
266
|
+
return 'waiting';
|
267
|
+
} else if (this._registration.active) {
|
268
|
+
return 'active';
|
269
|
+
}
|
270
|
+
|
271
|
+
return 'unknown';
|
272
|
+
}
|
273
|
+
|
274
|
+
// Private: Set up update handlers
|
275
|
+
_setupUpdateHandlers(registration) {
|
276
|
+
// Listen for updates
|
277
|
+
registration.addEventListener('updatefound', () => {
|
278
|
+
const newWorker = registration.installing;
|
279
|
+
|
280
|
+
if (!newWorker) return;
|
281
|
+
|
282
|
+
newWorker.addEventListener('statechange', () => {
|
283
|
+
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
284
|
+
// New service worker available
|
285
|
+
this._notifyUpdateCallbacks({
|
286
|
+
type: 'update-available',
|
287
|
+
worker: newWorker
|
288
|
+
});
|
289
|
+
|
290
|
+
// Automatically skip waiting and activate new worker
|
291
|
+
if (this.manager.config.serviceWorker?.autoUpdate !== false) {
|
292
|
+
this.skipWaiting();
|
293
|
+
}
|
294
|
+
}
|
295
|
+
});
|
296
|
+
});
|
297
|
+
|
298
|
+
// Listen for controller changes
|
299
|
+
let refreshing = false;
|
300
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
301
|
+
if (!refreshing) {
|
302
|
+
this._notifyUpdateCallbacks({
|
303
|
+
type: 'controller-change'
|
304
|
+
});
|
305
|
+
}
|
306
|
+
});
|
307
|
+
}
|
308
|
+
|
309
|
+
// Private: Notify update callbacks
|
310
|
+
_notifyUpdateCallbacks(event) {
|
311
|
+
this._updateCallbacks.forEach(callback => {
|
312
|
+
try {
|
313
|
+
callback(event);
|
314
|
+
} catch (error) {
|
315
|
+
console.error('Update callback error:', error);
|
316
|
+
}
|
317
|
+
});
|
318
|
+
}
|
319
|
+
|
320
|
+
// Private: Handle incoming messages
|
321
|
+
_handleMessage(event) {
|
322
|
+
const { type, ...data } = event.data || {};
|
323
|
+
|
324
|
+
if (!type) return;
|
325
|
+
|
326
|
+
const handlers = this._messageHandlers.get(type);
|
327
|
+
if (handlers) {
|
328
|
+
handlers.forEach(handler => {
|
329
|
+
try {
|
330
|
+
handler(data, event);
|
331
|
+
} catch (error) {
|
332
|
+
console.error('Message handler error:', error);
|
333
|
+
}
|
334
|
+
});
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
338
|
+
// Private: Set up message channel
|
339
|
+
_setupMessageChannel() {
|
340
|
+
// This ensures we can communicate with the service worker
|
341
|
+
navigator.serviceWorker.ready.then(() => {
|
342
|
+
console.log('Service Worker ready for messaging');
|
343
|
+
});
|
344
|
+
}
|
345
|
+
}
|
346
|
+
|
347
|
+
export default ServiceWorker;
|
package/dist/index.js
CHANGED
package/dist/modules/auth.js
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
import resolveAccount from 'resolve-account';
|
2
|
+
|
1
3
|
class Auth {
|
2
4
|
constructor(manager) {
|
3
5
|
this.manager = manager;
|
4
6
|
this._authStateCallbacks = [];
|
5
7
|
this._readyCallbacks = [];
|
8
|
+
this._hasProcessedStateChange = false;
|
6
9
|
}
|
7
10
|
|
8
11
|
// Check if user is authenticated
|
@@ -67,8 +70,14 @@ class Auth {
|
|
67
70
|
|
68
71
|
// If Firebase is not enabled, call callback immediately with null
|
69
72
|
if (!this.manager.config.firebase?.app?.enabled) {
|
70
|
-
callback
|
71
|
-
|
73
|
+
// Call callback with null user and empty account
|
74
|
+
callback({
|
75
|
+
user: null,
|
76
|
+
account: resolveAccount({}, {})
|
77
|
+
});
|
78
|
+
|
79
|
+
// Return empty unsubscribe function
|
80
|
+
return () => {};
|
72
81
|
}
|
73
82
|
|
74
83
|
// Function to get current state and call callback
|
@@ -77,18 +86,33 @@ class Auth {
|
|
77
86
|
const state = { user: this.getUser() };
|
78
87
|
|
79
88
|
// Then, add account data if requested and user exists
|
80
|
-
if (options.account && user && this.manager.firebaseFirestore) {
|
89
|
+
// if (options.account && user && this.manager.firebaseFirestore) {
|
90
|
+
// Fetch account if the user is logged in AND Firestore is available
|
91
|
+
if (user && this.manager.firebaseFirestore) {
|
81
92
|
try {
|
82
93
|
state.account = await this._getAccountData(user.uid);
|
83
94
|
} catch (error) {
|
84
|
-
|
95
|
+
// Pass error to Sentry
|
96
|
+
this.manager.sentry().captureException(new Error('Failed to get account data', { cause: error }));
|
85
97
|
}
|
86
|
-
} else {
|
87
|
-
state.account = null;
|
88
98
|
}
|
89
99
|
|
90
|
-
//
|
91
|
-
|
100
|
+
// Always ensure account is at least a default resolved object
|
101
|
+
state.account = state.account || resolveAccount({}, { uid: user?.uid });
|
102
|
+
|
103
|
+
// Process state change (update bindings and storage) only once across all callbacks
|
104
|
+
// Now ONLY the first listener will process the state change until the next auth state change
|
105
|
+
if (!this._hasProcessedStateChange) {
|
106
|
+
// Run update - nest state under 'auth' key for consistent access
|
107
|
+
this.manager.bindings().update({ auth: state });
|
108
|
+
|
109
|
+
// Save to storage
|
110
|
+
const storage = this.manager.storage();
|
111
|
+
storage.set('auth', state);
|
112
|
+
|
113
|
+
// Mark that we've processed this state change
|
114
|
+
this._hasProcessedStateChange = true;
|
115
|
+
}
|
92
116
|
|
93
117
|
// Call the provided callback with the state
|
94
118
|
callback(state);
|
@@ -97,15 +121,27 @@ class Auth {
|
|
97
121
|
let hasCalledback = false;
|
98
122
|
|
99
123
|
// Set up listener for auth state changes
|
100
|
-
|
124
|
+
const unsubscribe = this.onAuthStateChanged((user) => {
|
125
|
+
// If once option is set, unsubscribe
|
126
|
+
// We have to do this here because unsubscribe is only available after this call
|
127
|
+
if (options.once && unsubscribe) {
|
128
|
+
unsubscribe();
|
129
|
+
return;
|
130
|
+
}
|
131
|
+
|
101
132
|
// Wait for settled state before first callback
|
102
133
|
if (!hasCalledback && !this.manager._firebaseAuthInitialized) {
|
103
134
|
return; // Auth state not yet determined
|
104
135
|
}
|
105
136
|
|
137
|
+
// Mark that we've called back at least once
|
106
138
|
hasCalledback = true;
|
139
|
+
|
140
|
+
// Get current state and call the callback
|
107
141
|
getStateAndCallback(user);
|
108
142
|
});
|
143
|
+
|
144
|
+
return unsubscribe;
|
109
145
|
}
|
110
146
|
|
111
147
|
// Listen for auth state changes
|
@@ -128,11 +164,8 @@ class Auth {
|
|
128
164
|
|
129
165
|
// Internal method to handle auth state changes
|
130
166
|
_handleAuthStateChange(user) {
|
131
|
-
//
|
132
|
-
this.
|
133
|
-
user: this.getUser(),
|
134
|
-
account: null
|
135
|
-
});
|
167
|
+
// Reset state processing flag for new auth state
|
168
|
+
this._hasProcessedStateChange = false;
|
136
169
|
|
137
170
|
// Call all registered callbacks
|
138
171
|
this._authStateCallbacks.forEach(callback => {
|
@@ -173,6 +206,22 @@ class Auth {
|
|
173
206
|
}
|
174
207
|
}
|
175
208
|
|
209
|
+
// Sign in with email and password
|
210
|
+
async signInWithEmailAndPassword(email, password) {
|
211
|
+
try {
|
212
|
+
if (!this.manager.firebaseAuth) {
|
213
|
+
throw new Error('Firebase Auth is not initialized');
|
214
|
+
}
|
215
|
+
|
216
|
+
const { signInWithEmailAndPassword } = await import('firebase/auth');
|
217
|
+
const userCredential = await signInWithEmailAndPassword(this.manager.firebaseAuth, email, password);
|
218
|
+
return userCredential.user;
|
219
|
+
} catch (error) {
|
220
|
+
console.error('Sign in with email and password error:', error);
|
221
|
+
throw error;
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
176
225
|
// Sign out the current user
|
177
226
|
async signOut() {
|
178
227
|
try {
|
@@ -193,7 +242,6 @@ class Auth {
|
|
193
242
|
}
|
194
243
|
|
195
244
|
const { doc, getDoc } = await import('firebase/firestore');
|
196
|
-
const resolveAccount = (await import('resolve-account')).default;
|
197
245
|
|
198
246
|
const accountDoc = doc(this.manager.firebaseFirestore, 'users', uid);
|
199
247
|
const snapshot = await getDoc(accountDoc);
|
package/dist/modules/bindings.js
CHANGED
@@ -81,14 +81,14 @@ class Bindings {
|
|
81
81
|
const attrName = attrParts[0];
|
82
82
|
const attrExpression = attrParts.slice(1).join(' ');
|
83
83
|
const attrValue = this._resolvePath(context, attrExpression) || '';
|
84
|
-
|
84
|
+
|
85
85
|
if (attrValue) {
|
86
86
|
element.setAttribute(attrName, attrValue);
|
87
87
|
} else {
|
88
88
|
element.removeAttribute(attrName);
|
89
89
|
}
|
90
90
|
break;
|
91
|
-
|
91
|
+
|
92
92
|
case '@text':
|
93
93
|
default:
|
94
94
|
// Set text content (default behavior)
|
@@ -121,7 +121,7 @@ class Bindings {
|
|
121
121
|
_evaluateCondition(condition, context) {
|
122
122
|
try {
|
123
123
|
// Replace context references with actual values
|
124
|
-
// Support: user.field, account.field, simple comparisons
|
124
|
+
// Support: auth.user.field, auth.account.field, simple comparisons
|
125
125
|
|
126
126
|
// Check for negation operator at the start
|
127
127
|
if (condition.trim().startsWith('!')) {
|
@@ -169,7 +169,7 @@ class Bindings {
|
|
169
169
|
default: return false;
|
170
170
|
}
|
171
171
|
} else {
|
172
|
-
// Simple truthy check (e.g., "user.emailVerified" or "account")
|
172
|
+
// Simple truthy check (e.g., "auth.user.emailVerified" or "auth.account")
|
173
173
|
const value = this._resolvePath(context, condition.trim());
|
174
174
|
return !!value;
|
175
175
|
}
|
package/dist/modules/dom.js
CHANGED
@@ -12,21 +12,16 @@ export function loadScript(options) {
|
|
12
12
|
defer = false,
|
13
13
|
crossorigin = false,
|
14
14
|
integrity = null,
|
15
|
-
attributes =
|
15
|
+
attributes = {},
|
16
16
|
timeout = 60000,
|
17
|
-
retries = 0
|
17
|
+
retries = 0,
|
18
|
+
parent = null
|
18
19
|
} = options;
|
19
20
|
|
20
21
|
if (!src) {
|
21
22
|
return reject(new Error('Script source is required'));
|
22
23
|
}
|
23
24
|
|
24
|
-
// Check if script already exists
|
25
|
-
const existingScript = document.querySelector(`script[src="${src}"]`);
|
26
|
-
if (existingScript) {
|
27
|
-
return resolve({ script: existingScript, cached: true });
|
28
|
-
}
|
29
|
-
|
30
25
|
let timeoutId;
|
31
26
|
let retryCount = 0;
|
32
27
|
|
@@ -45,10 +40,8 @@ export function loadScript(options) {
|
|
45
40
|
}
|
46
41
|
|
47
42
|
// Add custom attributes
|
48
|
-
attributes.forEach(
|
49
|
-
|
50
|
-
script.setAttribute(attr.name, attr.value);
|
51
|
-
}
|
43
|
+
Object.keys(attributes).forEach(name => {
|
44
|
+
script.setAttribute(name, attributes[name]);
|
52
45
|
});
|
53
46
|
|
54
47
|
// Set up timeout
|
@@ -68,11 +61,12 @@ export function loadScript(options) {
|
|
68
61
|
script.onerror = (error) => {
|
69
62
|
clearTimeout(timeoutId);
|
70
63
|
script.remove();
|
71
|
-
handleError(new Error(`Failed to load script
|
64
|
+
handleError(new Error(`Failed to load script ${src}`, { cause: error }));
|
72
65
|
};
|
73
66
|
|
74
67
|
// Append to document
|
75
|
-
|
68
|
+
const $targetParent = parent || document.head || document.documentElement;
|
69
|
+
$targetParent.appendChild(script);
|
76
70
|
}
|
77
71
|
|
78
72
|
function handleError(error) {
|
@@ -11,6 +11,22 @@ class ServiceWorker {
|
|
11
11
|
return 'serviceWorker' in navigator;
|
12
12
|
}
|
13
13
|
|
14
|
+
// Return promise that resolves when service worker is ready
|
15
|
+
async ready() {
|
16
|
+
if (!this.isSupported()) {
|
17
|
+
throw new Error('Service Workers not supported');
|
18
|
+
}
|
19
|
+
|
20
|
+
// If already registered and active
|
21
|
+
if (this._registration?.active) {
|
22
|
+
return this._registration;
|
23
|
+
}
|
24
|
+
|
25
|
+
// Wait for service worker to be ready
|
26
|
+
const registration = await navigator.serviceWorker.ready;
|
27
|
+
return registration;
|
28
|
+
}
|
29
|
+
|
14
30
|
// Register service worker
|
15
31
|
async register(options = {}) {
|
16
32
|
try {
|
@@ -30,30 +46,11 @@ class ServiceWorker {
|
|
30
46
|
firebase: this.manager.config.firebase?.app?.config || null
|
31
47
|
};
|
32
48
|
|
33
|
-
//
|
34
|
-
const
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
// Check if service worker is already registered for this scope
|
40
|
-
let registration = registrations.find(reg =>
|
41
|
-
reg.scope === new URL(scope, window.location.href).href
|
42
|
-
);
|
43
|
-
|
44
|
-
// This helps the .register() method NOT HANG FOREVER
|
45
|
-
if (registration) {
|
46
|
-
console.log('Using existing service worker registration');
|
47
|
-
// Check for updates on existing registration
|
48
|
-
registration.update();
|
49
|
-
} else {
|
50
|
-
console.log('Registering new service worker');
|
51
|
-
// Register with config in URL
|
52
|
-
registration = await navigator.serviceWorker.register(swUrl, {
|
53
|
-
scope,
|
54
|
-
updateViaCache: options.updateViaCache || 'imports'
|
55
|
-
});
|
56
|
-
}
|
49
|
+
// Register and handle everything
|
50
|
+
const registration = await navigator.serviceWorker.register(swPath, {
|
51
|
+
scope,
|
52
|
+
updateViaCache: 'none' // Always check server for updates
|
53
|
+
});
|
57
54
|
|
58
55
|
this._registration = registration;
|
59
56
|
this.manager.state.serviceWorker = registration;
|
@@ -61,16 +58,27 @@ class ServiceWorker {
|
|
61
58
|
// Set up update handlers
|
62
59
|
this._setupUpdateHandlers(registration);
|
63
60
|
|
64
|
-
//
|
65
|
-
|
66
|
-
|
67
|
-
}
|
68
|
-
|
69
|
-
// Set up message channel
|
61
|
+
// Wait for service worker to be ready and send config
|
62
|
+
await navigator.serviceWorker.ready;
|
63
|
+
|
70
64
|
if (registration.active) {
|
65
|
+
try {
|
66
|
+
this.postMessage({
|
67
|
+
command: 'update-config',
|
68
|
+
payload: config
|
69
|
+
});
|
70
|
+
} catch (error) {
|
71
|
+
console.warn('Could not send config to service worker:', error);
|
72
|
+
}
|
73
|
+
|
71
74
|
this._setupMessageChannel();
|
72
75
|
}
|
73
76
|
|
77
|
+
// Check for updates (this will detect if service worker file changed)
|
78
|
+
if (options.checkForUpdate !== false) {
|
79
|
+
registration.update();
|
80
|
+
}
|
81
|
+
|
74
82
|
return registration;
|
75
83
|
} catch (error) {
|
76
84
|
console.error('Service Worker registration failed:', error);
|
@@ -260,7 +268,7 @@ class ServiceWorker {
|
|
260
268
|
type: 'update-available',
|
261
269
|
worker: newWorker
|
262
270
|
});
|
263
|
-
|
271
|
+
|
264
272
|
// Automatically skip waiting and activate new worker
|
265
273
|
if (this.manager.config.serviceWorker?.autoUpdate !== false) {
|
266
274
|
this.skipWaiting();
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "web-manager",
|
3
|
-
"version": "4.0.
|
3
|
+
"version": "4.0.2",
|
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",
|
@@ -41,14 +41,13 @@
|
|
41
41
|
"replace": {}
|
42
42
|
},
|
43
43
|
"dependencies": {
|
44
|
-
"@sentry/browser": "^10.
|
45
|
-
"firebase": "^12.
|
44
|
+
"@sentry/browser": "^10.15.0",
|
45
|
+
"firebase": "^12.3.0",
|
46
46
|
"itwcw-package-analytics": "^1.0.6",
|
47
47
|
"lodash": "^4.17.21",
|
48
|
-
"resolve-account": "^2.0.
|
48
|
+
"resolve-account": "^2.0.1"
|
49
49
|
},
|
50
50
|
"devDependencies": {
|
51
|
-
"lodash": "^4.17.21",
|
52
51
|
"mocha": "^8.4.0",
|
53
52
|
"prepare-package": "^1.2.2"
|
54
53
|
}
|