Create symlink for js assets in web directory

- Removed copied overlay-web-adapter.js from web/
- Created web/js symlink pointing to mbetterclient/web_dashboard/static/js
- Ensures templates reference the same file at /js/overlay-web-adapter.js
parent 476704e9
../mbetterclient/web_dashboard/static/js
\ No newline at end of file
/**
* Web Overlay Adapter for MbetterClient
*
* This script bridges the gap between Qt WebChannel and Web postMessage communication.
* It provides a compatible interface for overlay templates when running in a web browser
* instead of the Qt player.
*
* When included in a template, it will:
* 1. Detect if running in Qt WebChannel or Web environment
* 2. Set up appropriate communication channels
* 3. Provide a unified interface for data access
* 4. Prevent Qt-specific script loading errors in web browser
*
* Usage: Include this script BEFORE the Qt WebChannel scripts in templates:
* <script src="/js/overlay-web-adapter.js"></script>
* <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
* <script src="overlay://overlay.js"></script>
*/
(function() {
console.log('[WebAdapter] Loading overlay web adapter...');
// Check if we're running in Qt WebChannel environment
const isQtEnvironment = typeof qt !== 'undefined' && qt.webChannelTransport;
console.log('[WebAdapter] Environment detected:', isQtEnvironment ? 'Qt WebChannel' : 'Web Browser');
// If already in Qt environment, no need for adapter
if (isQtEnvironment) {
console.log('[WebAdapter] Qt environment detected, adapter not needed');
return;
}
// Web environment - prevent Qt script loading errors
// Create dummy QWebChannel for browsers (prevents errors from qrc:/// URLs)
window.QWebChannel = function(transport, callback) {
console.log('[WebAdapter] QWebChannel called in web environment - using adapter instead');
// The adapter already set up window.overlay, so just call callback if provided
if (callback && window.overlay) {
callback({ objects: { overlay: window.overlay } });
}
};
// Prevent fetch/XMLHttpRequest errors for Qt URLs
const originalFetch = window.fetch;
window.fetch = function(url, options) {
if (typeof url === 'string' && (url.startsWith('qrc:///') || url.startsWith('overlay://'))) {
console.log('[WebAdapter] Blocking fetch to Qt URL:', url);
return Promise.resolve(new Response('', { status: 200 }));
}
return originalFetch.apply(this, arguments);
};
// Override XMLHttpRequest to prevent Qt URL errors
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
if (typeof url === 'string' && (url.startsWith('qrc:///') || url.startsWith('overlay://'))) {
console.log('[WebAdapter] Blocking XHR to Qt URL:', url);
// Return without actually opening - will fail silently
return;
}
return originalOpen.apply(this, arguments);
};
// Prevent script errors from Qt URLs by creating a MutationObserver
// that intercepts script elements with Qt URLs
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.tagName === 'SCRIPT') {
const src = node.getAttribute('src') || '';
if (src.startsWith('qrc:///') || src.startsWith('overlay://')) {
console.log('[WebAdapter] Preventing load of Qt script:', src);
node.removeAttribute('src');
// Set empty content to prevent further errors
node.textContent = '// Qt script blocked by web adapter';
}
}
});
});
});
// Start observing immediately
observer.observe(document.documentElement || document, {
childList: true,
subtree: true
});
// Web environment - set up postMessage bridge
console.log('[WebAdapter] Setting up postMessage bridge for web environment');
// Data storage
let fixtureData = null;
let timerState = { running: false, remaining_seconds: 0 };
let licenseText = '';
let completedMatches = [];
let overlayData = {};
let winningOutcomes = [];
// Signal emulation - simple event system
class Signal {
constructor() {
this.callbacks = [];
}
connect(callback) {
this.callbacks.push(callback);
}
disconnect(callback) {
const index = this.callbacks.indexOf(callback);
if (index > -1) {
this.callbacks.splice(index, 1);
}
}
emit(data) {
this.callbacks.forEach(callback => {
try {
callback(data);
} catch (e) {
console.error('[WebAdapter] Signal callback error:', e);
}
});
}
}
// Create overlay object that mimics Qt WebChannel interface
window.overlay = {
// Signals
dataUpdated: new Signal(),
// Log function
log: function(message) {
console.log('[Overlay]', message);
},
// Get fixture data - returns Promise (async like Qt)
getFixtureData: function() {
return new Promise((resolve) => {
if (fixtureData) {
resolve(JSON.stringify(fixtureData));
} else {
// Request data from parent
requestFixtureData();
// Wait for response with timeout
let attempts = 0;
const checkData = setInterval(() => {
attempts++;
if (fixtureData) {
clearInterval(checkData);
resolve(JSON.stringify(fixtureData));
} else if (attempts > 50) {
clearInterval(checkData);
resolve(JSON.stringify([]));
}
}, 100);
}
});
},
// Get timer state - returns Promise
getTimerState: function() {
return new Promise((resolve) => {
resolve(JSON.stringify(timerState));
});
},
// Get license text - returns Promise
getLicenseText: function() {
return new Promise((resolve) => {
if (licenseText) {
resolve(JSON.stringify({ license_text: licenseText }));
} else {
// Request data from parent
requestLicenseText();
let attempts = 0;
const checkData = setInterval(() => {
attempts++;
if (licenseText) {
clearInterval(checkData);
resolve(JSON.stringify({ license_text: licenseText }));
} else if (attempts > 50) {
clearInterval(checkData);
resolve(JSON.stringify({ license_text: '' }));
}
}, 100);
}
});
},
// Get completed matches - returns Promise
getCompletedMatches: function() {
return new Promise((resolve) => {
if (completedMatches && completedMatches.length > 0) {
resolve(JSON.stringify(completedMatches));
} else {
// Request data from parent
requestCompletedMatches();
let attempts = 0;
const checkData = setInterval(() => {
attempts++;
if (completedMatches && completedMatches.length > 0) {
clearInterval(checkData);
resolve(JSON.stringify(completedMatches));
} else if (attempts > 50) {
clearInterval(checkData);
resolve(JSON.stringify([]));
}
}, 100);
}
});
},
// Get winning outcomes - returns Promise
getWinningOutcomes: function(matchId) {
return new Promise((resolve) => {
// Request data from parent
requestWinningOutcomes(matchId);
let attempts = 0;
const checkData = setInterval(() => {
attempts++;
if (winningOutcomes && winningOutcomes.length > 0) {
clearInterval(checkData);
resolve(JSON.stringify(winningOutcomes));
} else if (attempts > 30) {
clearInterval(checkData);
resolve(JSON.stringify([]));
}
}, 100);
});
}
};
// Request functions - send postMessage to parent
function requestFixtureData() {
if (window.parent !== window) {
window.parent.postMessage({ type: 'requestFixtureData' }, '*');
}
}
function requestTimerState() {
if (window.parent !== window) {
window.parent.postMessage({ type: 'requestTimerState' }, '*');
}
}
function requestLicenseText() {
if (window.parent !== window) {
window.parent.postMessage({ type: 'requestLicenseText' }, '*');
}
}
function requestCompletedMatches() {
if (window.parent !== window) {
window.parent.postMessage({ type: 'requestCompletedMatches' }, '*');
}
}
function requestWinningOutcomes(matchId) {
if (window.parent !== window) {
window.parent.postMessage({ type: 'requestWinningOutcomes', matchId: matchId }, '*');
}
}
// Listen for messages from parent window
window.addEventListener('message', function(event) {
// Accept messages from same origin or any origin (for iframe embedding)
const message = event.data;
if (!message || typeof message !== 'object') {
return;
}
console.log('[WebAdapter] Received message:', message.type);
switch (message.type) {
case 'fixtureData':
fixtureData = message.fixtures || [];
console.log('[WebAdapter] Fixture data updated:', fixtureData.length, 'fixtures');
break;
case 'timerState':
timerState = message.running !== undefined ? {
running: message.running,
remaining_seconds: message.remaining_seconds || 0
} : message;
console.log('[WebAdapter] Timer state updated:', timerState);
// Emit signal for templates listening to dataUpdated
window.overlay.dataUpdated.emit({ timer_update: timerState });
break;
case 'timerUpdate':
timerState = message.running !== undefined ? {
running: message.running,
remaining_seconds: message.remaining_seconds || 0
} : message;
console.log('[WebAdapter] Timer update received:', timerState);
window.overlay.dataUpdated.emit({ timer_update: timerState });
break;
case 'licenseText':
licenseText = message.licenseText || '';
console.log('[WebAdapter] License text updated');
break;
case 'completedMatches':
completedMatches = message.matches || [];
console.log('[WebAdapter] Completed matches updated:', completedMatches.length, 'matches');
break;
case 'winningOutcomes':
winningOutcomes = message.outcomes || [];
console.log('[WebAdapter] Winning outcomes updated:', winningOutcomes.length, 'outcomes');
break;
case 'initialData':
// Initial data bundle sent when template loads
if (message.overlayData) {
overlayData = message.overlayData;
}
if (message.timerState) {
timerState = message.timerState;
}
if (message.licenseText) {
licenseText = message.licenseText;
}
console.log('[WebAdapter] Initial data received');
// Emit signal for templates
window.overlay.dataUpdated.emit(overlayData);
break;
case 'dataUpdated':
// Overlay data update
overlayData = { ...overlayData, ...message };
console.log('[WebAdapter] Overlay data updated:', Object.keys(message));
// Emit signal for templates
window.overlay.dataUpdated.emit(message);
break;
case 'matchUpdate':
console.log('[WebAdapter] Match update received');
window.overlay.dataUpdated.emit({ match_update: message });
break;
case 'resultUpdate':
console.log('[WebAdapter] Result update received');
window.overlay.dataUpdated.emit({ result_update: message });
break;
case 'videoStateChanged':
console.log('[WebAdapter] Video state changed:', message.state);
break;
case 'streamStateChanged':
console.log('[WebAdapter] Stream state changed:', message.state);
break;
case 'positionChanged':
// Video position update - can be used for time-based overlays
break;
default:
console.log('[WebAdapter] Unknown message type:', message.type);
}
});
// Request initial data when DOM is ready
function requestInitialData() {
console.log('[WebAdapter] Requesting initial data from parent');
if (window.parent !== window) {
window.parent.postMessage({ type: 'requestInitialData' }, '*');
}
// Also request specific data
requestFixtureData();
requestTimerState();
requestLicenseText();
requestCompletedMatches();
}
// Wait for DOM to be ready, then request initial data
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
// Small delay to ensure parent is ready
setTimeout(requestInitialData, 100);
});
} else {
setTimeout(requestInitialData, 100);
}
console.log('[WebAdapter] Web overlay adapter initialized');
// Flush any buffered console logs
if (typeof window.flushConsoleBuffer === 'function') {
setTimeout(window.flushConsoleBuffer, 200);
}
})();
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment