Rehidratar Date al cargar caché y refresh no-destructivo
Bug raíz del click-no-abre-overlay: chrome.storage serializa con JSON, los Date (sessionStartTime, sessionEndTime, estimatedEndDate) volvían como strings ISO, y createWeeklyTracker llamaba .toLocaleDateString en un string. rehydrateDates los devuelve a Date al cargar. Mejoras adicionales: - showOverlayError ya no destruye el panel si hay tracker visible: saca un aviso temporal en el header (auto-oculta a los 7s). - DOM en el aviso construido con textContent, no innerHTML, para cerrar la puerta a XSS aunque el msg sea hardcodeado. - Limpieza del andamio [CUT-DIAG] usado para encontrar el bug; __claudeUsageTracker se queda expuesto para depuración futura. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
65
content.js
65
content.js
@@ -662,10 +662,34 @@
|
|||||||
let refreshPromise = null;
|
let refreshPromise = null;
|
||||||
|
|
||||||
// -------- Cache (chrome.storage.local) --------
|
// -------- Cache (chrome.storage.local) --------
|
||||||
|
// chrome.storage serializa con JSON: los Date salen como strings ISO. Al
|
||||||
|
// cargar, rehidratamos los campos conocidos para que createWeeklyTracker
|
||||||
|
// pueda llamar toLocaleDateString/Time sin TypeError.
|
||||||
|
function rehydrateDates(cache) {
|
||||||
|
if (!cache) return null;
|
||||||
|
const toDate = (v) => {
|
||||||
|
if (v == null) return null;
|
||||||
|
if (v instanceof Date) return v;
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
const d = new Date(v);
|
||||||
|
return isNaN(d.getTime()) ? null : d;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
if (cache.pageData) {
|
||||||
|
cache.pageData.sessionStartTime = toDate(cache.pageData.sessionStartTime);
|
||||||
|
cache.pageData.sessionEndTime = toDate(cache.pageData.sessionEndTime);
|
||||||
|
}
|
||||||
|
if (cache.dailyData) {
|
||||||
|
cache.dailyData.estimatedEndDate = toDate(cache.dailyData.estimatedEndDate);
|
||||||
|
}
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
function loadCache() {
|
function loadCache() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
try {
|
try {
|
||||||
chrome.storage.local.get([CACHE_KEY], result => resolve(result[CACHE_KEY] || null));
|
chrome.storage.local.get([CACHE_KEY], result => resolve(rehydrateDates(result[CACHE_KEY] || null)));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
}
|
}
|
||||||
@@ -815,12 +839,7 @@
|
|||||||
<span class="claude-usage-tracker-fab-text">…</span>
|
<span class="claude-usage-tracker-fab-text">…</span>
|
||||||
`;
|
`;
|
||||||
fab.addEventListener('click', onFabClick);
|
fab.addEventListener('click', onFabClick);
|
||||||
// DIAG: si pointerdown fira pero click no, algo está cancelando el click
|
|
||||||
fab.addEventListener('pointerdown', () => {
|
|
||||||
console.log('[CUT-DIAG] FAB pointerdown recibido');
|
|
||||||
});
|
|
||||||
document.body.appendChild(fab);
|
document.body.appendChild(fab);
|
||||||
console.log('[CUT-DIAG] FAB inyectado en DOM. id:', fab.id, 'parent:', fab.parentElement?.tagName);
|
|
||||||
loadCache().then(updateFabState);
|
loadCache().then(updateFabState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -842,27 +861,21 @@
|
|||||||
else fab.classList.remove('claude-usage-tracker-fab--hidden');
|
else fab.classList.remove('claude-usage-tracker-fab--hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onFabClick(event) {
|
async function onFabClick() {
|
||||||
console.log('[CUT-DIAG] onFabClick entrada, event:', event?.type, 'target:', event?.target?.tagName);
|
|
||||||
try {
|
try {
|
||||||
const cache = await loadCache();
|
const cache = await loadCache();
|
||||||
console.log('[CUT-DIAG] cache cargada:', cache ? 'OK pageData=' + !!cache.pageData + ' dailyData=' + !!cache.dailyData : 'null');
|
|
||||||
openOverlayPanel(cache);
|
openOverlayPanel(cache);
|
||||||
const ov = document.getElementById(OVERLAY_ID);
|
|
||||||
console.log('[CUT-DIAG] overlay tras openOverlayPanel: existe=' + !!ov,
|
|
||||||
ov ? 'display=' + getComputedStyle(ov).display + ' zIndex=' + getComputedStyle(ov).zIndex + ' opacity=' + getComputedStyle(ov).opacity : '');
|
|
||||||
const ageMin = cache ? cacheAgeMin(cache.scrapedAt) : Infinity;
|
const ageMin = cache ? cacheAgeMin(cache.scrapedAt) : Infinity;
|
||||||
if (ageMin > CACHE_TTL_MS / 60000) {
|
if (ageMin > CACHE_TTL_MS / 60000) {
|
||||||
refreshInBackground('overlay-stale');
|
refreshInBackground('overlay-stale');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[CUT-DIAG] onFabClick ERROR:', e);
|
console.error('[Claude Usage Tracker] FAB click error:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- Overlay propio --------
|
// -------- Overlay propio --------
|
||||||
function openOverlayPanel(cache) {
|
function openOverlayPanel(cache) {
|
||||||
console.log('[CUT-DIAG] openOverlayPanel called, cache válida:', !!(cache && cache.pageData));
|
|
||||||
try {
|
try {
|
||||||
closeOverlayPanel();
|
closeOverlayPanel();
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
@@ -904,9 +917,8 @@
|
|||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
document.addEventListener('keydown', overlayEscapeHandler);
|
document.addEventListener('keydown', overlayEscapeHandler);
|
||||||
console.log('[CUT-DIAG] overlay appended OK. body.lastChild.id=', document.body.lastChild?.id);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[CUT-DIAG] openOverlayPanel ERROR:', e);
|
console.error('[Claude Usage Tracker] overlay error:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,7 +967,26 @@
|
|||||||
const ov = document.getElementById(OVERLAY_ID);
|
const ov = document.getElementById(OVERLAY_ID);
|
||||||
if (!ov) return;
|
if (!ov) return;
|
||||||
const body = ov.querySelector('.claude-usage-tracker-overlay-body');
|
const body = ov.querySelector('.claude-usage-tracker-overlay-body');
|
||||||
if (body) body.innerHTML = `<div class="claude-usage-tracker-overlay-empty">${msg}</div>`;
|
const hasTracker = body && body.querySelector('.claude-usage-tracker-container');
|
||||||
|
if (hasTracker) {
|
||||||
|
// No destruir el tracker visible — mostrar aviso temporal en el header
|
||||||
|
const header = ov.querySelector('.claude-usage-tracker-overlay-header');
|
||||||
|
if (header) {
|
||||||
|
const existing = header.querySelector('.claude-usage-tracker-overlay-notice');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
const notice = document.createElement('span');
|
||||||
|
notice.className = 'claude-usage-tracker-overlay-notice';
|
||||||
|
notice.textContent = msg;
|
||||||
|
header.appendChild(notice);
|
||||||
|
setTimeout(() => { if (notice.parentNode) notice.remove(); }, 7000);
|
||||||
|
}
|
||||||
|
} else if (body) {
|
||||||
|
body.textContent = '';
|
||||||
|
const empty = document.createElement('div');
|
||||||
|
empty.className = 'claude-usage-tracker-overlay-empty';
|
||||||
|
empty.textContent = msg;
|
||||||
|
body.appendChild(empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- Refresh: abrir modal en background, scrapear, cerrar --------
|
// -------- Refresh: abrir modal en background, scrapear, cerrar --------
|
||||||
|
|||||||
12
styles.css
12
styles.css
@@ -666,6 +666,18 @@
|
|||||||
animation: claude-tracker-spin-pulse 1.2s ease-in-out infinite;
|
animation: claude-tracker-spin-pulse 1.2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.claude-usage-tracker-overlay-notice {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: rgba(245, 158, 11, 0.12);
|
||||||
|
border: 1px solid rgba(245, 158, 11, 0.35);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #f59e0b;
|
||||||
|
animation: claude-tracker-fade-in 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes claude-tracker-spin-pulse {
|
@keyframes claude-tracker-spin-pulse {
|
||||||
0%, 100% { opacity: 0.6; }
|
0%, 100% { opacity: 0.6; }
|
||||||
50% { opacity: 1; }
|
50% { opacity: 1; }
|
||||||
|
|||||||
Reference in New Issue
Block a user