Socket
Socket
Sign inDemoInstall

@vercel/node-bridge

Package Overview
Dependencies
Maintainers
7
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vercel/node-bridge - npm Package Compare versions

Comparing version 3.0.0 to 3.1.0

246

bridge.js

@@ -0,3 +1,9 @@

const { URL } = require('url');
const { request } = require('http');
const { Socket } = require('net');
const { createCipheriv } = require('crypto');
const { pipeline, Transform } = require('stream');
const CRLF = `\r\n`;
/**

@@ -20,5 +26,19 @@ * If the `http.Server` handler function throws an error asynchronously,

let bodyBuffer;
const { method, path, headers, encoding, body, payloads } = JSON.parse(
event.body
);
/**
* @type {import('./types').VercelProxyRequest}
*/
const payload = JSON.parse(event.body);
const {
method,
path,
headers,
encoding,
body,
payloads,
responseCallbackCipher,
responseCallbackCipherIV,
responseCallbackCipherKey,
responseCallbackStream,
responseCallbackUrl,
} = payload;

@@ -32,3 +52,3 @@ /**

if (b) {
if (encoding === 'base64') {
if (typeof b === 'string' && encoding === 'base64') {
bodyBuffer = Buffer.from(b, encoding);

@@ -47,9 +67,5 @@ } else if (encoding === undefined) {

if (payloads) {
/**
* @param {{ body: string | Buffer }} payload
*/
const normalizePayload = payload => {
payload.body = normalizeBody(payload.body);
};
payloads.forEach(normalizePayload);
for (const p of payloads) {
p.body = normalizeBody(payload.body);
}
}

@@ -65,2 +81,7 @@ bodyBuffer = normalizeBody(body);

payloads,
responseCallbackCipher,
responseCallbackCipherIV,
responseCallbackCipherKey,
responseCallbackStream,
responseCallbackUrl,
};

@@ -86,3 +107,14 @@ }

return { isApiGateway: true, method, path, headers, body: bodyBuffer };
return {
body: bodyBuffer,
headers,
isApiGateway: true,
method,
path,
responseCallbackCipher: undefined,
responseCallbackCipherIV: undefined,
responseCallbackCipherKey: undefined,
responseCallbackStream: undefined,
responseCallbackUrl: undefined,
};
}

@@ -92,2 +124,3 @@

* @param {import('./types').VercelProxyEvent | import('aws-lambda').APIGatewayProxyEvent} event
* @return {import('./types').VercelProxyRequest}
*/

