redux-cluster-ws 2.0.1 → 2.0.3
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/dist/cjs/client.js +0 -1
- package/dist/cjs/index.js +0 -1
- package/dist/cjs/server.js +0 -1
- package/dist/cjs/types.js +0 -1
- package/dist/cjs/utils.js +0 -1
- package/dist/esm/client.js +126 -141
- package/dist/esm/index.js +0 -1
- package/dist/esm/server.js +252 -263
- package/dist/esm/types.js +0 -1
- package/dist/esm/utils.js +0 -1
- package/package.json +22 -14
- package/FUNDING.yml +0 -7
- package/dist/cjs/client.js.map +0 -1
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/server.js.map +0 -1
- package/dist/cjs/types.js.map +0 -1
- package/dist/cjs/utils.js.map +0 -1
- package/dist/esm/client.d.ts +0 -43
- package/dist/esm/client.js.map +0 -1
- package/dist/esm/index.d.ts +0 -4
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/server.d.ts +0 -31
- package/dist/esm/server.js.map +0 -1
- package/dist/esm/types.d.ts +0 -83
- package/dist/esm/types.js.map +0 -1
- package/dist/esm/utils.d.ts +0 -17
- package/dist/esm/utils.js.map +0 -1
- package/eslint.config.js +0 -143
- package/examples/browser-example.cjs +0 -350
- package/examples/browser.html +0 -255
- package/examples/client.cjs +0 -155
- package/examples/cross-library-browser.html +0 -655
- package/examples/cross-library-client.cjs +0 -190
- package/examples/cross-library-server.cjs +0 -213
- package/examples/server.cjs +0 -96
- /package/dist/{cjs → types}/client.d.ts +0 -0
- /package/dist/{cjs → types}/index.d.ts +0 -0
- /package/dist/{cjs → types}/server.d.ts +0 -0
- /package/dist/{cjs → types}/types.d.ts +0 -0
- /package/dist/{cjs → types}/utils.d.ts +0 -0
package/dist/esm/server.js
CHANGED
|
@@ -5,295 +5,284 @@ import { hasher, replacer, generateUID } from "./utils.js";
|
|
|
5
5
|
// Import WebSocket server for Node.js
|
|
6
6
|
import { WebSocketServer } from "ws";
|
|
7
7
|
export class ReduxClusterWSServerWrapper {
|
|
8
|
-
store;
|
|
9
|
-
uid;
|
|
10
|
-
connections;
|
|
11
|
-
authDatabase;
|
|
12
|
-
ipBanDatabase;
|
|
13
|
-
config;
|
|
14
|
-
server;
|
|
15
|
-
wss;
|
|
16
|
-
unsubscribe;
|
|
17
|
-
IP_BAN_ATTEMPTS = 15;
|
|
18
|
-
IP_BAN_TIMEOUT = 10800000; // 3 hours
|
|
19
|
-
CONNECTION_TIMEOUT = 30000;
|
|
20
|
-
gcInterval;
|
|
21
8
|
constructor(store) {
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (this.config.logins) {
|
|
35
|
-
for (const [login, password] of Object.entries(this.config.logins)) {
|
|
36
|
-
const hashedLogin = hasher(`REDUX_CLUSTER${login}`);
|
|
37
|
-
const hashedPassword = hasher(`REDUX_CLUSTER${password}`);
|
|
38
|
-
this.authDatabase[hashedLogin] = hashedPassword;
|
|
9
|
+
this.IP_BAN_ATTEMPTS = 15;
|
|
10
|
+
this.IP_BAN_TIMEOUT = 10800000; // 3 hours
|
|
11
|
+
this.CONNECTION_TIMEOUT = 30000;
|
|
12
|
+
this.createWSServer = (config) => {
|
|
13
|
+
this.config = { ...this.config, ...config };
|
|
14
|
+
// Setup authentication database
|
|
15
|
+
if (this.config.logins) {
|
|
16
|
+
for (const [login, password] of Object.entries(this.config.logins)) {
|
|
17
|
+
const hashedLogin = hasher(`REDUX_CLUSTER${login}`);
|
|
18
|
+
const hashedPassword = hasher(`REDUX_CLUSTER${password}`);
|
|
19
|
+
this.authDatabase[hashedLogin] = hashedPassword;
|
|
20
|
+
}
|
|
39
21
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
22
|
+
// Start garbage collection for banned IPs
|
|
23
|
+
this.startGarbageCollection();
|
|
24
|
+
// Create HTTP/HTTPS server if not provided
|
|
25
|
+
if (!this.config.server) {
|
|
26
|
+
if (this.config.ssl?.key && this.config.ssl?.cert) {
|
|
27
|
+
const ssl = {
|
|
28
|
+
key: readFileSync(this.config.ssl.key),
|
|
29
|
+
cert: readFileSync(this.config.ssl.cert) +
|
|
30
|
+
(this.config.ssl.ca ? "\n" + readFileSync(this.config.ssl.ca) : ""),
|
|
31
|
+
};
|
|
32
|
+
this.server = createHttpsServer(ssl);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
this.server = createHttpServer();
|
|
36
|
+
}
|
|
37
|
+
this.server.setTimeout(this.CONNECTION_TIMEOUT);
|
|
38
|
+
this.server.listen(this.config.port, this.config.host);
|
|
52
39
|
}
|
|
53
40
|
else {
|
|
54
|
-
this.server =
|
|
41
|
+
this.server = this.config.server;
|
|
55
42
|
}
|
|
56
|
-
|
|
57
|
-
this.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
this.wss.on("connection", this.handleConnection.bind(this));
|
|
68
|
-
// Subscribe to store changes
|
|
69
|
-
this.unsubscribe = this.store.subscribe(() => {
|
|
70
|
-
if (this.store.mode === "snapshot") {
|
|
71
|
-
this.sendToAll();
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
// For action mode, override dispatch to send individual actions
|
|
75
|
-
if (this.store.mode === "action") {
|
|
76
|
-
const originalDispatch = this.store.dispatch;
|
|
77
|
-
this.store.dispatch = (action) => {
|
|
78
|
-
const result = originalDispatch(action);
|
|
79
|
-
// Send the action to all connected clients
|
|
80
|
-
if (action.type !== "@@INIT" && action.type !== "REDUX_CLUSTER_SYNC") {
|
|
81
|
-
this.sendToAll({
|
|
82
|
-
_msg: "REDUX_CLUSTER_MSGTOWORKER",
|
|
83
|
-
_hash: this.store.RCHash,
|
|
84
|
-
_action: action,
|
|
85
|
-
});
|
|
43
|
+
// Create WebSocket server
|
|
44
|
+
this.wss = new WebSocketServer({
|
|
45
|
+
server: this.server,
|
|
46
|
+
path: `/redux-cluster-${this.store.RCHash}`,
|
|
47
|
+
});
|
|
48
|
+
this.wss.on("connection", this.handleConnection.bind(this));
|
|
49
|
+
// Subscribe to store changes
|
|
50
|
+
this.unsubscribe = this.store.subscribe(() => {
|
|
51
|
+
if (this.store.mode === "snapshot") {
|
|
52
|
+
this.sendToAll();
|
|
86
53
|
}
|
|
87
|
-
|
|
54
|
+
});
|
|
55
|
+
// For action mode, override dispatch to send individual actions
|
|
56
|
+
if (this.store.mode === "action") {
|
|
57
|
+
const originalDispatch = this.store.dispatch;
|
|
58
|
+
this.store.dispatch = (action) => {
|
|
59
|
+
const result = originalDispatch(action);
|
|
60
|
+
// Send the action to all connected clients
|
|
61
|
+
if (action.type !== "@@INIT" && action.type !== "REDUX_CLUSTER_SYNC") {
|
|
62
|
+
this.sendToAll({
|
|
63
|
+
_msg: "REDUX_CLUSTER_MSGTOWORKER",
|
|
64
|
+
_hash: this.store.RCHash,
|
|
65
|
+
_action: action,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Add server role
|
|
72
|
+
if (!this.store.role.includes("server")) {
|
|
73
|
+
this.store.role.push("server");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
this.handleConnection = (ws, req) => {
|
|
77
|
+
const connectionId = generateUID();
|
|
78
|
+
const ip = this.extractIP(req);
|
|
79
|
+
const processedIP = replacer(ip, true);
|
|
80
|
+
// Check if IP is banned
|
|
81
|
+
if (this.isIPBanned(processedIP)) {
|
|
82
|
+
this.sendMessage(ws, {
|
|
83
|
+
_msg: "REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
84
|
+
_hash: this.store.RCHash,
|
|
85
|
+
_value: false,
|
|
86
|
+
_banned: true,
|
|
87
|
+
});
|
|
88
|
+
ws.close();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const connection = {
|
|
92
|
+
id: connectionId,
|
|
93
|
+
ws,
|
|
94
|
+
authenticated: false,
|
|
95
|
+
ip: processedIP,
|
|
88
96
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
this.connections.set(connectionId, connection);
|
|
98
|
+
ws.on("message", (data) => {
|
|
99
|
+
try {
|
|
100
|
+
const message = JSON.parse(data.toString());
|
|
101
|
+
this.handleMessage(connectionId, message);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.store.stderr(`ReduxCluster.createWSServer message parse error: ${error}`);
|
|
105
|
+
this.removeConnection(connectionId);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
ws.on("close", () => {
|
|
109
|
+
this.removeConnection(connectionId);
|
|
110
|
+
});
|
|
111
|
+
ws.on("error", (error) => {
|
|
112
|
+
this.store.stderr(`ReduxCluster.createWSServer connection error: ${error.message}`);
|
|
113
|
+
this.removeConnection(connectionId);
|
|
106
114
|
});
|
|
107
|
-
ws.close();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const connection = {
|
|
111
|
-
id: connectionId,
|
|
112
|
-
ws,
|
|
113
|
-
authenticated: false,
|
|
114
|
-
ip: processedIP,
|
|
115
115
|
};
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.handleMessage(connectionId, message);
|
|
116
|
+
this.handleMessage = (connectionId, message) => {
|
|
117
|
+
const connection = this.connections.get(connectionId);
|
|
118
|
+
if (!connection || message._hash !== this.store.RCHash) {
|
|
119
|
+
return;
|
|
121
120
|
}
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
switch (message._msg) {
|
|
122
|
+
case "REDUX_CLUSTER_SOCKET_AUTH":
|
|
123
|
+
this.handleAuthentication(connectionId, message);
|
|
124
|
+
break;
|
|
125
|
+
case "REDUX_CLUSTER_START":
|
|
126
|
+
if (connection.authenticated) {
|
|
127
|
+
this.sendMessage(connection.ws, {
|
|
128
|
+
_msg: "REDUX_CLUSTER_MSGTOWORKER",
|
|
129
|
+
_hash: this.store.RCHash,
|
|
130
|
+
_action: {
|
|
131
|
+
type: "REDUX_CLUSTER_SYNC",
|
|
132
|
+
payload: this.store.getState(),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
case "REDUX_CLUSTER_MSGTOMASTER":
|
|
138
|
+
if (connection.authenticated && message._action) {
|
|
139
|
+
if (message._action.type === "REDUX_CLUSTER_SYNC") {
|
|
140
|
+
throw new Error("Please don't use REDUX_CLUSTER_SYNC action type!");
|
|
141
|
+
}
|
|
142
|
+
this.store.dispatch(message._action);
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
this.handleAuthentication = (connectionId, message) => {
|
|
148
|
+
const connection = this.connections.get(connectionId);
|
|
149
|
+
if (!connection)
|
|
150
|
+
return;
|
|
151
|
+
const { _login, _password } = message;
|
|
152
|
+
if (_login &&
|
|
153
|
+
_password &&
|
|
154
|
+
this.authDatabase[_login] &&
|
|
155
|
+
this.authDatabase[_login] === _password) {
|
|
156
|
+
// Authentication successful
|
|
157
|
+
connection.authenticated = true;
|
|
158
|
+
this.clearIPBan(connection.ip);
|
|
159
|
+
this.sendMessage(connection.ws, {
|
|
160
|
+
_msg: "REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
161
|
+
_hash: this.store.RCHash,
|
|
162
|
+
_value: true,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// Authentication failed
|
|
167
|
+
this.recordFailedAttempt(connection.ip);
|
|
168
|
+
this.sendMessage(connection.ws, {
|
|
169
|
+
_msg: "REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
170
|
+
_hash: this.store.RCHash,
|
|
171
|
+
_value: false,
|
|
172
|
+
});
|
|
124
173
|
this.removeConnection(connectionId);
|
|
125
174
|
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
case "REDUX_CLUSTER_SOCKET_AUTH":
|
|
142
|
-
this.handleAuthentication(connectionId, message);
|
|
143
|
-
break;
|
|
144
|
-
case "REDUX_CLUSTER_START":
|
|
145
|
-
if (connection.authenticated) {
|
|
146
|
-
this.sendMessage(connection.ws, {
|
|
147
|
-
_msg: "REDUX_CLUSTER_MSGTOWORKER",
|
|
148
|
-
_hash: this.store.RCHash,
|
|
149
|
-
_action: {
|
|
150
|
-
type: "REDUX_CLUSTER_SYNC",
|
|
151
|
-
payload: this.store.getState(),
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
break;
|
|
156
|
-
case "REDUX_CLUSTER_MSGTOMASTER":
|
|
157
|
-
if (connection.authenticated && message._action) {
|
|
158
|
-
if (message._action.type === "REDUX_CLUSTER_SYNC") {
|
|
159
|
-
throw new Error("Please don't use REDUX_CLUSTER_SYNC action type!");
|
|
175
|
+
};
|
|
176
|
+
this.sendToAll = (message) => {
|
|
177
|
+
const msg = message || {
|
|
178
|
+
_msg: "REDUX_CLUSTER_MSGTOWORKER",
|
|
179
|
+
_hash: this.store.RCHash,
|
|
180
|
+
_action: { type: "REDUX_CLUSTER_SYNC", payload: this.store.getState() },
|
|
181
|
+
};
|
|
182
|
+
for (const connection of this.connections.values()) {
|
|
183
|
+
if (connection.authenticated && connection.ws.readyState === 1) {
|
|
184
|
+
// WebSocket.OPEN = 1
|
|
185
|
+
try {
|
|
186
|
+
this.sendMessage(connection.ws, msg);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
this.store.stderr(`ReduxCluster.createWSServer write error: ${error}`);
|
|
160
190
|
}
|
|
161
|
-
this.store.dispatch(message._action);
|
|
162
191
|
}
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
handleAuthentication = (connectionId, message) => {
|
|
167
|
-
const connection = this.connections.get(connectionId);
|
|
168
|
-
if (!connection)
|
|
169
|
-
return;
|
|
170
|
-
const { _login, _password } = message;
|
|
171
|
-
if (_login &&
|
|
172
|
-
_password &&
|
|
173
|
-
this.authDatabase[_login] &&
|
|
174
|
-
this.authDatabase[_login] === _password) {
|
|
175
|
-
// Authentication successful
|
|
176
|
-
connection.authenticated = true;
|
|
177
|
-
this.clearIPBan(connection.ip);
|
|
178
|
-
this.sendMessage(connection.ws, {
|
|
179
|
-
_msg: "REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
180
|
-
_hash: this.store.RCHash,
|
|
181
|
-
_value: true,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
// Authentication failed
|
|
186
|
-
this.recordFailedAttempt(connection.ip);
|
|
187
|
-
this.sendMessage(connection.ws, {
|
|
188
|
-
_msg: "REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
189
|
-
_hash: this.store.RCHash,
|
|
190
|
-
_value: false,
|
|
191
|
-
});
|
|
192
|
-
this.removeConnection(connectionId);
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
sendToAll = (message) => {
|
|
196
|
-
const msg = message || {
|
|
197
|
-
_msg: "REDUX_CLUSTER_MSGTOWORKER",
|
|
198
|
-
_hash: this.store.RCHash,
|
|
199
|
-
_action: { type: "REDUX_CLUSTER_SYNC", payload: this.store.getState() },
|
|
192
|
+
}
|
|
200
193
|
};
|
|
201
|
-
|
|
202
|
-
if (
|
|
194
|
+
this.sendMessage = (ws, message) => {
|
|
195
|
+
if (ws.readyState === 1) {
|
|
203
196
|
// WebSocket.OPEN = 1
|
|
197
|
+
ws.send(JSON.stringify(message));
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
this.extractIP = (req) => {
|
|
201
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
202
|
+
if (typeof forwarded === "string") {
|
|
203
|
+
return forwarded.split(",")[0].trim();
|
|
204
|
+
}
|
|
205
|
+
if (Array.isArray(forwarded)) {
|
|
206
|
+
return forwarded[0];
|
|
207
|
+
}
|
|
208
|
+
return req.socket.remoteAddress || "127.0.0.1";
|
|
209
|
+
};
|
|
210
|
+
this.isIPBanned = (ip) => {
|
|
211
|
+
const banInfo = this.ipBanDatabase[ip];
|
|
212
|
+
if (!banInfo)
|
|
213
|
+
return false;
|
|
214
|
+
const isTimedOut = banInfo.time + this.IP_BAN_TIMEOUT < Date.now();
|
|
215
|
+
if (isTimedOut) {
|
|
216
|
+
delete this.ipBanDatabase[ip];
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
return banInfo.count >= this.IP_BAN_ATTEMPTS;
|
|
220
|
+
};
|
|
221
|
+
this.recordFailedAttempt = (ip) => {
|
|
222
|
+
const existing = this.ipBanDatabase[ip];
|
|
223
|
+
let count = 0;
|
|
224
|
+
if (existing) {
|
|
225
|
+
const isTimedOut = existing.time + this.IP_BAN_TIMEOUT < Date.now();
|
|
226
|
+
count = isTimedOut ? 0 : existing.count;
|
|
227
|
+
}
|
|
228
|
+
this.ipBanDatabase[ip] = {
|
|
229
|
+
time: Date.now(),
|
|
230
|
+
count: count + 1,
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
this.clearIPBan = (ip) => {
|
|
234
|
+
delete this.ipBanDatabase[ip];
|
|
235
|
+
};
|
|
236
|
+
this.removeConnection = (connectionId) => {
|
|
237
|
+
const connection = this.connections.get(connectionId);
|
|
238
|
+
if (connection) {
|
|
204
239
|
try {
|
|
205
|
-
|
|
240
|
+
connection.ws.close();
|
|
206
241
|
}
|
|
207
|
-
catch
|
|
208
|
-
|
|
242
|
+
catch {
|
|
243
|
+
// Ignore errors when closing
|
|
209
244
|
}
|
|
245
|
+
this.connections.delete(connectionId);
|
|
210
246
|
}
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
sendMessage = (ws, message) => {
|
|
214
|
-
if (ws.readyState === 1) {
|
|
215
|
-
// WebSocket.OPEN = 1
|
|
216
|
-
ws.send(JSON.stringify(message));
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
extractIP = (req) => {
|
|
220
|
-
const forwarded = req.headers["x-forwarded-for"];
|
|
221
|
-
if (typeof forwarded === "string") {
|
|
222
|
-
return forwarded.split(",")[0].trim();
|
|
223
|
-
}
|
|
224
|
-
if (Array.isArray(forwarded)) {
|
|
225
|
-
return forwarded[0];
|
|
226
|
-
}
|
|
227
|
-
return req.socket.remoteAddress || "127.0.0.1";
|
|
228
|
-
};
|
|
229
|
-
isIPBanned = (ip) => {
|
|
230
|
-
const banInfo = this.ipBanDatabase[ip];
|
|
231
|
-
if (!banInfo)
|
|
232
|
-
return false;
|
|
233
|
-
const isTimedOut = banInfo.time + this.IP_BAN_TIMEOUT < Date.now();
|
|
234
|
-
if (isTimedOut) {
|
|
235
|
-
delete this.ipBanDatabase[ip];
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
return banInfo.count >= this.IP_BAN_ATTEMPTS;
|
|
239
|
-
};
|
|
240
|
-
recordFailedAttempt = (ip) => {
|
|
241
|
-
const existing = this.ipBanDatabase[ip];
|
|
242
|
-
let count = 0;
|
|
243
|
-
if (existing) {
|
|
244
|
-
const isTimedOut = existing.time + this.IP_BAN_TIMEOUT < Date.now();
|
|
245
|
-
count = isTimedOut ? 0 : existing.count;
|
|
246
|
-
}
|
|
247
|
-
this.ipBanDatabase[ip] = {
|
|
248
|
-
time: Date.now(),
|
|
249
|
-
count: count + 1,
|
|
250
247
|
};
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
248
|
+
this.startGarbageCollection = () => {
|
|
249
|
+
this.gcInterval = setInterval(() => {
|
|
250
|
+
const now = Date.now();
|
|
251
|
+
for (const [ip, banInfo] of Object.entries(this.ipBanDatabase)) {
|
|
252
|
+
if (banInfo.time + this.IP_BAN_TIMEOUT < now) {
|
|
253
|
+
delete this.ipBanDatabase[ip];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}, 60000); // Run every minute
|
|
257
|
+
};
|
|
258
|
+
this.destroy = () => {
|
|
259
|
+
if (this.gcInterval) {
|
|
260
|
+
clearInterval(this.gcInterval);
|
|
260
261
|
}
|
|
261
|
-
|
|
262
|
-
|
|
262
|
+
if (this.unsubscribe) {
|
|
263
|
+
this.unsubscribe();
|
|
263
264
|
}
|
|
264
|
-
this.connections.
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const now = Date.now();
|
|
270
|
-
for (const [ip, banInfo] of Object.entries(this.ipBanDatabase)) {
|
|
271
|
-
if (banInfo.time + this.IP_BAN_TIMEOUT < now) {
|
|
272
|
-
delete this.ipBanDatabase[ip];
|
|
273
|
-
}
|
|
265
|
+
for (const connection of this.connections.values()) {
|
|
266
|
+
this.removeConnection(connection.id);
|
|
267
|
+
}
|
|
268
|
+
if (this.wss) {
|
|
269
|
+
this.wss.close();
|
|
274
270
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.wss.close();
|
|
289
|
-
}
|
|
290
|
-
if (this.server && this.server.close) {
|
|
291
|
-
this.server.close();
|
|
292
|
-
}
|
|
293
|
-
};
|
|
271
|
+
if (this.server && this.server.close) {
|
|
272
|
+
this.server.close();
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
this.store = store;
|
|
276
|
+
this.uid = generateUID();
|
|
277
|
+
this.connections = new Map();
|
|
278
|
+
this.authDatabase = {};
|
|
279
|
+
this.ipBanDatabase = {};
|
|
280
|
+
this.config = { host: "0.0.0.0", port: 10002 };
|
|
281
|
+
// Attach createWSServer method to store
|
|
282
|
+
store.createWSServer = this.createWSServer.bind(this);
|
|
283
|
+
}
|
|
294
284
|
}
|
|
295
285
|
export function server(store) {
|
|
296
286
|
new ReduxClusterWSServerWrapper(store);
|
|
297
287
|
return store;
|
|
298
288
|
}
|
|
299
|
-
//# sourceMappingURL=server.js.map
|
package/dist/esm/types.js
CHANGED
package/dist/esm/utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,48 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redux-cluster-ws",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "WebSocket client/server wrapper for redux-cluster with TypeScript support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
7
7
|
"module": "./dist/esm/index.js",
|
|
8
|
-
"types": "./dist/
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
11
|
"import": {
|
|
12
|
-
"types": "./dist/
|
|
12
|
+
"types": "./dist/types/index.d.ts",
|
|
13
13
|
"default": "./dist/esm/index.js"
|
|
14
14
|
},
|
|
15
15
|
"require": {
|
|
16
|
-
"types": "./dist/
|
|
16
|
+
"types": "./dist/types/index.d.ts",
|
|
17
17
|
"default": "./dist/cjs/index.js"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"./server": {
|
|
21
21
|
"import": {
|
|
22
|
-
"types": "./dist/
|
|
22
|
+
"types": "./dist/types/server.d.ts",
|
|
23
23
|
"default": "./dist/esm/server.js"
|
|
24
24
|
},
|
|
25
25
|
"require": {
|
|
26
|
-
"types": "./dist/
|
|
26
|
+
"types": "./dist/types/server.d.ts",
|
|
27
27
|
"default": "./dist/cjs/server.js"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"./client": {
|
|
31
31
|
"import": {
|
|
32
|
-
"types": "./dist/
|
|
32
|
+
"types": "./dist/types/client.d.ts",
|
|
33
33
|
"default": "./dist/esm/client.js"
|
|
34
34
|
},
|
|
35
35
|
"require": {
|
|
36
|
-
"types": "./dist/
|
|
36
|
+
"types": "./dist/types/client.d.ts",
|
|
37
37
|
"default": "./dist/cjs/client.js"
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:pkg",
|
|
43
|
-
"build:esm": "tsc --module
|
|
44
|
-
"build:cjs": "tsc -p tsconfig.cjs.json --declaration",
|
|
42
|
+
"build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:types && npm run build:pkg",
|
|
43
|
+
"build:esm": "tsc --module ES2020 --outDir dist/esm --target ES2020 --declaration false",
|
|
44
|
+
"build:cjs": "tsc -p tsconfig.cjs.json --declaration false",
|
|
45
|
+
"build:types": "tsc --module ES2020 --outDir dist/types --target ES2020 --declaration true --emitDeclarationOnly",
|
|
45
46
|
"build:pkg": "echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
|
|
47
|
+
"dev": "npm run clean && npm run build:esm:dev && npm run build:cjs:dev && npm run build:types",
|
|
48
|
+
"build:esm:dev": "tsc --module ES2020 --outDir dist/esm --target ES2020 --declaration false --sourceMap",
|
|
49
|
+
"build:cjs:dev": "tsc -p tsconfig.cjs.json --declaration false --sourceMap",
|
|
46
50
|
"clean": "rm -rf dist",
|
|
47
51
|
"lint": "eslint src/",
|
|
48
52
|
"lint:fix": "eslint src/ --fix",
|
|
@@ -51,7 +55,6 @@
|
|
|
51
55
|
"test:basic": "node tests/basic.test.cjs",
|
|
52
56
|
"test:node": "node tests/node.test.cjs",
|
|
53
57
|
"test:browser": "npm run build && node tests/browser/server.js",
|
|
54
|
-
"dev": "tsc --watch --outDir dist/esm",
|
|
55
58
|
"prepublishOnly": "npm run build"
|
|
56
59
|
},
|
|
57
60
|
"repository": {
|
|
@@ -76,6 +79,11 @@
|
|
|
76
79
|
"url": "https://github.com/siarheidudko/redux-cluster-ws/issues"
|
|
77
80
|
},
|
|
78
81
|
"homepage": "https://github.com/siarheidudko/redux-cluster-ws#readme",
|
|
82
|
+
"files": [
|
|
83
|
+
"dist",
|
|
84
|
+
"README.md",
|
|
85
|
+
"LICENSE"
|
|
86
|
+
],
|
|
79
87
|
"funding": [
|
|
80
88
|
{
|
|
81
89
|
"type": "individual",
|
|
@@ -96,9 +104,9 @@
|
|
|
96
104
|
],
|
|
97
105
|
"dependencies": {
|
|
98
106
|
"@sergdudko/objectstream": "^3.2.26",
|
|
99
|
-
"protoobject": "^
|
|
107
|
+
"protoobject": "^2.0.1",
|
|
100
108
|
"redux": "^5.0.1",
|
|
101
|
-
"redux-cluster": "^2.0.
|
|
109
|
+
"redux-cluster": "^2.0.3"
|
|
102
110
|
},
|
|
103
111
|
"devDependencies": {
|
|
104
112
|
"@eslint/js": "^9.35.0",
|