+1








766fb96ff2
* Adding support for hosting with URL prefix to make reverse-proxy setup on sub-path possible
* Adjusting recent changes to auth.html (changing to relative URLs) after rebasing from upstream
* Adjusting service_worker and application manifest to support URL-prefix
* Add notes on Space Lua Pattern Matching (#1366)
* Remove common/space_lua/stdlib/math.md (#1371)
* Escape pipe symbol in string.gsub (#1370)
* feat: configure vim in CONFIG (#1369)
* Fix week calculation (#1368)
* Add more tests for week calculation
* Fix week calculation
* Add more tests
* Implement Lua built-in function os.difftime (#1367)
* Implement Lua built-in function os.difftime
* Test os.difftime
---------
Co-authored-by: Zef Hemel <zef@zef.me>
* Fix index page naming during server initialization (#1365)
* Allow non-zero shell exit codes (#1377)
* Generate version from git describe at build time (#1374)
* Generate version from git describe at build time
* Add 2.0-beta prefix
* Use Deno.Command instead of Deno.run
* Add missing case to math.random and check arguments (#1372)
* Fix paragraphs for the tag page (#1376)
* Render a predefined string for empty markdown table instead of pipe characters (#1387)
* Fix string.find (#1390)
* Fetch tags and history for proper version string generation (#1388)
* Fix support for disabling shortcuts (#1393)
* Add GitHub Actions Cache to reduce build times (#1394)
* Order strings in queries using collation algorithm (#1391)
Configurable with queryCollation, default value maintains old behaviour.
Added tests, schema, example settings, and documentation to query page.
Close: #614
Close: #1316
* Utility function to handle empty query results and templates (#1386)
* Add global Lua function 'some'
Moved documentation for 'each' function to non-standard section.
* Revert "Add optional argument to template.each for empty collection output (#1359)"
This reverts commit b73dadec67.
* Reimplement #1359 using 'some' function
* Update Lua to event.listEvents to match plugos syscall (#1397)
The documentation, example, and other function names use
`event.listEvents`, but `event.list` is being registered with the
client.
* Update widget header to not overflow container (#1410)
Also add a 10px margin below the header to maintain consistent spacing
around the widget contents.
* fix: case insensitive admonitions by default (#1409)
* fix: closing bracket is missing (#1408)
* Add optional priority attribute to PageCreatingContent (#1392)
* Add optional priority attribute to PageCreatingContent
* Minor simplification
* Fix comments
* Revert changes
* Add pageEventHandlers
* Use UTC year, month, and date for strings lacking time components (#1403)
* feat: allow CONFIG.md in any directory (#1398)
* fix: bundle external dependencies (#1379)
* Deno bump
* Copy-edit splash screen text (#1413)
* Fixes #1401 by implementing Lua's select
* Fixes offline capability of PWA (I hope).
Now storing config in localStorage.
* fix lint
* Reduce blast radius of Lua errors
* Fixes #1411: adds 'priority' setting for space style
* Refactor: Merging common/ into web/
* Fixes #1400
* Improves #1312 by rendering Lua expressions (but not widgets)
* Fix minor typos, add configuration to the docs
---------
Co-authored-by: wbhouston <wmbhouston@gmail.com>
Co-authored-by: Lennart Ochel <lennart.ochel@outlook.com>
Co-authored-by: Matthias Benaets <89214559+MatthiasBenaets@users.noreply.github.com>
Co-authored-by: Zef Hemel <zef@zef.me>
Co-authored-by: Marek S. Łukasiewicz <marek@lukasiewicz.tech>
Co-authored-by: Ivan Alejandro <ivanalejandro0@gmail.com>
Co-authored-by: Albert-Jan de Vries <aj+github@albert-jandevries.nl>
Co-authored-by: Cole Stowell <121599022+costowell@users.noreply.github.com>
Co-authored-by: Jim Tittsler <jimt@onjapan.net>
240 lines
7.4 KiB
TypeScript
240 lines
7.4 KiB
TypeScript
import type { FileContent } from "./spaces/datastore_space_primitives.ts";
|
|
import { simpleHash } from "$lib/crypto.ts";
|
|
import { DataStore } from "$lib/data/datastore.ts";
|
|
import { IndexedDBKvPrimitives } from "$lib/data/indexeddb_kv_primitives.ts";
|
|
import {
|
|
decodePageURI,
|
|
looksLikePathWithExtension,
|
|
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
|
|
|
const CACHE_NAME = "{{CACHE_NAME}}";
|
|
|
|
//`location.href` minus this worker's filename will be our base URL, including any URL prefix
|
|
//(-1 is to remove the trailing '/')
|
|
const workerFilename = location.pathname.substring(
|
|
location.pathname.lastIndexOf("/") + 1,
|
|
);
|
|
const baseURI = location.href.substring(
|
|
0,
|
|
location.href.length - workerFilename.length - 1,
|
|
);
|
|
const basePathName = location.pathname.substring(
|
|
0,
|
|
location.pathname.length - workerFilename.length - 1,
|
|
);
|
|
console.log(
|
|
`[Service Worker] Established baseURI=[${baseURI}]; basePathName=[${basePathName}]`,
|
|
);
|
|
|
|
const precacheFiles = Object.fromEntries([
|
|
"/",
|
|
"/.client/client.js",
|
|
"/.client/favicon.png",
|
|
"/.client/iAWriterMonoS-Bold.woff2",
|
|
"/.client/iAWriterMonoS-BoldItalic.woff2",
|
|
"/.client/iAWriterMonoS-Italic.woff2",
|
|
"/.client/iAWriterMonoS-Regular.woff2",
|
|
"/.client/logo.png",
|
|
"/.client/logo-dock.png",
|
|
"/.client/main.css",
|
|
"/.client/manifest.json",
|
|
].map((path) => [path, `${baseURI}${path}?v=${CACHE_NAME}`, path])); // Cache busting
|
|
|
|
self.addEventListener("install", (event: any) => {
|
|
console.log("[Service worker]", "Installing service worker...");
|
|
event.waitUntil(
|
|
(async () => {
|
|
const cache = await caches.open(CACHE_NAME);
|
|
console.log(
|
|
"[Service worker]",
|
|
"Now pre-caching client files",
|
|
);
|
|
await cache.addAll(Object.values(precacheFiles));
|
|
console.log(
|
|
"[Service worker]",
|
|
Object.keys(precacheFiles).length,
|
|
"client files cached",
|
|
);
|
|
// @ts-ignore: Force the waiting service worker to become the active service worker
|
|
await self.skipWaiting();
|
|
console.log("[Service worker]", "skipWaiting complete");
|
|
})(),
|
|
);
|
|
});
|
|
|
|
self.addEventListener("activate", (event: any) => {
|
|
console.log("[Service worker]", "Activating new service worker!");
|
|
event.waitUntil(
|
|
(async () => {
|
|
const cacheNames = await caches.keys();
|
|
await Promise.all(
|
|
cacheNames.map((cacheName) => {
|
|
if (cacheName !== CACHE_NAME) {
|
|
console.log("[Service worker]", "Removing old cache", cacheName);
|
|
return caches.delete(cacheName);
|
|
}
|
|
}),
|
|
);
|
|
// @ts-ignore: Take control of all clients as soon as the service worker activates
|
|
await clients.claim();
|
|
console.log("[Service worker]", "clients.claim complete");
|
|
})(),
|
|
);
|
|
});
|
|
|
|
let ds: DataStore | undefined;
|
|
const filesContentPrefix = ["file", "content"];
|
|
|
|
self.addEventListener("fetch", (event: any) => {
|
|
const url = new URL(event.request.url);
|
|
|
|
const pathname = url.pathname.substring(basePathName.length); //url.pathname with any URL prefix removed
|
|
|
|
// Use the custom cache key if available, otherwise use the request URL
|
|
const cacheKey = precacheFiles[pathname] || event.request.url;
|
|
|
|
event.respondWith(
|
|
(async () => {
|
|
const request = event.request;
|
|
const requestUrl = new URL(request.url);
|
|
|
|
// Are we fetching a URL from the same origin as the app? If not, we don't handle it and pass it on
|
|
if (!requestUrl.href.startsWith(baseURI)) {
|
|
return fetch(request);
|
|
}
|
|
|
|
// Any request with the X-Sync-Mode header originates from the sync engine: pass it on to the server
|
|
if (request.headers.has("x-sync-mode")) {
|
|
return fetch(request);
|
|
}
|
|
|
|
// Try the static (client) file cache first
|
|
const cachedResponse = await caches.match(cacheKey);
|
|
// Return the cached response if found
|
|
if (cachedResponse) {
|
|
return cachedResponse;
|
|
}
|
|
|
|
if (!ds) {
|
|
// Not initialzed yet
|
|
return fetch(request);
|
|
}
|
|
|
|
const pathname = requestUrl.pathname.substring(basePathName.length); //requestUrl.pathname without with any URL prefix removed
|
|
|
|
if (
|
|
pathname === "/.auth" ||
|
|
pathname === "/.logout" ||
|
|
pathname === "/.config" ||
|
|
pathname === "/index.json"
|
|
) {
|
|
return fetch(request);
|
|
} else if (
|
|
pathname.endsWith(".md") &&
|
|
request.headers.get("accept") !== "application/octet-stream" &&
|
|
request.headers.get("sec-fetch-mode") !== "cors"
|
|
) {
|
|
return Response.redirect(`${pathname.slice(0, -3)}`);
|
|
} else if (
|
|
(looksLikePathWithExtension(pathname) &&
|
|
!request.headers.get("accept").includes("text/html")) ||
|
|
requestUrl.searchParams.get("raw") === "true"
|
|
) {
|
|
// If this is a /*.* request, this can either be a plug worker load or an document load
|
|
return handleLocalFileRequest(pathname, request);
|
|
} else {
|
|
// Must be a page URL, let's serve index.html which will handle it
|
|
return (await caches.match(precacheFiles["/"])) || fetch(request);
|
|
}
|
|
})().catch((e) => {
|
|
console.warn("[Service worker]", "Fetch failed:", e);
|
|
return new Response("Offline", {
|
|
status: 503, // Service Unavailable
|
|
});
|
|
}),
|
|
);
|
|
});
|
|
|
|
async function handleLocalFileRequest(
|
|
pathname: string,
|
|
request: Request,
|
|
): Promise<Response> {
|
|
const path = decodePageURI(pathname.slice(1));
|
|
const data = await ds?.get<FileContent>([...filesContentPrefix, path]);
|
|
if (data) {
|
|
// console.log("Serving from space", path);
|
|
return new Response(
|
|
data.data,
|
|
{
|
|
headers: {
|
|
"Content-type": data.meta.contentType,
|
|
"Content-Length": "" + data.meta.size,
|
|
"X-Permission": data.meta.perm,
|
|
"X-Created": "" + data.meta.created,
|
|
"X-Last-Modified": "" + data.meta.lastModified,
|
|
},
|
|
},
|
|
);
|
|
} else {
|
|
console.warn(
|
|
"Did not find file in locally synced space",
|
|
path,
|
|
);
|
|
// If this is a _plug request and we don't have it, we may not have performed an initial sync yet
|
|
if (path.startsWith("_plug/")) {
|
|
console.info("Proxying _plug fetch to server", path);
|
|
return fetch(request);
|
|
}
|
|
|
|
return new Response("Not found", {
|
|
status: 404,
|
|
headers: {
|
|
"Cache-Control": "no-cache",
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
self.addEventListener("message", (event: any) => {
|
|
switch (event.data.type) {
|
|
case "skipWaiting": {
|
|
console.log(
|
|
"[Service worker]",
|
|
"Received skipWaiting message, activating immediately",
|
|
);
|
|
// @ts-ignore: Skip waiting to activate this service worker immediately
|
|
self.skipWaiting();
|
|
break;
|
|
}
|
|
case "flushCache": {
|
|
caches.delete(CACHE_NAME)
|
|
.then(() => {
|
|
console.log("[Service worker]", "Cache deleted");
|
|
// ds?.close();
|
|
event.source.postMessage({ type: "cacheFlushed" });
|
|
});
|
|
break;
|
|
}
|
|
case "config": {
|
|
const spaceFolderPath = event.data.config.spaceFolderPath;
|
|
const dbPrefix = "" + simpleHash(spaceFolderPath);
|
|
|
|
// Setup space
|
|
const kv = new IndexedDBKvPrimitives(dbPrefix);
|
|
kv.init().then(() => {
|
|
ds = new DataStore(kv);
|
|
console.log("Datastore in service worker initialized...");
|
|
});
|
|
break;
|
|
}
|
|
case "shutdown": {
|
|
if (ds) {
|
|
console.log("[Service worker]", "Disconnecting datastore");
|
|
ds.kv.close();
|
|
ds = undefined;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|