Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Plan: Fix local demo server connection refused on port 8080

## Problem Analysis

The `server.js` file has two issues preventing the demo from working:

### Issue 1: IPv6/IPv4 Binding
`server.listen(port)` (line 10) is called without a host argument. Per [Node.js docs](https://nodejs.org/api/net.html#serverlistenport-host-backlog-callback), when host is omitted the server binds to `::` (IPv6 unspecified address) when IPv6 is available. While most OSes support dual-stack (accepting both IPv4 and IPv6 on `::`), some environments (including containers and sandboxes) may have IPv6 disabled or `localhost` resolving only to `127.0.0.1`, causing "connection refused".

**Fix**: Pass `'0.0.0.0'` as the host to `server.listen()` to explicitly bind to all IPv4 interfaces.

### Issue 2: No Static File Serving
The current server handler only returns a plain text response (`"Hello from mogterm2!"`). It doesn't serve the HTML, CSS, or JS files that make up the demo (`demo/index.html`, `src/*.js`, `src/*.css`, `index.html`). Even if the binding issue were fixed, navigating to `http://localhost:8080` would show plain text, not the demo page.

**Fix**: Replace the stub handler with a static file server that:
- Serves files from the project root directory
- Maps `/` to `index.html` (the project already has one at root)
- Returns correct `Content-Type` headers based on file extension
- Returns 404 for missing files
- Uses only Node.js built-in modules (`node:fs`, `node:path`, `node:http`) — no new dependencies

## Scope

This is a **single-agent** task. Both fixes are in the same file (`server.js`) and are tightly coupled — you can't meaningfully test one without the other.

## Files to Change

- `server.js` — the only file that needs modification

## Implementation Steps

1. Add imports for `node:fs` and `node:path`
2. Define a MIME type map for file extensions used in the project (`.html`, `.css`, `.js`, `.json`, `.ts`)
3. Replace the request handler to:
- Resolve the requested URL path to a filesystem path relative to the project root
- Default `/` to `/index.html`
- Prevent path traversal (reject paths containing `..`)
- Read the file and serve it with the correct Content-Type
- Return 404 for missing files
4. Change `server.listen(port, ...)` to `server.listen(port, '0.0.0.0', ...)`
5. Update the console.log to show the full URL including `http://0.0.0.0:${port}`

## Validation

- Start the server with `node server.js`
- Verify `curl http://127.0.0.1:8080/` returns HTML content
- Verify `curl http://127.0.0.1:8080/demo/index.html` returns the demo page
- Verify `curl http://127.0.0.1:8080/src/terminal.js` returns JavaScript
- Run existing tests: `npx tsx --test test/**/*.test.ts` to ensure no regressions

## Sources

- [Node.js net.Server.listen() docs](https://nodejs.org/api/net.html#serverlistenport-host-backlog-callback) — confirms `::` default binding behavior
- [Node.js http.createServer() docs](https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener) — static file serving pattern
39 changes: 35 additions & 4 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
import { createServer } from "node:http";
import { readFile } from "node:fs";
import { dirname, join, extname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const port = process.env.PORT || 8080;

const mimeTypes = {
".html": "text/html",
".css": "text/css",
".js": "application/javascript",
".json": "application/json",
".ts": "text/plain",
};

const server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello from mogterm2!\n");
const pathname = new URL(req.url, "http://localhost").pathname;

if (pathname.includes("..")) {
res.writeHead(403);
res.end("Forbidden");
return;
}

const filePath = join(__dirname, pathname === "/" ? "/index.html" : pathname);
const ext = extname(filePath);
const contentType = mimeTypes[ext] || "application/octet-stream";

readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end("Not Found");
return;
}
res.writeHead(200, { "Content-Type": contentType });
res.end(data);
});
});

server.listen(port, () => {
console.log(`Server listening on port ${port}`);
server.listen(port, "0.0.0.0", () => {
console.log(`Server listening on http://0.0.0.0:${port}`);
});
6 changes: 6 additions & 0 deletions tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"mode": "single",
"claudeMd": "# Project Context\n\nYou are fixing `server.js` in a Node.js terminal emulator project (mogterm2). The project uses `\"type\": \"module\"` in package.json, so all imports must use ESM syntax (`import ... from ...`).\n\n## Bug\n\nThe demo server has two problems:\n1. `server.listen(port)` binds to `::` (IPv6 only) — must bind to `0.0.0.0` for IPv4 access\n2. The request handler only returns plain text — must serve static files from the project root\n\n## What to Change\n\nOnly modify `server.js`. The current file is:\n```js\nimport { createServer } from \"node:http\";\n\nconst port = process.env.PORT || 8080;\n\nconst server = createServer((req, res) => {\n res.writeHead(200, { \"Content-Type\": \"text/plain\" });\n res.end(\"Hello from mogterm2!\\n\");\n});\n\nserver.listen(port, () => {\n console.log(`Server listening on port ${port}`);\n});\n```\n\n## Requirements\n\n1. Add ESM imports for `node:fs`, `node:path`, and `node:url` (for `fileURLToPath` to get `__dirname` equivalent)\n2. Compute the project root using `path.dirname(fileURLToPath(import.meta.url))`\n3. Define a MIME type map: `.html` → `text/html`, `.css` → `text/css`, `.js` → `application/javascript`, `.json` → `application/json`, `.ts` → `text/plain`, default → `application/octet-stream`\n4. Replace the request handler to serve static files:\n - Parse `req.url` to get the pathname\n - Map `/` to `/index.html`\n - Reject paths containing `..` with a 403\n - Resolve the path relative to project root\n - Read the file with `fs.readFile`\n - Serve with correct Content-Type; 404 if file not found\n5. Change `server.listen(port, ...)` to `server.listen(port, '0.0.0.0', ...)`\n6. Update the console.log to show `http://0.0.0.0:${port}`\n\n## Conventions\n- ESM imports only (the project has `\"type\": \"module\"`)\n- Use `node:` protocol prefix for built-in modules (matches existing code)\n- Keep it minimal — no dependencies, no over-engineering\n- Conventional Commits for the commit message: `fix(server): bind to 0.0.0.0 and serve static files`\n\n## Validation\n- Start server: `node server.js &`\n- Test: `curl -s http://127.0.0.1:8080/ | head -5` should return HTML\n- Test: `curl -s http://127.0.0.1:8080/demo/index.html | head -5` should return HTML\n- Test: `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/nonexistent` should return 404\n- Kill the server after testing\n- Run existing tests: `npx tsx --test test/**/*.test.ts`\n\n## Gotchas\n- Must use `fileURLToPath(import.meta.url)` instead of `__dirname` (ESM has no `__dirname`)\n- `fs.readFile` callback-style is fine; no need for promises\n- URL pathname may include query strings — use `new URL(req.url, 'http://localhost').pathname` to extract just the path",
"subtasks": [],
"integration": null
}
Loading