Files
plainleaf/server/shell_endpoints.ts
T
+1 766fb96ff2 Adding support for hosting with URL prefix (#1357)
* Adding support for hosting with URL prefix to make reverse-proxy setup on sub-path possible

* Adjusting recent changes to auth.html (changing to relative URLs)  after rebasing from upstream

* Adjusting service_worker and application manifest to support URL-prefix

* Add notes on Space Lua Pattern Matching (#1366)

* Remove common/space_lua/stdlib/math.md (#1371)

* Escape pipe symbol in string.gsub (#1370)

* feat: configure vim in CONFIG (#1369)

* Fix week calculation (#1368)

* Add more tests for week calculation

* Fix week calculation

* Add more tests

* Implement Lua built-in function os.difftime (#1367)

* Implement Lua built-in function os.difftime

* Test os.difftime

---------

Co-authored-by: Zef Hemel <zef@zef.me>

* Fix index page naming during server initialization (#1365)

* Allow non-zero shell exit codes (#1377)

* Generate version from git describe at build time (#1374)

* Generate version from git describe at build time

* Add 2.0-beta prefix

* Use Deno.Command instead of Deno.run

* Add missing case to math.random and check arguments (#1372)

* Fix paragraphs for the tag page (#1376)

* Render a predefined string for empty markdown table instead of pipe characters (#1387)

* Fix string.find (#1390)

* Fetch tags and history for proper version string generation (#1388)

* Fix support for disabling shortcuts (#1393)

* Add GitHub Actions Cache to reduce build times (#1394)

* Order strings in queries using collation algorithm (#1391)

Configurable with queryCollation, default value maintains old behaviour.
Added tests, schema, example settings, and documentation to query page.

Close: #614
Close: #1316

* Utility function to handle empty query results and templates (#1386)

* Add global Lua function 'some'

Moved documentation for 'each' function to non-standard section.

* Revert "Add optional argument to template.each for empty collection output (#1359)"

This reverts commit b73dadec67.

* Reimplement #1359 using 'some' function

* Update Lua to event.listEvents to match plugos syscall (#1397)

The documentation, example, and other function names use
`event.listEvents`, but `event.list` is being registered with the
client.

* Update widget header to not overflow container (#1410)

Also add a 10px margin below the header to maintain consistent spacing
around the widget contents.

* fix: case insensitive admonitions by default (#1409)

* fix: closing bracket is missing (#1408)

* Add optional priority attribute to PageCreatingContent (#1392)

* Add optional priority attribute to PageCreatingContent

* Minor simplification

* Fix comments

* Revert changes

* Add pageEventHandlers

* Use UTC year, month, and date for strings lacking time components (#1403)

* feat: allow CONFIG.md in any directory (#1398)

* fix: bundle external dependencies (#1379)

* Deno bump

* Copy-edit splash screen text (#1413)

* Fixes #1401 by implementing Lua's select

* Fixes offline capability of PWA (I hope).

Now storing config in localStorage.

* fix lint

* Reduce blast radius of Lua errors

* Fixes #1411: adds 'priority' setting for space style

* Refactor: Merging common/ into web/

* Fixes #1400

* Improves #1312 by rendering Lua expressions (but not widgets)

* Fix minor typos, add configuration to the docs

---------

Co-authored-by: wbhouston <wmbhouston@gmail.com>
Co-authored-by: Lennart Ochel <lennart.ochel@outlook.com>
Co-authored-by: Matthias Benaets <89214559+MatthiasBenaets@users.noreply.github.com>
Co-authored-by: Zef Hemel <zef@zef.me>
Co-authored-by: Marek S. Łukasiewicz <marek@lukasiewicz.tech>
Co-authored-by: Ivan Alejandro <ivanalejandro0@gmail.com>
Co-authored-by: Albert-Jan de Vries <aj+github@albert-jandevries.nl>
Co-authored-by: Cole Stowell <121599022+costowell@users.noreply.github.com>
Co-authored-by: Jim Tittsler <jimt@onjapan.net>
2025-06-04 15:15:14 +02:00

128 lines
3.5 KiB
TypeScript

import type { Context } from "hono";
import type { ShellRequest } from "@silverbulletmd/silverbullet/type/rpc";
import type { ShellBackend } from "./shell_backend.ts";
import { StreamingShell } from "./shell_backend.ts";
import { removeUrlPrefix } from "$lib/url_prefix.ts";
/**
* Handles the /.shell endpoint for non-streaming shell commands
*/
export async function handleShellEndpoint(
c: Context,
shellBackend: ShellBackend,
readOnly: boolean,
): Promise<Response> {
const req = c.req;
const body = await req.json();
try {
if (readOnly) {
return c.text("Read only mode, no shell commands allowed", 405);
}
const shellCommand: ShellRequest = body;
// Note: in read-only this is set to NoShellSupport, so don't worry
const shellResponse = await shellBackend.handle(shellCommand);
return c.json(shellResponse);
} catch (e: any) {
console.error("Shell error", e);
return c.text(e.message, 500);
}
}
/**
* Handles the /.shell/stream endpoint for streaming shell commands
*/
export function handleShellStreamEndpoint(
c: Context,
cwd: string,
readOnly: boolean,
hostUrlPrefix?: string,
): Response {
const req = c.req;
const url = new URL(removeUrlPrefix(req.url, hostUrlPrefix));
// Check if read-only mode is enabled
if (readOnly) {
return c.text("Read only mode, no shell commands allowed", 405);
}
// Get command and arguments from query parameters
const cmd = url.searchParams.get("cmd");
if (!cmd) {
return c.text("Missing cmd parameter", 400);
}
let args: string[] = [];
const argsParam = url.searchParams.get("args");
if (argsParam) {
try {
args = JSON.parse(argsParam);
if (!Array.isArray(args)) {
return c.text("args must be a JSON array", 400);
}
} catch (_e) {
return c.text("Invalid args parameter: must be a JSON array", 400);
}
}
// Create a streaming shell
const shell = new StreamingShell(cwd);
// Upgrade the connection to a WebSocket
try {
const { socket, response } = Deno.upgradeWebSocket(req.raw);
// Set up WebSocket event handlers
socket.onopen = () => {
// Start the shell process when the WebSocket is opened
shell.start(cmd, args);
// Forward shell output to the WebSocket
shell.onOutput((type, data) => {
socket.send(JSON.stringify({
type,
data,
}));
});
};
socket.onmessage = async (event) => {
try {
const message = JSON.parse(event.data);
if (message.type === "stdin") {
await shell.writeToStdin(message.data);
} else if (message.type === "signal") {
const success = shell.sendSignal(message.signal);
if (!success) {
socket.send(JSON.stringify({
type: "error",
data: `Failed to send signal: ${message.signal}`,
}));
}
}
} catch (e) {
console.error("Error handling WebSocket message:", e);
socket.send(JSON.stringify({
type: "error",
data: `Error: ${e}`,
}));
}
};
socket.onclose = async () => {
await shell.close();
};
socket.onerror = (error) => {
console.error("WebSocket error:", error);
shell.close().catch((e) => {
console.error("Error closing shell:", e);
});
};
return response;
} catch (e) {
console.error("WebSocket upgrade failed:", e);
return c.text("WebSocket upgrade failed", 400);
}
}