dop
Version:
Distributed Object Protocol.
173 lines (150 loc) • 5.62 kB
JavaScript
import { isFunction, isInteger, isArray } from '../util/is.js'
import { createRequest, localProcedureCall } from '../util/nodes.js'
import { RPC_CREATOR } from '../const.js'
export default function createNodeFactory({ encode, decode }) {
return function createNode({
serialize = JSON.stringify,
deserialize = JSON.parse,
rpcFilter = (props) => props.rpc,
errorInstances = [Error],
} = {}) {
const local_rpcs_id = {}
const local_rpcs = new Map()
let local_rpc_index = 1 // 0 is reserved for the entry function used in open()
const requests = {}
let request_id_index = 0
const encode_params = {
local_rpcs,
registerLocalRpcFromEncode,
}
function registerRpc(function_id, fn) {
local_rpcs_id[function_id] = fn
local_rpcs.set(fn, function_id)
return function_id
}
function createRpc(function_id) {
const makeCall = (request_id, args) => {
const data = [request_id, function_id]
if (args.length > 0) data.push(args)
node.send(encode(data, encode_params))
return data
}
const rpc = (...args) => {
const request_id = ++request_id_index
const req = createRequest()
const { resolve, reject } = req
const resolveOrReject = (fn, value) => {
fn(value)
delete requests[request_id]
return req
}
req.data = makeCall(request_id, args)
req.node = node
req.createdAt = new Date().getTime()
req.resolve = (value) => resolveOrReject(resolve, value)
req.reject = (error) => resolveOrReject(reject, error)
requests[request_id] = req
return req
}
rpc.push = (...args) => {
makeCall(0, args)
}
return rpc
}
function createRemoteFunction({
function_id,
function_creator,
caller,
path,
}) {
return rpcFilter({
rpc: createRpc(function_id),
node,
function_id,
function_creator,
caller,
path,
})
}
function getNextLocalFunctionId() {
while (local_rpcs_id.hasOwnProperty(local_rpc_index)) {
local_rpc_index += 1
}
return local_rpc_index
}
function open(send, fn) {
const function_id = 0
if (isFunction(fn)) registerRpc(function_id, fn)
node.send = (msg) => send(serialize(msg))
return createRemoteFunction({
function_id,
function_creator: RPC_CREATOR.ENTRY,
})
}
function message(msg) {
msg = deserialize(msg)
if (!isArray(msg) || !isInteger(msg[0])) {
return false
}
const [id, function_id] = msg
const is_request = id > -1
const fn = is_request ? local_rpcs_id[function_id] : undefined
msg = decode(msg, {
createRemoteFunction,
caller: fn,
function_creator: is_request
? RPC_CREATOR.REQUEST
: RPC_CREATOR.RESPONSE,
})
let args = msg[2]
const response_id = -id
if (is_request && isFunction(fn)) {
args = isArray(msg[2]) ? msg[2] : []
// Request without response
if (id === 0) {
const req = { node }
args.push(req)
fn.apply(req, args)
}
// Request
else {
const req = createRequest()
const response = [response_id]
req.node = node
req.then((value) => {
response.push(0) // no errors
if (value !== undefined) response.push(value)
node.send(encode(response, encode_params))
}).catch((error) => {
response.push(error === 0 ? null : error)
node.send(encode(response, encode_params))
})
args.push(req)
localProcedureCall(fn, req, args, errorInstances)
}
return true
}
// Response
else if (id < 0 && requests.hasOwnProperty(response_id)) {
const response_status = function_id
const req = requests[response_id]
response_status === 0
? req.resolve(args)
: req.reject(response_status)
return true
}
return false
}
function registerLocalRpcFromEncode(fn) {
return registerRpc(getNextLocalFunctionId(), fn)
}
const node = {
open,
message,
requests,
registerRpc,
createRpc,
}
return node
}
}