start werk
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { SubscriptionResponse, SuccessResponse, ErrorResponse } from "../../../../vendor/generic/Types";
|
||||
import * as uuid from "uuid/v4"
|
||||
|
||||
type rpcType = 'hook' | 'unhook' | 'call'
|
||||
type visibility = 'public' | 'private'
|
||||
|
||||
export type Outcome = "Success" | "Error"
|
||||
|
||||
export type UnhookFunction = (uid:string) => Promise<SuccessResponse | ErrorResponse>
|
||||
export type callbackFunction = (...args) => Promise<SubscriptionResponse | ErrorResponse>
|
||||
export type AsyncFunction = (...args) => Promise<any>
|
||||
|
||||
export interface RPCExporter{
|
||||
name: string
|
||||
exportRPCs() : socketioRPC[]
|
||||
}
|
||||
|
||||
type baseRPC = {
|
||||
type: rpcType
|
||||
name: string
|
||||
visibility: visibility
|
||||
}
|
||||
|
||||
type hookRPC = baseRPC & {
|
||||
type: 'hook'
|
||||
func: callbackFunction
|
||||
unhook: UnhookFunction
|
||||
}
|
||||
|
||||
type unhookRPC = baseRPC & {
|
||||
type: 'unhook'
|
||||
func: UnhookFunction
|
||||
}
|
||||
|
||||
type callRPC = baseRPC & {
|
||||
type: 'call'
|
||||
func: (...args) => Promise<any>
|
||||
}
|
||||
|
||||
export type socketioRPC = callRPC | unhookRPC | hookRPC
|
||||
|
||||
export type baseInfo = {
|
||||
owner: string,
|
||||
argNames: string[],
|
||||
}
|
||||
|
||||
type HookInfo = baseRPC & baseInfo & {
|
||||
type: 'hook',
|
||||
generator: (socket) => callbackFunction
|
||||
unhook: UnhookFunction
|
||||
}
|
||||
|
||||
type UnhookInfo = baseRPC & baseInfo & {
|
||||
type: 'unhook',
|
||||
func: UnhookFunction
|
||||
}
|
||||
|
||||
type CallInfo = baseRPC & baseInfo & {
|
||||
type: 'call',
|
||||
func: AsyncFunction
|
||||
}
|
||||
|
||||
type RpcInfo = HookInfo | UnhookInfo | CallInfo
|
||||
|
||||
export type ExtendedRpcInfo = RpcInfo & { uniqueName: string }
|
||||
|
||||
export const rpcToRpcinfo = (rpc : socketioRPC, owner: string):RpcInfo => {
|
||||
switch(rpc.type){
|
||||
case "call" :
|
||||
return {
|
||||
owner: owner,
|
||||
argNames: extractArgs(rpc.func),
|
||||
type: rpc.type,
|
||||
visibility: rpc.visibility,
|
||||
name: rpc.name,
|
||||
func: rpc.func,
|
||||
}
|
||||
case "unhook" :
|
||||
return {
|
||||
owner: owner,
|
||||
argNames: extractArgs(rpc.func),
|
||||
type: rpc.type,
|
||||
visibility: rpc.visibility,
|
||||
name: rpc.name,
|
||||
func: rpc.func,
|
||||
}
|
||||
case "hook" :
|
||||
const generator = hookGenerator(rpc)
|
||||
return {
|
||||
owner: owner,
|
||||
argNames: extractArgs(generator(undefined)),
|
||||
type: rpc.type,
|
||||
visibility: rpc.visibility,
|
||||
name: rpc.name,
|
||||
unhook: rpc.unhook,
|
||||
generator: generator,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const rpcHooker = (socket, owner:string, RPCs: socketioRPC[], makeUnique = true):ExtendedRpcInfo[] => {
|
||||
const suffix = makeUnique?"-"+uuid().substr(0,4):""
|
||||
return RPCs.map(rpc => rpcToRpcinfo(rpc, owner))
|
||||
.map(info => {
|
||||
const ret:any = 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:hookRPC): HookInfo['generator'] => {
|
||||
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
|
||||
}`)
|
||||
}
|
||||
|
||||
function extractArgs(f:Function):string[]{
|
||||
let fn = String(f)
|
||||
let args = fn.substr(0, fn.indexOf(")"))
|
||||
args = args.substr(fn.indexOf("(")+1)
|
||||
let ret = args.split(",")
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { parseSubResponse, parseResponse } from "frontblock-generic/Types";
|
||||
import { ExtendedRpcInfo, UnhookFunction, callbackFunction, AsyncFunction } from "frontblock-generic/RPC";
|
||||
var bsock = require('bsock')
|
||||
|
||||
//fix args with defaults like "force = true" -> "force"
|
||||
function stripAfterEquals(str:string){
|
||||
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.
|
||||
*/
|
||||
export class RPCallable{
|
||||
private socket
|
||||
|
||||
constructor(){
|
||||
this.socket = bsock.connect(20000, 'localhost', false/*tls*/)
|
||||
this.init()
|
||||
}
|
||||
|
||||
// need this-context for eval-magic below
|
||||
// DO NOT REMOVE. They're not really unused
|
||||
private parseSubResponse = parseSubResponse
|
||||
private parseResponse = parseResponse
|
||||
|
||||
private async init(){
|
||||
const info:ExtendedRpcInfo[] = await this.info()
|
||||
info.forEach(i => {
|
||||
let f: any
|
||||
switch (i.type) {
|
||||
case 'call':
|
||||
f = this.callGenerator(i.uniqueName, i.argNames)
|
||||
break
|
||||
case 'hook':
|
||||
f = this.hookGenerator(i.uniqueName, i.argNames)
|
||||
break
|
||||
case 'unhook':
|
||||
f = this.unhookGenerator(i.uniqueName, i.argNames)
|
||||
break
|
||||
}
|
||||
if(this[i.owner] == null)
|
||||
this[i.owner] = {}
|
||||
this[i.owner][i.name] = f
|
||||
this[i.owner][i.name].bind(this)
|
||||
})
|
||||
}
|
||||
|
||||
async info(){
|
||||
return await this.socket.call('info')
|
||||
}
|
||||
|
||||
private callGenerator(fnName, fnArgs:string[]): AsyncFunction{
|
||||
const headerArgs = fnArgs.join(",")
|
||||
const argParams = fnArgs.map(stripAfterEquals).join(",")
|
||||
return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
|
||||
}
|
||||
|
||||
private hookGenerator(fnName, fnArgs:string[]): callbackFunction{
|
||||
const headerArgs = fnArgs.join(",")
|
||||
const argParams = fnArgs.map(stripAfterEquals).join(",")
|
||||
return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {
|
||||
const r = await this.socket.call("`+fnName+`", `+argParams+`)
|
||||
const res = await this.parseSubResponse(r);
|
||||
if(res.uid != null){
|
||||
this.socket.hook(res.uid, callback)
|
||||
}
|
||||
return res
|
||||
} )()` )
|
||||
}
|
||||
|
||||
private unhookGenerator(fnName, fnArgs:string[]): UnhookFunction{
|
||||
const headerArgs = fnArgs.join(",")
|
||||
const argParams = fnArgs.map(stripAfterEquals).join(",")
|
||||
if(fnArgs.length != 1)
|
||||
console.error("UnhookFunction", fnName, "specified more than one argument: ("+headerArgs+")")
|
||||
|
||||
return eval( `( () => async (`+headerArgs+`) => {
|
||||
const r = await this.socket.call("`+fnName+`", `+argParams+`)
|
||||
const res = await this.parseResponse(r)
|
||||
this.socket.unhook(`+argParams+`)
|
||||
return res
|
||||
} )()` )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window['rpc'] = new RPCallable()
|
||||
@@ -0,0 +1,40 @@
|
||||
const path = require('path');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
target: "web",
|
||||
entry: path.resolve(__dirname, 'module.ts'),
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../../dist'),
|
||||
filename: 'FrontendPlugin.js',
|
||||
libraryTarget: 'commonjs',
|
||||
},
|
||||
resolve: {
|
||||
// Add `.ts` and `.tsx` as a resolvable extension.
|
||||
extensions: [".ts", ".tsx", ".js"]
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.ts?$/, loader: "ts-loader" }
|
||||
]
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
exclude: [
|
||||
/\.\/(.*)\/.ts/,
|
||||
/\.\/(.*).ts/,
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
externals: {
|
||||
'@angular/core': '@angular/core',
|
||||
'@angular/common': '@angular/common',
|
||||
'@angular/router': '@angular/router',
|
||||
'@angular/animations': '@angular/animations',
|
||||
'@angular/forms': '@angular/forms',
|
||||
'@clr/angular': '@clr/angular'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user