🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@arcjet/body

Package Overview
Dependencies
Maintainers
2
Versions
48
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@arcjet/body - npm Package Compare versions

Comparing version
1.6.1
to
1.7.0-rc.0
+52
dist/index.d.ts
//#region src/index.d.ts
/**
* Configuration.
*/
type ReadBodyOpts = {
/**
* Length of the stream in bytes (optional);
* rejects an error if the contents of the stream do not add up to this length;
* useful when the exact size is known.
*/
expectedLength?: number | null | undefined;
/**
* Limit of the body in bytes (default: `1048576`);
* an error is returned if the body ends up being larger than this limit;
* used to prevent reading too much data from malicious clients.
*/
limit?: number | null | undefined;
};
type EventHandlerLike = (event: string, listener: (...args: any[]) => void) => void;
/**
* Minimal Node.js stream interface.
*/
interface ReadableStreamLike {
on?: EventHandlerLike | null | undefined;
readable?: boolean | null | undefined;
removeListener?: EventHandlerLike | null | undefined;
}
/**
* Read the body of a
* [web stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
*
* @param stream
* Stream.
* @param options
* Configuration (required).
* @returns
* Promise that resolves to a concatenated body.
*/
declare function readBodyWeb(stream: ReadableStream, options?: ReadBodyOpts | null | undefined): Promise<string>;
/**
* Read the body of a Node.js stream.
*
* @param stream
* Stream.
* @param options
* Configuration (optional).
* @returns
* Promise that resolves to a concatenated body.
*/
declare function readBody(stream: ReadableStreamLike, options?: ReadBodyOpts | null | undefined): Promise<string>;
//#endregion
export { ReadBodyOpts, ReadableStreamLike, readBody, readBodyWeb };
//#region src/index.ts
/**
* Read the body of a
* [web stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
*
* @param stream
* Stream.
* @param options
* Configuration (required).
* @returns
* Promise that resolves to a concatenated body.
*/
async function readBodyWeb(stream, options) {
const limit = options?.limit ?? 1048576;
const length = options?.expectedLength ?? void 0;
if (typeof limit !== "number" || limit < 0 || Number.isNaN(limit)) return Promise.reject(/* @__PURE__ */ new Error("Unexpected value `" + limit + "` for `options.limit`, expected positive number"));
if (length !== void 0 && (typeof length !== "number" || length < 0 || Number.isNaN(length))) return Promise.reject(/* @__PURE__ */ new Error("Unexpected value `" + length + "` for `options.expectedLength`, expected positive number"));
if (length !== void 0 && length > limit) return Promise.reject(/* @__PURE__ */ new Error("Cannot read stream whose expected length exceeds limit"));
const controller = new AbortController();
const timeout = setTimeout(function() {
controller.abort(/* @__PURE__ */ new Error("Cannot read stream, did not receive data in time limit"));
}, 100);
const decoder = new TextDecoder("utf-8");
let buffer = "";
let received = 0;
let written = false;
await stream.pipeTo(new WritableStream({ write(chunk) {
if (!written) {
clearTimeout(timeout);
written = true;
}
received += chunk.byteLength;
if (received > limit) throw new Error("Cannot read stream that exceeds limit");
buffer += decoder.decode(chunk, { stream: true });
} }), { signal: controller.signal });
if (!written) throw new Error("Cannot read stream, did not receive data");
if (length !== void 0 && received !== length) throw new Error("Cannot read stream whose length does not match expected length");
buffer += decoder.decode();
return buffer;
}
/**
* Read the body of a Node.js stream.
*
* @param stream
* Stream.
* @param options
* Configuration (optional).
* @returns
* Promise that resolves to a concatenated body.
*/
async function readBody(stream, options) {
const limit = options?.limit ?? 1048576;
const length = options?.expectedLength ?? void 0;
const decoder = new TextDecoder("utf-8");
let buffer = "";
let complete = false;
let received = 0;
if (typeof limit !== "number" || limit < 0 || Number.isNaN(limit)) return Promise.reject(/* @__PURE__ */ new Error("Unexpected value `" + limit + "` for `options.limit`, expected positive number"));
if (length !== void 0 && (typeof length !== "number" || length < 0 || Number.isNaN(length))) return Promise.reject(/* @__PURE__ */ new Error("Unexpected value `" + length + "` for `options.expectedLength`, expected positive number"));
if (typeof stream.on !== "function") return Promise.reject(/* @__PURE__ */ new Error("Unexpected value `" + stream.on + "` for `stream.on`, expected function"));
if (typeof stream.removeListener !== "function") return Promise.reject(/* @__PURE__ */ new Error("Unexpected value `" + stream.removeListener + "` for `stream.removeListener`, expected function"));
if (typeof stream.readable !== "undefined" && !stream.readable) return Promise.reject(/* @__PURE__ */ new Error("Cannot read unreadable stream"));
if (length !== void 0 && length > limit) return Promise.reject(/* @__PURE__ */ new Error("Cannot read stream whose expected length exceeds limit"));
return new Promise((resolve, reject) => {
if (typeof stream.on === "function") {
stream.on("aborted", onAborted);
stream.on("close", cleanup);
stream.on("data", onData);
stream.on("end", onEnd);
stream.on("error", onEnd);
}
function done(err, buffer) {
if (complete) return;
complete = true;
cleanup();
if (typeof err !== "undefined") reject(err);
else if (buffer !== null && buffer !== void 0) {
buffer += decoder.decode();
resolve(buffer);
}
}
function onAborted() {
done(/* @__PURE__ */ new Error("Cannot read aborted stream"));
}
function onData(chunk) {
received += chunk.length;
if (received > limit) done(/* @__PURE__ */ new Error("Cannot read stream that exceeds limit"));
else buffer += decoder.decode(chunk, { stream: true });
}
function onEnd(err) {
if (err) return done(err);
if (length !== void 0 && received !== length) done(/* @__PURE__ */ new Error("Cannot read stream whose length does not match expected length"));
else done(void 0, buffer);
}
function cleanup() {
buffer = "";
if (typeof stream.removeListener === "function") {
stream.removeListener("aborted", onAborted);
stream.removeListener("data", onData);
stream.removeListener("end", onEnd);
stream.removeListener("error", onEnd);
stream.removeListener("close", cleanup);
}
}
setTimeout(() => {
if (received === 0) done(/* @__PURE__ */ new Error("Cannot read stream, did not receive data in time limit"));
}, 100);
});
}
//#endregion
export { readBody, readBodyWeb };
+32
-29
{
"name": "@arcjet/body",
"version": "1.6.1",
"version": "1.7.0-rc.0",
"description": "Arcjet utilities for extracting the body from a stream",

@@ -9,12 +9,6 @@ "keywords": [

"stream",
"utility",
"util"
"util",
"utility"
],
"license": "Apache-2.0",
"homepage": "https://arcjet.com",
"repository": {
"type": "git",
"url": "git+https://github.com/arcjet/arcjet-js.git",
"directory": "body"
},
"bugs": {

@@ -24,2 +18,3 @@ "url": "https://github.com/arcjet/arcjet-js/issues",

},
"license": "Apache-2.0",
"author": {

@@ -30,33 +25,41 @@ "name": "Arcjet",

},
"engines": {
"node": ">=22.21.0 <23 || >=24.5.0"
"repository": {
"type": "git",
"url": "git+https://github.com/arcjet/arcjet-js.git",
"directory": "body"
},
"type": "module",
"main": "./index.js",
"types": "./index.d.ts",
"files": [
"index.d.ts",
"index.js"
"dist"
],
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"publishConfig": {
"access": "public",
"tag": "latest"
},
"scripts": {
"build": "rollup --config rollup.config.js",
"lint": "eslint .",
"build": "tsdown",
"typecheck": "tsgo --noEmit",
"pretest": "npm run build",
"test-api": "node --test -- test/*.test.js",
"test-coverage": "node --experimental-test-coverage --test -- test/*.test.js",
"test": "npm run build && npm run lint && npm run test-coverage"
"test-api": "node --test -- test/*.test.ts",
"test-coverage": "node --experimental-test-coverage --test -- test/*.test.ts",
"test": "npm run build && npm run test-coverage"
},
"dependencies": {},
"devDependencies": {
"@arcjet/eslint-config": "1.6.1",
"@arcjet/rollup-config": "1.6.1",
"@rollup/wasm-node": "4.62.2",
"@types/node": "22.19.21",
"eslint": "9.39.4",
"typescript": "5.9.3"
"tsdown": "0.22.3",
"typescript": "6.0.3"
},
"publishConfig": {
"access": "public",
"tag": "latest"
"engines": {
"node": ">=22.21.0 <23 || >=24.5.0"
}
}
/**
* Configuration.
*/
export type ReadBodyOpts = {
/**
* Length of the stream in bytes (optional);
* rejects an error if the contents of the stream do not add up to this length;
* useful when the exact size is known.
*/
expectedLength?: number | null | undefined;
/**
* Limit of the body in bytes (default: `1048576`);
* an error is returned if the body ends up being larger than this limit;
* used to prevent reading too much data from malicious clients.
*/
limit?: number | null | undefined;
};
type EventHandlerLike = (event: string, listener: (...args: any[]) => void) => void;
/**
* Minimal Node.js stream interface.
*/
export interface ReadableStreamLike {
on?: EventHandlerLike | null | undefined;
readable?: boolean | null | undefined;
removeListener?: EventHandlerLike | null | undefined;
}
/**
* Read the body of a
* [web stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
*
* @param stream
* Stream.
* @param options
* Configuration (required).
* @returns
* Promise that resolves to a concatenated body.
*/
export declare function readBodyWeb(stream: ReadableStream, options?: ReadBodyOpts | null | undefined): Promise<string>;
/**
* Read the body of a Node.js stream.
*
* @param stream
* Stream.
* @param options
* Configuration (optional).
* @returns
* Promise that resolves to a concatenated body.
*/
export declare function readBody(stream: ReadableStreamLike, options?: ReadBodyOpts | null | undefined): Promise<string>;
export {};
/**
* Read the body of a
* [web stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
*
* @param stream
* Stream.
* @param options
* Configuration (required).
* @returns
* Promise that resolves to a concatenated body.
*/
async function readBodyWeb(stream, options) {
const limit = options?.limit ?? 1048576; // 1mb.
const length = options?.expectedLength ?? undefined;
if (typeof limit !== "number" || limit < 0 || Number.isNaN(limit)) {
return Promise.reject(new Error("Unexpected value `" +
limit +
"` for `options.limit`, expected positive number"));
}
if (length !== undefined &&
(typeof length !== "number" || length < 0 || Number.isNaN(length))) {
return Promise.reject(new Error("Unexpected value `" +
length +
"` for `options.expectedLength`, expected positive number"));
}
// If we already know the length and it exceeds the limit, abort early.
if (length !== undefined && length > limit) {
return Promise.reject(new Error("Cannot read stream whose expected length exceeds limit"));
}
const controller = new AbortController();
// Ensure that we do not wait forever if the stream is incorrectly configured.
const timeout = setTimeout(function () {
controller.abort(new Error("Cannot read stream, did not receive data in time limit"));
}, 100);
const decoder = new TextDecoder("utf-8");
let buffer = "";
let received = 0;
let written = false;
await stream.pipeTo(new WritableStream({
write(chunk) {
if (!written) {
clearTimeout(timeout);
written = true;
}
received += chunk.byteLength;
if (received > limit) {
throw new Error("Cannot read stream that exceeds limit");
}
buffer += decoder.decode(chunk, { stream: true });
},
}), { signal: controller.signal });
if (!written) {
throw new Error("Cannot read stream, did not receive data");
}
if (length !== undefined && received !== length) {
throw new Error("Cannot read stream whose length does not match expected length");
}
// Need to call it one final time to flush any remaining chars.
buffer += decoder.decode();
return buffer;
}
// This `readBody` function is a derivitive of the `getRawBody` function in the `raw-body`
// npm package with deviations to strip down the implementation to specifically what is needed
// by Arcjet.
//
// These include:
// - The removal of the sync interface.
// - The removal of the ability to return the body as a `Buffer`. Instead the body is always
// parsed as a utf-8 string.
// - The removal of certain config options that are not relevant to us.
//
// Original source:
// https://github.com/stream-utils/raw-body/blob/191e4b6506dcf77198eed01c8feb4b6817008342/test/index.js
//
// Licensed: The MIT License (MIT)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: The above copyright
// notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
/**
* Read the body of a Node.js stream.
*
* @param stream
* Stream.
* @param options
* Configuration (optional).
* @returns
* Promise that resolves to a concatenated body.
*/
async function readBody(stream, options) {
const limit = options?.limit ?? 1048576; // 1mb.
const length = options?.expectedLength ?? undefined;
const decoder = new TextDecoder("utf-8");
let buffer = "";
let complete = false;
let received = 0;
if (typeof limit !== "number" || limit < 0 || Number.isNaN(limit)) {
return Promise.reject(new Error("Unexpected value `" +
limit +
"` for `options.limit`, expected positive number"));
}
if (length !== undefined &&
(typeof length !== "number" || length < 0 || Number.isNaN(length))) {
return Promise.reject(new Error("Unexpected value `" +
length +
"` for `options.expectedLength`, expected positive number"));
}
if (typeof stream.on !== "function") {
return Promise.reject(new Error("Unexpected value `" +
stream.on +
"` for `stream.on`, expected function"));
}
if (typeof stream.removeListener !== "function") {
return Promise.reject(new Error("Unexpected value `" +
stream.removeListener +
"` for `stream.removeListener`, expected function"));
}
if (typeof stream.readable !== "undefined" && !stream.readable) {
return Promise.reject(new Error("Cannot read unreadable stream"));
}
// If we already know the length and it exceeds the limit, abort early.
if (length !== undefined && length > limit) {
return Promise.reject(new Error("Cannot read stream whose expected length exceeds limit"));
}
return new Promise((resolve, reject) => {
// This was already checked at the top of the function but TypeScript lost
// the context
if (typeof stream.on === "function") {
stream.on("aborted", onAborted);
stream.on("close", cleanup);
stream.on("data", onData);
stream.on("end", onEnd);
stream.on("error", onEnd);
}
function done(err, buffer) {
// Ensure we avoid double resolve/reject if called more than once
if (complete)
return;
complete = true;
cleanup();
if (typeof err !== "undefined") {
reject(err);
}
else if (buffer !== null && buffer !== undefined) {
// Need to call it one final time to flush any remaining chars.
buffer += decoder.decode();
resolve(buffer);
}
}
function onAborted() {
done(new Error("Cannot read aborted stream"));
}
function onData(chunk) {
received += chunk.length;
if (received > limit) {
done(new Error("Cannot read stream that exceeds limit"));
}
else {
buffer += decoder.decode(chunk, { stream: true });
}
}
function onEnd(err) {
if (err)
return done(err);
if (length !== undefined && received !== length) {
done(new Error("Cannot read stream whose length does not match expected length"));
}
else {
done(undefined, buffer);
}
}
function cleanup() {
buffer = "";
// This was already checked at the top of the function but TypeScript lost
// the context
if (typeof stream.removeListener === "function") {
stream.removeListener("aborted", onAborted);
stream.removeListener("data", onData);
stream.removeListener("end", onEnd);
stream.removeListener("error", onEnd);
stream.removeListener("close", cleanup);
}
}
// Ensure that we don't poll forever if the stream is incorrectly configured
setTimeout(() => {
if (received === 0) {
done(new Error("Cannot read stream, did not receive data in time limit"));
}
}, 100);
});
}
export { readBody, readBodyWeb };