@remotion/studio-server 4.0.502 → 4.0.504

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.
Files changed (60) hide show
  1. package/dist/detect-outdated-remotion-skills.d.ts +25 -0
  2. package/dist/detect-outdated-remotion-skills.js +73 -0
  3. package/dist/helpers/custom-editor.d.ts +37 -0
  4. package/dist/helpers/custom-editor.js +167 -0
  5. package/dist/helpers/editor-registry.d.ts +18 -0
  6. package/dist/helpers/editor-registry.js +500 -0
  7. package/dist/helpers/open-in-editor.d.ts +4 -4
  8. package/dist/helpers/open-in-editor.js +23 -87
  9. package/dist/helpers/resolve-editor.d.ts +27 -0
  10. package/dist/helpers/resolve-editor.js +90 -0
  11. package/dist/helpers/resolve-file-inside-project.d.ts +2 -0
  12. package/dist/helpers/resolve-file-inside-project.js +5 -2
  13. package/dist/index.d.ts +17 -1
  14. package/dist/index.js +9 -1
  15. package/dist/preview-server/api-routes.js +6 -1
  16. package/dist/preview-server/api-types.d.ts +2 -1
  17. package/dist/preview-server/element-install-state.d.ts +9 -2
  18. package/dist/preview-server/element-install-state.js +15 -5
  19. package/dist/preview-server/handler.d.ts +3 -1
  20. package/dist/preview-server/handler.js +2 -1
  21. package/dist/preview-server/routes/can-update-sequence-props.js +2 -1
  22. package/dist/preview-server/routes/default-editor.d.ts +8 -0
  23. package/dist/preview-server/routes/default-editor.js +131 -0
  24. package/dist/preview-server/routes/delete-effect-keyframe.d.ts +3 -0
  25. package/dist/preview-server/routes/delete-effect-keyframe.js +89 -0
  26. package/dist/preview-server/routes/delete-sequence-keyframe.d.ts +3 -0
  27. package/dist/preview-server/routes/delete-sequence-keyframe.js +82 -0
  28. package/dist/preview-server/routes/element-install-plan.d.ts +33 -0
  29. package/dist/preview-server/routes/element-install-plan.js +122 -0
  30. package/dist/preview-server/routes/insert-element.d.ts +1 -1
  31. package/dist/preview-server/routes/insert-element.js +70 -107
  32. package/dist/preview-server/routes/install-dependency.d.ts +2 -1
  33. package/dist/preview-server/routes/install-dependency.js +33 -44
  34. package/dist/preview-server/routes/open-in-editor.d.ts +5 -1
  35. package/dist/preview-server/routes/open-in-editor.js +42 -12
  36. package/dist/preview-server/routes/prepare-element-install.d.ts +3 -0
  37. package/dist/preview-server/routes/prepare-element-install.js +25 -0
  38. package/dist/preview-server/routes/safe-element-install-path.d.ts +9 -0
  39. package/dist/preview-server/routes/safe-element-install-path.js +58 -0
  40. package/dist/preview-server/routes/save-props-mutex.d.ts +1 -0
  41. package/dist/preview-server/routes/save-props-mutex.js +11 -0
  42. package/dist/preview-server/routes/update-public-license.d.ts +4 -0
  43. package/dist/preview-server/routes/update-public-license.js +9 -5
  44. package/dist/preview-server/start-server.d.ts +2 -0
  45. package/dist/preview-server/start-server.js +1 -0
  46. package/dist/preview-server/studio-protocol/handle-discovery.js +57 -26
  47. package/dist/preview-server/studio-protocol/handle-install.js +13 -3
  48. package/dist/preview-server/studio-protocol/handle-license-key.d.ts +6 -0
  49. package/dist/preview-server/studio-protocol/handle-license-key.js +129 -0
  50. package/dist/preview-server/studio-protocol/origin-policy.d.ts +6 -3
  51. package/dist/preview-server/studio-protocol/origin-policy.js +43 -17
  52. package/dist/preview-server/update-available.d.ts +6 -0
  53. package/dist/preview-server/update-available.js +30 -4
  54. package/dist/remotion-skill-names.d.ts +2 -0
  55. package/dist/remotion-skill-names.js +16 -0
  56. package/dist/routes.d.ts +3 -1
  57. package/dist/routes.js +16 -5
  58. package/dist/start-studio.d.ts +3 -1
  59. package/dist/start-studio.js +2 -1
  60. package/package.json +8 -8
@@ -5,28 +5,58 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.openInEditorHandler = exports.getEditorName = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
+ const renderer_1 = require("@remotion/renderer");
9
+ const custom_editor_1 = require("../../helpers/custom-editor");
8
10
  const open_in_editor_1 = require("../../helpers/open-in-editor");
9
- const editorGuess = (0, open_in_editor_1.guessEditor)();
10
- const getEditorName = async () => {
11
- const [edit] = await editorGuess;
12
- return (0, open_in_editor_1.getDisplayNameForEditor)(edit ? edit.command : null);
11
+ const resolve_editor_1 = require("../../helpers/resolve-editor");
12
+ const getEditorName = async ({ getDefaultEditor, logLevel, }) => {
13
+ var _a;
14
+ const editor = await (0, resolve_editor_1.resolveEditor)({
15
+ defaultEditor: getDefaultEditor(),
16
+ logLevel,
17
+ preferredEditor: null,
18
+ });
19
+ return (_a = editor === null || editor === void 0 ? void 0 : editor.name) !== null && _a !== void 0 ? _a : null;
13
20
  };
14
21
  exports.getEditorName = getEditorName;
15
- const openInEditorHandler = async ({ input, remotionRoot, logLevel }) => {
22
+ const openInEditorHandler = async ({ input, remotionRoot, logLevel, getDefaultEditor }) => {
16
23
  try {
17
24
  if (!('stack' in input)) {
18
25
  throw new TypeError('Need to pass stack');
19
26
  }
20
27
  const { stack } = input;
21
- const guess = await editorGuess;
22
- const didOpen = await (0, open_in_editor_1.launchEditor)({
23
- colNumber: stack.originalColumnNumber,
24
- editor: guess[0],
25
- fileName: node_path_1.default.resolve(remotionRoot, stack.originalFileName),
26
- lineNumber: stack.originalLineNumber,
27
- vsCodeNewWindow: false,
28
+ if (input.editorId !== null &&
29
+ input.editorId !== 'custom' &&
30
+ !renderer_1.defaultEditorIds.includes(input.editorId)) {
31
+ return { success: false };
32
+ }
33
+ const editor = await (0, resolve_editor_1.resolveEditor)({
34
+ defaultEditor: getDefaultEditor(),
28
35
  logLevel,
36
+ preferredEditor: input.editorId,
29
37
  });
38
+ if (!editor) {
39
+ return { success: false };
40
+ }
41
+ const fileName = node_path_1.default.resolve(remotionRoot, stack.originalFileName);
42
+ const didOpen = editor.type === 'custom'
43
+ ? await (0, custom_editor_1.launchCustomEditor)({
44
+ editor: editor.editor,
45
+ resolvedExecutable: editor,
46
+ targetPath: fileName,
47
+ lineNumber: stack.originalLineNumber,
48
+ columnNumber: stack.originalColumnNumber,
49
+ logLevel,
50
+ spawnProcess: null,
51
+ })
52
+ : await (0, open_in_editor_1.launchEditor)({
53
+ colNumber: stack.originalColumnNumber,
54
+ editor,
55
+ fileName,
56
+ lineNumber: stack.originalLineNumber,
57
+ vsCodeNewWindow: false,
58
+ logLevel,
59
+ });
30
60
  return {
31
61
  success: didOpen,
32
62
  };
@@ -0,0 +1,3 @@
1
+ import type { PrepareElementInstallRequest, PrepareElementInstallResponse } from '@remotion/studio-shared';
2
+ import type { ApiHandler } from '../api-types';
3
+ export declare const prepareElementInstallHandler: ApiHandler<PrepareElementInstallRequest, PrepareElementInstallResponse>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepareElementInstallHandler = void 0;
4
+ const element_install_plan_1 = require("./element-install-plan");
5
+ const source_file_write_queue_1 = require("./source-file-write-queue");
6
+ const prepareElementInstallHandler = ({ input, remotionRoot }) => (0, source_file_write_queue_1.withSourceFileWriteQueue)(async () => {
7
+ try {
8
+ const plan = await (0, element_install_plan_1.getElementInstallPlan)({ ...input, remotionRoot });
9
+ return {
10
+ success: true,
11
+ plan: {
12
+ expectedFileState: plan.expectedFileState,
13
+ filePath: plan.filePath,
14
+ },
15
+ };
16
+ }
17
+ catch (error) {
18
+ return {
19
+ success: false,
20
+ reason: error.message,
21
+ stack: error.stack,
22
+ };
23
+ }
24
+ });
25
+ exports.prepareElementInstallHandler = prepareElementInstallHandler;
@@ -0,0 +1,9 @@
1
+ export declare const getSafeElementInstallPaths: ({ compositionFileName, elementFileName, remotionRoot, }: {
2
+ compositionFileName: string;
3
+ elementFileName: string;
4
+ remotionRoot: string;
5
+ }) => Promise<{
6
+ compositionFileName: string;
7
+ elementFileName: string;
8
+ remotionRoot: string;
9
+ }>;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getSafeElementInstallPaths = void 0;
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const isInside = ({ child, parent }) => {
10
+ const relative = node_path_1.default.relative(parent, child);
11
+ return (relative === '' ||
12
+ (!relative.startsWith('..') && !node_path_1.default.isAbsolute(relative)));
13
+ };
14
+ const assertInsideProject = ({ fileName, remotionRoot, }) => {
15
+ if (!isInside({ child: fileName, parent: remotionRoot })) {
16
+ throw new Error('Element installation must stay inside the Remotion project');
17
+ }
18
+ };
19
+ const getCanonicalProjectRoot = async (remotionRoot) => {
20
+ return (0, promises_1.realpath)(remotionRoot);
21
+ };
22
+ const getSafeElementInstallPaths = async ({ compositionFileName, elementFileName, remotionRoot, }) => {
23
+ const canonicalRoot = await getCanonicalProjectRoot(remotionRoot);
24
+ const canonicalCompositionFile = await (0, promises_1.realpath)(compositionFileName);
25
+ assertInsideProject({
26
+ fileName: canonicalCompositionFile,
27
+ remotionRoot: canonicalRoot,
28
+ });
29
+ // The Element directory already exists because it is the composition file's
30
+ // directory. Resolving it before appending the file name prevents writes
31
+ // through a directory symlink that leaves the project.
32
+ const canonicalElementDirectory = await (0, promises_1.realpath)(node_path_1.default.dirname(elementFileName));
33
+ assertInsideProject({
34
+ fileName: canonicalElementDirectory,
35
+ remotionRoot: canonicalRoot,
36
+ });
37
+ const canonicalElementFile = node_path_1.default.join(canonicalElementDirectory, node_path_1.default.basename(elementFileName));
38
+ try {
39
+ const elementStats = await (0, promises_1.lstat)(canonicalElementFile);
40
+ if (elementStats.isSymbolicLink()) {
41
+ throw new Error('Element source file must not be a symbolic link');
42
+ }
43
+ if (!elementStats.isFile()) {
44
+ throw new Error('Element source path must be a file');
45
+ }
46
+ }
47
+ catch (error) {
48
+ if (error.code !== 'ENOENT') {
49
+ throw error;
50
+ }
51
+ }
52
+ return {
53
+ compositionFileName: canonicalCompositionFile,
54
+ elementFileName: canonicalElementFile,
55
+ remotionRoot: canonicalRoot,
56
+ };
57
+ };
58
+ exports.getSafeElementInstallPaths = getSafeElementInstallPaths;
@@ -0,0 +1 @@
1
+ export declare const withSavePropsLock: <T>(fn: () => Promise<T>) => Promise<T>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withSavePropsLock = void 0;
4
+ let chain = Promise.resolve();
5
+ const withSavePropsLock = (fn) => {
6
+ const run = () => fn();
7
+ const next = chain.then(run, run);
8
+ chain = next.then(() => undefined, () => undefined);
9
+ return next;
10
+ };
11
+ exports.withSavePropsLock = withSavePropsLock;
@@ -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;
@@ -1,4 +1,5 @@
1
1
  import type { BundlerOverrideFn, RspackOverrideFn, WebpackOverrideFn } from '@remotion/bundler';
2
+ import type { DefaultEditor } from '@remotion/renderer';
2
3
  import type { GitSource, RenderDefaults, RenderJob, StudioRuntimeConfig } from '@remotion/studio-shared';
3
4
  import type { QueueMethods } from './api-types';
4
5
  import type { LiveEventsServer } from './live-events';
@@ -41,5 +42,6 @@ export declare const startServer: (options: {
41
42
  forceNew: boolean;
42
43
  rspack: boolean;
43
44
  getStudioRuntimeConfig: () => StudioRuntimeConfig;
45
+ getDefaultEditor: () => DefaultEditor | null;
44
46
  configFile: string | null;
45
47
  }) => Promise<StartServerResult>;
@@ -102,6 +102,7 @@ const startServer = async (options) => {
102
102
  getPreviewSampleRate: options.getPreviewSampleRate,
103
103
  enableCrossSiteIsolation: options.enableCrossSiteIsolation,
104
104
  getStudioRuntimeConfig: options.getStudioRuntimeConfig,
105
+ getDefaultEditor: options.getDefaultEditor,
105
106
  configFile: options.configFile,
106
107
  });
107
108
  })
@@ -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,29 +20,32 @@ 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 });
45
- if (!(0, origin_policy_1.isAllowedStudioProtocolOrigin)(request.headers.origin)) {
46
+ (0, origin_policy_1.setStudioProtocolCorsHeaders)({ licenseKey: false, request, response });
47
+ const requestOrigin = (0, origin_policy_1.getAllowedStudioProtocolOrigin)(request.headers.origin);
48
+ if (requestOrigin === null) {
46
49
  (0, protocol_response_1.writeStudioProtocolError)({
47
50
  code: 'unsupported-origin',
48
51
  message: 'Origin not allowed',
@@ -64,8 +67,25 @@ const handleStudioProtocolDiscovery = ({ gitSource, liveEventsServer, remotionRo
64
67
  return new Promise((resolve) => {
65
68
  setTimeout(() => {
66
69
  const now = Date.now();
67
- const target = getLiveInstallableTarget(requestId);
68
- const issued = target === null ? null : (0, element_install_state_1.issueStudioProtocolTarget)({ now, target });
70
+ const target = getLiveStudioTarget(requestId);
71
+ const installTarget = isInstallableTarget(target) ? target : null;
72
+ const issuedInstallTarget = installTarget === null
73
+ ? null
74
+ : (0, element_install_state_1.issueStudioProtocolTarget)({
75
+ now,
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',
87
+ target,
88
+ });
69
89
  response.writeHead(200, {
70
90
  'Cache-Control': 'no-store',
71
91
  'Content-Type': 'application/json',
@@ -74,23 +94,34 @@ const handleStudioProtocolDiscovery = ({ gitSource, liveEventsServer, remotionRo
74
94
  protocol: 'remotion-studio-protocol',
75
95
  protocolVersion: 1,
76
96
  studioVersion: version_1.VERSION,
77
- capabilities: {
78
- install: [
79
- {
80
- payloadType: 'remotion-element',
81
- payloadVersions: [1],
82
- },
83
- ],
84
- },
85
97
  projectName: getProject({ gitSource, remotionRoot }),
86
- installTarget: issued === null || target === null
87
- ? null
88
- : {
89
- id: issued.id,
90
- expiresAt: issued.expiresAt,
91
- compositionId: target.compositionId,
92
- 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
+ },
93
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
+ },
123
+ },
124
+ ],
94
125
  }));
95
126
  resolve();
96
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(),
@@ -16,7 +17,7 @@ const studioProtocolInstallRequestSchema = zod_1.z.object({
16
17
  // A valid payload may contain 250,000 JSON characters. Allow room for its
17
18
  // UTF-8 representation and the protocol envelope while keeping memory bounded.
18
19
  const MAX_STUDIO_PROTOCOL_INSTALL_BODY_SIZE = 1000000;
19
- const deliverElementInstall = ({ element, focusStudioTab, liveEventsServer, target, }) => {
20
+ const deliverElementInstall = ({ element, focusStudioTab, liveEventsServer, origin, target, }) => {
20
21
  if (target.compositionFile === null || target.compositionId === null) {
21
22
  return false;
22
23
  }
@@ -27,7 +28,12 @@ const deliverElementInstall = ({ element, focusStudioTab, liveEventsServer, targ
27
28
  compositionFile: target.compositionFile,
28
29
  compositionId: target.compositionId,
29
30
  element,
31
+ from: null,
30
32
  position: null,
33
+ source: {
34
+ type: 'studio-protocol',
35
+ origin,
36
+ },
31
37
  };
32
38
  const delivered = liveEventsServer.sendEventToClientId(target.clientId, {
33
39
  type: 'element-install-request',
@@ -39,8 +45,9 @@ const deliverElementInstall = ({ element, focusStudioTab, liveEventsServer, targ
39
45
  return delivered;
40
46
  };
41
47
  const handleStudioProtocolInstall = async ({ focusStudioTab, liveEventsServer, request, response, }) => {
42
- (0, origin_policy_1.setStudioProtocolCorsHeaders)({ request, response });
43
- if (!(0, origin_policy_1.isAllowedStudioProtocolOrigin)(request.headers.origin)) {
48
+ (0, origin_policy_1.setStudioProtocolCorsHeaders)({ licenseKey: false, request, response });
49
+ const requestOrigin = (0, origin_policy_1.getAllowedStudioProtocolOrigin)(request.headers.origin);
50
+ if (requestOrigin === null) {
44
51
  (0, protocol_response_1.writeStudioProtocolError)({
45
52
  code: 'unsupported-origin',
46
53
  message: 'Origin not allowed',
@@ -104,6 +111,8 @@ const handleStudioProtocolInstall = async ({ focusStudioTab, liveEventsServer, r
104
111
  }
105
112
  const target = (0, element_install_state_1.consumeStudioProtocolTarget)({
106
113
  now: Date.now(),
114
+ origin: requestOrigin,
115
+ purpose: 'install-element',
107
116
  targetId: parsedRequest.data.targetId,
108
117
  });
109
118
  if (target === null) {
@@ -119,6 +128,7 @@ const handleStudioProtocolInstall = async ({ focusStudioTab, liveEventsServer, r
119
128
  element: payload.element,
120
129
  focusStudioTab,
121
130
  liveEventsServer,
131
+ origin: requestOrigin,
122
132
  target,
123
133
  })) {
124
134
  (0, protocol_response_1.writeStudioProtocolError)({
@@ -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,10 +1,13 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
- export declare const isAllowedStudioProtocolOrigin: (origin: string | undefined) => boolean;
3
- export declare const setStudioProtocolCorsHeaders: ({ request, response, }: {
2
+ export declare const getAllowedStudioProtocolOrigin: (origin: string | undefined) => string | null;
3
+ export declare const getAllowedLicenseKeyOrigin: (origin: string | undefined) => string | null;
4
+ export declare const setStudioProtocolCorsHeaders: ({ licenseKey, request, response, }: {
5
+ readonly licenseKey: boolean;
4
6
  readonly request: IncomingMessage;
5
7
  readonly response: ServerResponse<IncomingMessage>;
6
8
  }) => void;
7
- export declare const handleStudioProtocolOptions: ({ request, response, }: {
9
+ export declare const handleStudioProtocolOptions: ({ licenseKey, request, response, }: {
10
+ readonly licenseKey: boolean;
8
11
  readonly request: IncomingMessage;
9
12
  readonly response: ServerResponse<IncomingMessage>;
10
13
  }) => Promise<void>;