web-manager 3.2.74 → 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.
- package/CLAUDE.md +42 -0
- package/README.md +63 -0
- package/TODO.md +14 -0
- package/_legacy/test/test.js +158 -0
- package/dist/index.js +540 -0
- package/dist/modules/auth.js +256 -0
- package/dist/modules/bindings.js +183 -0
- package/dist/modules/dom.js +102 -0
- package/dist/modules/firestore.js +242 -0
- package/dist/modules/notifications.js +285 -0
- package/dist/modules/sentry.js +166 -0
- package/dist/modules/service-worker.js +321 -0
- package/dist/modules/storage.js +132 -0
- package/dist/modules/utilities.js +143 -0
- package/package.json +9 -8
- /package/{helpers → _legacy/helpers}/auth-pages.js +0 -0
- /package/{index.js → _legacy/index.js} +0 -0
- /package/{lib → _legacy/lib}/account.js +0 -0
- /package/{lib → _legacy/lib}/debug.js +0 -0
- /package/{lib → _legacy/lib}/dom.js +0 -0
- /package/{lib → _legacy/lib}/require.js +0 -0
- /package/{lib → _legacy/lib}/storage.js +0 -0
- /package/{lib → _legacy/lib}/utilities.js +0 -0
package/dist/index.js
ADDED
@@ -0,0 +1,540 @@
|
|
1
|
+
import Storage from './modules/storage.js';
|
2
|
+
import * as utilities from './modules/utilities.js';
|
3
|
+
import * as domUtils from './modules/dom.js';
|
4
|
+
import Auth from './modules/auth.js';
|
5
|
+
import Bindings from './modules/bindings.js';
|
6
|
+
import Firestore from './modules/firestore.js';
|
7
|
+
import Notifications from './modules/notifications.js';
|
8
|
+
import ServiceWorker from './modules/service-worker.js';
|
9
|
+
import Sentry from './modules/sentry.js';
|
10
|
+
class Manager {
|
11
|
+
constructor() {
|
12
|
+
// Configuration from init()
|
13
|
+
this.config = {};
|
14
|
+
|
15
|
+
// Runtime state
|
16
|
+
this.state = {
|
17
|
+
serviceWorker: null
|
18
|
+
};
|
19
|
+
|
20
|
+
// Track Firebase auth initialization
|
21
|
+
this._firebaseAuthInitialized = false;
|
22
|
+
|
23
|
+
// Initialize modules
|
24
|
+
this._storage = new Storage();
|
25
|
+
this._auth = new Auth(this);
|
26
|
+
this._bindings = new Bindings(this);
|
27
|
+
this._firestore = new Firestore(this);
|
28
|
+
this._notifications = new Notifications(this);
|
29
|
+
this._serviceWorker = new ServiceWorker(this);
|
30
|
+
this._sentry = new Sentry(this);
|
31
|
+
}
|
32
|
+
|
33
|
+
// Module getters
|
34
|
+
storage() {
|
35
|
+
return this._storage;
|
36
|
+
}
|
37
|
+
|
38
|
+
auth() {
|
39
|
+
return this._auth;
|
40
|
+
}
|
41
|
+
|
42
|
+
bindings() {
|
43
|
+
return this._bindings;
|
44
|
+
}
|
45
|
+
|
46
|
+
firestore() {
|
47
|
+
return this._firestore;
|
48
|
+
}
|
49
|
+
|
50
|
+
notifications() {
|
51
|
+
return this._notifications;
|
52
|
+
}
|
53
|
+
|
54
|
+
serviceWorker() {
|
55
|
+
return this._serviceWorker;
|
56
|
+
}
|
57
|
+
|
58
|
+
sentry() {
|
59
|
+
return this._sentry;
|
60
|
+
}
|
61
|
+
|
62
|
+
// DOM utilities
|
63
|
+
dom() {
|
64
|
+
return domUtils;
|
65
|
+
}
|
66
|
+
|
67
|
+
utilities() {
|
68
|
+
return utilities;
|
69
|
+
}
|
70
|
+
|
71
|
+
// Initialize the manager
|
72
|
+
async initialize(configuration) {
|
73
|
+
try {
|
74
|
+
// Store configuration as-is
|
75
|
+
this.config = this._processConfiguration(configuration);
|
76
|
+
|
77
|
+
// Initialize Firebase if enabled
|
78
|
+
if (this.config.firebase?.app?.enabled) {
|
79
|
+
await this._initializeFirebase();
|
80
|
+
}
|
81
|
+
|
82
|
+
// Initialize Sentry if enabled
|
83
|
+
if (this.config.sentry?.enabled) {
|
84
|
+
await this._sentry.init(this.config.sentry.config);
|
85
|
+
}
|
86
|
+
|
87
|
+
// Initialize service worker if enabled
|
88
|
+
if (this.config.serviceWorker?.enabled) {
|
89
|
+
await this._serviceWorker.register({
|
90
|
+
path: this.config.serviceWorker?.config?.path
|
91
|
+
});
|
92
|
+
}
|
93
|
+
|
94
|
+
// Start version checking if enabled
|
95
|
+
if (this.config.refreshNewVersion?.enabled) {
|
96
|
+
this._startVersionCheck();
|
97
|
+
}
|
98
|
+
|
99
|
+
// Set up auth event listeners (uses event delegation, no need to wait for DOM)
|
100
|
+
this._auth.setupEventListeners();
|
101
|
+
|
102
|
+
// Old IE force polyfill
|
103
|
+
// await this._loadPolyfillsIfNeeded();
|
104
|
+
|
105
|
+
return this;
|
106
|
+
} catch (error) {
|
107
|
+
console.error('Manager initialization error:', error);
|
108
|
+
throw error;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
_processConfiguration(configuration) {
|
113
|
+
// Default configuration structure
|
114
|
+
const defaults = {
|
115
|
+
environment: 'production',
|
116
|
+
buildTime: Date.now(),
|
117
|
+
brand: {
|
118
|
+
id: 'app',
|
119
|
+
name: 'Application',
|
120
|
+
description: '',
|
121
|
+
type: 'Organization',
|
122
|
+
images: {
|
123
|
+
brandmark: '',
|
124
|
+
wordmark: '',
|
125
|
+
combomark: ''
|
126
|
+
},
|
127
|
+
contact: {
|
128
|
+
email: '',
|
129
|
+
phone: '',
|
130
|
+
'slapform-form-id': ''
|
131
|
+
},
|
132
|
+
address: {}
|
133
|
+
},
|
134
|
+
auth: {
|
135
|
+
enabled: true,
|
136
|
+
config: {
|
137
|
+
policy: null,
|
138
|
+
redirects: {
|
139
|
+
authenticated: '/account',
|
140
|
+
unauthenticated: '/signup'
|
141
|
+
}
|
142
|
+
}
|
143
|
+
},
|
144
|
+
firebase: {
|
145
|
+
app: {
|
146
|
+
enabled: true,
|
147
|
+
config: {}
|
148
|
+
},
|
149
|
+
appCheck: {
|
150
|
+
enabled: false,
|
151
|
+
config: {
|
152
|
+
siteKey: ''
|
153
|
+
}
|
154
|
+
}
|
155
|
+
},
|
156
|
+
cookieConsent: {
|
157
|
+
enabled: true,
|
158
|
+
config: {
|
159
|
+
palette: {
|
160
|
+
popup: {
|
161
|
+
background: '#237afc',
|
162
|
+
text: '#fff'
|
163
|
+
},
|
164
|
+
button: {
|
165
|
+
background: '#fff',
|
166
|
+
text: '#237afc'
|
167
|
+
}
|
168
|
+
},
|
169
|
+
theme: 'classic',
|
170
|
+
position: 'bottom-left',
|
171
|
+
// type: '',
|
172
|
+
// showLink: false,
|
173
|
+
content: {
|
174
|
+
message: 'We use cookies to ensure you get the best experience on our website. By continuing to use the site, you agree to our { terms }.',
|
175
|
+
dismiss: 'I Understand'
|
176
|
+
}
|
177
|
+
}
|
178
|
+
},
|
179
|
+
chatsy: {
|
180
|
+
enabled: false,
|
181
|
+
config: {
|
182
|
+
accountId: '',
|
183
|
+
chatId: '',
|
184
|
+
settings: {
|
185
|
+
openChatButton: {
|
186
|
+
background: '#237afc',
|
187
|
+
text: '#fff'
|
188
|
+
}
|
189
|
+
}
|
190
|
+
}
|
191
|
+
},
|
192
|
+
sentry: {
|
193
|
+
enabled: true,
|
194
|
+
config: {
|
195
|
+
dsn: '',
|
196
|
+
release: '',
|
197
|
+
replaysSessionSampleRate: 0.01,
|
198
|
+
replaysOnErrorSampleRate: 0.01
|
199
|
+
}
|
200
|
+
},
|
201
|
+
exitPopup: {
|
202
|
+
enabled: true,
|
203
|
+
config: {
|
204
|
+
timeout: 1000 * 60 * 60 * 4,
|
205
|
+
title: 'Want 15% off?',
|
206
|
+
message: 'Get 15% off your purchase of our Premium plans.',
|
207
|
+
okButton: {
|
208
|
+
text: 'Claim 15% Discount',
|
209
|
+
link: '/pricing'
|
210
|
+
}
|
211
|
+
}
|
212
|
+
},
|
213
|
+
lazyLoading: {
|
214
|
+
enabled: true,
|
215
|
+
config: {
|
216
|
+
selector: '[data-lazy]',
|
217
|
+
rootMargin: '50px 0px', // Start loading 50px before element comes into view
|
218
|
+
threshold: 0.01, // Trigger when 1% of element is visible
|
219
|
+
loadedClass: 'lazy-loaded',
|
220
|
+
loadingClass: 'lazy-loading',
|
221
|
+
errorClass: 'lazy-error'
|
222
|
+
}
|
223
|
+
},
|
224
|
+
socialSharing: {
|
225
|
+
enabled: false,
|
226
|
+
config: {
|
227
|
+
selector: '[data-social-share]',
|
228
|
+
defaultPlatforms: ['facebook', 'twitter', 'linkedin', 'pinterest', 'reddit', 'email', 'copy'],
|
229
|
+
buttonClass: '',
|
230
|
+
showLabels: false,
|
231
|
+
openInNewWindow: true,
|
232
|
+
windowWidth: 600,
|
233
|
+
windowHeight: 400,
|
234
|
+
}
|
235
|
+
},
|
236
|
+
pushNotifications: {
|
237
|
+
enabled: true,
|
238
|
+
config: {
|
239
|
+
autoRequest: 1000 * 60
|
240
|
+
}
|
241
|
+
},
|
242
|
+
validRedirectHosts: [],
|
243
|
+
|
244
|
+
// Non-configurable defaults
|
245
|
+
refreshNewVersion: {
|
246
|
+
enabled: true,
|
247
|
+
config: {
|
248
|
+
interval: 1000 * 60 * 60, // Check every hour
|
249
|
+
}
|
250
|
+
},
|
251
|
+
serviceWorker: {
|
252
|
+
enabled: true,
|
253
|
+
config: {
|
254
|
+
path: '/service-worker.js'
|
255
|
+
}
|
256
|
+
},
|
257
|
+
};
|
258
|
+
|
259
|
+
// Deep merge configuration with defaults
|
260
|
+
const merged = this._deepMerge(defaults, configuration);
|
261
|
+
|
262
|
+
// Evaluate string expressions for timeout values
|
263
|
+
if (merged.exitPopup?.config?.timeout) {
|
264
|
+
merged.exitPopup.config.timeout = safeEvaluate(merged.exitPopup.config.timeout);
|
265
|
+
}
|
266
|
+
|
267
|
+
if (merged.pushNotifications?.config?.autoRequest) {
|
268
|
+
merged.pushNotifications.config.autoRequest = safeEvaluate(merged.pushNotifications.config.autoRequest);
|
269
|
+
}
|
270
|
+
|
271
|
+
if (merged.refreshNewVersion?.config?.interval) {
|
272
|
+
merged.refreshNewVersion.config.interval = safeEvaluate(merged.refreshNewVersion.config.interval);
|
273
|
+
}
|
274
|
+
|
275
|
+
// Return merged configuration
|
276
|
+
return merged;
|
277
|
+
}
|
278
|
+
|
279
|
+
_deepMerge(target, source) {
|
280
|
+
const output = Object.assign({}, target);
|
281
|
+
if (isObject(target) && isObject(source)) {
|
282
|
+
Object.keys(source).forEach(key => {
|
283
|
+
if (isObject(source[key])) {
|
284
|
+
if (!(key in target))
|
285
|
+
Object.assign(output, { [key]: source[key] });
|
286
|
+
else
|
287
|
+
output[key] = this._deepMerge(target[key], source[key]);
|
288
|
+
} else {
|
289
|
+
Object.assign(output, { [key]: source[key] });
|
290
|
+
}
|
291
|
+
});
|
292
|
+
}
|
293
|
+
return output;
|
294
|
+
|
295
|
+
function isObject(item) {
|
296
|
+
return item && typeof item === 'object' && !Array.isArray(item);
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
async _initializeFirebase() {
|
301
|
+
const firebaseConfig = this.config.firebase.app.config;
|
302
|
+
|
303
|
+
// Dynamically import Firebase v12
|
304
|
+
const { initializeApp } = await import('firebase/app');
|
305
|
+
const { getAuth, onAuthStateChanged } = await import('firebase/auth');
|
306
|
+
const { getFirestore } = await import('firebase/firestore');
|
307
|
+
const { getMessaging } = await import('firebase/messaging');
|
308
|
+
|
309
|
+
// Initialize Firebase
|
310
|
+
const app = initializeApp(firebaseConfig);
|
311
|
+
|
312
|
+
// Store Firebase references
|
313
|
+
this._firebaseApp = app;
|
314
|
+
this._firebaseAuth = getAuth(app);
|
315
|
+
this._firebaseFirestore = getFirestore(app);
|
316
|
+
|
317
|
+
// Only initialize messaging if service workers are supported
|
318
|
+
if ('serviceWorker' in navigator) {
|
319
|
+
this._firebaseMessaging = getMessaging(app);
|
320
|
+
} else {
|
321
|
+
console.warn('Service workers not available - Firebase Messaging disabled');
|
322
|
+
this._firebaseMessaging = null;
|
323
|
+
}
|
324
|
+
|
325
|
+
// Initialize Firebase App Check if enabled
|
326
|
+
if (this.config.firebase.appCheck?.enabled) {
|
327
|
+
const { initializeAppCheck, ReCaptchaEnterpriseProvider } = await import('firebase/app-check');
|
328
|
+
const siteKey = this.config.firebase.appCheck.config.siteKey;
|
329
|
+
|
330
|
+
if (siteKey) {
|
331
|
+
initializeAppCheck(app, {
|
332
|
+
provider: new ReCaptchaEnterpriseProvider(siteKey),
|
333
|
+
isTokenAutoRefreshEnabled: true
|
334
|
+
});
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
338
|
+
// Setup auth state listener
|
339
|
+
onAuthStateChanged(this._firebaseAuth, (user) => {
|
340
|
+
// Mark auth as initialized after first callback
|
341
|
+
this._firebaseAuthInitialized = true;
|
342
|
+
|
343
|
+
// Let auth module handle everything including DOM updates
|
344
|
+
this._auth._handleAuthStateChange(user);
|
345
|
+
});
|
346
|
+
}
|
347
|
+
|
348
|
+
// Getters for Firebase services
|
349
|
+
get firebaseApp() { return this._firebaseApp; }
|
350
|
+
get firebaseAuth() { return this._firebaseAuth; }
|
351
|
+
get firebaseFirestore() { return this._firebaseFirestore; }
|
352
|
+
get firebaseMessaging() { return this._firebaseMessaging; }
|
353
|
+
|
354
|
+
isDevelopment() {
|
355
|
+
return this.config.environment === 'development';
|
356
|
+
}
|
357
|
+
|
358
|
+
getFunctionsUrl(environment) {
|
359
|
+
const env = environment || this.config.environment;
|
360
|
+
const projectId = this.config.firebase?.app?.config?.projectId;
|
361
|
+
|
362
|
+
if (!projectId) {
|
363
|
+
throw new Error('Firebase project ID not configured');
|
364
|
+
}
|
365
|
+
|
366
|
+
if (env === 'development') {
|
367
|
+
return 'http://localhost:5001/' + projectId + '/us-central1';
|
368
|
+
}
|
369
|
+
|
370
|
+
return 'https://us-central1-' + projectId + '.cloudfunctions.net';
|
371
|
+
}
|
372
|
+
|
373
|
+
getApiUrl(environment, url) {
|
374
|
+
const env = environment || this.config.environment;
|
375
|
+
|
376
|
+
if (env === 'development') {
|
377
|
+
return 'http://localhost:5002';
|
378
|
+
}
|
379
|
+
|
380
|
+
const baseUrl = url || window.location.origin;
|
381
|
+
const urlObj = new URL(baseUrl);
|
382
|
+
const hostnameParts = urlObj.hostname.split('.');
|
383
|
+
|
384
|
+
if (hostnameParts.length > 2) {
|
385
|
+
hostnameParts[0] = 'api';
|
386
|
+
} else {
|
387
|
+
hostnameParts.unshift('api');
|
388
|
+
}
|
389
|
+
|
390
|
+
urlObj.hostname = hostnameParts.join('.');
|
391
|
+
|
392
|
+
return urlObj.toString();
|
393
|
+
}
|
394
|
+
|
395
|
+
isValidRedirectUrl(url) {
|
396
|
+
try {
|
397
|
+
const returnUrlObject = new URL(decodeURIComponent(url));
|
398
|
+
const currentUrlObject = new URL(window.location.href);
|
399
|
+
|
400
|
+
return returnUrlObject.host === currentUrlObject.host
|
401
|
+
|| returnUrlObject.protocol === this.config.brand?.id + ':'
|
402
|
+
|| (this.config.validRedirectHosts || []).includes(returnUrlObject.host);
|
403
|
+
} catch (e) {
|
404
|
+
return false;
|
405
|
+
}
|
406
|
+
}
|
407
|
+
|
408
|
+
_startVersionCheck() {
|
409
|
+
// Re-focus events
|
410
|
+
window.addEventListener('focus', () => {
|
411
|
+
this._checkVersion();
|
412
|
+
});
|
413
|
+
|
414
|
+
window.addEventListener('online', () => {
|
415
|
+
this._checkVersion();
|
416
|
+
});
|
417
|
+
|
418
|
+
// Set up interval
|
419
|
+
this._versionCheckInterval = setInterval(() => {
|
420
|
+
this._checkVersion();
|
421
|
+
}, this.config.refreshNewVersion.config.interval);
|
422
|
+
}
|
423
|
+
|
424
|
+
|
425
|
+
// async _loadPolyfillsIfNeeded() {
|
426
|
+
// // Check if polyfills are needed by testing for ES6 features
|
427
|
+
// const featuresPass = (
|
428
|
+
// typeof Symbol !== 'undefined'
|
429
|
+
// );
|
430
|
+
|
431
|
+
// // If all features are supported, no polyfills needed
|
432
|
+
// if (featuresPass) {
|
433
|
+
// return;
|
434
|
+
// }
|
435
|
+
|
436
|
+
// // Load polyfills for older browsers (especially IE)
|
437
|
+
// try {
|
438
|
+
// await domUtils.loadScript({
|
439
|
+
// src: 'https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?flags=always%2Cgated&features=default%2Ces5%2Ces6%2Ces7%2CPromise.prototype.finally%2C%7Ehtml5-elements%2ClocalStorage%2Cfetch%2CURLSearchParams',
|
440
|
+
// crossorigin: 'anonymous'
|
441
|
+
// });
|
442
|
+
// console.log('Polyfills loaded for older browser');
|
443
|
+
// } catch (error) {
|
444
|
+
// console.error('Failed to load polyfills:', error);
|
445
|
+
// // Continue initialization even if polyfills fail to load
|
446
|
+
// }
|
447
|
+
// }
|
448
|
+
|
449
|
+
async _checkVersion() {
|
450
|
+
if (this.isDevelopment()) {
|
451
|
+
return;
|
452
|
+
}
|
453
|
+
|
454
|
+
try {
|
455
|
+
// Try new path first, then fallback to legacy path
|
456
|
+
const paths = ['/build.json', '/@output/build/build.json'];
|
457
|
+
let data = null;
|
458
|
+
let response = null;
|
459
|
+
|
460
|
+
for (const path of paths) {
|
461
|
+
try {
|
462
|
+
response = await fetch(`${path}?cb=${Date.now()}`);
|
463
|
+
if (response.ok) {
|
464
|
+
data = await response.json();
|
465
|
+
break;
|
466
|
+
}
|
467
|
+
} catch (e) {
|
468
|
+
// Continue to next path
|
469
|
+
}
|
470
|
+
}
|
471
|
+
|
472
|
+
if (!data) {
|
473
|
+
throw new Error('Failed to fetch build.json from any path');
|
474
|
+
}
|
475
|
+
|
476
|
+
// Extract timestamp - support both new format (data.timestamp) and legacy format (data['npm-build'].timestamp)
|
477
|
+
let buildTimeLive;
|
478
|
+
if (data.timestamp) {
|
479
|
+
buildTimeLive = new Date(data.timestamp);
|
480
|
+
} else if (data?.['npm-build']?.timestamp) {
|
481
|
+
buildTimeLive = new Date(data['npm-build'].timestamp);
|
482
|
+
} else {
|
483
|
+
throw new Error('No timestamp found in build.json');
|
484
|
+
}
|
485
|
+
|
486
|
+
const buildTimeCurrent = new Date(this.config.buildTime);
|
487
|
+
|
488
|
+
// Add 1 hour to current build time to account for npm build process
|
489
|
+
buildTimeCurrent.setHours(buildTimeCurrent.getHours() + 1);
|
490
|
+
|
491
|
+
// If live version is newer, reload the page
|
492
|
+
if (buildTimeCurrent >= buildTimeLive) {
|
493
|
+
return; // No update needed
|
494
|
+
}
|
495
|
+
|
496
|
+
console.log('New version detected, reloading page...');
|
497
|
+
|
498
|
+
// Force page reload
|
499
|
+
window.onbeforeunload = function () {
|
500
|
+
return undefined;
|
501
|
+
};
|
502
|
+
|
503
|
+
window.location.reload(true);
|
504
|
+
} catch (error) {
|
505
|
+
console.warn('Version check failed:', error);
|
506
|
+
}
|
507
|
+
}
|
508
|
+
}
|
509
|
+
|
510
|
+
// Safely evaluate timeout string expressions
|
511
|
+
const safeEvaluate = (str) => {
|
512
|
+
if (typeof str !== 'string') return str;
|
513
|
+
|
514
|
+
// Only allow numbers, *, +, -, /, parentheses, and whitespace
|
515
|
+
if (!/^[\d\s\*\+\-\/\(\)]+$/.test(str)) {
|
516
|
+
console.warn('Invalid expression format:', str);
|
517
|
+
return str;
|
518
|
+
}
|
519
|
+
|
520
|
+
try {
|
521
|
+
// Use Function constructor instead of eval for safer evaluation
|
522
|
+
return new Function('return ' + str)();
|
523
|
+
} catch (e) {
|
524
|
+
console.warn('Failed to evaluate expression:', str, e);
|
525
|
+
return str;
|
526
|
+
}
|
527
|
+
};
|
528
|
+
|
529
|
+
// Create singleton instance
|
530
|
+
const manager = new Manager();
|
531
|
+
|
532
|
+
// Export for different environments
|
533
|
+
export default manager;
|
534
|
+
export { Manager };
|
535
|
+
|
536
|
+
// For non-ES6 environments
|
537
|
+
if (typeof module !== 'undefined' && module.exports) {
|
538
|
+
module.exports = manager;
|
539
|
+
}
|
540
|
+
|