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.
- package/package.json +1 -1
- package/src/worker.js +48 -40
package/package.json
CHANGED
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
|
-
//
|
|
63
|
-
|
|
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',
|