Theseus: bundle Pithos 0.3.8
This commit is contained in:
parent
6b9ada6dbd
commit
155b445c68
6 changed files with 139 additions and 1 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "pithos",
|
"id": "pithos",
|
||||||
"name": "Pithos",
|
"name": "Pithos",
|
||||||
"version": "0.3.7",
|
"version": "0.3.8",
|
||||||
"description": "Run s3d, the Sia S3 gateway, from the Theseus sidebar: connect it to a Sia indexer, create S3 users and access keys, browse and share buckets, and watch uploads reach Sia.",
|
"description": "Run s3d, the Sia S3 gateway, from the Theseus sidebar: connect it to a Sia indexer, create S3 users and access keys, browse and share buckets, and watch uploads reach Sia.",
|
||||||
"author": "Silent Mode",
|
"author": "Silent Mode",
|
||||||
"icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIwIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2RkYzk3Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMWU4ZjZhIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTIyIDZoMjB2NWMwIDItMiAzLTIgNSA4IDMgMTQgMTEgMTQgMjEgMCAxMi05IDIxLTIyIDIxUzEwIDQ5IDEwIDM3YzAtMTAgNi0xOCAxNC0yMSAwLTItMi0zLTItNXoiIGZpbGw9InVybCgjZykiLz48cGF0aCBkPSJNMTQgMzBoMzZNMTMgNDBoMzgiIHN0cm9rZT0iIzBiMWYxOCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9Ii41NSIvPjwvc3ZnPgo=",
|
"icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIwIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2RkYzk3Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMWU4ZjZhIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTIyIDZoMjB2NWMwIDItMiAzLTIgNSA4IDMgMTQgMTEgMTQgMjEgMCAxMi05IDIxLTIyIDIxUzEwIDQ5IDEwIDM3YzAtMTAgNi0xOCAxNC0yMSAwLTItMi0zLTItNXoiIGZpbGw9InVybCgjZykiLz48cGF0aCBkPSJNMTQgMzBoMzZNMTMgNDBoMzgiIHN0cm9rZT0iIzBiMWYxOCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9Ii41NSIvPjwvc3ZnPgo=",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,13 @@ export async function createPithos(opts = {}) {
|
||||||
const tickets = new Map();
|
const tickets = new Map();
|
||||||
const subscribers = new Set();
|
const subscribers = new Set();
|
||||||
let versionCache = null;
|
let versionCache = null;
|
||||||
|
// Pithos's own version: addon.json in the Theseus add-on, package.json elsewhere.
|
||||||
|
const pithosVersion = (() => {
|
||||||
|
for (const f of ['../addon.json', '../package.json']) {
|
||||||
|
try { const v = JSON.parse(fs.readFileSync(new URL(f, import.meta.url), 'utf8')).version; if (v) return v; } catch {}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})();
|
||||||
|
|
||||||
const broadcast = (event, data) => {
|
const broadcast = (event, data) => {
|
||||||
const msg = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
const msg = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
||||||
|
|
@ -144,6 +151,7 @@ export async function createPithos(opts = {}) {
|
||||||
const v = await version();
|
const v = await version();
|
||||||
return {
|
return {
|
||||||
host: hostName,
|
host: hostName,
|
||||||
|
pithos: pithosVersion,
|
||||||
daemon: daemon.status(),
|
daemon: daemon.status(),
|
||||||
external: await probeExternal(c),
|
external: await probeExternal(c),
|
||||||
s3d: { ...v, pinned: S3D_VERSION, installable: !!assetForPlatform(), outdated: !!(v?.version && cmpVersion(v.version, S3D_VERSION) < 0) },
|
s3d: { ...v, pinned: S3D_VERSION, installable: !!assetForPlatform(), outdated: !!(v?.version && cmpVersion(v.version, S3D_VERSION) < 0) },
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,22 @@ module.exports = {
|
||||||
return { url: p.viewUrl(apiPath) };
|
return { url: p.viewUrl(apiPath) };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Footer updater: stage the newest signed Pithos from the extension
|
||||||
|
// channel, then (second click) ask Theseus to restart and apply it.
|
||||||
|
// Theseus asks the user before restarting. Older Theseus builds lack
|
||||||
|
// these hooks; the panel then points to Settings > Extensions.
|
||||||
|
api.onMessage("update", async ({ step = "stage" } = {}) => {
|
||||||
|
if (step === "apply") {
|
||||||
|
if (typeof api.restartApp !== "function") return { fallback: "settings" };
|
||||||
|
return await api.restartApp();
|
||||||
|
}
|
||||||
|
if (typeof api.checkAndStageSelfUpdate !== "function") return { fallback: "settings" };
|
||||||
|
const r = await api.checkAndStageSelfUpdate();
|
||||||
|
const staged = r?.status === "staged" || r?.status === "already-staged";
|
||||||
|
return { staged, status: r?.status || "unknown", detail: r?.detail || null, current: r?.current || null, next: r?.next || null };
|
||||||
|
});
|
||||||
|
api.onMessage("open-settings", ({ section = "" } = {}) => { api.openSettings?.(String(section)); return { ok: true }; });
|
||||||
|
|
||||||
// A full browser-tab view, for anyone who prefers it to the sidebar.
|
// A full browser-tab view, for anyone who prefers it to the sidebar.
|
||||||
api.onMessage("open-in-tab", async () => {
|
api.onMessage("open-in-tab", async () => {
|
||||||
const p = await ensureServer();
|
const p = await ensureServer();
|
||||||
|
|
|
||||||
|
|
@ -351,3 +351,27 @@ body.has-player .toasts { bottom: 84px; }
|
||||||
.pl-btn { width: 28px; height: 28px; font-size: 14px; }
|
.pl-btn { width: 28px; height: 28px; font-size: 14px; }
|
||||||
.pl-main { width: 34px; height: 34px; }
|
.pl-main { width: 34px; height: 34px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Footer: pithos.sia, the version and the updater (like Aegis's) */
|
||||||
|
.brandfoot { position: fixed; left: 0; right: 0; bottom: 0; z-index: 14; height: 28px; display: flex; align-items: center;
|
||||||
|
justify-content: space-between; padding: 0 12px; background: var(--surface); border-top: 1px solid var(--border);
|
||||||
|
font-size: 11.5px; color: var(--muted); }
|
||||||
|
@media (min-width: 761px) { .brandfoot { left: 220px; } }
|
||||||
|
.brandlink { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); text-decoration: none; padding: 2px 4px; border-radius: 4px; }
|
||||||
|
.brandlink:hover { color: var(--accent); }
|
||||||
|
.brandfoot-right { display: inline-flex; align-items: center; gap: 8px; }
|
||||||
|
.brandver { font-variant-numeric: tabular-nums; letter-spacing: .3px; }
|
||||||
|
.brandcheck { appearance: none; background: none; border: 0; color: var(--muted); cursor: pointer; padding: 0 2px; font: inherit;
|
||||||
|
font-size: 13px; line-height: 1; opacity: .75; }
|
||||||
|
.brandcheck:hover { color: var(--accent); opacity: 1; }
|
||||||
|
.brandcheck.spin { animation: spin 1s linear infinite; }
|
||||||
|
.brandupd { appearance: none; border: 0; font: inherit; font-size: 11.5px; font-weight: 600; padding: 2px 9px; border-radius: 999px;
|
||||||
|
background: var(--accent-soft); color: var(--accent); cursor: pointer; white-space: nowrap; }
|
||||||
|
.brandupd:disabled { cursor: default; }
|
||||||
|
.brandupd.ok, .brandupd.wait { background: var(--surface-2); color: var(--muted); font-weight: 500; }
|
||||||
|
.brandupd.err { background: var(--danger-soft); color: var(--danger); font-weight: 500; }
|
||||||
|
body.has-foot main { padding-bottom: 64px; }
|
||||||
|
body.has-foot .player { bottom: 28px; }
|
||||||
|
body.has-foot .toasts { bottom: 44px; }
|
||||||
|
body.has-foot.has-player main { padding-bottom: 124px; }
|
||||||
|
body.has-foot.has-player .toasts { bottom: 112px; }
|
||||||
|
|
|
||||||
|
|
@ -1752,6 +1752,87 @@ $('#unlock-btn').addEventListener('click', async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- Footer: pithos.sia, the version, and (in Theseus) the updater -----------
|
||||||
|
// Like Aegis: checks the extension channel when Pithos opens and every few
|
||||||
|
// hours; a newer release replaces the version with "↑ vX". One click
|
||||||
|
// downloads and verifies it, the next restarts Theseus to apply it.
|
||||||
|
const UPDATES_URL = 'https://navigate.st/bns/theseus.x/extensions/pithos/updates.json';
|
||||||
|
function cmpSemver(a, b) {
|
||||||
|
const pa = String(a || '0').split('.').map((n) => Number(n) || 0);
|
||||||
|
const pb = String(b || '0').split('.').map((n) => Number(n) || 0);
|
||||||
|
for (let i = 0; i < Math.max(pa.length, pb.length); i++) { const d = (pa[i] || 0) - (pb[i] || 0); if (d) return d < 0 ? -1 : 1; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
function setupFooter() {
|
||||||
|
const foot = $('#brandfoot');
|
||||||
|
const ver = $('#brandver');
|
||||||
|
const chip = $('#brandupd');
|
||||||
|
const check = $('#brandcheck');
|
||||||
|
const current = state.status?.pithos || null;
|
||||||
|
ver.textContent = current ? `v${current}` : '';
|
||||||
|
foot.hidden = false;
|
||||||
|
document.body.classList.add('has-foot');
|
||||||
|
if (bridge) {
|
||||||
|
$('#brandlink').addEventListener('click', (e) => { e.preventDefault(); bridge.invoke('open-external', { url: 'https://pithos.sia/' }).catch(fail); });
|
||||||
|
}
|
||||||
|
if (!bridge || !current) return; // only the Theseus add-on updates itself
|
||||||
|
|
||||||
|
let latest = null;
|
||||||
|
let busy = false;
|
||||||
|
const showChip = (text, cls, title, onclick) => {
|
||||||
|
chip.hidden = false; ver.hidden = true;
|
||||||
|
chip.className = `brandupd${cls ? ' ' + cls : ''}`;
|
||||||
|
chip.textContent = text; chip.title = title || '';
|
||||||
|
chip.disabled = !onclick; chip.onclick = onclick || null;
|
||||||
|
};
|
||||||
|
const rest = () => { chip.hidden = true; ver.hidden = false; };
|
||||||
|
const flash = (text, cls, title) => { showChip(text, cls, title, null); setTimeout(() => { if (!busy && chip.classList.contains(cls)) paint(); }, 2500); };
|
||||||
|
function paint() {
|
||||||
|
if (latest && cmpSemver(latest, current) > 0) showChip(`↑ v${latest}`, '', `Pithos v${latest} is available. Click to install it.`, install);
|
||||||
|
else rest();
|
||||||
|
}
|
||||||
|
async function scan(manual) {
|
||||||
|
check.classList.add('spin');
|
||||||
|
try {
|
||||||
|
const r = await fetch(`${UPDATES_URL}?t=${Math.floor(Date.now() / 60000)}`, { cache: 'no-store' });
|
||||||
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||||
|
const list = (await r.json())?.addons || [];
|
||||||
|
latest = list.reduce((best, e) => (!best || cmpSemver(e.version, best) > 0 ? e.version : best), null);
|
||||||
|
if (busy) return;
|
||||||
|
if (manual && !(latest && cmpSemver(latest, current) > 0)) flash('✓ Up to date', 'ok', `Pithos v${current} is the latest release`);
|
||||||
|
else paint();
|
||||||
|
} catch (e) {
|
||||||
|
if (manual) flash('⚠ Check failed', 'err', e.message);
|
||||||
|
} finally { check.classList.remove('spin'); }
|
||||||
|
}
|
||||||
|
async function install() {
|
||||||
|
busy = true;
|
||||||
|
showChip('Downloading…', 'wait', 'Downloading and verifying the signed release', null);
|
||||||
|
try {
|
||||||
|
const r = await bridge.invoke('update', { step: 'stage' });
|
||||||
|
if (r?.fallback) {
|
||||||
|
busy = false;
|
||||||
|
return showChip('Update in Settings', '', 'This Theseus updates extensions in Settings › Extensions', () => bridge.invoke('open-settings', { section: 'addons' }).catch(fail));
|
||||||
|
}
|
||||||
|
if (!r?.staged) {
|
||||||
|
busy = false;
|
||||||
|
return r?.status === 'up-to-date' ? flash('✓ Up to date', 'ok', '') : flash('⚠ Update failed', 'err', r?.detail || r?.status || '');
|
||||||
|
}
|
||||||
|
const next = r.next ? ` v${r.next}` : '';
|
||||||
|
showChip(`↻ Restart to apply${next}`, '', `Pithos${next} is ready. Theseus restarts to apply it and reopens your tabs.`, async () => {
|
||||||
|
try {
|
||||||
|
const a = await bridge.invoke('update', { step: 'apply' });
|
||||||
|
if (a?.deferred) showChip(`Applies on next start${next}`, 'ok', 'Pithos updates the next time Theseus starts', null);
|
||||||
|
} catch (e) { flash('⚠ Restart failed', 'err', e.message); }
|
||||||
|
});
|
||||||
|
} catch (e) { busy = false; flash('⚠ Update failed', 'err', e.message); }
|
||||||
|
}
|
||||||
|
check.hidden = false;
|
||||||
|
check.addEventListener('click', () => scan(true));
|
||||||
|
setTimeout(() => scan(false), 1500);
|
||||||
|
setInterval(() => scan(false), 6 * 3600 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
// The full-page tab opened from the Theseus panel: one button back.
|
// The full-page tab opened from the Theseus panel: one button back.
|
||||||
function addBackToSidebar() {
|
function addBackToSidebar() {
|
||||||
const row = h('div', { class: 'host-controls' },
|
const row = h('div', { class: 'host-controls' },
|
||||||
|
|
@ -1789,6 +1870,7 @@ async function boot() {
|
||||||
state.logSeq = state.logs.at(-1)?.seq || 0;
|
state.logSeq = state.logs.at(-1)?.seq || 0;
|
||||||
} catch {}
|
} catch {}
|
||||||
await refreshStatus();
|
await refreshStatus();
|
||||||
|
setupFooter();
|
||||||
if (!bridge && state.status?.host === 'theseus') addBackToSidebar();
|
if (!bridge && state.status?.host === 'theseus') addBackToSidebar();
|
||||||
connectEvents();
|
connectEvents();
|
||||||
window.addEventListener('hashchange', route);
|
window.addEventListener('hashchange', route);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,14 @@
|
||||||
<main id="main"></main>
|
<main id="main"></main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer class="brandfoot" id="brandfoot" hidden>
|
||||||
|
<a class="brandlink" id="brandlink" href="https://pithos.sia/" target="_blank" rel="noopener" title="Open pithos.sia"><img src="icon.svg" alt="" width="13" height="13"><span>pithos.sia</span></a>
|
||||||
|
<span class="brandfoot-right">
|
||||||
|
<button class="brandcheck" id="brandcheck" type="button" title="Check for updates" aria-label="Check for updates" hidden>↻</button>
|
||||||
|
<button class="brandupd" id="brandupd" type="button" hidden></button>
|
||||||
|
<span class="brandver" id="brandver"></span>
|
||||||
|
</span>
|
||||||
|
</footer>
|
||||||
<div id="modal-root"></div>
|
<div id="modal-root"></div>
|
||||||
<div id="toasts" class="toasts" aria-live="polite"></div>
|
<div id="toasts" class="toasts" aria-live="polite"></div>
|
||||||
<script type="module" src="app.js"></script>
|
<script type="module" src="app.js"></script>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue