Skip to content

Commit

Permalink
feat(indiekit): use service worker
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrobertlloyd committed Apr 15, 2024
1 parent 799134c commit fac293a
Show file tree
Hide file tree
Showing 18 changed files with 180 additions and 74 deletions.
138 changes: 64 additions & 74 deletions packages/frontend/lib/serviceworker.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,87 +134,77 @@ self.addEventListener("fetch", (event) => {
request.headers.get("Accept").includes("text/html")
) {
event.respondWith(
new Promise((resolveWithResponse) => {
const timer = setTimeout(() => {
// Time out: CACHE
retrieveFromCache.then((responseFromCache) => {
if (responseFromCache) {
resolveWithResponse(responseFromCache);
}
});
(async () => {
// CHECK CACHE
const timer = setTimeout(async () => {
const responseFromCache = await retrieveFromCache;
if (responseFromCache) {
return responseFromCache;
}
}, timeout);

Promise.resolve(event.preloadResponse)
.then((preloadResponse) => preloadResponse || fetch(request))
.then((responseFromPreloadOrFetch) => {
// NETWORK
clearTimeout(timer);
const copy = responseFromPreloadOrFetch.clone();
// Stash a copy of this page in the pages cache
try {
event.waitUntil(
caches.open(pagesCacheName).then((pagesCache) => {
return pagesCache.put(request, copy);
}),
);
} catch (error) {
console.error(error);
}
resolveWithResponse(responseFromPreloadOrFetch);
})
.catch((preloadOrFetchError) => {
clearTimeout(timer);
console.error(preloadOrFetchError, request);
// CACHE or FALLBACK
retrieveFromCache.then((responseFromCache) => {
resolveWithResponse(
responseFromCache || caches.match("/offline"),
);
});
});
}),
try {
const preloadResponse = await Promise.resolve(event.preloadResponse);
const responseFromPreloadOrFetch =
preloadResponse || (await fetch(request));

// NETWORK
// Save a copy of page to pages cache
clearTimeout(timer);
const copy = responseFromPreloadOrFetch.clone();
const pagesCache = await caches.open(pagesCacheName);
await pagesCache.put(request, copy);

return responseFromPreloadOrFetch;
} catch (error) {
console.error(error, request);

// CACHE or OFFLINE PAGE
clearTimeout(timer);
const responseFromCache = await retrieveFromCache;
return responseFromCache || caches.match("/offline");
}
})(),
);

return;
}

// For non-HTML requests, look in cache first, fall back to network
event.respondWith(
retrieveFromCache.then((responseFromCache) => {
// CACHE
return (
responseFromCache ||
fetch(request)
.then((responseFromFetch) => {
// NETWORK
// If request is for an image, stash copy in image cache
if (/\.(jpe?g|png|gif|svg|webp)/.test(request.url)) {
const copy = responseFromFetch.clone();
try {
event.waitUntil(
caches.open(imageCacheName).then((imagesCache) => {
return imagesCache.put(request, copy);
}),
);
} catch (error) {
console.error(error);
}
}
return responseFromFetch;
})
.catch((fetchError) => {
console.error(fetchError);
// FALLBACK
// Show an offline placeholder
if (/\.(jpe?g|png|gif|svg|webp)/.test(request.url)) {
return new Response(placeholderImage, {
headers: {
"Content-Type": "image/svg+xml",
"Cache-Control": "no-store",
},
});
}
})
);
}),
(async () => {
try {
const responseFromCache = await retrieveFromCache;

if (responseFromCache) {
// CACHE
return responseFromCache;
} else {
const responseFromFetch = await fetch(request);

// NETWORK
// If request is for an image, save a copy to images cache
if (/\.(jpe?g|png|gif|svg|webp)/.test(request.url)) {
const copy = responseFromFetch.clone();
const imagesCache = await caches.open(imageCacheName);
await imagesCache.put(request, copy);
}

return responseFromFetch;
}
} catch (error) {
console.error(error);

// OFFLINE IMAGE
if (/\.(jpe?g|png|gif|svg|webp)/.test(request.url)) {
return new Response(placeholderImage, {
headers: {
"Content-Type": "image/svg+xml",
"Cache-Control": "no-store",
},
});
}
}
})(),
);
});
14 changes: 14 additions & 0 deletions packages/indiekit/lib/controllers/offline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getServiceWorker } from "../utils.js";

export const offline = (request, response) => {
response.render("offline", {
title: response.locals.__("offline.title"),
});
};

export const serviceworker = async (request, response) => {
const { application } = request.app.locals;
const serviceworker = await getServiceWorker(application);

return response.type("text/javascript").send(serviceworker).end();
};
5 changes: 5 additions & 0 deletions packages/indiekit/lib/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as assetsController from "./controllers/assets.js";
import * as feedController from "./controllers/feed.js";
import * as homepageController from "./controllers/homepage.js";
import * as manifestController from "./controllers/manifest.js";
import * as offlineController from "./controllers/offline.js";
import * as pluginController from "./controllers/plugin.js";
import * as sessionController from "./controllers/session.js";
import * as statusController from "./controllers/status.js";
Expand Down Expand Up @@ -51,6 +52,10 @@ export const routes = (indiekitConfig) => {
assetsController.getShortcutIcon,
);

