@gjsify/example-node-net-sse-chat 0.1.6 → 0.1.7
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/dist/index.gjs.js +11327 -0
- package/dist/index.node.mjs +118 -0
- package/dist/public/index.html +66 -0
- package/dist/public/style.css +14 -0
- package/package.json +5 -5
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createRequire as __gjsify_createRequire } from 'module';
|
|
2
|
+
const require = __gjsify_createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// ../../../packages/gjs/runtime/lib/esm/index.js
|
|
5
|
+
var isGJS = typeof process !== "undefined" && typeof process.versions?.gjs === "string";
|
|
6
|
+
var isNode = typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
7
|
+
var runtimeName = isGJS ? "GJS" : isNode ? "Node.js" : "Unknown";
|
|
8
|
+
var runtimeVersion = isGJS ? process.versions.gjs : isNode ? process.versions.node : void 0;
|
|
9
|
+
|
|
10
|
+
// src/index.ts
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { EventEmitter } from "node:events";
|
|
16
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
var __dirname = dirname(__filename);
|
|
18
|
+
var PORT = parseInt(process.env.PORT || "3000", 10);
|
|
19
|
+
var chatBus = new EventEmitter();
|
|
20
|
+
var messages = [];
|
|
21
|
+
function serveStatic(res, filename, contentType) {
|
|
22
|
+
try {
|
|
23
|
+
const content = readFileSync(join(__dirname, "public", filename), "utf8");
|
|
24
|
+
res.writeHead(200, { "content-type": contentType });
|
|
25
|
+
res.end(content);
|
|
26
|
+
} catch {
|
|
27
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
28
|
+
res.end("404 Not Found");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function parseBody(req) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
let body = "";
|
|
34
|
+
req.setEncoding("utf8");
|
|
35
|
+
req.on("data", (chunk) => {
|
|
36
|
+
body += chunk;
|
|
37
|
+
});
|
|
38
|
+
req.on("end", () => {
|
|
39
|
+
try {
|
|
40
|
+
resolve(JSON.parse(body));
|
|
41
|
+
} catch {
|
|
42
|
+
reject(new Error("Invalid JSON"));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
req.on("error", reject);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function handleSSE(req, res) {
|
|
49
|
+
res.writeHead(200, {
|
|
50
|
+
"content-type": "text/event-stream",
|
|
51
|
+
"cache-control": "no-cache",
|
|
52
|
+
"connection": "keep-alive"
|
|
53
|
+
});
|
|
54
|
+
for (const msg of messages) {
|
|
55
|
+
res.write(`data: ${JSON.stringify(msg)}
|
|
56
|
+
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
59
|
+
const onMessage = (msg) => {
|
|
60
|
+
res.write(`data: ${JSON.stringify(msg)}
|
|
61
|
+
|
|
62
|
+
`);
|
|
63
|
+
};
|
|
64
|
+
chatBus.on("message", onMessage);
|
|
65
|
+
req.on("close", () => {
|
|
66
|
+
chatBus.removeListener("message", onMessage);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function handleRequest(req, res) {
|
|
70
|
+
const url = req.url || "/";
|
|
71
|
+
const method = req.method || "GET";
|
|
72
|
+
res.setHeader("access-control-allow-origin", "*");
|
|
73
|
+
res.setHeader("access-control-allow-methods", "GET, POST, OPTIONS");
|
|
74
|
+
res.setHeader("access-control-allow-headers", "Content-Type");
|
|
75
|
+
if (method === "OPTIONS") {
|
|
76
|
+
res.writeHead(204);
|
|
77
|
+
res.end();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (method === "GET" && url === "/") {
|
|
81
|
+
serveStatic(res, "index.html", "text/html; charset=utf-8");
|
|
82
|
+
} else if (method === "GET" && url === "/style.css") {
|
|
83
|
+
serveStatic(res, "style.css", "text/css; charset=utf-8");
|
|
84
|
+
} else if (method === "GET" && url === "/events") {
|
|
85
|
+
handleSSE(req, res);
|
|
86
|
+
} else if (method === "POST" && url === "/send") {
|
|
87
|
+
parseBody(req).then((body) => {
|
|
88
|
+
const user = String(body.user || "Anonymous");
|
|
89
|
+
const text = String(body.text || "");
|
|
90
|
+
if (!text.trim()) {
|
|
91
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
92
|
+
res.end(JSON.stringify({ error: "Empty message" }));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const msg = { user, text, time: (/* @__PURE__ */ new Date()).toISOString() };
|
|
96
|
+
messages.push(msg);
|
|
97
|
+
if (messages.length > 100) messages.shift();
|
|
98
|
+
chatBus.emit("message", msg);
|
|
99
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
100
|
+
res.end(JSON.stringify({ ok: true }));
|
|
101
|
+
}).catch(() => {
|
|
102
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
103
|
+
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
104
|
+
});
|
|
105
|
+
} else if (method === "GET" && url === "/api/messages") {
|
|
106
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
107
|
+
res.end(JSON.stringify({ messages, runtime: runtimeName }));
|
|
108
|
+
} else {
|
|
109
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
110
|
+
res.end("404 Not Found");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
var server = createServer(handleRequest);
|
|
114
|
+
server.listen(PORT, () => {
|
|
115
|
+
console.log(`SSE Chat running at http://localhost:${PORT}`);
|
|
116
|
+
console.log(`Runtime: ${runtimeName}`);
|
|
117
|
+
console.log("Open in browser to chat. Press Ctrl+C to stop.");
|
|
118
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
<title>gjsify SSE Chat</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1>gjsify Chat</h1>
|
|
12
|
+
<p id="status">Connecting...</p>
|
|
13
|
+
<div id="messages"></div>
|
|
14
|
+
<form id="chat-form">
|
|
15
|
+
<input type="text" id="user" placeholder="Your name" value="User" />
|
|
16
|
+
<input type="text" id="text" placeholder="Type a message..." autofocus />
|
|
17
|
+
<button type="submit">Send</button>
|
|
18
|
+
</form>
|
|
19
|
+
</main>
|
|
20
|
+
<script>
|
|
21
|
+
const messagesDiv = document.getElementById('messages');
|
|
22
|
+
const statusEl = document.getElementById('status');
|
|
23
|
+
const form = document.getElementById('chat-form');
|
|
24
|
+
const userInput = document.getElementById('user');
|
|
25
|
+
const textInput = document.getElementById('text');
|
|
26
|
+
|
|
27
|
+
// Connect to SSE endpoint
|
|
28
|
+
const evtSource = new EventSource('/events');
|
|
29
|
+
evtSource.onopen = () => { statusEl.textContent = 'Connected'; };
|
|
30
|
+
evtSource.onerror = () => { statusEl.textContent = 'Disconnected — retrying...'; };
|
|
31
|
+
evtSource.onmessage = (event) => {
|
|
32
|
+
const msg = JSON.parse(event.data);
|
|
33
|
+
const el = document.createElement('div');
|
|
34
|
+
el.className = 'message';
|
|
35
|
+
const time = new Date(msg.time).toLocaleTimeString();
|
|
36
|
+
|
|
37
|
+
const timeSpan = document.createElement('span');
|
|
38
|
+
timeSpan.className = 'time';
|
|
39
|
+
timeSpan.textContent = time + ' ';
|
|
40
|
+
|
|
41
|
+
const nameSpan = document.createElement('strong');
|
|
42
|
+
nameSpan.textContent = msg.user + ': ';
|
|
43
|
+
|
|
44
|
+
const textNode = document.createTextNode(msg.text);
|
|
45
|
+
|
|
46
|
+
el.appendChild(timeSpan);
|
|
47
|
+
el.appendChild(nameSpan);
|
|
48
|
+
el.appendChild(textNode);
|
|
49
|
+
messagesDiv.appendChild(el);
|
|
50
|
+
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
form.addEventListener('submit', async (e) => {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
const text = textInput.value.trim();
|
|
56
|
+
if (!text) return;
|
|
57
|
+
textInput.value = '';
|
|
58
|
+
await fetch('/send', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
body: JSON.stringify({ user: userInput.value || 'Anonymous', text }),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
</script>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #1e1e2e; color: #cdd6f4; }
|
|
3
|
+
main { max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
|
|
4
|
+
h1 { color: #89b4fa; margin-bottom: 0.5rem; }
|
|
5
|
+
#status { color: #6c7086; font-size: 0.85em; margin-bottom: 1rem; }
|
|
6
|
+
#messages { height: 400px; overflow-y: auto; border: 1px solid #45475a; border-radius: 8px; padding: 0.75rem; margin-bottom: 1rem; background: #181825; }
|
|
7
|
+
.message { margin: 0.3rem 0; line-height: 1.5; }
|
|
8
|
+
.time { color: #6c7086; font-size: 0.85em; }
|
|
9
|
+
strong { color: #a6e3a1; }
|
|
10
|
+
#chat-form { display: flex; gap: 0.5rem; }
|
|
11
|
+
input { flex: 1; padding: 0.5rem 0.75rem; border: 1px solid #45475a; border-radius: 6px; background: #313244; color: #cdd6f4; font-size: 1rem; }
|
|
12
|
+
input:first-child { flex: 0 0 120px; }
|
|
13
|
+
button { padding: 0.5rem 1.25rem; border: none; border-radius: 6px; background: #89b4fa; color: #1e1e2e; font-weight: 600; cursor: pointer; font-size: 1rem; }
|
|
14
|
+
button:hover { background: #74c7ec; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/example-node-net-sse-chat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Real-time chat using Server-Sent Events for Node.js and GJS",
|
|
5
5
|
"main": "dist/index.gjs.js",
|
|
6
6
|
"type": "module",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"test:gjs": "gjs -m test.gjs.mjs"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@gjsify/cli": "^0.1.
|
|
32
|
-
"@gjsify/node-globals": "^0.1.
|
|
33
|
-
"@gjsify/runtime": "^0.1.
|
|
34
|
-
"@gjsify/unit": "^0.1.
|
|
31
|
+
"@gjsify/cli": "^0.1.7",
|
|
32
|
+
"@gjsify/node-globals": "^0.1.7",
|
|
33
|
+
"@gjsify/runtime": "^0.1.7",
|
|
34
|
+
"@gjsify/unit": "^0.1.7",
|
|
35
35
|
"@types/node": "^25.5.2",
|
|
36
36
|
"typescript": "^6.0.2"
|
|
37
37
|
}
|