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;
});