web-manager 4.3.1 → 4.3.3

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,196 @@
1
+ class ServiceWorker {
2
+ constructor(manager) {
3
+ this.manager = manager;
4
+ this._registration = null;
5
+ this._messageHandlers = new Map();
6
+ }
7
+
8
+ // Check if service workers are supported
9
+ isSupported() {
10
+ return 'serviceWorker' in navigator;
11
+ }
12
+
13
+ // Return promise that resolves when service worker is ready
14
+ async ready() {
15
+ if (!this.isSupported()) {
16
+ throw new Error('Service Workers not supported');
17
+ }
18
+
19
+ // If already registered and active
20
+ if (this._registration?.active) {
21
+ return this._registration;
22
+ }
23
+
24
+ // Wait for service worker to be ready
25
+ const registration = await navigator.serviceWorker.ready;
26
+ return registration;
27
+ }
28
+
29
+ // Register service worker
30
+ async register(options = {}) {
31
+ try {
32
+ if (!this.isSupported()) {
33
+ console.warn('Service Workers are not supported');
34
+ return null;
35
+ }
36
+
37
+ const swPath = options.path || this.manager.config.serviceWorker?.config?.path || '/service-worker.js';
38
+ const scope = options.scope || '/';
39
+
40
+ // Build config object to pass to service worker
41
+ const config = {
42
+ brand: this.manager.config.brand?.id,
43
+ environment: this.manager.config.environment,
44
+ buildTime: this.manager.config.buildTime,
45
+ firebase: this.manager._resolveFirebaseConfig()
46
+ };
47
+
48
+ // Register service worker
49
+ const registration = await navigator.serviceWorker.register(swPath, {
50
+ scope,
51
+ updateViaCache: 'none'
52
+ });
53
+
54
+ // Store registration
55
+ this._registration = registration;
56
+ this.manager.state.serviceWorker = registration;
57
+
58
+ // Wait for service worker to be ready and send config
59
+ await navigator.serviceWorker.ready;
60
+
61
+ // Send config to active service worker
62
+ // Removed due to issues init'ing firebase asynchronously in SW (now config is fetched directly in SW)
63
+ // if (registration.active) {
64
+ // try {
65
+ // this.postMessage({
66
+ // command: 'update-config',
67
+ // payload: config
68
+ // });
69
+ // } catch (error) {
70
+ // console.warn('Could not send config to service worker:', error);
71
+ // }
72
+ // }
73
+
74
+ // Resolve with registration
75
+ return registration;
76
+ } catch (error) {
77
+ console.error('Service Worker registration failed:', error);
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ // Get current registration
83
+ getRegistration() {
84
+ return this._registration;
85
+ }
86
+
87
+ // Post message to service worker
88
+ postMessage(message, options = {}) {
89
+ return new Promise((resolve, reject) => {
90
+ // Check support
91
+ if (!this.isSupported()) {
92
+ return reject(new Error('Service Workers not supported'));
93
+ }
94
+
95
+ // Get active service worker
96
+ const controller = this._registration?.active || navigator.serviceWorker.controller;
97
+
98
+ if (!controller) {
99
+ return reject(new Error('No active service worker'));
100
+ }
101
+
102
+ // Create message channel for two-way communication
103
+ const messageChannel = new MessageChannel();
104
+ const timeout = options.timeout || 5000;
105
+ let timeoutId;
106
+
107
+ // Set up timeout to prevent hanging
108
+ if (timeout > 0) {
109
+ timeoutId = setTimeout(() => {
110
+ messageChannel.port1.close();
111
+ reject(new Error('Service worker message timeout'));
112
+ }, timeout);
113
+ }
114
+
115
+ // Listen for response from service worker
116
+ messageChannel.port1.onmessage = (event) => {
117
+ clearTimeout(timeoutId);
118
+
119
+ if (event.data.error) {
120
+ reject(new Error(event.data.error));
121
+ } else {
122
+ resolve(event.data);
123
+ }
124
+ };
125
+
126
+ // Send message with port for reply
127
+ controller.postMessage(message, [messageChannel.port2]);
128
+ });
129
+ }
130
+
131
+ // Listen for messages from service worker
132
+ onMessage(type, handler) {
133
+ if (!this.isSupported()) {
134
+ return () => {};
135
+ }
136
+
137
+ // Store handler
138
+ if (!this._messageHandlers.has(type)) {
139
+ this._messageHandlers.set(type, new Set());
140
+ }
141
+ this._messageHandlers.get(type).add(handler);
142
+
143
+ // Set up global message listener if not already done
144
+ if (this._messageHandlers.size === 1) {
145
+ navigator.serviceWorker.addEventListener('message', this._handleMessage.bind(this));
146
+ }
147
+
148
+ // Return unsubscribe function
149
+ return () => {
150
+ const handlers = this._messageHandlers.get(type);
151
+ if (handlers) {
152
+ handlers.delete(handler);
153
+ if (handlers.size === 0) {
154
+ this._messageHandlers.delete(type);
155
+ }
156
+ }
157
+ };
158
+ }
159
+
160
+ // Get service worker state
161
+ getState() {
162
+ if (!this._registration) {
163
+ return 'none';
164
+ }
165
+
166
+ if (this._registration.installing) {
167
+ return 'installing';
168
+ } else if (this._registration.waiting) {
169
+ return 'waiting';
170
+ } else if (this._registration.active) {
171
+ return 'active';
172
+ }
173
+
174
+ return 'unknown';
175
+ }
176
+
177
+ // Private: Handle incoming messages
178
+ _handleMessage(event) {
179
+ const { type, ...data } = event.data || {};
180
+
181
+ if (!type) return;
182
+
183
+ const handlers = this._messageHandlers.get(type);
184
+ if (handlers) {
185
+ handlers.forEach(handler => {
186
+ try {
187
+ handler(data, event);
188
+ } catch (error) {
189
+ console.error('Message handler error:', error);
190
+ }
191
+ });
192
+ }
193
+ }
194
+ }
195
+
196
+ export default ServiceWorker;
@@ -0,0 +1,133 @@
1
+ import lodash from 'lodash';
2
+ const { get: _get, set: _set } = lodash;
3
+
4
+ class Storage {
5
+ constructor() {
6
+ this.storageKey = '_manager';
7
+ this.pseudoStorage = {};
8
+ }
9
+
10
+ get(path, defaultValue) {
11
+ let usableStorage;
12
+
13
+ // Try to parse the localStorage object
14
+ try {
15
+ usableStorage = JSON.parse(window.localStorage.getItem(this.storageKey) || '{}');
16
+ } catch (e) {
17
+ usableStorage = this.pseudoStorage;
18
+ }
19
+
20
+ // If there's no path, return the entire storage object
21
+ if (!path) {
22
+ return usableStorage || defaultValue;
23
+ }
24
+
25
+ // Return the value at the path
26
+ return _get(usableStorage, path, defaultValue);
27
+ }
28
+
29
+ set(path, value) {
30
+ let usableStorage;
31
+
32
+ // Try to get the current storage
33
+ try {
34
+ usableStorage = this.get();
35
+ } catch (e) {
36
+ usableStorage = this.pseudoStorage;
37
+ }
38
+
39
+ // If there's no path, replace the entire storage object
40
+ if (!path) {
41
+ usableStorage = value || {};
42
+ } else {
43
+ // Set the value at the path
44
+ _set(usableStorage, path, value);
45
+ }
46
+
47
+ // Try to set the localStorage object
48
+ try {
49
+ window.localStorage.setItem(this.storageKey, JSON.stringify(usableStorage));
50
+ } catch (e) {
51
+ this.pseudoStorage = usableStorage;
52
+ }
53
+
54
+ return usableStorage;
55
+ }
56
+
57
+ remove(path) {
58
+ if (!path) {
59
+ this.clear();
60
+ } else {
61
+ this.set(path, undefined);
62
+ }
63
+ }
64
+
65
+ clear() {
66
+ try {
67
+ window.localStorage.setItem(this.storageKey, '{}');
68
+ } catch (e) {
69
+ this.pseudoStorage = {};
70
+ }
71
+ }
72
+
73
+ // Session storage methods
74
+ session = {
75
+ get: (path, defaultValue) => {
76
+ let usableStorage;
77
+
78
+ try {
79
+ usableStorage = JSON.parse(window.sessionStorage.getItem(this.storageKey) || '{}');
80
+ } catch (e) {
81
+ return defaultValue;
82
+ }
83
+
84
+ if (!path) {
85
+ return usableStorage || defaultValue;
86
+ }
87
+
88
+ return _get(usableStorage, path, defaultValue);
89
+ },
90
+
91
+ set: (path, value) => {
92
+ let usableStorage;
93
+
94
+ try {
95
+ usableStorage = this.session.get();
96
+ } catch (e) {
97
+ usableStorage = {};
98
+ }
99
+
100
+ if (!path) {
101
+ usableStorage = value || {};
102
+ } else {
103
+ _set(usableStorage, path, value);
104
+ }
105
+
106
+ try {
107
+ window.sessionStorage.setItem(this.storageKey, JSON.stringify(usableStorage));
108
+ } catch (e) {
109
+ // Silent fail
110
+ }
111
+
112
+ return usableStorage;
113
+ },
114
+
115
+ remove: (path) => {
116
+ if (!path) {
117
+ this.session.clear();
118
+ } else {
119
+ this.session.set(path, undefined);
120
+ }
121
+ },
122
+
123
+ clear: () => {
124
+ try {
125
+ window.sessionStorage.setItem(this.storageKey, '{}');
126
+ } catch (e) {
127
+ // Silent fail
128
+ }
129
+ }
130
+ };
131
+ }
132
+
133
+ export default Storage;
@@ -0,0 +1,264 @@
1
+ // Unit multipliers
2
+ const UNITS = {
3
+ milliseconds: 1,
4
+ seconds: 1000,
5
+ minutes: 1000 * 60,
6
+ hours: 1000 * 60 * 60,
7
+ days: 1000 * 60 * 60 * 24,
8
+ };
9
+
10
+ // Storage key
11
+ const STORAGE_KEY = 'wm_usage';
12
+
13
+ // Session timeout (30 minutes of inactivity = new session)
14
+ const SESSION_TIMEOUT = 30 * 60 * 1000;
15
+
16
+ class Usage {
17
+ constructor(manager) {
18
+ this.manager = manager;
19
+ this.data = null;
20
+ this.initialized = false;
21
+ this.isNewVersion = false;
22
+ }
23
+
24
+ // Check if we're in a browser extension context
25
+ _isExtension() {
26
+ return this.manager.utilities().getRuntime() === 'browser-extension';
27
+ }
28
+
29
+ // Get extension storage API
30
+ _getExtensionStorage() {
31
+ if (typeof chrome !== 'undefined' && chrome.storage?.local) {
32
+ return chrome.storage.local;
33
+ }
34
+ if (typeof browser !== 'undefined' && browser.storage?.local) {
35
+ return browser.storage.local;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ // Initialize - loads or creates usage data (async for extensions)
41
+ async initialize() {
42
+ // Skip if already initialized
43
+ if (this.initialized) {
44
+ return this.data;
45
+ }
46
+
47
+ // Load existing data based on runtime
48
+ let existing = null;
49
+
50
+ if (this._isExtension()) {
51
+ existing = await this._loadFromExtensionStorage();
52
+ } else {
53
+ existing = this._loadFromLocalStorage();
54
+ }
55
+
56
+ const now = Date.now();
57
+ const currentVersion = this.manager.config?.version || null;
58
+
59
+ if (existing) {
60
+ this.data = existing;
61
+
62
+ // Check if this is a new session (last activity was more than SESSION_TIMEOUT ago)
63
+ const timeSinceLastActive = now - (this.data.lastActive || 0);
64
+ if (timeSinceLastActive > SESSION_TIMEOUT) {
65
+ this.data.session.count = (this.data.session?.count || 0) + 1;
66
+ this.data.session.started = now;
67
+ }
68
+
69
+ // Update lastActive
70
+ this.data.lastActive = now;
71
+
72
+ // Check for version change
73
+ if (currentVersion && this.data.version?.current !== currentVersion) {
74
+ this.data.version = this.data.version || {};
75
+ this.data.version.previous = this.data.version.current;
76
+ this.data.version.current = currentVersion;
77
+ this.isNewVersion = true;
78
+ }
79
+
80
+ await this._save();
81
+ } else {
82
+ // First time usage
83
+ this.data = {
84
+ installed: now,
85
+ lastActive: now,
86
+ session: {
87
+ started: now,
88
+ count: 1,
89
+ },
90
+ version: {
91
+ initial: currentVersion,
92
+ current: currentVersion,
93
+ previous: null,
94
+ },
95
+ };
96
+ await this._save();
97
+ }
98
+
99
+ this.initialized = true;
100
+ return this.data;
101
+ }
102
+
103
+ // Load from extension storage (async)
104
+ async _loadFromExtensionStorage() {
105
+ const storage = this._getExtensionStorage();
106
+ if (!storage) {
107
+ return null;
108
+ }
109
+
110
+ try {
111
+ const result = await storage.get(STORAGE_KEY);
112
+ return result[STORAGE_KEY] || null;
113
+ } catch (e) {
114
+ console.warn('[Usage] Failed to load from extension storage:', e);
115
+ return null;
116
+ }
117
+ }
118
+
119
+ // Load from localStorage (sync)
120
+ _loadFromLocalStorage() {
121
+ try {
122
+ const data = localStorage.getItem(STORAGE_KEY);
123
+ return data ? JSON.parse(data) : null;
124
+ } catch (e) {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ // Save data to storage
130
+ async _save() {
131
+ if (this._isExtension()) {
132
+ await this._saveToExtensionStorage();
133
+ } else {
134
+ this._saveToLocalStorage();
135
+ }
136
+
137
+ return this.data;
138
+ }
139
+
140
+ // Save to extension storage (async)
141
+ async _saveToExtensionStorage() {
142
+ const storage = this._getExtensionStorage();
143
+ if (!storage) {
144
+ return;
145
+ }
146
+
147
+ try {
148
+ await storage.set({ [STORAGE_KEY]: this.data });
149
+ } catch (e) {
150
+ console.warn('[Usage] Failed to save to extension storage:', e);
151
+ }
152
+ }
153
+
154
+ // Save to localStorage (sync)
155
+ _saveToLocalStorage() {
156
+ try {
157
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(this.data));
158
+ } catch (e) {
159
+ // localStorage not available
160
+ }
161
+ }
162
+
163
+ // Calculate duration from a timestamp in specified units
164
+ _calculateDuration(timestamp, unit) {
165
+ if (!timestamp) {
166
+ return 0;
167
+ }
168
+
169
+ // Default to milliseconds
170
+ unit = unit || 'milliseconds';
171
+
172
+ // Get multiplier
173
+ const multiplier = UNITS[unit];
174
+ if (!multiplier) {
175
+ throw new Error(`Invalid unit: ${unit}. Valid units: ${Object.keys(UNITS).join(', ')}`);
176
+ }
177
+
178
+ return (Date.now() - timestamp) / multiplier;
179
+ }
180
+
181
+ // Get total usage duration in specified units (since installed)
182
+ getUsageDuration(unit) {
183
+ return this._calculateDuration(this.data?.installed, unit);
184
+ }
185
+
186
+ // Get current session duration in specified units
187
+ getSessionDuration(unit) {
188
+ return this._calculateDuration(this.data?.session?.started, unit);
189
+ }
190
+
191
+ // Get installed date
192
+ getInstalledDate() {
193
+ if (!this.data?.installed) {
194
+ return null;
195
+ }
196
+
197
+ return new Date(this.data.installed);
198
+ }
199
+
200
+ // Get session count
201
+ getSessionCount() {
202
+ return this.data?.session?.count || 0;
203
+ }
204
+
205
+ // Reset usage data (for testing or user request)
206
+ async reset() {
207
+ const now = Date.now();
208
+ const currentVersion = this.manager.config?.version || null;
209
+
210
+ this.data = {
211
+ installed: now,
212
+ lastActive: now,
213
+ session: {
214
+ started: now,
215
+ count: 1,
216
+ },
217
+ version: {
218
+ initial: currentVersion,
219
+ current: currentVersion,
220
+ previous: null,
221
+ },
222
+ };
223
+
224
+ this.isNewVersion = false;
225
+ await this._save();
226
+ return this.data;
227
+ }
228
+
229
+ // Get binding-friendly data object for bindings system
230
+ getBindingData() {
231
+ return {
232
+ installed: this.data?.installed || null,
233
+ lastActive: this.data?.lastActive || null,
234
+ session: {
235
+ started: this.data?.session?.started || null,
236
+ count: this.getSessionCount(),
237
+ },
238
+ version: {
239
+ initial: this.data?.version?.initial || null,
240
+ current: this.data?.version?.current || null,
241
+ previous: this.data?.version?.previous || null,
242
+ isNew: this.isNewVersion,
243
+ },
244
+ duration: {
245
+ total: {
246
+ milliseconds: this.getUsageDuration('milliseconds'),
247
+ seconds: this.getUsageDuration('seconds'),
248
+ minutes: this.getUsageDuration('minutes'),
249
+ hours: this.getUsageDuration('hours'),
250
+ days: this.getUsageDuration('days'),
251
+ },
252
+ session: {
253
+ milliseconds: this.getSessionDuration('milliseconds'),
254
+ seconds: this.getSessionDuration('seconds'),
255
+ minutes: this.getSessionDuration('minutes'),
256
+ hours: this.getSessionDuration('hours'),
257
+ days: this.getSessionDuration('days'),
258
+ },
259
+ },
260
+ };
261
+ }
262
+ }
263
+
264
+ export default Usage;