212 lines
7.9 KiB
JavaScript
212 lines
7.9 KiB
JavaScript
#!/usr/bin/env node
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||
|
|
// De opdrachtregel van de testcliënt.
|
||
|
|
//
|
||
|
|
// Alles wat hier gebeurt kan ook in het bedieningsvlak (`ui`), en dat is de
|
||
|
|
// bedoeling: dit is dezelfde laag, alleen zonder browser. Handig om een proef te
|
||
|
|
// scripten en om te zien wat er misgaat als de pagina niet meewerkt.
|
||
|
|
//
|
||
|
|
// De opdrachten staan in argumenten.js, het register in register.js, en het
|
||
|
|
// praten met Evolu in client.js. Hier staat alleen de lijm plus wat er op het
|
||
|
|
// scherm komt.
|
||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
import { join } from 'node:path';
|
||
|
|
|
||
|
|
import { gebruiksaanwijzing, leesArgumenten } from './argumenten.js';
|
||
|
|
import { eigenaarUitMnemonic, nieuweEigenaar, openStore } from './client.js';
|
||
|
|
import { leesConfig } from './config.js';
|
||
|
|
import { klopAan } from './probe.js';
|
||
|
|
import { openRegister } from './register.js';
|
||
|
|
|
||
|
|
const config = leesConfig();
|
||
|
|
const register = openRegister(config.dataDir);
|
||
|
|
const databasesIn = join(config.dataDir, 'db');
|
||
|
|
|
||
|
|
/** Zoekt een eigenaar op of stopt met een nette melding. */
|
||
|
|
const haalEigenaarOp = (naam) => {
|
||
|
|
const regel = register.zoek(naam);
|
||
|
|
if (!regel) {
|
||
|
|
console.error(`Geen eigenaar "${naam}". \`lijst\` toont wie er wel zijn.`);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
return { regel, owner: eigenaarUitMnemonic(regel.mnemonic) };
|
||
|
|
};
|
||
|
|
|
||
|
|
/** Opent de opslag van een eigenaar, met of zonder relay. */
|
||
|
|
const openVoor = (regel, { metRelay }) =>
|
||
|
|
openStore({
|
||
|
|
directory: databasesIn,
|
||
|
|
owner: eigenaarUitMnemonic(regel.mnemonic),
|
||
|
|
relayUrl: metRelay ? config.relayUrl : null,
|
||
|
|
// Een defect in een worker betekent dat deze instantie stuk is, en de
|
||
|
|
// opdracht erna wacht dan op een antwoord dat nooit komt. Afkappen is hier
|
||
|
|
// dus vriendelijker dan doordraaien: een opdrachtregel die blijft hangen
|
||
|
|
// zegt niets, een foutmelding wel.
|
||
|
|
onDefect: (defect) => {
|
||
|
|
console.error('Defect uit een worker, de opdracht wordt afgebroken:');
|
||
|
|
console.error(defect);
|
||
|
|
process.exit(1);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const eisRelay = () => {
|
||
|
|
if (config.relayUrl) return;
|
||
|
|
console.error('Geen relay-adres. Zet RELAY_URL in .env; zie .env.sample.');
|
||
|
|
process.exit(1);
|
||
|
|
};
|
||
|
|
|
||
|
|
const opdrachten = {
|
||
|
|
nieuw: async ({ naam }) => {
|
||
|
|
const owner = nieuweEigenaar();
|
||
|
|
const regel = register.voegToe({ naam, mnemonic: owner.mnemonic, ownerId: owner.id });
|
||
|
|
console.log(`Eigenaar "${regel.naam}" aangemaakt.`);
|
||
|
|
console.log(` OwnerId ${regel.ownerId}`);
|
||
|
|
console.log('');
|
||
|
|
console.log('Dat OwnerId is wat je op de statuspagina van de app terugziet. De mnemonic staat');
|
||
|
|
console.log(`in ${register.pad} en wordt hier niet afgedrukt.`);
|
||
|
|
},
|
||
|
|
|
||
|
|
lijst: async () => {
|
||
|
|
const eigenaars = register.lijst();
|
||
|
|
if (eigenaars.length === 0) {
|
||
|
|
console.log('Nog geen eigenaars. `nieuw <naam>` maakt er een.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const breedte = Math.max(...eigenaars.map((e) => e.naam.length), 4);
|
||
|
|
console.log(`${'naam'.padEnd(breedte)} OwnerId`);
|
||
|
|
for (const eigenaar of eigenaars) {
|
||
|
|
console.log(`${eigenaar.naam.padEnd(breedte)} ${eigenaar.ownerId}`);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
schrijf: async ({ naam, label }, vlaggen) => {
|
||
|
|
const { regel } = haalEigenaarOp(naam);
|
||
|
|
const bytes = Number.parseInt(vlaggen.bytes ?? '64', 10);
|
||
|
|
if (!Number.isInteger(bytes) || bytes < 1) {
|
||
|
|
console.error(`--bytes moet een positief geheel getal zijn, niet "${vlaggen.bytes}".`);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Een herkenbaar patroon en geen willekeur: bij het teruglezen wil je kunnen
|
||
|
|
// zien dát het jouw bytes zijn, en niet alleen hoeveel het er waren.
|
||
|
|
const body = new Uint8Array(bytes);
|
||
|
|
for (let i = 0; i < bytes; i += 1) body[i] = i % 256;
|
||
|
|
|
||
|
|
// Standaard mét relay, want schrijven zonder verbinding zegt niets over de
|
||
|
|
// relay en dat is waarvoor dit gereedschap bestaat. Ontbreekt het adres, dan
|
||
|
|
// is dat een fout en geen stille lokale schrijfactie: anders denk je dat je
|
||
|
|
// iets beproefd hebt. Met `--lokaal` kies je er expliciet voor, en dat is
|
||
|
|
// precies wat de proef "toelaten en opnieuw verbinden" nodig heeft.
|
||
|
|
const lokaal = vlaggen.lokaal === true || vlaggen.lokaal === 'true';
|
||
|
|
if (!lokaal) eisRelay();
|
||
|
|
|
||
|
|
const store = await openVoor(regel, { metRelay: !lokaal });
|
||
|
|
try {
|
||
|
|
const geschreven = await store.schrijf(label, body);
|
||
|
|
console.log(`Geschreven: ${geschreven.id} (${bytes} bytes, label "${label}")`);
|
||
|
|
if (lokaal) {
|
||
|
|
console.log('Lokaal, dus niet naar de relay gestuurd.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
console.log(`Verbonden met ${store.transportUrl}`);
|
||
|
|
// De synchronisatie loopt asynchroon. Even wachten is hier eerlijker dan
|
||
|
|
// meteen afsluiten en doen alsof het weg is. Wat er daarna op de relay
|
||
|
|
// staat, zegt de statuspagina van de app en niet deze uitdraai.
|
||
|
|
await new Promise((klaar) => {
|
||
|
|
setTimeout(klaar, 2000);
|
||
|
|
});
|
||
|
|
} finally {
|
||
|
|
await store.sluit();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
lees: async ({ naam }) => {
|
||
|
|
const { regel } = haalEigenaarOp(naam);
|
||
|
|
const store = await openVoor(regel, { metRelay: false });
|
||
|
|
try {
|
||
|
|
const rijen = await store.lees();
|
||
|
|
if (rijen.length === 0) {
|
||
|
|
console.log(`"${naam}" heeft nog geen blobs.`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
console.log(`${rijen.length} blob(s) voor "${naam}":`);
|
||
|
|
for (const rij of rijen) {
|
||
|
|
console.log(` ${rij.id} ${String(rij.body?.length ?? 0).padStart(8)} bytes ${rij.label}`);
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
await store.sluit();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
klop: async ({ naam }) => {
|
||
|
|
eisRelay();
|
||
|
|
const { regel } = haalEigenaarOp(naam);
|
||
|
|
console.log(`Aankloppen bij ${config.relayUrl} als ${regel.ownerId}`);
|
||
|
|
const uitkomst = await klopAan(config.relayUrl, regel.ownerId);
|
||
|
|
console.log(` status ${uitkomst.status ?? '-'}`);
|
||
|
|
console.log(` uitkomst ${uitkomst.uitleg}`);
|
||
|
|
if (!uitkomst.geaccepteerd && uitkomst.status === 401) {
|
||
|
|
console.log('');
|
||
|
|
console.log('Zet op de statuspagina van de app het venster voor nieuwe eigenaars open,');
|
||
|
|
console.log('of laat deze eigenaar daar alsnog toe.');
|
||
|
|
}
|
||
|
|
process.exit(uitkomst.geaccepteerd ? 0 : 1);
|
||
|
|
},
|
||
|
|
|
||
|
|
sync: async ({ naam }) => {
|
||
|
|
eisRelay();
|
||
|
|
const { regel } = haalEigenaarOp(naam);
|
||
|
|
const store = await openVoor(regel, { metRelay: true });
|
||
|
|
console.log(`"${naam}" verbonden met ${store.transportUrl}`);
|
||
|
|
console.log('Ctrl-C om te stoppen.');
|
||
|
|
|
||
|
|
const stop = async () => {
|
||
|
|
console.log('');
|
||
|
|
console.log('Afsluiten...');
|
||
|
|
await store.sluit();
|
||
|
|
process.exit(0);
|
||
|
|
};
|
||
|
|
process.on('SIGINT', () => {
|
||
|
|
void stop();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Blijven draaien. Een lopende verbinding is precies wat de proef "blokkeren
|
||
|
|
// tijdens een lopende verbinding" nodig heeft; zie PLAN.md §4c.
|
||
|
|
await new Promise(() => {});
|
||
|
|
},
|
||
|
|
|
||
|
|
vergeet: async ({ naam }) => {
|
||
|
|
const weg = register.vergeet(naam);
|
||
|
|
console.log(`"${weg.naam}" is uit het register.`);
|
||
|
|
console.log('De lokale database blijft staan en op de relay verandert er niets.');
|
||
|
|
},
|
||
|
|
|
||
|
|
ui: async (_waarden, vlaggen) => {
|
||
|
|
const { startUi } = await import('./ui.js');
|
||
|
|
const poort = vlaggen.poort ? Number.parseInt(vlaggen.poort, 10) : config.uiPoort;
|
||
|
|
await startUi({ config: { ...config, uiPoort: poort }, register, databasesIn });
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const { fout, opdracht, waarden, vlaggen } = leesArgumenten(process.argv.slice(2));
|
||
|
|
|
||
|
|
if (fout) {
|
||
|
|
console.error(fout);
|
||
|
|
console.error('');
|
||
|
|
console.error(gebruiksaanwijzing());
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!opdracht) {
|
||
|
|
console.log(gebruiksaanwijzing());
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
await opdrachten[opdracht](waarden, vlaggen);
|
||
|
|
} catch (oorzaak) {
|
||
|
|
console.error(oorzaak.message);
|
||
|
|
process.exit(1);
|
||
|
|
}
|