// sw.go serves the service worker as a static asset. It lives beside
// the ops rather than in internal/web because it is the counterpart of
// the me.push.* ops โ same file, same phase, same reason to change โ
// and because a page layer that has to remember to ship it is a page
// layer that will forget.
package api
import (
"bytes"
_ "embed"
"net/http"
"time"
)
//go:embed sw.js
var serviceWorkerJS []byte
// serviceWorkerModTime fixes the asset's Last-Modified so a browser
// conditional request is answerable. The zero build has no timestamp to
// use, and a fresh one on every start would defeat caching entirely.
var serviceWorkerModTime = time.Now()
// ServiceWorker serves /sw.js (RFC 0011 ยง6). Two headers matter:
// Content-Type, because a browser refuses to register a worker served
// as anything but JavaScript, and Service-Worker-Allowed: /, without
// which a worker may only claim the path it was served from โ quarry
// needs the whole site so a notification click can focus any page.
//
// Mounted at the ROOT, not under /api: a service worker's scope is
// its own URL's directory, so /api/v1/sw.js could never control a page.
func ServiceWorker() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/javascript")
w.Header().Set("Service-Worker-Allowed", "/")
// Short: a worker is how a browser learns about a fix to
// itself, so a long cache is a bug that cannot be shipped out.
w.Header().Set("Cache-Control", "max-age=0, must-revalidate")
http.ServeContent(w, r, "sw.js", serviceWorkerModTime, bytes.NewReader(serviceWorkerJS))
})
}
// ServiceWorkerJS is the asset's bytes, for a composition root that
// serves it through its own static-file machinery instead.
func ServiceWorkerJS() []byte { return serviceWorkerJS }