solver-sdk 1.6.3 → 1.6.5
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/cjs/api/dependencies-api.js +120 -0
- package/dist/cjs/api/dependencies-api.js.map +1 -1
- package/dist/cjs/api/projects-api.js +103 -0
- package/dist/cjs/api/projects-api.js.map +1 -1
- package/dist/cjs/api/reasoning-api.js +105 -0
- package/dist/cjs/api/reasoning-api.js.map +1 -1
- package/dist/cjs/code-solver-sdk.js +10 -0
- package/dist/cjs/code-solver-sdk.js.map +1 -1
- package/dist/cjs/utils/code-solver-websocket-client.js +142 -62
- package/dist/cjs/utils/code-solver-websocket-client.js.map +1 -1
- package/dist/cjs/utils/websocket-client.js +259 -539
- package/dist/cjs/utils/websocket-client.js.map +1 -1
- package/dist/esm/api/dependencies-api.js +120 -0
- package/dist/esm/api/dependencies-api.js.map +1 -1
- package/dist/esm/api/projects-api.js +103 -0
- package/dist/esm/api/projects-api.js.map +1 -1
- package/dist/esm/api/reasoning-api.js +105 -0
- package/dist/esm/api/reasoning-api.js.map +1 -1
- package/dist/esm/code-solver-sdk.js +10 -0
- package/dist/esm/code-solver-sdk.js.map +1 -1
- package/dist/esm/utils/code-solver-websocket-client.js +142 -62
- package/dist/esm/utils/code-solver-websocket-client.js.map +1 -1
- package/dist/esm/utils/websocket-client.js +259 -539
- package/dist/esm/utils/websocket-client.js.map +1 -1
- package/dist/types/api/dependencies-api.d.ts +55 -0
- package/dist/types/api/dependencies-api.d.ts.map +1 -1
- package/dist/types/api/projects-api.d.ts +50 -0
- package/dist/types/api/projects-api.d.ts.map +1 -1
- package/dist/types/api/reasoning-api.d.ts +55 -0
- package/dist/types/api/reasoning-api.d.ts.map +1 -1
- package/dist/types/code-solver-sdk.d.ts.map +1 -1
- package/dist/types/utils/code-solver-websocket-client.d.ts +11 -11
- package/dist/types/utils/code-solver-websocket-client.d.ts.map +1 -1
- package/dist/types/utils/websocket-client.d.ts +54 -42
- package/dist/types/utils/websocket-client.d.ts.map +1 -1
- package/package.json +3 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// Импортируем Socket.IO клиент
|
|
2
|
+
import { io } from 'socket.io-client';
|
|
1
3
|
/**
|
|
2
|
-
* Базовый класс для WebSocket
|
|
4
|
+
* Базовый класс для WebSocket клиентов, реализованный на базе Socket.IO
|
|
3
5
|
*/
|
|
4
6
|
export class WebSocketClient {
|
|
5
7
|
/**
|
|
@@ -8,8 +10,8 @@ export class WebSocketClient {
|
|
|
8
10
|
* @param {WebSocketClientOptions} [options] Опции клиента
|
|
9
11
|
*/
|
|
10
12
|
constructor(url, options = {}) {
|
|
11
|
-
/** Экземпляр
|
|
12
|
-
this.
|
|
13
|
+
/** Экземпляр Socket.IO */
|
|
14
|
+
this.socket = null;
|
|
13
15
|
/** Счетчик попыток переподключения */
|
|
14
16
|
this.retryCount = 0;
|
|
15
17
|
/** Флаг, указывающий, что соединение было закрыто намеренно */
|
|
@@ -30,6 +32,11 @@ export class WebSocketClient {
|
|
|
30
32
|
this.socketId = null;
|
|
31
33
|
/** Хранилище ожидающих callback-функций */
|
|
32
34
|
this._pendingCallbacks = new Map();
|
|
35
|
+
/**
|
|
36
|
+
* Тип для хранения отложенных обработчиков событий
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
this._pendingCallbackHandlers = new Map();
|
|
33
40
|
this.url = url;
|
|
34
41
|
this.options = {
|
|
35
42
|
headers: options.headers || {},
|
|
@@ -79,12 +86,12 @@ export class WebSocketClient {
|
|
|
79
86
|
});
|
|
80
87
|
}
|
|
81
88
|
/**
|
|
82
|
-
* Подключается к WebSocket серверу
|
|
89
|
+
* Подключается к WebSocket серверу используя Socket.IO клиент
|
|
83
90
|
* @returns {Promise<void>}
|
|
84
91
|
*/
|
|
85
92
|
connect() {
|
|
86
93
|
// Если соединение уже установлено, возвращаем Promise.resolve
|
|
87
|
-
if (this.
|
|
94
|
+
if (this.socket && this.socket.connected) {
|
|
88
95
|
this.logger('info', 'Соединение уже установлено');
|
|
89
96
|
return Promise.resolve();
|
|
90
97
|
}
|
|
@@ -92,366 +99,216 @@ export class WebSocketClient {
|
|
|
92
99
|
this.intentionallyClosed = false;
|
|
93
100
|
return new Promise((resolve, reject) => {
|
|
94
101
|
try {
|
|
95
|
-
//
|
|
102
|
+
// Формируем корректный URL с namespace для Socket.IO
|
|
96
103
|
let wsUrl = this.url;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.webSocket = new WebSocket(wsUrl, this.options.protocols);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
// Node.js окружение
|
|
119
|
-
try {
|
|
120
|
-
// Динамически импортируем ws модуль в Node.js
|
|
121
|
-
const WebSocketImpl = require('ws');
|
|
122
|
-
this.webSocket = new WebSocketImpl(wsUrl, {
|
|
123
|
-
headers: this.options.headers,
|
|
124
|
-
protocol: this.options.protocols,
|
|
125
|
-
rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0" ? false : (this.options.rejectUnauthorized === false ? false : true)
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
const errorMsg = `Не удалось загрузить модуль ws в Node.js: ${error.message}`;
|
|
130
|
-
this.logger('error', errorMsg);
|
|
131
|
-
reject(new Error(errorMsg));
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
104
|
+
let namespaceStr = this.namespace;
|
|
105
|
+
// Для Socket.IO не нужно добавлять параметры в URL, они будут добавлены библиотекой
|
|
106
|
+
this.logger('info', 'Выполняется подключение', { url: wsUrl, namespace: namespaceStr });
|
|
107
|
+
// Настройки для Socket.IO клиента
|
|
108
|
+
const socketOptions = {
|
|
109
|
+
transports: ['websocket'], // Используем только WebSocket транспорт
|
|
110
|
+
reconnection: this.options.autoReconnect, // Автоматическое переподключение
|
|
111
|
+
reconnectionAttempts: this.options.maxRetries, // Количество попыток
|
|
112
|
+
reconnectionDelay: this.options.retryDelay, // Задержка между попытками
|
|
113
|
+
timeout: this.options.connectionTimeout, // Таймаут соединения
|
|
114
|
+
forceNew: true, // Создавать новое соединение
|
|
115
|
+
extraHeaders: this.options.headers, // HTTP заголовки
|
|
116
|
+
rejectUnauthorized: this.options.rejectUnauthorized // Проверка сертификатов
|
|
117
|
+
};
|
|
118
|
+
// Если указан API ключ, добавляем его в query и auth
|
|
119
|
+
if (this.options.apiKey) {
|
|
120
|
+
socketOptions.auth = { token: this.options.apiKey };
|
|
121
|
+
socketOptions.query = { token: this.options.apiKey };
|
|
134
122
|
}
|
|
123
|
+
// Создаем Socket.IO клиент с namespace
|
|
124
|
+
this.socket = io(wsUrl + namespaceStr, socketOptions);
|
|
135
125
|
// Устанавливаем таймаут соединения
|
|
136
126
|
this.connectionTimeoutTimer = setTimeout(() => {
|
|
137
|
-
if (this.
|
|
138
|
-
|
|
127
|
+
if (this.socket && !this.socket.connected) {
|
|
128
|
+
const error = new Error('Таймаут подключения WebSocket');
|
|
129
|
+
this.logger('error', 'Превышен таймаут подключения', { timeout: this.options.connectionTimeout });
|
|
130
|
+
reject(error);
|
|
139
131
|
this.close();
|
|
140
132
|
}
|
|
141
133
|
}, this.options.connectionTimeout);
|
|
142
|
-
// Обработчик
|
|
143
|
-
this.
|
|
134
|
+
// Обработчик успешного подключения
|
|
135
|
+
this.socket.on('connect', () => {
|
|
144
136
|
clearTimeout(this.connectionTimeoutTimer);
|
|
145
137
|
this.retryCount = 0;
|
|
146
138
|
this.connected = true;
|
|
147
|
-
this.
|
|
139
|
+
this.socketId = this.socket?.id || null;
|
|
140
|
+
this.logger('info', 'WebSocket соединение установлено', { socketId: this.socketId });
|
|
148
141
|
// Отправляем сообщения из очереди
|
|
149
142
|
while (this.messageQueue.length > 0) {
|
|
150
143
|
const message = this.messageQueue.shift();
|
|
151
|
-
if (message && this.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
// Запускаем таймер для ping в Socket.IO
|
|
156
|
-
this.setupPingPongTimer();
|
|
157
|
-
resolve();
|
|
158
|
-
this.dispatchEvent('open', {});
|
|
159
|
-
};
|
|
160
|
-
// Обработчик сообщений
|
|
161
|
-
this.webSocket.onmessage = (event) => {
|
|
162
|
-
try {
|
|
163
|
-
// Пытаемся распарсить сообщение как JSON
|
|
164
|
-
let data = event.data;
|
|
165
|
-
// Обработка сообщений Socket.IO
|
|
166
|
-
if (typeof data === 'string') {
|
|
167
|
-
// Обработка Engine.IO - Ping/Pong (2/3)
|
|
168
|
-
if (data === '2') {
|
|
169
|
-
// Engine.IO ping, отправляем понг
|
|
170
|
-
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
|
|
171
|
-
this.webSocket.send('3');
|
|
172
|
-
}
|
|
173
|
-
this.dispatchEvent('ping', {});
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
else if (data === '3') {
|
|
177
|
-
// Engine.IO pong
|
|
178
|
-
this.dispatchEvent('pong', {});
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
// Обработка открытия соединения Socket.IO
|
|
182
|
-
if (data.startsWith('0')) {
|
|
183
|
-
try {
|
|
184
|
-
const authData = JSON.parse(data.substring(1));
|
|
185
|
-
this.dispatchEvent('socket.io_open', authData);
|
|
186
|
-
// Вызываем обработчик открытия соединения Socket.IO
|
|
187
|
-
this.handleSocketIOOpen(authData);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
catch (e) {
|
|
191
|
-
// Игнорируем ошибки парсинга
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Обработка Socket.IO событий (42)
|
|
195
|
-
if (data.startsWith('42')) {
|
|
196
|
-
// Формат: 42/namespace,[event,data]
|
|
197
|
-
let namespace = '';
|
|
198
|
-
let eventData = data.substring(2);
|
|
199
|
-
// Извлекаем namespace, если есть
|
|
200
|
-
if (eventData.startsWith('/')) {
|
|
201
|
-
const namespaceEnd = eventData.indexOf(',');
|
|
202
|
-
if (namespaceEnd !== -1) {
|
|
203
|
-
namespace = eventData.substring(0, namespaceEnd);
|
|
204
|
-
eventData = eventData.substring(namespaceEnd + 1);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
try {
|
|
208
|
-
const parsedData = JSON.parse(eventData);
|
|
209
|
-
if (Array.isArray(parsedData) && parsedData.length >= 1) {
|
|
210
|
-
const event = parsedData[0];
|
|
211
|
-
const eventPayload = parsedData.length > 1 ? parsedData[1] : null;
|
|
212
|
-
// Запускаем обработчик для данного события
|
|
213
|
-
this.dispatchEvent(event, eventPayload);
|
|
214
|
-
// Также отправляем общее событие socket.io с информацией о полученном событии
|
|
215
|
-
this.dispatchEvent('socket.io_event', {
|
|
216
|
-
event,
|
|
217
|
-
data: eventPayload,
|
|
218
|
-
namespace
|
|
219
|
-
});
|
|
220
|
-
// Обработка специальных событий для стриминга
|
|
221
|
-
if (event === 'text_delta' && eventPayload?.delta?.text) {
|
|
222
|
-
this.dispatchEvent('streaming_delta', { type: 'text', content: eventPayload.delta.text });
|
|
223
|
-
}
|
|
224
|
-
else if (event === 'thinking_delta' && eventPayload?.delta?.thinking) {
|
|
225
|
-
this.dispatchEvent('streaming_delta', { type: 'thinking', content: eventPayload.delta.thinking });
|
|
226
|
-
}
|
|
227
|
-
else if (event === 'message_stop') {
|
|
228
|
-
this.dispatchEvent('streaming_complete', {});
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
catch (e) {
|
|
233
|
-
this.dispatchEvent('error', { message: `Ошибка парсинга Socket.IO данных: ${e instanceof Error ? e.message : 'Неизвестная ошибка'}` });
|
|
234
|
-
}
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
// Определяем тип пакета Socket.IO по первому символу
|
|
238
|
-
const packetType = data.charAt(0);
|
|
239
|
-
// Если это пакет данных (тип 0-6)
|
|
240
|
-
if (packetType >= '0' && packetType <= '6') {
|
|
241
|
-
// Отправляем особое событие для Socket.IO пакетов
|
|
242
|
-
this.dispatchEvent('socket.io_raw', {
|
|
243
|
-
type: packetType,
|
|
244
|
-
data: data.substring(1)
|
|
245
|
-
});
|
|
246
|
-
// Пытаемся обработать содержимое
|
|
247
|
-
if (data.length > 1) {
|
|
248
|
-
try {
|
|
249
|
-
// Обработка пакета с namespace
|
|
250
|
-
let jsonStart = 1;
|
|
251
|
-
let namespace = '';
|
|
252
|
-
// Если есть namespace, извлекаем его
|
|
253
|
-
if (data.charAt(1) === '/') {
|
|
254
|
-
const namespaceEnd = data.indexOf('{', 1);
|
|
255
|
-
if (namespaceEnd !== -1) {
|
|
256
|
-
namespace = data.substring(1, namespaceEnd);
|
|
257
|
-
jsonStart = namespaceEnd;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
// Пытаемся преобразовать оставшуюся часть в JSON
|
|
261
|
-
const jsonStr = data.substring(jsonStart);
|
|
262
|
-
if (jsonStr) {
|
|
263
|
-
try {
|
|
264
|
-
const parsedData = JSON.parse(jsonStr);
|
|
265
|
-
this.dispatchEvent('message', parsedData);
|
|
266
|
-
}
|
|
267
|
-
catch (e) {
|
|
268
|
-
// Если не можем преобразовать в JSON, отправляем как есть
|
|
269
|
-
this.dispatchEvent('message', jsonStr);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
catch (e) {
|
|
274
|
-
// Игнорируем ошибки парсинга
|
|
275
|
-
}
|
|
276
|
-
}
|
|
144
|
+
if (message && this.socket && this.socket.connected) {
|
|
145
|
+
if (typeof message === 'object' && message.event) {
|
|
146
|
+
this.socket.emit(message.event, message.data);
|
|
277
147
|
}
|
|
278
148
|
else {
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
const parsedData = JSON.parse(data);
|
|
282
|
-
this.dispatchEvent('message', parsedData);
|
|
283
|
-
}
|
|
284
|
-
catch (e) {
|
|
285
|
-
// Если не можем преобразовать в JSON, отправляем как есть
|
|
286
|
-
this.dispatchEvent('message', data);
|
|
287
|
-
}
|
|
149
|
+
// Поддержка старого формата сообщений
|
|
150
|
+
this.socket.send(message);
|
|
288
151
|
}
|
|
289
152
|
}
|
|
290
|
-
else {
|
|
291
|
-
// Если данные не строка (например, ArrayBuffer), отправляем как есть
|
|
292
|
-
this.dispatchEvent('message', data);
|
|
293
|
-
}
|
|
294
153
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
154
|
+
resolve();
|
|
155
|
+
this.dispatchEvent('open', {});
|
|
156
|
+
});
|
|
157
|
+
// Обработчик ошибок соединения
|
|
158
|
+
this.socket.on('connect_error', (error) => {
|
|
159
|
+
clearTimeout(this.connectionTimeoutTimer);
|
|
160
|
+
this.logger('error', 'Ошибка соединения WebSocket', {
|
|
161
|
+
message: error.message,
|
|
162
|
+
name: error.name,
|
|
163
|
+
stack: error.stack
|
|
164
|
+
});
|
|
165
|
+
this.dispatchEvent('error', error);
|
|
166
|
+
if (!this.connected) {
|
|
167
|
+
reject(new Error('Ошибка подключения WebSocket'));
|
|
298
168
|
}
|
|
299
|
-
};
|
|
169
|
+
});
|
|
300
170
|
// Обработчик закрытия соединения
|
|
301
|
-
this.
|
|
171
|
+
this.socket.on('disconnect', (reason) => {
|
|
302
172
|
clearTimeout(this.connectionTimeoutTimer);
|
|
303
173
|
this.connected = false;
|
|
304
|
-
this.
|
|
174
|
+
this.logger('info', `WebSocket соединение закрыто: ${reason}`);
|
|
175
|
+
// Формируем объект события для совместимости с WebSocket API
|
|
176
|
+
const closeEvent = {
|
|
177
|
+
code: this.getCloseCodeFromReason(reason),
|
|
178
|
+
reason: reason
|
|
179
|
+
};
|
|
180
|
+
this.dispatchEvent('close', closeEvent);
|
|
305
181
|
// Если соединение было закрыто намеренно, не пытаемся переподключиться
|
|
306
182
|
if (this.intentionallyClosed) {
|
|
307
183
|
return;
|
|
308
184
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
reject(new Error('Ошибка подключения WebSocket'));
|
|
321
|
-
}
|
|
322
|
-
};
|
|
185
|
+
});
|
|
186
|
+
// Обработчик всех сообщений, используем 'message' для совместимости
|
|
187
|
+
this.socket.onAny((eventName, ...args) => {
|
|
188
|
+
// Отправляем в обработчик события по имени события
|
|
189
|
+
this.dispatchEvent(eventName, args.length === 1 ? args[0] : args);
|
|
190
|
+
// Также отправляем событие message для совместимости
|
|
191
|
+
this.dispatchEvent('message', {
|
|
192
|
+
event: eventName,
|
|
193
|
+
data: args.length === 1 ? args[0] : args
|
|
194
|
+
});
|
|
195
|
+
});
|
|
323
196
|
}
|
|
324
197
|
catch (error) {
|
|
198
|
+
clearTimeout(this.connectionTimeoutTimer);
|
|
199
|
+
this.logger('error', 'Ошибка при создании Socket.IO клиента', error);
|
|
325
200
|
reject(error);
|
|
326
201
|
}
|
|
327
202
|
});
|
|
328
203
|
}
|
|
329
204
|
/**
|
|
330
|
-
*
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
// Если URL содержит параметры Socket.IO, настраиваем автоматический ping/pong
|
|
334
|
-
if (this.url.includes('EIO=4') && this.url.includes('transport=websocket')) {
|
|
335
|
-
// Типичный интервал ping для Socket.IO - 25 секунд
|
|
336
|
-
const pingInterval = 25000;
|
|
337
|
-
// Периодически отправляем ping
|
|
338
|
-
const pingTimer = setInterval(() => {
|
|
339
|
-
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
|
|
340
|
-
// Socket.IO ping - это просто строка "2"
|
|
341
|
-
this.webSocket.send('2');
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
// Если соединение закрыто, останавливаем таймер
|
|
345
|
-
clearInterval(pingTimer);
|
|
346
|
-
}
|
|
347
|
-
}, pingInterval);
|
|
348
|
-
// Останавливаем таймер при закрытии соединения
|
|
349
|
-
this.on('close', () => clearInterval(pingTimer));
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Переподключается к WebSocket серверу
|
|
205
|
+
* Получает код закрытия WebSocket из строки причины Socket.IO
|
|
206
|
+
* @param {string} reason Причина закрытия Socket.IO
|
|
207
|
+
* @returns {number} Код закрытия WebSocket
|
|
354
208
|
* @private
|
|
355
209
|
*/
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
210
|
+
getCloseCodeFromReason(reason) {
|
|
211
|
+
switch (reason) {
|
|
212
|
+
case 'io server disconnect':
|
|
213
|
+
return 1000; // Нормальное закрытие соединения сервером
|
|
214
|
+
case 'io client disconnect':
|
|
215
|
+
return 1000; // Нормальное закрытие соединения клиентом
|
|
216
|
+
case 'ping timeout':
|
|
217
|
+
return 1001; // Выход из соединения по таймауту
|
|
218
|
+
case 'transport close':
|
|
219
|
+
return 1006; // Аномальное закрытие соединения
|
|
220
|
+
case 'transport error':
|
|
221
|
+
return 1002; // Протокольная ошибка
|
|
222
|
+
default:
|
|
223
|
+
return 1000; // По умолчанию - нормальное закрытие
|
|
363
224
|
}
|
|
364
|
-
// Вычисляем задержку перед переподключением с экспоненциальным ростом
|
|
365
|
-
const delay = Math.min((this.options.retryDelay || 1000) * Math.pow(2, this.retryCount - 1), this.options.maxRetryDelay || 30000);
|
|
366
|
-
// Пытаемся переподключиться после задержки
|
|
367
|
-
this.reconnectTimer = setTimeout(() => {
|
|
368
|
-
this.dispatchEvent('reconnect', { attempt: this.retryCount });
|
|
369
|
-
this.connect().catch(() => { });
|
|
370
|
-
}, delay);
|
|
371
225
|
}
|
|
372
226
|
/**
|
|
373
|
-
* Закрывает WebSocket
|
|
227
|
+
* Закрывает соединение WebSocket
|
|
374
228
|
* @param {number} [code=1000] Код закрытия
|
|
375
|
-
* @param {string} [reason] Причина закрытия
|
|
229
|
+
* @param {string} [reason='Closed by client'] Причина закрытия
|
|
376
230
|
*/
|
|
377
|
-
close(code = 1000, reason) {
|
|
231
|
+
close(code = 1000, reason = 'Closed by client') {
|
|
378
232
|
this.intentionallyClosed = true;
|
|
379
|
-
// Очищаем таймеры
|
|
380
233
|
clearTimeout(this.reconnectTimer);
|
|
381
234
|
clearTimeout(this.connectionTimeoutTimer);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
this.webSocket.close(code, reason);
|
|
388
|
-
}
|
|
389
|
-
this.webSocket = null;
|
|
235
|
+
if (this.socket) {
|
|
236
|
+
this.logger('info', 'Закрытие WebSocket соединения', { code, reason });
|
|
237
|
+
// Отключаем Socket.IO клиент
|
|
238
|
+
this.socket.disconnect();
|
|
239
|
+
this.socket = null;
|
|
390
240
|
}
|
|
391
241
|
this.connected = false;
|
|
392
242
|
}
|
|
393
243
|
/**
|
|
394
|
-
* Отправляет сообщение
|
|
395
|
-
* @param {
|
|
244
|
+
* Отправляет сообщение в WebSocket
|
|
245
|
+
* @param {any} data Данные для отправки
|
|
396
246
|
* @returns {boolean} Успешно ли отправлено сообщение
|
|
397
247
|
*/
|
|
398
|
-
send(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
// Формат сообщения для Socket.IO протокола 4
|
|
404
|
-
if (socketIOMessage.event && socketIOMessage.data) {
|
|
405
|
-
// Format: 42namespace,[event,data]
|
|
406
|
-
const namespace = socketIOMessage.nsp ? socketIOMessage.nsp : '';
|
|
407
|
-
const namespaceStr = namespace && !namespace.startsWith('/') ? '/' + namespace : namespace;
|
|
408
|
-
const eventData = [socketIOMessage.event];
|
|
409
|
-
if (socketIOMessage.data !== undefined) {
|
|
410
|
-
eventData.push(socketIOMessage.data);
|
|
411
|
-
}
|
|
412
|
-
// 4 - Engine.IO message type
|
|
413
|
-
// 2 - Socket.IO EVENT type
|
|
414
|
-
packet = `42${namespaceStr}${namespaceStr ? ',' : ''}${JSON.stringify(eventData)}`;
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
// Обратная совместимость со старым форматом
|
|
418
|
-
packet = `${socketIOMessage.type}${socketIOMessage.nsp ? socketIOMessage.nsp : ''}${JSON.stringify(socketIOMessage.data)}`;
|
|
419
|
-
}
|
|
420
|
-
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
|
|
421
|
-
this.webSocket.send(packet);
|
|
248
|
+
send(data) {
|
|
249
|
+
try {
|
|
250
|
+
// Если соединение еще не установлено, добавляем сообщение в очередь
|
|
251
|
+
if (!this.socket || !this.socket.connected) {
|
|
252
|
+
this.messageQueue.push(data);
|
|
422
253
|
return true;
|
|
423
254
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
this.
|
|
255
|
+
// Обработка разных типов сообщений для совместимости
|
|
256
|
+
if (typeof data === 'object') {
|
|
257
|
+
if (data.event) {
|
|
258
|
+
// Формат { event: 'event_name', data: {} }
|
|
259
|
+
this.socket.emit(data.event, data.data);
|
|
260
|
+
}
|
|
261
|
+
else if (data.type && data.type === '2' && data.data && Array.isArray(data.data)) {
|
|
262
|
+
// Socket.IO тип пакета '2' - событие с данными
|
|
263
|
+
// Формат { type: '2', nsp: '/namespace', data: ['event_name', {}] }
|
|
264
|
+
const eventName = data.data[0];
|
|
265
|
+
const eventData = data.data.length > 1 ? data.data[1] : null;
|
|
266
|
+
this.socket.emit(eventName, eventData);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
// Обычные объекты отправляем через 'message'
|
|
270
|
+
this.socket.emit('message', data);
|
|
429
271
|
}
|
|
430
|
-
return false;
|
|
431
272
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
this.webSocket.send(data);
|
|
273
|
+
else {
|
|
274
|
+
// Строки, бинарные данные и т.д.
|
|
275
|
+
this.socket.send(data);
|
|
276
|
+
}
|
|
437
277
|
return true;
|
|
438
278
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
this.messageQueue.push(data);
|
|
442
|
-
// Если соединение не установлено и не закрыто намеренно, пытаемся подключиться
|
|
443
|
-
if ((!this.webSocket || this.webSocket.readyState === WebSocket.CLOSED) && !this.intentionallyClosed) {
|
|
444
|
-
this.connect().catch(() => { });
|
|
445
|
-
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
this.logger('error', 'Ошибка при отправке сообщения', error);
|
|
446
281
|
return false;
|
|
447
282
|
}
|
|
448
283
|
}
|
|
449
284
|
/**
|
|
450
|
-
*
|
|
451
|
-
* @
|
|
285
|
+
* Отправляет событие с данными и ожидает ответа с помощью Promise
|
|
286
|
+
* @param {string} event Название события
|
|
287
|
+
* @param {any} data Данные события
|
|
288
|
+
* @param {number} [timeout=5000] Таймаут ожидания ответа в мс
|
|
289
|
+
* @returns {Promise<any>} Promise с ответом
|
|
452
290
|
*/
|
|
453
|
-
|
|
454
|
-
return
|
|
291
|
+
emitWithAck(event, data, timeout = 5000) {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
if (!this.socket || !this.socket.connected) {
|
|
294
|
+
reject(new Error('WebSocket не подключен'));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
// Используем встроенный механизм acknowledgements в Socket.IO
|
|
299
|
+
this.socket.timeout(timeout).emit(event, data, (err, response) => {
|
|
300
|
+
if (err) {
|
|
301
|
+
reject(err);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
resolve(response);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
reject(error);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
455
312
|
}
|
|
456
313
|
/**
|
|
457
314
|
* Добавляет обработчик события
|
|
@@ -463,6 +320,12 @@ export class WebSocketClient {
|
|
|
463
320
|
this.eventHandlers[eventType] = [];
|
|
464
321
|
}
|
|
465
322
|
this.eventHandlers[eventType].push(handler);
|
|
323
|
+
// Если соединение уже установлено, добавляем обработчик для Socket.IO
|
|
324
|
+
if (this.socket && this.socket.connected && eventType !== 'open' && eventType !== 'close') {
|
|
325
|
+
// Не добавляем обработчики для 'open' и 'close', так как они обрабатываются
|
|
326
|
+
// через 'connect' и 'disconnect' в методе connect()
|
|
327
|
+
this.socket.on(eventType, handler);
|
|
328
|
+
}
|
|
466
329
|
}
|
|
467
330
|
/**
|
|
468
331
|
* Удаляет обработчик события
|
|
@@ -473,23 +336,31 @@ export class WebSocketClient {
|
|
|
473
336
|
if (!this.eventHandlers[eventType]) {
|
|
474
337
|
return;
|
|
475
338
|
}
|
|
476
|
-
if (
|
|
477
|
-
//
|
|
478
|
-
|
|
339
|
+
if (handler) {
|
|
340
|
+
// Удаляем конкретный обработчик
|
|
341
|
+
const index = this.eventHandlers[eventType].indexOf(handler);
|
|
342
|
+
if (index !== -1) {
|
|
343
|
+
this.eventHandlers[eventType].splice(index, 1);
|
|
344
|
+
}
|
|
345
|
+
// Также удаляем обработчик из Socket.IO, если соединение установлено
|
|
346
|
+
if (this.socket && this.socket.connected) {
|
|
347
|
+
this.socket.off(eventType, handler);
|
|
348
|
+
}
|
|
479
349
|
}
|
|
480
350
|
else {
|
|
481
|
-
//
|
|
482
|
-
|
|
483
|
-
//
|
|
484
|
-
if (this.
|
|
485
|
-
|
|
351
|
+
// Удаляем все обработчики для данного типа события
|
|
352
|
+
delete this.eventHandlers[eventType];
|
|
353
|
+
// Также удаляем все обработчики из Socket.IO, если соединение установлено
|
|
354
|
+
if (this.socket && this.socket.connected) {
|
|
355
|
+
this.socket.off(eventType);
|
|
486
356
|
}
|
|
487
357
|
}
|
|
488
358
|
}
|
|
489
359
|
/**
|
|
490
|
-
*
|
|
360
|
+
* Отправляет событие в обработчики
|
|
491
361
|
* @param {string} eventType Тип события
|
|
492
362
|
* @param {any} data Данные события
|
|
363
|
+
* @private
|
|
493
364
|
*/
|
|
494
365
|
dispatchEvent(eventType, data) {
|
|
495
366
|
if (!this.eventHandlers[eventType]) {
|
|
@@ -499,250 +370,54 @@ export class WebSocketClient {
|
|
|
499
370
|
try {
|
|
500
371
|
handler(data);
|
|
501
372
|
}
|
|
502
|
-
catch (e) {
|
|
503
|
-
console.error(`Ошибка в обработчике события ${eventType}:`, e);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* Обработка полученного сообщения от сервера
|
|
509
|
-
* @param message Сообщение от сервера
|
|
510
|
-
*/
|
|
511
|
-
handleMessage(message) {
|
|
512
|
-
try {
|
|
513
|
-
if (!message) {
|
|
514
|
-
this.dispatchEvent('error', { message: 'Получено пустое сообщение' });
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
// Проверяем, является ли сообщение пингом Socket.IO (2)
|
|
518
|
-
if (message === '2') {
|
|
519
|
-
// Отправляем понг (3)
|
|
520
|
-
this.send('3');
|
|
521
|
-
this.dispatchEvent('debug', 'Получен ping, отправлен pong');
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
// Если это сообщение Socket.IO с данными
|
|
525
|
-
if (message.startsWith('42')) {
|
|
526
|
-
// Формат: 42[event,data]
|
|
527
|
-
const eventDataStr = message.substring(2);
|
|
528
|
-
try {
|
|
529
|
-
const [event, data] = JSON.parse(eventDataStr);
|
|
530
|
-
// Проверяем, есть ли это ack-ответ для callback
|
|
531
|
-
if (event.endsWith('_ack') || event.endsWith('_response') ||
|
|
532
|
-
event.endsWith('_success') || event.endsWith('_error')) {
|
|
533
|
-
const baseEvent = event.split('_')[0]; // Получаем базовое имя события
|
|
534
|
-
// Проверяем, есть ли у нас отложенный callback для этого события
|
|
535
|
-
const callbackEntries = Array.from(this._pendingCallbacks.entries())
|
|
536
|
-
.filter(([key]) => key.startsWith(`${baseEvent}_ack_`));
|
|
537
|
-
if (callbackEntries.length > 0) {
|
|
538
|
-
const [callbackId, callback] = callbackEntries[0];
|
|
539
|
-
this._pendingCallbacks.delete(callbackId);
|
|
540
|
-
// Вызываем callback с полученными данными
|
|
541
|
-
try {
|
|
542
|
-
callback(data);
|
|
543
|
-
}
|
|
544
|
-
catch (callbackError) {
|
|
545
|
-
this.logger('error', `Ошибка при вызове callback для ${baseEvent}: ${callbackError instanceof Error ? callbackError.message : String(callbackError)}`);
|
|
546
|
-
}
|
|
547
|
-
// Не продолжаем обработку, так как это ack-сообщение уже обработано
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
// Вызываем соответствующий обработчик события
|
|
552
|
-
this.dispatchEvent(event, data);
|
|
553
|
-
// Добавляем специальную обработку для событий стриминга
|
|
554
|
-
if (event === 'text_delta' && data.delta?.text) {
|
|
555
|
-
this.dispatchEvent('streaming_delta', { type: 'text', content: data.delta.text });
|
|
556
|
-
}
|
|
557
|
-
else if (event === 'thinking_delta' && data.delta?.thinking) {
|
|
558
|
-
this.dispatchEvent('streaming_delta', { type: 'thinking', content: data.delta.thinking });
|
|
559
|
-
}
|
|
560
|
-
else if (event === 'message_stop') {
|
|
561
|
-
this.dispatchEvent('streaming_complete', {});
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
catch (error) {
|
|
565
|
-
const errorMessage = error instanceof Error ? error.message : 'Неизвестная ошибка';
|
|
566
|
-
this.dispatchEvent('error', { message: `Ошибка разбора данных события: ${errorMessage}` });
|
|
567
|
-
}
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
// Пытаемся преобразовать сообщение в JSON
|
|
571
|
-
try {
|
|
572
|
-
const parsedMessage = JSON.parse(message);
|
|
573
|
-
// Если это объект с типом event и данными, это может быть событие
|
|
574
|
-
if (parsedMessage.event && parsedMessage.data) {
|
|
575
|
-
const { event, data } = parsedMessage;
|
|
576
|
-
this.dispatchEvent(event, data);
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
this.dispatchEvent('message', parsedMessage);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
373
|
catch (error) {
|
|
583
|
-
|
|
584
|
-
this.dispatchEvent('message', message);
|
|
374
|
+
this.logger('error', `Ошибка в обработчике события '${eventType}'`, error);
|
|
585
375
|
}
|
|
586
376
|
}
|
|
587
|
-
catch (error) {
|
|
588
|
-
const errorMessage = error instanceof Error ? error.message : 'Неизвестная ошибка';
|
|
589
|
-
this.dispatchEvent('error', { message: `Ошибка при обработке сообщения: ${errorMessage}` });
|
|
590
|
-
}
|
|
591
377
|
}
|
|
592
378
|
/**
|
|
593
|
-
*
|
|
594
|
-
* @
|
|
595
|
-
* @param {any} data Данные события
|
|
596
|
-
* @param {(response: any) => void} [callback] Функция обратного вызова для получения ответа
|
|
597
|
-
* @param {string} [namespace=''] Namespace для Socket.IO
|
|
598
|
-
* @returns {boolean} Успешно ли отправлено сообщение
|
|
379
|
+
* Возвращает текущий статус соединения
|
|
380
|
+
* @returns {boolean} Подключен ли клиент
|
|
599
381
|
*/
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const hasCallback = typeof callback === 'function';
|
|
603
|
-
this.logger('debug', `Отправка Socket.IO события ${event}`, {
|
|
604
|
-
hasData: !!data,
|
|
605
|
-
namespace,
|
|
606
|
-
hasCallback
|
|
607
|
-
});
|
|
608
|
-
// Формируем объект для отправки через метод send в стандартном формате Socket.IO
|
|
609
|
-
if (hasCallback) {
|
|
610
|
-
// Socket.IO ожидает callback как последний аргумент emit, поэтому
|
|
611
|
-
// мы должны отправить особое сообщение, указывающее, что нужно использовать callback
|
|
612
|
-
// Это важно: не добавляйте callback в data!
|
|
613
|
-
try {
|
|
614
|
-
// Для Socket.IO клиента (веб-браузер)
|
|
615
|
-
if (this.isBrowser && this.webSocket) {
|
|
616
|
-
// @ts-ignore - вызываем нативный метод emit у Socket.IO, если он доступен
|
|
617
|
-
if (this.webSocket.emit) {
|
|
618
|
-
const namespacedEvent = namespace ? `${namespace}#${event}` : event;
|
|
619
|
-
// @ts-ignore
|
|
620
|
-
return this.webSocket.emit(namespacedEvent, data, callback);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
// Формат данных для Socket.IO: [event, data, ack] в формате пакета типа 2 (EVENT)
|
|
624
|
-
const socketIOPacket = {
|
|
625
|
-
type: '2', // Socket.IO EVENT
|
|
626
|
-
event,
|
|
627
|
-
data,
|
|
628
|
-
namespace,
|
|
629
|
-
useCallback: true // Специальный флаг для внутренней обработки
|
|
630
|
-
};
|
|
631
|
-
// Сохраняем callback для последующего использования
|
|
632
|
-
const callbackId = `${event}_ack_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
633
|
-
this._pendingCallbacks.set(callbackId, callback);
|
|
634
|
-
// Устанавливаем таймаут для автоматического удаления callback
|
|
635
|
-
setTimeout(() => {
|
|
636
|
-
if (this._pendingCallbacks.has(callbackId)) {
|
|
637
|
-
const cb = this._pendingCallbacks.get(callbackId);
|
|
638
|
-
this._pendingCallbacks.delete(callbackId);
|
|
639
|
-
if (typeof cb === 'function') {
|
|
640
|
-
cb({
|
|
641
|
-
success: false,
|
|
642
|
-
error: `Таймаут ожидания ответа на событие ${event}`,
|
|
643
|
-
timeout: true
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}, 10000); // 10-секундный таймаут
|
|
648
|
-
// Отправляем объект с данными и callbackId
|
|
649
|
-
return this.send({
|
|
650
|
-
...socketIOPacket,
|
|
651
|
-
callbackId
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
catch (error) {
|
|
655
|
-
this.logger('error', `Ошибка при отправке Socket.IO события ${event} с callback: ${error instanceof Error ? error.message : String(error)}`);
|
|
656
|
-
return false;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
else {
|
|
660
|
-
// Стандартная отправка без callback
|
|
661
|
-
const socketIOMessage = {
|
|
662
|
-
type: '2',
|
|
663
|
-
event,
|
|
664
|
-
data,
|
|
665
|
-
nsp: namespace
|
|
666
|
-
};
|
|
667
|
-
return this.send(socketIOMessage);
|
|
668
|
-
}
|
|
382
|
+
isConnected() {
|
|
383
|
+
return this.socket !== null && this.socket.connected;
|
|
669
384
|
}
|
|
670
385
|
/**
|
|
671
|
-
*
|
|
386
|
+
* Выполняет принудительное переподключение
|
|
387
|
+
* @returns {Promise<void>} Promise без результата
|
|
672
388
|
*/
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
// Отправляем запрос на подключение к namespace
|
|
678
|
-
this.logger('info', `Подключение к пространству имен ${this.namespace}`);
|
|
679
|
-
const connectMessage = `40${this.namespace}`;
|
|
680
|
-
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
|
|
681
|
-
this.webSocket.send(connectMessage);
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
684
|
-
this.messageQueue.push(connectMessage);
|
|
389
|
+
async reconnect() {
|
|
390
|
+
// Если соединение уже установлено, сначала закрываем его
|
|
391
|
+
if (this.socket && this.socket.connected) {
|
|
392
|
+
this.close();
|
|
685
393
|
}
|
|
394
|
+
// Сбрасываем флаг намеренного закрытия для возможности переподключения
|
|
395
|
+
this.intentionallyClosed = false;
|
|
396
|
+
// Устанавливаем новое соединение
|
|
397
|
+
return this.connect();
|
|
686
398
|
}
|
|
687
399
|
/**
|
|
688
|
-
*
|
|
400
|
+
* Отправляет событие (алиас для более удобного использования)
|
|
401
|
+
* @param {string} eventName Название события
|
|
402
|
+
* @param {any} data Данные события
|
|
403
|
+
* @returns {boolean} Успешно ли отправлено событие
|
|
689
404
|
*/
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
this.logger('warn', 'API ключ не предоставлен, аутентификация пропущена');
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
const apiKeySafe = this.options.apiKey.length > 8
|
|
696
|
-
? `${this.options.apiKey.substring(0, 4)}...${this.options.apiKey.substring(this.options.apiKey.length - 4)}`
|
|
697
|
-
: '[короткий ключ]';
|
|
698
|
-
this.logger('info', 'Отправка аутентификационного сообщения', {
|
|
699
|
-
namespace: this.namespace,
|
|
700
|
-
apiKey: apiKeySafe,
|
|
701
|
-
authenticated: this.authenticated,
|
|
702
|
-
socketState: this.webSocket ? this.webSocket.readyState : 'нет соединения'
|
|
703
|
-
});
|
|
704
|
-
// Отправляем событие authenticate с API ключом
|
|
705
|
-
this.sendSocketIOEvent('authenticate', { apiKey: this.options.apiKey }, undefined, this.namespace);
|
|
405
|
+
emit(eventName, data) {
|
|
406
|
+
return this.send({ event: eventName, data });
|
|
706
407
|
}
|
|
707
408
|
/**
|
|
708
|
-
*
|
|
709
|
-
* @
|
|
409
|
+
* Возвращает ID сокета, если соединение установлено
|
|
410
|
+
* @returns {string|null} ID сокета или null, если соединение не установлено
|
|
710
411
|
*/
|
|
711
|
-
|
|
712
|
-
this.
|
|
713
|
-
pingInterval: data.pingInterval,
|
|
714
|
-
pingTimeout: data.pingTimeout,
|
|
715
|
-
sid: data.sid
|
|
716
|
-
});
|
|
717
|
-
// Если указано пространство имен, подключаемся к нему
|
|
718
|
-
if (this.namespace) {
|
|
719
|
-
this.handleNamespaceConnection();
|
|
720
|
-
}
|
|
721
|
-
// Добавляем обработчик успешного подключения к пространству имен
|
|
722
|
-
this.on('socket.io_event', (eventData) => {
|
|
723
|
-
// Если это событие connect и мы еще не аутентифицированы
|
|
724
|
-
if (!this.authenticated && eventData.event === 'connect') {
|
|
725
|
-
this.logger('info', `Подключено к пространству имен ${eventData.namespace || '/'}`, {
|
|
726
|
-
namespace: eventData.namespace || '/',
|
|
727
|
-
event: eventData.event,
|
|
728
|
-
data: eventData.data
|
|
729
|
-
});
|
|
730
|
-
// Аутентифицируем соединение
|
|
731
|
-
this.authenticate();
|
|
732
|
-
this.authenticated = true;
|
|
733
|
-
}
|
|
734
|
-
});
|
|
735
|
-
// Сохраняем ID сокета, если он пришел
|
|
736
|
-
if (data && data.sid) {
|
|
737
|
-
this.socketId = data.sid;
|
|
738
|
-
}
|
|
412
|
+
getSocketId() {
|
|
413
|
+
return this.socket?.id || null;
|
|
739
414
|
}
|
|
740
415
|
/**
|
|
741
|
-
*
|
|
742
|
-
* @
|
|
416
|
+
* Устанавливает функцию логирования
|
|
417
|
+
* @param {Function} loggerFn Функция для логирования
|
|
743
418
|
*/
|
|
744
|
-
|
|
745
|
-
|
|
419
|
+
setLogger(loggerFn) {
|
|
420
|
+
this.logger = loggerFn;
|
|
746
421
|
}
|
|
747
422
|
/**
|
|
748
423
|
* Регистрирует обработчик события, который будет вызван один раз и удален
|
|
@@ -751,6 +426,11 @@ export class WebSocketClient {
|
|
|
751
426
|
* @returns {void}
|
|
752
427
|
*/
|
|
753
428
|
once(event, handler) {
|
|
429
|
+
// Если есть нативная реализация в Socket.IO, используем её
|
|
430
|
+
if (this.socket && this.socket.connected) {
|
|
431
|
+
this.socket.once(event, handler);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
754
434
|
// Создаем обертку, которая удалит обработчик после первого вызова
|
|
755
435
|
const wrapperHandler = (data) => {
|
|
756
436
|
// Удаляем обработчик
|
|
@@ -761,5 +441,45 @@ export class WebSocketClient {
|
|
|
761
441
|
// Регистрируем обертку
|
|
762
442
|
this.on(event, wrapperHandler);
|
|
763
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Отправляет событие Socket.IO через WebSocket соединение
|
|
446
|
+
* @param {string} event Имя события
|
|
447
|
+
* @param {any} data Данные события
|
|
448
|
+
* @param {(response: any) => void} [callback] Функция обратного вызова для получения ответа
|
|
449
|
+
* @param {string} [namespace=''] Namespace для Socket.IO
|
|
450
|
+
* @returns {boolean} Успешно ли отправлено сообщение
|
|
451
|
+
*/
|
|
452
|
+
sendSocketIOEvent(event, data, callback, namespace = '') {
|
|
453
|
+
// Если нет соединения, сразу возвращаем false
|
|
454
|
+
if (!this.socket || !this.socket.connected) {
|
|
455
|
+
this.logger('error', 'Нельзя отправить событие: WebSocket не подключен');
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
// Проверяем, нужно ли использовать другой namespace
|
|
460
|
+
let targetSocket = this.socket;
|
|
461
|
+
// Если указан другой namespace, используем его
|
|
462
|
+
if (namespace && namespace !== this.namespace) {
|
|
463
|
+
const nsSocket = io(this.url + namespace, {
|
|
464
|
+
forceNew: false,
|
|
465
|
+
auth: { token: this.options.apiKey }
|
|
466
|
+
});
|
|
467
|
+
targetSocket = nsSocket;
|
|
468
|
+
}
|
|
469
|
+
// Отправляем событие с callback, если он указан
|
|
470
|
+
if (callback) {
|
|
471
|
+
targetSocket.emit(event, data, callback);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
targetSocket.emit(event, data);
|
|
475
|
+
}
|
|
476
|
+
this.logger('debug', `Отправлено Socket.IO событие ${event}`, { hasData: !!data, namespace });
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
this.logger('error', `Ошибка при отправке Socket.IO события ${event}`, error);
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
764
484
|
}
|
|
765
485
|
//# sourceMappingURL=websocket-client.js.map
|