Files
Announcements/server/src/assets/streaming.js
T

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,
};