1
0

Further iteration

This commit is contained in:
Zef Hemel 2023-08-30 17:25:54 +02:00
parent cbc00b73a0
commit 75da8e7ca9
18 changed files with 128 additions and 147 deletions

View File

@ -10,6 +10,7 @@ import { path } from "../common/deps.ts";
Deno.test("Test plug run", async () => { Deno.test("Test plug run", async () => {
// const tempDir = await Deno.makeTempDir(); // const tempDir = await Deno.makeTempDir();
const tempDbFile = await Deno.makeTempFile({ suffix: ".db" });
const assetBundle = new AssetBundle(assets); const assetBundle = new AssetBundle(assets);
@ -26,6 +27,7 @@ Deno.test("Test plug run", async () => {
assertEquals( assertEquals(
await runPlug( await runPlug(
testSpaceFolder, testSpaceFolder,
tempDbFile,
"test.run", "test.run",
[], [],
assetBundle, assetBundle,
@ -35,4 +37,5 @@ Deno.test("Test plug run", async () => {
// await Deno.remove(tempDir, { recursive: true }); // await Deno.remove(tempDir, { recursive: true });
esbuild.stop(); esbuild.stop();
await Deno.remove(tempDbFile);
}); });

View File

@ -9,16 +9,13 @@ import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_sp
export async function runPlug( export async function runPlug(
spacePath: string, spacePath: string,
dbPath: string,
functionName: string | undefined, functionName: string | undefined,
args: string[] = [], args: string[] = [],
builtinAssetBundle: AssetBundle, builtinAssetBundle: AssetBundle,
indexFirst = false,
httpServerPort = 3123, httpServerPort = 3123,
httpHostname = "127.0.0.1", httpHostname = "127.0.0.1",
) { ) {
spacePath = path.resolve(spacePath);
const tempFile = Deno.makeTempFileSync({ suffix: ".db" });
console.log("Tempt db file", tempFile);
const serverController = new AbortController(); const serverController = new AbortController();
const app = new Application(); const app = new Application();
@ -27,7 +24,7 @@ export async function runPlug(
new DiskSpacePrimitives(spacePath), new DiskSpacePrimitives(spacePath),
builtinAssetBundle, builtinAssetBundle,
), ),
tempFile, dbPath,
app, app,
); );
await serverSystem.init(); await serverSystem.init();
@ -37,13 +34,6 @@ export async function runPlug(
signal: serverController.signal, signal: serverController.signal,
}); });
if (indexFirst) {
await serverSystem.system.loadedPlugs.get("index")!.invoke(
"reindexSpace",
[],
);
}
if (functionName) { if (functionName) {
const [plugName, funcName] = functionName.split("."); const [plugName, funcName] = functionName.split(".");
@ -54,7 +44,6 @@ export async function runPlug(
const result = await plug.invoke(funcName, args); const result = await plug.invoke(funcName, args);
await serverSystem.close(); await serverSystem.close();
serverSystem.denoKv.close(); serverSystem.denoKv.close();
await Deno.remove(tempFile);
serverController.abort(); serverController.abort();
return result; return result;
} else { } else {

1
cmd/constants.ts Normal file
View File

@ -0,0 +1 @@
export const silverBulletDbFile = ".silverbullet.db";

View File

@ -4,14 +4,15 @@ import assets from "../dist/plug_asset_bundle.json" assert {
type: "json", type: "json",
}; };
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts"; import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
import { silverBulletDbFile } from "./constants.ts";
export async function plugRunCommand( export async function plugRunCommand(
{ {
noIndex, db,
hostname, hostname,
port, port,
}: { }: {
noIndex: boolean; db?: string;
hostname?: string; hostname?: string;
port?: number; port?: number;
}, },
@ -21,14 +22,18 @@ export async function plugRunCommand(
) { ) {
spacePath = path.resolve(spacePath); spacePath = path.resolve(spacePath);
console.log("Space path", spacePath); console.log("Space path", spacePath);
let dbPath = path.resolve(spacePath, silverBulletDbFile);
if (db) {
dbPath = path.resolve(db);
}
console.log("Function to run:", functionName, "with arguments", args); console.log("Function to run:", functionName, "with arguments", args);
try { try {
const result = await runPlug( const result = await runPlug(
spacePath, spacePath,
dbPath,
functionName, functionName,
args, args,
new AssetBundle(assets), new AssetBundle(assets),
!noIndex,
port, port,
hostname, hostname,
); );

View File

@ -17,6 +17,7 @@ import { ServerSystem } from "../server/server_system.ts";
import { sleep } from "$sb/lib/async.ts"; import { sleep } from "$sb/lib/async.ts";
import { SilverBulletHooks } from "../common/manifest.ts"; import { SilverBulletHooks } from "../common/manifest.ts";
import { System } from "../plugos/system.ts"; import { System } from "../plugos/system.ts";
import { silverBulletDbFile } from "./constants.ts";
export async function serveCommand( export async function serveCommand(
options: { options: {
@ -28,7 +29,7 @@ export async function serveCommand(
key?: string; key?: string;
reindex?: boolean; reindex?: boolean;
db?: string; db?: string;
serverProcessing?: boolean; storeOnly?: boolean;
}, },
folder?: string, folder?: string,
) { ) {
@ -36,11 +37,13 @@ export async function serveCommand(
"127.0.0.1"; "127.0.0.1";
const port = options.port || const port = options.port ||
(Deno.env.get("SB_PORT") && +Deno.env.get("SB_PORT")!) || 3000; (Deno.env.get("SB_PORT") && +Deno.env.get("SB_PORT")!) || 3000;
const storeOnly = options.storeOnly || Deno.env.get("SB_STORE_ONLY");
let dbFile = options.db || Deno.env.get("SB_DB_FILE") || ".silverbullet.db"; let dbFile = options.db || Deno.env.get("SB_DB_FILE") || silverBulletDbFile;
const app = new Application(); const app = new Application();
console.log(options);
if (!folder) { if (!folder) {
folder = Deno.env.get("SB_FOLDER"); folder = Deno.env.get("SB_FOLDER");
if (!folder) { if (!folder) {
@ -84,7 +87,8 @@ To allow outside connections, pass -L 0.0.0.0 as a flag, and put a TLS terminato
); );
let system: System<SilverBulletHooks> | undefined; let system: System<SilverBulletHooks> | undefined;
if (options.serverProcessing) { // system = undefined in storeOnly mode (no PlugOS instance on the server)
if (!storeOnly) {
// Enable server-side processing // Enable server-side processing
dbFile = path.resolve(folder, dbFile); dbFile = path.resolve(folder, dbFile);
console.log( console.log(

View File

@ -1,97 +0,0 @@
import type {
ProxyFetchRequest,
ProxyFetchResponse,
} from "../../common/proxy_fetch.ts";
import {
base64Decode,
base64Encode,
} from "../../plugos/asset_bundle/base64.ts";
async function readStream(
stream: ReadableStream<Uint8Array>,
): Promise<Uint8Array> {
const arrays: Uint8Array[] = [];
let totalRead = 0;
const reader = stream.getReader();
while (true) {
// The `read()` method returns a promise that
// resolves when a value has been received.
const { done, value } = await reader.read();
// Result objects contain two properties:
// `done` - `true` if the stream has already given you all its data.
// `value` - Some data. Always `undefined` when `done` is `true`.
if (done) {
const resultArray = new Uint8Array(totalRead);
let offset = 0;
for (const array of arrays) {
resultArray.set(array, offset);
offset += array.length;
}
return resultArray;
}
arrays.push(value);
totalRead += value.length;
}
}
export async function sandboxFetch(
reqInfo: RequestInfo,
options?: ProxyFetchRequest,
): Promise<ProxyFetchResponse> {
if (typeof reqInfo !== "string") {
const body = new Uint8Array(await reqInfo.arrayBuffer());
const encodedBody = body.length > 0 ? base64Encode(body) : undefined;
options = {
method: reqInfo.method,
headers: Object.fromEntries(reqInfo.headers.entries()),
base64Body: encodedBody,
};
reqInfo = reqInfo.url;
}
// @ts-ignore: monkey patching fetch
return syscall("sandboxFetch.fetch", reqInfo, options);
}
async function bodyInitToUint8Array(init: BodyInit): Promise<Uint8Array> {
if (init instanceof Blob) {
const buffer = await init.arrayBuffer();
return new Uint8Array(buffer);
} else if (init instanceof ArrayBuffer) {
return new Uint8Array(init);
} else if (init instanceof ReadableStream) {
return readStream(init);
} else if (typeof init === "string") {
return new TextEncoder().encode(init);
} else {
throw new Error("Unknown body init type");
}
}
export function monkeyPatchFetch() {
// @ts-ignore: monkey patching fetch
globalThis.nativeFetch = globalThis.fetch;
// @ts-ignore: monkey patching fetch
globalThis.fetch = async function (
reqInfo: RequestInfo,
init?: RequestInit,
): Promise<Response> {
const encodedBody = init && init.body
? base64Encode(
new Uint8Array(await (new Response(init.body)).arrayBuffer()),
)
: undefined;
// console.log("Encoded this body", encodedBody);
const r = await sandboxFetch(
reqInfo,
init && {
method: init.method,
headers: init.headers as Record<string, string>,
base64Body: encodedBody,
},
);
return new Response(r.base64Body ? base64Decode(r.base64Body) : null, {
status: r.status,
headers: r.headers,
});
};
}

View File

@ -1,7 +1,9 @@
// This is the runtime imported from the compiled plug worker code // This is the runtime imported from the compiled plug worker code
import type { ControllerMessage, WorkerMessage } from "./protocol.ts"; import type { ControllerMessage, WorkerMessage } from "./protocol.ts";
import type { Manifest } from "../common/manifest.ts"; import type {
ProxyFetchRequest,
ProxyFetchResponse,
} from "../common/proxy_fetch.ts";
declare global { declare global {
function syscall(name: string, ...args: any[]): Promise<any>; function syscall(name: string, ...args: any[]): Promise<any>;
@ -53,7 +55,7 @@ self.syscall = async (name: string, ...args: any[]) => {
export function setupMessageListener( export function setupMessageListener(
// deno-lint-ignore ban-types // deno-lint-ignore ban-types
functionMapping: Record<string, Function>, functionMapping: Record<string, Function>,
manifest: Manifest, manifest: any,
) { ) {
self.addEventListener("message", (event: { data: WorkerMessage }) => { self.addEventListener("message", (event: { data: WorkerMessage }) => {
(async () => { (async () => {
@ -81,7 +83,6 @@ export function setupMessageListener(
}); });
} }
} }
break; break;
case "sysr": case "sysr":
{ {
@ -109,6 +110,72 @@ export function setupMessageListener(
}); });
} }
export function base64Decode(s: string): Uint8Array {
const binString = atob(s);
const len = binString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binString.charCodeAt(i);
}
return bytes;
}
export function base64Encode(buffer: Uint8Array | string): string {
if (typeof buffer === "string") {
buffer = new TextEncoder().encode(buffer);
}
let binary = "";
const len = buffer.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(buffer[i]);
}
return btoa(binary);
}
export async function sandboxFetch(
reqInfo: RequestInfo,
options?: ProxyFetchRequest,
): Promise<ProxyFetchResponse> {
if (typeof reqInfo !== "string") {
const body = new Uint8Array(await reqInfo.arrayBuffer());
const encodedBody = body.length > 0 ? base64Encode(body) : undefined;
options = {
method: reqInfo.method,
headers: Object.fromEntries(reqInfo.headers.entries()),
base64Body: encodedBody,
};
reqInfo = reqInfo.url;
}
return syscall("sandboxFetch.fetch", reqInfo, options);
}
// Monkey patch fetch() // Monkey patch fetch()
import { monkeyPatchFetch } from "../plug-api/plugos-syscall/fetch.ts";
export function monkeyPatchFetch() {
globalThis.nativeFetch = globalThis.fetch;
// @ts-ignore: monkey patching fetch
globalThis.fetch = async function (
reqInfo: RequestInfo,
init?: RequestInit,
): Promise<Response> {
const encodedBody = init && init.body
? base64Encode(
new Uint8Array(await (new Response(init.body)).arrayBuffer()),
)
: undefined;
const r = await sandboxFetch(
reqInfo,
init && {
method: init.method,
headers: init.headers as Record<string, string>,
base64Body: encodedBody,
},
);
return new Response(r.base64Body ? base64Decode(r.base64Body) : null, {
status: r.status,
headers: r.headers,
});
};
}
monkeyPatchFetch(); monkeyPatchFetch();

View File

@ -104,7 +104,6 @@ functions:
command: command:
name: "Text: Strikethrough" name: "Text: Strikethrough"
key: "Ctrl-Shift-s" key: "Ctrl-Shift-s"
mac: "Cmd-Shift-s"
wrapper: "~~" wrapper: "~~"
marker: marker:
path: ./text.ts:wrapSelection path: ./text.ts:wrapSelection

View File

@ -45,9 +45,7 @@ export async function renamePageCommand(cmdDef: any) {
throw e; throw e;
} }
} }
const updatedReferences = await renamePage(oldName, newName); const updatedReferences = await renamePage(oldName, newName, true);
console.log("Navigating to new page");
await editor.navigate(newName, 0, true);
await editor.flashNotification( await editor.flashNotification(
`Renamed page, and updated ${updatedReferences} references`, `Renamed page, and updated ${updatedReferences} references`,
@ -57,12 +55,21 @@ export async function renamePageCommand(cmdDef: any) {
} }
} }
async function renamePage(oldName: string, newName: string): Promise<number> { async function renamePage(
oldName: string,
newName: string,
navigateThere = false,
): Promise<number> {
const text = await space.readPage(oldName); const text = await space.readPage(oldName);
console.log("Writing new page to space"); console.log("Writing new page to space");
const newPageMeta = await space.writePage(newName, text); const newPageMeta = await space.writePage(newName, text);
if (navigateThere) {
console.log("Navigating to new page");
await editor.navigate(newName, 0, true);
}
const pagesToUpdate = await getBackLinks(oldName); const pagesToUpdate = await getBackLinks(oldName);
console.log("All pages containing backlinks", pagesToUpdate); console.log("All pages containing backlinks", pagesToUpdate);

View File

@ -70,8 +70,8 @@ export class HttpServer {
this.options.pagesPath.replaceAll("\\", "\\\\"), this.options.pagesPath.replaceAll("\\", "\\\\"),
// ); // );
).replaceAll( ).replaceAll(
"{{SUPPORT_ONLINE_MODE}}", "{{SYNC_ONLY}}",
this.system ? "true" : "false", this.system ? "false" : "true",
); );
} }

View File

@ -45,11 +45,11 @@ await new Command()
"Path to TLS key", "Path to TLS key",
) )
.option( .option(
"--no-server-processing [type:boolean]", "--store-only",
"Disable online mode (no server-side processing)", "Run the server as a pure space (file) store only without any backend processing (this disables 'server mode' in the client)",
) )
.option( .option(
"--reindex [type:boolean]", "--reindex",
"Reindex space on startup", "Reindex space on startup",
) )
.option( .option(
@ -60,8 +60,8 @@ await new Command()
// plug:compile // plug:compile
.command("plug:compile", "Bundle (compile) one or more plug manifests") .command("plug:compile", "Bundle (compile) one or more plug manifests")
.arguments("<...name.plug.yaml:string>") .arguments("<...name.plug.yaml:string>")
.option("--debug [type:boolean]", "Do not minifiy code", { default: false }) .option("--debug", "Do not minifiy code", { default: false })
.option("--info [type:boolean]", "Print out size info per function", { .option("--info", "Print out size info per function", {
default: false, default: false,
}) })
.option("--watch, -w [type:boolean]", "Watch for changes and rebuild", { .option("--watch, -w [type:boolean]", "Watch for changes and rebuild", {
@ -78,14 +78,15 @@ await new Command()
// plug:run // plug:run
.command("plug:run", "Run a PlugOS function from the CLI") .command("plug:run", "Run a PlugOS function from the CLI")
.arguments("<spacePath> [function] [...args:string]") .arguments("<spacePath> [function] [...args:string]")
.option("--noIndex [type:boolean]", "Do not run a full space index first", {
default: false,
})
.option( .option(
"--hostname, -L <hostname:string>", "--hostname, -L <hostname:string>",
"Hostname or address to listen on", "Hostname or address to listen on",
) )
.option("-p, --port <port:number>", "Port to listen on") .option("-p, --port <port:number>", "Port to listen on")
.option(
"--db <db:string>",
"Path to database file",
)
.action(plugRunCommand) .action(plugRunCommand)
.command("user:add", "Add a new user to an authentication file") .command("user:add", "Add a new user to an authentication file")
.arguments("[username:string]") .arguments("[username:string]")

View File

@ -1,7 +1,7 @@
import { safeRun } from "../common/util.ts"; import { safeRun } from "../common/util.ts";
import { Client } from "./client.ts"; import { Client } from "./client.ts";
const syncMode = window.silverBulletConfig.supportOnlineMode !== "true" || const syncMode = window.silverBulletConfig.syncOnly ||
!!localStorage.getItem("syncMode"); !!localStorage.getItem("syncMode");
safeRun(async () => { safeRun(async () => {

View File

@ -52,7 +52,7 @@ declare global {
// Injected via index.html // Injected via index.html
silverBulletConfig: { silverBulletConfig: {
spaceFolderPath: string; spaceFolderPath: string;
supportOnlineMode: string; syncOnly: boolean;
}; };
client: Client; client: Client;
} }

View File

@ -46,8 +46,6 @@ export function TopBar({
lhs?: ComponentChildren; lhs?: ComponentChildren;
rhs?: ComponentChildren; rhs?: ComponentChildren;
}) { }) {
const inputRef = useRef<HTMLInputElement>(null);
// Another one of my less proud moments: // Another one of my less proud moments:
// Somehow I cannot seem to proerply limit the width of the page name, so I'm doing // Somehow I cannot seem to proerply limit the width of the page name, so I'm doing
// it this way. If you have a better way to do this, please let me know! // it this way. If you have a better way to do this, please let me know!
@ -66,7 +64,7 @@ export function TopBar({
// Then calculate a new width // Then calculate a new width
currentPageElement.style.width = `${ currentPageElement.style.width = `${
Math.min(editorWidth - 150, innerDiv.clientWidth - 150) Math.min(editorWidth - 170, innerDiv.clientWidth - 170)
}px`; }px`;
} }
} }

View File

@ -204,7 +204,8 @@ export class MainUI {
editor.focus(); editor.focus();
}} }}
actionButtons={[ actionButtons={[
...window.silverBulletConfig.supportOnlineMode === "true" ...!window.silverBulletConfig.syncOnly
// If we support syncOnly, don't show this toggle button
? [{ ? [{
icon: RefreshCwIcon, icon: RefreshCwIcon,
description: this.editor.syncMode description: this.editor.syncMode

View File

@ -35,13 +35,13 @@
window.silverBulletConfig = { window.silverBulletConfig = {
// These {{VARIABLES}} are replaced by http_server.ts // These {{VARIABLES}} are replaced by http_server.ts
spaceFolderPath: "{{SPACE_PATH}}", spaceFolderPath: "{{SPACE_PATH}}",
supportOnlineMode: "{{SUPPORT_ONLINE_MODE}}", syncOnly: "{{SYNC_ONLY}}" === "true",
}; };
// But in case these variables aren't replaced by the server, fall back fully static mode (no sync) // But in case these variables aren't replaced by the server, fall back sync only mode
if (window.silverBulletConfig.spaceFolderPath.includes("{{")) { if (window.silverBulletConfig.spaceFolderPath.includes("{{")) {
window.silverBulletConfig = { window.silverBulletConfig = {
spaceFolderPath: "", spaceFolderPath: "",
supportOnlineMode: false, syncOnly: true,
}; };
} }
</script> </script>

View File

@ -121,7 +121,9 @@ body {
display: inline-block; display: inline-block;
position: relative; position: relative;
top: -6px; top: -6px;
padding-right: 6px; padding: 4px;
background-color: var(--top-background-color);
margin-right: -2px;
} }
.progress-bar { .progress-bar {

View File

@ -130,6 +130,7 @@ You can configure SB with environment variables instead of flags as well. The fo
* `SB_PORT`: Sets the port to listen to, e.g. `SB_PORT=1234` * `SB_PORT`: Sets the port to listen to, e.g. `SB_PORT=1234`
* `SB_FOLDER`: Sets the folder to expose, e.g. `SB_FOLDER=/space` * `SB_FOLDER`: Sets the folder to expose, e.g. `SB_FOLDER=/space`
* `SB_AUTH`: Loads an [[Authentication]] database from a (JSON encoded) string, e.g. `SB_AUTH=$(cat /path/to/.auth.json)` * `SB_AUTH`: Loads an [[Authentication]] database from a (JSON encoded) string, e.g. `SB_AUTH=$(cat /path/to/.auth.json)`
* `SB_STORE_ONLY`: Runs the server in a "dumb" space store only mode (not indexing content or keeping other state), e.g. `SB_STORE_ONLY=1`
## Using Authelia ## Using Authelia
You need to adjust a few configuration options in [[Authelia]] in order for SilverBullet to work as intended. You need to adjust a few configuration options in [[Authelia]] in order for SilverBullet to work as intended.