import assert from 'node:assert/strict'; import { parseMediaServers, customMediaServers, mediaServers, saveMediaServers, resetMediaServers } from '../src/media-servers.js'; import { uploadPublicMedia, uploadStream, PUBLIC_MEDIA_SERVERS } from '../src/media-upload.js'; const stored = new Map(); globalThis.localStorage = { getItem: k => stored.get(k) ?? null, setItem: (k, v) => stored.set(k, v), removeItem: k => stored.delete(k) }; assert.equal(customMediaServers(), null); assert.deepEqual(mediaServers(PUBLIC_MEDIA_SERVERS), PUBLIC_MEDIA_SERVERS); assert.deepEqual(saveMediaServers('https://FIRST.example/\nhttps://first.example https://second.example/'), ['https://first.example', 'https://second.example']); for (const bad of ['', 'http://host.test', 'https://host.test/path', 'https://user:pass@host.test', 'https://host.test?x=1', 'https://host.test#x', 'https://good.test nope', Array.from({length:7}, (_,i)=>`https://host${i}.test`).join('\n')]) { assert.throws(() => saveMediaServers(bad)); assert.equal(customMediaServers()[0], 'https://first.example', 'invalid input must not overwrite saved servers'); } const calls = []; const fetcher = async (url, opts) => { calls.push(url); if (url.startsWith('https://first.')) return new Response('', {status:503}); if (opts.method === 'HEAD') return new Response(''); return Response.json({url: 'https://second.example/' + 'a'.repeat(64)}); }; const sign = async e => e; await uploadPublicMedia(new File(['picture'], 'photo.png', {type:'image/png'}), sign, {fetcher}); assert(calls.some(u => u.startsWith('https://second.'))); assert(calls.every(u => /^https:\/\/(first|second)\.example\/upload$/.test(u))); calls.length = 0; await uploadStream({master:'master.m3u8', files:new Map([ ['video.mp4', new File(['video'], 'video.mp4', {type:'video/mp4'})], ['master.m3u8', new File(['#EXTM3U\nvideo.mp4'], 'master.m3u8', {type:'application/vnd.apple.mpegurl'})], ])}, sign, {fetcher}); assert(calls.every(u => /^https:\/\/(first|second)\.example\/upload$/.test(u))); await assert.rejects(uploadPublicMedia(new File(['x'], 'x'), sign, {fetcher: async url => { assert(calls.includes(url), 'must not fall back to public hosts'); return new Response('', {status:503}); }})); // Exercise the attachment uploader used by DMs, communities and White Noise. const source = await Bun.file('src/features/messages.js').text(); const start = source.indexOf(' async function uploadEncrypted('); const end = source.indexOf(' const imetaTag', start); const encryptedCalls = []; const uploadEncrypted = new Function('mediaServers', 'MEDIA_SERVERS', 'fetch', 't', source.slice(start, end) + '\nreturn uploadEncrypted;')( mediaServers, ['https://public.example'], async (url, init) => { encryptedCalls.push({url, init}); return Response.json({url: url.replace('/upload', '/cipher')}); }, key => key); const cipher = new Uint8Array([9, 8, 7]); const urls = await uploadEncrypted({pubkey:'a'.repeat(64), signer:{signEvent: sign}}, {cipher, x:'b'.repeat(64)}); assert.deepEqual(urls, ['https://first.example/cipher', 'https://second.example/cipher']); assert(encryptedCalls.every(c => c.init.body === cipher && c.init.headers['content-type'] === 'application/octet-stream')); resetMediaServers(); assert.equal(customMediaServers(), null); assert.deepEqual(mediaServers(PUBLIC_MEDIA_SERVERS), PUBLIC_MEDIA_SERVERS); console.log('✓ Media server validation, persistence, custom-only file/HLS/encrypted attachment uploads, failover, and reset');