import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process"; import { copyFile, mkdir, mkdtemp } from "node:fs/promises"; import { createServer } from "node:http"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { CdpConnection, cleanupRun, createPage, evaluate, launchChrome, reservePort, waitForSample, waitForVersion, withDeadline, } from "./browser-gate-harness"; import { readAppEndpoint, type AppEndpoint as Endpoint, } from "./app-gate-endpoint"; import { NATIVE_CLIENT_PROTOCOL, NATIVE_CLIENT_SUBPROTOCOL, } from "../src/client/native/wire"; const ROOT = resolve(import.meta.dirname, ".."); const BUILD_ROOT = join(ROOT, "build", "go-check"); const SOURCE_TITLE = "Piik Native Gate Source"; const GATE_STUN_URLS = process.env.PIIK_CLIENT_GATE_STUN_URLS?.trim(); interface Probe { protocol: number; processAudio: boolean; systemAudio: boolean; adapters: Array<{ index: number; hardwareH264: Array<{ index: number }>; }>; } interface WindowTarget { kind: "window" | "display"; sourceId: string; pid?: number; creationTime?: string; title: string; } interface CaptureEvidence { starting: boolean; active: boolean; frames: number; keyFrameRequested: boolean; recoveryFrame: boolean; audioReady: boolean; audioFrames: number; audioNonZeroSamples: number; } interface MediaEvidence { captureActive: boolean; connected: boolean; connectedEdges: number; frames: number; edgeFrames: number[]; audioPackets: number; edgeAudioPackets: number[]; audioEnergy: number; edgeAudioEnergy: number[]; audioSamples: number; edgeAudioSamples: number[]; audioBytes: number; edgeAudioBytes: number[]; audioAvailable: boolean; width: number; height: number; localType: string | null; remoteType: string | null; nativeCandidateTypes: string[]; liveProfileUpdated: boolean; pausedProfileUpdated: boolean; error: string | null; } function run(command: string, args: string[], cwd = ROOT): string { const result = spawnSync(command, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], }); if (result.error) throw result.error; if (result.status !== 0) { throw new Error((result.stderr || result.stdout).trim() || command + " failed"); } return result.stdout.trim(); } function powershell(): string { const configured = process.env.PIIK_POWERSHELL?.trim(); if (configured) return configured; return join( process.env.SystemRoot || "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe", ); } function sourceHTML(): string { return [ "" + SOURCE_TITLE + "", "", "", "", ].join(""); } function startPageServer(port: number) { const server = createServer((_request, response) => { response.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", }); response.end(sourceHTML()); }); return new Promise<{ close(): Promise }>((resolveServer, rejectServer) => { server.once("error", rejectServer); server.listen(port, "127.0.0.1", () => { resolveServer({ close: () => new Promise((resolveClose, rejectClose) => { server.close((error) => error ? rejectClose(error) : resolveClose()); }), }); }); }); } function writeCaptureControl(child: ChildProcessWithoutNullStreams, command: string): void { const payload = Buffer.from(command, "ascii"); if (payload.length === 0 || payload.length > 64 || !/^[\x20-\x7e]+$/.test(command)) { throw new Error("Capture control exceeds its SMED bound"); } const frame = Buffer.alloc(32 + payload.length); frame.write("SMED", 0, "ascii"); frame[4] = 2; frame[5] = 7; frame.writeUInt32BE(payload.length, 28); payload.copy(frame, 32); child.stdin.write(frame); } function observeCapture( child: ChildProcessWithoutNullStreams, evidence: CaptureEvidence, ): void { let buffered = Buffer.alloc(0); let framesAtRequest = 0; child.stdout.on("data", (chunk: Buffer) => { buffered = Buffer.concat([buffered, chunk]); while (buffered.length >= 32) { if (buffered.subarray(0, 4).toString("ascii") !== "SMED" || buffered[4] !== 2) { child.kill(); return; } const kind = buffered[5]!; const flags = buffered[6]!; const size = buffered.readUInt32BE(28); const maximum = kind === 3 ? 4 * 1024 : 4 * 1024 * 1024; if ((size === 0 && kind !== 5) || size > maximum) { child.kill(); return; } if (buffered.length < 32 + size) return; const payload = buffered.subarray(32, 32 + size); buffered = buffered.subarray(32 + size); if (kind === 3) { const state = JSON.parse(payload.toString("utf8")) as { state?: string }; evidence.starting ||= state.state === "starting"; evidence.active ||= state.state === "active"; } else if (kind === 2) { evidence.frames += 1; if (!evidence.keyFrameRequested && evidence.frames >= 5) { evidence.keyFrameRequested = true; framesAtRequest = evidence.frames; writeCaptureControl(child, "K -1"); } else if (evidence.keyFrameRequested && evidence.frames > framesAtRequest && flags === 1) { evidence.recoveryFrame = true; } } } }); } function observeAudioCapture( child: ChildProcessWithoutNullStreams, evidence: CaptureEvidence, ): void { let buffered = Buffer.alloc(0); child.stdout.on("data", (chunk: Buffer) => { buffered = Buffer.concat([buffered, chunk]); while (buffered.length >= 32) { if (buffered.subarray(0, 4).toString("ascii") !== "SMED" || buffered[4] !== 2) { child.kill(); return; } const kind = buffered[5]!; const size = buffered.readUInt32BE(28); const maximum = kind === 3 ? 4 * 1024 : 4 * 1024 * 1024; if (size === 0 || size > maximum || buffered.length < 32 + size) { if (size === 0 || size > maximum) child.kill(); return; } const payload = buffered.subarray(32, 32 + size); buffered = buffered.subarray(32 + size); if (kind === 3) { const state = JSON.parse(payload.toString("utf8")) as { state?: string; audio?: boolean; }; evidence.audioReady ||= state.state === "active" && state.audio === true; } else if (kind === 1) { evidence.audioFrames += 1; for (let offset = 0; offset + 1 < payload.length; offset += 2) { if (payload.readInt16LE(offset) !== 0) { evidence.audioNonZeroSamples += 1; } } } } }); } async function stopCapture(child: ChildProcessWithoutNullStreams): Promise { if (child.exitCode !== null) { if (child.exitCode !== 0) throw new Error("Native capture exited unsuccessfully"); return; } writeCaptureControl(child, "Q"); const code = await withDeadline( () => new Promise((resolveExit) => child.once("exit", resolveExit)), Date.now() + 3_000, ); if (code !== 0) throw new Error("Native capture did not stop cleanly"); } async function browserMediaGate(input: { endpoint: Endpoint; protocol: number; subprotocol: string; sourceTitle: string; sourceKind: "window" | "display"; stunUrls: string; }): Promise { let socket: WebSocket | null = null; const peers: RTCPeerConnection[] = []; const result: MediaEvidence = { captureActive: false, connected: false, connectedEdges: 0, frames: 0, edgeFrames: [], audioPackets: 0, edgeAudioPackets: [], audioEnergy: 0, edgeAudioEnergy: [], audioSamples: 0, edgeAudioSamples: [], audioBytes: 0, edgeAudioBytes: [], audioAvailable: false, width: 0, height: 0, localType: null, remoteType: null, nativeCandidateTypes: [], liveProfileUpdated: false, pausedProfileUpdated: false, error: null, }; try { const healthResponse = await fetch(input.endpoint.url + "/health", { cache: "no-store", targetAddressSpace: "loopback", } as RequestInit); const health = await healthResponse.json(); if ( health.protocol !== input.protocol || health.service !== "piik-client" || health.instanceToken !== input.endpoint.instanceToken || health.nativeMedia?.video !== true || health.nativeMedia?.hardwareH264 !== true ) { throw new Error("Native App health is not ready"); } result.audioAvailable = input.sourceKind === "window" ? health.nativeMedia.processAudio === true : health.nativeMedia.systemAudio === true; socket = new WebSocket( "ws://127.0.0.1:" + input.endpoint.port + "/control", [input.subprotocol + "." + input.endpoint.instanceToken], ); await new Promise((resolveOpen, rejectOpen) => { const timer = window.setTimeout( () => rejectOpen(new Error("Native control open timed out")), 5_000, ); socket!.onopen = () => { window.clearTimeout(timer); resolveOpen(); }; socket!.onerror = () => { window.clearTimeout(timer); rejectOpen(new Error("Native control failed to open")); }; }); let requestSequence = 0; let eventError: Error | null = null; const edges = new Map; }>(); const pending = new Map void; reject: (error: Error) => void; timer: number; }>(); const request = (type: string, fields: Record = {}) => { requestSequence += 1; const id = "request_" + String(requestSequence).padStart(8, "0"); return new Promise((resolveRequest, rejectRequest) => { const timer = window.setTimeout(() => { pending.delete(id); rejectRequest(new Error("Native request timed out: " + type)); }, 8_000); pending.set(id, { resolve: resolveRequest, reject: rejectRequest, timer }); socket!.send(JSON.stringify({ version: 9, id, type, ...fields })); }); }; socket.onmessage = (event) => { const message = JSON.parse(String(event.data)); if (typeof message.id === "string") { const active = pending.get(message.id); if (active) { pending.delete(message.id); window.clearTimeout(active.timer); active.resolve(message); } return; } if (message.type === "capture-state" && message.state === "active") { result.captureActive = true; } else if (message.type === "edge-state") { const edge = edges.get(message.connectionId); if (edge) edge.connected = message.state === "connected"; } else if (message.type === "edge-path") { result.localType = message.localType; result.remoteType = message.remoteType; } else if (message.type === "edge-candidate") { const candidate = message.candidate as RTCIceCandidateInit | null; const fields = typeof candidate?.candidate === "string" ? candidate.candidate.split(/\s+/) : []; const typeIndex = fields.indexOf("typ"); if (typeIndex >= 0 && fields[typeIndex + 1] && !result.nativeCandidateTypes.includes(fields[typeIndex + 1]!)) { result.nativeCandidateTypes.push(fields[typeIndex + 1]!); } const edge = edges.get(message.connectionId); if (edge?.remoteDescriptionSet) { void edge.peer.addIceCandidate(candidate).catch((error) => { eventError = error instanceof Error ? error : new Error(String(error)); }); } else if (edge) { edge.queuedCandidates.push(candidate); } } else if (message.type === "share-ended" && message.failed) { eventError = new Error("Native share ended unexpectedly"); } }; socket.onclose = () => { for (const active of pending.values()) { window.clearTimeout(active.timer); active.reject(new Error("Native control closed")); } pending.clear(); }; const ready = await request("hello"); if (ready.type !== "ready") throw new Error("Native hello was rejected"); const options = await request("capture-options"); const adapter = options.adapters?.find( (candidate: { hardwareH264?: unknown[] }) => candidate.hardwareH264?.length, ); const encoder = adapter?.hardwareH264?.[0]; const sources = await request("list-sources"); const target = sources.sources?.find( (candidate: { kind?: string; title?: string }) => candidate.kind === input.sourceKind && (input.sourceKind === "display" || candidate.title?.includes(input.sourceTitle)), ); if (!adapter || !encoder || !target) { throw new Error("Native capture selection is unavailable"); } const shareId = "share_gate_0001"; const connectionId = "edge_gate_0001"; const started = await request("start-share", { shareId, codec: "h264", source: target, audio: result.audioAvailable, adapterIndex: adapter.index, encoderIndex: encoder.index, edgeCapacity: 2, profile: { resolution: "720p", maxFramerate: 30, maxBitrate: 3_000_000, degradationPreference: "balanced", screenAudioQuality: "music", }, }); if (started.shareId !== shareId || started.audio !== result.audioAvailable) { throw new Error("Native share audio capability was not reported consistently"); } const connectEdge = async (nextConnectionId: string) => { const peer = new RTCPeerConnection(); peers.push(peer); if (result.audioAvailable) { peer.addTransceiver("audio", { direction: "recvonly" }); } const video = document.createElement("video"); video.autoplay = true; video.volume = 0.001; video.playsInline = true; document.body.append(video); const stream = new MediaStream(); video.srcObject = stream; const edge = { peer, video, stream, connected: false, remoteDescriptionSet: false, queuedCandidates: [] as Array, }; edges.set(nextConnectionId, edge); peer.ontrack = (event) => { if (!stream.getTrackById(event.track.id)) { stream.addTrack(event.track); } void video.play(); }; peer.onicecandidate = (event) => { void request("edge-candidate", { shareId, connectionId: nextConnectionId, candidate: event.candidate?.toJSON() ?? null, }).catch((error) => { eventError = error instanceof Error ? error : new Error(String(error)); }); }; const offer = await request("prepare-edge", { shareId, connectionId: nextConnectionId, iceServers: [{ urls: input.stunUrls.split(",") }], }); await peer.setRemoteDescription({ type: "offer", sdp: offer.sdp }); edge.remoteDescriptionSet = true; for (const candidate of edge.queuedCandidates.splice(0)) { await peer.addIceCandidate(candidate); } const answer = await peer.createAnswer(); await peer.setLocalDescription(answer); await request("edge-answer", { shareId, connectionId: nextConnectionId, sdp: answer.sdp, }); }; await Promise.all([ connectEdge(connectionId), connectEdge("edge_gate_0002"), ]); const deadline = performance.now() + 20_000; while (performance.now() < deadline) { if (eventError) throw eventError; result.connectedEdges = [...edges.values()].filter((edge) => edge.connected).length; result.connected = result.connectedEdges === 2; result.edgeFrames = [...edges.values()].map( (edge) => edge.video.getVideoPlaybackQuality().totalVideoFrames, ); result.frames = Math.min(...result.edgeFrames); result.edgeAudioPackets = []; result.edgeAudioEnergy = []; result.edgeAudioSamples = []; result.edgeAudioBytes = []; for (const edge of edges.values()) { let packets = 0; let energy = 0; let samples = 0; let bytes = 0; for (const [, report] of await edge.peer.getStats()) { if (report.type === "inbound-rtp" && (report.kind === "audio" || report.mediaType === "audio")) { packets = Math.max(packets, Number(report.packetsReceived) || 0); energy = Math.max(energy, Number(report.totalAudioEnergy) || 0); samples = Math.max(samples, Number(report.totalSamplesReceived) || 0); bytes = Math.max(bytes, Number(report.bytesReceived) || 0); } } result.edgeAudioPackets.push(packets); result.edgeAudioEnergy.push(energy); result.edgeAudioSamples.push(samples); result.edgeAudioBytes.push(bytes); } result.audioPackets = result.edgeAudioPackets.length > 0 ? Math.min(...result.edgeAudioPackets) : 0; result.audioEnergy = result.edgeAudioEnergy.length > 0 ? Math.min(...result.edgeAudioEnergy) : 0; result.audioSamples = result.edgeAudioSamples.length > 0 ? Math.min(...result.edgeAudioSamples) : 0; result.audioBytes = result.edgeAudioBytes.length > 0 ? Math.min(...result.edgeAudioBytes) : 0; const videos = [...edges.values()].map((edge) => edge.video); result.width = videos.every((video) => video.videoWidth === 1280) ? 1280 : 0; result.height = videos.every((video) => video.videoHeight === 720) ? 720 : 0; if ( result.captureActive && result.connected && result.frames >= 30 && (!result.audioAvailable || (result.audioPackets > 10 && result.audioEnergy > 0)) && result.width === 1280 && result.height === 720 && result.nativeCandidateTypes.includes("srflx") ) { break; } await new Promise((resolveWait) => window.setTimeout(resolveWait, 100)); } if ( !result.captureActive || !result.connected || result.frames < 30 || (result.audioAvailable && (result.audioPackets <= 10 || result.audioEnergy <= 0)) || result.width !== 1280 || result.height !== 720 || !result.nativeCandidateTypes.includes("srflx") ) { throw new Error("Native H264 did not reach decoded Browser video"); } const videos = [...edges.values()].map((edge) => edge.video); const framesBeforeLiveUpdate = Math.min( ...videos.map((video) => video.getVideoPlaybackQuality().totalVideoFrames), ); const liveUpdate = await request("update-share", { shareId, profile: { resolution: "1440p", maxFramerate: 60, maxBitrate: 12_000_000, degradationPreference: "maintain-framerate", screenAudioQuality: "very-high", }, }); const liveDeadline = performance.now() + 15_000; while (performance.now() < liveDeadline) { const frames = Math.min( ...videos.map((video) => video.getVideoPlaybackQuality().totalVideoFrames), ); result.liveProfileUpdated = liveUpdate.type === "share-updated" && videos.every((video) => video.videoWidth === 2560 && video.videoHeight === 1440) && frames >= framesBeforeLiveUpdate + 10; if (result.liveProfileUpdated) break; await new Promise((resolveWait) => window.setTimeout(resolveWait, 100)); } if (!result.liveProfileUpdated) { throw new Error("Native live profile update did not reach both Viewers"); } await request("pause-share", { shareId, paused: true }); const pausedUpdate = await request("update-share", { shareId, profile: { resolution: "480p", maxFramerate: 15, maxBitrate: 2_000_000, degradationPreference: "maintain-resolution", screenAudioQuality: "saver", }, }); const framesBeforeResume = Math.min( ...videos.map((video) => video.getVideoPlaybackQuality().totalVideoFrames), ); await request("pause-share", { shareId, paused: false }); const resumeDeadline = performance.now() + 15_000; while (performance.now() < resumeDeadline) { const frames = Math.min( ...videos.map((video) => video.getVideoPlaybackQuality().totalVideoFrames), ); result.pausedProfileUpdated = pausedUpdate.type === "share-updated" && videos.every((video) => video.videoWidth === 854 && video.videoHeight === 480) && frames >= framesBeforeResume + 10; if (result.pausedProfileUpdated) { result.frames = frames; result.width = 854; result.height = 480; break; } await new Promise((resolveWait) => window.setTimeout(resolveWait, 100)); } if (!result.pausedProfileUpdated) { throw new Error("Native paused profile update did not resume both Viewers"); } } catch (error) { result.error = error instanceof Error ? error.message : String(error); } finally { for (const peer of peers) peer.close(); socket?.close(1000, "gate complete"); } return result; } async function main(): Promise { if (process.platform !== "win32") { throw new Error("The native capture gate currently requires Windows"); } if (process.env.PIIK_CLIENT_MEDIA_GATE !== "true") { throw new Error("PIIK_CLIENT_MEDIA_GATE=true is required"); } const chromePath = process.env.CHROME_PATH?.trim(); if (!chromePath) throw new Error("CHROME_PATH is required"); if (!GATE_STUN_URLS) { throw new Error("PIIK_CLIENT_GATE_STUN_URLS is required"); } const profile = await mkdtemp(join(tmpdir(), "piik-client-media-")); // Keep network-capable binaries at a stable repository path. Windows // associates its firewall decision with the full executable path; the // disposable Browser profile remains in the system temp directory. const buildRoot = BUILD_ROOT; await mkdir(buildRoot, { recursive: true }); const pagePort = await reservePort(); const debugPort = await reservePort(); let server: Awaited> | null = null; let chrome: ChildProcessWithoutNullStreams | null = null; let capture: ChildProcessWithoutNullStreams | null = null; let audioCapture: ChildProcessWithoutNullStreams | null = null; let app: ChildProcessWithoutNullStreams | null = null; let cdp: CdpConnection | null = null; let nativePort = 0; let browser: string | null = null; const evidence: CaptureEvidence = { starting: false, active: false, frames: 0, keyFrameRequested: false, recoveryFrame: false, audioReady: false, audioFrames: 0, audioNonZeroSamples: 0, }; let media: MediaEvidence = { captureActive: false, connected: false, connectedEdges: 0, frames: 0, edgeFrames: [], width: 0, height: 0, localType: null, remoteType: null, nativeCandidateTypes: [], liveProfileUpdated: false, pausedProfileUpdated: false, error: "not run", audioPackets: 0, edgeAudioPackets: [], audioEnergy: 0, edgeAudioEnergy: [], audioSamples: 0, edgeAudioSamples: [], audioBytes: 0, edgeAudioBytes: [], audioAvailable: false, }; let error: string | null = null; let cleanup = { browserExited: false, nativeExited: false, serverClosed: false, portsClosed: false, profileRemoved: false, }; try { run(powershell(), [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(ROOT, "native", "capture", "windows", "build.ps1"), "-OutputDirectory", buildRoot, ]); const executable = join(buildRoot, "piik-capture.exe"); const probe = JSON.parse(run(executable, ["--probe"])) as Probe; const adapter = probe.adapters.find((candidate) => candidate.hardwareH264.length > 0); const encoder = adapter?.hardwareH264[0]; if (probe.protocol !== 7 || !adapter || !encoder) { throw new Error("No hardware H264 capture path is available"); } server = await startPageServer(pagePort); chrome = await launchChrome(chromePath, debugPort, profile, [ "--no-first-run", "--no-default-browser-check", "--disable-extensions", "--disable-logging", "--disable-background-timer-throttling", "--disable-backgrounding-occluded-windows", "--disable-renderer-backgrounding", "--window-size=1280,900", ]); chrome.stdout.resume(); chrome.stderr.resume(); const version = await waitForVersion(debugPort, chrome); browser = version.Browser; cdp = await CdpConnection.connect(version.webSocketDebuggerUrl, Date.now() + 10_000); const sourcePage = await createPage( cdp, "http://127.0.0.1:" + pagePort + "/source", undefined, true, ); const audioState = await evaluate( cdp, sourcePage, `(() => { const context = new AudioContext(); const gain = context.createGain(); gain.gain.value = 0.05; const oscillator = context.createOscillator(); oscillator.frequency.value = 440; oscillator.connect(gain).connect(context.destination); oscillator.start(); globalThis.__piikAudioGate = { context, oscillator }; return context.resume().then(() => context.state); })()`, Date.now() + 5_000, ); if (audioState !== "running") { throw new Error("Browser could not start the selected-window audio source"); } const targets = JSON.parse(run(executable, ["--list"])) as WindowTarget[]; const sourceKind: "window" | "display" = process.env.PIIK_CLIENT_MEDIA_SOURCE === "display" ? "display" : "window"; const target = targets.find( (candidate) => candidate.kind === sourceKind && (sourceKind === "display" || candidate.title.includes(SOURCE_TITLE)), ); if (!target) throw new Error("The animated capture target was not enumerated"); capture = spawn(executable, [ "--capture-video", target.kind, target.sourceId, String(target.pid ?? 0), target.creationTime ?? "0", "--adapter-index", String(adapter.index), "--mft-index", String(encoder.index), "--width", "1280", "--height", "720", "--fps", "30", "--bitrate", "3000000", "--preference", "balanced", "--codec", "h264", "--protocol-v7", "--output", "1280", "720", "30", "3000000", ], { stdio: "pipe", windowsHide: true }); capture.stderr.resume(); observeCapture(capture, evidence); await waitForSample( async () => ({ ...evidence }), (sample) => sample.starting && sample.active && sample.frames >= 30 && sample.keyFrameRequested && sample.recoveryFrame, 15_000, ); await stopCapture(capture); capture = null; const audioAvailable = sourceKind === "window" ? probe.processAudio : probe.systemAudio; if (audioAvailable) { audioCapture = spawn(executable, [ "--capture-audio", target.kind, String(target.pid ?? 0), target.creationTime ?? "0", ], { stdio: "pipe", windowsHide: true }); audioCapture.stderr.resume(); observeAudioCapture(audioCapture, evidence); await waitForSample( async () => ({ ...evidence }), (sample) => sample.audioReady && sample.audioFrames >= 10 && sample.audioNonZeroSamples > 0, 10_000, ); await stopCapture(audioCapture); audioCapture = null; } const packageRoot = join(buildRoot, "media-package"); const nativeRoot = join(packageRoot, "runtime", "native"); await mkdir(nativeRoot, { recursive: true }); await copyFile( executable, join(nativeRoot, "piik-capture.exe"), ); const appExecutable = join(packageRoot, "piik-app.exe"); const go = process.env.PIIK_GO?.trim() || "go"; // The App embeds the Vite output, so the Web build precedes the Go build. run(process.env.ComSpec || "cmd.exe", [ "/d", "/s", "/c", "npm run build:web", ], ROOT); run( go, ["build", "-trimpath", "-o", appExecutable, "./cmd/piik-app"], ROOT, ); app = spawn(appExecutable, [ "--site", "http://127.0.0.1:" + pagePort, "--config", join(profile, "client.json"), ], { stdio: "pipe", windowsHide: true, env: { ...process.env, PIIK_CLIENT_GATE_NO_BROWSER: "true" }, }); app.stderr.resume(); const endpoint = await readAppEndpoint(app); nativePort = endpoint.port; const controlPage = await createPage( cdp, "http://127.0.0.1:" + pagePort + "/control", undefined, true, ); media = await evaluate( cdp, controlPage, "((__name) => (" + browserMediaGate.toString() + ")(" + JSON.stringify({ endpoint, protocol: NATIVE_CLIENT_PROTOCOL, subprotocol: NATIVE_CLIENT_SUBPROTOCOL, sourceTitle: SOURCE_TITLE, sourceKind, stunUrls: GATE_STUN_URLS, }) + "))((target) => target)", Date.now() + 70_000, ); if (media.error) throw new Error(media.error); } catch (caught) { error = caught instanceof Error ? caught.message : String(caught); } finally { cleanup = await cleanupRun({ cdp, native: app ?? capture ?? audioCapture, chrome, server, profile, ports: [pagePort, debugPort, ...(nativePort ? [nativePort] : [])], }); } const passed = error === null && evidence.starting && evidence.active && evidence.frames >= 30 && evidence.recoveryFrame && media.error === null && (evidence.audioFrames === 0 || evidence.audioNonZeroSamples > 0) && media.captureActive && media.connected && media.frames >= 30 && media.connectedEdges === 2 && media.edgeFrames.length === 2 && (!media.audioAvailable || (media.audioPackets > 10 && media.audioEnergy > 0)) && media.liveProfileUpdated && media.pausedProfileUpdated && media.width === 854 && media.height === 480 && media.nativeCandidateTypes.includes("srflx") && cleanup.browserExited && cleanup.nativeExited && cleanup.serverClosed && cleanup.portsClosed && cleanup.profileRemoved; process.stdout.write(JSON.stringify({ passed, browser, capture: evidence, media, cleanup, error }) + "\n"); if (!passed) process.exitCode = 1; } await main();