This commit is contained in:
Daniel Hübleitner
2019-09-18 02:58:51 +02:00
parent 2f32d7c9cd
commit 76dee64a54
29 changed files with 567 additions and 596 deletions
-19
View File
File diff suppressed because one or more lines are too long
+2 -9
View File
@@ -1,12 +1,5 @@
import { Socket } from "../backend/RPCSocketServer";
/**
* Dynamic library to communicate with FrontblockService remotely
*
* This will be automatically injected into the webpages served by FrontblockService
* Will ask it's service for available RPCs and parse them into methods of this object
* for convenient access.
*/
export declare class RPCSocket implements Socket {
import { Socket } from './interfaces/Socket';
export declare class Client implements Socket {
port: number;
private server;
private tls;
@@ -5,14 +5,7 @@ var bsock = require('bsock');
function stripAfterEquals(str) {
return str.split("=")[0];
}
/**
* Dynamic library to communicate with FrontblockService remotely
*
* This will be automatically injected into the webpages served by FrontblockService
* Will ask it's service for available RPCs and parse them into methods of this object
* for convenient access.
*/
class RPCSocket {
class Client {
constructor(port, server, tls = false) {
this.port = port;
this.server = server;
@@ -45,13 +38,13 @@ class RPCSocket {
info.forEach(i => {
let f;
switch (i.type) {
case 'call':
case 'Call':
f = this.callGenerator(i.uniqueName, i.argNames);
break;
case 'hook':
case 'Hook':
f = this.hookGenerator(i.uniqueName, i.argNames);
break;
case 'unhook':
case 'Unhook':
f = this.unhookGenerator(i.uniqueName, i.argNames);
break;
}
@@ -92,4 +85,4 @@ class RPCSocket {
} )()`);
}
}
exports.RPCSocket = RPCSocket;
exports.Client = Client;
+17
View File
@@ -0,0 +1,17 @@
export declare type Outcome = "Success" | "Error";
export declare class Response {
message?: string | undefined;
constructor(message?: string | undefined);
}
export declare class SuccessResponse extends Response {
result: Outcome;
constructor(message?: string);
}
export declare class ErrorResponse extends Response {
result: Outcome;
constructor(message?: string);
}
export declare class SubscriptionResponse extends SuccessResponse {
uid: string;
constructor(uid: string, message?: string);
}
+30
View File
@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/* Responses */
class Response {
constructor(message) {
this.message = message;
}
}
exports.Response = Response;
class SuccessResponse extends Response {
constructor(message) {
super(message);
this.result = "Success";
}
}
exports.SuccessResponse = SuccessResponse;
class ErrorResponse extends Response {
constructor(message = "Unknown error") {
super(message);
this.result = "Error";
}
}
exports.ErrorResponse = ErrorResponse;
class SubscriptionResponse extends SuccessResponse {
constructor(uid, message) {
super(message);
this.uid = uid;
}
}
exports.SubscriptionResponse = SubscriptionResponse;
+14
View File
@@ -0,0 +1,14 @@
import { SocketConf } from './Types';
import { Exporter } from './interfaces/Exporter';
import { Socket } from "./interfaces/Socket";
export declare class Server {
private port;
private rpcExporters;
private conf;
private io;
private wsServer;
constructor(port: number, rpcExporters?: Exporter[], conf?: SocketConf);
private startWebsocket;
protected initRPCs(socket: Socket): void;
protected initPublicRPCs(socket: Socket): void;
}
+58
View File
@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const http = require("http");
const bsock = require("bsock");
const Util_1 = require("./Util");
class Server {
constructor(port, rpcExporters = [], conf = {
errorHandler: (socket) => (error) => { socket.destroy(); console.error(error); },
closeHandler: (socket) => () => { console.log("Socket closing"); },
connectionHandler: (socket) => { console.log("New websocket connection in port " + socket.port); },
visibility: "127.0.0.1"
}) {
this.port = port;
this.rpcExporters = rpcExporters;
this.conf = conf;
this.io = bsock.createServer();
this.wsServer = http.createServer();
this.startWebsocket();
}
startWebsocket() {
try {
this.io.attach(this.wsServer);
this.io.on('socket', (socket) => {
socket.on('error', this.conf.errorHandler(socket));
socket.on('close', this.conf.closeHandler(socket));
if (this.conf.visibility === "127.0.0.1")
this.initRPCs(socket);
else
this.initPublicRPCs(socket);
});
this.wsServer.listen(this.port, this.conf.visibility);
}
catch (e) {
//@ts-ignore
this.errorHandler(undefined)("Unable to connect to socket");
}
}
initRPCs(socket) {
const infoRPC = [
{
name: 'info',
type: 'Call',
func: async () => rpcInfos
}
];
const rpcInfos = [
...Util_1.rpcHooker(socket, "RPC", infoRPC, false),
...this.rpcExporters.flatMap(exporter => Util_1.rpcHooker(socket, exporter.name, [...exporter.exportPublicRPCs(), ...exporter.exportRPCs()]))
];
}
initPublicRPCs(socket) {
const rpcInfos = [
...Util_1.rpcHooker(socket, "Admin", adminRPCs, false),
...this.rpcExporters.flatMap(exporter => Util_1.rpcHooker(socket, exporter.name, exporter.exportPublicRPCs()))
];
}
}
exports.Server = Server;
+54
View File
@@ -0,0 +1,54 @@
import { SuccessResponse, ErrorResponse, SubscriptionResponse } from "./Response";
import { Socket } from "./interfaces/Socket";
export declare type Visibility = "127.0.0.1" | "0.0.0.0";
export declare type Name = string;
export declare type SocketConf = {
connectionHandler: (socket: Socket) => void;
errorHandler: (socket: Socket) => (error: any) => void;
closeHandler: (socket: Socket) => () => void;
visibility: Visibility;
};
export declare type rpcType = 'Hook' | 'Unhook' | 'Call';
export declare type BaseRPC = {
type: rpcType;
name: string;
};
export declare type HookRPC = BaseRPC & {
type: 'Hook';
func: CallbackFunction;
unhook: UnhookFunction;
};
export declare type UnhookRPC = BaseRPC & {
type: 'Unhook';
func: UnhookFunction;
};
export declare type CallRPC = BaseRPC & {
type: 'Call';
func: (...args: any[]) => Promise<any>;
};
export declare type SocketioRPC = CallRPC | UnhookRPC | HookRPC;
export declare type BaseInfo = {
owner: string;
argNames: string[];
};
export declare type HookInfo = BaseRPC & BaseInfo & {
type: 'Hook';
generator: (socket: any) => CallbackFunction;
unhook: UnhookFunction;
};
export declare type UnhookInfo = BaseRPC & BaseInfo & {
type: 'Unhook';
func: UnhookFunction;
};
export declare type CallInfo = BaseRPC & BaseInfo & {
type: 'Call';
func: AsyncFunction;
};
export declare type RpcInfo = HookInfo | UnhookInfo | CallInfo;
export declare type ExtendedRpcInfo = RpcInfo & {
uniqueName: string;
};
export declare type OnFunction = (type: 'error' | 'close', f: (e?: any) => void) => Socket;
export declare type UnhookFunction = (uid: string) => Promise<SuccessResponse | ErrorResponse>;
export declare type CallbackFunction = (...args: any[]) => Promise<SubscriptionResponse | ErrorResponse>;
export declare type AsyncFunction = (...args: any[]) => Promise<any>;
+2
View File
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+4
View File
@@ -0,0 +1,4 @@
import { SocketioRPC, RpcInfo, ExtendedRpcInfo } from "./Types";
import { Socket } from "./interfaces/Socket";
export declare const rpcToRpcinfo: (rpc: SocketioRPC, owner: string) => RpcInfo;
export declare const rpcHooker: (socket: Socket, owner: string, RPCs: SocketioRPC[], makeUnique?: boolean) => ExtendedRpcInfo[];
+75
View File
@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const uuid = require("uuid/v4");
exports.rpcToRpcinfo = (rpc, owner) => {
switch (rpc.type) {
case "Call":
return {
owner: owner,
argNames: extractArgs(rpc.func),
type: rpc.type,
name: rpc.name,
func: rpc.func,
};
case "Unhook":
return {
owner: owner,
argNames: extractArgs(rpc.func),
type: rpc.type,
name: rpc.name,
func: rpc.func,
};
case "Hook":
const generator = hookGenerator(rpc);
return {
owner: owner,
argNames: extractArgs(generator(undefined)),
type: rpc.type,
name: rpc.name,
unhook: rpc.unhook,
generator: generator,
};
}
};
exports.rpcHooker = (socket, owner, RPCs, makeUnique = true) => {
const suffix = makeUnique ? "-" + uuid().substr(0, 4) : "";
return RPCs.map(rpc => exports.rpcToRpcinfo(rpc, owner))
.map(info => {
const ret = info;
ret.uniqueName = info.name + suffix;
switch (info.type) {
case "Hook":
socket.hook(ret.uniqueName, info.generator(socket));
break;
default:
socket.hook(ret.uniqueName, info.func);
}
socket.on('close', () => socket.unhook(info.name));
return ret;
});
};
const hookGenerator = (rpc) => {
const argsArr = extractArgs(rpc.func);
argsArr.pop();
const args = argsArr.join(',');
return eval(`(socket) => async (` + args + `) => {
const res = await rpc.func(` + args + (args.length !== 0 ? ',' : '') + ` (x) => {
socket.call(res.uid, x)
})
if(res.result == 'Success'){
socket.on('close', async () => {
const unhookRes = await rpc.unhook(res.uid)
console.log("Specific close handler for", rpc.name, res.uid, unhookRes)
})
}
return res
}`);
};
const extractArgs = (f) => {
let fn = String(f);
let args = fn.substr(0, fn.indexOf(")"));
args = args.substr(fn.indexOf("(") + 1);
let ret = args.split(",");
return ret;
};
-98
View File
@@ -1,98 +0,0 @@
import { Socket } from "./RPCSocketServer";
declare type rpcType = 'hook' | 'unhook' | 'call';
export declare type Outcome = "Success" | "Error";
export declare type Visibility = "127.0.0.1" | "0.0.0.0";
export declare class Response {
message?: string | undefined;
constructor(message?: string | undefined);
}
export declare class SuccessResponse extends Response {
result: Outcome;
constructor(message?: string);
}
export declare class ErrorResponse extends Response {
result: Outcome;
constructor(message?: string);
}
export declare class SubscriptionResponse extends SuccessResponse {
uid: string;
constructor(uid: string, message?: string);
}
export declare type UnhookFunction = (uid: string) => Promise<SuccessResponse | ErrorResponse>;
export declare type callbackFunction = (...args: any[]) => Promise<SubscriptionResponse | ErrorResponse>;
export declare type AsyncFunction = (...args: any[]) => Promise<any>;
export interface RPCExporter {
name: string;
exportRPCs(): socketioRPC[];
exportPublicRPCs(): socketioRPC[];
}
declare type baseRPC = {
type: rpcType;
name: string;
};
declare type hookRPC = baseRPC & {
type: 'hook';
func: callbackFunction;
unhook: UnhookFunction;
};
declare type unhookRPC = baseRPC & {
type: 'unhook';
func: UnhookFunction;
};
declare type callRPC = baseRPC & {
type: 'call';
func: (...args: any[]) => Promise<any>;
};
export declare type socketioRPC = callRPC | unhookRPC | hookRPC;
export declare type baseInfo = {
owner: string;
argNames: string[];
};
declare type HookInfo = baseRPC & baseInfo & {
type: 'hook';
generator: (socket: any) => callbackFunction;
unhook: UnhookFunction;
};
declare type UnhookInfo = baseRPC & baseInfo & {
type: 'unhook';
func: UnhookFunction;
};
declare type CallInfo = baseRPC & baseInfo & {
type: 'call';
func: AsyncFunction;
};
declare type RpcInfo = HookInfo | UnhookInfo | CallInfo;
export declare type ExtendedRpcInfo = RpcInfo & {
uniqueName: string;
};
export declare const rpcToRpcinfo: (rpc: socketioRPC, owner: string) => RpcInfo;
export declare const rpcHooker: (socket: Socket, owner: string, RPCs: socketioRPC[], makeUnique?: boolean) => ExtendedRpcInfo[];
declare type OnFunction = (type: 'error' | 'close', f: (e?: any) => void) => Socket;
export interface Socket {
port: number;
hook: (rpcname: string, ...args: any[]) => Socket;
unhook: (rpcname: string) => Socket;
call: (rpcname: string, ...args: any[]) => Promise<any>;
fire: (rpcname: string, ...args: any[]) => Promise<any>;
on: OnFunction;
destroy: () => void;
close: () => void;
}
export declare type RPCSocketConf = {
connectionHandler: (socket: Socket) => void;
errorHandler: (socket: Socket) => (error: any) => void;
closeHandler: (socket: Socket) => () => void;
};
export declare class RPCSocketServer {
private port;
private rpcExporters;
private visibility;
private conf;
private io;
private wsServer;
constructor(port: number, rpcExporters?: RPCExporter[], visibility?: Visibility, conf?: RPCSocketConf);
private startWebsocket;
protected initApis(socket: Socket): void;
protected initPublicApis(socket: Socket): void;
}
export {};
-165
View File
@@ -1,165 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const http = require("http");
const bsock = require("bsock");
const uuid = require("uuid/v4");
/* Responses */
class Response {
constructor(message) {
this.message = message;
}
}
exports.Response = Response;
class SuccessResponse extends Response {
constructor(message) {
super(message);
this.result = "Success";
}
}
exports.SuccessResponse = SuccessResponse;
class ErrorResponse extends Response {
constructor(message = "Unknown error") {
super(message);
this.result = "Error";
}
}
exports.ErrorResponse = ErrorResponse;
class SubscriptionResponse extends SuccessResponse {
constructor(uid, message) {
super(message);
this.uid = uid;
}
}
exports.SubscriptionResponse = SubscriptionResponse;
exports.rpcToRpcinfo = (rpc, owner) => {
switch (rpc.type) {
case "call":
return {
owner: owner,
argNames: extractArgs(rpc.func),
type: rpc.type,
name: rpc.name,
func: rpc.func,
};
case "unhook":
return {
owner: owner,
argNames: extractArgs(rpc.func),
type: rpc.type,
name: rpc.name,
func: rpc.func,
};
case "hook":
const generator = hookGenerator(rpc);
return {
owner: owner,
argNames: extractArgs(generator(undefined)),
type: rpc.type,
name: rpc.name,
unhook: rpc.unhook,
generator: generator,
};
}
};
exports.rpcHooker = (socket, owner, RPCs, makeUnique = true) => {
const suffix = makeUnique ? "-" + uuid().substr(0, 4) : "";
return RPCs.map(rpc => exports.rpcToRpcinfo(rpc, owner))
.map(info => {
const ret = info;
ret.uniqueName = info.name + suffix;
switch (info.type) {
case "hook":
socket.hook(ret.uniqueName, info.generator(socket));
break;
default:
socket.hook(ret.uniqueName, info.func);
}
socket.on('close', () => socket.unhook(info.name));
return ret;
});
};
const hookGenerator = (rpc) => {
const argsArr = extractArgs(rpc.func);
argsArr.pop();
const args = argsArr.join(',');
return eval(`(socket) => async (` + args + `) => {
const res = await rpc.func(` + args + (args.length !== 0 ? ',' : '') + ` (x) => {
socket.call(res.uid, x)
})
if(res.result == 'Success'){
socket.on('close', async () => {
const unhookRes = await rpc.unhook(res.uid)
console.log("Specific close handler for", rpc.name, res.uid, unhookRes)
})
}
return res
}`);
};
const extractArgs = (f) => {
let fn = String(f);
let args = fn.substr(0, fn.indexOf(")"));
args = args.substr(fn.indexOf("(") + 1);
let ret = args.split(",");
return ret;
};
class RPCSocketServer {
constructor(port, rpcExporters = [], visibility = "127.0.0.1", conf = {
errorHandler: (socket) => (error) => { socket.destroy(); console.error(error); },
closeHandler: (socket) => () => { console.log("Socket closing"); },
connectionHandler: (socket) => { console.log("New websocket connection in port " + socket.port); }
}) {
this.port = port;
this.rpcExporters = rpcExporters;
this.visibility = visibility;
this.conf = conf;
this.io = bsock.createServer();
this.wsServer = http.createServer();
this.startWebsocket();
}
startWebsocket() {
try {
this.io.attach(this.wsServer);
this.io.on('socket', (socket) => {
socket.on('error', this.conf.errorHandler(socket));
socket.on('close', this.conf.closeHandler(socket));
if (this.visibility === "127.0.0.1")
this.initApis(socket);
else
this.initPublicApis(socket);
});
this.wsServer.listen(this.port, this.visibility);
}
catch (e) {
//@ts-ignore
this.errorHandler(undefined)("Unable to connect to socket");
}
}
initApis(socket) {
const adminRPCs = [
{
name: 'info',
type: 'call',
func: async () => rpcInfos
}
];
const rpcInfos = [
...exports.rpcHooker(socket, "Admin", adminRPCs, false),
...this.rpcExporters.flatMap(exporter => exports.rpcHooker(socket, exporter.name, [...exporter.exportPublicRPCs(), ...exporter.exportRPCs()]))
];
}
initPublicApis(socket) {
const adminRPCs = [
{
name: 'info',
type: 'call',
func: async () => rpcInfos
}
];
const rpcInfos = [
...exports.rpcHooker(socket, "Admin", adminRPCs, false),
...this.rpcExporters.flatMap(exporter => exports.rpcHooker(socket, exporter.name, exporter.exportPublicRPCs()))
];
}
}
exports.RPCSocketServer = RPCSocketServer;
+6
View File
@@ -0,0 +1,6 @@
import { SocketioRPC, Name } from "../Types";
export interface Exporter {
name: Name;
exportRPCs(): SocketioRPC[];
exportPublicRPCs(): SocketioRPC[];
}
+2
View File
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+11
View File
@@ -0,0 +1,11 @@
import { OnFunction } from "../Types";
export interface Socket {
port: number;
hook: (rpcname: string, ...args: any[]) => Socket;
unhook: (rpcname: string) => Socket;
call: (rpcname: string, ...args: any[]) => Promise<any>;
fire: (rpcname: string, ...args: any[]) => Promise<any>;
on: OnFunction;
destroy: () => void;
close: () => void;
}
+2
View File
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+5 -5
View File
@@ -1,9 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const RPCSocketServer_1 = require("../src/backend/RPCSocketServer");
const Server_1 = require("../src/Server");
//@ts-ignore
const RPCSocket_1 = require("../src/frontend/RPCSocket");
new RPCSocketServer_1.RPCSocketServer(20000, [{
const Client_1 = require("../src/Client");
new Server_1.Server(20000, [{
name: "HelloWorldRPCGroup",
exportPublicRPCs: () => [],
exportRPCs: () => [{
@@ -11,8 +11,8 @@ new RPCSocketServer_1.RPCSocketServer(20000, [{
name: 'echo',
func: async (s) => s,
}],
}], "0.0.0.0");
const caller = new RPCSocket_1.RPCSocket(20000, 'localhost');
}]);
const caller = new Client_1.Client(20000, 'localhost');
caller.connect().then(_ => {
caller.info().then(console.log);
caller["HelloWorldRPCGroup"].echo("x").then(console.log);