// Only the explicit numeric projection below can leave the browser. Never // pass a receipt, device object, error string, frame, PCM or DOM value to fetch. export function reportMetrics(receipt) { const finite=value=>typeof value==='number'&&Number.isFinite(value)&&value>=0 ? value : null; const v=receipt.video??{},a=receipt.audio??{},s=receipt.settings??{},p=receipt.paired??{}; const c=p.continuity??{},pilot=p.pilot??{}; const estimate=typeof p.estimatedMedianSignedMs==='number'&&Number.isFinite(p.estimatedMedianSignedMs)?p.estimatedMedianSignedMs:null; return Object.fromEntries(Object.entries({ elapsed_ms:receipt.elapsedMs, video_width:s.video?.width,video_height:s.video?.height,video_fps:s.video?.frameRate, audio_rate:s.audio?.sampleRate,audio_channels:s.audio?.channelCount, video_callbacks:v.callbacks,video_unobserved:v.unobservedPresentedFrames, video_resets:v.clockOrCounterResets,video_interval_p95_ms:v.callbackIntervalP95Ms, video_gap_max_ms:v.callbackGapMaxMs,audio_input_frames:a.inputFrames, audio_processed_frames:a.processedFrames,audio_zero_frames:a.exactZeroFrames, audio_rms:a.rms,audio_peak:a.peak,audio_context_rate:a.sampleRate, paired_audio_signals:p.audioSignals,paired_video_signals:p.videoSignals, paired_matches:p.pairs,paired_ambiguous:p.ambiguous,paired_overflow:p.overflow, paired_estimate_audio_late_ms:estimate===null?null:Math.max(0,estimate), paired_estimate_audio_early_ms:estimate===null?null:Math.max(0,-estimate), marker_frames:c.frames,marker_bad:c.badMarkers,marker_ambiguous:c.ambiguousGaps, marker_missing_fraction:c.missingFraction,marker_duplicate_fraction:c.duplicateFraction, pilot_windows:pilot.windows,pilot_gap_max_ms:pilot.gapMaxMs, pilot_missing_fraction:pilot.missingFraction,pilot_trailing_gap_ms:pilot.trailingGapMs, pilot_discontinuities:pilot.discontinuities, }).map(([key,value])=>[key,finite(value)])); } export function stopState(reason) { if(reason==='120 seconds complete')return 'completed'; if(reason==='page hidden; timing invalid')return 'hidden'; if(reason==='page closed')return 'closed'; if(reason==='device ended'||reason==='device selection changed')return 'device-change'; if(reason==='operator'||reason==='selecting devices')return 'stopped'; return 'failed'; } export class RunReporter { constructor(status,request=globalThis.fetch.bind(globalThis)) { this.status=status;this.request=request;this.pending=null;this.busy=false; this.terminal=false;this.sequence=0;this.lastProgress=-Infinity;this.available=true; status('Reporting: connecting…'); this.ready=this.open(); } async open() { try { const r=await this.request('/av-test/api/start',{method:'POST',credentials:'omit', headers:{'Content-Type':'application/json'},body:'{}',signal:AbortSignal.timeout(5000)}); if(!r.ok)throw new Error('start rejected'); const data=await r.json(); if(!/^[a-f0-9]{32}$/.test(data.run_id)||!/^[A-Za-z0-9_-]{43}$/.test(data.write_token))throw new Error('bad receipt'); this.runId=data.run_id;this.token=data.write_token; this.status(`Reporting enabled · test ${this.runId.slice(0,8)}`); } catch { this.available=false;this.status('Reporting unavailable · Save metrics remains available'); } } observe(receipt) { if(this.terminal)return; // Paired tests reset elapsed time when the known source first arrives. // A long signal wait must not suppress the next minute of progress. if(receipt.elapsedMs=400&&response.status<500)break; } catch { /* No raw exception or endpoint response is displayed. */ } } if(report.state!=='running') { this.status(delivered ? `Result reported · test ${this.runId.slice(0,8)}` : 'Result not reported · use Save metrics'); } else if(!delivered) { this.status('Reporting interrupted · local test continues'); } } } finally {this.busy=false;} } }