Files
hyperframes/packages/player/src/runtime-message-handler.ts
T
Miguel Ángel 91bdffffe6 fix(ci): scope LOC check to studio, split useTimelinePlayer + hyperframes-player under 500 LOC (#750)
* refactor: split useTimelinePlayer.ts and hyperframes-player.ts into focused modules (<500 LOC each)

* fix(ci): scope 500 LOC check to packages/studio, add allowlist for grandfathered files

* feat(cli): Linux ARM64 support — auto-install Chromium on DGX Spark / GB10 / Jetson

Chrome Headless Shell has no Linux ARM64 binary. On arm64 Linux:
- Detects the platform automatically
- Tries to auto-install system Chromium via apt-get (works on Ubuntu/Debian ARM)
- Falls back to clear manual instructions with exact commands
- 'hyperframes browser ensure' guides through the setup interactively
- After setup, all render commands work without any flags

* fix(ci): disable Windows Defender real-time monitoring to prevent EPERM builds

Path exclusions are insufficient — Defender re-scans new files created
during bun install before the exclusion takes effect. Disable real-time
monitoring for the entire job duration instead (standard CI practice).

* refactor(studio): split all files >500 LOC + extract useToast, delete allowlist

All 11 large files split into focused modules under 500 LOC.
App.tsx extracted toast logic into useToast hook (493 LOC now).
.filesize-allowlist deleted — no longer needed.

* fix: remove unused imports from split files, extract useToast from App.tsx

App.tsx: 504 → 493 lines (toast logic extracted to useToast hook)
timelineDOM.ts: remove unused imports from re-export pattern
MotionPanel.tsx: remove unused clampStudioCustomEasePoints import
studioMotionOps.ts: remove unused StudioGsapMotionDirection import

* fix(ci): use Set-MpPreference to fully disable Windows Defender (both jobs)

* fix(producer): use node --experimental-strip-types instead of tsx for build:fonts

Eliminates the tsx binary dependency that Windows Defender locks during
bun install, causing EPERM errors. Node 22.6+ strips TypeScript types
natively with no external binary.

* chore: remove .filesize-allowlist — App.tsx is now 493 lines (<500)

* fix(ci): disable Windows Defender before checkout to prevent all EPERM races

* fix(producer): skip build:fonts if fontData.generated.ts already exists

The generated file is tracked in git, so CI doesn't need to regenerate
it. This avoids @fontsource/inter node_modules access on Windows which
triggers EPERM from Defender scanning during bun install.
2026-05-13 01:48:12 +02:00

95 lines
2.9 KiB
TypeScript

/**
* Routes postMessages from the composition iframe to the appropriate handlers.
*
* Accepts the raw MessageEvent and delegates through typed callbacks so the
* web component keeps its state fields private and this module stays stateless.
*/
import {
applyRuntimeStateMessage,
type PlaybackState,
type PlaybackStateCallbacks,
} from "./playback-state.js";
import type { ShaderLoaderState } from "./shader-loader-state.js";
import type { ShaderTransitionState } from "./shader-options.js";
const FPS = 30;
export interface MessageHandlerCallbacks extends PlaybackStateCallbacks {
getPlaybackState: () => PlaybackState;
setPlaybackState: (next: PlaybackState) => void;
getShaderLoadingMode: () => string;
shaderLoader: ShaderLoaderState;
setCompositionSize: (width: number, height: number) => void;
sendControl: (action: string, extra?: Record<string, unknown>) => void;
getIframeDoc: () => Document | null;
}
export function handleRuntimeMessage(
event: MessageEvent,
frameWindow: Window | null,
callbacks: MessageHandlerCallbacks,
): void {
if (event.source !== frameWindow) return;
const data = event.data as Record<string, unknown> | undefined;
if (!data || data["source"] !== "hf-preview") return;
if (data["type"] === "shader-transition-state") {
const state: ShaderTransitionState =
data["state"] && typeof data["state"] === "object"
? (data["state"] as ShaderTransitionState)
: {};
callbacks.shaderLoader.update(state, callbacks.getShaderLoadingMode());
callbacks.dispatchEvent(
new CustomEvent("shadertransitionstate", {
detail: { compositionId: data["compositionId"], state },
}),
);
return;
}
if (data["type"] === "state") {
callbacks.setPlaybackState(
applyRuntimeStateMessage(
{ frame: (data["frame"] as number) ?? 0, isPlaying: !!data["isPlaying"] },
FPS,
callbacks.getPlaybackState(),
callbacks,
),
);
return;
}
if (data["type"] === "media-autoplay-blocked") {
let iframeDoc: Document | null = null;
try {
iframeDoc = callbacks.getIframeDoc();
} catch {
/* cross-origin */
}
callbacks.media.promoteToParentProxy(iframeDoc, (t, opts) =>
callbacks.media.mirrorTime(t, opts),
);
callbacks.sendControl("set-media-output-muted", { muted: true });
return;
}
if (data["type"] === "timeline" && (data["durationInFrames"] as number) > 0) {
if (Number.isFinite(data["durationInFrames"])) {
const pb = callbacks.getPlaybackState();
const duration = (data["durationInFrames"] as number) / FPS;
callbacks.setPlaybackState({ ...pb, duration });
callbacks.updateControlsTime(pb.currentTime, duration);
}
return;
}
if (
data["type"] === "stage-size" &&
(data["width"] as number) > 0 &&
(data["height"] as number) > 0
) {
callbacks.setCompositionSize(data["width"] as number, data["height"] as number);
}
}