1
0

Timeouts for sync config

This commit is contained in:
Zef Hemel 2023-01-14 18:51:00 +01:00
parent d371f2f68a
commit e5276319e0
16 changed files with 225 additions and 70 deletions

32
common/async_util.ts Normal file
View File

@ -0,0 +1,32 @@
export function throttle(func: () => void, limit: number) {
let timer: any = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
func();
timer = null;
}, limit);
}
};
}
// race for promises returns first promise that resolves
export function race<T>(promises: Promise<T>[]): Promise<T> {
return new Promise((resolve, reject) => {
for (const p of promises) {
p.then(resolve, reject);
}
});
}
export function timeout(ms: number): Promise<never> {
return new Promise((_resolve, reject) =>
setTimeout(() => {
reject(new Error("timeout"));
}, ms)
);
}
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@ -1,3 +1,4 @@
import { LogEntry } from "../../plugos/sandbox.ts";
import type { FileMeta } from "../types.ts"; import type { FileMeta } from "../types.ts";
import { SpacePrimitives } from "./space_primitives.ts"; import { SpacePrimitives } from "./space_primitives.ts";
@ -7,12 +8,23 @@ type SyncHash = number;
// and the second item the lastModified value of the secondary space // and the second item the lastModified value of the secondary space
export type SyncStatusItem = [SyncHash, SyncHash]; export type SyncStatusItem = [SyncHash, SyncHash];
export interface Logger {
log(level: string, ...messageBits: any[]): void;
}
class ConsoleLogger implements Logger {
log(_level: string, ...messageBits: any[]) {
console.log(...messageBits);
}
}
// Implementation of this algorithm https://unterwaditzer.net/2016/sync-algorithm.html // Implementation of this algorithm https://unterwaditzer.net/2016/sync-algorithm.html
export class SpaceSync { export class SpaceSync {
constructor( constructor(
private primary: SpacePrimitives, private primary: SpacePrimitives,
private secondary: SpacePrimitives, private secondary: SpacePrimitives,
readonly snapshot: Map<string, SyncStatusItem>, readonly snapshot: Map<string, SyncStatusItem>,
readonly logger: Logger = new ConsoleLogger(),
) {} ) {}
async syncFiles( async syncFiles(
@ -21,15 +33,16 @@ export class SpaceSync {
snapshot: Map<string, SyncStatusItem>, snapshot: Map<string, SyncStatusItem>,
primarySpace: SpacePrimitives, primarySpace: SpacePrimitives,
secondarySpace: SpacePrimitives, secondarySpace: SpacePrimitives,
logger: Logger,
) => Promise<void>, ) => Promise<void>,
): Promise<number> { ): Promise<number> {
let operations = 0; let operations = 0;
console.log("Fetching snapshot from primary"); this.logger.log("info", "Fetching snapshot from primary");
const primaryAllPages = this.syncCandidates( const primaryAllPages = this.syncCandidates(
await this.primary.fetchFileList(), await this.primary.fetchFileList(),
); );
console.log("Fetching snapshot from secondary"); this.logger.log("info", "Fetching snapshot from secondary");
try { try {
const secondaryAllPages = this.syncCandidates( const secondaryAllPages = this.syncCandidates(
await this.secondary.fetchFileList(), await this.secondary.fetchFileList(),
@ -48,14 +61,15 @@ export class SpaceSync {
...secondaryFileMap.keys(), ...secondaryFileMap.keys(),
]); ]);
console.log("Iterating over all files"); this.logger.log("info", "Iterating over all files");
for (const name of allFilesToProcess) { for (const name of allFilesToProcess) {
if ( if (
primaryFileMap.has(name) && !secondaryFileMap.has(name) && primaryFileMap.has(name) && !secondaryFileMap.has(name) &&
!this.snapshot.has(name) !this.snapshot.has(name)
) { ) {
// New file, created on primary, copy from primary to secondary // New file, created on primary, copy from primary to secondary
console.log( this.logger.log(
"info",
"New file created on primary, copying to secondary", "New file created on primary, copying to secondary",
name, name,
); );
@ -75,7 +89,8 @@ export class SpaceSync {
!this.snapshot.has(name) !this.snapshot.has(name)
) { ) {
// New file, created on secondary, copy from secondary to primary // New file, created on secondary, copy from secondary to primary
console.log( this.logger.log(
"info",
"New file created on secondary, copying from secondary to primary", "New file created on secondary, copying from secondary to primary",
name, name,
); );
@ -95,7 +110,11 @@ export class SpaceSync {
!secondaryFileMap.has(name) !secondaryFileMap.has(name)
) { ) {
// File deleted on B // File deleted on B
console.log("File deleted on secondary, deleting from primary", name); this.logger.log(
"info",
"File deleted on secondary, deleting from primary",
name,
);
await this.primary.deleteFile(name); await this.primary.deleteFile(name);
this.snapshot.delete(name); this.snapshot.delete(name);
operations++; operations++;
@ -104,7 +123,11 @@ export class SpaceSync {
!primaryFileMap.has(name) !primaryFileMap.has(name)
) { ) {
// File deleted on A // File deleted on A
console.log("File deleted on primary, deleting from secondary", name); this.logger.log(
"info",
"File deleted on primary, deleting from secondary",
name,
);
await this.secondary.deleteFile(name); await this.secondary.deleteFile(name);
this.snapshot.delete(name); this.snapshot.delete(name);
operations++; operations++;
@ -113,7 +136,11 @@ export class SpaceSync {
!secondaryFileMap.has(name) !secondaryFileMap.has(name)
) { ) {
// File deleted on both sides, :shrug: // File deleted on both sides, :shrug:
console.log("File deleted on both ends, deleting from status", name); this.logger.log(
"info",
"File deleted on both ends, deleting from status",
name,
);
this.snapshot.delete(name); this.snapshot.delete(name);
operations++; operations++;
} else if ( } else if (
@ -123,7 +150,11 @@ export class SpaceSync {
secondaryFileMap.get(name) === this.snapshot.get(name)![1] secondaryFileMap.get(name) === this.snapshot.get(name)![1]
) { ) {
// File has changed on primary, but not secondary: copy from primary to secondary // File has changed on primary, but not secondary: copy from primary to secondary
console.log("File changed on primary, copying to secondary", name); this.logger.log(
"info",
"File changed on primary, copying to secondary",
name,
);
const { data } = await this.primary.readFile(name, "arraybuffer"); const { data } = await this.primary.readFile(name, "arraybuffer");
const writtenMeta = await this.secondary.writeFile( const writtenMeta = await this.secondary.writeFile(
name, name,
@ -165,13 +196,18 @@ export class SpaceSync {
primaryFileMap.get(name) !== this.snapshot.get(name)![0] primaryFileMap.get(name) !== this.snapshot.get(name)![0]
) )
) { ) {
console.log("File changed on both ends, conflict!", name); this.logger.log(
"info",
"File changed on both ends, potential conflict",
name,
);
if (conflictResolver) { if (conflictResolver) {
await conflictResolver( await conflictResolver(
name, name,
this.snapshot, this.snapshot,
this.primary, this.primary,
this.secondary, this.secondary,
this.logger,
); );
} else { } else {
throw Error( throw Error(
@ -184,9 +220,10 @@ export class SpaceSync {
} }
} }
} catch (e: any) { } catch (e: any) {
console.error("Boom", e.message); this.logger.log("error", "Sync error:", e.message);
throw e; throw e;
} }
this.logger.log("info", "Sync complete, operations performed", operations);
return operations; return operations;
} }
@ -197,8 +234,9 @@ export class SpaceSync {
snapshot: Map<string, SyncStatusItem>, snapshot: Map<string, SyncStatusItem>,
primary: SpacePrimitives, primary: SpacePrimitives,
secondary: SpacePrimitives, secondary: SpacePrimitives,
logger: Logger,
): Promise<void> { ): Promise<void> {
console.log("Hit a conflict for", name); logger.log("info", "Starting conflict resolution for", name);
const filePieces = name.split("."); const filePieces = name.split(".");
const fileNameBase = filePieces.slice(0, -1).join("."); const fileNameBase = filePieces.slice(0, -1).join(".");
const fileNameExt = filePieces[filePieces.length - 1]; const fileNameExt = filePieces[filePieces.length - 1];
@ -221,6 +259,7 @@ export class SpaceSync {
} }
// Byte wise they're still the same, so no confict // Byte wise they're still the same, so no confict
if (byteWiseMatch) { if (byteWiseMatch) {
logger.log("info", "Files are the same, no conflict");
snapshot.set(name, [ snapshot.set(name, [
pageData1.meta.lastModified, pageData1.meta.lastModified,
pageData2.meta.lastModified, pageData2.meta.lastModified,
@ -231,7 +270,8 @@ export class SpaceSync {
const revisionFileName = filePieces.length === 1 const revisionFileName = filePieces.length === 1
? `${name}.conflicted.${pageData2.meta.lastModified}` ? `${name}.conflicted.${pageData2.meta.lastModified}`
: `${fileNameBase}.conflicted.${pageData2.meta.lastModified}.${fileNameExt}`; : `${fileNameBase}.conflicted.${pageData2.meta.lastModified}.${fileNameExt}`;
console.log( logger.log(
"info",
"Going to create conflicting copy", "Going to create conflicting copy",
revisionFileName, revisionFileName,
); );

View File

@ -1,10 +1,14 @@
import { SysCallMapping } from "../../plugos/system.ts"; import { SysCallMapping, System } from "../../plugos/system.ts";
import type { SyncEndpoint } from "../../plug-api/silverbullet-syscall/sync.ts"; import type { SyncEndpoint } from "../../plug-api/silverbullet-syscall/sync.ts";
import { SpaceSync, SyncStatusItem } from "../spaces/sync.ts"; import { SpaceSync, SyncStatusItem } from "../spaces/sync.ts";
import { HttpSpacePrimitives } from "../spaces/http_space_primitives.ts"; import { HttpSpacePrimitives } from "../spaces/http_space_primitives.ts";
import { SpacePrimitives } from "../spaces/space_primitives.ts"; import { SpacePrimitives } from "../spaces/space_primitives.ts";
import { race, timeout } from "../async_util.ts";
export function syncSyscalls(localSpace: SpacePrimitives): SysCallMapping { export function syncSyscalls(
localSpace: SpacePrimitives,
system: System<any>,
): SysCallMapping {
return { return {
"sync.sync": async ( "sync.sync": async (
_ctx, _ctx,
@ -33,6 +37,8 @@ export function syncSyscalls(localSpace: SpacePrimitives): SysCallMapping {
localSpace, localSpace,
syncSpace, syncSpace,
syncStatusMap, syncStatusMap,
// Log to the "sync" plug sandbox
system.loadedPlugs.get("sync")!.sandbox!,
); );
try { try {
@ -58,8 +64,13 @@ export function syncSyscalls(localSpace: SpacePrimitives): SysCallMapping {
endpoint.user, endpoint.user,
endpoint.password, endpoint.password,
); );
// Let's just fetch the file list to see if it works // Let's just fetch the file list to see if it works with a timeout of 5s
await syncSpace.fetchFileList(); try {
await race([syncSpace.fetchFileList(), timeout(5000)]);
} catch (e: any) {
console.error("Sync check failure", e.message);
throw e;
}
}, },
}; };
} }

View File

@ -1,7 +1,6 @@
import { SETTINGS_TEMPLATE } from "./settings_template.ts"; import { SETTINGS_TEMPLATE } from "./settings_template.ts";
import { YAML } from "./deps.ts"; import { YAML } from "./deps.ts";
import { Space } from "./spaces/space.ts"; import { Space } from "./spaces/space.ts";
import { BuiltinSettings } from "../web/types.ts";
export function safeRun(fn: () => Promise<void>) { export function safeRun(fn: () => Promise<void>) {
fn().catch((e) => { fn().catch((e) => {
@ -13,18 +12,6 @@ export function isMacLike() {
return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform); return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
} }
export function throttle(func: () => void, limit: number) {
let timer: any = null;
return function () {
if (!timer) {
timer = setTimeout(() => {
func();
timer = null;
}, limit);
}
};
}
// TODO: This is naive, may be better to use a proper parser // TODO: This is naive, may be better to use a proper parser
const yamlSettingsRegex = /```yaml([^`]+)```/; const yamlSettingsRegex = /```yaml([^`]+)```/;

View File

@ -21,7 +21,8 @@
"desktop:build": "deno task build && deno task bundle && cd desktop && npm run make", "desktop:build": "deno task build && deno task bundle && cd desktop && npm run make",
// Mobile // Mobile
"mobile:deps": "cd mobile && npm install && npx cap sync", "mobile:deps": "cd mobile && npm install && npx cap sync",
"mobile:build": "deno task clean && deno task plugs && deno run -A --unstable --check build_mobile.ts && cd mobile && npx cap copy && npx cap open ios" "mobile:clean-build": "deno task clean && deno task plugs && deno run -A --unstable --check build_mobile.ts && cd mobile && npx cap copy && npx cap open ios",
"mobile:build": "deno run -A --unstable --check build_mobile.ts && cd mobile && npx cap copy && npx cap open ios"
}, },
"compilerOptions": { "compilerOptions": {

View File

@ -30,6 +30,7 @@ import { EventedSpacePrimitives } from "../common/spaces/evented_space_primitive
import { EventHook } from "../plugos/hooks/event.ts"; import { EventHook } from "../plugos/hooks/event.ts";
import { clientStoreSyscalls } from "./syscalls/clientStore.ts"; import { clientStoreSyscalls } from "./syscalls/clientStore.ts";
import { sandboxFetchSyscalls } from "../plugos/syscalls/fetch.ts"; import { sandboxFetchSyscalls } from "../plugos/syscalls/fetch.ts";
import { syncSyscalls } from "../common/syscalls/sync.ts";
safeRun(async () => { safeRun(async () => {
// Instantiate a PlugOS system for the client // Instantiate a PlugOS system for the client
@ -86,6 +87,7 @@ safeRun(async () => {
storeSyscalls(db, "store"), storeSyscalls(db, "store"),
indexSyscalls, indexSyscalls,
clientStoreSyscalls(db), clientStoreSyscalls(db),
syncSyscalls(spacePrimitives, system),
fullTextSearchSyscalls(db, "fts"), fullTextSearchSyscalls(db, "fts"),
sandboxFetchSyscalls(), sandboxFetchSyscalls(),
); );

View File

@ -350,7 +350,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = Z92J6WM6X8; DEVELOPMENT_TEAM = Z92J6WM6X8;
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SilverBullet; INFOPLIST_KEY_CFBundleDisplayName = SilverBullet;
@ -376,7 +376,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = Z92J6WM6X8; DEVELOPMENT_TEAM = Z92J6WM6X8;
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SilverBullet; INFOPLIST_KEY_CFBundleDisplayName = SilverBullet;

View File

@ -22,3 +22,8 @@ export function listCommands(): Promise<{ [key: string]: CommandDef }> {
export function reloadPlugs() { export function reloadPlugs() {
syscall("system.reloadPlugs"); syscall("system.reloadPlugs");
} }
// 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");
}

View File

@ -126,15 +126,7 @@ export class Sandbox {
break; break;
} }
case "log": { case "log": {
this.logBuffer.push({ this.log(data.level!, data.message!);
level: data.level!,
message: data.message!,
date: Date.now(),
});
if (this.logBuffer.length > this.maxLogBufferSize) {
this.logBuffer.shift();
}
console.log(`[Sandbox ${data.level}]`, data.message);
break; break;
} }
default: default:
@ -142,6 +134,19 @@ export class Sandbox {
} }
} }
log(level: string, ...messageBits: any[]) {
const message = messageBits.map((a) => "" + a).join(" ");
this.logBuffer.push({
message,
level: level as LogLevel,
date: Date.now(),
});
if (this.logBuffer.length > this.maxLogBufferSize) {
this.logBuffer.shift();
}
console.log(`[Sandbox ${level}]`, message);
}
invoke(name: string, args: any[]): Promise<any> { invoke(name: string, args: any[]): Promise<any> {
this.reqId++; this.reqId++;
this.worker.postMessage({ this.worker.postMessage({

View File

@ -385,6 +385,8 @@ functions:
name: "UI: Hide BHS" name: "UI: Hide BHS"
key: "Ctrl-Alt-b" key: "Ctrl-Alt-b"
mac: "Cmd-Alt-b" mac: "Cmd-Alt-b"
events:
- log:hide
# Link unfurl infrastructure # Link unfurl infrastructure
unfurlLink: unfurlLink:

View File

@ -3,6 +3,7 @@ import {
editor, editor,
markdown, markdown,
sandbox as serverSandbox, sandbox as serverSandbox,
system,
} from "$sb/silverbullet-syscall/mod.ts"; } from "$sb/silverbullet-syscall/mod.ts";
export async function parsePageCommand() { export async function parsePageCommand() {
@ -17,29 +18,38 @@ export async function parsePageCommand() {
} }
export async function showLogsCommand() { export async function showLogsCommand() {
const clientLogs = await sandbox.getLogs(); // Running in client/server mode?
const serverLogs = await serverSandbox.getServerLogs(); const clientServer = !!(await system.getEnv());
await editor.showPanel( if (clientServer) {
"bhs", const clientLogs = await sandbox.getLogs();
1, const serverLogs = await serverSandbox.getServerLogs();
` await editor.showPanel(
"bhs",
1,
`
<style> <style>
#reload {
width: 75%;
}
#close {
width: 20%;
}
#client-log-header { #client-log-header {
position: absolute; position: absolute;
left: 0; left: 0;
top: 5px; top: 35px;
} }
#server-log-header { #server-log-header {
position: absolute; position: absolute;
right: 0; right: 0;
top: 5px; top: 35px;
width: 50%; width: 50%;
} }
#client-log { #client-log {
position: absolute; position: absolute;
left: 0; left: 0;
top: 30px; top: 60px;
bottom: 0; bottom: 0;
width: 50%; width: 50%;
overflow: scroll; overflow: scroll;
@ -47,41 +57,93 @@ export async function showLogsCommand() {
#server-log { #server-log {
position: absolute; position: absolute;
right: 0; right: 0;
top: 30px; top: 60px;
bottom: 0; bottom: 0;
width: 50%; width: 50%;
overflow: scroll; overflow: scroll;
} }
</style> </style>
<button onclick="self.reloadLogs()" id="reload">Reload</button>
<button onclick="self.close()" id="close">Close</button>
<div id="client-log-header">Client logs (max 100)</div> <div id="client-log-header">Client logs (max 100)</div>
<div id="client-log"> <div id="client-log">
<pre>${ <pre>${
clientLogs clientLogs
.map((le) => `[${le.level}] ${le.message}`) .map((le) => `[${le.level}] ${le.message}`)
.join("\n") .join("\n")
}</pre> }</pre>
</div> </div>
<div id="server-log-header">Server logs (max 100)</div> <div id="server-log-header">Server logs (max 100)</div>
<div id="server-log"> <div id="server-log">
<pre>${ <pre>${
serverLogs serverLogs
.map((le) => `[${le.level}] ${le.message}`) .map((le) => `[${le.level}] ${le.message}`)
.join("\n") .join("\n")
}</pre> }</pre>
</div>`, </div>`,
` `
var clientDiv = document.getElementById("client-log"); var clientDiv = document.getElementById("client-log");
clientDiv.scrollTop = clientDiv.scrollHeight; clientDiv.scrollTop = clientDiv.scrollHeight;
var serverDiv = document.getElementById("server-log"); var serverDiv = document.getElementById("server-log");
serverDiv.scrollTop = serverDiv.scrollHeight; serverDiv.scrollTop = serverDiv.scrollHeight;
if(window.reloadInterval) {
clearInterval(window.reloadInterval); self.reloadLogs = () => {
}
window.reloadInterval = setInterval(() => {
sendEvent("log:reload"); sendEvent("log:reload");
}, 1000); };
self.close = () => {
sendEvent("log:hide");
};
`, `,
); );
} else {
const logs = await sandbox.getLogs();
await editor.showPanel(
"bhs",
1,
`
<style>
#reload {
width: 75%;
}
#close {
width: 20%;
}
#log-header {
position: absolute;
left: 0;
top: 35px;
}
#log {
position: absolute;
left: 0;
top: 60px;
bottom: 0;
width: 100%;
overflow: scroll;
}
</style>
<button onclick="self.reloadLogs()" id="reload">Reload</button>
<button onclick="self.close()" id="close">Close</button>
<div id="log-header">Logs (max 100)</div>
<div id="log">
<pre>${
logs
.map((le) => `[${le.level}] ${le.message}`)
.join("\n")
}</pre>
</div>`,
`
var clientDiv = document.getElementById("log");
clientDiv.scrollTop = clientDiv.scrollHeight;
self.reloadLogs = () => {
sendEvent("log:reload");
};
self.close = () => {
sendEvent("log:hide");
};
`,
);
}
} }
export async function hideBhsCommand() { export async function hideBhsCommand() {

View File

@ -54,6 +54,8 @@ export async function syncCommand() {
} }
await editor.flashNotification("Starting sync..."); await editor.flashNotification("Starting sync...");
try { try {
await system.invokeFunction("server", "check", config);
const operations = await system.invokeFunction("server", "performSync"); const operations = await system.invokeFunction("server", "performSync");
await editor.flashNotification( await editor.flashNotification(
`Sync complete. Performed ${operations} operations.`, `Sync complete. Performed ${operations} operations.`,

View File

@ -112,7 +112,7 @@ export class SpaceSystem {
storeSyscalls(this.db, "store"), storeSyscalls(this.db, "store"),
fullTextSearchSyscalls(this.db, "fts"), fullTextSearchSyscalls(this.db, "fts"),
spaceSyscalls(this.space), spaceSyscalls(this.space),
syncSyscalls(this.spacePrimitives), syncSyscalls(this.spacePrimitives, this.system),
eventSyscalls(this.eventHook), eventSyscalls(this.eventHook),
markdownSyscalls(buildMarkdown([])), markdownSyscalls(buildMarkdown([])),
esbuildSyscalls([globalModules]), esbuildSyscalls([globalModules]),

View File

@ -31,5 +31,8 @@ export function systemSyscalls(
"system.reloadPlugs": () => { "system.reloadPlugs": () => {
return plugReloader(); return plugReloader();
}, },
"system.getEnv": () => {
return system.env;
},
}; };
} }

View File

@ -43,7 +43,7 @@ import buildMarkdown from "../common/markdown_parser/parser.ts";
import { Space } from "../common/spaces/space.ts"; import { Space } from "../common/spaces/space.ts";
import { markdownSyscalls } from "../common/syscalls/markdown.ts"; import { markdownSyscalls } from "../common/syscalls/markdown.ts";
import { FilterOption, PageMeta } from "../common/types.ts"; import { FilterOption, PageMeta } from "../common/types.ts";
import { isMacLike, safeRun, throttle } from "../common/util.ts"; import { isMacLike, safeRun } from "../common/util.ts";
import { createSandbox } from "../plugos/environments/webworker_sandbox.ts"; import { createSandbox } from "../plugos/environments/webworker_sandbox.ts";
import { EventHook } from "../plugos/hooks/event.ts"; import { EventHook } from "../plugos/hooks/event.ts";
import assetSyscalls from "../plugos/syscalls/asset.ts"; import assetSyscalls from "../plugos/syscalls/asset.ts";
@ -98,6 +98,7 @@ import type {
import { CodeWidgetHook } from "./hooks/code_widget.ts"; import { CodeWidgetHook } from "./hooks/code_widget.ts";
import { sandboxFetchSyscalls } from "../plugos/syscalls/fetch.ts"; import { sandboxFetchSyscalls } from "../plugos/syscalls/fetch.ts";
import { syncSyscalls } from "../common/syscalls/sync.ts"; import { syncSyscalls } from "../common/syscalls/sync.ts";
import { throttle } from "../common/async_util.ts";
const frontMatterRegex = /^---\n(.*?)---\n/ms; const frontMatterRegex = /^---\n(.*?)---\n/ms;
@ -196,7 +197,6 @@ export class Editor {
markdownSyscalls(buildMarkdown(this.mdExtensions)), markdownSyscalls(buildMarkdown(this.mdExtensions)),
sandboxSyscalls(this.system), sandboxSyscalls(this.system),
assetSyscalls(this.system), assetSyscalls(this.system),
syncSyscalls(this.space.spacePrimitives),
collabSyscalls(this), collabSyscalls(this),
); );

View File

@ -50,5 +50,8 @@ export function systemSyscalls(
"sandbox.getServerLogs": (ctx) => { "sandbox.getServerLogs": (ctx) => {
return editor.space.proxySyscall(ctx.plug, "sandbox.getLogs", []); return editor.space.proxySyscall(ctx.plug, "sandbox.getLogs", []);
}, },
"system.getEnv": () => {
return system.env;
},
}; };
} }