wexa-chat 0.1.21 → 0.1.22
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 +89 -1
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +88 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -286,9 +286,97 @@ You can also send:
|
|
|
286
286
|
```ts
|
|
287
287
|
send({ type: 'leave-conversation', conversationId });
|
|
288
288
|
send({ type: 'typing', conversationId, isTyping: true });
|
|
289
|
+
|
|
290
|
+
// New: User-based subscriptions
|
|
291
|
+
send({ type: 'subscribe-to-user', userId: 'user-123' });
|
|
292
|
+
send({ type: 'unsubscribe-from-user', userId: 'user-123' });
|
|
289
293
|
```
|
|
290
294
|
|
|
291
|
-
##
|
|
295
|
+
## User-Based Subscriptions
|
|
296
|
+
|
|
297
|
+
The chat core now supports subscribing to all messages for a specific user, allowing you to receive messages from all conversations involving that user without needing to join each conversation individually.
|
|
298
|
+
|
|
299
|
+
### Server-Side Usage
|
|
300
|
+
|
|
301
|
+
The transport layer exposes new methods for user-based subscriptions:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
// Access the transport from your chat instance
|
|
305
|
+
const { transport } = await getChatInstance();
|
|
306
|
+
|
|
307
|
+
// Subscribe a socket to all messages for a user
|
|
308
|
+
transport.subscribeToUser(userId, socketId);
|
|
309
|
+
|
|
310
|
+
// Unsubscribe a socket from a user's messages
|
|
311
|
+
transport.unsubscribeFromUser(userId, socketId);
|
|
312
|
+
|
|
313
|
+
// Check if a socket is subscribed to a user
|
|
314
|
+
const isSubscribed = transport.isSubscribedToUser(userId, socketId);
|
|
315
|
+
|
|
316
|
+
// Fan out a message to all subscribers of a user
|
|
317
|
+
transport.fanoutToUser(userId, payload, (toSocketId, event) => {
|
|
318
|
+
// Your emit logic here
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Client-Side Usage
|
|
323
|
+
|
|
324
|
+
Subscribe to all messages for a user:
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
import { useEffect } from 'react';
|
|
328
|
+
import { useSocket } from 'wexa-chat/client';
|
|
329
|
+
|
|
330
|
+
export function GlobalMessageListener({ userId }: { userId: string }) {
|
|
331
|
+
const { isConnected, send, onEvent } = useSocket();
|
|
332
|
+
|
|
333
|
+
// Subscribe to user messages
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (isConnected && userId) {
|
|
336
|
+
send({ type: 'subscribe-to-user', userId });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return () => {
|
|
340
|
+
if (isConnected && userId) {
|
|
341
|
+
send({ type: 'unsubscribe-from-user', userId });
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}, [isConnected, userId, send]);
|
|
345
|
+
|
|
346
|
+
// Listen for messages
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
const off = onEvent((event) => {
|
|
349
|
+
if (event.type === 'message:created') {
|
|
350
|
+
// Handle message from any conversation involving the subscribed user
|
|
351
|
+
console.log('New message:', event.message);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return off;
|
|
356
|
+
}, [onEvent]);
|
|
357
|
+
|
|
358
|
+
return null; // This component just listens for messages
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Use Cases
|
|
363
|
+
|
|
364
|
+
- **Global notifications**: Receive notifications for all messages involving a user
|
|
365
|
+
- **Unread message tracking**: Track unread messages across all conversations
|
|
366
|
+
- **Real-time updates**: Keep conversation lists updated without joining each conversation
|
|
367
|
+
- **Background message sync**: Sync messages even when not actively viewing conversations
|
|
368
|
+
|
|
369
|
+
## Events
|
|
370
|
+
|
|
371
|
+
### Client → Server
|
|
372
|
+
|
|
373
|
+
- `join-conversation` — `{ type: 'join-conversation', conversationId }`
|
|
374
|
+
- `leave-conversation` — `{ type: 'leave-conversation', conversationId }`
|
|
375
|
+
- `typing` — `{ type: 'typing', conversationId, isTyping: boolean }`
|
|
376
|
+
- `subscribe-to-user` — `{ type: 'subscribe-to-user', userId }` *(New)*
|
|
377
|
+
- `unsubscribe-from-user` — `{ type: 'unsubscribe-from-user', userId }` *(New)*
|
|
378
|
+
|
|
379
|
+
### Server → Client
|
|
292
380
|
|
|
293
381
|
- `message:created` — `{ type: 'message:created', message }`
|
|
294
382
|
- `conversation:read` — `{ type: 'conversation:read', conversationId, messageId, participantModel, participantId, at }`
|
package/dist/index.d.cts
CHANGED
|
@@ -315,7 +315,7 @@ declare function createMessagesService(models: {
|
|
|
315
315
|
}, hooks?: MessageServiceHooks): MessagesService;
|
|
316
316
|
|
|
317
317
|
/**
|
|
318
|
-
* In-memory subscription maps for conversations and sockets
|
|
318
|
+
* In-memory subscription maps for conversations, users, and sockets
|
|
319
319
|
*/
|
|
320
320
|
/**
|
|
321
321
|
* Subscribe a socket to a conversation
|
|
@@ -341,6 +341,36 @@ declare function cleanupLocal(socketId: string): void;
|
|
|
341
341
|
* @param emitter Function to emit events to socket IDs
|
|
342
342
|
*/
|
|
343
343
|
declare function fanoutLocal(conversationId: string, payload: any, emitter: (toSocketId: string, event: any) => void): void;
|
|
344
|
+
/**
|
|
345
|
+
* Subscribe a socket to all messages for a user
|
|
346
|
+
* @param userId The user ID to subscribe to
|
|
347
|
+
* @param socketId The socket ID to subscribe
|
|
348
|
+
*/
|
|
349
|
+
declare function subscribeToUser(userId: string, socketId: string): void;
|
|
350
|
+
/**
|
|
351
|
+
* Unsubscribe a socket from a user's messages
|
|
352
|
+
* @param userId The user ID to unsubscribe from
|
|
353
|
+
* @param socketId The socket ID to unsubscribe
|
|
354
|
+
*/
|
|
355
|
+
declare function unsubscribeFromUser(userId: string, socketId: string): void;
|
|
356
|
+
/**
|
|
357
|
+
* Check if a socket is subscribed to a user
|
|
358
|
+
* @param userId The user ID to check
|
|
359
|
+
* @param socketId The socket ID to check
|
|
360
|
+
*/
|
|
361
|
+
declare function isSubscribedToUser(userId: string, socketId: string): boolean;
|
|
362
|
+
/**
|
|
363
|
+
* Get all sockets subscribed to a user
|
|
364
|
+
* @param userId The user ID
|
|
365
|
+
*/
|
|
366
|
+
declare function getSocketsForUser(userId: string): Set<string>;
|
|
367
|
+
/**
|
|
368
|
+
* Fan out a message to all local subscribers of a user
|
|
369
|
+
* @param userId The user ID to fan out to
|
|
370
|
+
* @param payload The payload to send
|
|
371
|
+
* @param emitter Function to emit events to socket IDs
|
|
372
|
+
*/
|
|
373
|
+
declare function fanoutToUser(userId: string, payload: any, emitter: (toSocketId: string, event: any) => void): void;
|
|
344
374
|
|
|
345
375
|
interface SocketConfig {
|
|
346
376
|
path?: string;
|
|
@@ -355,6 +385,7 @@ declare class SocketTransport {
|
|
|
355
385
|
private sockets;
|
|
356
386
|
private users;
|
|
357
387
|
private joinedConversations;
|
|
388
|
+
private subscribedToUsers;
|
|
358
389
|
private authenticate?;
|
|
359
390
|
private ioShim;
|
|
360
391
|
constructor(server: Server, transport: Transport, config?: SocketConfig);
|
|
@@ -391,6 +422,11 @@ interface Transport {
|
|
|
391
422
|
unsubscribeLocal: typeof unsubscribeLocal;
|
|
392
423
|
cleanupLocal: typeof cleanupLocal;
|
|
393
424
|
fanoutLocal: typeof fanoutLocal;
|
|
425
|
+
subscribeToUser: typeof subscribeToUser;
|
|
426
|
+
unsubscribeFromUser: typeof unsubscribeFromUser;
|
|
427
|
+
isSubscribedToUser: typeof isSubscribedToUser;
|
|
428
|
+
getSocketsForUser: typeof getSocketsForUser;
|
|
429
|
+
fanoutToUser: typeof fanoutToUser;
|
|
394
430
|
publishToConversation: (conversationId: string, payload: any) => Promise<number>;
|
|
395
431
|
startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => (() => void) | null;
|
|
396
432
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -315,7 +315,7 @@ declare function createMessagesService(models: {
|
|
|
315
315
|
}, hooks?: MessageServiceHooks): MessagesService;
|
|
316
316
|
|
|
317
317
|
/**
|
|
318
|
-
* In-memory subscription maps for conversations and sockets
|
|
318
|
+
* In-memory subscription maps for conversations, users, and sockets
|
|
319
319
|
*/
|
|
320
320
|
/**
|
|
321
321
|
* Subscribe a socket to a conversation
|
|
@@ -341,6 +341,36 @@ declare function cleanupLocal(socketId: string): void;
|
|
|
341
341
|
* @param emitter Function to emit events to socket IDs
|
|
342
342
|
*/
|
|
343
343
|
declare function fanoutLocal(conversationId: string, payload: any, emitter: (toSocketId: string, event: any) => void): void;
|
|
344
|
+
/**
|
|
345
|
+
* Subscribe a socket to all messages for a user
|
|
346
|
+
* @param userId The user ID to subscribe to
|
|
347
|
+
* @param socketId The socket ID to subscribe
|
|
348
|
+
*/
|
|
349
|
+
declare function subscribeToUser(userId: string, socketId: string): void;
|
|
350
|
+
/**
|
|
351
|
+
* Unsubscribe a socket from a user's messages
|
|
352
|
+
* @param userId The user ID to unsubscribe from
|
|
353
|
+
* @param socketId The socket ID to unsubscribe
|
|
354
|
+
*/
|
|
355
|
+
declare function unsubscribeFromUser(userId: string, socketId: string): void;
|
|
356
|
+
/**
|
|
357
|
+
* Check if a socket is subscribed to a user
|
|
358
|
+
* @param userId The user ID to check
|
|
359
|
+
* @param socketId The socket ID to check
|
|
360
|
+
*/
|
|
361
|
+
declare function isSubscribedToUser(userId: string, socketId: string): boolean;
|
|
362
|
+
/**
|
|
363
|
+
* Get all sockets subscribed to a user
|
|
364
|
+
* @param userId The user ID
|
|
365
|
+
*/
|
|
366
|
+
declare function getSocketsForUser(userId: string): Set<string>;
|
|
367
|
+
/**
|
|
368
|
+
* Fan out a message to all local subscribers of a user
|
|
369
|
+
* @param userId The user ID to fan out to
|
|
370
|
+
* @param payload The payload to send
|
|
371
|
+
* @param emitter Function to emit events to socket IDs
|
|
372
|
+
*/
|
|
373
|
+
declare function fanoutToUser(userId: string, payload: any, emitter: (toSocketId: string, event: any) => void): void;
|
|
344
374
|
|
|
345
375
|
interface SocketConfig {
|
|
346
376
|
path?: string;
|
|
@@ -355,6 +385,7 @@ declare class SocketTransport {
|
|
|
355
385
|
private sockets;
|
|
356
386
|
private users;
|
|
357
387
|
private joinedConversations;
|
|
388
|
+
private subscribedToUsers;
|
|
358
389
|
private authenticate?;
|
|
359
390
|
private ioShim;
|
|
360
391
|
constructor(server: Server, transport: Transport, config?: SocketConfig);
|
|
@@ -391,6 +422,11 @@ interface Transport {
|
|
|
391
422
|
unsubscribeLocal: typeof unsubscribeLocal;
|
|
392
423
|
cleanupLocal: typeof cleanupLocal;
|
|
393
424
|
fanoutLocal: typeof fanoutLocal;
|
|
425
|
+
subscribeToUser: typeof subscribeToUser;
|
|
426
|
+
unsubscribeFromUser: typeof unsubscribeFromUser;
|
|
427
|
+
isSubscribedToUser: typeof isSubscribedToUser;
|
|
428
|
+
getSocketsForUser: typeof getSocketsForUser;
|
|
429
|
+
fanoutToUser: typeof fanoutToUser;
|
|
394
430
|
publishToConversation: (conversationId: string, payload: any) => Promise<number>;
|
|
395
431
|
startRedisListener: (onEvent: (conversationId: string, payload: any) => void) => (() => void) | null;
|
|
396
432
|
}
|
package/dist/index.js
CHANGED
|
@@ -665,6 +665,8 @@ function createMessagesService(models, hooks = {}) {
|
|
|
665
665
|
// src/transport/localSubs.ts
|
|
666
666
|
var convSubs = /* @__PURE__ */ new Map();
|
|
667
667
|
var socketSubs = /* @__PURE__ */ new Map();
|
|
668
|
+
var userSubs = /* @__PURE__ */ new Map();
|
|
669
|
+
var socketUserSubs = /* @__PURE__ */ new Map();
|
|
668
670
|
function subscribeLocal(conversationId, socketId) {
|
|
669
671
|
var _a, _b;
|
|
670
672
|
if (!convSubs.has(conversationId)) {
|
|
@@ -706,6 +708,19 @@ function cleanupLocal(socketId) {
|
|
|
706
708
|
}
|
|
707
709
|
socketSubs.delete(socketId);
|
|
708
710
|
}
|
|
711
|
+
const users = socketUserSubs.get(socketId);
|
|
712
|
+
if (users) {
|
|
713
|
+
for (const userId of users) {
|
|
714
|
+
const sockets = userSubs.get(userId);
|
|
715
|
+
if (sockets) {
|
|
716
|
+
sockets.delete(socketId);
|
|
717
|
+
if (sockets.size === 0) {
|
|
718
|
+
userSubs.delete(userId);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
socketUserSubs.delete(socketId);
|
|
723
|
+
}
|
|
709
724
|
}
|
|
710
725
|
function fanoutLocal(conversationId, payload, emitter) {
|
|
711
726
|
const sockets = convSubs.get(conversationId);
|
|
@@ -719,6 +734,52 @@ function fanoutLocal(conversationId, payload, emitter) {
|
|
|
719
734
|
}
|
|
720
735
|
}
|
|
721
736
|
}
|
|
737
|
+
function subscribeToUser(userId, socketId) {
|
|
738
|
+
var _a, _b;
|
|
739
|
+
if (!userSubs.has(userId)) {
|
|
740
|
+
userSubs.set(userId, /* @__PURE__ */ new Set());
|
|
741
|
+
}
|
|
742
|
+
(_a = userSubs.get(userId)) == null ? void 0 : _a.add(socketId);
|
|
743
|
+
if (!socketUserSubs.has(socketId)) {
|
|
744
|
+
socketUserSubs.set(socketId, /* @__PURE__ */ new Set());
|
|
745
|
+
}
|
|
746
|
+
(_b = socketUserSubs.get(socketId)) == null ? void 0 : _b.add(userId);
|
|
747
|
+
}
|
|
748
|
+
function unsubscribeFromUser(userId, socketId) {
|
|
749
|
+
const sockets = userSubs.get(userId);
|
|
750
|
+
if (sockets) {
|
|
751
|
+
sockets.delete(socketId);
|
|
752
|
+
if (sockets.size === 0) {
|
|
753
|
+
userSubs.delete(userId);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
const users = socketUserSubs.get(socketId);
|
|
757
|
+
if (users) {
|
|
758
|
+
users.delete(userId);
|
|
759
|
+
if (users.size === 0) {
|
|
760
|
+
socketUserSubs.delete(socketId);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function isSubscribedToUser(userId, socketId) {
|
|
765
|
+
var _a;
|
|
766
|
+
return ((_a = socketUserSubs.get(socketId)) == null ? void 0 : _a.has(userId)) || false;
|
|
767
|
+
}
|
|
768
|
+
function getSocketsForUser(userId) {
|
|
769
|
+
return userSubs.get(userId) || /* @__PURE__ */ new Set();
|
|
770
|
+
}
|
|
771
|
+
function fanoutToUser(userId, payload, emitter) {
|
|
772
|
+
const sockets = userSubs.get(userId);
|
|
773
|
+
if (sockets) {
|
|
774
|
+
for (const socketId of sockets) {
|
|
775
|
+
try {
|
|
776
|
+
emitter(socketId, payload);
|
|
777
|
+
} catch (error) {
|
|
778
|
+
console.error(`Error emitting to socket ${socketId}:`, error);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
722
783
|
var SHARED_CLIENT = null;
|
|
723
784
|
var SHARED_CFG_FINGERPRINT = null;
|
|
724
785
|
function createRedisClient(config) {
|
|
@@ -815,6 +876,8 @@ var SocketTransport = class {
|
|
|
815
876
|
this.sockets = /* @__PURE__ */ new Map();
|
|
816
877
|
this.users = /* @__PURE__ */ new Map();
|
|
817
878
|
this.joinedConversations = /* @__PURE__ */ new Map();
|
|
879
|
+
// socketId -> convIds
|
|
880
|
+
this.subscribedToUsers = /* @__PURE__ */ new Map();
|
|
818
881
|
// Socket.IO-like shim for getIO().sockets.sockets.get(id).emit('chat-event', payload)
|
|
819
882
|
this.ioShim = {
|
|
820
883
|
sockets: {
|
|
@@ -924,6 +987,7 @@ var SocketTransport = class {
|
|
|
924
987
|
this.joinedConversations.set(socketId, /* @__PURE__ */ new Set());
|
|
925
988
|
syncShim();
|
|
926
989
|
ws$1.on("message", (raw) => {
|
|
990
|
+
var _a;
|
|
927
991
|
try {
|
|
928
992
|
const msg = JSON.parse(raw.toString());
|
|
929
993
|
const type = msg == null ? void 0 : msg.type;
|
|
@@ -994,6 +1058,17 @@ var SocketTransport = class {
|
|
|
994
1058
|
}
|
|
995
1059
|
});
|
|
996
1060
|
this.transport.publishToConversation(convId, payload);
|
|
1061
|
+
} else if (type === "subscribe-to-user" && typeof msg.userId === "string") {
|
|
1062
|
+
const userId2 = msg.userId;
|
|
1063
|
+
this.transport.subscribeToUser(userId2, socketId);
|
|
1064
|
+
if (!this.subscribedToUsers.has(socketId)) {
|
|
1065
|
+
this.subscribedToUsers.set(socketId, /* @__PURE__ */ new Set());
|
|
1066
|
+
}
|
|
1067
|
+
this.subscribedToUsers.get(socketId).add(userId2);
|
|
1068
|
+
} else if (type === "unsubscribe-from-user" && typeof msg.userId === "string") {
|
|
1069
|
+
const userId2 = msg.userId;
|
|
1070
|
+
this.transport.unsubscribeFromUser(userId2, socketId);
|
|
1071
|
+
(_a = this.subscribedToUsers.get(socketId)) == null ? void 0 : _a.delete(userId2);
|
|
997
1072
|
}
|
|
998
1073
|
} catch {
|
|
999
1074
|
}
|
|
@@ -1022,6 +1097,13 @@ var SocketTransport = class {
|
|
|
1022
1097
|
});
|
|
1023
1098
|
}
|
|
1024
1099
|
}
|
|
1100
|
+
const subscribedUsers = this.subscribedToUsers.get(socketId);
|
|
1101
|
+
if (subscribedUsers) {
|
|
1102
|
+
for (const userId2 of subscribedUsers) {
|
|
1103
|
+
this.transport.unsubscribeFromUser(userId2, socketId);
|
|
1104
|
+
}
|
|
1105
|
+
this.subscribedToUsers.delete(socketId);
|
|
1106
|
+
}
|
|
1025
1107
|
this.transport.cleanupLocal(socketId);
|
|
1026
1108
|
this.sockets.delete(socketId);
|
|
1027
1109
|
this.users.delete(socketId);
|
|
@@ -1086,6 +1168,12 @@ function createTransport(config, server) {
|
|
|
1086
1168
|
unsubscribeLocal,
|
|
1087
1169
|
cleanupLocal,
|
|
1088
1170
|
fanoutLocal,
|
|
1171
|
+
// User subscription methods
|
|
1172
|
+
subscribeToUser,
|
|
1173
|
+
unsubscribeFromUser,
|
|
1174
|
+
isSubscribedToUser,
|
|
1175
|
+
getSocketsForUser,
|
|
1176
|
+
fanoutToUser,
|
|
1089
1177
|
// Redis methods
|
|
1090
1178
|
publishToConversation: (conversationId, payload) => publishToConversation(redisClient, conversationId, payload),
|
|
1091
1179
|
startRedisListener: (onEvent) => startRedisListener(redisClient, onEvent)
|