New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@jsenv/server

Package Overview
Dependencies
Maintainers
2
Versions
219
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jsenv/server - npm Package Compare versions

Comparing version 15.0.2 to 15.0.3

6

package.json
{
"name": "@jsenv/server",
"version": "15.0.2",
"version": "15.0.3",
"description": "Write your Node.js server using pure functions",

@@ -13,3 +13,3 @@ "license": "MIT",

"type": "git",
"url": "https://github.com/jsenv/jsenv-core",
"url": "https://github.com/jsenv/core",
"directory": "packages/server"

@@ -39,3 +39,3 @@ },

"@jsenv/abort": "4.2.4",
"@jsenv/log": "3.3.4",
"@jsenv/log": "3.3.5",
"@jsenv/url-meta": "8.1.0",

@@ -42,0 +42,0 @@ "@jsenv/utils": "2.0.1",

@@ -6,3 +6,3 @@ # server [![npm package](https://img.shields.io/npm/v/@jsenv/server.svg?logo=npm&label=package)](https://www.npmjs.com/package/@jsenv/server)

```js
import { startServer } from "@jsenv/server"
import { startServer } from "@jsenv/server";

@@ -20,7 +20,7 @@ await startServer({

body: "Hello world",
}
};
},
},
],
})
});
```

@@ -40,3 +40,3 @@

*/
import { startServer, composeServices } from "@jsenv/server"
import { startServer, composeServices } from "@jsenv/server";

@@ -49,5 +49,5 @@ const server = await startServer({

if (request.resource === "/") {
return { status: 200 }
return { status: 200 };
}
return null
return null;
},

@@ -58,14 +58,14 @@ },

handleRequest: () => {
return { status: 404 }
return { status: 404 };
},
},
],
})
});
const fetch = await import("node-fetch")
const responseForOrigin = await fetch(server.origin)
responseForOrigin.status // 200
const fetch = await import("node-fetch");
const responseForOrigin = await fetch(server.origin);
responseForOrigin.status; // 200
const responseForFoo = await fetch(`${server.origin}/foo`)
responseForFoo.status // 404
const responseForFoo = await fetch(`${server.origin}/foo`);
responseForFoo.status; // 404
```

@@ -76,4 +76,4 @@

```js
import { readFileSync } from "node:fs"
import { startServer } from "@jsenv/server"
import { readFileSync } from "node:fs";
import { startServer } from "@jsenv/server";

@@ -89,3 +89,3 @@ await startServer({

handleRequest: (request) => {
const clientUsesHttp = request.origin.startsWith("http:")
const clientUsesHttp = request.origin.startsWith("http:");

@@ -98,7 +98,7 @@ return {

body: clientUsesHttp ? `Welcome http user` : `Welcome https user`,
}
};
},
},
],
})
});
```

@@ -109,3 +109,3 @@

```js
import { startServer, fetchFileSystem } from "@jsenv/server"
import { startServer, fetchFileSystem } from "@jsenv/server";

@@ -116,9 +116,9 @@ await startServer({

handleRequest: async (request) => {
const fileUrl = new URL(request.resource.slice(1), import.meta.url)
const response = await fetchFileSystem(fileUrl, request)
return response
const fileUrl = new URL(request.resource.slice(1), import.meta.url);
const response = await fetchFileSystem(fileUrl, request);
return response;
},
},
],
})
});
```

@@ -125,0 +125,0 @@

@@ -6,22 +6,22 @@ export const pickAcceptedContent = ({

}) => {
let highestScore = -1
let availableWithHighestScore = null
let availableIndex = 0
let highestScore = -1;
let availableWithHighestScore = null;
let availableIndex = 0;
while (availableIndex < availables.length) {
const available = availables[availableIndex]
availableIndex++
const available = availables[availableIndex];
availableIndex++;
let acceptedIndex = 0
let acceptedIndex = 0;
while (acceptedIndex < accepteds.length) {
const accepted = accepteds[acceptedIndex]
acceptedIndex++
const accepted = accepteds[acceptedIndex];
acceptedIndex++;
const score = getAcceptanceScore(accepted, available)
const score = getAcceptanceScore(accepted, available);
if (score > highestScore) {
availableWithHighestScore = available
highestScore = score
availableWithHighestScore = available;
highestScore = score;
}
}
}
return availableWithHighestScore
}
return availableWithHighestScore;
};

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

import { parseMultipleHeader } from "../internal/multiple-header.js"
import { pickAcceptedContent } from "./pick_accepted_content.js"
import { parseMultipleHeader } from "../internal/multiple-header.js";
import { pickAcceptedContent } from "./pick_accepted_content.js";
export const pickContentEncoding = (request, availableEncodings) => {
const { headers = {} } = request
const requestAcceptEncodingHeader = headers["accept-encoding"]
const { headers = {} } = request;
const requestAcceptEncodingHeader = headers["accept-encoding"];
if (!requestAcceptEncodingHeader) {
return null
return null;
}

@@ -13,3 +13,3 @@

requestAcceptEncodingHeader,
)
);
return pickAcceptedContent({

@@ -19,4 +19,4 @@ accepteds: encodingsAccepted,

getAcceptanceScore: getEncodingAcceptanceScore,
})
}
});
};

@@ -27,35 +27,35 @@ const parseAcceptEncodingHeader = (acceptEncodingHeaderString) => {

// read only q, anything else is ignored
return name === "q"
return name === "q";
},
})
});
const encodingsAccepted = []
const encodingsAccepted = [];
Object.keys(acceptEncodingHeader).forEach((key) => {
const { q = 1 } = acceptEncodingHeader[key]
const value = key
const { q = 1 } = acceptEncodingHeader[key];
const value = key;
encodingsAccepted.push({
value,
quality: q,
})
})
});
});
encodingsAccepted.sort((a, b) => {
return b.quality - a.quality
})
return encodingsAccepted
}
return b.quality - a.quality;
});
return encodingsAccepted;
};
const getEncodingAcceptanceScore = ({ value, quality }, availableEncoding) => {
if (value === "*") {
return quality
return quality;
}
// normalize br to brotli
if (value === "br") value = "brotli"
if (availableEncoding === "br") availableEncoding = "brotli"
if (value === "br") value = "brotli";
if (availableEncoding === "br") availableEncoding = "brotli";
if (value === availableEncoding) {
return quality
return quality;
}
return -1
}
return -1;
};

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

import { parseMultipleHeader } from "../internal/multiple-header.js"
import { pickAcceptedContent } from "./pick_accepted_content.js"
import { parseMultipleHeader } from "../internal/multiple-header.js";
import { pickAcceptedContent } from "./pick_accepted_content.js";
export const pickContentLanguage = (request, availableLanguages) => {
const { headers = {} } = request
const requestAcceptLanguageHeader = headers["accept-language"]
const { headers = {} } = request;
const requestAcceptLanguageHeader = headers["accept-language"];
if (!requestAcceptLanguageHeader) {
return null
return null;
}

@@ -13,3 +13,3 @@

requestAcceptLanguageHeader,
)
);
return pickAcceptedContent({

@@ -19,4 +19,4 @@ accepteds: languagesAccepted,

getAcceptanceScore: getLanguageAcceptanceScore,
})
}
});
};

@@ -27,54 +27,55 @@ const parseAcceptLanguageHeader = (acceptLanguageHeaderString) => {

// read only q, anything else is ignored
return name === "q"
return name === "q";
},
})
});
const languagesAccepted = []
const languagesAccepted = [];
Object.keys(acceptLanguageHeader).forEach((key) => {
const { q = 1 } = acceptLanguageHeader[key]
const value = key
const { q = 1 } = acceptLanguageHeader[key];
const value = key;
languagesAccepted.push({
value,
quality: q,
})
})
});
});
languagesAccepted.sort((a, b) => {
return b.quality - a.quality
})
return languagesAccepted
}
return b.quality - a.quality;
});
return languagesAccepted;
};
const getLanguageAcceptanceScore = ({ value, quality }, availableLanguage) => {
const [acceptedPrimary, acceptedVariant] = decomposeLanguage(value)
const [acceptedPrimary, acceptedVariant] = decomposeLanguage(value);
const [availablePrimary, availableVariant] =
decomposeLanguage(availableLanguage)
decomposeLanguage(availableLanguage);
const primaryAccepted =
acceptedPrimary === "*" ||
acceptedPrimary.toLowerCase() === availablePrimary.toLowerCase()
acceptedPrimary.toLowerCase() === availablePrimary.toLowerCase();
const variantAccepted =
acceptedVariant === "*" || compareVariant(acceptedVariant, availableVariant)
acceptedVariant === "*" ||
compareVariant(acceptedVariant, availableVariant);
if (primaryAccepted && variantAccepted) {
return quality + 1
return quality + 1;
}
if (primaryAccepted) {
return quality
return quality;
}
return -1
}
return -1;
};
const decomposeLanguage = (fullType) => {
const [primary, variant] = fullType.split("-")
return [primary, variant]
}
const [primary, variant] = fullType.split("-");
return [primary, variant];
};
const compareVariant = (left, right) => {
if (left === right) {
return true
return true;
}
if (left && right && left.toLowerCase() === right.toLowerCase()) {
return true
return true;
}
return false
}
return false;
};

@@ -1,12 +0,12 @@

import { parseMultipleHeader } from "../internal/multiple-header.js"
import { pickAcceptedContent } from "./pick_accepted_content.js"
import { parseMultipleHeader } from "../internal/multiple-header.js";
import { pickAcceptedContent } from "./pick_accepted_content.js";
export const pickContentType = (request, availableContentTypes) => {
const { headers = {} } = request
const requestAcceptHeader = headers.accept
const { headers = {} } = request;
const requestAcceptHeader = headers.accept;
if (!requestAcceptHeader) {
return null
return null;
}
const contentTypesAccepted = parseAcceptHeader(requestAcceptHeader)
const contentTypesAccepted = parseAcceptHeader(requestAcceptHeader);
return pickAcceptedContent({

@@ -16,4 +16,4 @@ accepteds: contentTypesAccepted,

getAcceptanceScore: getContentTypeAcceptanceScore,
})
}
});
};

@@ -24,20 +24,20 @@ const parseAcceptHeader = (acceptHeader) => {

// read only q, anything else is ignored
return name === "q"
return name === "q";
},
})
});
const accepts = []
const accepts = [];
Object.keys(acceptHeaderObject).forEach((key) => {
const { q = 1 } = acceptHeaderObject[key]
const value = key
const { q = 1 } = acceptHeaderObject[key];
const value = key;
accepts.push({
value,
quality: q,
})
})
});
});
accepts.sort((a, b) => {
return b.quality - a.quality
})
return accepts
}
return b.quality - a.quality;
});
return accepts;
};

@@ -48,19 +48,19 @@ const getContentTypeAcceptanceScore = (

) => {
const [acceptedType, acceptedSubtype] = decomposeContentType(value)
const [acceptedType, acceptedSubtype] = decomposeContentType(value);
const [availableType, availableSubtype] =
decomposeContentType(availableContentType)
decomposeContentType(availableContentType);
const typeAccepted = acceptedType === "*" || acceptedType === availableType
const typeAccepted = acceptedType === "*" || acceptedType === availableType;
const subtypeAccepted =
acceptedSubtype === "*" || acceptedSubtype === availableSubtype
acceptedSubtype === "*" || acceptedSubtype === availableSubtype;
if (typeAccepted && subtypeAccepted) {
return quality
return quality;
}
return -1
}
return -1;
};
const decomposeContentType = (fullType) => {
const [type, subtype] = fullType.split("/")
return [type, subtype]
}
const [type, subtype] = fullType.split("/");
return [type, subtype];
};

@@ -7,12 +7,15 @@ /*

import { createReadStream, statSync, readFile } from "node:fs"
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js"
import { createReadStream, statSync, readFile } from "node:fs";
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
import { isFileSystemPath, fileSystemPathToUrl } from "./internal/filesystem.js"
import { bufferToEtag } from "./internal/etag.js"
import { composeTwoResponses } from "./internal/response_composition.js"
import { convertFileSystemErrorToResponseProperties } from "./internal/convertFileSystemErrorToResponseProperties.js"
import { timeFunction } from "./server_timing/timing_measure.js"
import { pickContentEncoding } from "./content_negotiation/pick_content_encoding.js"
import { serveDirectory } from "./serve_directory.js"
import {
isFileSystemPath,
fileSystemPathToUrl,
} from "./internal/filesystem.js";
import { bufferToEtag } from "./internal/etag.js";
import { composeTwoResponses } from "./internal/response_composition.js";
import { convertFileSystemErrorToResponseProperties } from "./internal/convertFileSystemErrorToResponseProperties.js";
import { timeFunction } from "./server_timing/timing_measure.js";
import { pickContentEncoding } from "./content_negotiation/pick_content_encoding.js";
import { serveDirectory } from "./serve_directory.js";

@@ -38,7 +41,7 @@ export const fetchFileSystem = async (

) => {
const urlString = asUrlString(filesystemUrl)
const urlString = asUrlString(filesystemUrl);
if (!urlString) {
return create500Response(
`fetchFileSystem first parameter must be a file url, got ${filesystemUrl}`,
)
);
}

@@ -48,13 +51,13 @@ if (!urlString.startsWith("file://")) {

`fetchFileSystem url must use "file://" scheme, got ${filesystemUrl}`,
)
);
}
if (rootDirectoryUrl) {
let rootDirectoryUrlString = asUrlString(rootDirectoryUrl)
let rootDirectoryUrlString = asUrlString(rootDirectoryUrl);
if (!rootDirectoryUrlString) {
return create500Response(
`rootDirectoryUrl must be a string or an url, got ${rootDirectoryUrl}`,
)
);
}
if (!rootDirectoryUrlString.endsWith("/")) {
rootDirectoryUrlString = `${rootDirectoryUrlString}/`
rootDirectoryUrlString = `${rootDirectoryUrlString}/`;
}

@@ -64,5 +67,5 @@ if (!urlString.startsWith(rootDirectoryUrlString)) {

`fetchFileSystem url must be inside root directory, got ${urlString}`,
)
);
}
rootDirectoryUrl = rootDirectoryUrlString
rootDirectoryUrl = rootDirectoryUrlString;
}

@@ -75,8 +78,8 @@

if (etagEnabled) {
console.warn(`cannot enable etag when cache-control is ${cacheControl}`)
etagEnabled = false
console.warn(`cannot enable etag when cache-control is ${cacheControl}`);
etagEnabled = false;
}
if (mtimeEnabled) {
console.warn(`cannot enable mtime when cache-control is ${cacheControl}`)
mtimeEnabled = false
console.warn(`cannot enable mtime when cache-control is ${cacheControl}`);
mtimeEnabled = false;
}

@@ -87,4 +90,4 @@ }

`cannot enable both etag and mtime, mtime disabled in favor of etag.`,
)
mtimeEnabled = false
);
mtimeEnabled = false;
}

@@ -95,6 +98,6 @@

status: 501,
}
};
}
const sourceUrl = `file://${new URL(urlString).pathname}`
const sourceUrl = `file://${new URL(urlString).pathname}`;
try {

@@ -104,3 +107,3 @@ const [readStatTiming, sourceStat] = await timeFunction(

() => statSync(new URL(sourceUrl)),
)
);
if (sourceStat.isDirectory()) {

@@ -112,3 +115,3 @@ if (canReadDirectory) {

rootDirectoryUrl,
})
});
}

@@ -118,3 +121,3 @@ return {

statusText: "not allowed to read directory",
}
};
}

@@ -126,3 +129,3 @@ // not a file, give up

timing: readStatTiming,
}
};
}

@@ -138,3 +141,3 @@

sourceUrl,
})
});

@@ -152,6 +155,6 @@ // send 304 (redirect response to client cache)

clientCacheResponse,
)
);
}
let response
let response;
if (compressionEnabled && sourceStat.size >= compressionSizeThreshold) {

@@ -161,5 +164,5 @@ const compressedResponse = await getCompressedResponse({

sourceUrl,
})
});
if (compressedResponse) {
response = compressedResponse
response = compressedResponse;
}

@@ -171,3 +174,3 @@ }

sourceUrl,
})
});
}

@@ -189,4 +192,4 @@

response,
)
return composeTwoResponses(intermediateResponse, clientCacheResponse)
);
return composeTwoResponses(intermediateResponse, clientCacheResponse);
} catch (e) {

@@ -200,5 +203,5 @@ return composeTwoResponses(

convertFileSystemErrorToResponseProperties(e) || {},
)
);
}
}
};

@@ -213,4 +216,4 @@ const create500Response = (message) => {

body: message,
}
}
};
};

@@ -235,3 +238,3 @@ const getClientCacheResponse = async ({

) {
return { status: 200 }
return { status: 200 };
}

@@ -246,3 +249,3 @@

sourceUrl,
})
});
}

@@ -254,7 +257,7 @@

sourceStat,
})
});
}
return { status: 200 }
}
return { status: 200 };
};

@@ -277,5 +280,5 @@ const getEtagResponse = async ({

}),
)
);
const requestHasIfNoneMatchHeader = "if-none-match" in headers
const requestHasIfNoneMatchHeader = "if-none-match" in headers;
if (

@@ -288,3 +291,3 @@ requestHasIfNoneMatchHeader &&

timing: computeEtagTiming,
}
};
}

@@ -298,6 +301,6 @@

timing: computeEtagTiming,
}
}
};
};
const ETAG_MEMORY_MAP = new Map()
const ETAG_MEMORY_MAP = new Map();
const computeEtag = async ({

@@ -310,3 +313,3 @@ etagMemory,

if (etagMemory) {
const etagMemoryEntry = ETAG_MEMORY_MAP.get(sourceUrl)
const etagMemoryEntry = ETAG_MEMORY_MAP.get(sourceUrl);
if (

@@ -316,3 +319,3 @@ etagMemoryEntry &&

) {
return etagMemoryEntry.eTag
return etagMemoryEntry.eTag;
}

@@ -323,18 +326,18 @@ }

if (error) {
reject(error)
reject(error);
} else {
resolve(buffer)
resolve(buffer);
}
})
})
const eTag = bufferToEtag(fileContentAsBuffer)
});
});
const eTag = bufferToEtag(fileContentAsBuffer);
if (etagMemory) {
if (ETAG_MEMORY_MAP.size >= etagMemoryMaxSize) {
const firstKey = Array.from(ETAG_MEMORY_MAP.keys())[0]
ETAG_MEMORY_MAP.delete(firstKey)
const firstKey = Array.from(ETAG_MEMORY_MAP.keys())[0];
ETAG_MEMORY_MAP.delete(firstKey);
}
ETAG_MEMORY_MAP.set(sourceUrl, { sourceStat, eTag })
ETAG_MEMORY_MAP.set(sourceUrl, { sourceStat, eTag });
}
return eTag
}
return eTag;
};

@@ -344,7 +347,7 @@ // https://nodejs.org/api/fs.html#fs_class_fs_stats

