Added a ton of JS Doc
parent
e29352556b
commit
cafd001214
|
@ -8,7 +8,6 @@ import plugAssetBundle from "../dist/plug_asset_bundle.json" with {
|
|||
import { AssetBundle, type AssetJson } from "../lib/asset_bundle/bundle.ts";
|
||||
|
||||
import { determineDatabaseBackend } from "../server/db_backend.ts";
|
||||
import type { SpaceServerConfig } from "../server/instance.ts";
|
||||
import { runPlug } from "../cmd/plug_run.ts";
|
||||
import { PrefixedKvPrimitives } from "$lib/data/prefixed_kv_primitives.ts";
|
||||
import { sleep } from "$lib/async.ts";
|
||||
|
@ -73,19 +72,6 @@ export async function serveCommand(
|
|||
const backendConfig = Deno.env.get("SB_SHELL_BACKEND") || "local";
|
||||
const enableSpaceScript = Deno.env.get("SB_SPACE_SCRIPT") !== "off";
|
||||
|
||||
const configs = new Map<string, SpaceServerConfig>();
|
||||
configs.set("*", {
|
||||
hostname,
|
||||
namespace: "*",
|
||||
auth: userCredentials,
|
||||
authToken: Deno.env.get("SB_AUTH_TOKEN"),
|
||||
syncOnly,
|
||||
readOnly,
|
||||
shellBackend: backendConfig,
|
||||
enableSpaceScript,
|
||||
pagesPath: folder,
|
||||
});
|
||||
|
||||
const plugAssets = new AssetBundle(plugAssetBundle as AssetJson);
|
||||
|
||||
if (readOnly) {
|
||||
|
@ -137,9 +123,16 @@ export async function serveCommand(
|
|||
baseKvPrimitives,
|
||||
keyFile: options.key,
|
||||
certFile: options.cert,
|
||||
configs,
|
||||
|
||||
auth: userCredentials,
|
||||
authToken: Deno.env.get("SB_AUTH_TOKEN"),
|
||||
syncOnly,
|
||||
readOnly,
|
||||
shellBackend: backendConfig,
|
||||
enableSpaceScript,
|
||||
pagesPath: folder,
|
||||
});
|
||||
httpServer.start();
|
||||
await httpServer.start();
|
||||
|
||||
// Wait in an infinite loop (to keep the HTTP server running, only cancelable via Ctrl+C or other signal)
|
||||
while (true) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "@silverbulletmd/silverbullet",
|
||||
"version": "0.0.1",
|
||||
"version": "0.9.0",
|
||||
"description": "Silverbullet is a Personal Knowledge Management System",
|
||||
"exports": {
|
||||
"./syscall": "./plug-api/syscall.ts",
|
||||
"./syscalls": "./plug-api/syscalls.ts",
|
||||
|
|
|
@ -136,7 +136,9 @@ export async function extractFrontmatter(
|
|||
return data;
|
||||
}
|
||||
|
||||
// Updates the front matter of a markdown document and returns the text as a rendered string
|
||||
/**
|
||||
* Updates the front matter of a markdown document and returns the text as a rendered string
|
||||
*/
|
||||
export async function prepareFrontmatterDispatch(
|
||||
tree: ParseTree,
|
||||
data: string | Record<string, any>,
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
// Compares two objects deeply
|
||||
/**
|
||||
* Performs a deep comparison of two objects, returning true if they are equal
|
||||
* @param a first object
|
||||
* @param b second object
|
||||
* @returns
|
||||
*/
|
||||
export function deepEqual(a: any, b: any): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
|
@ -40,7 +45,10 @@ export function deepEqual(a: any, b: any): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Converts a Date object to a date string in the format YYYY-MM-DD if it just contains a date (and no significant time), or a full ISO string otherwise
|
||||
/**
|
||||
* Converts a Date object to a date string in the format YYYY-MM-DD if it just contains a date (and no significant time), or a full ISO string otherwise
|
||||
* @param d the date to convert
|
||||
*/
|
||||
export function cleanStringDate(d: Date): string {
|
||||
// If no significant time, return a date string only
|
||||
if (
|
||||
|
@ -54,9 +62,13 @@ export function cleanStringDate(d: Date): string {
|
|||
}
|
||||
}
|
||||
|
||||
// Processes a JSON (typically coming from parse YAML frontmatter) in two ways:
|
||||
// 1. Expands property names in an object containing a .-separated path
|
||||
// 2. Converts dates to strings in sensible ways
|
||||
/**
|
||||
* Processes a JSON (typically coming from parse YAML frontmatter) in two ways:
|
||||
* 1. Expands property names in an object containing a .-separated path
|
||||
* 2. Converts dates to strings in sensible ways
|
||||
* @param a
|
||||
* @returns
|
||||
*/
|
||||
export function cleanupJSON(a: any): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
|
|
|
@ -4,6 +4,9 @@ import {
|
|||
renderToText,
|
||||
} from "@silverbulletmd/silverbullet/lib/tree";
|
||||
|
||||
/**
|
||||
* Strips markdown from a ParseTree
|
||||
*/
|
||||
export function stripMarkdown(
|
||||
tree: ParseTree,
|
||||
): string {
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
/**
|
||||
* Represents a reference to a page, with optional position, anchor and header.
|
||||
*/
|
||||
export type PageRef = {
|
||||
page: string;
|
||||
pos?: number | { line: number; column: number };
|
||||
anchor?: string;
|
||||
header?: string;
|
||||
meta?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a name looks like a full path (with a file extension), is not a conflicted file and not a search page.
|
||||
*/
|
||||
|
@ -6,6 +17,9 @@ export function looksLikePathWithExtension(name: string): boolean {
|
|||
!name.startsWith("🔍 ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a name looks like a full path (with a file extension), is not a conflicted file and not a search page.
|
||||
*/
|
||||
export function validatePageName(name: string) {
|
||||
// Page can not be empty and not end with a file extension (e.g. "bla.md")
|
||||
if (name === "") {
|
||||
|
@ -19,19 +33,16 @@ export function validatePageName(name: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export type PageRef = {
|
||||
page: string;
|
||||
pos?: number | { line: number; column: number };
|
||||
anchor?: string;
|
||||
header?: string;
|
||||
meta?: boolean;
|
||||
};
|
||||
|
||||
const posRegex = /@(\d+)$/;
|
||||
const linePosRegex = /@[Ll](\d+)(?:[Cc](\d+))?$/; // column is optional, implicit 1
|
||||
const anchorRegex = /\$([a-zA-Z\.\-\/]+[\w\.\-\/]*)$/;
|
||||
const headerRegex = /#([^#]*)$/;
|
||||
|
||||
/**
|
||||
* Parses a page reference string into a PageRef object.
|
||||
* @param name the name of the page reference to parse
|
||||
* @returns the parsed PageRef object
|
||||
*/
|
||||
export function parsePageRef(name: string): PageRef {
|
||||
// Normalize the page name
|
||||
if (name.startsWith("[[") && name.endsWith("]]")) {
|
||||
|
@ -71,6 +82,11 @@ export function parsePageRef(name: string): PageRef {
|
|||
return pageRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* The inverse of parsePageRef, encodes a PageRef object into a string.
|
||||
* @param pageRef the page reference to encode
|
||||
* @returns a string representation of the page reference
|
||||
*/
|
||||
export function encodePageRef(pageRef: PageRef): string {
|
||||
let name = pageRef.page;
|
||||
if (pageRef.pos) {
|
||||
|
|
|
@ -12,6 +12,6 @@ if (typeof self === "undefined") {
|
|||
}
|
||||
|
||||
// Late binding syscall
|
||||
export function syscall(name: string, ...args: any[]) {
|
||||
export function syscall(name: string, ...args: any[]): Promise<any> {
|
||||
return (globalThis as any).syscall(name, ...args);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import { base64DecodeDataUrl } from "../../lib/crypto.ts";
|
||||
import { syscall } from "../syscall.ts";
|
||||
|
||||
/**
|
||||
* Reads an asset embedded in a plug (via the `assets` field in the plug manifest).
|
||||
* @param plugName name of the plug to read asset from
|
||||
* @param name name of the asset to read
|
||||
* @param encoding either "utf8" or "dataurl"
|
||||
* @returns the content of the asset in the requested encoding
|
||||
*/
|
||||
export async function readAsset(
|
||||
plugName: string,
|
||||
name: string,
|
||||
|
|
|
@ -5,14 +5,28 @@ import { syscall } from "../syscall.ts";
|
|||
* Generally should only be used to set some client-specific states, such as preferences.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets a value in the client store.
|
||||
* @param key the key to set
|
||||
* @param value the value to set
|
||||
*/
|
||||
export function set(key: string, value: any): Promise<void> {
|
||||
return syscall("clientStore.set", key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from the client store.
|
||||
* @param key the key to get
|
||||
* @returns the value associated with the key
|
||||
*/
|
||||
export function get(key: string): Promise<any> {
|
||||
return syscall("clientStore.get", key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a value from the client store.
|
||||
* @param key the key to delete
|
||||
*/
|
||||
export function del(key: string): Promise<void> {
|
||||
return syscall("clientStore.delete", key);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import type { CodeWidgetContent } from "../types.ts";
|
||||
import { syscall } from "../syscall.ts";
|
||||
|
||||
/**
|
||||
* Renders a code widget.
|
||||
* @param lang the language of the fenced code block
|
||||
* @param body the body of the code to render
|
||||
* @param pageName the name of the page the code widget appears on
|
||||
* @returns the rendered code widget content
|
||||
*/
|
||||
export function render(
|
||||
lang: string,
|
||||
body: string,
|
||||
|
@ -9,7 +16,9 @@ export function render(
|
|||
return syscall("codeWidget.render", lang, body, pageName);
|
||||
}
|
||||
|
||||
// Refresh all code widgets on the page that support it
|
||||
/**
|
||||
* Refreshes all code widgets on the page that support it.
|
||||
*/
|
||||
export function refreshAll(): Promise<void> {
|
||||
return syscall("codeWidget.refreshAll");
|
||||
}
|
||||
|
|
|
@ -1,30 +1,67 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
import type { KV, KvKey, KvQuery } from "../types.ts";
|
||||
|
||||
/**
|
||||
* Exposes a key value story with query capabilities.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets a value in the key value store.
|
||||
* @param key the key to set
|
||||
* @param value the value to set
|
||||
*/
|
||||
export function set(key: KvKey, value: any): Promise<void> {
|
||||
return syscall("datastore.set", key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple values in the key value store.
|
||||
* @param kvs the key value pairs to set
|
||||
*/
|
||||
export function batchSet(kvs: KV[]): Promise<void> {
|
||||
return syscall("datastore.batchSet", kvs);
|
||||
}
|
||||
|
||||
export function get(key: KvKey): Promise<any> {
|
||||
/**
|
||||
* Gets a value from the key value store.
|
||||
* @param key the key to get
|
||||
* @returns the value associated with the key (or undefined if not found)
|
||||
*/
|
||||
export function get(key: KvKey): Promise<any | undefined> {
|
||||
return syscall("datastore.get", key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets multiple values from the key value store.
|
||||
* @param keys the keys to get
|
||||
* @returns the values associated with the keys (or undefined if not found)
|
||||
*/
|
||||
export function batchGet(keys: KvKey[]): Promise<(any | undefined)[]> {
|
||||
return syscall("datastore.batchGet", keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a value from the key value store.
|
||||
* @param key the key to delete
|
||||
*/
|
||||
export function del(key: KvKey): Promise<void> {
|
||||
return syscall("datastore.delete", key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple values from the key value store.
|
||||
* @param keys the keys to delete
|
||||
*/
|
||||
export function batchDel(keys: KvKey[]): Promise<void> {
|
||||
return syscall("datastore.batchDelete", keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the key value store.
|
||||
* @param query the query to run
|
||||
* @param variables the variables that can be referenced inside the query
|
||||
* @returns the results of the query
|
||||
*/
|
||||
export function query(
|
||||
query: KvQuery,
|
||||
variables: Record<string, any> = {},
|
||||
|
@ -32,6 +69,11 @@ export function query(
|
|||
return syscall("datastore.query", query, variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the key value store and deletes all matching items
|
||||
* @param query the query to run
|
||||
* @param variables the variables that can be referenced inside the query
|
||||
*/
|
||||
export function queryDelete(
|
||||
query: KvQuery,
|
||||
variables?: Record<string, any>,
|
||||
|
@ -39,6 +81,10 @@ export function queryDelete(
|
|||
return syscall("datastore.queryDelete", query, variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all functions currently defined and available for use in queries
|
||||
* @returns the names of all functions in the key value store
|
||||
*/
|
||||
export function listFunctions(): Promise<string[]> {
|
||||
return syscall("datastore.listFunctions");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
|
||||
/**
|
||||
* Exposes various debugging utilities.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Completely wipes the client state, both cached files as well as databases (best effort)
|
||||
*/
|
||||
export function resetClient(): Promise<void> {
|
||||
return syscall("debug.resetClient");
|
||||
}
|
||||
|
|
|
@ -3,14 +3,22 @@ import { syscall } from "../syscall.ts";
|
|||
import type { PageRef } from "../lib/page_ref.ts";
|
||||
import type { FilterOption } from "@silverbulletmd/silverbullet/type/client";
|
||||
|
||||
/**
|
||||
* Exposes various editor utilities.
|
||||
* Important: These syscalls are only available in the client.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the name of the page currently open in the editor.
|
||||
* @returns the current page name
|
||||
*/
|
||||
export function getCurrentPage(): Promise<string> {
|
||||
return syscall("editor.getCurrentPage");
|
||||
}
|
||||
|
||||
export function setPage(newName: string): Promise<void> {
|
||||
return syscall("editor.setPage", newName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full text of the currently open page
|
||||
*/
|
||||
export function getText(): Promise<string> {
|
||||
return syscall("editor.getText");
|
||||
}
|
||||
|
@ -23,22 +31,42 @@ export function setText(newText: string): Promise<void> {
|
|||
return syscall("editor.setText", newText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position (in # of characters from the beginning of the file) of the cursor in the editor
|
||||
*/
|
||||
export function getCursor(): Promise<number> {
|
||||
return syscall("editor.getCursor");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the line number and column number of the cursor in the editor
|
||||
*/
|
||||
export function getSelection(): Promise<{ from: number; to: number }> {
|
||||
return syscall("editor.getSelection");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of the cursor in the editor
|
||||
* @param from the start position of the selection
|
||||
* @param to the end position of the selection
|
||||
*/
|
||||
export function setSelection(from: number, to: number): Promise<void> {
|
||||
return syscall("editor.setSelection", from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a save of the current page
|
||||
*/
|
||||
export function save(): Promise<void> {
|
||||
return syscall("editor.save");
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the specified page reference
|
||||
* @param pageRef the page reference to navigate to
|
||||
* @param replaceState whether to replace the current history state in the browser history
|
||||
* @param newWindow whether to open the page in a new window
|
||||
*/
|
||||
export function navigate(
|
||||
pageRef: PageRef,
|
||||
replaceState = false,
|
||||
|
@ -47,28 +75,49 @@ export function navigate(
|
|||
return syscall("editor.navigate", pageRef, replaceState, newWindow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the page navigator
|
||||
* @param mode the mode to open the navigator in
|
||||
*/
|
||||
export function openPageNavigator(
|
||||
mode: "page" | "meta" | "all" = "page",
|
||||
): Promise<void> {
|
||||
return syscall("editor.openPageNavigator", mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the command palette
|
||||
*/
|
||||
export function openCommandPalette(): Promise<void> {
|
||||
return syscall("editor.openCommandPalette");
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reloads the current page
|
||||
*/
|
||||
export function reloadPage(): Promise<void> {
|
||||
return syscall("editor.reloadPage");
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reloads the browser UI
|
||||
*/
|
||||
export function reloadUI(): Promise<void> {
|
||||
return syscall("editor.reloadUI");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the config and commands, also in the server
|
||||
*/
|
||||
export function reloadConfigAndCommands(): Promise<void> {
|
||||
return syscall("editor.reloadConfigAndCommands");
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the specified URL in the browser
|
||||
* @param url the URL to open
|
||||
* @param existingWindow whether to open the URL in an existing window
|
||||
*/
|
||||
export function openUrl(url: string, existingWindow = false): Promise<void> {
|
||||
return syscall("editor.openUrl", url, existingWindow);
|
||||
}
|
||||
|
@ -82,11 +131,20 @@ export function goHistory(delta: number): Promise<void> {
|
|||
return syscall("editor.goHistory", delta);
|
||||
}
|
||||
|
||||
// Force the client to download the file in dataUrl with filename as file name
|
||||
/**
|
||||
* Force the client to download the file in dataUrl with filename as file name
|
||||
* @param filename the name of the file to download
|
||||
* @param dataUrl the dataUrl of the file to download
|
||||
*/
|
||||
export function downloadFile(filename: string, dataUrl: string): Promise<void> {
|
||||
return syscall("editor.downloadFile", filename, dataUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the browser's native file upload dialog/popup
|
||||
* @param accept the file types to accept
|
||||
* @param capture the capture mode for the file input
|
||||
*/
|
||||
export function uploadFile(
|
||||
accept?: string,
|
||||
capture?: string,
|
||||
|
@ -94,6 +152,11 @@ export function uploadFile(
|
|||
return syscall("editor.uploadFile", accept, capture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a flash notification to the user (top right corner)
|
||||
* @param message the message to show
|
||||
* @param type the type of notification to show
|
||||
*/
|
||||
export function flashNotification(
|
||||
message: string,
|
||||
type: "info" | "error" = "info",
|
||||
|
@ -101,6 +164,13 @@ export function flashNotification(
|
|||
return syscall("editor.flashNotification", message, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes a filter box UI (similar to the page navigator and command palette)
|
||||
* @param label the label to show left of the input box
|
||||
* @param options the options to show and to filter on
|
||||
* @param helpText the help text to show below the input box
|
||||
* @param placeHolder the placeholder text to show in the input box
|
||||
*/
|
||||
export function filterBox(
|
||||
label: string,
|
||||
options: FilterOption[],
|
||||
|
@ -110,6 +180,13 @@ export function filterBox(
|
|||
return syscall("editor.filterBox", label, options, helpText, placeHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a panel in the editor
|
||||
* @param id the location of the panel to show
|
||||
* @param mode the mode or "size" of the panel
|
||||
* @param html the html content of the panel
|
||||
* @param script the script content of the panel
|
||||
*/
|
||||
export function showPanel(
|
||||
id: "lhs" | "rhs" | "bhs" | "modal",
|
||||
mode: number,
|
||||
|
@ -119,16 +196,31 @@ export function showPanel(
|
|||
return syscall("editor.showPanel", id, mode, html, script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides a panel in the editor
|
||||
* @param id the location of the panel to hide
|
||||
*/
|
||||
export function hidePanel(
|
||||
id: "lhs" | "rhs" | "bhs" | "modal",
|
||||
): Promise<void> {
|
||||
return syscall("editor.hidePanel", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert text at the specified position into the editor
|
||||
* @param text the text to insert
|
||||
* @param pos
|
||||
*/
|
||||
export function insertAtPos(text: string, pos: number): Promise<void> {
|
||||
return syscall("editor.insertAtPos", text, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the text in the specified range in the editor
|
||||
* @param from the start position of the range
|
||||
* @param to the end position of the range
|
||||
* @param text the text to replace with
|
||||
*/
|
||||
export function replaceRange(
|
||||
from: number,
|
||||
to: number,
|
||||
|
@ -137,10 +229,21 @@ export function replaceRange(
|
|||
return syscall("editor.replaceRange", from, to, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the cursor to the specified position in the editor
|
||||
* @param pos the position to move the cursor to
|
||||
* @param center whether to center the cursor in the editor after moving
|
||||
*/
|
||||
export function moveCursor(pos: number, center = false): Promise<void> {
|
||||
return syscall("editor.moveCursor", pos, center);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the cursor to the specified line and column in the editor
|
||||
* @param line the line number to move the cursor to
|
||||
* @param column the column number to move the cursor to
|
||||
* @param center whether to center the cursor in the editor after moving
|
||||
*/
|
||||
export function moveCursorToLine(
|
||||
line: number,
|
||||
column = 1,
|
||||
|
@ -149,14 +252,27 @@ export function moveCursorToLine(
|
|||
return syscall("editor.moveCursorToLine", line, column, center);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert text at the cursor position in the editor
|
||||
* @param text the text to insert
|
||||
*/
|
||||
export function insertAtCursor(text: string): Promise<void> {
|
||||
return syscall("editor.insertAtCursor", text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a CodeMirror transaction: https://codemirror.net/docs/ref/#state.Transaction
|
||||
*/
|
||||
export function dispatch(change: any): Promise<void> {
|
||||
return syscall("editor.dispatch", change);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for text input
|
||||
* @param message the message to show in the prompt
|
||||
* @param defaultValue a default value pre-filled in the prompt
|
||||
* @returns
|
||||
*/
|
||||
export function prompt(
|
||||
message: string,
|
||||
defaultValue = "",
|
||||
|
@ -164,62 +280,112 @@ export function prompt(
|
|||
return syscall("editor.prompt", message, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for confirmation
|
||||
* @param message the message to show in the confirmation dialog
|
||||
* @returns
|
||||
*/
|
||||
export function confirm(
|
||||
message: string,
|
||||
): Promise<boolean> {
|
||||
return syscall("editor.confirm", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a UI option
|
||||
* @param key the key of the UI option to get
|
||||
* @returns
|
||||
*/
|
||||
export function getUiOption(key: string): Promise<any> {
|
||||
return syscall("editor.getUiOption", key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a UI option
|
||||
* @param key the key of the UI option to set
|
||||
* @param value the value to set the UI option to
|
||||
*/
|
||||
export function setUiOption(key: string, value: any): Promise<void> {
|
||||
return syscall("editor.setUiOption", key, value);
|
||||
}
|
||||
|
||||
// Vim specific
|
||||
export function vimEx(exCommand: string): Promise<any> {
|
||||
return syscall("editor.vimEx", exCommand);
|
||||
}
|
||||
|
||||
// Folding
|
||||
/**
|
||||
* Perform a fold at the current cursor position
|
||||
*/
|
||||
export function fold(): Promise<void> {
|
||||
return syscall("editor.fold");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an unfold at the current cursor position
|
||||
*/
|
||||
export function unfold(): Promise<void> {
|
||||
return syscall("editor.unfold");
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the fold at the current cursor position
|
||||
*/
|
||||
export function toggleFold(): Promise<void> {
|
||||
return syscall("editor.toggleFold");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fold all code blocks in the editor
|
||||
*/
|
||||
export function foldAll(): Promise<void> {
|
||||
return syscall("editor.foldAll");
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfold all code blocks in the editor
|
||||
*/
|
||||
export function unfoldAll(): Promise<void> {
|
||||
return syscall("editor.unfoldAll");
|
||||
}
|
||||
|
||||
// Undo/redo
|
||||
/**
|
||||
* Perform an undo operation of the last edit in the editor
|
||||
*/
|
||||
export function undo(): Promise<void> {
|
||||
return syscall("editor.undo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a redo operation of the last undo in the editor
|
||||
*/
|
||||
export function redo(): Promise<void> {
|
||||
return syscall("editor.redo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the editor's native search panel
|
||||
*/
|
||||
export function openSearchPanel(): Promise<void> {
|
||||
return syscall("editor.openSearchPanel");
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the specified data to the clipboard
|
||||
* @param data the data to copy
|
||||
*/
|
||||
export function copyToClipboard(data: string | Blob): Promise<void> {
|
||||
return syscall("editor.copyToClipboard", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the current line in the editor
|
||||
*/
|
||||
export function deleteLine(): Promise<void> {
|
||||
return syscall("editor.deleteLine");
|
||||
}
|
||||
|
||||
// Vim-mode specific syscalls
|
||||
|
||||
/**
|
||||
* Execute a Vim ex command
|
||||
* @param exCommand the ex command to execute
|
||||
*/
|
||||
export function vimEx(exCommand: string): Promise<any> {
|
||||
return syscall("editor.vimEx", exCommand);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
|
||||
/**
|
||||
* Triggers an event on the SilverBullet event bus.
|
||||
* This can be used to implement an RPC-style system too, because event handlers can return values,
|
||||
* which are then accumulated in an array and returned to the caller.
|
||||
* @param eventName the name of the event to trigger
|
||||
* @param data payload to send with the event
|
||||
* @param timeout optional timeout in milliseconds to wait for a response
|
||||
* @returns an array of responses from the event handlers (if any)
|
||||
*/
|
||||
export function dispatchEvent(
|
||||
eventName: string,
|
||||
data: any,
|
||||
|
@ -24,6 +33,10 @@ export function dispatchEvent(
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List all events currently registered (listened to) on the SilverBullet event bus.
|
||||
* @returns an array of event names
|
||||
*/
|
||||
export function listEvents(): Promise<string[]> {
|
||||
return syscall("event.list");
|
||||
}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
|
||||
/**
|
||||
* Validates a JSON object against a JSON schema.
|
||||
* @param schema the JSON schema to validate against
|
||||
* @param object the JSON object to validate
|
||||
* @returns an error message if the object is invalid, or undefined if it is valid
|
||||
*/
|
||||
export function validateObject(
|
||||
schema: any,
|
||||
object: any,
|
||||
): Promise<any> {
|
||||
): Promise<string | undefined> {
|
||||
return syscall("jsonschema.validateObject", schema, object);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@ export function parseLanguage(
|
|||
return syscall("language.parseLanguage", language, code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all supported languages in fenced code blocks
|
||||
* @returns a list of all supported languages
|
||||
*/
|
||||
export function listLanguages(): Promise<string[]> {
|
||||
return syscall("language.listLanguages");
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
|
||||
import type { ParseTree } from "../lib/tree.ts";
|
||||
|
||||
/**
|
||||
* Parses a piece of markdown text into a ParseTree.
|
||||
* @param text the markdown text to parse
|
||||
* @returns a ParseTree representation of the markdown text
|
||||
*/
|
||||
export function parseMarkdown(text: string): Promise<ParseTree> {
|
||||
return syscall("markdown.parseMarkdown", text);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,50 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
import type { MQStats } from "../types.ts";
|
||||
|
||||
/**
|
||||
* Implements a simple Message Queue system.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sends a message to a queue.
|
||||
* @param queue the name of the queue to send the message to
|
||||
* @param body the body of the message to send
|
||||
*/
|
||||
export function send(queue: string, body: any): Promise<void> {
|
||||
return syscall("mq.send", queue, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a batch of messages to a queue.
|
||||
* @param queue the name of the queue
|
||||
* @param bodies the bodies of the messages to send
|
||||
*/
|
||||
export function batchSend(queue: string, bodies: any[]): Promise<void> {
|
||||
return syscall("mq.batchSend", queue, bodies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledges a message from a queue, in case it needs to be explicitly acknowledged.
|
||||
* @param queue the name of the queue the message came from
|
||||
* @param id the id of the message to acknowledge
|
||||
*/
|
||||
export function ack(queue: string, id: string): Promise<void> {
|
||||
return syscall("mq.ack", queue, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledges a batch of messages from a queue, in case they need to be explicitly acknowledged.
|
||||
* @param queue the name of the queue the messages came from
|
||||
* @param ids the ids of the messages to acknowledge
|
||||
*/
|
||||
export function batchAck(queue: string, ids: string[]): Promise<void> {
|
||||
return syscall("mq.batchAck", queue, ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves stats on a particular queue.
|
||||
* @param queue the name of the queue
|
||||
*/
|
||||
export function getQueueStats(queue: string): Promise<MQStats> {
|
||||
return syscall("mq.getQueueStats", queue);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
|
||||
/**
|
||||
* Runs a shell command.
|
||||
* @param cmd the command to run
|
||||
* @param args the arguments to pass to the command
|
||||
* @returns the stdout, stderr, and exit code of the command
|
||||
*/
|
||||
export function run(
|
||||
cmd: string,
|
||||
args: string[],
|
||||
|
|
|
@ -1,37 +1,74 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
|
||||
import type { AttachmentMeta, FileMeta, PageMeta } from "../types.ts";
|
||||
|
||||
export function listPages(unfiltered = false): Promise<PageMeta[]> {
|
||||
return syscall("space.listPages", unfiltered);
|
||||
/**
|
||||
* Lists all pages (files ending in .md) in the space.
|
||||
* @param unfiltered
|
||||
* @returns a list of all pages in the space represented as PageMeta objects
|
||||
*/
|
||||
export function listPages(): Promise<PageMeta[]> {
|
||||
return syscall("space.listPages");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for a page in the space.
|
||||
* @param name the name of the page to get metadata for
|
||||
* @returns the metadata for the page
|
||||
*/
|
||||
export function getPageMeta(name: string): Promise<PageMeta> {
|
||||
return syscall("space.getPageMeta", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a page from the space as text.
|
||||
* @param name the name of the page to read
|
||||
* @returns the text of the page
|
||||
*/
|
||||
export function readPage(
|
||||
name: string,
|
||||
): Promise<string> {
|
||||
return syscall("space.readPage", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a page to the space.
|
||||
* @param name the name of the page to write
|
||||
* @param text the text of the page to write
|
||||
* @returns the metadata for the written page
|
||||
*/
|
||||
export function writePage(name: string, text: string): Promise<PageMeta> {
|
||||
return syscall("space.writePage", name, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a page from the space.
|
||||
* @param name the name of the page to delete
|
||||
*/
|
||||
export function deletePage(name: string): Promise<void> {
|
||||
return syscall("space.deletePage", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all plugs in the space.
|
||||
* @returns a list of all plugs in the space represented as FileMeta objects
|
||||
*/
|
||||
export function listPlugs(): Promise<FileMeta[]> {
|
||||
return syscall("space.listPlugs");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all attachments in the space (all files not ending in .md).
|
||||
* @returns a list of all attachments in the space represented as AttachmentMeta objects
|
||||
*/
|
||||
export function listAttachments(): Promise<AttachmentMeta[]> {
|
||||
return syscall("space.listAttachments");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for an attachment in the space.
|
||||
* @param name the path of the attachment to get metadata for
|
||||
* @returns the metadata for the attachment
|
||||
*/
|
||||
export function getAttachmentMeta(name: string): Promise<AttachmentMeta> {
|
||||
return syscall("space.getAttachmentMeta", name);
|
||||
}
|
||||
|
@ -68,19 +105,40 @@ export function deleteAttachment(name: string): Promise<void> {
|
|||
return syscall("space.deleteAttachment", name);
|
||||
}
|
||||
|
||||
// FS
|
||||
// Lower level-file operations
|
||||
|
||||
/**
|
||||
* List all files in the space (pages, attachments and plugs).
|
||||
* @returns a list of all files in the space represented as FileMeta objects
|
||||
*/
|
||||
export function listFiles(): Promise<FileMeta[]> {
|
||||
return syscall("space.listFiles");
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a file from the space as a Uint8Array.
|
||||
* @param name the name of the file to read
|
||||
* @returns the data of the file
|
||||
*/
|
||||
export function readFile(name: string): Promise<Uint8Array> {
|
||||
return syscall("space.readFile", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for a file in the space.
|
||||
* @param name the name of the file to get metadata for
|
||||
* @returns the metadata for the file
|
||||
*/
|
||||
export function getFileMeta(name: string): Promise<FileMeta> {
|
||||
return syscall("space.getFileMeta", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a file to the space.
|
||||
* @param name the name of the file to write
|
||||
* @param data the data of the file to write
|
||||
* @returns the metadata for the written file
|
||||
*/
|
||||
export function writeFile(
|
||||
name: string,
|
||||
data: Uint8Array,
|
||||
|
@ -88,6 +146,10 @@ export function writeFile(
|
|||
return syscall("space.writeFile", name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file from the space.
|
||||
* @param name the name of the file to delete
|
||||
*/
|
||||
export function deleteFile(name: string): Promise<void> {
|
||||
return syscall("space.deleteFile", name);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,34 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
|
||||
/**
|
||||
* Syscalls that interact with the sync engine (when the client runs in Sync mode)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if a sync is currently in progress
|
||||
*/
|
||||
export function isSyncing(): Promise<boolean> {
|
||||
return syscall("sync.isSyncing");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an initial sync has completed
|
||||
*/
|
||||
export function hasInitialSyncCompleted(): Promise<boolean> {
|
||||
return syscall("sync.hasInitialSyncCompleted");
|
||||
}
|
||||
|
||||
/**
|
||||
* Actively schedules a file to be synced. Sync will happen by default too, but this prioritizes the file.
|
||||
* @param path the path to the file to sync
|
||||
*/
|
||||
export function scheduleFileSync(path: string): Promise<void> {
|
||||
return syscall("sync.scheduleFileSync", path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a sync of without waiting for the usual sync interval.
|
||||
*/
|
||||
export function scheduleSpaceSync(): Promise<number> {
|
||||
return syscall("sync.scheduleSpaceSync");
|
||||
}
|
||||
|
|
|
@ -4,6 +4,12 @@ import type { ParseTree } from "../lib/tree.ts";
|
|||
import { syscall } from "../syscall.ts";
|
||||
import type { Config } from "../../type/config.ts";
|
||||
|
||||
/**
|
||||
* Invoke a plug function
|
||||
* @param name a string representing the name of the function to invoke ("plug.functionName")
|
||||
* @param args arguments to pass to the function
|
||||
* @returns
|
||||
*/
|
||||
export function invokeFunction(
|
||||
name: string,
|
||||
...args: any[]
|
||||
|
@ -11,20 +17,38 @@ export function invokeFunction(
|
|||
return syscall("system.invokeFunction", name, ...args);
|
||||
}
|
||||
|
||||
// Only available on the client
|
||||
/**
|
||||
* Invoke a client command by name
|
||||
* Note: only available on the client
|
||||
* @param name name of the command
|
||||
* @param args arguments to pass to the command
|
||||
*/
|
||||
export function invokeCommand(name: string, args?: string[]): Promise<any> {
|
||||
return syscall("system.invokeCommand", name, args);
|
||||
}
|
||||
|
||||
// Only available on the client
|
||||
export function listCommands(): Promise<{ [key: string]: CommandDef }> {
|
||||
/**
|
||||
* Lists all commands available
|
||||
* @returns a map of all available commands
|
||||
*/
|
||||
export function listCommands(): Promise<Record<string, CommandDef>> {
|
||||
return syscall("system.listCommands");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all syscalls available
|
||||
* @returns a list of all available syscalls
|
||||
*/
|
||||
export function listSyscalls(): Promise<SyscallMeta[]> {
|
||||
return syscall("system.listSyscalls");
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a space function by name
|
||||
* @param name a string representing the name of the function to invoke
|
||||
* @param args arguments to pass to the function
|
||||
* @returns the value returned by the function
|
||||
*/
|
||||
export function invokeSpaceFunction(
|
||||
name: string,
|
||||
...args: any[]
|
||||
|
@ -32,6 +56,9 @@ export function invokeSpaceFunction(
|
|||
return syscall("system.invokeSpaceFunction", name, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies attribute extractors to a ParseTree
|
||||
*/
|
||||
export function applyAttributeExtractors(
|
||||
tags: string[],
|
||||
text: string,
|
||||
|
@ -43,6 +70,7 @@ export function applyAttributeExtractors(
|
|||
/**
|
||||
* Loads a particular space configuration key (or all of them when no key is spacified)
|
||||
* @param key the key to load, when not specified, all keys are loaded
|
||||
* @param defaultValue the default value to return when the key is not found
|
||||
* @returns either the value of the key or all keys as a Record<string, any>
|
||||
*/
|
||||
export async function getSpaceConfig(
|
||||
|
@ -52,23 +80,39 @@ export async function getSpaceConfig(
|
|||
return (await syscall("system.getSpaceConfig", key)) ?? defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a reload of all plugs
|
||||
* @returns
|
||||
*/
|
||||
export function reloadPlugs(): Promise<void> {
|
||||
return syscall("system.reloadPlugs");
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an explicit reload of the configuration
|
||||
* @returns the new configuration
|
||||
*/
|
||||
export function reloadConfig(): Promise<Config> {
|
||||
return syscall("system.reloadConfig");
|
||||
}
|
||||
|
||||
// Returns what runtime environment this plug is run in, e.g. "server" or "client" can be undefined, which would mean a hybrid environment (such as mobile)
|
||||
/**
|
||||
* Returns what runtime environment this plug is run in, e.g. "server" or "client" can be undefined, which would mean a hybrid environment (such as mobile)
|
||||
*/
|
||||
export function getEnv(): Promise<string | undefined> {
|
||||
return syscall("system.getEnv");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current mode of the system, either "ro" (read-only) or "rw" (read-write)
|
||||
*/
|
||||
export function getMode(): Promise<"ro" | "rw"> {
|
||||
return syscall("system.getMode");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SilverBullet version
|
||||
*/
|
||||
export function getVersion(): Promise<string> {
|
||||
return syscall("system.getVersion");
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import type { AST } from "@silverbulletmd/silverbullet/lib/tree";
|
||||
import { syscall } from "../syscall.ts";
|
||||
|
||||
/**
|
||||
* Renders
|
||||
* @param template
|
||||
* @param obj
|
||||
* @param globals
|
||||
* @returns
|
||||
* Renders a template with the given object and globals.
|
||||
* @param template the text of the template to render
|
||||
* @param obj the object to render the template with
|
||||
* @param globals the globals to render the template with
|
||||
* @returns the rendered template
|
||||
*/
|
||||
export function renderTemplate(
|
||||
template: string,
|
||||
|
@ -15,8 +16,13 @@ export function renderTemplate(
|
|||
return syscall("template.renderTemplate", template, obj, globals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a template into an AST.
|
||||
* @param template the text of the template to parse
|
||||
* @returns an AST representation of the template
|
||||
*/
|
||||
export function parseTemplate(
|
||||
template: string,
|
||||
): Promise<string> {
|
||||
): Promise<AST> {
|
||||
return syscall("template.parseTemplate", template);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
import { syscall } from "../syscall.ts";
|
||||
|
||||
/**
|
||||
* Parses a YAML string into a JavaScript object.
|
||||
* @param text the YAML text to parse
|
||||
* @returns a JavaScript object representation of the YAML text
|
||||
*/
|
||||
export function parse(
|
||||
text: string,
|
||||
): Promise<any> {
|
||||
return syscall("yaml.parse", text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JavaScript object into a YAML string.
|
||||
* @param obj the object to stringify
|
||||
* @returns a YAML string representation of the object
|
||||
*/
|
||||
export function stringify(
|
||||
obj: any,
|
||||
): Promise<string> {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { deleteCookie, getCookie, setCookie } from "hono/helper.ts";
|
||||
import { cors } from "hono/middleware.ts";
|
||||
import { type Context, Hono, type HonoRequest, validator } from "hono/mod.ts";
|
||||
import { type Context, Hono, validator } from "hono/mod.ts";
|
||||
import type { AssetBundle } from "$lib/asset_bundle/bundle.ts";
|
||||
import type {
|
||||
EndpointRequest,
|
||||
|
@ -8,8 +8,7 @@ import type {
|
|||
FileMeta,
|
||||
} from "@silverbulletmd/silverbullet/types";
|
||||
import type { ShellRequest } from "@silverbulletmd/silverbullet/type/rpc";
|
||||
import { SpaceServer } from "./instance.ts";
|
||||
import type { SpaceServerConfig } from "./instance.ts";
|
||||
import { SpaceServer } from "./space_server.ts";
|
||||
import type { KvPrimitives } from "$lib/data/kv_primitives.ts";
|
||||
import { PrefixedKvPrimitives } from "$lib/data/prefixed_kv_primitives.ts";
|
||||
import { extendedMarkdownLanguage } from "$common/markdown_parser/parser.ts";
|
||||
|
@ -33,7 +32,15 @@ export type ServerOptions = {
|
|||
certFile?: string;
|
||||
keyFile?: string;
|
||||
|
||||
configs: Map<string, SpaceServerConfig>;
|
||||
// Enable username/password auth
|
||||
auth?: { user: string; pass: string };
|
||||
// Additional API auth token
|
||||
authToken?: string;
|
||||
pagesPath: string;
|
||||
shellBackend: string;
|
||||
syncOnly: boolean;
|
||||
readOnly: boolean;
|
||||
enableSpaceScript: boolean;
|
||||
};
|
||||
|
||||
export class HttpServer {
|
||||
|
@ -46,11 +53,11 @@ export class HttpServer {
|
|||
keyFile: string | undefined;
|
||||
certFile: string | undefined;
|
||||
|
||||
spaceServers = new Map<string, Promise<SpaceServer>>();
|
||||
// Available after start()
|
||||
spaceServer!: SpaceServer;
|
||||
baseKvPrimitives: KvPrimitives;
|
||||
configs: Map<string, SpaceServerConfig>;
|
||||
|
||||
constructor(options: ServerOptions) {
|
||||
constructor(private options: ServerOptions) {
|
||||
this.app = new Hono();
|
||||
this.clientAssetBundle = options.clientAssetBundle;
|
||||
this.plugAssetBundle = options.plugAssetBundle;
|
||||
|
@ -59,60 +66,6 @@ export class HttpServer {
|
|||
this.keyFile = options.keyFile;
|
||||
this.certFile = options.certFile;
|
||||
this.baseKvPrimitives = options.baseKvPrimitives;
|
||||
this.configs = options.configs;
|
||||
}
|
||||
|
||||
async bootSpaceServer(config: SpaceServerConfig): Promise<SpaceServer> {
|
||||
const spaceServer = new SpaceServer(
|
||||
config,
|
||||
this.plugAssetBundle,
|
||||
new PrefixedKvPrimitives(this.baseKvPrimitives, [
|
||||
config.namespace,
|
||||
]),
|
||||
);
|
||||
await spaceServer.init();
|
||||
|
||||
return spaceServer;
|
||||
}
|
||||
|
||||
determineConfig(req: HonoRequest): [string, SpaceServerConfig] {
|
||||
const url = new URL(req.url);
|
||||
let hostname = url.host; // hostname:port
|
||||
|
||||
// First try a full match
|
||||
let config = this.configs.get(hostname);
|
||||
if (config) {
|
||||
return [hostname, config];
|
||||
}
|
||||
|
||||
// Then rip off the port and try again
|
||||
hostname = hostname.split(":")[0];
|
||||
config = this.configs.get(hostname);
|
||||
if (config) {
|
||||
return [hostname, config];
|
||||
}
|
||||
|
||||
// If all else fails, try the wildcard
|
||||
config = this.configs.get("*");
|
||||
|
||||
if (config) {
|
||||
return ["*", config];
|
||||
}
|
||||
|
||||
throw new Error(`No space server config found for hostname ${hostname}`);
|
||||
}
|
||||
|
||||
ensureSpaceServer(req: HonoRequest): Promise<SpaceServer> {
|
||||
const [matchedHostname, config] = this.determineConfig(req);
|
||||
const spaceServer = this.spaceServers.get(matchedHostname);
|
||||
if (spaceServer) {
|
||||
return spaceServer;
|
||||
}
|
||||
// And then boot the thing, async
|
||||
const spaceServerPromise = this.bootSpaceServer(config);
|
||||
// But immediately write the promise to the map so that we don't boot it twice
|
||||
this.spaceServers.set(matchedHostname, spaceServerPromise);
|
||||
return spaceServerPromise;
|
||||
}
|
||||
|
||||
// Replaces some template variables in index.html in a rather ad-hoc manner, but YOLO
|
||||
|
@ -179,19 +132,26 @@ export class HttpServer {
|
|||
);
|
||||
}
|
||||
|
||||
start() {
|
||||
async start() {
|
||||
// Serve static files (javascript, css, html)
|
||||
this.serveStatic();
|
||||
this.serveCustomEndpoints();
|
||||
this.addAuth();
|
||||
this.addFsRoutes();
|
||||
|
||||
// Boot space server
|
||||
this.spaceServer = new SpaceServer(
|
||||
this.options,
|
||||
this.plugAssetBundle,
|
||||
new PrefixedKvPrimitives(this.baseKvPrimitives, ["*"]), // * for backwards compatibility reasons
|
||||
);
|
||||
await this.spaceServer.init();
|
||||
|
||||
// Fallback, serve the UI index.html
|
||||
this.app.use("*", async (c) => {
|
||||
const spaceServer = await this.ensureSpaceServer(c.req);
|
||||
this.app.use("*", (c) => {
|
||||
const url = new URL(c.req.url);
|
||||
const pageName = decodeURI(url.pathname.slice(1));
|
||||
return this.renderHtmlPage(spaceServer, pageName, c);
|
||||
return this.renderHtmlPage(this.spaceServer, pageName, c);
|
||||
});
|
||||
|
||||
this.abortController = new AbortController();
|
||||
|
@ -221,16 +181,16 @@ export class HttpServer {
|
|||
// Custom endpoints can be defined in the server
|
||||
serveCustomEndpoints() {
|
||||
this.app.use("/_/*", async (ctx) => {
|
||||
const spaceServer = await this.ensureSpaceServer(ctx.req);
|
||||
const req = ctx.req;
|
||||
const url = new URL(req.url);
|
||||
if (!spaceServer.serverSystem) {
|
||||
if (!this.spaceServer.serverSystem) {
|
||||
return ctx.text("No server system available", 500);
|
||||
}
|
||||
|
||||
try {
|
||||
const path = url.pathname.slice(2); // Remove the /_
|
||||
const responses: EndpointResponse[] = await spaceServer.serverSystem
|
||||
const responses: EndpointResponse[] = await this.spaceServer
|
||||
.serverSystem
|
||||
.eventHook.dispatchEvent(`http:request:${path}`, {
|
||||
fullPath: url.pathname,
|
||||
path,
|
||||
|
@ -278,17 +238,17 @@ export class HttpServer {
|
|||
}
|
||||
|
||||
serveStatic() {
|
||||
this.app.use("*", async (c, next) => {
|
||||
this.app.use("*", (c, next): Promise<void | Response> => {
|
||||
const req = c.req;
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
const url = new URL(req.url);
|
||||
// console.log("URL", url);
|
||||
if (
|
||||
url.pathname === "/"
|
||||
) {
|
||||
// Serve the UI (index.html)
|
||||
const indexPage = parsePageRef(spaceServer.config?.indexPage!).page;
|
||||
return this.renderHtmlPage(spaceServer, indexPage, c);
|
||||
const indexPage =
|
||||
parsePageRef(this.spaceServer.config?.indexPage!).page;
|
||||
return this.renderHtmlPage(this.spaceServer, indexPage, c);
|
||||
}
|
||||
try {
|
||||
const assetName = url.pathname.slice(1);
|
||||
|
@ -301,7 +261,7 @@ export class HttpServer {
|
|||
utcDateString(this.clientAssetBundle.getMtime(assetName)) &&
|
||||
assetName !== "service_worker.js"
|
||||
) {
|
||||
return c.body(null, 304);
|
||||
return Promise.resolve(c.body(null, 304));
|
||||
}
|
||||
c.status(200);
|
||||
c.header("Content-type", this.clientAssetBundle.getMimeType(assetName));
|
||||
|
@ -327,18 +287,19 @@ export class HttpServer {
|
|||
"{{CONFIG_HASH}}",
|
||||
base64Encode(
|
||||
JSON.stringify([
|
||||
spaceServer.syncOnly,
|
||||
spaceServer.readOnly,
|
||||
spaceServer.enableSpaceScript,
|
||||
this.spaceServer.syncOnly,
|
||||
this.spaceServer.readOnly,
|
||||
this.spaceServer.enableSpaceScript,
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
return c.body(data);
|
||||
return Promise.resolve(c.body(data));
|
||||
} // else e.g. HEAD, OPTIONS, don't send body
|
||||
} catch {
|
||||
return next();
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -381,16 +342,14 @@ export class HttpServer {
|
|||
const url = new URL(c.req.url);
|
||||
const { username, password } = req.valid("form");
|
||||
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
|
||||
const {
|
||||
user: expectedUser,
|
||||
pass: expectedPassword,
|
||||
} = spaceServer.auth!;
|
||||
} = this.spaceServer.auth!;
|
||||
|
||||
if (username === expectedUser && password === expectedPassword) {
|
||||
// Generate a JWT and set it as a cookie
|
||||
const jwt = await spaceServer.jwtIssuer.createJWT(
|
||||
const jwt = await this.spaceServer.jwtIssuer.createJWT(
|
||||
{ username },
|
||||
authenticationExpirySeconds,
|
||||
);
|
||||
|
@ -417,8 +376,7 @@ export class HttpServer {
|
|||
// Check auth
|
||||
this.app.use("*", async (c, next) => {
|
||||
const req = c.req;
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
if (!spaceServer.auth && !spaceServer.authToken) {
|
||||
if (!this.spaceServer.auth && !this.spaceServer.authToken) {
|
||||
// Auth disabled in this config, skip
|
||||
return next();
|
||||
}
|
||||
|
@ -435,12 +393,12 @@ export class HttpServer {
|
|||
if (!excludedPaths.includes(url.pathname)) {
|
||||
const authCookie = getCookie(c, authCookieName(host));
|
||||
|
||||
if (!authCookie && spaceServer.authToken) {
|
||||
if (!authCookie && this.spaceServer.authToken) {
|
||||
// Attempt Bearer Authorization based authentication
|
||||
const authHeader = req.header("Authorization");
|
||||
if (authHeader && authHeader.startsWith("Bearer ")) {
|
||||
const authToken = authHeader.slice("Bearer ".length);
|
||||
if (authToken === spaceServer.authToken) {
|
||||
if (authToken === this.spaceServer.authToken) {
|
||||
// All good, let's proceed
|
||||
return next();
|
||||
} else {
|
||||
|
@ -455,10 +413,11 @@ export class HttpServer {
|
|||
console.log("Unauthorized access, redirecting to auth page");
|
||||
return redirectToAuth();
|
||||
}
|
||||
const { user: expectedUser } = spaceServer.auth!;
|
||||
const { user: expectedUser } = this.spaceServer.auth!;
|
||||
|
||||
try {
|
||||
const verifiedJwt = await spaceServer.jwtIssuer.verifyAndDecodeJWT(
|
||||
const verifiedJwt = await this.spaceServer.jwtIssuer
|
||||
.verifyAndDecodeJWT(
|
||||
authCookie,
|
||||
);
|
||||
if (verifiedJwt.username !== expectedUser) {
|
||||
|
@ -490,12 +449,11 @@ export class HttpServer {
|
|||
// File list
|
||||
this.app.get("/index.json", async (c) => {
|
||||
const req = c.req;
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
if (req.header("X-Sync-Mode")) {
|
||||
// Only handle direct requests for a JSON representation of the file list
|
||||
const files = await spaceServer.spacePrimitives.fetchFileList();
|
||||
const files = await this.spaceServer.spacePrimitives.fetchFileList();
|
||||
return c.json(files, 200, {
|
||||
"X-Space-Path": spaceServer.pagesPath,
|
||||
"X-Space-Path": this.spaceServer.pagesPath,
|
||||
});
|
||||
} else {
|
||||
// Otherwise, redirect to the UI
|
||||
|
@ -514,11 +472,10 @@ export class HttpServer {
|
|||
// RPC shell
|
||||
this.app.post("/.rpc/shell", async (c) => {
|
||||
const req = c.req;
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
const body = await req.json();
|
||||
try {
|
||||
const shellCommand: ShellRequest = body;
|
||||
const shellResponse = await spaceServer.shellBackend.handle(
|
||||
const shellResponse = await this.spaceServer.shellBackend.handle(
|
||||
shellCommand,
|
||||
);
|
||||
return c.json(shellResponse);
|
||||
|
@ -533,15 +490,14 @@ export class HttpServer {
|
|||
const req = c.req;
|
||||
const syscall = req.param("syscall")!;
|
||||
const plugName = req.param("plugName")!;
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
const body = await req.json();
|
||||
try {
|
||||
if (spaceServer.syncOnly) {
|
||||
if (this.spaceServer.syncOnly) {
|
||||
return c.text("Sync only mode, no syscalls allowed", 400);
|
||||
}
|
||||
const args: string[] = body;
|
||||
try {
|
||||
const result = await spaceServer.system!.syscall(
|
||||
const result = await this.spaceServer.system!.syscall(
|
||||
{ plug: plugName === "_" ? undefined : plugName },
|
||||
syscall,
|
||||
args,
|
||||
|
@ -566,7 +522,6 @@ export class HttpServer {
|
|||
this.app.get(filePathRegex, async (c) => {
|
||||
const req = c.req;
|
||||
const name = req.param("path")!;
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
console.log("Requested file", name);
|
||||
|
||||
if (
|
||||
|
@ -627,12 +582,12 @@ export class HttpServer {
|
|||
try {
|
||||
if (req.header("X-Get-Meta")) {
|
||||
// Getting meta via GET request
|
||||
const fileData = await spaceServer.spacePrimitives.getFileMeta(
|
||||
const fileData = await this.spaceServer.spacePrimitives.getFileMeta(
|
||||
name,
|
||||
);
|
||||
return c.text("", 200, this.fileMetaToHeaders(fileData));
|
||||
}
|
||||
const fileData = await spaceServer.spacePrimitives.readFile(name);
|
||||
const fileData = await this.spaceServer.spacePrimitives.readFile(name);
|
||||
const lastModifiedHeader = new Date(fileData.meta.lastModified)
|
||||
.toUTCString();
|
||||
if (
|
||||
|
@ -652,8 +607,7 @@ export class HttpServer {
|
|||
async (c) => {
|
||||
const req = c.req;
|
||||
const name = req.param("path")!;
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
if (spaceServer.readOnly) {
|
||||
if (this.spaceServer.readOnly) {
|
||||
return c.text("Read only mode, no writes allowed", 405);
|
||||
}
|
||||
console.log("Writing file", name);
|
||||
|
@ -670,7 +624,7 @@ export class HttpServer {
|
|||
const body = await req.arrayBuffer();
|
||||
|
||||
try {
|
||||
const meta = await spaceServer.spacePrimitives.writeFile(
|
||||
const meta = await this.spaceServer.spacePrimitives.writeFile(
|
||||
name,
|
||||
new Uint8Array(body),
|
||||
);
|
||||
|
@ -683,8 +637,7 @@ export class HttpServer {
|
|||
).delete(async (c) => {
|
||||
const req = c.req;
|
||||
const name = req.param("path")!;
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
if (spaceServer.readOnly) {
|
||||
if (this.spaceServer.readOnly) {
|
||||
return c.text("Read only mode, no writes allowed", 405);
|
||||
}
|
||||
console.log("Deleting file", name);
|
||||
|
@ -693,7 +646,7 @@ export class HttpServer {
|
|||
return c.text("Forbidden", 403);
|
||||
}
|
||||
try {
|
||||
await spaceServer.spacePrimitives.deleteFile(name);
|
||||
await this.spaceServer.spacePrimitives.deleteFile(name);
|
||||
return c.text("OK");
|
||||
} catch (e: any) {
|
||||
console.error("Error deleting attachment", e);
|
||||
|
@ -707,8 +660,7 @@ export class HttpServer {
|
|||
proxyPathRegex,
|
||||
async (c, next) => {
|
||||
const req = c.req;
|
||||
const spaceServer = await this.ensureSpaceServer(req);
|
||||
if (spaceServer.readOnly) {
|
||||
if (this.spaceServer.readOnly) {
|
||||
return c.text("Read only mode, no federation proxy allowed", 405);
|
||||
}
|
||||
let url = req.param("uri")!.slice(1);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { SpaceServerConfig } from "./instance.ts";
|
||||
import type { ShellRequest, ShellResponse } from "../type/rpc.ts";
|
||||
import type { ServerOptions } from "./http_server.ts";
|
||||
|
||||
/**
|
||||
* Configuration via environment variables:
|
||||
|
@ -7,12 +7,12 @@ import type { ShellRequest, ShellResponse } from "../type/rpc.ts";
|
|||
*/
|
||||
|
||||
export function determineShellBackend(
|
||||
spaceServerConfig: SpaceServerConfig,
|
||||
serverOptions: ServerOptions,
|
||||
): ShellBackend {
|
||||
const backendConfig = Deno.env.get("SB_SHELL_BACKEND") || "local";
|
||||
switch (backendConfig) {
|
||||
case "local":
|
||||
return new LocalShell(spaceServerConfig.pagesPath);
|
||||
return new LocalShell(serverOptions.pagesPath);
|
||||
default:
|
||||
console.info(
|
||||
"Running in shellless mode, meaning shell commands are disabled",
|
||||
|
|
|
@ -22,20 +22,7 @@ import { determineShellBackend, NotSupportedShell } from "./shell_backend.ts";
|
|||
import type { ShellBackend } from "./shell_backend.ts";
|
||||
import { determineStorageBackend } from "./storage_backend.ts";
|
||||
import type { Config } from "../type/config.ts";
|
||||
|
||||
export type SpaceServerConfig = {
|
||||
hostname: string;
|
||||
namespace: string;
|
||||
// Enable username/password auth
|
||||
auth?: { user: string; pass: string };
|
||||
// Additional API auth token
|
||||
authToken?: string;
|
||||
pagesPath: string;
|
||||
shellBackend: string;
|
||||
syncOnly: boolean;
|
||||
readOnly: boolean;
|
||||
enableSpaceScript: boolean;
|
||||
};
|
||||
import type { ServerOptions } from "./http_server.ts";
|
||||
|
||||
// Equivalent of Client on the server
|
||||
export class SpaceServer implements ConfigContainer {
|
||||
|
@ -58,24 +45,24 @@ export class SpaceServer implements ConfigContainer {
|
|||
enableSpaceScript: boolean;
|
||||
|
||||
constructor(
|
||||
config: SpaceServerConfig,
|
||||
options: ServerOptions,
|
||||
private plugAssetBundle: AssetBundle,
|
||||
private kvPrimitives: KvPrimitives,
|
||||
) {
|
||||
this.pagesPath = config.pagesPath;
|
||||
this.hostname = config.hostname;
|
||||
this.auth = config.auth;
|
||||
this.authToken = config.authToken;
|
||||
this.syncOnly = config.syncOnly;
|
||||
this.readOnly = config.readOnly;
|
||||
this.pagesPath = options.pagesPath;
|
||||
this.hostname = options.hostname;
|
||||
this.auth = options.auth;
|
||||
this.authToken = options.authToken;
|
||||
this.syncOnly = options.syncOnly;
|
||||
this.readOnly = options.readOnly;
|
||||
this.config = defaultConfig;
|
||||
this.enableSpaceScript = config.enableSpaceScript;
|
||||
this.enableSpaceScript = options.enableSpaceScript;
|
||||
|
||||
this.jwtIssuer = new JWTIssuer(kvPrimitives);
|
||||
|
||||
this.shellBackend = config.readOnly
|
||||
this.shellBackend = options.readOnly
|
||||
? new NotSupportedShell() // No shell for read only mode
|
||||
: determineShellBackend(config);
|
||||
: determineShellBackend(options);
|
||||
}
|
||||
|
||||
async init() {
|
Loading…
Reference in New Issue