@pushler/js 1.0.3 → 1.1.1

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/README.md CHANGED
@@ -23,8 +23,7 @@ import Pushler from '@pushler/js';
23
23
 
24
24
  // Создание клиента
25
25
  const pushler = Pushler.Create({
26
- appKey: 'your-app-key',
27
- apiUrl: 'https://api.pushler.ru' // URL вашего бэкенда для авторизации каналов
26
+ appKey: 'your-app-key'
28
27
  });
29
28
 
30
29
  // Обработка событий подключения
@@ -57,8 +56,7 @@ import Pushler from '@pushler/js';
57
56
  // Создание серверного клиента
58
57
  const server = Pushler.Server({
59
58
  appKey: 'your-app-key',
60
- appSecret: 'your-app-secret',
61
- apiUrl: 'https://api.pushler.ru'
59
+ appSecret: 'your-app-secret'
62
60
  });
63
61
 
64
62
  // Отправка сообщения в канал
@@ -84,19 +82,44 @@ await server.trigger(
84
82
  ```javascript
85
83
  const pushler = Pushler.Create({
86
84
  appKey: 'your-app-key', // Обязательно
87
- apiUrl: 'https://api.pushler.ru', // URL вашего бэкенда для авторизации каналов
88
85
  authEndpoint: '/pushler/auth', // Путь для авторизации приватных каналов
89
86
  autoConnect: true, // Автоподключение (по умолчанию: true)
90
87
  reconnectDelay: 1000, // Задержка переподключения (мс)
91
- maxReconnectAttempts: 5 // Максимум попыток переподключения
88
+ maxReconnectAttempts: 5, // Максимум попыток переподключения
89
+
90
+ // Кастомные заголовки авторизации (опционально)
91
+ authHeaders: {
92
+ 'Authorization': 'Bearer your-token'
93
+ },
94
+
95
+ // Или динамические заголовки
96
+ getAuthHeaders: () => ({
97
+ 'Authorization': `Bearer ${getToken()}`
98
+ })
92
99
  });
93
100
  ```
94
101
 
102
+ #### Promise-based подключение
103
+
104
+ ```javascript
105
+ // connect() возвращает Promise
106
+ const { socketId } = await pushler.connect();
107
+ console.log('Подключено! Socket ID:', socketId);
108
+
109
+ // Или с таймаутом
110
+ try {
111
+ await pushler.waitForConnection(10000); // 10 секунд
112
+ } catch (error) {
113
+ console.error('Не удалось подключиться:', error.message);
114
+ }
115
+ ```
116
+
95
117
  #### Методы
96
118
 
97
119
  | Метод | Описание |
98
120
  |-------|----------|
99
- | `connect()` | Подключиться к серверу |
121
+ | `connect()` | Подключиться к серверу (возвращает Promise) |
122
+ | `waitForConnection(timeout?)` | Ожидание подключения с таймаутом |
100
123
  | `disconnect()` | Отключиться от сервера |
101
124
  | `subscribe(channelName, options?)` | Подписаться на канал |
102
125
  | `unsubscribe(channelName)` | Отписаться от канала |
@@ -134,7 +157,6 @@ const pushler = Pushler.Create({
134
157
  const server = Pushler.Server({
135
158
  appKey: 'your-app-key', // Обязательно
136
159
  appSecret: 'your-app-secret', // Обязательно
137
- apiUrl: 'https://api.pushler.ru', // URL API
138
160
  timeout: 10000 // Таймаут запросов (мс)
139
161
  });
140
162
  ```
@@ -207,6 +207,8 @@ class PushlerClient {
207
207
  this.wsUrl = options.wsUrl || `wss://ws.pushler.ru/app/${this.appKey}`;
208
208
  this.apiUrl = options.apiUrl;
209
209
  this.authEndpoint = options.authEndpoint || '/pushler/auth'; // Путь для авторизации каналов
210
+ this.authHeaders = options.authHeaders || null; // Кастомные заголовки для авторизации
211
+ this.getAuthHeaders = options.getAuthHeaders || null; // Функция для динамических заголовков
210
212
  this.autoConnect = options.autoConnect !== false;
211
213
  this.reconnectDelay = options.reconnectDelay || 1000;
212
214
  this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
@@ -221,6 +223,11 @@ class PushlerClient {
221
223
  this.reconnectTimer = null;
222
224
  this.authTimeout = null;
223
225
 
226
+ // Promise для ожидания подключения
227
+ this.connectionPromise = null;
228
+ this.connectionResolve = null;
229
+ this.connectionReject = null;
230
+
224
231
  // Автоматическое подключение
225
232
  if (this.autoConnect) {
226
233
  this.connect();
@@ -256,13 +263,25 @@ class PushlerClient {
256
263
 
257
264
  /**
258
265
  * Подключение к WebSocket серверу
266
+ * @returns {Promise} Promise, который резолвится при успешном подключении
259
267
  */
260
268
  connect() {
261
- if (this.connectionState === ConnectionStates.CONNECTED ||
262
- this.connectionState === ConnectionStates.CONNECTING) {
263
- return;
269
+ // Если уже подключены - возвращаем резолвленный Promise
270
+ if (this.connectionState === ConnectionStates.CONNECTED) {
271
+ return Promise.resolve({ socketId: this.socketId });
272
+ }
273
+
274
+ // Если уже идёт подключение - возвращаем существующий Promise
275
+ if (this.connectionState === ConnectionStates.CONNECTING && this.connectionPromise) {
276
+ return this.connectionPromise;
264
277
  }
265
278
 
279
+ // Создаём новый Promise для ожидания подключения
280
+ this.connectionPromise = new Promise((resolve, reject) => {
281
+ this.connectionResolve = resolve;
282
+ this.connectionReject = reject;
283
+ });
284
+
266
285
  this.connectionState = ConnectionStates.CONNECTING;
267
286
  this.emit(Events.CONNECTION_STATE_CHANGED, this.connectionState);
268
287
 
@@ -277,7 +296,66 @@ class PushlerClient {
277
296
  } catch (error) {
278
297
  console.error('PushlerClient: Error creating WebSocket connection:', error);
279
298
  this.handleConnectionError(error);
299
+ if (this.connectionReject) {
300
+ this.connectionReject(error);
301
+ this.connectionPromise = null;
302
+ this.connectionResolve = null;
303
+ this.connectionReject = null;
304
+ }
280
305
  }
306
+
307
+ return this.connectionPromise;
308
+ }
309
+
310
+ /**
311
+ * Ожидание установления соединения
312
+ * @param {number} timeout - Таймаут в мс (по умолчанию 10000)
313
+ * @returns {Promise} Promise с socketId
314
+ */
315
+ waitForConnection(timeout = 10000) {
316
+ // Если уже подключены
317
+ if (this.connectionState === ConnectionStates.CONNECTED && this.socketId) {
318
+ return Promise.resolve({ socketId: this.socketId });
319
+ }
320
+
321
+ // Если есть активный Promise подключения
322
+ if (this.connectionPromise) {
323
+ return Promise.race([
324
+ this.connectionPromise,
325
+ new Promise((_, reject) =>
326
+ setTimeout(() => reject(new Error('Connection timeout')), timeout)
327
+ )
328
+ ]);
329
+ }
330
+
331
+ // Если не подключаемся - начинаем подключение
332
+ if (this.connectionState === ConnectionStates.DISCONNECTED) {
333
+ return this.connect();
334
+ }
335
+
336
+ // Для других состояний создаём Promise, который ждёт connected события
337
+ return new Promise((resolve, reject) => {
338
+ const timeoutId = setTimeout(() => {
339
+ this.off(Events.CONNECTED, onConnected);
340
+ this.off(Events.ERROR, onError);
341
+ reject(new Error('Connection timeout'));
342
+ }, timeout);
343
+
344
+ const onConnected = (data) => {
345
+ clearTimeout(timeoutId);
346
+ this.off(Events.ERROR, onError);
347
+ resolve(data);
348
+ };
349
+
350
+ const onError = (err) => {
351
+ clearTimeout(timeoutId);
352
+ this.off(Events.CONNECTED, onConnected);
353
+ reject(err);
354
+ };
355
+
356
+ this.on(Events.CONNECTED, onConnected);
357
+ this.on(Events.ERROR, onError);
358
+ });
281
359
  }
282
360
 
283
361
  /**
@@ -367,6 +445,14 @@ class PushlerClient {
367
445
 
368
446
  console.log('Connection established with socket ID:', this.socketId);
369
447
 
448
+ // Резолвим Promise подключения
449
+ if (this.connectionResolve) {
450
+ this.connectionResolve({ socketId: this.socketId });
451
+ this.connectionPromise = null;
452
+ this.connectionResolve = null;
453
+ this.connectionReject = null;
454
+ }
455
+
370
456
  // Переподписываемся на все каналы после переподключения
371
457
  this.resubscribeAllChannels();
372
458
 
@@ -543,6 +629,24 @@ class PushlerClient {
543
629
  }
544
630
  }
545
631
 
632
+ /**
633
+ * Получение заголовков для авторизации
634
+ */
635
+ getAuthRequestHeaders() {
636
+ const headers = { 'Content-Type': 'application/json' };
637
+
638
+ // Динамические заголовки имеют приоритет
639
+ if (typeof this.getAuthHeaders === 'function') {
640
+ const dynamicHeaders = this.getAuthHeaders();
641
+ Object.assign(headers, dynamicHeaders);
642
+ } else if (this.authHeaders) {
643
+ // Статические заголовки
644
+ Object.assign(headers, this.authHeaders);
645
+ }
646
+
647
+ return headers;
648
+ }
649
+
546
650
  /**
547
651
  * Получение подписи с бэкенда
548
652
  */
@@ -552,11 +656,15 @@ class PushlerClient {
552
656
  ? this.authEndpoint
553
657
  : `${this.apiUrl}${this.authEndpoint}`;
554
658
 
659
+ const headers = this.getAuthRequestHeaders();
660
+ const hasAuthHeaders = this.authHeaders || this.getAuthHeaders;
661
+
555
662
  const response = await fetch(authUrl, {
556
663
  method: 'POST',
557
- headers: {
558
- 'Content-Type': 'application/json',
559
- },
664
+ headers,
665
+ // Если есть authHeaders, не используем credentials (JWT режим)
666
+ // Если нет — используем cookies
667
+ ...(hasAuthHeaders ? {} : { credentials: 'include' }),
560
668
  body: JSON.stringify({
561
669
  channel_name: channelName,
562
670
  socket_id: socketId,
@@ -566,7 +674,7 @@ class PushlerClient {
566
674
  });
567
675
 
568
676
  if (!response.ok) {
569
- const error = await response.json();
677
+ const error = await response.json().catch(() => ({ error: 'Auth request failed' }));
570
678
  throw new Error(error.error || 'Failed to get channel signature');
571
679
  }
572
680
 
@@ -699,6 +807,14 @@ class PushlerClient {
699
807
 
700
808
  console.error('PushlerClient connection error:', fullErrorMessage);
701
809
 
810
+ // Реджектим Promise подключения
811
+ if (this.connectionReject) {
812
+ this.connectionReject(new Error(fullErrorMessage));
813
+ this.connectionPromise = null;
814
+ this.connectionResolve = null;
815
+ this.connectionReject = null;
816
+ }
817
+
702
818
  this.emit(Events.ERROR, {
703
819
  code: ErrorCodes.CONNECTION_FAILED,
704
820
  message: fullErrorMessage,
@@ -806,11 +922,12 @@ var PushlerClient$1 = PushlerClient;
806
922
  * Используется на сервере Node.js для отправки событий в каналы
807
923
  */
808
924
  class PushlerServer {
925
+ static API_URL = 'https://api.pushler.ru';
926
+
809
927
  /**
810
928
  * @param {Object} options - Параметры клиента
811
929
  * @param {string} options.appKey - Ключ приложения (key_xxx)
812
930
  * @param {string} options.appSecret - Секрет приложения (secret_xxx)
813
- * @param {string} [options.apiUrl='http://localhost:8000/api'] - URL API сервера
814
931
  * @param {number} [options.timeout=10000] - Таймаут запроса в мс
815
932
  */
816
933
  constructor(options = {}) {
@@ -823,7 +940,6 @@ class PushlerServer {
823
940
 
824
941
  this.appKey = options.appKey;
825
942
  this.appSecret = options.appSecret;
826
- this.apiUrl = (options.apiUrl || 'http://localhost:8000/api').replace(/\/$/, '');
827
943
  this.timeout = options.timeout || 10000;
828
944
  }
829
945
 
@@ -1107,7 +1223,7 @@ class PushlerServer {
1107
1223
  * @returns {Promise<Object>} Результат запроса
1108
1224
  */
1109
1225
  async makeSignedRequest(method, endpoint, data = null) {
1110
- const url = this.apiUrl + endpoint;
1226
+ const url = PushlerServer.API_URL + endpoint;
1111
1227
  const timestamp = Math.floor(Date.now() / 1000);
1112
1228
  const body = data ? JSON.stringify(data) : '';
1113
1229
  const signature = this.generateApiSignature(body, timestamp);
@@ -1155,7 +1271,7 @@ class PushlerServer {
1155
1271
  * @returns {Promise<Object>} Результат запроса
1156
1272
  */
1157
1273
  async makeHttpRequest(method, endpoint, data = null) {
1158
- const url = this.apiUrl + endpoint;
1274
+ const url = PushlerServer.API_URL + endpoint;
1159
1275
 
1160
1276
  const headers = {
1161
1277
  'Content-Type': 'application/json',
@@ -1196,7 +1312,7 @@ class PushlerServer {
1196
1312
  * @returns {string}
1197
1313
  */
1198
1314
  getApiUrl() {
1199
- return this.apiUrl;
1315
+ return PushlerServer.API_URL;
1200
1316
  }
1201
1317
  }
1202
1318