'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const root = path.resolve(__dirname, '..'); const player = fs.existsSync(path.join(root, 'package.json')); const site = player ? 'dc1b14a6-d1ba-4802-b895-553b3c81c677' : 'e00bb594-4c6e-4471-911d-be3f7fdd9fae'; const host = player ? 'player.nenasets.com' : 'tools.nenasets.com'; const route = player ? '/' : '/private/onvif/'; function boot(url) { const scripts = [], tracked = [], listeners = []; const window = { location: new URL(url), umami: {track: (...args) => tracked.push(args)} }; const document = { addEventListener: (type, fn, capture) => listeners.push({type, fn, capture}), createElement: () => ({setAttribute(k,v) { this[k] = v; }}), head: {appendChild: s => { assert.equal(typeof window.altaSafePageview, 'function', 'hook installed before load'); scripts.push(s); }} }; const context = vm.createContext({window, document}); const source = fs.readFileSync(path.join(root, 'static/privacy-pageview.js'), 'utf8'); vm.runInContext(source, context); return {window, scripts, tracked, listeners, context, source}; } test('production pageviews use one manually loaded no-referrer tracker', () => { const b = boot('https://' + host + route + '?customer=PRIVATE#PRIVATE'); assert.equal(b.scripts.length, 1); const s = b.scripts[0]; assert.equal(s.src, 'https://umami.nenasets.com/script.js'); assert.equal(s.referrerPolicy, 'no-referrer'); assert.equal(s.crossOrigin, 'anonymous'); assert.equal(s['data-auto-track'], 'false'); assert.equal(s['data-exclude-search'], 'true'); assert.equal(s['data-exclude-hash'], 'true'); assert.equal(s['data-fetch-credentials'], 'omit'); assert.equal(s['data-domains'], host); assert.equal(s['data-website-id'], site); s.onload(); s.onload(); vm.runInContext(b.source, b.context); assert.equal(b.scripts.length, 1); assert.equal(b.tracked.length, 1); }); test('before-send rebuilds a minimal static pageview; no identity, events or arbitrary data', () => { const b = boot('https://' + host + route); const guard = b.window[b.scripts[0]['data-before-send']]; const dirty = {website:'PRIVATE',url:'PRIVATE',title:'PRIVATE',hostname:'PRIVATE',referrer:'PRIVATE',name:'PRIVATE',data:{filename:'PRIVATE'},id:'PRIVATE',tag:'PRIVATE',screen:'1920x1080',language:'en-US'}; assert.equal(guard('identify', dirty), null); assert.equal(guard('event', dirty), null); delete dirty.name; const safe = JSON.parse(JSON.stringify(guard('event', dirty))); assert.deepEqual(safe, {website:site,hostname:host,url:route,title:player?'Alta Export Player':'ONVIF Tool',referrer:'',screen:'1920x1080',language:'en-US'}); assert.ok(!JSON.stringify(safe).includes('PRIVATE')); b.window.location = new URL('https://' + host + '/private/previous/'); assert.equal(guard('event', dirty), null); }); test('fail closed on DEV, previous, unknown, API, port, alternate host and HTTP routes', () => { const excluded = ['http://' + host + route, 'https://' + host + ':8443' + route, 'https://dev.' + host + route, 'https://127.0.0.1' + route, 'https://' + host + '/private/onvif-dev/', 'https://' + host + '/private/onvif-previous/', 'https://' + host + '/private/onvif/previous/', 'https://' + host + '/private/onvif/api/lookup', 'https://' + host + '/private/onvif/download', 'https://' + host + '/private/onvif/admin', 'https://' + host + '/customer-name', 'https://' + host + '/api/verify-cert']; for (const url of excluded) assert.equal(boot(url).scripts.length, 0, url); }); const downloads = player ? [['releaseAction', 'download_desktop_app', {tool:'alta-export-player',format:'desktop'}]] : [['download-template', 'download_csv_template', {tool:'onvif',format:'csv',kind:'template'}], ['download-report', 'download_csv_report', {tool:'onvif',format:'csv',kind:'report'}]]; test('static download clicks count once without changing native navigation or reading user data', () => { const b = boot('https://' + host + route); b.scripts[0].onload(); vm.runInContext(b.source, b.context); assert.equal(b.listeners.length, 1); assert.equal(b.listeners[0].type, 'click'); assert.equal(b.listeners[0].capture, true); for (const [id, name, data] of downloads) { const control = {id,tagName:'A',disabled:false,getAttribute:()=>null, form:{checkValidity:()=>false},closest:()=>control}; const event = {target:{closest:()=>control},button:0,preventDefault:()=>assert.fail('navigation intercepted')}; const n=b.tracked.length; b.listeners[0].fn(event); assert.equal(b.tracked.length,n+1); assert.equal(b.tracked.at(-1)[0],name); assert.deepEqual(JSON.parse(JSON.stringify(b.tracked.at(-1)[1])),data); const safe=b.window.altaSafePageview('event',{name,data:{url:'PRIVATE',filename:'PRIVATE'},id:'PRIVATE',tag:'PRIVATE'}); assert.equal(safe.name,name); assert.deepEqual(JSON.parse(JSON.stringify(safe.data)),data); assert.ok(!JSON.stringify(safe).includes('PRIVATE')); control.disabled=true;b.listeners[0].fn(event);assert.equal(b.tracked.length,n+1); } b.window.location=new URL('https://'+host+'/private/previous/'); const n=b.tracked.length; b.listeners[0].fn({target:{closest:()=>({id:downloads[0][0],tagName:'A'})},button:0}); assert.equal(b.tracked.length,n); }); test('analytics missing or throwing does not prevent download clicks', () => { const b=boot('https://'+host+route); const e={target:{closest:()=>({id:downloads[0][0],tagName:'A',getAttribute:()=>null})},button:0}; delete b.window.umami;assert.doesNotThrow(()=>b.listeners[0].fn(e)); b.window.umami={track:()=>{throw Error('offline');}};assert.doesNotThrow(()=>b.listeners[0].fn(e)); }); test('known events use an own-property whitelist, native desktop modes are excluded', () => { const b=boot('https://'+host+route); for(const name of ['__proto__','constructor','toString','PRIVATE', {toString:()=>downloads[0][1]}]) assert.equal(b.window.altaSafePageview('event',{name}),null); if(player){ b.window.webavpNative={checkForUpdates:()=>{}}; const n=b.tracked.length; b.listeners[0].fn({target:{closest:()=>({id:'releaseAction',tagName:'A',getAttribute:()=>null})},button:0}); assert.equal(b.tracked.length,n); } }); test('only native application document routes are eligible', () => { for(const suffix of (player ? [''] : ['', 'upload', 'results', 'process', 'clear'])) assert.equal(boot('https://' + host + route + suffix).scripts.length, 1, suffix); });