mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-06-16 18:29:08 +00:00
flowtyped everything, some functional, safety, and structural changes
This commit is contained in:
parent
6f3eca7a64
commit
d2aecb38ca
92 changed files with 17554 additions and 1440 deletions
111
rpc/index.js
Normal file
111
rpc/index.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
// @flow
|
||||
import logger from '../logger'
|
||||
import fnv from 'fnv-plus'
|
||||
import autoloader from './_autoloader'
|
||||
import RPCError from './_error'
|
||||
import type Roleypoly from '../Roleypoly'
|
||||
import type betterRouter from 'koa-better-router'
|
||||
import { type Context } from 'koa'
|
||||
const log = logger(__filename)
|
||||
|
||||
export default class RPCServer {
|
||||
ctx: Roleypoly
|
||||
|
||||
rpcMap: {
|
||||
[rpc: string]: Function
|
||||
}
|
||||
|
||||
mapHash: string
|
||||
rpcCalls: { name: string, args: number }[]
|
||||
|
||||
constructor (ctx: Roleypoly) {
|
||||
this.ctx = ctx
|
||||
this.reload()
|
||||
}
|
||||
|
||||
reload () {
|
||||
// actual function map
|
||||
this.rpcMap = autoloader(this.ctx.ctx)
|
||||
|
||||
// hash of the map
|
||||
// used for known-reloads in the client.
|
||||
this.mapHash = fnv.hash(Object.keys(this.rpcMap)).str()
|
||||
|
||||
// call map for the client.
|
||||
this.rpcCalls = Object.keys(this.rpcMap).map(fn => ({ name: this.rpcMap[fn].name, args: this.rpcMap[fn].length - 1 }))
|
||||
}
|
||||
|
||||
hookRoutes (router: betterRouter) {
|
||||
// RPC call reporter.
|
||||
// this is NEVER called in prod.
|
||||
// it is used to generate errors if RPC calls don't exist or are malformed in dev.
|
||||
router.get('/api/_rpc', async (ctx) => {
|
||||
ctx.body = {
|
||||
hash: this.mapHash,
|
||||
available: this.rpcCalls
|
||||
}
|
||||
ctx.status = 200
|
||||
return true
|
||||
})
|
||||
|
||||
router.post('/api/_rpc', this.handleRPC.bind(this))
|
||||
}
|
||||
|
||||
async handleRPC (ctx: Context) {
|
||||
// handle an impossible situation
|
||||
if (!(ctx.request.body instanceof Object)) {
|
||||
return this.rpcError(ctx, null, new RPCError('RPC format was very incorrect.', 400))
|
||||
}
|
||||
|
||||
// check if RPC exists
|
||||
const { fn, args } = ctx.request.body
|
||||
|
||||
if (!(fn in this.rpcMap)) {
|
||||
return this.rpcError(ctx, null, new RPCError(`RPC call ${fn}(...) not found.`, 404))
|
||||
}
|
||||
|
||||
const call = this.rpcMap[fn]
|
||||
|
||||
// if call.length (which is the solid args list)
|
||||
// is longer than args, we have too little to call the function.
|
||||
if (args.length < call.length) {
|
||||
return this.rpcError(ctx, null, new RPCError(`RPC call ${fn}() with ${args.length} arguments does not exist.`, 400))
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await call(ctx, ...args)
|
||||
|
||||
ctx.body = {
|
||||
hash: this.mapHash,
|
||||
response
|
||||
}
|
||||
|
||||
ctx.status = 200
|
||||
} catch (err) {
|
||||
return this.rpcError(ctx, 'RPC call errored', err, 500)
|
||||
}
|
||||
}
|
||||
|
||||
rpcError (ctx: Context & {body: any}, msg: ?string, err: ?Error = null, code: ?number = null) {
|
||||
log.error('rpc error', { msg, err })
|
||||
|
||||
ctx.body = {
|
||||
msg,
|
||||
error: true
|
||||
}
|
||||
|
||||
if (err instanceof RPCError) {
|
||||
// this is one of our own errors, so we have a lot of data on this.
|
||||
ctx.status = err.code || code || 500
|
||||
ctx.body.msg = `${msg || 'RPC Error'}: ${err.message}`
|
||||
} else {
|
||||
if (msg == null && err != null) {
|
||||
ctx.body.msg = msg = err.message
|
||||
}
|
||||
|
||||
// if not, just play cloudflare, say something was really weird, and move on.
|
||||
ctx.status = code || 520
|
||||
ctx.message = 'Unknown Error'
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue