@jsonjoy.com/reactive-rpc
Version:
Reactive-RPC is a library for building reactive APIs over WebSocket, HTTP, and other RPCs.
170 lines • 6.04 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConnectionContext = void 0;
const NullObject_1 = require("@jsonjoy.com/util/lib/NullObject");
const copy_1 = require("@jsonjoy.com/util/lib/buffers/copy");
const concat_1 = require("@jsonjoy.com/util/lib/buffers/concat");
const REGEX_AUTH_TOKEN_SPECIFIER = /tkn\.([a-zA-Z0-9\-_]+)(?:[^a-zA-Z0-9\-_]|$)/;
const REGEX_CODECS_SPECIFIER = /rpc\.(\w{0,32})\.(\w{0,32})\.(\w{0,32})(?:\-(\w{0,32}))?/;
class ConnectionContext {
static findIp(req, res) {
return (req.getHeader('x-forwarded-for') ||
req.getHeader('x-real-ip') ||
Buffer.from(res.getRemoteAddressAsText()).toString());
}
static findTokenInText(text) {
const match = REGEX_AUTH_TOKEN_SPECIFIER.exec(text);
if (!match)
return '';
return match[1] || '';
}
static findToken(req) {
let token = '';
let text = '';
text = req.getHeader('authorization');
if (text)
token = ConnectionContext.findTokenInText(text);
if (token)
return token;
text = req.getQuery();
if (text)
token = ConnectionContext.findTokenInText(text);
if (token)
return token;
text = req.getHeader('cookie');
if (text)
token = ConnectionContext.findTokenInText(text);
if (token)
return token;
text = req.getHeader('sec-websocket-protocol');
if (text)
token = ConnectionContext.findTokenInText(text);
return token;
}
static fromReqRes(req, res, params, app) {
const ip = ConnectionContext.findIp(req, res);
const token = ConnectionContext.findToken(req);
const codecs = app.codecs;
const valueCodecs = codecs.value;
const ctx = new ConnectionContext(ip, token, params, new NullObject_1.NullObject(), valueCodecs.json, valueCodecs.json, codecs.messages.compact, res);
const contentType = req.getHeader('content-type');
if (contentType)
ctx.setCodecs(contentType, codecs);
return ctx;
}
static fromWs(req, res, secWebSocketProtocol, params, app) {
const ip = ConnectionContext.findIp(req, res);
const token = ConnectionContext.findToken(req);
const codecs = app.codecs;
const valueCodecs = codecs.value;
const ctx = new ConnectionContext(ip, token, params, new NullObject_1.NullObject(), valueCodecs.json, valueCodecs.json, codecs.messages.compact, res);
const contentType = req.getHeader('content-type');
if (contentType)
ctx.setCodecs(contentType, codecs);
else if (secWebSocketProtocol)
ctx.setCodecs(secWebSocketProtocol, codecs);
return ctx;
}
constructor(ip, token, params, meta, reqCodec, resCodec, msgCodec, res = undefined) {
this.ip = ip;
this.token = token;
this.params = params;
this.meta = meta;
this.reqCodec = reqCodec;
this.resCodec = resCodec;
this.msgCodec = msgCodec;
this.res = res;
}
setCodecs(specifier, codecs) {
const match = REGEX_CODECS_SPECIFIER.exec(specifier);
if (!match)
return;
const [, protocol, messageFormat, request, response] = match;
switch (protocol) {
case 'rx': {
switch (messageFormat) {
case 'compact': {
this.msgCodec = codecs.messages.compact;
break;
}
case 'binary': {
this.msgCodec = codecs.messages.binary;
break;
}
}
break;
}
case 'json2': {
this.msgCodec = codecs.messages.jsonRpc2;
break;
}
}
switch (request) {
case 'cbor': {
this.resCodec = this.reqCodec = codecs.value.cbor;
break;
}
case 'json': {
this.resCodec = this.reqCodec = codecs.value.json;
break;
}
case 'msgpack': {
this.resCodec = this.reqCodec = codecs.value.msgpack;
break;
}
}
switch (response) {
case 'cbor': {
this.resCodec = codecs.value.cbor;
break;
}
case 'json': {
this.resCodec = codecs.value.json;
break;
}
case 'msgpack': {
this.resCodec = codecs.value.msgpack;
break;
}
}
}
requestBodyParts(max) {
const res = this.res;
return new Promise((resolve) => {
const list = [];
if (!res)
return resolve(list);
let running = 0;
res.onData((ab, isLast) => {
running += ab.byteLength;
if (running > max) {
res.aborted = true;
res.end('too large');
}
if (isLast)
list.push(new Uint8Array(ab)), resolve(list);
else
list.push((0, copy_1.copy)(new Uint8Array(ab)));
});
});
}
async requestBody(max) {
const parts = await this.requestBodyParts(max);
return (0, concat_1.concatList)(parts);
}
async requestBodyJson(max) {
const parts = await this.requestBodyParts(max);
const bodyUint8 = (0, concat_1.concatList)(parts);
return this.reqCodec.decoder.read(bodyUint8);
}
sendResponse(response) {
const res = this.res;
if (!res)
return;
if (res.aborted)
return;
res.end(response);
}
}
exports.ConnectionContext = ConnectionContext;
//# sourceMappingURL=context.js.map