Chapter 8: Debugging JavaScript#

Introduction#

Debugging JavaScript applications on BrightSign players requires specialized techniques due to the embedded Chromium environment and Node.js runtime. This chapter covers comprehensive debugging strategies for both browser-based HTML content and Node.js applications running on BrightSign hardware.

Browser DevTools#

Remote Debugging Setup#

BrightSign players support Chromium’s remote debugging protocol, enabling Chrome DevTools access over the network.

Enabling Web Inspector via BrightScript:

' Enable web inspector registry key (required for BOS 8.5.31+)
reg = CreateObject("roRegistrySection", "html")
reg.Write("enable_web_inspector", "1")
reg.Flush()

' Configure roHtmlWidget with inspector
r = CreateObject("roRectangle", 0, 0, 1920, 1080)
config = {
    url: "file:///sd:/index.html"
    inspector_server: { port: 2999 }
    nodejs_enabled: true
}
widget = CreateObject("roHtmlWidget", r, config)
widget.Show()

Accessing Remote Inspector:

  1. Open Chrome browser on development machine
  2. Navigate to chrome://inspect/#devices
  3. Click Configure and add player IP with port: 192.168.1.100:2999
  4. Click Inspect when page appears in device list

Security Warning: Disable web inspector in production deployments. The console logs data to memory even when disconnected, causing memory exhaustion and crashes.

Console Access#

JavaScript Console Methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Standard output
console.log('Debug message:', variable);
console.info('Information:', data);
console.warn('Warning:', issue);
console.error('Error:', error);

// Grouped logging
console.group('Processing Data');
console.log('Step 1: Load');
console.log('Step 2: Transform');
console.groupEnd();

// Table formatting
console.table([
    { id: 1, name: 'Item A' },
    { id: 2, name: 'Item B' }
]);

// Performance timing
console.time('operation');
performExpensiveTask();
console.timeEnd('operation');

Accessing Console Output:

  • Remote DevTools: chrome://inspect connected to player
  • Local logs: Available via Diagnostic Web Server (DWS) logs endpoint
  • Serial/SSH/Telnet: Standard output visible in terminal

Network Inspection#

Monitor network requests through DevTools Network tab:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Track XHR requests
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
    console.log('Response received:', this.responseText);
});
xhr.addEventListener('error', function() {
    console.error('Request failed:', this.status);
});
xhr.open('GET', '/api/data');
xhr.send();

// Fetch API with detailed logging
async function fetchWithLogging(url) {
    console.log('Fetching:', url);
    try {
        const response = await fetch(url);
        console.log('Status:', response.status);
        console.log('Headers:', [...response.headers.entries()]);
        const data = await response.json();
        console.log('Data:', data);
        return data;
    } catch (error) {
        console.error('Fetch error:', error);
        throw error;
    }
}

Network Panel Features:

  • Request/response headers inspection
  • Timing waterfall analysis
  • Payload examination
  • WebSocket frame inspection

Chromium Version Compatibility#

BrightSign players use older Chromium versions. For Web Inspector compatibility, download matching Chrome builds:

  • Linux x64: Chromium 576753
  • Mac: Chromium 576753
  • Windows 64-bit: Chromium 576753

Download from: https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/

Node.js Debugging#

Inspector Protocol#

Enable Node.js inspector when initializing roHtmlWidget:

config = {
    url: "file:///sd:/app.html"
    nodejs_enabled: true
    inspector_server: { port: 3000 }
    brightsign_js_objects_enabled: true
}
widget = CreateObject("roHtmlWidget", r, config)

Accessing Node.js Debugger:

  1. Navigate to chrome://inspect in Chrome
  2. Add target: <player-ip>:3000
  3. Click Inspect to open debugger

Programmatic Debugging:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Node.js debugging from code
const inspector = require('inspector');

// Start inspector session
inspector.open(9229, '0.0.0.0', false);

// Trigger breakpoint programmatically
debugger;

// Close inspector
inspector.close();

Debugging Tools#

Breakpoints in Node.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Conditional breakpoint
function processData(items) {
    for (let i = 0; i < items.length; i++) {
        if (items[i].id === 'debug-target') {
            debugger; // Execution pauses here
        }
        process(items[i]);
    }
}

// Exception breakpoints
process.on('uncaughtException', (error) => {
    console.error('Uncaught exception:', error.stack);
    debugger; // Pause on unhandled errors
});

process.on('unhandledRejection', (reason, promise) => {
    console.error('Unhandled rejection:', reason);
    debugger;
});

Module Debugging:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Debug module loading
console.log('Module paths:', module.paths);

// Trace require calls
const Module = require('module');
const originalRequire = Module.prototype.require;

Module.prototype.require = function(id) {
    console.log('Requiring:', id);
    return originalRequire.apply(this, arguments);
};

Profiling#

CPU Profiling:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const fs = require('fs');
const inspector = require('inspector');
const session = new inspector.Session();

session.connect();

// Start CPU profiling
session.post('Profiler.enable', () => {
    session.post('Profiler.start', () => {
        // Code to profile
        performExpensiveOperation();

        // Stop and save profile
        session.post('Profiler.stop', (err, { profile }) => {
            fs.writeFileSync('/storage/sd/profile.cpuprofile',
                JSON.stringify(profile));
            session.disconnect();
        });
    });
});

Memory Profiling:

1
2
3
4
5
6
7
8
// Heap snapshot
session.post('HeapProfiler.enable', () => {
    session.post('HeapProfiler.takeHeapSnapshot', null,
        (err, snapshot) => {
            fs.writeFileSync('/storage/sd/heap.heapsnapshot',
                JSON.stringify(snapshot));
    });
});

Import .cpuprofile and .heapsnapshot files into Chrome DevTools for analysis.

Error Handling#

JavaScript Error Patterns#

Structured Error Handling:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Custom error classes
class NetworkError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.name = 'NetworkError';
        this.statusCode = statusCode;
    }
}

class ValidationError extends Error {
    constructor(message, field) {
        super(message);
        this.name = 'ValidationError';
        this.field = field;
    }
}

// Usage
function validateData(data) {
    if (!data.id) {
        throw new ValidationError('ID is required', 'id');
    }
    if (typeof data.value !== 'number') {
        throw new ValidationError('Value must be number', 'value');
    }
}

try {
    validateData(inputData);
} catch (error) {
    if (error instanceof ValidationError) {
        console.error(`Validation failed on ${error.field}:`, error.message);
    } else {
        console.error('Unexpected error:', error);
    }
}

Exception Handling#

Global Error Handlers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Catch synchronous errors
window.addEventListener('error', (event) => {
    console.error('Global error:', {
        message: event.message,
        filename: event.filename,
        lineno: event.lineno,
        colno: event.colno,
        error: event.error
    });

    // Log to file
    logErrorToFile(event.error);

    // Prevent default browser error handling
    event.preventDefault();
});

// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled rejection:', event.reason);
    logErrorToFile(event.reason);
    event.preventDefault();
});

// Node.js error handlers
if (typeof process !== 'undefined') {
    process.on('uncaughtException', (error) => {
        console.error('Uncaught exception:', error);
        logErrorToFile(error);
    });

    process.on('unhandledRejection', (reason, promise) => {
        console.error('Unhandled rejection at:', promise, 'reason:', reason);
        logErrorToFile(reason);
    });
}

Error Logging Utility:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const fs = require('fs');

function logErrorToFile(error) {
    const timestamp = new Date().toISOString();
    const errorLog = {
        timestamp: timestamp,
        message: error.message,
        stack: error.stack,
        name: error.name
    };

    const logPath = '/storage/sd/errors.log';
    const logEntry = JSON.stringify(errorLog) + '\n';

    try {
        fs.appendFileSync(logPath, logEntry);
    } catch (writeError) {
        console.error('Failed to write error log:', writeError);
    }
}

Stack Traces#

Stack Trace Analysis:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Capture stack trace
function captureStack() {
    const stack = new Error().stack;
    console.log('Call stack:', stack);
    return stack;
}

// Parse stack trace
function parseStackTrace(error) {
    const stackLines = error.stack.split('\n');
    const parsed = stackLines.slice(1).map(line => {
        const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
        if (match) {
            return {
                function: match[1],
                file: match[2],
                line: parseInt(match[3]),
                column: parseInt(match[4])
            };
        }
        return { raw: line.trim() };
    });
    return parsed;
}

// Enhanced error reporting
function reportError(error) {
    const trace = parseStackTrace(error);
    console.error('Error details:', {
        message: error.message,
        type: error.name,
        stack: trace
    });
}

try {
    riskyOperation();
} catch (error) {
    reportError(error);
}

Performance Debugging#

Rendering Bottlenecks#

Identify Paint Issues:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Monitor frame rate
let lastTime = performance.now();
let frames = 0;

function measureFPS() {
    frames++;
    const currentTime = performance.now();

    if (currentTime >= lastTime + 1000) {
        const fps = Math.round((frames * 1000) / (currentTime - lastTime));
        console.log('FPS:', fps);
        frames = 0;
        lastTime = currentTime;
    }

    requestAnimationFrame(measureFPS);
}

requestAnimationFrame(measureFPS);

// Track long tasks
if (window.PerformanceObserver) {
    const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
            console.warn('Long task detected:', {
                duration: entry.duration,
                startTime: entry.startTime
            });
        }
    });

    observer.observe({ entryTypes: ['longtask'] });
}

Layout Thrashing Detection:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Bad: Forces multiple reflows
function badLayoutCode() {
    const element = document.getElementById('box');
    for (let i = 0; i < 100; i++) {
        element.style.width = (element.offsetWidth + 1) + 'px'; // Read-write cycle
    }
}

// Good: Batch reads and writes
function goodLayoutCode() {
    const element = document.getElementById('box');
    const width = element.offsetWidth; // Single read
    for (let i = 0; i < 100; i++) {
        element.style.width = (width + i) + 'px'; // Multiple writes
    }
}

// Detect layout thrashing
let layoutCount = 0;
const originalOffsetWidth = Object.getOwnPropertyDescriptor(
    HTMLElement.prototype, 'offsetWidth'
);

Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
    get: function() {
        layoutCount++;
        if (layoutCount > 10) {
            console.warn('Potential layout thrashing detected');
            console.trace();
        }
        return originalOffsetWidth.get.call(this);
    }
});

JavaScript Profiling#

Performance API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Mark timing points
performance.mark('operation-start');
processLargeDataset();
performance.mark('operation-end');

// Measure duration
performance.measure('operation', 'operation-start', 'operation-end');

// Get measurements
const measures = performance.getEntriesByType('measure');
measures.forEach(measure => {
    console.log(`${measure.name}: ${measure.duration}ms`);
});

// User Timing API
function profileFunction(fn, name) {
    return function(...args) {
        performance.mark(`${name}-start`);
        const result = fn.apply(this, args);
        performance.mark(`${name}-end`);
        performance.measure(name, `${name}-start`, `${name}-end`);

        const measure = performance.getEntriesByName(name)[0];
        console.log(`${name} took ${measure.duration}ms`);

        return result;
    };
}

// Usage
const processData = profileFunction(function(data) {
    return data.map(item => item * 2);
}, 'processData');

Memory Analysis#

Detect Memory Leaks:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Track object allocation
const objectRegistry = new WeakMap();
let allocationCount = 0;

function trackAllocation(obj, identifier) {
    allocationCount++;
    objectRegistry.set(obj, {
        id: identifier,
        timestamp: Date.now(),
        allocNumber: allocationCount
    });
}

// Monitor memory usage (Node.js)
if (typeof process !== 'undefined') {
    function logMemoryUsage() {
        const usage = process.memoryUsage();
        console.log('Memory usage:', {
            heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
            heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
            rss: `${Math.round(usage.rss / 1024 / 1024)}MB`,
            external: `${Math.round(usage.external / 1024 / 1024)}MB`
        });
    }

    setInterval(logMemoryUsage, 10000); // Log every 10 seconds
}

// Heap snapshot comparison (Node.js)
const v8 = require('v8');
const fs = require('fs');

function takeHeapSnapshot(filename) {
    const snapshot = v8.writeHeapSnapshot(filename);
    console.log('Heap snapshot written to:', snapshot);
}

// Take snapshots before and after operations
takeHeapSnapshot('/storage/sd/heap-before.heapsnapshot');
performOperation();
takeHeapSnapshot('/storage/sd/heap-after.heapsnapshot');

Memory Leak Patterns to Avoid:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// Bad: Event listener leak
function badEventListener() {
    const element = document.getElementById('button');
    element.addEventListener('click', function handler() {
        console.log('Clicked');
    });
    // Element removed but listener remains
    element.remove();
}

// Good: Clean up listeners
function goodEventListener() {
    const element = document.getElementById('button');
    const handler = function() {
        console.log('Clicked');
    };
    element.addEventListener('click', handler);

    // Clean up
    element.removeEventListener('click', handler);
    element.remove();
}

// Bad: Timer leak
function badTimer() {
    setInterval(() => {
        console.log('Running...');
    }, 1000);
    // Timer continues forever
}

// Good: Clean up timers
function goodTimer() {
    const timerId = setInterval(() => {
        console.log('Running...');
    }, 1000);

    // Clear when done
    setTimeout(() => {
        clearInterval(timerId);
    }, 10000);
}

DOM Debugging#

Element Inspection#

Query and Inspect Elements:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Advanced element selection
function inspectElement(selector) {
    const element = document.querySelector(selector);

    if (!element) {
        console.error('Element not found:', selector);
        return;
    }

    console.group('Element Inspection:', selector);
    console.log('Tag:', element.tagName);
    console.log('ID:', element.id);
    console.log('Classes:', [...element.classList]);
    console.log('Attributes:', Array.from(element.attributes).map(attr =>
        ({ name: attr.name, value: attr.value })
    ));
    console.log('Computed Style:', window.getComputedStyle(element));
    console.log('Bounding Box:', element.getBoundingClientRect());
    console.log('Scroll Position:', {
        top: element.scrollTop,
        left: element.scrollLeft
    });
    console.groupEnd();
}

// Monitor DOM mutations
const observer = new MutationObserver((mutations) => {
    mutations.forEach(mutation => {
        console.log('DOM mutation:', {
            type: mutation.type,
            target: mutation.target,
            addedNodes: mutation.addedNodes.length,
            removedNodes: mutation.removedNodes.length,
            attributeName: mutation.attributeName
        });
    });
});

observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeOldValue: true
});

Event Troubleshooting#

Debug Event Flow:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Log all events on element
function debugEvents(element) {
    const events = [
        'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
        'touchstart', 'touchend', 'touchmove',
        'keydown', 'keyup', 'keypress',
        'focus', 'blur', 'change', 'input'
    ];

    events.forEach(eventType => {
        element.addEventListener(eventType, (e) => {
            console.log(`Event: ${eventType}`, {
                target: e.target,
                currentTarget: e.currentTarget,
                phase: e.eventPhase,
                bubbles: e.bubbles,
                cancelable: e.cancelable,
                defaultPrevented: e.defaultPrevented
            });
        }, true); // Capture phase
    });
}

// Track event propagation
function trackEventPropagation(selector, eventType) {
    const element = document.querySelector(selector);

    // Capture phase
    element.addEventListener(eventType, (e) => {
        console.log(`[CAPTURE] ${eventType} on`, e.currentTarget);
// ... (see full example below)
    element.addEventListener = function(type, listener, options) {
        if (!listeners.has(type)) {
            listeners.set(type, []);
        }
        listeners.get(type).push({ listener, options });
        original(type, listener, options);
    };

    return listeners;
}
View complete code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Log all events on element
function debugEvents(element) {
    const events = [
        'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
        'touchstart', 'touchend', 'touchmove',
        'keydown', 'keyup', 'keypress',
        'focus', 'blur', 'change', 'input'
    ];

    events.forEach(eventType => {
        element.addEventListener(eventType, (e) => {
            console.log(`Event: ${eventType}`, {
                target: e.target,
                currentTarget: e.currentTarget,
                phase: e.eventPhase,
                bubbles: e.bubbles,
                cancelable: e.cancelable,
                defaultPrevented: e.defaultPrevented
            });
        }, true); // Capture phase
    });
}

// Track event propagation
function trackEventPropagation(selector, eventType) {
    const element = document.querySelector(selector);

    // Capture phase
    element.addEventListener(eventType, (e) => {
        console.log(`[CAPTURE] ${eventType} on`, e.currentTarget);
    }, true);

    // Bubble phase
    element.addEventListener(eventType, (e) => {
        console.log(`[BUBBLE] ${eventType} on`, e.currentTarget);
    }, false);
}

// Find event listeners (using Chrome DevTools protocol)
function getEventListeners(element) {
    // This function works in Chrome DevTools console
    // For programmatic access, track manually:
    const listeners = new Map();

    const original = element.addEventListener.bind(element);
    element.addEventListener = function(type, listener, options) {
        if (!listeners.has(type)) {
            listeners.set(type, []);
        }
        listeners.get(type).push({ listener, options });
        original(type, listener, options);
    };

    return listeners;
}

Layout Issues#

Debug Layout Problems:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Visualize element boundaries
function visualizeBoundaries(selector) {
    const elements = document.querySelectorAll(selector);

    elements.forEach((element, index) => {
        const rect = element.getBoundingClientRect();
        const overlay = document.createElement('div');

        overlay.style.cssText = `
            position: fixed;
            top: ${rect.top}px;
            left: ${rect.left}px;
            width: ${rect.width}px;
            height: ${rect.height}px;
            border: 2px solid red;
            pointer-events: none;
            z-index: 10000;
        `;

        const label = document.createElement('div');
        label.textContent = `${selector}[${index}]`;
        label.style.cssText = `
            background: red;
            color: white;
            padding: 2px 4px;
            font-size: 10px;
        `;

        overlay.appendChild(label);
        document.body.appendChild(overlay);
// ... (see full example below)
                    value: entry.value,
                    cumulative: cumulativeLayoutShift,
                    sources: entry.sources
                });
            }
        }
    });

    observer.observe({ entryTypes: ['layout-shift'] });
}
View complete code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// Visualize element boundaries
function visualizeBoundaries(selector) {
    const elements = document.querySelectorAll(selector);

    elements.forEach((element, index) => {
        const rect = element.getBoundingClientRect();
        const overlay = document.createElement('div');

        overlay.style.cssText = `
            position: fixed;
            top: ${rect.top}px;
            left: ${rect.left}px;
            width: ${rect.width}px;
            height: ${rect.height}px;
            border: 2px solid red;
            pointer-events: none;
            z-index: 10000;
        `;

        const label = document.createElement('div');
        label.textContent = `${selector}[${index}]`;
        label.style.cssText = `
            background: red;
            color: white;
            padding: 2px 4px;
            font-size: 10px;
        `;

        overlay.appendChild(label);
        document.body.appendChild(overlay);

        setTimeout(() => overlay.remove(), 5000);
    });
}

// Check for overflow issues
function checkOverflow(element) {
    const computed = window.getComputedStyle(element);
    const hasOverflow =
        element.scrollHeight > element.clientHeight ||
        element.scrollWidth > element.clientWidth;

    console.log('Overflow check:', {
        hasOverflow: hasOverflow,
        scrollHeight: element.scrollHeight,
        clientHeight: element.clientHeight,
        scrollWidth: element.scrollWidth,
        clientWidth: element.clientWidth,
        overflow: computed.overflow,
        overflowX: computed.overflowX,
        overflowY: computed.overflowY
    });
}

// Detect layout shifting
let cumulativeLayoutShift = 0;

if (window.PerformanceObserver) {
    const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
            if (!entry.hadRecentInput) {
                cumulativeLayoutShift += entry.value;
                console.warn('Layout shift:', {
                    value: entry.value,
                    cumulative: cumulativeLayoutShift,
                    sources: entry.sources
                });
            }
        }
    });

    observer.observe({ entryTypes: ['layout-shift'] });
}

Network Debugging#

AJAX Requests#

Debug XMLHttpRequest:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Wrap XMLHttpRequest for debugging
const OriginalXHR = window.XMLHttpRequest;

window.XMLHttpRequest = function() {
    const xhr = new OriginalXHR();
    const id = Math.random().toString(36).substr(2, 9);

    const originalOpen = xhr.open;
    xhr.open = function(method, url, ...args) {
        console.log(`[XHR ${id}] Opening:`, method, url);
        this._debugInfo = { id, method, url, startTime: Date.now() };
        return originalOpen.apply(this, [method, url, ...args]);
    };

    xhr.addEventListener('loadstart', function() {
        console.log(`[XHR ${id}] Load started`);
    });

    xhr.addEventListener('load', function() {
        const duration = Date.now() - this._debugInfo.startTime;
        console.log(`[XHR ${id}] Completed:`, {
            status: this.status,
            statusText: this.statusText,
            duration: `${duration}ms`,
            responseSize: this.responseText.length
        });
    });

    xhr.addEventListener('error', function() {
        console.error(`[XHR ${id}] Failed:`, {
// ... (see full example below)
            duration: `${duration}ms`,
            headers: Object.fromEntries(response.headers.entries())
        });

        return response;
    } catch (error) {
        console.error(`[Fetch ${id}] Failed:`, error);
        throw error;
    }
};
View complete code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Wrap XMLHttpRequest for debugging
const OriginalXHR = window.XMLHttpRequest;

window.XMLHttpRequest = function() {
    const xhr = new OriginalXHR();
    const id = Math.random().toString(36).substr(2, 9);

    const originalOpen = xhr.open;
    xhr.open = function(method, url, ...args) {
        console.log(`[XHR ${id}] Opening:`, method, url);
        this._debugInfo = { id, method, url, startTime: Date.now() };
        return originalOpen.apply(this, [method, url, ...args]);
    };

    xhr.addEventListener('loadstart', function() {
        console.log(`[XHR ${id}] Load started`);
    });

    xhr.addEventListener('load', function() {
        const duration = Date.now() - this._debugInfo.startTime;
        console.log(`[XHR ${id}] Completed:`, {
            status: this.status,
            statusText: this.statusText,
            duration: `${duration}ms`,
            responseSize: this.responseText.length
        });
    });

    xhr.addEventListener('error', function() {
        console.error(`[XHR ${id}] Failed:`, {
            status: this.status,
            statusText: this.statusText
        });
    });

    return xhr;
};

// Wrap Fetch API for debugging
const originalFetch = window.fetch;

window.fetch = async function(...args) {
    const [url, options = {}] = args;
    const id = Math.random().toString(36).substr(2, 9);
    const startTime = Date.now();

    console.log(`[Fetch ${id}] Requesting:`, {
        url: url,
        method: options.method || 'GET',
        headers: options.headers
    });

    try {
        const response = await originalFetch(...args);
        const duration = Date.now() - startTime;

        console.log(`[Fetch ${id}] Response:`, {
            status: response.status,
            statusText: response.statusText,
            duration: `${duration}ms`,
            headers: Object.fromEntries(response.headers.entries())
        });

        return response;
    } catch (error) {
        console.error(`[Fetch ${id}] Failed:`, error);
        throw error;
    }
};

CORS Issues#

Debug CORS Problems:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Test CORS configuration
async function testCORS(url) {
    console.log('Testing CORS for:', url);

    try {
        const response = await fetch(url, {
            method: 'OPTIONS',
            headers: {
                'Origin': window.location.origin,
                'Access-Control-Request-Method': 'GET'
            }
        });

        const corsHeaders = {
            'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
            'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
            'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers'),
            'Access-Control-Max-Age': response.headers.get('Access-Control-Max-Age')
        };

        console.log('CORS headers:', corsHeaders);

        if (corsHeaders['Access-Control-Allow-Origin'] === '*' ||
            corsHeaders['Access-Control-Allow-Origin'] === window.location.origin) {
            console.log('✓ CORS properly configured');
        } else {
            console.warn('✗ CORS may be blocked');
        }
    } catch (error) {
        console.error('CORS test failed:', error);
    }
}

// Disable CORS for development (BrightScript)
/*
config = {
    security_params: { websecurity: false }
    // Or use registry:
    // registry write html disable-web-security 1
}
*/

API Communication#

Debug API Integration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// API debugging wrapper
class DebugAPI {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.requestLog = [];
    }

    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        const requestId = this.requestLog.length + 1;
        const startTime = Date.now();

        const logEntry = {
            id: requestId,
            url: url,
            method: options.method || 'GET',
            timestamp: new Date().toISOString(),
            headers: options.headers || {}
        };

        console.group(`API Request #${requestId}`);
        console.log('URL:', url);
        console.log('Method:', logEntry.method);
        console.log('Headers:', logEntry.headers);

        if (options.body) {
            console.log('Body:', options.body);
            logEntry.requestBody = options.body;
        }

// ... (see full example below)
        const response = await api.request('/data', {
            method: 'GET',
            headers: { 'Authorization': 'Bearer token123' }
        });
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('API call failed:', error);
    }
}
View complete code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// API debugging wrapper
class DebugAPI {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.requestLog = [];
    }

    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        const requestId = this.requestLog.length + 1;
        const startTime = Date.now();

        const logEntry = {
            id: requestId,
            url: url,
            method: options.method || 'GET',
            timestamp: new Date().toISOString(),
            headers: options.headers || {}
        };

        console.group(`API Request #${requestId}`);
        console.log('URL:', url);
        console.log('Method:', logEntry.method);
        console.log('Headers:', logEntry.headers);

        if (options.body) {
            console.log('Body:', options.body);
            logEntry.requestBody = options.body;
        }

        try {
            const response = await fetch(url, options);
            const duration = Date.now() - startTime;

            logEntry.status = response.status;
            logEntry.duration = duration;

            console.log('Status:', response.status);
            console.log('Duration:', `${duration}ms`);
            console.log('Response Headers:',
                Object.fromEntries(response.headers.entries()));

            const clonedResponse = response.clone();
            const responseText = await clonedResponse.text();

            try {
                logEntry.responseBody = JSON.parse(responseText);
                console.log('Response:', logEntry.responseBody);
            } catch {
                logEntry.responseBody = responseText;
                console.log('Response (text):', responseText);
            }

            console.groupEnd();
            this.requestLog.push(logEntry);

            return response;
        } catch (error) {
            logEntry.error = error.message;
            console.error('Error:', error);
            console.groupEnd();
            this.requestLog.push(logEntry);
            throw error;
        }
    }

    getLog() {
        return this.requestLog;
    }

    exportLog() {
        const json = JSON.stringify(this.requestLog, null, 2);
        console.log('Request log:', json);
        return json;
    }
}

