ciphermesh 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -7
- package/README.pt-BR.md +40 -7
- package/bin/ciphermesh.js +5 -0
- package/docs/ARCHITECTURE.md +44 -1
- package/docs/PLUGINS.md +69 -0
- package/examples/plugins/poll.js +28 -0
- package/examples/plugins/roll.js +24 -0
- package/package.json +4 -3
- package/src/client/ChatController.js +814 -92
- package/src/client/UI.js +262 -6
- package/src/client/index.js +50 -7
- package/src/crypto/RoomKey.js +162 -0
- package/src/crypto/TrustStore.js +47 -0
- package/src/p2p/P2PChatController.js +9 -1
- package/src/protocol/messages.js +76 -8
- package/src/protocol/validators.js +61 -1
- package/src/server/SessionManager.js +124 -16
- package/src/server/WebSocketServer.js +339 -41
- package/src/shared/AuditLog.js +3 -0
- package/src/shared/PluginManager.js +9 -6
- package/src/shared/config.js +27 -2
- package/src/shared/constants.js +8 -0
- package/src/shared/lastSession.js +55 -0
- package/src/shared/onboarding.js +111 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// First-run setup wizard: ~30 seconds from `npx ciphermesh` to chatting, with
|
|
2
|
+
// just enough context to use the security features. Runs when no config file
|
|
3
|
+
// exists yet (or on demand via --setup) and persists the answers so it never
|
|
4
|
+
// asks twice.
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { SERVER_PORT } from './constants.js';
|
|
7
|
+
import { promptLabel, promptDim, promptError } from './banner.js';
|
|
8
|
+
import { themeNames, setTheme, getThemeName, THEMES } from './themes.js';
|
|
9
|
+
import { parseInvite } from './invite.js';
|
|
10
|
+
import { configPath, saveConfig } from './config.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a theme answer: a number from the printed list ("2"), a name
|
|
14
|
+
* ("matrix"), or empty → fallback. Pure — exported for testing.
|
|
15
|
+
*/
|
|
16
|
+
export function parseThemeChoice(input, names, fallback) {
|
|
17
|
+
const clean = (input || '').trim().toLowerCase();
|
|
18
|
+
if (!clean) {
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
if (/^\d+$/.test(clean)) {
|
|
22
|
+
const idx = Number(clean) - 1;
|
|
23
|
+
return names[idx] || fallback;
|
|
24
|
+
}
|
|
25
|
+
return names.includes(clean) ? clean : fallback;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the server answer into what to use this session and what to save
|
|
30
|
+
* as the default. Invites are used as-is for the session but saved as their
|
|
31
|
+
* host:port (a room invite is one-shot, the host is worth keeping).
|
|
32
|
+
* Pure — exported for testing.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveServerAnswer(input, fallback = `localhost:${SERVER_PORT}`) {
|
|
35
|
+
const clean = (input || '').trim();
|
|
36
|
+
if (!clean) {
|
|
37
|
+
return { session: fallback, save: fallback };
|
|
38
|
+
}
|
|
39
|
+
const invite = parseInvite(clean);
|
|
40
|
+
if (invite) {
|
|
41
|
+
return { session: clean, save: invite.wsUrl.replace(/^wss?:\/\//, '') };
|
|
42
|
+
}
|
|
43
|
+
return { session: clean, save: clean };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Interactive first-run wizard. Uses the same readline interface as the rest
|
|
48
|
+
* of startup. Returns `{ nickname, server, theme }` — the caller uses them
|
|
49
|
+
* directly for this session (no duplicate prompts) — after persisting them
|
|
50
|
+
* to the config file.
|
|
51
|
+
*/
|
|
52
|
+
export async function runOnboarding(rl, { savePath = configPath() } = {}) {
|
|
53
|
+
const dim = (t) => console.log(promptDim(` ${t}`));
|
|
54
|
+
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(chalk.bold.white(' First time here? Quick setup — 30 seconds.'));
|
|
57
|
+
dim(`Everything is saved to ${savePath} (re-run anytime with: ciphermesh --setup)`);
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(chalk.white(' How CipherMesh works, in three lines:'));
|
|
60
|
+
dim('• Everything is end-to-end encrypted — the relay only ever sees ciphertext.');
|
|
61
|
+
dim('• Your identity is a keypair; its short fingerprint is shown when you connect.');
|
|
62
|
+
dim('• Verify friends out-of-band with /verify — a green ✓ appears next to their name.');
|
|
63
|
+
console.log();
|
|
64
|
+
|
|
65
|
+
// 1. Nickname
|
|
66
|
+
let nickname = '';
|
|
67
|
+
while (!nickname) {
|
|
68
|
+
const raw = await rl.question(promptLabel(`Nickname ${promptDim('(a-z, 0-9, _, -)')}: `));
|
|
69
|
+
const clean = raw.trim().replace(/[^a-zA-Z0-9_-]/g, '');
|
|
70
|
+
if (clean.length >= 1 && clean.length <= 20) {
|
|
71
|
+
nickname = clean;
|
|
72
|
+
} else {
|
|
73
|
+
console.log(promptError('Invalid nickname. Use 1-20 alphanumeric characters.'));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 2. Theme
|
|
78
|
+
const names = themeNames();
|
|
79
|
+
console.log();
|
|
80
|
+
console.log(chalk.white(' Colour theme for nicknames:'));
|
|
81
|
+
names.forEach((name, i) => {
|
|
82
|
+
const swatch = THEMES[name]
|
|
83
|
+
.slice(0, 5)
|
|
84
|
+
.map((c) => (c.startsWith('#') ? chalk.hex(c)('█') : chalk[c]?.('█') || '█'))
|
|
85
|
+
.join('');
|
|
86
|
+
console.log(promptDim(` ${i + 1}. ${name.padEnd(8)} ${swatch}`));
|
|
87
|
+
});
|
|
88
|
+
const themeRaw = await rl.question(
|
|
89
|
+
promptLabel(`Theme ${promptDim(`(1-${names.length} or name, Enter = ${getThemeName()})`)}: `),
|
|
90
|
+
);
|
|
91
|
+
const theme = parseThemeChoice(themeRaw, names, getThemeName());
|
|
92
|
+
setTheme(theme);
|
|
93
|
+
|
|
94
|
+
// 3. Default server
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(chalk.white(' Which server should be your default?'));
|
|
97
|
+
dim(`• Same machine as the relay → localhost:${SERVER_PORT}`);
|
|
98
|
+
dim(`• Someone else hosts it (LAN/Tailscale) → their IP, e.g. 100.64.0.9:${SERVER_PORT}`);
|
|
99
|
+
dim('• Got a ciphermesh:// invite? Paste it here.');
|
|
100
|
+
const serverRaw = await rl.question(
|
|
101
|
+
promptLabel(`Server ${promptDim(`(Enter = localhost:${SERVER_PORT})`)}: `),
|
|
102
|
+
);
|
|
103
|
+
const { session: server, save: serverToSave } = resolveServerAnswer(serverRaw);
|
|
104
|
+
|
|
105
|
+
saveConfig({ nickname, theme, server: serverToSave }, savePath);
|
|
106
|
+
console.log();
|
|
107
|
+
console.log(promptLabel('Setup saved — next time you go straight to the chat.'));
|
|
108
|
+
console.log();
|
|
109
|
+
|
|
110
|
+
return { nickname, server, theme };
|
|
111
|
+
}
|