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.
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
+