shuttlepro-shared 1.1.68 → 1.1.70

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/index.js CHANGED
@@ -2,10 +2,12 @@ const bullConfig = require("./bull");
2
2
  const redisConfig = require("./redis");
3
3
  const config = require("./config");
4
4
  const databaseConfig = require("./database");
5
+ const socketConfig = require("./socket");
5
6
 
6
7
  module.exports = {
7
8
  bullConfig,
8
9
  redisConfig,
9
10
  config,
10
11
  databaseConfig,
12
+ socketConfig,
11
13
  };
package/config/redis.js CHANGED
@@ -1,18 +1,40 @@
1
1
  const { createClient } = require("redis");
2
- const { redis } = require("./config");
2
+ require("dotenv").config();
3
3
 
4
+ const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";
5
+
6
+ // ✅ Create a single Redis client instance
4
7
  const client = createClient({
5
- url: redis.url,
8
+ url: REDIS_URL,
9
+ socket: { reconnectStrategy: (retries) => Math.min(retries * 50, 1000) }, // Exponential backoff for reconnections
6
10
  });
7
- client.on("error", (err) => console.log("Redis Client Error", err));
8
11
 
12
+ // ✅ Handle Redis Connection Events
13
+ client.on("error", (err) => console.error("❌ Redis Error:", err));
14
+ client.on("connect", () => console.log("✅ Redis Connected"));
15
+ client.on("reconnecting", () => console.warn("⚠️ Redis Reconnecting..."));
16
+ client.on("ready", () => console.log("🚀 Redis Ready to use"));
17
+ client.on("end", () => console.log("❗ Redis Connection Closed"));
18
+
19
+ // ✅ Ensure Redis is connected before performing operations
9
20
  const connectRedis = async () => {
10
21
  if (!client.isOpen) {
11
- await client.connect();
22
+ try {
23
+ await client.connect();
24
+ } catch (err) {
25
+ console.error("❌ Failed to connect to Redis:", err);
26
+ }
12
27
  }
13
28
  };
14
29
 
