Skip to content

Commit fac293a

Browse files
feat(indiekit): use service worker
1 parent 799134c commit fac293a

File tree

18 files changed

+180
-74
lines changed

18 files changed

+180
-74
lines changed

packages/frontend/lib/serviceworker.js

+64-74
Original file line numberDiff line numberDiff line change
@@ -134,87 +134,77 @@ self.addEventListener("fetch", (event) => {
134134
request.headers.get("Accept").includes("text/html")
135135
) {
136136
event.respondWith(
137-
new Promise((resolveWithResponse) => {
138-
const timer = setTimeout(() => {
139-
// Time out: CACHE
140-
retrieveFromCache.then((responseFromCache) => {
141-
if (responseFromCache) {
142-
resolveWithResponse(responseFromCache);
143-
}
144-
});
137+
(async () => {
138+
// CHECK CACHE
139+
const timer = setTimeout(async () => {
140+
const responseFromCache = await retrieveFromCache;
141+
if (responseFromCache) {
142+
return responseFromCache;
143+
}
145144
}, timeout);
146145

147-
Promise.resolve(event.preloadResponse)
148-
.then((preloadResponse) => preloadResponse || fetch(request))
149-
.then((responseFromPreloadOrFetch) => {
150-
// NETWORK
151-
clearTimeout(timer);
152-
const copy = responseFromPreloadOrFetch.clone();
153-
// Stash a copy of this page in the pages cache
154-
try {
155-
event.waitUntil(
156-
caches.open(pagesCacheName).then((pagesCache) => {
157-
return pagesCache.put(request, copy);
158-
}),
159-
);
160-
} catch (error) {
161-
console.error(error);
162-
}
163-
resolveWithResponse(responseFromPreloadOrFetch);
164-
})
165-
.catch((preloadOrFetchError) => {
166-
clearTimeout(timer);
167-
console.error(preloadOrFetchError, request);
168-
// CACHE or FALLBACK
169-
retrieveFromCache.then((responseFromCache) => {
170-
resolveWithResponse(
171-
responseFromCache || caches.match("/offline"),
172-
);
173-
});
174-
});
175-
}),
146+
try {
147+
const preloadResponse = await Promise.resolve(event.preloadResponse);
148+
const responseFromPreloadOrFetch =
149+
preloadResponse || (await fetch(request));
150+
151+
// NETWORK
152+
// Save a copy of page to pages cache
153+
clearTimeout(timer);
154+
const copy = responseFromPreloadOrFetch.clone();
155+
const pagesCache = await caches.open(pagesCacheName);
156+
await pagesCache.put(request, copy);
157+
158+
return responseFromPreloadOrFetch;
159+
} catch (error) {
160+
console.error(error, request);
161+
162+
// CACHE or OFFLINE PAGE
163+
clearTimeout(timer);
164+
const responseFromCache = await retrieveFromCache;
165+
return responseFromCache || caches.match("/offline");
166+
}
167+
})(),
176168
);
169+
177170
return;
178171
}
179172

180173
// For non-HTML requests, look in cache first, fall back to network
181174
event.respondWith(
182-
retrieveFromCache.then((responseFromCache) => {
183-
// CACHE
184-
return (
185-
responseFromCache ||
186-
fetch(request)
187-
.then((responseFromFetch) => {
188-
// NETWORK
189-
// If request is for an image, stash copy in image cache
190-
if (/\.(jpe?g|png|gif|svg|webp)/.test(request.url)) {
191-
const copy = responseFromFetch.clone();
192-
try {
193-
event.waitUntil(
194-
caches.open(imageCacheName).then((imagesCache) => {
195-
return imagesCache.put(request, copy);
196-
}),
197-
);
198-
} catch (error) {
199-
console.error(error);
200-
}
201-
}
202-
return responseFromFetch;
203-
})
204-
.catch((fetchError) => {
205-
console.error(fetchError);
206-
// FALLBACK
207-
// Show an offline placeholder
208-
if (/\.(jpe?g|png|gif|svg|webp)/.test(request.url)) {
209-
return new Response(placeholderImage, {
210-
headers: {
211-
"Content-Type": "image/svg+xml",
212-
"Cache-Control": "no-store",
213-
},
214-
});
215-
}
216-
})
217-
);
218-
}),
175+
(async () => {
176+
try {
177+
const responseFromCache = await retrieveFromCache;
178+
179+
if (responseFromCache) {
180+
// CACHE
181+
return responseFromCache;
182+
} else {
183+
const responseFromFetch = await fetch(request);
184+
185+
// NETWORK
186+
// If request is for an image, save a copy to images cache
187+
if (/\.(jpe?g|png|gif|svg|webp)/.test(request.url)) {
188+
const copy = responseFromFetch.clone();
189+
const imagesCache = await caches.open(imageCacheName);
190+
await imagesCache.put(request, copy);
191+
}
192+
193+
return responseFromFetch;
194+
}
195+
} catch (error) {
196+
console.error(error);
197+
198+
// OFFLINE IMAGE
199+
if (/\.(jpe?g|png|gif|svg|webp)/.test(request.url)) {
200+
return new Response(placeholderImage, {
201+
headers: {
202+
"Content-Type": "image/svg+xml",
203+
"Cache-Control": "no-store",
204+
},
205+
});
206+
}
207+
}
208+
})(),
219209
);
220210
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { getServiceWorker } from "../utils.js";
2+
3+
export const offline = (request, response) => {
4+
response.render("offline", {
5+
title: response.locals.__("offline.title"),
6+
});
7+
};
8+
9+
export const serviceworker = async (request, response) => {
10+
const { application } = request.app.locals;
11+
const serviceworker = await getServiceWorker(application);
12+
13+
return response.type("text/javascript").send(serviceworker).end();
14+
};

packages/indiekit/lib/routes.js

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as assetsController from "./controllers/assets.js";
66
import * as feedController from "./controllers/feed.js";
77
import * as homepageController from "./controllers/homepage.js";
88
import * as manifestController from "./controllers/manifest.js";
9+
import * as offlineController from "./controllers/offline.js";
910
import * as pluginController from "./controllers/plugin.js";
1011
import * as sessionController from "./controllers/session.js";
1112
import * as statusController from "./controllers/status.js";
@@ -51,6 +52,10 @@ export const routes = (indiekitConfig) => {
5152
assetsController.getShortcutIcon,
5253
);
5354

55+
// Service worker
56+
router.get("/serviceworker.js", offlineController.serviceworker);
57+
router.get("/offline", offlineController.offline);
58+
5459
// Plug-in assets
5560
for (const plugin of application.installedPlugins) {
5661
if (plugin.filePath) {

packages/indiekit/lib/utils.js

+20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
22
import { Buffer } from "node:buffer";
3+
import { readFile } from "node:fs/promises";
34
import { createRequire } from "node:module";
45
import path from "node:path";
56

@@ -40,6 +41,25 @@ export const decrypt = (hash, iv) => {
4041
return decrypted.toString();
4142
};
4243

44+
/**
45+
* Get serviceworker.js and update asset versions
46+
* @param {object} application - Application locals
47+
* @returns {Promise<string>} - serviceworker.js file
48+
*/
49+
export const getServiceWorker = async (application) => {
50+
try {
51+
const filePath = require.resolve("@indiekit/frontend/lib/serviceworker.js");
52+
let serviceworker = await readFile(filePath, { encoding: "utf8" });
53+
serviceworker = serviceworker
54+
.replace("APP_VERSION", application.version)
55+
.replace("APP_CSS_PATH", application.cssPath)
56+
.replace("APP_JS_PATH", application.jsPath);
57+
return serviceworker;
58+
} catch (error) {
59+
console.error(error.message);
60+
}
61+
};
62+
4363
/**
4464
* Get fully resolved server URL
4565
* @param {import("express").Request} request - Request

packages/indiekit/locales/de.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "Willkommen!"
77
},
8+
"offline": {
9+
"description": "Diese Seite kann nicht angezeigt werden, da Sie derzeit offline sind.",
10+
"title": "Offline"
11+
},
812
"plugin": {
913
"options": "Optionen"
1014
},

packages/indiekit/locales/en.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "Welcome!"
77
},
8+
"offline": {
9+
"title": "Offline",
10+
"description": "This page cannot be displayed because you are currently offline."
11+
},
812
"plugin": {
913
"options": "Options"
1014
},

packages/indiekit/locales/es-419.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "¡Bienvenido!"
77
},
8+
"offline": {
9+
"description": "No se puede mostrar esta página porque estás desconectado actualmente.",
10+
"title": "Sin conexión"
11+
},
812
"plugin": {
913
"options": "Opciones"
1014
},

packages/indiekit/locales/es.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "¡Bienvenido!"
77
},
8+
"offline": {
9+
"description": "No se puede mostrar esta página porque estás desconectado actualmente.",
10+
"title": "Sin conexión"
11+
},
812
"plugin": {
913
"options": "Opciones"
1014
},

packages/indiekit/locales/fr.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "Bienvenue !"
77
},
8+
"offline": {
9+
"description": "Cette page ne peut pas être affichée car vous êtes actuellement hors ligne.",
10+
"title": "Hors ligne"
11+
},
812
"plugin": {
913
"options": "Paramètres"
1014
},

packages/indiekit/locales/id.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "Selamat datang!"
77
},
8+
"offline": {
9+
"description": "Halaman ini tidak dapat ditampilkan karena Anda sedang offline.",
10+
"title": "Luring"
11+
},
812
"plugin": {
913
"options": "Pilihan"
1014
},

packages/indiekit/locales/nl.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "Welkom!"
77
},
8+
"offline": {
9+
"description": "Deze pagina kan niet worden weergegeven omdat je momenteel offline bent.",
10+
"title": "Offline"
11+
},
812
"plugin": {
913
"options": "Opties"
1014
},

packages/indiekit/locales/pl.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "Witaj!"
77
},
8+
"offline": {
9+
"description": "Ta strona nie może być wyświetlana, ponieważ jesteś obecnie offline.",
10+
"title": "Offline"
11+
},
812
"plugin": {
913
"options": "Opcje"
1014
},

packages/indiekit/locales/pt.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "Bem-vindo!"
77
},
8+
"offline": {
9+
"description": "Esta página não pode ser exibida porque você está offline no momento.",
10+
"title": "Off-line"
11+
},
812
"plugin": {
913
"options": "Opções"
1014
},

packages/indiekit/locales/sr.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "Dobrodošli!"
77
},
8+
"offline": {
9+
"description": "Nije moguće prikazati ovu stranicu jer ste trenutno van mreže.",
10+
"title": "Vanmrežne"
11+
},
812
"plugin": {
913
"options": "Opcije"
1014
},

packages/indiekit/locales/sv.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "Välkommen!"
77
},
8+
"offline": {
9+
"description": "Den här sidan kan inte visas eftersom du för närvarande är offline.",
10+
"title": "Offline"
11+
},
812
"plugin": {
913
"options": "Alternativ"
1014
},

packages/indiekit/locales/zh-Hans-CN.json

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"homepage": {
66
"title": "欢迎!"
77
},
8+
"offline": {
9+
"description": "无法显示此页面,因为您当前处于脱机状态。",
10+
"title": "离线"
11+
},
812
"plugin": {
913
"options": "选项"
1014
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { strict as assert } from "node:assert";
2+
import { after, describe, it } from "node:test";
3+
import supertest from "supertest";
4+
import { testServer } from "@indiekit-test/server";
5+
6+
const server = await testServer();
7+
const request = supertest.agent(server);
8+
9+
describe("indiekit GET /offline", () => {
10+
it("Displays offline page", async () => {
11+
const result = await request.get("/offline");
12+
13+
assert.equal(result.status, 200);
14+
assert.equal(result.type, "text/html");
15+
});
16+
17+
after(() => {
18+
server.close(() => process.exit(0));
19+
});
20+
});

packages/indiekit/views/offline.njk

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends "document.njk" %}
2+
3+
{% block content %}
4+
{{ warningText({
5+
icon: "offline",
6+
iconFallbackText: false,
7+
text: __("offline.description")
8+
}) | indent(2) }}
9+
{% endblock %}

0 commit comments

Comments
 (0)