@pushler/js 1.0.3 → 1.2.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.
@@ -1,5 +1,5 @@
1
1
  import Channel from './Channel';
2
- import { ChannelTypes, ConnectionStates, Events, ErrorCodes } from './constants';
2
+ import { ChannelTypes, ConnectionStates, SubscriptionStates, Events, ErrorCodes } from './constants';
3
3
 
4
4
  /**
5
5
  * Основной класс для подключения к Pushler.ru WebSocket серверу
@@ -11,6 +11,8 @@ class PushlerClient {
11
11
  this.wsUrl = options.wsUrl || `wss://ws.pushler.ru/app/${this.appKey}`;
12
12
  this.apiUrl = options.apiUrl;
13
13
  this.authEndpoint = options.authEndpoint || '/pushler/auth'; // Путь для авторизации каналов
14
+ this.authHeaders = options.authHeaders || null; // Кастомные заголовки для авторизации
15
+ this.getAuthHeaders = options.getAuthHeaders || null; // Функция для динамических заголовков
14
16
  this.autoConnect = options.autoConnect !== false;
15
17
  this.reconnectDelay = options.reconnectDelay || 1000;
16
18
  this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
@@ -21,10 +23,16 @@ class PushlerClient {
21
23
  this.channels = new Map(); // Хранит каналы по полному имени (с appKey)
22
24
  this.channelAliases = new Map(); // Маппинг оригинальное имя -> полное имя
23
25
  this.eventListeners = new Map();
26
+ this.pendingAuthRequests = new Map(); // Отслеживание pending auth запросов для отмены при unsubscribe
24
27
  this.reconnectAttempts = 0;
25
28
  this.reconnectTimer = null;
26
29
  this.authTimeout = null;
27
30
 
31
+ // Promise для ожидания подключения
32
+ this.connectionPromise = null;
33
+ this.connectionResolve = null;
34
+ this.connectionReject = null;
35
+
28
36
  // Автоматическое подключение
29
37
  if (this.autoConnect) {
30
38
  this.connect();
@@ -60,13 +68,25 @@ class PushlerClient {
60
68
 
61
69
  /**
62
70
  * Подключение к WebSocket серверу
71
+ * @returns {Promise} Promise, который резолвится при успешном подключении
63
72
  */