return fileStatKeysToCompare.every((keyToCompare) => {
const leftValue = leftFileStat[keyToCompare]
const rightValue = rightFileStat[keyToCompare]
return leftValue === rightValue
})
}
const leftValue = leftFileStat[keyToCompare];
const rightValue = rightFileStat[keyToCompare];
return leftValue === rightValue;
});
};
const fileStatKeysToCompare = [

@@ -360,9 +363,9 @@ // mtime the the most likely to change, check it first

"blksize",
]
];
const getMtimeResponse = async ({ headers, sourceStat }) => {
if ("if-modified-since" in headers) {
let cachedModificationDate
let cachedModificationDate;
try {
cachedModificationDate = new Date(headers["if-modified-since"])
cachedModificationDate = new Date(headers["if-modified-since"]);
} catch (e) {

@@ -372,10 +375,10 @@ return {

statusText: "if-modified-since header is not a valid date",
}
};
}
const actualModificationDate = dateToSecondsPrecision(sourceStat.mtime)
const actualModificationDate = dateToSecondsPrecision(sourceStat.mtime);
if (Number(cachedModificationDate) >= Number(actualModificationDate)) {
return {
status: 304,
}
};
}

@@ -389,4 +392,4 @@ }

},
}
}
};
};

@@ -397,11 +400,11 @@ const getCompressedResponse = async ({ sourceUrl, headers }) => {

Object.keys(availableCompressionFormats),
)
);
if (!acceptedCompressionFormat) {
return null
return null;
}
const fileReadableStream = fileUrlToReadableStream(sourceUrl)
const fileReadableStream = fileUrlToReadableStream(sourceUrl);
const body = await availableCompressionFormats[acceptedCompressionFormat](
fileReadableStream,
)
);

@@ -416,4 +419,4 @@ return {

body,
}
}
};
};

@@ -424,19 +427,19 @@ const fileUrlToReadableStream = (fileUrl) => {

autoClose: true,
})
}
});
};
const availableCompressionFormats = {
br: async (fileReadableStream) => {
const { createBrotliCompress } = await import("node:zlib")
return fileReadableStream.pipe(createBrotliCompress())
const { createBrotliCompress } = await import("node:zlib");
return fileReadableStream.pipe(createBrotliCompress());
},
deflate: async (fileReadableStream) => {
const { createDeflate } = await import("node:zlib")
return fileReadableStream.pipe(createDeflate())
const { createDeflate } = await import("node:zlib");
return fileReadableStream.pipe(createDeflate());
},
gzip: async (fileReadableStream) => {
const { createGzip } = await import("node:zlib")
return fileReadableStream.pipe(createGzip())
const { createGzip } = await import("node:zlib");
return fileReadableStream.pipe(createGzip());
},
}
};

@@ -451,30 +454,30 @@ const getRawResponse = async ({ sourceUrl, sourceStat }) => {

body: fileUrlToReadableStream(sourceUrl),
}
}
};
};
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString
const dateToUTCString = (date) => date.toUTCString()
const dateToUTCString = (date) => date.toUTCString();
const dateToSecondsPrecision = (date) => {
const dateWithSecondsPrecision = new Date(date)
dateWithSecondsPrecision.setMilliseconds(0)
return dateWithSecondsPrecision
}
const dateWithSecondsPrecision = new Date(date);
dateWithSecondsPrecision.setMilliseconds(0);
return dateWithSecondsPrecision;
};
const asUrlString = (value) => {
if (value instanceof URL) {
return value.href
return value.href;
}
if (typeof value === "string") {
if (isFileSystemPath(value)) {
return fileSystemPathToUrl(value)
return fileSystemPathToUrl(value);
}
try {
const urlObject = new URL(value)
return String(urlObject)
const urlObject = new URL(value);
return String(urlObject);
} catch (e) {
return null
return null;
}
}
return null
}
return null;
};
export const fromFetchResponse = (fetchResponse) => {
const responseHeaders = {}
const headersToIgnore = ["connection"]
const responseHeaders = {};
const headersToIgnore = ["connection"];
fetchResponse.headers.forEach((value, name) => {
if (!headersToIgnore.includes(name)) {
responseHeaders[name] = value
responseHeaders[name] = value;
}
})
});
return {

@@ -14,3 +14,3 @@ status: fetchResponse.status,

body: fetchResponse.body, // node-fetch assumed
}
}
};
};

@@ -1,6 +0,6 @@

import { Stream, Writable, Readable } from "node:stream"
import { createReadStream } from "node:fs"
import { Stream, Writable, Readable } from "node:stream";
import { createReadStream } from "node:fs";
import { isObservable, observableFromValue } from "./observable.js"
import { observableFromNodeStream } from "./observable_from_node_stream.js"
import { isObservable, observableFromValue } from "./observable.js";
import { observableFromNodeStream } from "./observable_from_node_stream.js";

@@ -12,3 +12,3 @@ export const normalizeBodyMethods = (body) => {

destroy: () => {},
}
};
}

@@ -20,5 +20,5 @@

destroy: () => {
body.close()
body.close();
},
}
};
}

@@ -30,5 +30,5 @@

destroy: () => {
body.destroy()
body.destroy();
},
}
};
}

@@ -39,8 +39,8 @@

destroy: () => {},
}
}
};
};
export const isFileHandle = (value) => {
return value && value.constructor && value.constructor.name === "FileHandle"
}
return value && value.constructor && value.constructor.name === "FileHandle";
};

@@ -58,3 +58,3 @@ export const fileHandleToReadableStream = (fileHandle) => {

},
)
);
// I suppose it's required only when doing fs.createReadStream()

@@ -65,12 +65,12 @@ // and not fileHandle.createReadStream()

// })
return fileReadableStream
}
return fileReadableStream;
};
const fileHandleToObservable = (fileHandle) => {
return observableFromNodeStream(fileHandleToReadableStream(fileHandle))
}
return observableFromNodeStream(fileHandleToReadableStream(fileHandle));
};
const isNodeStream = (value) => {
if (value === undefined) {
return false
return false;
}

@@ -83,6 +83,6 @@

) {
return true
return true;
}
return false
}
return false;
};
// https://github.com/jamestalmage/stream-to-observable/blob/master/index.js
import { Readable } from "node:stream"
import { createObservable } from "./observable.js"
import { Readable } from "node:stream";
import { createObservable } from "./observable.js";

@@ -13,27 +13,27 @@ export const observableFromNodeStream = (

if (nodeStream.isPaused()) {
nodeStream.resume()
nodeStream.resume();
} else if (nodeStream.complete) {
complete()
return null
complete();
return null;
}
const cleanup = () => {
nodeStream.removeListener("data", next)
nodeStream.removeListener("error", error)
nodeStream.removeListener("end", complete)
nodeStream.removeListener("close", cleanup)
nodeStream.destroy()
}
nodeStream.removeListener("data", next);
nodeStream.removeListener("error", error);
nodeStream.removeListener("end", complete);
nodeStream.removeListener("close", cleanup);
nodeStream.destroy();
};
// should we do nodeStream.resume() in case the stream was paused ?
nodeStream.once("error", error)
nodeStream.once("error", error);
nodeStream.on("data", (data) => {
next(data)
})
next(data);
});
nodeStream.once("close", () => {
cleanup()
})
cleanup();
});
nodeStream.once("end", () => {
complete()
})
return cleanup
})
complete();
});
return cleanup;
});

@@ -53,22 +53,22 @@ if (nodeStream instanceof Readable) {

},
)
nodeStream.destroy()
}, readableStreamLifetime)
observable.timeout = timeout
);
nodeStream.destroy();
}, readableStreamLifetime);
observable.timeout = timeout;
onceReadableStreamUsedOrClosed(nodeStream, () => {
clearTimeout(timeout)
})
clearTimeout(timeout);
});
}
return observable
}
return observable;
};
const onceReadableStreamUsedOrClosed = (readableStream, callback) => {
const dataOrCloseCallback = () => {
readableStream.removeListener("data", dataOrCloseCallback)
readableStream.removeListener("close", dataOrCloseCallback)
callback()
}
readableStream.on("data", dataOrCloseCallback)
readableStream.once("close", dataOrCloseCallback)
}
readableStream.removeListener("data", dataOrCloseCallback);
readableStream.removeListener("close", dataOrCloseCallback);
callback();
};
readableStream.on("data", dataOrCloseCallback);
readableStream.once("close", dataOrCloseCallback);
};
if ("observable" in Symbol === false) {
Symbol.observable = Symbol.for("observable")
Symbol.observable = Symbol.for("observable");
}

@@ -7,3 +7,3 @@

if (typeof producer !== "function") {
throw new TypeError(`producer must be a function, got ${producer}`)
throw new TypeError(`producer must be a function, got ${producer}`);
}

@@ -16,94 +16,94 @@

error = (value) => {
throw value
throw value;
},
complete = () => {},
}) => {
let cleanup = () => {}
let cleanup = () => {};
const subscription = {
closed: false,
unsubscribe: () => {
subscription.closed = true
cleanup()
subscription.closed = true;
cleanup();
},
}
};
const producerReturnValue = producer({
next: (value) => {
if (subscription.closed) return
next(value)
if (subscription.closed) return;
next(value);
},
error: (value) => {
if (subscription.closed) return
error(value)
if (subscription.closed) return;
error(value);
},
complete: () => {
if (subscription.closed) return
complete()
if (subscription.closed) return;
complete();
},
})
});
if (typeof producerReturnValue === "function") {
cleanup = producerReturnValue
cleanup = producerReturnValue;
}
return subscription
return subscription;
},
}
};
return observable
}
return observable;
};
export const isObservable = (value) => {
if (value === null || value === undefined) {
return false
return false;
}
if (typeof value === "object" || typeof value === "function") {
return Symbol.observable in value
return Symbol.observable in value;
}
return false
}
return false;
};
export const observableFromValue = (value) => {
if (isObservable(value)) {
return value
return value;
}
return createObservable(({ next, complete }) => {
next(value)
next(value);
const timer = setTimeout(() => {
complete()
})
complete();
});
return () => {
clearTimeout(timer)
}
})
}
clearTimeout(timer);
};
});
};
export const createCompositeProducer = ({ cleanup = () => {} } = {}) => {
const observables = new Set()
const observers = new Set()
const observables = new Set();
const observers = new Set();
const addObservable = (observable) => {
if (observables.has(observable)) {
return false
return false;
}
observables.add(observable)
observables.add(observable);
observers.forEach((observer) => {
observer.observe(observable)
})
return true
}
observer.observe(observable);
});
return true;
};
const removeObservable = (observable) => {
if (!observables.has(observable)) {
return false
return false;
}
observables.delete(observable)
observables.delete(observable);
observers.forEach((observer) => {
observer.unobserve(observable)
})
return true
}
observer.unobserve(observable);
});
return true;
};

@@ -115,60 +115,60 @@ const producer = ({

}) => {
let completeCount = 0
let completeCount = 0;
const checkComplete = () => {
if (completeCount === observables.size) {
complete()
complete();
}
}
};
const subscriptions = new Map()
const subscriptions = new Map();
const observe = (observable) => {
const subscription = observable.subscribe({
next: (value) => {
next(value)
next(value);
},
error: (value) => {
error(value)
error(value);
},
complete: () => {
subscriptions.delete(observable)
completeCount++
checkComplete()
subscriptions.delete(observable);
completeCount++;
checkComplete();
},
})
subscriptions.set(observable, subscription)
}
});
subscriptions.set(observable, subscription);
};
const unobserve = (observable) => {
const subscription = subscriptions.get(observable)
const subscription = subscriptions.get(observable);
if (!subscription) {
return
return;
}
subscription.unsubscribe()
subscriptions.delete(observable)
checkComplete()
}
subscription.unsubscribe();
subscriptions.delete(observable);
checkComplete();
};
const observer = {
observe,
unobserve,
}
observers.add(observer)
};
observers.add(observer);
observables.forEach((observable) => {
observe(observable)
})
observe(observable);
});
return () => {
observers.delete(observer)
observers.delete(observer);
subscriptions.forEach((subscription) => {
subscription.unsubscribe()
})
subscriptions.clear()
cleanup()
}
}
subscription.unsubscribe();
});
subscriptions.clear();
cleanup();
};
};
producer.addObservable = addObservable
producer.removeObservable = removeObservable
producer.addObservable = addObservable;
producer.removeObservable = removeObservable;
return producer
}
return producer;
};

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

import { observableFromNodeStream } from "./observable_from_node_stream.js"
import { headersFromObject } from "../internal/headersFromObject.js"
import { observableFromNodeStream } from "./observable_from_node_stream.js";
import { headersFromObject } from "../internal/headersFromObject.js";

@@ -8,20 +8,20 @@ export const fromNodeRequest = (

) => {
const headers = headersFromObject(nodeRequest.headers)
const headers = headersFromObject(nodeRequest.headers);
const body = observableFromNodeStream(nodeRequest, {
readableStreamLifetime: requestBodyLifetime,
})
});
let requestOrigin
let requestOrigin;
if (nodeRequest.upgrade) {
requestOrigin = serverOrigin
requestOrigin = serverOrigin;
} else if (nodeRequest.authority) {
requestOrigin = nodeRequest.connection.encrypted
? `https://${nodeRequest.authority}`
: `http://${nodeRequest.authority}`
: `http://${nodeRequest.authority}`;
} else if (nodeRequest.headers.host) {
requestOrigin = nodeRequest.connection.encrypted
? `https://${nodeRequest.headers.host}`
: `http://${nodeRequest.headers.host}`
: `http://${nodeRequest.headers.host}`;
} else {
requestOrigin = serverOrigin
requestOrigin = serverOrigin;
}

@@ -40,4 +40,4 @@

body,
})
}
});
};

@@ -62,8 +62,8 @@ export const applyRedirectionToRequest = (

...rest,
}
}
};
};
const getPropertiesFromResource = ({ resource, baseUrl }) => {
const urlObject = new URL(resource, baseUrl)
let pathname = urlObject.pathname
const urlObject = new URL(resource, baseUrl);
let pathname = urlObject.pathname;

@@ -74,4 +74,4 @@ return {

resource,
}
}
};
};

@@ -82,4 +82,4 @@ const getPropertiesFromPathname = ({ pathname, baseUrl }) => {

baseUrl,
})
}
});
};

@@ -101,8 +101,8 @@ export const createPushRequest = (request, { signal, pathname, method }) => {

body: undefined,
})
return pushRequest
}
});
return pushRequest;
};
const getHeadersInheritedByPushRequest = (request) => {
const headersInherited = { ...request.headers }
const headersInherited = { ...request.headers };
// mtime sent by the client in request headers concerns the main request

@@ -115,5 +115,5 @@ // Time remains valid for request to other resources so we keep it

// A request made to an other resource must not inherit the eTag
delete headersInherited["if-none-match"]
delete headersInherited["if-none-match"];
return headersInherited
}
return headersInherited;
};

@@ -1,6 +0,6 @@

import http from "node:http"
import { Http2ServerResponse } from "node:http2"
import { raceCallbacks } from "@jsenv/abort"
import http from "node:http";
import { Http2ServerResponse } from "node:http2";
import { raceCallbacks } from "@jsenv/abort";
import { normalizeBodyMethods } from "./body.js"
import { normalizeBodyMethods } from "./body.js";

@@ -12,10 +12,10 @@ export const writeNodeResponse = async (

) => {
body = await body
const bodyMethods = normalizeBodyMethods(body)
body = await body;
const bodyMethods = normalizeBodyMethods(body);
if (signal.aborted) {
bodyMethods.destroy()
responseStream.destroy()
onAbort()
return
bodyMethods.destroy();
responseStream.destroy();
onAbort();
return;
}

@@ -28,27 +28,27 @@

onHeadersSent,
})
});
if (!body) {
onEnd()
responseStream.end()
return
onEnd();
responseStream.end();
return;
}
if (ignoreBody) {
onEnd()
bodyMethods.destroy()
responseStream.end()
return
onEnd();
bodyMethods.destroy();
responseStream.end();
return;
}
if (bodyEncoding) {
responseStream.setEncoding(bodyEncoding)
responseStream.setEncoding(bodyEncoding);
}
await new Promise((resolve) => {
const observable = bodyMethods.asObservable()
const observable = bodyMethods.asObservable();
const subscription = observable.subscribe({
next: (data) => {
try {
responseStream.write(data)
responseStream.write(data);
} catch (e) {

@@ -64,14 +64,14 @@ // Something inside Node.js sometimes puts stream

if (e.code === "ERR_HTTP2_INVALID_STREAM") {
return
return;
}
responseStream.emit("error", e)
responseStream.emit("error", e);
}
},
error: (value) => {
responseStream.emit("error", value)
responseStream.emit("error", value);
},
complete: () => {
responseStream.end()
responseStream.end();
},
})
});

@@ -81,24 +81,24 @@ raceCallbacks(

abort: (cb) => {
signal.addEventListener("abort", cb)
signal.addEventListener("abort", cb);
return () => {
signal.removeEventListener("abort", cb)
}
signal.removeEventListener("abort", cb);
};
},
error: (cb) => {
responseStream.on("error", cb)
responseStream.on("error", cb);
return () => {
responseStream.removeListener("error", cb)
}
responseStream.removeListener("error", cb);
};
},
close: (cb) => {
responseStream.on("close", cb)
responseStream.on("close", cb);
return () => {
responseStream.removeListener("close", cb)
}
responseStream.removeListener("close", cb);
};
},
finish: (cb) => {
responseStream.on("finish", cb)
responseStream.on("finish", cb);
return () => {
responseStream.removeListener("finish", cb)
}
responseStream.removeListener("finish", cb);
};
},

@@ -109,12 +109,12 @@ },

abort: () => {
subscription.unsubscribe()
responseStream.destroy()
onAbort()
resolve()
subscription.unsubscribe();
responseStream.destroy();
onAbort();
resolve();
},
error: (error) => {
subscription.unsubscribe()
responseStream.destroy()
onError(error)
resolve()
subscription.unsubscribe();
responseStream.destroy();
onError(error);
resolve();
},

@@ -127,17 +127,17 @@ close: () => {

// and the browser is reloaded or closed for instance
subscription.unsubscribe()
responseStream.destroy()
onAbort()
resolve()
subscription.unsubscribe();
responseStream.destroy();
onAbort();
resolve();
},
finish: () => {
onEnd()
resolve()
onEnd();
resolve();
},
}
raceEffects[winner.name](winner.data)
};
raceEffects[winner.name](winner.data);
},
)
})
}
);
});
};

@@ -149,5 +149,5 @@ const writeHead = (

const responseIsHttp2ServerResponse =
responseStream instanceof Http2ServerResponse
responseStream instanceof Http2ServerResponse;
const responseIsServerHttp2Stream =
responseStream.constructor.name === "ServerHttp2Stream"
responseStream.constructor.name === "ServerHttp2Stream";
let nodeHeaders = headersToNodeHeaders(headers, {

@@ -157,5 +157,5 @@ // https://github.com/nodejs/node/blob/79296dc2d02c0b9872bbfcbb89148ea036a546d0/lib/internal/http2/compat.js#L112

responseIsHttp2ServerResponse || responseIsServerHttp2Stream,
})
});
if (statusText === undefined) {
statusText = statusTextFromStatus(status)
statusText = statusTextFromStatus(status);
}

@@ -166,6 +166,6 @@ if (responseIsServerHttp2Stream) {

":status": status,
}
responseStream.respond(nodeHeaders)
onHeadersSent({ nodeHeaders, status, statusText })
return
};
responseStream.respond(nodeHeaders);
onHeadersSent({ nodeHeaders, status, statusText });
return;
}

