@eleven-am/pondsocket 0.1.65 → 0.1.67

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
@@ -33,7 +33,7 @@ Within each endpoint, sockets interact through channels. Channels provide an org
33
33
 
34
34
  ```javascript
35
35
  const channel = endpoint.createChannel('/channel/:id', (req, res) => {
36
- // Handle channel-specific events and actions
36
+ // Handle the join request, which is sent when a user attempts to join the channel
37
37
  });
38
38
  ```
39
39
 
@@ -74,9 +74,203 @@ This node client allows you to turn another server into a client, enabling easy
74
74
  - **Broadcasting**: PondSocket enables broadcasting messages to all users or specific groups within a channel, facilitating real-time updates.
75
75
  - **Typed and Well-documented**: The codebase is thoroughly documented and typed, providing a seamless development experience with improved IDE suggestions.
76
76
 
77
- ## License
77
+ ## Examples
78
+
79
+ ### Client-side Example with Authentication
80
+
81
+ To connect to the PondSocket server and send messages while associating a username with the client connection, follow the steps below:
82
+
83
+ ```javascript
84
+ import PondClient from "@eleven-am/pondsocket/client";
85
+
86
+ // Your server URL
87
+ const serverUrl = 'ws://your-server-url/api/socket';
88
+
89
+ // Your authenticated user's username (replace with actual username)
90
+ const authToken = 'your-auth-token';
91
+
92
+ // Your username (replace with actual username)
93
+ const username = 'user123';
94
+
95
+ // Create a new PondClient instance
96
+ const socket = new PondClient(serverUrl, { token: authToken });
97
+
98
+ // Connect to the server
99
+ socket.connect();
100
+
101
+ // Add event listeners to handle various scenarios
102
+ socket.onConnectionChange((connected) => {
103
+ if (connected) {
104
+ console.log('Connected to the server.');
105
+ } else {
106
+ console.log('Disconnected from the server.');
107
+ }
108
+ });
109
+
110
+ // Create a channel and join it
111
+ const channel = socket.createChannel('/channel/123', { username });
112
+ channel.join();
113
+
114
+ // Send a message to the server
115
+ const message = "Hello, PondSocket!";
116
+ channel.broadcast('message', { text: message });
117
+
118
+ // Handle received messages
119
+ channel.onMessage((event, message) => {
120
+ console.log(`Received message from server: ${message.text}`);
121
+ });
122
+ ```
123
+
124
+ The client will now connect to the server, and the server will receive the necessary headers automatically, including any authentication tokens or cookies, as required by the browser.
125
+
126
+ ### Server-side Example with Authentication and check for profanity before broadcasting
127
+
128
+ To create a PondSocket server that accepts authenticated connections and checks for profanity before broadcasting messages, follow the steps below:
129
+
130
+ ```javascript
131
+ import PondSocket from "@eleven-am/pondsocket";
78
132
 
79
- PondSocket is released under the MIT License. Please refer to the `LICENSE` file for detailed licensing information.
133
+ // Helper functions for token validation
134
+ function isValidToken(token) {
135
+ // Implement your token validation logic here
136
+ // Return true if the token is valid, false otherwise
137
+ return true;
138
+ }
139
+
140
+ function getRoleFromToken(token) {
141
+ // Implement the logic to extract the user's role from the token
142
+ // Return the user's role
143
+ return 'user';
144
+ }
145
+
146
+ function isTextProfane(text) {
147
+ // Implement your profanity check logic here
148
+ // Return true if the text is profane, false otherwise
149
+ return false;
150
+ }
151
+
152
+ function getMessagesFromDatabase(channelId) {
153
+ // Implement your logic to retrieve messages from the database
154
+ // Return an array of messages
155
+ return [];
156
+ }
157
+
158
+ const pond = new PondSocket();
159
+
160
+ // Create an endpoint for handling socket connections
161
+ const endpoint = pond.createEndpoint('/api/socket', (req, res) => {
162
+ // Depending if the user already has cookies set, they can be accessed from the request headers or the request address
163
+ const token = req.query.token; // If the token is passed as a query parameter
164
+
165
+ // Perform token validation here
166
+ if (isValidToken(token)) {
167
+ // Extract the authenticated user's username
168
+ const role = getRoleFromToken(token);
169
+
170
+ // Handle socket connection and authentication for valid users
171
+ res.accept({ role }); // Assign the user's role to the socket
172
+ } else {
173
+ // Reject the connection for invalid users or without a token
174
+ res.reject('Invalid token', 401);
175
+ }
176
+ });
177
+
178
+ // Create a channel, providing a callback that is called when a user attempts to join the channel
179
+ const profanityChannel = endpoint.createChannel('/channel/:id', async (req, res) => {
180
+ // When joining the channel, any joinParams passed from the client will be available in the request payload
181
+ // Also any previous assigns on the socket will be available in the request payload as well
182
+ const { role } = req.user.assigns;
183
+ const { username } = req.joinParams;
184
+ const { id } = req.event.params;
185
+
186
+ // maybe retrieve the channel from a database
187
+ const messages = await getMessagesFromDatabase(id);
188
+
189
+ // Check if the user has the required role to join the channel
190
+ if (role === 'admin') {
191
+ // Accept the join request
192
+ res.accept({ username, profanityCount: 0 })
193
+ // optionally you can track the presence of the user in the channel
194
+ .trackPresence({
195
+ username,
196
+ role,
197
+ status: 'online',
198
+ onlineSince: Date.now(),
199
+ })
200
+ // and send the user the channel history
201
+ .sendToUsers('history', { messages }, [req.user.id]);
202
+
203
+ // Alternatively, you can also send messages to the user, NOTE that the user would be automatically subscribed to the channel.
204
+ // res.send('history', { messages }, { username, profanityCount: 0 })
205
+ // .trackPresence({
206
+ // username,
207
+ // role,
208
+ // status: 'online',
209
+ // onlineSince: Date.now(),
210
+ // });
211
+ } else {
212
+ // Reject the join request
213
+ res.reject('You do not have the required role to join this channel', 403);
214
+ }
215
+ });
216
+
217
+ // Attach message event listener to the profanityChannel
218
+ profanityChannel.onEvent('message', (req, res) => {
219
+ const { text } = req.event.payload;
220
+
221
+ // Check for profanity
222
+ if (isTextProfane(text)) {
223
+ // Reject the message if it contains profanity
224
+ res.reject('Profanity is not allowed', 400, {
225
+ profanityCount: req.user.assigns.profanityCount + 1
226
+ });
227
+
228
+ // note that profanityCount is updated so req.user.assigns.profanityCount will be updated
229
+ if (req.user.assigns.profanityCount >= 3) {
230
+ // Kick the user from the channel if they have used profanity more than 3 times
231
+ res.evictUser('You have been kicked from the channel for using profanity');
232
+ } else {
233
+ // you can broadcast a message to all users or In the channel that profanity is not allowed
234
+ res.broadcast('profanity-warning', { message: 'Profanity is not allowed' })
235
+ // or you can send a message to the user that profanity is not allowed
236
+ .send('profanity-warning', { message: `You have used profanity ${profanityCount} times. You will be kicked from the channel if you use profanity more than 3 times.` });
237
+ }
238
+ } else {
239
+ // Accept the message to allow broadcasting to other clients in the channel
240
+ res.accept();
241
+ }
242
+
243
+ // for more complete access to the channel, you can use the client object
244
+ // const channel = req.channel;
245
+ });
246
+
247
+ profanityChannel.onEvent('presence/:presence', (req, res) => {
248
+ const { presence } = req.event.params;
249
+ const { username } = req.user.assigns;
250
+
251
+ // Handle presence events
252
+ res.updatePresence({
253
+ username,
254
+ role,
255
+ onlineSince: Date.now(),
256
+ status: presence,
257
+ });
258
+ });
259
+
260
+ profanityChannel.onLeave((req, res) => {
261
+ const { username } = req.user.assigns;
262
+
263
+ // When a user leaves the channel, PondSocket will automatically remove the user from the presence list and inform other users in the channel
264
+
265
+ // perform a cleanup operation here
266
+ });
267
+
268
+
269
+ // Start the server
270
+ pond.listen(3000, () => {
271
+ console.log('PondSocket server listening on port 3000');
272
+ });
273
+ ```
80
274
 
