@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.
- package/dist/detect-outdated-remotion-skills.d.ts +25 -0
- package/dist/detect-outdated-remotion-skills.js +73 -0
- package/dist/helpers/custom-editor.d.ts +37 -0
- package/dist/helpers/custom-editor.js +167 -0
- package/dist/helpers/editor-registry.d.ts +18 -0
- package/dist/helpers/editor-registry.js +500 -0
- package/dist/helpers/open-in-editor.d.ts +4 -4
- package/dist/helpers/open-in-editor.js +23 -87
- package/dist/helpers/resolve-editor.d.ts +27 -0
- package/dist/helpers/resolve-editor.js +90 -0
- package/dist/helpers/resolve-file-inside-project.d.ts +2 -0
- package/dist/helpers/resolve-file-inside-project.js +5 -2
- package/dist/index.d.ts +17 -1
- package/dist/index.js +9 -1
- package/dist/preview-server/api-routes.js +6 -1
- package/dist/preview-server/api-types.d.ts +2 -1
- package/dist/preview-server/element-install-state.d.ts +9 -2
- package/dist/preview-server/element-install-state.js +15 -5
- package/dist/preview-server/handler.d.ts +3 -1
- package/dist/preview-server/handler.js +2 -1
- package/dist/preview-server/routes/can-update-sequence-props.js +2 -1
- package/dist/preview-server/routes/default-editor.d.ts +8 -0
- package/dist/preview-server/routes/default-editor.js +131 -0
- package/dist/preview-server/routes/delete-effect-keyframe.d.ts +3 -0
- package/dist/preview-server/routes/delete-effect-keyframe.js +89 -0
- package/dist/preview-server/routes/delete-sequence-keyframe.d.ts +3 -0
- package/dist/preview-server/routes/delete-sequence-keyframe.js +82 -0
- package/dist/preview-server/routes/element-install-plan.d.ts +33 -0
- package/dist/preview-server/routes/element-install-plan.js +122 -0
- package/dist/preview-server/routes/insert-element.d.ts +1 -1
- package/dist/preview-server/routes/insert-element.js +70 -107
- package/dist/preview-server/routes/install-dependency.d.ts +2 -1
- package/dist/preview-server/routes/install-dependency.js +33 -44
- package/dist/preview-server/routes/open-in-editor.d.ts +5 -1
- package/dist/preview-server/routes/open-in-editor.js +42 -12
- package/dist/preview-server/routes/prepare-element-install.d.ts +3 -0
- package/dist/preview-server/routes/prepare-element-install.js +25 -0
- package/dist/preview-server/routes/safe-element-install-path.d.ts +9 -0
- package/dist/preview-server/routes/safe-element-install-path.js +58 -0
- package/dist/preview-server/routes/save-props-mutex.d.ts +1 -0
- package/dist/preview-server/routes/save-props-mutex.js +11 -0
- package/dist/preview-server/routes/update-public-license.d.ts +4 -0
- package/dist/preview-server/routes/update-public-license.js +9 -5
- package/dist/preview-server/start-server.d.ts +2 -0
- package/dist/preview-server/start-server.js +1 -0
- package/dist/preview-server/studio-protocol/handle-discovery.js +57 -26
- package/dist/preview-server/studio-protocol/handle-install.js +13 -3
- package/dist/preview-server/studio-protocol/handle-license-key.d.ts +6 -0
- package/dist/preview-server/studio-protocol/handle-license-key.js +129 -0
- package/dist/preview-server/studio-protocol/origin-policy.d.ts +6 -3
- package/dist/preview-server/studio-protocol/origin-policy.js +43 -17
- package/dist/preview-server/update-available.d.ts +6 -0
- package/dist/preview-server/update-available.js +30 -4
- package/dist/remotion-skill-names.d.ts +2 -0
- package/dist/remotion-skill-names.js +16 -0
- package/dist/routes.d.ts +3 -1
- package/dist/routes.js +16 -5
- package/dist/start-studio.d.ts +3 -1
- package/dist/start-studio.js +2 -1
- 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
|
|
10
|
-
const getEditorName = async () => {
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
configContents,
|
|
91
|
+
(0, exports.setPublicLicenseKeyInConfigFile)({
|
|
92
|
+
configFile,
|
|
89
93
|
publicLicenseKey: input.publicLicenseKey,
|
|
90
|
-
})
|
|
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
|
|
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
|
|
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 >=
|
|
30
|
-
|
|
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
|
-
|
|
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 =
|
|
68
|
-
const
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
|
3
|
-
export declare const
|
|
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>;
|