@@ -178,9 +178,9 @@ // nodejs strange signature for writeHead force this

) {
responseStream.writeHead(status, nodeHeaders)
onHeadersSent({ nodeHeaders, status, statusText })
return
responseStream.writeHead(status, nodeHeaders);
onHeadersSent({ nodeHeaders, status, statusText });
return;
}
try {
responseStream.writeHead(status, statusText, nodeHeaders)
responseStream.writeHead(status, statusText, nodeHeaders);
} catch (e) {

@@ -193,23 +193,23 @@ if (

--- status message ---
${statusText}`)
${statusText}`);
}
throw e
throw e;
}
onHeadersSent({ nodeHeaders, status, statusText })
}
onHeadersSent({ nodeHeaders, status, statusText });
};
const statusTextFromStatus = (status) =>
http.STATUS_CODES[status] || "not specified"
http.STATUS_CODES[status] || "not specified";
const headersToNodeHeaders = (headers, { ignoreConnectionHeader }) => {
const nodeHeaders = {}
const nodeHeaders = {};
Object.keys(headers).forEach((name) => {
if (name === "connection" && ignoreConnectionHeader) return
const nodeHeaderName = name in mapping ? mapping[name] : name
nodeHeaders[nodeHeaderName] = headers[name]
})
if (name === "connection" && ignoreConnectionHeader) return;
const nodeHeaderName = name in mapping ? mapping[name] : name;
nodeHeaders[nodeHeaderName] = headers[name];
});
return nodeHeaders
}
return nodeHeaders;
};

@@ -219,2 +219,2 @@ const mapping = {

// "last-modified": "Last-Modified",
}
};
// https://github.com/Marak/colors.js/blob/b63ef88e521b42920a9e908848de340b31e68c9d/lib/styles.js#L29
const close = "\x1b[0m"
const red = "\x1b[31m"
const green = "\x1b[32m"
const yellow = "\x1b[33m"
const close = "\x1b[0m";
const red = "\x1b[31m";
const green = "\x1b[32m";
const yellow = "\x1b[33m";
// const blue = "\x1b[34m"
const magenta = "\x1b[35m"
const cyan = "\x1b[36m"
const magenta = "\x1b[35m";
const cyan = "\x1b[36m";
// const white = "\x1b[37m"
export const colorizeResponseStatus = (status) => {
const statusType = statusToType(status)
if (statusType === "information") return `${cyan}${status}${close}`
if (statusType === "success") return `${green}${status}${close}`
if (statusType === "redirection") return `${magenta}${status}${close}`
if (statusType === "client_error") return `${yellow}${status}${close}`
if (statusType === "server_error") return `${red}${status}${close}`
return status
}
const statusType = statusToType(status);
if (statusType === "information") return `${cyan}${status}${close}`;
if (statusType === "success") return `${green}${status}${close}`;
if (statusType === "redirection") return `${magenta}${status}${close}`;
if (statusType === "client_error") return `${yellow}${status}${close}`;
if (statusType === "server_error") return `${red}${status}${close}`;
return status;
};
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
export const statusToType = (status) => {
if (statusIsInformation(status)) return "information"
if (statusIsSuccess(status)) return "success"
if (statusIsRedirection(status)) return "redirection"
if (statusIsClientError(status)) return "client_error"
if (statusIsServerError(status)) return "server_error"
return "unknown"
}
if (statusIsInformation(status)) return "information";
if (statusIsSuccess(status)) return "success";
if (statusIsRedirection(status)) return "redirection";
if (statusIsClientError(status)) return "client_error";
if (statusIsServerError(status)) return "server_error";
return "unknown";
};
const statusIsInformation = (status) => status >= 100 && status < 200
const statusIsInformation = (status) => status >= 100 && status < 200;
const statusIsSuccess = (status) => status >= 200 && status < 300
const statusIsSuccess = (status) => status >= 200 && status < 300;
const statusIsRedirection = (status) => status >= 300 && status < 400
const statusIsRedirection = (status) => status >= 300 && status < 400;
const statusIsClientError = (status) => status >= 400 && status < 500
const statusIsClientError = (status) => status >= 400 && status < 500;
const statusIsServerError = (status) => status >= 500 && status < 600
const statusIsServerError = (status) => status >= 500 && status < 600;

@@ -7,3 +7,3 @@ export const convertFileSystemErrorToResponseProperties = (error) => {

statusText: `EACCES: No permission to read file at ${error.path}`,
}
};
}

@@ -14,3 +14,3 @@ if (isErrorWithCode(error, "EPERM")) {

statusText: `EPERM: No permission to read file at ${error.path}`,
}
};
}

@@ -21,3 +21,3 @@ if (isErrorWithCode(error, "ENOENT")) {

statusText: `ENOENT: File not found at ${error.path}`,
}
};
}

@@ -33,3 +33,3 @@ // file access may be temporarily blocked

},
}
};
}

@@ -44,3 +44,3 @@ // emfile means there is too many files currently opened

},
}
};
}

@@ -51,9 +51,9 @@ if (isErrorWithCode(error, "EISDIR")) {

statusText: `EISDIR: Unexpected directory operation at ${error.path}`,
}
};
}
return null
}
return null;
};
const isErrorWithCode = (error, code) => {
return typeof error === "object" && error.code === code
}
return typeof error === "object" && error.code === code;
};

@@ -1,2 +0,2 @@

import { lookup } from "node:dns"
import { lookup } from "node:dns";

@@ -10,9 +10,9 @@ export const applyDnsResolution = async (

if (error) {
reject(error)
reject(error);
} else {
resolve({ address, family })
resolve({ address, family });
}
})
})
return dnsResolution
}
});
});
return dnsResolution;
};

@@ -1,22 +0,22 @@

import { createHash } from "node:crypto"
import { createHash } from "node:crypto";
const ETAG_FOR_EMPTY_CONTENT = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'
const ETAG_FOR_EMPTY_CONTENT = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
export const bufferToEtag = (buffer) => {
if (!Buffer.isBuffer(buffer)) {
throw new TypeError(`buffer expected, got ${buffer}`)
throw new TypeError(`buffer expected, got ${buffer}`);
}
if (buffer.length === 0) {
return ETAG_FOR_EMPTY_CONTENT
return ETAG_FOR_EMPTY_CONTENT;
}
const hash = createHash("sha1")
hash.update(buffer, "utf8")
const hash = createHash("sha1");
hash.update(buffer, "utf8");
const hashBase64String = hash.digest("base64")
const hashBase64StringSubset = hashBase64String.slice(0, 27)
const length = buffer.length
const hashBase64String = hash.digest("base64");
const hashBase64StringSubset = hashBase64String.slice(0, 27);
const length = buffer.length;
return `"${length.toString(16)}-${hashBase64StringSubset}"`
}
return `"${length.toString(16)}-${hashBase64StringSubset}"`;
};

@@ -1,2 +0,2 @@

import { pathToFileURL, fileURLToPath } from "node:url"
import { pathToFileURL, fileURLToPath } from "node:url";

@@ -7,38 +7,38 @@ export const isFileSystemPath = (value) => {

`isFileSystemPath first arg must be a string, got ${value}`,
)
);
}
if (value[0] === "/") {
return true
return true;
}
return startsWithWindowsDriveLetter(value)
}
return startsWithWindowsDriveLetter(value);
};
const startsWithWindowsDriveLetter = (string) => {
const firstChar = string[0]
if (!/[a-zA-Z]/.test(firstChar)) return false
const firstChar = string[0];
if (!/[a-zA-Z]/.test(firstChar)) return false;
const secondChar = string[1]
if (secondChar !== ":") return false
const secondChar = string[1];
if (secondChar !== ":") return false;
return true
}
return true;
};
export const fileSystemPathToUrl = (value) => {
if (!isFileSystemPath(value)) {
throw new Error(`received an invalid value for fileSystemPath: ${value}`)
throw new Error(`received an invalid value for fileSystemPath: ${value}`);
}
return String(pathToFileURL(value))
}
return String(pathToFileURL(value));
};
export const urlToFileSystemPath = (url) => {
let urlString = String(url)
let urlString = String(url);
if (urlString[urlString.length - 1] === "/") {
// remove trailing / so that nodejs path becomes predictable otherwise it logs
// the trailing slash on linux but does not on windows
urlString = urlString.slice(0, -1)
urlString = urlString.slice(0, -1);
}
const fileSystemPath = fileURLToPath(urlString)
return fileSystemPath
}
const fileSystemPath = fileURLToPath(urlString);
return fileSystemPath;
};

@@ -1,2 +0,2 @@

import { composeTwoObjects } from "./object_composition.js"
import { composeTwoObjects } from "./object_composition.js";

@@ -7,14 +7,14 @@ export const composeTwoHeaders = (firstHeaders, secondHeaders) => {

forceLowerCase: true,
})
}
});
};
const composeHeaderValues = (value, nextValue) => {
const headerValues = value.split(", ")
const headerValues = value.split(", ");
nextValue.split(", ").forEach((value) => {
if (!headerValues.includes(value)) {
headerValues.push(value)
headerValues.push(value);
}
})
return headerValues.join(", ")
}
});
return headerValues.join(", ");
};

@@ -33,2 +33,2 @@ const HEADER_NAMES_COMPOSITION = {

"vary": composeHeaderValues,
}
};

