Cleanup and progress
parent
7e591c6f44
commit
a916088215
|
@ -0,0 +1,23 @@
|
||||||
|
import * as plugbox from "../plugbox/types";
|
||||||
|
import { EndpointHook } from "../plugbox/types";
|
||||||
|
|
||||||
|
export type CommandDef = {
|
||||||
|
// Function name to invoke
|
||||||
|
invoke: string;
|
||||||
|
|
||||||
|
// Bind to keyboard shortcut
|
||||||
|
key?: string;
|
||||||
|
mac?: string;
|
||||||
|
|
||||||
|
// If to show in slash invoked menu and if so, with what label
|
||||||
|
// should match slashCommandRegexp
|
||||||
|
slashCommand?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SilverBulletHooks = {
|
||||||
|
commands?: {
|
||||||
|
[key: string]: CommandDef;
|
||||||
|
};
|
||||||
|
} & plugbox.EndpointHook;
|
||||||
|
|
||||||
|
export type Manifest = plugbox.Manifest<SilverBulletHooks>;
|
|
@ -6,14 +6,14 @@
|
||||||
<style>
|
<style>
|
||||||
@import "../../webapp/src/styles/main.scss";
|
@import "../../webapp/src/styles/main.scss";
|
||||||
</style>
|
</style>
|
||||||
<script type="module" src="boot.ts"></script>
|
<script type="module" src="server.ts"></script>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import "./boot";
|
import "./server";
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
"watch": "parcel watch",
|
"watch": "parcel watch",
|
||||||
"build": "parcel build",
|
"build": "parcel build",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"plugs": "node dist/bundler/plugbox-bundle.js plugs/core/core.plug.json webapp/generated/core.plug.json",
|
"plugs": "node dist/bundler/plugbox-bundle.js plugs/core/core.plug.json plugs/dist/core.plug.json",
|
||||||
"server": "nodemon dist/server/server.js pages",
|
"server": "nodemon -w dist/server dist/server/server.js pages",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"targets": {
|
"targets": {
|
||||||
|
@ -40,6 +40,7 @@
|
||||||
"test": {
|
"test": {
|
||||||
"source": [
|
"source": [
|
||||||
"plugbox/runtime.test.ts",
|
"plugbox/runtime.test.ts",
|
||||||
|
"plugbox/endpoint.test.ts",
|
||||||
"server/api.test.ts"
|
"server/api.test.ts"
|
||||||
],
|
],
|
||||||
"outputFormat": "commonjs",
|
"outputFormat": "commonjs",
|
||||||
|
@ -75,12 +76,14 @@
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"knex": "^1.0.4",
|
"knex": "^1.0.4",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"node-cron": "^3.0.0",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"parcel": "^2.3.2",
|
"parcel": "^2.3.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"socket.io": "^4.4.1",
|
"socket.io": "^4.4.1",
|
||||||
"socket.io-client": "^4.4.1",
|
"socket.io-client": "^4.4.1",
|
||||||
|
"supertest": "^6.2.2",
|
||||||
"ts-jest": "^27.1.3",
|
"ts-jest": "^27.1.3",
|
||||||
"vm2": "^3.9.9",
|
"vm2": "^3.9.9",
|
||||||
"yargs": "^17.3.1"
|
"yargs": "^17.3.1"
|
||||||
|
@ -95,8 +98,10 @@
|
||||||
"@types/events": "^3.0.0",
|
"@types/events": "^3.0.0",
|
||||||
"@types/jest": "^27.4.1",
|
"@types/jest": "^27.4.1",
|
||||||
"@types/node": "^17.0.21",
|
"@types/node": "^17.0.21",
|
||||||
|
"@types/node-cron": "^3.0.1",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
|
"@types/supertest": "^2.0.11",
|
||||||
"@vscode/sqlite3": "^5.0.7",
|
"@vscode/sqlite3": "^5.0.7",
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
"crypto-browserify": "^3.12.0",
|
"crypto-browserify": "^3.12.0",
|
||||||
|
|
|
@ -47,7 +47,9 @@ export default ${functionName};`
|
||||||
|
|
||||||
async function bundle(manifestPath: string, sourceMaps: boolean) {
|
async function bundle(manifestPath: string, sourceMaps: boolean) {
|
||||||
const rootPath = path.dirname(manifestPath);
|
const rootPath = path.dirname(manifestPath);
|
||||||
const manifest = JSON.parse((await readFile(manifestPath)).toString()) as Manifest<any>;
|
const manifest = JSON.parse(
|
||||||
|
(await readFile(manifestPath)).toString()
|
||||||
|
) as Manifest<any>;
|
||||||
|
|
||||||
for (let [name, def] of Object.entries(manifest.functions)) {
|
for (let [name, def] of Object.entries(manifest.functions)) {
|
||||||
let jsFunctionName = "default",
|
let jsFunctionName = "default",
|
||||||
|
@ -69,7 +71,10 @@ async function run() {
|
||||||
.parse();
|
.parse();
|
||||||
|
|
||||||
let generatedManifest = await bundle(args._[0] as string, !!args.debug);
|
let generatedManifest = await bundle(args._[0] as string, !!args.debug);
|
||||||
await writeFile(args._[1] as string, JSON.stringify(generatedManifest, null, 2));
|
await writeFile(
|
||||||
|
args._[1] as string,
|
||||||
|
JSON.stringify(generatedManifest, null, 2)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
run().catch((e) => {
|
run().catch((e) => {
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { System } from "./runtime";
|
||||||
|
import { CronHook } from "./types";
|
||||||
|
import cron from "node-cron";
|
||||||
|
|
||||||
|
export function cronSystem(system: System<CronHook>) {
|
||||||
|
let task = cron.schedule("* * * * *", () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
// @ts-ignore
|
||||||
|
task.destroy();
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { createSandbox } from "./node_sandbox";
|
||||||
|
import { System } from "./runtime";
|
||||||
|
import { test, expect } from "@jest/globals";
|
||||||
|
import { EndPointDef, EndpointHook, Manifest } from "./types";
|
||||||
|
import express from "express";
|
||||||
|
import request from "supertest";
|
||||||
|
import { exposeSystem } from "./endpoints";
|
||||||
|
|
||||||
|
test("Run a plugbox endpoint server", async () => {
|
||||||
|
let system = new System<EndpointHook>();
|
||||||
|
let plug = await system.load(
|
||||||
|
"test",
|
||||||
|
{
|
||||||
|
functions: {
|
||||||
|
testhandler: {
|
||||||
|
code: `(() => {
|
||||||
|
return {
|
||||||
|
default: (req) => {
|
||||||
|
console.log("Req", req);
|
||||||
|
return {status: 200, body: [1, 2, 3], headers: {"Content-type": "application/json"}};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})()`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
endpoints: [{ method: "GET", path: "/", handler: "testhandler" }],
|
||||||
|
},
|
||||||
|
} as Manifest<EndpointHook>,
|
||||||
|
createSandbox(system)
|
||||||
|
);
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = 3123;
|
||||||
|
app.use(exposeSystem(system));
|
||||||
|
let server = app.listen(port, () => {
|
||||||
|
console.log(`Listening on port ${port}`);
|
||||||
|
});
|
||||||
|
let resp = await request(app)
|
||||||
|
.get("/_/test/?name=Pete")
|
||||||
|
.expect((resp) => {
|
||||||
|
expect(resp.status).toBe(200);
|
||||||
|
expect(resp.header["content-type"]).toContain("application/json");
|
||||||
|
expect(resp.text).toBe(JSON.stringify([1, 2, 3]));
|
||||||
|
});
|
||||||
|
server.close();
|
||||||
|
});
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { System } from "./runtime";
|
||||||
|
import { EndpointHook } from "./types";
|
||||||
|
import express from "express";
|
||||||
|
|
||||||
|
export type EndpointRequest = {
|
||||||
|
method: string;
|
||||||
|
path: string;
|
||||||
|
query: { [key: string]: string };
|
||||||
|
headers: { [key: string]: string };
|
||||||
|
body: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EndpointResponse = {
|
||||||
|
status: number;
|
||||||
|
headers?: { [key: string]: string };
|
||||||
|
body: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const endPointPrefix = "/_";
|
||||||
|
|
||||||
|
export function exposeSystem(system: System<EndpointHook>) {
|
||||||
|
return (
|
||||||
|
req: express.Request,
|
||||||
|
res: express.Response,
|
||||||
|
next: express.NextFunction
|
||||||
|
) => {
|
||||||
|
if (!req.path.startsWith(endPointPrefix)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
Promise.resolve()
|
||||||
|
.then(async () => {
|
||||||
|
for (const [plugName, plug] of system.loadedPlugs.entries()) {
|
||||||
|
const manifest = plug.manifest;
|
||||||
|
if (!manifest) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const endpoints = manifest.hooks?.endpoints;
|
||||||
|
if (endpoints) {
|
||||||
|
let prefix = `${endPointPrefix}/${plugName}`;
|
||||||
|
if (!req.path.startsWith(prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const { path, method, handler } of endpoints) {
|
||||||
|
let prefixedPath = `${prefix}${path}`;
|
||||||
|
if (prefixedPath === req.path && method === req.method) {
|
||||||
|
try {
|
||||||
|
const response: EndpointResponse = await plug.invoke(
|
||||||
|
handler,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: req.path,
|
||||||
|
method: req.method,
|
||||||
|
body: req.body,
|
||||||
|
query: req.query,
|
||||||
|
headers: req.headers,
|
||||||
|
} as EndpointRequest,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
let resp = res.status(response.status);
|
||||||
|
if (response.headers) {
|
||||||
|
for (const [key, value] of Object.entries(
|
||||||
|
response.headers
|
||||||
|
)) {
|
||||||
|
resp = resp.header(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.send(response.body);
|
||||||
|
return;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error("Error executing function", e);
|
||||||
|
res.status(500).send(e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
next(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import sandboxHtml from "bundle-text:./iframe_sandbox.html";
|
||||||
class IFrameWrapper implements WorkerLike {
|
class IFrameWrapper implements WorkerLike {
|
||||||
private iframe: HTMLIFrameElement;
|
private iframe: HTMLIFrameElement;
|
||||||
onMessage?: (message: any) => Promise<void>;
|
onMessage?: (message: any) => Promise<void>;
|
||||||
|
ready: Promise<void>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const iframe = document.createElement("iframe", {});
|
const iframe = document.createElement("iframe", {});
|
||||||
|
@ -27,6 +28,12 @@ class IFrameWrapper implements WorkerLike {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
|
this.ready = new Promise((resolve) => {
|
||||||
|
iframe.onload = () => {
|
||||||
|
resolve();
|
||||||
|
iframe.onload = null;
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
postMessage(message: any): void {
|
postMessage(message: any): void {
|
||||||
|
|
|
@ -6,14 +6,12 @@ import * as fs from "fs";
|
||||||
import { safeRun } from "./util";
|
import { safeRun } from "./util";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import workerCode from "bundle-text:./node_worker.ts"
|
import workerCode from "bundle-text:./node_worker.ts";
|
||||||
|
|
||||||
// ParcelJS will simply inline this into the bundle.
|
|
||||||
// const workerCode = fs.readFileSync(__dirname + "/node_worker.ts", "utf-8");
|
|
||||||
|
|
||||||
class NodeWorkerWrapper implements WorkerLike {
|
class NodeWorkerWrapper implements WorkerLike {
|
||||||
onMessage?: (message: any) => Promise<void>;
|
onMessage?: (message: any) => Promise<void>;
|
||||||
private worker: Worker;
|
private worker: Worker;
|
||||||
|
ready: Promise<void>;
|
||||||
|
|
||||||
constructor(worker: Worker) {
|
constructor(worker: Worker) {
|
||||||
this.worker = worker;
|
this.worker = worker;
|
||||||
|
@ -22,6 +20,9 @@ class NodeWorkerWrapper implements WorkerLike {
|
||||||
await this.onMessage!(message);
|
await this.onMessage!(message);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
this.ready = new Promise((resolve) => {
|
||||||
|
worker.once("online", resolve);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
postMessage(message: any): void {
|
postMessage(message: any): void {
|
||||||
|
@ -34,6 +35,9 @@ class NodeWorkerWrapper implements WorkerLike {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSandbox(system: System<any>) {
|
export function createSandbox(system: System<any>) {
|
||||||
|
let worker = new Worker(workerCode, {
|
||||||
|
eval: true,
|
||||||
|
});
|
||||||
return new Sandbox(
|
return new Sandbox(
|
||||||
system,
|
system,
|
||||||
new NodeWorkerWrapper(
|
new NodeWorkerWrapper(
|
||||||
|
|
|
@ -62,7 +62,8 @@ parentPort.on("message", (data : any) => {
|
||||||
parentPort.postMessage({
|
parentPort.postMessage({
|
||||||
type: "result",
|
type: "result",
|
||||||
id: data.id,
|
id: data.id,
|
||||||
result: result,
|
// TOOD: Figure out if this is necessary, because it's expensive
|
||||||
|
result: result && JSON.parse(JSON.stringify(result)),
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// console.log("ERROR", e);
|
// console.log("ERROR", e);
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import fs, { stat, watch } from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
import { createSandbox } from "./node_sandbox";
|
||||||
|
import { System } from "./runtime";
|
||||||
|
import { safeRun } from "../server/util";
|
||||||
|
|
||||||
|
function extractPlugName(localPath: string): string {
|
||||||
|
const baseName = path.basename(localPath);
|
||||||
|
return baseName.substring(0, baseName.length - ".plug.json".length);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiskPlugLoader<HookT> {
|
||||||
|
private system: System<HookT>;
|
||||||
|
private plugPath: string;
|
||||||
|
|
||||||
|
constructor(system: System<HookT>, plugPath: string) {
|
||||||
|
this.system = system;
|
||||||
|
this.plugPath = plugPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher() {
|
||||||
|
safeRun(async () => {
|
||||||
|
for await (const { filename, eventType } of watch(this.plugPath, {
|
||||||
|
recursive: true,
|
||||||
|
})) {
|
||||||
|
if (!filename.endsWith(".plug.json")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let localPath = path.join(this.plugPath, filename);
|
||||||
|
const plugName = extractPlugName(localPath);
|
||||||
|
try {
|
||||||
|
await fs.stat(localPath);
|
||||||
|
} catch (e) {
|
||||||
|
// Likely removed
|
||||||
|
await this.system.unload(plugName);
|
||||||
|
this.system.emit("plugRemoved", plugName);
|
||||||
|
}
|
||||||
|
const plugDef = await this.loadPlugFromFile(localPath);
|
||||||
|
this.system.emit("plugUpdated", plugName, plugDef);
|
||||||
|
} catch {
|
||||||
|
// ignore, error handled by loadPlug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadPlugFromFile(localPath: string) {
|
||||||
|
const plug = await fs.readFile(localPath, "utf8");
|
||||||
|
const plugName = extractPlugName(localPath);
|
||||||
|
|
||||||
|
console.log("Now loading plug", plugName);
|
||||||
|
try {
|
||||||
|
const plugDef = JSON.parse(plug);
|
||||||
|
await this.system.load(plugName, plugDef, createSandbox(this.system));
|
||||||
|
return plugDef;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Could not parse plugin file", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadPlugs() {
|
||||||
|
for (let filename of await fs.readdir(this.plugPath)) {
|
||||||
|
if (filename.endsWith(".plug.json")) {
|
||||||
|
let localPath = path.join(this.plugPath, filename);
|
||||||
|
await this.loadPlugFromFile(localPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,5 +75,5 @@ test("Run a Node sandbox", async () => {
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
expect(e.message).toBe("#fail");
|
expect(e.message).toBe("#fail");
|
||||||
}
|
}
|
||||||
await system.stop();
|
await system.unloadAll();
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
WorkerLike,
|
WorkerLike,
|
||||||
WorkerMessage,
|
WorkerMessage,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { EventEmitter } from "../common/event";
|
||||||
|
|
||||||
interface SysCallMapping {
|
interface SysCallMapping {
|
||||||
[key: string]: (...args: any) => Promise<any> | any;
|
[key: string]: (...args: any) => Promise<any> | any;
|
||||||
|
@ -31,6 +32,7 @@ export class Sandbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(name: string, code: string): Promise<void> {
|
async load(name: string, code: string): Promise<void> {
|
||||||
|
await this.worker.ready;
|
||||||
this.worker.postMessage({
|
this.worker.postMessage({
|
||||||
type: "load",
|
type: "load",
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -119,18 +121,20 @@ export class Plug<HookT> {
|
||||||
if (!funDef) {
|
if (!funDef) {
|
||||||
throw new Error(`Function ${name} not found in manifest`);
|
throw new Error(`Function ${name} not found in manifest`);
|
||||||
}
|
}
|
||||||
await this.sandbox.load(name, this.manifest!.functions[name].code!);
|
await this.sandbox.load(name, funDef.code!);
|
||||||
}
|
}
|
||||||
return await this.sandbox.invoke(name, args);
|
return await this.sandbox.invoke(name, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dispatchEvent(name: string, data?: any): Promise<any[]> {
|
async dispatchEvent(name: string, data?: any): Promise<any[]> {
|
||||||
|
if (!this.manifest!.hooks?.events) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
let functionsToSpawn = this.manifest!.hooks.events[name];
|
let functionsToSpawn = this.manifest!.hooks.events[name];
|
||||||
if (functionsToSpawn) {
|
if (functionsToSpawn) {
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
functionsToSpawn.map(
|
functionsToSpawn.map((functionToSpawn: string) =>
|
||||||
async (functionToSpawn: string) =>
|
this.invoke(functionToSpawn, [data])
|
||||||
await this.invoke(functionToSpawn, [data])
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -143,10 +147,21 @@ export class Plug<HookT> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class System<HookT> {
|
export type SystemJSON<HookT> = { [key: string]: Manifest<HookT> };
|
||||||
|
|
||||||
|
export type SystemEvents<HookT> = {
|
||||||
|
plugUpdated: (name: string, plug: Plug<HookT>) => void;
|
||||||
|
plugRemoved: (name: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
||||||
protected plugs = new Map<string, Plug<HookT>>();
|
protected plugs = new Map<string, Plug<HookT>>();
|
||||||
registeredSyscalls: SysCallMapping = {};
|
registeredSyscalls: SysCallMapping = {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
registerSyscalls(...registrationObjects: SysCallMapping[]) {
|
registerSyscalls(...registrationObjects: SysCallMapping[]) {
|
||||||
for (const registrationObject of registrationObjects) {
|
for (const registrationObject of registrationObjects) {
|
||||||
for (let p in registrationObject) {
|
for (let p in registrationObject) {
|
||||||
|
@ -171,23 +186,63 @@ export class System<HookT> {
|
||||||
manifest: Manifest<HookT>,
|
manifest: Manifest<HookT>,
|
||||||
sandbox: Sandbox
|
sandbox: Sandbox
|
||||||
): Promise<Plug<HookT>> {
|
): Promise<Plug<HookT>> {
|
||||||
|
if (this.plugs.has(name)) {
|
||||||
|
await this.unload(name);
|
||||||
|
}
|
||||||
const plug = new Plug(this, name, sandbox);
|
const plug = new Plug(this, name, sandbox);
|
||||||
await plug.load(manifest);
|
await plug.load(manifest);
|
||||||
this.plugs.set(name, plug);
|
this.plugs.set(name, plug);
|
||||||
return plug;
|
return plug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async unload(name: string) {
|
||||||
|
const plug = this.plugs.get(name);
|
||||||
|
if (!plug) {
|
||||||
|
throw Error(`Plug ${name} not found`);
|
||||||
|
}
|
||||||
|
await plug.stop();
|
||||||
|
this.plugs.delete(name);
|
||||||
|
}
|
||||||
|
|
||||||
async dispatchEvent(name: string, data?: any): Promise<any[]> {
|
async dispatchEvent(name: string, data?: any): Promise<any[]> {
|
||||||
let promises = [];
|
let promises = [];
|
||||||
for (let plug of this.plugs.values()) {
|
for (let plug of this.plugs.values()) {
|
||||||
promises.push(plug.dispatchEvent(name, data));
|
for (let result of await plug.dispatchEvent(name, data)) {
|
||||||
|
promises.push(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return await Promise.all(promises);
|
return await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async stop(): Promise<void[]> {
|
get loadedPlugs(): Map<string, Plug<HookT>> {
|
||||||
|
return this.plugs;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): SystemJSON<HookT> {
|
||||||
|
let plugJSON: { [key: string]: Manifest<HookT> } = {};
|
||||||
|
for (let [name, plug] of this.plugs) {
|
||||||
|
if (!plug.manifest) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
plugJSON[name] = plug.manifest;
|
||||||
|
}
|
||||||
|
return plugJSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
async replaceAllFromJSON(
|
||||||
|
json: SystemJSON<HookT>,
|
||||||
|
sandboxFactory: () => Sandbox
|
||||||
|
) {
|
||||||
|
await this.unloadAll();
|
||||||
|
for (let [name, manifest] of Object.entries(json)) {
|
||||||
|
console.log("Loading plug", name);
|
||||||
|
await this.load(name, manifest, sandboxFactory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async unloadAll(): Promise<void[]> {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
Array.from(this.plugs.values()).map((plug) => plug.stop())
|
Array.from(this.plugs.keys()).map(this.unload.bind(this))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,7 @@ declare global {
|
||||||
let postMessage = self.postMessage.bind(self);
|
let postMessage = self.postMessage.bind(self);
|
||||||
|
|
||||||
if (window.parent !== window) {
|
if (window.parent !== window) {
|
||||||
console.log("running in an iframe");
|
|
||||||
postMessage = window.parent.postMessage.bind(window.parent);
|
postMessage = window.parent.postMessage.bind(window.parent);
|
||||||
// postMessage({ type: "test" }, "*");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.syscall = async (id: number, name: string, args: any[]) => {
|
self.syscall = async (id: number, name: string, args: any[]) => {
|
||||||
|
@ -43,7 +41,6 @@ return fn["default"].apply(null, arguments);`;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener("message", (event: { data: WorkerMessage }) => {
|
self.addEventListener("message", (event: { data: WorkerMessage }) => {
|
||||||
// console.log("Got a message", event.data);
|
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
let messageEvent = event;
|
let messageEvent = event;
|
||||||
let data = messageEvent.data;
|
let data = messageEvent.data;
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
export type EventHook = {
|
|
||||||
events: { [key: string]: string[] };
|
|
||||||
};
|
|
||||||
|
|
||||||
export type WorkerMessageType = "load" | "invoke" | "syscall-response";
|
export type WorkerMessageType = "load" | "invoke" | "syscall-response";
|
||||||
|
|
||||||
export type WorkerMessage = {
|
export type WorkerMessage = {
|
||||||
|
@ -37,7 +33,30 @@ export interface FunctionDef {
|
||||||
code?: string;
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type EventHook = {
|
||||||
|
events?: { [key: string]: string[] };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EndpointHook = {
|
||||||
|
endpoints?: EndPointDef[];
|
||||||
|
};
|
||||||
|
export type EndPointDef = {
|
||||||
|
method: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS";
|
||||||
|
path: string;
|
||||||
|
handler: string; // function name
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CronHook = {
|
||||||
|
crons?: CronDef[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CronDef = {
|
||||||
|
cron: string;
|
||||||
|
handler: string; // function name
|
||||||
|
};
|
||||||
|
|
||||||
export interface WorkerLike {
|
export interface WorkerLike {
|
||||||
|
ready: Promise<void>;
|
||||||
onMessage?: (message: any) => Promise<void>;
|
onMessage?: (message: any) => Promise<void>;
|
||||||
postMessage(message: any): void;
|
postMessage(message: any): void;
|
||||||
terminate(): void;
|
terminate(): void;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { safeRun } from "./util";
|
||||||
class WebWorkerWrapper implements WorkerLike {
|
class WebWorkerWrapper implements WorkerLike {
|
||||||
private worker: Worker;
|
private worker: Worker;
|
||||||
onMessage?: (message: any) => Promise<void>;
|
onMessage?: (message: any) => Promise<void>;
|
||||||
|
ready: Promise<void>;
|
||||||
|
|
||||||
constructor(worker: Worker) {
|
constructor(worker: Worker) {
|
||||||
this.worker = worker;
|
this.worker = worker;
|
||||||
|
@ -15,6 +16,7 @@ class WebWorkerWrapper implements WorkerLike {
|
||||||
await this.onMessage!(data);
|
await this.onMessage!(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
this.ready = Promise.resolve();
|
||||||
}
|
}
|
||||||
postMessage(message: any): void {
|
postMessage(message: any): void {
|
||||||
this.worker.postMessage(message);
|
this.worker.postMessage(message);
|
||||||
|
|
|
@ -36,8 +36,16 @@
|
||||||
"events": {
|
"events": {
|
||||||
"page:click": ["taskToggle", "clickNavigate"],
|
"page:click": ["taskToggle", "clickNavigate"],
|
||||||
"editor:complete": ["pageComplete"],
|
"editor:complete": ["pageComplete"],
|
||||||
"page:index": ["indexLinks"]
|
"page:index": ["indexLinks"],
|
||||||
|
"load": ["welcome"]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/",
|
||||||
|
"handler": "endpointTest"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"functions": {
|
"functions": {
|
||||||
"indexLinks": {
|
"indexLinks": {
|
||||||
|
@ -76,8 +84,11 @@
|
||||||
"toggle_h2": {
|
"toggle_h2": {
|
||||||
"path": "./markup.ts:toggleH2"
|
"path": "./markup.ts:toggleH2"
|
||||||
},
|
},
|
||||||
"server_test": {
|
"endpointTest": {
|
||||||
"path": "./server.ts:test"
|
"path": "./server.ts:endpointTest"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"path": "./server.ts:welcome"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
import { syscall } from "./lib/syscall";
|
import { EndpointRequest, EndpointResponse } from "../../plugbox/endpoints";
|
||||||
export function test() {
|
|
||||||
console.log("I'm running on the server!");
|
export function endpointTest(req: EndpointRequest): EndpointResponse {
|
||||||
return 5;
|
console.log("I'm running on the server!", req);
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: "Hello world!",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function welcome() {
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
console.log("Welcome to you all!!!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import { Server } from "socket.io";
|
||||||
import { SocketServer } from "./api_server";
|
import { SocketServer } from "./api_server";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { SilverBulletHooks } from "../common/manifest";
|
||||||
|
import { System } from "../plugbox/runtime";
|
||||||
|
|
||||||
describe("Server test", () => {
|
describe("Server test", () => {
|
||||||
let io: Server,
|
let io: Server,
|
||||||
|
@ -38,7 +40,11 @@ describe("Server test", () => {
|
||||||
const port = httpServer.address().port;
|
const port = httpServer.address().port;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
clientSocket = new Client(`http://localhost:${port}`);
|
clientSocket = new Client(`http://localhost:${port}`);
|
||||||
socketServer = new SocketServer(tmpDir, io);
|
socketServer = new SocketServer(
|
||||||
|
tmpDir,
|
||||||
|
io,
|
||||||
|
new System<SilverBulletHooks>()
|
||||||
|
);
|
||||||
clientSocket.on("connect", done);
|
clientSocket.on("connect", done);
|
||||||
await socketServer.init();
|
await socketServer.init();
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,9 +4,7 @@ import * as path from "path";
|
||||||
import { IndexApi } from "./index_api";
|
import { IndexApi } from "./index_api";
|
||||||
import { PageApi } from "./page_api";
|
import { PageApi } from "./page_api";
|
||||||
import { System } from "../plugbox/runtime";
|
import { System } from "../plugbox/runtime";
|
||||||
import { createSandbox } from "../plugbox/node_sandbox";
|
import { SilverBulletHooks } from "../common/manifest";
|
||||||
import { NuggetHook } from "../webapp/types";
|
|
||||||
import corePlug from "../webapp/generated/core.plug.json";
|
|
||||||
import pageIndexSyscalls from "./syscalls/page_index";
|
import pageIndexSyscalls from "./syscalls/page_index";
|
||||||
|
|
||||||
export class ClientConnection {
|
export class ClientConnection {
|
||||||
|
@ -25,12 +23,16 @@ export class SocketServer {
|
||||||
private apis = new Map<string, ApiProvider>();
|
private apis = new Map<string, ApiProvider>();
|
||||||
readonly rootPath: string;
|
readonly rootPath: string;
|
||||||
private serverSocket: Server;
|
private serverSocket: Server;
|
||||||
system: System<NuggetHook>;
|
system: System<SilverBulletHooks>;
|
||||||
|
|
||||||
constructor(rootPath: string, serverSocket: Server) {
|
constructor(
|
||||||
|
rootPath: string,
|
||||||
|
serverSocket: Server,
|
||||||
|
system: System<SilverBulletHooks>
|
||||||
|
) {
|
||||||
this.rootPath = path.resolve(rootPath);
|
this.rootPath = path.resolve(rootPath);
|
||||||
this.serverSocket = serverSocket;
|
this.serverSocket = serverSocket;
|
||||||
this.system = new System<NuggetHook>();
|
this.system = system;
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerApi(name: string, apiProvider: ApiProvider) {
|
async registerApi(name: string, apiProvider: ApiProvider) {
|
||||||
|
@ -52,12 +54,6 @@ export class SocketServer {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let plug = await this.system.load(
|
|
||||||
"core",
|
|
||||||
corePlug,
|
|
||||||
createSandbox(this.system)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.serverSocket.on("connection", (socket) => {
|
this.serverSocket.on("connection", (socket) => {
|
||||||
const clientConn = new ClientConnection(socket);
|
const clientConn = new ClientConnection(socket);
|
||||||
|
|
||||||
|
@ -112,6 +108,9 @@ export class SocketServer {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Sending the sytem to the client");
|
||||||
|
socket.emit("loadSystem", this.system.toJSON());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Express } from "express";
|
||||||
|
import { System } from "../plugbox/runtime";
|
||||||
|
import { SilverBulletHooks } from "../common/manifest";
|
||||||
|
import { exposeSystem } from "../plugbox/endpoints";
|
||||||
|
import { readFile } from "fs/promises";
|
||||||
|
|
||||||
|
export class ExpressServer {
|
||||||
|
app: Express;
|
||||||
|
system: System<SilverBulletHooks>;
|
||||||
|
private rootPath: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
app: Express,
|
||||||
|
rootPath: string,
|
||||||
|
distDir: string,
|
||||||
|
system: System<SilverBulletHooks>
|
||||||
|
) {
|
||||||
|
this.app = app;
|
||||||
|
this.rootPath = rootPath;
|
||||||
|
this.system = system;
|
||||||
|
|
||||||
|
app.use(exposeSystem(this.system));
|
||||||
|
|
||||||
|
// Fallback, serve index.html
|
||||||
|
let cachedIndex: string | undefined = undefined;
|
||||||
|
app.get("/*", async (req, res) => {
|
||||||
|
if (!cachedIndex) {
|
||||||
|
cachedIndex = await readFile(`${distDir}/index.html`, "utf8");
|
||||||
|
}
|
||||||
|
res.status(200).header("Content-Type", "text/html").send(cachedIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {}
|
||||||
|
}
|
|
@ -10,20 +10,20 @@ import path from "path";
|
||||||
import { stat } from "fs/promises";
|
import { stat } from "fs/promises";
|
||||||
import { Cursor, cursorEffect } from "../webapp/cursorEffect";
|
import { Cursor, cursorEffect } from "../webapp/cursorEffect";
|
||||||
import { System } from "../plugbox/runtime";
|
import { System } from "../plugbox/runtime";
|
||||||
import { NuggetHook } from "../webapp/types";
|
import { SilverBulletHooks } from "../common/manifest";
|
||||||
|
|
||||||
export class PageApi implements ApiProvider {
|
export class PageApi implements ApiProvider {
|
||||||
openPages: Map<string, Page>;
|
openPages: Map<string, Page>;
|
||||||
pageStore: DiskStorage;
|
pageStore: DiskStorage;
|
||||||
rootPath: string;
|
rootPath: string;
|
||||||
connectedSockets: Set<Socket>;
|
connectedSockets: Set<Socket>;
|
||||||
private system: System<NuggetHook>;
|
private system: System<SilverBulletHooks>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
rootPath: string,
|
rootPath: string,
|
||||||
connectedSockets: Set<Socket>,
|
connectedSockets: Set<Socket>,
|
||||||
openPages: Map<string, Page>,
|
openPages: Map<string, Page>,
|
||||||
system: System<NuggetHook>
|
system: System<SilverBulletHooks>
|
||||||
) {
|
) {
|
||||||
this.pageStore = new DiskStorage(rootPath);
|
this.pageStore = new DiskStorage(rootPath);
|
||||||
this.rootPath = rootPath;
|
this.rootPath = rootPath;
|
||||||
|
@ -34,6 +34,20 @@ export class PageApi implements ApiProvider {
|
||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
this.fileWatcher();
|
this.fileWatcher();
|
||||||
|
this.system.on({
|
||||||
|
plugUpdated: (plugName, plugDef) => {
|
||||||
|
console.log("Plug updated on disk, broadcasting to all clients");
|
||||||
|
this.connectedSockets.forEach((socket) => {
|
||||||
|
socket.emit("plugUpdated", plugName, plugDef);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
plugRemoved: (plugName) => {
|
||||||
|
console.log("Plug removed on disk, broadcasting to all clients");
|
||||||
|
this.connectedSockets.forEach((socket) => {
|
||||||
|
socket.emit("plugRemoved", plugName);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastCursors(page: Page) {
|
broadcastCursors(page: Page) {
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { readFile } from "fs/promises";
|
|
||||||
import http from "http";
|
import http from "http";
|
||||||
import { Server } from "socket.io";
|
import { Server } from "socket.io";
|
||||||
import { SocketServer } from "./api_server";
|
import { SocketServer } from "./api_server";
|
||||||
import yargs from "yargs";
|
import yargs from "yargs";
|
||||||
import { hideBin } from "yargs/helpers";
|
import { hideBin } from "yargs/helpers";
|
||||||
|
import { System } from "../plugbox/runtime";
|
||||||
|
import { SilverBulletHooks } from "../common/manifest";
|
||||||
|
import { ExpressServer } from "./express_server";
|
||||||
|
import { DiskPlugLoader } from "../plugbox/plug_loader";
|
||||||
|
|
||||||
let args = yargs(hideBin(process.argv))
|
let args = yargs(hideBin(process.argv))
|
||||||
.option("debug", {
|
.option("debug", {
|
||||||
|
@ -16,8 +19,12 @@ let args = yargs(hideBin(process.argv))
|
||||||
})
|
})
|
||||||
.parse();
|
.parse();
|
||||||
|
|
||||||
|
const pagesPath = args._[0] as string;
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
const system = new System<SilverBulletHooks>();
|
||||||
|
|
||||||
const io = new Server(server, {
|
const io = new Server(server, {
|
||||||
cors: {
|
cors: {
|
||||||
methods: "GET,HEAD,PUT,OPTIONS,POST,DELETE",
|
methods: "GET,HEAD,PUT,OPTIONS,POST,DELETE",
|
||||||
|
@ -29,18 +36,26 @@ const port = args.port;
|
||||||
const distDir = `${__dirname}/../webapp`;
|
const distDir = `${__dirname}/../webapp`;
|
||||||
|
|
||||||
app.use("/", express.static(distDir));
|
app.use("/", express.static(distDir));
|
||||||
let socketServer = new SocketServer(args._[0] as string, io);
|
|
||||||
socketServer.init();
|
|
||||||
|
|
||||||
// Fallback, serve index.html
|
let socketServer = new SocketServer(pagesPath, io, system);
|
||||||
let cachedIndex: string | undefined = undefined;
|
socketServer.init().catch((e) => {
|
||||||
app.get("/*", async (req, res) => {
|
console.error(e);
|
||||||
if (!cachedIndex) {
|
|
||||||
cachedIndex = await readFile(`${distDir}/index.html`, "utf8");
|
|
||||||
}
|
|
||||||
res.status(200).header("Content-Type", "text/html").send(cachedIndex);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const expressServer = new ExpressServer(app, pagesPath, distDir, system);
|
||||||
|
expressServer
|
||||||
|
.init()
|
||||||
|
.then(async () => {
|
||||||
|
let plugLoader = new DiskPlugLoader(
|
||||||
|
system,
|
||||||
|
`${__dirname}/../../plugs/dist`
|
||||||
|
);
|
||||||
|
await plugLoader.loadPlugs();
|
||||||
|
plugLoader.watcher();
|
||||||
server.listen(port, () => {
|
server.listen(port, () => {
|
||||||
console.log(`Server listening on port ${port}`);
|
console.log(`Server listening on port ${port}`);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { promisify } from "util";
|
||||||
|
import { execFile } from "child_process";
|
||||||
|
|
||||||
|
const execFilePromise = promisify(execFile);
|
||||||
|
|
||||||
|
export default function (cwd: string) {
|
||||||
|
return {
|
||||||
|
"shell.run": async (cmd: string, args: string[]) => {
|
||||||
|
let { stdout, stderr } = await execFilePromise(cmd, args, {
|
||||||
|
cwd: cwd,
|
||||||
|
});
|
||||||
|
return { stdout, stderr };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -17,5 +17,5 @@ window.editor = editor;
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register(new URL("service_worker.ts", import.meta.url), { type: "module" })
|
.register(new URL("service_worker.ts", import.meta.url), { type: "module" })
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
console.log("Service worker registered", r);
|
// console.log("Service worker registered", r);
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
} from "@codemirror/view";
|
} from "@codemirror/view";
|
||||||
import { throttle } from "./util";
|
import { throttle } from "./util";
|
||||||
import { Cursor, cursorEffect } from "./cursorEffect";
|
import { Cursor, cursorEffect } from "./cursorEffect";
|
||||||
import { EventEmitter } from "./event";
|
import { EventEmitter } from "../common/event";
|
||||||
|
|
||||||
const throttleInterval = 250;
|
const throttleInterval = 250;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ import {
|
||||||
KeyBinding,
|
KeyBinding,
|
||||||
keymap,
|
keymap,
|
||||||
} from "@codemirror/view";
|
} from "@codemirror/view";
|
||||||
// import { debounce } from "lodash";
|
|
||||||
import React, { useEffect, useReducer } from "react";
|
import React, { useEffect, useReducer } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { Plug, System } from "../plugbox/runtime";
|
import { Plug, System } from "../plugbox/runtime";
|
||||||
|
@ -31,7 +30,6 @@ import { CommandPalette } from "./components/command_palette";
|
||||||
import { PageNavigator } from "./components/page_navigator";
|
import { PageNavigator } from "./components/page_navigator";
|
||||||
import { TopBar } from "./components/top_bar";
|
import { TopBar } from "./components/top_bar";
|
||||||
import { Cursor } from "./cursorEffect";
|
import { Cursor } from "./cursorEffect";
|
||||||
import coreManifest from "./generated/core.plug.json";
|
|
||||||
import { lineWrapper } from "./line_wrapper";
|
import { lineWrapper } from "./line_wrapper";
|
||||||
import { markdown } from "./markdown";
|
import { markdown } from "./markdown";
|
||||||
import { IPageNavigator, PathPageNavigator } from "./navigator";
|
import { IPageNavigator, PathPageNavigator } from "./navigator";
|
||||||
|
@ -49,9 +47,9 @@ import {
|
||||||
AppCommand,
|
AppCommand,
|
||||||
AppViewState,
|
AppViewState,
|
||||||
initialViewState,
|
initialViewState,
|
||||||
NuggetHook,
|
|
||||||
slashCommandRegexp,
|
slashCommandRegexp,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { SilverBulletHooks } from "../common/manifest";
|
||||||
import { safeRun } from "./util";
|
import { safeRun } from "./util";
|
||||||
|
|
||||||
class PageState {
|
class PageState {
|
||||||
|
@ -65,20 +63,19 @@ class PageState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Editor implements AppEventDispatcher {
|
export class Editor implements AppEventDispatcher {
|
||||||
|
private system = new System<SilverBulletHooks>();
|
||||||
editorView?: EditorView;
|
editorView?: EditorView;
|
||||||
viewState: AppViewState;
|
viewState: AppViewState;
|
||||||
viewDispatch: React.Dispatch<Action>;
|
viewDispatch: React.Dispatch<Action>;
|
||||||
openPages: Map<string, PageState>;
|
openPages: Map<string, PageState>;
|
||||||
space: Space;
|
space: Space;
|
||||||
editorCommands: Map<string, AppCommand>;
|
editorCommands: Map<string, AppCommand>;
|
||||||
plugs: Plug<NuggetHook>[];
|
|
||||||
navigationResolve?: (val: undefined) => void;
|
navigationResolve?: (val: undefined) => void;
|
||||||
pageNavigator: IPageNavigator;
|
pageNavigator: IPageNavigator;
|
||||||
|
|
||||||
constructor(space: Space, parent: Element) {
|
constructor(space: Space, parent: Element) {
|
||||||
this.editorCommands = new Map();
|
this.editorCommands = new Map();
|
||||||
this.openPages = new Map();
|
this.openPages = new Map();
|
||||||
this.plugs = [];
|
|
||||||
this.space = space;
|
this.space = space;
|
||||||
this.viewState = initialViewState;
|
this.viewState = initialViewState;
|
||||||
this.viewDispatch = () => {};
|
this.viewDispatch = () => {};
|
||||||
|
@ -91,6 +88,13 @@ export class Editor implements AppEventDispatcher {
|
||||||
parent: document.getElementById("editor")!,
|
parent: document.getElementById("editor")!,
|
||||||
});
|
});
|
||||||
this.pageNavigator = new PathPageNavigator();
|
this.pageNavigator = new PathPageNavigator();
|
||||||
|
|
||||||
|
this.system.registerSyscalls(
|
||||||
|
dbSyscalls,
|
||||||
|
editorSyscalls(this),
|
||||||
|
spaceSyscalls(this),
|
||||||
|
indexerSyscalls(this.space)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
@ -128,6 +132,39 @@ export class Editor implements AppEventDispatcher {
|
||||||
pages: pages,
|
pages: pages,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
loadSystem: (systemJSON) => {
|
||||||
|
safeRun(async () => {
|
||||||
|
console.log("Received SYSTEM", systemJSON);
|
||||||
|
await this.system.replaceAllFromJSON(systemJSON, () =>
|
||||||
|
createIFrameSandbox(this.system)
|
||||||
|
);
|
||||||
|
console.log("Loaded plugs, now updating editor comands");
|
||||||
|
this.editorCommands = new Map<string, AppCommand>();
|
||||||
|
for (let plug of this.system.loadedPlugs.values()) {
|
||||||
|
this.buildCommands(plug);
|
||||||
|
}
|
||||||
|
this.viewDispatch({
|
||||||
|
type: "update-commands",
|
||||||
|
commands: this.editorCommands,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
plugUpdated: (plugName, plug) => {
|
||||||
|
safeRun(async () => {
|
||||||
|
console.log("Plug updated", plugName);
|
||||||
|
await this.system.load(
|
||||||
|
plugName,
|
||||||
|
plug,
|
||||||
|
createIFrameSandbox(this.system)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
plugRemoved: (plugName) => {
|
||||||
|
safeRun(async () => {
|
||||||
|
console.log("Plug removed", plugName);
|
||||||
|
await this.system.unload(plugName);
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.pageNavigator.getCurrentPage() === "") {
|
if (this.pageNavigator.getCurrentPage() === "") {
|
||||||
|
@ -154,32 +191,16 @@ export class Editor implements AppEventDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPlugs() {
|
async loadPlugs() {
|
||||||
const system = new System<NuggetHook>();
|
const system = new System<SilverBulletHooks>();
|
||||||
system.registerSyscalls(
|
system.registerSyscalls(
|
||||||
dbSyscalls,
|
dbSyscalls,
|
||||||
editorSyscalls(this),
|
editorSyscalls(this),
|
||||||
spaceSyscalls(this),
|
spaceSyscalls(this),
|
||||||
indexerSyscalls(this.space)
|
indexerSyscalls(this.space)
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("Now loading core plug");
|
|
||||||
let mainPlug = await system.load(
|
|
||||||
"core",
|
|
||||||
coreManifest,
|
|
||||||
createIFrameSandbox(system)
|
|
||||||
);
|
|
||||||
this.plugs.push(mainPlug);
|
|
||||||
this.editorCommands = new Map<string, AppCommand>();
|
|
||||||
for (let plug of this.plugs) {
|
|
||||||
this.buildCommands(plug);
|
|
||||||
}
|
|
||||||
this.viewDispatch({
|
|
||||||
type: "update-commands",
|
|
||||||
commands: this.editorCommands,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCommands(plug: Plug<NuggetHook>) {
|
private buildCommands(plug: Plug<SilverBulletHooks>) {
|
||||||
const cmds = plug.manifest!.hooks.commands;
|
const cmds = plug.manifest!.hooks.commands;
|
||||||
for (let name in cmds) {
|
for (let name in cmds) {
|
||||||
let cmd = cmds[name];
|
let cmd = cmds[name];
|
||||||
|
@ -192,18 +213,8 @@ export class Editor implements AppEventDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Parallelize?
|
|
||||||
async dispatchAppEvent(name: AppEvent, data?: any): Promise<any[]> {
|
async dispatchAppEvent(name: AppEvent, data?: any): Promise<any[]> {
|
||||||
let results: any[] = [];
|
return this.system.dispatchEvent(name, data);
|
||||||
for (let plug of this.plugs) {
|
|
||||||
let plugResults = await plug.dispatchEvent(name, data);
|
|
||||||
if (plugResults) {
|
|
||||||
for (let result of plugResults) {
|
|
||||||
results.push(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentPage(): string | undefined {
|
get currentPage(): string | undefined {
|
||||||
|
@ -338,6 +349,7 @@ export class Editor implements AppEventDispatcher {
|
||||||
|
|
||||||
async plugCompleter(): Promise<CompletionResult | null> {
|
async plugCompleter(): Promise<CompletionResult | null> {
|
||||||
let allCompletionResults = await this.dispatchAppEvent("editor:complete");
|
let allCompletionResults = await this.dispatchAppEvent("editor:complete");
|
||||||
|
console.log("Completion results", allCompletionResults);
|
||||||
if (allCompletionResults.length === 1) {
|
if (allCompletionResults.length === 1) {
|
||||||
return allCompletionResults[0];
|
return allCompletionResults[0];
|
||||||
} else if (allCompletionResults.length > 1) {
|
} else if (allCompletionResults.length > 1) {
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { ChangeSet, Text, Transaction } from "@codemirror/state";
|
||||||
|
|
||||||
import { CollabDocument, CollabEvents } from "./collab";
|
import { CollabDocument, CollabEvents } from "./collab";
|
||||||
import { cursorEffect } from "./cursorEffect";
|
import { cursorEffect } from "./cursorEffect";
|
||||||
import { EventEmitter } from "./event";
|
import { EventEmitter } from "../common/event";
|
||||||
|
import { SystemJSON } from "../plugbox/runtime";
|
||||||
|
import { Manifest } from "../common/manifest";
|
||||||
|
|
||||||
export type SpaceEvents = {
|
export type SpaceEvents = {
|
||||||
connect: () => void;
|
connect: () => void;
|
||||||
|
@ -13,6 +15,9 @@ export type SpaceEvents = {
|
||||||
pageChanged: (meta: PageMeta) => void;
|
pageChanged: (meta: PageMeta) => void;
|
||||||
pageDeleted: (name: string) => void;
|
pageDeleted: (name: string) => void;
|
||||||
pageListUpdated: (pages: Set<PageMeta>) => void;
|
pageListUpdated: (pages: Set<PageMeta>) => void;
|
||||||
|
loadSystem: (systemJSON: SystemJSON<any>) => void;
|
||||||
|
plugUpdated: (plugName: string, plug: Manifest) => void;
|
||||||
|
plugRemoved: (plugName: string) => void;
|
||||||
} & CollabEvents;
|
} & CollabEvents;
|
||||||
|
|
||||||
export type KV = {
|
export type KV = {
|
||||||
|
@ -35,6 +40,9 @@ export class Space extends EventEmitter<SpaceEvents> {
|
||||||
"pageCreated",
|
"pageCreated",
|
||||||
"pageChanged",
|
"pageChanged",
|
||||||
"pageDeleted",
|
"pageDeleted",
|
||||||
|
"loadSystem",
|
||||||
|
"plugUpdated",
|
||||||
|
"plugRemoved",
|
||||||
].forEach((eventName) => {
|
].forEach((eventName) => {
|
||||||
socket.on(eventName, (...args) => {
|
socket.on(eventName, (...args) => {
|
||||||
this.emit(eventName as keyof SpaceEvents, ...args);
|
this.emit(eventName as keyof SpaceEvents, ...args);
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
import * as plugbox from "../plugbox/types";
|
import { CommandDef } from "../common/manifest";
|
||||||
|
|
||||||
export type NuggetHook = {
|
|
||||||
commands: {
|
|
||||||
[key: string]: CommandDef;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Manifest = plugbox.Manifest<NuggetHook>;
|
|
||||||
|
|
||||||
export type PageMeta = {
|
export type PageMeta = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -22,19 +14,6 @@ export type AppCommand = {
|
||||||
|
|
||||||
export const slashCommandRegexp = /\/[\w\-]*/;
|
export const slashCommandRegexp = /\/[\w\-]*/;
|
||||||
|
|
||||||
export interface CommandDef {
|
|
||||||
// Function name to invoke
|
|
||||||
invoke: string;
|
|
||||||
|
|
||||||
// Bind to keyboard shortcut
|
|
||||||
key?: string;
|
|
||||||
mac?: string;
|
|
||||||
|
|
||||||
// If to show in slash invoked menu and if so, with what label
|
|
||||||
// should match slashCommandRegexp
|
|
||||||
slashCommand?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
id: number;
|
id: number;
|
||||||
message: string;
|
message: string;
|
||||||
|
|
138
yarn.lock
138
yarn.lock
|
@ -1553,6 +1553,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
|
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
|
||||||
integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==
|
integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==
|
||||||
|
|
||||||
|
"@types/cookiejar@*":
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8"
|
||||||
|
integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==
|
||||||
|
|
||||||
"@types/cors@^2.8.12":
|
"@types/cors@^2.8.12":
|
||||||
version "2.8.12"
|
version "2.8.12"
|
||||||
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
|
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
|
||||||
|
@ -1621,6 +1626,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
||||||
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
|
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
|
||||||
|
|
||||||
|
"@types/node-cron@^3.0.1":
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.1.tgz#e01a874d4c2aa1a02ebc64cfd1cd8ebdbad7a996"
|
||||||
|
integrity sha512-BkMHHonDT8NJUE/pQ3kr5v2GLDKm5or9btLBoBx4F2MB2cuqYC748LYMDC55VlrLI5qZZv+Qgc3m4P3dBPcmeg==
|
||||||
|
|
||||||
"@types/node@*", "@types/node@>=10.0.0", "@types/node@^17.0.21":
|
"@types/node@*", "@types/node@>=10.0.0", "@types/node@^17.0.21":
|
||||||
version "17.0.21"
|
version "17.0.21"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644"
|
||||||
|
@ -1685,6 +1695,21 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||||
|
|
||||||
|
"@types/superagent@*":
|
||||||
|
version "4.1.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.15.tgz#63297de457eba5e2bc502a7609426c4cceab434a"
|
||||||
|
integrity sha512-mu/N4uvfDN2zVQQ5AYJI/g4qxn2bHB6521t1UuH09ShNWjebTqN0ZFuYK9uYjcgmI0dTQEs+Owi1EO6U0OkOZQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/cookiejar" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/supertest@^2.0.11":
|
||||||
|
version "2.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.11.tgz#2e70f69f220bc77b4f660d72c2e1a4231f44a77d"
|
||||||
|
integrity sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==
|
||||||
|
dependencies:
|
||||||
|
"@types/superagent" "*"
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "21.0.0"
|
version "21.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
|
||||||
|
@ -1838,6 +1863,11 @@ array-flatten@1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||||
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||||
|
|
||||||
|
asap@^2.0.0:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||||
|
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
||||||
|
|
||||||
asn1.js@^5.2.0:
|
asn1.js@^5.2.0:
|
||||||
version "5.4.1"
|
version "5.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
|
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
|
||||||
|
@ -2395,7 +2425,7 @@ commander@^8.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||||
|
|
||||||
component-emitter@~1.3.0:
|
component-emitter@^1.3.0, component-emitter@~1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||||
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
||||||
|
@ -2451,6 +2481,11 @@ cookie@0.4.2, cookie@~0.4.1:
|
||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
|
||||||
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
||||||
|
|
||||||
|
cookiejar@^2.1.3:
|
||||||
|
version "2.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc"
|
||||||
|
integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
core-util-is@~1.0.0:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||||
|
@ -2672,7 +2707,7 @@ debug@2.6.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@~4.3.1, debug@~4.3.2:
|
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@~4.3.1, debug@~4.3.2:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||||
|
@ -2792,6 +2827,14 @@ dexie@^3.2.1:
|
||||||
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.1.tgz#ef21456d725e700c1ab7ac4307896e4fdabaf753"
|
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.1.tgz#ef21456d725e700c1ab7ac4307896e4fdabaf753"
|
||||||
integrity sha512-Y8oz3t2XC9hvjkP35B5I8rUkKKwM36GGRjWQCMjzIYScg7W+GHKDXobSYswkisW7CxL1/tKQtggMDsiWqDUc1g==
|
integrity sha512-Y8oz3t2XC9hvjkP35B5I8rUkKKwM36GGRjWQCMjzIYScg7W+GHKDXobSYswkisW7CxL1/tKQtggMDsiWqDUc1g==
|
||||||
|
|
||||||
|
dezalgo@1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456"
|
||||||
|
integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=
|
||||||
|
dependencies:
|
||||||
|
asap "^2.0.0"
|
||||||
|
wrappy "1"
|
||||||
|
|
||||||
diff-sequences@^27.5.1:
|
diff-sequences@^27.5.1:
|
||||||
version "27.5.1"
|
version "27.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
|
||||||
|
@ -3287,6 +3330,11 @@ fast-levenshtein@~2.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||||
|
|
||||||
|
fast-safe-stringify@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
|
||||||
|
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
|
||||||
|
|
||||||
fb-watchman@^2.0.0:
|
fb-watchman@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85"
|
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85"
|
||||||
|
@ -3341,6 +3389,25 @@ form-data@^3.0.0:
|
||||||
combined-stream "^1.0.8"
|
combined-stream "^1.0.8"
|
||||||
mime-types "^2.1.12"
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
|
form-data@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||||
|
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||||
|
dependencies:
|
||||||
|
asynckit "^0.4.0"
|
||||||
|
combined-stream "^1.0.8"
|
||||||
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
|
formidable@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff"
|
||||||
|
integrity sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==
|
||||||
|
dependencies:
|
||||||
|
dezalgo "1.0.3"
|
||||||
|
hexoid "1.0.0"
|
||||||
|
once "1.4.0"
|
||||||
|
qs "6.9.3"
|
||||||
|
|
||||||
forwarded@0.2.0:
|
forwarded@0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||||
|
@ -3577,6 +3644,11 @@ hash.js@^1.0.0, hash.js@^1.0.3:
|
||||||
inherits "^2.0.3"
|
inherits "^2.0.3"
|
||||||
minimalistic-assert "^1.0.1"
|
minimalistic-assert "^1.0.1"
|
||||||
|
|
||||||
|
hexoid@1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
||||||
|
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
|
||||||
|
|
||||||
hmac-drbg@^1.0.1:
|
hmac-drbg@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||||
|
@ -4666,7 +4738,7 @@ merge-stream@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||||
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
||||||
|
|
||||||
methods@~1.1.2:
|
methods@^1.1.2, methods@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||||
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||||
|
@ -4704,7 +4776,7 @@ mime@1.6.0:
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
mime@^2.4.4:
|
mime@^2.4.4, mime@^2.5.0:
|
||||||
version "2.6.0"
|
version "2.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
|
||||||
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
|
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
|
||||||
|
@ -4751,6 +4823,18 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||||
|
|
||||||
|
moment-timezone@^0.5.31:
|
||||||
|
version "0.5.34"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
|
||||||
|
integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
|
||||||
|
dependencies:
|
||||||
|
moment ">= 2.9.0"
|
||||||
|
|
||||||
|
"moment@>= 2.9.0":
|
||||||
|
version "2.29.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||||
|
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
@ -4823,6 +4907,13 @@ node-addon-api@^4.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
|
||||||
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
|
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
|
||||||
|
|
||||||
|
node-cron@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.0.tgz#b33252803e430f9cd8590cf85738efa1497a9522"
|
||||||
|
integrity sha512-DDwIvvuCwrNiaU7HEivFDULcaQualDv7KoNlB/UU1wPW0n1tDEmBJKhEIE6DlF2FuoOHcNbLJ8ITL2Iv/3AWmA==
|
||||||
|
dependencies:
|
||||||
|
moment-timezone "^0.5.31"
|
||||||
|
|
||||||
node-gyp-build@^4.2.3, node-gyp-build@^4.3.0:
|
node-gyp-build@^4.2.3, node-gyp-build@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
|
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
|
||||||
|
@ -4955,7 +5046,7 @@ on-finished@~2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ee-first "1.1.1"
|
ee-first "1.1.1"
|
||||||
|
|
||||||
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
once@1.4.0, once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||||
|
@ -5538,11 +5629,23 @@ pupa@^2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
escape-goat "^2.0.0"
|
escape-goat "^2.0.0"
|
||||||
|
|
||||||
|
qs@6.9.3:
|
||||||
|
version "6.9.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e"
|
||||||
|
integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==
|
||||||
|
|
||||||
qs@6.9.7:
|
qs@6.9.7:
|
||||||
version "6.9.7"
|
version "6.9.7"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
|
||||||
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
|
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
|
||||||
|
|
||||||
|
qs@^6.10.1:
|
||||||
|
version "6.10.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
|
||||||
|
integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
querystring-es3@^0.2.1:
|
querystring-es3@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||||
|
@ -6120,6 +6223,31 @@ stylehacks@^*:
|
||||||
browserslist "^4.16.6"
|
browserslist "^4.16.6"
|
||||||
postcss-selector-parser "^6.0.4"
|
postcss-selector-parser "^6.0.4"
|
||||||
|
|
||||||
|
superagent@^7.1.0:
|
||||||
|
version "7.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/superagent/-/superagent-7.1.1.tgz#2ab187d38c3078c31c3771c0b751f10163a27136"
|
||||||
|
integrity sha512-CQ2weSS6M+doIwwYFoMatklhRbx6sVNdB99OEJ5czcP3cng76Ljqus694knFWgOj3RkrtxZqIgpe6vhe0J7QWQ==
|
||||||
|
dependencies:
|
||||||
|
component-emitter "^1.3.0"
|
||||||
|
cookiejar "^2.1.3"
|
||||||
|
debug "^4.3.3"
|
||||||
|
fast-safe-stringify "^2.1.1"
|
||||||
|
form-data "^4.0.0"
|
||||||
|
formidable "^2.0.1"
|
||||||
|
methods "^1.1.2"
|
||||||
|
mime "^2.5.0"
|
||||||
|
qs "^6.10.1"
|
||||||
|
readable-stream "^3.6.0"
|
||||||
|
semver "^7.3.5"
|
||||||
|
|
||||||
|
supertest@^6.2.2:
|
||||||
|
version "6.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/supertest/-/supertest-6.2.2.tgz#04a5998fd3efaff187cb69f07a169755d655b001"
|
||||||
|
integrity sha512-wCw9WhAtKJsBvh07RaS+/By91NNE0Wh0DN19/hWPlBOU8tAfOtbZoVSV4xXeoKoxgPx0rx2y+y+8660XtE7jzg==
|
||||||
|
dependencies:
|
||||||
|
methods "^1.1.2"
|
||||||
|
superagent "^7.1.0"
|
||||||
|
|
||||||
supports-color@^5.3.0, supports-color@^5.5.0:
|
supports-color@^5.3.0, supports-color@^5.5.0:
|
||||||
version "5.5.0"
|
version "5.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||||
|
|
Loading…
Reference in New Issue