Skip to content

Commit 1a28974

Browse files
authored
Merge pull request #51 from lklynet/feature/new-version-alert
[feature] Add new version check and refactor api/chat commands
2 parents 3211ae0 + e8ee38f commit 1a28974

File tree

22 files changed

+1166
-440
lines changed

22 files changed

+1166
-440
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,22 @@ Add this to your `services.yaml`:
181181
182182
To get the icon to work, you have to add the icon to `/app/public/icons`. See detailed [instructions](https://gethomepage.dev/configs/services/#icons).
183183

184+
185+
### API Documentation
186+
187+
For developers who are not satisfied in just looking a number on the screen but are craving to add new features, a comprehensive development API documentation is available in [`devdocs/API.md`](devdocs/API.md).
188+
189+
**Available Endpoints:**
190+
- `GET /api/stats` - Current node statistics
191+
- `POST /api/chat` - Send P2P chat messages
192+
- `GET /api/github/latest-release` - Latest release information
193+
- `GET /events` - Server-Sent Events stream for real-time updates
194+
- `GET /js/lists.js` - Dynamic adjectives/nouns for screenname generation
195+
- `GET /js/screenname.js` - Screenname generation logic
196+
- `GET /` - Main HTML page
197+
198+
The documentation includes security best practices, rate limiting guidelines, modular route structure, and examples for creating new endpoints.
199+
184200
</details>
185201

186202
<details>

devdocs/API.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# API Documentation
2+
3+
HTTP endpoints for Hypermind integration and development.
4+
5+
## Endpoints
6+
7+
You can test all the available endpoints running: `nude test-api.js`.
8+
9+
<details>
10+
<summary><code>GET /api/stats</code></summary>
11+
12+
Returns node statistics and swarm information.
13+
14+
```json
15+
{
16+
"count": 42,
17+
"totalUnique": 1337,
18+
"direct": 8,
19+
"id": "abc123...",
20+
"screenname": "BraveElephant",
21+
"diagnostics": {...},
22+
"chatEnabled": true,
23+
"mapEnabled": true,
24+
"peers": [...]
25+
}
26+
```
27+
28+
</details>
29+
30+
<details>
31+
<summary><code>POST /api/chat</code></summary>
32+
33+
Send P2P chat message. Rate limited: 5 messages per 5 seconds.
34+
35+
```json
36+
{
37+
"content": "Hello, world!",
38+
"scope": "GLOBAL",
39+
"target": null
40+
}
41+
```
42+
43+
</details>
44+
45+
<details>
46+
<summary><code>GET /api/github/latest-release</code></summary>
47+
48+
Latest GitHub release information.
49+
50+
```json
51+
{
52+
"tag_name": "v1.2.3",
53+
"html_url": "https://github.com/lklynet/hypermind/releases/tag/v1.2.3",
54+
"published_at": "2026-01-08T12:00:00Z",
55+
"body": "Release notes..."
56+
}
57+
```
58+
59+
</details>
60+
61+
<details>
62+
<summary><code>GET /events</code></summary>
63+
64+
Server-Sent Events stream for real-time updates. Returns same data as `/api/stats`.
65+
66+
</details>
67+
68+
<details>
69+
<summary><code>GET /js/lists.js</code></summary>
70+
71+
Dynamic JavaScript file containing adjectives and nouns for screenname generation.
72+
73+
</details>
74+
75+
<details>
76+
<summary><code>GET /js/screenname.js</code></summary>
77+
78+
Dynamic JavaScript file with screenname generation logic for browser.
79+
80+
</details>
81+
82+
<details>
83+
<summary><code>GET /</code></summary>
84+
85+
Main HTML page with server-side template rendering.
86+
87+
</details>
88+
89+
## Development
90+
91+
### Route Structure
92+
93+
Routes are modular in `src/web/routes/`:
94+
95+
```
96+
src/web/routes/
97+
├── your-new-route.js
98+
```
99+
100+
### Creating Routes
101+
102+
<details>
103+
<summary>1. Create module</summary>
104+
105+
`src/web/routes/your-new-route.js`:
106+
107+
```javascript
108+
const setupYourNewRouteRoutes = (router, dependencies) => {
109+
const { identity, peerManager } = dependencies;
110+
111+
router.get("/api/your-new-route", (req, res) => {
112+
res.json({ success: true });
113+
});
114+
};
115+
116+
module.exports = { setupYourNewRouteRoutes };
117+
```
118+
119+
</details>
120+
121+
<details>
122+
<summary>2. Register route</summary>
123+
124+
In `src/web/routes.js`:
125+
126+
```javascript
127+
const { setupYourNewRouteRoutes } = require("./routes/your-new-route");
128+
129+
const yourNewRouteDeps = { identity, peerManager };
130+
setupYourNewRouteRoutes(app, yourNewRouteDeps);
131+
```
132+
133+
</details>
134+
135+
<details>
136+
<summary>3. Add constants</summary>
137+
138+
In `src/config/constants.js`:
139+
140+
```javascript
141+
const YOUR_CONFIG = {
142+
enabled: process.env.ENABLE_YOUR_FEATURE === "true",
143+
rateLimit: parseInt(process.env.YOUR_RATE_LIMIT) || 1000,
144+
};
145+
146+
module.exports = { YOUR_CONFIG };
147+
```
148+
149+
</details>
150+
151+
### Security
152+
153+
<details>
154+
<summary>Rate limiting</summary>
155+
156+
```javascript
157+
let requestHistory = [];
158+
159+
router.get("/api/your-new-route", (req, res) => {
160+
const now = Date.now();
161+
requestHistory = requestHistory.filter((time) => now - time < 10000);
162+
163+
if (requestHistory.length >= 5) {
164+
return res.status(429).json({ error: "Rate limit exceeded" });
165+
}
166+
167+
requestHistory.push(now);
168+
});
169+
```
170+
171+
</details>
172+
173+
<details>
174+
<summary>Input validation</summary>
175+
176+
```javascript
177+
router.post("/api/your-new-route", (req, res) => {
178+
const { data } = req.body;
179+
180+
if (!data || typeof data !== "string" || data.length > 1000) {
181+
return res.status(400).json({ error: "Invalid data" });
182+
}
183+
});
184+
```
185+
186+
</details>
187+
188+
<details>
189+
<summary>Error handling</summary>
190+
191+
```javascript
192+
router.get("/api/your-new-route", async (req, res) => {
193+
try {
194+
const result = await someOperation();
195+
res.json({ success: true, data: result });
196+
} catch (error) {
197+
console.error("Error:", error);
198+
res.status(500).json({ error: "Operation failed" });
199+
}
200+
});
201+
```
202+
203+
</details>
204+
205+
### Testing
206+
207+
```bash
208+
curl http://localhost:3000/api/your-new-route
209+
```

devdocs/CHAT-COMMANDS.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Chat Commands
2+
3+
Chat commands are modular and located in `public/js/chat-commands/`.
4+
5+
## Adding a Command
6+
7+
1. Create `chat-commands/mycommand.js`:
8+
```javascript
9+
export const myCommand = {
10+
description: "What it does",
11+
execute: () => {
12+
const output = document.getElementById("terminal-output");
13+
// implementation
14+
},
15+
};
16+
```
17+
18+
2. Import and register in `commands.js`:
19+
```javascript
20+
import { myCommand } from "./chat-commands/mycommand.js";
21+
22+
const actions = {
23+
// ... existing commands
24+
"/mycommand": myCommand,
25+
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hypermind",
3-
"version": "1.0.0",
3+
"version": "0.9.0",
44
"description": "A decentralized P2P counter of active deployments",
55
"main": "server.js",
66
"scripts": {

public/app.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ const appendMessage = (msg) => {
466466
senderName = msg.sender.slice(-8);
467467
}
468468

469-
// Update name map
469+
// Update name map (before changing senderName to "You")
470470
nameToId.set(senderName, msg.sender);
471471

472472
if (msg.sender === myId) senderName = "You";
@@ -491,7 +491,7 @@ const appendMessage = (msg) => {
491491

492492
const contentSpan = document.createElement("span");
493493
contentSpan.className = "msg-content";
494-
// Use formatMessage for rich text rendering
494+
495495
const rawContent = ` > ${msg.content}`;
496496
if (window.ChatCommands) {
497497
contentSpan.innerHTML = window.ChatCommands.formatMessage(rawContent);
@@ -523,13 +523,24 @@ terminalInput.addEventListener("keypress", async (e) => {
523523
if (window.ChatCommands) {
524524
const result = window.ChatCommands.processInput(content);
525525
if (result.type === "action") {
526-
window.ChatCommands.actions[result.command].execute();
526+
const action = window.ChatCommands.actions[result.command];
527+
if (action && typeof action.execute === "function") {
528+
try {
529+
action.execute();
530+
} catch (err) {
531+
console.error("Command execution error:", err);
532+
systemStatusBar.innerText = `[SYSTEM] Command failed: ${result.command}`;
533+
}
534+
} else {
535+
systemStatusBar.innerText = `[SYSTEM] Unknown command: ${result.command}`;
536+
}
527537
return;
528538
} else if (result.type === "text") {
529539
content = result.content;
530540
}
531541
}
532542

543+
533544
let scope = "GLOBAL";
534545
let target = null;
535546

@@ -594,10 +605,11 @@ terminalInput.addEventListener("keypress", async (e) => {
594605
target = nameToId.get(potentialName);
595606
content = msg;
596607
scope = "GLOBAL";
608+
} else if (!msg) {
609+
systemStatusBar.innerText = `[SYSTEM] Usage: /${potentialName} <message>`;
610+
return;
597611
} else {
598-
// If it looks like a command but we don't recognize the user or command
599-
// Prevent sending it as raw text to chat
600-
systemStatusBar.innerText = `[SYSTEM] Unknown command or user: ${potentialName}`;
612+
systemStatusBar.innerText = `[SYSTEM] Unknown user: ${potentialName}`;
601613
return;
602614
}
603615
}
@@ -805,4 +817,6 @@ function cycleTheme() {
805817
}
806818
}
807819

820+
window.cycleTheme = cycleTheme;
821+
808822
document.getElementById("theme-switcher").addEventListener("click", cycleTheme);

0 commit comments

Comments
 (0)