v3 · 37 files

ProjectElly/lib/ellyQuietStats.js 2.5 KB Raw
/**
 * Sends aggregate Elly statistics to VrilsoftApi (JWT). Fails silently.
 * @module ellyQuietStats
 */

import { getApiBase, getJwt } from '../options/ellyPremium.js';

const ELLY_HOST_TALLY_KEY = 'ellyBlockTallyByHost';
const MAX_HOST_KEYS = 400;

export { ELLY_HOST_TALLY_KEY, MAX_HOST_KEYS };

/**
 * @param {Record<string, number>} tally
 * @param {number} n
 * @returns {{ host: string, count: number }[]}
 */
function topBlockedHosts(tally, n) {
    return Object.entries(tally || {})
        .map(([host, count]) => ({ host, count: Number(count) || 0 }))
        .filter((x) => x.host && x.count > 0)
        .sort((a, b) => b.count - a.count)
        .slice(0, n);
}

/**
 * Same ordering as sent to the API (for Options UI).
 * @param {Record<string, number>} tally
 * @param {number} [limit]
 * @returns {{ host: string, count: number }[]}
 */
export function getTopBlockedSitesForUi(tally, limit = 10) {
    return topBlockedHosts(tally, limit);
}

/**
 * Builds the JSON body for POST /i/v1/e/s (server derives email from JWT; no email in body).
 * @returns {Promise<Record<string, unknown>>}
 */
export async function buildQuietStatsPayload() {
    const data = await chrome.storage.sync.get(['statistics', ELLY_HOST_TALLY_KEY]);
    const stats = data.statistics || {};
    const totalBlocks = Object.values(stats).reduce((sum, day) => sum + (day.blocks || 0), 0);
    const timeSavedSeconds = Object.values(stats).reduce((sum, day) => sum + (day.timeSaved || 0), 0);
    const daysActive = Object.keys(stats).length;
    const mostBlocked = topBlockedHosts(data[ELLY_HOST_TALLY_KEY], 15);
    const manifest = chrome.runtime.getManifest();
    return {
        totalBlocks,
        timeSavedSeconds,
        daysActive,
        mostBlocked,
        extensionVersion: manifest?.version || ''
    };
}

/**
 * POSTs stats when a JWT exists. Swallows errors (network, 503 if server key unset).
 * @returns {Promise<boolean>} True if the server returned a success status (e.g. 204).
 */
export async function tryPostQuietStatsReport() {
    try {
        const jwt = await getJwt();
        if (!jwt) {
            return false;
        }
        const base = await getApiBase();
        const body = await buildQuietStatsPayload();
        const res = await fetch(`${base}/i/v1/e/s`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${jwt}`
            },
            body: JSON.stringify(body)
        });
        return res.ok;
    } catch {
        return false;
    }
}