Files
UmbrelApps/tools/evolu-relay/src/store.js
T

107 lines
4.0 KiB
JavaScript
Raw Normal View History

2026-10-09 10:40:25 +02:00
// ═══════════════════════════════════════════════════════════════════════════════
// De staat op schijf: de allowlist en het opdrachtbestand van de statuspagina.
//
// Dit is de enige plek met bestanden erin. Het beleid zelf staat in `policy.js`
// en weet hier niets van.
//
// Waar het staat: onder `${APP_DATA_DIR}/data/`, náást de database van de relay
// en niet erin. De relay bezit zijn eigen SQLite en daar schrijven wij niet in.
// ═══════════════════════════════════════════════════════════════════════════════
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { createEmptyState, normalizeState } from './policy.js';
export const stateFileName = 'owners.json';
export const commandFileName = 'command.json';
/**
* Leest de allowlist.
*
* Drie uitkomsten, en de derde is de interessante:
* - `new`: er is nog niets, dus een verse staat met de leerstand aan;
* - `ok`: gelezen en begrepen;
* - `corrupt`: er stond iets wat we niet begrijpen. Dan schuiven we het bestand
* opzij en beginnen we met de leerstand **uit**.
*
* Die laatste keuze is bewust en volgt de regel die deze store al kent van
* Electrum Gate: bij twijfel weigert de app. Een onleesbaar bestand betekent dat
* we niet weten wie er toegelaten was; met de leerstand aan zou de eerstvolgende
* die verbindt stilzwijgend de nieuwe eigenaar worden, en dat hoeft je eigen
* apparaat niet te zijn. Dan liever een app die niemand toelaat en dat op de
* pagina toont.
*/
export const readState = (dataDir, now) => {
const file = join(dataDir, stateFileName);
if (!existsSync(file)) return { state: createEmptyState(), status: 'new' };
let parsed = null;
try {
parsed = JSON.parse(readFileSync(file, 'utf8'));
} catch {
parsed = null;
}
const state = parsed === null ? null : normalizeState(parsed);
if (state !== null) return { state, status: 'ok' };
// Opzij schuiven en niet weggooien: als dit ooit gebeurt, wil je kunnen zien
// wat erin stond.
const aside = `${file}.corrupt-${now.replace(/[:.]/g, '-')}`;
try {
renameSync(file, aside);
} catch {
// Lukt het opzij schuiven niet, dan gaan we alsnog dicht. De melding hieronder
// is dan het enige dat de gebruiker heeft, en dat is nog steeds beter dan
// stilzwijgend iemand toelaten.
}
return { state: { ...createEmptyState(), learning: false }, status: 'corrupt' };
};
/**
* Schrijft de allowlist. Eerst naar een tijdelijk bestand en dan hernoemen, zodat
* een onderbroken schrijfactie geen half bestand achterlaat: hernoemen binnen
* dezelfde map is atomair.
*/
export const writeState = (dataDir, state) => {
mkdirSync(dataDir, { recursive: true });
const file = join(dataDir, stateFileName);
const temporary = `${file}.tmp`;
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
renameSync(temporary, file);
};
/**
* Haalt een opdracht van de statuspagina op en verwijdert het bestand meteen.
*
* Verwijderen vóór het uitvoeren is met opzet: een opdracht die het proces laat
* struikelen mag niet bij elke ronde opnieuw worden geprobeerd.
*/
export const takeCommand = (dataDir) => {
const file = join(dataDir, commandFileName);
if (!existsSync(file)) return null;
let raw = null;
try {
raw = readFileSync(file, 'utf8');
} catch {
raw = null;
}
try {
unlinkSync(file);
} catch {
// Niets aan te doen; de volgende ronde probeert het opnieuw.
}
if (raw === null) return null;
try {
return JSON.parse(raw);
} catch {
return null;
}
};
/** De map waarin de staat hoort, afgeleid van het pad van de relay-database. */
export const dataDirectoryFor = (relayDatabaseFile) => dirname(relayDatabaseFile);