This commit is contained in:
2019-09-21 02:56:03 +02:00
parent 968885767a
commit 2b765eb2a2
11 changed files with 165 additions and 77 deletions
+7
View File
@@ -0,0 +1,7 @@
import * as Back from './src/backend/RPCSocketServer';
import * as Front from './src/frontend/RPCSocket';
export {
Back as Backend,
Front as Frontend
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "rpclibrary", "name": "rpclibrary",
"version": "1.0.1", "version": "1.0.3",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
+39 -20
View File
@@ -18,17 +18,46 @@ export {
export class Server{ export class Server{
private ws = http.createServer() private ws = http.createServer()
private io = bsock.createServer() private io = bsock.createServer()
private visibility:T.Visibility
private closeHandler:T.CloseHandler
private errorHandler: T.ErrorHandler
private connectionHandler: T.ConnectionHandler
constructor( constructor(
private port:number, private port:number,
private exporters: I.Exporter[] = [], private exporters: I.Exporter[] = [],
private conf: T.SocketConf = { conf: T.SocketConf = {}
errorHandler: (socket:I.Socket) => (error:any) => { socket.destroy(); console.error(error) },
closeHandler: (socket:I.Socket) => () => { console.log("Socket closing") },
connectionHandler: (socket:I.Socket) => { console.log("New websocket connection in port "+socket.port)},
visibility: "127.0.0.1"
}
){ ){
if(!conf.visibility) this.visibility = "127.0.0.1"
if(!conf.errorHandler) this.errorHandler =
(socket:I.Socket) => (error:any) => {
socket.destroy();
console.error(error)
}
if(!conf.closeHandler) this.closeHandler =
(socket:I.Socket) => () => {
console.log("Socket on port "+socket.port+"closing")
}
if(!conf.connectionHandler) this.connectionHandler =
(socket:I.Socket) => {
console.log("New websocket connection in port "+socket.port)
}
let badRPC
if(badRPC = exporters.flatMap(ex => ex.exportRPCs()).find(rpc => !rpc.name))
throw new Error(`
RPC did not provide a name.
\nUse funtion name(..){ .. } syntax instead.
\n
\n<------------OFFENDING RPC:
\n`+badRPC.toString()+`
\n>------------OFFENDING RPC`)
this.startWebsocket() this.startWebsocket()
} }
@@ -36,14 +65,12 @@ export class Server{
try{ try{
this.io.attach(this.ws) this.io.attach(this.ws)
this.io.on('socket', (socket:I.Socket) => { this.io.on('socket', (socket:I.Socket) => {
socket.on('error', this.conf.errorHandler(socket)) socket.on('error', this.errorHandler(socket))
socket.on('close', this.conf.closeHandler(socket)) socket.on('close', this.closeHandler(socket))
if(this.conf.visibility === "127.0.0.1") this.connectionHandler(socket)
this.initRPCs(socket) this.initRPCs(socket)
else
this.initPublicRPCs(socket)
}) })
this.ws.listen(this.port, this.conf.visibility) this.ws.listen(this.port, this.visibility)
}catch(e){ }catch(e){
//@ts-ignore //@ts-ignore
this.errorHandler(undefined)("Unable to connect to socket") this.errorHandler(undefined)("Unable to connect to socket")
@@ -57,12 +84,4 @@ export class Server{
...this.exporters.flatMap(exporter => U.rpcHooker(socket, exporter)) ...this.exporters.flatMap(exporter => U.rpcHooker(socket, exporter))
] ]
} }
protected initPublicRPCs(socket:I.Socket){
socket.hook('info', () => rpcInfos)
const rpcInfos:T.ExtendedRpcInfo[] = [
...this.exporters.flatMap(exporter => U.rpcHooker(socket, exporter))
]
}
} }
+4 -6
View File
@@ -1,6 +1,6 @@
'use strict' 'use strict'
var bsock = require('bsock') import bsock = require('bsock');
import * as T from './Types'; import * as T from './Types';
import * as U from './Utils'; import * as U from './Utils';
@@ -26,8 +26,6 @@ export class Client implements I.Socket{
constructor(public port:number, private server: string, private tls: boolean = false){ constructor(public port:number, private server: string, private tls: boolean = false){
} }
public hook(name: T.Name, args: T.Arg){ public hook(name: T.Name, args: T.Arg){
return this.socket.hook(name, args) return this.socket.hook(name, args)
} }
@@ -96,9 +94,9 @@ export class Client implements I.Socket{
return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => { return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {
const r = await this.socket.call("`+fnName+`", `+argParams+`) const r = await this.socket.call("`+fnName+`", `+argParams+`)
if(r.uid != null){ if(r.uid != null){
this.socket.hook(res.uid, callback) this.socket.hook(r.uid, callback)
} }
return res return r
} )()` ) } )()` )
} }
@@ -111,7 +109,7 @@ export class Client implements I.Socket{
return eval( `( () => async (`+headerArgs+`) => { return eval( `( () => async (`+headerArgs+`) => {
const r = await this.socket.call("`+fnName+`", `+argParams+`) const r = await this.socket.call("`+fnName+`", `+argParams+`)
this.socket.unhook(`+argParams+`) this.socket.unhook(`+argParams+`)
return res return r
} )()` ) } )()` )
} }
+1 -2
View File
@@ -3,8 +3,7 @@ import * as I from "./Interfaces"
export interface Exporter{ export interface Exporter{
name: T.Name name: T.Name
localRPCs() : T.RPC[] exportRPCs() : T.RPC[]
publicRPCs() : T.RPC[]
} }
export interface Socket { export interface Socket {
+10 -8
View File
@@ -6,12 +6,14 @@ export type Any = any
export type Arg = string export type Arg = string
export type Name = Arg export type Name = Arg
export type Owner = Name export type Owner = Name
export type ConnectionHandler = (socket:I.Socket) => void
export type ErrorHandler = (socket:I.Socket) => (error:any) => void
export type CloseHandler = (socket:I.Socket) => () => void
export type SocketConf = { export type SocketConf = {
connectionHandler: (socket:I.Socket) => void connectionHandler?: ConnectionHandler
errorHandler: (socket:I.Socket) => (error:any) => void errorHandler?: ErrorHandler
closeHandler: (socket:I.Socket) => () => void closeHandler?: CloseHandler
visibility: Visibility visibility?: Visibility
} }
export type RPCType = 'Hook' | 'Unhook' | 'Call' export type RPCType = 'Hook' | 'Unhook' | 'Call'
@@ -23,7 +25,7 @@ export type BaseRPC = {
export type HookRPC = BaseRPC & { export type HookRPC = BaseRPC & {
type: 'Hook' type: 'Hook'
clbk: CallbackFunction hook: CallbackFunction
unhook: UnhookFunction unhook: UnhookFunction
} }
@@ -32,10 +34,10 @@ export type UnhookRPC = BaseRPC & {
unhook: UnhookFunction unhook: UnhookFunction
} }
export type CallRPC = BaseRPC & { export type CallRPC = (BaseRPC & {
type: 'Call' type: 'Call'
call: AsyncFunction call: AsyncFunction
} } ) | Function
export type RPC = CallRPC | UnhookRPC | HookRPC export type RPC = CallRPC | UnhookRPC | HookRPC
+24 -4
View File
@@ -4,6 +4,8 @@ import * as T from "./Types";
import * as I from "./Interfaces"; import * as I from "./Interfaces";
export const rpcToRpcinfo = (rpc : T.RPC, owner: T.Owner):T.RpcInfo => { export const rpcToRpcinfo = (rpc : T.RPC, owner: T.Owner):T.RpcInfo => {
switch (typeof rpc){
case "object":
switch(rpc.type){ switch(rpc.type){
case "Call" : case "Call" :
return { return {
@@ -32,10 +34,29 @@ export const rpcToRpcinfo = (rpc : T.RPC, owner: T.Owner):T.RpcInfo => {
generator: generator, generator: generator,
} }
} }
break;
case "function":
if(!rpc.name) throw new Error(`
RPC did not provide a name.
\nUse funtion name(..){ .. } syntax instead.
\n
\n<------------OFFENDING RPC:
\n`+rpc.toString()+`
\n>------------OFFENDING RPC`)
return {
type: "Call",
owner : owner,
argNames: extractArgs(rpc),
call: async(...args) => rpc.apply({}, args),
name: rpc.name
} }
}
throw new Error("Bad socketIORPC type "+ typeof rpc)
}
export function rpcHooker(socket: I.Socket, exporter:I.Exporter, makeUnique = true):T.ExtendedRpcInfo[]{ export function rpcHooker(socket: I.Socket, exporter:I.Exporter, makeUnique = true):T.ExtendedRpcInfo[]{
const owner = exporter.name const owner = exporter.name
const RPCs = [...exporter.publicRPCs(), ...exporter.localRPCs()] const RPCs = [...exporter.exportRPCs()]
const suffix = makeUnique?"-"+uuid().substr(0,4):"" const suffix = makeUnique?"-"+uuid().substr(0,4):""
return RPCs.map(rpc => rpcToRpcinfo(rpc, owner)) return RPCs.map(rpc => rpcToRpcinfo(rpc, owner))
.map(info => { .map(info => {
@@ -59,12 +80,12 @@ export function rpcHooker(socket: I.Socket, exporter:I.Exporter, makeUnique = tr
} }
const hookGenerator = (rpc:T.HookRPC): T.HookInfo['generator'] => { const hookGenerator = (rpc:T.HookRPC): T.HookInfo['generator'] => {
const argsArr = extractArgs(rpc.clbk) const argsArr = extractArgs(rpc.hook)
argsArr.pop() argsArr.pop()
const args = argsArr.join(',') const args = argsArr.join(',')
return eval(`(socket) => async (`+args+`) => { return eval(`(socket) => async (`+args+`) => {
const res = await rpc.clbk(`+args+(args.length!==0?',':'')+` (x) => { const res = await rpc.hook(`+args+(args.length!==0?',':'')+` (x) => {
socket.call(res.uid, x) socket.call(res.uid, x)
}) })
if(res.result == 'Success'){ if(res.result == 'Success'){
@@ -72,7 +93,6 @@ const hookGenerator = (rpc:T.HookRPC): T.HookInfo['generator'] => {
const unhookRes = await rpc.unhook(res.uid) const unhookRes = await rpc.unhook(res.uid)
console.log("Specific close handler for", rpc.name, res.uid, unhookRes) console.log("Specific close handler for", rpc.name, res.uid, unhookRes)
}) })
} }
return res return res
}`) }`)
+1 -1
View File
@@ -5,7 +5,7 @@ const frontendConf = {
target: "web", target: "web",
entry: path.resolve(__dirname, '..', 'js', 'src', 'Frontend.js'), entry: path.resolve(__dirname, '..', 'js', 'src', 'Frontend.js'),
output: { output: {
path: path.resolve(__dirname, '..', 'js', 'src'), path: path.resolve(__dirname, '..', 'js', 'rpclibrary.browser.js'),
filename: "Frontend.js", filename: "Frontend.js",
libraryTarget: 'commonjs', libraryTarget: 'commonjs',
}, },
+28 -3
View File
@@ -1,11 +1,36 @@
import { Server } from '../src/Backend' import { Server } from '../src/Backend'
import { SubscriptionResponse } from '../src/Responses'
let subcallback
new Server(20000, [{ new Server(20000, [{
name: "HelloWorldRPCGroup", name: "HelloWorldRPCGroup",
publicRPCs: () => [], exportRPCs: () => [{
localRPCs: () => [{
type: 'Call', type: 'Call',
name: 'echo', name: 'echo',
call: async (s:string) => s, call: async (s:string) => s,
}] },
{
type: 'Hook',
name: 'subscribe',
hook: async (callback):Promise<any> => {
subcallback = callback
return new SubscriptionResponse(""+Math.random())
},
unhook: async (uid):Promise<any> => { subcallback = null }
},
function add(...args:number[]):number {return args.reduce((a,b)=>a+b, 0)},
function triggerCallback(message):number {return subcallback(message)},
]
}]) }])
try{
new Server(20001, [{
name: "bad",
exportRPCs: () => [
(aaa,bbb,ccc) => { return aaa+bbb+ccc }
]
}])
}catch(badRPCError){
console.log("expected bad-RPC error happened: "+ !!badRPCError)
}
+21 -4
View File
@@ -1,7 +1,24 @@
import { Client } from '../src/Frontend' import { Client } from '../src/Frontend'
console.log(Client)
const client = new Client(20000, 'localhost') const client = new Client(20000, 'localhost')
client.connect().then(_ => { client.connect().then(async _ => {
client.info().then(console.log) await client.info().then(console.log)
client["HelloWorldRPCGroup"].echo("x").then(console.log)
await client["HelloWorldRPCGroup"].echo("x")
.then(hopefullyX => console.log("echo('x') returned x: ", hopefullyX === "x", hopefullyX))
await client["HelloWorldRPCGroup"].add(1,2,3)
.then(hopefully6 => console.log("add(1,2,3) returned 6: ", hopefully6 === 6, hopefully6))
let counter = 0
const handler = (s) => {
counter++
if(counter === 3)
console.log("callback was called 3 times", counter === 3)
}
await client["HelloWorldRPCGroup"].subscribe(handler)
client["HelloWorldRPCGroup"].triggerCallback("test1")
client["HelloWorldRPCGroup"].triggerCallback("test2")
client["HelloWorldRPCGroup"].triggerCallback("test3")
}) })
+1
View File
@@ -0,0 +1 @@
<script src="../lib/RPCaller.min.js"></script>