Cleanup
This commit is contained in:
parent
b6046ca974
commit
653e77c4dd
4
mobile/.expo-shared/assets.json
Normal file
4
mobile/.expo-shared/assets.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
|
||||
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
|
||||
}
|
14
mobile/.gitignore
vendored
Normal file
14
mobile/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
node_modules/
|
||||
.expo/
|
||||
dist/
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
47
mobile/App.tsx
Normal file
47
mobile/App.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import React from "react";
|
||||
import { SafeAreaView, StyleSheet, Text, View } from "react-native";
|
||||
import { WebView } from "react-native-webview";
|
||||
|
||||
function safeRun(fn: () => Promise<void>) {
|
||||
return fn().catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const html = require("./bundle.json");
|
||||
let ref = React.useRef<WebView>(null);
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<Text
|
||||
style={{
|
||||
color: "#fff",
|
||||
backgroundColor: "#333",
|
||||
height: 40,
|
||||
}}
|
||||
onPress={() => {
|
||||
ref.current?.injectJavaScript('receiveMessage("Sup");');
|
||||
}}
|
||||
>
|
||||
This is a header
|
||||
</Text>
|
||||
<WebView
|
||||
style={styles.container}
|
||||
ref={ref}
|
||||
originWhitelist={["*"]}
|
||||
source={{ html: html.html }}
|
||||
onMessage={(event) => {
|
||||
console.log("Got event", event.nativeEvent.data);
|
||||
}}
|
||||
/>
|
||||
<StatusBar style="auto" />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
32
mobile/app.json
Normal file
32
mobile/app.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "mobile",
|
||||
"slug": "mobile",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
}
|
||||
}
|
||||
}
|
BIN
mobile/assets/adaptive-icon.png
Normal file
BIN
mobile/assets/adaptive-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
mobile/assets/favicon.png
Normal file
BIN
mobile/assets/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
mobile/assets/icon.png
Normal file
BIN
mobile/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
mobile/assets/splash.png
Normal file
BIN
mobile/assets/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
6
mobile/babel.config.js
Normal file
6
mobile/babel.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = function(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
};
|
||||
};
|
1
mobile/bla.json
Normal file
1
mobile/bla.json
Normal file
@ -0,0 +1 @@
|
||||
{ "html": "<h1><center>Hello world!!</center></h1>" }
|
6
mobile/build.py
Normal file
6
mobile/build.py
Normal file
@ -0,0 +1,6 @@
|
||||
import json
|
||||
|
||||
html = open("dist/index.html", "r").read()
|
||||
f = open("bundle.json", "w")
|
||||
f.write(json.dumps({"html": html}))
|
||||
f.close()
|
1
mobile/bundle.json
Normal file
1
mobile/bundle.json
Normal file
File diff suppressed because one or more lines are too long
48
mobile/html/boot.ts
Normal file
48
mobile/html/boot.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Editor } from "../../webapp/src/editor";
|
||||
import { HttpRemoteSpace } from "../../webapp/src/space";
|
||||
|
||||
declare namespace window {
|
||||
var ReactNativeWebView: any;
|
||||
var receiveMessage: any;
|
||||
}
|
||||
|
||||
function safeRun(fn: () => Promise<void>) {
|
||||
fn().catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
window.receiveMessage = (msg: string) => {
|
||||
console.log("Received message", msg);
|
||||
};
|
||||
// @ts-ignore
|
||||
window.onerror = (msg, source, lineno, colno, error) => {
|
||||
console.error("Error", msg, source, lineno, error);
|
||||
};
|
||||
|
||||
console.log = (...args) => {
|
||||
window.ReactNativeWebView.postMessage(
|
||||
JSON.stringify({ type: "console.log", args: args })
|
||||
);
|
||||
};
|
||||
|
||||
console.error = (...args) => {
|
||||
window.ReactNativeWebView.postMessage(
|
||||
JSON.stringify({ type: "console.error", args: args })
|
||||
);
|
||||
};
|
||||
try {
|
||||
let editor = new Editor(
|
||||
new HttpRemoteSpace(`http://192.168.2.22:3000/fs`, null),
|
||||
document.getElementById("root")!
|
||||
);
|
||||
console.log("Initing editor");
|
||||
safeRun(async () => {
|
||||
await editor.loadPageList();
|
||||
await editor.loadPlugs();
|
||||
editor.focus();
|
||||
console.log("Inited", editor.viewState);
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error("Got an error", e.message);
|
||||
}
|
19
mobile/html/index.html
Normal file
19
mobile/html/index.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Page</title>
|
||||
<style>
|
||||
@import "../../webapp/src/styles/main.scss";
|
||||
</style>
|
||||
<script type="module" src="boot.ts"></script>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module">
|
||||
import "./boot";
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
31
mobile/package.json
Normal file
31
mobile/package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "mobile",
|
||||
"version": "1.0.0",
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "~44.0.0",
|
||||
"expo-status-bar": "~1.2.0",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"react-native": "0.64.3",
|
||||
"react-native-fs": "^2.19.0",
|
||||
"react-native-web": "0.17.1",
|
||||
"react-native-webview": "11.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@parcel/transformer-sass": "2.3.2",
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-native": "~0.64.12",
|
||||
"parcel": "^2.3.2",
|
||||
"typescript": "~4.3.5"
|
||||
},
|
||||
"private": true
|
||||
}
|
6
mobile/tsconfig.json
Normal file
6
mobile/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
}
|
||||
}
|
6892
mobile/yarn.lock
Normal file
6892
mobile/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,22 +7,39 @@ import path from "path";
|
||||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
|
||||
async function compile(filePath, sourceMap) {
|
||||
let tempFile = "out.js";
|
||||
async function compile(filePath, functionName, debug) {
|
||||
let outFile = "out.js";
|
||||
|
||||
let inFile = filePath;
|
||||
|
||||
if (functionName) {
|
||||
// Generate a new file importing just this one function and exporting it
|
||||
inFile = "in.js";
|
||||
await writeFile(
|
||||
inFile,
|
||||
`import {${functionName}} from "./${filePath}";
|
||||
export default ${functionName};`
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Figure out how to make source maps work correctly with eval() code
|
||||
let js = await esbuild.build({
|
||||
entryPoints: [filePath],
|
||||
entryPoints: [inFile],
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
globalName: "mod",
|
||||
platform: "neutral",
|
||||
sourcemap: sourceMap ? "inline" : false,
|
||||
minify: true,
|
||||
outfile: tempFile,
|
||||
sourcemap: false, //sourceMap ? "inline" : false,
|
||||
minify: !debug,
|
||||
outfile: outFile,
|
||||
});
|
||||
|
||||
let jsCode = (await readFile(tempFile)).toString();
|
||||
let jsCode = (await readFile(outFile)).toString();
|
||||
jsCode = jsCode.replace(/^var mod ?= ?/, "");
|
||||
await unlink(tempFile);
|
||||
await unlink(outFile);
|
||||
if (inFile !== filePath) {
|
||||
await unlink(inFile);
|
||||
}
|
||||
return jsCode;
|
||||
}
|
||||
|
||||
@ -35,13 +52,10 @@ async function bundle(manifestPath, sourceMaps) {
|
||||
filePath = path.join(rootPath, def.path);
|
||||
if (filePath.indexOf(":") !== -1) {
|
||||
[filePath, jsFunctionName] = filePath.split(":");
|
||||
} else if (!jsFunctionName) {
|
||||
jsFunctionName = "default";
|
||||
}
|
||||
|
||||
def.code = await compile(filePath, sourceMaps);
|
||||
def.path = filePath;
|
||||
def.functionName = jsFunctionName;
|
||||
def.code = await compile(filePath, jsFunctionName, sourceMaps);
|
||||
delete def.path;
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
88
plugbox/src/function_worker.ts
Normal file
88
plugbox/src/function_worker.ts
Normal file
@ -0,0 +1,88 @@
|
||||
declare global {
|
||||
function syscall(id: string, name: string, args: any[]): Promise<any>;
|
||||
}
|
||||
import { safeRun } from "./util";
|
||||
let func: Function | null = null;
|
||||
let pendingRequests = new Map<string, (result: unknown) => void>();
|
||||
|
||||
self.syscall = async (id: string, name: string, args: any[]) => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
pendingRequests.set(id, resolve);
|
||||
self.postMessage({
|
||||
type: "syscall",
|
||||
id,
|
||||
name,
|
||||
args,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
self.addEventListener("result", (event) => {
|
||||
let customEvent = event as CustomEvent;
|
||||
self.postMessage({
|
||||
type: "result",
|
||||
result: customEvent.detail,
|
||||
});
|
||||
});
|
||||
|
||||
self.addEventListener("app-error", (event) => {
|
||||
let customEvent = event as CustomEvent;
|
||||
self.postMessage({
|
||||
type: "error",
|
||||
reason: customEvent.detail,
|
||||
});
|
||||
});
|
||||
|
||||
function wrapScript(code: string): string {
|
||||
return `const fn = ${code};
|
||||
return fn["default"].apply(null, arguments);`;
|
||||
}
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
safeRun(async () => {
|
||||
let messageEvent = event;
|
||||
let data = messageEvent.data;
|
||||
switch (data.type) {
|
||||
case "boot":
|
||||
console.log("Booting", data.name);
|
||||
func = new Function(wrapScript(data.code));
|
||||
self.postMessage({
|
||||
type: "inited",
|
||||
});
|
||||
break;
|
||||
case "invoke":
|
||||
if (!func) {
|
||||
throw new Error("No function loaded");
|
||||
}
|
||||
try {
|
||||
let result = await Promise.resolve(func(...(data.args || [])));
|
||||
self.postMessage({
|
||||
type: "result",
|
||||
result: result,
|
||||
});
|
||||
} catch (e: any) {
|
||||
self.postMessage({
|
||||
type: "error",
|
||||
reason: e.message,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
|
||||
break;
|
||||
case "syscall-response":
|
||||
let id = data.id;
|
||||
const lookup = pendingRequests.get(id);
|
||||
if (!lookup) {
|
||||
console.log(
|
||||
"Current outstanding requests",
|
||||
pendingRequests,
|
||||
"looking up",
|
||||
id
|
||||
);
|
||||
throw Error("Invalid request id");
|
||||
}
|
||||
pendingRequests.delete(id);
|
||||
lookup(data.data);
|
||||
}
|
||||
});
|
||||
});
|
@ -13,18 +13,17 @@ export class FunctionWorker {
|
||||
private invokeReject?: (reason?: any) => void;
|
||||
private plug: Plug<any>;
|
||||
|
||||
constructor(plug: Plug<any>, pathPrefix: string, name: string) {
|
||||
let worker = window.Worker;
|
||||
this.worker = new worker("/function_worker.js");
|
||||
constructor(plug: Plug<any>, name: string, code: string) {
|
||||
// let worker = window.Worker;
|
||||
this.worker = new Worker(new URL("function_worker.ts", import.meta.url), {
|
||||
type: "module",
|
||||
});
|
||||
|
||||
// console.log("Starting worker", this.worker);
|
||||
this.worker.onmessage = this.onmessage.bind(this);
|
||||
this.worker.postMessage({
|
||||
type: "boot",
|
||||
prefix: pathPrefix,
|
||||
name: name,
|
||||
// @ts-ignore
|
||||
userAgent: navigator.userAgent,
|
||||
code: code,
|
||||
});
|
||||
this.inited = new Promise((resolve) => {
|
||||
this.initCallback = resolve;
|
||||
@ -81,33 +80,31 @@ export interface PlugLoader<HookT> {
|
||||
}
|
||||
|
||||
export class Plug<HookT> {
|
||||
pathPrefix: string;
|
||||
system: System<HookT>;
|
||||
private runningFunctions: Map<string, FunctionWorker>;
|
||||
public manifest?: Manifest<HookT>;
|
||||
private name: string;
|
||||
|
||||
constructor(system: System<HookT>, pathPrefix: string, name: string) {
|
||||
this.name = name;
|
||||
this.pathPrefix = `${pathPrefix}/${name}`;
|
||||
constructor(system: System<HookT>, name: string) {
|
||||
this.system = system;
|
||||
this.runningFunctions = new Map<string, FunctionWorker>();
|
||||
}
|
||||
|
||||
async load(manifest: Manifest<HookT>) {
|
||||
this.manifest = manifest;
|
||||
await this.system.plugLoader.load(this.name, manifest);
|
||||
await this.dispatchEvent("load");
|
||||
}
|
||||
|
||||
async invoke(name: string, args: Array<any>): Promise<any> {
|
||||
if (!this.runningFunctions.has(name)) {
|
||||
this.runningFunctions.set(
|
||||
let worker = this.runningFunctions.get(name);
|
||||
if (!worker) {
|
||||
worker = new FunctionWorker(
|
||||
this,
|
||||
name,
|
||||
new FunctionWorker(this, this.pathPrefix, name)
|
||||
this.manifest!.functions[name].code!
|
||||
);
|
||||
this.runningFunctions.set(name, worker);
|
||||
}
|
||||
return await this.runningFunctions.get(name)!.invoke(args);
|
||||
return await worker.invoke(args);
|
||||
}
|
||||
|
||||
async dispatchEvent(name: string, data?: any): Promise<any[]> {
|
||||
@ -137,13 +134,9 @@ export class Plug<HookT> {
|
||||
|
||||
export class System<HookT> {
|
||||
protected plugs: Map<string, Plug<HookT>>;
|
||||
protected pathPrefix: string;
|
||||
registeredSyscalls: SysCallMapping;
|
||||
plugLoader: PlugLoader<HookT>;
|
||||
|
||||
constructor(plugLoader: PlugLoader<HookT>, pathPrefix: string) {
|
||||
this.plugLoader = plugLoader;
|
||||
this.pathPrefix = pathPrefix;
|
||||
constructor() {
|
||||
this.plugs = new Map<string, Plug<HookT>>();
|
||||
this.registeredSyscalls = {};
|
||||
}
|
||||
@ -168,7 +161,7 @@ export class System<HookT> {
|
||||
}
|
||||
|
||||
async load(name: string, manifest: Manifest<HookT>): Promise<Plug<HookT>> {
|
||||
const plug = new Plug(this, this.pathPrefix, name);
|
||||
const plug = new Plug(this, name);
|
||||
await plug.load(manifest);
|
||||
this.plugs.set(name, plug);
|
||||
return plug;
|
||||
|
@ -10,7 +10,6 @@ export interface Manifest<HookT> {
|
||||
}
|
||||
|
||||
export interface FunctionDef {
|
||||
path: string;
|
||||
functionName?: string;
|
||||
path?: string;
|
||||
code?: string;
|
||||
}
|
||||
|
@ -1,13 +1,6 @@
|
||||
export function safeRun(fn: () => Promise<void>) {
|
||||
fn().catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, ms);
|
||||
// console.error(e);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
@ -1,16 +1,21 @@
|
||||
export function syscall(name: string, ...args: any[]): any {
|
||||
let reqId = Math.floor(Math.random() * 1000000);
|
||||
// console.log("Syscall", name, reqId);
|
||||
return new Promise((resolve, reject) => {
|
||||
self.dispatchEvent(
|
||||
new CustomEvent("syscall", {
|
||||
detail: {
|
||||
id: reqId,
|
||||
name: name,
|
||||
args: args,
|
||||
callback: resolve,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
declare global {
|
||||
function syscall(id: string, name: string, args: any[]): Promise<any>;
|
||||
}
|
||||
|
||||
export async function syscall(name: string, ...args: any[]): Promise<any> {
|
||||
let reqId = "" + Math.floor(Math.random() * 1000000);
|
||||
// console.log("Syscall", name, reqId);
|
||||
return await self.syscall(reqId, name, args);
|
||||
// return new Promise((resolve, reject) => {
|
||||
// self.dispatchEvent(
|
||||
// new CustomEvent("syscall", {
|
||||
// detail: {
|
||||
// id: reqId,
|
||||
// name: name,
|
||||
// args: args,
|
||||
// callback: resolve,
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
// });
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ async function navigate(syntaxNode: any) {
|
||||
if (match) {
|
||||
await syscall("editor.openUrl", match[1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ export async function indexLinks({ name, text }: IndexEvent) {
|
||||
});
|
||||
}
|
||||
console.log("Found", backLinks.length, "wiki link(s)");
|
||||
// throw Error("Boom");
|
||||
await syscall("indexer.batchSet", name, backLinks);
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@
|
||||
"license": "MIT",
|
||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||
"scripts": {
|
||||
"start": "mkdir -p dist && cp src/function_worker.js dist/ && parcel",
|
||||
"build": "parcel build && cp src/function_worker.js dist/",
|
||||
"start": "parcel",
|
||||
"build": "parcel build",
|
||||
"clean": "rm -rf dist",
|
||||
"check-watch": "tsc --noEmit --watch"
|
||||
},
|
||||
|
@ -37,7 +37,7 @@ import { lineWrapper } from "./lineWrapper";
|
||||
import { markdown } from "./markdown";
|
||||
import { IPageNavigator, PathPageNavigator } from "./navigator";
|
||||
import customMarkDown from "./parser";
|
||||
import { BrowserSystem } from "./plugbox_browser/browser_system";
|
||||
import { System } from "../../plugbox/src/runtime";
|
||||
import { Plug } from "../../plugbox/src/runtime";
|
||||
import { slashCommandRegexp } from "./types";
|
||||
|
||||
@ -124,7 +124,7 @@ export class Editor implements AppEventDispatcher {
|
||||
}
|
||||
|
||||
async loadPlugs() {
|
||||
const system = new BrowserSystem<NuggetHook>("/plug");
|
||||
const system = new System<NuggetHook>();
|
||||
system.registerSyscalls(
|
||||
dbSyscalls,
|
||||
editorSyscalls(this),
|
||||
@ -132,7 +132,6 @@ export class Editor implements AppEventDispatcher {
|
||||
indexerSyscalls(this.indexer)
|
||||
);
|
||||
|
||||
await system.bootServiceWorker();
|
||||
console.log("Now loading core plug");
|
||||
let mainPlug = await system.load("core", coreManifest);
|
||||
this.plugs.push(mainPlug);
|
||||
|
@ -1,76 +0,0 @@
|
||||
// Page: this file is not built by Parcel, it's simply copied to the distribution
|
||||
// The reason is that somehow Parcel cannot accept using importScripts otherwise
|
||||
function safeRun(fn) {
|
||||
fn().catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
let func = null;
|
||||
let pendingRequests = {};
|
||||
|
||||
self.addEventListener("syscall", (event) => {
|
||||
let customEvent = event;
|
||||
let detail = customEvent.detail;
|
||||
pendingRequests[detail.id] = detail.callback;
|
||||
self.postMessage({
|
||||
type: "syscall",
|
||||
id: detail.id,
|
||||
name: detail.name,
|
||||
args: detail.args,
|
||||
});
|
||||
});
|
||||
|
||||
self.addEventListener("result", (event) => {
|
||||
let customEvent = event;
|
||||
self.postMessage({
|
||||
type: "result",
|
||||
result: customEvent.detail,
|
||||
});
|
||||
});
|
||||
|
||||
self.addEventListener("app-error", (event) => {
|
||||
let customEvent = event;
|
||||
self.postMessage({
|
||||
type: "error",
|
||||
reason: customEvent.detail,
|
||||
});
|
||||
});
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
safeRun(async () => {
|
||||
let messageEvent = event;
|
||||
let data = messageEvent.data;
|
||||
switch (data.type) {
|
||||
case "boot":
|
||||
console.log("Booting", `${data.prefix}/function/${data.name}`);
|
||||
importScripts(`${data.prefix}/function/${data.name}`);
|
||||
self.postMessage({
|
||||
type: "inited",
|
||||
});
|
||||
break;
|
||||
case "invoke":
|
||||
self.dispatchEvent(
|
||||
new CustomEvent("invoke-function", {
|
||||
detail: {
|
||||
args: data.args || [],
|
||||
},
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "syscall-response":
|
||||
let id = data.id;
|
||||
const lookup = pendingRequests[id];
|
||||
if (!lookup) {
|
||||
console.log(
|
||||
"Current outstanding requests",
|
||||
pendingRequests,
|
||||
"looking up",
|
||||
id
|
||||
);
|
||||
throw Error("Invalid request id");
|
||||
}
|
||||
return await lookup(data.data);
|
||||
}
|
||||
});
|
||||
});
|
@ -1,57 +0,0 @@
|
||||
import { PlugLoader, System } from "../../../plugbox/src/runtime";
|
||||
import { Manifest } from "../../../plugbox/src/types";
|
||||
import { sleep } from "../util";
|
||||
|
||||
export class BrowserLoader<HookT> implements PlugLoader<HookT> {
|
||||
readonly pathPrefix: string;
|
||||
|
||||
constructor(pathPrefix: string) {
|
||||
this.pathPrefix = pathPrefix;
|
||||
}
|
||||
|
||||
async load(name: string, manifest: Manifest<HookT>): Promise<void> {
|
||||
await fetch(`${this.pathPrefix}/${name}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(manifest),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserSystem<HookT> extends System<HookT> {
|
||||
constructor(pathPrefix: string) {
|
||||
super(new BrowserLoader(pathPrefix), pathPrefix);
|
||||
}
|
||||
// Service worker stuff
|
||||
async pollServiceWorkerActive() {
|
||||
for (let i = 0; i < 25; i++) {
|
||||
try {
|
||||
console.log("Pinging...", `${this.pathPrefix}/$ping`);
|
||||
let ping = await fetch(`${this.pathPrefix}/$ping`);
|
||||
let text = await ping.text();
|
||||
if (ping.status === 200 && text === "ok") {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Not yet");
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
// Alright, something's messed up
|
||||
throw new Error("Worker not successfully activated");
|
||||
}
|
||||
|
||||
async bootServiceWorker() {
|
||||
// @ts-ignore
|
||||
let reg = navigator.serviceWorker.register(
|
||||
new URL("../plugbox_sw.ts", import.meta.url),
|
||||
{
|
||||
type: "module",
|
||||
scope: "/",
|
||||
}
|
||||
);
|
||||
|
||||
console.log("Service worker registered successfully");
|
||||
|
||||
await this.pollServiceWorkerActive();
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
import { Manifest } from "./types";
|
||||
|
||||
import { openDB } from "idb";
|
||||
|
||||
const rootUrl = location.origin + "/plug";
|
||||
|
||||
// Storing manifests in IndexedDB, y'all
|
||||
const db = openDB("manifests-store", undefined, {
|
||||
upgrade(db) {
|
||||
db.createObjectStore("manifests");
|
||||
},
|
||||
});
|
||||
|
||||
async function saveManifest(name: string, manifest: Manifest) {
|
||||
await (await db).put("manifests", manifest, name);
|
||||
}
|
||||
|
||||
async function getManifest(name: string): Promise<Manifest | undefined> {
|
||||
return (await (await db).get("manifests", name)) as Manifest | undefined;
|
||||
}
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
console.log("Installing");
|
||||
// @ts-ignore
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
async function handlePut(req: Request, path: string) {
|
||||
console.log("Got manifest load for", path);
|
||||
let manifest = (await req.json()) as Manifest;
|
||||
await saveManifest(path, manifest);
|
||||
// loadedBundles.set(path, manifest);
|
||||
return new Response("ok");
|
||||
}
|
||||
|
||||
function wrapScript(functionName: string, code: string): string {
|
||||
return `const mod = ${code}
|
||||
|
||||
self.addEventListener('invoke-function', async e => {
|
||||
try {
|
||||
let result = await mod['${functionName}'](...e.detail.args);
|
||||
self.dispatchEvent(new CustomEvent('result', {detail: result}));
|
||||
} catch(e) {
|
||||
console.error(\`Error while running ${functionName}\`, e);
|
||||
self.dispatchEvent(new CustomEvent('app-error', {detail: e.message}));
|
||||
}
|
||||
});
|
||||
`;
|
||||
}
|
||||
|
||||
self.addEventListener("fetch", (event: any) => {
|
||||
const req = event.request;
|
||||
if (req.url.startsWith(rootUrl)) {
|
||||
let path = req.url.substring(rootUrl.length + 1);
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
// console.log("Service worker is serving", path);
|
||||
if (path === `$ping`) {
|
||||
// console.log("Got ping");
|
||||
return new Response("ok");
|
||||
}
|
||||
|
||||
if (req.method === "PUT") {
|
||||
return await handlePut(req, path);
|
||||
}
|
||||
|
||||
let [plugName, resourceType, functionName] = path.split("/");
|
||||
|
||||
let manifest = await getManifest(plugName);
|
||||
|
||||
if (!manifest) {
|
||||
// console.log("Ain't got", plugName);
|
||||
return new Response(`Plug not loaded: ${plugName}`, {
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
if (resourceType === "$manifest") {
|
||||
return new Response(JSON.stringify(manifest));
|
||||
}
|
||||
|
||||
if (resourceType === "function") {
|
||||
let func = manifest.functions[functionName];
|
||||
// console.log("Serving function", functionName, func);
|
||||
if (!func) {
|
||||
return new Response("Not found", {
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
return new Response(wrapScript(func.functionName!, func.code!), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-type": "application/javascript",
|
||||
},
|
||||
});
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
// console.log("Now ready to pick up fetches");
|
||||
// @ts-ignore
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
// console.log("I'm a service worker, look at me!", location.href);
|
@ -13,15 +13,15 @@ export interface Space {
|
||||
|
||||
export class HttpRemoteSpace implements Space {
|
||||
url: string;
|
||||
socket: Socket;
|
||||
socket?: Socket;
|
||||
|
||||
constructor(url: string, socket: Socket) {
|
||||
constructor(url: string, socket: Socket | null) {
|
||||
this.url = url;
|
||||
this.socket = socket;
|
||||
// this.socket = socket;
|
||||
|
||||
socket.on("connect", () => {
|
||||
console.log("connected via SocketIO", serverEvents.pageText);
|
||||
});
|
||||
// socket.on("connect", () => {
|
||||
// console.log("connected via SocketIO", serverEvents.pageText);
|
||||
// });
|
||||
}
|
||||
|
||||
async listPages(): Promise<PageMeta[]> {
|
||||
@ -36,10 +36,10 @@ export class HttpRemoteSpace implements Space {
|
||||
}
|
||||
|
||||
async openPage(name: string) {
|
||||
this.socket.on(serverEvents.pageText, (pageName, text) => {
|
||||
this.socket!.on(serverEvents.pageText, (pageName, text) => {
|
||||
console.log("Got this", pageName, text);
|
||||
});
|
||||
this.socket.emit(serverEvents.openPage, "start");
|
||||
this.socket!.emit(serverEvents.openPage, "start");
|
||||
}
|
||||
|
||||
async readPage(name: string): Promise<{ text: string; meta: PageMeta }> {
|
||||
|
@ -14,14 +14,6 @@ export function safeRun(fn: () => Promise<void>) {
|
||||
});
|
||||
}
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export function isMacLike() {
|
||||
return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user