@pushler/js 1.0.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.
- package/README.md +243 -0
- package/dist/pushler-ru.esm.js +1233 -0
- package/dist/pushler-ru.esm.js.map +1 -0
- package/dist/pushler-ru.js +1253 -0
- package/dist/pushler-ru.js.map +1 -0
- package/dist/pushler-ru.min.js +2 -0
- package/dist/pushler-ru.min.js.map +1 -0
- package/package.json +48 -0
- package/src/Channel.js +142 -0
- package/src/PushlerClient.js +605 -0
- package/src/PushlerServer.js +403 -0
- package/src/constants.js +51 -0
- package/src/index.js +24 -0
- package/src/utils.js +74 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { createHmac } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Серверный SDK для отправки сообщений через Pushler.ru API
|
|
5
|
+
* Используется на сервере Node.js для отправки событий в каналы
|
|
6
|
+
*/
|
|
7
|
+
class PushlerServer {
|
|
8
|
+
/**
|
|
9
|
+
* @param {Object} options - Параметры клиента
|
|
10
|
+
* @param {string} options.appKey - Ключ приложения (key_xxx)
|
|
11
|
+
* @param {string} options.appSecret - Секрет приложения (secret_xxx)
|
|
12
|
+
* @param {string} [options.apiUrl='http://localhost:8000/api'] - URL API сервера
|
|
13
|
+
* @param {number} [options.timeout=30000] - Таймаут запроса в мс
|
|
14
|
+
*/
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
if (!options.appKey) {
|
|
17
|
+
throw new Error('appKey is required');
|
|
18
|
+
}
|
|
19
|
+
if (!options.appSecret) {
|
|
20
|
+
throw new Error('appSecret is required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.appKey = options.appKey;
|
|
24
|
+
this.appSecret = options.appSecret;
|
|
25
|
+
this.apiUrl = (options.apiUrl || 'http://localhost:8000/api').replace(/\/$/, '');
|
|
26
|
+
this.timeout = options.timeout || 30000;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Формирование полного имени канала с префиксом appKey
|
|
31
|
+
* Формат: {appKey}:{channelName}
|
|
32
|
+
* @param {string} channelName - Оригинальное имя канала
|
|
33
|
+
* @returns {string} Полное имя канала
|
|
34
|
+
*/
|
|
35
|
+
formatChannelName(channelName) {
|
|
36
|
+
// Если канал уже содержит appKey, возвращаем как есть
|
|
37
|
+
if (channelName.startsWith(this.appKey + ':')) {
|
|
38
|
+
return channelName;
|
|
39
|
+
}
|
|
40
|
+
return `${this.appKey}:${channelName}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Извлечение оригинального имени канала (без appKey)
|
|
45
|
+
* @param {string} fullChannelName - Полное имя канала
|
|
46
|
+
* @returns {string} Оригинальное имя канала
|
|
47
|
+
*/
|
|
48
|
+
extractChannelName(fullChannelName) {
|
|
49
|
+
const prefix = this.appKey + ':';
|
|
50
|
+
if (fullChannelName.startsWith(prefix)) {
|
|
51
|
+
return fullChannelName.substring(prefix.length);
|
|
52
|
+
}
|
|
53
|
+
return fullChannelName;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Генерация подписи для API запроса
|
|
58
|
+
*
|
|
59
|
+
* @param {string} body - JSON тело запроса
|
|
60
|
+
* @param {number} timestamp - Unix timestamp
|
|
61
|
+
* @returns {string} HMAC-SHA256 подпись
|
|
62
|
+
*/
|
|
63
|
+
generateApiSignature(body, timestamp) {
|
|
64
|
+
const signatureString = body + timestamp;
|
|
65
|
+
return createHmac('sha256', this.appSecret)
|
|
66
|
+
.update(signatureString)
|
|
67
|
+
.digest('hex');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Генерация подписи для авторизации канала
|
|
72
|
+
*
|
|
73
|
+
* @param {string} channelName - Название канала (полное с appKey)
|
|
74
|
+
* @param {string} socketId - ID сокета
|
|
75
|
+
* @returns {string} HMAC-SHA256 подпись
|
|
76
|
+
*/
|
|
77
|
+
generateChannelSignature(channelName, socketId) {
|
|
78
|
+
// Подпись генерируется для полного имени канала
|
|
79
|
+
const fullChannelName = this.formatChannelName(channelName);
|
|
80
|
+
const signatureString = socketId + ':' + fullChannelName;
|
|
81
|
+
return createHmac('sha256', this.appSecret)
|
|
82
|
+
.update(signatureString)
|
|
83
|
+
.digest('hex');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Авторизация приватного канала
|
|
88
|
+
*
|
|
89
|
+
* @param {string} channelName - Название канала (appKey будет добавлен автоматически)
|
|
90
|
+
* @param {string} socketId - ID сокета
|
|
91
|
+
* @returns {Object} Результат авторизации с полным именем канала
|
|
92
|
+
*/
|
|
93
|
+
authorizeChannel(channelName, socketId) {
|
|
94
|
+
const fullChannelName = this.formatChannelName(channelName);
|
|
95
|
+
const signature = this.generateChannelSignature(fullChannelName, socketId);
|
|
96
|
+
return {
|
|
97
|
+
auth: this.appKey + ':' + signature,
|
|
98
|
+
channel: fullChannelName
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Авторизация presence канала
|
|
104
|
+
*
|
|
105
|
+
* @param {string} channelName - Название канала (appKey будет добавлен автоматически)
|
|
106
|
+
* @param {string} socketId - ID сокета
|
|
107
|
+
* @param {Object} userData - Данные пользователя
|
|
108
|
+
* @param {string|number} userData.user_id - ID пользователя (обязательно)
|
|
109
|
+
* @param {Object} [userData.user_info] - Дополнительная информация о пользователе
|
|
110
|
+
* @returns {Object} Результат авторизации с полным именем канала
|
|
111
|
+
*/
|
|
112
|
+
authorizePresenceChannel(channelName, socketId, userData) {
|
|
113
|
+
if (!userData || !userData.user_id) {
|
|
114
|
+
throw new Error('user_id is required for presence channels');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const fullChannelName = this.formatChannelName(channelName);
|
|
118
|
+
const signature = this.generateChannelSignature(fullChannelName, socketId);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
auth: this.appKey + ':' + signature,
|
|
122
|
+
channel: fullChannelName,
|
|
123
|
+
channel_data: JSON.stringify({
|
|
124
|
+
user_id: userData.user_id,
|
|
125
|
+
user_info: userData.user_info || {}
|
|
126
|
+
})
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Аутентификация пользователя для WebSocket соединения
|
|
132
|
+
*
|
|
133
|
+
* @param {string} socketId - ID сокета
|
|
134
|
+
* @param {Object} userData - Данные пользователя
|
|
135
|
+
* @returns {Object} Результат аутентификации
|
|
136
|
+
*/
|
|
137
|
+
authenticateUser(socketId, userData) {
|
|
138
|
+
if (!userData || !userData.user_id) {
|
|
139
|
+
throw new Error('user_id is required for user authentication');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const signature = this.generateChannelSignature('user-' + userData.user_id, socketId);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
auth: this.appKey + ':' + signature,
|
|
146
|
+
user_data: JSON.stringify(userData)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Отправка события в канал
|
|
152
|
+
*
|
|
153
|
+
* @param {string|string[]} channels - Канал или массив каналов (appKey будет добавлен автоматически)
|
|
154
|
+
* @param {string} event - Название события
|
|
155
|
+
* @param {Object} data - Данные события
|
|
156
|
+
* @param {string} [socketId] - ID сокета (для исключения отправителя)
|
|
157
|
+
* @returns {Promise<Object>} Результат отправки
|
|
158
|
+
*/
|
|
159
|
+
async trigger(channels, event, data, socketId = null) {
|
|
160
|
+
const channelList = Array.isArray(channels) ? channels : [channels];
|
|
161
|
+
|
|
162
|
+
// Добавляем appKey ко всем каналам
|
|
163
|
+
const formattedChannels = channelList.map(ch => this.formatChannelName(ch));
|
|
164
|
+
|
|
165
|
+
// Для одного канала используем /messages/send
|
|
166
|
+
if (formattedChannels.length === 1) {
|
|
167
|
+
const payload = {
|
|
168
|
+
channel: formattedChannels[0],
|
|
169
|
+
event,
|
|
170
|
+
data
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (socketId) {
|
|
174
|
+
payload.socket_id = socketId;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return this.makeSignedRequest('POST', '/messages/send', payload);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Для нескольких каналов используем /messages/broadcast
|
|
181
|
+
const payload = {
|
|
182
|
+
channels: formattedChannels,
|
|
183
|
+
event,
|
|
184
|
+
data
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
if (socketId) {
|
|
188
|
+
payload.socket_id = socketId;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return this.makeSignedRequest('POST', '/messages/broadcast', payload);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Отправка события в несколько каналов одновременно
|
|
196
|
+
*
|
|
197
|
+
* @param {string[]} channels - Массив каналов
|
|
198
|
+
* @param {string} event - Название события
|
|
199
|
+
* @param {Object} data - Данные события
|
|
200
|
+
* @param {string} [socketId] - ID сокета (для исключения отправителя)
|
|
201
|
+
* @returns {Promise<Object>} Результат отправки
|
|
202
|
+
*/
|
|
203
|
+
async triggerBatch(channels, event, data, socketId = null) {
|
|
204
|
+
return this.trigger(channels, event, data, socketId);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Получение статуса сообщения
|
|
209
|
+
*
|
|
210
|
+
* @param {string|number} messageId - ID сообщения
|
|
211
|
+
* @returns {Promise<Object>} Статус сообщения
|
|
212
|
+
*/
|
|
213
|
+
async getMessageStatus(messageId) {
|
|
214
|
+
return this.makeSignedRequest('GET', `/messages/${messageId}/status`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Получение списка сообщений
|
|
219
|
+
*
|
|
220
|
+
* @param {Object} [params] - Параметры запроса
|
|
221
|
+
* @param {string} [params.status] - Фильтр по статусу
|
|
222
|
+
* @param {number} [params.per_page] - Количество на страницу
|
|
223
|
+
* @returns {Promise<Object>} Список сообщений
|
|
224
|
+
*/
|
|
225
|
+
async getMessages(params = {}) {
|
|
226
|
+
const query = new URLSearchParams(params).toString();
|
|
227
|
+
const endpoint = '/messages' + (query ? '?' + query : '');
|
|
228
|
+
return this.makeSignedRequest('GET', endpoint);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Проверка валидности приложения
|
|
233
|
+
*
|
|
234
|
+
* @returns {Promise<Object>} Информация о приложении
|
|
235
|
+
*/
|
|
236
|
+
async validateApplication() {
|
|
237
|
+
const response = await this.makeHttpRequest('GET', `/applications/validate/${this.appKey}`);
|
|
238
|
+
|
|
239
|
+
if (!response.id) {
|
|
240
|
+
throw new Error(response.error || 'Application validation failed');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return response;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Получение информации о приложении
|
|
248
|
+
*
|
|
249
|
+
* @returns {Promise<Object>} Информация о приложении
|
|
250
|
+
*/
|
|
251
|
+
async getApplicationInfo() {
|
|
252
|
+
return this.validateApplication();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Валидация вебхука
|
|
257
|
+
*
|
|
258
|
+
* @param {Object} headers - Заголовки запроса
|
|
259
|
+
* @param {string} body - Тело запроса
|
|
260
|
+
* @returns {Object} Данные вебхука
|
|
261
|
+
* @throws {Error} При невалидной подписи
|
|
262
|
+
*/
|
|
263
|
+
verifyWebhook(headers, body) {
|
|
264
|
+
const signature = headers['X-Pushler-Signature'] || headers['x-pushler-signature'] || '';
|
|
265
|
+
|
|
266
|
+
if (!signature) {
|
|
267
|
+
throw new Error('Missing X-Pushler-Signature header');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const expectedSignature = 'sha256=' + createHmac('sha256', this.appSecret)
|
|
271
|
+
.update(body)
|
|
272
|
+
.digest('hex');
|
|
273
|
+
|
|
274
|
+
// Timing-safe сравнение
|
|
275
|
+
if (!this.timingSafeEqual(expectedSignature, signature)) {
|
|
276
|
+
throw new Error('Invalid webhook signature');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const data = JSON.parse(body);
|
|
280
|
+
return data;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Timing-safe сравнение строк
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
timingSafeEqual(a, b) {
|
|
288
|
+
if (a.length !== b.length) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let result = 0;
|
|
293
|
+
for (let i = 0; i < a.length; i++) {
|
|
294
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
295
|
+
}
|
|
296
|
+
return result === 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Выполнение подписанного HTTP запроса
|
|
301
|
+
*
|
|
302
|
+
* @private
|
|
303
|
+
* @param {string} method - HTTP метод
|
|
304
|
+
* @param {string} endpoint - API endpoint
|
|
305
|
+
* @param {Object} [data] - Данные запроса
|
|
306
|
+
* @returns {Promise<Object>} Результат запроса
|
|
307
|
+
*/
|
|
308
|
+
async makeSignedRequest(method, endpoint, data = null) {
|
|
309
|
+
const url = this.apiUrl + endpoint;
|
|
310
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
311
|
+
const body = data ? JSON.stringify(data) : '';
|
|
312
|
+
const signature = this.generateApiSignature(body, timestamp);
|
|
313
|
+
|
|
314
|
+
const headers = {
|
|
315
|
+
'Content-Type': 'application/json',
|
|
316
|
+
'Accept': 'application/json',
|
|
317
|
+
'User-Agent': 'PushlerRu-JS-SDK/1.0.0',
|
|
318
|
+
'X-Pushler-Key': this.appKey,
|
|
319
|
+
'X-Pushler-Signature': signature,
|
|
320
|
+
'X-Pushler-Timestamp': String(timestamp)
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const options = {
|
|
324
|
+
method,
|
|
325
|
+
headers,
|
|
326
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
if (method === 'POST' && body) {
|
|
330
|
+
options.body = body;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const response = await fetch(url, options);
|
|
334
|
+
const result = await response.json();
|
|
335
|
+
|
|
336
|
+
if (!response.ok) {
|
|
337
|
+
const message = result.message || `HTTP request failed with status: ${response.status}`;
|
|
338
|
+
const error = new Error(message);
|
|
339
|
+
error.statusCode = response.status;
|
|
340
|
+
error.response = result;
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Выполнение обычного HTTP запроса (без подписи)
|
|
349
|
+
*
|
|
350
|
+
* @private
|
|
351
|
+
* @param {string} method - HTTP метод
|
|
352
|
+
* @param {string} endpoint - API endpoint
|
|
353
|
+
* @param {Object} [data] - Данные запроса
|
|
354
|
+
* @returns {Promise<Object>} Результат запроса
|
|
355
|
+
*/
|
|
356
|
+
async makeHttpRequest(method, endpoint, data = null) {
|
|
357
|
+
const url = this.apiUrl + endpoint;
|
|
358
|
+
|
|
359
|
+
const headers = {
|
|
360
|
+
'Content-Type': 'application/json',
|
|
361
|
+
'Accept': 'application/json',
|
|
362
|
+
'User-Agent': 'PushlerRu-JS-SDK/1.0.0'
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const options = {
|
|
366
|
+
method,
|
|
367
|
+
headers,
|
|
368
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
if (method === 'POST' && data) {
|
|
372
|
+
options.body = JSON.stringify(data);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const response = await fetch(url, options);
|
|
376
|
+
const result = await response.json();
|
|
377
|
+
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
throw new Error(`HTTP request failed with status: ${response.status}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Получение App Key
|
|
387
|
+
* @returns {string}
|
|
388
|
+
*/
|
|
389
|
+
getAppKey() {
|
|
390
|
+
return this.appKey;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Получение API URL
|
|
395
|
+
* @returns {string}
|
|
396
|
+
*/
|
|
397
|
+
getApiUrl() {
|
|
398
|
+
return this.apiUrl;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export default PushlerServer;
|
|
403
|
+
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Типы каналов
|
|
3
|
+
*/
|
|
4
|
+
export const ChannelTypes = {
|
|
5
|
+
PUBLIC: 'public',
|
|
6
|
+
PRIVATE: 'private',
|
|
7
|
+
PRESENCE: 'presence'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Состояния подключения
|
|
12
|
+
*/
|
|
13
|
+
export const ConnectionStates = {
|
|
14
|
+
CONNECTING: 'connecting',
|
|
15
|
+
CONNECTED: 'connected',
|
|
16
|
+
DISCONNECTED: 'disconnected',
|
|
17
|
+
RECONNECTING: 'reconnecting',
|
|
18
|
+
FAILED: 'failed'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* События WebSocket
|
|
23
|
+
*/
|
|
24
|
+
export const Events = {
|
|
25
|
+
// События подключения
|
|
26
|
+
CONNECTION_STATE_CHANGED: 'connection_state_changed',
|
|
27
|
+
CONNECTED: 'connected',
|
|
28
|
+
DISCONNECTED: 'disconnected',
|
|
29
|
+
ERROR: 'error',
|
|
30
|
+
|
|
31
|
+
// События каналов
|
|
32
|
+
CHANNEL_SUBSCRIBED: 'channel_subscribed',
|
|
33
|
+
CHANNEL_UNSUBSCRIBED: 'channel_unsubscribed',
|
|
34
|
+
CHANNEL_AUTH_SUCCESS: 'pushler:auth_success',
|
|
35
|
+
CHANNEL_AUTH_ERROR: 'pushler:auth_error',
|
|
36
|
+
|
|
37
|
+
// События сообщений
|
|
38
|
+
MESSAGE_RECEIVED: 'message_received'
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Коды ошибок
|
|
43
|
+
*/
|
|
44
|
+
export const ErrorCodes = {
|
|
45
|
+
INVALID_APP_KEY: 'INVALID_APP_KEY',
|
|
46
|
+
INVALID_SIGNATURE: 'INVALID_SIGNATURE',
|
|
47
|
+
AUTHENTICATION_FAILED: 'AUTHENTICATION_FAILED',
|
|
48
|
+
CHANNEL_ACCESS_DENIED: 'CHANNEL_ACCESS_DENIED',
|
|
49
|
+
CONNECTION_FAILED: 'CONNECTION_FAILED',
|
|
50
|
+
AUTHENTICATION_TIMEOUT: 'AUTHENTICATION_TIMEOUT'
|
|
51
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import PushlerClient from './PushlerClient';
|
|
2
|
+
import PushlerServer from './PushlerServer';
|
|
3
|
+
import Channel from './Channel';
|
|
4
|
+
import { ChannelTypes, ConnectionStates } from './constants';
|
|
5
|
+
import { generateSocketId } from './utils';
|
|
6
|
+
|
|
7
|
+
// Создаем объект с методами для удобного API
|
|
8
|
+
const Pushler = {
|
|
9
|
+
// Клиентский SDK для браузера (WebSocket)
|
|
10
|
+
CreateClient: (options) => new PushlerClient(options),
|
|
11
|
+
Create: (options) => new PushlerClient(options), // Алиас для краткости
|
|
12
|
+
|
|
13
|
+
// Серверный SDK для Node.js (отправка сообщений через API)
|
|
14
|
+
CreateServer: (options) => new PushlerServer(options),
|
|
15
|
+
Server: (options) => new PushlerServer(options), // Алиас для краткости
|
|
16
|
+
|
|
17
|
+
Channel,
|
|
18
|
+
ChannelTypes,
|
|
19
|
+
ConnectionStates,
|
|
20
|
+
generateSocketId
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { PushlerClient, PushlerServer, Channel, ChannelTypes, ConnectionStates, generateSocketId, Pushler };
|
|
24
|
+
export default Pushler;
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Валидация данных канала
|
|
3
|
+
* @param {string} channelName - Название канала
|
|
4
|
+
* @param {string} channelType - Тип канала
|
|
5
|
+
* @param {Object} options - Опции канала
|
|
6
|
+
* @returns {Object} - Результат валидации
|
|
7
|
+
*/
|
|
8
|
+
export function validateChannelOptions(channelName, channelType, options) {
|
|
9
|
+
const errors = [];
|
|
10
|
+
|
|
11
|
+
if (!channelName) {
|
|
12
|
+
errors.push('Channel name is required');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (channelType === 'private' || channelType === 'presence') {
|
|
16
|
+
if (!options.signature && !options.autoAuth) {
|
|
17
|
+
errors.push('Signature or autoAuth is required for private/presence channels');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (channelType === 'presence') {
|
|
22
|
+
if (!options.user) {
|
|
23
|
+
errors.push('User data is required for presence channels');
|
|
24
|
+
} else if (!options.user.id) {
|
|
25
|
+
errors.push('User ID is required for presence channels');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
isValid: errors.length === 0,
|
|
31
|
+
errors
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Генерация уникального ID
|
|
37
|
+
* @returns {string} - Уникальный ID
|
|
38
|
+
*/
|
|
39
|
+
export function generateId() {
|
|
40
|
+
return Math.random().toString(36).substr(2, 9) + Date.now();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Генерация уникального ID сокета
|
|
45
|
+
* @returns {string} - Уникальный ID сокета
|
|
46
|
+
*/
|
|
47
|
+
export function generateSocketId() {
|
|
48
|
+
return Math.random().toString(36).substr(2, 9) + '.' + Math.floor(Math.random() * 1000000);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Проверка поддержки WebSocket в браузере
|
|
53
|
+
* @returns {boolean} - Поддерживается ли WebSocket
|
|
54
|
+
*/
|
|
55
|
+
export function isWebSocketSupported() {
|
|
56
|
+
return typeof WebSocket !== 'undefined';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Форматирование ошибки
|
|
61
|
+
* @param {Error|string} error - Ошибка
|
|
62
|
+
* @returns {Object} - Форматированная ошибка
|
|
63
|
+
*/
|
|
64
|
+
export function formatError(error) {
|
|
65
|
+
if (typeof error === 'string') {
|
|
66
|
+
return { message: error };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (error instanceof Error) {
|
|
70
|
+
return { message: error.message, stack: error.stack };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { message: 'Unknown error' };
|
|
74
|
+
}
|