Skip to content

Commit fdbe659

Browse files
committed
Make it PWA
1 parent 99d996e commit fdbe659

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed

index.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,24 @@
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<title>RetroChat - IRC Style</title>
7+
8+
<!-- PWA Manifest -->
9+
<link rel="manifest" href="/manifest.json">
10+
11+
<!-- PWA Meta Tags -->
12+
<meta name="theme-color" content="#00ff00">
13+
<meta name="background-color" content="#000000">
14+
<meta name="display" content="standalone">
15+
<meta name="orientation" content="portrait-primary">
16+
17+
<!-- Apple PWA Meta Tags -->
18+
<meta name="apple-mobile-web-app-capable" content="yes">
19+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
20+
<meta name="apple-mobile-web-app-title" content="RetroChat">
21+
22+
<!-- Microsoft PWA Meta Tags -->
23+
<meta name="msapplication-TileColor" content="#000000">
24+
<meta name="msapplication-tap-highlight" content="no">
725
<style>
826
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono:wght@400&display=swap');
927

@@ -1989,6 +2007,17 @@
19892007
// Initialize the chat application
19902008
window.addEventListener('DOMContentLoaded', () => {
19912009
new RetroIRC();
2010+
2011+
// Register service worker for PWA functionality
2012+
if ('serviceWorker' in navigator) {
2013+
navigator.serviceWorker.register('/sw.js')
2014+
.then((registration) => {
2015+
console.log('Service Worker registered successfully:', registration);
2016+
})
2017+
.catch((error) => {
2018+
console.log('Service Worker registration failed:', error);
2019+
});
2020+
}
19922021
});
19932022
</script>
19942023
</body>

