initial commit

This commit is contained in:
peter
2019-06-17 14:25:27 +02:00
commit 369e9171ad
22 changed files with 8538 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
node_modules
lib
+3
View File
@@ -0,0 +1,3 @@
{
"httpPort": 8080
}
+7
View File
@@ -0,0 +1,7 @@
{
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}
+7851
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
{
"name": "assignment1",
"version": "1.0.0",
"scripts": {
"start": "npm run build; node --experimental-modules lib/FrontblockService.js",
"build": "npm run clean; tsc; node_modules/.bin/webpack",
"clean": "rm -rf lib",
"update-frontblock": "rm -rf node_modules/frontblock*; npm install"
},
"repository": {
"type": "git",
"url": "http://todo"
},
"author": "frontblock.me",
"license": "ISC",
"dependencies": {
"@types/express": "^4.16.1",
"@types/node": "^11.13.13",
"bsock": "^0.1.9",
"express": "^4.16.4",
"frontblock": "latest",
"http": "0.0.0",
"key-file-storage": "^2.1.5",
"log4js": "^4.3.1",
"node-fetch": "^2.6.0",
"socket.io": "^2.2.0"
},
"devDependencies": {
"@types/jest": "^24.0.11",
"jest": "^24.7.1",
"prettier": "^1.16.4",
"terser-webpack-plugin": "^1.3.0",
"ts-jest": "^24.0.2",
"tslint": "^5.15.0",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.4.2",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.1"
},
"files": [
"lib/**/*"
]
}
+75
View File
@@ -0,0 +1,75 @@
import { parseSubResponse, parseResponse } from "frontblock-generic/Service";
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
}
this[i.name] = f
this[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()
+222
View File
@@ -0,0 +1,222 @@
'use strict'
import * as Logger from 'log4js'
import { Plugin, socketioRPC } from 'frontblock-generic/Plugin';
import { ErrorResponse, SuccessResponse } from 'frontblock-generic/Service';
const pluginList = [
'../../paymentmanager/static/Plugin',
'frontblock/FrontblockLib'
]
const express = require('express')
const http = require('http')
const bsock = require('bsock')
const kfs = require("key-file-storage")('conf') //'conf' is a directory that will be generated if it doesn't exist
const logger = Logger.getLogger() // logs to STDOUT
logger.level = 'debug'
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 = {
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 express
private httpServer
private io = bsock.createServer()
private wsServer = http.createServer()
constructor(){
if(!('FrontblockService' in kfs)){
logger.warn('No config file found! Generating one')
kfs.FrontblockService = { httpPort: 8080 }
}
this.initialize()
}
private async initialize(){
await this.loadPlugins()
this.plugins.forEach(plugin => plugin.start())
this.startWebsocket()
this.startWebserver()
}
private async loadPlugins(){
const promises = pluginList.map(path => {
return import(path)
})
const pluginsClasses = await Promise.all(promises)
this.plugins = pluginsClasses.map(clazz => new clazz.default())
}
private initApis(socket){
const rpcInfos:rpcInfo[] = [
{
name: 'restartWebserver',
args: 'port',
info:{
type:'call',
fn: (port:number) => { this.restartWebserver(port) }
}
},{
name: 'info',
args: '',
info:{
type:'call',
fn: () => { return rpcInfos }
}
}
]
this.plugins.forEach(plugin => {
plugin.exportRPCs().forEach(rpc => rpcInfos.push(this.rpcToRpcInfo(rpc)))
})
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
}
}
socket.on('close', () => {
logger.info("Client disconnected")
rpcInfos.forEach(rpc => {
socket.unhook(rpc.name)
})
})
}
private startWebserver(){
if(this.httpServer != null || this.express != null){
logger.warn("Webserver is already running")
return
}
let port:number = kfs.FrontblockService.httpPort
this.express = express()
this.express.use(express.static('static'))
this.httpServer = http.Server(this.express)
this.httpServer.listen(port, () => {
logger.info('listening 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.FrontblockService = { 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('websocket listening on *20000')
this.wsServer.listen(20000)
}catch(e){
logger.error(String(e))
}
}
private rpcToRpcInfo(rpc:socketioRPC):rpcInfo{
switch(rpc.type){
case 'hook':
let f = this.hookGenerator(rpc)
return {name: rpc.name, args: this.extractArgs(f(null)), info: { type: 'hook', generator: f, unhook: rpc.unhook } }
case 'unhook':
return {name: rpc.name, args: this.extractArgs(rpc.rpc), info: { type: 'unhook', fn: rpc.rpc } }
case 'call':
return {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!
*
* TODO: Maybe kill open callbacks when socket closes? See rpc.info.unhook for the appropriate function to call.
*/
hookGenerator = (rpc) => {
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) => {
if(res.uid != null){
console.log("calling "+res.uid)
socket.call(res.uid, x).catch(e => {
rpc.unhook(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()
})()
+4
View File
@@ -0,0 +1,4 @@
import { Greeter } from '../index';
test('My Greeter', () => {
expect(Greeter('Carl')).toBe('Hello Carl');
});
File diff suppressed because one or more lines are too long
+12
View File
@@ -0,0 +1,12 @@
<html>
<head>
<script src="FrontblockLib.js"></script>
</head>
<body>
yo whatup <a href="/wallet">wallets are here</a><br />
yo whatup <a href="/paymentmanager">paymentmanager is here</a><br />
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<!doctype html> <html lang=en> <head> <meta charset=UTF-8> <title>Document</title> <link href="css/app.css?v=a5f963f6" rel="stylesheet"></head> <body> <root></root> <script type="text/javascript" src="js/vendor.461d1c56.js"></script><script type="text/javascript" src="js/app.5d10c2e6.js"></script></body> </html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<!doctype html> <html lang=en> <head> <meta charset=UTF-8> <title>Document</title> <link href="css/app.css?v=a54d3946" rel="stylesheet"></head> <body> <root></root> <script type="text/javascript" src="js/vendor.195e4982.js"></script><script type="text/javascript" src="js/app.10653f4e.js"></script></body> </html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"strictPropertyInitialization": false,
"noImplicitAny": false,
"target": "ESnext",
"module": "commonjs",
"declaration": true,
"outDir": "./lib",
"strict": true
},
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"],
}
+3
View File
@@ -0,0 +1,3 @@
{
"extends": ["tslint:recommended", "tslint-config-prettier"]
}
+46
View File
@@ -0,0 +1,46 @@
var path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
entry: path.resolve(__dirname, 'lib/FrontblockLib.js'),
output: {
path: path.resolve(__dirname, 'static'),
filename: 'FrontblockLib.js',
},
module: {
rules: [
{ test: /\FrontblockLib.ts/, use: 'ts-loader' }
]
},
optimization: {
minimizer: [new TerserPlugin({
cache: true,
parallel: true,
terserOptions:{
mangle: false,
keep_classnames: true
}
})],
splitChunks: {
cacheGroups: {
common: {
chunks: 'all',
minChunks: 2,
maxInitialRequests: 5,
minSize: 0,
name: 'common'
},
vendor: {
test: /node_modules/,
chunks: 'all',
name: 'vendor',
priority: 10,
enforce: true,
minChunks: 2
}
}
}
},
};