hacker-lobby 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/worker.js +48 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hacker-lobby",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Multiplayer terminal chat application",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/worker.js CHANGED
@@ -1,6 +1,3 @@
1
- // Active SSE connection writers
2
- const activeConnections = new Set();
3
-
4
1
  // Regex to match ANSI escape sequences (control codes, color formatting, etc.)
5
2
  const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
6
3
 
@@ -14,6 +11,52 @@ function sanitizeAnsi(str) {
14
11
  return str.replace(ansiRegex, '');
15
12
  }
16
13
 
14
+ /**
15
+ * Periodically polls the D1 database for new messages and writes them to the SSE stream.
16
+ * This ensures multi-isolate reliability since Cloudflare Workers are stateless.
17
+ * @param {WritableStreamDefaultWriter} writer
18
+ * @param {Object} env
19
+ * @param {Request} request
20
+ */
21
+ async function pollAndStream(writer, env, request) {
22
+ const encoder = new TextEncoder();
23
+ try {
24
+ const initialResult = await env.DB.prepare('SELECT MAX(id) as maxId FROM messages').first();
25
+ let lastSeenId = initialResult?.maxId || 0;
26
+
27
+ // Send initial handshake
28
+ await writer.write(encoder.encode(': ok\n\n'));
29
+
30
+ while (!request.signal.aborted) {
31
+ const { results } = await env.DB.prepare(
32
+ 'SELECT id, username, content, created_at FROM messages WHERE id > ? ORDER BY id ASC'
33
+ )
34
+ .bind(lastSeenId)
35
+ .all();
36
+
37
+ if (results && results.length > 0) {
38
+ for (const msg of results) {
39
+ const payload = JSON.stringify({
40
+ username: msg.username,
41
+ content: msg.content,
42
+ created_at: msg.created_at,
43
+ });
44
+ await writer.write(encoder.encode(`data: ${payload}\n\n`));
45
+ lastSeenId = msg.id;
46
+ }
47
+ }
48
+
49
+ await new Promise((resolve) => setTimeout(resolve, 1000));
50
+ }
51
+ } catch (err) {
52
+ console.error('SSE Stream error:', err);
53
+ } finally {
54
+ try {
55
+ await writer.close();
56
+ } catch (_) {}
57
+ }
58
+ }
59
+
17
60
  export default {
18
61
  /**
19
62
  * Fetch handler for Cloudflare Worker.
@@ -59,20 +102,8 @@ export default {
59
102
  const { readable, writable } = new TransformStream();
60
103
  const writer = writable.getWriter();
61
104
 
62
- // Add writer to active connections tracking
63
- activeConnections.add(writer);
64
-
65
- // Handle connection disconnect to prevent memory leaks
66
- request.signal.addEventListener('abort', () => {
67
- activeConnections.delete(writer);
68
- try {
69
- writer.close();
70
- } catch (_) {}
71
- });
72
-
73
- // Keep-alive or immediate connection handshake
74
- const encoder = new TextEncoder();
75
- writer.write(encoder.encode(': ok\n\n')).catch(() => {});
105
+ // Start the D1 polling loop in the background, keeping it alive with ctx.waitUntil
106
+ ctx.waitUntil(pollAndStream(writer, env, request));
76
107
 
77
108
  return new Response(readable, {
78
109
  headers: {
@@ -121,29 +152,6 @@ export default {
121
152
  .bind(sanitizedUser, sanitizedText)
122
153
  .run();
123
154
 
124
- // Broadcast the message payload to all active SSE subscribers
125
- const encoder = new TextEncoder();
126
- const payload = JSON.stringify({
127
- username: sanitizedUser,
128
- content: sanitizedText,
129
- created_at: new Date().toISOString(),
130
- });
131
- const messageChunk = encoder.encode(`data: ${payload}\n\n`);
132
-
133
- const broadcastPromises = Array.from(activeConnections).map(async (w) => {
134
- try {
135
- await w.write(messageChunk);
136
- } catch (_) {
137
- activeConnections.delete(w);
138
- try {
139
- w.close();
140
- } catch (__) {}
141
- }
142
- });
143
-
144
- // Wait for all active broadcasts
145
- await Promise.all(broadcastPromises);
146
-
147
155
  return jsonResponse({
148
156
  success: true,
149
157
  message: 'Message sent successfully',