manifest.json

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"name": "RetroChat - Decentralized Chat",
3+
"short_name": "RetroChat",
4+
"description": "A decentralized chat application with retro terminal aesthetics using Waku protocol",
5+
"start_url": "/",
6+
"display": "standalone",
7+
"background_color": "#000000",
8+
"theme_color": "#00ff00",
9+
"orientation": "portrait-primary",
10+
"categories": ["social", "communication"],
11+
"lang": "en",
12+
"icons": [
13+
{
14+
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiIgdmlld0JveD0iMCAwIDcyIDcyIj4KICA8cmVjdCB3aWR0aD0iNzIiIGhlaWdodD0iNzIiIGZpbGw9IiMwMDAiLz4KICA8dGV4dCB4PSIzNiIgeT0iNDUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMwMGZmMDAiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSJib2xkIj5SQzwvdGV4dD4KPC9zdmc+",
15+
"sizes": "72x72",
16+
"type": "image/svg+xml",
17+
"purpose": "any maskable"
18+
},
19+
{
20+
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NiIgaGVpZ2h0PSI5NiIgdmlld0JveD0iMCAwIDk2IDk2Ij4KICA8cmVjdCB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIGZpbGw9IiMwMDAiLz4KICA8dGV4dCB4PSI0OCIgeT0iNTgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMwMGZmMDAiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMzIiIGZvbnQtd2VpZ2h0PSJib2xkIj5SQzwvdGV4dD4KPC9zdmc+",
21+
"sizes": "96x96",
22+
"type": "image/svg+xml",
23+
"purpose": "any"
24+
},
25+
{
26+
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiB2aWV3Qm94PSIwIDAgMTQ0IDE0NCI+CiAgPHJlY3Qgd2lkdGg9IjE0NCIgaGVpZ2h0PSIxNDQiIGZpbGw9IiMwMDAiLz4KICA8dGV4dCB4PSI3MiIgeT0iODciIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMwMGZmMDAiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iNDgiIGZvbnQtd2VpZ2h0PSJib2xkIj5SQzwvdGV4dD4KPC9zdmc+",
27+
"sizes": "144x144",
28+
"type": "image/svg+xml",
29+
"purpose": "any"
30+
},
31+
{
32+
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOTIiIGhlaWdodD0iMTkyIiB2aWV3Qm94PSIwIDAgMTkyIDE5MiI+CiAgPHJlY3Qgd2lkdGg9IjE5MiIgaGVpZ2h0PSIxOTIiIGZpbGw9IiMwMDAiLz4KICA8dGV4dCB4PSI5NiIgeT0iMTE2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjMDBmZjAwIiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjY0IiBmb250LXdlaWdodD0iYm9sZCI+UkM8L3RleHQ+Cjwvc3ZnPg==",
33+
"sizes": "192x192",
34+
"type": "image/svg+xml",
35+
"purpose": "any"
36+
},
37+
{
38+
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+CiAgPHJlY3Qgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIGZpbGw9IiMwMDAiLz4KICA8dGV4dCB4PSIyNTYiIHk9IjMxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0iIzAwZmYwMCIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSIgZm9udC1zaXplPSIxNzAiIGZvbnQtd2VpZ2h0PSJib2xkIj5SQzwvdGV4dD4KPC9zdmc+",
39+
"sizes": "512x512",
40+
"type": "image/svg+xml",
41+
"purpose": "any maskable"
42+
}
43+
],
44+
"shortcuts": [
45+
{
46+
"name": "Open RetroChat",
47+
"short_name": "Chat",
48+
"description": "Start chatting on the decentralized network",
49+
"url": "/",
50+
"icons": [
51+
{
52+
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NiIgaGVpZ2h0PSI5NiIgdmlld0JveD0iMCAwIDk2IDk2Ij4KICA8cmVjdCB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIGZpbGw9IiMwMDAiLz4KICA8dGV4dCB4PSI0OCIgeT0iNTgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMwMGZmMDAiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMzIiIGZvbnQtd2VpZ2h0PSJib2xkIj5SQzwvdGV4dD4KPC9zdmc+",
53+
"sizes": "96x96"
54+
}
55+
]
56+
}
57+
],
58+
"screenshots": [
59+
{
60+
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NDAiIGhlaWdodD0iNzIwIiB2aWV3Qm94PSIwIDAgNTQwIDcyMCI+CiAgPHJlY3Qgd2lkdGg9IjU0MCIgaGVpZ2h0PSI3MjAiIGZpbGw9IiMwMDAiLz4KICA8cmVjdCB4PSIyMCIgeT0iMjAiIHdpZHRoPSI1MDAiIGhlaWdodD0iNjAiIGZpbGw9IiMwMDExMDAiIHN0cm9rZT0iIzAwZmYwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPHRleHQgeD0iNDAiIHk9IjU1IiBmaWxsPSIjMDBmZjAwIiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjE4Ij5SZXRyb0NoYXQ8L3RleHQ+CiAgPHJlY3QgeD0iMjAiIHk9IjEwMCIgd2lkdGg9IjUwMCIgaGVpZ2h0PSI1MDAiIGZpbGw9IiMwMDAiIHN0cm9rZT0iIzAwZmYwMCIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsLW9wYWNpdHk9IjAuMSIvPgogIDx0ZXh0IHg9IjQwIiB5PSIxNDAiIGZpbGw9IiMwMGZmMDAiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMTIiPls5OjQxXSAmbHQ7Q3liZXJIYWNrZXI0Mjo+IEhlbGxvIGV2ZXJ5b25lITwvdGV4dD4KICA8dGV4dCB4PSI0MCIgeT0iMTcwIiBmaWxsPSIjMDBmZjAwIiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjEyIj5bOTo0Ml0gJmx0O1JldHJvR2FtZXI3Nzo+IEhpIHRoZXJlITwvdGV4dD4KICA8cmVjdCB4PSIyMCIgeT0iNjIwIiB3aWR0aD0iNTAwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjMDAxMTAwIiBzdHJva2U9IiMwMGZmMDAiIHN0cm9rZS13aWR0aD0iMiIvPgogIDxyZWN0IHg9IjQwIiB5PSI2NDAiIHdpZHRoPSIzODAiIGhlaWdodD0iNDAiIGZpbGw9IiMwMDAiIHN0cm9rZT0iIzAwZmYwMCIvPgogIDxyZWN0IHg9IjQ0MCIgeT0iNjQwIiB3aWR0aD0iNjAiIGhlaWdodD0iNDAiIGZpbGw9IiMwMDMzMDAiIHN0cm9rZT0iIzAwZmYwMCIvPgogIDx0ZXh0IHg9IjQ3MCIgeT0iNjY1IiBmaWxsPSIjMDBmZjAwIiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjEyIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5TRU5EPC90ZXh0Pgo8L3N2Zz4=",
61+
"sizes": "540x720",
62+
"type": "image/svg+xml",
63+
"form_factor": "narrow"
64+
}
65+
]
66+
}

