SilverBullet pivot to become an offline-first PWA (#403)

This commit is contained in:
Zef Hemel
2023-05-23 20:53:53 +02:00
committed by GitHub
parent b256269897
commit 5f484bed57
389 changed files with 4484 additions and 291129 deletions
-64
View File
@@ -1,64 +0,0 @@
export type LogLevel = "info" | "warn" | "error" | "log";
export class ConsoleLogger {
print: boolean;
callback: (level: LogLevel, entry: string) => void;
constructor(
callback: (level: LogLevel, entry: string) => void,
print: boolean = true,
) {
this.print = print;
this.callback = callback;
}
log(...args: any[]): void {
this.push("log", args);
}
warn(...args: any[]): void {
this.push("warn", args);
}
error(...args: any[]): void {
this.push("error", args);
}
info(...args: any[]): void {
this.push("info", args);
}
push(level: LogLevel, args: any[]) {
this.callback(level, this.logMessage(args));
if (this.print) {
console[level](...args);
}
}
logMessage(values: any[]): string {
const pieces: string[] = [];
for (const val of values) {
switch (typeof val) {
case "string":
case "number":
pieces.push("" + val);
break;
case "undefined":
pieces.push("undefined");
break;
default:
try {
let s = JSON.stringify(val, null, 2);
if (s.length > 500) {
s = s.substring(0, 500) + "...";
}
pieces.push(s);
} catch {
// May be cyclical reference
pieces.push("[circular object]");
}
}
}
return pieces.join(" ");
}
}
+20 -64
View File
@@ -1,69 +1,25 @@
import { safeRun } from "../util.ts";
import { Sandbox } from "../sandbox.ts";
import { WorkerLike } from "./worker.ts";
import { Plug } from "../plug.ts";
import { AssetBundle } from "../asset_bundle/bundle.ts";
class DenoWorkerWrapper implements WorkerLike {
private worker: Worker;
onMessage?: (message: any) => Promise<void>;
ready: Promise<void>;
constructor(worker: Worker) {
this.worker = worker;
this.worker.addEventListener("message", (evt: any) => {
const data = evt.data;
if (!data) return;
safeRun(async () => {
await this.onMessage!(data);
});
});
this.ready = Promise.resolve();
}
postMessage(message: any): void {
this.worker.postMessage(message);
}
terminate() {
return this.worker.terminate();
}
}
import workerBundleJson from "./worker_bundle.json" assert { type: "json" };
const workerBundle = new AssetBundle(workerBundleJson);
export function createSandbox(plug: Plug<any>) {
const workerHref = URL.createObjectURL(
new Blob([
workerBundle.readFileSync("worker.js"),
], {
type: "application/javascript",
}),
);
const worker = new Worker(
workerHref,
{
type: "module",
deno: {
permissions: {
// Disallow network access
net: false,
// This is required for console logging to work, apparently?
env: true,
// No talking to native code
ffi: false,
// No invocation of shell commands
run: false,
// No read access to the file system
read: false,
// No write access to the file system
write: false,
},
// Uses Deno's permissions to lock the worker down significantly
export function createSandbox<HookT>(plug: Plug<HookT>): Sandbox<HookT> {
return new Sandbox(plug, {
deno: {
permissions: {
// Disallow network access
net: false,
// This is required for console logging to work, apparently?
env: true,
// No talking to native code
ffi: false,
// No invocation of shell commands
run: false,
// No read access to the file system
read: false,
// No write access to the file system
write: false,
},
// Have to do this because the "deno" option is not standard and doesn't typecheck yet
} as any,
);
return new Sandbox(plug, new DenoWorkerWrapper(worker));
},
// Have to do this because the "deno" option is not standard and doesn't typecheck yet
} as any);
}
-165
View File
@@ -1,165 +0,0 @@
// IMPORTANT: After modifiying this file, run `deno task generate` in the SB root to regenerate the asset bundle (`worker_bundle.json`), which will be imported for the runtime.
import { safeRun } from "../util.ts";
import { ConsoleLogger } from "./custom_logger.ts";
import type { ControllerMessage, WorkerMessage } from "./worker.ts";
if (typeof Deno === "undefined") {
// @ts-ignore: Deno hack
self.Deno = {
args: [],
// @ts-ignore: Deno hack
build: {
arch: "x86_64",
},
env: {
// @ts-ignore: Deno hack
get() {
},
},
};
}
// deno-lint-ignore ban-types
const loadedFunctions = new Map<string, Function>();
const pendingRequests = new Map<
number,
{
resolve: (result: unknown) => void;
reject: (e: any) => void;
}
>();
function workerPostMessage(msg: ControllerMessage) {
if (typeof window !== "undefined" && window.parent !== window) {
window.parent.postMessage(msg, "*");
} else {
self.postMessage(msg);
}
}
declare global {
function syscall(name: string, ...args: any[]): Promise<any>;
// function require(moduleName: string): any;
}
let syscallReqId = 0;
self.syscall = async (name: string, ...args: any[]) => {
return await new Promise((resolve, reject) => {
syscallReqId++;
pendingRequests.set(syscallReqId, { resolve, reject });
workerPostMessage({
type: "syscall",
id: syscallReqId,
name,
args,
});
});
};
const loadedModules = new Map<string, any>();
// @ts-ignore: global to load dynamic imports
self.require = (moduleName: string): any => {
// console.log("Requiring", moduleName, loadedModules.get(moduleName));
const mod = loadedModules.get(moduleName);
if (!mod) {
throw new Error(
`Dynamically importing non-preloaded library ${moduleName}`,
);
}
return mod;
};
// @ts-ignore: global overwrite on purpose
self.console = new ConsoleLogger((level, message) => {
workerPostMessage({ type: "log", level, message });
}, false);
function wrapScript(code: string) {
return `return (${code})["default"]`;
}
self.addEventListener("message", (event: { data: WorkerMessage }) => {
safeRun(async () => {
const data = event.data;
switch (data.type) {
case "load":
{
const fn2 = new Function(wrapScript(data.code!));
loadedFunctions.set(data.name!, fn2());
workerPostMessage({
type: "inited",
name: data.name,
});
}
break;
case "load-dependency":
{
// console.log("Received dep", data.name);
const fn3 = new Function(`return ${data.code!}`);
const v = fn3();
loadedModules.set(data.name!, v);
// console.log("Dep val", v);
workerPostMessage({
type: "dependency-inited",
name: data.name,
});
}
break;
case "invoke":
{
const fn = loadedFunctions.get(data.name!);
if (!fn) {
throw new Error(`Function not loaded: ${data.name}`);
}
try {
const result = await Promise.resolve(fn(...(data.args || [])));
workerPostMessage({
type: "result",
id: data.id,
result: result,
} as ControllerMessage);
} catch (e: any) {
workerPostMessage({
type: "result",
id: data.id,
error: e.message,
stack: e.stack,
});
// console.error("Error invoking function", data.name, e.message);
// throw e;
}
}
break;
case "syscall-response":
{
const syscallId = data.id!;
const lookup = pendingRequests.get(syscallId);
if (!lookup) {
console.log(
"Current outstanding requests",
pendingRequests,
"looking up",
syscallId,
);
throw Error("Invalid request id");
}
pendingRequests.delete(syscallId);
if (data.error) {
lookup.reject(new Error(data.error));
} else {
lookup.resolve(data.result);
}
}
break;
}
});
});
import { monkeyPatchFetch } from "../../plug-api/plugos-syscall/fetch.ts";
monkeyPatchFetch();
+2 -37
View File
@@ -1,41 +1,6 @@
import { safeRun } from "../util.ts";
import { Sandbox } from "../sandbox.ts";
import { WorkerLike } from "./worker.ts";
import type { Plug } from "../plug.ts";
class WebWorkerWrapper implements WorkerLike {
private worker: Worker;
onMessage?: (message: any) => Promise<void>;
ready: Promise<void>;
constructor(worker: Worker) {
this.worker = worker;
this.worker.addEventListener("message", (evt: any) => {
let data = evt.data;
if (!data) return;
safeRun(async () => {
await this.onMessage!(data);
});
});
this.ready = Promise.resolve();
}
postMessage(message: any): void {
this.worker.postMessage(message);
}
terminate() {
return this.worker.terminate();
}
}
export function createSandbox(plug: Plug<any>) {
const worker = new Worker(
import.meta.url
? new URL("sandbox_worker.ts", import.meta.url)
: new URL("worker.js", location.origin),
{
type: "module",
},
);
return new Sandbox(plug, new WebWorkerWrapper(worker));
export function createSandbox<HookT>(plug: Plug<HookT>): Sandbox<HookT> {
return new Sandbox(plug);
}
-45
View File
@@ -1,45 +0,0 @@
import type { LogLevel } from "./custom_logger.ts";
export type ControllerMessageType =
| "inited"
| "dependency-inited"
| "result"
| "syscall"
| "log";
export type ControllerMessage = {
type: ControllerMessageType;
id?: number;
name?: string;
args?: any[];
error?: string;
stack?: string;
level?: LogLevel;
message?: string;
result?: any;
};
export interface WorkerLike {
ready: Promise<void>;
onMessage?: (message: any) => Promise<void>;
postMessage(message: any): void;
terminate(): void;
}
export type WorkerMessageType =
| "load"
| "load-dependency"
| "invoke"
| "syscall-response";
export type WorkerMessage = {
type: WorkerMessageType;
id?: number;
name?: string;
code?: string;
args?: any[];
result?: any;
error?: any;
};
-3
View File
@@ -1,3 +0,0 @@
{
"worker.js": "data:application/javascript;base64,KCgpID0+IHsgdmFyIG1vZD0oKCk9PntmdW5jdGlvbiBsKHQpe3QoKS5jYXRjaChlPT57Y29uc29sZS5lcnJvcigiQ2F1Z2h0IGVycm9yIixlLm1lc3NhZ2UpfSl9dmFyIGE9Y2xhc3N7Y29uc3RydWN0b3IoZSxuPSEwKXt0aGlzLnByaW50PW4sdGhpcy5jYWxsYmFjaz1lfWxvZyguLi5lKXt0aGlzLnB1c2goImxvZyIsZSl9d2FybiguLi5lKXt0aGlzLnB1c2goIndhcm4iLGUpfWVycm9yKC4uLmUpe3RoaXMucHVzaCgiZXJyb3IiLGUpfWluZm8oLi4uZSl7dGhpcy5wdXNoKCJpbmZvIixlKX1wdXNoKGUsbil7dGhpcy5jYWxsYmFjayhlLHRoaXMubG9nTWVzc2FnZShuKSksdGhpcy5wcmludCYmY29uc29sZVtlXSguLi5uKX1sb2dNZXNzYWdlKGUpe2xldCBuPVtdO2ZvcihsZXQgciBvZiBlKXN3aXRjaCh0eXBlb2Ygcil7Y2FzZSJzdHJpbmciOmNhc2UibnVtYmVyIjpuLnB1c2goIiIrcik7YnJlYWs7Y2FzZSJ1bmRlZmluZWQiOm4ucHVzaCgidW5kZWZpbmVkIik7YnJlYWs7ZGVmYXVsdDp0cnl7bGV0IHM9SlNPTi5zdHJpbmdpZnkocixudWxsLDIpO3MubGVuZ3RoPjUwMCYmKHM9cy5zdWJzdHJpbmcoMCw1MDApKyIuLi4iKSxuLnB1c2gocyl9Y2F0Y2h7bi5wdXNoKCJbY2lyY3VsYXIgb2JqZWN0XSIpfX1yZXR1cm4gbi5qb2luKCIgIil9fTtmdW5jdGlvbiBkKHQpe2xldCBlPWF0b2IodCksbj1lLmxlbmd0aCxyPW5ldyBVaW50OEFycmF5KG4pO2ZvcihsZXQgcz0wO3M8bjtzKyspcltzXT1lLmNoYXJDb2RlQXQocyk7cmV0dXJuIHJ9ZnVuY3Rpb24geSh0LGUpe3JldHVybiBzeXNjYWxsKCJzYW5kYm94RmV0Y2guZmV0Y2giLHQsZSl9ZnVuY3Rpb24gdSgpe2dsb2JhbFRoaXMuZmV0Y2g9YXN5bmMgZnVuY3Rpb24odCxlKXtsZXQgbj1hd2FpdCB5KHQsZSYme21ldGhvZDplLm1ldGhvZCxoZWFkZXJzOmUuaGVhZGVycyxib2R5OmUuYm9keX0pO3JldHVybiBuZXcgUmVzcG9uc2Uobi5iYXNlNjRCb2R5P2Qobi5iYXNlNjRCb2R5KTpudWxsLHtzdGF0dXM6bi5zdGF0dXMsaGVhZGVyczpuLmhlYWRlcnN9KX19dHlwZW9mIERlbm8+InUiJiYoc2VsZi5EZW5vPXthcmdzOltdLGJ1aWxkOnthcmNoOiJ4ODZfNjQifSxlbnY6e2dldCgpe319fSk7dmFyIGc9bmV3IE1hcCxpPW5ldyBNYXA7ZnVuY3Rpb24gbyh0KXt0eXBlb2Ygd2luZG93PCJ1IiYmd2luZG93LnBhcmVudCE9PXdpbmRvdz93aW5kb3cucGFyZW50LnBvc3RNZXNzYWdlKHQsIioiKTpzZWxmLnBvc3RNZXNzYWdlKHQpfXZhciBjPTA7c2VsZi5zeXNjYWxsPWFzeW5jKHQsLi4uZSk9PmF3YWl0IG5ldyBQcm9taXNlKChuLHIpPT57YysrLGkuc2V0KGMse3Jlc29sdmU6bixyZWplY3Q6cn0pLG8oe3R5cGU6InN5c2NhbGwiLGlkOmMsbmFtZTp0LGFyZ3M6ZX0pfSk7dmFyIHA9bmV3IE1hcDtzZWxmLnJlcXVpcmU9dD0+e2xldCBlPXAuZ2V0KHQpO2lmKCFlKXRocm93IG5ldyBFcnJvcihgRHluYW1pY2FsbHkgaW1wb3J0aW5nIG5vbi1wcmVsb2FkZWQgbGlicmFyeSAke3R9YCk7cmV0dXJuIGV9O3NlbGYuY29uc29sZT1uZXcgYSgodCxlKT0+e28oe3R5cGU6ImxvZyIsbGV2ZWw6dCxtZXNzYWdlOmV9KX0sITEpO2Z1bmN0aW9uIGgodCl7cmV0dXJuYHJldHVybiAoJHt0fSlbImRlZmF1bHQiXWB9c2VsZi5hZGRFdmVudExpc3RlbmVyKCJtZXNzYWdlIix0PT57bChhc3luYygpPT57bGV0IGU9dC5kYXRhO3N3aXRjaChlLnR5cGUpe2Nhc2UibG9hZCI6e2xldCBuPW5ldyBGdW5jdGlvbihoKGUuY29kZSkpO2cuc2V0KGUubmFtZSxuKCkpLG8oe3R5cGU6ImluaXRlZCIsbmFtZTplLm5hbWV9KX1icmVhaztjYXNlImxvYWQtZGVwZW5kZW5jeSI6e2xldCByPW5ldyBGdW5jdGlvbihgcmV0dXJuICR7ZS5jb2RlfWApKCk7cC5zZXQoZS5uYW1lLHIpLG8oe3R5cGU6ImRlcGVuZGVuY3ktaW5pdGVkIixuYW1lOmUubmFtZX0pfWJyZWFrO2Nhc2UiaW52b2tlIjp7bGV0IG49Zy5nZXQoZS5uYW1lKTtpZighbil0aHJvdyBuZXcgRXJyb3IoYEZ1bmN0aW9uIG5vdCBsb2FkZWQ6ICR7ZS5uYW1lfWApO3RyeXtsZXQgcj1hd2FpdCBQcm9taXNlLnJlc29sdmUobiguLi5lLmFyZ3N8fFtdKSk7byh7dHlwZToicmVzdWx0IixpZDplLmlkLHJlc3VsdDpyfSl9Y2F0Y2gocil7byh7dHlwZToicmVzdWx0IixpZDplLmlkLGVycm9yOnIubWVzc2FnZSxzdGFjazpyLnN0YWNrfSl9fWJyZWFrO2Nhc2Uic3lzY2FsbC1yZXNwb25zZSI6e2xldCBuPWUuaWQscj1pLmdldChuKTtpZighcil0aHJvdyBjb25zb2xlLmxvZygiQ3VycmVudCBvdXRzdGFuZGluZyByZXF1ZXN0cyIsaSwibG9va2luZyB1cCIsbiksRXJyb3IoIkludmFsaWQgcmVxdWVzdCBpZCIpO2kuZGVsZXRlKG4pLGUuZXJyb3I/ci5yZWplY3QobmV3IEVycm9yKGUuZXJyb3IpKTpyLnJlc29sdmUoZS5yZXN1bHQpfWJyZWFrfX0pfSk7dSgpO30pKCk7CiByZXR1cm4gbW9kO30pKCk="
}