Skip to content

Commit 65e41dc

Browse files
Merge pull request #1 from p2plabsxyz/feat/peerchat
feat: implement peerchat 'peersky://p2p/peerchat/'
2 parents 9a7d145 + a8df123 commit 65e41dc

25 files changed

+5229
-1
lines changed

.DS_Store

6 KB
Binary file not shown.

README.md

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,164 @@
1-
# chat.p2plabs.xyz
1+
# PeerChat
2+
3+
<div align="center">
4+
<img src="./demo.png" width="639" alt="Screenshot of PeerChat in light mode: browser tab with peersky://p2p/peerchat URL, sidebar with rooms and a selected chat with “Capt Jack Sparrow,” conversation bubbles, an embedded video, and the emoji picker above the input field.">
5+
</div>
6+
7+
Small-group chat inside [PeerSky Browser](https://github.com/p2plabsxyz/peersky-browser). You create a room, share a key, and everyone who has that key joins the same swarm and sees the same history—no chat server in the middle.
8+
9+
**No account.** **Everything stays local** on your machine (room list, profile, keys file)—except what you explicitly sync over the peer network. **You own your chats:** there’s no company holding logs or resetting your password; the room key is the shared secret.
10+
11+
## Who this is for
12+
13+
Small teams or group of friends who already trust each other and want something p2p without sign-up flows.
14+
15+
**Not** a replacement for apps built for journalists, activists, or **very confidential** communication. PeerChat has no verified identities, no perfect forward secrecy, and no professional security audit. If leaking a thread would be serious, use something designed for that threat model
16+
17+
## What it does
18+
19+
- Rooms with name, bio, optional link, and optional picture
20+
- Messages stored in a **Hypercore** per room (append-only log), synced across peers
21+
- Live delivery over **Hyperswarm** (Noise-encrypted transport) plus **SSE** (`receive-all`) so the web UI updates without polling every room
22+
- Join / leave, @mentions, replies, **emoji reactions** on messages (stored in the room feed and synced like other events), **file attachments** via a dedicated Hyperdrive (`peerchat` shows up in Settings → Archive like other apps). **No file upload limits** in PeerChat.
23+
- **Direct Messages (DMs):** click a peer's avatar to send a private message; the recipient gets an accept/decline popup, and the room key is derived deterministically from both peer IDs so only those two people share it
24+
- Room list, unread counts, and local settings persist on disk
25+
- **Emoji picker** in the message composer: type keywords to filter characters; data comes from [emojilib](https://github.com/muan/emojilib), vendored as `lib/emojilib-emoji-en-US.json`.
26+
27+
## How it works
28+
29+
**Room key** — A random 32-byte value shown as hex. It doubles as the Hypercore name/discussion topic and as the secret used to encrypt message payloads before they hit the feed. Sharing the key means sharing read access to that room’s history (with peers who actually have the blocks).
30+
31+
**Data path** — Outgoing messages are encrypted with **AES-256-GCM** using a key derived from the room key (`SHA-256` over a fixed prefix + room key). The feed stores ciphertext + IV + tag; peers decrypt after sync. The wire between peers is already encrypted by the swarm.
32+
33+
**Process split** — The UI (`app.js`, static HTML/CSS) talks to `hyper://chat?action=…` over `fetch` and `EventSource`. The handler in `p2p.js` runs in the main process with the shared Hyper SDK instance: it joins swarms for each saved room, relays JSON lines between peers (newline-delimited), and broadcasts events to all connected SSE clients.
34+
35+
**Storage** — Room metadata, your profile, and encrypted room keys (when available) live in a JSON file under Electron user data (`CHAT_STORAGE` in `p2p.js`, wired from `hyper-handler.js`). Optional **safeStorage** encrypts that blob when the OS supports it.
36+
37+
**Joining again** — Use **Join room** with the 64-character key. For room metadata or keys stored in your archive, open **Settings → Archive** in PeerSky and look under Hyperdrives for **peerchat-rooms**.
38+
39+
## Security
40+
41+
These apps solve different problems; the table is to set expectations, not to pick a “winner.”
42+
43+
| | **Signal** | **Matrix (e.g. Element)** | **PeerChat** |
44+
|---|------------|---------------------------|--------------|
45+
| **Shape** | Central service, E2E by default | Federated homeservers; E2E optional per room | **P2P:** no chat servers; Hyperswarm + Hypercore |
46+
| **Account** | Phone number | Matrix ID + homeserver | **None** |
47+
| **Pros** | Strong E2E story, PFS, large user base, safety numbers | Self-host, bridges, optional E2E | **No signup;** data synced directly between peers; you control local files |
48+
| **Cons** | Depends on Signal’s infrastructure and updates | Server sees metadata; E2E history can be fiddly | **Room key = full access** to history for anyone who gets it; **no PFS** on the room key; metadata on the network is a research topic |
49+
| **Good when** | You want mainstream, audited E2E messaging | You want federation or a public server | You want **local-first, small groups**, same app as Hyper browsing |
50+
| **File uploads** | Platform limits | Varies by server | **No limit** |
51+
52+
**P2P angle:** PeerChat avoids a message database run by a third party, but **discovery and relays** still touch the public stack (DHT, etc.). Noise protects the bytes on the wire; the **room key** protects message content on disk. That’s simpler than Signal’s ratchet—it’s also **weaker** if the key is stolen or shared carelessly.
53+
54+
### PeerChat specifics
55+
56+
- **Room key is the capability.** Anyone with it can join the swarm and decrypt traffic for that room. Treat it like a strong shared secret.
57+
- **No “real” host** in the network sense: peers are symmetric. “Host” in the UI only marks who created the room on that device.
58+
- Rate limits and a max **message text** length cut spam in the feed; file uploads have **no size limit** in the app. The UI escapes text before rendering to limit XSS.
59+
- **Not** Matrix/Signal-class identity, device verification, or perfect forward secrecy.
60+
61+
## Development
62+
63+
### Chat API
64+
65+
PeerChat provides a clean JavaScript API in `chat-api.js` that wraps the protocol handler. Import and use it in your app:
66+
67+
```js
68+
import { chat } from "./path/to/peerchat/chat-api.js";
69+
70+
// Get user profile
71+
const profile = await chat.getProfile();
72+
73+
// Get all rooms
74+
const { rooms, peerProfiles, onlinePeers } = await chat.getRooms();
75+
76+
// Send a message
77+
await chat.sendMessage(roomKey, { message: "Hello!" });
78+
79+
// React to a message
80+
await chat.react(roomKey, { msgId, emoji: "👍" });
81+
82+
// Subscribe to live updates
83+
const es = new EventSource(chat.receiveAllUrl());
84+
es.addEventListener("message", (ev) => {
85+
const msg = JSON.parse(ev.data);
86+
// Handle incoming message
87+
});
88+
```
89+
90+
**Available methods:**
91+
- `chat.getProfile()` — Get current user profile
92+
- `chat.getRooms()` — Get all rooms, peer profiles, online status
93+
- `chat.saveProfile(body)` — Update username, bio, avatar, notifications
94+
- `chat.createRoom(body)` — Create new room with name, bio, link, avatar
95+
- `chat.joinRoom(roomKey)` — Join room by key
96+
- `chat.getHistory(roomKey)` — Get message history for room
97+
- `chat.setActive(roomKey)` — Mark room as active
98+
- `chat.markRead(roomKey)` — Mark room as read
99+
- `chat.sendMessage(roomKey, body)` — Send message with optional reply, file
100+
- `chat.react(roomKey, body)` — React to message with emoji
101+
- `chat.joinDM(body)` — Initiate DM with peer
102+
- `chat.acceptDM(body)` — Accept incoming DM request
103+
- `chat.rejectDM(body)` — Reject incoming DM request
104+
- `chat.updateRoom(roomKey, body)` — Update room settings (pin, mute)
105+
- `chat.deleteRoom(roomKey)` — Leave room
106+
- `chat.requestMeta(roomKey)` — Broadcast a request to connected peers to re-send room metadata (name, bio, avatar, creator)
107+
- `chat.receiveAllUrl()` — Get SSE endpoint URL for live updates
108+
109+
### Porting to another Hyper browser
110+
111+
Copy the `chat/` folder. Your browser already needs a **single shared Hyper SDK** instance (same pattern as PeerSky: one swarm for browsing + apps).
112+
113+
#### 1. Import and initialize after `createSDK`
114+
115+
Use your own path to `p2p.js`. On Electron you can pass `safeStorage` and a file under `userData`; elsewhere omit `safeStorage` or stub it.
116+
117+
```js
118+
import path from "path";
119+
import { app, safeStorage } from "electron"; // or skip safeStorage
120+
import {
121+
initChat,
122+
handleChatRequest,
123+
CHAT_STORAGE,
124+
} from "./path/to/peerchat/p2p.js";
125+
126+
// After: sdk = await createSDK(options)
127+
initChat(sdk, {
128+
safeStorage, // optional; Electron only
129+
storagePath: path.join(app.getPath("userData"), CHAT_STORAGE),
130+
});
131+
```
132+
133+
`CHAT_STORAGE` is the JSON filename (`peersky-chat-rooms.json`); change the export in `p2p.js` if you want a different name for your software.
134+
135+
#### 2. Route `hyper://chat` to the handler
136+
137+
PeerSky branches **before** generic `hypercore-fetch` handling so chat never hits the default Hyper resolver. Match your URL shape; PeerSky uses hostname `chat` or path `/chat`:
138+
139+
```js
140+
export async function createHandler(options) {
141+
await initializeHyperSDK(options); // must call initChat inside this
142+
143+
return async function protocolHandler(req) {
144+
const urlObj = new URL(req.url);
145+
const protocol = urlObj.protocol.replace(":", "");
146+
const pathname = urlObj.pathname;
147+
148+
if (
149+
protocol === "hyper" &&
150+
(urlObj.hostname === "chat" || pathname.startsWith("/chat"))
151+
) {
152+
return handleChatRequest(req, sdk);
153+
}
154+
155+
// …existing hyper:// handling (fetchFn, etc.)
156+
};
157+
}
158+
```
159+
160+
### Theming
161+
162+
`styles.css` imports [`browser://theme/vars.css`](https://github.com/p2plabsxyz/peersky-browser/blob/main/docs/Theme.md) and maps layout colors from **`--browser-theme-background`**, **`--browser-theme-text-color`**, **`--browser-theme-primary-highlight`**, **`--browser-theme-secondary-highlight`**, and **`--browser-theme-font-family`**, with Peersky extras (`--peersky-nav-background`, `--base02`, etc.) when present. The UI should follow PeerSky’s selected theme and stay compatible with other browsers that implement the same protocol.
163+
164+
> All sound effects used in PeerChat are royalty-free and sourced from [Pixabay](https://pixabay.com/).

about.html

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<link rel="icon" type="image/png" href="peersky://static/assets/favicon.ico" />
7+
<link rel="stylesheet" href="./styles.css" />
8+
<title>About — PeerChat</title>
9+
<style>
10+
.about-wrap {
11+
max-width: 42rem;
12+
margin: 0 auto;
13+
padding: 2rem 1.25rem 3rem;
14+
height: 100vh;
15+
overflow-y: auto;
16+
line-height: 1.55;
17+
}
18+
.about-wrap h1 { font-size: 1.5rem; margin-bottom: 0.75rem; }
19+
.about-wrap h2 { font-size: 1.05rem; margin: 1.75rem 0 0.5rem; color: var(--text); }
20+
.about-wrap p { margin-bottom: 0.9rem; color: var(--text); }
21+
.about-wrap .lead { font-size: 1.05rem; color: var(--text); }
22+
.about-wrap .muted { color: var(--muted); font-size: 0.9rem; }
23+
.about-wrap ul { margin: 0.5rem 0 1rem 1.25rem; color: var(--muted); font-size: 0.92rem; }
24+
.about-wrap a { color: var(--accent); }
25+
.about-back { margin-bottom: 1.25rem; display: inline-block; font-size: 0.9rem; }
26+
.about-wrap table {
27+
width: 100%;
28+
border-collapse: collapse;
29+
font-size: 0.78rem;
30+
margin: 0.75rem 0 1rem;
31+
}
32+
.about-wrap th, .about-wrap td {
33+
border: 1px solid var(--border);
34+
padding: 0.45rem 0.5rem;
35+
text-align: left;
36+
vertical-align: top;
37+
}
38+
.about-wrap th { background: var(--header-bg); color: var(--text); }
39+
</style>
40+
</head>
41+
<body>
42+
<div class="about-wrap">
43+
<a class="about-back" href="./">Back to chat</a>
44+
<h1>About PeerChat</h1>
45+
46+
<p class="lead">
47+
Small-group chat inside PeerSky Browser. You create a room, share a key, and everyone who has that key joins the same swarm and sees the same history; no chat server in the middle. <br>
48+
<strong>No account.</strong> <strong>Everything stays local on your machine</strong> (room list, profile, keys file); except what you explicitly sync over the peer network.<br>
49+
<strong>You own your chats:</strong> there’s no company holding logs or resetting your password; the room key is the shared secret.
50+
</p>
51+
52+
<h2>What it does (technical)</h2>
53+
<ul>
54+
<li>Rooms with name, bio, optional link, and optional picture; local profile.</li>
55+
<li>One Hypercore feed per room; Hyperswarm for transport; live UI via SSE (<code>receive-all</code>).</li>
56+
<li>Messages encrypted with AES-256-GCM derived from the room key before they’re stored in the feed.</li>
57+
<li><strong>Reactions:</strong> emoji reactions on messages are stored in the same Hypercore (as reaction events) and sync across peers.</li>
58+
<li>Attachments go through a Hyperdrive (<code>peerchat</code>), listed in Settings → Archive like other PeerSky apps. <strong>No app-enforced upload size limit</strong>—only disk, memory while uploading, and the network.</li>
59+
<li><strong>Direct Messages (DMs):</strong> click a peer's avatar to send a private message; the recipient gets an accept/decline popup, and the room key is derived from both peer IDs so only those two people share it.</li>
60+
<li>Optional Electron <code>safeStorage</code> for the local JSON that holds rooms and keys.</li>
61+
</ul>
62+
63+
<h2>How the room key works</h2>
64+
<p class="muted">
65+
The key is a random secret shown as 64 hex characters. Anyone with it can join that room and read history
66+
from peers who have the data. Treat it like a shared password. Use <strong>Join room</strong> with the key;
67+
for room details or keys from your archive, see <strong>peerchat-rooms</strong> under Hyperdrives in
68+
<a href="peersky://settings/archive">Settings → Archive</a>.
69+
</p>
70+
71+
<h2>Security</h2>
72+
<p class="muted" style="margin-bottom: 0.5rem">These apps solve different problems; the table is to set expectations, not to pick a “winner.”</p>
73+
<table>
74+
<thead>
75+
<tr>
76+
<th></th>
77+
<th>Signal</th>
78+
<th>Matrix</th>
79+
<th>PeerChat</th>
80+
</tr>
81+
</thead>
82+
<tbody>
83+
<tr>
84+
<th>Shape</th>
85+
<td>Central + E2E</td>
86+
<td>Federated servers</td>
87+
<td>P2P swarm + core</td>
88+
</tr>
89+
<tr>
90+
<th>Account</th>
91+
<td>Phone #</td>
92+
<td>Matrix ID</td>
93+
<td>None</td>
94+
</tr>
95+
<tr>
96+
<th>Strength</th>
97+
<td>Audited E2E, PFS</td>
98+
<td>Self-host, bridges</td>
99+
<td>No signup, local-first, direct sync</td>
100+
</tr>
101+
<tr>
102+
<th>Tradeoff</th>
103+
<td>Depends on their service</td>
104+
<td>Server metadata; E2E optional</td>
105+
<td>Key = access; no ratchet / verified IDs</td>
106+
</tr>
107+
<tr>
108+
<th>File uploads</th>
109+
<td>Platform limits</td>
110+
<td>Varies by server</td>
111+
<td><strong>No limit</strong></td>
112+
</tr>
113+
</tbody>
114+
</table>
115+
</body>
116+
</html>

0 commit comments

Comments
 (0)