64
73
  connect() {
65
- if (this.connectionState === ConnectionStates.CONNECTED ||
66
- this.connectionState === ConnectionStates.CONNECTING) {
67
- return;
74
+ // Если уже подключены - возвращаем резолвленный Promise
75
+ if (this.connectionState === ConnectionStates.CONNECTED) {
76
+ return Promise.resolve({ socketId: this.socketId });
77
+ }
78
+
79
+ // Если уже идёт подключение - возвращаем существующий Promise
80
+ if (this.connectionState === ConnectionStates.CONNECTING && this.connectionPromise) {
81
+ return this.connectionPromise;
68
82
  }
69
83
 
84
+ // Создаём новый Promise для ожидания подключения
85
+ this.connectionPromise = new Promise((resolve, reject) => {
86
+ this.connectionResolve = resolve;
87
+ this.connectionReject = reject;
88
+ });
89
+
70
90
  this.connectionState = ConnectionStates.CONNECTING;
71
91
  this.emit(Events.CONNECTION_STATE_CHANGED, this.connectionState);
72
92
 
@@ -81,7 +101,66 @@ class PushlerClient {
81
101
  } catch (error) {
82
102
  console.error('PushlerClient: Error creating WebSocket connection:', error);
83
103
  this.handleConnectionError(error);
104
+ if (this.connectionReject) {
105
+ this.connectionReject(error);
106
+ this.connectionPromise = null;
107
+ this.connectionResolve = null;
108
+ this.connectionReject = null;
109
+ }
110
+ }
111
+
112
+ return this.connectionPromise;
113
+ }
114
+
115
+ /**
116
+ * Ожидание установления соединения
117
+ * @param {number} timeout - Таймаут в мс (по умолчанию 10000)
118
+ * @returns {Promise} Promise с socketId
119
+ */
120
+ waitForConnection(timeout = 10000) {
121
+ // Если уже подключены
122
+ if (this.connectionState === ConnectionStates.CONNECTED && this.socketId) {
123
+ return Promise.resolve({ socketId: this.socketId });
124
+ }
125
+
126
+ // Если есть активный Promise подключения
127
+ if (this.connectionPromise) {
128
+ return Promise.race([
129
+ this.connectionPromise,
130
+ new Promise((_, reject) =>
131
+ setTimeout(() => reject(new Error('Connection timeout')), timeout)
132
+ )
133
+ ]);
84
134
  }
135
+
136
+ // Если не подключаемся - начинаем подключение
137
+ if (this.connectionState === ConnectionStates.DISCONNECTED) {
138
+ return this.connect();
139
+ }
140
+
141
+ // Для других состояний создаём Promise, который ждёт connected события
142
+ return new Promise((resolve, reject) => {
143
+ const timeoutId = setTimeout(() => {
144
+ this.off(Events.CONNECTED, onConnected);
145
+ this.off(Events.ERROR, onError);
146
+ reject(new Error('Connection timeout'));
147
+ }, timeout);
148
+
149
+ const onConnected = (data) => {
150
+ clearTimeout(timeoutId);
151
+ this.off(Events.ERROR, onError);
152
+ resolve(data);
153
+ };
154
+
155
+ const onError = (err) => {
156
+ clearTimeout(timeoutId);
157
+ this.off(Events.CONNECTED, onConnected);
158
+ reject(err);
159
+ };
160
+
161
+ this.on(Events.CONNECTED, onConnected);
162
+ this.on(Events.ERROR, onError);
163
+ });
85
164
  }
86
165
 
87
166
  /**
@@ -133,31 +212,98 @@ class PushlerClient {
133
212
  };
134
213
  }
135
214
 
215
+ /**
216
+ * Разбор склеенных JSON из одного WebSocket фрейма
217
+ * Сервер иногда отправляет несколько JSON объектов подряд в одном фрейме:
218
+ * {"event":"a",...}{"event":"b",...}
219
+ * @param {string} str - Строка с одним или несколькими JSON объектами
220
+ * @returns {string[]} Массив отдельных JSON строк
221
+ */
222
+ splitConcatenatedJSON(str) {
223
+ const results = [];
224
+ let depth = 0;
225
+ let start = 0;
226
+ let inString = false;
227
+ let escape = false;
228
+
229
+ for (let i = 0; i < str.length; i++) {
230
+ const char = str[i];
231
+
232
+ // Обработка escape-символов внутри строк
233
+ if (escape) {
234
+ escape = false;
235
+ continue;
236
+ }
237
+
238
+ if (char === '\\' && inString) {
239
+ escape = true;
240
+ continue;
241
+ }
242
+
243
+ // Отслеживание строковых литералов
244
+ if (char === '"') {
245
+ inString = !inString;
246
+ continue;
247
+ }
248
+
249
+ // Считаем скобки только вне строк
250
+ if (!inString) {
251
+ if (char === '{') {
252
+ depth++;
253
+ } else if (char === '}') {
254
+ depth--;
255
+ if (depth === 0) {
256
+ results.push(str.slice(start, i + 1));
257
+ start = i + 1;
258
+ }
259
+ }
260
+ }
261
+ }
262
+
263
+ return results;
264
+ }
265
+
136
266
  /**
137
267
  * Обработка входящих сообщений
138
268
  */
139
269
  handleMessage(event) {
140
270
  try {
141
- const message = JSON.parse(event.data);
271
+ // Пытаемся распарсить как один JSON
272
+ const messages = this.splitConcatenatedJSON(event.data);
142
273
 
143
- switch (message.event) {
144
- case 'pushler:connection_established':
145
- this.handleConnectionEstablished(message.data);
146
- break;
147
- case 'pushler:subscription_succeeded':
148
- this.handleSubscriptionSucceeded(message);
149
- break;
150
- case 'pushler:auth_success':
151
- this.handleAuthSuccess(message);
152
- break;
153
- case 'pushler:auth_error':
154
- this.handleAuthError(message);
155
- break;
156
- default:
157
- this.handleChannelMessage(message);
274
+ // Если несколько JSON объектов, обрабатываем каждый
275
+ for (const jsonStr of messages) {
276
+ try {
277
+ const message = JSON.parse(jsonStr);
278
+ this.processMessage(message);
279
+ } catch (parseError) {
280
+ console.error('Error parsing JSON message:', parseError, 'Raw:', jsonStr);
281
+ }
158
282
  }
159
283
  } catch (error) {
160
- console.error('Error parsing WebSocket message:', error);
284
+ console.error('Error handling WebSocket message:', error);
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Обработка распарсенного сообщения
290
+ */
291
+ processMessage(message) {
292
+ switch (message.event) {
293
+ case 'pushler:connection_established':
294
+ this.handleConnectionEstablished(message.data);
295
+ break;
296
+ case 'pushler:subscription_succeeded':
297
+ this.handleSubscriptionSucceeded(message);
298
+ break;
299
+ case 'pushler:auth_success':
300
+ this.handleAuthSuccess(message);
301
+ break;
302
+ case 'pushler:auth_error':
303
+ this.handleAuthError(message);
304
+ break;
305
+ default:
306
+ this.handleChannelMessage(message);
161
307
  }
162
308
  }
163
309
 
@@ -171,6 +317,14 @@ class PushlerClient {
171
317
 
172
318
  console.log('Connection established with socket ID:', this.socketId);
173
319
 
320
+ // Резолвим Promise подключения
321
+ if (this.connectionResolve) {
322
+ this.connectionResolve({ socketId: this.socketId });
323
+ this.connectionPromise = null;
324
+ this.connectionResolve = null;
325
+ this.connectionReject = null;
326
+ }
327
+
174
328
  // Переподписываемся на все каналы после переподключения
175
329
  this.resubscribeAllChannels();
176
330
 
@@ -210,7 +364,13 @@ class PushlerClient {
210
364
  const { channel } = message.data;
211
365
  const channelInstance = this.channels.get(channel);
212
366
 
367
+ // Очищаем pending auth запрос
368
+ this.cancelPendingAuth(channel);
369
+
213
370
  if (channelInstance) {
371
+ // Устанавливаем состояние subscribed
372
+ channelInstance.subscriptionState = SubscriptionStates.SUBSCRIBED;
373
+
214
374
  // На сервере уже происходит подписка после успешной авторизации,
215
375
  // поэтому просто помечаем канал как подписанный
216
376
  channelInstance.handleAuthSuccess(message.data);
@@ -223,11 +383,31 @@ class PushlerClient {
223
383
  * Обработка ошибки аутентификации
224
384
  */
225
385
  handleAuthError(message) {
226
- const { error } = message.data;
227
- this.emit(Events.CHANNEL_AUTH_ERROR, error);
386
+ const { error, channel } = message.data;
387
+ const channelName = channel || null;
388
+
389
+ // Очищаем pending auth запрос
390
+ if (channelName) {
391
+ this.cancelPendingAuth(channelName);
392
+
393
+ const channelInstance = this.channels.get(channelName);
394
+ if (channelInstance) {
395
+ channelInstance.subscriptionState = SubscriptionStates.FAILED;
396
+ }
397
+ }
398
+
399
+ const originalChannelName = channelName ? this.extractChannelName(channelName) : null;
400
+
401
+ this.emit(Events.CHANNEL_AUTH_ERROR, {
402
+ error,
403
+ channel: originalChannelName,
404
+ socketId: this.socketId
405
+ });
228
406
  this.emit(Events.ERROR, {
229
407
  code: ErrorCodes.AUTHENTICATION_FAILED,
230
- message: error
408
+ message: error,
409
+ channel: originalChannelName,
410
+ socketId: this.socketId
231
411
  });
232
412
  }
233
413
 
@@ -285,8 +465,12 @@ class PushlerClient {
285
465
  // Поддерживаем оба формата имени
286
466
  const fullChannelName = this.channelAliases.get(channelName) || this.formatChannelName(channelName);
287
467
 
468
+ // Отменяем pending auth запросы для этого канала (исправление race condition)
469
+ this.cancelPendingAuth(fullChannelName);
470
+
288
471
  const channel = this.channels.get(fullChannelName);
289
472
  if (channel) {
473
+ channel.subscriptionState = SubscriptionStates.UNSUBSCRIBED;
290
474
  channel.unsubscribe();
291
475
  this.channels.delete(fullChannelName);
292
476
  this.channelAliases.delete(channel.originalName || channelName);
@@ -321,32 +505,141 @@ class PushlerClient {
321
505
 
322
506
  /**
323
507
  * Аутентификация канала
508
+ * С поддержкой отмены при unsubscribe
324
509
  */
325
510
  async authenticateChannel(channel) {
511
+ // Создаём AbortController для возможности отмены
512
+ const abortController = new AbortController();
513
+ const channelName = channel.name;
514
+
515
+ // Сохраняем для возможности отмены при unsubscribe
516
+ this.pendingAuthRequests.set(channelName, {
517
+ controller: abortController,
518
+ timeoutId: null
519
+ });
520
+
521
+ // Устанавливаем состояние канала
522
+ channel.subscriptionState = SubscriptionStates.PENDING_AUTH;
523
+
326
524
  try {
525
+ // Проверка отмены перед началом
526
+ if (abortController.signal.aborted) {
527
+ return;
528
+ }
529
+
327
530
  const authData = await this.getChannelAuthData(channel);
328
531
 
532
+ // Проверка отмены после получения данных
533
+ if (abortController.signal.aborted) {
534
+ return;
535
+ }
536
+
329
537
  this.sendMessage({
330
538
  event: 'pushler:auth',
331
539
  data: authData
332
540
  });
333
541
 
334
542
  // Устанавливаем таймаут аутентификации
335
- this.authTimeout = setTimeout(() => {
543
+ const timeoutId = setTimeout(() => {
544
+ // Не отправляем ошибку, если запрос уже отменён
545
+ if (abortController.signal.aborted) {
546
+ return;
547
+ }
548
+
549
+ // Очищаем pending запрос
550
+ this.pendingAuthRequests.delete(channelName);
551
+
552
+ // Устанавливаем состояние failed
553
+ channel.subscriptionState = SubscriptionStates.FAILED;
554
+
336
555
  this.emit(Events.ERROR, {
337
556
  code: ErrorCodes.AUTHENTICATION_TIMEOUT,
338
- message: 'Authentication timeout'
557
+ message: 'Authentication timeout',
558
+ channel: channel.originalName || this.extractChannelName(channelName),
559
+ socketId: this.socketId
339
560
  });
340
561
  }, 10000);
341
562
 
563
+ // Сохраняем timeoutId для возможности очистки
564
+ const pending = this.pendingAuthRequests.get(channelName);
565
+ if (pending) {
566
+ pending.timeoutId = timeoutId;
567
+ }
568
+
342
569
  } catch (error) {
570
+ // Не отправляем ошибку, если запрос уже отменён
571
+ if (abortController.signal.aborted) {
572
+ return;
573
+ }
574
+
575
+ // Очищаем pending запрос
576
+ this.pendingAuthRequests.delete(channelName);
577
+
578
+ // Устанавливаем состояние failed
579
+ channel.subscriptionState = SubscriptionStates.FAILED;
580
+
343
581
  this.emit(Events.ERROR, {
344
582
  code: ErrorCodes.AUTHENTICATION_FAILED,
345
- message: error.message
583
+ message: error.message,
584
+ channel: channel.originalName || this.extractChannelName(channelName),
585
+ socketId: this.socketId
346
586
  });
347
587
  }
348
588
  }
349
589
 
590
+ /**
591
+ * Отмена pending auth запроса для канала
592
+ */
593
+ cancelPendingAuth(channelName) {
594
+ const pending = this.pendingAuthRequests.get(channelName);
595
+ if (pending) {
596
+ // Отменяем запрос
597
+ pending.controller.abort();
598
+
599
+ // Очищаем таймаут
600
+ if (pending.timeoutId) {
601
+ clearTimeout(pending.timeoutId);
602
+ }
603
+
604
+ // Удаляем из карты
605
+ this.pendingAuthRequests.delete(channelName);
606
+ }
607
+ }
608
+
609
+ /**
610
+ * Получение заголовков для авторизации
611
+ * Поддерживает как синхронные, так и асинхронные функции getAuthHeaders
612
+ * @returns {Promise<Object>} Заголовки для авторизации
613
+ */
614
+ async getAuthRequestHeaders() {
615
+ const headers = { 'Content-Type': 'application/json' };
616
+
617
+ // Динамические заголовки имеют приоритет
618
+ if (typeof this.getAuthHeaders === 'function') {
619
+ // Поддержка асинхронных функций (AsyncStorage, SecureStore, Keychain)
620
+ const dynamicHeaders = await Promise.resolve(this.getAuthHeaders());
621
+ Object.assign(headers, dynamicHeaders);
622
+ } else if (this.authHeaders) {
623
+ // Статические заголовки
624
+ Object.assign(headers, this.authHeaders);
625
+ }
626
+
627
+ return headers;
628
+ }
629
+
630
+ /**
631
+ * Установка токена авторизации
632
+ * Удобный метод для установки Bearer токена без создания функции
633
+ * @param {string} token - JWT токен
634
+ */
635
+ setAuthToken(token) {
636
+ if (token) {
637
+ this.authHeaders = { 'Authorization': `Bearer ${token}` };
638
+ } else {
639
+ this.authHeaders = null;
640
+ }
641
+ }
642
+
350
643
  /**
351
644
  * Получение подписи с бэкенда
352
645
  */
@@ -356,11 +649,16 @@ class PushlerClient {
356
649
  ? this.authEndpoint
357
650
  : `${this.apiUrl}${this.authEndpoint}`;
358
651
 
652
+ // Асинхронное получение заголовков (поддержка AsyncStorage/SecureStore)
653
+ const headers = await this.getAuthRequestHeaders();
654
+ const hasAuthHeaders = this.authHeaders || this.getAuthHeaders;
655
+
359
656
  const response = await fetch(authUrl, {
360
657
  method: 'POST',
361
- headers: {
362
- 'Content-Type': 'application/json',
363
- },
658
+ headers,
659
+ // Если есть authHeaders, не используем credentials (JWT режим)
660
+ // Если нет — используем cookies
661
+ ...(hasAuthHeaders ? {} : { credentials: 'include' }),
364
662
  body: JSON.stringify({
365
663
  channel_name: channelName,
366
664
  socket_id: socketId,
@@ -370,7 +668,7 @@ class PushlerClient {
370
668
  });
371
669
 
372
670
  if (!response.ok) {
373
- const error = await response.json();
671
+ const error = await response.json().catch(() => ({ error: 'Auth request failed' }));
374
672
  throw new Error(error.error || 'Failed to get channel signature');
375
673
  }
376
674
 
@@ -503,6 +801,14 @@ class PushlerClient {
503
801
 
504
802
  console.error('PushlerClient connection error:', fullErrorMessage);
505
803
 
804
+ // Реджектим Promise подключения
805
+ if (this.connectionReject) {
806
+ this.connectionReject(new Error(fullErrorMessage));
807
+ this.connectionPromise = null;
808
+ this.connectionResolve = null;
809
+ this.connectionReject = null;
810
+ }
811
+
506
812
  this.emit(Events.ERROR, {
507
813
  code: ErrorCodes.CONNECTION_FAILED,
508
814
  message: fullErrorMessage,
@@ -601,6 +907,69 @@ class PushlerClient {
601
907
  const fullChannelName = this.channelAliases.get(channelName) || this.formatChannelName(channelName);
602
908
  return this.channels.get(fullChannelName);
603
909
  }
910
+
911
+ /**
912
+ * Проверка подписки на канал
913
+ * @param {string} channelName - Имя канала
914
+ * @returns {boolean} true если подписан
915
+ */
916
+ isSubscribed(channelName) {
917
+ const fullChannelName = this.channelAliases.get(channelName) || this.formatChannelName(channelName);
918
+ const channel = this.channels.get(fullChannelName);
919
+ return channel ? channel.subscribed : false;
920
+ }
921
+
922
+ /**
923
+ * Получение состояния подписки на канал
924
+ * @param {string} channelName - Имя канала
925
+ * @returns {string|null} Состояние подписки или null если канал не найден
926
+ */
927
+ getSubscriptionState(channelName) {
928
+ const fullChannelName = this.channelAliases.get(channelName) || this.formatChannelName(channelName);
929
+ const channel = this.channels.get(fullChannelName);
930
+
931
+ if (!channel) {
932
+ return null;
933
+ }
934
+
935
+ return channel.subscriptionState || (channel.subscribed ? SubscriptionStates.SUBSCRIBED : SubscriptionStates.PENDING);
936
+ }
937
+
938
+ /**
939
+ * Получение списка подписанных каналов
940
+ * @returns {string[]} Массив имён подписанных каналов
941
+ */
942
+ getSubscribedChannels() {
943
+ const subscribedChannels = [];
944
+
945
+ for (const [fullName, channel] of this.channels.entries()) {
946
+ if (channel.subscribed) {
947
+ subscribedChannels.push(channel.originalName || this.extractChannelName(fullName));
948
+ }
949
+ }
950
+
951
+ return subscribedChannels;
952
+ }
953
+
954
+ /**
955
+ * Получение всех каналов с их состояниями
956
+ * @returns {Object[]} Массив объектов с информацией о каналах
957
+ */
958
+ getAllChannelsInfo() {
959
+ const channelsInfo = [];
960
+
961
+ for (const [fullName, channel] of this.channels.entries()) {
962
+ channelsInfo.push({
963
+ name: channel.originalName || this.extractChannelName(fullName),
964
+ fullName: fullName,
965
+ subscribed: channel.subscribed,
966
+ state: channel.subscriptionState || (channel.subscribed ? SubscriptionStates.SUBSCRIBED : SubscriptionStates.PENDING),
967
+ type: channel.getType()
968
+ });
969
+ }
970
+
971
+ return channelsInfo;
972
+ }
604
973
  }
605
974
 
606
975
  export default PushlerClient;
@@ -5,11 +5,12 @@ import { createHmac } from 'crypto';
5
5
  * Используется на сервере Node.js для отправки событий в каналы
6
6
  */
7
7
  class PushlerServer {
8
+ static API_URL = 'https://api.pushler.ru';
9
+
8
10
  /**
9
11
  * @param {Object} options - Параметры клиента
10
12
  * @param {string} options.appKey - Ключ приложения (key_xxx)
11
13
  * @param {string} options.appSecret - Секрет приложения (secret_xxx)
12
- * @param {string} [options.apiUrl='http://localhost:8000/api'] - URL API сервера
13
14
  * @param {number} [options.timeout=10000] - Таймаут запроса в мс
14
15
  */
15
16
  constructor(options = {}) {
@@ -22,7 +23,6 @@ class PushlerServer {
22
23
 
23
24
  this.appKey = options.appKey;
24
25
  this.appSecret = options.appSecret;
25
- this.apiUrl = (options.apiUrl || 'http://localhost:8000/api').replace(/\/$/, '');
26
26
  this.timeout = options.timeout || 10000;
27
27
  }
28
28
 
@@ -306,7 +306,7 @@ class PushlerServer {
306
306
  * @returns {Promise<Object>} Результат запроса
307
307
  */
308
308
  async makeSignedRequest(method, endpoint, data = null) {
309
- const url = this.apiUrl + endpoint;
309
+ const url = PushlerServer.API_URL + endpoint;
310
310
  const timestamp = Math.floor(Date.now() / 1000);
311
311
  const body = data ? JSON.stringify(data) : '';
312
312
  const signature = this.generateApiSignature(body, timestamp);
@@ -354,7 +354,7 @@ class PushlerServer {
354
354
  * @returns {Promise<Object>} Результат запроса
355
355
  */
356
356
  async makeHttpRequest(method, endpoint, data = null) {
357
- const url = this.apiUrl + endpoint;
357
+ const url = PushlerServer.API_URL + endpoint;
358
358
 
359
359
  const headers = {
360
360
  'Content-Type': 'application/json',
@@ -395,7 +395,7 @@ class PushlerServer {
395
395
  * @returns {string}
396
396
  */
397
397
  getApiUrl() {
398
- return this.apiUrl;
398
+ return PushlerServer.API_URL;
399
399
  }
400
400
  }
401
401
 
package/src/constants.js CHANGED
@@ -18,6 +18,18 @@ export const ConnectionStates = {
18
18
  FAILED: 'failed'
19
19
  };
20
20
 
21
+ /**
22
+ * Состояния подписки на канал
23
+ */
24
+ export const SubscriptionStates = {
25
+ PENDING: 'pending',
26
+ PENDING_AUTH: 'pending_auth',
27
+ SUBSCRIBING: 'subscribing',
28
+ SUBSCRIBED: 'subscribed',
29
+ FAILED: 'failed',
30
+ UNSUBSCRIBED: 'unsubscribed'
31
+ };
32
+
21
33
  /**
22
34
  * События WebSocket
23
35
  */
package/src/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import PushlerClient from './PushlerClient';
2
2
  import PushlerServer from './PushlerServer';
3
3
  import Channel from './Channel';
4
- import { ChannelTypes, ConnectionStates } from './constants';
4
+ import { ChannelTypes, ConnectionStates, SubscriptionStates, ErrorCodes, Events } from './constants';
5
5
  import { generateSocketId } from './utils';
6
6
 
7
7
  // Создаем объект с методами для удобного API
@@ -17,8 +17,22 @@ const Pushler = {
17
17
  Channel,
18
18
  ChannelTypes,
19
19
  ConnectionStates,
20
+ SubscriptionStates,
21
+ ErrorCodes,
22
+ Events,
20
23
  generateSocketId
21
24
  };
22
25
 
23
- export { PushlerClient, PushlerServer, Channel, ChannelTypes, ConnectionStates, generateSocketId, Pushler };
26
+ export {
27
+ PushlerClient,
28
+ PushlerServer,
29
+ Channel,
30
+ ChannelTypes,
31
+ ConnectionStates,
32
+ SubscriptionStates,
33
+ ErrorCodes,
34
+ Events,
35
+ generateSocketId,
36
+ Pushler
37
+ };
24
38
  export default Pushler;