67 lines
2.0 KiB
JavaScript
67 lines
2.0 KiB
JavaScript
const { Readable } = require("node:stream");
|
|
const { pipeline } = require("node:stream/promises");
|
|
const { AssetError } = require("./errors");
|
|
|
|
function readableBody(body) {
|
|
if (body && typeof body.pipe === "function") return body;
|
|
if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
|
return Readable.from([body]);
|
|
}
|
|
if (body && typeof body.getReader === "function") {
|
|
return Readable.fromWeb(body);
|
|
}
|
|
throw new AssetError(
|
|
"STORAGE_INCONSISTENCY",
|
|
"Stored asset bytes could not be streamed",
|
|
{ statusCode: 503 },
|
|
);
|
|
}
|
|
|
|
function validateDescriptor(descriptor) {
|
|
if (
|
|
typeof descriptor.contentType !== "string" ||
|
|
!Number.isSafeInteger(descriptor.contentLength) ||
|
|
descriptor.contentLength < 0
|
|
) {
|
|
throw new AssetError(
|
|
"STORAGE_INCONSISTENCY",
|
|
"Stored asset metadata is incomplete",
|
|
{ statusCode: 503 },
|
|
);
|
|
}
|
|
}
|
|
|
|
function setCommonHeaders(response, descriptor) {
|
|
response.set("Content-Type", descriptor.contentType);
|
|
response.set("Content-Length", String(descriptor.contentLength));
|
|
response.set("X-Content-Type-Options", "nosniff");
|
|
}
|
|
|
|
async function storedBody(assetService, descriptor) {
|
|
const stored = await assetService.readStoredObject(descriptor);
|
|
return readableBody(stored.body);
|
|
}
|
|
|
|
async function sendImmutableMedia(assetService, response, descriptor) {
|
|
validateDescriptor(descriptor);
|
|
const body = await storedBody(assetService, descriptor);
|
|
setCommonHeaders(response, descriptor);
|
|
response.set("Cache-Control", "public, max-age=31536000, immutable");
|
|
response.set("ETag", `"${descriptor.hash}"`);
|
|
await pipeline(body, response);
|
|
}
|
|
|
|
async function sendOriginal(assetService, response, descriptor) {
|
|
validateDescriptor(descriptor);
|
|
const body = await storedBody(assetService, descriptor);
|
|
response.attachment(descriptor.asset.original_filename);
|
|
setCommonHeaders(response, descriptor);
|
|
response.set("Cache-Control", "private, no-store");
|
|
await pipeline(body, response);
|
|
}
|
|
|
|
module.exports = {
|
|
sendImmutableMedia,
|
|
sendOriginal,
|
|
};
|