/** * Strata Shared Worker * Manages cross-tab state, caching, and API deduplication */ interface TabConnection { port: MessagePort; tabId: string; connectedAt: number; lastActivity: number; } interface CacheEntry { data: any; timestamp: number; ttl: number; etag?: string; } interface InFlightRequest { promise: Promise; subscribers: string[]; // tabIds waiting for this request } interface StoreState { [storeName: string]: { shared: Record; tabs: Record>; }; } class StrataSharedWorker { private tabs: Map = new Map(); private cache: Map = new Map(); private inFlight: Map = new Map(); private stores: StoreState = {}; private encryptionKey: Uint8Array | null = null; constructor() { self.onconnect = (e: MessageEvent) => this.handleConnect(e); } private generateTabId(): string { return 'tab_' + crypto.randomUUID().slice(0, 8); } private handleConnect(e: MessageEvent) { const port = e.ports[0]; const tabId = this.generateTabId(); const connection: TabConnection = { port, tabId, connectedAt: Date.now(), lastActivity: Date.now(), }; this.tabs.set(tabId, connection); port.onmessage = (event) => this.handleMessage(tabId, event.data); port.start(); // Send tabId to the new tab port.postMessage({ type: 'connected', tabId, tabCount: this.tabs.size, }); // Notify other tabs this.broadcast('tab:joined', { tabId, tabCount: this.tabs.size }, [tabId]); } private handleMessage(tabId: string, message: any) { const tab = this.tabs.get(tabId); if (tab) { tab.lastActivity = Date.now(); } switch (message.type) { case 'fetch': this.handleFetch(tabId, message); break; case 'store:get': this.handleStoreGet(tabId, message); break; case 'store:set': this.handleStoreSet(tabId, message); break; case 'store:subscribe': this.handleStoreSubscribe(tabId, message); break; case 'broadcast': this.handleBroadcast(tabId, message); break; case 'cache:invalidate': this.handleCacheInvalidate(message); break; case 'disconnect': this.handleDisconnect(tabId); break; case 'setEncryptionKey': this.setEncryptionKey(message.key); break; } } private async handleFetch(tabId: string, message: any) { const { url, options, requestId } = message; const cacheKey = this.getCacheKey(url, options); // Check cache first const cached = this.cache.get(cacheKey); if (cached && !this.isCacheExpired(cached)) { this.sendToTab(tabId, { type: 'fetch:response', requestId, data: cached.data, fromCache: true, }); return; } // Check if request is already in flight const inFlight = this.inFlight.get(cacheKey); if (inFlight) { // Add this tab to subscribers inFlight.subscribers.push(tabId); const data = await inFlight.promise; this.sendToTab(tabId, { type: 'fetch:response', requestId, data, deduplicated: true, }); return; } // Make the request const fetchPromise = this.executeFetch(url, options); this.inFlight.set(cacheKey, { promise: fetchPromise, subscribers: [tabId], }); try { const data = await fetchPromise; const inFlightEntry = this.inFlight.get(cacheKey); // Cache the response if (options?.cache !== 'none') { const ttl = this.parseTTL(options?.cache || 'smart'); this.cache.set(cacheKey, { data, timestamp: Date.now(), ttl, }); } // Send to all subscribers if (inFlightEntry) { for (const subTabId of inFlightEntry.subscribers) { this.sendToTab(subTabId, { type: 'fetch:response', requestId, data, fromCache: false, }); } } } catch (error) { const inFlightEntry = this.inFlight.get(cacheKey); if (inFlightEntry) { for (const subTabId of inFlightEntry.subscribers) { this.sendToTab(subTabId, { type: 'fetch:error', requestId, error: error instanceof Error ? error.message : 'Unknown error', }); } } } finally { this.inFlight.delete(cacheKey); } } private async executeFetch(url: string, options: any): Promise { const response = await fetch(url, { method: options?.method || 'GET', headers: options?.headers, body: options?.body ? JSON.stringify(options.body) : undefined, }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response.json(); } private getCacheKey(url: string, options: any): string { const method = options?.method || 'GET'; const body = options?.body ? JSON.stringify(options.body) : ''; return `${method}:${url}:${body}`; } private isCacheExpired(entry: CacheEntry): boolean { if (entry.ttl === -1) return false; // permanent return Date.now() - entry.timestamp > entry.ttl; } private parseTTL(cache: string): number { if (cache === 'permanent') return -1; if (cache === 'none') return 0; if (cache === 'smart') return 5 * 60 * 1000; // 5 minutes default const match = cache.match(/^(\d+)(s|m|h|d)$/); if (match) { const value = parseInt(match[1]); const unit = match[2]; const multipliers: Record = { s: 1000, m: 60 * 1000, h: 60 * 60 * 1000, d: 24 * 60 * 60 * 1000, }; return value * multipliers[unit]; } return 5 * 60 * 1000; // default 5 minutes } private handleStoreGet(tabId: string, message: any) { const { storeName, key, scope } = message; const store = this.stores[storeName]; if (!store) { this.sendToTab(tabId, { type: 'store:value', storeName, key, value: undefined, }); return; } let value; if (scope === 'tab') { value = store.tabs[tabId]?.[key]; } else { value = store.shared[key]; } // Decrypt if needed if (this.encryptionKey && value) { value = this.decrypt(value); } this.sendToTab(tabId, { type: 'store:value', storeName, key, value, }); } private handleStoreSet(tabId: string, message: any) { const { storeName, key, value, scope, encrypt } = message; if (!this.stores[storeName]) { this.stores[storeName] = { shared: {}, tabs: {} }; } let storedValue = value; if (encrypt && this.encryptionKey) { storedValue = this.encrypt(value); } if (scope === 'tab') { if (!this.stores[storeName].tabs[tabId]) { this.stores[storeName].tabs[tabId] = {}; } this.stores[storeName].tabs[tabId][key] = storedValue; } else { this.stores[storeName].shared[key] = storedValue; // Notify all tabs about shared state change this.broadcast('store:changed', { storeName, key, value, }); } } private handleStoreSubscribe(tabId: string, message: any) { // Store subscriptions are handled via broadcast // When store changes, all tabs receive updates } private handleBroadcast(fromTabId: string, message: any) { const { event, data, targetTabs } = message; this.broadcast(event, data, targetTabs ? undefined : [fromTabId]); } private handleCacheInvalidate(message: any) { const { pattern } = message; if (pattern === '*') { this.cache.clear(); } else { for (const key of this.cache.keys()) { if (key.includes(pattern)) { this.cache.delete(key); } } } } private handleDisconnect(tabId: string) { this.tabs.delete(tabId); // Clean up tab-specific store data for (const storeName in this.stores) { delete this.stores[storeName].tabs[tabId]; } // Notify remaining tabs this.broadcast('tab:left', { tabId, tabCount: this.tabs.size }); } private broadcast(event: string, data: any, excludeTabs: string[] = []) { for (const [tabId, tab] of this.tabs) { if (!excludeTabs.includes(tabId)) { tab.port.postMessage({ type: 'broadcast', event, data, }); } } } private sendToTab(tabId: string, message: any) { const tab = this.tabs.get(tabId); if (tab) { tab.port.postMessage(message); } } private setEncryptionKey(keyArray: number[]) { this.encryptionKey = new Uint8Array(keyArray); } private encrypt(data: any): string { if (!this.encryptionKey) return JSON.stringify(data); const jsonStr = JSON.stringify(data); const encoder = new TextEncoder(); const dataBytes = encoder.encode(jsonStr); // XOR encryption with key (simple, fast) // In production, use SubtleCrypto AES-GCM const encrypted = new Uint8Array(dataBytes.length); for (let i = 0; i < dataBytes.length; i++) { encrypted[i] = dataBytes[i] ^ this.encryptionKey[i % this.encryptionKey.length]; } return btoa(String.fromCharCode(...encrypted)); } private decrypt(encryptedStr: string): any { if (!this.encryptionKey) return JSON.parse(encryptedStr); const encrypted = new Uint8Array( atob(encryptedStr) .split('') .map((c) => c.charCodeAt(0)) ); const decrypted = new Uint8Array(encrypted.length); for (let i = 0; i < encrypted.length; i++) { decrypted[i] = encrypted[i] ^ this.encryptionKey[i % this.encryptionKey.length]; } const decoder = new TextDecoder(); return JSON.parse(decoder.decode(decrypted)); } } // Initialize worker new StrataSharedWorker();