// No receiver streams: playback failures are injected into the browser request only. const fs=require('node:fs'); const {execFileSync}=require('node:child_process'); const {chromium}=require('C:/Users/michal/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright'); const ssh=code=>JSON.parse(execFileSync('ssh',['-o','BatchMode=yes','root@192.168.100.25','pct exec 100191 -- python3 -'],{input:code,encoding:'utf8',timeout:30000}).trim().split('\n').at(-1)); const admin=`import sqlite3,json,urllib.request,secrets,pathlib c=sqlite3.connect('file:/var/lib/jellyfin/data/jellyfin.db?mode=ro',uri=True);k=c.execute('SELECT AccessToken FROM ApiKeys LIMIT 1').fetchone()[0];c.close() def api(p,d=None,method=None): r=urllib.request.Request('http://127.0.0.1:8096'+p,headers={'Authorization':'MediaBrowser Token="'+k+'", Client="Analysis", Device="Maintenance", DeviceId="decoder-analysis", Version="1"','Content-Type':'application/json'},data=json.dumps(d).encode() if d is not None else None,method=method) b=urllib.request.urlopen(r,timeout=15).read();return json.loads(b) if b else None `; const origin='http://192.168.100.191:8096',web=origin+'/web/index.html'; const report={checked_utc:new Date().toISOString(),receiver_connections_opened_by_test:0,configuration:[],playback:[]}; const users=[];let browser; const safe=s=>String(s).replace(/([?&](?:api_key|AccessToken)=)[^&\s]+/gi,'$1').slice(0,1800); async function login(page,u){await page.goto(web);await page.locator('#txtManualName').fill(u.name);await page.locator('#txtManualPassword').fill(u.password);await page.getByRole('checkbox').check();await page.getByRole('button',{name:'Sign In',exact:true}).click();await page.waitForURL(/home/,{timeout:20000});await page.waitForFunction(()=>JSON.parse(localStorage.getItem('jellyfin_credentials')||'{}').Servers?.some(s=>s.AccessToken&&s.UserId));await page.waitForTimeout(1000);} async function traceBridge(page){ const source=fs.readFileSync(__dirname+'/web/decoder-channel-playback.js','utf8') .replace('await playbackManager.stop();',`await Promise.resolve(playbackManager.stop()).then(()=>{(window.__leaveTrace??=[]).push('stop resolved');},e=>{(window.__leaveTrace??=[]).push('stop rejected: '+String(e));throw e;});`) .replace("await appRouter.show('livetv?tab=2');",`await Promise.resolve(appRouter.show('livetv?tab=2')).then(()=>{(window.__leaveTrace??=[]).push('route resolved');},e=>{(window.__leaveTrace??=[]).push('route rejected: '+String(e));throw e;});`); await page.route('**/decoder-channel-playback.js',r=>r.fulfill({status:200,contentType:'application/javascript',body:source})); } function create(isAdmin){ const u=ssh(admin+`n='decoder-analysis-'+secrets.token_hex(4);p=secrets.token_urlsafe(24);u=api('/Users/New',{'Name':n});print(json.dumps({'id':u['Id'],'name':n,'password':p}))`);users.push(u); ssh(admin+`api('/Users/${u.id}/Password',{'CurrentPw':'','NewPw':${JSON.stringify(u.password)}}) u=api('/Users/${u.id}');p=u['Policy'];p['IsAdministrator']=${isAdmin?'True':'False'};api('/Users/${u.id}/Policy',p) print('{}')`);return u; } (async()=>{ try{ browser=await chromium.launch({headless:true,channel:'msedge'}); const elevated=create(true),ordinary=create(false); for(const bridge of [true,false]){ const context=await browser.newContext(),page=await context.newPage();const responses=[],errors=[]; if(!bridge)await page.route('**/decoder-channel-playback.js',r=>r.fulfill({status:200,contentType:'application/javascript',body:'window.DecoderChannelPlayback = async () => class { constructor({playbackManager,appRouter}) { this.id="decoder-channel-playback";this.type="integration";window.__diagnosticPlayer=playbackManager;window.__diagnosticRouter=appRouter; } };'})); page.on('pageerror',e=>errors.push(safe(e.stack||e.message))); page.on('response',r=>{const path=new URL(r.url()).pathname;if(/ConfigurationPage|\/Plugins\//i.test(path))responses.push({path,status:r.status()});}); await login(page,elevated);await page.evaluate(()=>location.hash='#/configurationpage?name=enigma2'); let opened=false;try{await page.locator('#txtHostName').waitFor({timeout:12000});await page.waitForFunction(()=>document.querySelector('#txtHostName')?.value==='192.168.10.20',{},{timeout:12000});opened=true;}catch{} const state=await page.evaluate(()=>({route:location.hash,login_fields:document.querySelectorAll('#txtManualPassword').length,configuration_fields:document.querySelectorAll('#txtHostName').length})); let values;if(opened){values={host:await page.locator('#txtHostName').inputValue(),port:await page.locator('#txtStreamingPort').inputValue(),vu_recordings:await page.locator('#chkEnableVuRecordings').isChecked()};} report.configuration.push({bridge,administrator_before_initial_login:true,opened,state,values,responses,errors});console.log('configuration '+bridge+': '+opened);await context.close(); } for(const bridge of [true,false]){ const context=await browser.newContext(),page=await context.newPage();const errors=[];let injected=0; if(bridge)await traceBridge(page); if(!bridge)await page.route('**/decoder-channel-playback.js',r=>r.fulfill({status:200,contentType:'application/javascript',body:'window.DecoderChannelPlayback = async () => class { constructor({playbackManager,appRouter}) { this.id="decoder-channel-playback";this.type="integration";window.__diagnosticPlayer=playbackManager;window.__diagnosticRouter=appRouter; } };'})); await page.addInitScript(()=>{window.__rejections=[];window.addEventListener('unhandledrejection',e=>window.__rejections.push({type:typeof e.reason,message:String(e.reason?.message||e.reason),stack:e.reason?.stack||null}));}); page.on('pageerror',e=>errors.push(safe(e.stack||e.message))); await page.route('**/*PlaybackInfo*',r=>{++injected;return r.fulfill({status:500,contentType:'application/json',body:JSON.stringify({Message:'Diagnostic unavailable source',StatusCode:500})});}); // Defense in depth: an unexpected live-open request is also blocked locally. await page.route('**/LiveTv/LiveStreams/Open*',r=>r.fulfill({status:503,body:'Diagnostic: blocked live open'})); await login(page,ordinary);await page.goto(web+'#/livetv?tab=2');await page.locator('.card[data-type="TvChannel"]').first().waitFor({timeout:15000}); const card=page.locator('.card[data-type="TvChannel"]').filter({has:page.locator('a.textActionButton',{hasText:/^\d+ TVP 1 HD$/})}); if(bridge)await card.locator('a.cardImageContainer').click({position:{x:20,y:20}}); else { // Start the same native player directly; the bridge is the only excluded layer. await page.evaluate(async()=>{const Plugin=await window.DecoderChannelPlayback();window.__nativeStart=Plugin;}); const play=card.locator('[data-action="play"]'); if(await play.count())await play.first().click({force:true});else await card.locator('a.cardImageContainer').click(); if(await page.getByRole('button',{name:'Play',exact:true}).count())await page.getByRole('button',{name:'Play',exact:true}).first().click(); } await page.waitForTimeout(5000); if(bridge&&await page.locator('#decoderPlaybackExit').count()) {await page.locator('#decoderPlaybackExit').click();await page.waitForTimeout(1000);} if(!bridge) {await page.evaluate(async()=>{try{await window.__diagnosticPlayer.stop();}catch{}try{await window.__diagnosticRouter.show('livetv?tab=2');}catch{}});await page.waitForTimeout(1000);} const rejected=await page.evaluate(()=>window.__rejections);report.playback.push({bridge,injected_failures:injected,errors,rejected:rejected.map(r=>({...r,stack:r.stack?safe(r.stack):null})),leave_trace:await page.evaluate(()=>window.__leaveTrace||[])});console.log('failure '+bridge+': '+errors.length);await context.close(); } const context=await browser.newContext(),page=await context.newPage();let injected=0;const errors=[]; await page.addInitScript(()=>{window.__rejections=[];window.addEventListener('unhandledrejection',e=>window.__rejections.push({type:typeof e.reason,message:String(e.reason?.message||e.reason),stack:e.reason?.stack||null}));}); page.on('pageerror',e=>errors.push(safe(e.stack||e.message))); await page.route('**/*PlaybackInfo*',async r=>{++injected;await new Promise(resolve=>setTimeout(resolve,10000));try{await r.fulfill({status:500,body:'Diagnostic unavailable source'});}catch{}}); await page.route('**/LiveTv/LiveStreams/Open*',r=>r.fulfill({status:503,body:'Diagnostic: blocked live open'})); await login(page,ordinary);await page.goto(web+'#/livetv?tab=2');await page.locator('.card[data-type="TvChannel"]').first().waitFor(); await page.locator('.card[data-type="TvChannel"]').first().locator('a.cardImageContainer').click({position:{x:20,y:20}});await page.locator('#decoderPlaybackExit').waitFor(); await page.waitForTimeout(1000);await page.locator('#decoderPlaybackExit').click();await page.waitForTimeout(12000); report.playback.push({bridge:true,scenario:'cancel pending playback request',injected_failures:injected,errors,rejected:await page.evaluate(()=>window.__rejections),final_route:new URL(page.url()).hash});await context.close(); const emptyContext=await browser.newContext(),empty=await emptyContext.newPage();const emptyErrors=[];let emptyResponses=0; empty.on('pageerror',e=>emptyErrors.push(safe(e.stack||e.message))); await empty.route('**/*PlaybackInfo*',r=>{++emptyResponses;return r.fulfill({status:200,contentType:'application/json',body:JSON.stringify({MediaSources:[],PlaySessionId:'00000000000000000000000000000000',ErrorCode:'NoCompatibleStream'})});}); await empty.route('**/LiveTv/LiveStreams/Open*',r=>r.fulfill({status:503,body:'Diagnostic: blocked live open'})); await login(empty,ordinary);await empty.goto(web+'#/livetv?tab=2');await empty.locator('.card[data-type="TvChannel"]').first().waitFor();await empty.locator('.card[data-type="TvChannel"]').first().locator('a.cardImageContainer').click({position:{x:20,y:20}});await empty.waitForTimeout(5000); if(await empty.locator('#decoderPlaybackExit').count())await empty.locator('#decoderPlaybackExit').click();await empty.waitForTimeout(1000); report.playback.push({bridge:true,scenario:'empty media sources and exit',injected_failures:emptyResponses,errors:emptyErrors,final_route:new URL(empty.url()).hash});await emptyContext.close(); }catch(e){report.error=safe(e.stack||e.message);} finally{ if(browser)await browser.close();for(const u of users)ssh(admin+`api('/Users/${u.id}',method='DELETE');pathlib.Path('/var/lib/jellyfin/decoder-integration/users/${u.id.replace(/-/g,'')}.json').unlink(missing_ok=True);print('{}')`); report.finished_utc=new Date().toISOString();fs.writeFileSync(__dirname+'/web-analysis-20261012.json',JSON.stringify(report,null,2)+'\n');console.log(JSON.stringify(report)); } })();