package main import ( "git.smesh.lol/smesh/web/common/helpers" "git.smesh.lol/smesh/web/common/jsbridge/idb" "git.smesh.lol/smesh/web/common/jsbridge/sw" "git.smesh.lol/smesh/web/common/mw" ) // Service worker: relay pool, subscription router, IDB event cache, // DM decryption relay, MLS dispatch, cache lifecycle. // WASM target. No goroutines. All async operations are explicit callback // chains registered through the jsbridge. JS calls exported WASM functions; // WASM calls back via registered imports. const cacheName = "smesh" var staticFiles = []string{ "/", "/index.html", "/style.css", "/smesh-logo.svg", "/sw-register.js", // Critical .mjs modules - pre-cache so the network-first path can't fall // through to the 503 "offline" Response on transient network failures // during SW activation. Without these, a failed network fetch + empty // cache returns a non-JS Response that fails module import with the // "error loading dynamically imported module" error. "/wasm-host.mjs", "/bridge-common.mjs", "/$runtime/dom.mjs", "/$runtime/localstorage.mjs", "/$runtime/ws.mjs", "/$runtime/markdown.mjs", "/$runtime/builtin.mjs", "/$runtime/aead.mjs", "/$runtime/idb.mjs", "/$runtime/poly1305.mjs", "/$runtime/sw.mjs", } // --- Subscription router --- // --- Entry point --- func main() { idb.Open(func() {}) sw.OnInstall(onInstall) sw.OnActivate(onActivate) sw.OnFetch(onFetch) sw.OnMessage(onMessage) connectSSE() } // --- Lifecycle --- func onInstall(event sw.Event) { sw.WaitUntil(event, func(done func()) { sw.CacheOpen(cacheName, func(cache sw.Cache) { sw.CacheFromManifests(cache, staticFiles, func() { sw.SkipWaiting() done() }) }) }) } func onActivate(event sw.Event) { sw.WaitUntil(event, func(done func()) { // Delete both old cache name and current cache to evict any stale WASM // binaries cached from previous sessions (e.g. a broken relay-proxy.wasm). // onInstall will repopulate only the files in staticFiles. sw.CacheDelete("sm3sh", func() { sw.CacheDelete(cacheName, func() { sw.CacheOpen(cacheName, func(cache sw.Cache) { sw.CacheFromManifests(cache, staticFiles, func() { sw.ClaimClients(func() { done() }) }) }) }) }) }) } func onFetch(event sw.Event) { url := sw.GetRequestURL(event) origin := sw.Origin() if len(url) < len(origin) || url[:len(origin)] != origin { return } path := sw.GetRequestPath(event) if path == "/__sse" || path == "/__version" { return } if isNavigationPath(path) { return } if isJSPath(path) { sw.RespondWithNetworkFirst(event) } else { sw.RespondWithCacheFirst(event) } } func isNavigationPath(path string) (ok bool) { if path == "/" || path == "/index.html" { return true } if len(path) > 2 && path[:2] == "/p" { return true } if len(path) > 2 && path[:2] == "/t" { return true } if len(path) > 3 && path[:4] == "/msg" { return true } dot := false for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- { if path[i] == '.' { dot = true break } } return !dot } func isJSPath(path string) (ok bool) { n := len(path) if n > 4 && path[n-4:] == ".mjs" { return true } if n > 3 && path[n-3:] == ".js" { return true } return false } // --- Message dispatch --- func onMessage(event sw.Event) { data := sw.GetMessageData(event) clientID := sw.GetMessageClientID(event) switch data { case "activate-update": sw.CacheOpen(cacheName, func(cache sw.Cache) { sw.CacheFromManifests(cache, staticFiles, func() { sw.MatchClients(func(client sw.Client) { sw.PostMessage(client, "reload") }) }) }) return case "CLAIM": sw.ClaimClients(func() {}) return } w := mw.New(data) msgType := w.Str() switch msgType { case "SKIP_WAITING": sw.SkipWaiting() case "DIAG": sendToClient(clientID, "[\"DIAG\",\"\"]") default: routeMessage(clientID, &w, msgType) } } func routeMessage(clientID string, w *mw.MW, msgType string) { sw.Log("UNROUTED MSG type=" | msgType) } // --- Client messaging --- func sendToClient(clientID, msg string) { sw.GetClientByID(clientID, func(c sw.Client, ok bool) { if ok { sw.PostMessageJSON(c, msg) } else { evictDeadClient(clientID) } }) } func evictDeadClient(_ string) {} // --- SSE version check --- var currentVersion string func connectSSE() { sw.SSEConnect("/__version", func(data string) { v := helpers.JsonGetString(data, "v") if v == "" { v = data } if v == "" { return } if currentVersion == "" { currentVersion = v return } if v != currentVersion { currentVersion = v sw.CacheDelete("sm3sh", func() { sw.CacheDelete(cacheName, func() { sw.CacheOpen(cacheName, func(cache sw.Cache) { sw.CacheFromManifests(cache, staticFiles, func() { sw.MatchClients(func(c sw.Client) { sw.PostMessage(c, "reload") }) }) }) }) }) } }) } // --- JSON helpers --- func jstr(s string) (sv string) { return helpers.JsonString(s) }