Newer
Older
gnexus-creds-extension / src / background.js
import { listSecrets, revealSecret, createSecret, getMe, getCategories } from "./api.js";

const CACHE_TTL_MS = 5 * 60 * 1000;

async function getSettings() {
  return chrome.storage.local.get({ token: "", baseUrl: "https://creds.gnexus.space" });
}

async function setSettings(settings) {
  return chrome.storage.local.set(settings);
}

async function getSecretsCached(force = false) {
  const now = Date.now();
  if (!force) {
    try {
      const cached = await chrome.storage.session.get(["secretsCache", "cacheTimestamp"]);
      if (cached.secretsCache && cached.cacheTimestamp && now - cached.cacheTimestamp < CACHE_TTL_MS) {
        return cached.secretsCache;
      }
    } catch {
      // session storage may be unavailable in some contexts
    }
  }
  const { token, baseUrl } = await getSettings();
  if (!token) throw new Error("No API token configured");
  const data = await listSecrets(token, baseUrl);
  try {
    await chrome.storage.session.set({ secretsCache: data, cacheTimestamp: now });
  } catch {
    // ignore session storage errors
  }
  return data;
}

async function invalidateCache() {
  try {
    await chrome.storage.session.remove(["secretsCache", "cacheTimestamp"]);
  } catch {
    // ignore
  }
}

function secretsForDomain(secrets, domain) {
  if (!secrets?.items) return [];
  return secrets.items.filter((s) => {
    const src = (s.source || "").toLowerCase();
    const title = (s.title || "").toLowerCase();
    const d = domain.toLowerCase();
    return src.includes(d) || title.includes(d);
  });
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  (async () => {
    try {
      const { token, baseUrl } = await getSettings();
      switch (message.type) {
        case "GET_SETTINGS":
          sendResponse({ ok: true, data: await getSettings() });
          break;
        case "SAVE_SETTINGS":
          await setSettings(message.payload);
          invalidateCache();
          sendResponse({ ok: true });
          break;
        case "VERIFY_TOKEN":
          {
            const me = await getMe(message.payload.token, message.payload.baseUrl);
            sendResponse({ ok: true, data: me });
          }
          break;
        case "LIST_SECRETS":
          {
            const data = await getSecretsCached(message.payload?.force);
            sendResponse({ ok: true, data });
          }
          break;
        case "SEARCH_SECRETS":
          {
            const data = await listSecrets(token, baseUrl, { q: message.payload.q });
            sendResponse({ ok: true, data });
          }
          break;
        case "REVEAL_SECRET":
          {
            const data = await revealSecret(token, baseUrl, message.payload.id);
            sendResponse({ ok: true, data });
          }
          break;
        case "CREATE_SECRET":
          {
            const data = await createSecret(token, baseUrl, message.payload);
            invalidateCache();
            try {
              await chrome.storage.session.remove("pendingSave");
              await chrome.action.setBadgeText({ text: "" });
            } catch {
              // ignore
            }
            sendResponse({ ok: true, data });
          }
          break;
        case "GET_SECRETS_FOR_DOMAIN":
          {
            const all = await getSecretsCached();
            const matched = secretsForDomain(all, message.payload.domain);
            sendResponse({ ok: true, data: matched });
          }
          break;
        case "GET_CATEGORIES":
          {
            const data = await getCategories(token, baseUrl);
            sendResponse({ ok: true, data });
          }
          break;
        case "PENDING_SAVE":
          {
            const pending = {
              ...message.payload,
              timestamp: Date.now(),
            };
            await chrome.storage.session.set({ pendingSave: pending });
            try {
              await chrome.action.setBadgeText({ text: "1" });
              await chrome.action.setBadgeBackgroundColor({ color: "#238636" });
            } catch {
              // ignore
            }
            sendResponse({ ok: true });
          }
          break;
        case "GET_PENDING_SAVE":
          {
            const stored = await chrome.storage.session.get("pendingSave");
            sendResponse({ ok: true, data: stored.pendingSave || null });
          }
          break;
        case "CLEAR_PENDING_SAVE":
          {
            await chrome.storage.session.remove("pendingSave");
            try {
              await chrome.action.setBadgeText({ text: "" });
            } catch {
              // ignore
            }
            sendResponse({ ok: true });
          }
          break;
        default:
          sendResponse({ ok: false, error: "Unknown message type" });
      }
    } catch (err) {
      sendResponse({ ok: false, error: err.message });
    }
  })();
  return true;
});