Static site hosting via tangled

Add memory cache limits and ws reconnect

+24 -4
src/knot-event-listener.js
··· 1 1 import EventEmitter from "node:events"; 2 2 3 3 export class KnotEventListener extends EventEmitter { 4 - constructor({ knotDomain }) { 4 + constructor({ knotDomain, reconnectTimeout = 10000 }) { 5 5 super(); 6 6 this.knotDomain = knotDomain; 7 + this.reconnectTimeout = reconnectTimeout; 8 + this.connection = null; 7 9 } 8 10 9 11 async start() { 10 - const ws = new WebSocket(`wss://${this.knotDomain}/events`); 11 - ws.onmessage = (event) => this.handleMessage(event); 12 + this.connection = new WebSocket(`wss://${this.knotDomain}/events`); 13 + this.connection.onmessage = (event) => this.handleMessage(event); 14 + this.connection.onerror = (event) => this.handleError(event); 15 + this.connection.onclose = () => this.handleClose(); 12 16 return new Promise((resolve) => { 13 - ws.onopen = () => { 17 + this.connection.onopen = () => { 14 18 console.log("Knot event listener connected to:", this.knotDomain); 15 19 resolve(); 16 20 }; ··· 27 31 }, 28 32 }; 29 33 this.emit("refUpdate", event); 34 + } 35 + } 36 + 37 + handleError(event) { 38 + console.error("Knot event listener error:", event); 39 + this.emit("error", event); 40 + } 41 + 42 + handleClose() { 43 + console.log("Knot event listener closed"); 44 + this.emit("close"); 45 + if (this.reconnectTimeout) { 46 + setTimeout(() => { 47 + console.log("Knot event listener reconnecting..."); 48 + this.start(); 49 + }, this.reconnectTimeout).unref(); 30 50 } 31 51 } 32 52 }
+17 -3
src/pages-service.js
··· 7 7 import { KnotClient } from "./knot-client.js"; 8 8 9 9 class FileCache { 10 - constructor() { 10 + constructor({ maxSize } = {}) { 11 11 this.cache = new Map(); 12 + this.maxSize = maxSize; 12 13 } 13 14 14 15 get(filename) { ··· 17 18 18 19 set(filename, content) { 19 20 this.cache.set(filename, content); 21 + // Evict oldest item if cache is full 22 + if (this.maxSize && this.cache.size > this.maxSize) { 23 + const oldestKey = this.cache.keys().next().value; 24 + this.cache.delete(oldestKey); 25 + } 20 26 } 21 27 22 28 clear() { ··· 49 55 this.fileCache = null; 50 56 if (cache) { 51 57 console.log("Enabling cache for", this.ownerDid, this.repoName); 52 - this.fileCache = new FileCache(); 58 + this.fileCache = new FileCache({ maxSize: 100 }); 53 59 } 54 60 } 55 61 ··· 66 72 } else { 67 73 content = blob.contents; 68 74 } 69 - this.fileCache?.set(filename, content); 75 + if (this.fileCache && content) { 76 + const contentSize = Buffer.isBuffer(content) 77 + ? content.length 78 + : Buffer.byteLength(content, "utf8"); 79 + // Cache unless content is too large (5MB) 80 + if (contentSize < 5 * 1024 * 1024) { 81 + this.fileCache.set(filename, content); 82 + } 83 + } 70 84 return content; 71 85 } 72 86