UNPKG

vlibras-player-webjs

Version:

Biblioteca JavaScript moderna para integração do VLibras Player com React, Vue, Angular e vanilla JS

515 lines 16.2 kB
"use strict"; /** * Sistema de cache inteligente para VLibras Player * Implementa diferentes estratégias de cache e preload */ Object.defineProperty(exports, "__esModule", { value: true }); exports.vlibrasCache = exports.VLibrasCacheUtils = exports.VLibrasCache = void 0; /** * Cache principal do VLibras */ class VLibrasCache { constructor(strategy) { this.memoryCache = new Map(); this.stats = { hits: 0, misses: 0, totalRequests: 0, hitRate: 0, currentSize: 0, maxSize: 0, entries: 0, oldestEntry: 0, newestEntry: 0 }; this.strategy = strategy; this.stats.maxSize = strategy.maxSize * 1024 * 1024; // Convert MB to bytes } /** * Obtém instância singleton */ static getInstance(strategy) { if (!this.instance) { this.instance = new VLibrasCache(strategy || { type: 'hybrid', maxSize: 50, ttl: 3600, // 1 hora compression: true, maxEntries: 1000 }); } return this.instance; } /** * Configura estratégia de cache */ static configure(strategy) { const instance = this.getInstance(); instance.strategy = { ...instance.strategy, ...strategy }; instance.stats.maxSize = instance.strategy.maxSize * 1024 * 1024; } /** * Armazena item no cache */ async set(key, data, customTtl) { const entry = { key, data, timestamp: Date.now(), ttl: customTtl || this.strategy.ttl, size: this.calculateSize(data), accessCount: 0, lastAccess: Date.now() }; // Verifica se precisa liberar espaço await this.ensureSpace(entry.size); // Armazena baseado na estratégia await this.storeEntry(entry); this.updateStats(); } /** * Recupera item do cache */ async get(key) { this.stats.totalRequests++; const entry = await this.retrieveEntry(key); if (!entry) { this.stats.misses++; this.updateHitRate(); return null; } // Verifica TTL if (this.isExpired(entry)) { await this.delete(key); this.stats.misses++; this.updateHitRate(); return null; } // Atualiza estatísticas de acesso entry.accessCount++; entry.lastAccess = Date.now(); await this.storeEntry(entry); this.stats.hits++; this.updateHitRate(); return entry.data; } /** * Remove item do cache */ async delete(key) { const deleted = await this.deleteEntry(key); if (deleted) { this.updateStats(); } return deleted; } /** * Limpa todo o cache */ async clear() { this.memoryCache.clear(); if (this.strategy.type === 'localStorage' || this.strategy.type === 'hybrid') { this.clearLocalStorage(); } if (this.strategy.type === 'indexedDB' || this.strategy.type === 'hybrid') { await this.clearIndexedDB(); } this.resetStats(); } /** * Verifica se uma chave existe no cache */ async has(key) { const entry = await this.retrieveEntry(key); return entry !== null && !this.isExpired(entry); } /** * Obtém estatísticas do cache */ getStats() { this.updateStats(); return { ...this.stats }; } /** * Preload de palavras comuns */ async preloadCommonWords(words) { console.log(`📦 Preload de ${words.length} palavras comuns...`); for (const word of words) { if (!(await this.has(`gloss:${word}`))) { // Simula tradução e armazena // TODO: Integrar com serviço real de tradução const mockGloss = `gloss_data_for_${word}`; await this.set(`gloss:${word}`, mockGloss, this.strategy.ttl * 2); // TTL maior para palavras comuns } } console.log('✅ Preload concluído'); } /** * Cache preditivo - precarrega palavras relacionadas */ async enablePredictiveCache(config) { if (!config.enabled) return; // TODO: Implementar IA para sugerir palavras relacionadas console.log('🤖 Cache preditivo ativado'); } /** * Cache por contexto */ async preloadForContext(context) { const contextWords = { dictionary: ['olá', 'obrigado', 'por favor', 'com licença', 'desculpa'], quiz: ['correto', 'errado', 'parabéns', 'tente novamente', 'próxima'], tutorial: ['início', 'continuar', 'próximo', 'anterior', 'fim'] }; const words = contextWords[context] || []; await this.preloadCommonWords(words); } /** * Calcula tamanho de um objeto */ calculateSize(data) { try { return JSON.stringify(data).length * 2; // Aproximação UTF-16 } catch { return 1024; // Fallback } } /** * Verifica se entrada está expirada */ isExpired(entry) { return Date.now() > entry.timestamp + (entry.ttl * 1000); } /** * Garante espaço disponível no cache */ async ensureSpace(requiredSize) { if (this.stats.currentSize + requiredSize <= this.stats.maxSize) { return; } // Estratégia LRU - remove os menos usados const entries = Array.from(this.memoryCache.values()) .sort((a, b) => a.lastAccess - b.lastAccess); let freedSize = 0; for (const entry of entries) { if (freedSize >= requiredSize) break; await this.delete(entry.key); freedSize += entry.size; } } /** * Armazena entrada baseado na estratégia */ async storeEntry(entry) { switch (this.strategy.type) { case 'memory': this.memoryCache.set(entry.key, entry); break; case 'localStorage': await this.storeInLocalStorage(entry); break; case 'indexedDB': await this.storeInIndexedDB(entry); break; case 'hybrid': // Pequenos no memory, grandes no localStorage/IndexedDB if (entry.size < 10 * 1024) { // 10KB this.memoryCache.set(entry.key, entry); } else { await this.storeInIndexedDB(entry); } break; } } /** * Recupera entrada baseado na estratégia */ async retrieveEntry(key) { // Tenta memory cache primeiro const memoryEntry = this.memoryCache.get(key); if (memoryEntry) { return memoryEntry; } // Tenta localStorage if (this.strategy.type === 'localStorage' || this.strategy.type === 'hybrid') { const localEntry = await this.retrieveFromLocalStorage(key); if (localEntry) return localEntry; } // Tenta IndexedDB if (this.strategy.type === 'indexedDB' || this.strategy.type === 'hybrid') { const idbEntry = await this.retrieveFromIndexedDB(key); if (idbEntry) return idbEntry; } return null; } /** * Remove entrada baseado na estratégia */ async deleteEntry(key) { let deleted = false; if (this.memoryCache.has(key)) { this.memoryCache.delete(key); deleted = true; } if (this.strategy.type === 'localStorage' || this.strategy.type === 'hybrid') { try { localStorage.removeItem(`vlibras:${key}`); deleted = true; } catch { } } if (this.strategy.type === 'indexedDB' || this.strategy.type === 'hybrid') { try { await this.deleteFromIndexedDB(key); deleted = true; } catch { } } return deleted; } /** * Armazena no localStorage */ async storeInLocalStorage(entry) { try { const data = this.strategy.compression ? this.compress(JSON.stringify(entry)) : JSON.stringify(entry); localStorage.setItem(`vlibras:${entry.key}`, data); } catch (error) { console.warn('Erro ao armazenar no localStorage:', error); } } /** * Recupera do localStorage */ async retrieveFromLocalStorage(key) { try { const data = localStorage.getItem(`vlibras:${key}`); if (!data) return null; const parsedData = this.strategy.compression ? JSON.parse(this.decompress(data)) : JSON.parse(data); return parsedData; } catch { return null; } } /** * Remove do localStorage */ deleteFromLocalStorage(key) { try { const fullKey = `vlibras:${key}`; if (localStorage.getItem(fullKey)) { localStorage.removeItem(fullKey); return true; } return false; } catch (error) { console.warn('Erro ao remover do localStorage:', error); return false; } } /** * Armazena no IndexedDB */ async storeInIndexedDB(entry) { try { const db = await this.openIndexedDB(); const transaction = db.transaction(['vlibras_cache'], 'readwrite'); const store = transaction.objectStore('vlibras_cache'); await new Promise((resolve, reject) => { const request = store.put(entry, entry.key); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } catch (error) { console.warn('Erro ao armazenar no IndexedDB:', error); // Fallback para localStorage await this.storeInLocalStorage(entry); } } /** * Recupera do IndexedDB */ async retrieveFromIndexedDB(key) { try { const db = await this.openIndexedDB(); const transaction = db.transaction(['vlibras_cache'], 'readonly'); const store = transaction.objectStore('vlibras_cache'); return new Promise((resolve, reject) => { const request = store.get(key); request.onsuccess = () => resolve(request.result || null); request.onerror = () => reject(request.error); }); } catch (error) { console.warn('Erro ao recuperar do IndexedDB:', error); // Fallback para localStorage return this.retrieveFromLocalStorage(key); } } /** * Remove do IndexedDB */ async deleteFromIndexedDB(key) { try { const db = await this.openIndexedDB(); const transaction = db.transaction(['vlibras_cache'], 'readwrite'); const store = transaction.objectStore('vlibras_cache'); await new Promise((resolve, reject) => { const request = store.delete(key); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } catch (error) { console.warn('Erro ao remover do IndexedDB:', error); // Fallback para localStorage this.deleteFromLocalStorage(key); } } /** * Abre conexão com IndexedDB */ async openIndexedDB() { return new Promise((resolve, reject) => { const request = indexedDB.open('vlibras_cache_db', 1); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains('vlibras_cache')) { const store = db.createObjectStore('vlibras_cache'); store.createIndex('timestamp', 'timestamp', { unique: false }); store.createIndex('ttl', 'ttl', { unique: false }); } }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } /** * Limpa localStorage */ clearLocalStorage() { const keys = Object.keys(localStorage); keys.forEach(key => { if (key.startsWith('vlibras:')) { localStorage.removeItem(key); } }); } /** * Limpa IndexedDB */ async clearIndexedDB() { // TODO: Implementar IndexedDB clear } /** * Comprime dados (implementação simples) */ compress(data) { // TODO: Implementar compressão real (LZ-string, gzip, etc.) return btoa(data); } /** * Descomprime dados */ decompress(data) { try { return atob(data); } catch { return data; } } /** * Atualiza estatísticas gerais */ updateStats() { this.stats.entries = this.memoryCache.size; this.stats.currentSize = Array.from(this.memoryCache.values()) .reduce((total, entry) => total + entry.size, 0); if (this.stats.entries > 0) { const timestamps = Array.from(this.memoryCache.values()).map(e => e.timestamp); this.stats.oldestEntry = Math.min(...timestamps); this.stats.newestEntry = Math.max(...timestamps); } } /** * Atualiza taxa de acerto */ updateHitRate() { this.stats.hitRate = this.stats.totalRequests > 0 ? this.stats.hits / this.stats.totalRequests : 0; } /** * Reseta estatísticas */ resetStats() { this.stats = { hits: 0, misses: 0, totalRequests: 0, hitRate: 0, currentSize: 0, maxSize: this.stats.maxSize, entries: 0, oldestEntry: 0, newestEntry: 0 }; } } exports.VLibrasCache = VLibrasCache; /** * Utilitários de cache */ class VLibrasCacheUtils { /** * Cria chave de cache baseada em parâmetros */ static createKey(type, ...params) { return `${type}:${params.map(p => String(p)).join(':')}`; } /** * Estima tamanho ideal de cache baseado no dispositivo */ static getOptimalCacheSize() { const memory = performance.memory; const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); if (memory) { // Usa 5% da memória disponível const availableMemory = memory.jsHeapSizeLimit; const optimalSize = (availableMemory * 0.05) / (1024 * 1024); // Convert to MB return Math.min(optimalSize, isMobile ? 25 : 100); } return isMobile ? 10 : 50; // Fallback } /** * Migra cache antigo para nova versão */ static async migrateCache(oldVersion, newVersion) { console.log(`🔄 Migrando cache de ${oldVersion} para ${newVersion}`); // TODO: Implementar migração baseada na versão const cache = VLibrasCache.getInstance(); await cache.clear(); // Por enquanto, limpa cache antigo console.log('✅ Migração de cache concluída'); } } exports.VLibrasCacheUtils = VLibrasCacheUtils; // Exporta instância singleton exports.vlibrasCache = VLibrasCache.getInstance(); //# sourceMappingURL=VLibrasCache.js.map