// Usage
const api = new DebugAPI('https://api.example.com');

async function fetchData() {
    try {
        const response = await api.request('/data', {
            method: 'GET',
            headers: { 'Authorization': 'Bearer token123' }
        });
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('API call failed:', error);
    }
}

Media Debugging#

HTML5 Video/Audio Issues#

Debug Media Playback:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Comprehensive media event logging
function debugMediaElement(mediaElement) {
    const events = [
        'loadstart', 'progress', 'suspend', 'abort', 'error',
        'emptied', 'stalled', 'loadedmetadata', 'loadeddata',
        'canplay', 'canplaythrough', 'playing', 'waiting',
        'seeking', 'seeked', 'ended', 'durationchange',
        'timeupdate', 'play', 'pause', 'ratechange',
        'volumechange'
    ];

    events.forEach(eventType => {
        mediaElement.addEventListener(eventType, (e) => {
            console.log(`[Media] ${eventType}:`, {
                currentTime: mediaElement.currentTime,
                duration: mediaElement.duration,
                paused: mediaElement.paused,
                ended: mediaElement.ended,
                readyState: mediaElement.readyState,
                networkState: mediaElement.networkState,
                buffered: mediaElement.buffered.length > 0 ?
                    `${mediaElement.buffered.start(0)}-${mediaElement.buffered.end(0)}` : 'none'
            });
        });
    });

    // Log errors with detail
    mediaElement.addEventListener('error', (e) => {
        const error = mediaElement.error;
        console.error('[Media] Error:', {
// ... (see full example below)

    return supported || canPlay !== '';
}

// Example usage
const video = document.querySelector('video');
debugMediaElement(video);

checkMediaSupport('video/mp4; codecs="avc1.42E01E"');
checkMediaSupport('video/webm; codecs="vp8"');
View complete code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// Comprehensive media event logging
function debugMediaElement(mediaElement) {
    const events = [
        'loadstart', 'progress', 'suspend', 'abort', 'error',
        'emptied', 'stalled', 'loadedmetadata', 'loadeddata',
        'canplay', 'canplaythrough', 'playing', 'waiting',
        'seeking', 'seeked', 'ended', 'durationchange',
        'timeupdate', 'play', 'pause', 'ratechange',
        'volumechange'
    ];

    events.forEach(eventType => {
        mediaElement.addEventListener(eventType, (e) => {
            console.log(`[Media] ${eventType}:`, {
                currentTime: mediaElement.currentTime,
                duration: mediaElement.duration,
                paused: mediaElement.paused,
                ended: mediaElement.ended,
                readyState: mediaElement.readyState,
                networkState: mediaElement.networkState,
                buffered: mediaElement.buffered.length > 0 ?
                    `${mediaElement.buffered.start(0)}-${mediaElement.buffered.end(0)}` : 'none'
            });
        });
    });

    // Log errors with detail
    mediaElement.addEventListener('error', (e) => {
        const error = mediaElement.error;
        console.error('[Media] Error:', {
            code: error.code,
            message: error.message,
            MEDIA_ERR_ABORTED: error.code === 1,
            MEDIA_ERR_NETWORK: error.code === 2,
            MEDIA_ERR_DECODE: error.code === 3,
            MEDIA_ERR_SRC_NOT_SUPPORTED: error.code === 4
        });
    });
}

// Check media capabilities
async function checkMediaSupport(mimeType) {
    if (!window.MediaSource) {
        console.warn('MediaSource API not supported');
        return false;
    }

    const supported = MediaSource.isTypeSupported(mimeType);
    console.log(`MediaSource support for ${mimeType}:`, supported);

    // Check HTMLMediaElement support
    const video = document.createElement('video');
    const canPlay = video.canPlayType(mimeType);
    console.log(`HTMLMediaElement canPlayType ${mimeType}:`, canPlay);

    return supported || canPlay !== '';
}

// Example usage
const video = document.querySelector('video');
debugMediaElement(video);

checkMediaSupport('video/mp4; codecs="avc1.42E01E"');
checkMediaSupport('video/webm; codecs="vp8"');

Codec Problems#

Identify Codec Issues:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Detect supported codecs
function detectSupportedCodecs() {
    const video = document.createElement('video');
    const audio = document.createElement('audio');

    const videoCodecs = [
        { mime: 'video/mp4; codecs="avc1.42E01E"', name: 'H.264 Baseline' },
        { mime: 'video/mp4; codecs="avc1.4D401E"', name: 'H.264 Main' },
        { mime: 'video/mp4; codecs="avc1.64001E"', name: 'H.264 High' },
        { mime: 'video/mp4; codecs="hev1.1.6.L93.B0"', name: 'H.265/HEVC' },
        { mime: 'video/webm; codecs="vp8"', name: 'VP8' },
        { mime: 'video/webm; codecs="vp9"', name: 'VP9' },
        { mime: 'video/webm; codecs="av1"', name: 'AV1' }
    ];

    const audioCodecs = [
        { mime: 'audio/mpeg', name: 'MP3' },
        { mime: 'audio/mp4; codecs="mp4a.40.2"', name: 'AAC' },
        { mime: 'audio/webm; codecs="opus"', name: 'Opus' },
        { mime: 'audio/ogg; codecs="vorbis"', name: 'Vorbis' },
        { mime: 'audio/flac', name: 'FLAC' }
    ];

    console.group('Supported Video Codecs');
    videoCodecs.forEach(codec => {
        const support = video.canPlayType(codec.mime);
        console.log(`${codec.name}: ${support || 'no'}`);
    });
    console.groupEnd();

    console.group('Supported Audio Codecs');
    audioCodecs.forEach(codec => {
        const support = audio.canPlayType(codec.mime);
        console.log(`${codec.name}: ${support || 'no'}`);
    });
    console.groupEnd();
}

detectSupportedCodecs();

Synchronization#

Debug A/V Sync:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Monitor audio/video synchronization
function monitorAVSync(videoElement) {
    const audioContext = new AudioContext();
    const source = audioContext.createMediaElementSource(videoElement);
    const analyser = audioContext.createAnalyser();

    source.connect(analyser);
    analyser.connect(audioContext.destination);

    let lastVideoTime = 0;
    let lastAudioTime = 0;

    function checkSync() {
        const videoTime = videoElement.currentTime;
        const audioTime = audioContext.currentTime;

        const videoDelta = videoTime - lastVideoTime;
        const audioDelta = audioTime - lastAudioTime;
        const drift = Math.abs(videoDelta - audioDelta);

        if (drift > 0.1) { // More than 100ms drift
            console.warn('A/V sync drift detected:', {
                drift: `${(drift * 1000).toFixed(2)}ms`,
                videoTime: videoTime,
                audioTime: audioTime
            });
        }

        lastVideoTime = videoTime;
        lastAudioTime = audioTime;

        requestAnimationFrame(checkSync);
    }

    videoElement.addEventListener('play', () => {
        audioContext.resume();
        requestAnimationFrame(checkSync);
    });
}

// Usage
const video = document.querySelector('video');
monitorAVSync(video);

Cross-Platform Issues#

Browser Compatibility#

Detect BrightSign Environment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Identify BrightSign player
function detectBrightSign() {
    const userAgent = navigator.userAgent;
    const isBrightSign = /BrightSign/.test(userAgent);

    if (isBrightSign) {
        const match = userAgent.match(/BrightSign\/(\d+\.\d+\.\d+)/);
        const version = match ? match[1] : 'unknown';

        console.log('Running on BrightSign:', {
            version: version,
            userAgent: userAgent
        });

        return { isBrightSign: true, version: version };
    }

    return { isBrightSign: false };
}

const platform = detectBrightSign();

// Feature detection
function checkFeatures() {
    const features = {
        'Promise': typeof Promise !== 'undefined',
        'Fetch': typeof fetch !== 'undefined',
        'Arrow Functions': (() => true)(),
        'Async/Await': (async () => true)().constructor.name === 'AsyncFunction',
        'WeakMap': typeof WeakMap !== 'undefined',
        'Proxy': typeof Proxy !== 'undefined',
        'SharedArrayBuffer': typeof SharedArrayBuffer !== 'undefined',
        'WebAssembly': typeof WebAssembly !== 'undefined',
        'Intl': typeof Intl !== 'undefined',
        'ResizeObserver': typeof ResizeObserver !== 'undefined',
        'IntersectionObserver': typeof IntersectionObserver !== 'undefined'
    };

    console.table(features);
    return features;
}

checkFeatures();

BrightSign-Specific Behaviors#

Debug BrightSign APIs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Check for BrightSign JavaScript objects
function checkBrightSignAPIs() {
    const apis = [
        'BSDeviceInfo',
        'BSSystemTime',
        'BSControlPort',
        'BSSerialPort',
        'BSDatagramSocket',
        'BSMessagePort'
    ];

    console.group('BrightSign API Availability');
    apis.forEach(api => {
        const available = typeof window[api] !== 'undefined';
        console.log(`${api}: ${available ? '✓' : '✗'}`);

        if (available) {
            try {
                const instance = new window[api]();
                console.log(`  Can instantiate: ✓`);
            } catch (error) {
                console.log(`  Can instantiate: ✗`, error.message);
            }
        }
    });
    console.groupEnd();
}

// Check for @brightsign modules (Node.js)
async function checkBrightSignModules() {
// ... (see full example below)
            console.log(`${moduleName}: ✗`, error.message);
        }
    }
    console.groupEnd();
}

checkBrightSignAPIs();
if (typeof require !== 'undefined') {
    checkBrightSignModules();
}
View complete code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Check for BrightSign JavaScript objects
function checkBrightSignAPIs() {
    const apis = [
        'BSDeviceInfo',
        'BSSystemTime',
        'BSControlPort',
        'BSSerialPort',
        'BSDatagramSocket',
        'BSMessagePort'
    ];

    console.group('BrightSign API Availability');
    apis.forEach(api => {
        const available = typeof window[api] !== 'undefined';
        console.log(`${api}: ${available ? '✓' : '✗'}`);

        if (available) {
            try {
                const instance = new window[api]();
                console.log(`  Can instantiate: ✓`);
            } catch (error) {
                console.log(`  Can instantiate: ✗`, error.message);
            }
        }
    });
    console.groupEnd();
}

// Check for @brightsign modules (Node.js)
async function checkBrightSignModules() {
    const modules = [
        '@brightsign/deviceinfo',
        '@brightsign/system',
        '@brightsign/registry',
        '@brightsign/networkstatus',
        '@brightsign/storage'
    ];

    console.group('BrightSign Node Modules');
    for (const moduleName of modules) {
        try {
            const module = require(moduleName);
            console.log(`${moduleName}: ✓`, Object.keys(module));
        } catch (error) {
            console.log(`${moduleName}: ✗`, error.message);
        }
    }
    console.groupEnd();
}

checkBrightSignAPIs();
if (typeof require !== 'undefined') {
    checkBrightSignModules();
}

Integration Debugging#

JavaScript-BrightScript Communication#

Debug Message Passing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// JavaScript side
class BrightScriptBridge {
    constructor() {
        this.messagePort = null;
        this.handlers = new Map();
        this.messageId = 0;

        this.initMessagePort();
    }

    initMessagePort() {
        if (typeof BSMessagePort === 'undefined') {
            console.error('BSMessagePort not available');
            return;
        }

        try {
            this.messagePort = new BSMessagePort();
            this.messagePort.onmessage = (e) => this.handleMessage(e);
            console.log('Message port initialized');
        } catch (error) {
            console.error('Failed to initialize message port:', error);
        }
    }

    handleMessage(event) {
        console.log('Received from BrightScript:', event.data);

        try {
            const message = JSON.parse(event.data);
// ... (see full example below)
}

// Usage
const bridge = new BrightScriptBridge();

bridge.on('response', (data) => {
    console.log('Got response:', data);
});

bridge.send('request', { action: 'getData', params: { id: 123 } });
View complete code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// JavaScript side
class BrightScriptBridge {
    constructor() {
        this.messagePort = null;
        this.handlers = new Map();
        this.messageId = 0;

        this.initMessagePort();
    }

    initMessagePort() {
        if (typeof BSMessagePort === 'undefined') {
            console.error('BSMessagePort not available');
            return;
        }

        try {
            this.messagePort = new BSMessagePort();
            this.messagePort.onmessage = (e) => this.handleMessage(e);
            console.log('Message port initialized');
        } catch (error) {
            console.error('Failed to initialize message port:', error);
        }
    }

    handleMessage(event) {
        console.log('Received from BrightScript:', event.data);

        try {
            const message = JSON.parse(event.data);
            const handler = this.handlers.get(message.type);

            if (handler) {
                handler(message.payload);
            } else {
                console.warn('No handler for message type:', message.type);
            }
        } catch (error) {
            console.error('Error handling message:', error);
        }
    }

    on(type, handler) {
        this.handlers.set(type, handler);
    }

    send(type, payload) {
        const message = {
            id: ++this.messageId,
            type: type,
            payload: payload,
            timestamp: Date.now()
        };

        console.log('Sending to BrightScript:', message);

        try {
            this.messagePort.postMessage(JSON.stringify(message));
        } catch (error) {
            console.error('Failed to send message:', error);
        }
    }
}

// Usage
const bridge = new BrightScriptBridge();

bridge.on('response', (data) => {
    console.log('Got response:', data);
});

bridge.send('request', { action: 'getData', params: { id: 123 } });

BrightScript Side:

' BrightScript message handler
function SetupMessagePort(htmlWidget as Object) as Object
    port = CreateObject("roMessagePort")
    htmlWidget.SetPort(port)

    ' Send message to JavaScript
    msgToJS = {
        type: "response"
        payload: { status: "ok", data: [1, 2, 3] }
    }
    htmlWidget.PostJSMessage(FormatJson(msgToJS))

    return port
end function

' Main event loop
sub Main()
    htmlWidget = CreateWidget()
    port = SetupMessagePort(htmlWidget)

    while true
        msg = wait(0, port)

        if type(msg) = "roHtmlWidgetEvent" then
            eventData = msg.GetData()
            print "Received from JavaScript: "; eventData

            ' Parse and handle
            parsed = ParseJson(eventData)
            if parsed <> invalid then
                print "Message type: "; parsed.type
                print "Payload: "; FormatJson(parsed.payload)
            end if
        end if
    end while
end sub

Security Debugging#

CSP Violations#

Monitor Content Security Policy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Listen for CSP violations
document.addEventListener('securitypolicyviolation', (e) => {
    console.error('CSP Violation:', {
        blockedURI: e.blockedURI,
        violatedDirective: e.violatedDirective,
        originalPolicy: e.originalPolicy,
        sourceFile: e.sourceFile,
        lineNumber: e.lineNumber,
        columnNumber: e.columnNumber,
        sample: e.sample
    });

    // Log to file for analysis
    logSecurityViolation(e);
});

function logSecurityViolation(event) {
    const violation = {
        timestamp: new Date().toISOString(),
        type: 'csp-violation',
        details: {
            blocked: event.blockedURI,
            directive: event.violatedDirective,
            source: event.sourceFile,
            line: event.lineNumber
        }
    };

    // Send to logging endpoint or save locally
    fetch('/api/log-security', {
// ... (see full example below)
        console.log('CSP blocking eval: ✓');
    }

    // Try to load external script
    const script = document.createElement('script');
    script.src = 'https://external.com/script.js';
    script.onerror = () => console.log('CSP blocked external script: ✓');
    script.onload = () => console.warn('CSP allowed external script');
    document.head.appendChild(script);
}
View complete code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Listen for CSP violations
document.addEventListener('securitypolicyviolation', (e) => {
    console.error('CSP Violation:', {
        blockedURI: e.blockedURI,
        violatedDirective: e.violatedDirective,
        originalPolicy: e.originalPolicy,
        sourceFile: e.sourceFile,
        lineNumber: e.lineNumber,
        columnNumber: e.columnNumber,
        sample: e.sample
    });

    // Log to file for analysis
    logSecurityViolation(e);
});

function logSecurityViolation(event) {
    const violation = {
        timestamp: new Date().toISOString(),
        type: 'csp-violation',
        details: {
            blocked: event.blockedURI,
            directive: event.violatedDirective,
            source: event.sourceFile,
            line: event.lineNumber
        }
    };

    // Send to logging endpoint or save locally
    fetch('/api/log-security', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(violation)
    }).catch(err => console.error('Failed to log violation:', err));
}

// Test CSP configuration
function testCSP() {
    // Try to inline script (blocked if CSP active)
    try {
        eval('console.log("Eval allowed")');
        console.warn('CSP not blocking eval');
    } catch (e) {
        console.log('CSP blocking eval: ✓');
    }

    // Try to load external script
    const script = document.createElement('script');
    script.src = 'https://external.com/script.js';
    script.onerror = () => console.log('CSP blocked external script: ✓');
    script.onload = () => console.warn('CSP allowed external script');
    document.head.appendChild(script);
}

Sandboxing Issues#

Debug Iframe Sandbox:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Check iframe sandbox restrictions
function checkIframeSandbox(iframe) {
    const sandbox = iframe.getAttribute('sandbox');

    console.group('Iframe Sandbox Analysis');
    console.log('Sandbox attribute:', sandbox || 'none');

    if (sandbox) {
        const permissions = sandbox.split(' ');
        const checks = {
            'allow-scripts': permissions.includes('allow-scripts'),
            'allow-same-origin': permissions.includes('allow-same-origin'),
            'allow-forms': permissions.includes('allow-forms'),
            'allow-popups': permissions.includes('allow-popups'),
            'allow-top-navigation': permissions.includes('allow-top-navigation')
        };

        console.table(checks);
    } else {
        console.log('No sandbox restrictions');
    }
    console.groupEnd();
}

// Test cross-origin restrictions
function testCrossOrigin() {
    try {
        const iframe = document.createElement('iframe');
        iframe.src = 'https://example.com';
        document.body.appendChild(iframe);

        iframe.onload = () => {
            try {
                const doc = iframe.contentDocument;
                console.log('Cross-origin access: ✓ (same-origin or CORS)');
            } catch (e) {
                console.log('Cross-origin access: ✗ (blocked)', e.message);
            }
        };
    } catch (error) {
        console.error('Failed to create iframe:', error);
    }
}

Permission Errors#

Debug Permission Issues:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Check API permissions
async function checkPermissions() {
    const permissions = [
        'geolocation',
        'notifications',
        'camera',
        'microphone'
    ];

    console.group('Permission Status');

    for (const name of permissions) {
        try {
            if (navigator.permissions) {
                const result = await navigator.permissions.query({ name: name });
                console.log(`${name}:`, result.state);

                result.addEventListener('change', () => {
                    console.log(`${name} permission changed to:`, result.state);
                });
            } else {
                console.log(`${name}: Permissions API not available`);
            }
        } catch (error) {
            console.log(`${name}: ${error.message}`);
        }
    }

    console.groupEnd();
}

// Test camera access (if enabled in security_params)
async function testCameraAccess() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({
            video: true
        });

        console.log('Camera access: ✓', {
            videoTracks: stream.getVideoTracks().length,
            settings: stream.getVideoTracks()[0].getSettings()
        });

        stream.getTracks().forEach(track => track.stop());
    } catch (error) {
        console.error('Camera access: ✗', error.name, error.message);
    }
}

checkPermissions();

Trace Events#

Chromium TraceEvent System#

Enable TraceEvents (BrightScript):

' Enable trace events via registry
sub EnableTraceEvents()
    reg = CreateObject("roRegistrySection", "html")

    ' Set trace categories
    categories = "toplevel,blink_gc,disabled-by-default-memory-infra,"
    categories = categories + "disabled-by-default-blink_gc,"
    categories = categories + "disabled-by-default.skia.gpu.cache"

    reg.Write("tracecategories", categories)
    reg.Write("tracemaxsnapshots", "25")
    reg.Write("tracemonitorinterval", "60")
    reg.Flush()

    print "Trace events enabled"
end sub

Using TraceEvents:

  1. Create directory: /storage/sd/brightsign-webinspector/
  2. Enable via registry (see above)
  3. Reboot player to activate
  4. Trace files written to directory every 60 seconds
  5. Transfer .json files to development machine
  6. Import into chrome://tracing

Common Trace Categories:

  • toplevel - Top-level events
  • blink_gc - Garbage collection
  • disabled-by-default-memory-infra - Memory instrumentation
  • disabled-by-default-blink_gc - Detailed GC events
  • v8 - V8 JavaScript engine events
  • renderer - Rendering events

Diagnostic Web Server#

Local DWS Access#

Access Player Diagnostics:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Fetch player logs via DWS
async function fetchPlayerLogs(playerIP, username, password) {
    const auth = btoa(`${username}:${password}`);
    const baseURL = `http://${playerIP}`;

    try {
        // Get system log
        const logResponse = await fetch(`${baseURL}/api/v1/logs/system`, {
            headers: {
                'Authorization': `Basic ${auth}`
            }
        });

        const logs = await logResponse.text();
        console.log('System logs:', logs);

        // Get player status
        const statusResponse = await fetch(`${baseURL}/api/v1/status`, {
            headers: {
                'Authorization': `Basic ${auth}`
            }
        });

        const status = await statusResponse.json();
        console.log('Player status:', status);

        return { logs, status };
    } catch (error) {
        console.error('Failed to fetch diagnostics:', error);
    }
}

// Usage
fetchPlayerLogs('192.168.1.100', 'admin', 'password');

Best Practices#

Development Workflow#

Debug Configuration Management:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Environment-aware debugging
const DEBUG_CONFIG = {
    isDevelopment: window.location.hostname === 'localhost' ||
                    /^192\.168\./.test(window.location.hostname),

    enableLogging: true,
    enableRemoteInspector: true,
    enableTraceEvents: false,
    logLevel: 'debug' // 'error', 'warn', 'info', 'debug'
};

// Conditional logging
function log(level, ...args) {
    const levels = ['error', 'warn', 'info', 'debug'];
    const currentLevelIndex = levels.indexOf(DEBUG_CONFIG.logLevel);
    const messageLevelIndex = levels.indexOf(level);

    if (messageLevelIndex <= currentLevelIndex) {
        console[level](...args);
    }
}

// Usage
log('debug', 'Detailed debug info');
log('info', 'Important information');
log('error', 'Critical error');

// Disable debugging for production
if (!DEBUG_CONFIG.isDevelopment) {
    console.log = () => {};
    console.debug = () => {};
    console.info = () => {};
}

Error Recovery Strategies#

Automatic Error Recovery:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Watchdog timer for hung applications
class Watchdog {
    constructor(timeout = 30000) {
        this.timeout = timeout;
        this.timerId = null;
        this.lastPing = Date.now();
    }

    start() {
        this.timerId = setInterval(() => {
            const elapsed = Date.now() - this.lastPing;

            if (elapsed > this.timeout) {
                console.error('Application hung, attempting recovery');
                this.recover();
            }
        }, 5000);
    }

    ping() {
        this.lastPing = Date.now();
    }

    recover() {
        // Log error state
        console.error('Watchdog timeout - reloading application');

        // Attempt graceful cleanup
        try {
            this.cleanup();
// ... (see full example below)
}

// Usage
const watchdog = new Watchdog(30000);
watchdog.start();

// Ping watchdog in main loop
setInterval(() => {
    watchdog.ping();
}, 1000);
View complete code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Watchdog timer for hung applications
class Watchdog {
    constructor(timeout = 30000) {
        this.timeout = timeout;
        this.timerId = null;
        this.lastPing = Date.now();
    }

    start() {
        this.timerId = setInterval(() => {
            const elapsed = Date.now() - this.lastPing;

            if (elapsed > this.timeout) {
                console.error('Application hung, attempting recovery');
                this.recover();
            }
        }, 5000);
    }

    ping() {
        this.lastPing = Date.now();
    }

    recover() {
        // Log error state
        console.error('Watchdog timeout - reloading application');

        // Attempt graceful cleanup
        try {
            this.cleanup();
        } catch (error) {
            console.error('Cleanup failed:', error);
        }

        // Reload page
        window.location.reload();
    }

    cleanup() {
        // Close connections, clear timers, etc.
        clearInterval(this.timerId);
    }
}

// Usage
const watchdog = new Watchdog(30000);
watchdog.start();

// Ping watchdog in main loop
setInterval(() => {
    watchdog.ping();
}, 1000);

Troubleshooting Guide#

Common Issues and Solutions#

Memory Crashes:

  • Symptom: Player reboots unexpectedly
  • Debug: Enable heap snapshots, monitor memory usage
  • Solution: Identify and fix memory leaks, reduce allocation

Slow Performance:

  • Symptom: Laggy UI, dropped frames
  • Debug: Use Performance API, check long tasks
  • Solution: Optimize rendering, reduce JavaScript execution

Network Failures:

  • Symptom: Failed requests, timeouts
  • Debug: Monitor network tab, log requests
  • Solution: Check CORS, verify connectivity, add retry logic

Media Playback Issues:

  • Symptom: Video won’t play, audio sync problems
  • Debug: Log media events, check codec support
  • Solution: Use supported codecs, verify file integrity

Integration Failures:

  • Symptom: BrightScript communication broken
  • Debug: Log message passing, verify message port
  • Solution: Check message format, ensure port configured

Summary#

Effective JavaScript debugging on BrightSign requires:

  1. Remote DevTools: Essential for browser debugging
  2. Node.js Inspector: Critical for server-side JavaScript
  3. Comprehensive Logging: Track execution flow and errors
  4. Performance Monitoring: Identify bottlenecks early
  5. Error Handling: Graceful failure recovery
  6. Security Awareness: Monitor CSP and permissions
  7. Platform Knowledge: Understand BrightSign-specific behaviors

Always disable debugging features in production deployments to prevent memory leaks and security vulnerabilities.

Next Steps#

Continue to Chapter 9: Writing Extensions to learn how to extend BrightSign functionality with custom modules and integrations.