import assert from 'node:assert/strict'; import { finalizeEvent, generateSecretKey, getPublicKey, verifyEvent } from 'nostr-tools/pure'; import { mediaUploadSigner, uploadPublicMedia, uploadStream, MediaUploadError } from '../src/media-upload.js'; const walletKey = generateSecretKey(), loginKey = generateSecretKey(); let walletSigns = 0, loginSigns = 0, resumes = 0; const wallet = { nostrSign: event => { walletSigns++; return finalizeEvent(event, walletKey); } }; const signer = { signEvent(event) { loginSigns++; return finalizeEvent(event, loginKey); } }; const login = {pubkey: getPublicKey(loginKey), signer}; let id = login, resumed = signer; const hook = name => name === 'nostrLoginIdentity' ? id : (resumes++, resumed); const file = new File(['image'], 'test.png', {type: 'image/png'}); const seen = []; // Haven's owner/allowlist rule: only this login key can upload. const fetcher = async (url, init) => { const event = JSON.parse(atob(init.headers.authorization.slice(6))); assert(verifyEvent(event)); seen.push(event.pubkey); if (event.pubkey !== login.pubkey) return new Response('', {status:403, headers:{'x-reason':'only media signed by whitelisted pubkeys are allowed'}}); return Response.json({url:'https://haven.example/' + event.tags.find(t => t[0] === 'x')[1]}); }; const options = {servers:['https://haven.example'], fetcher}; await assert.rejects(uploadPublicMedia(file, event => wallet.nostrSign(event), options), e => e instanceof MediaUploadError && e.code === 'unauthorized'); const walletBefore = walletSigns; await uploadPublicMedia(file, await mediaUploadSigner(wallet, hook), options); assert.equal(walletSigns, walletBefore); assert.equal(loginSigns, 1); assert.equal(resumes, 0); id = {...login, signer:null}; await uploadPublicMedia(file, await mediaUploadSigner(wallet, hook), options); assert.equal(resumes, 1); resumed = null; await assert.rejects(mediaUploadSigner(wallet, hook), e => e.code === 'no-identity'); assert.equal(walletSigns, walletBefore, 'an unavailable login signer must never fall back to the wallet'); id = null; const nativeEvent = await (await mediaUploadSigner(wallet, hook))({kind:24242, created_at:1, tags:[], content:''}); assert.equal(nativeEvent.pubkey, getPublicKey(walletKey)); id = login; await uploadStream({master:'master.m3u8', files:new Map([ ['clip.mp4', new File(['clip'], 'clip.mp4', {type:'video/mp4'})], ['master.m3u8', new File(['#EXTM3U\nclip.mp4'], 'master.m3u8', {type:'application/vnd.apple.mpegurl'})], ])}, await mediaUploadSigner(wallet, hook), options); assert(seen.slice(1).every(pk => pk === login.pubkey)); // Exercise the app's actual routing for both regular files and HLS videos. const app = await Bun.file('src/app.js').text(); const start = app.indexOf('async function publicMediaUpload(file) {'); const end = app.indexOf('// Whether the wizard', start); const body = app.slice(start, end).replace("await import('./video-compress.js')", 'video'); const stream = {dim:'10x10',seconds:2}; const signs = []; const ui = {}; const upload = new Function('wallet','featureHook','mediaUploadSigner','uploadPublicMedia','uploadStream','MediaUploadError','mediaProgress','ui','t','video', body + '\nreturn publicMediaUpload;')( wallet, hook, mediaUploadSigner, async (file, sign) => { signs.push(await sign({kind:24242, created_at:1, tags:[], content:''})); return 'photo'; }, async (stream, sign) => { signs.push(await sign({kind:24242, created_at:1, tags:[], content:''})); return 'video'; }, MediaUploadError, () => {}, ui, key => key, {streamVideo: async () => stream}); assert.equal(await upload(file), 'photo'); assert.equal((await upload(new File(['video'], 'clip.mp4', {type:'video/mp4'}))).url, 'video'); assert(signs.every(e => e.pubkey === login.pubkey)); assert.equal(ui.mediaProgress, null); console.log('✓ Haven-style allowlist: login identity, signer resume, no wallet fallback, native identity and app file/HLS routing');