Android app (TWA) in android/, built with podman; serve assetlinks.json for it
This commit is contained in:
parent
44708d5f3c
commit
7900bf5b3e
21 changed files with 313 additions and 0 deletions
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The site installs as an app (a PWA): a web app manifest, plus the service worker in
|
||||
|
|
@ -62,6 +63,34 @@ func (s *Server) manifest(staff bool) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// assetLinks tells Chrome the Android app belongs to this site, so it opens the site full screen
|
||||
// (a Trusted Web Activity) instead of with its URL bar showing.
|
||||
func (s *Server) assetLinks(w http.ResponseWriter, r *http.Request) {
|
||||
var certs []string
|
||||
for _, c := range s.cfg.AndroidCerts {
|
||||
if c = strings.ToUpper(strings.TrimSpace(c)); c != "" {
|
||||
certs = append(certs, c)
|
||||
}
|
||||
}
|
||||
if s.cfg.AndroidPackage == "" || len(certs) == 0 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
type target struct {
|
||||
Namespace string `json:"namespace"`
|
||||
PackageName string `json:"package_name"`
|
||||
Fingerprints []string `json:"sha256_cert_fingerprints"`
|
||||
}
|
||||
type statement struct {
|
||||
Relation []string `json:"relation"`
|
||||
Target target `json:"target"`
|
||||
}
|
||||
writeJSON(w, http.StatusOK, []statement{{
|
||||
Relation: []string{"delegate_permission/common.handle_all_urls"},
|
||||
Target: target{Namespace: "android_app", PackageName: s.cfg.AndroidPackage, Fingerprints: certs},
|
||||
}})
|
||||
}
|
||||
|
||||
// serviceWorker serves sw.js from the root, since a worker only controls pages under its own path.
|
||||
func serviceWorker(static fs.FS) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -44,3 +44,19 @@ func TestPWA(t *testing.T) {
|
|||
}
|
||||
get("/static/offline.html")
|
||||
}
|
||||
|
||||
func TestAssetLinks(t *testing.T) {
|
||||
serve := func(cfg Config) *httptest.ResponseRecorder {
|
||||
rec := httptest.NewRecorder()
|
||||
(&Server{cfg: cfg, site: defaultSite()}).Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/.well-known/assetlinks.json", nil))
|
||||
return rec
|
||||
}
|
||||
if rec := serve(Config{AndroidCerts: []string{""}}); rec.Code != http.StatusNotFound {
|
||||
t.Errorf("unset: status %d", rec.Code)
|
||||
}
|
||||
rec := serve(Config{AndroidPackage: "sh.zio.cs2", AndroidCerts: []string{" aa:bb ", "", "CC:DD"}})
|
||||
want := `[{"relation":["delegate_permission/common.handle_all_urls"],"target":{"namespace":"android_app","package_name":"sh.zio.cs2","sha256_cert_fingerprints":["AA:BB","CC:DD"]}}]`
|
||||
if got := strings.TrimSpace(rec.Body.String()); rec.Code != http.StatusOK || got != want {
|
||||
t.Errorf("status %d, body %s", rec.Code, got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ type Config struct {
|
|||
// Limits used when SimpleAdmin's config file isn't available.
|
||||
MaxBanDuration int
|
||||
MaxMuteDuration int
|
||||
// The Android app (android/ in this repo), for /.well-known/assetlinks.json: its package and
|
||||
// the SHA-256 fingerprints of the keys it's signed with. Empty serves no assetlinks.json.
|
||||
AndroidPackage string
|
||||
AndroidCerts []string
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
|
|
@ -84,6 +88,7 @@ func (s *Server) Handler() http.Handler {
|
|||
mux.HandleFunc("GET /manifest.webmanifest", s.manifest(false))
|
||||
mux.HandleFunc("GET /admin.webmanifest", s.manifest(true))
|
||||
mux.HandleFunc("GET /sw.js", serviceWorker(static))
|
||||
mux.HandleFunc("GET /.well-known/assetlinks.json", s.assetLinks)
|
||||
|
||||
mux.HandleFunc("GET /auth/login", s.login)
|
||||
mux.HandleFunc("GET /auth/callback", s.callback)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue