Skip to content
quarry Artifact [1d0ebedead]
Login

Artifact [1d0ebedead]

Artifact 1d0ebedeade47980ca58274b11cd7b55756ec28ad379384fd4461110deefbc6e:


// 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))
	})
}