Skip to content

Commit 23df9b0

Browse files
committed
added front service worker
1 parent 9701d5f commit 23df9b0

4 files changed

Lines changed: 348 additions & 13 deletions

File tree

components/chead.html

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1-
<script src = "/assets/scripts/monuloader.js"></script>
1+
<script>
2+
if ('serviceWorker' in navigator) {
3+
window.addEventListener('load', () => {
4+
// Register the service worker from the root path
5+
navigator.serviceWorker.register('/sw.js', { scope: '/' })
6+
.then(registration => {
7+
console.log('Game service worker registered:', registration);
8+
})
9+
.catch(error => {
10+
console.error('Service worker registration failed:', error);
11+
});
12+
});
13+
}
14+
</script>
15+
<script src="/assets/scripts/monuloader.js"></script>
216
<gtag />
3-
<link rel = "icon" href = "/assets/images/logo.jpg" />
4-
<meta
5-
name="viewport"
6-
content="width=device-width, initial-scale=1, maximum-scale=1"
7-
/>
17+
<link rel="icon" href="/assets/images/logo.jpg" />
18+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
819
<link rel="stylesheet" href="/assets/styles/home.css" />
920
<link rel="stylesheet" href="/assets/styles/master.css" />
10-
<link
11-
rel="stylesheet"
12-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
21+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
1322
integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg=="
14-
crossorigin="anonymous"
15-
referrerpolicy="no-referrer"
16-
/>
23+
crossorigin="anonymous" referrerpolicy="no-referrer" />
1724
<title>{{title}}</title>
1825
<script src="/assets/scripts/navigation.js"></script>

static/big_game_script.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ window.ccPorted = window.ccPorted || {};
3636
console.error('Service worker registration failed:', error);
3737
});
3838
});
39+
} else {
40+
console.warn('Service workers are not supported in this browser.');
3941
}
4042
// Setup communication with the parent frame for cache control
4143
function setupCacheControl() {

static/game_worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ self.addEventListener('fetch', event => {
228228
// Choose caching strategy based on request type
229229
if (isCacheableRequest(request)) {
230230
// For most game assets, use time-aware cache-first
231-
if (request.url.includes('game-')) {
231+
if (request.url.includes('game_')) {
232232
event.respondWith(timeAwareCacheFirstStrategy(request));
233233
}
234234
// For HTML and JSON files, use network-first to get latest versions

static/sw.js

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
// service-worker.js - Enhanced for cross-origin caching
2+
const CACHE_NAME = 'ccported-cache-v1';
3+
const CACHE_METADATA_KEY = 'ccported-cache-metadata';
4+
const MAX_AGE_DAYS = 7; // Revalidate files older than 7 days
5+
6+
// Assets to cache immediately on service worker installation
7+
const PRECACHE_ASSETS = [
8+
'./index.html',
9+
];
10+
11+
// Add the domains you want to cache from
12+
const ALLOWED_DOMAINS = [
13+
'ccgstatic.com',
14+
// Add other domains here as needed
15+
];
16+
const BLACKLIST = [
17+
"pagead2.googlesyndication.com",
18+
"storage.ko-fi.com",
19+
"www.google-analytics.com",
20+
"amazonaws.com"
21+
]
22+
23+
// Install event - precache critical resources
24+
self.addEventListener('install', event => {
25+
fetch("/servers.txt").then(response => response.text()).then(text => {
26+
const servers = text.split('\n').map(line => line.split(",")[0].trim()).filter(line => line.length > 0);
27+
ALLOWED_DOMAINS.push(...servers);
28+
});
29+
event.waitUntil(
30+
caches.open(CACHE_NAME)
31+
.then(cache => {
32+
console.log('Precaching game assets');
33+
return cache.addAll(PRECACHE_ASSETS);
34+
})
35+
.then(() => self.skipWaiting())
36+
);
37+
});
38+
39+
// Activate event - clean up old caches
40+
self.addEventListener('activate', event => {
41+
event.waitUntil(
42+
caches.keys().then(cacheNames => {
43+
return Promise.all(
44+
cacheNames.filter(cacheName => {
45+
return cacheName.startsWith('ccported-cache-') &&
46+
cacheName !== CACHE_NAME;
47+
}).map(cacheName => {
48+
return caches.delete(cacheName);
49+
})
50+
);
51+
}).then(() => self.clients.claim())
52+
);
53+
});
54+
55+
// Helper function to determine if a request should be cached
56+
function isCacheableRequest(request) {
57+
const url = new URL(request.url);
58+
59+
// Never cache txt files, change often
60+
if (url.pathname.endsWith('.txt')) {
61+
return false;
62+
}
63+
64+
// Only cache GET requests
65+
if (request.method !== 'GET') {
66+
return false;
67+
}
68+
69+
// Check if domain is allowed for cross-origin caching
70+
const isAllowedDomain = ALLOWED_DOMAINS.some(domain => url.hostname.includes(domain));
71+
const isBlacklisted = BLACKLIST.some(domain => url.hostname.includes(domain));
72+
if (isBlacklisted) {
73+
return false;
74+
}
75+
// Allow caching for origin domain or explicitly allowed domains
76+
const isSameOrigin = url.origin === self.location.origin;
77+
if (!isSameOrigin && !isAllowedDomain) {
78+
return false;
79+
}
80+
81+
// Cache based on file extensions (website - non game- assets)
82+
const extensions = [
83+
'.html', '.js', '.css', '.json',
84+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg',
85+
'.woff', '.woff2', '.ttf', '.otf',
86+
'.mp3', '.ogg', '.wav',
87+
'.mp4', '.webm'
88+
];
89+
90+
if (extensions.some(ext => url.pathname.endsWith(ext))) {
91+
return true;
92+
}
93+
94+
return false;
95+
}
96+
97+
// Network-first strategy with fallback to cache
98+
async function networkFirstStrategy(request) {
99+
try {
100+
// For cross-origin requests that need credentials, use appropriate fetch options
101+
const fetchOptions = {};
102+
if (isCrossOrigin(request)) {
103+
fetchOptions.mode = 'cors';
104+
// Don't send credentials by default for cross-origin requests
105+
// unless you explicitly need them
106+
fetchOptions.credentials = 'same-origin';
107+
}
108+
109+
// Try network first
110+
const networkResponse = await fetch(request, fetchOptions);
111+
112+
// If successful, clone and cache
113+
if (networkResponse.ok) {
114+
const cache = await caches.open(CACHE_NAME);
115+
cache.put(request, networkResponse.clone());
116+
return networkResponse;
117+
}
118+
119+
// If network fails with an error status, try cache
120+
throw new Error('Network response was not ok');
121+
} catch (error) {
122+
// Fall back to cache
123+
const cachedResponse = await caches.match(request);
124+
if (cachedResponse) {
125+
return cachedResponse;
126+
}
127+
128+
// Nothing in cache, return the error response
129+
throw error;
130+
}
131+
}
132+
133+
// Helper function to check if request is cross-origin
134+
function isCrossOrigin(request) {
135+
const url = new URL(request.url);
136+
return url.origin !== self.location.origin;
137+
}
138+
139+
// Cache-first strategy with network fallback
140+
async function cacheFirstStrategy(request) {
141+
// Try the cache first
142+
const cachedResponse = await caches.match(request);
143+
if (cachedResponse) {
144+
return cachedResponse;
145+
}
146+
147+
// If not in cache, get from network with appropriate options
148+
try {
149+
const fetchOptions = {};
150+
if (isCrossOrigin(request)) {
151+
fetchOptions.mode = 'cors';
152+
fetchOptions.credentials = 'same-origin';
153+
}
154+
155+
const networkResponse = await fetch(request, fetchOptions);
156+
157+
// Cache the network response for future
158+
if (networkResponse.ok) {
159+
const cache = await caches.open(CACHE_NAME);
160+
cache.put(request, networkResponse.clone());
161+
}
162+
163+
return networkResponse;
164+
} catch (error) {
165+
// Handle fetch failure
166+
console.error('Fetch failed:', error);
167+
throw error;
168+
}
169+
}
170+
171+
// Stale-while-revalidate strategy
172+
async function staleWhileRevalidateStrategy(request) {
173+
// Try to get from cache immediately
174+
const cachedResponse = await caches.match(request);
175+
176+
// Fetch from network and update cache in the background
177+
const fetchOptions = {};
178+
if (isCrossOrigin(request)) {
179+
fetchOptions.mode = 'cors';
180+
fetchOptions.credentials = 'same-origin';
181+
}
182+
183+
const fetchPromise = fetch(request, fetchOptions).then(networkResponse => {
184+
if (networkResponse.ok) {
185+
const cache = caches.open(CACHE_NAME).then(cache => {
186+
cache.put(request, networkResponse.clone());
187+
return networkResponse;
188+
});
189+
}
190+
return networkResponse;
191+
}).catch(error => {
192+
console.error('Background fetch failed:', error);
193+
});
194+
195+
// Return the cached response immediately if we have it
196+
return cachedResponse || fetchPromise;
197+
}
198+
199+
// Time-aware cache-first strategy with network fallback
200+
async function timeAwareCacheFirstStrategy(request) {
201+
// Check if we have a cached version first
202+
const cachedResponse = await caches.match(request.url);
203+
// Determine if the cached file is stale
204+
const isStale = await isFileStale(request.url);
205+
206+
// If we have a non-stale cached response, return it
207+
if (cachedResponse && !isStale) {
208+
return cachedResponse;
209+
}
210+
console.log(request.url, isStale)
211+
212+
// Otherwise, get from network (either no cache or stale cache)
213+
try {
214+
const fetchOptions = {};
215+
if (isCrossOrigin(request)) {
216+
fetchOptions.mode = 'cors';
217+
fetchOptions.credentials = 'same-origin';
218+
}
219+
220+
const networkResponse = await fetch(request, fetchOptions);
221+
222+
// Cache the network response for future
223+
if (networkResponse.ok) {
224+
const cache = await caches.open(CACHE_NAME);
225+
await cache.put(request, networkResponse.clone());
226+
await updateCacheTimestamp(request.url);
227+
}
228+
229+
return networkResponse;
230+
} catch (error) {
231+
// If network fails and we have a cached version (even if stale), return it
232+
if (cachedResponse) {
233+
return cachedResponse;
234+
}
235+
236+
// No cached fallback available
237+
console.error('Fetch failed:', error);
238+
throw error;
239+
}
240+
}
241+
242+
// Helper function to save cache metadata
243+
async function saveMetadataStore(metadata) {
244+
const cache = await caches.open(CACHE_NAME);
245+
const metadataBlob = new Blob([JSON.stringify(metadata)], { type: 'application/json' });
246+
const metadataResponse = new Response(metadataBlob);
247+
await cache.put(CACHE_METADATA_KEY, metadataResponse);
248+
}
249+
250+
// Helper function to update timestamp for a cached file
251+
async function updateCacheTimestamp(url) {
252+
const metadata = await getMetadataStore();
253+
metadata[url] = Date.now();
254+
await saveMetadataStore(metadata);
255+
}
256+
257+
async function isFileStale(url) {
258+
const metadata = await getMetadataStore();
259+
const timestamp = metadata[url];
260+
261+
if (!timestamp) {
262+
console.log("No timestamp for", url);
263+
return true; // No timestamp means we should revalidate
264+
}
265+
266+
const now = Date.now();
267+
const age = now - timestamp;
268+
const maxAgeMs = MAX_AGE_DAYS * 24 * 60 * 60 * 1000; // Convert days to milliseconds
269+
return age > maxAgeMs;
270+
}
271+
272+
// Helper function to get the cache metadata store
273+
async function getMetadataStore() {
274+
const cache = await caches.open(CACHE_NAME);
275+
const metadataResponse = await cache.match(CACHE_METADATA_KEY);
276+
277+
if (metadataResponse) {
278+
return metadataResponse.json();
279+
} else {
280+
// Initialize with empty metadata if none exists
281+
return {};
282+
}
283+
}
284+
285+
// Fetch event - handle all requests
286+
self.addEventListener('fetch', event => {
287+
// Ignore non-GET requests
288+
if (event.request.method !== 'GET') return;
289+
290+
const request = event.request;
291+
292+
// Choose caching strategy based on request type
293+
if (isCacheableRequest(request)) {
294+
// For image assets from external domains, use cache-first
295+
const url = new URL(request.url);
296+
const isImage = /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(url.pathname);
297+
const isExternalDomain = ALLOWED_DOMAINS.some(domain => url.hostname.includes(domain));
298+
299+
if (isImage && isExternalDomain) {
300+
event.respondWith(timeAwareCacheFirstStrategy(request));
301+
}
302+
// For game assets, use cache-first
303+
else if (request.url.includes('game_')) {
304+
event.respondWith(cacheFirstStrategy(request));
305+
}
306+
// For HTML and JSON files, use network-first to get latest versions
307+
else if (request.url.endsWith('.html') || request.url.endsWith('.json') || request.url.endsWith('.txt')) {
308+
event.respondWith(networkFirstStrategy(request));
309+
}
310+
// For everything else cacheable, use time-aware cache
311+
else {
312+
event.respondWith(timeAwareCacheFirstStrategy(request));
313+
}
314+
}
315+
// Let non-cacheable requests go through without service worker intervention
316+
});
317+
318+
// Listen for messages from the main thread
319+
self.addEventListener('message', event => {
320+
// Handle custom cache invalidation
321+
if (event.data && event.data.action === 'CLEAR_CACHE') {
322+
caches.delete(CACHE_NAME).then(() => {
323+
event.ports[0].postMessage({ status: 'Cache cleared' });
324+
});
325+
}
326+
});

0 commit comments

Comments
 (0)