Skip to content

Commit 14fa6d6

Browse files
feat: add PWA support for home screen installation (#12)
Add manifest.json, service worker, and app icons. Users can now install TermBeam to their home screen for instant access without typing URLs. Closes #9 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 003f272 commit 14fa6d6

8 files changed

Lines changed: 90 additions & 0 deletions

File tree

public/icons/icon-192.png

939 Bytes
Loading

public/icons/icon-512.png

4.25 KB
Loading

public/icons/icon.svg

Lines changed: 6 additions & 0 deletions
Loading

public/index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
<meta name="apple-mobile-web-app-capable" content="yes" />
1010
<meta name="mobile-web-app-capable" content="yes" />
1111
<meta name="theme-color" content="#1e1e1e" />
12+
<link rel="manifest" href="/manifest.json" />
13+
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
1214
<title>TermBeam</title>
1315
<style>
1416
:root {
@@ -941,6 +943,10 @@ <h3>
941943

942944
loadSessions();
943945
setInterval(loadSessions, 3000);
946+
947+
if ('serviceWorker' in navigator) {
948+
navigator.serviceWorker.register('/sw.js').catch(() => {});
949+
}
944950
</script>
945951
</body>
946952
</html>

public/manifest.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "TermBeam",
3+
"short_name": "TermBeam",
4+
"description": "Beam your terminal to any device",
5+
"start_url": "/",
6+
"display": "standalone",
7+
"background_color": "#1e1e1e",
8+
"theme_color": "#1e1e1e",
9+
"icons": [
10+
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
11+
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
12+
{ "src": "/icons/icon.svg", "sizes": "any", "type": "image/svg+xml" }
13+
]
14+
}

public/sw.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
const CACHE_NAME = 'termbeam-v1';
2+
const SHELL_URLS = ['/', '/terminal'];
3+
4+
self.addEventListener('install', (event) => {
5+
event.waitUntil(
6+
caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_URLS))
7+
);
8+
self.skipWaiting();
9+
});
10+
11+
self.addEventListener('activate', (event) => {
12+
event.waitUntil(
13+
caches.keys().then((keys) =>
14+
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
15+
)
16+
);
17+
self.clients.claim();
18+
});
19+
20+
self.addEventListener('fetch', (event) => {
21+
const url = new URL(event.request.url);
22+
23+
// Don't cache WebSocket upgrades or external resources
24+
if (
25+
event.request.mode === 'websocket' ||
26+
url.protocol === 'ws:' ||
27+
url.protocol === 'wss:' ||
28+
url.origin !== self.location.origin
29+
) {
30+
return;
31+
}
32+
33+
// Network-first for API calls
34+
if (url.pathname.startsWith('/api/')) {
35+
event.respondWith(
36+
fetch(event.request).catch(() => caches.match(event.request))
37+
);
38+
return;
39+
}
40+
41+
// Cache-first for static assets
42+
event.respondWith(
43+
caches.match(event.request).then((cached) => {
44+
if (cached) return cached;
45+
return fetch(event.request).then((response) => {
46+
if (response.ok) {
47+
const clone = response.clone();
48+
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
49+
}
50+
return response;
51+
});
52+
})
53+
);
54+
});

public/terminal.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
<meta name="apple-mobile-web-app-capable" content="yes" />
1010
<meta name="mobile-web-app-capable" content="yes" />
1111
<meta name="theme-color" content="#1e1e1e" />
12+
<link rel="manifest" href="/manifest.json" />
13+
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
1214
<title>TermBeam — Terminal</title>
1315
<link
1416
rel="stylesheet"
@@ -801,6 +803,10 @@
801803

802804
connect();
803805
}
806+
807+
if ('serviceWorker' in navigator) {
808+
navigator.serviceWorker.register('/sw.js').catch(() => {});
809+
}
804810
</script>
805811
</body>
806812
</html>

src/routes.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
const path = require('path');
22
const os = require('os');
33
const fs = require('fs');
4+
const express = require('express');
45
const { detectShells } = require('./shells');
56

67
const PUBLIC_DIR = path.join(__dirname, '..', 'public');
78

89
function setupRoutes(app, { auth, sessions, config }) {
10+
// Serve static files (manifest.json, sw.js, icons, etc.)
11+
app.use(express.static(PUBLIC_DIR, { index: false }));
12+
913
// Login page
1014
app.get('/login', (_req, res) => {
1115
if (!auth.password) return res.redirect('/');

0 commit comments

Comments
 (0)