Skip to content
quarry Artifact [7cc528eae9]
Login

Artifact [7cc528eae9]

Artifact 7cc528eae95fc6cb9caf60113fb3d1624b178ec0a10221250a060ca21b9ae001:


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