Professional widget integration goes beyond basic implementation to ensure reliable, performant, and maintainable deployments. This tutorial covers advanced techniques for production-ready widget integration including:
The practices outlined here build upon fundamental integration knowledge to deliver enterprise-grade widget implementations.
This tutorial is designed for:
Prerequisites: Basic familiarity with widget integration, JavaScript, and web development concepts.
By completing this tutorial, you will:
To complete this tutorial, you will need:
Professional widget integration requires comprehensive error handling that gracefully manages various failure scenarios.
First, let's create a robust widget loader with comprehensive error detection:
<!DOCTYPE html>
<html lang="en"
class WidgetManager {
constructor(containerId
class WidgetManager {
// ... previous methods
async initializeWidget
This tutorial provides production-ready patterns for professional widget integration. Always test thoroughly in staging environments before deploying to production, and monitor performance metrics continuously to ensure optimal user experience.
class PerformantWidgetManager extends WidgetManager {
constructor(containerId, widgetConfig) {
super(containerId, widgetConfig);
this.intersectionObserver = null;
this.isVisible = false;
this.resourcesPreloaded = false;
}
initLazyLoading() {
if (!('IntersectionObserver' in window)) {
// Fallback for older browsers
this.loadWidget();
return;
}
this.intersectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !this.isVisible) {
this.isVisible = true;
this.preloadResources();
setTimeout(() => this.loadWidget(), 100);
this.intersectionObserver.disconnect();
}
});
},
{
rootMargin: '100px',
threshold: 0.1
}
);
this.intersectionObserver.observe(this.container);
}
async preloadResources() {
if (this.resourcesPreloaded) return;
const resources = [
'https://widgets.sir.sportradar.com/css/api/widgets.css',
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'
];
const preloadPromises = resources.map(url => {
return new Promise((resolve) => {
const link = document.createElement('link');
link.rel = 'preload';
link.as = url.includes('.css') ? 'style' : 'script';
link.href = url;
link.onload = resolve;
link.onerror = resolve; // Don't fail on preload errors
document.head.appendChild(link);
});
});
await Promise.allSettled(preloadPromises);
this.resourcesPreloaded = true;
}
async loadWidget() {
// Show skeleton loading instead of basic loading
this.showSkeletonLoader();
try {
await super.loadWidget();
} catch (error) {
this.handleError(error);
}
}
showSkeletonLoader() {
this.container.innerHTML = `
<div class="skeleton-loader">
<div class="skeleton-header"></div>
<div class="skeleton-content">
<div class="skeleton-team">
<div class="skeleton-logo"></div>
<div class="skeleton-name"></div>
</div>
<div class="skeleton-score"></div>
<div class="skeleton-team">
<div class="skeleton-logo"></div>
<div class="skeleton-name"></div>
</div>
</div>
</div>
`;
}
}
// Usage with performance monitoring
const performantManager = new PerformantWidgetManager('scoreboard-widget', {
widget: 'sr:match:scoreboard',
matchId: 12345678
});
// Start performance monitoring
performance.mark('widget-init-start');
performantManager.initLazyLoading();class MonitoredWidgetManager extends PerformantWidgetManager {
constructor(containerId, widgetConfig) {
super(containerId, widgetConfig);
this.metrics = {
loadStartTime: null,
scriptLoadTime: null,
widgetRenderTime: null,
totalLoadTime: null,
errorCount: 0,
retryCount: 0
};
this.setupGlobalErrorHandling();
}
setupGlobalErrorHandling() {
// Capture unhandled widget errors
window.addEventListener('error', (event) => {
if (event.filename && event.filename.includes('widgets.sir.sportradar.com')) {
this.reportError({
type: 'script_error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno
});
}
});
// Capture promise rejections
window.addEventListener('unhandledrejection', (event) => {
if (event.reason && event.reason.toString().includes('widget')) {
this.reportError({
type: 'promise_rejection',
message: event.reason.toString(),
stack: event.reason.stack
});
}
});
}
async loadWidget() {
this.metrics.loadStartTime = performance.now();
performance.mark('widget-load-start');
try {
await super.loadWidget();
this.recordSuccessMetrics();
} catch (error) {
this.metrics.errorCount++;
this.handleError(error);
}
}
recordSuccessMetrics() {
const endTime = performance.now();
this.metrics.totalLoadTime = endTime - this.metrics.loadStartTime;
performance.mark('widget-load-end');
performance.measure('widget-total-load', 'widget-load-start', 'widget-load-end');
// Report metrics to analytics
this.reportMetrics({
widget_load_time: this.metrics.totalLoadTime,
widget_type: this.config.widget,
match_id: this.config.matchId,
retry_count: this.retryCount,
success: true
});
// Log to console for debugging
console.log('Widget Metrics:', this.metrics);
}
reportMetrics(metrics) {
// Google Analytics 4
if (window.gtag) {
gtag('event', 'widget_performance', {
custom_map: { metric_1: 'load_time' },
metric_1: Math.round(metrics.widget_load_time)
});
}
// Custom analytics endpoint
if (this.config.analyticsEndpoint) {
fetch(this.config.analyticsEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
timestamp: new Date().toISOString(),
page_url: window.location.href,
user_agent: navigator.userAgent,
...metrics
})
}).catch(err => console.warn('Analytics reporting failed:', err));
}
}
reportError(error) {
const errorReport = {
timestamp: new Date().toISOString(),
page_url: window.location.href,
user_agent: navigator.userAgent,
widget_type: this.config.widget,
match_id: this.config.matchId,
error_type: error.type || 'unknown',
error_message: error.message,
stack_trace: error.stack,
retry_count: this.retryCount,
metrics: this.metrics
};
// Log to console
console.error('Widget Error Report:', errorReport);
// Send to error tracking service
if (window.Sentry) {
Sentry.captureException(error, {
tags: {
component: 'widget',
widget_type: this.config.widget
},
extra: errorReport
});
}
// Custom error endpoint
if (this.config.errorEndpoint) {
fetch(this.config.errorEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorReport)
}).catch(err => console.warn('Error reporting failed:', err));
}
}
}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secure Widget Integration</title>
<!-- Content Security Policy -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline' https://widgets.sir.sportradar.com;
style-src 'self' 'unsafe-inline' https://widgets.sir.sportradar.com https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https://widgets.sir.sportradar.com https://*.sportradar.com;
connect-src 'self' https://api.sportradar.com https://widgets.sir.sportradar.com;
frame-src 'none';
object-src 'none';
">
<!-- Additional security headers -->
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta http-equiv="X-Frame-Options" content="DENY">
<meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin">
</head>
<body>
<main>
<h1>Live Match Scoreboard</h1>
<div
id="scoreboard-widget"
class="widget-container"
role="region"
aria-label="Live match scoreboard"
aria-live="polite"
aria-busy="true"
>
<div class="widget-loading" aria-label="Loading match data">
<span class="sr-only">Loading match data, please wait...</span>
<div class="spinner" aria-hidden="true"></div>
Loading match data...
</div>
</div>
</main>
<script>
class SecureWidgetManager extends MonitoredWidgetManager {
constructor(containerId, widgetConfig) {
super(containerId, widgetConfig);
this.validateConfiguration();
this.setupSecurityHeaders();
}
validateConfiguration() {
// Validate required parameters
if (!this.config.matchId || typeof this.config.matchId !== 'number') {
throw new Error('Invalid or missing matchId');
}
if (!this.config.widget || typeof this.config.widget !== 'string') {
throw new Error('Invalid or missing widget type');
}
// Sanitize string inputs
if (this.config.title) {
this.config.title = this.sanitizeString(this.config.title);
}
// Validate numeric inputs
if (this.config.seasonId && !Number.isInteger(this.config.seasonId)) {
throw new Error('Invalid seasonId - must be an integer');
}
}
sanitizeString(input) {
if (typeof input !== 'string') return '';
// Remove potentially dangerous characters
return input
.replace(/[<>]/g, '') // Remove angle brackets
.replace(/javascript:/gi, '') // Remove javascript: protocol
.replace(/on\w+=/gi, '') // Remove event handlers
.trim()
.substring(0, 200); // Limit length
}
setupSecurityHeaders() {
// Verify CSP is in place
const metaCSP = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
if (!metaCSP) {
console.warn('Content Security Policy not found - consider adding CSP headers');
}
// Check for secure context
if (!window.isSecureContext) {
console.warn('Insecure context detected - widgets should be served over HTTPS');
}
}
async loadWidgetScript() {
// Verify script integrity
const expectedOrigin = 'https://widgets.sir.sportradar.com';
if (!this.config.allowInsecure && !window.location.protocol.startsWith('https')) {
throw new Error('Widgets require HTTPS in production');
}
return super.loadWidgetScript();
}
initializeWidget() {
// Add accessibility attributes
this.container.setAttribute('aria-busy', 'false');
this.container.setAttribute('aria-live', 'polite');
return super.initializeWidget();
}
onSuccess() {
// Update accessibility state
this.container.setAttribute('aria-busy', 'false');
this.container.removeAttribute('aria-label');
// Announce success to screen readers
this.announceToScreenReader('Match data loaded successfully');
super.onSuccess();
}
announceToScreenReader(message) {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'assertive');
announcement.setAttribute('aria-atomic', 'true');
announcement.style.position = 'absolute';
announcement.style.left = '-10000px';
announcement.style.width = '1px';
announcement.style.height = '1px';
announcement.style.overflow = 'hidden';
document.body.appendChild(announcement);
announcement.textContent = message;
setTimeout(() => {
document.body.removeChild(announcement);
}, 1000);
}
}
// Initialize secure widget manager
const secureManager = new SecureWidgetManager('scoreboard-widget', {
widget: 'sr:match:scoreboard',
matchId: 12345678,
allowInsecure: false // Enforce HTTPS in production
});
secureManager.initLazyLoading();
</script>
<style>
.sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
border: 0 !important;
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #1976d2;
border-radius: 50%;
animation: spin 1s linear infinite;
display: inline-block;
margin-right: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.widget-container {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.widget-container:focus-within {
outline: 2px solid #1976d2;
outline-offset: 2px;
}
</style>
</body>
</html>class ProductionWidgetManager extends SecureWidgetManager {
constructor(containerId, widgetConfig) {
super(containerId, widgetConfig);
this.deploymentChecks = [];
this.healthCheckInterval = null;
this.performanceObserver = null;
this.runDeploymentChecks();
}
async runDeploymentChecks() {
console.log('🚀 Running production deployment checks...');
const checks = [
{ name: 'HTTPS', check: () => window.location.protocol === 'https:' },
{ name: 'CSP', check: () => document.querySelector('meta[http-equiv="Content-Security-Policy"]') },
{ name: 'Error Handling', check: () => typeof this.reportError === 'function' },
{ name: 'Analytics', check: () => window.gtag || this.config.analyticsEndpoint },
{ name: 'Accessibility', check: () => this.container.hasAttribute('aria-label') },
{ name: 'Performance Monitoring', check: () => 'PerformanceObserver' in window },
{ name: 'Valid Configuration', check: () => this.validateProductionConfig() }
];
for (const check of checks) {
try {
const passed = await check.check();
this.deploymentChecks.push({
name: check.name,
status: passed ? 'PASS' : 'FAIL',
timestamp: new Date().toISOString()
});
console.log(`${passed ? '✅' : '❌'} ${check.name}`);
} catch (error) {
this.deploymentChecks.push({
name: check.name,
status: 'ERROR',
error: error.message,
timestamp: new Date().toISOString()
});
console.log(`❌ ${check.name}: ${error.message}`);
}
}
this.reportDeploymentStatus();
}
validateProductionConfig() {
const requiredFields = ['widget', 'matchId'];
const missingFields = requiredFields.filter(field => !this.config[field]);
if (missingFields.length > 0) {
throw new Error(`Missing required fields: ${missingFields.join(', ')}`);
}
// Check for development-only settings
if (this.config.debug && window.location.hostname !== 'localhost') {
console.warn('Debug mode enabled in production environment');
}
return true;
}
async loadWidget() {
// Start health monitoring
this.startHealthMonitoring();
// Initialize performance monitoring
this.initPerformanceMonitoring();
try {
await super.loadWidget();
this.reportDeploymentSuccess();
} catch (error) {
this.reportDeploymentFailure(error);
throw error;
}
}
startHealthMonitoring() {
// Periodic health checks
this.healthCheckInterval = setInterval(() => {
this.performHealthCheck();
}, 60000); // Check every minute
// Clean up on page unload
window.addEventListener('beforeunload', () => {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
if (this.performanceObserver) {
this.performanceObserver.disconnect();
}
});
}
performHealthCheck() {
const healthMetrics = {
timestamp: new Date().toISOString(),
widget_responsive: this.isWidgetResponsive(),
memory_usage: this.getMemoryUsage(),
error_rate: this.calculateErrorRate(),
load_time: this.metrics.totalLoadTime
};
// Report to monitoring service
if (this.config.healthCheckEndpoint) {
fetch(this.config.healthCheckEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(healthMetrics)
}).catch(err => console.warn('Health check reporting failed:', err));
}
// Log locally for debugging
console.log('Health Check:', healthMetrics);
}
isWidgetResponsive() {
try {
// Check if widget container has content
const hasContent = this.container.children.length > 0;
// Check if widget is visible
const rect = this.container.getBoundingClientRect();
const isVisible = rect.width > 0 && rect.height > 0;
return hasContent && isVisible;
} catch (error) {
return false;
}
}
getMemoryUsage() {
if ('memory' in performance) {
return {
used: Math.round(performance.memory.usedJSHeapSize / 1048576), // MB
total: Math.round(performance.memory.totalJSHeapSize / 1048576), // MB
limit: Math.round(performance.memory.jsHeapSizeLimit / 1048576) // MB
};
}
return null;
}
calculateErrorRate() {
const totalAttempts = this.retryCount + 1;
return totalAttempts > 0 ? (this.metrics.errorCount / totalAttempts) * 100 : 0;
}
initPerformanceMonitoring() {
if ('PerformanceObserver' in window) {
this.performanceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name.includes('widget')) {
this.reportPerformanceEntry(entry);
}
}
});
this.performanceObserver.observe({ entryTypes: ['measure', 'navigation', 'resource'] });
}
}
reportPerformanceEntry(entry) {
const performanceData = {
name: entry.name,
duration: entry.duration,
startTime: entry.startTime,
entryType: entry.entryType,
timestamp: new Date().toISOString()
};
console.log('Performance Entry:', performanceData);
// Report to analytics
this.reportMetrics(performanceData);
}
reportDeploymentStatus() {
const report = {
timestamp: new Date().toISOString(),
page_url: window.location.href,
user_agent: navigator.userAgent,
checks: this.deploymentChecks,
overall_status: this.deploymentChecks.every(check => check.status === 'PASS') ? 'HEALTHY' : 'ISSUES_DETECTED'
};
console.log('📊 Deployment Status Report:', report);
if (this.config.deploymentReportEndpoint) {
fetch(this.config.deploymentReportEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(report)
}).catch(err => console.warn('Deployment reporting failed:', err));
}
}
reportDeploymentSuccess() {
performance.mark('widget-deployment-success');
console.log('🎉 Widget deployment successful');
this.reportMetrics({
deployment_status: 'success',
deployment_time: performance.now(),
environment: this.getEnvironment()
});
}
reportDeploymentFailure(error) {
performance.mark('widget-deployment-failure');
console.error('💥 Widget deployment failed:', error);
this.reportError({
type: 'deployment_failure',
message: error.message,
stack: error.stack,
environment: this.getEnvironment(),
deployment_checks: this.deploymentChecks
});
}
getEnvironment() {
const hostname = window.location.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1') return 'development';
if (hostname.includes('staging') || hostname.includes('test')) return 'staging';
return 'production';
}
}
// Production deployment with comprehensive configuration
const productionManager = new ProductionWidgetManager('scoreboard-widget', {
widget: 'sr:match:scoreboard',
matchId: 12345678,
// Analytics endpoints
analyticsEndpoint: 'https://analytics.yoursite.com/api/events',
errorEndpoint: 'https://monitoring.yoursite.com/api/errors',
healthCheckEndpoint: 'https://monitoring.yoursite.com/api/health',
deploymentReportEndpoint: 'https://monitoring.yoursite.com/api/deployment',
// Security settings
allowInsecure: false,
// Performance settings
maxRetries: 3,
loadTimeout: 15000,
// Feature flags
debug: false,
enableHealthChecks: true,
enablePerformanceMonitoring: true
});
// Initialize with full monitoring
productionManager.initLazyLoading();