81
275
  ## API Documentation
82
276
 
@@ -172,6 +366,8 @@ The `PondChannel` class represents a Generic channel in the PondSocket server. I
172
366
 
173
367
  - `broadcast(event: string, payload: PondMessage, channelName?: string): void`: Broadcasts a message to all users in the channel with the specified event and payload. Optionally, a specific channel name can be provided to broadcast the message only to users in that channel.
174
368
 
369
+ - `onLeave(handler: (event: LeaveEvent) => void | Promise<void>): void`: Handles a leave event for the channel with the provided handler function when a user leaves the channel.
370
+
175
371
  ### EventRequest
176
372
 
177
373
  The `EventRequest` class represents the request object when an event is received from a client.
@@ -206,9 +402,7 @@ The `EventResponse` class represents the response object for handling events fro
206
402
 
207
403
  - `broadcastFromUser(event: string, payload: PondMessage): EventResponse`: Sends a message to all clients in the channel except the sender with the specified event and payload.
208
404
 
209
- - `sendToUsers(event: string, payload: PondMessage, userIds: string[]): EventResponse`: Sends
210
-
211
- a message to a specific set of clients identified by the provided userIds with the specified event and payload.
405
+ - `sendToUsers(event: string, payload: PondMessage, userIds: string[]): EventResponse`: Sends a message to a specific set of clients identified by the provided userIds with the specified event and payload.
212
406
 
