PolyTools Trade Cost 0.1.1 / src/background.js
| 1 | /* PolyTools — background service worker: cached API proxy for content scripts + tab helpers. */ |
| 2 | 'use strict'; |
| 3 | |
| 4 | importScripts('flavor.js', 'lib/journal-stats.js', 'lib/api.js'); |
| 5 | |
| 6 | const { api, flavor } = self.PWL; |
| 7 | |
| 8 | const TTL = { event: 60000, book: 900 }; |
| 9 | const cache = new Map(); // key → { at, promise } |
| 10 | |
| 11 | function cached(key, ttl, fn) { |
| 12 | const hit = cache.get(key); |
| 13 | const now = Date.now(); |
| 14 | if (hit && now - hit.at < ttl) return hit.promise; |
| 15 | const promise = fn().catch((e) => { cache.delete(key); throw e; }); |
| 16 | cache.set(key, { at: now, promise }); |
| 17 | if (cache.size > 300) { // drop the oldest entries |
| 18 | const keys = [...cache.keys()].slice(0, cache.size - 200); |
| 19 | for (const k of keys) cache.delete(k); |
| 20 | } |
| 21 | return promise; |
| 22 | } |
| 23 | |
| 24 | const handlers = { |
| 25 | async event({ slug }) { |
| 26 | if (!flavor.cost) throw new Error('no cost panel in this build'); |
| 27 | if (!slug || typeof slug !== 'string' || slug.length > 300) throw new Error('bad slug'); |
| 28 | return cached('ev:' + slug, TTL.event, () => api.eventBySlug(slug)); |
| 29 | }, |
| 30 | async book({ tokenId }) { |
| 31 | if (!flavor.cost) throw new Error('no cost panel in this build'); |
| 32 | if (!/^\d{5,90}$/.test(String(tokenId || ''))) throw new Error('bad token id'); |
| 33 | return cached('bk:' + tokenId, TTL.book, () => api.book(tokenId)); |
| 34 | }, |
| 35 | async openJournal({ address, user }) { |
| 36 | if (!flavor.journal) throw new Error('no journal in this build'); |
| 37 | const q = address && /^0x[0-9a-fA-F]{40}$/.test(address) ? 'address=' + address |
| 38 | : user ? 'user=' + encodeURIComponent(String(user).slice(0, 80)) : ''; |
| 39 | await chrome.tabs.create({ url: chrome.runtime.getURL('src/journal/journal.html') + (q ? '?' + q : '') }); |
| 40 | return true; |
| 41 | }, |
| 42 | async openOptions() { |
| 43 | await chrome.runtime.openOptionsPage(); |
| 44 | return true; |
| 45 | }, |
| 46 | }; |
| 47 | |
| 48 | chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { |
| 49 | const h = msg && typeof msg.type === 'string' && Object.hasOwn(handlers, msg.type) ? handlers[msg.type] : null; |
| 50 | if (!h) return false; |
| 51 | h(msg).then( |
| 52 | (data) => sendResponse({ ok: true, data }), |
| 53 | (e) => sendResponse({ ok: false, error: e && e.message ? e.message : String(e), status: e && e.status }), |
| 54 | ); |
| 55 | return true; // async response |
| 56 | }); |