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

View File

@ -9,16 +9,13 @@ import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_sp
export async function runPlug(
spacePath: string,
dbPath: string,
functionName: string | undefined,
args: string[] = [],
builtinAssetBundle: AssetBundle,
indexFirst = false,
httpServerPort = 3123,
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 app = new Application();
@ -27,7 +24,7 @@ export async function runPlug(
new DiskSpacePrimitives(spacePath),
builtinAssetBundle,
),
tempFile,
dbPath,
app,
);
await serverSystem.init();
@ -37,13 +34,6 @@ export async function runPlug(
signal: serverController.signal,
});
if (indexFirst) {
await serverSystem.system.loadedPlugs.get("index")!.invoke(
"reindexSpace",
[],
);
}
if (functionName) {
const [plugName, funcName] = functionName.split(".");
@ -54,7 +44,6 @@ export async function runPlug(
const result = await plug.invoke(funcName, args);
await serverSystem.close();
serverSystem.denoKv.close();
await Deno.remove(tempFile);
serverController.abort();
return result;
} 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",
};
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
import { silverBulletDbFile } from "./constants.ts";
export async function plugRunCommand(
{
noIndex,
db,
hostname,
port,
}: {
noIndex: boolean;
db?: string;
hostname?: string;
port?: number;
},
@ -21,14 +22,18 @@ export async function plugRunCommand(
) {
spacePath = path.resolve(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);
try {
const result = await runPlug(
spacePath,
dbPath,
functionName,
args,
new AssetBundle(assets),
!noIndex,
port,
hostname,
);

View File

@ -17,6 +17,7 @@ import { ServerSystem } from "../server/server_system.ts";
import { sleep } from "$sb/lib/async.ts";
import { SilverBulletHooks } from "../common/manifest.ts";
import { System } from "../plugos/system.ts";
import { silverBulletDbFile } from "./constants.ts";
export async function serveCommand(
options: {
@ -28,7 +29,7 @@ export async function serveCommand(
key?: string;
reindex?: boolean;
db?: string;
serverProcessing?: boolean;
storeOnly?: boolean;
},
folder?: string,
) {
@ -36,11 +37,13 @@ export async function serveCommand(
"127.0.0.1";
const port = options.port ||
(Deno.env.get("SB_PORT") && +Deno.env.get("SB_PORT")!) || 3000;
let dbFile = options.db || Deno.env.get("SB_DB_FILE") || ".silverbullet.db";
const storeOnly = options.storeOnly || Deno.env.get("SB_STORE_ONLY");
let dbFile = options.db || Deno.env.get("SB_DB_FILE") || silverBulletDbFile;
const app = new Application();
console.log(options);
if (!folder) {
folder = Deno.env.get("SB_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;
if (options.serverProcessing) {
// system = undefined in storeOnly mode (no PlugOS instance on the server)
if (!storeOnly) {
// Enable server-side processing
dbFile = path.resolve(folder, dbFile);
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
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 {
function syscall(name: string, ...args: any[]): Promise<any>;
@ -53,7 +55,7 @@ self.syscall = async (name: string, ...args: any[]) => {
export function setupMessageListener(
// deno-lint-ignore ban-types
functionMapping: Record<string, Function>,
manifest: Manifest,
manifest: any,
) {
self.addEventListener("message", (event: { data: WorkerMessage }) => {
(async () => {
@ -81,7 +83,6 @@ export function setupMessageListener(
});
}
}
break;
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()
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();

View File

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

View File

@ -45,9 +45,7 @@ export async function renamePageCommand(cmdDef: any) {
throw e;
}
}
const updatedReferences = await renamePage(oldName, newName);
console.log("Navigating to new page");
await editor.navigate(newName, 0, true);
const updatedReferences = await renamePage(oldName, newName, true);
await editor.flashNotification(
`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);
console.log("Writing new page to space");
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);
console.log("All pages containing backlinks", pagesToUpdate);

View File

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

View File

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

View File

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

View File

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

View File

@ -46,8 +46,6 @@ export function TopBar({
lhs?: ComponentChildren;
rhs?: ComponentChildren;
}) {
const inputRef = useRef<HTMLInputElement>(null);
// Another one of my less proud moments:
// 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!
@ -66,7 +64,7 @@ export function TopBar({
// Then calculate a new width
currentPageElement.style.width = `${
Math.min(editorWidth - 150, innerDiv.clientWidth - 150)
Math.min(editorWidth - 170, innerDiv.clientWidth - 170)
}px`;
}
}

View File

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

View File

@ -35,13 +35,13 @@
window.silverBulletConfig = {
// These {{VARIABLES}} are replaced by http_server.ts
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("{{")) {
window.silverBulletConfig = {
spaceFolderPath: "",
supportOnlineMode: false,
syncOnly: true,
};
}
</script>

View File

@ -121,7 +121,9 @@ body {
display: inline-block;
position: relative;
top: -6px;
padding-right: 6px;
padding: 4px;
background-color: var(--top-background-color);
margin-right: -2px;
}
.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_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_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
You need to adjust a few configuration options in [[Authelia]] in order for SilverBullet to work as intended.