Class_generator/js/core/env-config.js
StillHammer 1f8688c4aa Fix WebSocket logging system and add comprehensive network features
- Fix WebSocket server to properly broadcast logs to all connected clients
- Integrate professional logging system with real-time WebSocket interface
- Add network status indicator with DigitalOcean Spaces connectivity
- Implement AWS Signature V4 authentication for private bucket access
- Add JSON content loader with backward compatibility to JS modules
- Restore navigation breadcrumb system with comprehensive logging
- Add multiple content formats: JSON + JS with automatic discovery
- Enhance top bar with logger toggle and network status indicator
- Remove deprecated temp-games module and clean up unused files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-15 23:05:14 +08:00

269 lines
9.1 KiB
JavaScript

// === CONFIGURATION ENVIRONNEMENT ===
// Configuration basée sur les variables d'environnement
class EnvConfig {
constructor() {
this.config = {
// DigitalOcean Spaces Configuration
DO_ENDPOINT: 'https://autocollant.fra1.digitaloceanspaces.com',
DO_CONTENT_PATH: 'Class_generator/ContentMe',
// Authentification DigitalOcean Spaces (depuis .env)
DO_ACCESS_KEY: 'DO801XTYPE968NZGAQM3',
DO_SECRET_KEY: '5aCCBiS9K+J8gsAe3M3/0GlliHCNjtLntwla1itCN1s',
DO_REGION: 'fra1',
// Content loading configuration - PRIORITÉ AU LOCAL
USE_REMOTE_CONTENT: true, // Activé maintenant qu'on a les clés
FALLBACK_TO_LOCAL: true, // TOUJOURS essayer local
TRY_REMOTE_FIRST: false, // Essayer local d'abord
REMOTE_TIMEOUT: 3000, // Timeout rapide pour éviter les attentes
// Debug et logging
DEBUG_MODE: true, // Activé pour debugging
LOG_CONTENT_LOADING: true
};
this.remoteContentUrl = this.buildContentUrl();
if (typeof logSh !== 'undefined') {
logSh(`🔧 EnvConfig initialisé: ${this.remoteContentUrl}`, 'INFO');
} else {
console.log('🔧 EnvConfig initialisé:', this.remoteContentUrl);
}
}
buildContentUrl() {
const endpoint = this.config.DO_ENDPOINT.replace(/\/$/, ''); // Supprimer / final si présent
const path = this.config.DO_CONTENT_PATH.replace(/^\//, ''); // Supprimer / initial si présent
return `${endpoint}/${path}/`;
}
get(key) {
return this.config[key];
}
set(key, value) {
this.config[key] = value;
// Rebuilder l'URL si changement d'endpoint ou path
if (key === 'DO_ENDPOINT' || key === 'DO_CONTENT_PATH') {
this.remoteContentUrl = this.buildContentUrl();
}
}
// Méthodes utilitaires
isRemoteContentEnabled() {
return this.config.USE_REMOTE_CONTENT;
}
isFallbackEnabled() {
return this.config.FALLBACK_TO_LOCAL;
}
isDebugMode() {
return this.config.DEBUG_MODE;
}
shouldLogContentLoading() {
return this.config.LOG_CONTENT_LOADING;
}
getRemoteContentUrl() {
return this.remoteContentUrl;
}
// Configuration dynamique depuis l'interface
updateRemoteConfig(endpoint, path) {
this.set('DO_ENDPOINT', endpoint);
this.set('DO_CONTENT_PATH', path);
logSh(`🔄 Configuration distante mise à jour: ${this.remoteContentUrl}`, 'INFO');
}
// Méthode pour tester la connectivité
async testRemoteConnection() {
try {
// Test simple avec un fichier qui devrait exister
const testUrl = `${this.remoteContentUrl}test.json`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.config.REMOTE_TIMEOUT);
const authHeaders = await this.getAuthHeaders('HEAD', testUrl);
const response = await fetch(testUrl, {
method: 'HEAD',
signal: controller.signal,
headers: authHeaders
});
clearTimeout(timeoutId);
return {
success: response.ok || response.status === 403, // 403 = connexion OK mais privé
status: response.status,
url: testUrl,
isPrivate: response.status === 403
};
} catch (error) {
return {
success: false,
error: error.message,
url: `${this.remoteContentUrl}test.json`,
isTimeout: error.name === 'AbortError'
};
}
}
// Génère les headers d'authentification AWS Signature V4 pour DigitalOcean Spaces
async getAuthHeaders(method = 'HEAD', url = '') {
const headers = {};
if (this.config.DO_ACCESS_KEY && this.config.DO_SECRET_KEY) {
try {
const authHeaders = await this.generateAWSSignature(method, url);
Object.assign(headers, authHeaders);
if (typeof logSh !== 'undefined') {
logSh('🔐 Headers d\'authentification DigitalOcean générés', 'DEBUG');
}
} catch (error) {
if (typeof logSh !== 'undefined') {
logSh(`⚠️ Erreur génération signature AWS: ${error.message}`, 'ERROR');
} else {
console.warn('⚠️ Erreur génération signature AWS:', error.message);
}
}
}
return headers;
}
// Implémentation AWS Signature V4 pour DigitalOcean Spaces
async generateAWSSignature(method, url) {
const accessKey = this.config.DO_ACCESS_KEY;
const secretKey = this.config.DO_SECRET_KEY;
const region = this.config.DO_REGION;
const service = 's3';
const now = new Date();
const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, '');
const timeStamp = now.toISOString().slice(0, 19).replace(/[-:]/g, '') + 'Z';
// Parse URL
const urlObj = new URL(url || this.remoteContentUrl);
const host = urlObj.hostname;
const canonicalUri = urlObj.pathname || '/';
const canonicalQueryString = urlObj.search ? urlObj.search.slice(1) : '';
// Canonical headers
const canonicalHeaders = `host:${host}\nx-amz-date:${timeStamp}\n`;
const signedHeaders = 'host;x-amz-date';
// Create canonical request
const payloadHash = 'UNSIGNED-PAYLOAD'; // Pour HEAD requests
const canonicalRequest = [
method,
canonicalUri,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
payloadHash
].join('\n');
// Create string to sign
const algorithm = 'AWS4-HMAC-SHA256';
const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
const canonicalRequestHash = await this.sha256(canonicalRequest);
const stringToSign = [
algorithm,
timeStamp,
credentialScope,
canonicalRequestHash
].join('\n');
// Calculate signature
const signingKey = await this.getSigningKey(secretKey, dateStamp, region, service);
const signatureBytes = await this.hmacSha256(signingKey, stringToSign);
const signature = this.uint8ArrayToHex(signatureBytes);
// Create authorization header
const authorization = `${algorithm} Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
return {
'Authorization': authorization,
'X-Amz-Date': timeStamp,
'X-Amz-Content-Sha256': payloadHash
};
}
// Utilitaires cryptographiques avec crypto.subtle (vraie implémentation)
async sha256(message) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
async hmacSha256(key, message) {
const encoder = new TextEncoder();
// Si key est une string, l'encoder
let keyData;
if (typeof key === 'string') {
keyData = encoder.encode(key);
} else {
keyData = key;
}
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(message));
return new Uint8Array(signature);
}
async getSigningKey(secretKey, dateStamp, region, service) {
const encoder = new TextEncoder();
let key = encoder.encode('AWS4' + secretKey);
key = await this.hmacSha256(key, dateStamp);
key = await this.hmacSha256(key, region);
key = await this.hmacSha256(key, service);
key = await this.hmacSha256(key, 'aws4_request');
return key;
}
// Convertir Uint8Array en hex string
uint8ArrayToHex(uint8Array) {
return Array.from(uint8Array).map(b => b.toString(16).padStart(2, '0')).join('');
}
// Diagnostic complet
getDiagnostics() {
return {
remoteContentUrl: this.remoteContentUrl,
remoteEnabled: this.isRemoteContentEnabled(),
fallbackEnabled: this.isFallbackEnabled(),
debugMode: this.isDebugMode(),
endpoint: this.config.DO_ENDPOINT,
contentPath: this.config.DO_CONTENT_PATH,
timestamp: new Date().toISOString()
};
}
}
// Export global
window.EnvConfig = EnvConfig;
// Instance globale
window.envConfig = new EnvConfig();
// Export Node.js (optionnel)
if (typeof module !== 'undefined' && module.exports) {
module.exports = EnvConfig;
}