@@ -185,3 +218,3 @@ function normalizeEvent(event) {

* @param {import('aws-lambda').Context} context
* @return {Promise<{statusCode: number, headers: import('http').IncomingHttpHeaders, body: string, encoding: 'base64'}>}
* @return {Promise<import('./types').VercelProxyResponse>}
*/

@@ -278,2 +311,6 @@ async launcher(event, context) {

} else {
// TODO We expect this to error as it is possible to resolve to empty.
// For now it is not very important as we will only pass
// `responseCallbackUrl` in production.
// @ts-ignore
return this.handleEvent(normalizedEvent);

@@ -286,7 +323,17 @@ }

* @param {ReturnType<typeof normalizeEvent>} normalizedEvent
* @return {Promise<{statusCode: number, headers: import('http').IncomingHttpHeaders, body: string, encoding: 'base64'}>}
* @return {Promise<import('./types').VercelProxyResponse | import('./types').VercelStreamProxyResponse>}
*/
async handleEvent(normalizedEvent) {
const { port } = await this.listening;
const { isApiGateway, method, headers, body } = normalizedEvent;
const {
body,
headers,
isApiGateway,
method,
responseCallbackCipher,
responseCallbackCipherIV,
responseCallbackCipherKey,
responseCallbackStream,
responseCallbackUrl,
} = normalizedEvent;
let { path } = normalizedEvent;

@@ -300,4 +347,26 @@

// eslint-disable-next-line consistent-return
return new Promise((resolve, reject) => {
let socket;
let cipher;
let url;
if (responseCallbackUrl) {
socket = new Socket();
url = new URL(responseCallbackUrl);
socket.connect(parseInt(url.port, 10), url.hostname);
socket.write(`${responseCallbackStream}${CRLF}`);
}
if (
responseCallbackCipher &&
responseCallbackCipherKey &&
responseCallbackCipherIV
) {
cipher = createCipheriv(
responseCallbackCipher,
Buffer.from(responseCallbackCipherKey, 'base64'),
Buffer.from(responseCallbackCipherIV, 'base64')
);
}
// if the path is improperly encoded we need to encode it or

@@ -309,30 +378,9 @@ // http.request will throw an error (related check: https://github.com/nodejs/node/blob/4ece669c6205ec78abfdadfe78869bbb8411463e/lib/_http_client.js#L84)

const opts = { hostname: '127.0.0.1', port, path, method };
const req = request(opts, res => {
const response = res;
/**
* @type {Buffer[]}
*/
const respBodyChunks = [];
response.on('data', chunk => respBodyChunks.push(Buffer.from(chunk)));
response.on('error', reject);
response.on('end', () => {
const bodyBuffer = Buffer.concat(respBodyChunks);
delete response.headers.connection;
const req = request(
{ hostname: '127.0.0.1', port, path, method },
socket && url && cipher
? getStreamResponseCallback({ url, socket, cipher, resolve, reject })
: getResponseCallback({ isApiGateway, resolve, reject })
);
if (isApiGateway) {
delete response.headers['content-length'];
} else if (response.headers['content-length']) {
response.headers['content-length'] = String(bodyBuffer.length);
}
resolve({
statusCode: response.statusCode || 200,
headers: response.headers,
body: bodyBuffer.toString('base64'),
encoding: 'base64',
});
});
});
req.on('error', error => {

@@ -346,12 +394,6 @@ setTimeout(() => {

for (const [name, value] of Object.entries(headers)) {
if (value === undefined) {
console.error(
`Skipping HTTP request header "${name}" because value is undefined`
);
continue;
}
for (const [name, value] of getHeadersIterator(headers)) {
try {
req.setHeader(name, value);
} catch (err) {
} catch (/** @type any */ err) {
console.error(`Skipping HTTP request header: "${name}: ${value}"`);

@@ -378,2 +420,104 @@ console.error(err.message);

/**
* Generates the streaming response callback which writes in the given socket client a raw
* HTTP Request message to later pipe the response body into the socket. It will pass request
* headers namespace and an additional header with the status code. Once everything is
* written it will destroy the socket and resolve to an empty object. If a cipher is given
* it will be used to pipe bytes.
*
* @type {(params: {
* url: import('url').URL,
* socket: import('net').Socket,
* cipher: import('crypto').Cipher
* resolve: (result: (Record<string, never>)) => void,
* reject: (err: Error) => void
* }) => (response: import("http").IncomingMessage) => void}
*/
function getStreamResponseCallback({ url, socket, cipher, resolve, reject }) {
return response => {
const chunked = new Transform();
chunked._transform = function (chunk, _, callback) {
this.push(Buffer.byteLength(chunk).toString(16) + CRLF);
this.push(chunk);
this.push(CRLF);
callback();
};
let headers = `Host: ${url.host}${CRLF}`;
headers += `transfer-encoding: chunked${CRLF}`;
headers += `x-vercel-status-code: ${response.statusCode || 200}${CRLF}`;
for (const [name, value] of getHeadersIterator(response.headers)) {
if (!['connection', 'transfer-encoding'].includes(name)) {
headers += `x-vercel-header-${name}: ${value}${CRLF}`;
}
}
cipher.write(`POST ${url.pathname} HTTP/1.1${CRLF}${headers}${CRLF}`);
pipeline(response, chunked, cipher, socket, err => {
if (err) return reject(err);
resolve({});
});
};
}
/**
* Generates the normal response callback which waits until the body is fully
* received before resolving the promise. It caches the entire body and resolve
* with an object that describes the response.
*
* @type {(params: {
* isApiGateway: boolean,
* resolve: (result: (import('./types').VercelProxyResponse)) => void,
* reject: (err: Error) => void
* }) => (response: import("http").IncomingMessage) => void}
*/
function getResponseCallback({ isApiGateway, resolve, reject }) {
return response => {
/**
* @type {Buffer[]}
*/
const respBodyChunks = [];
response.on('data', chunk => respBodyChunks.push(Buffer.from(chunk)));
response.on('error', reject);
response.on('end', () => {
const bodyBuffer = Buffer.concat(respBodyChunks);
delete response.headers.connection;
if (isApiGateway) {
delete response.headers['content-length'];
} else if (response.headers['content-length']) {
response.headers['content-length'] = String(bodyBuffer.length);
}
resolve({
statusCode: response.statusCode || 200,
headers: response.headers,
body: bodyBuffer.toString('base64'),
encoding: 'base64',
});
});
};
}
/**
* Get an iterator for the headers object and yield the name and value when
* the value is not undefined only.
*
* @type {(headers: import('http').IncomingHttpHeaders) =>
* Generator<[string, string | string[]], void, unknown>}
*/
function* getHeadersIterator(headers) {
for (const [name, value] of Object.entries(headers)) {
if (value === undefined) {
console.error(
`Skipping HTTP request header "${name}" because value is undefined`
);
continue;
}
yield [name, value];
}
}
module.exports = { Bridge };
{
"name": "@vercel/node-bridge",
"version": "3.0.0",
"version": "3.1.0",
"license": "MIT",

@@ -26,5 +26,7 @@ "main": "./index.js",

"@types/node": "*",
"jsonlines": "0.1.1",
"test-listen": "1.1.0",
"typescript": "4.3.4"
},
"gitHead": "de0d2fba0b32588726a2799015eaff4e6bb65ffb"
"gitHead": "a630e1989613052f64411fd7e5468de58c5999ba"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc