diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js | 71 |
1 files changed, 13 insertions, 58 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js b/packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js index 964f76c..b9db9f7 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js @@ -1,6 +1,6 @@ -// Contacts backup: a copy of this phone's address book sent to a folder of a -// group only its owner belongs to, once a day when it changed -// (docs/MESHBAY_DESIGN.md §9.13). +// Contacts backup: a copy of this phone's address book sent to +// `<folder>/<account>-contacts` of the destination every backup shares +// (sync-destination.js), once a day when it changed (docs/MESHBAY_DESIGN.md §9.13). // // The same split as photos (photo-sync.js): the phone writes what is new into // one file and remembers what the node took; this module decides when a run @@ -15,6 +15,7 @@ import { ConnectionPool } from './connection-pool.js'; import * as platform from './platform.js'; import { t } from './i18n.js'; import { destinationProblem, makeDirs, upload } from './photo-sync.js'; +import { setSyncContext, checkDestination } from './sync-destination.js'; const DAY_MS = 24 * 3600 * 1000; // A run cut short by something passing is tried again at the next chance, @@ -79,7 +80,7 @@ function available() { /** True when `status` says a scheduled run should go now. */ export function isDue(status, now = Date.now()) { - if (!status || !status.config) return false; + if (!status || !status.on || !status.destination) return false; if (status.failure && status.failureAt && now - status.failureAt < DAY_MS) return false; const last = status.lastCompleted; return last == null || now - last >= DAY_MS || now < last; @@ -89,6 +90,7 @@ export function isDue(status, now = Date.now()) { export function startPhoneSync(context) { if (ctx || !available().length) return; ctx = context; + setSyncContext(context); document.addEventListener('visibilitychange', onVisible); for (const kind of available()) runDocSync(kind); } @@ -107,7 +109,7 @@ function onVisible() { function scheduleNext(kind, status) { clearTimeout(timers[kind]); - if (!ctx || !status || !status.config) return; + if (!ctx || !status || !status.on || !status.destination) return; const base = Math.max(status.lastCompleted || 0, status.failure ? (status.failureAt || 0) : 0); const wait = base + DAY_MS - Date.now(); if (wait > 0) timers[kind] = setTimeout(() => runDocSync(kind), Math.min(wait, DAY_MS)); @@ -136,9 +138,9 @@ export function runDocSync(kind, { force = false } = {}) { } async function run(kind, sync, status, force) { - const cfg = status.config; + const cfg = status.destination; const user = ctx && ctx.getUser(); - if (!cfg) { setState(kind, { phase: 'off' }); return; } + if (!status.on || !cfg) { setState(kind, { phase: 'off' }); return; } if (!user || cfg.account !== user.username) { setState(kind, { phase: 'other_account' }); return; } if (status.permission !== 'granted') { await lasting(kind, sync, 'permission'); return; } if (!force && !isDue(status)) { @@ -148,18 +150,11 @@ async function run(kind, sync, status, force) { if (!force && Date.now() - (lastAttempt[kind] || 0) < RETRY_MS) return; lastAttempt[kind] = Date.now(); - // Asked of the hub at every run, not only at set-up: somebody who joins the - // group later would otherwise read every copy from then on. + // Before anything is read: still this account's own group, and nobody else's. setState(kind, { phase: 'connecting' }); - let alone; - try { - alone = await isPrivate(cfg.groupId, user); - } catch (e) { - if (/not a member|not found/i.test(e.message || '')) { await lasting(kind, sync, 'not_a_member'); return; } - setState(kind, { phase: 'offline' }); - return; - } - if (!alone) { await lasting(kind, sync, 'not_private'); return; } + const refused = await checkDestination(cfg, user); + if (refused === 'offline') { setState(kind, { phase: 'offline' }); return; } + if (refused) { await lasting(kind, sync, refused); return; } const { item } = await sync.plan(); if (!item) { @@ -207,43 +202,3 @@ async function lasting(kind, sync, code) { const text = t('phonesync.notice', { what: t(KINDS[kind].heading), reason: failureText(code) }); await sync.failed(code, text).catch(() => {}); } - -// ── For the sections ──────────────────────────────────────────────────────── - -/** True when this account is the group's one member and nobody is invited. */ -export async function isPrivate(groupId, user) { - const r = await ctx.hubFetch(`/v1/groups/${groupId}/members`, { token: user.token }); - const members = r.members || []; - return members.length === 1 && members[0].username === user.username - && !(r.invited || []).length; -} - -/** The groups among `groups` this account is alone in; a failed look leaves one out. */ -export async function privateGroups(groups) { - const user = ctx && ctx.getUser(); - if (!user) return []; - const alone = await Promise.all(groups.map((g) => isPrivate(g.id, user).catch(() => false))); - return groups.filter((_, i) => alone[i]); -} - -/** Every folder a member can add to: on a writable root whose drive is there. */ -export function writableFolders(index) { - const roots = new Set((index.roots || []) - .filter((r) => r.writable && r.available !== false).map((r) => r.name)); - const all = new Set([...(index.dirs || []), ...roots]); - return [...all].filter((d) => roots.has(d.split('/')[0])).sort((a, b) => a.localeCompare(b)); -} - -/** A one-off look at a group, for choosing a folder: connected, read, closed. */ -export async function probeWritable(groupId) { - const user = ctx && ctx.getUser(); - if (!user) throw new Error('Not signed in'); - const pool = new ConnectionPool(ctx.hub); - try { - const bundleKey = await ctx.getBundleKey(); - const conn = await pool.connect(groupId, user.token, bundleKey, user.username, user.userId); - return { folders: writableFolders(await conn.transport.fetchIndex()) }; - } finally { - pool.closeAll(); - } -} |