RPC update
This commit is contained in:
Generated
+273
-161
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -26,7 +26,7 @@
|
|||||||
"child-process-promise": "^2.2.1",
|
"child-process-promise": "^2.2.1",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"frontblock": "^0.9.9",
|
"frontblock": "^0.9.9",
|
||||||
"frontblock-generic": "latest",
|
"frontblock-generic": "^0.28.4",
|
||||||
"git-cherrypicker": "0.0.3",
|
"git-cherrypicker": "0.0.3",
|
||||||
"git-describe": "^4.0.4",
|
"git-describe": "^4.0.4",
|
||||||
"http": "0.0.0",
|
"http": "0.0.0",
|
||||||
|
|||||||
+22
-155
@@ -1,14 +1,13 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
import * as Logger from 'log4js'
|
import * as Logger from 'log4js'
|
||||||
import { socketioRPC, ConfigLoader, Plugin } from 'frontblock-generic/Plugin';
|
import { ConfigLoader, Plugin } from 'frontblock-generic/Plugin';
|
||||||
|
import { socketioRPC, rpcHooker, ExtendedRpcInfo } from 'frontblock-generic/RPC';
|
||||||
import { ErrorResponse, SuccessResponse, SubscriptionResponse } from 'frontblock-generic/Types';
|
import { ErrorResponse, SuccessResponse, SubscriptionResponse } from 'frontblock-generic/Types';
|
||||||
import express = require('express');
|
import express = require('express');
|
||||||
import http = require('http');
|
import http = require('http');
|
||||||
import bsock = require('bsock');
|
import bsock = require('bsock');
|
||||||
import { FrontblockCherryPicker } from "git-cherrypicker"
|
|
||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import { GitUpdater, RepoFolderStatus, } from "./GitUpdater";
|
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { UpdateManager } from './UpdateManger';
|
import { UpdateManager } from './UpdateManger';
|
||||||
var exec = require('child-process-promise').exec;
|
var exec = require('child-process-promise').exec;
|
||||||
@@ -26,32 +25,6 @@ Logger.configure({
|
|||||||
})
|
})
|
||||||
const logger = Logger.getLogger("admin")
|
const logger = Logger.getLogger("admin")
|
||||||
|
|
||||||
type hookRPC =
|
|
||||||
{
|
|
||||||
type: 'hook',
|
|
||||||
generator: (socket) => Function,
|
|
||||||
unhook:(uid:string)=>Promise<ErrorResponse|SuccessResponse>
|
|
||||||
}
|
|
||||||
|
|
||||||
type unhookRPC =
|
|
||||||
{
|
|
||||||
type: 'unhook',
|
|
||||||
fn: Function
|
|
||||||
}
|
|
||||||
|
|
||||||
type callRPC =
|
|
||||||
{
|
|
||||||
type: 'call',
|
|
||||||
fn: Function
|
|
||||||
}
|
|
||||||
|
|
||||||
export type rpcInfo =
|
|
||||||
{
|
|
||||||
owner: string,
|
|
||||||
name: string,
|
|
||||||
args: string,
|
|
||||||
info: hookRPC | unhookRPC | callRPC
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AdminConf = { httpPort: number}
|
export type AdminConf = { httpPort: number}
|
||||||
|
|
||||||
@@ -63,9 +36,17 @@ export type AdminConf = { httpPort: number}
|
|||||||
*
|
*
|
||||||
* The list of available plugins is published via the frontblock API and downloaded via gitea-releases
|
* The list of available plugins is published via the frontblock API and downloaded via gitea-releases
|
||||||
*/
|
*/
|
||||||
export class FrontblockAdmin extends ConfigLoader<AdminConf>{
|
export class FrontblockAdmin extends ConfigLoader<AdminConf> implements Plugin<AdminConf>{
|
||||||
|
exportRPCs(): socketioRPC[] {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
start(): void | Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
stop(): void | Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
private hookToUids:{[hookName:string]:string[]} = {}
|
|
||||||
private express
|
private express
|
||||||
private httpServer
|
private httpServer
|
||||||
private io = bsock.createServer()
|
private io = bsock.createServer()
|
||||||
@@ -93,88 +74,22 @@ export class FrontblockAdmin extends ConfigLoader<AdminConf>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initApis(socket){
|
private initApis(socket){
|
||||||
//Declare own functions
|
|
||||||
const rpcInfos:rpcInfo[] = [
|
const adminRPCs:socketioRPC[] = [{
|
||||||
{
|
|
||||||
owner: 'Admin',
|
|
||||||
name: 'restartWebserver',
|
|
||||||
args: 'port',
|
|
||||||
info:{
|
|
||||||
type:'call',
|
|
||||||
fn: (port:number) => { this.restartWebserver(port) }
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
owner: 'Admin',
|
|
||||||
name: 'info',
|
name: 'info',
|
||||||
args: '',
|
|
||||||
info:{
|
|
||||||
type: 'call',
|
type: 'call',
|
||||||
fn: () => { return rpcInfos }
|
visibility: 'private',
|
||||||
}
|
func: async () => rpcInfos
|
||||||
}
|
}]
|
||||||
|
|
||||||
|
const rpcInfos:ExtendedRpcInfo[] = [
|
||||||
|
...rpcHooker(socket, "Admin", adminRPCs, false),
|
||||||
|
...rpcHooker(socket, "UpdateManager", this.updateManager.exportRPCs()),
|
||||||
|
...this.updateManager.getLoadedPlugins().flatMap(plugin => rpcHooker(socket, plugin.name, plugin.exportRPCs()))
|
||||||
]
|
]
|
||||||
|
|
||||||
//translate RPCs to socket-bound function metadata
|
|
||||||
const loadedPlugins:Plugin[] = [this.updateManager, ...this.updateManager.getLoadedPlugins()]
|
|
||||||
loadedPlugins.forEach(plugin => plugin.exportRPCs().forEach(rpc => {
|
|
||||||
const info = this.rpcToRpcInfo(plugin.name, rpc)
|
|
||||||
rpcInfos.push(info)
|
|
||||||
}))
|
|
||||||
|
|
||||||
//Hook up all the functions
|
|
||||||
for(const api of rpcInfos){
|
|
||||||
switch(api.info.type){
|
|
||||||
case 'call':
|
|
||||||
try{
|
|
||||||
socket.unhook(api.name)
|
|
||||||
}catch(e){
|
|
||||||
|
|
||||||
}
|
|
||||||
socket.hook(api.name, api.info.fn)
|
|
||||||
break
|
|
||||||
case 'hook':
|
|
||||||
const hook = api.info.generator(socket)
|
|
||||||
hook.bind(this)
|
|
||||||
try{
|
|
||||||
socket.unhook(api.name)
|
|
||||||
}catch(e){
|
|
||||||
|
|
||||||
}
|
|
||||||
socket.hook(api.name, hook)
|
|
||||||
break
|
|
||||||
case 'unhook':
|
|
||||||
try{
|
|
||||||
socket.unhook(api.name)
|
|
||||||
}catch(e){
|
|
||||||
|
|
||||||
}
|
|
||||||
socket.hook(api.name, api.info.fn)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//initialize the lists of open hooks
|
|
||||||
rpcInfos
|
|
||||||
.filter(rpc => rpc.info.type === "hook")
|
|
||||||
.forEach(hook => {
|
|
||||||
this.hookToUids[hook.name] = []
|
|
||||||
})
|
|
||||||
|
|
||||||
//On close, unhook open hooks
|
|
||||||
socket.on('close', () => {
|
socket.on('close', () => {
|
||||||
logger.info("Client disconnected")
|
logger.info("Client disconnected")
|
||||||
|
|
||||||
rpcInfos.forEach((rpc) => {
|
|
||||||
if(this.hookToUids[rpc.name] == null)
|
|
||||||
return
|
|
||||||
|
|
||||||
this.hookToUids[rpc.name].forEach((uid) => {
|
|
||||||
if(rpc.info.type === "hook"){
|
|
||||||
logger.info("Closing consumer `"+uid+"` owned by `"+rpc.name+"`")
|
|
||||||
rpc.info.unhook(uid)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,12 +153,6 @@ export class FrontblockAdmin extends ConfigLoader<AdminConf>{
|
|||||||
logger.info("Webserver stopped")
|
logger.info("Webserver stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
private restartWebserver(port:number){
|
|
||||||
this.stopWebserver()
|
|
||||||
this.setConfigKey("httpPort", port)
|
|
||||||
this.startWebserver()
|
|
||||||
}
|
|
||||||
|
|
||||||
private startWebsocket(){
|
private startWebsocket(){
|
||||||
try{
|
try{
|
||||||
this.io.attach(this.wsServer)
|
this.io.attach(this.wsServer)
|
||||||
@@ -264,48 +173,6 @@ export class FrontblockAdmin extends ConfigLoader<AdminConf>{
|
|||||||
logger.error(String(e))
|
logger.error(String(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private rpcToRpcInfo(owner:string, rpc:socketioRPC):rpcInfo{
|
|
||||||
switch(rpc.type){
|
|
||||||
case 'hook':
|
|
||||||
let f = this.hookGenerator(rpc)
|
|
||||||
return {owner: owner, name: rpc.name, args: this.extractArgs(f(null)), info: { type: 'hook', generator: f, unhook: rpc.unhook } }
|
|
||||||
case 'unhook':
|
|
||||||
return {owner: owner, name: rpc.name, args: this.extractArgs(rpc.rpc), info: { type: 'unhook', fn: rpc.rpc } }
|
|
||||||
case 'call':
|
|
||||||
return {owner: owner, name: rpc.name, args: this.extractArgs(rpc.rpc), info: { type: 'call', fn: rpc.rpc } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates RPC hooks which support a callback.
|
|
||||||
*
|
|
||||||
* Note callbacks *need* to accept a singular argument and they have to be the last parameter!
|
|
||||||
*/
|
|
||||||
hookGenerator = (rpc:socketioRPC): (socket)=>(...args:any[])=>Promise<SubscriptionResponse> => {
|
|
||||||
const argsArr = this.extractArgs(rpc.rpc).split(',')
|
|
||||||
argsArr.pop()
|
|
||||||
const args = argsArr.join(',')
|
|
||||||
|
|
||||||
return eval(`(socket) => async (`+args+`) => {
|
|
||||||
const res = await rpc.rpc(`+args+(args.length!==0?',':'')+` (x) => {
|
|
||||||
socket.call(res.uid, x).catch(e => {
|
|
||||||
logger.debug(String(e))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if(res.uid != null){
|
|
||||||
this.hookToUids[rpc.name].push(res.uid)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractArgs(f:Function):string{
|
|
||||||
let fn = String(f)
|
|
||||||
let args = fn.substr(0, fn.indexOf(")"))
|
|
||||||
args = args.substr(fn.indexOf("(")+1)
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new FrontblockAdmin()
|
new FrontblockAdmin()
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { parseSubResponse, parseResponse } from "frontblock-generic/Types";
|
import { parseSubResponse, parseResponse } from "frontblock-generic/Types";
|
||||||
|
import { ExtendedRpcInfo, UnhookFunction, callbackFunction, AsyncFunction } from "frontblock-generic/RPC";
|
||||||
var bsock = require('bsock')
|
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
|
* Dynamic library to communicate with FrontblockService remotely
|
||||||
*
|
*
|
||||||
@@ -8,7 +14,7 @@ var bsock = require('bsock')
|
|||||||
* Will ask it's service for available RPCs and parse them into methods of this object
|
* Will ask it's service for available RPCs and parse them into methods of this object
|
||||||
* for convenient access.
|
* for convenient access.
|
||||||
*/
|
*/
|
||||||
class FrontblockConfigLib{
|
export class FrontblockConfigLib{
|
||||||
private socket
|
private socket
|
||||||
|
|
||||||
constructor(){
|
constructor(){
|
||||||
@@ -22,38 +28,42 @@ class FrontblockConfigLib{
|
|||||||
private parseResponse = parseResponse
|
private parseResponse = parseResponse
|
||||||
|
|
||||||
private async init(){
|
private async init(){
|
||||||
const info = await this.info()
|
const info:ExtendedRpcInfo[] = await this.info()
|
||||||
for (const i of info) {
|
info.forEach(i => {
|
||||||
let f: any
|
let f: any
|
||||||
switch (i.info.type) {
|
switch (i.type) {
|
||||||
case 'call':
|
case 'call':
|
||||||
f = this.callGenerator(i.name, i.args)
|
f = this.callGenerator(i.uniqueName, i.argNames)
|
||||||
break
|
break
|
||||||
case 'hook':
|
case 'hook':
|
||||||
f = this.hookGenerator(i.name, i.args)
|
f = this.hookGenerator(i.uniqueName, i.argNames)
|
||||||
break
|
break
|
||||||
case 'unhook':
|
case 'unhook':
|
||||||
f = this.unhookGenerator(i.name, i.args)
|
f = this.unhookGenerator(i.uniqueName, i.argNames)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if(this[i.owner] == null)
|
if(this[i.owner] == null)
|
||||||
this[i.owner] = {}
|
this[i.owner] = {}
|
||||||
this[i.owner][i.name] = f
|
this[i.owner][i.name] = f
|
||||||
this[i.owner][i.name].bind(this)
|
this[i.owner][i.name].bind(this)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async info(){
|
async info(){
|
||||||
return await this.socket.call('info')
|
return await this.socket.call('info')
|
||||||
}
|
}
|
||||||
|
|
||||||
private callGenerator(fnName, fnArgs): Function{
|
private callGenerator(fnName, fnArgs:string[]): AsyncFunction{
|
||||||
return eval( '( () => async ('+fnArgs+') => { return await this.socket.call("'+fnName+'", '+fnArgs+')} )()' )
|
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): Function{
|
private hookGenerator(fnName, fnArgs:string[]): callbackFunction{
|
||||||
return eval( `( () => async (`+fnArgs+(fnArgs.length!==0?",":"")+` callback) => {
|
const headerArgs = fnArgs.join(",")
|
||||||
const r = await this.socket.call("`+fnName+`", `+fnArgs+`)
|
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);
|
const res = await this.parseSubResponse(r);
|
||||||
if(res.uid != null){
|
if(res.uid != null){
|
||||||
this.socket.hook(res.uid, callback)
|
this.socket.hook(res.uid, callback)
|
||||||
@@ -62,13 +72,16 @@ class FrontblockConfigLib{
|
|||||||
} )()` )
|
} )()` )
|
||||||
}
|
}
|
||||||
|
|
||||||
private unhookGenerator(fnName, fnArgs): Function{
|
private unhookGenerator(fnName, fnArgs:string[]): UnhookFunction{
|
||||||
return eval( `( () => async (`+fnArgs+`) => {
|
const headerArgs = fnArgs.join(",")
|
||||||
const r = await this.socket.call("`+fnName+`", `+fnArgs+`)
|
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)
|
const res = await this.parseResponse(r)
|
||||||
console.log(res)
|
this.socket.unhook(`+argParams+`)
|
||||||
if(res.uid != null)
|
|
||||||
this.socket.unhook(res.uid)
|
|
||||||
return res
|
return res
|
||||||
} )()` )
|
} )()` )
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Plugin, socketioRPC } from "frontblock-generic/Plugin";
|
import { Plugin } from "frontblock-generic/Plugin";
|
||||||
|
import { socketioRPC } from "frontblock-generic/RPC";
|
||||||
import { GitUpdater, RepoFolderStatus } from "./GitUpdater";
|
import { GitUpdater, RepoFolderStatus } from "./GitUpdater";
|
||||||
import { FrontblockCherryPicker } from "git-cherrypicker";
|
import { FrontblockCherryPicker } from "git-cherrypicker";
|
||||||
import * as Logger from 'log4js'
|
import * as Logger from 'log4js'
|
||||||
@@ -33,32 +34,32 @@ export class UpdateManager implements Plugin{
|
|||||||
exportRPCs(): socketioRPC[] {
|
exportRPCs(): socketioRPC[] {
|
||||||
return [{
|
return [{
|
||||||
name: 'installPlugin',
|
name: 'installPlugin',
|
||||||
rpc: async (name:string, force = false) => {return await this.installPlugin(name, force)},
|
func: async (name:string, force = false) => {return await this.installPlugin(name, force)},
|
||||||
type: 'call',
|
type: 'call',
|
||||||
visibility: 'private'
|
visibility: 'private'
|
||||||
},{
|
},{
|
||||||
name: 'startPlugin',
|
name: 'startPlugin',
|
||||||
rpc: async (name:string) => {return await this.startPlugin(name)},
|
func: async (name:string) => {return await this.startPlugin(name)},
|
||||||
type: 'call',
|
type: 'call',
|
||||||
visibility: 'private'
|
visibility: 'private'
|
||||||
},{
|
},{
|
||||||
name: 'updatePlugin',
|
name: 'updatePlugin',
|
||||||
rpc: async (name) => {return await this.updatePlugin(name)},
|
func: async (name) => {return await this.updatePlugin(name)},
|
||||||
type: 'call',
|
type: 'call',
|
||||||
visibility: 'private'
|
visibility: 'private'
|
||||||
},{
|
},{
|
||||||
name: 'setPluginVersion',
|
name: 'setPluginVersion',
|
||||||
rpc: async (name, tag) => {return await this.setPluginVersion(name, tag)},
|
func: async (name, tag) => {return await this.setPluginVersion(name, tag)},
|
||||||
type: 'call',
|
type: 'call',
|
||||||
visibility: 'private'
|
visibility: 'private'
|
||||||
},{
|
},{
|
||||||
name: 'updateDashboard',
|
name: 'updateDashboard',
|
||||||
rpc: async () => {return await this.updateDashboard()},
|
func: async () => {return await this.updateDashboard()},
|
||||||
type: 'call',
|
type: 'call',
|
||||||
visibility: 'private'
|
visibility: 'private'
|
||||||
},{
|
},{
|
||||||
name: 'getLoadedPluginNames',
|
name: 'getLoadedPluginNames',
|
||||||
rpc: async () => {return await this.getLoadedPluginNames()},
|
func: async () => {return await this.getLoadedPluginNames()},
|
||||||
type: 'call',
|
type: 'call',
|
||||||
visibility: 'private'
|
visibility: 'private'
|
||||||
}]
|
}]
|
||||||
|
|||||||
Reference in New Issue
Block a user