web-manager 3.2.75 → 4.0.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.
@@ -0,0 +1,321 @@
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 config
34
+ const swUrl = `${swPath}?config=${encodeURIComponent(JSON.stringify(config))}`;
35
+
36
+ // Get existing registrations
37
+ const registrations = await navigator.serviceWorker.getRegistrations();
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
+ }
57
+
58
+ this._registration = registration;
59
+ this.manager.state.serviceWorker = registration;
60
+
61
+ // Set up update handlers
62
+ this._setupUpdateHandlers(registration);
63
+
64
+ // Check for updates
65
+ if (options.checkForUpdate !== false) {
66
+ registration.update();
67
+ }
68
+
69
+ // Set up message channel
70
+ if (registration.active) {
71
+ this._setupMessageChannel();
72
+ }
73
+
74
+ return registration;
75
+ } catch (error) {
76
+ console.error('Service Worker registration failed:', error);
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ // Unregister service worker
82
+ async unregister() {
83
+ try {
84
+ if (!this._registration) {
85
+ const registrations = await navigator.serviceWorker.getRegistrations();
86
+ for (const registration of registrations) {
87
+ await registration.unregister();
88
+ }
89
+ } else {
90
+ await this._registration.unregister();
91
+ }
92
+
93
+ this._registration = null;
94
+ this.manager.state.serviceWorker = null;
95
+
96
+ return true;
97
+ } catch (error) {
98
+ console.error('Service Worker unregistration failed:', error);
99
+ return false;
100
+ }
101
+ }
102
+
103
+ // Get current registration
104
+ getRegistration() {
105
+ return this._registration;
106
+ }
107
+
108
+ // Check for updates
109
+ async update() {
110
+ try {
111
+ if (!this._registration) {
112
+ throw new Error('No service worker registered');
113
+ }
114
+
115
+ await this._registration.update();
116
+ return true;
117
+ } catch (error) {
118
+ console.error('Service Worker update failed:', error);
119
+ return false;
120
+ }
121
+ }
122
+
123
+ // Post message to service worker
124
+ postMessage(message, options = {}) {
125
+ return new Promise((resolve, reject) => {
126
+ if (!this.isSupported()) {
127
+ return reject(new Error('Service Workers not supported'));
128
+ }
129
+
130
+ const controller = this._registration?.active || navigator.serviceWorker.controller;
131
+
132
+ if (!controller) {
133
+ return reject(new Error('No active service worker'));
134
+ }
135
+
136
+ const messageChannel = new MessageChannel();
137
+ const timeout = options.timeout || 5000;
138
+ let timeoutId;
139
+
140
+ // Set up timeout
141
+ if (timeout > 0) {
142
+ timeoutId = setTimeout(() => {
143
+ messageChannel.port1.close();
144
+ reject(new Error('Service worker message timeout'));
145
+ }, timeout);
146
+ }
147
+
148
+ // Listen for response
149
+ messageChannel.port1.onmessage = (event) => {
150
+ clearTimeout(timeoutId);
151
+
152
+ if (event.data.error) {
153
+ reject(new Error(event.data.error));
154
+ } else {
155
+ resolve(event.data);
156
+ }
157
+ };
158
+
159
+ // Send message
160
+ controller.postMessage(message, [messageChannel.port2]);
161
+ });
162
+ }
163
+
164
+ // Listen for messages from service worker
165
+ onMessage(type, handler) {
166
+ if (!this.isSupported()) {
167
+ return () => {};
168
+ }
169
+
170
+ // Store handler
171
+ if (!this._messageHandlers.has(type)) {
172
+ this._messageHandlers.set(type, new Set());
173
+ }
174
+ this._messageHandlers.get(type).add(handler);
175
+
176
+ // Set up global message listener if not already done
177
+ if (this._messageHandlers.size === 1) {
178
+ navigator.serviceWorker.addEventListener('message', this._handleMessage.bind(this));
179
+ }
180
+
181
+ // Return unsubscribe function
182
+ return () => {
183
+ const handlers = this._messageHandlers.get(type);
184
+ if (handlers) {
185
+ handlers.delete(handler);
186
+ if (handlers.size === 0) {
187
+ this._messageHandlers.delete(type);
188
+ }
189
+ }
190
+ };
191
+ }
192
+
193
+ // Skip waiting and activate new service worker
194
+ async skipWaiting() {
195
+ try {
196
+ if (!this._registration?.waiting) {
197
+ throw new Error('No service worker waiting');
198
+ }
199
+
200
+ // Post message to skip waiting
201
+ await this.postMessage({ action: 'skipWaiting' });
202
+
203
+ // Reload page after activation
204
+ let refreshing = false;
205
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
206
+ if (!refreshing) {
207
+ refreshing = true;
208
+ window.location.reload();
209
+ }
210
+ });
211
+
212
+ return true;
213
+ } catch (error) {
214
+ console.error('Skip waiting failed:', error);
215
+ return false;
216
+ }
217
+ }
218
+
219
+ // Listen for update events
220
+ onUpdateFound(callback) {
221
+ this._updateCallbacks.push(callback);
222
+
223
+ return () => {
224
+ const index = this._updateCallbacks.indexOf(callback);
225
+ if (index > -1) {
226
+ this._updateCallbacks.splice(index, 1);
227
+ }
228
+ };
229
+ }
230
+
231
+ // Get service worker state
232
+ getState() {
233
+ if (!this._registration) {
234
+ return 'none';
235
+ }
236
+
237
+ if (this._registration.installing) {
238
+ return 'installing';
239
+ } else if (this._registration.waiting) {
240
+ return 'waiting';
241
+ } else if (this._registration.active) {
242
+ return 'active';
243
+ }
244
+
245
+ return 'unknown';
246
+ }
247
+
248
+ // Private: Set up update handlers
249
+ _setupUpdateHandlers(registration) {
250
+ // Listen for updates
251
+ registration.addEventListener('updatefound', () => {
252
+ const newWorker = registration.installing;
253
+
254
+ if (!newWorker) return;
255
+
256
+ newWorker.addEventListener('statechange', () => {
257
+ if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
258
+ // New service worker available
259
+ this._notifyUpdateCallbacks({
260
+ type: 'update-available',
261
+ worker: newWorker
262
+ });
263
+
264
+ // Automatically skip waiting and activate new worker
265
+ if (this.manager.config.serviceWorker?.autoUpdate !== false) {
266
+ this.skipWaiting();
267
+ }
268
+ }
269
+ });
270
+ });
271
+
272
+ // Listen for controller changes
273
+ let refreshing = false;
274
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
275
+ if (!refreshing) {
276
+ this._notifyUpdateCallbacks({
277
+ type: 'controller-change'
278
+ });
279
+ }
280
+ });
281
+ }
282
+
283
+ // Private: Notify update callbacks
284
+ _notifyUpdateCallbacks(event) {
285
+ this._updateCallbacks.forEach(callback => {
286
+ try {
287
+ callback(event);
288
+ } catch (error) {
289
+ console.error('Update callback error:', error);
290
+ }
291
+ });
292
+ }
293
+
294
+ // Private: Handle incoming messages
295
+ _handleMessage(event) {
296
+ const { type, ...data } = event.data || {};
297
+
298
+ if (!type) return;
299
+
300
+ const handlers = this._messageHandlers.get(type);
301
+ if (handlers) {
302
+ handlers.forEach(handler => {
303
+ try {
304
+ handler(data, event);
305
+ } catch (error) {
306
+ console.error('Message handler error:', error);
307
+ }
308
+ });
309
+ }
310
+ }
311
+
312
+ // Private: Set up message channel
313
+ _setupMessageChannel() {
314
+ // This ensures we can communicate with the service worker
315
+ navigator.serviceWorker.ready.then(() => {
316
+ console.log('Service Worker ready for messaging');
317
+ });
318
+ }
319
+ }
320
+
321
+ export default ServiceWorker;
@@ -0,0 +1,132 @@
1
+ import { get as _get, set as _set } from 'lodash';
2
+
3
+ class Storage {
4
+ constructor() {
5
+ this.storageKey = '_manager';
6
+ this.pseudoStorage = {};
7
+ }
8
+
9
+ get(path, defaultValue) {
10
+ let usableStorage;
11
+
12
+ // Try to parse the localStorage object
13
+ try {
14
+ usableStorage = JSON.parse(window.localStorage.getItem(this.storageKey) || '{}');
15
+ } catch (e) {
16
+ usableStorage = this.pseudoStorage;
17
+ }
18
+
19
+ // If there's no path, return the entire storage object
20
+ if (!path) {
21
+ return usableStorage || defaultValue;
22
+ }
23
+
24
+ // Return the value at the path
25
+ return _get(usableStorage, path, defaultValue);
26
+ }
27
+
28
+ set(path, value) {
29
+ let usableStorage;
30
+
31
+ // Try to get the current storage
32
+ try {
33
+ usableStorage = this.get();
34
+ } catch (e) {
35
+ usableStorage = this.pseudoStorage;
36
+ }
37
+
38
+ // If there's no path, replace the entire storage object
39
+ if (!path) {
40
+ usableStorage = value || {};
41
+ } else {
42
+ // Set the value at the path
43
+ _set(usableStorage, path, value);
44
+ }
45
+
46
+ // Try to set the localStorage object
47
+ try {
48
+ window.localStorage.setItem(this.storageKey, JSON.stringify(usableStorage));
49
+ } catch (e) {
50
+ this.pseudoStorage = usableStorage;
51
+ }
52
+
53
+ return usableStorage;
54
+ }
55
+
56
+ remove(path) {
57
+ if (!path) {
58
+ this.clear();
59
+ } else {
60
+ this.set(path, undefined);
61
+ }
62
+ }
63
+
64
+ clear() {
65
+ try {
66
+ window.localStorage.setItem(this.storageKey, '{}');
67
+ } catch (e) {
68
+ this.pseudoStorage = {};
69
+ }
70
+ }
71
+
72
+ // Session storage methods
73
+ session = {
74
+ get: (path, defaultValue) => {
75
+ let usableStorage;
76
+
77
+ try {
78
+ usableStorage = JSON.parse(window.sessionStorage.getItem(this.storageKey) || '{}');
79
+ } catch (e) {
80
+ return defaultValue;
81
+ }
82
+
83
+ if (!path) {
84
+ return usableStorage || defaultValue;
85
+ }
86
+
87
+ return _get(usableStorage, path, defaultValue);
88
+ },
89
+
90
+ set: (path, value) => {
91
+ let usableStorage;
92
+
93
+ try {
94
+ usableStorage = this.session.get();
95
+ } catch (e) {
96
+ usableStorage = {};
97
+ }
98
+
99
+ if (!path) {
100
+ usableStorage = value || {};
101
+ } else {
102
+ _set(usableStorage, path, value);
103
+ }
104
+
105
+ try {
106
+ window.sessionStorage.setItem(this.storageKey, JSON.stringify(usableStorage));
107
+ } catch (e) {
108
+ // Silent fail
109
+ }
110
+
111
+ return usableStorage;
112
+ },
113
+
114
+ remove: (path) => {
115
+ if (!path) {
116
+ this.session.clear();
117
+ } else {
118
+ this.session.set(path, undefined);
119
+ }
120
+ },
121
+
122
+ clear: () => {
123
+ try {
124
+ window.sessionStorage.setItem(this.storageKey, '{}');
125
+ } catch (e) {
126
+ // Silent fail
127
+ }
128
+ }
129
+ };
130
+ }
131
+
132
+ export default Storage;
@@ -0,0 +1,143 @@
1
+ // Copy text to clipboard
2
+ export function clipboardCopy(input) {
3
+ // Get the text from the input
4
+ const text = input && input.nodeType
5
+ ? input.value || input.innerText || input.innerHTML
6
+ : input;
7
+
8
+ // Try to use the modern clipboard API
9
+ if (navigator.clipboard && navigator.clipboard.writeText) {
10
+ return navigator.clipboard.writeText(text).catch(() => {
11
+ fallbackCopy(text);
12
+ });
13
+ } else {
14
+ fallbackCopy(text);
15
+ }
16
+
17
+ function fallbackCopy(text) {
18
+ const el = document.createElement('textarea');
19
+ el.setAttribute('style', 'width:1px;border:0;opacity:0;');
20
+ el.value = text;
21
+ document.body.appendChild(el);
22
+ el.select();
23
+
24
+ try {
25
+ document.execCommand('copy');
26
+ } catch (e) {
27
+ console.error('Failed to copy to clipboard');
28
+ }
29
+
30
+ document.body.removeChild(el);
31
+ }
32
+ }
33
+
34
+ // Escape HTML to prevent XSS
35
+ let shadowElement;
36
+ export function escapeHTML(str) {
37
+ if (typeof str !== 'string') {
38
+ return '';
39
+ }
40
+
41
+ shadowElement = shadowElement || document.createElement('p');
42
+ shadowElement.innerHTML = '';
43
+
44
+ // This automatically escapes HTML entities like <, >, &, etc.
45
+ shadowElement.appendChild(document.createTextNode(str));
46
+
47
+ // This is needed to escape quotes to prevent attribute injection
48
+ return shadowElement.innerHTML.replace(/["']/g, (m) => {
49
+ switch (m) {
50
+ case '"':
51
+ return '&quot;';
52
+ default:
53
+ return '&#039;';
54
+ }
55
+ });
56
+ }
57
+
58
+ // Show notification
59
+ export function showNotification(message, options = {}) {
60
+ // Handle different input types
61
+ let text = message;
62
+ let type = options.type || 'info';
63
+
64
+ // If message is an Error object, extract message and default to danger
65
+ if (message instanceof Error) {
66
+ text = message.message;
67
+ type = options.type || 'danger';
68
+ }
69
+
70
+ // Handle string as second parameter for backwards compatibility
71
+ if (typeof options === 'string') {
72
+ options = { type: options };
73
+ type = options.type;
74
+ }
75
+
76
+ // Extract options
77
+ const timeout = options.timeout !== undefined ? options.timeout : 5000;
78
+
79
+ const $notification = document.createElement('div');
80
+ $notification.className = `alert alert-${type} alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-5`;
81
+ $notification.style.zIndex = '9999';
82
+ $notification.innerHTML = `
83
+ ${text}
84
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
85
+ `;
86
+
87
+ document.body.appendChild($notification);
88
+
89
+ // Auto-remove after timeout (unless timeout is 0)
90
+ if (timeout > 0) {
91
+ setTimeout(() => {
92
+ $notification.remove();
93
+ }, timeout);
94
+ }
95
+ }
96
+
97
+ // Get context information
98
+ export function getContext() {
99
+ // Check if mobile
100
+ function isMobile() {
101
+ try {
102
+ // Try modern API first
103
+ const m = navigator.userAgentData?.mobile;
104
+ if (typeof m !== 'undefined') {
105
+ return m === true;
106
+ }
107
+ } catch (e) {
108
+ // Silent fail
109
+ }
110
+
111
+ // Fallback to media query
112
+ try {
113
+ return window.matchMedia('(max-width: 767px)').matches;
114
+ } catch (e) {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ // Return context information
120
+ return {
121
+ client: {
122
+ mobile: isMobile(),
123
+ },
124
+ browser: {
125
+ userAgent: navigator.userAgent,
126
+ language: navigator.language,
127
+ platform: navigator.platform,
128
+ vendor: navigator.vendor,
129
+ },
130
+ screen: {
131
+ width: window.screen?.width,
132
+ height: window.screen?.height,
133
+ availWidth: window.screen?.availWidth,
134
+ availHeight: window.screen?.availHeight,
135
+ colorDepth: window.screen?.colorDepth,
136
+ pixelRatio: window.devicePixelRatio || 1,
137
+ },
138
+ viewport: {
139
+ width: window.innerWidth,
140
+ height: window.innerHeight,
141
+ }
142
+ };
143
+ }
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "web-manager",
3
- "version": "3.2.75",
3
+ "version": "4.0.0",
4
4
  "description": "Easily access important variables such as the query string, current domain, and current page in a single object.",
5
- "main": "index.js",
5
+ "main": "dist/index.js",
6
+ "module": "src/index.js",
6
7
  "scripts": {
7
8
  "test": "./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
8
9
  "test_": "npm run prepare && ./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
9
10
  "prepare_": "node -e 'require(`prepare-package`)()'",
10
11
  "prepare": "node -e \"require('prepare-package')()\"",
11
- "prepare:watch": "nodemon -w ./src -e '*' --exec 'npm run prepare'"
12
+ "prepare:watch": "node -e \"require('prepare-package/watch')()\""
12
13
  },
13
14
  "engines": {
14
15
  "node": ">=12"
@@ -40,15 +41,15 @@
40
41
  "replace": {}
41
42
  },
42
43
  "dependencies": {
43
- "@sentry/browser": "^8.54.0",
44
- "cookieconsent": "^3.1.1",
45
- "firebase": "^9.23.0",
44
+ "@sentry/browser": "^10.8.0",
45
+ "firebase": "^12.2.1",
46
46
  "itwcw-package-analytics": "^1.0.6",
47
- "lazysizes": "^5.3.2"
47
+ "lodash": "^4.17.21",
48
+ "resolve-account": "^2.0.0"
48
49
  },
49
50
  "devDependencies": {
50
51
  "lodash": "^4.17.21",
51
52
  "mocha": "^8.4.0",
52
- "prepare-package": "^1.1.13"
53
+ "prepare-package": "^1.2.2"
53
54
  }
54
55
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes