From 946d8d695c038e565fb2e8466e6935d1fe147cb4 Mon Sep 17 00:00:00 2001
From: fynks <75840152+fynks@users.noreply.github.com>
Date: Thu, 18 Sep 2025 10:52:36 +0500
Subject: [PATCH] feat: implement fetch with timeout and fallback for improved
data loading reliability
---
dist/js/app.js | 45 +++++++++++++++++++++++++++++++--------
scripts/replace-assets.js | 34 ++++++++++++++++++++++++++---
workbox-config.js | 20 ++++++-----------
3 files changed, 74 insertions(+), 25 deletions(-)
diff --git a/dist/js/app.js b/dist/js/app.js
index bc1250f..d4f411b 100644
--- a/dist/js/app.js
+++ b/dist/js/app.js
@@ -1083,12 +1083,38 @@ document.addEventListener('DOMContentLoaded', async () => {
loadingManager.show('#pricing-table-container', 'Loading pricing data...')
];
- const dataUrls = ['./json/file-hosts-optimized.json', './json/adult-hosts-optimized.json', './json/pricing.json'];
- const fetchPromises = dataUrls.map(url =>
- fetch(url).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status} for ${url}`)))
- );
+ // Helper: fetch with timeout and graceful fallbacks
+ const fetchWithTimeout = (url, { timeout = 8000 } = {}) => {
+ const controller = new AbortController();
+ const id = setTimeout(() => controller.abort(), timeout);
+ return fetch(url, { signal: controller.signal })
+ .finally(() => clearTimeout(id));
+ };
- const results = await Promise.allSettled(fetchPromises);
+ const fetchJsonFallback = async (urls, timeout = 8000) => {
+ let lastErr;
+ for (const url of urls) {
+ try {
+ const res = await fetchWithTimeout(url, { timeout });
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
+ return await res.json();
+ } catch (err) {
+ lastErr = err;
+ continue;
+ }
+ }
+ throw lastErr || new Error('All fetch fallbacks failed');
+ };
+
+ // Prefer optimized JSON, fall back to original if missing
+ const datasets = [
+ ['./json/file-hosts-optimized.json', './json/file-hosts.json'],
+ ['./json/adult-hosts-optimized.json', './json/adult-hosts.json'],
+ ['./json/pricing.json']
+ ];
+
+ const fetchPromises = datasets.map(list => fetchJsonFallback(list, 8000));
+ const results = await Promise.allSettled(fetchPromises);
results.forEach((result, index) => {
const containers = ['#file-hosts-table-container', '#adult-hosts-table-container', '#pricing-table-container'];
@@ -1111,9 +1137,9 @@ document.addEventListener('DOMContentLoaded', async () => {
}
}
} else {
- console.error(`Error loading data (${dataUrls[index]}):`, result.reason);
+ console.error('Error loading data set:', result.reason);
const el = document.querySelector(containers[index]);
- if (el) el.innerHTML = `
Failed to load data: ${dataUrls[index]}
`;
+ if (el) el.innerHTML = `Failed to load data. Please check your connection and refresh.
`;
}
});
@@ -1147,8 +1173,9 @@ window.addEventListener('unhandledrejection', e => {
});
if ('serviceWorker' in navigator) {
- window.addEventListener('load', () => {
- navigator.serviceWorker.register('/sw.js').catch(err => {
+ window.addEventListener('load', () => {
+ // Use relative path to work under subdirectories too
+ navigator.serviceWorker.register('sw.js').catch(err => {
console.warn('Service worker registration failed:', err);
});
});
diff --git a/scripts/replace-assets.js b/scripts/replace-assets.js
index dec8495..0185bd9 100644
--- a/scripts/replace-assets.js
+++ b/scripts/replace-assets.js
@@ -29,6 +29,26 @@ function replaceAssets() {
let cssReplaced = 0;
let jsReplaced = 0;
+ // Compute simple query hash for cache-busting if files exist
+ const cssMinPath = path.resolve(__dirname, '..', 'dist', 'css', 'styles-min.css');
+ const jsMinPath = path.resolve(__dirname, '..', 'dist', 'js', 'app-min.js');
+ const safeHash = (p) => {
+ try {
+ const buf = fs.readFileSync(p);
+ let hash = 0;
+ for (let i = 0; i < buf.length; i += Math.ceil(buf.length / 128) || 1) {
+ hash = (hash * 31 + buf[i]) >>> 0;
+ }
+ return hash.toString(36);
+ } catch {
+ return null;
+ }
+ };
+ const cssHash = safeHash(cssMinPath);
+ const jsHash = safeHash(jsMinPath);
+ const cssTarget = cssHash ? `href="./css/styles-min.css?v=${cssHash}"` : 'href="./css/styles-min.css"';
+ const jsTarget = jsHash ? `src="js/app-min.js?v=${jsHash}"` : 'src="js/app-min.js"';
+
// Replace CSS links in head - more flexible patterns
const cssPatterns = [
/href=["']\.\/css\/styles\.css["']/g,
@@ -39,11 +59,19 @@ function replaceAssets() {
cssPatterns.forEach(pattern => {
const matches = updatedHeadContent.match(pattern);
if (matches) {
- updatedHeadContent = updatedHeadContent.replace(pattern, 'href="./css/styles-min.css"');
+ updatedHeadContent = updatedHeadContent.replace(pattern, cssTarget);
cssReplaced += matches.length;
}
});
+ // Ensure existing preload to styles-min.css carries the same version hash
+ if (cssHash) {
+ const preloadMinCssPattern = /href=["']\/?css\/styles-min\.css["']/g;
+ if (preloadMinCssPattern.test(updatedHeadContent)) {
+ updatedHeadContent = updatedHeadContent.replace(preloadMinCssPattern, `href="/css/styles-min.css?v=${cssHash}"`);
+ }
+ }
+
// Replace JS script src in head - more flexible patterns
const jsPatterns = [
/src=["']\.\/js\/app\.js["']/g,
@@ -54,7 +82,7 @@ function replaceAssets() {
jsPatterns.forEach(pattern => {
const matches = updatedHeadContent.match(pattern);
if (matches) {
- updatedHeadContent = updatedHeadContent.replace(pattern, 'src="js/app-min.js"');
+ updatedHeadContent = updatedHeadContent.replace(pattern, jsTarget);
jsReplaced += matches.length;
}
});
@@ -68,7 +96,7 @@ function replaceAssets() {
jsPatterns.forEach(pattern => {
const matches = updatedBodyContent.match(pattern);
if (matches) {
- updatedBodyContent = updatedBodyContent.replace(pattern, 'src="js/app-min.js"');
+ updatedBodyContent = updatedBodyContent.replace(pattern, jsTarget);
jsReplaced += matches.length;
}
});
diff --git a/workbox-config.js b/workbox-config.js
index be45328..323b41f 100644
--- a/workbox-config.js
+++ b/workbox-config.js
@@ -1,9 +1,8 @@
module.exports = {
// Base directory - still needed for SW generation
globDirectory: "dist/",
-
- // REMOVE globPatterns entirely - no precaching!
- // globPatterns: [], // Explicitly empty or just omit
+ // Explicitly disable precaching for lean SW install
+ globPatterns: [],
// Enhanced runtime caching strategies
runtimeCaching: [
@@ -72,8 +71,8 @@ module.exports = {
plugins: [
{
handlerDidError: async () => {
- // Return fallback image on error
- return caches.match('/assets/fallback-image.svg');
+ // Return fallback image on error (use an existing icon)
+ return caches.match('/favicon.svg');
}
}
]
@@ -81,9 +80,9 @@ module.exports = {
},
- // 5. JSON responses - NetworkFirst with timeout
+ // 5. JSON responses (local and API) - NetworkFirst with timeout
{
- urlPattern: /^https:\/\/api\.|\/api\/|\.json$/i,
+ urlPattern: ({url}) => /\.json$/i.test(url.pathname) || /\/api\//i.test(url.pathname) || /^https:\/\/api\./i.test(url.href),
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
@@ -92,12 +91,7 @@ module.exports = {
maxEntries: 100,
maxAgeSeconds: 5 * 60 // 5 minutes for fresh API data
},
- cacheableResponse: {
- statuses: [0, 200],
- headers: {
- 'x-cache': 'hit' // Optional: only cache specific responses
- }
- },
+ cacheableResponse: { statuses: [0, 200] },
matchOptions: {
ignoreSearch: false // Consider query params
}