@@ -6,7 +6,7 @@ /*

import { normalizeHeaderName } from "./normalizeHeaderName.js"
import { normalizeHeaderValue } from "./normalizeHeaderValue.js"
import { normalizeHeaderName } from "./normalizeHeaderName.js";
import { normalizeHeaderValue } from "./normalizeHeaderValue.js";
export const headersFromObject = (headersObject) => {
const headers = {}
const headers = {};

@@ -16,10 +16,10 @@ Object.keys(headersObject).forEach((headerName) => {

// exclude http2 headers
return
return;
}
headers[normalizeHeaderName(headerName)] = normalizeHeaderValue(
headersObject[headerName],
)
})
);
});
return headers
}
return headers;
};

@@ -1,21 +0,21 @@

import { normalizeHeaderName } from "./normalizeHeaderName.js"
import { normalizeHeaderValue } from "./normalizeHeaderValue.js"
import { normalizeHeaderName } from "./normalizeHeaderName.js";
import { normalizeHeaderValue } from "./normalizeHeaderValue.js";
// https://gist.github.com/mmazer/5404301
export const headersFromString = (headerString) => {
const headers = {}
const headers = {};
if (headerString) {
const pairs = headerString.split("\r\n")
const pairs = headerString.split("\r\n");
pairs.forEach((pair) => {
const index = pair.indexOf(": ")
const index = pair.indexOf(": ");
if (index > 0) {
const key = pair.slice(0, index)
const value = pair.slice(index + 2)
headers[normalizeHeaderName(key)] = normalizeHeaderValue(value)
const key = pair.slice(0, index);
const value = pair.slice(index + 2);
headers[normalizeHeaderName(key)] = normalizeHeaderValue(value);
}
})
});
}
return headers
}
return headers;
};
export const headersToObject = (headers) => {
const headersObject = {}
const headersObject = {};
headers.forEach((value, name) => {
headersObject[name] = value
})
return headersObject
}
headersObject[name] = value;
});
return headersObject;
};
export const headersToString = (headers, { convertName = (name) => name }) => {
const headersString = headersToArray(headers).map(({ name, value }) => {
return `${convertName(name)}: ${value}`
})
return `${convertName(name)}: ${value}`;
});
return headersString.join("\r\n")
}
return headersString.join("\r\n");
};

@@ -14,4 +14,4 @@ const headersToArray = (headers) => {

value: headers[name],
}
})
}
};
});
};

@@ -1,2 +0,2 @@

import { isIP } from "node:net"
import { isIP } from "node:net";

@@ -9,3 +9,3 @@ export const parseHostname = (hostname) => {

version: 4,
}
};
}

@@ -20,3 +20,3 @@ if (

version: 6,
}
};
}

@@ -28,3 +28,3 @@ if (hostname === "127.0.0.1") {

version: 4,
}
};
}

@@ -39,9 +39,9 @@ if (

version: 6,
}
};
}
const ipVersion = isIP(hostname)
const ipVersion = isIP(hostname);
if (ipVersion === 0) {
return {
type: "hostname",
}
};
}

@@ -51,3 +51,3 @@ return {

version: ipVersion,
}
}
};
};

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

import { createServer } from "node:net"
import { Abort } from "@jsenv/abort"
import { createServer } from "node:net";
import { Abort } from "@jsenv/abort";

@@ -11,24 +11,24 @@ const listen = async ({

}) => {
const listeningOperation = Abort.startOperation()
const listeningOperation = Abort.startOperation();
try {
listeningOperation.addAbortSignal(signal)
listeningOperation.addAbortSignal(signal);
if (portHint) {
listeningOperation.throwIfAborted()
listeningOperation.throwIfAborted();
port = await findFreePort(portHint, {
signal: listeningOperation.signal,
hostname,
})
});
}
listeningOperation.throwIfAborted()
port = await startListening({ server, port, hostname })
listeningOperation.addAbortCallback(() => stopListening(server))
listeningOperation.throwIfAborted()
listeningOperation.throwIfAborted();
port = await startListening({ server, port, hostname });
listeningOperation.addAbortCallback(() => stopListening(server));
listeningOperation.throwIfAborted();
return port
return port;
} finally {
await listeningOperation.end()
await listeningOperation.end();
}
}
};

@@ -45,31 +45,31 @@ export const findFreePort = async (

) => {
const findFreePortOperation = Abort.startOperation()
const findFreePortOperation = Abort.startOperation();
try {
findFreePortOperation.addAbortSignal(signal)
findFreePortOperation.throwIfAborted()
findFreePortOperation.addAbortSignal(signal);
findFreePortOperation.throwIfAborted();
const testUntil = async (port, host) => {
findFreePortOperation.throwIfAborted()
const free = await portIsFree(port, host)
findFreePortOperation.throwIfAborted();
const free = await portIsFree(port, host);
if (free) {
return port
return port;
}
const nextPort = next(port)
const nextPort = next(port);
if (nextPort > max) {
throw new Error(
`${hostname} has no available port between ${min} and ${max}`,
)
);
}
return testUntil(nextPort, hostname)
}
const freePort = await testUntil(initialPort, hostname)
return freePort
return testUntil(nextPort, hostname);
};
const freePort = await testUntil(initialPort, hostname);
return freePort;
} finally {
await findFreePortOperation.end()
await findFreePortOperation.end();
}
}
};
const portIsFree = async (port, hostname) => {
const server = createServer()
const server = createServer();

@@ -81,38 +81,38 @@ try {

hostname,
})
});
} catch (error) {
if (error && error.code === "EADDRINUSE") {
return false
return false;
}
if (error && error.code === "EACCES") {
return false
return false;
}
throw error
throw error;
}
await stopListening(server)
return true
}
await stopListening(server);
return true;
};
const startListening = ({ server, port, hostname }) => {
return new Promise((resolve, reject) => {
server.on("error", reject)
server.on("error", reject);
server.on("listening", () => {
// in case port is 0 (randomly assign an available port)
// https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback
resolve(server.address().port)
})
server.listen(port, hostname)
})
}
resolve(server.address().port);
});
server.listen(port, hostname);
});
};
export const stopListening = (server) => {
return new Promise((resolve, reject) => {
server.on("error", reject)
server.on("close", resolve)
server.close()
})
}
server.on("error", reject);
server.on("close", resolve);
server.close();
});
};
// unit test exports
export { listen, portIsFree }
export { listen, portIsFree };

@@ -1,2 +0,2 @@

import { listenEvent } from "./listenEvent.js"
import { listenEvent } from "./listenEvent.js";

@@ -9,3 +9,3 @@ export const listenClientError = (nodeServer, clientErrorCallback) => {

clientErrorCallback,
)
);
const removeHttpClientError = listenEvent(

@@ -15,3 +15,3 @@ nodeServer._httpServer,

clientErrorCallback,
)
);
const removeTlsClientError = listenEvent(

@@ -21,10 +21,10 @@ nodeServer._tlsServer,

clientErrorCallback,
)
);
return () => {
removeNetClientError()
removeHttpClientError()
removeTlsClientError()
}
removeNetClientError();
removeHttpClientError();
removeTlsClientError();
};
}
return listenEvent(nodeServer, "clientError", clientErrorCallback)
}
return listenEvent(nodeServer, "clientError", clientErrorCallback);
};

@@ -8,9 +8,9 @@ export const listenEvent = (

if (once) {
objectWithEventEmitter.once(eventName, callback)
objectWithEventEmitter.once(eventName, callback);
} else {
objectWithEventEmitter.addListener(eventName, callback)
objectWithEventEmitter.addListener(eventName, callback);
}
return () => {
objectWithEventEmitter.removeListener(eventName, callback)
}
}
objectWithEventEmitter.removeListener(eventName, callback);
};
};

@@ -1,2 +0,2 @@

import { listenEvent } from "./listenEvent.js"
import { listenEvent } from "./listenEvent.js";

@@ -9,3 +9,3 @@ export const listenRequest = (nodeServer, requestCallback) => {

requestCallback,
)
);
const removeTlsRequestListener = listenEvent(

@@ -15,9 +15,9 @@ nodeServer._tlsServer,

requestCallback,
)
);
return () => {
removeHttpRequestListener()
removeTlsRequestListener()
}
removeHttpRequestListener();
removeTlsRequestListener();
};
}
return listenEvent(nodeServer, "request", requestCallback)
}
return listenEvent(nodeServer, "request", requestCallback);
};

@@ -1,2 +0,2 @@

import { listenEvent } from "./listenEvent.js"
import { listenEvent } from "./listenEvent.js";

@@ -8,3 +8,3 @@ export const listenServerConnectionError = (

) => {
const cleanupSet = new Set()
const cleanupSet = new Set();

@@ -20,7 +20,7 @@ const removeConnectionListener = listenEvent(

if (ignoreErrorAfterConnectionIsDestroyed && socket.destroyed) {
return
return;
}
connectionErrorCallback(error, socket)
connectionErrorCallback(error, socket);
},
)
);
const removeOnceSocketCloseListener = listenEvent(

@@ -30,4 +30,4 @@ socket,

() => {
removeSocketErrorListener()
cleanupSet.delete(cleanup)
removeSocketErrorListener();
cleanupSet.delete(cleanup);
},

@@ -37,17 +37,17 @@ {

},
)
);
const cleanup = () => {
removeSocketErrorListener()
removeOnceSocketCloseListener()
}
cleanupSet.add(cleanup)
removeSocketErrorListener();
removeOnceSocketCloseListener();
};
cleanupSet.add(cleanup);
},
)
);
return () => {
removeConnectionListener()
removeConnectionListener();
cleanupSet.forEach((cleanup) => {
cleanup()
})
cleanupSet.clear()
}
}
cleanup();
});
cleanupSet.clear();
};
};

@@ -18,11 +18,11 @@ /**

) => {
const values = multipleHeaderString.split(",")
const multipleHeader = {}
const values = multipleHeaderString.split(",");
const multipleHeader = {};
values.forEach((value) => {
const valueTrimmed = value.trim()
const valueParts = valueTrimmed.split(";")
const name = valueParts[0]
const nameValidation = validateName(name)
const valueTrimmed = value.trim();
const valueParts = valueTrimmed.split(";");
const name = valueParts[0];
const nameValidation = validateName(name);
if (!nameValidation) {
return
return;
}

@@ -32,16 +32,16 @@

validateProperty,
})
multipleHeader[name] = properties
})
return multipleHeader
}
});
multipleHeader[name] = properties;
});
return multipleHeader;
};
const parseHeaderProperties = (headerProperties, { validateProperty }) => {
const properties = headerProperties.reduce((previous, valuePart) => {
const [propertyName, propertyValueString] = valuePart.split("=")
const propertyValue = parseHeaderPropertyValue(propertyValueString)
const property = { name: propertyName, value: propertyValue }
const propertyValidation = validateProperty(property)
const [propertyName, propertyValueString] = valuePart.split("=");
const propertyValue = parseHeaderPropertyValue(propertyValueString);
const property = { name: propertyName, value: propertyValue };
const propertyValidation = validateProperty(property);
if (!propertyValidation) {
return previous
return previous;
}

@@ -51,19 +51,19 @@ return {

[property.name]: property.value,
}
}, {})
return properties
}
};
}, {});
return properties;
};
const parseHeaderPropertyValue = (headerPropertyValueString) => {
const firstChar = headerPropertyValueString[0]
const firstChar = headerPropertyValueString[0];
const lastChar =
headerPropertyValueString[headerPropertyValueString.length - 1]
headerPropertyValueString[headerPropertyValueString.length - 1];
if (firstChar === '"' && lastChar === '"') {
return headerPropertyValueString.slice(1, -1)
return headerPropertyValueString.slice(1, -1);
}
if (isNaN(headerPropertyValueString)) {
return headerPropertyValueString
return headerPropertyValueString;
}
return parseFloat(headerPropertyValueString)
}
return parseFloat(headerPropertyValueString);
};

@@ -76,17 +76,17 @@ export const stringifyMultipleHeader = (

.filter((name) => {
const headerProperties = multipleHeader[name]
const headerProperties = multipleHeader[name];
if (!headerProperties) {
return false
return false;
}
if (typeof headerProperties !== "object") {
return false
return false;
}
const nameValidation = validateName(name)
const nameValidation = validateName(name);
if (!nameValidation) {
return false
return false;
}
return true
return true;
})
.map((name) => {
const headerProperties = multipleHeader[name]
const headerProperties = multipleHeader[name];
const headerPropertiesString = stringifyHeaderProperties(

@@ -97,10 +97,10 @@ headerProperties,

},
)
);
if (headerPropertiesString.length) {
return `${name};${headerPropertiesString}`
return `${name};${headerPropertiesString}`;
}
return name
return name;
})
.join(", ")
}
.join(", ");
};

@@ -113,22 +113,22 @@ const stringifyHeaderProperties = (headerProperties, { validateProperty }) => {

value: headerProperties[name],
}
return property
};
return property;
})
.filter((property) => {
const propertyValidation = validateProperty(property)
const propertyValidation = validateProperty(property);
if (!propertyValidation) {
return false
return false;
}
return true
return true;
})
.map(stringifyHeaderProperty)
.join(";")
return headerPropertiesString
}
.join(";");
return headerPropertiesString;
};
const stringifyHeaderProperty = ({ name, value }) => {
if (typeof value === "string") {
return `${name}="${value}"`
return `${name}="${value}"`;
}
return `${name}=${value}`
}
return `${name}=${value}`;
};
export const normalizeHeaderName = (headerName) => {
headerName = String(headerName)
headerName = String(headerName);
if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(headerName)) {
throw new TypeError("Invalid character in header field name")
throw new TypeError("Invalid character in header field name");
}
return headerName.toLowerCase()
}
return headerName.toLowerCase();
};
export const normalizeHeaderValue = (headerValue) => {
return String(headerValue)
}
return String(headerValue);
};

@@ -10,3 +10,3 @@ export const composeTwoObjects = (

strict,
})
});
}

@@ -17,4 +17,4 @@

strict,
})
}
});
};

@@ -27,3 +27,3 @@ const applyCaseSensitiveComposition = (

if (strict) {
const composed = {}
const composed = {};
Object.keys(keysComposition).forEach((key) => {

@@ -37,11 +37,11 @@ composed[key] = composeValueAtKey({

secondKey: keyExistsIn(key, secondObject) ? key : null,
})
})
return composed
});
});
return composed;
}
const composed = {}
const composed = {};
Object.keys(firstObject).forEach((key) => {
composed[key] = firstObject[key]
})
composed[key] = firstObject[key];
});
Object.keys(secondObject).forEach((key) => {

@@ -55,6 +55,6 @@ composed[key] = composeValueAtKey({

secondKey: keyExistsIn(key, secondObject) ? key : null,
})
})
return composed
}
});
});
return composed;
};

@@ -67,10 +67,10 @@ const applyCompositionForcingLowerCase = (

if (strict) {
const firstObjectKeyMapping = {}
const firstObjectKeyMapping = {};
Object.keys(firstObject).forEach((key) => {
firstObjectKeyMapping[key.toLowerCase()] = key
})
const secondObjectKeyMapping = {}
firstObjectKeyMapping[key.toLowerCase()] = key;
});
const secondObjectKeyMapping = {};
Object.keys(secondObject).forEach((key) => {
secondObjectKeyMapping[key.toLowerCase()] = key
})
secondObjectKeyMapping[key.toLowerCase()] = key;
});
Object.keys(keysComposition).forEach((key) => {

@@ -84,12 +84,12 @@ composed[key] = composeValueAtKey({

secondKey: secondObjectKeyMapping[key] || null,
})
})
});
});
}
const composed = {}
const composed = {};
Object.keys(firstObject).forEach((key) => {
composed[key.toLowerCase()] = firstObject[key]
})
composed[key.toLowerCase()] = firstObject[key];
});
Object.keys(secondObject).forEach((key) => {
const keyLowercased = key.toLowerCase()
const keyLowercased = key.toLowerCase();

@@ -111,6 +111,6 @@ composed[key.toLowerCase()] = composeValueAtKey({

: null,
})
})
return composed
}
});
});
return composed;
};

@@ -126,20 +126,22 @@ const composeValueAtKey = ({

if (!firstKey) {
return secondObject[secondKey]
return secondObject[secondKey];
}
if (!secondKey) {
return firstObject[firstKey]
return firstObject[firstKey];
}
const keyForCustomComposition = keyExistsIn(key, keysComposition) ? key : null
const keyForCustomComposition = keyExistsIn(key, keysComposition)
? key
: null;
if (!keyForCustomComposition) {
return secondObject[secondKey]
return secondObject[secondKey];
}
const composeTwoValues = keysComposition[keyForCustomComposition]
return composeTwoValues(firstObject[firstKey], secondObject[secondKey])
}
const composeTwoValues = keysComposition[keyForCustomComposition];
return composeTwoValues(firstObject[firstKey], secondObject[secondKey]);
};
const keyExistsIn = (key, object) => {
return Object.prototype.hasOwnProperty.call(object, key)
}
return Object.prototype.hasOwnProperty.call(object, key);
};

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

import { composeTwoObjects } from "./object_composition.js"
import { composeTwoHeaders } from "./headers_composition.js"
import { composeTwoObjects } from "./object_composition.js";
import { composeTwoHeaders } from "./headers_composition.js";

@@ -8,4 +8,4 @@ export const composeTwoResponses = (firstResponse, secondResponse) => {

strict: true,
})
}
});
};

@@ -20,4 +20,4 @@ const RESPONSE_KEYS_COMPOSITION = {

timing: (prevTiming, timing) => {
return { ...prevTiming, ...timing }
return { ...prevTiming, ...timing };
},
}
};

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

import { networkInterfaces } from "node:os"
import { networkInterfaces } from "node:os";
export const createIpGetters = () => {
const networkAddresses = []
const networkInterfaceMap = networkInterfaces()
const networkAddresses = [];
const networkInterfaceMap = networkInterfaces();
for (const key of Object.keys(networkInterfaceMap)) {
for (const networkAddress of networkInterfaceMap[key]) {
networkAddresses.push(networkAddress)
networkAddresses.push(networkAddress);
}

@@ -13,29 +13,29 @@ }

getFirstInternalIp: ({ preferIpv6 }) => {
const isPref = preferIpv6 ? isIpV6 : isIpV4
let firstInternalIp
const isPref = preferIpv6 ? isIpV6 : isIpV4;
let firstInternalIp;
for (const networkAddress of networkAddresses) {
if (networkAddress.internal) {
firstInternalIp = networkAddress.address
firstInternalIp = networkAddress.address;
if (isPref(networkAddress)) {
break
break;
}
}
}
return firstInternalIp
return firstInternalIp;
},
getFirstExternalIp: ({ preferIpv6 }) => {
const isPref = preferIpv6 ? isIpV6 : isIpV4
let firstExternalIp
const isPref = preferIpv6 ? isIpV6 : isIpV4;
let firstExternalIp;
for (const networkAddress of networkAddresses) {
if (!networkAddress.internal) {
firstExternalIp = networkAddress.address
firstExternalIp = networkAddress.address;
if (isPref(networkAddress)) {
break
break;
}
}
}
return firstExternalIp
return firstExternalIp;
},
}
}
};
};

@@ -45,7 +45,7 @@ const isIpV4 = (networkAddress) => {

if (typeof networkAddress.family === "number") {
return networkAddress.family === 4
return networkAddress.family === 4;
}
return networkAddress.family === "IPv4"
}
return networkAddress.family === "IPv4";
};
const isIpV6 = (networkAddress) => !isIpV4(networkAddress)
const isIpV6 = (networkAddress) => !isIpV4(networkAddress);

@@ -7,5 +7,5 @@ /**

import http from "node:http"
import net from "node:net"
import { listenEvent } from "./listenEvent.js"
import http from "node:http";
import net from "node:net";
import { listenEvent } from "./listenEvent.js";

@@ -18,3 +18,3 @@ export const createPolyglotServer = async ({

}) => {
const httpServer = http.createServer()
const httpServer = http.createServer();
const tlsServer = await createSecureServer({

@@ -25,6 +25,6 @@ certificate,

http1Allowed,
})
});
const netServer = net.createServer({
allowHalfOpen: false,
})
});

@@ -34,9 +34,9 @@ listenEvent(netServer, "connection", (socket) => {

if (protocol === "http") {
httpServer.emit("connection", socket)
return
httpServer.emit("connection", socket);
return;
}
if (protocol === "tls") {
tlsServer.emit("connection", socket)
return
tlsServer.emit("connection", socket);
return;
}

@@ -49,6 +49,6 @@

"",
].join("\r\n")
socket.write(response)
socket.end()
socket.destroy()
].join("\r\n");
socket.write(response);
socket.end();
socket.destroy();
netServer.emit(

@@ -58,11 +58,11 @@ "clientError",

socket,
)
})
})
);
});
});
netServer._httpServer = httpServer
netServer._tlsServer = tlsServer
netServer._httpServer = httpServer;
netServer._tlsServer = tlsServer;
return netServer
}
return netServer;
};

@@ -79,3 +79,3 @@ // The async part is just to lazyly import "http2" or "https"

if (http2) {
const { createSecureServer } = await import("node:http2")
const { createSecureServer } = await import("node:http2");
return createSecureServer({

@@ -85,40 +85,40 @@ cert: certificate,

allowHTTP1: http1Allowed,
})
});
}
const { createServer } = await import("node:https")
const { createServer } = await import("node:https");
return createServer({
cert: certificate,
key: privateKey,
})
}
});
};
const detectSocketProtocol = (socket, protocolDetectedCallback) => {
let removeOnceReadableListener = () => {}
let removeOnceReadableListener = () => {};
const tryToRead = () => {
const buffer = socket.read(1)
const buffer = socket.read(1);
if (buffer === null) {
removeOnceReadableListener = socket.once("readable", tryToRead)
return
removeOnceReadableListener = socket.once("readable", tryToRead);
return;
}
const firstByte = buffer[0]
socket.unshift(buffer)
const firstByte = buffer[0];
socket.unshift(buffer);
if (firstByte === 22) {
protocolDetectedCallback("tls")
return
protocolDetectedCallback("tls");
return;
}
if (firstByte > 32 && firstByte < 127) {
protocolDetectedCallback("http")
return
protocolDetectedCallback("http");
return;
}
protocolDetectedCallback(null)
}
protocolDetectedCallback(null);
};
tryToRead()
tryToRead();
return () => {
removeOnceReadableListener()
}
}
removeOnceReadableListener();
};
};

@@ -1,2 +0,2 @@

import { listenEvent } from "./listenEvent.js"
import { listenEvent } from "./listenEvent.js";

@@ -6,6 +6,6 @@ export const trackServerPendingConnections = (nodeServer, { http2 }) => {

// see http2.js: we rely on https://nodejs.org/api/http2.html#http2_compatibility_api
return trackHttp1ServerPendingConnections(nodeServer)
return trackHttp1ServerPendingConnections(nodeServer);
}
return trackHttp1ServerPendingConnections(nodeServer)
}
return trackHttp1ServerPendingConnections(nodeServer);
};

@@ -15,3 +15,3 @@ // const trackHttp2ServerPendingSessions = () => {}

const trackHttp1ServerPendingConnections = (nodeServer) => {
const pendingConnections = new Set()
const pendingConnections = new Set();

@@ -22,3 +22,3 @@ const removeConnectionListener = listenEvent(

(connection) => {
pendingConnections.add(connection)
pendingConnections.add(connection);
listenEvent(

@@ -28,23 +28,23 @@ connection,

() => {
pendingConnections.delete(connection)
pendingConnections.delete(connection);
},
{ once: true },
)
);
},
)
);
const stop = async (reason) => {
removeConnectionListener()
const pendingConnectionsArray = Array.from(pendingConnections)
pendingConnections.clear()
removeConnectionListener();
const pendingConnectionsArray = Array.from(pendingConnections);
pendingConnections.clear();
await Promise.all(
pendingConnectionsArray.map(async (pendingConnection) => {
await destroyConnection(pendingConnection, reason)
await destroyConnection(pendingConnection, reason);
}),
)
}
);
};
return { stop }
}
return { stop };
};

@@ -56,12 +56,12 @@ const destroyConnection = (connection, reason) => {

if (error === reason || error.code === "ENOTCONN") {
resolve()
resolve();
} else {
reject(error)
reject(error);
}
} else {
resolve()
resolve();
}
})
})
}
});
});
};

@@ -68,0 +68,0 @@ // export const trackServerPendingStreams = (nodeServer) => {

@@ -1,2 +0,2 @@

import { listenRequest } from "./listenRequest.js"
import { listenRequest } from "./listenRequest.js";

@@ -6,9 +6,9 @@ export const trackServerPendingRequests = (nodeServer, { http2 }) => {

// see http2.js: we rely on https://nodejs.org/api/http2.html#http2_compatibility_api
return trackHttp1ServerPendingRequests(nodeServer)
return trackHttp1ServerPendingRequests(nodeServer);
}
return trackHttp1ServerPendingRequests(nodeServer)
}
return trackHttp1ServerPendingRequests(nodeServer);
};
const trackHttp1ServerPendingRequests = (nodeServer) => {
const pendingClients = new Set()
const pendingClients = new Set();

@@ -18,18 +18,18 @@ const removeRequestListener = listenRequest(

(nodeRequest, nodeResponse) => {
const client = { nodeRequest, nodeResponse }
pendingClients.add(client)
const client = { nodeRequest, nodeResponse };
pendingClients.add(client);
nodeResponse.once("close", () => {
pendingClients.delete(client)
})
pendingClients.delete(client);
});
},
)
);
const stop = async ({ status, reason }) => {
removeRequestListener()
const pendingClientsArray = Array.from(pendingClients)
pendingClients.clear()
removeRequestListener();
const pendingClientsArray = Array.from(pendingClients);
pendingClients.clear();
await Promise.all(
pendingClientsArray.map(({ nodeResponse }) => {
if (nodeResponse.headersSent === false) {
nodeResponse.writeHead(status, String(reason))
nodeResponse.writeHead(status, String(reason));
}

@@ -41,13 +41,13 @@

if (nodeResponse.closed) {
resolve()
resolve();
} else {
nodeResponse.close((error) => {
if (error) {
reject(error)
reject(error);
} else {
resolve()
resolve();
}
})
});
}
})
});
}

@@ -58,15 +58,15 @@

if (nodeResponse.destroyed) {
resolve()
resolve();
} else {
nodeResponse.once("close", () => {
resolve()
})
nodeResponse.destroy()
resolve();
});
nodeResponse.destroy();
}
})
});
}),
)
}
);
};
return { stop }
}
return { stop };
};

@@ -7,6 +7,6 @@ /*

export { startServer } from "./start_server.js"
export { setupRoutes } from "./service_composition/routing.js"
export { readRequestBody } from "./readRequestBody.js"
export { fetchFileSystem } from "./fetch_filesystem.js"
export { startServer } from "./start_server.js";
export { setupRoutes } from "./service_composition/routing.js";
export { readRequestBody } from "./readRequestBody.js";
export { fetchFileSystem } from "./fetch_filesystem.js";
export {

@@ -20,4 +20,4 @@ STOP_REASON_INTERNAL_ERROR,

STOP_REASON_NOT_SPECIFIED,
} from "./stopReasons.js"
export { jsenvServiceErrorHandler } from "./services/error_handler/jsenv_service_error_handler.js"
} from "./stopReasons.js";
export { jsenvServiceErrorHandler } from "./services/error_handler/jsenv_service_error_handler.js";

@@ -29,21 +29,21 @@ // CORS

jsenvAccessControlAllowedMethods,
} from "./services/cors/jsenv_service_cors.js"
} from "./services/cors/jsenv_service_cors.js";
// server timing
export { timeFunction, timeStart } from "./server_timing/timing_measure.js"
export { timeFunction, timeStart } from "./server_timing/timing_measure.js";
// SSE
export { createSSERoom } from "./sse/sse_room.js"
export { createSSERoom } from "./sse/sse_room.js";
// content-negotiation
export { pickContentType } from "./content_negotiation/pick_content_type.js"
export { pickContentEncoding } from "./content_negotiation/pick_content_encoding.js"
export { pickContentLanguage } from "./content_negotiation/pick_content_language.js"
export { jsenvServiceResponseAcceptanceCheck } from "./services/response_acceptance_check/jsenv_service_response_acceptance_check.js"
export { pickContentType } from "./content_negotiation/pick_content_type.js";
export { pickContentEncoding } from "./content_negotiation/pick_content_encoding.js";
export { pickContentLanguage } from "./content_negotiation/pick_content_language.js";
export { jsenvServiceResponseAcceptanceCheck } from "./services/response_acceptance_check/jsenv_service_response_acceptance_check.js";
// others
export { serveDirectory } from "./serve_directory.js"
export { fromFetchResponse } from "./from_fetch_response.js"
export { composeTwoResponses } from "./internal/response_composition.js"
export { jsenvServiceRequestAliases } from "./services/request_aliases/jsenv_service_request_aliases.js"
export { findFreePort } from "./internal/listen.js"
export { serveDirectory } from "./serve_directory.js";
export { fromFetchResponse } from "./from_fetch_response.js";
export { composeTwoResponses } from "./internal/response_composition.js";
export { jsenvServiceRequestAliases } from "./services/request_aliases/jsenv_service_request_aliases.js";
export { findFreePort } from "./internal/listen.js";
export const readRequestBody = (request, { as = "string" } = {}) => {
return new Promise((resolve, reject) => {
const bufferArray = []
const bufferArray = [];
request.body.subscribe({
error: reject,
next: (buffer) => {
bufferArray.push(buffer)
bufferArray.push(buffer);
},
complete: () => {
const bodyAsBuffer = Buffer.concat(bufferArray)
const bodyAsBuffer = Buffer.concat(bufferArray);
if (as === "buffer") {
resolve(bodyAsBuffer)
return
resolve(bodyAsBuffer);
return;
}
if (as === "string") {
const bodyAsString = bodyAsBuffer.toString()
resolve(bodyAsString)
return
const bodyAsString = bodyAsBuffer.toString();
resolve(bodyAsString);
return;
}
if (as === "json") {
const bodyAsString = bodyAsBuffer.toString()
const bodyAsJSON = JSON.parse(bodyAsString)
resolve(bodyAsJSON)
return
const bodyAsString = bodyAsBuffer.toString();
const bodyAsJSON = JSON.parse(bodyAsString);
resolve(bodyAsJSON);
return;
}
},
})
})
}
});
});
};

@@ -1,4 +0,4 @@

import { readdirSync } from "node:fs"
import { readdirSync } from "node:fs";
import { pickContentType } from "./content_negotiation/pick_content_type.js"
import { pickContentType } from "./content_negotiation/pick_content_type.js";

@@ -9,8 +9,8 @@ export const serveDirectory = (

) => {
url = String(url)
url = url[url.length - 1] === "/" ? url : `${url}/`
const directoryContentArray = readdirSync(new URL(url))
url = String(url);
url = url[url.length - 1] === "/" ? url : `${url}/`;
const directoryContentArray = readdirSync(new URL(url));
const responseProducers = {
"application/json": () => {
const directoryContentJson = JSON.stringify(directoryContentArray)
const directoryContentJson = JSON.stringify(directoryContentArray);
return {

@@ -23,3 +23,3 @@ status: 200,

body: directoryContentJson,
}
};
},

@@ -39,9 +39,9 @@ "text/html": () => {

${directoryContentArray.map((filename) => {
const fileUrl = String(new URL(filename, url))
const fileUrl = String(new URL(filename, url));
const fileUrlRelativeToServer = fileUrl.slice(
String(rootDirectoryUrl).length,
)
);
return `<li>
<a href="/${fileUrlRelativeToServer}">${fileUrlRelativeToServer}</a>
</li>`
</li>`;
}).join(`

@@ -51,3 +51,3 @@ `)}

</body>
</html>`
</html>`;

@@ -61,10 +61,10 @@ return {

body: directoryAsHtml,
}
};
},
}
};
const bestContentType = pickContentType(
{ headers },
Object.keys(responseProducers),
)
return responseProducers[bestContentType || "application/json"]()
}
);
return responseProducers[bestContentType || "application/json"]();
};
import {
parseMultipleHeader,
stringifyMultipleHeader,
} from "../internal/multiple-header.js"
} from "../internal/multiple-header.js";

@@ -13,15 +13,15 @@ // to predict order in chrome devtools we should put a,b,c,d,e or something

export const timingToServerTimingResponseHeaders = (timing) => {
const serverTimingHeader = {}
const serverTimingHeader = {};
Object.keys(timing).forEach((key, index) => {
const name = letters[index] || "zz"
const name = letters[index] || "zz";
serverTimingHeader[name] = {
desc: key,
dur: timing[key],
}
})
};
});
const serverTimingHeaderString =
stringifyServerTimingHeader(serverTimingHeader)
stringifyServerTimingHeader(serverTimingHeader);
return { "server-timing": serverTimingHeaderString }
}
return { "server-timing": serverTimingHeaderString };
};

@@ -34,17 +34,17 @@ export const parseServerTimingHeader = (serverTimingHeaderString) => {

validateProperty: ({ name }) => {
return name === "desc" || name === "dur"
return name === "desc" || name === "dur";
},
},
)
);
const serverTiming = {}
const serverTiming = {};
Object.keys(serverTimingHeaderObject).forEach((key) => {
const { desc, dur } = serverTimingHeaderObject[key]
const { desc, dur } = serverTimingHeaderObject[key];
serverTiming[key] = {
...(desc ? { description: desc } : {}),
...(dur ? { duration: dur } : {}),
}
})
return serverTiming
}
};
});
return serverTiming;
};

@@ -54,4 +54,4 @@ export const stringifyServerTimingHeader = (serverTimingHeader) => {

validateName: validateServerTimingName,
})
}
});
};

@@ -65,9 +65,9 @@ // (),/:;<=>?@[\]{}" Don't allowed

const validateServerTimingName = (name) => {
const valid = /^[!#$%&'*+\-.^_`|~0-9a-z]+$/gi.test(name)
const valid = /^[!#$%&'*+\-.^_`|~0-9a-z]+$/gi.test(name);
if (!valid) {
console.warn(`server timing contains invalid symbols`)
return false
console.warn(`server timing contains invalid symbols`);
return false;
}
return true
}
return true;
};

@@ -101,2 +101,2 @@ const letters = [

"z",
]
];

@@ -1,2 +0,2 @@

import { performance } from "node:perf_hooks"
import { performance } from "node:perf_hooks";

@@ -6,22 +6,22 @@ export const timeStart = (name) => {

// duration is a https://www.w3.org/TR/hr-time-2/#sec-domhighrestimestamp
const startTimestamp = performance.now()
const startTimestamp = performance.now();
const timeEnd = () => {
const endTimestamp = performance.now()
const endTimestamp = performance.now();
const timing = {
[name]: endTimestamp - startTimestamp,
}
return timing
}
return timeEnd
}
};
return timing;
};
return timeEnd;
};
export const timeFunction = (name, fn) => {
const timeEnd = timeStart(name)
const returnValue = fn()
const timeEnd = timeStart(name);
const returnValue = fn();
if (returnValue && typeof returnValue.then === "function") {
return returnValue.then((value) => {
return [timeEnd(), value]
})
return [timeEnd(), value];
});
}
return [timeEnd(), returnValue]
}
return [timeEnd(), returnValue];
};

@@ -8,27 +8,27 @@ // ESM version of "path-to-regexp@6.2.1"

function lexer(str) {
var tokens = []
var i = 0
var tokens = [];
var i = 0;
while (i < str.length) {
var char = str[i]
var char = str[i];
if (char === "*" || char === "+" || char === "?") {
tokens.push({ type: "MODIFIER", index: i, value: str[i++] })
continue
tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
continue;
}
if (char === "\\") {
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] })
continue
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
continue;
}
if (char === "{") {
tokens.push({ type: "OPEN", index: i, value: str[i++] })
continue
tokens.push({ type: "OPEN", index: i, value: str[i++] });
continue;
}
if (char === "}") {
tokens.push({ type: "CLOSE", index: i, value: str[i++] })
continue
tokens.push({ type: "CLOSE", index: i, value: str[i++] });
continue;
}
if (char === ":") {
var name = ""
let j = i + 1
var name = "";
let j = i + 1;
while (j < str.length) {
var code = str.charCodeAt(j)
var code = str.charCodeAt(j);
if (

@@ -44,50 +44,50 @@ // `0-9`

) {
name += str[j++]
continue
name += str[j++];
continue;
}
break
break;
}
if (!name) throw new TypeError("Missing parameter name at ".concat(i))
tokens.push({ type: "NAME", index: i, value: name })
i = j
continue
if (!name) throw new TypeError("Missing parameter name at ".concat(i));
tokens.push({ type: "NAME", index: i, value: name });
i = j;
continue;
}
if (char === "(") {
var count = 1
var pattern = ""
var j = i + 1
var count = 1;
var pattern = "";
var j = i + 1;
if (str[j] === "?") {
throw new TypeError('Pattern cannot start with "?" at '.concat(j))
throw new TypeError('Pattern cannot start with "?" at '.concat(j));
}
while (j < str.length) {
if (str[j] === "\\") {
pattern += str[j++] + str[j++]
continue
pattern += str[j++] + str[j++];
continue;
}
if (str[j] === ")") {
count--
count--;
if (count === 0) {
j++
break
j++;
break;
}
} else if (str[j] === "(") {
count++
count++;
if (str[j + 1] !== "?") {
throw new TypeError(
"Capturing groups are not allowed at ".concat(j),
)
);
}
}
pattern += str[j++]
pattern += str[j++];
}
if (count) throw new TypeError("Unbalanced pattern at ".concat(i))
if (!pattern) throw new TypeError("Missing pattern at ".concat(i))
tokens.push({ type: "PATTERN", index: i, value: pattern })
i = j
continue
if (count) throw new TypeError("Unbalanced pattern at ".concat(i));
if (!pattern) throw new TypeError("Missing pattern at ".concat(i));
tokens.push({ type: "PATTERN", index: i, value: pattern });
i = j;
continue;
}
tokens.push({ type: "CHAR", index: i, value: str[i++] })
tokens.push({ type: "CHAR", index: i, value: str[i++] });
}
tokens.push({ type: "END", index: i, value: "" })
return tokens
tokens.push({ type: "END", index: i, value: "" });
return tokens;
}

@@ -98,19 +98,19 @@ /**

export function parse(str, { prefixes = "./", delimiter = "/#?" } = {}) {
var tokens = lexer(str)
var tokens = lexer(str);
var defaultPattern = "[^".concat(escapeString(delimiter), "]+?")
var result = []
var key = 0
var i = 0
var path = ""
var defaultPattern = "[^".concat(escapeString(delimiter), "]+?");
var result = [];
var key = 0;
var i = 0;
var path = "";
var tryConsume = function (type) {
if (i < tokens.length && tokens[i].type === type) return tokens[i++].value
return undefined
}
if (i < tokens.length && tokens[i].type === type) return tokens[i++].value;
return undefined;
};
var mustConsume = function (type) {
var value = tryConsume(type)
if (value !== undefined) return value
var _a = tokens[i]
var nextType = _a.type
var index = _a.inde
var value = tryConsume(type);
if (value !== undefined) return value;
var _a = tokens[i];
var nextType = _a.type;
var index = _a.inde;
throw new TypeError(

@@ -121,25 +121,25 @@ "Unexpected "

.concat(type),
)
}
);
};
var consumeText = function () {
var result = ""
var value
var result = "";
var value;
while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) {
result += value
result += value;
}
return result
}
return result;
};
while (i < tokens.length) {
var char = tryConsume("CHAR")
var name = tryConsume("NAME")
var pattern = tryConsume("PATTERN")
var char = tryConsume("CHAR");
var name = tryConsume("NAME");
var pattern = tryConsume("PATTERN");
if (name || pattern) {
let prefix = char || ""
let prefix = char || "";
if (prefixes.indexOf(prefix) === -1) {
path += prefix
prefix = ""
path += prefix;
prefix = "";
}
if (path) {
result.push(path)
path = ""
result.push(path);
path = "";
}

@@ -152,21 +152,21 @@ result.push({

modifier: tryConsume("MODIFIER") || "",
})
continue
});
continue;
}
var value = char || tryConsume("ESCAPED_CHAR")
var value = char || tryConsume("ESCAPED_CHAR");
if (value) {
path += value
continue
path += value;
continue;
}
if (path) {
result.push(path)
path = ""
result.push(path);
path = "";
}
var open = tryConsume("OPEN")
var open = tryConsume("OPEN");
if (open) {
var prefix = consumeText()
var name_1 = tryConsume("NAME") || ""
var pattern_1 = tryConsume("PATTERN") || ""
var suffix = consumeText()
mustConsume("CLOSE")
var prefix = consumeText();
var name_1 = tryConsume("NAME") || "";
var pattern_1 = tryConsume("PATTERN") || "";
var suffix = consumeText();
mustConsume("CLOSE");
result.push({

@@ -178,8 +178,8 @@ name: name_1 || (pattern_1 ? key++ : ""),

modifier: tryConsume("MODIFIER") || "",
})
continue
});
continue;
}
mustConsume("END")
mustConsume("END");
}
return result
return result;
}

@@ -190,3 +190,3 @@ /**

export function compile(str, options) {
return tokensToFunction(parse(str, options), options)
return tokensToFunction(parse(str, options), options);
}

@@ -206,17 +206,17 @@ /**

sensitive ? "" : "i",
)
);
}
return undefined
})
return undefined;
});
return function (data) {
var path = ""
var path = "";
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i]
var token = tokens[i];
if (typeof token === "string") {
path += token
continue
path += token;
continue;
}
var value = data ? data[token.name] : undefined
var optional = token.modifier === "?" || token.modifier === "*"
var repeat = token.modifier === "*" || token.modifier === "+"
var value = data ? data[token.name] : undefined;
var optional = token.modifier === "?" || token.modifier === "*";
var repeat = token.modifier === "*" || token.modifier === "+";
if (Array.isArray(value)) {

@@ -229,12 +229,12 @@ if (!repeat) {

),
)
);
}
if (value.length === 0) {
if (optional) continue
if (optional) continue;
throw new TypeError(
'Expected "'.concat(token.name, '" to not be empty'),
)
);
}
for (var j = 0; j < value.length; j++) {
let segment = encode(value[j], token)
let segment = encode(value[j], token);
if (validate && !matches[i].test(segment)) {

@@ -246,10 +246,10 @@ throw new TypeError(

.concat(segment, '"'),
)
);
}
path += token.prefix + segment + token.suffix
path += token.prefix + segment + token.suffix;
}
continue
continue;
}
if (typeof value === "string" || typeof value === "number") {
var segment = encode(String(value), token)
var segment = encode(String(value), token);
if (validate && !matches[i].test(segment)) {

@@ -261,15 +261,15 @@ throw new TypeError(

.concat(segment, '"'),
)
);
}
path += token.prefix + segment + token.suffix
continue
path += token.prefix + segment + token.suffix;
continue;
}
if (optional) continue
var typeOfMessage = repeat ? "an array" : "a string"
if (optional) continue;
var typeOfMessage = repeat ? "an array" : "a string";
throw new TypeError(
'Expected "'.concat(token.name, '" to be ').concat(typeOfMessage),
)
);
}
return path
}
return path;
};
}

@@ -280,5 +280,5 @@ /**

export function match(str, options) {
var keys = []
var re = pathToRegexp(str, keys, options)
return regexpToFunction(re, keys, options)
var keys = [];
var re = pathToRegexp(str, keys, options);
return regexpToFunction(re, keys, options);
}

@@ -290,10 +290,10 @@ /**

return function (pathname) {
var m = re.exec(pathname)
if (!m) return false
var path = m[0]
var index = m.index
var params = Object.create(null)
var m = re.exec(pathname);
if (!m) return false;
var path = m[0];
var index = m.index;
var params = Object.create(null);
var _loop_1 = function (i) {
if (m[i] === undefined) return "continue"
var key = keys[i - 1]
if (m[i] === undefined) return "continue";
var key = keys[i - 1];
if (key.modifier === "*" || key.modifier === "+") {

@@ -303,14 +303,14 @@ params[key.name] = m[i]

.map(function (value) {
return decode(value, key)
})
return decode(value, key);
});
} else {
params[key.name] = decode(m[i], key)
params[key.name] = decode(m[i], key);
}
return undefined
}
return undefined;
};
for (var i = 1; i < m.length; i++) {
_loop_1(i)
_loop_1(i);
}
return { path, index, params }
}
return { path, index, params };
};
}

@@ -321,3 +321,3 @@ /**

function escapeString(str) {
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1")
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
}

@@ -328,6 +328,6 @@ /**

function regexpToRegexp(path, keys) {
if (!keys) return path
var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g
var index = 0
var execResult = groupsRegex.exec(path.source)
if (!keys) return path;
var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
var index = 0;
var execResult = groupsRegex.exec(path.source);
while (execResult) {

@@ -341,6 +341,6 @@ keys.push({

pattern: "",
})
execResult = groupsRegex.exec(path.source)
});
execResult = groupsRegex.exec(path.source);
}
return path
return path;
}

@@ -352,5 +352,5 @@ /**

var parts = paths.map(function (path) {
return pathToRegexp(path, keys, { sensitive }).source
})
return new RegExp("(?:".concat(parts.join("|"), ")"), sensitive ? "" : "i")
return pathToRegexp(path, keys, { sensitive }).source;
});
return new RegExp("(?:".concat(parts.join("|"), ")"), sensitive ? "" : "i");
}

@@ -361,3 +361,3 @@ /**

function stringToRegexp(path, keys, options) {
return tokensToRegexp(parse(path, options), keys, options)
return tokensToRegexp(parse(path, options), keys, options);
}

@@ -380,18 +380,18 @@ /**

) {
var endsWithRe = "[".concat(escapeString(endsWith), "]|$")
var delimiterRe = "[".concat(escapeString(delimiter), "]")
var route = start ? "^" : ""
var endsWithRe = "[".concat(escapeString(endsWith), "]|$");
var delimiterRe = "[".concat(escapeString(delimiter), "]");
var route = start ? "^" : "";
// Iterate over the tokens and create our regexp string.
for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
var token = tokens_1[_i]
var token = tokens_1[_i];
if (typeof token === "string") {
route += escapeString(encode(token))
route += escapeString(encode(token));
} else {
var prefix = escapeString(encode(token.prefix))
var suffix = escapeString(encode(token.suffix))
var prefix = escapeString(encode(token.prefix));
var suffix = escapeString(encode(token.suffix));
if (token.pattern) {
if (keys) keys.push(token)
if (keys) keys.push(token);
if (prefix || suffix) {
if (token.modifier === "+" || token.modifier === "*") {
var mod = token.modifier === "*" ? "?" : ""
var mod = token.modifier === "*" ? "?" : "";
route += "(?:"

@@ -404,3 +404,3 @@ .concat(prefix, "((?:")

.concat(suffix, ")")
.concat(mod)
.concat(mod);
} else {

@@ -411,11 +411,16 @@ route += "(?:"

.concat(suffix, ")")
.concat(token.modifier)
.concat(token.modifier);
}
} else if (token.modifier === "+" || token.modifier === "*") {
route += "((?:".concat(token.pattern, ")").concat(token.modifier, ")")
route += "((?:"
.concat(token.pattern, ")")
.concat(token.modifier, ")");
} else {
route += "(".concat(token.pattern, ")").concat(token.modifier)
route += "(".concat(token.pattern, ")").concat(token.modifier);
}
} else {
route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier)
route += "(?:"
.concat(prefix)
.concat(suffix, ")")
.concat(token.modifier);
}

@@ -425,18 +430,18 @@ }

if (end) {
if (!strict) route += "".concat(delimiterRe, "?")
route += endsWith ? "(?=".concat(endsWithRe, ")") : "$"
if (!strict) route += "".concat(delimiterRe, "?");
route += endsWith ? "(?=".concat(endsWithRe, ")") : "$";
} else {
var endToken = tokens[tokens.length - 1]
var endToken = tokens[tokens.length - 1];
var isEndDelimited =
typeof endToken === "string"
? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1
: endToken === undefined
: endToken === undefined;
if (!strict) {
route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?")
route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?");
}
if (!isEndDelimited) {
route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")")
route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")");
}
}
return new RegExp(route, sensitive ? "" : "i")
return new RegExp(route, sensitive ? "" : "i");
}

@@ -451,5 +456,5 @@ /**

export function pathToRegexp(path, keys, options) {
if (path instanceof RegExp) return regexpToRegexp(path, keys)
if (Array.isArray(path)) return arrayToRegexp(path, keys, options)
return stringToRegexp(path, keys, options)
if (path instanceof RegExp) return regexpToRegexp(path, keys);
if (Array.isArray(path)) return arrayToRegexp(path, keys, options);
return stringToRegexp(path, keys, options);
}

@@ -1,2 +0,2 @@

import { match } from "./path_to_regexp.js"
import { match } from "./path_to_regexp.js";

@@ -7,15 +7,15 @@ export const setupRoutes = (routes) => {

decode: decodeURIComponent,
})
});
return {
applyPatternMatching,
requestHandler: routes[pathPattern],
}
})
};
});
return (request, { pushResponse, redirectRequest }) => {
let result
let result;
const found = candidates.find((candidate) => {
result = candidate.applyPatternMatching(request.pathname)
return Boolean(result)
})
result = candidate.applyPatternMatching(request.pathname);
return Boolean(result);
});
if (found) {

@@ -28,6 +28,6 @@ return found.requestHandler(

{ pushResponse, redirectRequest },
)
);
}
return null
}
}
return null;
};
};

@@ -1,2 +0,2 @@

import { timeStart } from "./server_timing/timing_measure.js"
import { timeStart } from "./server_timing/timing_measure.js";

@@ -13,21 +13,21 @@ const HOOK_NAMES = [

"serverStopped",
]
];
export const createServiceController = (services) => {
const flatServices = flattenAndFilterServices(services)
const hookGroups = {}
const flatServices = flattenAndFilterServices(services);
const hookGroups = {};
const addService = (service) => {
Object.keys(service).forEach((key) => {
if (key === "name") return
const isHook = HOOK_NAMES.includes(key)
if (key === "name") return;
const isHook = HOOK_NAMES.includes(key);
if (!isHook) {
console.warn(
`Unexpected "${key}" property on "${service.name}" service`,
)
);
}
const hookName = key
const hookValue = service[hookName]
const hookName = key;
const hookValue = service[hookName];
if (hookValue) {
const group = hookGroups[hookName] || (hookGroups[hookName] = [])
const group = hookGroups[hookName] || (hookGroups[hookName] = []);
group.push({

@@ -37,66 +37,66 @@ service,

value: hookValue,
})
});
}
})
}
});
};
flatServices.forEach((service) => {
addService(service)
})
addService(service);
});
let currentService = null
let currentHookName = null
let currentService = null;
let currentHookName = null;
const callHook = (hook, info, context) => {
const hookFn = hook.value
const hookFn = hook.value;
if (!hookFn) {
return null
return null;
}
currentService = hook.service
currentHookName = hook.name
let timeEnd
currentService = hook.service;
currentHookName = hook.name;
let timeEnd;
if (context && context.timing) {
timeEnd = timeStart(
`${currentService.name.replace("jsenv:", "")}.${currentHookName}`,
)
);
}
let valueReturned = hookFn(info, context)
let valueReturned = hookFn(info, context);
if (context && context.timing) {
Object.assign(context.timing, timeEnd())
Object.assign(context.timing, timeEnd());
}
currentService = null
currentHookName = null
return valueReturned
}
currentService = null;
currentHookName = null;
return valueReturned;
};
const callAsyncHook = async (hook, info, context) => {
const hookFn = hook.value
const hookFn = hook.value;
if (!hookFn) {
return null
return null;
}
currentService = hook.service
currentHookName = hook.name
let timeEnd
currentService = hook.service;
currentHookName = hook.name;
let timeEnd;
if (context && context.timing) {
timeEnd = timeStart(
`${currentService.name.replace("jsenv:", "")}.${currentHookName}`,
)
);
}
let valueReturned = await hookFn(info, context)
let valueReturned = await hookFn(info, context);
if (context && context.timing) {
Object.assign(context.timing, timeEnd())
Object.assign(context.timing, timeEnd());
}
currentService = null
currentHookName = null
return valueReturned
}
currentService = null;
currentHookName = null;
return valueReturned;
};
const callHooks = (hookName, info, context, callback = () => {}) => {
const hooks = hookGroups[hookName]
const hooks = hookGroups[hookName];
if (hooks) {
for (const hook of hooks) {
const returnValue = callHook(hook, info, context)
const returnValue = callHook(hook, info, context);
if (returnValue) {
callback(returnValue)
callback(returnValue);
}
}
}
}
};
const callHooksUntil = (

@@ -108,21 +108,21 @@ hookName,

) => {
const hooks = hookGroups[hookName]
const hooks = hookGroups[hookName];
if (hooks) {
for (const hook of hooks) {
const returnValue = callHook(hook, info, context)
const untilReturnValue = until(returnValue)
const returnValue = callHook(hook, info, context);
const untilReturnValue = until(returnValue);
if (untilReturnValue) {
return untilReturnValue
return untilReturnValue;
}
}
}
return null
}
return null;
};
const callAsyncHooksUntil = (hookName, info, context) => {
const hooks = hookGroups[hookName]
const hooks = hookGroups[hookName];
if (!hooks) {
return null
return null;
}
if (hooks.length === 0) {
return null
return null;
}

@@ -132,16 +132,16 @@ return new Promise((resolve, reject) => {

if (index >= hooks.length) {
return resolve()
return resolve();
}
const hook = hooks[index]
const returnValue = callAsyncHook(hook, info, context)
const hook = hooks[index];
const returnValue = callAsyncHook(hook, info, context);
return Promise.resolve(returnValue).then((output) => {
if (output) {
return resolve(output)
return resolve(output);
}
return visit(index + 1)
}, reject)
}
visit(0)
})
}
return visit(index + 1);
}, reject);
};
visit(0);
});
};

@@ -157,23 +157,23 @@ return {

getCurrentHookName: () => currentHookName,
}
}
};
};
const flattenAndFilterServices = (services) => {
const flatServices = []
const flatServices = [];
const visitServiceEntry = (serviceEntry) => {
if (Array.isArray(serviceEntry)) {
serviceEntry.forEach((value) => visitServiceEntry(value))
return
serviceEntry.forEach((value) => visitServiceEntry(value));
return;
}
if (typeof serviceEntry === "object" && serviceEntry !== null) {
if (!serviceEntry.name) {
serviceEntry.name = "anonymous"
serviceEntry.name = "anonymous";
}
flatServices.push(serviceEntry)
return
flatServices.push(serviceEntry);
return;
}
throw new Error(`services must be objects, got ${serviceEntry}`)
}
services.forEach((serviceEntry) => visitServiceEntry(serviceEntry))
return flatServices
}
throw new Error(`services must be objects, got ${serviceEntry}`);
};
services.forEach((serviceEntry) => visitServiceEntry(serviceEntry));
return flatServices;
};

@@ -1,2 +0,2 @@

export const jsenvAccessControlAllowedHeaders = ["x-requested-with"]
export const jsenvAccessControlAllowedHeaders = ["x-requested-with"];

@@ -9,3 +9,3 @@ export const jsenvAccessControlAllowedMethods = [

"OPTIONS",
]
];

@@ -28,6 +28,6 @@ export const jsenvServiceCORS = ({

const corsEnabled =
accessControlAllowRequestOrigin || accessControlAllowedOrigins.length
accessControlAllowRequestOrigin || accessControlAllowedOrigins.length;
if (!corsEnabled) {
return []
return [];
}

@@ -47,5 +47,5 @@

},
}
};
}
return null
return null;
},

@@ -65,7 +65,7 @@

timingAllowOrigin,
})
return accessControlHeaders
});
return accessControlHeaders;
},
}
}
};
};

@@ -88,18 +88,18 @@ // https://www.w3.org/TR/cors/

} = {}) => {
const vary = []
const vary = [];
const allowedOriginArray = [...accessControlAllowedOrigins]
const allowedOriginArray = [...accessControlAllowedOrigins];
if (accessControlAllowRequestOrigin) {
if ("origin" in headers && headers.origin !== "null") {
allowedOriginArray.push(headers.origin)
vary.push("origin")
allowedOriginArray.push(headers.origin);
vary.push("origin");
} else if ("referer" in headers) {
allowedOriginArray.push(new URL(headers.referer).origin)
vary.push("referer")
allowedOriginArray.push(new URL(headers.referer).origin);
vary.push("referer");
} else {
allowedOriginArray.push("*")
allowedOriginArray.push("*");
}
}
const allowedMethodArray = [...accessControlAllowedMethods]
const allowedMethodArray = [...accessControlAllowedMethods];
if (

@@ -109,10 +109,10 @@ accessControlAllowRequestMethod &&

) {
const requestMethodName = headers["access-control-request-method"]
const requestMethodName = headers["access-control-request-method"];
if (!allowedMethodArray.includes(requestMethodName)) {
allowedMethodArray.push(requestMethodName)
vary.push("access-control-request-method")
allowedMethodArray.push(requestMethodName);
vary.push("access-control-request-method");
}
}
const allowedHeaderArray = [...accessControlAllowedHeaders]
const allowedHeaderArray = [...accessControlAllowedHeaders];
if (

@@ -123,12 +123,12 @@ accessControlAllowRequestHeaders &&

const requestHeaderNameArray =
headers["access-control-request-headers"].split(", ")
headers["access-control-request-headers"].split(", ");
requestHeaderNameArray.forEach((headerName) => {
const headerNameLowerCase = headerName.toLowerCase()
const headerNameLowerCase = headerName.toLowerCase();
if (!allowedHeaderArray.includes(headerNameLowerCase)) {
allowedHeaderArray.push(headerNameLowerCase)
allowedHeaderArray.push(headerNameLowerCase);
if (!vary.includes("access-control-request-headers")) {
vary.push("access-control-request-headers")
vary.push("access-control-request-headers");
}
}
})
});
}

@@ -148,3 +148,3 @@

...(vary.length ? { vary: vary.join(", ") } : {}),
}
}
};
};

@@ -1,2 +0,2 @@

import { pickContentType } from "../../content_negotiation/pick_content_type.js"
import { pickContentType } from "../../content_negotiation/pick_content_type.js";

@@ -10,5 +10,5 @@ export const jsenvServiceErrorHandler = ({ sendErrorDetails = false } = {}) => {

(typeof serverInternalError !== "object" &&
typeof serverInternalError !== "function")
typeof serverInternalError !== "function");
if (!serverInternalErrorIsAPrimitive && serverInternalError.asResponse) {
return serverInternalError.asResponse()
return serverInternalError.asResponse();
}

@@ -28,3 +28,3 @@ const dataToSend = serverInternalErrorIsAPrimitive

: {}),
}
};

@@ -34,4 +34,4 @@ const availableContentTypes = {

const renderHtmlForErrorWithoutDetails = () => {
return `<p>Details not available: to enable them use jsenvServiceErrorHandler({ sendErrorDetails: true }).</p>`
}
return `<p>Details not available: to enable them use jsenvServiceErrorHandler({ sendErrorDetails: true }).</p>`;
};

@@ -44,6 +44,6 @@ const renderHtmlForErrorWithDetails = () => {

" ",
)}</pre>`
)}</pre>`;
}
return `<pre>${serverInternalError.stack}</pre>`
}
return `<pre>${serverInternalError.stack}</pre>`;
};

@@ -74,3 +74,3 @@ const body = `<!DOCTYPE html>

</body>
</html>`
</html>`;

@@ -83,6 +83,6 @@ return {

body,
}
};
},
"application/json": () => {
const body = JSON.stringify(dataToSend)
const body = JSON.stringify(dataToSend);
return {

@@ -94,12 +94,12 @@ headers: {

body,
}
};
},
}
};
const bestContentType = pickContentType(
request,
Object.keys(availableContentTypes),
)
return availableContentTypes[bestContentType || "application/json"]()
);
return availableContentTypes[bestContentType || "application/json"]();
},
}
}
};
};

@@ -1,37 +0,37 @@

import { URL_META } from "@jsenv/url-meta"
import { URL_META } from "@jsenv/url-meta";
export const jsenvServiceRequestAliases = (resourceAliases) => {
const aliases = {}
const aliases = {};
Object.keys(resourceAliases).forEach((key) => {
aliases[asFileUrl(key)] = asFileUrl(resourceAliases[key])
})
aliases[asFileUrl(key)] = asFileUrl(resourceAliases[key]);
});
return {
name: "jsenv:request_aliases",
redirectRequest: (request) => {
const resourceBeforeAlias = request.resource
const resourceBeforeAlias = request.resource;
const urlAfterAliasing = URL_META.applyAliases({
url: asFileUrl(request.pathname),
aliases,
})
const resourceAfterAlias = urlAfterAliasing.slice("file://".length)
});
const resourceAfterAlias = urlAfterAliasing.slice("file://".length);
if (resourceBeforeAlias === resourceAfterAlias) {
return null
return null;
}
const resource = replaceResource(resourceBeforeAlias, resourceAfterAlias)
return { resource }
const resource = replaceResource(resourceBeforeAlias, resourceAfterAlias);
return { resource };
},
}
}
};
};
const asFileUrl = (specifier) => new URL(specifier, "file:///").href
const asFileUrl = (specifier) => new URL(specifier, "file:///").href;
const replaceResource = (resourceBeforeAlias, newValue) => {
const urlObject = new URL(resourceBeforeAlias, "file://")
const searchSeparatorIndex = newValue.indexOf("?")
const urlObject = new URL(resourceBeforeAlias, "file://");
const searchSeparatorIndex = newValue.indexOf("?");
if (searchSeparatorIndex > -1) {
return newValue // let new value override search params
return newValue; // let new value override search params
}
urlObject.pathname = newValue
const resource = `${urlObject.pathname}${urlObject.search}`
return resource
}
urlObject.pathname = newValue;
const resource = `${urlObject.pathname}${urlObject.search}`;
return resource;
};

@@ -1,4 +0,4 @@

import { pickContentType } from "@jsenv/server/src/content_negotiation/pick_content_type.js"
import { pickContentLanguage } from "@jsenv/server/src/content_negotiation/pick_content_language.js"
import { pickContentEncoding } from "@jsenv/server/src/content_negotiation/pick_content_encoding.js"
import { pickContentType } from "@jsenv/server/src/content_negotiation/pick_content_type.js";
import { pickContentLanguage } from "@jsenv/server/src/content_negotiation/pick_content_language.js";
import { pickContentEncoding } from "@jsenv/server/src/content_negotiation/pick_content_encoding.js";

@@ -9,10 +9,10 @@ export const jsenvServiceResponseAcceptanceCheck = () => {

inspectResponse: (request, { response, warn }) => {
checkResponseAcceptance(request, response, { warn })
checkResponseAcceptance(request, response, { warn });
},
}
}
};
};
const checkResponseAcceptance = (request, response, { warn }) => {
const requestAcceptHeader = request.headers.accept
const responseContentTypeHeader = response.headers["content-type"]
const requestAcceptHeader = request.headers.accept;
const responseContentTypeHeader = response.headers["content-type"];
if (

@@ -27,7 +27,7 @@ requestAcceptHeader &&

--- request accept header ---
${requestAcceptHeader}`)
${requestAcceptHeader}`);
}
const requestAcceptLanguageHeader = request.headers["accept-language"]
const responseContentLanguageHeader = response.headers["content-language"]
const requestAcceptLanguageHeader = request.headers["accept-language"];
const responseContentLanguageHeader = response.headers["content-language"];
if (

@@ -42,7 +42,7 @@ requestAcceptLanguageHeader &&

--- request accept-language header ---
${requestAcceptLanguageHeader}`)
${requestAcceptLanguageHeader}`);
}
const requestAcceptEncodingHeader = request.headers["accept-encoding"]
const responseContentEncodingHeader = response.headers["content-encoding"]
const requestAcceptEncodingHeader = request.headers["accept-encoding"];
const responseContentEncodingHeader = response.headers["content-encoding"];
if (

@@ -57,4 +57,4 @@ requestAcceptLanguageHeader &&

--- request accept-encoding header ---
${requestAcceptEncodingHeader}`)
${requestAcceptEncodingHeader}`);
}
}
};

@@ -1,2 +0,2 @@

import { createLogger } from "@jsenv/log"
import { createLogger } from "@jsenv/log";

@@ -6,3 +6,3 @@ import {

createCompositeProducer,
} from "@jsenv/server/src/interfacing_with_node/observable.js"
} from "@jsenv/server/src/interfacing_with_node/observable.js";

@@ -23,14 +23,14 @@ // https://www.html5rocks.com/en/tutorials/eventsource/basics/

} = {}) => {
const logger = createLogger({ logLevel })
const logger = createLogger({ logLevel });
const room = {}
const clients = new Set()
const eventHistory = createEventHistory(historyLength)
const room = {};
const clients = new Set();
const eventHistory = createEventHistory(historyLength);
// what about previousEventId that keeps growing ?
// we could add some limit
// one limit could be that an event older than 24h is deleted
let previousEventId = 0
let opened = false
let interval
let cleanupEffect = CLEANUP_NOOP
let previousEventId = 0;
let opened = false;
let interval;
let cleanupEffect = CLEANUP_NOOP;

@@ -42,3 +42,3 @@ const join = (request) => {

request.headers["last-event-id"] ||
new URL(request.url).searchParams.get("last-event-id")
new URL(request.url).searchParams.get("last-event-id");

@@ -48,3 +48,3 @@ if (clients.size >= maxClientAllowed) {

status: 503,
}
};
}

@@ -55,3 +55,3 @@

status: 204,
}
};
}

@@ -64,26 +64,26 @@

request,
}
};
if (clients.size === 0) {
const effectReturnValue = effect()
const effectReturnValue = effect();
if (typeof effectReturnValue === "function") {
cleanupEffect = effectReturnValue
cleanupEffect = effectReturnValue;
} else {
cleanupEffect = CLEANUP_NOOP
cleanupEffect = CLEANUP_NOOP;
}
}
clients.add(client)
clients.add(client);
logger.debug(
`A client has joined. Number of client in room: ${clients.size}`,
)
);
if (lastKnownId !== undefined) {
const previousEvents = getAllEventSince(lastKnownId)
const eventMissedCount = previousEvents.length
const previousEvents = getAllEventSince(lastKnownId);
const eventMissedCount = previousEvents.length;
if (eventMissedCount > 0) {
logger.info(
`send ${eventMissedCount} event missed by client since event with id "${lastKnownId}"`,
)
);
previousEvents.forEach((previousEvent) => {
next(stringifySourceEvent(previousEvent))
})
next(stringifySourceEvent(previousEvent));
});
}

@@ -97,4 +97,4 @@ }

data: new Date().toLocaleTimeString(),
}
addEventToHistory(welcomeEvent)
};
addEventToHistory(welcomeEvent);

@@ -105,7 +105,7 @@ // send to everyone

history: false,
})
});
}
// send only to this client
else {
next(stringifySourceEvent(welcomeEvent))
next(stringifySourceEvent(welcomeEvent));
}

@@ -117,15 +117,17 @@ } else {

data: new Date().toLocaleTimeString(),
}
next(stringifySourceEvent(firstEvent))
};
next(stringifySourceEvent(firstEvent));
}
return () => {
clients.delete(client)
clients.delete(client);
if (clients.size === 0) {
cleanupEffect()
cleanupEffect = CLEANUP_NOOP
cleanupEffect();
cleanupEffect = CLEANUP_NOOP;
}
logger.debug(`A client left. Number of client in room: ${clients.size}`)
}
})
logger.debug(
`A client left. Number of client in room: ${clients.size}`,
);
};
});

@@ -136,3 +138,3 @@ const requestSSEObservable = connectRequestAndRoom(

sseRoomObservable,
)
);

@@ -147,37 +149,37 @@ return {

body: requestSSEObservable,
}
}
};
};
const leave = (request) => {
disconnectRequestFromRoom(request, room)
}
disconnectRequestFromRoom(request, room);
};
const addEventToHistory = (event) => {
if (typeof event.id === "undefined") {
event.id = computeEventId(event, previousEventId)
event.id = computeEventId(event, previousEventId);
}
previousEventId = event.id
eventHistory.add(event)
}
previousEventId = event.id;
eventHistory.add(event);
};
const sendEventToAllClients = (event, { history = true } = {}) => {
if (history) {
addEventToHistory(event)
addEventToHistory(event);
}
logger.debug(
`send "${event.type}" event to ${clients.size} client in the room`,
)
const eventString = stringifySourceEvent(event)
);
const eventString = stringifySourceEvent(event);
clients.forEach((client) => {
client.next(eventString)
})
}
client.next(eventString);
});
};
const getAllEventSince = (id) => {
const events = eventHistory.since(id)
const events = eventHistory.since(id);
if (welcomeEventEnabled && !welcomeEventPublic) {
return events.filter((event) => event.type !== "welcome")
return events.filter((event) => event.type !== "welcome");
}
return events
}
return events;
};

@@ -188,3 +190,3 @@ const keepAlive = () => {

`send keep alive event, number of client listening event source: ${clients.size}`,
)
);
sendEventToAllClients(

@@ -196,25 +198,25 @@ {

{ history: false },
)
}
);
};
const open = () => {
if (opened) return
opened = true
interval = setInterval(keepAlive, keepaliveDuration)
if (opened) return;
opened = true;
interval = setInterval(keepAlive, keepaliveDuration);
if (!keepProcessAlive) {
interval.unref()
interval.unref();
}
}
};
const close = () => {
if (!opened) return
logger.debug(`closing room, number of client in the room: ${clients.size}`)
clients.forEach((client) => client.complete())
clients.clear()
clearInterval(interval)
eventHistory.reset()
opened = false
}
if (!opened) return;
logger.debug(`closing room, number of client in the room: ${clients.size}`);
clients.forEach((client) => client.complete());
clients.clear();
clearInterval(interval);
eventHistory.reset();
opened = false;
};
open()
open();

@@ -237,43 +239,43 @@ Object.assign(room, {

open,
})
return room
}
});
return room;
};
const CLEANUP_NOOP = () => {}
const CLEANUP_NOOP = () => {};
const requestMap = new Map()
const requestMap = new Map();
const connectRequestAndRoom = (request, room, roomObservable) => {
let sseProducer
let roomObservableMap
const requestInfo = requestMap.get(request)
let sseProducer;
let roomObservableMap;
const requestInfo = requestMap.get(request);
if (requestInfo) {
sseProducer = requestInfo.sseProducer
roomObservableMap = requestInfo.roomObservableMap
sseProducer = requestInfo.sseProducer;
roomObservableMap = requestInfo.roomObservableMap;
} else {
sseProducer = createCompositeProducer({
cleanup: () => {
requestMap.delete(request)
requestMap.delete(request);
},
})
roomObservableMap = new Map()
requestMap.set(request, { sseProducer, roomObservableMap })
});
roomObservableMap = new Map();
requestMap.set(request, { sseProducer, roomObservableMap });
}
roomObservableMap.set(room, roomObservable)
sseProducer.addObservable(roomObservable)
roomObservableMap.set(room, roomObservable);
sseProducer.addObservable(roomObservable);
return createObservable(sseProducer)
}
return createObservable(sseProducer);
};
const disconnectRequestFromRoom = (request, room) => {
const requestInfo = requestMap.get(request)
const requestInfo = requestMap.get(request);
if (!requestInfo) {
return
return;
}
const { sseProducer, roomObservableMap } = requestInfo
const roomObservable = roomObservableMap.get(room)
roomObservableMap.delete(room)
sseProducer.removeObservable(roomObservable)
}
const { sseProducer, roomObservableMap } = requestInfo;
const roomObservable = roomObservableMap.get(room);
roomObservableMap.delete(room);
sseProducer.removeObservable(roomObservable);
};

@@ -285,42 +287,42 @@ // https://github.com/dmail-old/project/commit/da7d2c88fc8273850812972885d030a22f9d7448

const stringifySourceEvent = ({ data, type = "message", id, retry }) => {
let string = ""
let string = "";
if (id !== undefined) {
string += `id:${id}\n`
string += `id:${id}\n`;
}
if (retry) {
string += `retry:${retry}\n`
string += `retry:${retry}\n`;
}
if (type !== "message") {
string += `event:${type}\n`
string += `event:${type}\n`;
}
string += `data:${data}\n\n`
string += `data:${data}\n\n`;
return string
}
return string;
};
const createEventHistory = (limit) => {
const events = []
const events = [];
const add = (data) => {
events.push(data)
events.push(data);
if (events.length >= limit) {
events.shift()
events.shift();
}
}
};
const since = (id) => {
const index = events.findIndex((event) => String(event.id) === id)
return index === -1 ? [] : events.slice(index + 1)
}
const index = events.findIndex((event) => String(event.id) === id);
return index === -1 ? [] : events.slice(index + 1);
};
const reset = () => {
events.length = 0
}
events.length = 0;
};
return { add, since, reset }
}
return { add, since, reset };
};

@@ -1,4 +0,4 @@

import { isIP } from "node:net"
import cluster from "node:cluster"
import { createDetailedMessage, createLogger } from "@jsenv/log"
import { isIP } from "node:net";
import cluster from "node:cluster";
import { createDetailedMessage, createLogger } from "@jsenv/log";
import {

@@ -8,10 +8,10 @@ Abort,

createCallbackListNotifiedOnce,
} from "@jsenv/abort"
import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
} from "@jsenv/abort";
import { memoize } from "@jsenv/utils/src/memoize/memoize.js";
import { createServiceController } from "./service_controller.js"
import { timingToServerTimingResponseHeaders } from "./server_timing/timing_header.js"
import { createPolyglotServer } from "./internal/server-polyglot.js"
import { trackServerPendingConnections } from "./internal/trackServerPendingConnections.js"
import { trackServerPendingRequests } from "./internal/trackServerPendingRequests.js"
import { createServiceController } from "./service_controller.js";
import { timingToServerTimingResponseHeaders } from "./server_timing/timing_header.js";
import { createPolyglotServer } from "./internal/server-polyglot.js";
import { trackServerPendingConnections } from "./internal/trackServerPendingConnections.js";
import { trackServerPendingRequests } from "./internal/trackServerPendingRequests.js";
import {

@@ -21,13 +21,13 @@ fromNodeRequest,

applyRedirectionToRequest,
} from "./interfacing_with_node/request_factory.js"
import { writeNodeResponse } from "./interfacing_with_node/write_node_response.js"
} from "./interfacing_with_node/request_factory.js";
import { writeNodeResponse } from "./interfacing_with_node/write_node_response.js";
import {
statusToType,
colorizeResponseStatus,
} from "./internal/colorizeResponseStatus.js"
import { listen, stopListening } from "./internal/listen.js"
import { composeTwoResponses } from "./internal/response_composition.js"
import { listenRequest } from "./internal/listenRequest.js"
import { listenEvent } from "./internal/listenEvent.js"
import { listenServerConnectionError } from "./internal/listenServerConnectionError.js"
} from "./internal/colorizeResponseStatus.js";
import { listen, stopListening } from "./internal/listen.js";
import { composeTwoResponses } from "./internal/response_composition.js";
import { listenRequest } from "./internal/listenRequest.js";
import { listenEvent } from "./internal/listenEvent.js";
import { listenServerConnectionError } from "./internal/listenServerConnectionError.js";
import {

@@ -41,8 +41,8 @@ STOP_REASON_INTERNAL_ERROR,

STOP_REASON_NOT_SPECIFIED,
} from "./stopReasons.js"
import { composeTwoHeaders } from "./internal/headers_composition.js"
} from "./stopReasons.js";
import { composeTwoHeaders } from "./internal/headers_composition.js";
import { createIpGetters } from "./internal/server_ips.js"
import { parseHostname } from "./internal/hostname_parser.js"
import { applyDnsResolution } from "./internal/dns_resolution.js"
import { createIpGetters } from "./internal/server_ips.js";
import { parseHostname } from "./internal/hostname_parser.js";
import { applyDnsResolution } from "./internal/dns_resolution.js";

@@ -88,3 +88,3 @@ export const startServer = async ({

),
)
);
},

@@ -102,24 +102,24 @@ // timeAllocated to start responding to a request

{
const unexpectedParamNames = Object.keys(rest)
const unexpectedParamNames = Object.keys(rest);
if (unexpectedParamNames.length > 0) {
throw new TypeError(
`${unexpectedParamNames.join(",")}: there is no such param`,
)
);
}
if (https) {
if (typeof https !== "object") {
throw new TypeError(`https must be an object, got ${https}`)
throw new TypeError(`https must be an object, got ${https}`);
}
const { certificate, privateKey } = https
const { certificate, privateKey } = https;
if (!certificate || !privateKey) {
throw new TypeError(
`https must be an object with { certificate, privateKey }`,
)
);
}
}
if (http2 && !https) {
throw new Error(`http2 needs https`)
throw new Error(`http2 needs https`);
}
}
const logger = createLogger({ logLevel })
const logger = createLogger({ logLevel });
// param warnings and normalization

@@ -132,7 +132,7 @@ {

) {
redirectHttpToHttps = true
redirectHttpToHttps = true;
}
if (redirectHttpToHttps && !https) {
logger.warn(`redirectHttpToHttps ignored because protocol is http`)
redirectHttpToHttps = false
logger.warn(`redirectHttpToHttps ignored because protocol is http`);
redirectHttpToHttps = false;
}

@@ -142,14 +142,14 @@ if (allowHttpRequestOnHttps && redirectHttpToHttps) {

`redirectHttpToHttps ignored because allowHttpRequestOnHttps is enabled`,
)
redirectHttpToHttps = false
);
redirectHttpToHttps = false;
}
if (allowHttpRequestOnHttps && !https) {
logger.warn(`allowHttpRequestOnHttps ignored because protocol is http`)
allowHttpRequestOnHttps = false
logger.warn(`allowHttpRequestOnHttps ignored because protocol is http`);
allowHttpRequestOnHttps = false;
}
}
const server = {}
const serviceController = createServiceController(services)
const server = {};
const serviceController = createServiceController(services);
const processTeardownEvents = {

@@ -161,21 +161,21 @@ SIGHUP: stopOnExit,

exit: stopOnExit,
}
};
let status = "starting"
let nodeServer
const startServerOperation = Abort.startOperation()
const stopCallbackList = createCallbackListNotifiedOnce()
let status = "starting";
let nodeServer;
const startServerOperation = Abort.startOperation();
const stopCallbackList = createCallbackListNotifiedOnce();
const serverOrigins = {
local: "", // favors hostname when possible
}
};
try {
startServerOperation.addAbortSignal(signal)
startServerOperation.addAbortSignal(signal);
startServerOperation.addAbortSource((abort) => {
return raceProcessTeardownEvents(processTeardownEvents, ({ name }) => {
logger.info(`process teardown (${name}) -> aborting start server`)
abort()
})
})
startServerOperation.throwIfAborted()
logger.info(`process teardown (${name}) -> aborting start server`);
abort();
});
});
startServerOperation.throwIfAborted();
nodeServer = await createNodeServer({

@@ -187,31 +187,31 @@ https,

http1Allowed,
})
startServerOperation.throwIfAborted()
});
startServerOperation.throwIfAborted();
// https://nodejs.org/api/net.html#net_server_unref
if (!keepProcessAlive) {
nodeServer.unref()
nodeServer.unref();
}
const createOrigin = (hostname) => {
const protocol = https ? "https" : "http"
const protocol = https ? "https" : "http";
if (isIP(hostname) === 6) {
return `${protocol}://[${hostname}]`
return `${protocol}://[${hostname}]`;
}
return `${protocol}://${hostname}`
}
return `${protocol}://${hostname}`;
};
const ipGetters = createIpGetters()
let hostnameToListen
const ipGetters = createIpGetters();
let hostnameToListen;
if (acceptAnyIp) {
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 })
serverOrigins.local = createOrigin(firstInternalIp)
serverOrigins.localip = createOrigin(firstInternalIp)
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 })
serverOrigins.externalip = createOrigin(firstExternalIp)
hostnameToListen = preferIpv6 ? "::" : "0.0.0.0"
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 });
serverOrigins.local = createOrigin(firstInternalIp);
serverOrigins.localip = createOrigin(firstInternalIp);
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 });
serverOrigins.externalip = createOrigin(firstExternalIp);
hostnameToListen = preferIpv6 ? "::" : "0.0.0.0";
} else {
hostnameToListen = hostname
hostnameToListen = hostname;
}
const hostnameInfo = parseHostname(hostname)
const hostnameInfo = parseHostname(hostname);
if (hostnameInfo.type === "ip") {

@@ -221,16 +221,16 @@ if (acceptAnyIp) {

`hostname cannot be an ip when acceptAnyIp is enabled, got ${hostname}`,
)
);
}
preferIpv6 = hostnameInfo.version === 6
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 })
serverOrigins.local = createOrigin(firstInternalIp)
serverOrigins.localip = createOrigin(firstInternalIp)
preferIpv6 = hostnameInfo.version === 6;
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 });
serverOrigins.local = createOrigin(firstInternalIp);
serverOrigins.localip = createOrigin(firstInternalIp);
if (hostnameInfo.label === "unspecified") {
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 })
serverOrigins.externalip = createOrigin(firstExternalIp)
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 });
serverOrigins.externalip = createOrigin(firstExternalIp);
} else if (hostnameInfo.label === "loopback") {
// nothing
} else {
serverOrigins.local = createOrigin(hostname)
serverOrigins.local = createOrigin(hostname);
}

@@ -240,14 +240,14 @@ } else {

verbatim: true,
})
});
if (hostnameDnsResolution) {
const hostnameIp = hostnameDnsResolution.address
serverOrigins.localip = createOrigin(hostnameIp)
serverOrigins.local = createOrigin(hostname)
const hostnameIp = hostnameDnsResolution.address;
serverOrigins.localip = createOrigin(hostnameIp);
serverOrigins.local = createOrigin(hostname);
} else {
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 })
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 });
// fallback to internal ip because there is no ip
// associated to this hostname on operating system (in hosts file)
hostname = firstInternalIp
hostnameToListen = firstInternalIp
serverOrigins.local = createOrigin(firstInternalIp)
hostname = firstInternalIp;
hostnameToListen = firstInternalIp;
serverOrigins.local = createOrigin(firstInternalIp);
}

@@ -262,16 +262,16 @@ }

hostname: hostnameToListen,
})
});
// normalize origins (remove :80 when port is 80 for instance)
Object.keys(serverOrigins).forEach((key) => {
serverOrigins[key] = new URL(`${serverOrigins[key]}:${port}`).origin
})
serverOrigins[key] = new URL(`${serverOrigins[key]}:${port}`).origin;
});
serviceController.callHooks("serverListening", { port })
serviceController.callHooks("serverListening", { port });
startServerOperation.addAbortCallback(async () => {
await stopListening(nodeServer)
})
startServerOperation.throwIfAborted()
await stopListening(nodeServer);
});
startServerOperation.throwIfAborted();
} finally {
await startServerOperation.end()
await startServerOperation.end();
}

@@ -289,3 +289,3 @@

// over the ip
const serverOrigin = serverOrigins.local
const serverOrigin = serverOrigins.local;

@@ -297,18 +297,18 @@ // now the server is started (listening) it cannot be aborted anymore

stopCallbackList.add(({ reason }) => {
logger.info(`${serverName} stopping server (reason: ${reason})`)
})
logger.info(`${serverName} stopping server (reason: ${reason})`);
});
stopCallbackList.add(async () => {
await stopListening(nodeServer)
})
let stoppedResolve
await stopListening(nodeServer);
});
let stoppedResolve;
const stoppedPromise = new Promise((resolve) => {
stoppedResolve = resolve
})
stoppedResolve = resolve;
});
const stop = memoize(async (reason = STOP_REASON_NOT_SPECIFIED) => {
status = "stopping"
await Promise.all(stopCallbackList.notify({ reason }))
serviceController.callHooks("serverStopped", { reason })
status = "stopped"
stoppedResolve(reason)
})
status = "stopping";
await Promise.all(stopCallbackList.notify({ reason }));
serviceController.callHooks("serverStopped", { reason });
status = "stopped";
stoppedResolve(reason);
});

@@ -318,15 +318,15 @@ const cancelProcessTeardownRace = raceProcessTeardownEvents(

(winner) => {
stop(PROCESS_TEARDOWN_EVENTS_MAP[winner.name])
stop(PROCESS_TEARDOWN_EVENTS_MAP[winner.name]);
},
)
stopCallbackList.add(cancelProcessTeardownRace)
);
stopCallbackList.add(cancelProcessTeardownRace);
const onError = (error) => {
if (status === "stopping" && error.code === "ECONNRESET") {
return
return;
}
throw error
}
throw error;
};
status = "opened"
status = "opened";

@@ -336,14 +336,14 @@ const removeConnectionErrorListener = listenServerConnectionError(

onError,
)
stopCallbackList.add(removeConnectionErrorListener)
);
stopCallbackList.add(removeConnectionErrorListener);
const connectionsTracker = trackServerPendingConnections(nodeServer, {
http2,
})
});
// opened connection must be shutdown before the close event is emitted
stopCallbackList.add(connectionsTracker.stop)
stopCallbackList.add(connectionsTracker.stop);
const pendingRequestsTracker = trackServerPendingRequests(nodeServer, {
http2,
})
});
// ensure pending requests got a response from the server

@@ -354,4 +354,4 @@ stopCallbackList.add((reason) => {

reason,
})
})
});
});

@@ -363,5 +363,5 @@ request: {

// might be closed when we'll try to attach "data" and "end" listeners to it
nodeRequest.pause()
nodeRequest.pause();
if (!nagle) {
nodeRequest.connection.setNoDelay(true)
nodeRequest.connection.setNoDelay(true);
}

@@ -371,31 +371,31 @@ if (redirectHttpToHttps && !nodeRequest.connection.encrypted) {

location: `${serverOrigin}${nodeRequest.url}`,
})
nodeResponse.end()
return
});
nodeResponse.end();
return;
}
const receiveRequestOperation = Abort.startOperation()
const receiveRequestOperation = Abort.startOperation();
receiveRequestOperation.addAbortSource((abort) => {
const closeEventCallback = () => {
if (nodeRequest.complete) {
receiveRequestOperation.end()
receiveRequestOperation.end();
} else {
nodeResponse.destroy()
abort()
nodeResponse.destroy();
abort();
}
}
nodeRequest.once("close", closeEventCallback)
};
nodeRequest.once("close", closeEventCallback);
return () => {
nodeRequest.removeListener("close", closeEventCallback)
}
})
nodeRequest.removeListener("close", closeEventCallback);
};
});
receiveRequestOperation.addAbortSource((abort) => {
return stopCallbackList.add(abort)
})
return stopCallbackList.add(abort);
});
const sendResponseOperation = Abort.startOperation()
sendResponseOperation.addAbortSignal(receiveRequestOperation.signal)
const sendResponseOperation = Abort.startOperation();
sendResponseOperation.addAbortSignal(receiveRequestOperation.signal);
sendResponseOperation.addAbortSource((abort) => {
return stopCallbackList.add(abort)
})
return stopCallbackList.add(abort);
});

@@ -405,3 +405,3 @@ const request = fromNodeRequest(nodeRequest, {

signal: receiveRequestOperation.signal,
})
});

@@ -415,14 +415,14 @@ // Handling request is asynchronous, we buffer logs for that request

children: [],
}
};
const addRequestLog = (node, { type, value }) => {
node.logs.push({ type, value })
}
node.logs.push({ type, value });
};
const onRequestHandled = (node) => {
if (node !== rootRequestNode) {
// keep buffering until root request write logs for everyone
return
return;
}
const prefixLines = (string, prefix) => {
return string.replace(/^(?!\s*$)/gm, prefix)
}
return string.replace(/^(?!\s*$)/gm, prefix);
};
const writeLog = (

@@ -433,36 +433,36 @@ { type, value },

if (depth > 0) {
value = prefixLines(value, " ".repeat(depth))
value = prefixLines(value, " ".repeat(depth));
}
if (type === "info") {
if (someLogIsError) {
type = "error"
type = "error";
} else if (someLogIsWarn) {
type = "warn"
type = "warn";
}
}
logger[type](value)
}
logger[type](value);
};
const visitRequestNodeToLog = (requestNode, depth) => {
let someLogIsError = false
let someLogIsWarn = false
let someLogIsError = false;
let someLogIsWarn = false;
requestNode.logs.forEach((log) => {
if (log.type === "error") {
someLogIsError = true
someLogIsError = true;
}
if (log.type === "warn") {
someLogIsWarn = true
someLogIsWarn = true;
}
})
});
const firstLog = requestNode.logs.shift()
const lastLog = requestNode.logs.pop()
const middleLogs = requestNode.logs
const firstLog = requestNode.logs.shift();
const lastLog = requestNode.logs.pop();
const middleLogs = requestNode.logs;
writeLog(firstLog, { someLogIsError, someLogIsWarn, depth })
writeLog(firstLog, { someLogIsError, someLogIsWarn, depth });
middleLogs.forEach((log) => {
writeLog(log, { someLogIsError, someLogIsWarn, depth })
})
writeLog(log, { someLogIsError, someLogIsWarn, depth });
});
requestNode.children.forEach((child) => {
visitRequestNodeToLog(child, depth + 1)
})
visitRequestNodeToLog(child, depth + 1);
});
if (lastLog) {

@@ -473,7 +473,7 @@ writeLog(lastLog, {

depth: depth + 1,
})
});
}
}
visitRequestNodeToLog(rootRequestNode, 0)
}
};
visitRequestNodeToLog(rootRequestNode, 0);
};
nodeRequest.on("error", (error) => {

@@ -486,3 +486,3 @@ if (error.message === "aborted") {

}),
})
});
} else {

@@ -495,8 +495,8 @@ // I'm not sure this can happen but it's here in case

}),
})
});
}
})
});
const pushResponse = async ({ path, method }, { requestNode }) => {
const http2Stream = nodeResponse.stream
const http2Stream = nodeResponse.stream;

@@ -514,8 +514,8 @@ // being able to push a stream is nice to have

),
})
}
});
};
// not aborted, let's try to push a stream into that response
// https://nodejs.org/docs/latest-v16.x/api/http2.html#http2streampushstreamheaders-options-callback
let pushStream
let pushStream;
try {

@@ -534,29 +534,29 @@ pushStream = await new Promise((resolve, reject) => {

if (error) {
reject(error)
reject(error);
}
resolve(pushStream)
resolve(pushStream);
},
)
})
);
});
} catch (e) {
onPushStreamError(e)
return
onPushStreamError(e);
return;
}
const abortController = new AbortController()
const abortController = new AbortController();
// It's possible to get NGHTTP2_REFUSED_STREAM errors here
// https://github.com/nodejs/node/issues/20824
const pushErrorCallback = (error) => {
onPushStreamError(error)
abortController.abort()
}
pushStream.on("error", pushErrorCallback)
onPushStreamError(error);
abortController.abort();
};
pushStream.on("error", pushErrorCallback);
sendResponseOperation.addEndCallback(() => {
pushStream.removeListener("error", onPushStreamError)
})
pushStream.removeListener("error", onPushStreamError);
});
await sendResponseOperation.withSignal(async (signal) => {
const pushResponseOperation = Abort.startOperation()
pushResponseOperation.addAbortSignal(signal)
pushResponseOperation.addAbortSignal(abortController.signal)
const pushResponseOperation = Abort.startOperation();
pushResponseOperation.addAbortSignal(signal);
pushResponseOperation.addAbortSignal(abortController.signal);

@@ -567,3 +567,3 @@ const pushRequest = createPushRequest(request, {

method,
})
});

@@ -573,14 +573,14 @@ try {

requestNode,
})
});
if (!abortController.signal.aborted) {
if (pushStream.destroyed) {
abortController.abort()
abortController.abort();
} else if (!http2Stream.pushAllowed) {
abortController.abort()
abortController.abort();
} else if (responseProperties.requestAborted) {
} else {
const responseLength =
responseProperties.headers["content-length"] || 0
responseProperties.headers["content-length"] || 0;
const { effectiveRecvDataLength, remoteWindowSize } =
http2Stream.session.state
http2Stream.session.state;
if (

@@ -593,4 +593,4 @@ effectiveRecvDataLength + responseLength >

value: `Aborting stream to prevent exceeding remoteWindowSize`,
})
abortController.abort()
});
abortController.abort();
}

@@ -605,13 +605,13 @@ }

responseProperties,
})
});
} finally {
await pushResponseOperation.end()
await pushResponseOperation.end();
}
})
}
});
};
const handleRequest = async (request, { requestNode }) => {
let requestReceivedMeasure
let requestReceivedMeasure;
if (serverTiming) {
requestReceivedMeasure = performance.now()
requestReceivedMeasure = performance.now();
}

@@ -623,3 +623,3 @@ addRequestLog(requestNode, {

: `${request.method} ${request.url}`,
})
});
const warn = (value) => {

@@ -629,6 +629,6 @@ addRequestLog(requestNode, {

value,
})
}
});
};
let requestWaitingTimeout
let requestWaitingTimeout;
if (requestWaitingMs) {

@@ -638,3 +638,3 @@ requestWaitingTimeout = setTimeout(

requestWaitingMs,
).unref()
).unref();
}

@@ -652,12 +652,12 @@

...newRequestProperties,
})
});
}
},
)
);
let handleRequestReturnValue
let errorWhileHandlingRequest = null
let handleRequestTimings = serverTiming ? {} : null
let handleRequestReturnValue;
let errorWhileHandlingRequest = null;
let handleRequestTimings = serverTiming ? {} : null;
let timeout
let timeout;
const timeoutPromise = new Promise((resolve) => {

@@ -674,5 +674,5 @@ timeout = setTimeout(() => {

}s waiting to handle request`,
})
}, responseTimeout)
})
});
}, responseTimeout);
});
const handleRequestPromise = serviceController.callAsyncHooksUntil(

@@ -689,4 +689,4 @@ "handleRequest",

value: `response push ignored because path is invalid (must be a string starting with "/", found ${path})`,
})
return
});
return;
}

@@ -697,6 +697,6 @@ if (!request.http2) {

value: `response push ignored because request is not http2`,
})
return
});
return;
}
const canPushStream = testCanPushStream(nodeResponse.stream)
const canPushStream = testCanPushStream(nodeResponse.stream);
if (!canPushStream.can) {

@@ -706,10 +706,10 @@ addRequestLog(requestNode, {

value: `response push ignored because ${canPushStream.reason}`,
})
return
});
return;
}
let preventedByService = null
let preventedByService = null;
const prevent = () => {
preventedByService = serviceController.getCurrentService()
}
preventedByService = serviceController.getCurrentService();
};
serviceController.callHooksUntil(

@@ -724,3 +724,3 @@ "onResponsePush",

() => preventedByService,
)
);
if (preventedByService) {

@@ -730,8 +730,8 @@ addRequestLog(requestNode, {

value: `response push prevented by "${preventedByService.name}" service`,
})
return
});
return;
}
const requestChildNode = { logs: [], children: [] }
requestNode.children.push(requestChildNode)
const requestChildNode = { logs: [], children: [] };
requestNode.children.push(requestChildNode);
await pushResponse(

@@ -743,6 +743,6 @@ { path, method },

},
)
);
},
},
)
);
try {

@@ -752,9 +752,9 @@ handleRequestReturnValue = await Promise.race([

handleRequestPromise,
])
]);
} catch (e) {
errorWhileHandlingRequest = e
errorWhileHandlingRequest = e;
}
clearTimeout(timeout)
clearTimeout(timeout);
let responseProperties
let responseProperties;
if (errorWhileHandlingRequest) {

@@ -765,3 +765,3 @@ if (

) {
responseProperties = { requestAborted: true }
responseProperties = { requestAborted: true };
} else {

@@ -777,3 +777,3 @@ // internal error, create 500 response

// il faudrais pouvoir stop que les autres response ?
stop(STOP_REASON_INTERNAL_ERROR)
stop(STOP_REASON_INTERNAL_ERROR);
}

@@ -788,5 +788,5 @@ const handleErrorReturnValue =

},
)
);
if (!handleErrorReturnValue) {
throw errorWhileHandlingRequest
throw errorWhileHandlingRequest;
}

@@ -801,3 +801,3 @@ addRequestLog(requestNode, {

),
})
});
responseProperties = composeTwoResponses(

@@ -814,3 +814,3 @@ {

handleErrorReturnValue,
)
);
}

@@ -825,3 +825,3 @@ } else {

...rest
} = handleRequestReturnValue || {}
} = handleRequestReturnValue || {};
responseProperties = {

@@ -834,9 +834,9 @@ status,

...rest,
}
};
}
if (serverTiming) {
const responseReadyMeasure = performance.now()
const responseReadyMeasure = performance.now();
const timeToStartResponding =
responseReadyMeasure - requestReceivedMeasure
responseReadyMeasure - requestReceivedMeasure;
const serverTiming = {

@@ -846,10 +846,10 @@ ...handleRequestTimings,

"time to start responding": timeToStartResponding,
}
};
responseProperties.headers = composeTwoHeaders(
responseProperties.headers,
timingToServerTimingResponseHeaders(serverTiming),
)
);
}
if (requestWaitingMs) {
clearTimeout(requestWaitingTimeout)
clearTimeout(requestWaitingTimeout);
}

@@ -864,3 +864,3 @@ if (

value: `content-length header is ${responseProperties.headers["content-length"]} but body is empty`,
})
});
}

@@ -879,12 +879,12 @@ serviceController.callHooks(

returnValue,
)
);
}
},
)
);
serviceController.callHooks("responseReady", responseProperties, {
request,
warn,
})
return responseProperties
}
});
return responseProperties;
};

@@ -902,6 +902,6 @@ const sendResponse = async ({

// To let a chance to pushed streams we wait a little before sending the response
const ignoreBody = request.method === "HEAD"
const bodyIsEmpty = !responseProperties.body || ignoreBody
const ignoreBody = request.method === "HEAD";
const bodyIsEmpty = !responseProperties.body || ignoreBody;
if (bodyIsEmpty && requestNode.children.length > 0) {
await new Promise((resolve) => setTimeout(resolve))
await new Promise((resolve) => setTimeout(resolve));
}

@@ -916,4 +916,4 @@

value: `response aborted`,
})
onRequestHandled(requestNode)
});
onRequestHandled(requestNode);
},

@@ -929,7 +929,7 @@ onError: (error) => {

),
})
onRequestHandled(requestNode)
});
onRequestHandled(requestNode);
},
onHeadersSent: ({ status, statusText }) => {
const statusType = statusToType(status)
const statusType = statusToType(status);
addRequestLog(requestNode, {

@@ -949,20 +949,20 @@ type:

}`,
})
});
},
onEnd: () => {
onRequestHandled(requestNode)
onRequestHandled(requestNode);
},
})
}
});
};
try {
if (receiveRequestOperation.signal.aborted) {
return
return;
}
const responseProperties = await handleRequest(request, {
requestNode: rootRequestNode,
})
nodeRequest.resume()
});
nodeRequest.resume();
if (receiveRequestOperation.signal.aborted) {
return
return;
}

@@ -974,3 +974,3 @@

if (responseProperties.headers.connection === "keep-alive") {
clearTimeout(request.body.timeout)
clearTimeout(request.body.timeout);
}

@@ -984,10 +984,10 @@

responseProperties,
})
});
} finally {
await sendResponseOperation.end()
await sendResponseOperation.end();
}
}
const removeRequestListener = listenRequest(nodeServer, requestCallback)
};
const removeRequestListener = listenRequest(nodeServer, requestCallback);
// ensure we don't try to handle new requests while server is stopping
stopCallbackList.add(removeRequestListener)
stopCallbackList.add(removeRequestListener);
}

@@ -997,17 +997,17 @@

// https://github.com/websockets/ws/blob/master/doc/ws.md#class-websocket
const websocketHandlers = []
const websocketHandlers = [];
serviceController.services.forEach((service) => {
const { handleWebsocket } = service
const { handleWebsocket } = service;
if (handleWebsocket) {
websocketHandlers.push(handleWebsocket)
websocketHandlers.push(handleWebsocket);
}
})
});
if (websocketHandlers.length > 0) {
const websocketClients = new Set()
const { WebSocketServer } = await import("ws")
let websocketServer = new WebSocketServer({ noServer: true })
const websocketClients = new Set();
const { WebSocketServer } = await import("ws");
let websocketServer = new WebSocketServer({ noServer: true });
const websocketOrigin = https
? `wss://${hostname}:${port}`
: `ws://${hostname}:${port}`
server.websocketOrigin = websocketOrigin
: `ws://${hostname}:${port}`;
server.websocketOrigin = websocketOrigin;
const upgradeCallback = (nodeRequest, socket, head) => {

@@ -1019,6 +1019,6 @@ websocketServer.handleUpgrade(

async (websocket) => {
websocketClients.add(websocket)
websocketClients.add(websocket);
websocket.once("close", () => {
websocketClients.delete(websocket)
})
websocketClients.delete(websocket);
});
const request = fromNodeRequest(nodeRequest, {

@@ -1028,3 +1028,3 @@ serverOrigin: websocketOrigin,

requestBodyLifetime,
})
});
serviceController.callAsyncHooksUntil(

@@ -1036,9 +1036,9 @@ "handleWebsocket",

},
)
);
},
)
}
);
};
// see server-polyglot.js, upgrade must be listened on https server when used
const facadeServer = nodeServer._tlsServer || nodeServer
const facadeServer = nodeServer._tlsServer || nodeServer;
const removeUpgradeCallback = listenEvent(

@@ -1048,12 +1048,12 @@ facadeServer,

upgradeCallback,
)
stopCallbackList.add(removeUpgradeCallback)
);
stopCallbackList.add(removeUpgradeCallback);
stopCallbackList.add(() => {
websocketClients.forEach((websocketClient) => {
websocketClient.close()
})
websocketClients.clear()
websocketServer.close()
websocketServer = null
})
websocketClient.close();
});
websocketClients.clear();
websocketServer.close();
websocketServer = null;
});
}

@@ -1066,5 +1066,5 @@ }

`${serverName} started at ${serverOrigins.local} (${serverOrigins.network})`,
)
);
} else {
logger.info(`${serverName} started at ${serverOrigins.local}`)
logger.info(`${serverName} started at ${serverOrigins.local}`);
}

@@ -1083,10 +1083,10 @@ }

addEffect: (callback) => {
const cleanup = callback()
const cleanup = callback();
if (typeof cleanup === "function") {
stopCallbackList.add(cleanup)
stopCallbackList.add(cleanup);
}
},
})
return server
}
});
return server;
};

@@ -1101,3 +1101,3 @@ const createNodeServer = async ({

if (https) {
const { certificate, privateKey } = https
const { certificate, privateKey } = https;
if (redirectHttpToHttps || allowHttpRequestOnHttps) {

@@ -1109,13 +1109,13 @@ return createPolyglotServer({

http1Allowed,
})
});
}
const { createServer } = await import("node:https")
const { createServer } = await import("node:https");
return createServer({
cert: certificate,
key: privateKey,
})
});
}
const { createServer } = await import("node:http")
return createServer()
}
const { createServer } = await import("node:http");
return createServer();
};

@@ -1127,3 +1127,3 @@ const testCanPushStream = (http2Stream) => {

reason: `stream.pushAllowed is false`,
}
};
}

@@ -1133,3 +1133,3 @@

// And https://github.com/google/node-h2-auto-push/blob/67a36c04cbbd6da7b066a4e8d361c593d38853a4/src/index.ts#L100-L106
const { remoteWindowSize } = http2Stream.session.state
const { remoteWindowSize } = http2Stream.session.state;
if (remoteWindowSize === 0) {

@@ -1139,3 +1139,3 @@ return {

reason: `no more remoteWindowSize`,
}
};
}

@@ -1145,4 +1145,4 @@

can: true,
}
}
};
};

@@ -1155,2 +1155,2 @@ const PROCESS_TEARDOWN_EVENTS_MAP = {

exit: STOP_REASON_PROCESS_EXIT,
}
};
const createReason = (reasonString) => {
return {
toString: () => reasonString,
}
}
};
};
export const STOP_REASON_INTERNAL_ERROR = createReason("Internal error")
export const STOP_REASON_PROCESS_SIGHUP = createReason("process SIGHUP")
export const STOP_REASON_PROCESS_SIGTERM = createReason("process SIGTERM")
export const STOP_REASON_PROCESS_SIGINT = createReason("process SIGINT")
export const STOP_REASON_INTERNAL_ERROR = createReason("Internal error");
export const STOP_REASON_PROCESS_SIGHUP = createReason("process SIGHUP");
export const STOP_REASON_PROCESS_SIGTERM = createReason("process SIGTERM");
export const STOP_REASON_PROCESS_SIGINT = createReason("process SIGINT");
export const STOP_REASON_PROCESS_BEFORE_EXIT = createReason(
"process before exit",
)
export const STOP_REASON_PROCESS_EXIT = createReason("process exit")
export const STOP_REASON_NOT_SPECIFIED = createReason("not specified")
);
export const STOP_REASON_PROCESS_EXIT = createReason("process exit");
export const STOP_REASON_NOT_SPECIFIED = createReason("not specified");
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