UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 10.1 kB
{"version":3,"file":"stream-single.mjs","names":["constants","cdnServices.streamMedia","streamFallbackMedia","cdnServices.streamFallbackMedia"],"sources":["../../../../../src/libs/http/controllers/cdn/stream-single.ts"],"sourcesContent":["import { Readable } from \"node:stream\";\nimport { minutesToMilliseconds } from \"date-fns\";\nimport { createFactory } from \"hono/factory\";\nimport { stream } from \"hono/streaming\";\nimport { describeRoute } from \"hono-openapi\";\nimport constants from \"../../../../constants/constants.js\";\nimport { controllerSchemas } from \"../../../../schemas/cdn.js\";\nimport { cdnServices } from \"../../../../services/index.js\";\nimport { LucidAPIError } from \"../../../../utils/errors/index.js\";\nimport { getTenantConfig } from \"../../../../utils/helpers/index.js\";\nimport {\n\tgetMediaKeyTenantKey,\n\tnormalizeMediaKey,\n} from \"../../../../utils/media/index.js\";\nimport serviceWrapper from \"../../../../utils/services/service-wrapper.js\";\nimport authorizePrivateMedia from \"../../middleware/authorize-private-media.js\";\nimport rateLimiter from \"../../middleware/rate-limiter.js\";\nimport validate from \"../../middleware/validate.js\";\nimport openAPI from \"../../openapi/index.js\";\nimport createServiceContext from \"../../utils/create-service-context.js\";\nimport {\n\tapplyRangeHeaders,\n\tapplyStreamingHeaders,\n\tparseRangeHeader,\n} from \"../../utils/streaming.js\";\n\nconst factory = createFactory();\n\n/**\n * Steam a piece of media based on the given key.\n */\nconst streamSingleController = factory.createHandlers(\n\tdescribeRoute({\n\t\tdescription:\n\t\t\t\"Streams a piece of media based on the given key. If its an image, you can resize and format it on request. These will count towards the processed image usage that is unique to each image. This limit is configurable on a per project bases. Once it has been hit, instead of returning the processed image, it will return the original image. This is to prevent abuse of the endpoint.\",\n\t\ttags: [\"cdn\"],\n\t\tsummary: \"Stream Media\",\n\t\tparameters: openAPI.parameters({\n\t\t\tparams: controllerSchemas.streamSingle.params,\n\t\t\tquery: controllerSchemas.streamSingle.query.string,\n\t\t}),\n\t\tresponses: {\n\t\t\t200: {\n\t\t\t\tdescription: \"Successfully streamed media content\",\n\t\t\t\tcontent: {\n\t\t\t\t\t\"*/*\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tformat: \"binary\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"Content-Length\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"integer\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"Content-Disposition\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"Cache-Control\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"Accept-Ranges\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t206: {\n\t\t\t\tdescription: \"Partial content - range request response\",\n\t\t\t\tcontent: {\n\t\t\t\t\t\"*/*\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tformat: \"binary\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"Content-Length\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"integer\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"Content-Range\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"Accept-Ranges\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t404: {\n\t\t\t\tdescription: \"Media not found - returns an error image\",\n\t\t\t\tcontent: {\n\t\t\t\t\t\"image/*\": {\n\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\tformat: \"binary\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t416: {\n\t\t\t\tdescription: \"Range Not Satisfiable\",\n\t\t\t\tcontent: {\n\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\tschema: openAPI.defaultErrorResponse,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdefault: openAPI.defaultErrorResponse,\n\t\t},\n\t}),\n\trateLimiter({\n\t\tmode: \"ip\",\n\t\tscope: constants.rateLimit.scopes.stream.scopeKey,\n\t\tlimit: constants.rateLimit.scopes.stream.limit,\n\t\twindowMs: minutesToMilliseconds(1),\n\t}),\n\tvalidate(\"param\", controllerSchemas.streamSingle.params),\n\tauthorizePrivateMedia,\n\tvalidate(\"query\", controllerSchemas.streamSingle.query.string),\n\tasync (c) => {\n\t\tconst params = c.req.valid(\"param\");\n\t\tconst query = c.req.valid(\"query\");\n\n\t\tconst normalizedKey = normalizeMediaKey(params.key);\n\t\tconst tenantKey = getMediaKeyTenantKey(normalizedKey);\n\n\t\tconst tenant = tenantKey\n\t\t\t? getTenantConfig(c.get(\"config\"), tenantKey)\n\t\t\t: undefined;\n\n\t\tc.set(\"tenant\", tenant ? { key: tenant.key } : null);\n\n\t\tconst context = createServiceContext(c);\n\n\t\tconst range = parseRangeHeader(c.req.header(\"range\"));\n\t\tconst ifNoneMatch = c.req.header(\"if-none-match\");\n\t\tconst cacheControl = \"public, max-age=0, must-revalidate\";\n\t\tlet etag: string | undefined;\n\n\t\tconst response = await serviceWrapper(cdnServices.streamMedia, {\n\t\t\ttransaction: false,\n\t\t})(context, {\n\t\t\tkey: normalizedKey,\n\t\t\tquery: query,\n\t\t\taccept: c.req.header(\"accept\"),\n\t\t\tifNoneMatch: !range ? ifNoneMatch : undefined,\n\t\t\trange,\n\t\t});\n\n\t\tif (response.error) {\n\t\t\tconst streamFallbackMedia = await serviceWrapper(\n\t\t\t\tcdnServices.streamFallbackMedia,\n\t\t\t\t{\n\t\t\t\t\ttransaction: false,\n\t\t\t\t},\n\t\t\t)(context, {\n\t\t\t\tfallback: query?.fallback ? Boolean(query?.fallback) : undefined,\n\t\t\t\tsecFetchDest: c.req.header(\"sec-fetch-dest\"),\n\t\t\t\terror: response.error,\n\t\t\t});\n\t\t\tif (streamFallbackMedia.error)\n\t\t\t\tthrow new LucidAPIError(streamFallbackMedia.error);\n\n\t\t\treturn c.redirect(streamFallbackMedia.data.redirectUrl, 302);\n\t\t}\n\n\t\tetag = response.data.etag ?? undefined;\n\n\t\tif (response.data.notModified) {\n\t\t\tapplyRangeHeaders(c, { cacheControl });\n\t\t\tapplyStreamingHeaders(c, {\n\t\t\t\tkey: response.data.key,\n\t\t\t\tcontentType: response.data.contentType,\n\t\t\t\tetag,\n\t\t\t});\n\t\t\tc.status(304);\n\t\t\treturn c.body(null);\n\t\t}\n\n\t\tapplyRangeHeaders(c, {\n\t\t\tisPartial: response.data.isPartialContent,\n\t\t\trange: response.data.range,\n\t\t\ttotalSize: response.data.totalSize,\n\t\t\tcacheControl,\n\t\t});\n\t\tapplyStreamingHeaders(c, {\n\t\t\tkey: response.data.key,\n\t\t\tcontentLength: response.data.contentLength,\n\t\t\tcontentType: response.data.contentType,\n\t\t\tetag,\n\t\t});\n\n\t\treturn stream(c, async (stream) => {\n\t\t\tif (response.data.body instanceof ReadableStream) {\n\t\t\t\tawait stream.pipe(response.data.body);\n\t\t\t} else if (response.data.body instanceof Uint8Array) {\n\t\t\t\tawait stream.write(response.data.body);\n\t\t\t} else if (response.data.body instanceof Readable) {\n\t\t\t\tfor await (const chunk of response.data.body) {\n\t\t\t\t\tawait stream.write(chunk);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n);\n\nexport default streamSingleController;\n"],"mappings":"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"}