larasopp 1.2.23 → 1.2.24

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
@@ -1,70 +1,443 @@
1
- # Laravel websocket client
1
+ # Larasopp Client
2
2
 
3
- #### Server
3
+ WebSocket client for Laravel with full TypeScript support and type safety.
4
+
5
+ ## Features
6
+
7
+ - 🔌 **WebSocket Connection** - Real-time bidirectional communication
8
+ - 🎯 **Full TypeScript Support** - Complete type safety for channels, events, and users
9
+ - 📡 **Channel Subscriptions** - Subscribe to multiple channels
10
+ - 🎧 **Event Listeners** - Listen to specific events with typed callbacks
11
+ - 👥 **Presence Channels** - Track users joining/leaving channels
12
+ - 🔐 **Authentication** - Token-based authentication
13
+ - 🔄 **Auto Reconnect** - Automatic reconnection with configurable delays
14
+ - ✅ **Type Safety** - Compile-time type checking for all operations
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install larasopp
20
+ # or
21
+ yarn add larasopp
22
+ # or
23
+ pnpm add larasopp
4
24
  ```
5
- https://www.npmjs.com/package/larasopp-server
25
+
26
+ ## Related Packages
27
+
28
+ - **Server**: [larasopp-server](https://www.npmjs.com/package/larasopp-server)
29
+ - **Laravel Package**: `composer require larasopp/larasopp`
30
+
31
+ ## Quick Start
32
+
33
+ ```typescript
34
+ import Larasopp from 'larasopp';
35
+
36
+ const larasopp = new Larasopp({
37
+ host: 'ws://127.0.0.1:3001',
38
+ token: 'your-auth-token'
39
+ });
40
+
41
+ larasopp.connect();
6
42
  ```
7
- #### Laravel package
43
+
44
+ ## TypeScript Support
45
+
46
+ Larasopp provides full TypeScript support with generic types for channels, events, and users.
47
+
48
+ ### Defining Types
49
+
50
+ ```typescript
51
+ // Define your channel events structure
52
+ type ChannelsEvents = {
53
+ chat: {
54
+ message: { text: string; author: string; timestamp: number };
55
+ typing: { user: string };
56
+ userJoined: { user: string };
57
+ };
58
+ notifications: {
59
+ alert: { title: string; body: string; type: 'info' | 'warning' | 'error' };
60
+ update: { version: string };
61
+ };
62
+ };
63
+
64
+ // Define your user type
65
+ interface MyUser {
66
+ id: number;
67
+ name: string;
68
+ email: string;
69
+ avatar?: string;
70
+ }
71
+
72
+ // Create typed instance
73
+ const larasopp = new Larasopp<ChannelsEvents, MyUser>({
74
+ host: 'ws://127.0.0.1:3001',
75
+ token: 'your-token'
76
+ });
8
77
  ```
9
- composer require larasopp/larasopp
78
+
79
+ ## API Reference
80
+
81
+ ### Configuration
82
+
83
+ ```typescript
84
+ interface IConfig {
85
+ host: string; // WebSocket server URL
86
+ token?: string; // Authentication token
87
+ reviver?: (key: string, value: any) => any; // JSON reviver function
88
+ dataReviver?: { [key: string]: (value: any) => any }; // Per-key revivers
89
+ reconnect?: number; // Max reconnection attempts
90
+ reconnectDelay?: number; // Delay between reconnections (ms)
91
+ }
10
92
  ```
11
93
 
12
- ### Connect app to websocket
94
+ ### Connection
13
95
 
14
- ```js
15
- ...
16
- import Larasopp from "Larasopp";
96
+ #### `connect(token?: string)`
17
97
 
18
- const larasopp = new Larasopp({
19
- host: 'ws://127.0.0.1:3001',
20
- token: 'token'
21
- });
98
+ Connect to the WebSocket server.
22
99
 
100
+ ```typescript
23
101
  larasopp.connect();
24
- //or
25
- larasopp.connect('token');
102
+ // or with token
103
+ larasopp.connect('new-token');
104
+ ```
105
+
106
+ #### `disconnect()`
107
+
108
+ Disconnect from the WebSocket server.
109
+
110
+ ```typescript
111
+ larasopp.disconnect();
112
+ ```
113
+
114
+ #### `setToken(token: string)`
115
+
116
+ Update authentication token.
117
+
118
+ ```typescript
119
+ larasopp.setToken('new-token');
120
+ ```
26
121
 
122
+ ### Channels
123
+
124
+ #### `subscribe<K>(channel: K): Listener`
125
+
126
+ Subscribe to a channel. Returns a `Listener` instance for the channel.
127
+
128
+ ```typescript
129
+ // TypeScript will enforce that 'chat' exists in ChannelsEvents
130
+ const listener = larasopp.subscribe('chat');
27
131
  ```
28
132
 
29
- ### Update user token
133
+ #### `unsubscribe<K>(channel: K)`
134
+
135
+ Unsubscribe from a channel.
136
+
137
+ ```typescript
138
+ larasopp.unsubscribe('chat');
139
+ ```
140
+
141
+ #### `countListeners<K>(channel: K): number`
142
+
143
+ Get the number of listeners for a channel.
30
144
 
31
- ```js
32
- larasopp.setToken('new token');
145
+ ```typescript
146
+ const count = larasopp.countListeners('chat');
33
147
  ```
34
148
 
35
- ### Subscribe on channel and listen event
149
+ ### Events
36
150
 
37
- ```js
38
- const listener = larasopp.subscribe('chat').listen('message',(data) => {
39
- console.log(data.text); // Hello World
151
+ #### `listen<K>(event: K, callback, withCache?: boolean): this`
152
+
153
+ Listen to a specific event on the subscribed channel.
154
+
155
+ ```typescript
156
+ const listener = larasopp.subscribe('chat');
157
+
158
+ // TypeScript knows 'message' exists and its data type
159
+ listener.listen('message', (data) => {
160
+ // data is typed as { text: string; author: string; timestamp: number }
161
+ console.log(data.text, data.author);
40
162
  });
41
163
 
42
- // Unsubscribe event
43
- listener.remove();
164
+ // withCache: true will call callback immediately if cached data exists
165
+ listener.listen('typing', (data) => {
166
+ console.log(data.user, 'is typing');
167
+ }, true);
44
168
  ```
45
169
 
46
- ### Trigger event on subscribe channel
170
+ #### `trigger<K, E>(channel: K, event: E, message, permission?, waitSubscribe?): void`
171
+
172
+ Trigger an event on a channel.
173
+
174
+ **Permission Levels:**
175
+
176
+ - **`'public'`** - Event will be sent to all channel subscribers AND to the API application
177
+ - **`'protected'`** - Event will be sent to all channel subscribers but NOT to the API application
178
+ - **`'private'`** - Event will be sent ONLY to the API application, not to channel subscribers
179
+
180
+ ```typescript
181
+ // TypeScript ensures 'chat' channel and 'message' event exist
182
+ // and that the message matches the event type
183
+
184
+ // Public: sent to all subscribers and API
185
+ larasopp.trigger('chat', 'message', {
186
+ text: 'Hello World',
187
+ author: 'John Doe',
188
+ timestamp: Date.now()
189
+ }, 'public');
47
190
 
48
- ```js
49
- larasopp.trigger('chat','message',{
50
- text: 'Hello World'
51
- },'public');
191
+ // Protected: sent to subscribers only, not to API
192
+ larasopp.trigger('chat', 'message', {
193
+ text: 'User message',
194
+ author: 'Jane Doe',
195
+ timestamp: Date.now()
196
+ }, 'protected');
197
+
198
+ // Private: sent to API only, not to subscribers
199
+ larasopp.trigger('chat', 'message', {
200
+ text: 'Admin notification',
201
+ author: 'System',
202
+ timestamp: Date.now()
203
+ }, 'private', true); // waitSubscribe: true waits for subscription
52
204
  ```
53
205
 
54
- ### Unsubscribe channel
206
+ ### Event Permissions
55
207
 
56
- ```js
57
- larasopp.unsubscribe('chat');
208
+ When triggering events, you can specify one of three permission levels:
209
+
210
+ | Permission | Channel Subscribers | API Application | Use Case |
211
+ |------------|---------------------|-----------------|----------|
212
+ | `'public'` | ✅ Yes | ✅ Yes | Broadcast to everyone including server-side processing |
213
+ | `'protected'` | ✅ Yes | ❌ No | Client-to-client communication without server handling |
214
+ | `'private'` | ❌ No | ✅ Yes | Server-side only events (notifications, admin actions) |
215
+
216
+ **Examples:**
217
+
218
+ ```typescript
219
+ // Public: Broadcast message to all users and process on server
220
+ larasopp.trigger('chat', 'message', {
221
+ text: 'Hello everyone!',
222
+ author: 'John'
223
+ }, 'public');
224
+
225
+ // Protected: Send to users but don't trigger server-side handlers
226
+ larasopp.trigger('chat', 'typing', {
227
+ user: 'John'
228
+ }, 'protected');
229
+
230
+ // Private: Server-side only (e.g., admin notifications, system events)
231
+ larasopp.trigger('notifications', 'alert', {
232
+ title: 'System Maintenance',
233
+ body: 'Scheduled maintenance in 5 minutes'
234
+ }, 'private');
58
235
  ```
59
236
 
60
- ### Disconnect
237
+ ### Presence Channels
61
238
 
62
- ```js
63
- larasopp.disconnect();
239
+ #### `here(callback, withCache?: boolean): this`
240
+
241
+ Get list of users currently in the channel.
242
+
243
+ ```typescript
244
+ listener.here((users) => {
245
+ // users is typed as MyUser[]
246
+ users.forEach(user => {
247
+ console.log(user.name, 'is here');
248
+ });
249
+ });
250
+ ```
251
+
252
+ #### `joining(callback, withCache?: boolean): this`
253
+
254
+ Listen for users joining the channel.
255
+
256
+ ```typescript
257
+ listener.joining((user) => {
258
+ // user is typed as MyUser
259
+ console.log(user.name, 'joined');
260
+ });
261
+ ```
262
+
263
+ #### `leaving(callback, withCache?: boolean): this`
264
+
265
+ Listen for users leaving the channel.
266
+
267
+ ```typescript
268
+ listener.leaving((user) => {
269
+ // user is typed as MyUser
270
+ console.log(user.name, 'left');
271
+ });
272
+ ```
273
+
274
+ ### User Management
275
+
276
+ #### `user(callback): void`
277
+
278
+ Get current authenticated user.
279
+
280
+ ```typescript
281
+ larasopp.user((user) => {
282
+ // user is typed as MyUser
283
+ console.log('Current user:', user.name);
284
+ });
285
+ ```
286
+
287
+ #### `userRefresh(callback): void`
288
+
289
+ Refresh current user data.
290
+
291
+ ```typescript
292
+ larasopp.userRefresh((user) => {
293
+ // user is typed as MyUser
294
+ console.log('Refreshed user:', user);
295
+ });
296
+ ```
297
+
298
+ ### Socket Events
299
+
300
+ #### `addListener(event, callback): EventListener | undefined`
301
+
302
+ Listen to socket-level events: `'open'`, `'close'`, `'error'`.
303
+
304
+ ```typescript
305
+ larasopp.addListener('open', () => {
306
+ console.log('Connected to WebSocket');
307
+ });
308
+
309
+ larasopp.addListener('close', () => {
310
+ console.log('Disconnected from WebSocket');
311
+ });
312
+
313
+ larasopp.addListener('error', (error) => {
314
+ console.error('WebSocket error:', error);
315
+ });
316
+ ```
317
+
318
+ ### Listener Management
319
+
320
+ #### `remove()`
321
+
322
+ Remove all event listeners from a channel subscription.
323
+
324
+ ```typescript
325
+ const listener = larasopp.subscribe('chat');
326
+ listener.listen('message', handleMessage);
327
+
328
+ // Later, remove all listeners
329
+ listener.remove();
330
+ ```
331
+
332
+ #### `unsubscribe()`
333
+
334
+ Unsubscribe from the channel and remove all listeners.
335
+
336
+ ```typescript
337
+ listener.unsubscribe();
64
338
  ```
65
339
 
66
- ### Permissions
340
+ ## Complete Example
341
+
342
+ ```typescript
343
+ import Larasopp from 'larasopp';
344
+
345
+ // Define types
346
+ type AppChannels = {
347
+ chat: {
348
+ message: { text: string; author: string };
349
+ typing: { user: string };
350
+ };
351
+ notifications: {
352
+ alert: { title: string; body: string };
353
+ };
354
+ };
355
+
356
+ interface AppUser {
357
+ id: number;
358
+ name: string;
359
+ email: string;
360
+ }
361
+
362
+ // Create instance
363
+ const larasopp = new Larasopp<AppChannels, AppUser>({
364
+ host: 'ws://127.0.0.1:3001',
365
+ token: 'auth-token',
366
+ reconnect: 5,
367
+ reconnectDelay: 1000
368
+ });
369
+
370
+ // Connect
371
+ larasopp.connect();
372
+
373
+ // Listen to connection events
374
+ larasopp.addListener('open', () => {
375
+ console.log('Connected');
376
+ });
377
+
378
+ // Subscribe to channel
379
+ const chatListener = larasopp.subscribe('chat');
67
380
 
68
- ```js
69
- 'public' | 'protected' | 'private'
381
+ // Listen to events with full type safety
382
+ chatListener.listen('message', (data) => {
383
+ // TypeScript knows data structure
384
+ console.log(`${data.author}: ${data.text}`);
385
+ });
386
+
387
+ chatListener.listen('typing', (data) => {
388
+ console.log(`${data.user} is typing...`);
389
+ });
390
+
391
+ // Presence channel features
392
+ chatListener.here((users) => {
393
+ console.log('Users in channel:', users.map(u => u.name));
394
+ });
395
+
396
+ chatListener.joining((user) => {
397
+ console.log(`${user.name} joined`);
398
+ });
399
+
400
+ chatListener.leaving((user) => {
401
+ console.log(`${user.name} left`);
402
+ });
403
+
404
+ // Trigger events
405
+ larasopp.trigger('chat', 'message', {
406
+ text: 'Hello!',
407
+ author: 'John'
408
+ }, 'public');
409
+
410
+ // Get current user
411
+ larasopp.user((user) => {
412
+ console.log('Logged in as:', user.name);
413
+ });
414
+
415
+ // Cleanup
416
+ chatListener.remove();
417
+ larasopp.unsubscribe('chat');
418
+ larasopp.disconnect();
70
419
  ```
420
+
421
+ ## Type Safety Benefits
422
+
423
+ With TypeScript support, you get:
424
+
425
+ - ✅ **Compile-time checks** - Catch errors before runtime
426
+ - ✅ **Autocomplete** - IDE suggests available channels and events
427
+ - ✅ **Type inference** - Automatic type detection for callbacks
428
+ - ✅ **Refactoring safety** - Rename channels/events with confidence
429
+ - ✅ **Documentation** - Types serve as inline documentation
430
+
431
+ ## License
432
+
433
+ MIT
434
+
435
+ ## Author
436
+
437
+ Sergey Serov
438
+
439
+ ## Links
440
+
441
+ - [GitHub Repository](https://github.com/SergoMorello/larasopp.client)
442
+ - [NPM Package](https://www.npmjs.com/package/larasopp)
443
+ - [Server Package](https://www.npmjs.com/package/larasopp-server)
package/lib/Listener.d.ts CHANGED
@@ -1,16 +1,18 @@
1
1
  import EventEmmiter from "easy-event-emitter";
2
2
  import type Larasopp from ".";
3
- declare class Listener extends EventEmmiter.Stack {
3
+ import type { TListenCallback, TJoiningCallback, TLeavingCallback, THereCallback, TUser } from "./types";
4
+ declare class Listener<TEvents = Record<string, unknown>, TUserType extends TUser = TUser> extends EventEmmiter.Stack {
4
5
  private readonly context;
5
6
  private channel;
6
7
  private listener?;
7
8
  private cacheEvents;
8
9
  private hereMap;
9
- constructor(channel: string, constext: Larasopp);
10
- listen(event: string, callback: (data: any) => void, withCache?: boolean): this;
11
- here(callback: (data: any) => void, withCache?: boolean): this;
12
- joining(callback: (data: any) => void, withCache?: boolean): this;
13
- leaving(callback: (data: any) => void, withCache?: boolean): this;
10
+ constructor(channel: string, constext: Larasopp<any, TUserType>);
11
+ listen<K extends keyof TEvents>(event: K, callback: TListenCallback<TEvents[K]>, withCache?: boolean): this;
12
+ private listenInternal;
13
+ here(callback: THereCallback<TUserType>, withCache?: boolean): this;
14
+ joining(callback: TJoiningCallback<TUserType>, withCache?: boolean): this;
15
+ leaving(callback: TLeavingCallback<TUserType>, withCache?: boolean): this;
14
16
  unsubscribe(): void;
15
17
  private hasCache;
16
18
  private getCache;
package/lib/Listener.js CHANGED
@@ -43,8 +43,22 @@ class Listener extends easy_event_emitter_1.default.Stack {
43
43
  this.here(() => { }, true);
44
44
  }
45
45
  listen(event, callback, withCache = false) {
46
- if (withCache && this.hasCache(event))
46
+ const eventString = String(event);
47
+ if (withCache && this.hasCache(eventString)) {
48
+ callback(this.getCache(eventString));
49
+ }
50
+ const listener = this.context.events.addListener(`${this.channel}:${eventString}`, (data) => {
51
+ callback(data);
52
+ if (withCache)
53
+ this.pushCache(eventString, data);
54
+ });
55
+ this.push(listener);
56
+ return this;
57
+ }
58
+ listenInternal(event, callback, withCache = false) {
59
+ if (withCache && this.hasCache(event)) {
47
60
  callback(this.getCache(event));
61
+ }
48
62
  const listener = this.context.events.addListener(`${this.channel}:${event}`, (data) => {
49
63
  callback(data);
50
64
  if (withCache)
@@ -63,17 +77,17 @@ class Listener extends easy_event_emitter_1.default.Stack {
63
77
  this.hereMap.delete(leave.id);
64
78
  callback([...this.hereMap.values()]);
65
79
  }));
66
- const hereListener = this.listen('__HERE', (here) => {
80
+ const hereListener = this.listenInternal('__HERE', (here) => {
67
81
  this.hereMap = new Map(here.map((u) => [u.id, u]));
68
82
  callback([...this.hereMap.values()]);
69
83
  }, withCache);
70
84
  return hereListener;
71
85
  }
72
86
  joining(callback, withCache = false) {
73
- return this.listen('__JOIN', callback, withCache);
87
+ return this.listenInternal('__JOIN', callback, withCache);
74
88
  }
75
89
  leaving(callback, withCache = false) {
76
- return this.listen('__LEAVE', callback, withCache);
90
+ return this.listenInternal('__LEAVE', callback, withCache);
77
91
  }
78
92
  unsubscribe() {
79
93
  this.context.unsubscribe(this.channel);
package/lib/index.d.ts CHANGED
@@ -1,18 +1,19 @@
1
1
  import { type EventListener } from "easy-event-emitter";
2
2
  import Core from "./Core";
3
3
  import Listener from "./Listener";
4
- import type { IConfig, TPermissions, TSocketEvents, TListenerCallback } from "./types";
5
- declare class Larasopp extends Core {
4
+ import type { IConfig, TPermissions, TSocketEvents, TListenerCallback, TUser } from "./types";
5
+ declare class Larasopp<TChannelsEvents = Record<string, Record<string, unknown>>, TUserType extends TUser = TUser> extends Core {
6
6
  private readonly channels;
7
7
  constructor(config: IConfig);
8
8
  private listenResumeSubscribes;
9
- user(callback: (user: any) => void): void;
10
- userRefresh(callback: (user: any) => void): void;
11
- subscribe(channel: string): Listener;
12
- unsubscribe(channel: string): void;
13
- trigger<T>(channel: string, event: string, message: T, permission?: TPermissions, waitSubscribe?: boolean): void;
9
+ user(callback: (user: TUserType) => void): void;
10
+ userRefresh(callback: (user: TUserType) => void): void;
11
+ subscribe<K extends keyof TChannelsEvents>(channel: K): Listener<TChannelsEvents[K], TUserType>;
12
+ unsubscribe<K extends keyof TChannelsEvents>(channel: K): void;
13
+ trigger<K extends keyof TChannelsEvents, E extends keyof TChannelsEvents[K]>(channel: K, event: E, message: TChannelsEvents[K][E], permission?: TPermissions, waitSubscribe?: boolean): void;
14
+ trigger(channel: string, event: string, message: unknown, permission?: TPermissions, waitSubscribe?: boolean): void;
14
15
  private pushListener;
15
- countListeners(channel: string): number;
16
+ countListeners<K extends keyof TChannelsEvents>(channel: K): number;
16
17
  addListener(event: TSocketEvents, callback: TListenerCallback): EventListener | undefined;
17
18
  }
18
19
  export type { Listener, EventListener };
package/lib/index.js CHANGED
@@ -29,19 +29,13 @@ class Larasopp extends Core_1.default {
29
29
  if (!this.status)
30
30
  return;
31
31
  this.send({ me: true });
32
- const listener = this.events.addListener(`__SYSTEM:user`, (e) => {
33
- callback(e);
34
- listener.remove();
35
- });
32
+ this.events.once(`__SYSTEM:user`, callback);
36
33
  }
37
34
  userRefresh(callback) {
38
35
  if (!this.status)
39
36
  return;
40
37
  this.send({ me: 'refresh' });
41
- const listener = this.events.addListener(`__SYSTEM:user-refresh`, (e) => {
42
- callback(e);
43
- listener.remove();
44
- });
38
+ this.events.once(`__SYSTEM:user-refresh`, callback);
45
39
  }
46
40
  subscribe(channel) {
47
41
  const listener = new Listener_1.default(channel, this);
package/lib/types.d.ts CHANGED
@@ -27,6 +27,16 @@ export interface IConfig {
27
27
  reconnect?: number;
28
28
  reconnectDelay?: number;
29
29
  }
30
- export type TChannels = {
31
- [channel: string]: Listener[];
30
+ export type TUser = {
31
+ id: string | number;
32
+ [key: string]: unknown;
32
33
  };
34
+ export type TChannels<TChannelsEvents = Record<string, Record<string, unknown>>, TUserType extends TUser = TUser> = {
35
+ [K in keyof TChannelsEvents]?: Listener<TChannelsEvents[K], TUserType>[];
36
+ };
37
+ export type TJoinData<T extends TUser = TUser> = T;
38
+ export type TLeaveData<T extends TUser = TUser> = T;
39
+ export type THereCallback<T extends TUser = TUser> = (users: T[]) => void;
40
+ export type TListenCallback<T = unknown> = (data: T) => void;
41
+ export type TJoiningCallback<T extends TUser = TUser> = (data: TJoinData<T>) => void;
42
+ export type TLeavingCallback<T extends TUser = TUser> = (data: TLeaveData<T>) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larasopp",
3
- "version": "1.2.23",
3
+ "version": "1.2.24",
4
4
  "description": "Websocket client for laravel",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -23,9 +23,30 @@
23
23
  },
24
24
  "keywords": [
25
25
  "websocket",
26
+ "websocket-client",
26
27
  "laravel",
28
+ "laravel-broadcast",
27
29
  "broadcast",
28
- "events"
30
+ "events",
31
+ "typescript",
32
+ "types",
33
+ "type-safe",
34
+ "type-safety",
35
+ "typing",
36
+ "real-time",
37
+ "realtime",
38
+ "presence",
39
+ "channels",
40
+ "client",
41
+ "socket",
42
+ "ws",
43
+ "event-emitter",
44
+ "subscription",
45
+ "reconnect",
46
+ "authentication",
47
+ "token",
48
+ "pubsub",
49
+ "publish-subscribe"
29
50
  ],
30
51
  "author": "Sergey Serov",
31
52
  "license": "MIT",