rezilient.js
Version:
Rezilient.js - Revolutionary offline-first framework with AI-awareness, principle-driven development, carbon-conscious computing, and self-healing capabilities
673 lines (569 loc) • 18.2 kB
JavaScript
// src/compatibility/BrowserCompat.js
import { EnvironmentDetector, BrowserAPICompat } from '../utils/environment.js';
/**
* @class BrowserCompat
* Comprehensive browser compatibility layer for Aether.js
*
* Supports:
* - Chrome 60+ (2017)
* - Firefox 55+ (2017)
* - Safari 11+ (2017)
* - Edge 79+ (2020)
* - Mobile browsers
* - Progressive enhancement
* - Polyfill management
*/
export class BrowserCompat {
static _features = new Map();
static _polyfills = new Map();
static _initialized = false;
/**
* Initialize browser compatibility
*/
static async init() {
if (this._initialized) return;
console.log('🌐 BrowserCompat: Initializing universal browser support...');
// Detect browser features
this.detectFeatures();
// Load required polyfills
await this.loadPolyfills();
// Setup progressive enhancement
this.setupProgressiveEnhancement();
this._initialized = true;
console.log('✅ BrowserCompat: Universal browser support active');
}
/**
* Detect browser features
*/
static detectFeatures() {
if (!EnvironmentDetector.isBrowser()) {
// Node.js environment - assume modern features
this._features.set('es6', true);
this._features.set('modules', true);
this._features.set('promises', true);
this._features.set('fetch', true);
return;
}
const features = {
// ES6+ Features
es6: this.supportsES6(),
modules: this.supportsModules(),
promises: this.supportsPromises(),
asyncAwait: this.supportsAsyncAwait(),
classes: this.supportsClasses(),
arrow: this.supportsArrowFunctions(),
destructuring: this.supportsDestructuring(),
spread: this.supportsSpread(),
// Web APIs
fetch: this.supportsFetch(),
webWorkers: this.supportsWebWorkers(),
serviceWorker: this.supportsServiceWorker(),
indexedDB: this.supportsIndexedDB(),
localStorage: this.supportsLocalStorage(),
sessionStorage: this.supportsSessionStorage(),
// Modern APIs
intersectionObserver: this.supportsIntersectionObserver(),
mutationObserver: this.supportsMutationObserver(),
performanceObserver: this.supportsPerformanceObserver(),
resizeObserver: this.supportsResizeObserver(),
// WebXR and Spatial
webXR: this.supportsWebXR(),
webGL: this.supportsWebGL(),
webGL2: this.supportsWebGL2(),
// Media APIs
mediaDevices: this.supportsMediaDevices(),
webRTC: this.supportsWebRTC(),
// Crypto APIs
crypto: this.supportsCrypto(),
subtleCrypto: this.supportsSubtleCrypto(),
// Network APIs
networkInformation: this.supportsNetworkInformation(),
// Performance APIs
performanceMemory: this.supportsPerformanceMemory(),
performanceTiming: this.supportsPerformanceTiming(),
// CSS Features
cssGrid: this.supportsCSSGrid(),
cssFlexbox: this.supportsCSSFlexbox(),
cssCustomProperties: this.supportsCSSCustomProperties(),
// Touch and Input
touchEvents: this.supportsTouchEvents(),
pointerEvents: this.supportsPointerEvents()
};
// Store detected features
for (const [feature, supported] of Object.entries(features)) {
this._features.set(feature, supported);
}
console.log('🔍 Browser features detected:', features);
}
/**
* Load required polyfills
*/
static async loadPolyfills() {
const polyfillsNeeded = [];
// Check which polyfills are needed
if (!this._features.get('fetch')) {
polyfillsNeeded.push('fetch');
}
if (!this._features.get('promises')) {
polyfillsNeeded.push('promises');
}
if (!this._features.get('intersectionObserver')) {
polyfillsNeeded.push('intersection-observer');
}
if (!this._features.get('resizeObserver')) {
polyfillsNeeded.push('resize-observer');
}
if (!this._features.get('webWorkers')) {
polyfillsNeeded.push('web-workers');
}
// Load polyfills
for (const polyfill of polyfillsNeeded) {
await this.loadPolyfill(polyfill);
}
if (polyfillsNeeded.length > 0) {
console.log('📦 Polyfills loaded:', polyfillsNeeded);
}
}
/**
* Load individual polyfill
*/
static async loadPolyfill(name) {
try {
switch (name) {
case 'fetch':
await this.loadFetchPolyfill();
break;
case 'promises':
await this.loadPromisePolyfill();
break;
case 'intersection-observer':
await this.loadIntersectionObserverPolyfill();
break;
case 'resize-observer':
await this.loadResizeObserverPolyfill();
break;
case 'web-workers':
await this.loadWebWorkerPolyfill();
break;
default:
console.warn(`Unknown polyfill: ${name}`);
}
this._polyfills.set(name, true);
} catch (error) {
console.warn(`Failed to load polyfill ${name}:`, error);
this._polyfills.set(name, false);
}
}
/**
* Fetch polyfill
*/
static async loadFetchPolyfill() {
if (typeof fetch === 'undefined') {
// Simple fetch polyfill using XMLHttpRequest
window.fetch = function(url, options = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', url);
// Set headers
if (options.headers) {
for (const [key, value] of Object.entries(options.headers)) {
xhr.setRequestHeader(key, value);
}
}
xhr.onload = () => {
const response = {
ok: xhr.status >= 200 && xhr.status < 300,
status: xhr.status,
statusText: xhr.statusText,
text: () => Promise.resolve(xhr.responseText),
json: () => Promise.resolve(JSON.parse(xhr.responseText)),
headers: {
get: (name) => xhr.getResponseHeader(name)
}
};
resolve(response);
};
xhr.onerror = () => reject(new Error('Network error'));
xhr.send(options.body);
});
};
}
}
/**
* Promise polyfill
*/
static async loadPromisePolyfill() {
if (typeof Promise === 'undefined') {
// Basic Promise polyfill
window.Promise = class Promise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.handlers = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.handlers.forEach(handler => handler.onFulfilled(value));
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.value = reason;
this.handlers.forEach(handler => handler.onRejected(reason));
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
const handler = {
onFulfilled: (value) => {
try {
const result = onFulfilled ? onFulfilled(value) : value;
resolve(result);
} catch (error) {
reject(error);
}
},
onRejected: (reason) => {
try {
const result = onRejected ? onRejected(reason) : reason;
reject(result);
} catch (error) {
reject(error);
}
}
};
if (this.state === 'fulfilled') {
handler.onFulfilled(this.value);
} else if (this.state === 'rejected') {
handler.onRejected(this.value);
} else {
this.handlers.push(handler);
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
return new Promise(resolve => resolve(value));
}
static reject(reason) {
return new Promise((_, reject) => reject(reason));
}
};
}
}
/**
* IntersectionObserver polyfill
*/
static async loadIntersectionObserverPolyfill() {
if (typeof IntersectionObserver === 'undefined') {
// Basic IntersectionObserver polyfill
window.IntersectionObserver = class IntersectionObserver {
constructor(callback, options = {}) {
this.callback = callback;
this.options = options;
this.targets = new Set();
}
observe(target) {
this.targets.add(target);
// Simplified implementation - always consider visible
setTimeout(() => {
this.callback([{
target,
isIntersecting: true,
intersectionRatio: 1
}]);
}, 100);
}
unobserve(target) {
this.targets.delete(target);
}
disconnect() {
this.targets.clear();
}
};
}
}
/**
* ResizeObserver polyfill
*/
static async loadResizeObserverPolyfill() {
if (typeof ResizeObserver === 'undefined') {
// Basic ResizeObserver polyfill
window.ResizeObserver = class ResizeObserver {
constructor(callback) {
this.callback = callback;
this.targets = new Set();
}
observe(target) {
this.targets.add(target);
// Simplified implementation using window resize
const handler = () => {
this.callback([{
target,
contentRect: target.getBoundingClientRect()
}]);
};
window.addEventListener('resize', handler);
target._resizeHandler = handler;
}
unobserve(target) {
this.targets.delete(target);
if (target._resizeHandler) {
window.removeEventListener('resize', target._resizeHandler);
delete target._resizeHandler;
}
}
disconnect() {
this.targets.forEach(target => this.unobserve(target));
}
};
}
}
/**
* Web Worker polyfill
*/
static async loadWebWorkerPolyfill() {
if (typeof Worker === 'undefined') {
// Basic Worker polyfill using setTimeout
window.Worker = class Worker {
constructor(scriptURL) {
this.scriptURL = scriptURL;
this.onmessage = null;
this.onerror = null;
}
postMessage(data) {
// Simulate async processing
setTimeout(() => {
if (this.onmessage) {
this.onmessage({ data: `Processed: ${JSON.stringify(data)}` });
}
}, 10);
}
terminate() {
// No-op in polyfill
}
addEventListener(type, listener) {
if (type === 'message') {
this.onmessage = listener;
} else if (type === 'error') {
this.onerror = listener;
}
}
removeEventListener(type, listener) {
if (type === 'message') {
this.onmessage = null;
} else if (type === 'error') {
this.onerror = null;
}
}
};
}
}
/**
* Setup progressive enhancement
*/
static setupProgressiveEnhancement() {
if (!EnvironmentDetector.isBrowser()) return;
// Add feature classes to document
const html = document.documentElement;
for (const [feature, supported] of this._features) {
html.classList.add(supported ? `aether-${feature}` : `aether-no-${feature}`);
}
// Add browser detection classes
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.includes('chrome')) {
html.classList.add('aether-chrome');
} else if (userAgent.includes('firefox')) {
html.classList.add('aether-firefox');
} else if (userAgent.includes('safari')) {
html.classList.add('aether-safari');
} else if (userAgent.includes('edge')) {
html.classList.add('aether-edge');
}
// Add mobile detection
if (/android|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent)) {
html.classList.add('aether-mobile');
} else {
html.classList.add('aether-desktop');
}
}
// Feature detection methods
static supportsES6() {
try {
return typeof Symbol !== 'undefined' &&
typeof Map !== 'undefined' &&
typeof Set !== 'undefined';
} catch { return false; }
}
static supportsModules() {
const script = document.createElement('script');
return 'noModule' in script;
}
static supportsPromises() {
return typeof Promise !== 'undefined';
}
static supportsAsyncAwait() {
try {
return (async () => {})().constructor === Promise;
} catch { return false; }
}
static supportsClasses() {
try {
eval('class Test {}');
return true;
} catch { return false; }
}
static supportsArrowFunctions() {
try {
eval('() => {}');
return true;
} catch { return false; }
}
static supportsDestructuring() {
try {
eval('const {a} = {a: 1}');
return true;
} catch { return false; }
}
static supportsSpread() {
try {
eval('[...[], 1]');
return true;
} catch { return false; }
}
static supportsFetch() {
return typeof fetch !== 'undefined';
}
static supportsWebWorkers() {
return typeof Worker !== 'undefined';
}
static supportsServiceWorker() {
return 'serviceWorker' in navigator;
}
static supportsIndexedDB() {
return typeof indexedDB !== 'undefined';
}
static supportsLocalStorage() {
try {
return typeof localStorage !== 'undefined' && localStorage !== null;
} catch { return false; }
}
static supportsSessionStorage() {
try {
return typeof sessionStorage !== 'undefined' && sessionStorage !== null;
} catch { return false; }
}
static supportsIntersectionObserver() {
return typeof IntersectionObserver !== 'undefined';
}
static supportsMutationObserver() {
return typeof MutationObserver !== 'undefined';
}
static supportsPerformanceObserver() {
return typeof PerformanceObserver !== 'undefined';
}
static supportsResizeObserver() {
return typeof ResizeObserver !== 'undefined';
}
static supportsWebXR() {
return 'xr' in navigator;
}
static supportsWebGL() {
try {
const canvas = document.createElement('canvas');
return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
} catch { return false; }
}
static supportsWebGL2() {
try {
const canvas = document.createElement('canvas');
return !!canvas.getContext('webgl2');
} catch { return false; }
}
static supportsMediaDevices() {
return 'mediaDevices' in navigator;
}
static supportsWebRTC() {
return typeof RTCPeerConnection !== 'undefined';
}
static supportsCrypto() {
return typeof crypto !== 'undefined' && 'getRandomValues' in crypto;
}
static supportsSubtleCrypto() {
return typeof crypto !== 'undefined' && 'subtle' in crypto;
}
static supportsNetworkInformation() {
return 'connection' in navigator;
}
static supportsPerformanceMemory() {
return typeof performance !== 'undefined' && 'memory' in performance;
}
static supportsPerformanceTiming() {
return typeof performance !== 'undefined' && 'timing' in performance;
}
static supportsCSSGrid() {
return CSS.supports('display', 'grid');
}
static supportsCSSFlexbox() {
return CSS.supports('display', 'flex');
}
static supportsCSSCustomProperties() {
return CSS.supports('--custom', 'property');
}
static supportsTouchEvents() {
return 'ontouchstart' in window;
}
static supportsPointerEvents() {
return 'onpointerdown' in window;
}
/**
* Check if feature is supported
*/
static isSupported(feature) {
return this._features.get(feature) || false;
}
/**
* Get all supported features
*/
static getSupportedFeatures() {
return Object.fromEntries(this._features);
}
/**
* Get browser compatibility report
*/
static getCompatibilityReport() {
const supported = Array.from(this._features.entries()).filter(([, support]) => support);
const unsupported = Array.from(this._features.entries()).filter(([, support]) => !support);
const polyfilled = Array.from(this._polyfills.entries()).filter(([, loaded]) => loaded);
return {
environment: EnvironmentDetector.getEnvironment(),
totalFeatures: this._features.size,
supportedFeatures: supported.length,
unsupportedFeatures: unsupported.length,
polyfillsLoaded: polyfilled.length,
compatibilityScore: (supported.length / this._features.size) * 100,
supported: Object.fromEntries(supported),
unsupported: Object.fromEntries(unsupported),
polyfills: Object.fromEntries(polyfilled)
};
}
}
// Auto-initialize in browser
if (EnvironmentDetector.isBrowser()) {
// Initialize after DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => BrowserCompat.init());
} else {
BrowserCompat.init();
}
}