initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
lib
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"httpPort": 8080
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)sx?$": "ts-jest"
|
||||||
|
},
|
||||||
|
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||||
|
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
|
||||||
|
}
|
||||||
Generated
+7851
File diff suppressed because it is too large
Load Diff
@@ -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/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
|
})()
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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__/*"],
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": ["tslint:recommended", "tslint-config-prettier"]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user