sw.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// RetroChat Service Worker
2+
const CACHE_NAME = 'retro-chat-v1';
3+
const STATIC_ASSETS = [
4+
'/',
5+
'/index.html',
6+
'/manifest.json',
7+
// External dependencies - will be cached when first loaded
8+
'https://cdn.jsdelivr.net/npm/[email protected]/dist/protobuf.min.js',
9+
'https://unpkg.com/@waku/[email protected]/bundle/index.js',
10+
'https://fonts.googleapis.com/css2?family=Share+Tech+Mono:wght@400&display=swap'
11+
];
12+
13+
// Install event - cache static assets
14+
self.addEventListener('install', (event) => {
15+
console.log('[SW] Installing service worker');
16+
event.waitUntil(
17+
caches.open(CACHE_NAME).then((cache) => {
18+
console.log('[SW] Caching static assets');
19+
return cache.addAll(STATIC_ASSETS.filter(url => !url.startsWith('http')));
20+
}).then(() => {
21+
// Force the waiting service worker to become the active service worker
22+
return self.skipWaiting();
23+
})
24+
);
25+
});
26+
27+
// Activate event - clean up old caches
28+
self.addEventListener('activate', (event) => {
29+
console.log('[SW] Activating service worker');
30+
event.waitUntil(
31+
caches.keys().then((cacheNames) => {
32+
return Promise.all(
33+
cacheNames.map((cacheName) => {
34+
if (cacheName !== CACHE_NAME) {
35+
console.log('[SW] Deleting old cache:', cacheName);
36+
return caches.delete(cacheName);
37+
}
38+
})
39+
);
40+
}).then(() => {
41+
// Ensure the service worker takes control immediately
42+
return self.clients.claim();
43+
})
44+
);
45+
});
46+
47+
// Fetch event - serve from cache first, then network
48+
self.addEventListener('fetch', (event) => {
49+
// Skip non-GET requests
50+
if (event.request.method !== 'GET') {
51+
return;
52+
}
53+
54+
// Skip WebSocket and other non-HTTP requests
55+
if (!event.request.url.startsWith('http')) {
56+
return;
57+
}
58+
59+
// Skip Waku network requests
60+
if (event.request.url.includes('waku') && event.request.url.includes('wss://')) {
61+
return;
62+
}
63+
64+
event.respondWith(
65+
caches.match(event.request).then((cachedResponse) => {
66+
if (cachedResponse) {
67+
console.log('[SW] Serving from cache:', event.request.url);
68+
return cachedResponse;
69+
}
70+
71+
console.log('[SW] Fetching from network:', event.request.url);
72+
return fetch(event.request).then((response) => {
73+
// Don't cache non-successful responses
74+
if (!response || response.status !== 200 || response.type !== 'basic') {
75+
return response;
76+
}
77+
78+
// Clone the response before caching
79+
const responseToCache = response.clone();
80+
81+
caches.open(CACHE_NAME).then((cache) => {
82+
console.log('[SW] Caching new resource:', event.request.url);
83+
cache.put(event.request, responseToCache);
84+
});
85+
86+
return response;
87+
}).catch((error) => {
88+
console.log('[SW] Fetch failed, trying cache:', error);
89+
90+
// If it's the main page and we're offline, serve from cache
91+
if (event.request.url.endsWith('/') || event.request.url.endsWith('index.html')) {
92+
return caches.match('/index.html');
93+
}
94+
95+
throw error;
96+
});
97+
})
98+
);
99+
});
100+
101+
// Handle background sync for offline message queuing (future enhancement)
102+
self.addEventListener('sync', (event) => {
103+
console.log('[SW] Background sync triggered:', event.tag);
104+
if (event.tag === 'background-sync') {
105+
event.waitUntil(
106+
// This could be used to queue messages when offline
107+
console.log('[SW] Handling background sync')
108+
);
109+
}
110+
});
111+
112+
// Handle push notifications (future enhancement)
113+
self.addEventListener('push', (event) => {
114+
console.log('[SW] Push message received');
115+
116+
const options = {
117+
body: 'New message in RetroChat',
118+
icon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NiIgaGVpZ2h0PSI5NiIgdmlld0JveD0iMCAwIDk2IDk2Ij4KICA8cmVjdCB3aWR0aD0iOTYiIGhlaWdodD0iOTYiIGZpbGw9IiMwMDAiLz4KICA8dGV4dCB4PSI0OCIgeT0iNTgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMwMGZmMDAiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMzIiIGZvbnQtd2VpZ2h0PSJib2xkIj5SQzwvdGV4dD4KPC9zdmc+',
119+
badge: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MiIgaGVpZ2h0PSI3MiIgdmlld0JveD0iMCAwIDcyIDcyIj4KICA8cmVjdCB3aWR0aD0iNzIiIGhlaWdodD0iNzIiIGZpbGw9IiMwMGZmMDAiLz4KICA8dGV4dCB4PSIzNiIgeT0iNDUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMwMDAiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSJib2xkIj5SQzwvdGV4dD4KPC9zdmc+',
120+
tag: 'retro-chat-message',
121+
requireInteraction: false
122+
};
123+
124+
event.waitUntil(
125+
self.registration.showNotification('RetroChat', options)
126+
);
127+
});
128+
129+
// Handle notification click
130+
self.addEventListener('notificationclick', (event) => {
131+
console.log('[SW] Notification clicked');
132+
event.notification.close();
133+
134+
event.waitUntil(
135+
clients.openWindow('/')
136+
);
137+
});
138+
139+
console.log('[SW] Service worker loaded');

0 commit comments

Comments
 (0)