213
407
  - `trackPresence(presence: PondPresence, userId?: string): EventResponse`: Tracks a user's presence in the channel.
214
408
 
@@ -264,7 +458,7 @@ The `PondClient` class represents a client that connects to the PondSocket serve
264
458
 
265
459
  ### Channel
266
460
 
267
- The `Channel` class represents a channel in the PondSocket server.
461
+ The `Channel` class represents a channel in the PondClient.
268
462
 
269
463
  **Methods:**
270
464
 
@@ -298,6 +492,10 @@ The `Channel` class represents a channel in the PondSocket server.
298
492
 
299
493
  - `onConnectionChange(callback: (connected: boolean) => void): Unsubscribe`: Monitors the connection state of the channel and calls the provided callback when the connection state changes.
300
494
 
495
+ ## License
496
+
497
+ PondSocket is released under the GPL-3.0 License. Please refer to the `LICENSE` file for detailed licensing information.
498
+
301
499
  ## Conclusion
302
500
 
303
501
  PondSocket is a powerful and versatile solution for building real-time applications that require efficient bidirectional communication between server and client components. Its minimalist design and comprehensive feature set make it an excellent choice for WebSocket-based projects, providing developers with a straightforward and reliable tool for building real-time communication systems. With the Node.js client, it also allows for easy communication between multiple server instances, expanding its capabilities even further.
@@ -21,9 +21,9 @@ var __rest = (this && this.__rest) || function (s, e) {
21
21
  }
22
22
  return t;
23
23
  };
24
- var _ChannelEngine_instances, _ChannelEngine_receiver, _ChannelEngine_presenceEngine, _ChannelEngine_users, _ChannelEngine_parentEngine, _ChannelEngine_subscribe, _ChannelEngine_getUsersFromRecipients, _Client_engine;
24
+ var _ChannelEngine_instances, _ChannelEngine_receiver, _ChannelEngine_presenceEngine, _ChannelEngine_users, _ChannelEngine_parentEngine, _ChannelEngine_subscribe, _ChannelEngine_getUsersFromRecipients, _Channel_engine;
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Client = exports.ChannelEngine = void 0;
26
+ exports.Channel = exports.ChannelEngine = void 0;
27
27
  const eventRequest_1 = require("./eventRequest");
28
28
  const eventResponse_1 = require("./eventResponse");
29
29
  const enums_1 = require("../enums");
@@ -257,37 +257,37 @@ _ChannelEngine_receiver = new WeakMap(), _ChannelEngine_presenceEngine = new Wea
257
257
  }
258
258
  return users;
259
259
  };
260
- class Client {
260
+ class Channel {
261
261
  constructor(engine) {
262
- _Client_engine.set(this, void 0);
263
- __classPrivateFieldSet(this, _Client_engine, engine, "f");
262
+ _Channel_engine.set(this, void 0);
263
+ __classPrivateFieldSet(this, _Channel_engine, engine, "f");
264
264
  }
265
265
  getAssigns() {
266
- return __classPrivateFieldGet(this, _Client_engine, "f").getAssigns();
266
+ return __classPrivateFieldGet(this, _Channel_engine, "f").getAssigns();
267
267
  }
268
268
  getUserData(userId) {
269
- return __classPrivateFieldGet(this, _Client_engine, "f").getUserData(userId);
269
+ return __classPrivateFieldGet(this, _Channel_engine, "f").getUserData(userId);
270
270
  }
271
271
  broadcastMessage(event, payload) {
272
- __classPrivateFieldGet(this, _Client_engine, "f").sendMessage(enums_1.SystemSender.CHANNEL, enums_1.ChannelReceiver.ALL_USERS, enums_1.ServerActions.BROADCAST, event, payload);
272
+ __classPrivateFieldGet(this, _Channel_engine, "f").sendMessage(enums_1.SystemSender.CHANNEL, enums_1.ChannelReceiver.ALL_USERS, enums_1.ServerActions.BROADCAST, event, payload);
273
273
  }
274
274
  sendToUser(userId, event, payload) {
275
- __classPrivateFieldGet(this, _Client_engine, "f").sendMessage(enums_1.SystemSender.CHANNEL, [userId], enums_1.ServerActions.BROADCAST, event, payload);
275
+ __classPrivateFieldGet(this, _Channel_engine, "f").sendMessage(enums_1.SystemSender.CHANNEL, [userId], enums_1.ServerActions.BROADCAST, event, payload);
276
276
  }
277
277
  banUser(userId, reason) {
278
- __classPrivateFieldGet(this, _Client_engine, "f").kickUser(userId, reason !== null && reason !== void 0 ? reason : 'You have been banned from the channel');
278
+ __classPrivateFieldGet(this, _Channel_engine, "f").kickUser(userId, reason !== null && reason !== void 0 ? reason : 'You have been banned from the channel');
279
279
  }
280
280
  trackPresence(userId, presence) {
281
- __classPrivateFieldGet(this, _Client_engine, "f").trackPresence(userId, presence);
281
+ __classPrivateFieldGet(this, _Channel_engine, "f").trackPresence(userId, presence);
282
282
  }
283
283
  removePresence(userId) {
284
284
  var _a;
285
- (_a = __classPrivateFieldGet(this, _Client_engine, "f").presenceEngine) === null || _a === void 0 ? void 0 : _a.removePresence(userId);
285
+ (_a = __classPrivateFieldGet(this, _Channel_engine, "f").presenceEngine) === null || _a === void 0 ? void 0 : _a.removePresence(userId);
286
286
  }
287
287
  updatePresence(userId, presence) {
288
288
  var _a;
289
- (_a = __classPrivateFieldGet(this, _Client_engine, "f").presenceEngine) === null || _a === void 0 ? void 0 : _a.updatePresence(userId, presence);
289
+ (_a = __classPrivateFieldGet(this, _Channel_engine, "f").presenceEngine) === null || _a === void 0 ? void 0 : _a.updatePresence(userId, presence);
290
290
  }
291
291
  }
292
- exports.Client = Client;
293
- _Client_engine = new WeakMap();
292
+ exports.Channel = Channel;
293
+ _Channel_engine = new WeakMap();
@@ -18,8 +18,8 @@ class EventRequest extends abstractRequest_1.AbstractRequest {
18
18
  }
19
19
  return assigns;
20
20
  }
21
- get client() {
22
- return new channel_1.Client(this._engine);
21
+ get channel() {
22
+ return new channel_1.Channel(this._engine);
23
23
  }
24
24
  }
25
25
  exports.EventRequest = EventRequest;
@@ -35,8 +35,8 @@ class JoinRequest extends abstractRequest_1.AbstractRequest {
35
35
  presence: {},
36
36
  };
37
37
  }
38
- get client() {
39
- return new channel_1.Client(this._engine);
38
+ get channel() {
39
+ return new channel_1.Channel(this._engine);
40
40
  }
41
41
  }
42
42
  exports.JoinRequest = JoinRequest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eleven-am/pondsocket",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "description": "PondSocket is a fast simple socket server",