15
- exports.setRedisData = async (
30
+ /**
31
+ * ✅ Set Data in Redis (Supports String & Hash)
32
+ * @param {string} key - The Redis key
33
+ * @param {any} value - The value to store
34
+ * @param {string|null} field - Optional field for Hash storage
35
+ * @param {number} expiryInSeconds - Expiry time in seconds (default: 3600)
36
+ */
37
+ const setRedisData = async (
16
38
  key,
17
39
  value,
18
40
  field = null,
@@ -20,59 +42,78 @@ exports.setRedisData = async (
20
42
  ) => {
21
43
  try {
22
44
  await connectRedis();
23
- let resp;
45
+ let result;
24
46
  if (field) {
25
- resp = await client.hSet(key, field, JSON.stringify(value));
47
+ result = await client.hSet(key, field, JSON.stringify(value));
26
48
  } else {
27
- resp = await client.set(key, JSON.stringify(value));
49
+ result = await client.set(key, JSON.stringify(value), {
50
+ EX: expiryInSeconds,
51
+ });
28
52
  }
29
- await client.expire(key, expiryInSeconds); // Set expiry for the key
30
- return resp;
53
+ return result;
31
54
  } catch (err) {
32
- console.error("Error setting Redis data:", err);
55
+ console.error("Error setting Redis data:", err);
33
56
  throw err;
34
57
  }
35
58
  };
36
59
 
37
- exports.getRedisData = async (key, field = null) => {
60
+ /**
61
+ * ✅ Get Data from Redis (Supports String & Hash)
62
+ * @param {string} key - The Redis key
63
+ * @param {string|null} field - Optional field for Hash retrieval
64
+ */
65
+ const getRedisData = async (key, field = null) => {
38
66
  try {
39
67
  await connectRedis();
40
- let resp;
41
- if (field) {
42
- resp = await client.hGet(key, field);
43
- } else {
44
- resp = await client.get(key);
45
- }
46
- return resp ? JSON.parse(resp) : null;
68
+ let result = field ? await client.hGet(key, field) : await client.get(key);
69
+ return result ? JSON.parse(result) : null;
47
70
  } catch (err) {
48
- console.error("Error getting Redis data:", err);
71
+ console.error("Error getting Redis data:", err);
49
72
  throw err;
50
73
  }
51
74
  };
52
75
 
53
- exports.deleteRedisData = async (key) => {
76
+ /**
77
+ * ✅ Delete Data from Redis (Supports String & Hash)
78
+ * @param {string} key - The Redis key
79
+ * @param {string|null} field - Optional field for Hash deletion
80
+ */
81
+ const deleteRedisData = async (key, field = null) => {
54
82
  try {
55
83
  await connectRedis();
56
- return await client.del(key);
84
+ return field ? await client.hDel(key, field) : await client.del(key);
57
85
  } catch (err) {
58
- console.error("Error deleting Redis data:", err);
86
+ console.error("Error deleting Redis data:", err);
59
87
  throw err;
60
88
  }
61
89
  };
62
90
 
63
- // Ensure the Redis client is closed when the application exits
64
- const closeClient = () => {
91
+ /**
92
+ * Graceful Shutdown Handling
93
+ */
94
+ const closeClient = async () => {
65
95
  if (client.isOpen) {
66
- client.quit();
96
+ await client.quit();
97
+ console.log("❗ Redis Disconnected Gracefully");
67
98
  }
68
99
  };
69
100
 
101
+ // ✅ Ensure Redis disconnects properly on process exit
70
102
  process.on("exit", closeClient);
71
- process.on("SIGINT", () => {
72
- closeClient();
103
+ process.on("SIGINT", async () => {
104
+ await closeClient();
73
105
  process.exit();
74
106
  });
75
- process.on("SIGTERM", () => {
76
- closeClient();
107
+ process.on("SIGTERM", async () => {
108
+ await closeClient();
77
109
  process.exit();
78
110
  });
111
+
112
+ // ✅ Export Functions
113
+ module.exports = {
114
+ client, // Export Redis client for reuse
115
+ setRedisData,
116
+ getRedisData,
117
+ deleteRedisData,
118
+ connectRedis,
119
+ };
@@ -0,0 +1,116 @@
1
+ const { Server } = require("socket.io");
2
+ const { client: redisClient, connectRedis } = require("./redis");
3
+ const { createClient } = require("redis");
4
+ require("dotenv").config();
5
+
6
+ let io;
7
+
8
+ /**
9
+ * ✅ Initialize WebSocket Server with Redis Pub/Sub
10
+ * @param {Object} server - HTTP Server instance
11
+ * @param {string|null} namespace - Optional namespace (passed from `index.js`)
12
+ * @param {string} [redisChannel="socket_events"] - Custom Redis Pub/Sub Channel
13
+ */
14
+ const initializeSocket = async (
15
+ server,
16
+ namespace = null,
17
+ redisChannel = "socket_events"
18
+ ) => {
19
+ await connectRedis(); // Ensure Redis is connected
20
+
21
+ io = new Server(server, {
22
+ cors: { origin: "*" },
23
+ });
24
+
25
+ console.log(
26
+ `✅ WebSocket Server Initialized ${
27
+ namespace ? `with namespace: ${namespace}` : "globally"
28
+ }, using Redis channel: ${redisChannel}`
29
+ );
30
+
31
+ // Create Redis Pub/Sub Clients
32
+ const pubClient = redisClient;
33
+ const subClient = createClient({ url: process.env.REDIS_URL });
34
+ await subClient.connect();
35
+
36
+ // ✅ Subscribe to the dynamic Redis channel
37
+ await subClient.subscribe(redisChannel, (message) => {
38
+ const { workspaceId, event, data } = JSON.parse(message);
39
+
40
+ if (namespace) {
41
+ io.of(namespace).to(workspaceId).emit(event, data);
42
+ } else {
43
+ io.to(workspaceId).emit(event, data);
44
+ }
45
+ });
46
+
47
+ // ✅ Handle WebSocket Connection (Global or Namespace)
48
+ const socketServer = namespace ? io.of(namespace) : io;
49
+ socketServer.on("connection", (socket) =>
50
+ handleConnection(socket, namespace)
51
+ );
52
+ };
53
+
54
+ /**
55
+ * ✅ Handle WebSocket Connection
56
+ * @param {Object} socket - WebSocket client socket
57
+ * @param {string} [namespace] - Optional namespace
58
+ */
59
+ const handleConnection = async (socket, namespace = null) => {
60
+ const { workspaceId } = socket.handshake.query;
61
+
62
+ if (!workspaceId) {
63
+ console.warn(`❌ Client ${socket.id} rejected due to missing workspaceId`);
64
+ socket.disconnect(true);
65
+ return;
66
+ }
67
+
68
+ console.log(
69
+ `✅ Client connected: ${socket.id} (Workspace: ${workspaceId}) Namespace: ${
70
+ namespace || "Global"
71
+ }`
72
+ );
73
+
74
+ await redisClient.hSet(
75
+ `${workspaceId}:sockets`,
76
+ socket.id,
77
+ JSON.stringify({
78
+ id: socket.id,
79
+ workspaceId,
80
+ namespace,
81
+ connectedAt: new Date().toISOString(),
82
+ })
83
+ );
84
+
85
+ // ✅ Handle Disconnection
86
+ socket.on("disconnect", async () => {
87
+ console.log(
88
+ `❌ Client disconnected: ${socket.id} (Workspace: ${workspaceId})`
89
+ );
90
+ await redisClient.hDel(`${workspaceId}:sockets`, socket.id);
91
+ });
92
+ };
93
+
94
+ /**
95
+ * ✅ Publish WebSocket Events via Redis
96
+ * @param {string} workspaceId - Target workspace ID
97
+ * @param {string} event - Event name
98
+ * @param {Object} data - Event payload
99
+ * @param {string} [redisChannel="socket_events"] - Custom Redis Pub/Sub Channel
100
+ */
101
+ const sendEventToServer = async ({
102
+ workspaceId,
103
+ event,
104
+ data,
105
+ redisChannel = "socket_events",
106
+ }) => {
107
+ await redisClient.publish(
108
+ redisChannel,
109
+ JSON.stringify({ workspaceId, event, data })
110
+ );
111
+ };
112
+
113
+ module.exports = {
114
+ initializeSocket,
115
+ sendEventToServer,
116
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shuttlepro-shared",
3
- "version": "1.1.68",
3
+ "version": "1.1.70",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,6 +22,7 @@
22
22
  "mongoose": "^8.7.1",
23
23
  "cors": "^2.8.5",
24
24
  "helmet": "^8.0.0",
25
- "bull": "^4.10.4"
25
+ "bull": "^4.10.4",
26
+ "socket.io": "^4.8.1"
26
27
  }
27
28
  }