Smart Shared Cache System

Shared cache with TTL, background refresh and tab sync

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Smart Shared Cache System
// @namespace    smart.cache.shared
// @version      2.0
// @description  Shared cache with TTL, background refresh and tab sync
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const PREFIX = "GLOBAL_CACHE_";
    const channel = new BroadcastChannel("GLOBAL_CACHE_SYNC");

    const Cache = {

        // 📦 set with TTL
        set(key, value, ttl = 300) {
            const data = {
                value,
                expiry: Date.now() + ttl * 1000
            };

            GM_setValue(PREFIX + key, JSON.stringify(data));

            // уведомляем другие вкладки
            channel.postMessage({ type: "update", key });
        },

        // 📥 get with auto cleanup
        get(key) {
            const raw = GM_getValue(PREFIX + key, null);
            if (!raw) return null;

            try {
                const data = JSON.parse(raw);

                if (Date.now() > data.expiry) {
                    GM_deleteValue(PREFIX + key);
                    return null;
                }

                return data.value;

            } catch {
                GM_deleteValue(PREFIX + key);
                return null;
            }
        },

        // 🔄 get with background refresh
        async getOrRefresh(key, ttl, fetchFunction) {

            const existing = this.get(key);

            if (existing) return existing;

            // если нет — загружаем в фоне
            const fresh = await fetchFunction();
            this.set(key, fresh, ttl);
            return fresh;
        },

        // 🗑 удалить
        delete(key) {
            GM_deleteValue(PREFIX + key);
            channel.postMessage({ type: "delete", key });
        },

        // 🧹 очистить всё
        clearAll() {
            GM_listValues().forEach(k => {
                if (k.startsWith(PREFIX)) {
                    GM_deleteValue(k);
                }
            });

            channel.postMessage({ type: "clear" });
        }
    };

    // 🔔 Синхронизация вкладок
    channel.onmessage = (event) => {
        const msg = event.data;
        if (!msg) return;

        if (msg.type === "update") {
            console.log("Cache updated in another tab:", msg.key);
        }

        if (msg.type === "delete") {
            console.log("Cache deleted in another tab:", msg.key);
        }

        if (msg.type === "clear") {
            console.log("Cache cleared in another tab");
        }
    };

    // ====== Пример использования ======

    async function example() {
        const data = await Cache.getOrRefresh(
            "apiData",
            600,
            async () => {
                console.log("Fetching fresh data...");
                const response = await fetch("https://api.example.com/data");
                return await response.json();
            }
        );

        console.log("Data:", data);
    }

    // example(); // раскомментируй при необходимости

})();