46 lines
2.7 KiB
JavaScript
46 lines
2.7 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { attachAudioBridge } from './archipelago-audio.mjs'
|
|
function fixture() {
|
|
const sent=[], handlers=new Map(), calls=[]
|
|
const parent={postMessage:(data,origin)=>sent.push({data,origin})}
|
|
const host={parent,addEventListener:(name,fn)=>handlers.set(name,fn),removeEventListener:name=>handlers.delete(name)}
|
|
let unlocked=true
|
|
const state={title:'Own track',artist:'Artist',playing:true,shuffle:false,position:4,duration:30}
|
|
const actions=Object.fromEntries(['play','pause','next','previous','shuffle','seek'].map(name=>[name,async value=>calls.push([name,value])]))
|
|
const bridge=attachAudioBridge({parentOrigin:'https://node.test',host,snapshot:()=>state,unlocked:()=>unlocked,actions})
|
|
const receive=(data,origin='https://node.test',source=parent)=>handlers.get('message')?.({source,origin,data:{version:1,...data}})
|
|
return {sent,handlers,calls,state,bridge,receive,lock:()=>{unlocked=false}}
|
|
}
|
|
test('validates parent and nonce; delegates real controls with bounded seeks',async()=>{
|
|
const f=fixture(),session='a'.repeat(32)
|
|
f.receive({type:'archipelago:media-connect',session},'https://foreign.test')
|
|
assert.equal(f.sent.length,1)
|
|
f.receive({type:'archipelago:media-connect',session})
|
|
f.receive({type:'archipelago:media-control',session:'b'.repeat(32),command:'play'})
|
|
f.receive({type:'archipelago:media-control',session,command:'seek',position:99})
|
|
f.receive({type:'archipelago:media-control',session,command:'previous'})
|
|
await new Promise(setImmediate)
|
|
assert.deepEqual(f.calls,[['seek',30],['previous',undefined]])
|
|
assert(f.sent.every(message=>message.origin==='https://node.test'))
|
|
f.lock();f.bridge.publish();assert.equal(f.sent.at(-1).data.available,false)
|
|
assert.equal('title' in f.sent.at(-1).data,false)
|
|
f.bridge.dispose();assert.equal(f.handlers.size,0)
|
|
})
|
|
test('stale queued commands do not control a replacement handshake',async()=>{
|
|
const f=fixture(),session='a'.repeat(32)
|
|
f.receive({type:'archipelago:media-connect',session})
|
|
f.receive({type:'archipelago:media-control',session,command:'next'})
|
|
f.receive({type:'archipelago:media-connect',session:'b'.repeat(32)})
|
|
await new Promise(setImmediate)
|
|
assert.deepEqual(f.calls,[])
|
|
f.bridge.dispose()
|
|
})
|
|
|
|
test('dispose releases listeners even if the app pause action rejects',async()=>{
|
|
const handlers=new Map()
|
|
const host={parent:{postMessage(){}},addEventListener:(name,fn)=>handlers.set(name,fn),removeEventListener:name=>handlers.delete(name)}
|
|
const bridge=attachAudioBridge({parentOrigin:'https://node.test',host,snapshot:()=>null,actions:{pause:async()=>{throw Error('player already closed')}}})
|
|
bridge.dispose();await new Promise(setImmediate);assert.equal(handlers.size,0)
|
|
})
|