Files

422 lines
16 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleRoutes = void 0;
const bundler_1 = require("@remotion/bundler");
const studio_shared_1 = require("@remotion/studio-shared");
const fs_1 = __importStar(require("fs"));
const node_fs_1 = require("node:fs");
const node_path_1 = __importStar(require("node:path"));
const node_url_1 = require("node:url");
const client_render_queue_1 = require("./client-render-queue");
const get_file_source_1 = require("./helpers/get-file-source");
const get_installed_installable_packages_1 = require("./helpers/get-installed-installable-packages");
const open_in_editor_1 = require("./helpers/open-in-editor");
const resolve_output_path_1 = require("./helpers/resolve-output-path");
const api_routes_1 = require("./preview-server/api-routes");
const get_package_manager_1 = require("./preview-server/get-package-manager");
const handler_1 = require("./preview-server/handler");
const parse_body_1 = require("./preview-server/parse-body");
const public_folder_1 = require("./preview-server/public-folder");
const serve_static_1 = require("./preview-server/serve-static");
const editorGuess = (0, open_in_editor_1.guessEditor)();
const static404 = (response) => {
response.writeHead(404);
response.end('The static/ prefix has been changed, this URL is no longer valid.');
return Promise.resolve();
};
const output404 = (response) => {
response.writeHead(404);
response.end('The outputs/ prefix has been changed, this URL is no longer valid.');
return Promise.resolve();
};
const handleRemotionConfig = (response, remotionRoot) => {
var _a;
response.writeHead(200, {
'Content-Type': 'application/json',
});
const body = {
isRemotion: true,
cwd: remotionRoot,
version: (_a = process.env.REMOTION_VERSION) !== null && _a !== void 0 ? _a : null,
};
response.end(JSON.stringify(body));
return Promise.resolve();
};
const handleFallback = async ({ remotionRoot, hash, response, getCurrentInputProps, getEnvVariables, publicDir, getRenderQueue, getRenderDefaults, numberOfAudioTags, audioLatencyHint, gitSource, logLevel, enableCrossSiteIsolation, }) => {
const [edit] = await editorGuess;
const displayName = (0, open_in_editor_1.getDisplayNameForEditor)(edit ? edit.command : null);
response.setHeader('content-type', 'text/html');
if (enableCrossSiteIsolation) {
response.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
response.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
}
const packageManager = (0, get_package_manager_1.getPackageManager)({
remotionRoot,
packageManager: undefined,
dirUp: 0,
logLevel,
});
(0, public_folder_1.fetchFolder)({ publicDir, staticHash: hash });
const installedDependencies = (0, get_installed_installable_packages_1.getInstalledInstallablePackages)(remotionRoot);
response.end(bundler_1.BundlerInternals.indexHtml({
staticHash: hash,
publicPath: '/',
editorName: displayName,
envVariables: getEnvVariables(),
inputProps: getCurrentInputProps(),
remotionRoot,
studioServerCommand: packageManager === 'unknown' ? null : packageManager.startCommand,
renderQueue: getRenderQueue(),
completedClientRenders: (0, client_render_queue_1.getCompletedClientRenders)(),
numberOfAudioTags,
publicFiles: (0, public_folder_1.getFiles)(),
includeFavicon: true,
title: 'Remotion Studio',
renderDefaults: getRenderDefaults(),
publicFolderExists: (0, node_fs_1.existsSync)(publicDir) ? publicDir : null,
gitSource,
projectName: (0, studio_shared_1.getProjectName)({
basename: node_path_1.default.basename,
gitSource,
resolvedRemotionRoot: remotionRoot,
}),
installedDependencies,
packageManager: packageManager === 'unknown' ? 'unknown' : packageManager.manager,
logLevel,
mode: 'dev',
audioLatencyHint: audioLatencyHint !== null && audioLatencyHint !== void 0 ? audioLatencyHint : 'interactive',
}));
};
const handleFileSource = async ({ method, remotionRoot, search, response, }) => {
if (method === 'OPTIONS') {
response.writeHead(200);
response.end();
return Promise.resolve();
}
if (!search.startsWith('?')) {
throw new Error('query must start with ?');
}
const query = new node_url_1.URLSearchParams(search);
const f = query.get('f');
if (typeof f !== 'string') {
throw new Error('must pass `f` parameter');
}
const data = await (0, get_file_source_1.getFileSource)(remotionRoot, decodeURIComponent(f));
response.writeHead(200);
response.write(data);
response.end();
return Promise.resolve();
};
const handleOpenInEditor = async (remotionRoot, req, res, logLevel) => {
if (req.method === 'OPTIONS') {
res.statusCode = 200;
res.end();
return;
}
try {
const body = (await (0, parse_body_1.parseRequestBody)(req));
if (!('stack' in body)) {
throw new TypeError('Need to pass stack');
}
const stack = body.stack;
const guess = await editorGuess;
const didOpen = await (0, open_in_editor_1.launchEditor)({
colNumber: stack.originalColumnNumber,
editor: guess[0],
fileName: node_path_1.default.resolve(remotionRoot, stack.originalFileName),
lineNumber: stack.originalLineNumber,
vsCodeNewWindow: false,
logLevel,
});
res.setHeader('content-type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify({
success: didOpen,
}));
}
catch (_a) {
res.setHeader('content-type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify({
success: false,
}));
}
};
const validateSameOrigin = (req) => {
const { origin, host } = req.headers;
if (origin) {
const originUrl = new URL(origin);
if (originUrl.host !== host) {
throw new Error('Request from different origin not allowed');
}
}
};
const handleAddAsset = ({ req, res, search, publicDir, }) => {
try {
validateSameOrigin(req);
const query = new node_url_1.URLSearchParams(search);
const filePath = query.get('filePath');
if (typeof filePath !== 'string') {
throw new Error('No `filePath` provided');
}
const absolutePath = node_path_1.default.join(publicDir, filePath);
const relativeToPublicDir = node_path_1.default.relative(publicDir, absolutePath);
if (relativeToPublicDir.startsWith('..')) {
throw new Error(`Not allowed to write to ${relativeToPublicDir}`);
}
fs_1.default.mkdirSync(node_path_1.default.dirname(absolutePath), { recursive: true });
const writeStream = (0, fs_1.createWriteStream)(absolutePath);
writeStream.on('close', () => {
res.end(JSON.stringify({ success: true }));
});
req.pipe(writeStream);
}
catch (err) {
res.statusCode = 500;
res.end(JSON.stringify({ error: err.message }));
}
return Promise.resolve();
};
const handleUploadOutput = ({ req, res, search, remotionRoot, }) => {
try {
validateSameOrigin(req);
const query = new node_url_1.URLSearchParams(search);
const filePath = query.get('filePath');
if (typeof filePath !== 'string') {
throw new Error('No `filePath` provided');
}
const absolutePath = (0, resolve_output_path_1.resolveOutputPath)(remotionRoot, filePath);
fs_1.default.mkdirSync(node_path_1.default.dirname(absolutePath), { recursive: true });
const writeStream = (0, fs_1.createWriteStream)(absolutePath);
writeStream.on('close', () => {
res.end(JSON.stringify({ success: true }));
});
writeStream.on('error', (err) => {
res.statusCode = 500;
res.end(JSON.stringify({ error: err.message }));
});
req.on('error', (err) => {
writeStream.destroy();
res.statusCode = 500;
res.end(JSON.stringify({ error: err.message }));
});
req.pipe(writeStream);
}
catch (err) {
res.statusCode = 500;
res.end(JSON.stringify({ error: err.message }));
}
return Promise.resolve();
};
const handleRegisterClientRender = async ({ req, res, remotionRoot, }) => {
try {
validateSameOrigin(req);
const body = (await (0, parse_body_1.parseRequestBody)(req));
(0, client_render_queue_1.addCompletedClientRender)({ render: body, remotionRoot });
res.setHeader('content-type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify({ success: true }));
}
catch (err) {
res.statusCode = 500;
res.end(JSON.stringify({ error: err.message }));
}
};
const handleUnregisterClientRender = async ({ req, res, }) => {
try {
validateSameOrigin(req);
const body = (await (0, parse_body_1.parseRequestBody)(req));
(0, client_render_queue_1.removeCompletedClientRender)(body.id);
res.setHeader('content-type', 'application/json');
res.writeHead(200);
res.end(JSON.stringify({ success: true }));
}
catch (err) {
res.statusCode = 500;
res.end(JSON.stringify({ error: err.message }));
}
};
const handleFavicon = (_, response) => {
const filePath = node_path_1.default.join(__dirname, '..', 'web', 'favicon.png');
const stat = (0, node_fs_1.statSync)(filePath);
response.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': stat.size,
});
const readStream = (0, node_fs_1.createReadStream)(filePath);
readStream.pipe(response);
return Promise.resolve();
};
const handleBeep = (_, response) => {
const filePath = node_path_1.default.join(__dirname, '..', 'web', 'beep.wav');
const stat = (0, node_fs_1.statSync)(filePath);
response.writeHead(200, {
'Content-Type': 'audio/wav',
'Content-Length': stat.size,
});
const readStream = (0, node_fs_1.createReadStream)(filePath);
readStream.pipe(response);
return Promise.resolve();
};
const handleWasm = (_, response) => {
const filePath = node_path_1.default.resolve(require.resolve('source-map'), '..', 'lib', 'mappings.wasm');
const stat = (0, node_fs_1.statSync)(filePath);
response.writeHead(200, {
'Content-Type': 'application/wasm',
'Content-Length': stat.size,
});
const readStream = (0, node_fs_1.createReadStream)(filePath);
readStream.pipe(response);
return Promise.resolve();
};
const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPrefix, request, response, liveEventsServer, getCurrentInputProps, getEnvVariables, remotionRoot, entryPoint, publicDir, logLevel, getRenderQueue, getRenderDefaults, numberOfAudioTags, queueMethods: methods, gitSource, binariesDirectory, audioLatencyHint, enableCrossSiteIsolation, }) => {
const url = new URL(request.url, 'http://localhost');
if (url.pathname === '/api/file-source') {
return handleFileSource({
remotionRoot,
search: url.search,
method: request.method,
response,
});
}
if (url.pathname === '/api/open-in-editor') {
return handleOpenInEditor(remotionRoot, request, response, logLevel);
}
if (url.pathname === `${staticHash}/api/add-asset`) {
return handleAddAsset({
req: request,
res: response,
search: url.search,
publicDir,
});
}
if (url.pathname === '/api/upload-output') {
return handleUploadOutput({
req: request,
res: response,
search: url.search,
remotionRoot,
});
}
if (url.pathname === '/api/register-client-render') {
return handleRegisterClientRender({
req: request,
res: response,
remotionRoot,
});
}
if (url.pathname === '/api/unregister-client-render') {
return handleUnregisterClientRender({
req: request,
res: response,
});
}
for (const [key, value] of Object.entries(api_routes_1.allApiRoutes)) {
if (url.pathname === key) {
return (0, handler_1.handleRequest)({
remotionRoot,
entryPoint,
handler: value,
request,
response,
logLevel,
methods,
binariesDirectory,
publicDir,
});
}
}
if (url.pathname === '/favicon.ico') {
return handleFavicon(request, response);
}
if (url.pathname === '/beep.wav') {
return handleBeep(request, response);
}
if (url.pathname === studio_shared_1.SOURCE_MAP_ENDPOINT) {
return handleWasm(request, response);
}
if (url.pathname === '/__remotion_config') {
return handleRemotionConfig(response, remotionRoot);
}
if (url.pathname === '/events') {
return liveEventsServer.router(request, response);
}
if (url.pathname.startsWith(staticHash)) {
const filename = new URL(request.url, 'http://localhost').pathname.replace(new RegExp(`^${staticHash}`), '');
const filePath = (0, node_path_1.join)(publicDir, decodeURIComponent(filename));
return (0, serve_static_1.serveStatic)({
root: publicDir,
path: filePath,
req: request,
res: response,
allowOutsidePublicFolder: false,
});
}
if (url.pathname.startsWith(staticHashPrefix)) {
return static404(response);
}
if (url.pathname.startsWith(outputHash)) {
const filename = new URL(request.url, 'http://localhost').pathname.replace(new RegExp(`^${outputHash}`), '');
const filePath = (0, node_path_1.join)(remotionRoot, decodeURIComponent(filename));
return (0, serve_static_1.serveStatic)({
root: remotionRoot,
path: filePath,
req: request,
res: response,
allowOutsidePublicFolder: false,
});
}
if (url.pathname.startsWith(outputHashPrefix)) {
return output404(response);
}
return handleFallback({
remotionRoot,
hash: staticHash,
response,
getCurrentInputProps,
getEnvVariables,
publicDir,
getRenderQueue,
getRenderDefaults,
numberOfAudioTags,
gitSource,
logLevel,
audioLatencyHint,
enableCrossSiteIsolation,
});
};
exports.handleRoutes = handleRoutes;