aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js
diff options
context:
space:
mode:
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.js71
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();
- }
-}