@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.
@@ -20,6 +20,18 @@ const ConnectionStates = {
20
20
  FAILED: 'failed'
21
21
  };
22
22
 
23
+ /**
24
+ * Состояния подписки на канал
25
+ */
26
+ const SubscriptionStates = {
27
+ PENDING: 'pending',
28
+ PENDING_AUTH: 'pending_auth',
29
+ SUBSCRIBING: 'subscribing',
30
+ SUBSCRIBED: 'subscribed',
31
+ FAILED: 'failed',
32
+ UNSUBSCRIBED: 'unsubscribed'
33
+ };
34
+
23
35
  /**
24
36
  * События WebSocket
25
37
  */
@@ -61,6 +73,7 @@ class Channel {
61
73
  this.name = name;
62
74
  this.options = options;
63
75
  this.subscribed = false;
76
+ this.subscriptionState = SubscriptionStates.PENDING;
64
77
  this.eventListeners = new Map();
65
78
  this.presenceData = null;
66
79
  }
@@ -108,6 +121,7 @@ class Channel {
108
121
  */
109
122
  handleAuthSuccess(data) {
110
123
  this.subscribed = true;
124
+ this.subscriptionState = SubscriptionStates.SUBSCRIBED;
111
125
  this.emit(Events.CHANNEL_SUBSCRIBED, data);
112
126
 
113
127
  // Для presence каналов сохраняем данные пользователя
@@ -135,6 +149,7 @@ class Channel {
135
149
  }
136
150
 
137
151
  this.subscribed = false;
152
+ this.subscriptionState = SubscriptionStates.UNSUBSCRIBED;
138
153
  this.emit(Events.CHANNEL_UNSUBSCRIBED);
139
154
  }
140
155
 
@@ -145,6 +160,14 @@ class Channel {
145
160
  return this.subscribed;
146
161
  }
147
162
 
163
+ /**
164
+ * Получение состояния подписки
165
+ * @returns {string} Состояние подписки
166
+ */
167
+ getSubscriptionState() {
168
+ return this.subscriptionState;
169
+ }
170
+
148
171
  /**
149
172
  * Получение данных присутствия (для presence каналов)
150
173
  */
@@ -203,6 +226,8 @@ class PushlerClient {
203
226
  this.wsUrl = options.wsUrl || `wss://ws.pushler.ru/app/${this.appKey}`;
204
227
  this.apiUrl = options.apiUrl;
205
228
  this.authEndpoint = options.authEndpoint || '/pushler/auth'; // Путь для авторизации каналов
229
+ this.authHeaders = options.authHeaders || null; // Кастомные заголовки для авторизации
230
+ this.getAuthHeaders = options.getAuthHeaders || null; // Функция для динамических заголовков
206
231
  this.autoConnect = options.autoConnect !== false;
207
232
  this.reconnectDelay = options.reconnectDelay || 1000;
208
233
  this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
@@ -213,10 +238,16 @@ class PushlerClient {
213
238
  this.channels = new Map(); // Хранит каналы по полному имени (с appKey)
214
239
  this.channelAliases = new Map(); // Маппинг оригинальное имя -> полное имя
215
240
  this.eventListeners = new Map();
241
+ this.pendingAuthRequests = new Map(); // Отслеживание pending auth запросов для отмены при unsubscribe
216
242
  this.reconnectAttempts = 0;
217
243
  this.reconnectTimer = null;
218
244
  this.authTimeout = null;
219
245
 
246
+ // Promise для ожидания подключения
247
+ this.connectionPromise = null;
248
+ this.connectionResolve = null;
249
+ this.connectionReject = null;
250
+
220
251
  // Автоматическое подключение
221
252
  if (this.autoConnect) {
222
253
  this.connect();
@@ -252,13 +283,25 @@ class PushlerClient {
252
283
 
253
284
  /**
254
285
  * Подключение к WebSocket серверу
286
+ * @returns {Promise} Promise, который резолвится при успешном подключении
255
287
  */
256
288
  connect() {
257
- if (this.connectionState === ConnectionStates.CONNECTED ||
258
- this.connectionState === ConnectionStates.CONNECTING) {
259
- return;
289
+ // Если уже подключены - возвращаем резолвленный Promise
290
+ if (this.connectionState === ConnectionStates.CONNECTED) {
291
+ return Promise.resolve({ socketId: this.socketId });
292
+ }
293
+
294
+ // Если уже идёт подключение - возвращаем существующий Promise
295
+ if (this.connectionState === ConnectionStates.CONNECTING && this.connectionPromise) {
296
+ return this.connectionPromise;
260
297
  }
261
298
 
299
+ // Создаём новый Promise для ожидания подключения
300
+ this.connectionPromise = new Promise((resolve, reject) => {
301
+ this.connectionResolve = resolve;
302
+ this.connectionReject = reject;
303
+ });
304
+
262
305
  this.connectionState = ConnectionStates.CONNECTING;
263
306
  this.emit(Events.CONNECTION_STATE_CHANGED, this.connectionState);
264
307
 
@@ -273,7 +316,66 @@ class PushlerClient {
273
316
  } catch (error) {
274
317
  console.error('PushlerClient: Error creating WebSocket connection:', error);
275
318
  this.handleConnectionError(error);
319
+ if (this.connectionReject) {
320
+ this.connectionReject(error);
321
+ this.connectionPromise = null;
322
+ this.connectionResolve = null;
323
+ this.connectionReject = null;
324
+ }
276
325
  }
326
+
327
+ return this.connectionPromise;
328
+ }
329
+
330
+ /**
331
+ * Ожидание установления соединения
332
+ * @param {number} timeout - Таймаут в мс (по умолчанию 10000)
333
+ * @returns {Promise} Promise с socketId
334
+ */
335
+ waitForConnection(timeout = 10000) {
336
+ // Если уже подключены
337
+ if (this.connectionState === ConnectionStates.CONNECTED && this.socketId) {
338
+ return Promise.resolve({ socketId: this.socketId });
339
+ }
340
+
341
+ // Если есть активный Promise подключения
342
+ if (this.connectionPromise) {
343
+ return Promise.race([
344
+ this.connectionPromise,
345
+ new Promise((_, reject) =>
346
+ setTimeout(() => reject(new Error('Connection timeout')), timeout)
347
+ )
348
+ ]);
349
+ }
350
+
351
+ // Если не подключаемся - начинаем подключение
352
+ if (this.connectionState === ConnectionStates.DISCONNECTED) {
353
+ return this.connect();
354
+ }
355
+
356
+ // Для других состояний создаём Promise, который ждёт connected события
357
+ return new Promise((resolve, reject) => {
358
+ const timeoutId = setTimeout(() => {
359
+ this.off(Events.CONNECTED, onConnected);
360
+ this.off(Events.ERROR, onError);
361
+ reject(new Error('Connection timeout'));
362
+ }, timeout);
363
+
364
+ const onConnected = (data) => {
365
+ clearTimeout(timeoutId);
366
+ this.off(Events.ERROR, onError);
367
+ resolve(data);
368
+ };
369
+
370
+ const onError = (err) => {
371
+ clearTimeout(timeoutId);
372
+ this.off(Events.CONNECTED, onConnected);
373
+ reject(err);
374
+ };
375
+
376
+ this.on(Events.CONNECTED, onConnected);
377
+ this.on(Events.ERROR, onError);
378
+ });
277
379
  }
278
380
 
279
381
  /**
@@ -325,31 +427,98 @@ class PushlerClient {
325
427
  };
326
428
  }
327
429
 
430
+ /**
431
+ * Разбор склеенных JSON из одного WebSocket фрейма
432
+ * Сервер иногда отправляет несколько JSON объектов подряд в одном фрейме:
433
+ * {"event":"a",...}{"event":"b",...}
434
+ * @param {string} str - Строка с одним или несколькими JSON объектами
435
+ * @returns {string[]} Массив отдельных JSON строк
436
+ */
437
+ splitConcatenatedJSON(str) {
438
+ const results = [];
439
+ let depth = 0;
440
+ let start = 0;
441
+ let inString = false;
442
+ let escape = false;
443
+
444
+ for (let i = 0; i < str.length; i++) {
445
+ const char = str[i];
446
+
447
+ // Обработка escape-символов внутри строк
448
+ if (escape) {
449
+ escape = false;
450
+ continue;
451
+ }
452
+
453
+ if (char === '\\' && inString) {
454
+ escape = true;
455
+ continue;
456
+ }
457
+
458
+ // Отслеживание строковых литералов
459
+ if (char === '"') {
460
+ inString = !inString;
461
+ continue;
462
+ }
463
+
464
+ // Считаем скобки только вне строк
465
+ if (!inString) {
466
+ if (char === '{') {
467
+ depth++;
468
+ } else if (char === '}') {
469
+ depth--;
470
+ if (depth === 0) {
471
+ results.push(str.slice(start, i + 1));
472
+ start = i + 1;
473
+ }
474
+ }
475
+ }
476
+ }
477
+
478
+ return results;
479
+ }
480
+
328
481
  /**
329
482
  * Обработка входящих сообщений
330
483
  */
331
484
  handleMessage(event) {
332
485
  try {
333
- const message = JSON.parse(event.data);
486
+ // Пытаемся распарсить как один JSON
487
+ const messages = this.splitConcatenatedJSON(event.data);
334
488
 
335
- switch (message.event) {
336
- case 'pushler:connection_established':
337
- this.handleConnectionEstablished(message.data);
338
- break;
339
- case 'pushler:subscription_succeeded':
340
- this.handleSubscriptionSucceeded(message);
341
- break;
342
- case 'pushler:auth_success':
343
- this.handleAuthSuccess(message);
344
- break;
345
- case 'pushler:auth_error':
346
- this.handleAuthError(message);
347
- break;
348
- default:
349
- this.handleChannelMessage(message);
489
+ // Если несколько JSON объектов, обрабатываем каждый
490
+ for (const jsonStr of messages) {
491
+ try {
492
+ const message = JSON.parse(jsonStr);
493
+ this.processMessage(message);
494
+ } catch (parseError) {
495
+ console.error('Error parsing JSON message:', parseError, 'Raw:', jsonStr);
496
+ }
350
497
  }
351
498
  } catch (error) {
352
- console.error('Error parsing WebSocket message:', error);
499
+ console.error('Error handling WebSocket message:', error);
500
+ }
501
+ }
502
+
503
+ /**
504
+ * Обработка распарсенного сообщения
505
+ */
506
+ processMessage(message) {
507
+ switch (message.event) {
508
+ case 'pushler:connection_established':
509
+ this.handleConnectionEstablished(message.data);
510
+ break;
511
+ case 'pushler:subscription_succeeded':
512
+ this.handleSubscriptionSucceeded(message);
513
+ break;
514
+ case 'pushler:auth_success':
515
+ this.handleAuthSuccess(message);
516
+ break;
517
+ case 'pushler:auth_error':
518
+ this.handleAuthError(message);
519
+ break;
520
+ default:
521
+ this.handleChannelMessage(message);
353
522
  }
354
523
  }
355
524
 
@@ -363,6 +532,14 @@ class PushlerClient {
363
532
 
364
533
  console.log('Connection established with socket ID:', this.socketId);
365
534
 
535
+ // Резолвим Promise подключения
536
+ if (this.connectionResolve) {
537
+ this.connectionResolve({ socketId: this.socketId });
538
+ this.connectionPromise = null;
539
+ this.connectionResolve = null;
540
+ this.connectionReject = null;
541
+ }
542
+
366
543
  // Переподписываемся на все каналы после переподключения
367
544
  this.resubscribeAllChannels();
368
545
 
@@ -402,7 +579,13 @@ class PushlerClient {
402
579
  const { channel } = message.data;
403
580
  const channelInstance = this.channels.get(channel);
404
581
 
582
+ // Очищаем pending auth запрос
583
+ this.cancelPendingAuth(channel);
584
+
405
585
  if (channelInstance) {
586
+ // Устанавливаем состояние subscribed
587
+ channelInstance.subscriptionState = SubscriptionStates.SUBSCRIBED;
588
+
406
589
  // На сервере уже происходит подписка после успешной авторизации,
407
590
  // поэтому просто помечаем канал как подписанный
408
591
  channelInstance.handleAuthSuccess(message.data);
@@ -415,11 +598,31 @@ class PushlerClient {
415
598
  * Обработка ошибки аутентификации
416
599
  */
417
600
  handleAuthError(message) {
418
- const { error } = message.data;
419
- this.emit(Events.CHANNEL_AUTH_ERROR, error);
601
+ const { error, channel } = message.data;
602
+ const channelName = channel || null;
603
+
604
+ // Очищаем pending auth запрос
605
+ if (channelName) {
606
+ this.cancelPendingAuth(channelName);
607
+
608
+ const channelInstance = this.channels.get(channelName);
609
+ if (channelInstance) {
610
+ channelInstance.subscriptionState = SubscriptionStates.FAILED;
611
+ }
612
+ }
613
+
614
+ const originalChannelName = channelName ? this.extractChannelName(channelName) : null;
615
+
616
+ this.emit(Events.CHANNEL_AUTH_ERROR, {
617
+ error,
618
+ channel: originalChannelName,
619
+ socketId: this.socketId
620
+ });
420
621
  this.emit(Events.ERROR, {
421
622
  code: ErrorCodes.AUTHENTICATION_FAILED,
422
- message: error
623
+ message: error,
624
+ channel: originalChannelName,
625
+ socketId: this.socketId
423
626
  });
424
627
  }
425
628
 
@@ -477,8 +680,12 @@ class PushlerClient {
477
680
  // Поддерживаем оба формата имени
478
681
  const fullChannelName = this.channelAliases.get(channelName) || this.formatChannelName(channelName);
479
682
 
683
+ // Отменяем pending auth запросы для этого канала (исправление race condition)
684
+ this.cancelPendingAuth(fullChannelName);
685
+
480
686
  const channel = this.channels.get(fullChannelName);
481
687
  if (channel) {
688
+ channel.subscriptionState = SubscriptionStates.UNSUBSCRIBED;
482
689
  channel.unsubscribe();
483
690
  this.channels.delete(fullChannelName);
484
691
  this.channelAliases.delete(channel.originalName || channelName);
@@ -513,32 +720,141 @@ class PushlerClient {
513
720
 
514
721
  /**
515
722
  * Аутентификация канала
723
+ * С поддержкой отмены при unsubscribe
516
724
  */
517
725
  async authenticateChannel(channel) {
726
+ // Создаём AbortController для возможности отмены
727
+ const abortController = new AbortController();
728
+ const channelName = channel.name;
729
+
730
+ // Сохраняем для возможности отмены при unsubscribe
731
+ this.pendingAuthRequests.set(channelName, {
732
+ controller: abortController,
733
+ timeoutId: null
734
+ });
735
+
736
+ // Устанавливаем состояние канала
737
+ channel.subscriptionState = SubscriptionStates.PENDING_AUTH;
738
+
518
739
  try {
740
+ // Проверка отмены перед началом
741
+ if (abortController.signal.aborted) {
742
+ return;
743
+ }
744
+
519
745
  const authData = await this.getChannelAuthData(channel);
520
746
 
747
+ // Проверка отмены после получения данных
748
+ if (abortController.signal.aborted) {
749
+ return;
750
+ }
751
+
521
752
  this.sendMessage({
522
753
  event: 'pushler:auth',
523
754
  data: authData
524
755
  });
525
756
 
526
757
  // Устанавливаем таймаут аутентификации
527
- this.authTimeout = setTimeout(() => {
758
+ const timeoutId = setTimeout(() => {
759
+ // Не отправляем ошибку, если запрос уже отменён
760
+ if (abortController.signal.aborted) {
761
+ return;
762
+ }
763
+
764
+ // Очищаем pending запрос
765
+ this.pendingAuthRequests.delete(channelName);
766
+
767
+ // Устанавливаем состояние failed
768
+ channel.subscriptionState = SubscriptionStates.FAILED;
769
+
528
770
  this.emit(Events.ERROR, {
529
771
  code: ErrorCodes.AUTHENTICATION_TIMEOUT,
530
- message: 'Authentication timeout'
772
+ message: 'Authentication timeout',
773
+ channel: channel.originalName || this.extractChannelName(channelName),
774
+ socketId: this.socketId
531
775
  });
532
776
  }, 10000);
533
777
 
778
+ // Сохраняем timeoutId для возможности очистки
779
+ const pending = this.pendingAuthRequests.get(channelName);
780
+ if (pending) {
781
+ pending.timeoutId = timeoutId;
782
+ }
783
+
534
784
  } catch (error) {
785
+ // Не отправляем ошибку, если запрос уже отменён
786
+ if (abortController.signal.aborted) {
787
+ return;
788
+ }
789
+
790
+ // Очищаем pending запрос
791
+ this.pendingAuthRequests.delete(channelName);
792
+
793
+ // Устанавливаем состояние failed
794
+ channel.subscriptionState = SubscriptionStates.FAILED;
795
+
535
796
  this.emit(Events.ERROR, {
536
797
  code: ErrorCodes.AUTHENTICATION_FAILED,
537
- message: error.message
798
+ message: error.message,
799
+ channel: channel.originalName || this.extractChannelName(channelName),
800
+ socketId: this.socketId
538
801
  });
539
802
  }
540
803
  }
541
804
 
805
+ /**
806
+ * Отмена pending auth запроса для канала
807
+ */
808
+ cancelPendingAuth(channelName) {
809
+ const pending = this.pendingAuthRequests.get(channelName);
810
+ if (pending) {
811
+ // Отменяем запрос
812
+ pending.controller.abort();
813
+
814
+ // Очищаем таймаут
815
+ if (pending.timeoutId) {
816
+ clearTimeout(pending.timeoutId);
817
+ }
818
+
819
+ // Удаляем из карты
820
+ this.pendingAuthRequests.delete(channelName);
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Получение заголовков для авторизации
826
+ * Поддерживает как синхронные, так и асинхронные функции getAuthHeaders
827
+ * @returns {Promise<Object>} Заголовки для авторизации
828
+ */
829
+ async getAuthRequestHeaders() {
830
+ const headers = { 'Content-Type': 'application/json' };
831
+
832
+ // Динамические заголовки имеют приоритет
833
+ if (typeof this.getAuthHeaders === 'function') {
834
+ // Поддержка асинхронных функций (AsyncStorage, SecureStore, Keychain)
835
+ const dynamicHeaders = await Promise.resolve(this.getAuthHeaders());
836
+ Object.assign(headers, dynamicHeaders);
837
+ } else if (this.authHeaders) {
838
+ // Статические заголовки
839
+ Object.assign(headers, this.authHeaders);
840
+ }
841
+
842
+ return headers;
843
+ }
844
+
845
+ /**
846
+ * Установка токена авторизации
847
+ * Удобный метод для установки Bearer токена без создания функции
848
+ * @param {string} token - JWT токен
849
+ */
850
+ setAuthToken(token) {
851
+ if (token) {
852
+ this.authHeaders = { 'Authorization': `Bearer ${token}` };
853
+ } else {
854
+ this.authHeaders = null;
855
+ }
856
+ }
857
+
542
858
  /**
543
859
  * Получение подписи с бэкенда
544
860
  */
@@ -548,11 +864,16 @@ class PushlerClient {
548
864
  ? this.authEndpoint
549
865
  : `${this.apiUrl}${this.authEndpoint}`;
550
866
 
867
+ // Асинхронное получение заголовков (поддержка AsyncStorage/SecureStore)
868
+ const headers = await this.getAuthRequestHeaders();
869
+ const hasAuthHeaders = this.authHeaders || this.getAuthHeaders;
870
+
551
871
  const response = await fetch(authUrl, {
552
872
  method: 'POST',
553
- headers: {
554
- 'Content-Type': 'application/json',
555
- },
873
+ headers,
874
+ // Если есть authHeaders, не используем credentials (JWT режим)
875
+ // Если нет — используем cookies
876
+ ...(hasAuthHeaders ? {} : { credentials: 'include' }),
556
877
  body: JSON.stringify({
557
878
  channel_name: channelName,
558
879
  socket_id: socketId,
@@ -562,7 +883,7 @@ class PushlerClient {
562
883
  });
563
884
 
564
885
  if (!response.ok) {
565
- const error = await response.json();
886
+ const error = await response.json().catch(() => ({ error: 'Auth request failed' }));
566
887
  throw new Error(error.error || 'Failed to get channel signature');
567
888
  }
568
889
 
@@ -695,6 +1016,14 @@ class PushlerClient {
695
1016
 
696
1017
  console.error('PushlerClient connection error:', fullErrorMessage);
697
1018
 
1019
+ // Реджектим Promise подключения
1020
+ if (this.connectionReject) {
1021
+ this.connectionReject(new Error(fullErrorMessage));
1022
+ this.connectionPromise = null;
1023
+ this.connectionResolve = null;
1024
+ this.connectionReject = null;
1025
+ }
1026
+
698
1027
  this.emit(Events.ERROR, {
699
1028
  code: ErrorCodes.CONNECTION_FAILED,
700
1029
  message: fullErrorMessage,
@@ -793,6 +1122,69 @@ class PushlerClient {
793
1122
  const fullChannelName = this.channelAliases.get(channelName) || this.formatChannelName(channelName);
794
1123
  return this.channels.get(fullChannelName);
795
1124
  }
1125
+
1126
+ /**
1127
+ * Проверка подписки на канал
1128
+ * @param {string} channelName - Имя канала
1129
+ * @returns {boolean} true если подписан
1130
+ */
1131
+ isSubscribed(channelName) {
1132
+ const fullChannelName = this.channelAliases.get(channelName) || this.formatChannelName(channelName);
1133
+ const channel = this.channels.get(fullChannelName);
1134
+ return channel ? channel.subscribed : false;
1135
+ }
1136
+
1137
+ /**
1138
+ * Получение состояния подписки на канал
1139
+ * @param {string} channelName - Имя канала
1140
+ * @returns {string|null} Состояние подписки или null если канал не найден
1141
+ */
1142
+ getSubscriptionState(channelName) {
1143
+ const fullChannelName = this.channelAliases.get(channelName) || this.formatChannelName(channelName);
1144
+ const channel = this.channels.get(fullChannelName);
1145
+
1146
+ if (!channel) {
1147
+ return null;
1148
+ }
1149
+
1150
+ return channel.subscriptionState || (channel.subscribed ? SubscriptionStates.SUBSCRIBED : SubscriptionStates.PENDING);
1151
+ }
1152
+
1153
+ /**
1154
+ * Получение списка подписанных каналов
1155
+ * @returns {string[]} Массив имён подписанных каналов
1156
+ */
1157
+ getSubscribedChannels() {
1158
+ const subscribedChannels = [];
1159
+
1160
+ for (const [fullName, channel] of this.channels.entries()) {
1161
+ if (channel.subscribed) {
1162
+ subscribedChannels.push(channel.originalName || this.extractChannelName(fullName));
1163
+ }
1164
+ }
1165
+
1166
+ return subscribedChannels;
1167
+ }
1168
+
1169
+ /**
1170
+ * Получение всех каналов с их состояниями
1171
+ * @returns {Object[]} Массив объектов с информацией о каналах
1172
+ */
1173
+ getAllChannelsInfo() {
1174
+ const channelsInfo = [];
1175
+
1176
+ for (const [fullName, channel] of this.channels.entries()) {
1177
+ channelsInfo.push({
1178
+ name: channel.originalName || this.extractChannelName(fullName),
1179
+ fullName: fullName,
1180
+ subscribed: channel.subscribed,
1181
+ state: channel.subscriptionState || (channel.subscribed ? SubscriptionStates.SUBSCRIBED : SubscriptionStates.PENDING),
1182
+ type: channel.getType()
1183
+ });
1184
+ }
1185
+
1186
+ return channelsInfo;
1187
+ }
796
1188
  }
797
1189
 
798
1190
  var PushlerClient$1 = PushlerClient;
@@ -802,11 +1194,12 @@ var PushlerClient$1 = PushlerClient;
802
1194
  * Используется на сервере Node.js для отправки событий в каналы
803
1195
  */
804
1196
  class PushlerServer {
1197
+ static API_URL = 'https://api.pushler.ru';
1198
+
805
1199
  /**
806
1200
  * @param {Object} options - Параметры клиента
807
1201
  * @param {string} options.appKey - Ключ приложения (key_xxx)
808
1202
  * @param {string} options.appSecret - Секрет приложения (secret_xxx)
809
- * @param {string} [options.apiUrl='http://localhost:8000/api'] - URL API сервера
810
1203
  * @param {number} [options.timeout=10000] - Таймаут запроса в мс
811
1204
  */
812
1205
  constructor(options = {}) {
@@ -819,7 +1212,6 @@ class PushlerServer {
819
1212
 
820
1213
  this.appKey = options.appKey;
821
1214
  this.appSecret = options.appSecret;
822
- this.apiUrl = (options.apiUrl || 'http://localhost:8000/api').replace(/\/$/, '');
823
1215
  this.timeout = options.timeout || 10000;
824
1216
  }
825
1217
 
@@ -1103,7 +1495,7 @@ class PushlerServer {
1103
1495
  * @returns {Promise<Object>} Результат запроса
1104
1496
  */
1105
1497
  async makeSignedRequest(method, endpoint, data = null) {
1106
- const url = this.apiUrl + endpoint;
1498
+ const url = PushlerServer.API_URL + endpoint;
1107
1499
  const timestamp = Math.floor(Date.now() / 1000);
1108
1500
  const body = data ? JSON.stringify(data) : '';
1109
1501
  const signature = this.generateApiSignature(body, timestamp);
@@ -1151,7 +1543,7 @@ class PushlerServer {
1151
1543
  * @returns {Promise<Object>} Результат запроса
1152
1544
  */
1153
1545
  async makeHttpRequest(method, endpoint, data = null) {
1154
- const url = this.apiUrl + endpoint;
1546
+ const url = PushlerServer.API_URL + endpoint;
1155
1547
 
1156
1548
  const headers = {
1157
1549
  'Content-Type': 'application/json',
@@ -1192,7 +1584,7 @@ class PushlerServer {
1192
1584
  * @returns {string}
1193
1585
  */
1194
1586
  getApiUrl() {
1195
- return this.apiUrl;
1587
+ return PushlerServer.API_URL;
1196
1588
  }
1197
1589
  }
1198
1590
 
@@ -1227,8 +1619,11 @@ const Pushler = {
1227
1619
  Channel: Channel$1,
1228
1620
  ChannelTypes,
1229
1621
  ConnectionStates,
1622
+ SubscriptionStates,
1623
+ ErrorCodes,
1624
+ Events,
1230
1625
  generateSocketId
1231
1626
  };
1232
1627
 
1233
- export { Channel$1 as Channel, ChannelTypes, ConnectionStates, Pushler, PushlerClient$1 as PushlerClient, PushlerServer$1 as PushlerServer, Pushler as default, generateSocketId };
1628
+ export { Channel$1 as Channel, ChannelTypes, ConnectionStates, ErrorCodes, Events, Pushler, PushlerClient$1 as PushlerClient, PushlerServer$1 as PushlerServer, SubscriptionStates, Pushler as default, generateSocketId };
1234
1629
  //# sourceMappingURL=pushler-ru.esm.js.map