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
JavaScript
"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