main.mx raw

   1  package main
   2  
   3  import (
   4  	"git.smesh.lol/smesh/web/common/helpers"
   5  	"git.smesh.lol/smesh/web/common/jsbridge/idb"
   6  	"git.smesh.lol/smesh/web/common/jsbridge/sw"
   7  	"git.smesh.lol/smesh/web/common/mw"
   8  )
   9  
  10  // Service worker: relay pool, subscription router, IDB event cache,
  11  // DM decryption relay, MLS dispatch, cache lifecycle.
  12  // WASM target. No goroutines. All async operations are explicit callback
  13  // chains registered through the jsbridge. JS calls exported WASM functions;
  14  // WASM calls back via registered imports.
  15  
  16  const cacheName = "smesh"
  17  
  18  var staticFiles = []string{
  19  	"/",
  20  	"/index.html",
  21  	"/style.css",
  22  	"/smesh-logo.svg",
  23  	"/sw-register.js",
  24  	// Critical .mjs modules - pre-cache so the network-first path can't fall
  25  	// through to the 503 "offline" Response on transient network failures
  26  	// during SW activation. Without these, a failed network fetch + empty
  27  	// cache returns a non-JS Response that fails module import with the
  28  	// "error loading dynamically imported module" error.
  29  	"/wasm-host.mjs",
  30  	"/bridge-common.mjs",
  31  	"/$runtime/dom.mjs",
  32  	"/$runtime/localstorage.mjs",
  33  	"/$runtime/ws.mjs",
  34  	"/$runtime/markdown.mjs",
  35  	"/$runtime/builtin.mjs",
  36  	"/$runtime/aead.mjs",
  37  	"/$runtime/idb.mjs",
  38  	"/$runtime/poly1305.mjs",
  39  	"/$runtime/sw.mjs",
  40  }
  41  
  42  // --- Subscription router ---
  43  
  44  // --- Entry point ---
  45  
  46  func main() {
  47  	idb.Open(func() {})
  48  	sw.OnInstall(onInstall)
  49  	sw.OnActivate(onActivate)
  50  	sw.OnFetch(onFetch)
  51  	sw.OnMessage(onMessage)
  52  	connectSSE()
  53  }
  54  
  55  // --- Lifecycle ---
  56  
  57  func onInstall(event sw.Event) {
  58  	sw.WaitUntil(event, func(done func()) {
  59  		sw.CacheOpen(cacheName, func(cache sw.Cache) {
  60  			sw.CacheFromManifests(cache, staticFiles, func() {
  61  				sw.SkipWaiting()
  62  				done()
  63  			})
  64  		})
  65  	})
  66  }
  67  
  68  func onActivate(event sw.Event) {
  69  	sw.WaitUntil(event, func(done func()) {
  70  		// Delete both old cache name and current cache to evict any stale WASM
  71  		// binaries cached from previous sessions (e.g. a broken relay-proxy.wasm).
  72  		// onInstall will repopulate only the files in staticFiles.
  73  		sw.CacheDelete("sm3sh", func() {
  74  			sw.CacheDelete(cacheName, func() {
  75  				sw.CacheOpen(cacheName, func(cache sw.Cache) {
  76  					sw.CacheFromManifests(cache, staticFiles, func() {
  77  						sw.ClaimClients(func() {
  78  							done()
  79  						})
  80  					})
  81  				})
  82  			})
  83  		})
  84  	})
  85  }
  86  
  87  func onFetch(event sw.Event) {
  88  	url := sw.GetRequestURL(event)
  89  	origin := sw.Origin()
  90  	if len(url) < len(origin) || url[:len(origin)] != origin {
  91  		return
  92  	}
  93  	path := sw.GetRequestPath(event)
  94  	if path == "/__sse" || path == "/__version" {
  95  		return
  96  	}
  97  	if isNavigationPath(path) {
  98  		return
  99  	}
 100  	if isJSPath(path) {
 101  		sw.RespondWithNetworkFirst(event)
 102  	} else {
 103  		sw.RespondWithCacheFirst(event)
 104  	}
 105  }
 106  
 107  func isNavigationPath(path string) (ok bool) {
 108  	if path == "/" || path == "/index.html" {
 109  		return true
 110  	}
 111  	if len(path) > 2 && path[:2] == "/p" {
 112  		return true
 113  	}
 114  	if len(path) > 2 && path[:2] == "/t" {
 115  		return true
 116  	}
 117  	if len(path) > 3 && path[:4] == "/msg" {
 118  		return true
 119  	}
 120  	dot := false
 121  	for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
 122  		if path[i] == '.' {
 123  			dot = true
 124  			break
 125  		}
 126  	}
 127  	return !dot
 128  }
 129  
 130  func isJSPath(path string) (ok bool) {
 131  	n := len(path)
 132  	if n > 4 && path[n-4:] == ".mjs" {
 133  		return true
 134  	}
 135  	if n > 3 && path[n-3:] == ".js" {
 136  		return true
 137  	}
 138  	return false
 139  }
 140  
 141  // --- Message dispatch ---
 142  
 143  func onMessage(event sw.Event) {
 144  	data := sw.GetMessageData(event)
 145  	clientID := sw.GetMessageClientID(event)
 146  
 147  	switch data {
 148  	case "activate-update":
 149  		sw.CacheOpen(cacheName, func(cache sw.Cache) {
 150  			sw.CacheFromManifests(cache, staticFiles, func() {
 151  				sw.MatchClients(func(client sw.Client) {
 152  					sw.PostMessage(client, "reload")
 153  				})
 154  			})
 155  		})
 156  		return
 157  	case "CLAIM":
 158  		sw.ClaimClients(func() {})
 159  		return
 160  	}
 161  
 162  	w := mw.New(data)
 163  	msgType := w.Str()
 164  
 165  	switch msgType {
 166  	case "SKIP_WAITING":
 167  		sw.SkipWaiting()
 168  	case "DIAG":
 169  		sendToClient(clientID, "[\"DIAG\",\"\"]")
 170  	default:
 171  		routeMessage(clientID, &w, msgType)
 172  	}
 173  }
 174  
 175  func routeMessage(clientID string, w *mw.MW, msgType string) {
 176  	sw.Log("UNROUTED MSG type=" | msgType)
 177  }
 178  
 179  // --- Client messaging ---
 180  
 181  func sendToClient(clientID, msg string) {
 182  	sw.GetClientByID(clientID, func(c sw.Client, ok bool) {
 183  		if ok {
 184  			sw.PostMessageJSON(c, msg)
 185  		} else {
 186  			evictDeadClient(clientID)
 187  		}
 188  	})
 189  }
 190  
 191  func evictDeadClient(_ string) {}
 192  
 193  // --- SSE version check ---
 194  
 195  var currentVersion string
 196  
 197  func connectSSE() {
 198  	sw.SSEConnect("/__version", func(data string) {
 199  		v := helpers.JsonGetString(data, "v")
 200  		if v == "" {
 201  			v = data
 202  		}
 203  		if v == "" {
 204  			return
 205  		}
 206  		if currentVersion == "" {
 207  			currentVersion = v
 208  			return
 209  		}
 210  		if v != currentVersion {
 211  			currentVersion = v
 212  			sw.CacheDelete("sm3sh", func() {
 213  				sw.CacheDelete(cacheName, func() {
 214  					sw.CacheOpen(cacheName, func(cache sw.Cache) {
 215  						sw.CacheFromManifests(cache, staticFiles, func() {
 216  							sw.MatchClients(func(c sw.Client) {
 217  								sw.PostMessage(c, "reload")
 218  							})
 219  						})
 220  					})
 221  				})
 222  			})
 223  		}
 224  	})
 225  }
 226  
 227  // --- JSON helpers ---
 228  
 229  func jstr(s string) (sv string) { return helpers.JsonString(s) }
 230  
 231