1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306 | <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>sueta — serverless p2p e2e chat stack</title>
<style>
:root {
--bg: #0d1117; --panel: #161b22; --panel-2: #1c2129; --border: #262c36;
--text: #e6edf3; --muted: #8b949e; --accent: #3fb68b; --accent-2: #2ea043;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg); color: var(--text);
font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
padding: 40px 20px 80px;
}
main { max-width: 780px; margin: 0 auto; }
h1 { font-size: 34px; letter-spacing: 1px; }
h1 .lock { font-size: 24px; }
.tag { color: var(--muted); margin: 6px 0 30px; font-size: 17px; }
h2 { margin: 44px 0 12px; font-size: 22px; color: var(--accent); }
h3 { margin: 24px 0 8px; font-size: 17px; }
p, li { color: #c9d3dd; }
ul { padding-left: 22px; margin: 10px 0; }
li { margin: 5px 0; }
a { color: var(--accent); }
pre {
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
padding: 14px 16px; overflow-x: auto; margin: 12px 0;
font: 13.5px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
}
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px;
background: var(--panel-2); border-radius: 5px; padding: 1px 6px; }
pre code { background: none; padding: 0; }
.card {
background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
padding: 18px 20px; margin: 14px 0;
}
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.muted { color: var(--muted); font-size: 14px; }
.btn {
display: inline-block; background: var(--accent-2); color: #fff; text-decoration: none;
font-weight: 600; border-radius: 10px; padding: 12px 22px; margin: 8px 12px 8px 0;
}
.btn.ghost { background: var(--panel-2); border: 1px solid var(--border); }
footer { margin-top: 60px; color: var(--muted); font-size: 13px; }
.langs {
position: fixed; top: 12px; right: 14px; color: var(--muted); font-size: 13px;
letter-spacing: .06em; user-select: none;
}
.langs button {
font: inherit; color: inherit; background: none; border: 0; padding: 3px 4px;
cursor: pointer; opacity: .55;
}
.langs button.on { color: var(--accent); opacity: 1; }
</style>
</head>
<body>
<nav class="langs" id="langs"></nav>
<main data-lang-block lang="en">
<h1><span class="lock">🔒</span> sueta</h1>
<p class="tag">a complete, working stack for <strong>serverless p2p end-to-end-encrypted web apps</strong> — this page is also the git repo.</p>
<a class="btn" href="https://chat.ardegazu.ro">try the live chat</a>
<a class="btn ghost" href="browse/index.html">browse the code</a>
<a class="btn ghost" href="#clone">clone the repo</a>
<h2 id="clone">Clone</h2>
<p>This site serves the repository over git's dumb-HTTP protocol as plain static files (plain files — there is no git server here, or any server at all):</p>
<pre><code>git clone https://git.ardegazu.ro/chat.git
cd sueta</code></pre>
<h2>What you get</h2>
<div class="grid">
<div class="card"><h3>client/src/lib/ — the p2p core</h3>
<p class="muted">Reusable, app-agnostic TypeScript on libp2p: browser⇄browser WebRTC with a circuit-relay bootstrap, an end-to-end-encrypted replicated log (OrbitDB) with a deterministic per-room address, E2E crypto (HKDF + AES-GCM, structurally nonce-safe), persistent Ed25519 identities signing every log entry.</p></div>
<div class="card"><h3>deploy/relay/ — ~100 lines of relay</h3>
<p class="muted">A libp2p circuit relay + gossipsub peer discovery over websockets. It carries encrypted handshakes only — room traffic flows member⇄member and never touches it. Dev server and self-host example in one file.</p></div>
<div class="card"><h3>client/src/app/ — the chat</h3>
<p class="muted">Installable PWA: rooms as capability links, threads, reactions (removals replicate too), EXIF-stripped image sharing over encrypted bitswap blocks, p2p audio/video room calls (DTLS-SRTP), offline-capable replicated history, local notifications, numbered releases with an in-app update banner.</p></div>
<div class="card"><h3>docs/ — the contracts</h3>
<p class="muted"><code>PROTOCOL.md</code> — crypto & protocol spec (v2). <code>RELAY.md</code> — the relay: dev usage and self-hosting.</p></div>
</div>
<h2>How it works (the short version)</h2>
<ul>
<li><strong>The room secret never leaves the browser.</strong> It lives in the URL fragment (<code>#…</code>). HKDF derives every network-visible identifier one-way — the gossip topic, the log address, all AES-256-GCM keys.</li>
<li><strong>The relay is blind — more than before.</strong> Browsers meet through a libp2p circuit relay that carries noise-encrypted handshakes only; room traffic runs member⇄member and never touches it. Verified by tests that grep every relay frame for plaintext.</li>
<li><strong>Everything else is browser ⇄ browser.</strong> Direct WebRTC (noise + app-layer AES-GCM), full mesh up to ~12 peers, STUN/TURN for NAT traversal with ephemeral HMAC credentials.</li>
<li><strong>The room IS a replicated encrypted log.</strong> Messages, threads, reactions and images are entries in an append-only OrbitDB log, sealed end-to-end before they become content-addressed blocks. Devices replicate it to each other — reopen offline and your copy is there; join late and members' devices feed you everything, images included. No server stores a byte.</li>
<li><strong>Identity without accounts.</strong> One Ed25519 seed per device (password-manager-sized), session binding signatures, emoji fingerprints, trust-on-first-use with key-change warnings.</li>
<li><strong>Hosting without an origin server.</strong> The app is a static build served as plain files — there is no backend anywhere. This page you're reading works the same way.</li>
</ul>
<h2>Run it yourself</h2>
<pre><code># dev: relay + app
cd deploy/relay && npm install && npm run dev # :9090, deterministic dev PeerId
cd client && npm install && npm run dev # :5173
# tests (real Chromium — WebRTC needs real UDP; each spawns its own relay)
cd client && node e2e/mesh.e2e.mjs</code></pre>
<p>To self-host the network side: run <code>deploy/relay/</code> (a ~100-line Node script) behind any TLS proxy, plus coturn for TURN, and point the client build at them with <code>VITE_RELAY_MULTIADDR</code> / <code>VITE_TURN_CREDS_URL</code>. Any willing libp2p circuit relay works — it holds no state and learns nothing. Host the client build anywhere static — IPFS, a CDN, a folder behind nginx. Full steps in <code>docs/RELAY.md</code>.</p>
<h2>Build your own p2p app on this</h2>
<p>The chat is just one consumer of the core. A new app is ~40 lines of bootstrap:</p>
<ul>
<li>pick an <strong>app salt</strong> (e.g. <code>"myapp.example.com/v1"</code>) — it namespaces your rooms so apps can share one relay without collisions,</li>
<li>copy <code>client/src/lib/</code> (zero app-specific code in it),</li>
<li>point it at any willing libp2p relay — the relay never learns what app it's carrying,</li>
<li>broadcast live payloads (sealed per-sender automatically) — and, if your app wants durable shared state, append to the room's encrypted replicated log and fold entries into your UI.</li>
</ul>
<p>The recipe with code is in <code>client/src/lib/README.md</code>. Shared whiteboards, turn-based games, collaborative editors, file drops — anything that fits "small group, real-time, private" fits this stack.</p>
<h2>Guarantees & limits, honestly</h2>
<ul>
<li>The relay operator (or anyone on the wire) sees: connection metadata, ephemeral per-session peer ids, opaque topic strings, encrypted handshake frames. Not content, not names, not who-is-who inside a room — and, new in v2, not even the room traffic itself, which flows member⇄member. Other clients of a shared relay see your ephemeral peer id exist (unlinkable across page loads), nothing more.</li>
<li>Anyone with the room link is a full member — capability model. Guard links like keys.</li>
<li>Identity verification is human: compare the four emoji out-of-band, like reading safety numbers aloud. No servers means no authority that can vouch for anyone.</li>
<li>Full mesh scales to ~a dozen peers; beyond that you'd want an SFU, which is no longer serverless.</li>
<li>History survives as long as any member's device kept it; a room everyone truly abandons is forgotten. That's the design, not a bug.</li>
</ul>
<footer>
MIT licensed. Built with WebCrypto, WebRTC, libp2p, OrbitDB and coturn — one relay + one TURN server involved, both replaceable.<br>
Live app: <a href="https://chat.ardegazu.ro">chat.ardegazu.ro</a> · this repo: <code>git clone https://git.ardegazu.ro/chat.git</code>
</footer>
</main>
<main data-lang-block lang="ro" hidden>
<h1><span class="lock">🔒</span> sueta</h1>
<p class="tag">un stack complet, funcțional, pentru <strong>aplicații web p2p criptate cap-la-cap, fără servere</strong> — pagina asta e și repo-ul git.</p>
<a class="btn" href="https://chat.ardegazu.ro">încearcă chatul live</a>
<a class="btn ghost" href="browse/index.html">răsfoiește codul</a>
<a class="btn ghost" href="#clone-ro">clonează repo-ul</a>
<h2 id="clone-ro">Clonare</h2>
<p>Situl servește repository-ul prin protocolul dumb-HTTP al lui git, ca simple fișiere statice (chiar simple fișiere — nu există aici niciun server git, de fapt niciun server):</p>
<pre><code>git clone https://git.ardegazu.ro/chat.git
cd sueta</code></pre>
<h2>Ce primești</h2>
<div class="grid">
<div class="card"><h3>client/src/lib/ — nucleul p2p</h3>
<p class="muted">TypeScript reutilizabil, agnostic de aplicație, pe libp2p: WebRTC browser⇄browser cu bootstrap prin circuit relay, un log replicat criptat cap-la-cap (OrbitDB) cu adresă deterministă per cameră, criptografie E2E (HKDF + AES-GCM, structural sigură la nonce-uri), identități Ed25519 persistente care semnează fiecare intrare din log.</p></div>
<div class="card"><h3>deploy/relay/ — ~100 de linii de releu</h3>
<p class="muted">Un circuit relay libp2p + descoperire de peers prin gossipsub, peste websockets. Poartă doar handshake-uri criptate — traficul camerei curge membru⇄membru și nu-l atinge niciodată. Server de dev și exemplu de self-hosting într-un singur fișier.</p></div>
<div class="card"><h3>client/src/app/ — chatul</h3>
<p class="muted">PWA instalabil: camere ca linkuri-capabilitate, fire de discuție, reacții (și ștergerile se replică), poze cu EXIF-ul scos, partajate ca blocuri bitswap criptate, apeluri audio/video p2p în cameră (DTLS-SRTP), istoric replicat care merge offline, notificări locale, versiuni numerotate cu banner de actualizare în aplicație.</p></div>
<div class="card"><h3>docs/ — contractele</h3>
<p class="muted"><code>PROTOCOL.md</code> — specificația cripto și de protocol (v2). <code>RELAY.md</code> — releul: folosire în dev și self-hosting.</p></div>
</div>
<h2>Cum funcționează (versiunea scurtă)</h2>
<ul>
<li><strong>Secretul camerei nu pleacă niciodată din browser.</strong> Stă în fragmentul URL-ului (<code>#…</code>). HKDF derivă într-un singur sens fiecare identificator vizibil în rețea — topicul de gossip, adresa logului, toate cheile AES-256-GCM.</li>
<li><strong>Releul e orb — mai orb ca înainte.</strong> Browserele se întâlnesc printr-un circuit relay libp2p care poartă doar handshake-uri criptate noise; traficul camerei curge membru⇄membru și nu-l atinge niciodată. Verificat de teste care caută plaintext în fiecare cadru trecut prin releu.</li>
<li><strong>Restul e browser ⇄ browser.</strong> WebRTC direct (noise + AES-GCM la nivel de aplicație), mesh complet până la ~12 peers, STUN/TURN pentru traversarea NAT-urilor, cu credențiale HMAC efemere.</li>
<li><strong>Camera ESTE un log criptat replicat.</strong> Mesajele, firele, reacțiile și pozele sunt intrări într-un log OrbitDB doar-adaugă, sigilate cap-la-cap înainte să devină blocuri adresate după conținut. Dispozitivele și-l replică între ele — redeschide offline și copia ta e acolo; intră târziu și dispozitivele membrilor îți dau tot, cu poze cu tot. Niciun server nu stochează un octet.</li>
<li><strong>Identitate fără conturi.</strong> Un seed Ed25519 per dispozitiv (cât să încapă în managerul de parole), semnături de legare a sesiunii, amprente emoji, trust-on-first-use cu avertismente la schimbarea cheii.</li>
<li><strong>Găzduire fără server de origine.</strong> Aplicația e un build static servit ca simple fișiere — nu există backend nicăieri. Pagina pe care o citești funcționează la fel.</li>
</ul>
<h2>Rulează-l singur</h2>
<pre><code># dev: releu + aplicație
cd deploy/relay && npm install && npm run dev # :9090, PeerId de dev determinist
cd client && npm install && npm run dev # :5173
# teste (Chromium adevărat — WebRTC are nevoie de UDP adevărat; fiecare își pornește propriul releu)
cd client && node e2e/mesh.e2e.mjs</code></pre>
<p>Ca să-ți self-hostezi partea de rețea: rulează <code>deploy/relay/</code> (un script Node de ~100 de linii) în spatele oricărui proxy TLS, plus coturn pentru TURN, și îndreaptă buildul clientului spre ele cu <code>VITE_RELAY_MULTIADDR</code> / <code>VITE_TURN_CREDS_URL</code>. Merge orice circuit relay libp2p binevoitor — nu ține stare și nu află nimic. Găzduiește buildul clientului oriunde static — IPFS, un CDN, un director în spatele lui nginx. Pașii compleți în <code>docs/RELAY.md</code>.</p>
<h2>Construiește-ți propria aplicație p2p pe asta</h2>
<p>Chatul e doar un consumator al nucleului. O aplicație nouă înseamnă ~40 de linii de bootstrap:</p>
<ul>
<li>alege un <strong>app salt</strong> (de ex. <code>"myapp.example.com/v1"</code>) — pune camerele tale într-un namespace, ca mai multe aplicații să împartă un releu fără coliziuni,</li>
<li>copiază <code>client/src/lib/</code> (zero cod specific aplicației în el),</li>
<li>îndreaptă-l spre orice releu libp2p binevoitor — releul nu află niciodată ce aplicație poartă,</li>
<li>difuzează payloaduri live (sigilate automat per expeditor) — iar dacă aplicația vrea stare comună durabilă, adaugă în logul criptat replicat al camerei și împăturește intrările în UI.</li>
</ul>
<p>Rețeta cu cod e în <code>client/src/lib/README.md</code>. Table albe comune, jocuri pe ture, editoare colaborative, schimb de fișiere — orice încape în „grup mic, timp real, privat" încape în stackul ăsta.</p>
<h2>Garanții și limite, pe bune</h2>
<ul>
<li>Operatorul releului (sau oricine de pe fir) vede: metadate de conexiune, peer id-uri efemere per sesiune, șiruri opace de topic, cadre criptate de handshake. Nu conținut, nu nume, nu cine-i cine într-o cameră — și, nou în v2, nici măcar traficul camerei, care curge membru⇄membru. Alți clienți ai unui releu partajat văd că peer id-ul tău efemer există (de nelegat între încărcări de pagină), nimic mai mult.</li>
<li>Oricine are linkul camerei e membru cu drepturi depline — model de capabilitate. Păzește linkurile ca pe niște chei.</li>
<li>Verificarea identității e umană: compari cele patru emoji pe alt canal, cum ai citi cu voce tare numerele de siguranță. Fără servere înseamnă fără vreo autoritate care să garanteze pentru cineva.</li>
<li>Mesh-ul complet ține până la vreo duzină de peers; peste, ai vrea un SFU, care nu mai e fără server.</li>
<li>Istoricul supraviețuiește cât timp l-a păstrat dispozitivul vreunui membru; o cameră părăsită de-adevăratelea de toți e uitată. Așa e proiectat, nu e un bug.</li>
</ul>
<footer>
Licență MIT. Construit cu WebCrypto, WebRTC, libp2p, OrbitDB și coturn — un releu + un server TURN implicate, ambele înlocuibile.<br>
Aplicația live: <a href="https://chat.ardegazu.ro">chat.ardegazu.ro</a> · repo-ul: <code>git clone https://git.ardegazu.ro/chat.git</code>
</footer>
</main>
<main data-lang-block lang="hu" hidden>
<h1><span class="lock">🔒</span> sueta</h1>
<p class="tag">teljes, működő stack <strong>szerver nélküli, végponttól végpontig titkosított p2p webappokhoz</strong> — ez az oldal egyben a git repó is.</p>
<a class="btn" href="https://chat.ardegazu.ro">próbáld ki az élő chatet</a>
<a class="btn ghost" href="browse/index.html">böngészd a kódot</a>
<a class="btn ghost" href="#clone-hu">repó klónozása</a>
<h2 id="clone-hu">Klónozás</h2>
<p>Az oldal a repót a git dumb-HTTP protokollján át szolgálja ki, sima statikus fájlokként (tényleg sima fájlok — nincs itt git szerver, sőt semmilyen szerver):</p>
<pre><code>git clone https://git.ardegazu.ro/chat.git
cd sueta</code></pre>
<h2>Mit kapsz</h2>
<div class="grid">
<div class="card"><h3>client/src/lib/ — a p2p mag</h3>
<p class="muted">Újrafelhasználható, alkalmazás-agnosztikus TypeScript libp2p-n: böngésző⇄böngésző WebRTC circuit-relay bootstrappel, végponttól végpontig titkosított replikált log (OrbitDB) determinisztikus szobánkénti címmel, E2E kriptográfia (HKDF + AES-GCM, szerkezetileg nonce-biztos), tartós Ed25519 identitások, amelyek minden logbejegyzést aláírnak.</p></div>
<div class="card"><h3>deploy/relay/ — ~100 sor relé</h3>
<p class="muted">Egy libp2p circuit relay + gossipsub peer-felfedezés websocketeken. Csak titkosított kézfogásokat visz — a szoba forgalma tag⇄tag megy, és sosem érinti. Dev szerver és self-host példa egyetlen fájlban.</p></div>
<div class="card"><h3>client/src/app/ — a chat</h3>
<p class="muted">Telepíthető PWA: szobák capability-linkekként, szálak, reakciók (a törlés is replikálódik), EXIF-től megfosztott képmegosztás titkosított bitswap-blokkokon, p2p hang-/videóhívás a szobában (DTLS-SRTP), offline is működő replikált előzmények, helyi értesítések, számozott kiadások app-beli frissítési sávval.</p></div>
<div class="card"><h3>docs/ — a szerződések</h3>
<p class="muted"><code>PROTOCOL.md</code> — kripto- és protokollspecifikáció (v2). <code>RELAY.md</code> — a relé: dev használat és self-hosting.</p></div>
</div>
<h2>Hogyan működik (a rövid változat)</h2>
<ul>
<li><strong>A szoba titka soha nem hagyja el a böngészőt.</strong> Az URL fragmentben él (<code>#…</code>). Minden hálózaton látható azonosítót HKDF származtat egyirányúan — a gossip topicot, a log címét, az összes AES-256-GCM kulcsot.</li>
<li><strong>A relé vak — jobban, mint valaha.</strong> A böngészők egy libp2p circuit relay-n át találkoznak, amely csak noise-titkosított kézfogásokat visz; a szoba forgalma tag⇄tag megy, és sosem érinti. Tesztek igazolják, amelyek minden relékeretben plaintextet keresnek.</li>
<li><strong>Minden más böngésző ⇄ böngésző.</strong> Közvetlen WebRTC (noise + app-rétegű AES-GCM), teljes mesh ~12 peerig, STUN/TURN a NAT-átjáráshoz efemer HMAC hitelesítéssel.</li>
<li><strong>A szoba MAGA egy replikált, titkosított log.</strong> Az üzenetek, szálak, reakciók és képek egy csak-hozzáfűzős OrbitDB log bejegyzései, végponttól végpontig lepecsételve, mielőtt tartalomcímzett blokkokká válnának. Az eszközök egymás közt replikálják — nyisd meg offline, és a másolatod ott van; érkezz későn, és a tagok eszközei mindent odaadnak, képekkel együtt. Egyetlen szerver sem tárol egyetlen bájtot sem.</li>
<li><strong>Identitás fiókok nélkül.</strong> Eszközönként egy Ed25519 seed (jelszókezelő-méretű), munkamenet-kötő aláírások, emoji-ujjlenyomatok, trust-on-first-use kulcsváltási figyelmeztetésekkel.</li>
<li><strong>Hosztolás origin-szerver nélkül.</strong> Az app statikus build, sima fájlokként kiszolgálva — nincs sehol backend. Ez az oldal is pontosan így működik.</li>
</ul>
<h2>Futtasd magad</h2>
<pre><code># dev: relé + app
cd deploy/relay && npm install && npm run dev # :9090, determinisztikus dev PeerId
cd client && npm install && npm run dev # :5173
# tesztek (igazi Chromium — a WebRTC-nek igazi UDP kell; mindegyik saját relét indít)
cd client && node e2e/mesh.e2e.mjs</code></pre>
<p>A hálózati oldal self-hostolásához: futtasd a <code>deploy/relay/</code>-t (~100 soros Node szkript) bármilyen TLS proxy mögött, mellé coturnt a TURN-höz, és irányítsd rájuk a kliens buildet a <code>VITE_RELAY_MULTIADDR</code> / <code>VITE_TURN_CREDS_URL</code> változókkal. Bármilyen készséges libp2p circuit relay megteszi — nem tart állapotot és nem tud meg semmit. A kliens buildet bárhová kiteheted statikusan — IPFS, CDN, egy mappa nginx mögött. A teljes lépéssor a <code>docs/RELAY.md</code>-ben.</p>
<h2>Építsd rá a saját p2p appod</h2>
<p>A chat csak egy fogyasztója a magnak. Egy új app ~40 sor bootstrap:</p>
<ul>
<li>válassz egy <strong>app saltot</strong> (pl. <code>"myapp.example.com/v1"</code>) — ez ad névteret a szobáidnak, hogy több app osztozhasson egy relén ütközés nélkül,</li>
<li>másold be a <code>client/src/lib/</code>-et (nulla app-specifikus kód van benne),</li>
<li>irányítsd bármilyen készséges libp2p relére — a relé sosem tudja meg, milyen appot visz,</li>
<li>sugározz élő payloadokat (automatikusan feladónként lepecsételve) — és ha az appnak tartós közös állapot kell, fűzz hozzá a szoba titkosított replikált logjához, és hajtogasd a bejegyzéseket a UI-ba.</li>
</ul>
<p>A recept kóddal a <code>client/src/lib/README.md</code>-ben van. Közös rajztáblák, körökre osztott játékok, kollaboratív szerkesztők, fájl-drop — bármi, amire ráillik a „kis csapat, valós idő, privát", ráillik erre a stackre.</p>
<h2>Garanciák és korlátok, őszintén</h2>
<ul>
<li>A relé üzemeltetője (vagy bárki a vonalon) ezt látja: kapcsolati metaadatok, munkamenetenkénti efemer peer id-k, átlátszatlan topic-sztringek, titkosított kézfogás-keretek. Tartalmat nem, neveket nem, azt sem, ki kicsoda egy szobában — és, ami a v2 újdonsága, magát a szobaforgalmat sem, az tag⇄tag megy. Egy közös relé más kliensei annyit látnak, hogy az efemer peer id-d létezik (oldalbetöltések közt összekapcsolhatatlan), semmi többet.</li>
<li>Bárki, akinél ott a szoba linkje, teljes jogú tag — capability modell. Őrizd a linkeket, mint a kulcsokat.</li>
<li>Az identitás ellenőrzése emberi: a négy emojit más csatornán hasonlítod össze, mintha biztonsági számokat olvasnál fel. Ha nincs szerver, nincs hatóság sem, amely bárkiért kezeskedne.</li>
<li>A teljes mesh nagyjából egy tucat peerig skálázódik; afölött SFU kellene, az viszont már nem szerver nélküli.</li>
<li>Az előzmények addig élnek, amíg bármely tag eszköze megőrizte őket; a mindenki által tényleg elhagyott szoba feledésbe merül. Ez a terv, nem hiba.</li>
</ul>
<footer>
MIT licenc. WebCryptóval, WebRTC-vel, libp2p-vel, OrbitDB-vel és coturnnal épült — egy relé + egy TURN szerver van a képben, mindkettő cserélhető.<br>
Az élő app: <a href="https://chat.ardegazu.ro">chat.ardegazu.ro</a> · a repó: <code>git clone https://git.ardegazu.ro/chat.git</code>
</footer>
</main>
<script>
(function () {
var LANGS = ["en", "ro", "hu"];
var KEY = "ardegazu-site:lang";
var lang = null;
try { lang = localStorage.getItem(KEY); } catch (e) {}
if (LANGS.indexOf(lang) < 0) {
var cands = navigator.languages || [navigator.language];
for (var i = 0; i < cands.length; i++) {
var p = (cands[i] || "").slice(0, 2).toLowerCase();
if (LANGS.indexOf(p) >= 0) { lang = p; break; }
}
}
if (LANGS.indexOf(lang) < 0) lang = "en";
var nav = document.getElementById("langs");
function apply(l) {
lang = l;
document.documentElement.lang = l;
try { localStorage.setItem(KEY, l); } catch (e) {}
var blocks = document.querySelectorAll("[data-lang-block]");
for (var i = 0; i < blocks.length; i++) blocks[i].hidden = blocks[i].getAttribute("lang") !== l;
var btns = nav.querySelectorAll("button");
for (var j = 0; j < btns.length; j++) btns[j].className = btns[j].textContent === l ? "on" : "";
}
LANGS.forEach(function (l, i) {
if (i) nav.appendChild(document.createTextNode(" · "));
var b = document.createElement("button");
b.type = "button";
b.textContent = l;
b.addEventListener("click", function () { apply(l); });
nav.appendChild(b);
});
apply(lang);
})();
</script>
</body>
</html>
|