starts but delivers empty page
This commit is contained in:
@@ -1,256 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
import * as Logger from 'log4js'
|
||||
import { Plugin, socketioRPC } from 'frontblock-generic/Plugin';
|
||||
import { ErrorResponse, SuccessResponse } from 'frontblock-generic/Types';
|
||||
import { FrontblockApiConf } from "frontblock/FrontblockApiClient"
|
||||
|
||||
type pluginEntry = {pluginPath:string, conf?:any}
|
||||
const pluginList:pluginEntry[] = [
|
||||
//{ pluginPath: '../../paymentmanager/static/Plugin', conf:<FrontblockApiConf>{ apiHost: 'localhost' /*/ 'api.testnet.frontblock.me' */, apiPort: 10001 } },
|
||||
//{ pluginPath: '../../htmlsupplier/static/Plugin' },
|
||||
{ pluginPath: 'frontblock/FrontblockApiClient', conf:<FrontblockApiConf>{ apiHost: /*/ 'localhost' */ 'api.testnet.frontblock.me' , apiPort: 10001 } }
|
||||
]
|
||||
|
||||
const express = require('express')
|
||||
const http = require('http')
|
||||
const bsock = require('bsock')
|
||||
const kfs = require("key-file-storage").default('conf') //'conf' is a directory that will be generated if it doesn't exist
|
||||
Logger.configure({
|
||||
appenders: {
|
||||
"admin": { type: 'stdout' },
|
||||
//app: { type: 'file', filename: 'application.log' }
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: [ 'admin' ], level: 'debug' }
|
||||
}
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* FrontblockAdmin
|
||||
*
|
||||
* The customer-facing dynamic component of the customer backend
|
||||
* Supports (un)loading plugins which will be communicated to its library component (See FrontblockLib.ts and the info() RPC)
|
||||
*
|
||||
* The list of available plugins is published via the frontblock API and downloaded via gitea-releases
|
||||
*/
|
||||
export class FrontblockAdmin{
|
||||
private plugins: Plugin[]
|
||||
|
||||
private hookToUids:{[hookName:string]:string[]} = {}
|
||||
|
||||
private express
|
||||
private httpServer
|
||||
private io = bsock.createServer()
|
||||
private wsServer = http.createServer()
|
||||
|
||||
constructor(){
|
||||
if(!('FrontblockAdmin' in kfs)){
|
||||
logger.warn('No config file found! Generating one')
|
||||
kfs.FrontblockAdmin = { httpPort: 8080 }
|
||||
}
|
||||
|
||||
this.initialize()
|
||||
}
|
||||
|
||||
private async initialize(){
|
||||
await this.loadPlugins()
|
||||
this.plugins.forEach(plugin => plugin.start())
|
||||
this.startWebsocket()
|
||||
this.startWebserver()
|
||||
}
|
||||
|
||||
private async loadPlugins(){
|
||||
this.plugins = []
|
||||
pluginList.forEach(async (entry:pluginEntry) => {
|
||||
const clazz = await import(entry.pluginPath)
|
||||
let obj = new clazz.default(entry.conf)
|
||||
this.plugins.push(obj)
|
||||
})
|
||||
}
|
||||
|
||||
private initApis(socket){
|
||||
//Declare own functions
|
||||
const rpcInfos:rpcInfo[] = [
|
||||
{
|
||||
owner: 'Admin',
|
||||
name: 'restartWebserver',
|
||||
args: 'port',
|
||||
info:{
|
||||
type:'call',
|
||||
fn: (port:number) => { this.restartWebserver(port) }
|
||||
}
|
||||
},{
|
||||
owner: 'Admin',
|
||||
name: 'info',
|
||||
args: '',
|
||||
info:{
|
||||
type:'call',
|
||||
fn: () => { return rpcInfos }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
//translate RPCs to socket-bound function metadata
|
||||
this.plugins.forEach(plugin => {
|
||||
const pluginName = plugin.name
|
||||
plugin.exportRPCs().forEach(rpc => {
|
||||
const info = this.rpcToRpcInfo(pluginName, rpc)
|
||||
rpcInfos.push(info)
|
||||
})
|
||||
})
|
||||
|
||||
//Hook up all the functions
|
||||
for(const api of rpcInfos){
|
||||
switch(api.info.type){
|
||||
case 'call':
|
||||
socket.hook(api.name, api.info.fn)
|
||||
break
|
||||
case 'hook':
|
||||
const hook = api.info.generator(socket)
|
||||
hook.bind(this)
|
||||
socket.hook(api.name, hook)
|
||||
break
|
||||
case 'unhook':
|
||||
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', () => {
|
||||
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)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private startWebserver(){
|
||||
if(this.httpServer != null || this.express != null){
|
||||
logger.warn("Webserver is already running")
|
||||
return
|
||||
}
|
||||
|
||||
let port:number = kfs.FrontblockAdmin.httpPort
|
||||
this.express = express()
|
||||
this.express.use(express.static('static'))
|
||||
|
||||
this.httpServer = http.Server(this.express)
|
||||
this.httpServer.listen(port, () => {
|
||||
logger.info('Admin panel listening for HTTP on *'+port)
|
||||
})
|
||||
}
|
||||
|
||||
private stopWebserver(){
|
||||
if(this.httpServer == null || this.express == null){
|
||||
logger.warn("Webserver is not running")
|
||||
return
|
||||
}
|
||||
this.httpServer.close()
|
||||
this.httpServer = null
|
||||
this.express = null
|
||||
logger.info("Webserver stopped")
|
||||
}
|
||||
|
||||
private restartWebserver(port:number){
|
||||
this.stopWebserver()
|
||||
kfs.FrontblockAdmin = { httpPort: port }
|
||||
this.startWebserver()
|
||||
}
|
||||
|
||||
private startWebsocket(){
|
||||
try{
|
||||
this.io.attach(this.wsServer)
|
||||
this.io.on('socket', (socket) => {
|
||||
logger.info("New Websocket connection on port", socket.port)
|
||||
|
||||
const handleError = (e: any) => {
|
||||
logger.info("Websocket closing", String(e))
|
||||
socket.close()
|
||||
}
|
||||
|
||||
socket.on('error', handleError)
|
||||
this.initApis(socket)
|
||||
})
|
||||
logger.info('Admin websocket listening on *20000')
|
||||
this.wsServer.listen(20000)
|
||||
}catch(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) => {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
(async() => {
|
||||
new FrontblockAdmin()
|
||||
})()
|
||||
@@ -1,76 +0,0 @@
|
||||
import { parseSubResponse, parseResponse } from "frontblock-generic/Types";
|
||||
var bsock = require('bsock')
|
||||
|
||||
/**
|
||||
* 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 FrontblockConfigLib{
|
||||
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 = await this.info()
|
||||
for (const i of info) {
|
||||
let f: any
|
||||
switch (i.info.type) {
|
||||
case 'call':
|
||||
f = this.callGenerator(i.name, i.args)
|
||||
break
|
||||
case 'hook':
|
||||
f = this.hookGenerator(i.name, i.args)
|
||||
break
|
||||
case 'unhook':
|
||||
f = this.unhookGenerator(i.name, i.args)
|
||||
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): Function{
|
||||
return eval( '( () => async ('+fnArgs+') => { return await this.socket.call("'+fnName+'", '+fnArgs+')} )()' )
|
||||
}
|
||||
|
||||
private hookGenerator(fnName, fnArgs): Function{
|
||||
return eval( `( () => async (`+fnArgs+(fnArgs.length!==0?",":"")+` callback) => {
|
||||
const r = await this.socket.call("`+fnName+`", `+fnArgs+`)
|
||||
const res = await this.parseSubResponse(r);
|
||||
if(res.uid != null){
|
||||
this.socket.hook(res.uid, callback)
|
||||
}
|
||||
return res
|
||||
} )()` )
|
||||
}
|
||||
private unhookGenerator(fnName, fnArgs): Function{
|
||||
return eval( `( () => async (`+fnArgs+`) => {
|
||||
const r = await this.socket.call("`+fnName+`", `+fnArgs+`)
|
||||
const res = await this.parseResponse(r)
|
||||
if(res.uid != null)
|
||||
this.socket.unhook(res.uid)
|
||||
return res
|
||||
} )()` )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window['fb'] = new FrontblockConfigLib()
|
||||
@@ -1,4 +1,4 @@
|
||||
import html from './index.html'
|
||||
import html from './index'
|
||||
import './index.scss'
|
||||
import rootCtrl from './controller'
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/// <reference path='./type.d.ts'/>
|
||||
|
||||
import './index.scss'
|
||||
import angular = require('angular')
|
||||
import uiRouter from '@uirouter/angularjs'
|
||||
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
declare module '*.html' {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
|
||||
declare function require(arg: string): any
|
||||
|
||||
declare var module
|
||||
@@ -1,5 +1,5 @@
|
||||
import ctrl from './controller'
|
||||
import html from './index.html'
|
||||
import html from './index'
|
||||
import './index.scss'
|
||||
|
||||
export default {
|
||||
|
||||
Reference in New Issue
Block a user