shuttlepro-shared 1.1.73 → 1.1.75
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/config/redis.js +53 -12
- package/config/socket.js +108 -18
- package/package.json +1 -1
package/config/redis.js
CHANGED
|
@@ -3,16 +3,19 @@ require("dotenv").config();
|
|
|
3
3
|
|
|
4
4
|
const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";
|
|
5
5
|
|
|
6
|
-
// ✅ Create
|
|
6
|
+
// ✅ Create main Redis client
|
|
7
7
|
const client = createClient({
|
|
8
8
|
url: REDIS_URL,
|
|
9
|
-
socket: { reconnectStrategy: (retries) => Math.min(retries * 50, 1000) }, // Exponential backoff
|
|
9
|
+
socket: { reconnectStrategy: (retries) => Math.min(retries * 50, 1000) }, // Exponential backoff
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
+
// ✅ Create separate Pub/Sub clients
|
|
13
|
+
const publisher = client.duplicate();
|
|
14
|
+
const subscriber = client.duplicate();
|
|
15
|
+
|
|
12
16
|
// ✅ Handle Redis Connection Events
|
|
13
17
|
client.on("error", (err) => console.error("❌ Redis Error:", err));
|
|
14
18
|
client.on("connect", () => console.log("✅ Redis Connected"));
|
|
15
|
-
client.on("reconnecting", () => console.warn("⚠️ Redis Reconnecting..."));
|
|
16
19
|
client.on("ready", () => console.log("🚀 Redis Ready to use"));
|
|
17
20
|
client.on("end", () => console.log("❗ Redis Connection Closed"));
|
|
18
21
|
|
|
@@ -21,12 +24,46 @@ const connectRedis = async () => {
|
|
|
21
24
|
if (!client.isOpen) {
|
|
22
25
|
try {
|
|
23
26
|
await client.connect();
|
|
27
|
+
await publisher.connect();
|
|
28
|
+
await subscriber.connect();
|
|
24
29
|
} catch (err) {
|
|
25
30
|
console.error("❌ Failed to connect to Redis:", err);
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
33
|
};
|
|
29
34
|
|
|
35
|
+
/**
|
|
36
|
+
* ✅ Publish Data to a Channel
|
|
37
|
+
* @param {string} channel - The Redis Pub/Sub channel
|
|
38
|
+
* @param {any} message - The message to send
|
|
39
|
+
*/
|
|
40
|
+
const publishToChannel = async (channel, message) => {
|
|
41
|
+
try {
|
|
42
|
+
await connectRedis();
|
|
43
|
+
await publisher.publish(channel, JSON.stringify(message));
|
|
44
|
+
console.log(`📢 Message published to channel: ${channel}`);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error("❌ Error publishing message:", err);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* ✅ Subscribe & Listen for Messages
|
|
52
|
+
* @param {string} channel - The Redis Pub/Sub channel
|
|
53
|
+
* @param {function} callback - Callback function to handle messages
|
|
54
|
+
*/
|
|
55
|
+
const subscribeToChannel = async (channel, callback) => {
|
|
56
|
+
try {
|
|
57
|
+
await connectRedis();
|
|
58
|
+
await subscriber.subscribe(channel, (message) => {
|
|
59
|
+
console.log(`📥 Message received on channel: ${channel}`);
|
|
60
|
+
callback(JSON.parse(message));
|
|
61
|
+
});
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error("❌ Error subscribing to channel:", err);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
30
67
|
/**
|
|
31
68
|
* ✅ Set Data in Redis (Supports String & Hash)
|
|
32
69
|
* @param {string} key - The Redis key
|
|
@@ -91,29 +128,33 @@ const deleteRedisData = async (key, field = null) => {
|
|
|
91
128
|
/**
|
|
92
129
|
* ✅ Graceful Shutdown Handling
|
|
93
130
|
*/
|
|
94
|
-
const
|
|
95
|
-
if (client.isOpen)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
131
|
+
const closeClients = async () => {
|
|
132
|
+
if (client.isOpen) await client.quit();
|
|
133
|
+
if (publisher.isOpen) await publisher.quit();
|
|
134
|
+
if (subscriber.isOpen) await subscriber.quit();
|
|
135
|
+
console.log("❗ Redis Clients Disconnected Gracefully");
|
|
99
136
|
};
|
|
100
137
|
|
|
101
138
|
// ✅ Ensure Redis disconnects properly on process exit
|
|
102
|
-
process.on("exit",
|
|
139
|
+
process.on("exit", closeClients);
|
|
103
140
|
process.on("SIGINT", async () => {
|
|
104
|
-
await
|
|
141
|
+
await closeClients();
|
|
105
142
|
process.exit();
|
|
106
143
|
});
|
|
107
144
|
process.on("SIGTERM", async () => {
|
|
108
|
-
await
|
|
145
|
+
await closeClients();
|
|
109
146
|
process.exit();
|
|
110
147
|
});
|
|
111
148
|
|
|
112
149
|
// ✅ Export Functions
|
|
113
150
|
module.exports = {
|
|
114
|
-
client,
|
|
151
|
+
client,
|
|
152
|
+
publisher,
|
|
153
|
+
subscriber,
|
|
115
154
|
setRedisData,
|
|
116
155
|
getRedisData,
|
|
117
156
|
deleteRedisData,
|
|
157
|
+
publishToChannel,
|
|
158
|
+
subscribeToChannel,
|
|
118
159
|
connectRedis,
|
|
119
160
|
};
|
package/config/socket.js
CHANGED
|
@@ -1,50 +1,88 @@
|
|
|
1
1
|
const { Server } = require("socket.io");
|
|
2
|
-
const {
|
|
2
|
+
const { publisher, subscriber, connectRedis } = require("./redis");
|
|
3
3
|
require("dotenv").config();
|
|
4
4
|
|
|
5
5
|
let io;
|
|
6
6
|
const namespaceSockets = {}; // Store different namespace sockets
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Initialize Socket.IO server with namespaces
|
|
10
|
+
* @param {http.Server} server - HTTP server instance
|
|
11
|
+
* @param {string} namespaceParam - Socket namespace name (default: "conversation")
|
|
12
|
+
* @param {string} redisChannel - Redis channel for this namespace (default: "socket_events")
|
|
13
|
+
* @returns {SocketIO.Namespace} - The initialized namespace
|
|
14
|
+
*/
|
|
8
15
|
const initializeSocket = async (
|
|
9
16
|
server,
|
|
10
17
|
namespaceParam = "conversation",
|
|
11
18
|
redisChannel = "socket_events"
|
|
12
19
|
) => {
|
|
20
|
+
// Initialize Socket.IO server if not already done
|
|
13
21
|
if (!io) {
|
|
14
22
|
io = new Server(server, {
|
|
15
|
-
cors: {
|
|
23
|
+
cors: {
|
|
24
|
+
origin: "*",
|
|
25
|
+
methods: ["GET", "POST"],
|
|
26
|
+
},
|
|
16
27
|
});
|
|
28
|
+
console.log("✅ Socket.IO server initialized");
|
|
17
29
|
}
|
|
18
30
|
|
|
19
31
|
// Return existing namespace if already initialized
|
|
20
32
|
if (namespaceSockets[namespaceParam]) {
|
|
33
|
+
console.log(`📢 Using existing namespace: /${namespaceParam}`);
|
|
21
34
|
return namespaceSockets[namespaceParam];
|
|
22
35
|
}
|
|
23
36
|
|
|
24
|
-
// Create and store namespace
|
|
37
|
+
// Create and store new namespace
|
|
25
38
|
const namespace = io.of(`/${namespaceParam}`);
|
|
26
39
|
namespaceSockets[namespaceParam] = namespace;
|
|
40
|
+
console.log(`🚀 Created new namespace: /${namespaceParam}`);
|
|
27
41
|
|
|
28
|
-
// Ensure Redis
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
// Ensure Redis is connected
|
|
43
|
+
await connectRedis();
|
|
44
|
+
|
|
45
|
+
// Create a unique Redis channel for this namespace
|
|
46
|
+
const nsRedisChannel = `${redisChannel}_${namespaceParam}`;
|
|
32
47
|
|
|
33
48
|
// Subscribe to Redis events for this namespace
|
|
34
|
-
await
|
|
49
|
+
await subscriber.subscribe(nsRedisChannel, (messageStr) => {
|
|
35
50
|
try {
|
|
36
|
-
const
|
|
37
|
-
|
|
51
|
+
const message = JSON.parse(messageStr);
|
|
52
|
+
const { workspaceId, event, data } = message;
|
|
53
|
+
|
|
54
|
+
console.log(`📥 Redis message received on ${nsRedisChannel}:`, {
|
|
55
|
+
workspaceId,
|
|
56
|
+
event,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Emit to specific workspace room in this namespace
|
|
60
|
+
if (workspaceId) {
|
|
61
|
+
namespace.to(workspaceId).emit(event, data);
|
|
62
|
+
console.log(
|
|
63
|
+
`📢 Event ${event} emitted to workspace ${workspaceId} in /${namespaceParam}`
|
|
64
|
+
);
|
|
65
|
+
} else {
|
|
66
|
+
// Broadcast to all clients in this namespace if no workspaceId specified
|
|
67
|
+
namespace.emit(event, data);
|
|
68
|
+
console.log(
|
|
69
|
+
`📢 Event ${event} broadcasted to all clients in /${namespaceParam}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
38
72
|
} catch (err) {
|
|
39
|
-
console.error(
|
|
73
|
+
console.error(
|
|
74
|
+
`❌ Error handling Redis message on ${nsRedisChannel}:`,
|
|
75
|
+
err
|
|
76
|
+
);
|
|
40
77
|
}
|
|
41
78
|
});
|
|
42
79
|
|
|
43
|
-
// Handle new socket connections
|
|
80
|
+
// Handle new socket connections to this namespace
|
|
44
81
|
namespace.on("connection", (socket) => {
|
|
45
82
|
const { workspaceId } = socket.handshake.query;
|
|
46
83
|
|
|
47
84
|
if (!workspaceId) {
|
|
85
|
+
console.warn(`⚠️ Connection rejected: No workspaceId provided`);
|
|
48
86
|
socket.disconnect(true);
|
|
49
87
|
return;
|
|
50
88
|
}
|
|
@@ -53,29 +91,81 @@ const initializeSocket = async (
|
|
|
53
91
|
`✅ Client connected: ${socket.id} (Workspace: ${workspaceId}) in /${namespaceParam}`
|
|
54
92
|
);
|
|
55
93
|
|
|
56
|
-
|
|
94
|
+
// Join workspace room
|
|
95
|
+
socket.join(workspaceId);
|
|
96
|
+
|
|
97
|
+
// Send connection confirmation to client
|
|
98
|
+
socket.emit("connected", {
|
|
99
|
+
status: "connected",
|
|
100
|
+
socketId: socket.id,
|
|
101
|
+
namespace: namespaceParam,
|
|
102
|
+
workspaceId,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Handle custom events from clients
|
|
106
|
+
socket.on("client_event", (data) => {
|
|
107
|
+
console.log(`📥 Client event from ${socket.id}:`, data);
|
|
108
|
+
// You can process client events here
|
|
109
|
+
});
|
|
57
110
|
|
|
58
|
-
|
|
111
|
+
// Handle disconnect
|
|
112
|
+
socket.on("disconnect", (reason) => {
|
|
113
|
+
console.log(
|
|
114
|
+
`❌ Client disconnected: ${socket.id} (Workspace: ${workspaceId}, Reason: ${reason})`
|
|
115
|
+
);
|
|
59
116
|
socket.leave(workspaceId);
|
|
60
117
|
});
|
|
118
|
+
|
|
119
|
+
// Handle errors
|
|
120
|
+
socket.on("error", (error) => {
|
|
121
|
+
console.error(`❌ Socket error for ${socket.id}:`, error);
|
|
122
|
+
});
|
|
61
123
|
});
|
|
62
124
|
|
|
125
|
+
console.log(
|
|
126
|
+
`✅ Namespace /${namespaceParam} initialized and listening on Redis channel: ${nsRedisChannel}`
|
|
127
|
+
);
|
|
63
128
|
return namespace;
|
|
64
129
|
};
|
|
65
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Send event to clients via Redis pub/sub
|
|
133
|
+
* @param {object} params - Event parameters
|
|
134
|
+
* @param {string} params.workspaceId - Target workspace ID
|
|
135
|
+
* @param {string} params.event - Event name
|
|
136
|
+
* @param {any} params.data - Event data payload
|
|
137
|
+
* @param {string} [params.namespace="conversation"] - Target namespace
|
|
138
|
+
* @param {string} [params.redisChannel="socket_events"] - Base Redis channel
|
|
139
|
+
* @returns {Promise<void>}
|
|
140
|
+
*/
|
|
66
141
|
const sendEventToServer = async ({
|
|
67
142
|
workspaceId,
|
|
68
143
|
event,
|
|
69
144
|
data,
|
|
145
|
+
namespace = "conversation",
|
|
70
146
|
redisChannel = "socket_events",
|
|
71
147
|
}) => {
|
|
72
148
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
149
|
+
// Create namespace-specific Redis channel
|
|
150
|
+
const nsRedisChannel = `${redisChannel}_${namespace}`;
|
|
151
|
+
|
|
152
|
+
// Ensure Redis is connected
|
|
153
|
+
await connectRedis();
|
|
154
|
+
|
|
155
|
+
// Create message payload
|
|
156
|
+
const message = JSON.stringify({ workspaceId, event, data });
|
|
157
|
+
|
|
158
|
+
// Publish to Redis
|
|
159
|
+
await publisher.publish(nsRedisChannel, message);
|
|
160
|
+
|
|
161
|
+
console.log(
|
|
162
|
+
`📢 Event published to Redis channel ${nsRedisChannel}: ${event} (Workspace: ${workspaceId})`
|
|
76
163
|
);
|
|
164
|
+
|
|
165
|
+
return true;
|
|
77
166
|
} catch (err) {
|
|
78
|
-
console.error("Error publishing to Redis:", err);
|
|
167
|
+
console.error("❌ Error publishing to Redis:", err);
|
|
168
|
+
throw err;
|
|
79
169
|
}
|
|
80
170
|
};
|
|
81
171
|
|