// Service worker
router.get("/serviceworker.js", offlineController.serviceworker);
router.get("/offline", offlineController.offline);

// Plug-in assets
for (const plugin of application.installedPlugins) {
if (plugin.filePath) {
Expand Down
20 changes: 20 additions & 0 deletions packages/indiekit/lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
import { Buffer } from "node:buffer";
import { readFile } from "node:fs/promises";
import { createRequire } from "node:module";
import path from "node:path";

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

/**
* Get serviceworker.js and update asset versions
* @param {object} application - Application locals
* @returns {Promise<string>} - serviceworker.js file
*/
export const getServiceWorker = async (application) => {
try {
const filePath = require.resolve("@indiekit/frontend/lib/serviceworker.js");
let serviceworker = await readFile(filePath, { encoding: "utf8" });
serviceworker = serviceworker
.replace("APP_VERSION", application.version)
.replace("APP_CSS_PATH", application.cssPath)
.replace("APP_JS_PATH", application.jsPath);
return serviceworker;
} catch (error) {
console.error(error.message);
}
};

/**
* Get fully resolved server URL
* @param {import("express").Request} request - Request
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "Willkommen!"
},
"offline": {
"description": "Diese Seite kann nicht angezeigt werden, da Sie derzeit offline sind.",
"title": "Offline"
},
"plugin": {
"options": "Optionen"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "Welcome!"
},
"offline": {
"title": "Offline",
"description": "This page cannot be displayed because you are currently offline."
},
"plugin": {
"options": "Options"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/es-419.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "¡Bienvenido!"
},
"offline": {
"description": "No se puede mostrar esta página porque estás desconectado actualmente.",
"title": "Sin conexión"
},
"plugin": {
"options": "Opciones"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "¡Bienvenido!"
},
"offline": {
"description": "No se puede mostrar esta página porque estás desconectado actualmente.",
"title": "Sin conexión"
},
"plugin": {
"options": "Opciones"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "Bienvenue !"
},
"offline": {
"description": "Cette page ne peut pas être affichée car vous êtes actuellement hors ligne.",
"title": "Hors ligne"
},
"plugin": {
"options": "Paramètres"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "Selamat datang!"
},
"offline": {
"description": "Halaman ini tidak dapat ditampilkan karena Anda sedang offline.",
"title": "Luring"
},
"plugin": {
"options": "Pilihan"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "Welkom!"
},
"offline": {
"description": "Deze pagina kan niet worden weergegeven omdat je momenteel offline bent.",
"title": "Offline"
},
"plugin": {
"options": "Opties"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "Witaj!"
},
"offline": {
"description": "Ta strona nie może być wyświetlana, ponieważ jesteś obecnie offline.",
"title": "Offline"
},
"plugin": {
"options": "Opcje"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "Bem-vindo!"
},
"offline": {
"description": "Esta página não pode ser exibida porque você está offline no momento.",
"title": "Off-line"
},
"plugin": {
"options": "Opções"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/sr.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "Dobrodošli!"
},
"offline": {
"description": "Nije moguće prikazati ovu stranicu jer ste trenutno van mreže.",
"title": "Vanmrežne"
},
"plugin": {
"options": "Opcije"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "Välkommen!"
},
"offline": {
"description": "Den här sidan kan inte visas eftersom du för närvarande är offline.",
"title": "Offline"
},
"plugin": {
"options": "Alternativ"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/indiekit/locales/zh-Hans-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"homepage": {
"title": "欢迎!"
},
"offline": {
"description": "无法显示此页面,因为您当前处于脱机状态。",
"title": "离线"
},
"plugin": {
"options": "选项"
},
Expand Down
20 changes: 20 additions & 0 deletions packages/indiekit/test/integration/200-offline-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { strict as assert } from "node:assert";
import { after, describe, it } from "node:test";
import supertest from "supertest";
import { testServer } from "@indiekit-test/server";

const server = await testServer();
const request = supertest.agent(server);

describe("indiekit GET /offline", () => {
it("Displays offline page", async () => {
const result = await request.get("/offline");

assert.equal(result.status, 200);
assert.equal(result.type, "text/html");
});

after(() => {
server.close(() => process.exit(0));
});
});
9 changes: 9 additions & 0 deletions packages/indiekit/views/offline.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% extends "document.njk" %}

{% block content %}
{{ warningText({
icon: "offline",
iconFallbackText: false,
text: __("offline.description")
}) | indent(2) }}
{% endblock %}

0 comments on commit fac293a

Please sign in to comment.