import { createInterface } from "node:readline"; import { createRequire } from "node:module"; import assert from "node:assert/strict"; import { validateBaseUrl } from "../packages/harness/dist/index.js"; import { parseHls } from "../packages/harness/dist/media/hls.js"; // Read-only acceptance preflight. No signed URLs, auth tokens or subtitles are logged. const require = createRequire(new URL("../packages/harness/package.json", import.meta.url)); const { createClient } = require("@supabase/supabase-js"); const dramaId = "06aa396d-175a-48ed-ad76-02786ef4d0a9"; const cmsBase = "https://swave.pencil-stub.com/swave-api/api/v1/cms/playback/"; const reader = createInterface({ input: process.stdin }); console.log("Provide CMS email/password/publishableKey via hidden stdin."); let stage = "login"; async function boundedText(response) { assert.equal(response.status, 200); const stream = response.body.getReader(); const chunks = []; let length = 0; try { for (;;) { const {done,value} = await stream.read(); if (done) break; length += value.byteLength; assert(length <= 2 * 1024 * 1024); chunks.push(value); } } finally { await stream.cancel().catch(() => {}); } return Buffer.concat(chunks).toString("utf8"); } try { const input = JSON.parse((await reader[Symbol.asyncIterator]().next()).value); reader.close(); const client = createClient("https://lmcysqgfhoxppwuorsmu.supabase.co", input.publishableKey, { auth:{persistSession:false,autoRefreshToken:false,detectSessionInUrl:false}, global:{fetch:(url,init)=>fetch(url,{...init,redirect:"error",signal:AbortSignal.timeout(20_000)})}, }); const {data:auth,error:authError} = await client.auth.signInWithPassword({email:input.email,password:input.password}); input.email = ""; input.password = ""; assert(!authError && auth.session); const {data:user,error:userError} = await client.auth.getUser(auth.session.access_token); assert(!userError && user.user?.app_metadata.role === "admin"); stage = "inventory"; const {data:drama,error:dramaError} = await client.from("drama").select("id,title,language,total_episodes").eq("id",dramaId).single(); assert(!dramaError && drama.id === dramaId && drama.language === "en"); const {data:episodes,error:episodeError} = await client.from("episode").select("id,episode_no,drama_id").eq("drama_id",dramaId).order("episode_no").range(0,999); assert(!episodeError && episodes.length === drama.total_episodes); assert(episodes.every((e,i)=>e.drama_id===dramaId && e.episode_no===i+1)); const sampled = [episodes[0],episodes[Math.floor(episodes.length/2)],episodes.at(-1)]; const checks = []; for (const episode of sampled) { stage = `episode_${episode.episode_no}_signing`; const headers = {Authorization:`Bearer ${auth.session.access_token}`}; const signedResponse = await fetch(cmsBase+episode.id,{headers,redirect:"error",signal:AbortSignal.timeout(20_000)}); const signed = JSON.parse(await boundedText(signedResponse)); const url = new URL(signed.url); assert(url.protocol==="https:" && !url.username && !url.password && !url.hash && url.pathname.endsWith(".m3u8")); await validateBaseUrl(url.origin); assert(Date.parse(signed.expires_at)>Date.now()); stage = `episode_${episode.episode_no}_manifest`; const manifest = await boundedText(await fetch(url,{redirect:"error",signal:AbortSignal.timeout(20_000)})); assert(manifest.trimStart().startsWith("#EXTM3U")); console.log(JSON.stringify({episodeNo:episode.episode_no,cdnOrigin:url.origin,master:manifest.includes("#EXT-X-STREAM-INF"),segments:(manifest.match(/#EXTINF:/g)??[]).length,encrypted:manifest.includes("#EXT-X-KEY"),byteranges:manifest.includes("#EXT-X-BYTERANGE"),fragmentedMp4:manifest.includes("#EXT-X-MAP")})); const parsed = parseHls(manifest); if (parsed.kind === "master") { const child = new URL(parsed.variants[0], url); console.log(JSON.stringify({sameOrigin:child.origin===url.origin,childSameQuery:!child.search||child.search===url.search,childWithinEpisode:child.pathname.startsWith(url.pathname.slice(0,url.pathname.lastIndexOf("/")+1)),childExtension:child.pathname.split(".").at(-1)})); assert(child.origin === url.origin && child.pathname.startsWith(url.pathname.slice(0,url.pathname.lastIndexOf("/")+1)) && !child.username && !child.password && !child.hash); console.log(JSON.stringify({queryKeys:[...child.searchParams.keys()],authShape:/^\d+-[a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-fA-F0-9]+$/.test(child.searchParams.get("auth_key")??"")})); const resolved = child; const variant = await boundedText(await fetch(resolved,{redirect:"error",signal:AbortSignal.timeout(20_000)})); console.log(JSON.stringify({tags:[...new Set(variant.split("\n").filter(line=>line.startsWith("#")).map(line=>line.split(":")[0]))],segments:(variant.match(/#EXTINF:/g)??[]).length,ends:variant.includes("#EXT-X-ENDLIST"),parseAccepted:(()=>{try{parseHls(variant);return true;}catch{return false;}})()})); } stage = `episode_${episode.episode_no}_subtitle`; const subtitle = await boundedText(await fetch(cmsBase+episode.id+"/subtitles/en.vtt",{headers,redirect:"error",signal:AbortSignal.timeout(20_000)})); assert(subtitle.trimStart().startsWith("WEBVTT") && subtitle.includes("-->")); checks.push({episodeNo:episode.episode_no,manifestStatus:200,hlsHeader:true,englishWebVtt:true}); } console.log(JSON.stringify({result:"PASS",scope:"CMS source preflight only",drama,checks,checkedAt:new Date().toISOString(),mediaDownloaded:false,modelContentSent:false,videoAnalysisComplete:false})); } catch { console.error(JSON.stringify({result:"FAIL",stage})); process.exitCode = 1; } finally { reader.close(); }