2022-03-20 08:56:28 +00:00
|
|
|
import { Server, Socket } from "socket.io";
|
|
|
|
import { Page } from "./types";
|
|
|
|
import * as path from "path";
|
|
|
|
import { IndexApi } from "./index_api";
|
|
|
|
import { PageApi } from "./page_api";
|
2022-03-21 14:21:34 +00:00
|
|
|
import { SilverBulletHooks } from "../common/manifest";
|
2022-03-20 08:56:28 +00:00
|
|
|
import pageIndexSyscalls from "./syscalls/page_index";
|
2022-03-23 14:41:12 +00:00
|
|
|
import { safeRun } from "./util";
|
|
|
|
import { System } from "../plugbox/system";
|
2022-03-20 08:56:28 +00:00
|
|
|
|
|
|
|
export class ClientConnection {
|
|
|
|
openPages = new Set<string>();
|
2022-03-23 14:41:12 +00:00
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
constructor(readonly sock: Socket) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ApiProvider {
|
|
|
|
init(): Promise<void>;
|
2022-03-23 14:41:12 +00:00
|
|
|
|
2022-03-20 08:56:28 +00:00
|
|
|
api(): Object;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class SocketServer {
|
|
|
|
private openPages = new Map<string, Page>();
|
|
|
|
private connectedSockets = new Set<Socket>();
|
|
|
|
private apis = new Map<string, ApiProvider>();
|
|
|
|
readonly rootPath: string;
|
|
|
|
private serverSocket: Server;
|
2022-03-21 14:21:34 +00:00
|
|
|
system: System<SilverBulletHooks>;
|
2022-03-20 08:56:28 +00:00
|
|
|
|
2022-03-21 14:21:34 +00:00
|
|
|
constructor(
|
|
|
|
rootPath: string,
|
|
|
|
serverSocket: Server,
|
|
|
|
system: System<SilverBulletHooks>
|
|
|
|
) {
|
2022-03-20 08:56:28 +00:00
|
|
|
this.rootPath = path.resolve(rootPath);
|
|
|
|
this.serverSocket = serverSocket;
|
2022-03-21 14:21:34 +00:00
|
|
|
this.system = system;
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async registerApi(name: string, apiProvider: ApiProvider) {
|
|
|
|
await apiProvider.init();
|
|
|
|
this.apis.set(name, apiProvider);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async init() {
|
|
|
|
const indexApi = new IndexApi(this.rootPath);
|
|
|
|
await this.registerApi("index", indexApi);
|
2022-03-25 11:03:06 +00:00
|
|
|
this.system.registerSyscalls("indexer", [], pageIndexSyscalls(indexApi.db));
|
2022-03-20 08:56:28 +00:00
|
|
|
await this.registerApi(
|
|
|
|
"page",
|
|
|
|
new PageApi(
|
|
|
|
this.rootPath,
|
|
|
|
this.connectedSockets,
|
|
|
|
this.openPages,
|
|
|
|
this.system
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
this.serverSocket.on("connection", (socket) => {
|
|
|
|
const clientConn = new ClientConnection(socket);
|
|
|
|
|
|
|
|
console.log("Connected", socket.id);
|
|
|
|
this.connectedSockets.add(socket);
|
|
|
|
|
|
|
|
socket.on("disconnect", () => {
|
|
|
|
console.log("Disconnected", socket.id);
|
2022-03-23 14:41:12 +00:00
|
|
|
clientConn.openPages.forEach((pageName) => {
|
|
|
|
safeRun(async () => {
|
|
|
|
await disconnectPageSocket(pageName);
|
|
|
|
});
|
|
|
|
});
|
2022-03-20 08:56:28 +00:00
|
|
|
this.connectedSockets.delete(socket);
|
|
|
|
});
|
|
|
|
|
|
|
|
socket.on("page.closePage", (pageName: string) => {
|
|
|
|
console.log("Client closed page", pageName);
|
2022-03-23 14:41:12 +00:00
|
|
|
safeRun(async () => {
|
|
|
|
await disconnectPageSocket(pageName);
|
|
|
|
});
|
2022-03-20 08:56:28 +00:00
|
|
|
clientConn.openPages.delete(pageName);
|
|
|
|
});
|
|
|
|
|
|
|
|
const onCall = (
|
|
|
|
eventName: string,
|
|
|
|
cb: (...args: any[]) => Promise<any>
|
|
|
|
) => {
|
|
|
|
socket.on(eventName, (reqId: number, ...args) => {
|
|
|
|
cb(...args)
|
|
|
|
.then((result) => {
|
|
|
|
socket.emit(`${eventName}Resp${reqId}`, null, result);
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
socket.emit(`${eventName}Resp${reqId}`, err.message);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-03-23 14:41:12 +00:00
|
|
|
const disconnectPageSocket = async (pageName: string) => {
|
2022-03-20 08:56:28 +00:00
|
|
|
let page = this.openPages.get(pageName);
|
|
|
|
if (page) {
|
|
|
|
for (let client of page.clientStates) {
|
|
|
|
if (client.socket === socket) {
|
2022-03-23 14:41:12 +00:00
|
|
|
await (this.apis.get("page")! as PageApi).disconnectClient(
|
2022-03-20 08:56:28 +00:00
|
|
|
client,
|
|
|
|
page
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
for (let [apiName, apiProvider] of this.apis) {
|
|
|
|
Object.entries(apiProvider.api()).forEach(([eventName, cb]) => {
|
|
|
|
onCall(`${apiName}.${eventName}`, (...args: any[]): any => {
|
|
|
|
// @ts-ignore
|
|
|
|
return cb(clientConn, ...args);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-03-21 14:21:34 +00:00
|
|
|
|
2022-03-25 11:03:06 +00:00
|
|
|
onCall(
|
2022-03-27 07:55:29 +00:00
|
|
|
"invokeFunction",
|
|
|
|
(plugName: string, name: string, ...args: any[]): Promise<any> => {
|
|
|
|
let plug = this.system.loadedPlugs.get(plugName);
|
|
|
|
if (!plug) {
|
|
|
|
throw new Error(`Plug ${plugName} not loaded`);
|
2022-03-25 11:03:06 +00:00
|
|
|
}
|
2022-03-27 07:55:29 +00:00
|
|
|
console.log(
|
|
|
|
"Invoking function",
|
|
|
|
name,
|
|
|
|
"for plug",
|
|
|
|
plugName,
|
|
|
|
"as requested over socket"
|
|
|
|
);
|
|
|
|
return plug.invoke(name, args);
|
|
|
|
}
|
2022-03-25 11:03:06 +00:00
|
|
|
);
|
|
|
|
|
2022-03-21 14:21:34 +00:00
|
|
|
console.log("Sending the sytem to the client");
|
|
|
|
socket.emit("loadSystem", this.system.toJSON());
|
2022-03-20 08:56:28 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
close() {
|
2022-03-20 09:22:38 +00:00
|
|
|
console.log("Closing server");
|
|
|
|
(this.apis.get("index")! as IndexApi).db.destroy().catch((err) => {
|
|
|
|
console.error(err);
|
|
|
|
});
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
}
|