@hyperframes/studio 0.6.42 → 0.6.44

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/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-DmiO2Ufp.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CaRE7VOD.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-SKRp8mGz.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.6.42",
3
+ "version": "0.6.44",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,8 +31,8 @@
31
31
  "@codemirror/view": "6.40.0",
32
32
  "@phosphor-icons/react": "^2.1.10",
33
33
  "mediabunny": "^1.45.3",
34
- "@hyperframes/core": "0.6.42",
35
- "@hyperframes/player": "0.6.42"
34
+ "@hyperframes/core": "0.6.44",
35
+ "@hyperframes/player": "0.6.44"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/react": "19",
@@ -46,7 +46,7 @@
46
46
  "vite": "^6.4.2",
47
47
  "vitest": "^3.2.4",
48
48
  "zustand": "^5.0.0",
49
- "@hyperframes/producer": "0.6.42"
49
+ "@hyperframes/producer": "0.6.44"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": "19",
@@ -62,33 +62,46 @@ function parsePositiveNumber(value: string | null): number | null {
62
62
  return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
63
63
  }
64
64
 
65
+ // fallow-ignore-next-line complexity
65
66
  function resolveIframeDuration(iframe: HTMLIFrameElement | null): number | null {
66
- const win = iframe?.contentWindow as PreviewWindow | null;
67
- const playerDuration = win?.__player?.getDuration?.();
68
- if (Number.isFinite(playerDuration) && playerDuration != null && playerDuration > 0) {
69
- return playerDuration;
67
+ try {
68
+ const win = iframe?.contentWindow as PreviewWindow | null;
69
+ const playerDuration = win?.__player?.getDuration?.();
70
+ if (Number.isFinite(playerDuration) && playerDuration != null && playerDuration > 0) {
71
+ return playerDuration;
72
+ }
73
+ } catch {
74
+ /* cross-origin iframe */
70
75
  }
71
76
 
72
- const doc = iframe?.contentDocument;
73
- const root = doc?.querySelector("[data-composition-id]") ?? doc?.documentElement ?? null;
74
- return (
75
- parsePositiveNumber(root?.getAttribute("data-composition-duration") ?? null) ??
76
- parsePositiveNumber(root?.getAttribute("data-duration") ?? null)
77
- );
77
+ try {
78
+ const doc = iframe?.contentDocument;
79
+ const root = doc?.querySelector("[data-composition-id]") ?? doc?.documentElement ?? null;
80
+ return (
81
+ parsePositiveNumber(root?.getAttribute("data-composition-duration") ?? null) ??
82
+ parsePositiveNumber(root?.getAttribute("data-duration") ?? null)
83
+ );
84
+ } catch {
85
+ return null;
86
+ }
78
87
  }
79
88
 
80
89
  function syncIframePlayback(iframe: HTMLIFrameElement | null, shouldPlay: boolean): boolean {
81
- const player = (iframe?.contentWindow as PreviewWindow | null)?.__player;
82
- if (!player) return false;
90
+ try {
91
+ const player = (iframe?.contentWindow as PreviewWindow | null)?.__player;
92
+ if (!player) return false;
93
+
94
+ if (shouldPlay) {
95
+ player.play?.();
96
+ return true;
97
+ }
83
98
 
84
- if (shouldPlay) {
85
- player.play?.();
99
+ player.pause?.();
100
+ player.seek?.(resolveThumbnailSeekTime(resolveIframeDuration(iframe)));
86
101
  return true;
102
+ } catch {
103
+ return false;
87
104
  }
88
-
89
- player.pause?.();
90
- player.seek?.(resolveThumbnailSeekTime(resolveIframeDuration(iframe)));
91
- return true;
92
105
  }
93
106
 
94
107
  function CompCard({
@@ -7,6 +7,35 @@ import { STUDIO_MOTION_PATH } from "../components/editor/studioMotion";
7
7
  import { shouldHandleTimelineToggleHotkey, isEditableTarget } from "../utils/timelineDiscovery";
8
8
  import { shouldIgnoreHistoryShortcut } from "../utils/studioHelpers";
9
9
 
10
+ /** Safely resolves contentWindow for a potentially cross-origin iframe. */
11
+ function iframeContentWindow(iframe: HTMLIFrameElement | null): Window | null {
12
+ try {
13
+ return iframe?.contentWindow ?? null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Handles Cmd/Ctrl+Z (undo) and Cmd/Ctrl+Shift+Z / Ctrl+Y (redo) key events.
21
+ * Returns true if the event was handled, false otherwise.
22
+ */
23
+ // fallow-ignore-next-line complexity
24
+ function handleUndoRedoKey(event: KeyboardEvent, onUndo: () => void, onRedo: () => void): boolean {
25
+ const key = event.key.toLowerCase();
26
+ if (key === "z" && !event.shiftKey) {
27
+ event.preventDefault();
28
+ onUndo();
29
+ return true;
30
+ }
31
+ if ((key === "z" && event.shiftKey) || (event.ctrlKey && !event.metaKey && key === "y")) {
32
+ event.preventDefault();
33
+ onRedo();
34
+ return true;
35
+ }
36
+ return false;
37
+ }
38
+
10
39
  // ── Types ──
11
40
 
12
41
  interface EditHistoryHandle {
@@ -177,18 +206,15 @@ export function useAppHotkeys({
177
206
 
178
207
  // Cmd/Ctrl+Z — undo, Cmd/Ctrl+Shift+Z or Ctrl+Y — redo
179
208
  if (event.metaKey || event.ctrlKey) {
180
- if (!shouldIgnoreHistoryShortcut(event.target)) {
181
- const key = event.key.toLowerCase();
182
- if (key === "z" && !event.shiftKey) {
183
- event.preventDefault();
184
- void handleUndoRef.current();
185
- return;
186
- }
187
- if ((key === "z" && event.shiftKey) || (event.ctrlKey && !event.metaKey && key === "y")) {
188
- event.preventDefault();
189
- void handleRedoRef.current();
190
- return;
191
- }
209
+ if (
210
+ !shouldIgnoreHistoryShortcut(event.target) &&
211
+ handleUndoRedoKey(
212
+ event,
213
+ () => void handleUndoRef.current(),
214
+ () => void handleRedoRef.current(),
215
+ )
216
+ ) {
217
+ return;
192
218
  }
193
219
 
194
220
  // Cmd/Ctrl+1 — sidebar: Compositions tab
@@ -310,13 +336,21 @@ export function useAppHotkeys({
310
336
 
311
337
  const syncPreviewTimelineHotkey = useCallback(
312
338
  (iframe: HTMLIFrameElement | null) => {
313
- const nextWindow = iframe?.contentWindow ?? null;
339
+ const nextWindow = iframeContentWindow(iframe);
314
340
  if (previewHotkeyWindowRef.current === nextWindow) return;
315
341
  if (previewHotkeyWindowRef.current) {
316
- previewHotkeyWindowRef.current.removeEventListener("keydown", previewAppKeyDownHandler);
342
+ try {
343
+ previewHotkeyWindowRef.current.removeEventListener("keydown", previewAppKeyDownHandler);
344
+ } catch {
345
+ /* cross-origin iframe */
346
+ }
317
347
  }
318
348
  previewHotkeyWindowRef.current = nextWindow;
319
- nextWindow?.addEventListener("keydown", previewAppKeyDownHandler, true);
349
+ try {
350
+ nextWindow?.addEventListener("keydown", previewAppKeyDownHandler, true);
351
+ } catch {
352
+ /* cross-origin iframe */
353
+ }
320
354
  },
321
355
  [previewAppKeyDownHandler],
322
356
  );
@@ -324,7 +358,11 @@ export function useAppHotkeys({
324
358
  useEffect(
325
359
  () => () => {
326
360
  if (previewHotkeyWindowRef.current) {
327
- previewHotkeyWindowRef.current.removeEventListener("keydown", previewAppKeyDownHandler);
361
+ try {
362
+ previewHotkeyWindowRef.current.removeEventListener("keydown", previewAppKeyDownHandler);
363
+ } catch {
364
+ /* cross-origin iframe */
365
+ }
328
366
  previewHotkeyWindowRef.current = null;
329
367
  }
330
368
  },
@@ -336,16 +374,11 @@ export function useAppHotkeys({
336
374
  const handleHistoryHotkey = useCallback((event: KeyboardEvent) => {
337
375
  if (!(event.metaKey || event.ctrlKey)) return;
338
376
  if (shouldIgnoreHistoryShortcut(event.target)) return;
339
- const key = event.key.toLowerCase();
340
- if (key === "z" && !event.shiftKey) {
341
- event.preventDefault();
342
- void handleUndoRef.current();
343
- return;
344
- }
345
- if ((key === "z" && event.shiftKey) || (event.ctrlKey && !event.metaKey && key === "y")) {
346
- event.preventDefault();
347
- void handleRedoRef.current();
348
- }
377
+ handleUndoRedoKey(
378
+ event,
379
+ () => void handleUndoRef.current(),
380
+ () => void handleRedoRef.current(),
381
+ );
349
382
  }, []);
350
383
 
351
384
  const syncPreviewHistoryHotkey = useCallback(
@@ -353,7 +386,7 @@ export function useAppHotkeys({
353
386
  previewHistoryHotkeyCleanupRef.current?.();
354
387
  previewHistoryHotkeyCleanupRef.current = null;
355
388
 
356
- const win = iframe?.contentWindow ?? null;
389
+ const win = iframeContentWindow(iframe);
357
390
  let doc: Document | null = null;
358
391
  try {
359
392
  doc = iframe?.contentDocument ?? null;
@@ -362,10 +395,18 @@ export function useAppHotkeys({
362
395
  }
363
396
  if (!win && !doc) return;
364
397
 
365
- win?.addEventListener("keydown", handleHistoryHotkey, true);
398
+ try {
399
+ win?.addEventListener("keydown", handleHistoryHotkey, true);
400
+ } catch {
401
+ /* cross-origin */
402
+ }
366
403
  doc?.addEventListener("keydown", handleHistoryHotkey, true);
367
404
  previewHistoryHotkeyCleanupRef.current = () => {
368
- win?.removeEventListener("keydown", handleHistoryHotkey, true);
405
+ try {
406
+ win?.removeEventListener("keydown", handleHistoryHotkey, true);
407
+ } catch {
408
+ /* cross-origin */
409
+ }
369
410
  doc?.removeEventListener("keydown", handleHistoryHotkey, true);
370
411
  };
371
412
  },
package/src/main.tsx CHANGED
@@ -22,8 +22,13 @@ function errorProps(value: unknown): {
22
22
  return { error_message: String(value), error_name: null, stack_trace: null };
23
23
  }
24
24
 
25
- function isCompositionAssetError(msg: string): boolean {
26
- return msg.includes("Error fetching") && (msg.includes("404") || msg.includes("Not Found"));
25
+ // fallow-ignore-next-line complexity
26
+ function isCompositionAssetError(msg: string, name: string | null): boolean {
27
+ if (msg.includes("Error fetching") && (msg.includes("404") || msg.includes("Not Found")))
28
+ return true;
29
+ if (name === "EncodingError" || msg.includes("unsupported or unrecognizable format")) return true;
30
+ if (msg.includes("MEDIA_ERR_SRC_NOT_SUPPORTED")) return true;
31
+ return false;
27
32
  }
28
33
 
29
34
  const ERROR_CAP = 50;
@@ -57,9 +62,22 @@ window.addEventListener("error", (event) => {
57
62
  });
58
63
  });
59
64
 
65
+ let filteredAssetErrorCount = 0;
66
+
67
+ // fallow-ignore-next-line complexity
60
68
  window.addEventListener("unhandledrejection", (event) => {
61
69
  const props = errorProps(event.reason);
62
- if (isCompositionAssetError(props.error_message)) return;
70
+ if (isCompositionAssetError(props.error_message, props.error_name)) {
71
+ filteredAssetErrorCount++;
72
+ if (filteredAssetErrorCount === 1 || filteredAssetErrorCount % 100 === 0) {
73
+ trackStudioEvent("composition_asset_error_filtered", {
74
+ error_message: props.error_message.slice(0, 200),
75
+ error_name: props.error_name,
76
+ total_filtered: filteredAssetErrorCount,
77
+ });
78
+ }
79
+ return;
80
+ }
63
81
 
64
82
  rejectionCount++;
65
83
  if (rejectionCount > ERROR_CAP) {
@@ -182,23 +182,38 @@ export function usePlaybackKeyboard({
182
182
  playbackKeyDownRef.current = handlePlaybackKeyDown;
183
183
  playbackKeyUpRef.current = handlePlaybackKeyUp;
184
184
 
185
+ // fallow-ignore-next-line complexity
185
186
  const attachIframeShortcutListeners = useCallback(() => {
186
187
  iframeShortcutCleanupRef.current?.();
187
188
  iframeShortcutCleanupRef.current = null;
188
189
 
189
- const iframeWin = iframeRef.current?.contentWindow;
190
- const iframeDoc = iframeRef.current?.contentDocument;
190
+ let iframeWin: Window | null = null;
191
+ let iframeDoc: Document | null = null;
192
+ try {
193
+ iframeWin = iframeRef.current?.contentWindow ?? null;
194
+ iframeDoc = iframeRef.current?.contentDocument ?? null;
195
+ } catch {
196
+ return;
197
+ }
191
198
  if (!iframeWin && !iframeDoc) return;
192
199
 
193
200
  const handleIframeKeyDown = (e: KeyboardEvent) => playbackKeyDownRef.current(e);
194
201
  const handleIframeKeyUp = (e: KeyboardEvent) => playbackKeyUpRef.current(e);
195
- iframeWin?.addEventListener("keydown", handleIframeKeyDown, true);
196
- iframeWin?.addEventListener("keyup", handleIframeKeyUp, true);
202
+ try {
203
+ iframeWin?.addEventListener("keydown", handleIframeKeyDown, true);
204
+ iframeWin?.addEventListener("keyup", handleIframeKeyUp, true);
205
+ } catch {
206
+ /* cross-origin iframe */
207
+ }
197
208
  iframeDoc?.addEventListener("keydown", handleIframeKeyDown, true);
198
209
  iframeDoc?.addEventListener("keyup", handleIframeKeyUp, true);
199
210
  iframeShortcutCleanupRef.current = () => {
200
- iframeWin?.removeEventListener("keydown", handleIframeKeyDown, true);
201
- iframeWin?.removeEventListener("keyup", handleIframeKeyUp, true);
211
+ try {
212
+ iframeWin?.removeEventListener("keydown", handleIframeKeyDown, true);
213
+ iframeWin?.removeEventListener("keyup", handleIframeKeyUp, true);
214
+ } catch {
215
+ /* cross-origin iframe */
216
+ }
202
217
  iframeDoc?.removeEventListener("keydown", handleIframeKeyDown, true);
203
218
  iframeDoc?.removeEventListener("keyup", handleIframeKeyUp, true);
204
219
  };