@silverbulletmd/silverbullet 2.5.3 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -6
- package/client/asset_bundle/bundle.ts +3 -9
- package/client/data/datastore.ts +4 -5
- package/client/markdown_parser/constants.ts +4 -2
- package/client/plugos/hooks/code_widget.ts +3 -5
- package/client/plugos/hooks/command.ts +54 -24
- package/client/plugos/hooks/document_editor.ts +10 -12
- package/client/plugos/hooks/event.ts +33 -36
- package/client/plugos/hooks/mq.ts +17 -17
- package/client/plugos/hooks/plug_namespace.ts +3 -5
- package/client/plugos/hooks/slash_command.ts +15 -28
- package/client/plugos/hooks/syscall.ts +3 -3
- package/client/plugos/manifest_cache.ts +22 -15
- package/client/plugos/plug.ts +7 -7
- package/client/plugos/plug_compile.ts +119 -67
- package/client/plugos/protocol.ts +28 -28
- package/client/plugos/proxy_fetch.ts +7 -6
- package/client/plugos/sandboxes/worker_sandbox.ts +26 -15
- package/client/plugos/syscalls/asset.ts +1 -3
- package/client/plugos/syscalls/code_widget.ts +1 -3
- package/client/plugos/syscalls/config.ts +10 -7
- package/client/plugos/syscalls/datastore.ts +1 -1
- package/client/plugos/syscalls/editor.ts +199 -60
- package/client/plugos/syscalls/event.ts +9 -12
- package/client/plugos/syscalls/fetch.ts +30 -22
- package/client/plugos/syscalls/index.ts +11 -2
- package/client/plugos/syscalls/jsonschema.ts +54 -32
- package/client/plugos/syscalls/language.ts +9 -5
- package/client/plugos/syscalls/markdown.ts +29 -7
- package/client/plugos/syscalls/mq.ts +3 -11
- package/client/plugos/syscalls/service_registry.ts +1 -4
- package/client/plugos/syscalls/shell.ts +2 -5
- package/client/plugos/syscalls/sync.ts +69 -60
- package/client/plugos/syscalls/system.ts +15 -3
- package/client/plugos/system.ts +18 -20
- package/client/plugos/types.ts +25 -0
- package/client/plugos/util.ts +20 -0
- package/client/plugos/worker_runtime.ts +4 -3
- package/client/space_lua/aggregates.ts +633 -60
- package/client/space_lua/ast.ts +21 -9
- package/client/space_lua/ast_narrow.ts +4 -2
- package/client/space_lua/eval.ts +842 -536
- package/client/space_lua/labels.ts +6 -11
- package/client/space_lua/numeric.ts +5 -8
- package/client/space_lua/parse.ts +290 -169
- package/client/space_lua/query_collection.ts +213 -149
- package/client/space_lua/render_lua_markdown.ts +369 -0
- package/client/space_lua/rp.ts +5 -4
- package/client/space_lua/runtime.ts +245 -142
- package/client/space_lua/sliq_null.ts +6 -0
- package/client/space_lua/stdlib/format.ts +34 -20
- package/client/space_lua/stdlib/js.ts +3 -7
- package/client/space_lua/stdlib/load.ts +1 -3
- package/client/space_lua/stdlib/math.ts +15 -14
- package/client/space_lua/stdlib/net.ts +25 -15
- package/client/space_lua/stdlib/os.ts +76 -85
- package/client/space_lua/stdlib/pattern.ts +28 -35
- package/client/space_lua/stdlib/prng.ts +15 -12
- package/client/space_lua/stdlib/space_lua.ts +16 -17
- package/client/space_lua/stdlib/string.ts +7 -17
- package/client/space_lua/stdlib/string_pack.ts +23 -19
- package/client/space_lua/stdlib/table.ts +5 -9
- package/client/space_lua/stdlib.ts +20 -30
- package/client/space_lua/tonumber.ts +79 -40
- package/client/space_lua/util.ts +14 -10
- package/dist/plug-compile.js +87 -42
- package/package.json +24 -22
- package/plug-api/constants.ts +20 -0
- package/plug-api/lib/async.ts +19 -6
- package/plug-api/lib/crypto.ts +5 -6
- package/plug-api/lib/dates.ts +15 -7
- package/plug-api/lib/json.ts +10 -4
- package/plug-api/lib/ref.ts +27 -20
- package/plug-api/lib/resolve.ts +7 -11
- package/plug-api/lib/shortcut.ts +16 -0
- package/plug-api/lib/tags.ts +13 -4
- package/plug-api/lib/transclusion.ts +6 -17
- package/plug-api/lib/tree.ts +146 -43
- package/plug-api/lib/yaml.ts +25 -15
- package/plug-api/syscalls/asset.ts +1 -1
- package/plug-api/syscalls/config.ts +38 -4
- package/plug-api/syscalls/editor.ts +169 -14
- package/plug-api/syscalls/index.ts +18 -0
- package/plug-api/syscalls/jsonschema.ts +1 -3
- package/plug-api/syscalls/lua.ts +3 -9
- package/plug-api/syscalls/mq.ts +1 -4
- package/plug-api/syscalls/shell.ts +4 -1
- package/plug-api/syscalls/space.ts +3 -10
- package/plug-api/syscalls/system.ts +16 -4
- package/plug-api/syscalls/yaml.ts +2 -6
- package/plug-api/types/client.ts +16 -1
- package/plug-api/types/event.ts +6 -4
- package/plug-api/types/manifest.ts +71 -9
- package/plugs/builtin_plugs.ts +3 -3
- package/client/plugos/sandboxes/web_worker_sandbox.ts +0 -17
- package/dist/worker_runtime_bundle.js +0 -233
|
@@ -1,40 +1,44 @@
|
|
|
1
1
|
import type { SysCallMapping } from "../system.ts";
|
|
2
|
-
import {
|
|
2
|
+
import { type OutputUnit, Validator, format } from "@cfworker/json-schema";
|
|
3
|
+
import { stripFunctions } from "../util.ts";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
// Register custom formats
|
|
6
|
+
format.email = (data: string) => data.includes("@");
|
|
7
|
+
format["page-ref"] = (data: string) =>
|
|
8
|
+
data.startsWith("[[") && data.endsWith("]]");
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
validate: (data: string) => {
|
|
8
|
-
// TODO: Implement email validation
|
|
9
|
-
return data.includes("@");
|
|
10
|
-
},
|
|
11
|
-
async: false,
|
|
12
|
-
});
|
|
10
|
+
const schemaCache = new Map<string, Validator>();
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async: false,
|
|
19
|
-
});
|
|
12
|
+
function formatErrors(errors: OutputUnit[]): string {
|
|
13
|
+
// Filter out "properties" wrapper errors, keep only the specific leaf errors
|
|
14
|
+
const leafErrors = errors.filter((e) => e.keyword !== "properties");
|
|
15
|
+
const errorsToUse = leafErrors.length > 0 ? leafErrors : errors;
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
return errorsToUse
|
|
18
|
+
.map((e) => {
|
|
19
|
+
// Convert instanceLocation from "#/foo/bar" to "foo.bar"
|
|
20
|
+
const path =
|
|
21
|
+
e.instanceLocation === "#"
|
|
22
|
+
? ""
|
|
23
|
+
: e.instanceLocation.slice(2).replaceAll("/", ".");
|
|
24
|
+
return path ? `${path}: ${e.error}` : e.error;
|
|
25
|
+
})
|
|
26
|
+
.join(", ");
|
|
27
|
+
}
|
|
22
28
|
|
|
23
29
|
export function validateObject(schema: any, object: any): undefined | string {
|
|
24
30
|
try {
|
|
25
31
|
const schemaKey = JSON.stringify(schema);
|
|
26
32
|
if (!schemaCache.has(schemaKey)) {
|
|
27
|
-
const
|
|
28
|
-
schemaCache.set(schemaKey,
|
|
33
|
+
const validator = new Validator(schema, "7");
|
|
34
|
+
schemaCache.set(schemaKey, validator);
|
|
29
35
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
36
|
+
const validator = schemaCache.get(schemaKey)!;
|
|
37
|
+
const result = validator.validate(stripFunctions(object));
|
|
38
|
+
if (result.valid) {
|
|
32
39
|
return;
|
|
33
40
|
} else {
|
|
34
|
-
|
|
35
|
-
text = text.replaceAll("/", ".");
|
|
36
|
-
text = text.replace(/^data[.\s]/, "");
|
|
37
|
-
return text;
|
|
41
|
+
return formatErrors(result.errors);
|
|
38
42
|
}
|
|
39
43
|
} catch (e: any) {
|
|
40
44
|
return e.message;
|
|
@@ -42,12 +46,33 @@ export function validateObject(schema: any, object: any): undefined | string {
|
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export function validateSchema(schema: any): undefined | string {
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
if (schema === null || schema === undefined) {
|
|
50
|
+
return "schema must not be null or undefined";
|
|
51
|
+
}
|
|
52
|
+
if (typeof schema === "boolean") {
|
|
47
53
|
return;
|
|
48
|
-
} else {
|
|
49
|
-
return ajv.errorsText(ajv.errors);
|
|
50
54
|
}
|
|
55
|
+
if (typeof schema !== "object" || Array.isArray(schema)) {
|
|
56
|
+
return "schema must be an object or boolean";
|
|
57
|
+
}
|
|
58
|
+
if (schema.type !== undefined) {
|
|
59
|
+
const validTypes = [
|
|
60
|
+
"string",
|
|
61
|
+
"number",
|
|
62
|
+
"integer",
|
|
63
|
+
"boolean",
|
|
64
|
+
"object",
|
|
65
|
+
"array",
|
|
66
|
+
"null",
|
|
67
|
+
];
|
|
68
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
69
|
+
for (const t of types) {
|
|
70
|
+
if (!validTypes.includes(t)) {
|
|
71
|
+
return `schema.type must be one of ${validTypes.join(", ")}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
51
76
|
}
|
|
52
77
|
|
|
53
78
|
export function jsonschemaSyscalls(): SysCallMapping {
|
|
@@ -59,10 +84,7 @@ export function jsonschemaSyscalls(): SysCallMapping {
|
|
|
59
84
|
): undefined | string => {
|
|
60
85
|
return validateObject(schema, object);
|
|
61
86
|
},
|
|
62
|
-
"jsonschema.validateSchema": (
|
|
63
|
-
_ctx,
|
|
64
|
-
schema: any,
|
|
65
|
-
): undefined | string => {
|
|
87
|
+
"jsonschema.validateSchema": (_ctx, schema: any): undefined | string => {
|
|
66
88
|
return validateSchema(schema);
|
|
67
89
|
},
|
|
68
90
|
};
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import type { SysCallMapping } from "../system.ts";
|
|
2
2
|
import { parse } from "../../markdown_parser/parse_tree.ts";
|
|
3
3
|
import type { ParseTree } from "@silverbulletmd/silverbullet/lib/tree";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
allLanguageNames,
|
|
6
|
+
languageFor,
|
|
7
|
+
loadLanguageFor,
|
|
8
|
+
} from "../../languages.ts";
|
|
5
9
|
|
|
6
10
|
export function languageSyscalls(): SysCallMapping {
|
|
7
11
|
return {
|
|
8
|
-
"language.parseLanguage": (
|
|
12
|
+
"language.parseLanguage": async (
|
|
9
13
|
_ctx,
|
|
10
14
|
language: string,
|
|
11
15
|
code: string,
|
|
12
|
-
): ParseTree => {
|
|
13
|
-
const lang = languageFor(language);
|
|
16
|
+
): Promise<ParseTree> => {
|
|
17
|
+
const lang = languageFor(language) ?? await loadLanguageFor(language);
|
|
14
18
|
if (!lang) {
|
|
15
19
|
throw new Error(`Unknown language ${language}`);
|
|
16
20
|
}
|
|
17
21
|
return parse(lang, code);
|
|
18
22
|
},
|
|
19
23
|
"language.listLanguages": (): string[] => {
|
|
20
|
-
return
|
|
24
|
+
return allLanguageNames;
|
|
21
25
|
},
|
|
22
26
|
};
|
|
23
27
|
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type ParseTree,
|
|
5
5
|
renderToText,
|
|
6
6
|
} from "@silverbulletmd/silverbullet/lib/tree";
|
|
7
|
-
import {
|
|
7
|
+
import { buildExtendedMarkdownLanguage } from "../../markdown_parser/parser.ts";
|
|
8
8
|
import {
|
|
9
9
|
expandMarkdown,
|
|
10
10
|
type MarkdownExpandOptions,
|
|
@@ -18,11 +18,12 @@ import {
|
|
|
18
18
|
jsonToMDTable,
|
|
19
19
|
refCellTransformer,
|
|
20
20
|
} from "../../markdown_renderer/result_render.ts";
|
|
21
|
+
import * as TagConstants from "../../../plugs/index/constants.ts";
|
|
21
22
|
|
|
22
23
|
export function markdownSyscalls(client: Client): SysCallMapping {
|
|
23
24
|
return {
|
|
24
25
|
"markdown.parseMarkdown": (_ctx, text: string): ParseTree => {
|
|
25
|
-
return parse(
|
|
26
|
+
return parse(markdownLanguageWithUserExtensions(client), text);
|
|
26
27
|
},
|
|
27
28
|
"markdown.renderParseTree": (_ctx, tree: ParseTree): string => {
|
|
28
29
|
return renderToText(tree);
|
|
@@ -34,7 +35,10 @@ export function markdownSyscalls(client: Client): SysCallMapping {
|
|
|
34
35
|
): Promise<ParseTree | string> => {
|
|
35
36
|
const outputString = typeof treeOrText === "string";
|
|
36
37
|
if (typeof treeOrText === "string") {
|
|
37
|
-
treeOrText = parse(
|
|
38
|
+
treeOrText = parse(
|
|
39
|
+
markdownLanguageWithUserExtensions(client),
|
|
40
|
+
treeOrText,
|
|
41
|
+
);
|
|
38
42
|
}
|
|
39
43
|
const result = await expandMarkdownWithClient(
|
|
40
44
|
client,
|
|
@@ -52,23 +56,38 @@ export function markdownSyscalls(client: Client): SysCallMapping {
|
|
|
52
56
|
text: string,
|
|
53
57
|
options: MarkdownRenderOptions = {},
|
|
54
58
|
) => {
|
|
55
|
-
let mdTree = parse(
|
|
59
|
+
let mdTree = parse(markdownLanguageWithUserExtensions(client), text);
|
|
56
60
|
if (options.expand) {
|
|
57
61
|
mdTree = await expandMarkdownWithClient(client, mdTree);
|
|
58
62
|
}
|
|
63
|
+
if (!options.resolveTagHref) {
|
|
64
|
+
options.resolveTagHref = (tagName: string) => {
|
|
65
|
+
return client.config.get<string | null>(
|
|
66
|
+
["tags", tagName, "tagPage"],
|
|
67
|
+
null,
|
|
68
|
+
) ?? TagConstants.tagPrefix + tagName;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
59
71
|
return renderMarkdownToHtml(mdTree, options);
|
|
60
72
|
},
|
|
61
73
|
"markdown.objectsToTable": (
|
|
62
74
|
_ctx,
|
|
63
75
|
data: any[],
|
|
64
|
-
options: {
|
|
65
|
-
|
|
76
|
+
options: {
|
|
77
|
+
renderCell?: (val: any, key: string) => Promise<any> | any;
|
|
78
|
+
} = {},
|
|
66
79
|
) => {
|
|
67
80
|
return jsonToMDTable(data, options.renderCell || refCellTransformer);
|
|
68
81
|
},
|
|
69
82
|
};
|
|
70
83
|
}
|
|
71
84
|
|
|
85
|
+
function markdownLanguageWithUserExtensions(client: Client) {
|
|
86
|
+
return buildExtendedMarkdownLanguage(
|
|
87
|
+
client.config.get("syntaxExtensions", {}),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
function expandMarkdownWithClient(
|
|
73
92
|
client: Client,
|
|
74
93
|
tree: ParseTree,
|
|
@@ -79,6 +98,9 @@ function expandMarkdownWithClient(
|
|
|
79
98
|
client.currentName(),
|
|
80
99
|
tree,
|
|
81
100
|
client.clientSystem.spaceLuaEnv,
|
|
82
|
-
|
|
101
|
+
{
|
|
102
|
+
...options,
|
|
103
|
+
syntaxExtensions: client.config.get("syntaxExtensions", {}),
|
|
104
|
+
},
|
|
83
105
|
);
|
|
84
106
|
}
|
|
@@ -6,23 +6,15 @@ export type EventSubscription = MQListenerSpec & {
|
|
|
6
6
|
run: (...args: any[]) => Promise<any>;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export function mqSyscalls(
|
|
10
|
-
mq: DataStoreMQ,
|
|
11
|
-
): SysCallMapping {
|
|
9
|
+
export function mqSyscalls(mq: DataStoreMQ): SysCallMapping {
|
|
12
10
|
return {
|
|
13
11
|
/**
|
|
14
12
|
* Define a Lua event listener
|
|
15
13
|
*/
|
|
16
|
-
"mq.subscribe": (
|
|
17
|
-
_ctx,
|
|
18
|
-
def: MQListenerSpec,
|
|
19
|
-
) => {
|
|
14
|
+
"mq.subscribe": (_ctx, def: MQListenerSpec) => {
|
|
20
15
|
def.autoAck = def.autoAck !== false;
|
|
21
16
|
// console.log("Registering Lua event listener: ", def.name);
|
|
22
|
-
client.config.insert([
|
|
23
|
-
"mqSubscriptions",
|
|
24
|
-
def.queue,
|
|
25
|
-
], def);
|
|
17
|
+
client.config.insert(["mqSubscriptions", def.queue], def);
|
|
26
18
|
},
|
|
27
19
|
"mq.send": (_ctx, queue: string, body: any) => {
|
|
28
20
|
return mq.send(queue, body);
|
|
@@ -12,10 +12,7 @@ export function serviceRegistrySyscalls(
|
|
|
12
12
|
/**
|
|
13
13
|
* Define a Lua event listener
|
|
14
14
|
*/
|
|
15
|
-
"service.define": (
|
|
16
|
-
_ctx,
|
|
17
|
-
def: ServiceSpec,
|
|
18
|
-
) => {
|
|
15
|
+
"service.define": (_ctx, def: ServiceSpec) => {
|
|
19
16
|
return serviceRegistry.define(def);
|
|
20
17
|
},
|
|
21
18
|
"service.discover": (
|
|
@@ -2,9 +2,7 @@ import type { SysCallMapping } from "../system.ts";
|
|
|
2
2
|
import type { Client } from "../../client.ts";
|
|
3
3
|
import { fsEndpoint } from "../../spaces/constants.ts";
|
|
4
4
|
|
|
5
|
-
export function shellSyscalls(
|
|
6
|
-
client: Client,
|
|
7
|
-
): SysCallMapping {
|
|
5
|
+
export function shellSyscalls(client: Client): SysCallMapping {
|
|
8
6
|
return {
|
|
9
7
|
"shell.run": async (
|
|
10
8
|
_ctx,
|
|
@@ -34,6 +32,5 @@ export function shellSyscalls(
|
|
|
34
32
|
|
|
35
33
|
function buildShellUrl(client: Client) {
|
|
36
34
|
// Strip off the /.fs and replace with /.shell
|
|
37
|
-
return client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length)
|
|
38
|
-
"/.shell";
|
|
35
|
+
return `${client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length)}/.shell`;
|
|
39
36
|
}
|
|
@@ -1,77 +1,86 @@
|
|
|
1
1
|
import type { SysCallMapping } from "../system.ts";
|
|
2
2
|
import type { Client } from "../../client.ts";
|
|
3
3
|
|
|
4
|
-
// TODO: Reimplement this
|
|
5
4
|
export function syncSyscalls(client: Client): SysCallMapping {
|
|
6
|
-
|
|
7
|
-
"sync.hasInitialSyncCompleted": (): boolean => {
|
|
8
|
-
return client.fullSyncCompleted;
|
|
9
|
-
},
|
|
10
|
-
"sync.performFileSync": (_ctx, path: string): Promise<void> => {
|
|
11
|
-
client.postServiceWorkerMessage({ type: "perform-file-sync", path });
|
|
12
|
-
return waitForServiceWorkerActivation(path);
|
|
13
|
-
},
|
|
14
|
-
"sync.performSpaceSync": (): Promise<number> => {
|
|
15
|
-
client.postServiceWorkerMessage({ type: "perform-space-sync" });
|
|
16
|
-
return waitForServiceWorkerActivation();
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
}
|
|
5
|
+
const syncTimeoutMs = 30000;
|
|
20
6
|
|
|
21
|
-
function waitForServiceWorkerActivation(path?: string): Promise<any> {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
function waitForServiceWorkerActivation(path?: string): Promise<any> {
|
|
8
|
+
return new Promise<any>((resolve, reject) => {
|
|
9
|
+
const timeout = setTimeout(() => {
|
|
10
|
+
cleanup();
|
|
11
|
+
reject(new Error(`Sync timeout after ${syncTimeoutMs / 1000}s`));
|
|
12
|
+
}, syncTimeoutMs);
|
|
13
|
+
|
|
14
|
+
function cleanup() {
|
|
15
|
+
clearTimeout(timeout);
|
|
16
|
+
client.eventHook.removeLocalListener(
|
|
17
|
+
"service-worker:file-sync-complete",
|
|
18
|
+
eventHandler,
|
|
19
|
+
);
|
|
20
|
+
client.eventHook.removeLocalListener(
|
|
21
|
+
"service-worker:space-sync-complete",
|
|
22
|
+
eventHandler,
|
|
23
|
+
);
|
|
24
|
+
client.eventHook.removeLocalListener(
|
|
25
|
+
"service-worker:sync-error",
|
|
26
|
+
errorHandler,
|
|
27
|
+
);
|
|
42
28
|
}
|
|
43
|
-
// If we were waiting for a specific path, ignore other paths
|
|
44
|
-
resolve(data);
|
|
45
29
|
|
|
46
|
-
|
|
47
|
-
client.eventHook.removeLocalListener(
|
|
48
|
-
"service-worker:file-sync-complete",
|
|
49
|
-
eventHandler,
|
|
50
|
-
);
|
|
51
|
-
client.eventHook.removeLocalListener(
|
|
52
|
-
"service-worker:space-sync-complete",
|
|
53
|
-
eventHandler,
|
|
54
|
-
);
|
|
55
|
-
client.eventHook.removeLocalListener(
|
|
56
|
-
"service-worker:sync-error",
|
|
57
|
-
errorHandler,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
function errorHandler(e: any) {
|
|
61
|
-
reject(e);
|
|
62
|
-
// Unsubscribe from all these events
|
|
63
|
-
client.eventHook.removeLocalListener(
|
|
30
|
+
client.eventHook.addLocalListener(
|
|
64
31
|
"service-worker:file-sync-complete",
|
|
65
32
|
eventHandler,
|
|
66
33
|
);
|
|
67
|
-
client.eventHook.
|
|
34
|
+
client.eventHook.addLocalListener(
|
|
68
35
|
"service-worker:space-sync-complete",
|
|
69
36
|
eventHandler,
|
|
70
37
|
);
|
|
71
|
-
client.eventHook.
|
|
38
|
+
client.eventHook.addLocalListener(
|
|
72
39
|
"service-worker:sync-error",
|
|
73
40
|
errorHandler,
|
|
74
41
|
);
|
|
75
|
-
|
|
76
|
-
|
|
42
|
+
|
|
43
|
+
function eventHandler(data: any) {
|
|
44
|
+
if (data.path && path && data.path !== path) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
cleanup();
|
|
48
|
+
resolve(data);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function errorHandler(e: any) {
|
|
52
|
+
// Only reject if the error is for our specific path, or if no path was specified
|
|
53
|
+
if (e.path && path && e.path !== path) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
cleanup();
|
|
57
|
+
reject(e);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
"sync.hasInitialSyncCompleted": (): boolean => {
|
|
64
|
+
return client.fullSyncCompleted;
|
|
65
|
+
},
|
|
66
|
+
"sync.performFileSync": async (_ctx, path: string): Promise<void> => {
|
|
67
|
+
await client.postServiceWorkerMessage({
|
|
68
|
+
type: "perform-file-sync",
|
|
69
|
+
path,
|
|
70
|
+
});
|
|
71
|
+
// postServiceWorkerMessage returns silently if no SW, so only wait if SW is active
|
|
72
|
+
const registration = await navigator.serviceWorker.getRegistration();
|
|
73
|
+
if (registration?.active) {
|
|
74
|
+
return waitForServiceWorkerActivation(path);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"sync.performSpaceSync": async (): Promise<number> => {
|
|
78
|
+
await client.postServiceWorkerMessage({ type: "perform-space-sync" });
|
|
79
|
+
const registration = await navigator.serviceWorker.getRegistration();
|
|
80
|
+
if (registration?.active) {
|
|
81
|
+
return waitForServiceWorkerActivation();
|
|
82
|
+
}
|
|
83
|
+
return 0;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
77
86
|
}
|
|
@@ -60,15 +60,19 @@ export function systemSyscalls(
|
|
|
60
60
|
mac: def.mac,
|
|
61
61
|
hide: def.hide,
|
|
62
62
|
requireMode: def.requireMode,
|
|
63
|
+
requireEditor: def.requireEditor,
|
|
64
|
+
menu: def.menu,
|
|
65
|
+
menuMac: def.menuMac,
|
|
66
|
+
menuWindows: def.menuWindows,
|
|
67
|
+
menuLinux: def.menuLinux,
|
|
63
68
|
};
|
|
64
69
|
}
|
|
65
70
|
return allCommands;
|
|
66
71
|
},
|
|
67
72
|
"system.listSyscalls": (): SyscallMeta[] => {
|
|
68
73
|
const syscalls: SyscallMeta[] = [];
|
|
69
|
-
for (
|
|
70
|
-
|
|
71
|
-
) {
|
|
74
|
+
for (const [name, info] of client.clientSystem.system
|
|
75
|
+
.registeredSyscalls) {
|
|
72
76
|
syscalls.push({
|
|
73
77
|
name,
|
|
74
78
|
requiredPermissions: info.requiredPermissions,
|
|
@@ -83,6 +87,14 @@ export function systemSyscalls(
|
|
|
83
87
|
}
|
|
84
88
|
return client.loadPlugs();
|
|
85
89
|
},
|
|
90
|
+
"system.loadPlug": async (_ctx, path: string) => {
|
|
91
|
+
const meta = await client.space.spacePrimitives.getFileMeta(path);
|
|
92
|
+
await client.clientSystem.loadPlugFromPath(path, meta.lastModified);
|
|
93
|
+
await client.dispatchAppEvent("plugs:loaded");
|
|
94
|
+
},
|
|
95
|
+
"system.unloadPlug": (_ctx, path: string) => {
|
|
96
|
+
return client.clientSystem.system.unloadByPath(path);
|
|
97
|
+
},
|
|
86
98
|
"system.reloadConfig": (): Record<string, any> => {
|
|
87
99
|
console.warn("system.reloadConfig is deprecated, it's now a no-op");
|
|
88
100
|
return client.config.values;
|
package/client/plugos/system.ts
CHANGED
|
@@ -24,9 +24,7 @@ export type SyscallContext = {
|
|
|
24
24
|
plug?: string;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
type SyscallSignature = (
|
|
28
|
-
...args: any[]
|
|
29
|
-
) => Promise<any> | any;
|
|
27
|
+
type SyscallSignature = (...args: any[]) => Promise<any> | any;
|
|
30
28
|
|
|
31
29
|
type Syscall = {
|
|
32
30
|
requiredPermissions: string[];
|
|
@@ -112,11 +110,7 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
112
110
|
return this.syscall({}, name, args);
|
|
113
111
|
}
|
|
114
112
|
|
|
115
|
-
syscall(
|
|
116
|
-
ctx: SyscallContext,
|
|
117
|
-
name: string,
|
|
118
|
-
args: any[],
|
|
119
|
-
): Promise<any> {
|
|
113
|
+
syscall(ctx: SyscallContext, name: string, args: any[]): Promise<any> {
|
|
120
114
|
const syscall = this.registeredSyscalls.get(name);
|
|
121
115
|
if (!syscall) {
|
|
122
116
|
throw Error(`Unregistered syscall ${name}`);
|
|
@@ -139,26 +133,20 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
139
133
|
}
|
|
140
134
|
|
|
141
135
|
/**
|
|
142
|
-
* @param
|
|
136
|
+
* @param path The plug file's space path; also used as the manifest cache key.
|
|
143
137
|
* @param cacheHash Used to determine if the manifest is up to date. Should be `lastModified`
|
|
144
138
|
*/
|
|
145
139
|
async loadPlug(
|
|
146
140
|
sandboxFactory: SandboxFactory<HookT>,
|
|
147
|
-
|
|
141
|
+
path: string,
|
|
148
142
|
cacheHash: number = -1,
|
|
149
143
|
): Promise<Plug<HookT>> {
|
|
150
|
-
const plug = await Plug.createLazily(
|
|
151
|
-
this,
|
|
152
|
-
cacheKey,
|
|
153
|
-
cacheHash,
|
|
154
|
-
sandboxFactory,
|
|
155
|
-
);
|
|
144
|
+
const plug = await Plug.createLazily(this, path, cacheHash, sandboxFactory);
|
|
156
145
|
|
|
157
146
|
const manifest = plug.manifest;
|
|
158
147
|
|
|
159
|
-
// This depends on cacheKey being the file path
|
|
160
148
|
if (
|
|
161
|
-
!builtinPlugPaths.includes(
|
|
149
|
+
!builtinPlugPaths.includes(path) &&
|
|
162
150
|
builtinPlugNames.includes(manifest.name)
|
|
163
151
|
) {
|
|
164
152
|
plug.stop();
|
|
@@ -183,13 +171,23 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
183
171
|
return plug;
|
|
184
172
|
}
|
|
185
173
|
|
|
174
|
+
unloadByPath(path: string): boolean {
|
|
175
|
+
for (const [name, plug] of this.plugs) {
|
|
176
|
+
if (plug.path === path) {
|
|
177
|
+
this.unload(name);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
186
184
|
unload(name: string) {
|
|
187
185
|
const plug = this.plugs.get(name);
|
|
188
186
|
if (!plug) {
|
|
189
187
|
return;
|
|
190
188
|
}
|
|
191
|
-
plug.stop();
|
|
192
|
-
this.emit("plugUnloaded", name);
|
|
189
|
+
void plug.stop();
|
|
190
|
+
void this.emit("plugUnloaded", name);
|
|
193
191
|
this.plugs.delete(name);
|
|
194
192
|
}
|
|
195
193
|
|
package/client/plugos/types.ts
CHANGED
|
@@ -34,6 +34,31 @@ export interface Manifest<HookT> {
|
|
|
34
34
|
* A map of configuration options for the plug (to be merged with the system configuration).
|
|
35
35
|
*/
|
|
36
36
|
config?: any;
|
|
37
|
+
|
|
38
|
+
/** Build steps run before asset bundling. Each step transforms an `in` file
|
|
39
|
+
* into an `out` file, which is then picked up by the `assets` glob.
|
|
40
|
+
*
|
|
41
|
+
* Currently only `type: "esbuild"` is supported (the default when omitted),
|
|
42
|
+
* which runs esbuild in bundle mode on `in` and writes the result to `out`.
|
|
43
|
+
*/
|
|
44
|
+
build?: BuildStep[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A pre-asset-bundling transformation, e.g. bundling a TypeScript entry point
|
|
48
|
+
* into a single JS file that is then loaded from plug assets.
|
|
49
|
+
*
|
|
50
|
+
* Supported types:
|
|
51
|
+
* - `esbuild` (default): bundle `in` into `out` as a minified browser IIFE.
|
|
52
|
+
* - `sass`: compile an SCSS/SASS file at `in` to CSS at `out`.
|
|
53
|
+
* - `copy`: copy `in` verbatim to `out`.
|
|
54
|
+
*/
|
|
55
|
+
export interface BuildStep {
|
|
56
|
+
/** Transformation type. Defaults to "esbuild" when omitted. */
|
|
57
|
+
type?: "esbuild" | "sass" | "copy";
|
|
58
|
+
/** Input path relative to the manifest. */
|
|
59
|
+
in: string;
|
|
60
|
+
/** Output path relative to the manifest. */
|
|
61
|
+
out: string;
|
|
37
62
|
}
|
|
38
63
|
|
|
39
64
|
/** Associates hooks with a function. This is the generic base structure, that identifies the function. Hooks are defined by the type parameter. */
|
package/client/plugos/util.ts
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-clone a value, replacing any functions with null.
|
|
3
|
+
* Needed because values containing functions can't be serialized
|
|
4
|
+
* via postMessage or validated with JSON Schema.
|
|
5
|
+
*/
|
|
6
|
+
export function stripFunctions(value: any): any {
|
|
7
|
+
if (typeof value === "function") return null;
|
|
8
|
+
if (value === null || value === undefined || typeof value !== "object") {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return value.map(stripFunctions);
|
|
13
|
+
}
|
|
14
|
+
const result: Record<string, any> = {};
|
|
15
|
+
for (const key of Object.keys(value)) {
|
|
16
|
+
result[key] = stripFunctions(value[key]);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
1
21
|
/**
|
|
2
22
|
* Checks if an object is sendable across the plugos worker boundary.
|
|
3
23
|
*
|
|
@@ -19,7 +19,8 @@ let workerPostMessage = (_msg: ControllerMessage): void => {
|
|
|
19
19
|
// - in a browser's main thread, typeof window is "object"
|
|
20
20
|
// - in a browser's worker threads, typeof window === "undefined"
|
|
21
21
|
// - in Cloudflare workers typeof window === "undefined", but typeof globalThis.WebSocketPair is defined
|
|
22
|
-
const runningAsWebWorker =
|
|
22
|
+
const runningAsWebWorker =
|
|
23
|
+
typeof window === "undefined" &&
|
|
23
24
|
// @ts-expect-error: globalThis
|
|
24
25
|
typeof globalThis.WebSocketPair === "undefined";
|
|
25
26
|
|
|
@@ -147,8 +148,8 @@ export function monkeyPatchFetch() {
|
|
|
147
148
|
): Promise<Response> => {
|
|
148
149
|
const encodedBody = init?.body
|
|
149
150
|
? base64Encode(
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
new Uint8Array(await new Response(init.body).arrayBuffer()),
|
|
152
|
+
)
|
|
152
153
|
: undefined;
|
|
153
154
|
const r = await sandboxFetch(
|
|
154
155
|
reqInfo,
|