tinyrelaycommit

Commit addbbb83bc9f

browse files at this commit | history

1diff --git a/internal/webui/apple-touch-icon.png b/internal/webui/apple-touch-icon.png
2new file mode 100644
3index 0000000..8065eee
4Binary files /dev/null and b/internal/webui/apple-touch-icon.png differ
5diff --git a/internal/webui/bridge.js b/internal/webui/bridge.js
6index 404f781..13a5ed7 100644
7--- a/internal/webui/bridge.js
8+++ b/internal/webui/bridge.js
9@@ -66,8 +66,11 @@
10 // in step with the rail colour of whichever theme is active.
11 const paintChrome = () => {
12 const rail = getComputedStyle(root_).getPropertyValue("--rail").trim();
13- const themeMeta = document.querySelector('meta[name="theme-color"]');
14- if (themeMeta && rail) themeMeta.content = rail;
15+ if (rail) document.querySelectorAll('meta[name="theme-color"]').forEach(meta => { meta.content = rail; });
16+ // The manifest is fetched at install and on later update checks, so
17+ // point it at the variant whose launch colours match the active theme.
18+ const manifest = document.querySelector('link[rel="manifest"]');
19+ if (manifest) manifest.href = localPath("/manifest.webmanifest") + (currentTheme() === "dark" ? "?theme=dark" : "");
20 };
21 paintChrome();
22 matchMedia("(prefers-color-scheme: dark)").addEventListener("change", paintChrome);
23diff --git a/internal/webui/icon-192.png b/internal/webui/icon-192.png
24new file mode 100644
25index 0000000..f4303f0
26Binary files /dev/null and b/internal/webui/icon-192.png differ
27diff --git a/internal/webui/icon-512.png b/internal/webui/icon-512.png
28new file mode 100644
29index 0000000..feaaed1
30Binary files /dev/null and b/internal/webui/icon-512.png differ
31diff --git a/internal/webui/icon-maskable-512.png b/internal/webui/icon-maskable-512.png
32new file mode 100644
33index 0000000..c685eb7
34Binary files /dev/null and b/internal/webui/icon-maskable-512.png differ
35diff --git a/internal/webui/icon-maskable.svg b/internal/webui/icon-maskable.svg
36new file mode 100644
37index 0000000..876ca08
38--- /dev/null
39+++ b/internal/webui/icon-maskable.svg
40@@ -0,0 +1 @@
41+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" fill="#f4f2ec"/><path d="M19 43 L32 20 L45 43 Z" fill="#2f6b3a"/><rect x="22" y="45.5" width="20" height="3" fill="#8a5a1f"/></svg>
42diff --git a/internal/webui/manifest.webmanifest b/internal/webui/manifest.webmanifest
43index 264d641..378fb8e 100644
44--- a/internal/webui/manifest.webmanifest
45+++ b/internal/webui/manifest.webmanifest
46@@ -1,14 +1,27 @@
47 {
48+ "id": "BASE/",
49 "name": "NAME",
50 "short_name": "NAME",
51 "description": "A tiny Nostr relay",
52 "start_url": "BASE/",
53 "scope": "BASE/",
54 "display": "standalone",
55- "background_color": "#fbfaf7",
56- "theme_color": "#f4f2ec",
57+ "display_override": ["standalone", "minimal-ui"],
58+ "launch_handler": {"client_mode": "navigate-existing"},
59+ "background_color": "BACKGROUND",
60+ "theme_color": "THEME",
61+ "categories": ["social", "utilities"],
62+ "prefer_related_applications": false,
63 "icons": [
64+ {"src": "BASE/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any"},
65+ {"src": "BASE/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any"},
66+ {"src": "BASE/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable"},
67 {"src": "BASE/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any"},
68 {"src": "BASE/icon-mono.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "monochrome"}
69+ ],
70+ "shortcuts": [
71+ {"name": "Files", "url": "BASE/files", "icons": [{"src": "BASE/icon-192.png", "sizes": "192x192"}]},
72+ {"name": "Repositories", "url": "BASE/repos", "icons": [{"src": "BASE/icon-192.png", "sizes": "192x192"}]},
73+ {"name": "Manage", "url": "BASE/manage", "icons": [{"src": "BASE/icon-192.png", "sizes": "192x192"}]}
74 ]
75 }
76diff --git a/internal/webui/page.html b/internal/webui/page.html
77index e818619..32f6adb 100644
78--- a/internal/webui/page.html
79+++ b/internal/webui/page.html
80@@ -2,12 +2,18 @@
81 <html lang="en" data-js="no">
82 <head>
83 <meta charset="utf-8">
84-<meta name="viewport" content="width=device-width,initial-scale=1">
85+<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
86 <meta name="color-scheme" content="light dark">
87-<meta name="theme-color" content="#f4f2ec">
88+<meta name="theme-color" content="#f4f2ec" media="(prefers-color-scheme: light)">
89+<meta name="theme-color" content="#1e1d1a" media="(prefers-color-scheme: dark)">
90+<meta name="mobile-web-app-capable" content="yes">
91+<meta name="apple-mobile-web-app-capable" content="yes">
92+<meta name="apple-mobile-web-app-status-bar-style" content="default">
93+<meta name="apple-mobile-web-app-title" content="{{.Slug}}">
94 <title>{{.Title}}</title>
95 <link rel="manifest" href="/manifest.webmanifest">
96 <link rel="icon" href="/icon.svg" type="image/svg+xml">
97+<link rel="apple-touch-icon" href="/apple-touch-icon.png">
98 <style>{{stylesheet}}</style>
99 <script src="/fixi.js"></script>
100 <script src="/signer.js"></script>
101diff --git a/internal/webui/style.css b/internal/webui/style.css
102index 2fd2589..a21d9b3 100644
103--- a/internal/webui/style.css
104+++ b/internal/webui/style.css
105@@ -24,13 +24,13 @@ img{max-width:100%}
106
107 /* Shell: a table with three columns; each column scrolls on its own. */
108 #topbar{display:none}
109-#layout{border-collapse:collapse;table-layout:fixed;width:100%;height:calc(100vh - 28px)}
110+#layout{border-collapse:collapse;table-layout:fixed;width:100%;height:calc(100vh - 28px - env(safe-area-inset-bottom,0px))}
111 #layout>tbody>tr>td{vertical-align:top;padding:0;position:relative;height:100%}
112 #railbox,#content,#panel{position:absolute;top:0;right:0;bottom:0;left:0;overflow:auto;overscroll-behavior:contain}
113 #rail{width:240px;background:var(--rail);border-right:1px solid var(--line)}
114 #aside{width:280px;border-left:1px solid var(--line)}
115 #content>section{padding:14px 22px 30px}
116-#footer{position:fixed;left:0;right:0;bottom:0;height:28px;border-top:1px solid var(--line);background:var(--rail);font:13px var(--mono);padding:0 2ch 0 2.5ch;box-sizing:border-box}
117+#footer{position:fixed;left:0;right:0;bottom:0;height:calc(28px + env(safe-area-inset-bottom,0px));border-top:1px solid var(--line);background:var(--rail);font:13px var(--mono);padding:0 calc(2ch + env(safe-area-inset-right,0px)) env(safe-area-inset-bottom,0px) calc(2.5ch + env(safe-area-inset-left,0px));box-sizing:border-box}
118 #footer table{width:100%;border-collapse:collapse;height:27px}#footer td{border:0;padding:0;vertical-align:middle}
119 #footer td:last-child{text-align:right}
120 #prompt{color:var(--muted)}#prompt b{color:var(--ink);font-weight:500}
121diff --git a/internal/webui/sw.js b/internal/webui/sw.js
122index 6baf324..52c91d7 100644
123--- a/internal/webui/sw.js
124+++ b/internal/webui/sw.js
125@@ -1,9 +1,25 @@
126 // Service worker: makes the relay installable and keeps navigation working
127-// when the network fails by serving the last shell seen. Relay data is never
128-// cached; every request goes to the network first.
129+// when the network fails. Every request goes to the network first. Successful
130+// page loads and the shell scripts are kept as the offline fallback; relay
131+// data requested by scripts is never cached.
132+const CACHE = "tiny-shell-v1";
133+const shellAsset = url => /\/(fixi\.js|signer\.js|webmcp\.js|sw\.js|icon[^/]*\.(svg|png)|apple-touch-icon\.png|manifest\.webmanifest)$/.test(url.pathname);
134 self.addEventListener("install", () => self.skipWaiting());
135-self.addEventListener("activate", event => event.waitUntil(self.clients.claim()));
136+self.addEventListener("activate", event => {
137+ event.waitUntil(caches.keys().then(keys => Promise.all(keys.filter(key => key !== CACHE).map(key => caches.delete(key)))).then(() => self.clients.claim()));
138+});
139 self.addEventListener("fetch", event => {
140- if (event.request.method !== "GET") return;
141- event.respondWith(fetch(event.request).catch(() => caches.match(event.request)));
142+ const request = event.request;
143+ if (request.method !== "GET") return;
144+ const url = new URL(request.url);
145+ if (url.origin !== location.origin) return;
146+ const keep = request.mode === "navigate" || shellAsset(url);
147+ if (!keep) return;
148+ event.respondWith(fetch(request).then(response => {
149+ if (response.ok && response.type === "basic") {
150+ const copy = response.clone();
151+ caches.open(CACHE).then(cache => cache.put(request, copy)).catch(() => {});
152+ }
153+ return response;
154+ }).catch(async () => (await caches.match(request)) || (request.mode === "navigate" ? caches.match(new Request(url.origin + url.pathname.replace(/\/[^/]*$/, "/"))) : undefined)));
155 });
156diff --git a/internal/webui/templates.go b/internal/webui/templates.go
157index 846a1f5..8e4f849 100644
158--- a/internal/webui/templates.go
159+++ b/internal/webui/templates.go
160@@ -59,6 +59,18 @@ var fileWorkspaceJS string
161 //go:embed manifest.webmanifest
162 var manifestTemplate string
163
164+//go:embed icon-192.png
165+var icon192PNG []byte
166+
167+//go:embed icon-512.png
168+var icon512PNG []byte
169+
170+//go:embed icon-maskable-512.png
171+var iconMaskablePNG []byte
172+
173+//go:embed apple-touch-icon.png
174+var appleTouchIconPNG []byte
175+
176 //go:embed sw.js
177 var serviceWorkerJS []byte
178
179diff --git a/internal/webui/webui.go b/internal/webui/webui.go
180index dd502d7..ce250d2 100644
181--- a/internal/webui/webui.go
182+++ b/internal/webui/webui.go
183@@ -199,6 +199,12 @@ func (a *App) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
184 _, _ = writer.Write(serviceWorkerJS)
185 return
186 }
187+ if png, ok := map[string][]byte{"/icon-192.png": icon192PNG, "/icon-512.png": icon512PNG, "/icon-maskable-512.png": iconMaskablePNG, "/apple-touch-icon.png": appleTouchIconPNG}[request.URL.Path]; ok {
188+ writer.Header().Set("content-type", "image/png")
189+ writer.Header().Set("cache-control", "public, max-age=86400")
190+ _, _ = writer.Write(png)
191+ return
192+ }
193 if request.URL.Path == "/icon.svg" || request.URL.Path == "/icon-mono.svg" {
194 writer.Header().Set("content-type", "image/svg+xml; charset=utf-8")
195 if request.URL.Path == "/icon.svg" {
196@@ -730,6 +736,15 @@ func (a *App) manifest(writer http.ResponseWriter, request *http.Request) {
197 encoded, _ := json.Marshal(name)
198 body := strings.ReplaceAll(manifestTemplate, `"NAME"`, string(encoded))
199 body = strings.ReplaceAll(body, "BASE/", requestPrefix(request)+"/")
200+ // A manifest carries one set of launch colours. The page links the
201+ // variant that matches the active theme so the installed app's splash
202+ // screen and status bar do not flash light on a dark phone.
203+ theme, background := "#f4f2ec", "#fbfaf7"
204+ if request.URL.Query().Get("theme") == "dark" {
205+ theme, background = "#1e1d1a", "#171614"
206+ }
207+ body = strings.ReplaceAll(body, "THEME", theme)
208+ body = strings.ReplaceAll(body, "BACKGROUND", background)
209 writer.Header().Set("content-type", "application/manifest+json; charset=utf-8")
210 _, _ = writer.Write([]byte(body))
211 }

About

ownerc01503e8035f
visibilitypublic
headaddbbb83bc9f

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs