1/** 2 * Centralized storage service for localStorage operations 3 */ 4 5class StorageService { 6 constructor() { 7 this.listeners = new Map() 8 } 9 10 /** 11 * Get item from localStorage with error handling 12 */ 13 getItem(key, defaultValue = null) { 14 try { 15 const item = localStorage.getItem(key) 16 return item ? JSON.parse(item) : defaultValue 17 } catch (error) { 18 console.error(`Failed to get localStorage item "${key}":`, error) 19 return defaultValue 20 } 21 } 22 23 /** 24 * Set item in localStorage with error handling 25 */ 26 setItem(key, value) { 27 try { 28 localStorage.setItem(key, JSON.stringify(value)) 29 this.notifyListeners(key, value) 30 return true 31 } catch (error) { 32 console.error(`Failed to set localStorage item "${key}":`, error) 33 return false 34 } 35 } 36 37 /** 38 * Remove item from localStorage 39 */ 40 removeItem(key) { 41 try { 42 localStorage.removeItem(key) 43 this.notifyListeners(key, null) 44 return true 45 } catch (error) { 46 console.error(`Failed to remove localStorage item "${key}":`, error) 47 return false 48 } 49 } 50 51 /** 52 * Add listener for storage changes 53 */ 54 addListener(key, callback) { 55 if (!this.listeners.has(key)) { 56 this.listeners.set(key, new Set()) 57 } 58 this.listeners.get(key).add(callback) 59 60 // Return unsubscribe function 61 return () => { 62 const keyListeners = this.listeners.get(key) 63 if (keyListeners) { 64 keyListeners.delete(callback) 65 if (keyListeners.size === 0) { 66 this.listeners.delete(key) 67 } 68 } 69 } 70 } 71 72 /** 73 * Notify listeners of storage changes 74 */ 75 notifyListeners(key, value) { 76 const keyListeners = this.listeners.get(key) 77 if (keyListeners) { 78 keyListeners.forEach(callback => { 79 try { 80 callback(value, key) 81 } catch (error) { 82 console.error(`Error in storage listener for key "${key}":`, error) 83 } 84 }) 85 } 86 } 87 88 /** 89 * Trip planner specific methods 90 */ 91 getTripPlannerIds() { 92 return this.getItem('trip_planner_ids', []) 93 } 94 95 setTripPlannerIds(ids) { 96 const success = this.setItem('trip_planner_ids', ids) 97 if (success) { 98 // Dispatch custom event for backward compatibility 99 window.dispatchEvent(new CustomEvent('tripPlannerUpdate', { 100 detail: { ids } 101 })) 102 } 103 return success 104 } 105 106 addToTripPlanner(id) { 107 const ids = this.getTripPlannerIds() 108 if (!ids.includes(id)) { 109 ids.push(id) 110 return this.setTripPlannerIds(ids) 111 } 112 return true 113 } 114 115 removeFromTripPlanner(id) { 116 const ids = this.getTripPlannerIds() 117 const filteredIds = ids.filter(existingId => existingId !== id) 118 if (filteredIds.length !== ids.length) { 119 return this.setTripPlannerIds(filteredIds) 120 } 121 return true 122 } 123 124 toggleTripPlannerItem(id) { 125 const ids = this.getTripPlannerIds() 126 if (ids.includes(id)) { 127 return this.removeFromTripPlanner(id) 128 } else { 129 return this.addToTripPlanner(id) 130 } 131 } 132 133 isTripPlannerItem(id) { 134 return this.getTripPlannerIds().includes(id) 135 } 136} 137 138// Create and export singleton instance 139export const storageService = new StorageService() 140 141// Export class for testing or multiple instances 142export { StorageService }
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.