fox-wamp
Version:
Web Application Message Router/Server WAMP/MQTT
338 lines (292 loc) • 7.92 kB
JavaScript
const util = require('util')
const { defaultParse, restoreUri } = require('../topic_pattern')
const Context = require('../context')
const { RESULT_OK, RESULT_ACK, RESULT_EMIT, RESULT_ERR,
REQUEST_TASK, REQUEST_EVENT } = require('../messages')
const { errorCodes } = require('../realm_error')
const { getBodyValue } = require('../base_gate')
const { parseHyperBody } = require('./gate')
function localAck(action, cmd) {
action.resolve(cmd.qid)
}
function localOkey(action, cmd) {
action.resolve(getBodyValue(cmd.data))
}
function localError(action, cmd) {
action.reject(cmd.data)
}
function localEvent(action, cmd) {
let eventOpt = {
publisher: cmd.sid,
publication: cmd.qid,
topic: restoreUri(cmd.uri),
// retained: false,
headers: cmd.hdr
}
action.cb(getBodyValue(cmd.data), eventOpt)
}
function localEmit(action, cmd) {
action.cb(getBodyValue(cmd.data))
}
function localInvoke(ctx, realm, action, cmd) {
const callOpt = Object.assign(
{
procedure: restoreUri(cmd.uri),
progress: (progressBody, opt) => {
realm.cmdYield(ctx, {
qid: cmd.qid,
rqt: RESULT_EMIT,
data: parseHyperBody(progressBody),
opt: opt
})
},
headers: cmd.hdr
},
cmd.opt
)
try {
const taskResult = action.cb(getBodyValue(cmd.data), callOpt)
Promise.resolve(taskResult).then(result => {
realm.cmdYield(ctx, {
rqt: RESULT_OK,
qid: cmd.qid,
data: parseHyperBody(result)
})
})
} catch (e) {
realm.cmdYield(ctx, {
rqt: RESULT_ERR,
qid: cmd.qid,
err: errorCodes.ERROR_CALLEE_FAILURE,
data: e.message
})
}
}
class HyperApiContext extends Context {
constructor (router, session, realm) {
super(router, session)
this._realm = realm
}
sendInvoke (cmd) {
localInvoke(this, this._realm, cmd.id, cmd)
}
sendResult (cmd) {
if (cmd.rsp === RESULT_EMIT) {
localEmit(cmd.id, cmd)
} else {
localOkey(cmd.id, cmd)
}
}
sendEvent (cmd) {
localEvent(cmd.id, cmd)
}
sendOkey (cmd) {
localOkey(cmd.id, cmd)
}
sendRegistered (cmd) {
localAck(cmd.id, cmd)
}
sendUnregistered (cmd) {
localOkey(cmd.id, cmd)
}
sendSubscribed (cmd) {
localAck(cmd.id, cmd)
}
sendUnsubscribed (cmd) {
localOkey(cmd.id, cmd)
}
sendEndSubscribe (cmd) {
localOkey(cmd.id, cmd)
}
sendPublished (cmd) {
localAck(cmd.id, cmd)
}
sendError (cmd, code, text) {
if (cmd.id && cmd.id.reject) {
cmd.id.reject({ error: code, message: text })
}
}
}
function HyperClient (realm, ctx) {
this.afterOpen = function (callback) {
callback()
}
this.echo = function (data) {
return new Promise((resolve, reject) => {
realm.cmdEcho(ctx, {
id: {resolve, reject},
data: parseHyperBody(data)
})
})
}
// API functions
// register callback = function(id, args, kwargs, opt)
this.register = (uri, cb, opt) => {
return new Promise((resolve, reject) => {
realm.cmdRegRpc(ctx, {
id: {cb, resolve, reject},
uri: defaultParse(uri),
opt: opt || {}
})
})
}
this.unregister = function (regId) {
return new Promise((resolve, reject) => {
// todo: restoreUri(
realm.cmdUnRegRpc(ctx, {
id: {resolve, reject},
unr: regId
})
})
}
this.callrpc = function (uri, data, opt) {
const callOpt = opt || {}
const progress_cb = callOpt.progress
delete callOpt.progress
return new Promise((resolve, reject) => {
realm.cmdCallRpc(ctx, {
id: {cb: progress_cb, resolve, reject},
uri: defaultParse(uri),
data: parseHyperBody(data),
opt: callOpt
})
})
}
// event (args, headers, opt.publication)
// resolve traceId
this.subscribe = function (uri, cb, opt) {
return new Promise((resolve, reject) => {
return realm.cmdTrace(ctx, {
id: {cb, resolve, reject},
uri: defaultParse(uri),
opt: opt || {}
})
})
}
this.unsubscribe = function (subId) {
return new Promise((resolve, reject) => {
// todo: restoreUri(
realm.cmdUnTrace(ctx, {
id: {resolve, reject},
unr: subId
})
})
}
this.publish = function (uri, data, opt) {
opt = opt || {}
if (opt.exclude_me !== false) {
opt.exclude_me = true
}
let result
let subContainer
let ack = false
if ('acknowledge' in opt) {
ack = true
delete opt.acknowledge
subContainer = {}
result = new Promise((resolve, reject) => {
subContainer.resolve = resolve
subContainer.reject = reject
})
} else {
result = Promise.resolve()
}
let headers
if ('headers' in opt) {
headers = opt.headers
delete opt.headers
} else {
headers = {}
}
// do not wait if no ack in request
realm.cmdPush(ctx, {
id: subContainer,
uri: defaultParse(uri),
opt,
data: parseHyperBody(data),
hdr: headers,
ack
})
return result
}
}
// implements realm interface
function HyperSocketFormatter (socketWriter) {
let commandId = 0
let cmdList = new Map()
this.sendCommand = (id, command) => {
command.id = ++commandId
cmdList.set(commandId, id)
socketWriter.hyperPkgWrite(command)
return commandId
}
this.cmdEcho = function (ctx, cmd) {
return this.sendCommand(cmd.id, {ft: 'ECHO', data: cmd.data})
}
this.cmdRegRpc = function (ctx, cmd) {
return this.sendCommand(cmd.id, {ft: 'REG', uri: cmd.uri, opt: cmd.opt})
}
this.cmdUnRegRpc = function (ctx, cmd) {
return this.sendCommand(cmd.id, {ft: 'UNREG', unr: cmd.unr})
}
this.cmdCallRpc = function (ctx, cmd) {
return this.sendCommand(cmd.id, {ft: 'CALL', uri: cmd.uri, data: cmd.data, opt: cmd.opt})
}
this.cmdTrace = function (ctx, cmd) {
return this.sendCommand(cmd.id, {ft: 'TRACE', uri: cmd.uri, opt: cmd.opt})
}
this.cmdUnTrace = function (ctx, cmd) {
return this.sendCommand(cmd.id, {ft: 'UNTRACE', unr: cmd.unr})
}
this.cmdPush = function (ctx, cmd) {
return this.sendCommand(cmd.id, {ft: 'PUSH', uri: cmd.uri, data: cmd.data, hdr: cmd.hdr, opt: cmd.opt, ack: cmd.ack})
}
this.cmdYield = function (ctx, cmd) {
socketWriter.hyperPkgWrite(Object.assign({ft: 'YIELD'}, cmd))
}
const settle = (action, cmd) => {
let mode = cmd.rsp || ''
switch (mode) {
case RESULT_ACK: localAck(action, cmd); return false
case RESULT_OK: localOkey(action, cmd); return true
case RESULT_ERR: localError(action, cmd); return true
case RESULT_EMIT: localEmit(action, cmd); return false
case REQUEST_EVENT: localEvent(action, cmd); return false
case REQUEST_TASK: localInvoke(this, this, action, cmd); return false
default:
action.reject(cmd.data)
return true
}
}
this.onMessage = (msg) => {
if (msg.id && cmdList.has(msg.id)) {
let action = cmdList.get(msg.id)
if (settle(action, msg)) {
delete cmdList[msg.id]
}
} else {
// unknown command ID arrived, nothing to do, could write error?
console.log('UNKNOWN PACKAGE', msg)
}
}
}
function RemoteHyperClient (formater) {
HyperClient.call(this, formater, formater)
const cmdLogin = function (cmd) {
return formater.sendCommand(cmd.id, {ft: 'LOGIN', data: cmd.data})
}
this.login = function (data) {
return new Promise((resolve, reject) => {
cmdLogin({
id: {resolve, reject},
data: data
})
})
}
}
util.inherits(RemoteHyperClient, HyperClient)
exports.HyperClient = HyperClient
exports.HyperApiContext = HyperApiContext
exports.HyperSocketFormatter = HyperSocketFormatter
exports.RemoteHyperClient = RemoteHyperClient