5
5
  "keywords": [
6
6
  "socket",
package/types.d.ts CHANGED
@@ -1,9 +1,11 @@
1
- import { Express } from 'express';
2
1
  import { Server as HTTPServer, IncomingHttpHeaders } from 'http';
2
+
3
+ import type { Express } from 'express';
3
4
  import { WebSocketServer } from 'ws';
4
5
 
5
6
  type Unsubscribe = () => void;
6
7
 
8
+ export type default_t<T = any> = Record<string, T>;
7
9
  type IsParam<Path> = Path extends `:${infer Param}` ? Param : never;
8
10
 
9
11
  type FilteredParams<Path> = Path extends `${infer First}/${infer Second}`
@@ -21,12 +23,7 @@ type EventParams<Path> = {
21
23
  params: Params<Path>;
22
24
  }
23
25
 
24
- type Primitives = number | string | boolean | null | undefined;
25
-
26
- type PondObject = {
27
- [key: string]: Primitives | PondObject | PondObject[];
28
- }
29
-
26
+ type PondObject = default_t;
30
27
  type PondPresence = PondObject;
31
28
  type PondMessage = PondObject;
32
29
  type PondAssigns = PondObject;
@@ -56,6 +53,13 @@ type IncomingConnection<Path> = EventParams<Path> & {
56
53
  address: string;
57
54
  }
58
55
 
56
+ interface LeaveEvent {
57
+ userId: string;
58
+ assigns: PondAssigns;
59
+ }
60
+
61
+ type LeaveCallback = (event: LeaveEvent) => void;
62
+
59
63
  interface UserData {
60
64
  assigns: PondAssigns;
61
65
  presence: PondPresence;
@@ -107,7 +111,7 @@ declare abstract class PondResponse {
107
111
  declare class EventRequest<Path extends string> extends AbstractRequest<Path> {
108
112
  user: UserData;
109
113
 
110
- client: Client;
114
+ channel: Channel;
111
115
  }
112
116
 
113
117
  declare class EventResponse extends PondResponse {
@@ -189,7 +193,7 @@ declare class EventResponse extends PondResponse {
189
193
  closeChannel (reason: string): void;
190
194
  }
191
195
 
192
- export declare class Channel {
196
+ export declare class ClientChannel {
193
197
  channelState: ChannelState;
194
198
 
195
199
  /**
@@ -315,7 +319,7 @@ declare class Endpoint {
315
319
  closeConnection (clientIds: string | string[]): void;
316
320
  }
317
321
 
318
- export declare class Client {
322
+ export declare class Channel {
319
323
  /**
320
324
  * @desc Gets the current assign data for the channel.
321
325
  */
@@ -375,7 +379,7 @@ declare class JoinRequest<Path extends string> extends AbstractRequest<Path> {
375
379
 
376
380
  user: UserData;
377
381
 
378
- client: Client;
382
+ channel: Channel;
379
383
  }
380
384
 
381
385
  declare class JoinResponse extends PondResponse {
@@ -479,6 +483,12 @@ export declare class PondChannel {
479
483
  *});
480
484
  */
481
485
  broadcast (event: string, payload: PondMessage, channelName?: string): void;
486
+
487
+ /**
488
+ * @desc Handles the leave event for a user, can occur when a user disconnects or leaves a channel, use this to clean up any resources
489
+ * @param callback - The callback to execute when a user leaves
490
+ */
491
+ public onLeave (callback: LeaveCallback): void;
482
492
  }
483
493
 
484
494
  declare class PondSocket {
@@ -515,6 +525,8 @@ declare class PondSocket {
515
525
  createEndpoint<Path extends string> (path: PondPath<Path>, handler: (request: IncomingConnection<Path>, response: ConnectionResponse) => void | Promise<void>): Endpoint;
516
526
  }
517
527
 
528
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
529
+ // @ts-ignore
518
530
  interface PondSocketExpressApp extends Express {
519
531
 
520
532
  /**
@@ -559,7 +571,7 @@ declare class PondClient {
559
571
  * @param name - The name of the channel.
560
572
  * @param params - The params to send to the server.
561
573
  */
562
- createChannel (name: string, params?: JoinParams): Channel;
574
+ createChannel (name: string, params?: JoinParams): ClientChannel;
563
575
 
564
576
  /**
565
577
  * @desc Subscribes to the connection state.
@@ -569,4 +581,3 @@ declare class PondClient {
569
581
  }
570
582
 
571
583
  declare const pondSocket: (app: Express) => PondSocketExpressApp;
572
-