import {runJuice, juiceRequest, giteaToken} from './juice.mjs'; import {vaultUnlocked, vaultUsername, useVaultCredential} from './vault-worker.mjs'; import {CONNECTION} from './connection-state.mjs'; import {vaultRequest} from './human-api.mjs'; function trustedPage(message, sender, type) { return sender.origin === 'https://persimmons.app' && new URL(sender.url).origin === sender.origin && sender.frameId === 0 && message?.type === type; } export async function connectionStatus(message, sender) { if (!trustedPage(message, sender, 'persimmons.connection.v1')) return null; const result = {connected: false, version: chrome.runtime.getManifest().version}; if (!/^[a-z0-9][a-z0-9_.-]{0,99}$/.test(message.owner)) return result; const hint = (await chrome.storage.local.get(CONNECTION))[CONNECTION]; if (hint?.owner && hint.owner !== message.owner) return result; try { const data = await vaultRequest('GET', message.owner); if (!hint?.owner) await chrome.storage.local.set({[CONNECTION]: {owner: message.owner}}); const storageId = `human-vault:https://persimmons.app:${message.owner}`; result.connected = true; result.initialized = !!(await chrome.storage.local.get(storageId))[storageId]; result.unlocked = vaultUnlocked(message.owner); result.empty = data.items.length === 0; } catch {} return result; } export async function vaultInventory(message, sender) { if (!trustedPage(message, sender, 'persimmons.inventory.v1')) return null; if (!/^[a-z0-9][a-z0-9_.-]{0,99}$/.test(message.owner)) return null; const hint = (await chrome.storage.local.get(CONNECTION))[CONNECTION]; if (hint?.owner !== message.owner || !vaultUnlocked(message.owner)) return null; try { const data = await vaultRequest('GET', hint.owner); const items = []; for (const metadata of data.items) { const full = await vaultRequest('GET', hint.owner, metadata.id); const username = await vaultUsername(hint.owner, full.item); if (typeof username === 'string' && username) { items.push({id: metadata.id, origin: metadata.origin, username}); } } return {items}; } catch { return null; } } async function liveJuice(owner) { const hint = (await chrome.storage.local.get(CONNECTION))[CONNECTION]; if (hint?.owner !== owner) throw new Error(); await vaultRequest('GET', owner); } let juiceBusy = false; export async function juiceMessage(message, sender) { if (juiceBusy) return {ok: false, message: 'A Juice operation is already running.'}; juiceBusy = true; try { return await runJuice(message, sender, { live: liveJuice, unlocked: async owner => { if (!vaultUnlocked(owner) || !(await chrome.storage.local.get(`human-vault:https://persimmons.app:${owner}`))[`human-vault:https://persimmons.app:${owner}`]) throw new Error(); }, source: async (owner, id) => (await vaultRequest('GET', owner, id)).item, use: useVaultCredential, request: juiceRequest, provider: giteaToken, id: () => crypto.randomUUID().replaceAll('-', '') }); } finally { juiceBusy = false; } } // External responses contain only status, operation IDs and safe username metadata. chrome.runtime.onMessageExternal.addListener((message, sender, reply) => { const work = message?.type === 'persimmons.juice.v1' ? juiceMessage(message, sender) : message?.type === 'persimmons.inventory.v1' ? vaultInventory(message, sender) : connectionStatus(message, sender); work.then(reply, () => reply(null)); return true; });