@pushler/vue 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 ADDED
@@ -0,0 +1,499 @@
1
+ # Pushler Vue SDK
2
+
3
+ Реактивный Vue.js SDK для работы с Pushler.ru WebSocket сервером.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@pushler/vue.svg)](https://www.npmjs.com/package/@pushler/vue)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@pushler/vue.svg)](https://www.npmjs.com/package/@pushler/vue)
7
+ [![license](https://img.shields.io/npm/l/@pushler/vue.svg)](https://github.com/pushler/vue-sdk/blob/main/LICENSE)
8
+
9
+ ## Установка
10
+
11
+ ### npm / yarn / pnpm
12
+
13
+ ```bash
14
+ npm install @pushler/vue
15
+
16
+ # или
17
+ yarn add @pushler/vue
18
+
19
+ # или
20
+ pnpm add @pushler/vue
21
+ ```
22
+
23
+ ### CDN
24
+
25
+ ```html
26
+ <!-- Последняя версия -->
27
+ <script src="https://unpkg.com/@pushler/vue"></script>
28
+
29
+ <!-- Конкретная версия -->
30
+ <script src="https://unpkg.com/@pushler/vue@1.0.0/dist/pushler-vue.umd.min.js"></script>
31
+
32
+ <!-- jsDelivr -->
33
+ <script src="https://cdn.jsdelivr.net/npm/@pushler/vue"></script>
34
+ ```
35
+
36
+ ## Быстрый старт
37
+
38
+ ### Использование composable
39
+
40
+ ```vue
41
+ <script setup>
42
+ import { usePushler } from '@pushler/vue';
43
+
44
+ // Создаём экземпляр Pushler
45
+ const pushler = usePushler({
46
+ appKey: 'your-app-key',
47
+ wsUrl: 'wss://ws.pushler.ru/app',
48
+ autoConnect: true, // Автоматическое подключение
49
+ });
50
+
51
+ // Подписка на канал
52
+ const channel = pushler.subscribe('my-channel');
53
+
54
+ // Слушаем события
55
+ channel.on('message', (data) => {
56
+ console.log('Получено сообщение:', data);
57
+ });
58
+
59
+ channel.on('notification', (data) => {
60
+ console.log('Уведомление:', data);
61
+ });
62
+ </script>
63
+
64
+ <template>
65
+ <div>
66
+ <p>Статус: {{ pushler.connectionState }}</p>
67
+ <p v-if="pushler.socketId">Socket ID: {{ pushler.socketId }}</p>
68
+ <button @click="pushler.connect()" :disabled="pushler.isConnected.value">
69
+ Подключиться
70
+ </button>
71
+ <button @click="pushler.disconnect()" :disabled="!pushler.isConnected.value">
72
+ Отключиться
73
+ </button>
74
+ </div>
75
+ </template>
76
+ ```
77
+
78
+ ### Использование Vue Plugin
79
+
80
+ ```js
81
+ // main.js
82
+ import { createApp } from 'vue';
83
+ import { PushlerPlugin } from '@pushler/vue';
84
+ import App from './App.vue';
85
+
86
+ const app = createApp(App);
87
+
88
+ app.use(PushlerPlugin, {
89
+ appKey: 'your-app-key',
90
+ wsUrl: 'wss://ws.pushler.ru/app',
91
+ autoConnect: true,
92
+ });
93
+
94
+ app.mount('#app');
95
+ ```
96
+
97
+ Затем в любом компоненте:
98
+
99
+ ```vue
100
+ <script setup>
101
+ import { inject } from 'vue';
102
+ import { PushlerKey } from '@pushler/vue';
103
+
104
+ const pushler = inject(PushlerKey);
105
+
106
+ const channel = pushler.subscribe('notifications');
107
+ channel.on('new', (data) => {
108
+ console.log('Новое уведомление:', data);
109
+ });
110
+ </script>
111
+ ```
112
+
113
+ ## API Reference
114
+
115
+ ### `usePushler(options)`
116
+
117
+ Основной composable для работы с WebSocket подключением.
118
+
119
+ #### Опции
120
+
121
+ | Параметр | Тип | По умолчанию | Описание |
122
+ |----------|-----|--------------|----------|
123
+ | `appKey` | `string` | `''` | Ключ приложения Pushler |
124
+ | `wsUrl` | `string` | `'ws://localhost:8081/app'` | URL WebSocket сервера |
125
+ | `authEndpoint` | `string` | `'/pushler/auth'` | Эндпоинт для авторизации приватных каналов |
126
+ | `autoConnect` | `boolean` | `false` | Автоматическое подключение при инициализации |
127
+ | `reconnectDelay` | `number` | `1000` | Задержка между попытками переподключения (мс) |
128
+ | `maxReconnectAttempts` | `number` | `5` | Максимальное количество попыток переподключения |
129
+
130
+ #### Возвращаемые значения
131
+
132
+ ##### Реактивное состояние
133
+
134
+ ```js
135
+ const pushler = usePushler(options);
136
+
137
+ // Состояние подключения: 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'failed'
138
+ pushler.connectionState // Ref<string>
139
+
140
+ // ID сокета (после подключения)
141
+ pushler.socketId // Ref<string | null>
142
+
143
+ // Ошибка (если есть)
144
+ pushler.error // Ref<string | null>
145
+
146
+ // Количество попыток переподключения
147
+ pushler.reconnectAttempts // Ref<number>
148
+
149
+ // Вычисляемые свойства
150
+ pushler.isConnected // ComputedRef<boolean>
151
+ pushler.isConnecting // ComputedRef<boolean>
152
+ pushler.isReconnecting // ComputedRef<boolean>
153
+ ```
154
+
155
+ ##### Методы
156
+
157
+ ```js
158
+ // Подключение к серверу
159
+ pushler.connect(options?: { appKey?, wsUrl?, secret? })
160
+
161
+ // Отключение
162
+ pushler.disconnect()
163
+
164
+ // Подписка на канал
165
+ const channel = pushler.subscribe('channel-name', {
166
+ signature?: 'pre-signed-signature',
167
+ user?: { id: 1, name: 'User' } // для presence-каналов
168
+ })
169
+
170
+ // Отписка от канала
171
+ pushler.unsubscribe('channel-name')
172
+
173
+ // Получение канала
174
+ const channel = pushler.channel('channel-name')
175
+
176
+ // Получение списка каналов
177
+ const channels = pushler.getChannels()
178
+ // [{ name, fullName, type, subscribed }]
179
+
180
+ // Подписка на глобальные события
181
+ pushler.on('connected', (data) => console.log('Connected:', data.socketId))
182
+ pushler.on('message', (msg) => console.log('Message:', msg))
183
+ pushler.on('auth_error', (err) => console.error('Auth error:', err))
184
+
185
+ // Отписка от событий
186
+ pushler.off('connected', handler)
187
+ ```
188
+
189
+ ### Канал
190
+
191
+ ```js
192
+ const channel = pushler.subscribe('my-channel');
193
+
194
+ // Свойства канала
195
+ channel.name // Полное имя с appKey
196
+ channel.originalName // Оригинальное имя
197
+ channel.type // 'public' | 'private' | 'presence'
198
+ channel.subscribed // boolean
199
+
200
+ // Методы
201
+ channel.on('event-name', callback)
202
+ channel.off('event-name', callback)
203
+ channel.unsubscribe()
204
+ ```
205
+
206
+ ### Типы каналов
207
+
208
+ ```js
209
+ // Публичные каналы (без авторизации)
210
+ pushler.subscribe('public-channel')
211
+
212
+ // Приватные каналы (требуют авторизации через бэкенд)
213
+ pushler.subscribe('private-channel')
214
+ // SDK автоматически запросит подпись с authEndpoint
215
+
216
+ // Или можно передать предварительно полученную подпись
217
+ pushler.subscribe('private-channel', {
218
+ signature: 'hmac-signature-from-backend'
219
+ })
220
+
221
+ // Presence-каналы (информация о присутствии)
222
+ pushler.subscribe('presence-chat', {
223
+ user: {
224
+ id: 123,
225
+ name: 'Иван Петров',
226
+ avatar: 'https://...'
227
+ }
228
+ })
229
+ ```
230
+
231
+ ## Безопасность
232
+
233
+ ### ⚠️ Важно: Секретный ключ
234
+
235
+ **НИКОГДА не храните секретный ключ в клиентском коде!**
236
+
237
+ Секретный ключ (`secret`) должен храниться только на вашем бэкенде. Для приватных каналов SDK автоматически запрашивает подпись с вашего бэкенда через `authEndpoint`.
238
+
239
+ ### Схема авторизации приватных каналов
240
+
241
+ ```
242
+ ┌─────────────┐ 1. subscribe('private-chat') ┌─────────────────┐
243
+ │ Browser │ ──────────────────────────────────►│ Pushler WS │
244
+ │ (SDK) │ │ Server │
245
+ └──────┬──────┘ └────────┬────────┘
246
+ │ │
247
+ │ 2. POST /pushler/auth │
248
+ │ { channel, socket_id } │
249
+ ▼ │
250
+ ┌─────────────┐ 3. Проверка авторизации │
251
+ │ Ваш │ + генерация подписи (с secret) │
252
+ │ Backend │ ───────────────────────────────────────────►│
253
+ └─────────────┘ 4. signature │
254
+ ```
255
+
256
+ ### Пример бэкенда для авторизации
257
+
258
+ ```php
259
+ // Laravel пример
260
+ Route::post('/pushler/auth', function (Request $request) {
261
+ $user = auth()->user();
262
+
263
+ // Проверяем право доступа к каналу
264
+ $channelName = $request->channel_name;
265
+ if (!$user->canAccessChannel($channelName)) {
266
+ return response()->json(['error' => 'Forbidden'], 403);
267
+ }
268
+
269
+ // Генерируем подпись
270
+ $socketId = $request->socket_id;
271
+ $signature = hash_hmac('sha256', "{$socketId}:{$channelName}", env('PUSHLER_SECRET'));
272
+
273
+ return response()->json(['signature' => $signature]);
274
+ });
275
+ ```
276
+
277
+ ### `usePushlerChannel(pushler, channelName, options)`
278
+
279
+ Composable для работы с отдельным каналом.
280
+
281
+ ```vue
282
+ <script setup>
283
+ import { usePushler, usePushlerChannel } from '@pushler/vue';
284
+
285
+ const pushler = usePushler({ ... });
286
+ const { channel, isSubscribed, events, lastEvent, on, clearEvents } = usePushlerChannel(
287
+ pushler,
288
+ 'notifications',
289
+ {
290
+ autoSubscribe: true,
291
+ maxEvents: 50
292
+ }
293
+ );
294
+
295
+ on('new', (data) => {
296
+ console.log('Новое уведомление:', data);
297
+ });
298
+ </script>
299
+
300
+ <template>
301
+ <div>
302
+ <p>Подписан: {{ isSubscribed }}</p>
303
+ <p>Последнее событие: {{ lastEvent }}</p>
304
+ <ul>
305
+ <li v-for="event in events" :key="event.timestamp">
306
+ {{ event.event }}: {{ event.data }}
307
+ </li>
308
+ </ul>
309
+ <button @click="clearEvents">Очистить</button>
310
+ </div>
311
+ </template>
312
+ ```
313
+
314
+ ## Константы
315
+
316
+ ```js
317
+ import { ConnectionStates, ChannelTypes } from '@pushler/vue';
318
+
319
+ // Состояния подключения
320
+ ConnectionStates.CONNECTING // 'connecting'
321
+ ConnectionStates.CONNECTED // 'connected'
322
+ ConnectionStates.DISCONNECTED // 'disconnected'
323
+ ConnectionStates.RECONNECTING // 'reconnecting'
324
+ ConnectionStates.FAILED // 'failed'
325
+
326
+ // Типы каналов
327
+ ChannelTypes.PUBLIC // 'public'
328
+ ChannelTypes.PRIVATE // 'private'
329
+ ChannelTypes.PRESENCE // 'presence'
330
+ ```
331
+
332
+ ## Примеры
333
+
334
+ ### Чат в реальном времени
335
+
336
+ ```vue
337
+ <script setup>
338
+ import { ref } from 'vue';
339
+ import { usePushler } from '@pushler/vue';
340
+
341
+ const pushler = usePushler({
342
+ appKey: 'chat-app-key',
343
+ wsUrl: 'wss://ws.pushler.ru/app',
344
+ autoConnect: true,
345
+ });
346
+
347
+ const messages = ref([]);
348
+ const newMessage = ref('');
349
+
350
+ // Подписка на канал чата
351
+ const chatChannel = pushler.subscribe('chat-room-1');
352
+
353
+ chatChannel.on('message', (data) => {
354
+ messages.value.push(data);
355
+ });
356
+
357
+ chatChannel.on('user_joined', (data) => {
358
+ messages.value.push({
359
+ type: 'system',
360
+ text: `${data.name} присоединился к чату`
361
+ });
362
+ });
363
+
364
+ async function sendMessage() {
365
+ // Отправка через ваш API
366
+ await fetch('/api/chat/send', {
367
+ method: 'POST',
368
+ body: JSON.stringify({
369
+ channel: 'chat-room-1',
370
+ message: newMessage.value
371
+ })
372
+ });
373
+ newMessage.value = '';
374
+ }
375
+ </script>
376
+ ```
377
+
378
+ ### Уведомления
379
+
380
+ ```vue
381
+ <script setup>
382
+ import { usePushler } from '@pushler/vue';
383
+ import { useNotifications } from './composables/useNotifications';
384
+
385
+ const pushler = usePushler({ autoConnect: true, ... });
386
+ const { show } = useNotifications();
387
+
388
+ // Приватный канал для уведомлений пользователя
389
+ const notificationChannel = pushler.subscribe('private-user-notifications');
390
+
391
+ notificationChannel.on('new', (data) => {
392
+ show({
393
+ title: data.title,
394
+ message: data.message,
395
+ type: data.type || 'info'
396
+ });
397
+ });
398
+
399
+ notificationChannel.on('badge_update', (data) => {
400
+ // Обновление счётчика непрочитанных
401
+ document.title = data.count > 0
402
+ ? `(${data.count}) Мой сайт`
403
+ : 'Мой сайт';
404
+ });
405
+ </script>
406
+ ```
407
+
408
+ ### Индикатор набора текста (Typing indicator)
409
+
410
+ ```vue
411
+ <script setup>
412
+ import { ref, computed } from 'vue';
413
+ import { usePushler } from '@pushler/vue';
414
+
415
+ const pushler = usePushler({ ... });
416
+ const typingUsers = ref(new Set());
417
+
418
+ const channel = pushler.subscribe('presence-chat', {
419
+ user: { id: currentUserId, name: currentUserName }
420
+ });
421
+
422
+ channel.on('typing_start', (data) => {
423
+ typingUsers.value.add(data.user_name);
424
+ });
425
+
426
+ channel.on('typing_stop', (data) => {
427
+ typingUsers.value.delete(data.user_name);
428
+ });
429
+
430
+ const typingText = computed(() => {
431
+ const users = Array.from(typingUsers.value);
432
+ if (users.length === 0) return '';
433
+ if (users.length === 1) return `${users[0]} печатает...`;
434
+ if (users.length === 2) return `${users.join(' и ')} печатают...`;
435
+ return `${users.length} человек печатают...`;
436
+ });
437
+ </script>
438
+ ```
439
+
440
+ ## Использование через CDN
441
+
442
+ ```html
443
+ <!DOCTYPE html>
444
+ <html>
445
+ <head>
446
+ <title>Pushler Vue Example</title>
447
+ <script src="https://unpkg.com/vue@3"></script>
448
+ <script src="https://unpkg.com/@pushler/vue"></script>
449
+ </head>
450
+ <body>
451
+ <div id="app">
452
+ <p>Status: {{ connectionState }}</p>
453
+ <p v-if="socketId">Socket ID: {{ socketId }}</p>
454
+ <button @click="connect" :disabled="isConnected">Connect</button>
455
+ <button @click="disconnect" :disabled="!isConnected">Disconnect</button>
456
+ </div>
457
+
458
+ <script>
459
+ const { createApp } = Vue;
460
+ const { usePushler } = PushlerVue;
461
+
462
+ createApp({
463
+ setup() {
464
+ const pushler = usePushler({
465
+ appKey: 'your-app-key',
466
+ wsUrl: 'wss://ws.pushler.ru/app'
467
+ });
468
+
469
+ return {
470
+ connectionState: pushler.connectionState,
471
+ socketId: pushler.socketId,
472
+ isConnected: pushler.isConnected,
473
+ connect: () => pushler.connect(),
474
+ disconnect: () => pushler.disconnect()
475
+ };
476
+ }
477
+ }).mount('#app');
478
+ </script>
479
+ </body>
480
+ </html>
481
+ ```
482
+
483
+ ## TypeScript
484
+
485
+ SDK включает TypeScript определения. Импортируйте типы:
486
+
487
+ ```ts
488
+ import type {
489
+ UsePushlerReturn,
490
+ PushlerChannel,
491
+ ChannelInfo,
492
+ ConnectionState,
493
+ ChannelType
494
+ } from '@pushler/vue';
495
+ ```
496
+
497
+ ## Лицензия
498
+
499
+ MIT
@@ -0,0 +1,176 @@
1
+ import { Ref, ComputedRef } from 'vue';
2
+
3
+ /**
4
+ * Состояния подключения
5
+ */
6
+ export declare const ConnectionStates: {
7
+ readonly CONNECTING: 'connecting';
8
+ readonly CONNECTED: 'connected';
9
+ readonly DISCONNECTED: 'disconnected';
10
+ readonly RECONNECTING: 'reconnecting';
11
+ readonly FAILED: 'failed';
12
+ };
13
+
14
+ export type ConnectionState = typeof ConnectionStates[keyof typeof ConnectionStates];
15
+
16
+ /**
17
+ * Типы каналов
18
+ */
19
+ export declare const ChannelTypes: {
20
+ readonly PUBLIC: 'public';
21
+ readonly PRIVATE: 'private';
22
+ readonly PRESENCE: 'presence';
23
+ };
24
+
25
+ export type ChannelType = typeof ChannelTypes[keyof typeof ChannelTypes];
26
+
27
+ /**
28
+ * Опции подключения
29
+ */
30
+ export interface PushlerOptions {
31
+ appKey?: string;
32
+ wsUrl?: string;
33
+ authEndpoint?: string;
34
+ autoConnect?: boolean;
35
+ reconnectDelay?: number;
36
+ maxReconnectAttempts?: number;
37
+ }
38
+
39
+ /**
40
+ * Опции подписки на канал
41
+ */
42
+ export interface ChannelOptions {
43
+ signature?: string;
44
+ user?: {
45
+ id: string | number;
46
+ name?: string;
47
+ [key: string]: any;
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Реактивный канал
53
+ */
54
+ export interface PushlerChannel {
55
+ name: string;
56
+ originalName: string;
57
+ type: ChannelType;
58
+ subscribed: boolean;
59
+ options: ChannelOptions;
60
+
61
+ on(event: string, callback: (data: any) => void): PushlerChannel;
62
+ off(event: string, callback: (data: any) => void): PushlerChannel;
63
+ emit(event: string, data: any): void;
64
+ unsubscribe(): void;
65
+ }
66
+
67
+ /**
68
+ * Информация о канале
69
+ */
70
+ export interface ChannelInfo {
71
+ name: string;
72
+ fullName: string;
73
+ type: ChannelType;
74
+ subscribed: boolean;
75
+ }
76
+
77
+ /**
78
+ * Возвращаемое значение usePushler
79
+ */
80
+ export interface UsePushlerReturn {
81
+ // Состояние
82
+ connectionState: Readonly<Ref<ConnectionState>>;
83
+ socketId: Readonly<Ref<string | null>>;
84
+ error: Readonly<Ref<string | null>>;
85
+ reconnectAttempts: Readonly<Ref<number>>;
86
+
87
+ // Вычисляемые свойства
88
+ isConnected: ComputedRef<boolean>;
89
+ isConnecting: ComputedRef<boolean>;
90
+ isReconnecting: ComputedRef<boolean>;
91
+
92
+ // Методы
93
+ connect(options?: PushlerOptions): void;
94
+ disconnect(): void;
95
+ subscribe(channelName: string, options?: ChannelOptions): PushlerChannel;
96
+ unsubscribe(channelName: string): void;
97
+ channel(channelName: string): PushlerChannel | undefined;
98
+ getChannels(): ChannelInfo[];
99
+ on(event: string, callback: (data: any) => void): void;
100
+ off(event: string, callback: (data: any) => void): void;
101
+
102
+ // Утилиты
103
+ formatChannelName(channelName: string): string;
104
+ extractChannelName(fullChannelName: string): string;
105
+ getChannelType(channelName: string): ChannelType;
106
+
107
+ // Константы
108
+ ConnectionStates: typeof ConnectionStates;
109
+ ChannelTypes: typeof ChannelTypes;
110
+ }
111
+
112
+ /**
113
+ * Vue composable для работы с Pushler WebSocket
114
+ */
115
+ export declare function usePushler(options?: PushlerOptions): UsePushlerReturn;
116
+
117
+ /**
118
+ * Опции usePushlerChannel
119
+ */
120
+ export interface ChannelHookOptions extends ChannelOptions {
121
+ autoSubscribe?: boolean;
122
+ maxEvents?: number;
123
+ }
124
+
125
+ /**
126
+ * Событие канала
127
+ */
128
+ export interface ChannelEvent {
129
+ event: string;
130
+ data: any;
131
+ timestamp: Date;
132
+ }
133
+
134
+ /**
135
+ * Возвращаемое значение usePushlerChannel
136
+ */
137
+ export interface UsePushlerChannelReturn {
138
+ channel: Ref<PushlerChannel | null>;
139
+ isSubscribed: Ref<boolean>;
140
+ lastEvent: Ref<ChannelEvent | null>;
141
+ events: ChannelEvent[];
142
+
143
+ subscribe(): PushlerChannel | undefined;
144
+ unsubscribe(): void;
145
+ on(event: string, callback: (data: any) => void): () => void;
146
+ off(event: string): void;
147
+ clearEvents(): void;
148
+ }
149
+
150
+ /**
151
+ * Vue composable для работы с отдельным каналом
152
+ */
153
+ export declare function usePushlerChannel(
154
+ pushler: UsePushlerReturn,
155
+ channelName: string,
156
+ options?: ChannelHookOptions
157
+ ): UsePushlerChannelReturn;
158
+
159
+ /**
160
+ * Символ для инъекции Pushler
161
+ */
162
+ export declare const PushlerKey: symbol;
163
+
164
+ /**
165
+ * Vue Plugin
166
+ */
167
+ export declare const PushlerPlugin: {
168
+ install(app: any, options?: PushlerOptions): void;
169
+ };
170
+
171
+ /**
172
+ * Хелпер для получения инстанса Pushler
173
+ */
174
+ export declare function usePushlerInstance(): UsePushlerReturn;
175
+
176
+ export default usePushler;