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