webtun 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/bin/webtun.js +73 -0
- package/package.json +115 -0
- package/postinstall.js +120 -0
- package/public/commands.js +7 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/index.html +4963 -0
- package/public/manifest.json +23 -0
- package/public/sw.js +70 -0
- package/server.js +1610 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "WebTun",
|
|
3
|
+
"short_name": "WebTun",
|
|
4
|
+
"description": "Self-hosted web terminal with file explorer and Cloudflare Tunnel",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"scope": "/",
|
|
7
|
+
"lang": "en",
|
|
8
|
+
"display": "standalone",
|
|
9
|
+
"display_override": ["window-controls-overlay", "standalone"],
|
|
10
|
+
"orientation": "any",
|
|
11
|
+
"background_color": "#1a1b26",
|
|
12
|
+
"theme_color": "#1a1b26",
|
|
13
|
+
"categories": ["developer", "utilities"],
|
|
14
|
+
"screenshots": [],
|
|
15
|
+
"prefer_related_applications": false,
|
|
16
|
+
"icons": [
|
|
17
|
+
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
|
|
18
|
+
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
|
|
19
|
+
],
|
|
20
|
+
"shortcuts": [
|
|
21
|
+
{ "name": "New Terminal", "url": "/", "description": "Open a new terminal session" }
|
|
22
|
+
]
|
|
23
|
+
}
|
package/public/sw.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const CACHE = 'webtun-v3';
|
|
2
|
+
const PRECACHE = [
|
|
3
|
+
'/',
|
|
4
|
+
'/index.html',
|
|
5
|
+
'/manifest.json',
|
|
6
|
+
'/icon-192.png',
|
|
7
|
+
'/icon-512.png',
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
self.addEventListener('install', e => {
|
|
11
|
+
e.waitUntil(
|
|
12
|
+
caches.open(CACHE).then(c => c.addAll(PRECACHE)).then(() => self.skipWaiting())
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
self.addEventListener('activate', e => {
|
|
17
|
+
e.waitUntil(
|
|
18
|
+
caches.keys().then(keys =>
|
|
19
|
+
Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
|
|
20
|
+
).then(() => self.clients.claim())
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
self.addEventListener('message', e => {
|
|
25
|
+
if (e.data === 'skipWaiting') {
|
|
26
|
+
self.skipWaiting();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
self.addEventListener('fetch', e => {
|
|
31
|
+
if (e.request.method !== 'GET') return;
|
|
32
|
+
if (!e.request.url.startsWith('http://') && !e.request.url.startsWith('https://')) return;
|
|
33
|
+
|
|
34
|
+
const url = new URL(e.request.url);
|
|
35
|
+
if (url.pathname.startsWith('/api')) return;
|
|
36
|
+
|
|
37
|
+
// Network-First for HTML/navigation
|
|
38
|
+
const isNavigation = e.request.mode === 'navigate' || (url.pathname === '/') || (url.pathname.endsWith('.html') && e.request.mode === 'same-origin');
|
|
39
|
+
if (isNavigation) {
|
|
40
|
+
e.respondWith(
|
|
41
|
+
fetch(e.request).then(res => {
|
|
42
|
+
if (res && res.status === 200 && res.type !== 'opaque') {
|
|
43
|
+
const clone = res.clone();
|
|
44
|
+
caches.open(CACHE).then(c => { try { c.put(e.request, clone); } catch {} });
|
|
45
|
+
}
|
|
46
|
+
return res;
|
|
47
|
+
}).catch(() =>
|
|
48
|
+
caches.match(e.request).then(cached =>
|
|
49
|
+
cached || caches.match('/index.html')
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Cache-First for static assets
|
|
57
|
+
// CDN resources are fetched from network (not precached) to avoid opaque response failures
|
|
58
|
+
e.respondWith(
|
|
59
|
+
caches.match(e.request).then(cached => {
|
|
60
|
+
if (cached) return cached;
|
|
61
|
+
return fetch(e.request).then(res => {
|
|
62
|
+
if (res && res.status === 200 && res.type !== 'opaque') {
|
|
63
|
+
const clone = res.clone();
|
|
64
|
+
caches.open(CACHE).then(c => { try { c.put(e.request, clone); } catch {} });
|
|
65
|
+
}
|
|
66
|
+
return res;
|
|
67
|
+
}).catch(() => cached || new Response('Offline', { status: 503 }));
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
});
|