onelaraveljs 1.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.
Files changed (67) hide show
  1. package/README.md +87 -0
  2. package/docs/integration_analysis.md +116 -0
  3. package/docs/onejs_analysis.md +108 -0
  4. package/docs/optimization_implementation_group2.md +458 -0
  5. package/docs/optimization_plan.md +130 -0
  6. package/index.js +16 -0
  7. package/package.json +13 -0
  8. package/src/app.js +61 -0
  9. package/src/core/API.js +72 -0
  10. package/src/core/ChildrenRegistry.js +410 -0
  11. package/src/core/DOMBatcher.js +207 -0
  12. package/src/core/ErrorBoundary.js +226 -0
  13. package/src/core/EventDelegator.js +416 -0
  14. package/src/core/Helper.js +817 -0
  15. package/src/core/LoopContext.js +97 -0
  16. package/src/core/OneDOM.js +246 -0
  17. package/src/core/OneMarkup.js +444 -0
  18. package/src/core/Router.js +996 -0
  19. package/src/core/SEOConfig.js +321 -0
  20. package/src/core/SectionEngine.js +75 -0
  21. package/src/core/TemplateEngine.js +83 -0
  22. package/src/core/View.js +273 -0
  23. package/src/core/ViewConfig.js +229 -0
  24. package/src/core/ViewController.js +1410 -0
  25. package/src/core/ViewControllerOptimized.js +164 -0
  26. package/src/core/ViewIdentifier.js +361 -0
  27. package/src/core/ViewLoader.js +272 -0
  28. package/src/core/ViewManager.js +1962 -0
  29. package/src/core/ViewState.js +761 -0
  30. package/src/core/ViewSystem.js +301 -0
  31. package/src/core/ViewTemplate.js +4 -0
  32. package/src/core/helpers/BindingHelper.js +239 -0
  33. package/src/core/helpers/ConfigHelper.js +37 -0
  34. package/src/core/helpers/EventHelper.js +172 -0
  35. package/src/core/helpers/LifecycleHelper.js +17 -0
  36. package/src/core/helpers/ReactiveHelper.js +169 -0
  37. package/src/core/helpers/RenderHelper.js +15 -0
  38. package/src/core/helpers/ResourceHelper.js +89 -0
  39. package/src/core/helpers/TemplateHelper.js +11 -0
  40. package/src/core/managers/BindingManager.js +671 -0
  41. package/src/core/managers/ConfigurationManager.js +136 -0
  42. package/src/core/managers/EventManager.js +309 -0
  43. package/src/core/managers/LifecycleManager.js +356 -0
  44. package/src/core/managers/ReactiveManager.js +334 -0
  45. package/src/core/managers/RenderEngine.js +292 -0
  46. package/src/core/managers/ResourceManager.js +441 -0
  47. package/src/core/managers/ViewHierarchyManager.js +258 -0
  48. package/src/core/managers/ViewTemplateManager.js +127 -0
  49. package/src/core/reactive/ReactiveComponent.js +592 -0
  50. package/src/core/services/EventService.js +418 -0
  51. package/src/core/services/HttpService.js +106 -0
  52. package/src/core/services/LoggerService.js +57 -0
  53. package/src/core/services/StateService.js +512 -0
  54. package/src/core/services/StorageService.js +856 -0
  55. package/src/core/services/StoreService.js +258 -0
  56. package/src/core/services/TemplateDetectorService.js +361 -0
  57. package/src/core/services/Test.js +18 -0
  58. package/src/helpers/devWarnings.js +205 -0
  59. package/src/helpers/performance.js +226 -0
  60. package/src/helpers/utils.js +287 -0
  61. package/src/init.js +343 -0
  62. package/src/plugins/auto-plugin.js +34 -0
  63. package/src/services/Test.js +18 -0
  64. package/src/types/index.js +193 -0
  65. package/src/utils/date-helper.js +51 -0
  66. package/src/utils/helpers.js +39 -0
  67. package/src/utils/validation.js +32 -0
@@ -0,0 +1,856 @@
1
+ import logger from "./LoggerService.js";
2
+
3
+ // StorageService - Service thuần túy quản lý localStorage với Event System, TTL và Encrypt
4
+ export class StorageService {
5
+ static instance = null;
6
+ static privateProperties = [
7
+ '__key',
8
+ '__isSupport',
9
+ '__data',
10
+ '__listeners', '__eventQueue', '__isUpdating', 'instance',
11
+ 'set', 'get', 'remove', 'clear', 'getAll', 'has', 'getAllKeys', 'size', 'isEmpty', 'getInfo', 'debug',
12
+ 'export', 'import', 'backup', 'restore', 'getStorageUsage',
13
+ 'isStorageFull',
14
+ 'setKey', 'getKey', 'support', '__loadData', '__updateData', 'emit', 'on', 'off', 'removeAllListeners', 'getEvents', 'getListenerCount',
15
+ 'getTTLInfo', 'cleanExpired', 'enableEncryption', '__generateEncryptionKey',
16
+ '__encrypt', '__decrypt', '__isExpired', '__cleanExpiredData', '__createDynamicProperty', '__removeDynamicProperty'
17
+ ];
18
+
19
+
20
+ constructor(key = 'onejs_storage') {
21
+ this.__key = key || "onejs_storage";
22
+ this.__isSupport = typeof (Storage) !== "undefined";
23
+ this.__data = {};
24
+ this.__listeners = new Map(); // Event listeners
25
+ this.__eventQueue = []; // Event queue for batching
26
+ this.__isUpdating = false;
27
+
28
+ // Encryption system
29
+ this.encryptionKey = null;
30
+ this.useEncryption = false;
31
+ this.dynamicProperties = [];
32
+ if (this.__isSupport) {
33
+ this.__loadData();
34
+ }
35
+ }
36
+
37
+ // ==========================================
38
+ // PUBLIC STATIC METHODS
39
+ // ==========================================
40
+
41
+ /**
42
+ * Get the instance of the StorageService
43
+ * @param {string} key - The key to use for the storage
44
+ * @returns {StorageService} - The instance of the StorageService
45
+ */
46
+ static getInstance(key) {
47
+ if (!StorageService.instance) {
48
+ StorageService.instance = new StorageService(key);
49
+ }
50
+ return StorageService.instance;
51
+ }
52
+
53
+ static make(key) {
54
+ return new StorageService(key);
55
+ }
56
+
57
+ // ==========================================
58
+ // PUBLIC INSTANCE METHODS - Configuration
59
+ // ==========================================
60
+
61
+ /**
62
+ * Set the key for the storage
63
+ * @param {string} key - The key to use for the storage
64
+ * @returns {StorageService} - The instance of the StorageService
65
+ */
66
+ setKey(key) {
67
+ if (typeof key !== 'string' || !key.trim()) {
68
+ throw new Error('Storage key must be a non-empty string');
69
+ }
70
+
71
+ this.__key = key;
72
+ this.__loadData();
73
+ return this;
74
+ }
75
+
76
+ /**
77
+ * Get the current storage key
78
+ * @returns {string} - The current storage key
79
+ */
80
+ getKey() {
81
+ return this.__key;
82
+ }
83
+
84
+ /**
85
+ * Check if the storage is supported
86
+ * @returns {boolean} - True if the storage is supported, false otherwise
87
+ */
88
+ support() {
89
+ return this.__isSupport;
90
+ }
91
+
92
+ /**
93
+ * Enable/disable encryption
94
+ * @param {boolean} enable - Enable encryption
95
+ * @param {string} key - Encryption key (optional, will generate if not provided)
96
+ */
97
+ enableEncryption(enable = true, key = null) {
98
+ this.useEncryption = enable;
99
+
100
+ if (enable && !key) {
101
+ // Generate a random encryption key
102
+ this.encryptionKey = this.__generateEncryptionKey();
103
+ } else if (enable && key) {
104
+ this.encryptionKey = key;
105
+ } else {
106
+ this.encryptionKey = null;
107
+ }
108
+
109
+ logger.log(`🔧 StorageService: Encryption ${enable ? 'enabled' : 'disabled'}`);
110
+ }
111
+
112
+ // ==========================================
113
+ // PUBLIC INSTANCE METHODS - Core Storage Operations
114
+ // ==========================================
115
+
116
+ /**
117
+ * Set the data in the storage with optional TTL
118
+ * @param {string|object} key - The key to use for the storage
119
+ * @param {any} value - The value to use for the storage
120
+ * @param {number|null} ttl - Time to live in seconds (null = no expiration)
121
+ * @returns {boolean} - True if the data is set, false otherwise
122
+ */
123
+ set(key, value, ttl = null) {
124
+ if (!this.__isSupport) return false;
125
+
126
+ if (key === null || key === undefined) {
127
+ throw new Error('Key cannot be null or undefined');
128
+ }
129
+
130
+ if (typeof key === 'object' && key !== null) {
131
+ // Nếu key là object, set nhiều key-value
132
+ let success = true;
133
+ Object.keys(key).forEach(k => {
134
+ const v = key[k];
135
+ if (!this.set(k, v, ttl)) { success = false; }
136
+ })
137
+ return success;
138
+ }
139
+
140
+ if (!(typeof key === 'string' || typeof key === 'number')) {
141
+ throw new Error('Key must be a string when setting single value');
142
+ }
143
+
144
+ // Validate TTL
145
+ if (ttl && (typeof ttl !== 'number' || ttl <= 0)) {
146
+ throw new Error('TTL must be a positive number or null');
147
+ }
148
+
149
+ const oldValue = this.__data[key];
150
+
151
+ // Create data structure with TTL
152
+ const dataItem = {
153
+ value: value,
154
+ timestamp: Date.now()
155
+ };
156
+
157
+ if (ttl !== null) {
158
+ dataItem.ttl = ttl * 1000;
159
+ }
160
+
161
+ this.__data[key] = dataItem;
162
+
163
+ try {
164
+ this.__updateData();
165
+ logger.log(`💾 StorageService: Set ${key}:`, value, ttl ? `(TTL: ${ttl}ms)` : '(no TTL)');
166
+
167
+ this.__createDynamicProperty(key, ttl);
168
+ // Emit events
169
+ this.emit(`set:${key}`, { key, value, oldValue, ttl });
170
+ this.emit('set', { key, value, oldValue, ttl });
171
+
172
+ // Create dynamic property for direct access
173
+
174
+ return true;
175
+ } catch (error) {
176
+ // Revert on error
177
+ this.__data[key] = oldValue;
178
+ throw error;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Get the data from the storage with TTL check
184
+ * @param {string} key - The key to use for the storage
185
+ * @param {any} defaultValue - Default value if key not found or expired
186
+ * @returns {any} - The value of the data or null if expired
187
+ */
188
+ get(key, defaultValue = null) {
189
+ if (!this.__isSupport) return defaultValue;
190
+
191
+ if (typeof key !== 'string') {
192
+ throw new Error('Key must be a string');
193
+ }
194
+
195
+ if (typeof this.__data[key] === 'undefined') return defaultValue;
196
+
197
+ const dataItem = this.__data[key];
198
+
199
+ // Check if data has TTL structure
200
+ if (dataItem && typeof dataItem === 'object' && 'value' in dataItem) {
201
+ // Check if expired
202
+ if (this.__isExpired(dataItem)) {
203
+ logger.log(`⏰ StorageService: Key ${key} has expired, removing...`);
204
+ this.remove(key);
205
+ return defaultValue;
206
+ }
207
+
208
+ logger.log(`📖 StorageService: Retrieved ${key}:`, dataItem.value);
209
+ return dataItem.value;
210
+ } else {
211
+ // Legacy data without TTL structure
212
+ logger.log(`📖 StorageService: Retrieved ${key}:`, dataItem);
213
+ return dataItem;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Remove the data from the storage
219
+ * @param {string} key - The key to use for the storage
220
+ * @returns {boolean} - True if the data is removed, false otherwise
221
+ */
222
+ remove(key) {
223
+ if (!this.__isSupport) return false;
224
+
225
+ if (typeof key !== 'string') {
226
+ throw new Error('Key must be a string');
227
+ }
228
+
229
+ if (Object.keys(this.__data).length === 0 || typeof this.__data[key] === 'undefined') {
230
+ return false;
231
+ }
232
+
233
+ const oldValue = this.__data[key];
234
+ delete this.__data[key];
235
+
236
+ try {
237
+ this.__updateData();
238
+ logger.log('🗑️ StorageService: Removed', key);
239
+
240
+ // Emit events
241
+ this.emit('remove', { key, oldValue });
242
+ this.emit(`remove:${key}`, { key, oldValue });
243
+
244
+ // Remove dynamic property
245
+ this.__removeDynamicProperty(key);
246
+
247
+ return true;
248
+ } catch (error) {
249
+ // Revert on error
250
+ this.__data[key] = oldValue;
251
+ throw error;
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Clear the data from the storage
257
+ */
258
+ clear() {
259
+ const oldData = { ...this.__data };
260
+ this.__data = {};
261
+
262
+ // Remove all dynamic properties
263
+ if (this.dynamicProperties) {
264
+ for (const key of this.dynamicProperties) {
265
+ this.__removeDynamicProperty(key);
266
+ }
267
+ this.dynamicProperties = [];
268
+ }
269
+
270
+ try {
271
+ this.__updateData();
272
+ logger.log('🧹 StorageService: Cleared all data');
273
+
274
+ // Emit events
275
+ this.emit('clear', { oldData });
276
+ } catch (error) {
277
+ // Revert on error
278
+ this.__data = oldData;
279
+ throw error;
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Get all data from the storage (excluding expired items)
285
+ * @returns {object} - All valid data in storage
286
+ */
287
+ getAll() {
288
+ // Clean expired data first
289
+ this.__cleanExpiredData();
290
+
291
+ const result = {};
292
+ for (const [key, dataItem] of Object.entries(this.__data)) {
293
+ if (dataItem && typeof dataItem === 'object' && 'value' in dataItem) {
294
+ result[key] = dataItem.value;
295
+ } else {
296
+ result[key] = dataItem; // Legacy data
297
+ }
298
+ }
299
+ return result;
300
+ }
301
+
302
+ /**
303
+ * Check if key exists and is not expired
304
+ * @param {string} key - The key to check
305
+ * @returns {boolean} - True if key exists and not expired, false otherwise
306
+ */
307
+ has(key) {
308
+ if (typeof key !== 'string') {
309
+ throw new Error('Key must be a string');
310
+ }
311
+
312
+ if (typeof this.__data[key] === 'undefined') return false;
313
+
314
+ const dataItem = this.__data[key];
315
+
316
+ // Check if data has TTL structure
317
+ if (dataItem && typeof dataItem === 'object' && 'value' in dataItem) {
318
+ return !this.__isExpired(dataItem);
319
+ }
320
+
321
+ return true; // Legacy data without TTL
322
+ }
323
+
324
+ /**
325
+ * Get all keys (excluding expired items)
326
+ * @returns {string[]} - Array of all valid keys
327
+ */
328
+ getAllKeys() {
329
+ // Clean expired data first
330
+ this.__cleanExpiredData();
331
+
332
+ return Object.keys(this.__data);
333
+ }
334
+
335
+ /**
336
+ * Get storage size (number of valid keys)
337
+ * @returns {number} - Number of valid keys in storage
338
+ */
339
+ size() {
340
+ // Clean expired data first
341
+ this.__cleanExpiredData();
342
+
343
+ return Object.keys(this.__data).length;
344
+ }
345
+
346
+ /**
347
+ * Check if storage is empty
348
+ * @returns {boolean} - True if storage is empty, false otherwise
349
+ */
350
+ isEmpty() {
351
+ return this.size() === 0;
352
+ }
353
+
354
+ /**
355
+ * Get storage info
356
+ * @returns {object} - Storage information
357
+ */
358
+ getInfo() {
359
+ // Clean expired data first
360
+ const expiredRemoved = this.__cleanExpiredData();
361
+
362
+ return {
363
+ key: this.__key,
364
+ isSupport: this.__isSupport,
365
+ size: this.size(),
366
+ keys: this.getAllKeys(),
367
+ isEmpty: this.isEmpty(),
368
+ events: this.getEvents(),
369
+ totalListeners: Array.from(this.__listeners.values()).reduce((sum, listeners) => sum + listeners.length, 0),
370
+ isUpdating: this.__isUpdating,
371
+ useEncryption: this.useEncryption,
372
+ hasEncryptionKey: !!this.encryptionKey,
373
+ expiredRemoved: expiredRemoved,
374
+ dynamicProperties: this.dynamicProperties ? this.dynamicProperties.length : 0
375
+ };
376
+ }
377
+
378
+ /**
379
+ * Debug storage
380
+ */
381
+ debug() {
382
+ logger.log('🔍 StorageService Debug:', this.getInfo());
383
+
384
+ // Show TTL info for each key
385
+ logger.log('📋 TTL Information:');
386
+ for (const [key, dataItem] of Object.entries(this.__data)) {
387
+ if (dataItem && typeof dataItem === 'object' && 'value' in dataItem) {
388
+ const isExpired = this.__isExpired(dataItem);
389
+ const ttl = dataItem.ttl;
390
+ const remaining = dataItem.ttl ? Math.max(0, (dataItem.timestamp + dataItem.ttl) - Date.now()) : null;
391
+ logger.log(` ${key}: ${isExpired ? 'EXPIRED' : 'VALID'}${dataItem.ttl ? ` (TTL: ${dataItem.ttl}ms, Remaining: ${remaining}ms)` : ' (no TTL)'}`);
392
+ } else {
393
+ logger.log(` ${key}: LEGACY (no TTL)`);
394
+ }
395
+ }
396
+
397
+ // Show dynamic properties
398
+ logger.log('🔧 Dynamic Properties:', this.dynamicProperties || []);
399
+ }
400
+
401
+ // ==========================================
402
+ // PUBLIC INSTANCE METHODS - TTL Operations
403
+ // ==========================================
404
+
405
+ /**
406
+ * Get TTL information for a key
407
+ * @param {string} key - The key to check
408
+ * @returns {object|null} - TTL information or null if not found
409
+ */
410
+ getTTLInfo(key) {
411
+ if (typeof key !== 'string') {
412
+ throw new Error('Key must be a string');
413
+ }
414
+
415
+ if (typeof this.__data[key] === 'undefined') return null;
416
+
417
+ const dataItem = this.__data[key];
418
+
419
+ if (dataItem && typeof dataItem === 'object' && 'value' in dataItem) {
420
+ const isExpired = this.__isExpired(dataItem);
421
+ const remaining = dataItem.ttl ? Math.max(0, (dataItem.timestamp + dataItem.ttl) - Date.now()) : null;
422
+
423
+ return {
424
+ hasTTL: !!dataItem.ttl,
425
+ ttl: dataItem.ttl,
426
+ timestamp: dataItem.timestamp,
427
+ isExpired,
428
+ remaining,
429
+ expiryTime: dataItem.ttl ? dataItem.timestamp + dataItem.ttl : null
430
+ };
431
+ }
432
+
433
+ return null; // Legacy data without TTL
434
+ }
435
+
436
+ /**
437
+ * Clean all expired data
438
+ * @returns {number} - Number of expired items removed
439
+ */
440
+ cleanExpired() {
441
+ return this.__cleanExpiredData();
442
+ }
443
+
444
+ // ==========================================
445
+ // PUBLIC INSTANCE METHODS - Import/Export
446
+ // ==========================================
447
+
448
+ /**
449
+ * Export data to JSON string
450
+ * @returns {string} - JSON string of all data
451
+ */
452
+ export() {
453
+ return JSON.stringify(this.getAll(), null, 2);
454
+ }
455
+
456
+ /**
457
+ * Import data from JSON string
458
+ * @param {string} jsonString - JSON string to import
459
+ * @returns {boolean} - True if import successful, false otherwise
460
+ */
461
+ import(jsonString) {
462
+ if (typeof jsonString !== 'string') {
463
+ throw new Error('JSON string must be a string');
464
+ }
465
+
466
+ try {
467
+ const data = JSON.parse(jsonString);
468
+ const oldData = { ...this.__data };
469
+
470
+ // Convert imported data to TTL structure
471
+ this.__data = {};
472
+ for (const [key, value] of Object.entries(data)) {
473
+ this.__data[key] = {
474
+ value: value,
475
+ timestamp: Date.now()
476
+ };
477
+ }
478
+
479
+ this.__updateData();
480
+
481
+ logger.log('📥 StorageService: Imported data successfully');
482
+
483
+ // Emit events
484
+ this.emit('import', { oldData, newData: data });
485
+
486
+ return true;
487
+ } catch (error) {
488
+ logger.error('❌ StorageService: Failed to import data:', error);
489
+ return false;
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Backup current data
495
+ * @returns {object} - Backup data with timestamp
496
+ */
497
+ backup() {
498
+ return {
499
+ timestamp: Date.now(),
500
+ key: this.__key,
501
+ data: { ...this.__data } // Return copy
502
+ };
503
+ }
504
+
505
+ /**
506
+ * Restore from backup
507
+ * @param {object} backup - Backup data
508
+ * @returns {boolean} - True if restore successful, false otherwise
509
+ */
510
+ restore(backup) {
511
+ if (!backup || typeof backup !== 'object') {
512
+ throw new Error('Backup must be a valid object');
513
+ }
514
+
515
+ if (!backup.data) {
516
+ logger.error('❌ StorageService: Invalid backup data');
517
+ return false;
518
+ }
519
+
520
+ const oldData = { ...this.__data };
521
+ this.__data = { ...backup.data }; // Use copy
522
+
523
+ try {
524
+ this.__updateData();
525
+ logger.log('📤 StorageService: Restored from backup:', backup.timestamp);
526
+
527
+ // Emit events
528
+ this.emit('restore', { oldData, newData: backup.data, backup });
529
+
530
+ return true;
531
+ } catch (error) {
532
+ // Revert on error
533
+ this.__data = oldData;
534
+ throw error;
535
+ }
536
+ }
537
+
538
+ // ==========================================
539
+ // PUBLIC INSTANCE METHODS - Storage Utilities
540
+ // ==========================================
541
+
542
+ /**
543
+ * Get storage usage in bytes
544
+ * @returns {number} - Storage usage in bytes
545
+ */
546
+ getStorageUsage() {
547
+ if (!this.__isSupport) return 0;
548
+
549
+ try {
550
+ const data = localStorage.getItem(this.__key);
551
+ return data ? new Blob([data]).size : 0;
552
+ } catch (error) {
553
+ logger.error('❌ StorageService: Failed to get storage usage:', error);
554
+ return 0;
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Check if storage is full
560
+ * @returns {boolean} - True if storage is full, false otherwise
561
+ */
562
+ isStorageFull() {
563
+ try {
564
+ const testKey = '__storage_test__';
565
+ const testValue = 'x'.repeat(1024); // 1KB test
566
+
567
+ localStorage.setItem(testKey, testValue);
568
+ localStorage.removeItem(testKey);
569
+ return false;
570
+ } catch (error) {
571
+ return true; // Storage is full
572
+ }
573
+ }
574
+
575
+ // ==========================================
576
+ // PUBLIC INSTANCE METHODS - Event System
577
+ // ==========================================
578
+
579
+ /**
580
+ * Add event listener
581
+ * @param {string} event - Event name
582
+ * @param {function} callback - Callback function
583
+ * @returns {function} - Unsubscribe function
584
+ */
585
+ on(event, callback) {
586
+ if (typeof event !== 'string' || !event.trim()) {
587
+ throw new Error('Event name must be a non-empty string');
588
+ }
589
+
590
+ if (typeof callback !== 'function') {
591
+ throw new Error('Callback must be a function');
592
+ }
593
+
594
+ if (!this.__listeners.has(event)) {
595
+ this.__listeners.set(event, []);
596
+ }
597
+
598
+ this.__listeners.get(event).push(callback);
599
+ logger.log(`🎧 StorageService: Added listener for event: ${event}`);
600
+
601
+ // Return unsubscribe function
602
+ return () => this.off(event, callback);
603
+ }
604
+
605
+ /**
606
+ * Remove event listener
607
+ * @param {string} event - Event name
608
+ * @param {function} callback - Callback function
609
+ */
610
+ off(event, callback) {
611
+ if (!this.__listeners.has(event)) return;
612
+
613
+ const listeners = this.__listeners.get(event);
614
+ const index = listeners.indexOf(callback);
615
+ if (index > -1) {
616
+ listeners.splice(index, 1);
617
+ logger.log(`🎧 StorageService: Removed listener for event: ${event}`);
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Remove all listeners for an event
623
+ * @param {string} event - Event name
624
+ */
625
+ removeAllListeners(event) {
626
+ if (event) {
627
+ this.__listeners.delete(event);
628
+ logger.log(`🎧 StorageService: Removed all listeners for event: ${event}`);
629
+ } else {
630
+ this.__listeners.clear();
631
+ logger.log('🎧 StorageService: Removed all listeners');
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Get all registered events
637
+ * @returns {string[]} - Array of event names
638
+ */
639
+ getEvents() {
640
+ return Array.from(this.__listeners.keys());
641
+ }
642
+
643
+ /**
644
+ * Get listener count for an event
645
+ * @param {string} event - Event name
646
+ * @returns {number} - Number of listeners
647
+ */
648
+ getListenerCount(event) {
649
+ return this.__listeners.has(event) ? this.__listeners.get(event).length : 0;
650
+ }
651
+
652
+ // ==========================================
653
+ // PRIVATE METHODS
654
+ // ==========================================
655
+
656
+ /**
657
+ * Generate encryption key
658
+ * @returns {string} - Generated encryption key
659
+ */
660
+ __generateEncryptionKey() {
661
+ const array = new Uint8Array(32);
662
+ crypto.getRandomValues(array);
663
+ return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
664
+ }
665
+
666
+ /**
667
+ * Encrypt data
668
+ * @param {string} data - Data to encrypt
669
+ * @returns {string} - Encrypted data
670
+ */
671
+ __encrypt(data) {
672
+ if (!this.useEncryption || !this.encryptionKey) {
673
+ return data;
674
+ }
675
+
676
+ try {
677
+ // Simple XOR encryption (for development purposes)
678
+ // In production, use proper encryption like AES
679
+ let encrypted = '';
680
+ for (let i = 0; i < data.length; i++) {
681
+ const charCode = data.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length);
682
+ encrypted += String.fromCharCode(charCode);
683
+ }
684
+ return btoa(encrypted); // Base64 encode
685
+ } catch (error) {
686
+ console.error('❌ StorageService: Encryption failed:', error);
687
+ return data;
688
+ }
689
+ }
690
+
691
+ /**
692
+ * Decrypt data
693
+ * @param {string} data - Data to decrypt
694
+ * @returns {string} - Decrypted data
695
+ */
696
+ __decrypt(data) {
697
+ if (!this.useEncryption || !this.encryptionKey) {
698
+ return data;
699
+ }
700
+
701
+ try {
702
+ // Simple XOR decryption (for development purposes)
703
+ const decoded = atob(data); // Base64 decode
704
+ let decrypted = '';
705
+ for (let i = 0; i < decoded.length; i++) {
706
+ const charCode = decoded.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length);
707
+ decrypted += String.fromCharCode(charCode);
708
+ }
709
+ return decrypted;
710
+ } catch (error) {
711
+ console.error('❌ StorageService: Decryption failed:', error);
712
+ return data;
713
+ }
714
+ }
715
+
716
+ /**
717
+ * Check if data is expired
718
+ * @param {object} dataItem - Data item with timestamp and ttl
719
+ * @returns {boolean} - True if expired, false otherwise
720
+ */
721
+ __isExpired(dataItem) {
722
+ if (!dataItem || !dataItem.timestamp || !dataItem.ttl) {
723
+ return false; // No TTL, not expired
724
+ }
725
+
726
+ const now = Date.now();
727
+ const expiryTime = dataItem.timestamp + dataItem.ttl;
728
+ return now > expiryTime;
729
+ }
730
+
731
+ /**
732
+ * Clean expired data
733
+ * @returns {number} - Number of expired items removed
734
+ */
735
+ __cleanExpiredData() {
736
+ let removed = 0;
737
+ const keysToRemove = [];
738
+
739
+ for (const [key, dataItem] of Object.entries(this.__data)) {
740
+ if (this.__isExpired(dataItem)) {
741
+ keysToRemove.push(key);
742
+ }
743
+ }
744
+
745
+ for (const key of keysToRemove) {
746
+ delete this.__data[key];
747
+ removed++;
748
+ logger.log(`🗑️ StorageService: Removed expired key: ${key}`);
749
+ }
750
+
751
+ if (removed > 0) {
752
+ this.__updateData();
753
+ }
754
+
755
+ return removed;
756
+ }
757
+
758
+ /**
759
+ * Load the data from the storage (private method)
760
+ */
761
+ __loadData() {
762
+ if (!this.__isSupport) return;
763
+
764
+ try {
765
+ const data = localStorage.getItem(this.__key);
766
+ if (data) {
767
+ const decryptedData = this.__decrypt(data);
768
+ this.__data = JSON.parse(decryptedData);
769
+
770
+ // Clean expired data on load
771
+ this.__cleanExpiredData();
772
+ }
773
+ } catch (error) {
774
+ console.error('❌ StorageService: Failed to load data:', error);
775
+ this.__data = {};
776
+ }
777
+ }
778
+
779
+ /**
780
+ * Update the data in the storage (private method)
781
+ */
782
+ __updateData() {
783
+ if (!this.__isSupport || this.__isUpdating) return;
784
+
785
+ this.__isUpdating = true;
786
+
787
+ try {
788
+ const jsonData = JSON.stringify(this.__data);
789
+ const encryptedData = this.__encrypt(jsonData);
790
+ localStorage.setItem(this.__key, encryptedData);
791
+ logger.log('💾 StorageService: Updated data for key:', this.__key);
792
+ } catch (error) {
793
+ logger.error('❌ StorageService: Failed to update data:', error);
794
+ throw error;
795
+ } finally {
796
+ this.__isUpdating = false;
797
+ }
798
+ }
799
+
800
+ /**
801
+ * Emit event to all listeners (private method)
802
+ * @param {string} event - Event name
803
+ * @param {any} data - Event data
804
+ */
805
+ emit(event, data) {
806
+ if (!this.__listeners.has(event)) return;
807
+
808
+ const listeners = this.__listeners.get(event);
809
+ listeners.forEach(callback => {
810
+ try {
811
+ callback(data);
812
+ } catch (error) {
813
+ console.error(`❌ StorageService: Error in event listener for ${event}:`, error);
814
+ }
815
+ });
816
+ }
817
+
818
+ /**
819
+ * Create dynamic property for direct access
820
+ * @param {string} key - The key
821
+ * @param {number|null} ttl - TTL value
822
+ */
823
+ __createDynamicProperty(key, ttl) {
824
+ try {
825
+ if (!StorageService.privateProperties.includes(key) && !this.dynamicProperties.includes(key)) {
826
+ this.dynamicProperties.push(key);
827
+ Object.defineProperty(this, key, {
828
+ set: (value) => this.set(key, value, ttl),
829
+ get: () => this.get(key),
830
+ configurable: true
831
+ });
832
+ }
833
+ } catch (error) {
834
+ logger.error('❌ StorageService: Failed to create dynamic property:', error);
835
+ }
836
+ }
837
+
838
+ /**
839
+ * Remove dynamic property
840
+ * @param {string} key - The key to remove
841
+ */
842
+ __removeDynamicProperty(key) {
843
+ try {
844
+ if (this.dynamicProperties && this.dynamicProperties.includes(key)) {
845
+ delete this[key];
846
+ this.dynamicProperties = this.dynamicProperties.filter(k => k !== key);
847
+ }
848
+ } catch (error) {
849
+ logger.error('❌ StorageService: Failed to remove dynamic property:', error);
850
+ }
851
+ }
852
+ }
853
+
854
+ // Export singleton instance
855
+ const storage = StorageService.getInstance('onejs_storage');
856
+ export default storage;