/* Shared bounded control protocol: media bytes belong exclusively on DataChannel. */ (function (root) { 'use strict'; const CHUNK = 65536; const text = (v, n) => typeof v === 'string' && v.length <= n && !/[\u0000-\u001f]/.test(v); const id = (v) => text(v, 128) && /^[A-Za-z0-9_-]+$/.test(v); function file(v) { if (!v || !id(v.id) || !/^[a-f0-9]{64}$/.test(v.cid) || !Number.isSafeInteger(v.size) || v.size <= 0 || v.size > 4 * 1024 ** 3) return null; if (!text(v.title, 300) || !text(v.channel || '', 200)) return null; const streamMime = typeof v.streamMime === 'string' && /^video\/mp4; codecs="avc1\.[a-f0-9]{6}(, mp4a\.40\.2)?"$/.test(v.streamMime) ? v.streamMime : ''; return { streamMime, extension: /^(mp4|m4a|webm|ogg|opus|mkv|mp3|wav|aac)$/.test(v.extension) ? v.extension : 'mp4', id: v.id, cid: v.cid, size: v.size, title: v.title, channel: v.channel || '', duration: Number.isFinite(v.duration) && v.duration >= 0 ? v.duration : 0 }; } function parse(value) { if (typeof value === 'string') { if (value.length > 65536) return null; try { value = JSON.parse(value); } catch { return null; } } if (!value || typeof value !== 'object' || Array.isArray(value) || value.type !== 'direct') return null; const m = { type: 'direct', action: value.action }; if (value.action === 'request') return id(value.to) && id(value.id) ? { ...m, to: value.to, id: value.id } : null; if (value.action === 'invite') { const f = file(value.file); if (!id(value.to) || !f) return null; return { ...m, to: value.to, file: f }; } if (!['accept', 'decline', 'signal', 'complete'].includes(value.action) || !id(value.token)) return null; m.token = value.token; if (value.action === 'signal') { const d = value.data; if (!d || typeof d !== 'object') return null; if (['offer', 'answer'].includes(d.kind) && typeof d.sdp === 'string' && d.sdp.length <= 60000) m.data = { kind: d.kind, sdp: d.sdp }; else if (d.kind === 'ice' && d.candidate && text(d.candidate.candidate, 2048)) { const c = d.candidate; if (!(c.sdpMid == null || text(c.sdpMid, 64)) || !(c.sdpMLineIndex == null || Number.isInteger(c.sdpMLineIndex) && c.sdpMLineIndex >= 0 && c.sdpMLineIndex < 100)) return null; m.data = { kind: 'ice', candidate: { candidate: c.candidate, sdpMid: c.sdpMid ?? null, sdpMLineIndex: c.sdpMLineIndex ?? null } }; } else return null; } return m; } function offset(v, size) { if (!Number.isSafeInteger(v) || v < 0 || v > size) throw new Error('Invalid resume offset'); return v; } function nextChunk(pos, size) { offset(pos, size); return { start: pos, end: Math.min(size, pos + CHUNK) }; } const api = { CHUNK, file, parse, offset, nextChunk }; if (typeof module !== 'undefined') module.exports = api; root.DirectProtocol = api; }(typeof globalThis !== 'undefined' ? globalThis : this));