@remotion/studio-server 4.0.486 → 4.0.488

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.
@@ -1,3 +1,4 @@
1
+ export declare const getChromiumBrowsersToTry: (processes: string) => ("Arc" | "Brave Browser" | "Chromium" | "Google Chrome" | "Google Chrome Canary" | "Microsoft Edge" | "Vivaldi")[];
1
2
  export declare const openBrowser: ({ url, browserFlag, browserArgs, }: {
2
3
  url: string;
3
4
  browserFlag: string | undefined;
@@ -1,9 +1,26 @@
1
1
  "use strict";
2
2
  // Copied from https://github.com/michaellzc/better-opn#readme
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.openBrowser = void 0;
4
+ exports.openBrowser = exports.getChromiumBrowsersToTry = void 0;
5
5
  const node_child_process_1 = require("node:child_process");
6
6
  const open = require("open");
7
+ const supportedChromiumBrowsers = [
8
+ 'Google Chrome',
9
+ 'Google Chrome Canary',
10
+ 'Microsoft Edge',
11
+ 'Brave Browser',
12
+ 'Vivaldi',
13
+ 'Chromium',
14
+ 'Arc',
15
+ ];
16
+ const getChromiumBrowsersToTry = (processes) => {
17
+ const processNames = new Set(processes
18
+ .split('\n')
19
+ .map((line) => line.trim())
20
+ .filter(Boolean));
21
+ return supportedChromiumBrowsers.filter((browser) => processNames.has(browser));
22
+ };
23
+ exports.getChromiumBrowsersToTry = getChromiumBrowsersToTry;
7
24
  const normalizeURLToMatch = (target) => {
8
25
  // We may encounter URL parse error but want to fallback to default behavior
9
26
  try {
@@ -26,17 +43,8 @@ const startBrowserProcess = async ({ browser, url, args, }) => {
26
43
  if (shouldTryOpenChromiumWithAppleScript) {
27
44
  let appleScriptDenied = false;
28
45
  // Will use the first open browser found from list
29
- const supportedChromiumBrowsers = [
30
- 'Google Chrome',
31
- 'Google Chrome Canary',
32
- 'Microsoft Edge',
33
- 'Brave Browser',
34
- 'Vivaldi',
35
- 'Chromium',
36
- 'Arc',
37
- ];
38
46
  const processes = await new Promise((resolve, reject) => {
39
- (0, node_child_process_1.exec)('ps cax', (err, stdout) => {
47
+ (0, node_child_process_1.exec)('ps -cax -o comm=', (err, stdout) => {
40
48
  if (err) {
41
49
  reject(err);
42
50
  }
@@ -45,7 +53,7 @@ const startBrowserProcess = async ({ browser, url, args, }) => {
45
53
  }
46
54
  });
47
55
  });
48
- const browsersToTry = supportedChromiumBrowsers.filter((b) => processes.includes(b));
56
+ const browsersToTry = (0, exports.getChromiumBrowsersToTry)(processes);
49
57
  for (const chromiumBrowser of browsersToTry) {
50
58
  if (appleScriptDenied) {
51
59
  continue;
@@ -37,6 +37,7 @@ exports.updateEffectKeyframes = exports.updateEffectKeyframesAst = exports.updat
37
37
  const studio_shared_1 = require("@remotion/studio-shared");
38
38
  const recast = __importStar(require("recast"));
39
39
  const get_ast_node_path_1 = require("../../helpers/get-ast-node-path");
40
+ const parse_keyframe_easing_expression_1 = require("../../helpers/parse-keyframe-easing-expression");
40
41
  const can_update_sequence_props_1 = require("../../preview-server/routes/can-update-sequence-props");
41
42
  const format_file_content_1 = require("../format-file-content");
42
43
  const parse_ast_1 = require("../parse-ast");
@@ -225,55 +226,7 @@ const setOptionsProperty = ({ options, propertyName, value, }) => {
225
226
  options.properties.push(b.objectProperty(b.identifier(propertyName), value));
226
227
  };
227
228
  const isLinearEasing = (easing) => easing.type === 'linear';
228
- const getKeyframeEasing = (node) => {
229
- if (node.type === 'TSAsExpression') {
230
- return getKeyframeEasing(node.expression);
231
- }
232
- if (node.type === 'MemberExpression' &&
233
- node.object.type === 'Identifier' &&
234
- node.object.name === 'Easing' &&
235
- node.property.type === 'Identifier' &&
236
- node.property.name === 'linear' &&
237
- node.computed === false) {
238
- return { type: 'linear' };
239
- }
240
- if (node.type !== 'CallExpression' ||
241
- node.callee.type !== 'MemberExpression' ||
242
- node.callee.object.type !== 'Identifier' ||
243
- node.callee.object.name !== 'Easing' ||
244
- node.callee.property.type !== 'Identifier' ||
245
- node.callee.computed) {
246
- return null;
247
- }
248
- if (node.callee.property.name === 'spring') {
249
- if (node.arguments.length > 1) {
250
- return null;
251
- }
252
- const springConfig = node.arguments[0];
253
- if ((springConfig === null || springConfig === void 0 ? void 0 : springConfig.type) === 'ArgumentPlaceholder' ||
254
- (springConfig === null || springConfig === void 0 ? void 0 : springConfig.type) === 'JSXNamespacedName' ||
255
- (springConfig === null || springConfig === void 0 ? void 0 : springConfig.type) === 'SpreadElement') {
256
- return null;
257
- }
258
- return (0, studio_shared_1.parseSpringEasingConfig)(springConfig);
259
- }
260
- if (node.callee.property.name !== 'bezier' || node.arguments.length !== 4) {
261
- return null;
262
- }
263
- const values = node.arguments.map((arg) => {
264
- if (arg.type === 'ArgumentPlaceholder' ||
265
- arg.type === 'JSXNamespacedName' ||
266
- arg.type === 'SpreadElement') {
267
- return null;
268
- }
269
- return getNumericValue(arg);
270
- });
271
- if (values.some((value) => value === null)) {
272
- return null;
273
- }
274
- const [x1, y1, x2, y2] = values;
275
- return { type: 'bezier', x1, y1, x2, y2 };
276
- };
229
+ const getKeyframeEasing = (node) => (0, parse_keyframe_easing_expression_1.parseKeyframeEasingExpression)(node);
277
230
  const getKeyframeEasingArray = ({ easingNode, segmentCount, }) => {
278
231
  if (segmentCount === 0) {
279
232
  return [];
@@ -0,0 +1,3 @@
1
+ import type { Expression } from '@babel/types';
2
+ import type { CanUpdateSequencePropStatusEasing } from 'remotion';
3
+ export declare const parseKeyframeEasingExpression: (node: Expression) => CanUpdateSequencePropStatusEasing | null;
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseKeyframeEasingExpression = void 0;
4
+ const studio_shared_1 = require("@remotion/studio-shared");
5
+ const getNumericValue = (node) => {
6
+ if (node.type === 'NumericLiteral') {
7
+ return node.value;
8
+ }
9
+ if (node.type === 'UnaryExpression' &&
10
+ (node.operator === '-' || node.operator === '+') &&
11
+ node.argument.type === 'NumericLiteral') {
12
+ return node.operator === '-' ? -node.argument.value : node.argument.value;
13
+ }
14
+ if (node.type === 'TSAsExpression') {
15
+ return getNumericValue(node.expression);
16
+ }
17
+ return null;
18
+ };
19
+ const getExpressionArgument = (arg) => {
20
+ if (arg === undefined ||
21
+ arg.type === 'ArgumentPlaceholder' ||
22
+ arg.type === 'JSXNamespacedName' ||
23
+ arg.type === 'SpreadElement') {
24
+ return null;
25
+ }
26
+ return arg;
27
+ };
28
+ const getMemberExpressionKeyframeEasing = (name) => {
29
+ if (name === 'linear') {
30
+ return studio_shared_1.LINEAR_KEYFRAME_EASING;
31
+ }
32
+ if (name === 'ease') {
33
+ return studio_shared_1.EASE_KEYFRAME_EASING;
34
+ }
35
+ if (name === 'quad') {
36
+ return studio_shared_1.QUAD_KEYFRAME_EASING;
37
+ }
38
+ if (name === 'cubic') {
39
+ return studio_shared_1.CUBIC_KEYFRAME_EASING;
40
+ }
41
+ return null;
42
+ };
43
+ const parseKeyframeEasingExpression = (node) => {
44
+ if (node.type === 'TSAsExpression') {
45
+ return (0, exports.parseKeyframeEasingExpression)(node.expression);
46
+ }
47
+ if (node.type === 'MemberExpression' &&
48
+ node.object.type === 'Identifier' &&
49
+ node.object.name === 'Easing' &&
50
+ node.property.type === 'Identifier' &&
51
+ node.computed === false) {
52
+ return getMemberExpressionKeyframeEasing(node.property.name);
53
+ }
54
+ if (node.type !== 'CallExpression' ||
55
+ node.callee.type !== 'MemberExpression' ||
56
+ node.callee.object.type !== 'Identifier' ||
57
+ node.callee.object.name !== 'Easing' ||
58
+ node.callee.property.type !== 'Identifier' ||
59
+ node.callee.computed) {
60
+ return null;
61
+ }
62
+ const propertyName = node.callee.property.name;
63
+ if (propertyName === 'spring') {
64
+ if (node.arguments.length > 1) {
65
+ return null;
66
+ }
67
+ if (node.arguments.length === 0) {
68
+ return (0, studio_shared_1.parseSpringEasingConfig)(undefined);
69
+ }
70
+ const springConfig = getExpressionArgument(node.arguments[0]);
71
+ return springConfig ? (0, studio_shared_1.parseSpringEasingConfig)(springConfig) : null;
72
+ }
73
+ if (propertyName === 'bezier') {
74
+ if (node.arguments.length !== 4) {
75
+ return null;
76
+ }
77
+ const values = node.arguments.map((arg) => {
78
+ const expression = getExpressionArgument(arg);
79
+ return expression ? getNumericValue(expression) : null;
80
+ });
81
+ if (values.some((value) => value === null)) {
82
+ return null;
83
+ }
84
+ const [x1, y1, x2, y2] = values;
85
+ return { type: 'bezier', x1, y1, x2, y2 };
86
+ }
87
+ if (propertyName === 'back') {
88
+ if (node.arguments.length > 1) {
89
+ return null;
90
+ }
91
+ if (node.arguments.length === 0) {
92
+ return (0, studio_shared_1.getBackKeyframeEasing)();
93
+ }
94
+ const expression = getExpressionArgument(node.arguments[0]);
95
+ if (!expression) {
96
+ return null;
97
+ }
98
+ const s = getNumericValue(expression);
99
+ if (s === null) {
100
+ return null;
101
+ }
102
+ return (0, studio_shared_1.getBackKeyframeEasing)(s);
103
+ }
104
+ if (propertyName === 'poly') {
105
+ if (node.arguments.length !== 1) {
106
+ return null;
107
+ }
108
+ const expression = getExpressionArgument(node.arguments[0]);
109
+ const n = expression ? getNumericValue(expression) : null;
110
+ return n === null ? null : (0, studio_shared_1.getPolyKeyframeEasing)(n);
111
+ }
112
+ if (propertyName === 'in') {
113
+ if (node.arguments.length !== 1) {
114
+ return null;
115
+ }
116
+ const expression = getExpressionArgument(node.arguments[0]);
117
+ return expression ? (0, exports.parseKeyframeEasingExpression)(expression) : null;
118
+ }
119
+ if (propertyName === 'out') {
120
+ if (node.arguments.length !== 1) {
121
+ return null;
122
+ }
123
+ const expression = getExpressionArgument(node.arguments[0]);
124
+ const easing = expression
125
+ ? (0, exports.parseKeyframeEasingExpression)(expression)
126
+ : null;
127
+ return easing ? (0, studio_shared_1.getOutKeyframeEasing)(easing) : null;
128
+ }
129
+ if (propertyName === 'inOut') {
130
+ if (node.arguments.length !== 1) {
131
+ return null;
132
+ }
133
+ const expression = getExpressionArgument(node.arguments[0]);
134
+ const easing = expression
135
+ ? (0, exports.parseKeyframeEasingExpression)(expression)
136
+ : null;
137
+ return (easing === null || easing === void 0 ? void 0 : easing.type) === 'linear' ? easing : null;
138
+ }
139
+ return null;
140
+ };
141
+ exports.parseKeyframeEasingExpression = parseKeyframeEasingExpression;
@@ -29,7 +29,7 @@ export declare const insertJsxElementIntoComposition: ({ remotionRoot, compositi
29
29
  dimensions: {
30
30
  width: number;
31
31
  height: number;
32
- };
32
+ } | null;
33
33
  durationInFrames?: number | null | undefined;
34
34
  name: string | null;
35
35
  position: InsertableCompositionElementPosition | null;
@@ -583,8 +583,12 @@ const createComponentElement = ({ addPositionStyle, localName, props, position,
583
583
  const createSequenceWrappedElement = ({ child, dimensions, durationInFrames, name, position, sequenceLocalName, }) => {
584
584
  return recast.types.builders.jsxElement(recast.types.builders.jsxOpeningElement(recast.types.builders.jsxIdentifier(sequenceLocalName), [
585
585
  ...(name === null ? [] : [createStringAttribute('name', name)]),
586
- createNumberAttribute('width', dimensions.width),
587
- createNumberAttribute('height', dimensions.height),
586
+ ...(dimensions !== null
587
+ ? [
588
+ createNumberAttribute('width', dimensions.width),
589
+ createNumberAttribute('height', dimensions.height),
590
+ ]
591
+ : []),
588
592
  ...(durationInFrames === null
589
593
  ? []
590
594
  : [createNumberAttribute('durationInFrames', durationInFrames)]),
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { ApiRoutes, CopyStillToClipboardRequest, getDefaultOutLocation, OpenInFi
2
2
  export type { AggregateRenderProgress, BundlingState, CopyingState, DownloadProgress, HotMiddlewareOptions, JobProgressCallback, ModuleMap, PackageManager, ProjectInfo, RenderingProgressInput, RenderJob, RenderJobWithCleanup, RequiredChromiumOptions, StitchingProgressInput, UiOpenGlOptions, } from '@remotion/studio-shared';
3
3
  import { AnsiDiff } from './ansi-diff';
4
4
  export declare const StudioServerInternals: {
5
- startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, previewSampleRate, enableCrossSiteIsolation, askAIEnabled, forceNew, rspack, }: {
5
+ startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, previewSampleRate, enableCrossSiteIsolation, askAIEnabled, interactivityEnabled, forceNew, rspack, }: {
6
6
  browserArgs: string;
7
7
  browserFlag: string;
8
8
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
@@ -31,6 +31,7 @@ export declare const StudioServerInternals: {
31
31
  binariesDirectory: string | null;
32
32
  forceIPv4: boolean;
33
33
  askAIEnabled: boolean;
34
+ interactivityEnabled: boolean;
34
35
  forceNew: boolean;
35
36
  rspack: boolean;
36
37
  }) => Promise<import("./start-studio").StartStudioResult>;
@@ -0,0 +1,9 @@
1
+ export declare const registerOpenBrowserShortcut: ({ browserArgs, browserFlag, url, logLevel, }: {
2
+ browserArgs: string;
3
+ browserFlag: string;
4
+ url: string;
5
+ logLevel: "error" | "info" | "trace" | "verbose" | "warn";
6
+ }) => {
7
+ registered: boolean;
8
+ cleanup: () => void;
9
+ };
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerOpenBrowserShortcut = void 0;
37
+ const readline = __importStar(require("node:readline"));
38
+ const renderer_1 = require("@remotion/renderer");
39
+ const maybe_open_browser_1 = require("./maybe-open-browser");
40
+ const live_events_1 = require("./preview-server/live-events");
41
+ const registerOpenBrowserShortcut = ({ browserArgs, browserFlag, url, logLevel, }) => {
42
+ if (!process.stdin.isTTY) {
43
+ return { registered: false, cleanup: () => undefined };
44
+ }
45
+ if (typeof process.stdin.setRawMode !== 'function') {
46
+ return { registered: false, cleanup: () => undefined };
47
+ }
48
+ const wasRaw = process.stdin.isRaw;
49
+ const shouldPauseAfterCleanup = process.stdin.isPaused();
50
+ let cleanedUp = false;
51
+ let isOpeningBrowser = false;
52
+ const cleanup = () => {
53
+ if (cleanedUp) {
54
+ return;
55
+ }
56
+ cleanedUp = true;
57
+ process.stdin.removeListener('keypress', onKeypress);
58
+ process.removeListener('SIGINT', cleanup);
59
+ process.removeListener('exit', cleanup);
60
+ if (!wasRaw && process.stdin.isTTY) {
61
+ process.stdin.setRawMode(false);
62
+ }
63
+ if (shouldPauseAfterCleanup) {
64
+ process.stdin.pause();
65
+ }
66
+ };
67
+ const openStudio = () => {
68
+ if (isOpeningBrowser) {
69
+ return;
70
+ }
71
+ isOpeningBrowser = true;
72
+ (0, live_events_1.clearPrintPortMessageTimeout)();
73
+ (0, maybe_open_browser_1.maybeOpenBrowser)({
74
+ browserArgs,
75
+ browserFlag,
76
+ shouldOpenBrowser: true,
77
+ url,
78
+ logLevel,
79
+ })
80
+ .then((result) => {
81
+ if (result.didOpenBrowser) {
82
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `Opened ${url} in browser`);
83
+ }
84
+ })
85
+ .catch((err) => {
86
+ renderer_1.RenderInternals.Log.error({ indent: false, logLevel }, 'Could not open browser:', err);
87
+ })
88
+ .finally(() => {
89
+ isOpeningBrowser = false;
90
+ });
91
+ };
92
+ function onKeypress(_str, key) {
93
+ if ((key === null || key === void 0 ? void 0 : key.ctrl) && key.name === 'c') {
94
+ cleanup();
95
+ process.kill(process.pid, 'SIGINT');
96
+ return;
97
+ }
98
+ if ((key === null || key === void 0 ? void 0 : key.meta) || (key === null || key === void 0 ? void 0 : key.ctrl) || (key === null || key === void 0 ? void 0 : key.name) !== 's') {
99
+ return;
100
+ }
101
+ openStudio();
102
+ }
103
+ readline.emitKeypressEvents(process.stdin);
104
+ process.stdin.setRawMode(true);
105
+ process.stdin.resume();
106
+ process.stdin.on('keypress', onKeypress);
107
+ process.once('SIGINT', cleanup);
108
+ process.once('exit', cleanup);
109
+ return { registered: true, cleanup };
110
+ };
111
+ exports.registerOpenBrowserShortcut = registerOpenBrowserShortcut;
@@ -47,6 +47,7 @@ const unsubscribe_from_sequence_props_1 = require("./routes/unsubscribe-from-seq
47
47
  const update_available_1 = require("./routes/update-available");
48
48
  const update_default_props_1 = require("./routes/update-default-props");
49
49
  const update_effect_keyframe_settings_1 = require("./routes/update-effect-keyframe-settings");
50
+ const update_element_install_target_1 = require("./routes/update-element-install-target");
50
51
  const update_sequence_keyframe_settings_1 = require("./routes/update-sequence-keyframe-settings");
51
52
  exports.allApiRoutes = {
52
53
  '/api/composition-component-info': composition_component_info_1.compositionComponentInfoHandler,
@@ -92,6 +93,7 @@ exports.allApiRoutes = {
92
93
  '/api/install-package': install_dependency_1.handleInstallPackage,
93
94
  '/api/insert-jsx-element': insert_jsx_element_1.insertJsxElementHandler,
94
95
  '/api/insert-element': insert_element_1.insertElementHandler,
96
+ '/api/update-element-install-target': update_element_install_target_1.updateElementInstallTargetHandler,
95
97
  '/api/download-remote-asset': download_remote_asset_1.downloadRemoteAssetHandler,
96
98
  '/api/undo': undo_1.undoHandler,
97
99
  '/api/redo': redo_1.redoHandler,
@@ -0,0 +1,13 @@
1
+ export declare const ELEMENT_INSTALL_TARGET_MAX_AGE = 5000;
2
+ export type ElementInstallTarget = {
3
+ clientId: string;
4
+ compositionFile: string | null;
5
+ compositionId: string | null;
6
+ canInstall: boolean;
7
+ lastFocusedAt: number | null;
8
+ readOnly: boolean;
9
+ updatedAt: number;
10
+ };
11
+ export declare const updateElementInstallTarget: (newTarget: Omit<ElementInstallTarget, "updatedAt">) => void;
12
+ export declare const getElementInstallTarget: () => ElementInstallTarget | null;
13
+ export declare const clearElementInstallStateForTests: () => void;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearElementInstallStateForTests = exports.getElementInstallTarget = exports.updateElementInstallTarget = exports.ELEMENT_INSTALL_TARGET_MAX_AGE = void 0;
4
+ exports.ELEMENT_INSTALL_TARGET_MAX_AGE = 5000;
5
+ const targetsByClientId = new Map();
6
+ const compareTargets = (a, b) => {
7
+ var _a, _b;
8
+ const aFocusedAt = (_a = a.lastFocusedAt) !== null && _a !== void 0 ? _a : 0;
9
+ const bFocusedAt = (_b = b.lastFocusedAt) !== null && _b !== void 0 ? _b : 0;
10
+ if (aFocusedAt !== bFocusedAt) {
11
+ return aFocusedAt - bFocusedAt;
12
+ }
13
+ return a.updatedAt - b.updatedAt;
14
+ };
15
+ const updateElementInstallTarget = (newTarget) => {
16
+ targetsByClientId.set(newTarget.clientId, {
17
+ ...newTarget,
18
+ updatedAt: Date.now(),
19
+ });
20
+ };
21
+ exports.updateElementInstallTarget = updateElementInstallTarget;
22
+ const getElementInstallTarget = () => {
23
+ const now = Date.now();
24
+ let bestTarget = null;
25
+ for (const [clientId, currentTarget] of targetsByClientId) {
26
+ if (now - currentTarget.updatedAt >= exports.ELEMENT_INSTALL_TARGET_MAX_AGE) {
27
+ targetsByClientId.delete(clientId);
28
+ continue;
29
+ }
30
+ if (bestTarget === null || compareTargets(currentTarget, bestTarget) > 0) {
31
+ bestTarget = currentTarget;
32
+ }
33
+ }
34
+ return bestTarget;
35
+ };
36
+ exports.getElementInstallTarget = getElementInstallTarget;
37
+ const clearElementInstallStateForTests = () => {
38
+ targetsByClientId.clear();
39
+ };
40
+ exports.clearElementInstallStateForTests = clearElementInstallStateForTests;
@@ -2,11 +2,12 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import type { EventSourceEvent } from '@remotion/studio-shared';
3
3
  export type LiveEventsServer = {
4
4
  sendEventToClient: (event: EventSourceEvent) => void;
5
- sendEventToClientId: (clientId: string, event: EventSourceEvent) => void;
5
+ sendEventToClientId: (clientId: string, event: EventSourceEvent) => boolean;
6
6
  router: (request: IncomingMessage, response: ServerResponse) => Promise<void>;
7
7
  closeConnections: () => Promise<void>;
8
8
  addNewClientListener: (cb: () => void) => () => void;
9
9
  };
10
+ export declare const clearPrintPortMessageTimeout: () => void;
10
11
  export type InitialUndoRedoState = {
11
12
  undoFile: string | null;
12
13
  redoFile: string | null;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setLiveEventsListener = exports.waitForLiveEventsListener = exports.makeLiveEventsRouter = void 0;
3
+ exports.setLiveEventsListener = exports.waitForLiveEventsListener = exports.makeLiveEventsRouter = exports.clearPrintPortMessageTimeout = void 0;
4
4
  const server_ready_1 = require("../server-ready");
5
5
  const default_props_watchers_1 = require("./default-props-watchers");
6
6
  const file_existence_watchers_1 = require("./file-existence-watchers");
@@ -9,6 +9,13 @@ const serializeMessage = (message) => {
9
9
  return `data: ${JSON.stringify(message)}\n\n`;
10
10
  };
11
11
  let printPortMessageTimeout = null;
12
+ const clearPrintPortMessageTimeout = () => {
13
+ if (printPortMessageTimeout) {
14
+ clearTimeout(printPortMessageTimeout);
15
+ printPortMessageTimeout = null;
16
+ }
17
+ };
18
+ exports.clearPrintPortMessageTimeout = clearPrintPortMessageTimeout;
12
19
  const makeLiveEventsRouter = (logLevel, getInitialUndoRedoState) => {
13
20
  let clients = [];
14
21
  let newClientListeners = [];
@@ -33,9 +40,7 @@ const makeLiveEventsRouter = (logLevel, getInitialUndoRedoState) => {
33
40
  };
34
41
  clients.push(newClient);
35
42
  newClientListeners.forEach((cb) => cb());
36
- if (printPortMessageTimeout) {
37
- clearTimeout(printPortMessageTimeout);
38
- }
43
+ (0, exports.clearPrintPortMessageTimeout)();
39
44
  request.on('close', () => {
40
45
  (0, default_props_watchers_1.unsubscribeClientDefaultPropsWatchers)(clientId);
41
46
  (0, file_existence_watchers_1.unsubscribeClientFileExistenceWatchers)(clientId);
@@ -43,9 +48,7 @@ const makeLiveEventsRouter = (logLevel, getInitialUndoRedoState) => {
43
48
  clients = clients.filter((client) => client.id !== clientId);
44
49
  // If all clients disconnected, print a comment so user can easily restart it.
45
50
  if (clients.length === 0) {
46
- if (printPortMessageTimeout) {
47
- clearTimeout(printPortMessageTimeout);
48
- }
51
+ (0, exports.clearPrintPortMessageTimeout)();
49
52
  printPortMessageTimeout = setTimeout(() => {
50
53
  (0, server_ready_1.printServerReadyComment)('To restart', logLevel);
51
54
  }, 2500);
@@ -60,9 +63,11 @@ const makeLiveEventsRouter = (logLevel, getInitialUndoRedoState) => {
60
63
  };
61
64
  const sendEventToClientId = (clientId, event) => {
62
65
  const client = clients.find((c) => c.id === clientId);
63
- if (client) {
64
- client.response.write(serializeMessage(event));
66
+ if (!client) {
67
+ return false;
65
68
  }
69
+ client.response.write(serializeMessage(event));
70
+ return true;
66
71
  };
67
72
  const addNewClientListener = (cb) => {
68
73
  newClientListeners.push(cb);
@@ -42,6 +42,7 @@ const no_react_1 = require("remotion/no-react");
42
42
  const parse_ast_1 = require("../../codemods/parse-ast");
43
43
  const get_ast_node_path_1 = require("../../helpers/get-ast-node-path");
44
44
  const import_agnostic_node_path_1 = require("../../helpers/import-agnostic-node-path");
45
+ const parse_keyframe_easing_expression_1 = require("../../helpers/parse-keyframe-easing-expression");
45
46
  const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
46
47
  const jsx_component_identity_1 = require("../jsx-component-identity");
47
48
  const jsx_element_not_found_at_location_error_1 = require("../jsx-element-not-found-at-location-error");
@@ -200,55 +201,7 @@ const getExtrapolateType = (node) => {
200
201
  }
201
202
  return null;
202
203
  };
203
- const getKeyframeEasing = (node) => {
204
- if (node.type === 'TSAsExpression') {
205
- return getKeyframeEasing(node.expression);
206
- }
207
- if (node.type === 'MemberExpression' &&
208
- node.object.type === 'Identifier' &&
209
- node.object.name === 'Easing' &&
210
- node.property.type === 'Identifier' &&
211
- node.property.name === 'linear' &&
212
- node.computed === false) {
213
- return { type: 'linear' };
214
- }
215
- if (node.type !== 'CallExpression' ||
216
- node.callee.type !== 'MemberExpression' ||
217
- node.callee.object.type !== 'Identifier' ||
218
- node.callee.object.name !== 'Easing' ||
219
- node.callee.property.type !== 'Identifier' ||
220
- node.callee.computed) {
221
- return null;
222
- }
223
- if (node.callee.property.name === 'spring') {
224
- if (node.arguments.length > 1) {
225
- return null;
226
- }
227
- const springConfig = node.arguments[0];
228
- if ((springConfig === null || springConfig === void 0 ? void 0 : springConfig.type) === 'ArgumentPlaceholder' ||
229
- (springConfig === null || springConfig === void 0 ? void 0 : springConfig.type) === 'JSXNamespacedName' ||
230
- (springConfig === null || springConfig === void 0 ? void 0 : springConfig.type) === 'SpreadElement') {
231
- return null;
232
- }
233
- return (0, studio_shared_1.parseSpringEasingConfig)(springConfig);
234
- }
235
- if (node.callee.property.name !== 'bezier' || node.arguments.length !== 4) {
236
- return null;
237
- }
238
- const values = node.arguments.map((arg) => {
239
- if (arg.type === 'ArgumentPlaceholder' ||
240
- arg.type === 'JSXNamespacedName' ||
241
- arg.type === 'SpreadElement') {
242
- return null;
243
- }
244
- return getNumericValue(arg);
245
- });
246
- if (values.some((v) => v === null)) {
247
- return null;
248
- }
249
- const [x1, y1, x2, y2] = values;
250
- return { type: 'bezier', x1, y1, x2, y2 };
251
- };
204
+ const getKeyframeEasing = (node) => (0, parse_keyframe_easing_expression_1.parseKeyframeEasingExpression)(node);
252
205
  const getKeyframeEasingArray = ({ easingNode, segments, }) => {
253
206
  if (segments === 0) {
254
207
  return [];
@@ -45,6 +45,9 @@ const makeRelativeImportPath = ({ fromFile, toFile, }) => {
45
45
  return relative;
46
46
  };
47
47
  const validateDimensions = (dimensions) => {
48
+ if (dimensions === null) {
49
+ return;
50
+ }
48
51
  if (!Number.isFinite(dimensions.width) ||
49
52
  !Number.isFinite(dimensions.height) ||
50
53
  dimensions.width <= 0 ||
@@ -0,0 +1,3 @@
1
+ import type { UpdateElementInstallTargetRequest, UpdateElementInstallTargetResponse } from '@remotion/studio-shared';
2
+ import type { ApiHandler } from '../api-types';
3
+ export declare const updateElementInstallTargetHandler: ApiHandler<UpdateElementInstallTargetRequest, UpdateElementInstallTargetResponse>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updateElementInstallTargetHandler = void 0;
4
+ const element_install_state_1 = require("../element-install-state");
5
+ const isFiniteTimestamp = (value) => {
6
+ return value === null || (Number.isFinite(value) && value > 0);
7
+ };
8
+ const updateElementInstallTargetHandler = ({ input }) => {
9
+ if (typeof input.clientId !== 'string' || input.clientId.length === 0) {
10
+ throw new Error('Invalid client ID');
11
+ }
12
+ if (!isFiniteTimestamp(input.lastFocusedAt)) {
13
+ throw new Error('Invalid focus timestamp');
14
+ }
15
+ if (input.compositionFile !== null &&
16
+ typeof input.compositionFile !== 'string') {
17
+ throw new Error('Invalid composition file');
18
+ }
19
+ if (input.compositionId !== null && typeof input.compositionId !== 'string') {
20
+ throw new Error('Invalid composition ID');
21
+ }
22
+ (0, element_install_state_1.updateElementInstallTarget)(input);
23
+ return Promise.resolve({});
24
+ };
25
+ exports.updateElementInstallTargetHandler = updateElementInstallTargetHandler;
@@ -41,6 +41,7 @@ export declare const startServer: (options: {
41
41
  previewSampleRate: number | null;
42
42
  enableCrossSiteIsolation: boolean;
43
43
  askAIEnabled: boolean;
44
+ interactivityEnabled: boolean;
44
45
  forceNew: boolean;
45
46
  rspack: boolean;
46
47
  }) => Promise<StartServerResult>;
@@ -45,6 +45,7 @@ const startServer = async (options) => {
45
45
  poll: options.poll,
46
46
  bufferStateDelayInMilliseconds: options.bufferStateDelayInMilliseconds,
47
47
  askAIEnabled: options.askAIEnabled,
48
+ interactivityEnabled: options.interactivityEnabled,
48
49
  extraPlugins: [watchIgnorePlugin],
49
50
  };
50
51
  let compiler;
@@ -3,11 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateSameOrigin = void 0;
4
4
  const validateSameOrigin = (req) => {
5
5
  const { origin, host } = req.headers;
6
- if (origin) {
7
- const originUrl = new URL(origin);
8
- if (originUrl.host !== host) {
9
- throw new Error('Request from different origin not allowed');
10
- }
6
+ if (typeof origin !== 'string') {
7
+ throw new Error('Request without Origin header not allowed');
8
+ }
9
+ if (typeof host !== 'string') {
10
+ throw new Error('Request without Host header not allowed');
11
+ }
12
+ let originUrl;
13
+ try {
14
+ originUrl = new URL(origin);
15
+ }
16
+ catch (_a) {
17
+ throw new Error('Request from different origin not allowed');
18
+ }
19
+ if (originUrl.origin !== `http://${host}`) {
20
+ throw new Error('Request from different origin not allowed');
11
21
  }
12
22
  };
13
23
  exports.validateSameOrigin = validateSameOrigin;
package/dist/routes.js CHANGED
@@ -46,15 +46,18 @@ const get_file_source_1 = require("./helpers/get-file-source");
46
46
  const get_installed_installable_packages_1 = require("./helpers/get-installed-installable-packages");
47
47
  const resolve_output_path_1 = require("./helpers/resolve-output-path");
48
48
  const api_routes_1 = require("./preview-server/api-routes");
49
+ const element_install_state_1 = require("./preview-server/element-install-state");
49
50
  const get_package_manager_1 = require("./preview-server/get-package-manager");
50
51
  const get_static_file_fallback_hint_1 = require("./preview-server/get-static-file-fallback-hint");
51
52
  const handler_1 = require("./preview-server/handler");
53
+ const parse_body_1 = require("./preview-server/parse-body");
52
54
  const public_folder_1 = require("./preview-server/public-folder");
53
55
  const open_in_editor_1 = require("./preview-server/routes/open-in-editor");
54
56
  const serve_static_1 = require("./preview-server/serve-static");
55
57
  const validate_same_origin_1 = require("./preview-server/validate-same-origin");
56
58
  const watch_ignore_next_change_1 = require("./preview-server/watch-ignore-next-change");
57
59
  const loggedStaticFileHints = new Set();
60
+ const ELEMENT_INSTALL_FOCUS_MAX_AGE = 5 * 60 * 1000;
58
61
  const static404 = (response) => {
59
62
  response.writeHead(404);
60
63
  response.end('The static/ prefix has been changed, this URL is no longer valid.');
@@ -78,6 +81,137 @@ const handleRemotionConfig = (response, remotionRoot) => {
78
81
  response.end(JSON.stringify(body));
79
82
  return Promise.resolve();
80
83
  };
84
+ const isAllowedElementInstallOrigin = (origin) => {
85
+ if (!origin) {
86
+ return false;
87
+ }
88
+ try {
89
+ const url = new URL(origin);
90
+ return ((url.protocol === 'https:' &&
91
+ (url.hostname === 'remotion.dev' ||
92
+ url.hostname === 'www.remotion.dev')) ||
93
+ (url.protocol === 'http:' &&
94
+ (url.hostname === 'localhost' || url.hostname === '127.0.0.1')));
95
+ }
96
+ catch (_a) {
97
+ return false;
98
+ }
99
+ };
100
+ const setElementInstallCorsHeaders = ({ request, response, }) => {
101
+ const { origin } = request.headers;
102
+ if (isAllowedElementInstallOrigin(origin)) {
103
+ response.setHeader('Access-Control-Allow-Origin', origin);
104
+ response.setHeader('Vary', 'Origin');
105
+ response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
106
+ response.setHeader('Access-Control-Allow-Headers', 'Content-Type');
107
+ response.setHeader('Access-Control-Max-Age', '600');
108
+ response.setHeader('Access-Control-Allow-Private-Network', 'true');
109
+ }
110
+ };
111
+ const handleElementInstallOptions = ({ request, response, }) => {
112
+ setElementInstallCorsHeaders({ request, response });
113
+ response.writeHead(isAllowedElementInstallOrigin(request.headers.origin) ? 204 : 403);
114
+ response.end();
115
+ return Promise.resolve();
116
+ };
117
+ const handleElementInstallTarget = ({ request, response, remotionRoot, gitSource, }) => {
118
+ var _a, _b, _c, _d, _e;
119
+ if (!isAllowedElementInstallOrigin(request.headers.origin)) {
120
+ response.writeHead(403);
121
+ response.end(JSON.stringify({ success: false, reason: 'Origin not allowed' }));
122
+ return Promise.resolve();
123
+ }
124
+ const target = (0, element_install_state_1.getElementInstallTarget)();
125
+ const now = Date.now();
126
+ const targetIsLive = target !== null && now - target.updatedAt < element_install_state_1.ELEMENT_INSTALL_TARGET_MAX_AGE;
127
+ const host = (_a = request.headers.host) !== null && _a !== void 0 ? _a : null;
128
+ const port = (_b = host === null || host === void 0 ? void 0 : host.split(':').at(-1)) !== null && _b !== void 0 ? _b : null;
129
+ setElementInstallCorsHeaders({ request, response });
130
+ response.writeHead(200, { 'Content-Type': 'application/json' });
131
+ response.end(JSON.stringify({
132
+ type: 'remotion-studio',
133
+ projectName: (0, studio_shared_1.getProjectName)({
134
+ basename: node_path_1.default.basename,
135
+ gitSource,
136
+ resolvedRemotionRoot: remotionRoot,
137
+ }),
138
+ port: port === null ? null : Number(port),
139
+ lastFocusedAt: (_c = target === null || target === void 0 ? void 0 : target.lastFocusedAt) !== null && _c !== void 0 ? _c : null,
140
+ canInstall: target !== null && target.canInstall && targetIsLive,
141
+ activeCompositionId: (_d = target === null || target === void 0 ? void 0 : target.compositionId) !== null && _d !== void 0 ? _d : null,
142
+ readOnly: (_e = target === null || target === void 0 ? void 0 : target.readOnly) !== null && _e !== void 0 ? _e : false,
143
+ }));
144
+ return Promise.resolve();
145
+ };
146
+ const handleRequestElementInstall = async ({ liveEventsServer, request, response, }) => {
147
+ if (!isAllowedElementInstallOrigin(request.headers.origin)) {
148
+ response.writeHead(403);
149
+ response.end(JSON.stringify({ success: false, reason: 'Origin not allowed' }));
150
+ return;
151
+ }
152
+ setElementInstallCorsHeaders({ request, response });
153
+ response.setHeader('Content-Type', 'application/json');
154
+ try {
155
+ const body = await (0, parse_body_1.parseRequestBody)(request);
156
+ const parsed = (0, studio_shared_1.parseElementDragData)(JSON.stringify({
157
+ type: 'remotion-element',
158
+ version: 1,
159
+ element: body.element,
160
+ }));
161
+ if (parsed === null) {
162
+ response.writeHead(400);
163
+ response.end(JSON.stringify({ success: false, reason: 'Invalid Element payload' }));
164
+ return;
165
+ }
166
+ const target = (0, element_install_state_1.getElementInstallTarget)();
167
+ const now = Date.now();
168
+ const targetIsLive = target !== null &&
169
+ now - target.updatedAt < element_install_state_1.ELEMENT_INSTALL_TARGET_MAX_AGE;
170
+ const targetWasRecentlyFocused = target !== null &&
171
+ target.lastFocusedAt !== null &&
172
+ now - target.lastFocusedAt < ELEMENT_INSTALL_FOCUS_MAX_AGE;
173
+ if (target === null ||
174
+ !target.canInstall ||
175
+ target.compositionFile === null ||
176
+ target.compositionId === null ||
177
+ !targetIsLive ||
178
+ !targetWasRecentlyFocused) {
179
+ response.writeHead(409);
180
+ response.end(JSON.stringify({
181
+ success: false,
182
+ reason: 'No focused writable Remotion Studio composition',
183
+ }));
184
+ return;
185
+ }
186
+ const installRequest = {
187
+ id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
188
+ clientId: target.clientId,
189
+ createdAt: Date.now(),
190
+ compositionFile: target.compositionFile,
191
+ compositionId: target.compositionId,
192
+ element: parsed.element,
193
+ position: null,
194
+ };
195
+ const delivered = liveEventsServer.sendEventToClientId(target.clientId, {
196
+ type: 'element-install-request',
197
+ request: installRequest,
198
+ });
199
+ if (delivered === false) {
200
+ response.writeHead(409);
201
+ response.end(JSON.stringify({
202
+ success: false,
203
+ reason: 'The selected Remotion Studio tab is no longer connected',
204
+ }));
205
+ return;
206
+ }
207
+ response.writeHead(200);
208
+ response.end(JSON.stringify({ success: true, status: 'sent' }));
209
+ }
210
+ catch (err) {
211
+ response.writeHead(500);
212
+ response.end(JSON.stringify({ success: false, reason: err.message }));
213
+ }
214
+ };
81
215
  const handleFallback = async ({ remotionRoot, hash, response, request, getCurrentInputProps, getEnvVariables, publicDir, getRenderQueue, getRenderDefaults, numberOfAudioTags, audioLatencyHint, previewSampleRate, gitSource, logLevel, enableCrossSiteIsolation, }) => {
82
216
  var _a;
83
217
  const acceptsHtml = ((_a = request.headers.accept) !== null && _a !== void 0 ? _a : '').includes('text/html');
@@ -145,12 +279,13 @@ const handleFallback = async ({ remotionRoot, hash, response, request, getCurren
145
279
  sampleRate: previewSampleRate,
146
280
  }));
147
281
  };
148
- const handleFileSource = async ({ method, remotionRoot, search, response, }) => {
282
+ const handleFileSource = async ({ method, remotionRoot, search, response, request, }) => {
149
283
  if (method === 'OPTIONS') {
150
284
  response.writeHead(200);
151
285
  response.end();
152
286
  return Promise.resolve();
153
287
  }
288
+ (0, validate_same_origin_1.validateSameOrigin)(request);
154
289
  if (!search.startsWith('?')) {
155
290
  throw new Error('query must start with ?');
156
291
  }
@@ -252,6 +387,7 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
252
387
  search: url.search,
253
388
  method: request.method,
254
389
  response,
390
+ request,
255
391
  });
256
392
  }
257
393
  if (url.pathname === `${staticHash}/api/add-asset`) {
@@ -270,6 +406,25 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
270
406
  remotionRoot,
271
407
  });
272
408
  }
409
+ if (url.pathname === '/api/element-install-target' ||
410
+ url.pathname === '/api/request-element-install') {
411
+ if (request.method === 'OPTIONS') {
412
+ return handleElementInstallOptions({ request, response });
413
+ }
414
+ if (url.pathname === '/api/element-install-target') {
415
+ return handleElementInstallTarget({
416
+ request,
417
+ response,
418
+ remotionRoot,
419
+ gitSource,
420
+ });
421
+ }
422
+ return handleRequestElementInstall({
423
+ liveEventsServer,
424
+ request,
425
+ response,
426
+ });
427
+ }
273
428
  for (const [key, value] of Object.entries(api_routes_1.allApiRoutes)) {
274
429
  if (url.pathname === key) {
275
430
  return (0, handler_1.handleRequest)({
@@ -6,7 +6,7 @@ export type StartStudioResult = {
6
6
  } | {
7
7
  type: 'already-running';
8
8
  };
9
- export declare const startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, previewSampleRate, enableCrossSiteIsolation, askAIEnabled, forceNew, rspack, }: {
9
+ export declare const startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, previewSampleRate, enableCrossSiteIsolation, askAIEnabled, interactivityEnabled, forceNew, rspack, }: {
10
10
  browserArgs: string;
11
11
  browserFlag: string;
12
12
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
@@ -35,6 +35,7 @@ export declare const startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser
35
35
  binariesDirectory: string | null;
36
36
  forceIPv4: boolean;
37
37
  askAIEnabled: boolean;
38
+ interactivityEnabled: boolean;
38
39
  forceNew: boolean;
39
40
  rspack: boolean;
40
41
  }) => Promise<StartStudioResult>;
@@ -11,6 +11,7 @@ const renderer_1 = require("@remotion/renderer");
11
11
  const file_watcher_1 = require("./file-watcher");
12
12
  const get_network_address_1 = require("./get-network-address");
13
13
  const maybe_open_browser_1 = require("./maybe-open-browser");
14
+ const open_browser_shortcut_1 = require("./open-browser-shortcut");
14
15
  const close_and_restart_1 = require("./preview-server/close-and-restart");
15
16
  const get_absolute_public_dir_1 = require("./preview-server/get-absolute-public-dir");
16
17
  const live_events_1 = require("./preview-server/live-events");
@@ -18,7 +19,7 @@ const public_folder_1 = require("./preview-server/public-folder");
18
19
  const start_server_1 = require("./preview-server/start-server");
19
20
  const server_ready_1 = require("./server-ready");
20
21
  const watch_root_file_1 = require("./watch-root-file");
21
- const startStudio = async ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, previewSampleRate, enableCrossSiteIsolation, askAIEnabled, forceNew, rspack, }) => {
22
+ const startStudio = async ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, maxTimelineTracks, remotionRoot, keyboardShortcutsEnabled, experimentalClientSideRenderingEnabled, relativePublicDir, webpackOverride, poll, getRenderDefaults, getRenderQueue, numberOfAudioTags, queueMethods, previewEntry, gitSource, bufferStateDelayInMilliseconds, binariesDirectory, forceIPv4, audioLatencyHint, previewSampleRate, enableCrossSiteIsolation, askAIEnabled, interactivityEnabled, forceNew, rspack, }) => {
22
23
  try {
23
24
  if (typeof Bun === 'undefined') {
24
25
  process.title = 'node (npx remotion studio)';
@@ -92,6 +93,7 @@ const startStudio = async ({ browserArgs, browserFlag, shouldOpenBrowser, fullEn
92
93
  previewSampleRate,
93
94
  enableCrossSiteIsolation,
94
95
  askAIEnabled,
96
+ interactivityEnabled,
95
97
  forceNew,
96
98
  rspack,
97
99
  });
@@ -120,14 +122,26 @@ const startStudio = async ({ browserArgs, browserFlag, shouldOpenBrowser, fullEn
120
122
  }
121
123
  (0, server_ready_1.printServerReadyComment)('Server ready', logLevel);
122
124
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, 'Building...');
123
- await (0, maybe_open_browser_1.maybeOpenBrowser)({
125
+ const studioUrl = `http://localhost:${port}`;
126
+ const openBrowserShortcut = (0, open_browser_shortcut_1.registerOpenBrowserShortcut)({
124
127
  browserArgs,
125
128
  browserFlag,
126
- shouldOpenBrowser,
127
- url: `http://localhost:${port}`,
129
+ url: studioUrl,
128
130
  logLevel,
129
131
  });
130
- await (0, close_and_restart_1.noOpUntilRestart)();
132
+ try {
133
+ await (0, maybe_open_browser_1.maybeOpenBrowser)({
134
+ browserArgs,
135
+ browserFlag,
136
+ shouldOpenBrowser,
137
+ url: studioUrl,
138
+ logLevel,
139
+ });
140
+ await (0, close_and_restart_1.noOpUntilRestart)();
141
+ }
142
+ finally {
143
+ openBrowserShortcut.cleanup();
144
+ }
131
145
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, 'Closing server to restart...');
132
146
  await liveEventsServer.closeConnections();
133
147
  cleanupLiveEventsListener();
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.486",
6
+ "version": "4.0.488",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "scripts": {
@@ -27,11 +27,11 @@
27
27
  "@babel/parser": "7.24.1",
28
28
  "semver": "7.5.3",
29
29
  "prettier": "3.8.1",
30
- "remotion": "4.0.486",
30
+ "remotion": "4.0.488",
31
31
  "recast": "0.23.11",
32
- "@remotion/bundler": "4.0.486",
33
- "@remotion/renderer": "4.0.486",
34
- "@remotion/studio-shared": "4.0.486",
32
+ "@remotion/bundler": "4.0.488",
33
+ "@remotion/renderer": "4.0.488",
34
+ "@remotion/studio-shared": "4.0.488",
35
35
  "memfs": "3.4.3",
36
36
  "open": "8.4.2"
37
37
  },
@@ -39,7 +39,7 @@
39
39
  "ast-types": "0.16.1",
40
40
  "react": "19.2.3",
41
41
  "@types/semver": "7.5.3",
42
- "@remotion/eslint-config-internal": "4.0.486",
42
+ "@remotion/eslint-config-internal": "4.0.488",
43
43
  "eslint": "9.19.0",
44
44
  "@types/node": "20.12.14",
45
45
  "@typescript/native-preview": "7.0.0-dev.20260217.1"