@remotion/studio-server 4.0.503 → 4.0.505

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,7 @@ export type ProcessAndCommand = {
11
11
  process: string;
12
12
  command: Editor;
13
13
  };
14
+ export declare const findMacOsEditorsFromProcessOutput: (output: string) => ProcessAndCommand[];
14
15
  export declare function guessEditor(): Promise<ProcessAndCommand[]>;
15
16
  export declare function launchEditor({ colNumber, editor, fileName, lineNumber, vsCodeNewWindow, logLevel }: {
16
17
  fileName: string;
@@ -37,7 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.getDisplayNameForEditor = void 0;
40
+ exports.findMacOsEditorsFromProcessOutput = exports.getDisplayNameForEditor = void 0;
41
41
  exports.guessEditor = guessEditor;
42
42
  exports.launchEditor = launchEditor;
43
43
  const node_child_process_1 = __importStar(require("node:child_process"));
@@ -167,6 +167,7 @@ const COMMON_EDITORS_OSX = {
167
167
  '/Applications/Sublime Text.app/Contents/MacOS/Sublime Text': '/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl',
168
168
  '/Applications/Sublime Text Dev.app/Contents/MacOS/Sublime Text': '/Applications/Sublime Text Dev.app/Contents/SharedSupport/bin/subl',
169
169
  '/Applications/Sublime Text 2.app/Contents/MacOS/Sublime Text 2': '/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl',
170
+ '/Applications/Visual Studio Code.app/Contents/MacOS/Code': 'code',
170
171
  '/Applications/Visual Studio Code.app/Contents/MacOS/Electron': 'code',
171
172
  '/Applications/Visual Studio Code - Insiders.app/Contents/MacOS/Electron': 'code-insiders',
172
173
  '/Applications/VSCodium.app/Contents/MacOS/Electron': 'vscodium',
@@ -323,6 +324,21 @@ function getArgumentsForLineNumber(editor, fileName, lineNumber, colNumber) {
323
324
  return [fileName];
324
325
  }
325
326
  }
327
+ const findMacOsEditorsFromProcessOutput = (output) => {
328
+ const availableEditors = [];
329
+ const processNames = Object.keys(COMMON_EDITORS_OSX);
330
+ for (let i = 0; i < processNames.length; i++) {
331
+ const processName = processNames[i];
332
+ if (output.includes(processName)) {
333
+ availableEditors.push({
334
+ process: processName,
335
+ command: COMMON_EDITORS_OSX[processName],
336
+ });
337
+ }
338
+ }
339
+ return availableEditors;
340
+ };
341
+ exports.findMacOsEditorsFromProcessOutput = findMacOsEditorsFromProcessOutput;
326
342
  async function guessEditor() {
327
343
  // We can find out which editor is currently running by:
328
344
  // `ps x` on macOS and Linux
@@ -331,17 +347,7 @@ async function guessEditor() {
331
347
  try {
332
348
  if (process.platform === 'darwin') {
333
349
  const output = (await execProm('ps x')).stdout.toString();
334
- const processNames = Object.keys(COMMON_EDITORS_OSX);
335
- for (let i = 0; i < processNames.length; i++) {
336
- const processName = processNames[i];
337
- if (output.indexOf(processName) !== -1) {
338
- availableEditors.push({
339
- process: processName,
340
- command: COMMON_EDITORS_OSX[processName],
341
- });
342
- }
343
- }
344
- return availableEditors;
350
+ return (0, exports.findMacOsEditorsFromProcessOutput)(output);
345
351
  }
346
352
  if (process.platform === 'win32') {
347
353
  // Some processes need elevated rights to get its executable path.
@@ -11,23 +11,27 @@ export type ElementInstallTarget = {
11
11
  studioUrl: string;
12
12
  updatedAt: number;
13
13
  };
14
+ export type StudioProtocolTargetPurpose = 'install-element' | 'set-license-key';
14
15
  type StudioProtocolTarget = {
15
16
  readonly id: string;
16
17
  readonly origin: string;
18
+ readonly purpose: StudioProtocolTargetPurpose;
17
19
  readonly target: ElementInstallTarget;
18
20
  readonly expiresAt: number;
19
21
  };
20
22
  export declare const updateElementInstallTarget: (newTarget: Omit<ElementInstallTarget, "updatedAt">) => void;
21
23
  export declare const getElementInstallTargetByClientId: (clientId: string) => ElementInstallTarget | null;
22
24
  export declare const getElementInstallTarget: (requestId: string | null) => ElementInstallTarget | null;
23
- export declare const issueStudioProtocolTarget: ({ now, origin, target, }: {
25
+ export declare const issueStudioProtocolTarget: ({ now, origin, purpose, target, }: {
24
26
  readonly now: number;
25
27
  readonly origin: string;
28
+ readonly purpose: StudioProtocolTargetPurpose;
26
29
  readonly target: ElementInstallTarget;
27
30
  }) => StudioProtocolTarget;
28
- export declare const consumeStudioProtocolTarget: ({ now, origin, targetId, }: {
31
+ export declare const consumeStudioProtocolTarget: ({ now, origin, purpose, targetId, }: {
29
32
  readonly now: number;
30
33
  readonly origin: string;
34
+ readonly purpose: StudioProtocolTargetPurpose;
31
35
  readonly targetId: string;
32
36
  }) => ElementInstallTarget | null;
33
37
  export declare const clearElementInstallStateForTests: () => void;
@@ -45,7 +45,7 @@ const getElementInstallTarget = (requestId) => {
45
45
  return bestTarget;
46
46
  };
47
47
  exports.getElementInstallTarget = getElementInstallTarget;
48
- const issueStudioProtocolTarget = ({ now, origin, target, }) => {
48
+ const issueStudioProtocolTarget = ({ now, origin, purpose, target, }) => {
49
49
  for (const [id, existing] of studioProtocolTargets) {
50
50
  if (existing.expiresAt <= now) {
51
51
  studioProtocolTargets.delete(id);
@@ -54,6 +54,7 @@ const issueStudioProtocolTarget = ({ now, origin, target, }) => {
54
54
  const issued = {
55
55
  id: (0, node_crypto_1.randomUUID)(),
56
56
  origin,
57
+ purpose,
57
58
  target: { ...target },
58
59
  expiresAt: now + exports.STUDIO_PROTOCOL_TARGET_MAX_AGE,
59
60
  };
@@ -61,19 +62,25 @@ const issueStudioProtocolTarget = ({ now, origin, target, }) => {
61
62
  return issued;
62
63
  };
63
64
  exports.issueStudioProtocolTarget = issueStudioProtocolTarget;
64
- const consumeStudioProtocolTarget = ({ now, origin, targetId, }) => {
65
+ const consumeStudioProtocolTarget = ({ now, origin, purpose, targetId, }) => {
65
66
  const issued = studioProtocolTargets.get(targetId);
66
67
  studioProtocolTargets.delete(targetId);
67
68
  if (issued === undefined ||
68
69
  issued.expiresAt <= now ||
69
- issued.origin !== origin) {
70
+ issued.origin !== origin ||
71
+ issued.purpose !== purpose) {
70
72
  return null;
71
73
  }
72
74
  const current = (0, exports.getElementInstallTargetByClientId)(issued.target.clientId);
73
75
  if (current === null ||
74
76
  now - current.updatedAt >= exports.ELEMENT_INSTALL_TARGET_MAX_AGE ||
75
- !current.canInstall ||
76
- current.readOnly ||
77
+ current.readOnly) {
78
+ return null;
79
+ }
80
+ if (purpose === 'set-license-key') {
81
+ return issued.target;
82
+ }
83
+ if (!current.canInstall ||
77
84
  current.compositionFile !== issued.target.compositionFile ||
78
85
  current.compositionId !== issued.target.compositionId) {
79
86
  return null;
@@ -3,7 +3,8 @@ export declare const normalizeElementSourceForComparison: (source: string) => st
3
3
  export declare const getElementSourceHash: (source: string) => string;
4
4
  export declare const validateElementInstallPosition: (position: import("@remotion/studio-shared").InsertableCompositionElementPosition | null) => void;
5
5
  export declare const validateElementForInstallation: (element: {
6
- dependencies: string[];
6
+ dependencies: import("@remotion/studio-protocol").ElementDependency[];
7
+ durationInFrames?: number | undefined;
7
8
  slug: string;
8
9
  displayName: string;
9
10
  sourceCode: string;
@@ -22,6 +22,7 @@ const hasExpectedFileState = ({ expected, actual, }) => {
22
22
  return actual.sourceHash === expected.sourceHash;
23
23
  };
24
24
  const insertElementHandler = ({ input: { compositionFile, compositionId, element, expectedFileState, from, position, overwriteExisting, }, remotionRoot, logLevel, }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
25
+ var _a;
25
26
  try {
26
27
  (0, element_install_plan_1.validateElementInstallPosition)(position);
27
28
  if (from !== null &&
@@ -87,6 +88,7 @@ const insertElementHandler = ({ input: { compositionFile, compositionId, element
87
88
  prettierConfigOverride: null,
88
89
  wrapInSequence: {
89
90
  dimensions: element.dimensions,
91
+ durationInFrames: (_a = element.durationInFrames) !== null && _a !== void 0 ? _a : null,
90
92
  from,
91
93
  name: element.displayName,
92
94
  position,
@@ -1,4 +1,5 @@
1
+ import type { ElementDependency } from '@remotion/studio-protocol';
1
2
  import { type InstallPackageRequest, type InstallPackageResponse } from '@remotion/studio-shared';
2
3
  import type { ApiHandler } from '../api-types';
3
- export declare const getPackageInstallSpec: (packageName: string) => string;
4
+ export declare const getPackageInstallSpec: (dependency: string | ElementDependency) => string;
4
5
  export declare const handleInstallPackage: ApiHandler<InstallPackageRequest, InstallPackageResponse>;
@@ -12,21 +12,26 @@ const getExtraPackageVersion = (packageName) => {
12
12
  const pkg = studio_shared_1.extraPackages.find((p) => p.name === packageName);
13
13
  return pkg ? pkg.version : null;
14
14
  };
15
- const getPackageInstallSpec = (packageName) => {
16
- const extraVersion = getExtraPackageVersion(packageName);
17
- if (extraVersion) {
18
- return `${packageName}@${extraVersion}`;
19
- }
20
- if (packageName === 'remotion' || packageName.startsWith('@remotion/')) {
21
- return `${packageName}@${version_1.VERSION}`;
22
- }
23
- return packageName;
15
+ const getPackageInstallSpec = (dependency) => {
16
+ const { name, version } = typeof dependency === 'string'
17
+ ? { name: dependency, version: null }
18
+ : dependency;
19
+ const extraVersion = getExtraPackageVersion(name);
20
+ if (extraVersion)
21
+ return `${name}@${extraVersion}`;
22
+ if (name === 'remotion' || name.startsWith('@remotion/'))
23
+ return `${name}@${version_1.VERSION}`;
24
+ return version === null ? name : `${name}@${version}`;
24
25
  };
25
26
  exports.getPackageInstallSpec = getPackageInstallSpec;
26
- const handleInstallPackage = async ({ logLevel, remotionRoot, input: { packageNames } }) => {
27
- for (const packageName of packageNames) {
28
- if (!(0, studio_shared_1.isValidPackageName)(packageName)) {
29
- return Promise.reject(new Error(`Package name ${JSON.stringify(packageName)} is invalid.`));
27
+ const handleInstallPackage = async ({ logLevel, remotionRoot, input: { dependencies } }) => {
28
+ for (const dependency of dependencies) {
29
+ if (!(0, studio_shared_1.isValidPackageName)(dependency.name)) {
30
+ return Promise.reject(new Error(`Package name ${JSON.stringify(dependency.name)} is invalid.`));
31
+ }
32
+ if (dependency.version !== null &&
33
+ !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(dependency.version)) {
34
+ return Promise.reject(new Error(`Package version ${JSON.stringify(dependency.version)} is invalid.`));
30
35
  }
31
36
  }
32
37
  const manager = (0, get_package_manager_1.getPackageManager)({
@@ -36,13 +41,9 @@ const handleInstallPackage = async ({ logLevel, remotionRoot, input: { packageNa
36
41
  logLevel,
37
42
  });
38
43
  if (manager === 'unknown') {
39
- throw new Error(`No lockfile was found in your project (one of ${get_package_manager_1.lockFilePaths
40
- .map((p) => p.path)
41
- .join(', ')}). Install dependencies using your favorite manager!`);
44
+ throw new Error(`No lockfile was found in your project (one of ${get_package_manager_1.lockFilePaths.map((p) => p.path).join(', ')}). Install dependencies using your favorite manager!`);
42
45
  }
43
- // Remotion packages must match the Studio version and catalogued extra
44
- // packages use their supported version. Other packages resolve normally.
45
- const packagesWithVersions = packageNames.map(exports.getPackageInstallSpec);
46
+ const packagesWithVersions = dependencies.map(exports.getPackageInstallSpec);
46
47
  const command = (0, install_command_1.getInstallCommand)({
47
48
  manager: manager.manager,
48
49
  packages: packagesWithVersions,
@@ -54,34 +55,22 @@ const handleInstallPackage = async ({ logLevel, remotionRoot, input: { packageNa
54
55
  try {
55
56
  await new Promise((resolve, reject) => {
56
57
  const cmd = (0, node_child_process_1.spawn)(manager.manager, command, (0, package_manager_spawn_options_1.getPackageManagerSpawnOptions)());
57
- cmd.on('error', (err) => {
58
- reject(err);
59
- });
60
- cmd.stdout.on('data', (d) => {
61
- const splitted = d.toString().trim().split('\n');
62
- splitted.forEach((line) => {
63
- renderer_1.RenderInternals.Log.info({ indent: true, logLevel }, line);
64
- });
65
- });
66
- cmd.stdout.on('end', () => {
67
- resolve();
68
- });
69
- cmd.on('close', (code, signal) => {
70
- if (code === 0) {
71
- resolve();
72
- }
73
- else {
74
- reject(new Error(`Command exited with code ${code} and signal ${signal}`));
75
- }
76
- });
58
+ cmd.on('error', reject);
59
+ cmd.stdout.on('data', (d) => d
60
+ .toString()
61
+ .trim()
62
+ .split('\n')
63
+ .forEach((line) => renderer_1.RenderInternals.Log.info({ indent: true, logLevel }, line)));
64
+ cmd.stdout.on('end', resolve);
65
+ cmd.on('close', (code, signal) => code === 0
66
+ ? resolve()
67
+ : reject(new Error(`Command exited with code ${code} and signal ${signal}`)));
77
68
  });
78
- const timeEnd = Date.now();
79
- renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray('╰─ '), `Done in ${timeEnd - time}ms`);
80
- return Promise.resolve({});
69
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray('╰─ '), `Done in ${Date.now() - time}ms`);
70
+ return {};
81
71
  }
82
72
  catch (err) {
83
- const timeEnd = Date.now();
84
- renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray('╰─ '), renderer_1.RenderInternals.chalk.red(`Errored in ${timeEnd - time}ms`));
73
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, renderer_1.RenderInternals.chalk.gray('╰─ '), renderer_1.RenderInternals.chalk.red(`Errored in ${Date.now() - time}ms`));
85
74
  return Promise.reject(err);
86
75
  }
87
76
  };
@@ -4,4 +4,8 @@ export declare const updatePublicLicenseInConfig: ({ configContents, publicLicen
4
4
  configContents: string;
5
5
  publicLicenseKey: string;
6
6
  }) => string;
7
+ export declare const setPublicLicenseKeyInConfigFile: ({ configFile, publicLicenseKey, }: {
8
+ readonly configFile: string;
9
+ readonly publicLicenseKey: string;
10
+ }) => void;
7
11
  export declare const updatePublicLicenseHandler: ApiHandler<UpdatePublicLicenseRequest, UpdatePublicLicenseResponse>;
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.updatePublicLicenseHandler = exports.updatePublicLicenseInConfig = void 0;
36
+ exports.updatePublicLicenseHandler = exports.setPublicLicenseKeyInConfigFile = exports.updatePublicLicenseInConfig = void 0;
37
37
  const node_fs_1 = require("node:fs");
38
38
  const recast = __importStar(require("recast"));
39
39
  const parse_ast_1 = require("../../codemods/parse-ast");
@@ -70,6 +70,11 @@ const updatePublicLicenseInConfig = ({ configContents, publicLicenseKey, }) => {
70
70
  return `${configWithoutExistingCalls}${separator}Config.setPublicLicenseKey('${escapedPublicLicenseKey}');\n`;
71
71
  };
72
72
  exports.updatePublicLicenseInConfig = updatePublicLicenseInConfig;
73
+ const setPublicLicenseKeyInConfigFile = ({ configFile, publicLicenseKey, }) => {
74
+ const configContents = (0, node_fs_1.readFileSync)(configFile, 'utf8');
75
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(configFile, (0, exports.updatePublicLicenseInConfig)({ configContents, publicLicenseKey }), undefined);
76
+ };
77
+ exports.setPublicLicenseKeyInConfigFile = setPublicLicenseKeyInConfigFile;
73
78
  const updatePublicLicenseHandler = ({ input, configFile }) => {
74
79
  if (configFile === null || configFile === undefined) {
75
80
  return Promise.resolve({
@@ -83,11 +88,10 @@ const updatePublicLicenseHandler = ({ input, configFile }) => {
83
88
  reason: 'The public license key must not be empty.',
84
89
  });
85
90
  }
86
- const configContents = (0, node_fs_1.readFileSync)(configFile, 'utf8');
87
- (0, file_watcher_1.writeFileAndNotifyFileWatchers)(configFile, (0, exports.updatePublicLicenseInConfig)({
88
- configContents,
91
+ (0, exports.setPublicLicenseKeyInConfigFile)({
92
+ configFile,
89
93
  publicLicenseKey: input.publicLicenseKey,
90
- }), undefined);
94
+ });
91
95
  return Promise.resolve({ success: true });
92
96
  };
93
97
  exports.updatePublicLicenseHandler = updatePublicLicenseHandler;
@@ -10,7 +10,7 @@ const version_1 = require("remotion/version");
10
10
  const element_install_state_1 = require("../element-install-state");
11
11
  const origin_policy_1 = require("./origin-policy");
12
12
  const protocol_response_1 = require("./protocol-response");
13
- const ELEMENT_INSTALL_FOCUS_MAX_AGE = 5 * 60 * 1000;
13
+ const STUDIO_PROTOCOL_FOCUS_MAX_AGE = 5 * 60 * 1000;
14
14
  exports.ELEMENT_INSTALL_TARGET_RESPONSE_WAIT = 250;
15
15
  const requestInstallTarget = ({ liveEventsServer, }) => {
16
16
  const requestId = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
@@ -20,28 +20,30 @@ const requestInstallTarget = ({ liveEventsServer, }) => {
20
20
  });
21
21
  return requestId;
22
22
  };
23
- const getLiveInstallableTarget = (requestId) => {
23
+ const getLiveStudioTarget = (requestId) => {
24
24
  const target = (0, element_install_state_1.getElementInstallTarget)(requestId);
25
25
  const now = Date.now();
26
26
  if (target === null ||
27
27
  now - target.updatedAt >= element_install_state_1.ELEMENT_INSTALL_TARGET_MAX_AGE ||
28
28
  target.lastFocusedAt === null ||
29
- now - target.lastFocusedAt >= ELEMENT_INSTALL_FOCUS_MAX_AGE ||
30
- !target.canInstall ||
31
- target.readOnly ||
32
- target.compositionFile === null ||
33
- target.compositionId === null) {
29
+ now - target.lastFocusedAt >= STUDIO_PROTOCOL_FOCUS_MAX_AGE ||
30
+ target.readOnly) {
34
31
  return null;
35
32
  }
36
33
  return target;
37
34
  };
35
+ const isInstallableTarget = (target) => target !== null &&
36
+ target.canInstall &&
37
+ target.compositionFile !== null &&
38
+ target.compositionId !== null &&
39
+ target.lastFocusedAt !== null;
38
40
  const getProject = ({ gitSource, remotionRoot, }) => (0, studio_shared_1.getProjectName)({
39
41
  basename: node_path_1.default.basename,
40
42
  gitSource,
41
43
  resolvedRemotionRoot: remotionRoot,
42
44
  });
43
45
  const handleStudioProtocolDiscovery = ({ gitSource, liveEventsServer, remotionRoot, request, response, }) => {
44
- (0, origin_policy_1.setStudioProtocolCorsHeaders)({ request, response });
46
+ (0, origin_policy_1.setStudioProtocolCorsHeaders)({ licenseKey: false, request, response });
45
47
  const requestOrigin = (0, origin_policy_1.getAllowedStudioProtocolOrigin)(request.headers.origin);
46
48
  if (requestOrigin === null) {
47
49
  (0, protocol_response_1.writeStudioProtocolError)({
@@ -65,12 +67,23 @@ const handleStudioProtocolDiscovery = ({ gitSource, liveEventsServer, remotionRo
65
67
  return new Promise((resolve) => {
66
68
  setTimeout(() => {
67
69
  const now = Date.now();
68
- const target = getLiveInstallableTarget(requestId);
69
- const issued = target === null
70
+ const target = getLiveStudioTarget(requestId);
71
+ const installTarget = isInstallableTarget(target) ? target : null;
72
+ const issuedInstallTarget = installTarget === null
70
73
  ? null
71
74
  : (0, element_install_state_1.issueStudioProtocolTarget)({
72
75
  now,
73
76
  origin: requestOrigin,
77
+ purpose: 'install-element',
78
+ target: installTarget,
79
+ });
80
+ const licenseKeyOrigin = (0, origin_policy_1.getAllowedLicenseKeyOrigin)(request.headers.origin);
81
+ const issuedLicenseKeyTarget = target === null || licenseKeyOrigin === null
82
+ ? null
83
+ : (0, element_install_state_1.issueStudioProtocolTarget)({
84
+ now,
85
+ origin: licenseKeyOrigin,
86
+ purpose: 'set-license-key',
74
87
  target,
75
88
  });
76
89
  response.writeHead(200, {
@@ -81,23 +94,34 @@ const handleStudioProtocolDiscovery = ({ gitSource, liveEventsServer, remotionRo
81
94
  protocol: 'remotion-studio-protocol',
82
95
  protocolVersion: 1,
83
96
  studioVersion: version_1.VERSION,
84
- capabilities: {
85
- install: [
86
- {
87
- payloadType: 'remotion-element',
88
- payloadVersions: [1],
89
- },
90
- ],
91
- },
92
97
  projectName: getProject({ gitSource, remotionRoot }),
93
- installTarget: issued === null || target === null
94
- ? null
95
- : {
96
- id: issued.id,
97
- expiresAt: issued.expiresAt,
98
- compositionId: target.compositionId,
99
- lastFocusedAt: target.lastFocusedAt,
98
+ capabilities: [
99
+ {
100
+ type: 'install-element',
101
+ payloadType: 'remotion-element',
102
+ payloadVersions: [1],
103
+ target: issuedInstallTarget === null || installTarget === null
104
+ ? null
105
+ : {
106
+ id: issuedInstallTarget.id,
107
+ expiresAt: issuedInstallTarget.expiresAt,
108
+ compositionId: installTarget.compositionId,
109
+ lastFocusedAt: installTarget.lastFocusedAt,
110
+ },
111
+ },
112
+ {
113
+ type: 'set-license-key',
114
+ target: issuedLicenseKeyTarget === null ||
115
+ target === null ||
116
+ target.lastFocusedAt === null
117
+ ? null
118
+ : {
119
+ id: issuedLicenseKeyTarget.id,
120
+ expiresAt: issuedLicenseKeyTarget.expiresAt,
121
+ lastFocusedAt: target.lastFocusedAt,
122
+ },
100
123
  },
124
+ ],
101
125
  }));
102
126
  resolve();
103
127
  }, exports.ELEMENT_INSTALL_TARGET_RESPONSE_WAIT);
@@ -8,6 +8,7 @@ const parse_body_1 = require("../parse-body");
8
8
  const origin_policy_1 = require("./origin-policy");
9
9
  const protocol_response_1 = require("./protocol-response");
10
10
  const studioProtocolInstallRequestSchema = zod_1.z.object({
11
+ operation: zod_1.z.literal('install-element'),
11
12
  protocol: zod_1.z.literal('remotion-studio-protocol'),
12
13
  protocolVersion: zod_1.z.literal(1),
13
14
  targetId: zod_1.z.string(),
@@ -44,7 +45,7 @@ const deliverElementInstall = ({ element, focusStudioTab, liveEventsServer, orig
44
45
  return delivered;
45
46
  };
46
47
  const handleStudioProtocolInstall = async ({ focusStudioTab, liveEventsServer, request, response, }) => {
47
- (0, origin_policy_1.setStudioProtocolCorsHeaders)({ request, response });
48
+ (0, origin_policy_1.setStudioProtocolCorsHeaders)({ licenseKey: false, request, response });
48
49
  const requestOrigin = (0, origin_policy_1.getAllowedStudioProtocolOrigin)(request.headers.origin);
49
50
  if (requestOrigin === null) {
50
51
  (0, protocol_response_1.writeStudioProtocolError)({
@@ -111,6 +112,7 @@ const handleStudioProtocolInstall = async ({ focusStudioTab, liveEventsServer, r
111
112
  const target = (0, element_install_state_1.consumeStudioProtocolTarget)({
112
113
  now: Date.now(),
113
114
  origin: requestOrigin,
115
+ purpose: 'install-element',
114
116
  targetId: parsedRequest.data.targetId,
115
117
  });
116
118
  if (target === null) {
@@ -0,0 +1,6 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export declare const handleStudioProtocolLicenseKey: ({ configFile, request, response, }: {
3
+ readonly configFile: string | null;
4
+ readonly request: IncomingMessage;
5
+ readonly response: ServerResponse<IncomingMessage>;
6
+ }) => Promise<void>;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleStudioProtocolLicenseKey = void 0;
4
+ const studio_protocol_1 = require("@remotion/studio-protocol");
5
+ const zod_1 = require("zod");
6
+ const element_install_state_1 = require("../element-install-state");
7
+ const parse_body_1 = require("../parse-body");
8
+ const update_public_license_1 = require("../routes/update-public-license");
9
+ const origin_policy_1 = require("./origin-policy");
10
+ const protocol_response_1 = require("./protocol-response");
11
+ const studioProtocolLicenseKeyRequestSchema = zod_1.z.object({
12
+ operation: zod_1.z.literal('set-license-key'),
13
+ protocol: zod_1.z.literal('remotion-studio-protocol'),
14
+ protocolVersion: zod_1.z.literal(1),
15
+ targetId: zod_1.z.string().min(1),
16
+ licenseKey: zod_1.z.string(),
17
+ });
18
+ const MAX_STUDIO_PROTOCOL_LICENSE_KEY_BODY_SIZE = 4096;
19
+ const handleStudioProtocolLicenseKey = async ({ configFile, request, response, }) => {
20
+ (0, origin_policy_1.setStudioProtocolCorsHeaders)({ licenseKey: true, request, response });
21
+ const requestOrigin = (0, origin_policy_1.getAllowedLicenseKeyOrigin)(request.headers.origin);
22
+ if (requestOrigin === null) {
23
+ (0, protocol_response_1.writeStudioProtocolError)({
24
+ code: 'unsupported-origin',
25
+ message: 'Origin not allowed',
26
+ response,
27
+ status: 403,
28
+ });
29
+ return;
30
+ }
31
+ if (request.method !== 'POST') {
32
+ (0, protocol_response_1.writeStudioProtocolError)({
33
+ code: 'method-not-allowed',
34
+ message: 'Use POST to set a public license key.',
35
+ response,
36
+ status: 405,
37
+ });
38
+ return;
39
+ }
40
+ let body;
41
+ try {
42
+ body = await (0, parse_body_1.parseRequestBody)(request, {
43
+ maxBytes: MAX_STUDIO_PROTOCOL_LICENSE_KEY_BODY_SIZE,
44
+ });
45
+ }
46
+ catch (error) {
47
+ if (error instanceof parse_body_1.RequestBodyTooLargeError) {
48
+ (0, protocol_response_1.writeStudioProtocolError)({
49
+ code: 'request-too-large',
50
+ message: 'The request body is too large.',
51
+ response,
52
+ status: 413,
53
+ });
54
+ return;
55
+ }
56
+ (0, protocol_response_1.writeStudioProtocolError)({
57
+ code: 'invalid-request',
58
+ message: 'The request body is not valid JSON.',
59
+ response,
60
+ status: 400,
61
+ });
62
+ return;
63
+ }
64
+ const parsedRequest = studioProtocolLicenseKeyRequestSchema.safeParse(body);
65
+ if (!parsedRequest.success) {
66
+ (0, protocol_response_1.writeStudioProtocolError)({
67
+ code: 'unsupported-protocol',
68
+ message: 'Invalid Remotion Studio Protocol request.',
69
+ response,
70
+ status: 400,
71
+ });
72
+ return;
73
+ }
74
+ if (!studio_protocol_1.StudioProtocolInternals.isValidPublicLicenseKey(parsedRequest.data.licenseKey)) {
75
+ (0, protocol_response_1.writeStudioProtocolError)({
76
+ code: 'invalid-license-key',
77
+ message: 'The license key is not a valid public Remotion license key.',
78
+ response,
79
+ status: 400,
80
+ });
81
+ return;
82
+ }
83
+ const target = (0, element_install_state_1.consumeStudioProtocolTarget)({
84
+ now: Date.now(),
85
+ origin: requestOrigin,
86
+ purpose: 'set-license-key',
87
+ targetId: parsedRequest.data.targetId,
88
+ });
89
+ if (target === null) {
90
+ (0, protocol_response_1.writeStudioProtocolError)({
91
+ code: 'target-expired',
92
+ message: 'The selected Studio target is no longer available.',
93
+ response,
94
+ status: 409,
95
+ });
96
+ return;
97
+ }
98
+ if (configFile === null) {
99
+ (0, protocol_response_1.writeStudioProtocolError)({
100
+ code: 'no-config-file',
101
+ message: 'The selected Studio did not load a Remotion config file.',
102
+ response,
103
+ status: 409,
104
+ });
105
+ return;
106
+ }
107
+ try {
108
+ (0, update_public_license_1.setPublicLicenseKeyInConfigFile)({
109
+ configFile,
110
+ publicLicenseKey: parsedRequest.data.licenseKey,
111
+ });
112
+ }
113
+ catch (_a) {
114
+ (0, protocol_response_1.writeStudioProtocolError)({
115
+ code: 'config-write-failed',
116
+ message: 'Could not update the Remotion config file.',
117
+ response,
118
+ status: 500,
119
+ });
120
+ return;
121
+ }
122
+ response.writeHead(200, { 'Content-Type': 'application/json' });
123
+ response.end(JSON.stringify({
124
+ protocol: 'remotion-studio-protocol',
125
+ protocolVersion: 1,
126
+ status: 'license-key-set',
127
+ }));
128
+ };
129
+ exports.handleStudioProtocolLicenseKey = handleStudioProtocolLicenseKey;
@@ -1,11 +1,13 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  export declare const getAllowedStudioProtocolOrigin: (origin: string | undefined) => string | null;
3
- export declare const isAllowedStudioProtocolOrigin: (origin: string | undefined) => boolean;
4
- export declare const setStudioProtocolCorsHeaders: ({ request, response, }: {
3
+ export declare const getAllowedLicenseKeyOrigin: (origin: string | undefined) => string | null;
4
+ export declare const setStudioProtocolCorsHeaders: ({ licenseKey, request, response, }: {
5
+ readonly licenseKey: boolean;
5
6
  readonly request: IncomingMessage;
6
7
  readonly response: ServerResponse<IncomingMessage>;
7
8
  }) => void;
8
- export declare const handleStudioProtocolOptions: ({ request, response, }: {
9
+ export declare const handleStudioProtocolOptions: ({ licenseKey, request, response, }: {
10
+ readonly licenseKey: boolean;
9
11
  readonly request: IncomingMessage;
10
12
  readonly response: ServerResponse<IncomingMessage>;
11
13
  }) => Promise<void>;
@@ -1,15 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleStudioProtocolOptions = exports.setStudioProtocolCorsHeaders = exports.isAllowedStudioProtocolOrigin = exports.getAllowedStudioProtocolOrigin = void 0;
3
+ exports.handleStudioProtocolOptions = exports.setStudioProtocolCorsHeaders = exports.getAllowedLicenseKeyOrigin = exports.getAllowedStudioProtocolOrigin = void 0;
4
+ const isLoopbackHttp = (url) => url.protocol === 'http:' &&
5
+ (url.hostname === 'localhost' || url.hostname === '127.0.0.1');
4
6
  const getAllowedStudioProtocolOrigin = (origin) => {
5
7
  if (!origin) {
6
8
  return null;
7
9
  }
8
10
  try {
9
11
  const url = new URL(origin);
10
- const isLoopbackHttp = url.protocol === 'http:' &&
11
- (url.hostname === 'localhost' || url.hostname === '127.0.0.1');
12
- if (url.protocol !== 'https:' && !isLoopbackHttp) {
12
+ if (url.protocol !== 'https:' && !isLoopbackHttp(url)) {
13
13
  return null;
14
14
  }
15
15
  return url.origin;
@@ -19,10 +19,30 @@ const getAllowedStudioProtocolOrigin = (origin) => {
19
19
  }
20
20
  };
21
21
  exports.getAllowedStudioProtocolOrigin = getAllowedStudioProtocolOrigin;
22
- const isAllowedStudioProtocolOrigin = (origin) => (0, exports.getAllowedStudioProtocolOrigin)(origin) !== null;
23
- exports.isAllowedStudioProtocolOrigin = isAllowedStudioProtocolOrigin;
24
- const setStudioProtocolCorsHeaders = ({ request, response, }) => {
25
- const origin = (0, exports.getAllowedStudioProtocolOrigin)(request.headers.origin);
22
+ const getAllowedLicenseKeyOrigin = (origin) => {
23
+ if (!origin) {
24
+ return null;
25
+ }
26
+ try {
27
+ const url = new URL(origin);
28
+ if (isLoopbackHttp(url)) {
29
+ return url.origin;
30
+ }
31
+ if (url.origin === 'https://remotion.pro' ||
32
+ url.origin === 'https://www.remotion.pro') {
33
+ return url.origin;
34
+ }
35
+ return null;
36
+ }
37
+ catch (_a) {
38
+ return null;
39
+ }
40
+ };
41
+ exports.getAllowedLicenseKeyOrigin = getAllowedLicenseKeyOrigin;
42
+ const setStudioProtocolCorsHeaders = ({ licenseKey, request, response, }) => {
43
+ const origin = licenseKey
44
+ ? (0, exports.getAllowedLicenseKeyOrigin)(request.headers.origin)
45
+ : (0, exports.getAllowedStudioProtocolOrigin)(request.headers.origin);
26
46
  if (origin === null) {
27
47
  return;
28
48
  }
@@ -34,9 +54,12 @@ const setStudioProtocolCorsHeaders = ({ request, response, }) => {
34
54
  response.setHeader('Access-Control-Allow-Private-Network', 'true');
35
55
  };
36
56
  exports.setStudioProtocolCorsHeaders = setStudioProtocolCorsHeaders;
37
- const handleStudioProtocolOptions = ({ request, response, }) => {
38
- (0, exports.setStudioProtocolCorsHeaders)({ request, response });
39
- response.writeHead((0, exports.getAllowedStudioProtocolOrigin)(request.headers.origin) === null ? 403 : 204);
57
+ const handleStudioProtocolOptions = ({ licenseKey, request, response, }) => {
58
+ (0, exports.setStudioProtocolCorsHeaders)({ licenseKey, request, response });
59
+ const origin = licenseKey
60
+ ? (0, exports.getAllowedLicenseKeyOrigin)(request.headers.origin)
61
+ : (0, exports.getAllowedStudioProtocolOrigin)(request.headers.origin);
62
+ response.writeHead(origin === null ? 403 : 204);
40
63
  response.end();
41
64
  return Promise.resolve();
42
65
  };
package/dist/routes.js CHANGED
@@ -55,6 +55,7 @@ const open_in_editor_1 = require("./preview-server/routes/open-in-editor");
55
55
  const serve_static_1 = require("./preview-server/serve-static");
56
56
  const handle_discovery_1 = require("./preview-server/studio-protocol/handle-discovery");
57
57
  const handle_install_1 = require("./preview-server/studio-protocol/handle-install");
58
+ const handle_license_key_1 = require("./preview-server/studio-protocol/handle-license-key");
58
59
  const origin_policy_1 = require("./preview-server/studio-protocol/origin-policy");
59
60
  const validate_same_origin_1 = require("./preview-server/validate-same-origin");
60
61
  const watch_ignore_next_change_1 = require("./preview-server/watch-ignore-next-change");
@@ -279,9 +280,14 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
279
280
  });
280
281
  }
281
282
  if (url.pathname === '/api/studio-protocol' ||
282
- url.pathname === '/api/studio-protocol/install') {
283
+ url.pathname === '/api/studio-protocol/install' ||
284
+ url.pathname === '/api/studio-protocol/license-key') {
283
285
  if (request.method === 'OPTIONS') {
284
- return (0, origin_policy_1.handleStudioProtocolOptions)({ request, response });
286
+ return (0, origin_policy_1.handleStudioProtocolOptions)({
287
+ licenseKey: url.pathname === '/api/studio-protocol/license-key',
288
+ request,
289
+ response,
290
+ });
285
291
  }
286
292
  if (url.pathname === '/api/studio-protocol') {
287
293
  return (0, handle_discovery_1.handleStudioProtocolDiscovery)({
@@ -292,6 +298,9 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
292
298
  response,
293
299
  });
294
300
  }
301
+ if (url.pathname === '/api/studio-protocol/license-key') {
302
+ return (0, handle_license_key_1.handleStudioProtocolLicenseKey)({ configFile, request, response });
303
+ }
295
304
  return (0, handle_install_1.handleStudioProtocolInstall)({
296
305
  focusStudioTab: (studioUrl) => {
297
306
  (0, better_opn_1.focusBrowserTab)({ url: studioUrl }).catch(() => undefined);
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/studio-server"
4
4
  },
5
5
  "name": "@remotion/studio-server",
6
- "version": "4.0.503",
6
+ "version": "4.0.505",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "scripts": {
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@remotion/studio-protocol": "4.0.503",
26
+ "@remotion/studio-protocol": "4.0.505",
27
27
  "@babel/types": "7.24.0",
28
28
  "@babel/parser": "7.24.1",
29
29
  "@svgr/core": "8.1.0",
@@ -32,12 +32,12 @@
32
32
  "semver": "7.5.3",
33
33
  "zod": "4.4.3",
34
34
  "prettier": "3.8.1",
35
- "remotion": "4.0.503",
35
+ "remotion": "4.0.505",
36
36
  "recast": "0.23.11",
37
- "@remotion/bundler": "4.0.503",
38
- "@remotion/renderer": "4.0.503",
39
- "@remotion/studio-codemods": "4.0.503",
40
- "@remotion/studio-shared": "4.0.503",
37
+ "@remotion/bundler": "4.0.505",
38
+ "@remotion/renderer": "4.0.505",
39
+ "@remotion/studio-codemods": "4.0.505",
40
+ "@remotion/studio-shared": "4.0.505",
41
41
  "memfs": "3.4.3",
42
42
  "open": "8.4.2"
43
43
  },
@@ -45,7 +45,7 @@
45
45
  "ast-types": "0.16.1",
46
46
  "react": "19.2.3",
47
47
  "@types/semver": "7.5.3",
48
- "@remotion/eslint-config-internal": "4.0.503",
48
+ "@remotion/eslint-config-internal": "4.0.505",
49
49
  "eslint": "9.19.0",
50
50
  "@types/node": "20.12.14",
51
51
  "@typescript/native-preview": "7.0.0-dev.20260217.1"