@mooncompany/uplink-chat 0.5.0
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.
Potentially problematic release.
This version of @mooncompany/uplink-chat might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/bin/uplink.js +279 -0
- package/middleware/error-handler.js +69 -0
- package/package.json +93 -0
- package/public/css/agents.36b98c0f.css +1469 -0
- package/public/css/agents.css +1469 -0
- package/public/css/app.a6a7f8f5.css +2731 -0
- package/public/css/app.css +2731 -0
- package/public/css/artifacts.css +444 -0
- package/public/css/commands.css +55 -0
- package/public/css/connection.css +131 -0
- package/public/css/dashboard.css +233 -0
- package/public/css/developer.css +328 -0
- package/public/css/files.css +123 -0
- package/public/css/markdown.css +156 -0
- package/public/css/message-actions.css +278 -0
- package/public/css/mobile.css +614 -0
- package/public/css/panels-unified.css +483 -0
- package/public/css/premium.css +415 -0
- package/public/css/realtime.css +189 -0
- package/public/css/satellites.css +401 -0
- package/public/css/shortcuts.css +185 -0
- package/public/css/split-view.4def0262.css +673 -0
- package/public/css/split-view.css +673 -0
- package/public/css/theme-generator.css +391 -0
- package/public/css/themes.css +387 -0
- package/public/css/timestamps.css +54 -0
- package/public/css/variables.css +78 -0
- package/public/dist/bundle.b55050c4.js +15757 -0
- package/public/favicon.svg +24 -0
- package/public/img/agents/ada.png +0 -0
- package/public/img/agents/clarice.png +0 -0
- package/public/img/agents/dennis-nedry.png +0 -0
- package/public/img/agents/elliot-alderson.png +0 -0
- package/public/img/agents/main.png +0 -0
- package/public/img/agents/scotty.png +0 -0
- package/public/img/agents/top-flight-security.png +0 -0
- package/public/index.html +1083 -0
- package/public/js/agents-data.js +234 -0
- package/public/js/agents-ui.js +72 -0
- package/public/js/agents.js +1525 -0
- package/public/js/app.js +79 -0
- package/public/js/appearance-settings.js +111 -0
- package/public/js/artifacts.js +432 -0
- package/public/js/audio-queue.js +168 -0
- package/public/js/bootstrap.js +54 -0
- package/public/js/chat.js +1211 -0
- package/public/js/commands.js +581 -0
- package/public/js/connection-api.js +121 -0
- package/public/js/connection.js +1231 -0
- package/public/js/context-tracker.js +271 -0
- package/public/js/core.js +172 -0
- package/public/js/dashboard.js +452 -0
- package/public/js/developer.js +432 -0
- package/public/js/encryption.js +124 -0
- package/public/js/errors.js +122 -0
- package/public/js/event-bus.js +77 -0
- package/public/js/fetch-utils.js +171 -0
- package/public/js/file-handler.js +229 -0
- package/public/js/files.js +352 -0
- package/public/js/gateway-chat.js +538 -0
- package/public/js/logger.js +112 -0
- package/public/js/markdown.js +190 -0
- package/public/js/message-actions.js +431 -0
- package/public/js/message-renderer.js +288 -0
- package/public/js/missed-messages.js +235 -0
- package/public/js/mobile-debug.js +95 -0
- package/public/js/notifications.js +367 -0
- package/public/js/offline-queue.js +178 -0
- package/public/js/onboarding.js +543 -0
- package/public/js/panels.js +156 -0
- package/public/js/premium.js +412 -0
- package/public/js/realtime-voice.js +844 -0
- package/public/js/satellite-sync.js +256 -0
- package/public/js/satellite-ui.js +175 -0
- package/public/js/satellites.js +1516 -0
- package/public/js/settings.js +1087 -0
- package/public/js/shortcuts.js +381 -0
- package/public/js/split-chat.js +1234 -0
- package/public/js/split-resize.js +211 -0
- package/public/js/splitview.js +340 -0
- package/public/js/storage.js +408 -0
- package/public/js/streaming-handler.js +324 -0
- package/public/js/stt-settings.js +316 -0
- package/public/js/theme-generator.js +661 -0
- package/public/js/themes.js +164 -0
- package/public/js/timestamps.js +198 -0
- package/public/js/tts-settings.js +575 -0
- package/public/js/ui.js +267 -0
- package/public/js/update-notifier.js +143 -0
- package/public/js/utils/constants.js +165 -0
- package/public/js/utils/sanitize.js +93 -0
- package/public/js/utils/sse-parser.js +195 -0
- package/public/js/voice.js +883 -0
- package/public/manifest.json +58 -0
- package/public/moon_texture.jpg +0 -0
- package/public/sw.js +221 -0
- package/public/three.min.js +6 -0
- package/server/channel.js +529 -0
- package/server/chat.js +270 -0
- package/server/config-store.js +362 -0
- package/server/config.js +159 -0
- package/server/context.js +131 -0
- package/server/gateway-commands.js +211 -0
- package/server/gateway-proxy.js +318 -0
- package/server/index.js +22 -0
- package/server/logger.js +89 -0
- package/server/middleware/auth.js +188 -0
- package/server/middleware.js +218 -0
- package/server/openclaw-discover.js +308 -0
- package/server/premium/index.js +156 -0
- package/server/premium/license.js +140 -0
- package/server/realtime/bridge.js +837 -0
- package/server/realtime/index.js +349 -0
- package/server/realtime/tts-stream.js +446 -0
- package/server/routes/agents.js +564 -0
- package/server/routes/artifacts.js +174 -0
- package/server/routes/chat.js +311 -0
- package/server/routes/config-settings.js +345 -0
- package/server/routes/config.js +603 -0
- package/server/routes/files.js +307 -0
- package/server/routes/index.js +18 -0
- package/server/routes/media.js +451 -0
- package/server/routes/missed-messages.js +107 -0
- package/server/routes/premium.js +75 -0
- package/server/routes/push.js +156 -0
- package/server/routes/satellite.js +406 -0
- package/server/routes/status.js +251 -0
- package/server/routes/stt.js +35 -0
- package/server/routes/voice.js +260 -0
- package/server/routes/webhooks.js +203 -0
- package/server/routes.js +206 -0
- package/server/runtime-config.js +336 -0
- package/server/share.js +305 -0
- package/server/stt/faster-whisper.js +72 -0
- package/server/stt/groq.js +51 -0
- package/server/stt/index.js +196 -0
- package/server/stt/openai.js +49 -0
- package/server/sync.js +244 -0
- package/server/tailscale-https.js +175 -0
- package/server/tts.js +646 -0
- package/server/update-checker.js +172 -0
- package/server/utils/filename.js +129 -0
- package/server/utils.js +147 -0
- package/server/watchdog.js +318 -0
- package/server/websocket/broadcast.js +359 -0
- package/server/websocket/connections.js +339 -0
- package/server/websocket/index.js +215 -0
- package/server/websocket/routing.js +277 -0
- package/server/websocket/sync.js +102 -0
- package/server.js +404 -0
- package/utils/detect-tool-usage.js +93 -0
- package/utils/errors.js +158 -0
- package/utils/html-escape.js +84 -0
- package/utils/id-sanitize.js +94 -0
- package/utils/response.js +130 -0
- package/utils/with-retry.js +105 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Routing Module
|
|
3
|
+
* Message type dispatch, chat handler, abort handling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { log } from '../utils.js';
|
|
7
|
+
import { sendMessage } from '../channel.js';
|
|
8
|
+
import { SESSION_USER, WEBSOCKET } from '../config.js';
|
|
9
|
+
import { validateSetUser, isAuthEnabled } from '../middleware/auth.js';
|
|
10
|
+
import {
|
|
11
|
+
wsClients,
|
|
12
|
+
activeStreamingRequests,
|
|
13
|
+
safeSend,
|
|
14
|
+
trimStreamingRequests,
|
|
15
|
+
WS_CONNECTION_LIMITS,
|
|
16
|
+
} from './connections.js';
|
|
17
|
+
import {
|
|
18
|
+
generateMessageId,
|
|
19
|
+
broadcastSyncMessage,
|
|
20
|
+
broadcastSyncThinking,
|
|
21
|
+
broadcastSyncTool,
|
|
22
|
+
sendToClient,
|
|
23
|
+
} from './broadcast.js';
|
|
24
|
+
import {
|
|
25
|
+
broadcastSyncDelta,
|
|
26
|
+
cleanupSyncDeltaThrottle,
|
|
27
|
+
} from './sync.js';
|
|
28
|
+
|
|
29
|
+
// ============================================
|
|
30
|
+
// Chat Handler
|
|
31
|
+
// ============================================
|
|
32
|
+
|
|
33
|
+
async function handleWebSocketChat(clientId, payload, requestHelpers, saveMessageToSync) {
|
|
34
|
+
const client = wsClients.get(clientId);
|
|
35
|
+
if (!client) return;
|
|
36
|
+
|
|
37
|
+
const { message, mode = 'text', requestId } = payload;
|
|
38
|
+
const { canAcceptRequest, startRequest, endRequest, textToSpeech } = requestHelpers;
|
|
39
|
+
|
|
40
|
+
if (!message) {
|
|
41
|
+
safeSend(client.ws, {
|
|
42
|
+
type: 'error',
|
|
43
|
+
requestId,
|
|
44
|
+
error: 'No message provided'
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!canAcceptRequest()) {
|
|
50
|
+
safeSend(client.ws, {
|
|
51
|
+
type: 'error',
|
|
52
|
+
requestId,
|
|
53
|
+
error: 'Server busy. Please wait a moment.'
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const trackingId = startRequest('ws-chat');
|
|
59
|
+
const startTime = Date.now();
|
|
60
|
+
|
|
61
|
+
const abortController = new AbortController();
|
|
62
|
+
|
|
63
|
+
// Ensure streaming requests map doesn't grow unbounded
|
|
64
|
+
trimStreamingRequests();
|
|
65
|
+
|
|
66
|
+
activeStreamingRequests.set(requestId, { abortController, clientId, startedAt: Date.now() });
|
|
67
|
+
|
|
68
|
+
log('debug', `[WS Chat] "${message.substring(0, 50)}..." from ${clientId}`);
|
|
69
|
+
|
|
70
|
+
// Extract satellite ID from session user (format: SESSION_USER or SESSION_USER:satelliteId)
|
|
71
|
+
// Must be declared before any usage to avoid TDZ error in strict mode
|
|
72
|
+
const sessionParts = (client.sessionUser || SESSION_USER).split(':');
|
|
73
|
+
const satelliteId = sessionParts[1] || 'main';
|
|
74
|
+
|
|
75
|
+
await saveMessageToSync('user', message);
|
|
76
|
+
|
|
77
|
+
// Generate message ID for sync
|
|
78
|
+
const userMessageId = generateMessageId();
|
|
79
|
+
|
|
80
|
+
// Broadcast user message to other clients for sync
|
|
81
|
+
broadcastSyncMessage('user', message, satelliteId, userMessageId, clientId);
|
|
82
|
+
|
|
83
|
+
safeSend(client.ws, {
|
|
84
|
+
type: 'chat_start',
|
|
85
|
+
requestId,
|
|
86
|
+
timestamp: new Date().toISOString()
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Generate a request ID for correlating sync stream deltas with final message
|
|
90
|
+
const syncRequestId = generateMessageId();
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
let fullResponse = '';
|
|
94
|
+
let detectedTools = [];
|
|
95
|
+
|
|
96
|
+
// Use unified sendMessage (routes through channel if enabled)
|
|
97
|
+
const result = await sendMessage({
|
|
98
|
+
message,
|
|
99
|
+
satelliteId,
|
|
100
|
+
mode,
|
|
101
|
+
requestId,
|
|
102
|
+
signal: abortController.signal,
|
|
103
|
+
onThinking: () => {
|
|
104
|
+
sendToClient(clientId, {
|
|
105
|
+
type: 'chat_thinking',
|
|
106
|
+
requestId,
|
|
107
|
+
timestamp: Date.now()
|
|
108
|
+
});
|
|
109
|
+
broadcastSyncThinking(syncRequestId, satelliteId, clientId);
|
|
110
|
+
},
|
|
111
|
+
onChunk: (content) => {
|
|
112
|
+
fullResponse += content;
|
|
113
|
+
sendToClient(clientId, {
|
|
114
|
+
type: 'chat_chunk',
|
|
115
|
+
requestId,
|
|
116
|
+
content,
|
|
117
|
+
timestamp: Date.now()
|
|
118
|
+
});
|
|
119
|
+
broadcastSyncDelta(syncRequestId, content, satelliteId, clientId);
|
|
120
|
+
},
|
|
121
|
+
onTool: (tool) => {
|
|
122
|
+
if (!detectedTools.includes(tool)) {
|
|
123
|
+
detectedTools.push(tool);
|
|
124
|
+
sendToClient(clientId, {
|
|
125
|
+
type: 'tool_used',
|
|
126
|
+
requestId,
|
|
127
|
+
tool,
|
|
128
|
+
timestamp: Date.now()
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
broadcastSyncTool(syncRequestId, tool, satelliteId, clientId);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const elapsed = Date.now() - startTime;
|
|
136
|
+
|
|
137
|
+
if (result.response) {
|
|
138
|
+
await saveMessageToSync('assistant', result.response);
|
|
139
|
+
|
|
140
|
+
// Flush remaining deltas before sending final sync message
|
|
141
|
+
cleanupSyncDeltaThrottle(syncRequestId);
|
|
142
|
+
|
|
143
|
+
// Broadcast assistant response to other clients for sync (with requestId for stream finalization)
|
|
144
|
+
const assistantMessageId = generateMessageId();
|
|
145
|
+
broadcastSyncMessage('assistant', result.response, satelliteId, assistantMessageId, clientId, syncRequestId);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let audioUrl = null;
|
|
149
|
+
if (mode === 'voice' && result.response && textToSpeech) {
|
|
150
|
+
try {
|
|
151
|
+
audioUrl = await textToSpeech(result.response);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
log('error', '[WS Chat] TTS failed:', e.message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
sendToClient(clientId, {
|
|
158
|
+
type: 'chat_complete',
|
|
159
|
+
requestId,
|
|
160
|
+
response: result.response,
|
|
161
|
+
audioUrl,
|
|
162
|
+
elapsed,
|
|
163
|
+
usage: result.usage,
|
|
164
|
+
tools: result.tools,
|
|
165
|
+
timestamp: Date.now()
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
log('debug', `[WS Chat] Complete in ${elapsed}ms, tokens: ${result.usage?.total_tokens || '?'}${result.usage?.estimated ? ' (est)' : ''}`);
|
|
169
|
+
|
|
170
|
+
} catch (err) {
|
|
171
|
+
cleanupSyncDeltaThrottle(syncRequestId);
|
|
172
|
+
if (err.name === 'AbortError') {
|
|
173
|
+
sendToClient(clientId, {
|
|
174
|
+
type: 'chat_aborted',
|
|
175
|
+
requestId,
|
|
176
|
+
response: '',
|
|
177
|
+
timestamp: Date.now()
|
|
178
|
+
});
|
|
179
|
+
log('info', `[WS Chat] Aborted by user`);
|
|
180
|
+
} else {
|
|
181
|
+
log('error', `[WS Chat] Error:`, err.message);
|
|
182
|
+
sendToClient(clientId, {
|
|
183
|
+
type: 'chat_error',
|
|
184
|
+
requestId,
|
|
185
|
+
error: err.message,
|
|
186
|
+
timestamp: Date.now()
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
} finally {
|
|
190
|
+
cleanupSyncDeltaThrottle(syncRequestId);
|
|
191
|
+
activeStreamingRequests.delete(requestId);
|
|
192
|
+
endRequest(trackingId);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ============================================
|
|
197
|
+
// Message Dispatcher
|
|
198
|
+
// ============================================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Handle an incoming WebSocket message, dispatching by type
|
|
202
|
+
*/
|
|
203
|
+
export async function handleWebSocketMessage(clientId, message, requestHelpers, saveMessageToSync) {
|
|
204
|
+
const client = wsClients.get(clientId);
|
|
205
|
+
if (!client) return;
|
|
206
|
+
|
|
207
|
+
const { type, ...payload } = message;
|
|
208
|
+
const { isProcessing, activeRequests, MAX_CONCURRENT_REQUESTS } = requestHelpers;
|
|
209
|
+
|
|
210
|
+
switch (type) {
|
|
211
|
+
case 'ping':
|
|
212
|
+
client.lastPing = Date.now();
|
|
213
|
+
safeSend(client.ws, { type: 'pong', timestamp: Date.now() });
|
|
214
|
+
break;
|
|
215
|
+
|
|
216
|
+
case 'status':
|
|
217
|
+
safeSend(client.ws, {
|
|
218
|
+
type: 'status',
|
|
219
|
+
data: {
|
|
220
|
+
processing: isProcessing(),
|
|
221
|
+
activeRequests: activeRequests.size,
|
|
222
|
+
maxConcurrent: MAX_CONCURRENT_REQUESTS,
|
|
223
|
+
connectedClients: wsClients.size,
|
|
224
|
+
maxConnections: WS_CONNECTION_LIMITS.maxTotal,
|
|
225
|
+
maxPerIp: WS_CONNECTION_LIMITS.maxPerIp,
|
|
226
|
+
timestamp: new Date().toISOString()
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case 'chat':
|
|
232
|
+
await handleWebSocketChat(clientId, payload, requestHelpers, saveMessageToSync);
|
|
233
|
+
break;
|
|
234
|
+
|
|
235
|
+
case 'abort':
|
|
236
|
+
if (payload.requestId) {
|
|
237
|
+
const activeReq = activeStreamingRequests.get(payload.requestId);
|
|
238
|
+
if (activeReq && activeReq.clientId === clientId) {
|
|
239
|
+
log('info', `[WS] Aborting request ${payload.requestId}`);
|
|
240
|
+
activeReq.abortController.abort();
|
|
241
|
+
activeStreamingRequests.delete(payload.requestId);
|
|
242
|
+
safeSend(client.ws, {
|
|
243
|
+
type: 'chat_aborted',
|
|
244
|
+
requestId: payload.requestId,
|
|
245
|
+
timestamp: Date.now()
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
case 'set_user':
|
|
252
|
+
if (payload.user) {
|
|
253
|
+
// M-39 (H-02): Validate set_user when auth is enabled to prevent impersonation
|
|
254
|
+
if (!validateSetUser(payload.user, client.sessionUser)) {
|
|
255
|
+
log('warn', `[WS] set_user rejected for ${clientId}: cannot impersonate ${payload.user}`);
|
|
256
|
+
safeSend(client.ws, {
|
|
257
|
+
type: 'error',
|
|
258
|
+
message: 'Authentication required to change user identity'
|
|
259
|
+
});
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
client.sessionUser = payload.user;
|
|
264
|
+
safeSend(client.ws, {
|
|
265
|
+
type: 'user_set',
|
|
266
|
+
user: payload.user
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
|
|
271
|
+
default:
|
|
272
|
+
safeSend(client.ws, {
|
|
273
|
+
type: 'error',
|
|
274
|
+
error: `Unknown message type: ${type}`
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Sync Module
|
|
3
|
+
* Sync delta throttling, cross-device streaming state
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { log } from '../utils.js';
|
|
7
|
+
import { WEBSOCKET } from '../config.js';
|
|
8
|
+
import { broadcastToProxyClients } from '../gateway-proxy.js';
|
|
9
|
+
import { broadcast } from './broadcast.js';
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// Sync Delta Throttling
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
/** Sync delta throttles: requestId -> { lastSent, pendingContent, timer, satelliteId, excludeClientId } */
|
|
16
|
+
const syncDeltaThrottles = new Map();
|
|
17
|
+
const SYNC_DELTA_THROTTLE_MS = WEBSOCKET.syncDeltaThrottleMs;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Flush accumulated sync delta content to all clients
|
|
21
|
+
*/
|
|
22
|
+
function flushSyncDelta(requestId) {
|
|
23
|
+
const state = syncDeltaThrottles.get(requestId);
|
|
24
|
+
if (!state || !state.pendingContent) return;
|
|
25
|
+
|
|
26
|
+
const message = {
|
|
27
|
+
type: 'event',
|
|
28
|
+
event: 'sync.delta',
|
|
29
|
+
payload: {
|
|
30
|
+
requestId,
|
|
31
|
+
content: state.pendingContent,
|
|
32
|
+
satelliteId: state.satelliteId || 'main',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
broadcast(message, state.excludeClientId);
|
|
36
|
+
broadcastToProxyClients(message);
|
|
37
|
+
|
|
38
|
+
state.pendingContent = '';
|
|
39
|
+
state.lastSent = Date.now();
|
|
40
|
+
if (state.timer) {
|
|
41
|
+
clearTimeout(state.timer);
|
|
42
|
+
state.timer = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Broadcast sync.delta event with throttling
|
|
48
|
+
* Accumulates content and flushes at most every SYNC_DELTA_THROTTLE_MS
|
|
49
|
+
*/
|
|
50
|
+
export function broadcastSyncDelta(requestId, content, satelliteId, excludeClientId = null) {
|
|
51
|
+
let state = syncDeltaThrottles.get(requestId);
|
|
52
|
+
if (!state) {
|
|
53
|
+
state = { lastSent: 0, pendingContent: '', timer: null, satelliteId, excludeClientId };
|
|
54
|
+
syncDeltaThrottles.set(requestId, state);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
state.pendingContent += content;
|
|
58
|
+
|
|
59
|
+
const elapsed = Date.now() - state.lastSent;
|
|
60
|
+
if (elapsed >= SYNC_DELTA_THROTTLE_MS) {
|
|
61
|
+
// Enough time has passed, flush immediately
|
|
62
|
+
flushSyncDelta(requestId);
|
|
63
|
+
} else if (!state.timer) {
|
|
64
|
+
// Schedule a flush for the remaining time
|
|
65
|
+
const remaining = SYNC_DELTA_THROTTLE_MS - elapsed;
|
|
66
|
+
state.timer = setTimeout(() => {
|
|
67
|
+
state.timer = null;
|
|
68
|
+
flushSyncDelta(requestId);
|
|
69
|
+
}, remaining);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Cleanup sync delta throttle state for a completed request
|
|
75
|
+
* Flushes any remaining content and removes the entry
|
|
76
|
+
*/
|
|
77
|
+
export function cleanupSyncDeltaThrottle(requestId) {
|
|
78
|
+
const state = syncDeltaThrottles.get(requestId);
|
|
79
|
+
if (!state) return;
|
|
80
|
+
|
|
81
|
+
// Flush remaining content
|
|
82
|
+
if (state.pendingContent) {
|
|
83
|
+
flushSyncDelta(requestId);
|
|
84
|
+
}
|
|
85
|
+
if (state.timer) {
|
|
86
|
+
clearTimeout(state.timer);
|
|
87
|
+
}
|
|
88
|
+
syncDeltaThrottles.delete(requestId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clean up sync delta throttles for a specific client (called on disconnect)
|
|
93
|
+
* @param {string} clientId - The client ID to clean up throttles for
|
|
94
|
+
*/
|
|
95
|
+
export function cleanupSyncForClient(clientId) {
|
|
96
|
+
for (const [reqId, state] of syncDeltaThrottles) {
|
|
97
|
+
if (state.excludeClientId === clientId) {
|
|
98
|
+
if (state.timer) clearTimeout(state.timer);
|
|
99
|
+
syncDeltaThrottles.delete(reqId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|