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 +2 -0
- package/config/redis.js +71 -30
- package/config/socket.js +116 -0
- package/package.json +3 -2
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
45
|
+
let result;
|
|
24
46
|
if (field) {
|
|
25
|
-
|
|
47
|
+
result = await client.hSet(key, field, JSON.stringify(value));
|
|
26
48
|
} else {
|
|
27
|
-
|
|
49
|
+
result = await client.set(key, JSON.stringify(value), {
|
|
50
|
+
EX: expiryInSeconds,
|
|
51
|
+
});
|
|
28
52
|
}
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
+
};
|
package/config/socket.js
ADDED
|
@@ -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.
|
|
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
|
}
|