redux-cluster 1.9.2 → 2.0.0
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/LICENSE +21 -21
- package/README.md +345 -471
- package/dist/cjs/core/backup.d.ts +10 -0
- package/dist/cjs/core/backup.d.ts.map +1 -0
- package/dist/cjs/core/backup.js +166 -0
- package/dist/cjs/core/redux-cluster.d.ts +47 -0
- package/dist/cjs/core/redux-cluster.d.ts.map +1 -0
- package/dist/cjs/core/redux-cluster.js +367 -0
- package/dist/cjs/index.d.ts +22 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +43 -0
- package/dist/cjs/network/client.d.ts +23 -0
- package/dist/cjs/network/client.d.ts.map +1 -0
- package/dist/cjs/network/client.js +251 -0
- package/dist/cjs/network/server.d.ts +39 -0
- package/dist/cjs/network/server.d.ts.map +1 -0
- package/dist/cjs/network/server.js +439 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/types/index.d.ts +125 -0
- package/dist/cjs/types/index.d.ts.map +1 -0
- package/dist/cjs/types/index.js +20 -0
- package/dist/cjs/utils/crypto.d.ts +22 -0
- package/dist/cjs/utils/crypto.d.ts.map +1 -0
- package/dist/cjs/utils/crypto.js +404 -0
- package/dist/esm/core/backup.d.ts +10 -0
- package/dist/esm/core/backup.d.ts.map +1 -0
- package/dist/esm/core/backup.js +134 -0
- package/dist/esm/core/redux-cluster.d.ts +47 -0
- package/dist/esm/core/redux-cluster.d.ts.map +1 -0
- package/dist/esm/core/redux-cluster.js +376 -0
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +25 -0
- package/dist/esm/network/client.d.ts +23 -0
- package/dist/esm/network/client.d.ts.map +1 -0
- package/dist/esm/network/client.js +221 -0
- package/dist/esm/network/server.d.ts +39 -0
- package/dist/esm/network/server.d.ts.map +1 -0
- package/dist/esm/network/server.js +408 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/types/index.d.ts +125 -0
- package/dist/esm/types/index.d.ts.map +1 -0
- package/dist/esm/types/index.js +17 -0
- package/dist/esm/utils/crypto.d.ts +22 -0
- package/dist/esm/utils/crypto.d.ts.map +1 -0
- package/dist/esm/utils/crypto.js +351 -0
- package/package.json +115 -42
- package/index.js +0 -678
- package/test.auto.js +0 -94
- package/test.auto.proc1.js +0 -97
- package/test.auto.proc2.js +0 -85
- package/test.visual.client.highload.js +0 -102
- package/test.visual.client.js +0 -103
- package/test.visual.error.js +0 -45
- package/test.visual.js +0 -97
- package/test.visual.server.highload.js +0 -102
- package/test.visual.server.js +0 -103
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ServerSettings, ReduxClusterStore, ClusterMessage, ClusterSocket } from "../types";
|
|
2
|
+
export declare class ClusterServer {
|
|
3
|
+
private store;
|
|
4
|
+
private settings;
|
|
5
|
+
readonly uid: string;
|
|
6
|
+
readonly sockets: Record<string, ClusterSocket>;
|
|
7
|
+
readonly database: Record<string, string>;
|
|
8
|
+
readonly ip2ban: Record<string, {
|
|
9
|
+
time: number;
|
|
10
|
+
count: number;
|
|
11
|
+
}>;
|
|
12
|
+
private readonly ip2banTimeout;
|
|
13
|
+
private readonly ip2banGC;
|
|
14
|
+
private server;
|
|
15
|
+
private unsubscribe?;
|
|
16
|
+
private shouldAutoRestart;
|
|
17
|
+
constructor(store: ReduxClusterStore, settings?: ServerSettings);
|
|
18
|
+
private setupDatabase;
|
|
19
|
+
private setupBanSystem;
|
|
20
|
+
private cleanupBannedIPs;
|
|
21
|
+
private setupServer;
|
|
22
|
+
private getListenOptions;
|
|
23
|
+
private startListening;
|
|
24
|
+
private handleNewConnection;
|
|
25
|
+
private isIPBanned;
|
|
26
|
+
private rejectConnection;
|
|
27
|
+
private setupSocket;
|
|
28
|
+
private setupSocketPipeline;
|
|
29
|
+
private handleSocketMessage;
|
|
30
|
+
private handleAuthentication;
|
|
31
|
+
private recordFailedLogin;
|
|
32
|
+
private closeSocket;
|
|
33
|
+
private setupStoreIntegration;
|
|
34
|
+
sendtoall(message?: ClusterMessage): void;
|
|
35
|
+
ip2banGCStop(): void;
|
|
36
|
+
private cleanup;
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/network/server.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,cAAc,EAEd,aAAa,EACd,MAAM,UAAU,CAAC;AAGlB,qBAAa,aAAa;IAatB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAblB,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B,SAAgB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAM;IAC5D,SAAgB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IACtD,SAAgB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAM;IAE7E,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAY;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,iBAAiB,CAAiB;gBAGhC,KAAK,EAAE,iBAAiB,EACxB,QAAQ,GAAE,cAAmB;IAavC,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,WAAW;IA8CnB,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,gBAAgB;IAyBxB,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,mBAAmB;IA2D3B,OAAO,CAAC,mBAAmB;IA0C3B,OAAO,CAAC,oBAAoB;IAyD5B,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,qBAAqB;IActB,SAAS,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI;IAezC,YAAY,IAAI,IAAI;IAI3B,OAAO,CAAC,OAAO;IAQR,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAiD9B"}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import * as net from "net";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as zlib from "zlib";
|
|
6
|
+
import * as stream from "stream";
|
|
7
|
+
import * as crypto from "crypto";
|
|
8
|
+
import { MessageType, } from "../types";
|
|
9
|
+
import { hasher } from "../utils/crypto";
|
|
10
|
+
export class ClusterServer {
|
|
11
|
+
store;
|
|
12
|
+
settings;
|
|
13
|
+
uid;
|
|
14
|
+
sockets = {};
|
|
15
|
+
database = {};
|
|
16
|
+
ip2ban = {};
|
|
17
|
+
ip2banTimeout = 10800000; // 3 hours
|
|
18
|
+
ip2banGC;
|
|
19
|
+
server;
|
|
20
|
+
unsubscribe;
|
|
21
|
+
shouldAutoRestart = true;
|
|
22
|
+
constructor(store, settings = {}) {
|
|
23
|
+
this.store = store;
|
|
24
|
+
this.settings = settings;
|
|
25
|
+
this.uid = crypto.randomUUID();
|
|
26
|
+
this.setupDatabase();
|
|
27
|
+
this.setupBanSystem();
|
|
28
|
+
this.setupServer();
|
|
29
|
+
this.setupStoreIntegration();
|
|
30
|
+
this.ip2banGC = setInterval(() => {
|
|
31
|
+
this.cleanupBannedIPs();
|
|
32
|
+
}, 60000);
|
|
33
|
+
}
|
|
34
|
+
setupDatabase() {
|
|
35
|
+
if (this.settings.logins) {
|
|
36
|
+
for (const login in this.settings.logins) {
|
|
37
|
+
const hashedLogin = hasher(`REDUX_CLUSTER${login}`);
|
|
38
|
+
const hashedPassword = hasher(`REDUX_CLUSTER${this.settings.logins[login]}`);
|
|
39
|
+
if (hashedLogin && hashedPassword) {
|
|
40
|
+
this.database[hashedLogin] = hashedPassword;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
setupBanSystem() {
|
|
46
|
+
// IP ban system is initialized in constructor
|
|
47
|
+
}
|
|
48
|
+
cleanupBannedIPs() {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
for (const key in this.ip2ban) {
|
|
51
|
+
if (this.ip2ban[key].time + this.ip2banTimeout < now) {
|
|
52
|
+
delete this.ip2ban[key];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
setupServer() {
|
|
57
|
+
const listenOptions = this.getListenOptions();
|
|
58
|
+
this.server = net.createServer((socket) => {
|
|
59
|
+
this.handleNewConnection(socket);
|
|
60
|
+
});
|
|
61
|
+
this.server.on("listening", () => {
|
|
62
|
+
this.store.connected = true;
|
|
63
|
+
this.store.sendtoall({
|
|
64
|
+
_msg: MessageType.CONN_STATUS,
|
|
65
|
+
_hash: this.store.RCHash,
|
|
66
|
+
_connected: true,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
this.server.on("close", () => {
|
|
70
|
+
this.store.connected = false;
|
|
71
|
+
this.store.sendtoall({
|
|
72
|
+
_msg: MessageType.CONN_STATUS,
|
|
73
|
+
_hash: this.store.RCHash,
|
|
74
|
+
_connected: false,
|
|
75
|
+
});
|
|
76
|
+
this.cleanup();
|
|
77
|
+
// Auto-restart server after 10 seconds
|
|
78
|
+
if (this.shouldAutoRestart) {
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
new ClusterServer(this.store, this.settings);
|
|
81
|
+
}, 10000);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
this.server.on("error", (err) => {
|
|
85
|
+
this.store.stderr(`ReduxCluster.createServer socket error: ${err.message}`);
|
|
86
|
+
if (typeof this.server.close === "function") {
|
|
87
|
+
this.server.close();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
this.startListening(listenOptions);
|
|
91
|
+
}
|
|
92
|
+
getListenOptions() {
|
|
93
|
+
const defaultOptions = { port: 10001 };
|
|
94
|
+
if (typeof this.settings.path === "string") {
|
|
95
|
+
switch (os.platform()) {
|
|
96
|
+
case "win32":
|
|
97
|
+
return { path: path.join("\\\\?\\pipe", this.settings.path) };
|
|
98
|
+
default:
|
|
99
|
+
return { path: path.join(this.settings.path) };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const options = { ...defaultOptions };
|
|
103
|
+
if (typeof this.settings.host === "string") {
|
|
104
|
+
options.host = this.settings.host;
|
|
105
|
+
}
|
|
106
|
+
if (typeof this.settings.port === "number") {
|
|
107
|
+
options.port = this.settings.port;
|
|
108
|
+
}
|
|
109
|
+
return options;
|
|
110
|
+
}
|
|
111
|
+
startListening(options) {
|
|
112
|
+
if (typeof options.path === "string") {
|
|
113
|
+
// Remove existing socket file
|
|
114
|
+
fs.unlink(options.path, (err) => {
|
|
115
|
+
if (err &&
|
|
116
|
+
!err.message.toLowerCase().includes("no such file or directory")) {
|
|
117
|
+
this.store.stderr(`ReduxCluster.createServer socket error: ${err.message}`);
|
|
118
|
+
}
|
|
119
|
+
this.server.listen(options);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.server.listen(options);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
handleNewConnection(socket) {
|
|
127
|
+
// Hash IP address for security using hasher function
|
|
128
|
+
const clientIP = socket.remoteAddress
|
|
129
|
+
? hasher(socket.remoteAddress)
|
|
130
|
+
: "";
|
|
131
|
+
const uid = crypto.randomUUID();
|
|
132
|
+
const clusterSocket = socket;
|
|
133
|
+
clusterSocket.uid = uid;
|
|
134
|
+
// Check if IP is banned
|
|
135
|
+
if (this.isIPBanned(clientIP)) {
|
|
136
|
+
this.rejectConnection(clusterSocket, true);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this.setupSocket(clusterSocket, clientIP);
|
|
140
|
+
}
|
|
141
|
+
isIPBanned(ip) {
|
|
142
|
+
if (!ip || !this.ip2ban[ip])
|
|
143
|
+
return false;
|
|
144
|
+
const banInfo = this.ip2ban[ip];
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
return banInfo.count >= 5 && banInfo.time + this.ip2banTimeout > now;
|
|
147
|
+
}
|
|
148
|
+
rejectConnection(socket, banned = false) {
|
|
149
|
+
// Create rejection message
|
|
150
|
+
const rejectionMessage = {
|
|
151
|
+
_msg: MessageType.SOCKET_AUTH_STATE,
|
|
152
|
+
_hash: this.store.RCHash,
|
|
153
|
+
_value: false,
|
|
154
|
+
...(banned && { _banned: true }),
|
|
155
|
+
};
|
|
156
|
+
// Manually serialize and compress since custom write is not set up yet
|
|
157
|
+
try {
|
|
158
|
+
const compressed = zlib.gzipSync(Buffer.from(JSON.stringify(rejectionMessage)));
|
|
159
|
+
// Use the native write method directly, not the overridden one
|
|
160
|
+
const originalWrite = socket.writeNEW || socket.write.bind(socket);
|
|
161
|
+
originalWrite(compressed);
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
this.store.stderr(`ReduxCluster.rejectConnection error: ${err.message}`);
|
|
165
|
+
}
|
|
166
|
+
this.closeSocket(socket);
|
|
167
|
+
}
|
|
168
|
+
setupSocket(socket, clientIP) {
|
|
169
|
+
// Override write method for object mode + compression
|
|
170
|
+
socket.writeNEW = socket.write;
|
|
171
|
+
socket.write = (data) => {
|
|
172
|
+
try {
|
|
173
|
+
const compressed = zlib.gzipSync(Buffer.from(JSON.stringify(data)));
|
|
174
|
+
return socket.writeNEW(compressed);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
this.store.stderr(`ReduxCluster.createServer write error: ${err.message}`);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
socket.on("error", (err) => {
|
|
182
|
+
this.store.stderr(`ReduxCluster.createServer client error: ${err.message}`);
|
|
183
|
+
this.closeSocket(socket);
|
|
184
|
+
});
|
|
185
|
+
this.setupSocketPipeline(socket, clientIP);
|
|
186
|
+
}
|
|
187
|
+
setupSocketPipeline(socket, clientIP) {
|
|
188
|
+
// Create processing pipeline
|
|
189
|
+
const mbstring = new stream.Transform({
|
|
190
|
+
transform(buffer, encoding, callback) {
|
|
191
|
+
this.push(buffer);
|
|
192
|
+
callback();
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
mbstring.setEncoding("utf8");
|
|
196
|
+
const gunzipper = zlib.createGunzip();
|
|
197
|
+
// For now, we'll create a simple parser instead of using external library
|
|
198
|
+
const parser = new stream.Transform({
|
|
199
|
+
transform(chunk, encoding, callback) {
|
|
200
|
+
try {
|
|
201
|
+
const data = JSON.parse(chunk.toString());
|
|
202
|
+
this.push(data);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Invalid JSON, ignore
|
|
206
|
+
}
|
|
207
|
+
callback();
|
|
208
|
+
},
|
|
209
|
+
objectMode: true,
|
|
210
|
+
});
|
|
211
|
+
const eventHandler = new stream.Writable({
|
|
212
|
+
write: (data, encoding, callback) => {
|
|
213
|
+
this.handleSocketMessage(data, socket, clientIP);
|
|
214
|
+
callback();
|
|
215
|
+
},
|
|
216
|
+
objectMode: true,
|
|
217
|
+
});
|
|
218
|
+
// Setup error handlers
|
|
219
|
+
[gunzipper, mbstring, parser, eventHandler].forEach((stream) => {
|
|
220
|
+
stream.on("error", (err) => {
|
|
221
|
+
this.store.stderr(`ReduxCluster.createServer stream error: ${err.message}`);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
// Connect pipeline
|
|
225
|
+
socket.pipe(gunzipper).pipe(mbstring).pipe(parser).pipe(eventHandler);
|
|
226
|
+
}
|
|
227
|
+
handleSocketMessage(data, socket, clientIP) {
|
|
228
|
+
if (data._hash !== this.store.RCHash)
|
|
229
|
+
return;
|
|
230
|
+
switch (data._msg) {
|
|
231
|
+
case MessageType.MSG_TO_MASTER:
|
|
232
|
+
if (this.sockets[socket.uid]) {
|
|
233
|
+
if (data._action.type === MessageType.SYNC) {
|
|
234
|
+
throw new Error("Please don't use REDUX_CLUSTER_SYNC action type!");
|
|
235
|
+
}
|
|
236
|
+
// Apply action to server state
|
|
237
|
+
// This will automatically trigger sendActionsToNodes via reducer
|
|
238
|
+
this.store.dispatch(data._action);
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
case MessageType.START:
|
|
242
|
+
if (this.sockets[socket.uid]) {
|
|
243
|
+
socket.write({
|
|
244
|
+
_msg: MessageType.MSG_TO_WORKER,
|
|
245
|
+
_hash: this.store.RCHash,
|
|
246
|
+
_action: {
|
|
247
|
+
type: MessageType.SYNC,
|
|
248
|
+
payload: this.store.getState(),
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
case MessageType.SOCKET_AUTH:
|
|
254
|
+
this.handleAuthentication(data, socket, clientIP);
|
|
255
|
+
break;
|
|
256
|
+
default:
|
|
257
|
+
// Ignore unknown message types
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
handleAuthentication(data, socket, clientIP) {
|
|
262
|
+
const { _login, _password } = data;
|
|
263
|
+
// If no authentication is configured (empty database), allow all connections
|
|
264
|
+
if (Object.keys(this.database).length === 0) {
|
|
265
|
+
// No authentication required
|
|
266
|
+
this.sockets[socket.uid] = socket;
|
|
267
|
+
socket.write({
|
|
268
|
+
_msg: MessageType.SOCKET_AUTH_STATE,
|
|
269
|
+
_hash: this.store.RCHash,
|
|
270
|
+
_value: true,
|
|
271
|
+
});
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (typeof _login !== "undefined" &&
|
|
275
|
+
typeof _password !== "undefined" &&
|
|
276
|
+
typeof this.database[_login] !== "undefined" &&
|
|
277
|
+
this.database[_login] === _password) {
|
|
278
|
+
// Successful authentication
|
|
279
|
+
this.sockets[socket.uid] = socket;
|
|
280
|
+
// Clear ban if exists
|
|
281
|
+
if (clientIP && this.ip2ban[clientIP]) {
|
|
282
|
+
delete this.ip2ban[clientIP];
|
|
283
|
+
}
|
|
284
|
+
// Use the custom write method that should be set up by now
|
|
285
|
+
socket.write({
|
|
286
|
+
_msg: MessageType.SOCKET_AUTH_STATE,
|
|
287
|
+
_hash: this.store.RCHash,
|
|
288
|
+
_value: true,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
// Failed authentication
|
|
293
|
+
if (clientIP) {
|
|
294
|
+
this.recordFailedLogin(clientIP);
|
|
295
|
+
}
|
|
296
|
+
// Use the custom write method that should be set up by now
|
|
297
|
+
socket.write({
|
|
298
|
+
_msg: MessageType.SOCKET_AUTH_STATE,
|
|
299
|
+
_hash: this.store.RCHash,
|
|
300
|
+
_value: false,
|
|
301
|
+
});
|
|
302
|
+
this.closeSocket(socket);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
recordFailedLogin(ip) {
|
|
306
|
+
let count = 0;
|
|
307
|
+
if (this.ip2ban[ip]) {
|
|
308
|
+
count = this.ip2ban[ip].count;
|
|
309
|
+
if (count >= 5)
|
|
310
|
+
count = 0; // Reset on timeout
|
|
311
|
+
}
|
|
312
|
+
this.ip2ban[ip] = {
|
|
313
|
+
time: Date.now(),
|
|
314
|
+
count: count + 1,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
closeSocket(socket) {
|
|
318
|
+
if (typeof socket.end === "function") {
|
|
319
|
+
socket.end();
|
|
320
|
+
}
|
|
321
|
+
if (socket.uid && this.sockets[socket.uid]) {
|
|
322
|
+
delete this.sockets[socket.uid];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
setupStoreIntegration() {
|
|
326
|
+
// Register with store if in action mode
|
|
327
|
+
if (this.store.mode === "action") {
|
|
328
|
+
this.store.allsock[this.uid] = this;
|
|
329
|
+
}
|
|
330
|
+
// Subscribe to store changes in snapshot mode
|
|
331
|
+
this.unsubscribe = this.store.subscribe(() => {
|
|
332
|
+
if (this.store.mode === "snapshot") {
|
|
333
|
+
this.sendtoall();
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
sendtoall(message) {
|
|
338
|
+
const msg = message || {
|
|
339
|
+
_msg: MessageType.MSG_TO_WORKER,
|
|
340
|
+
_hash: this.store.RCHash,
|
|
341
|
+
_action: {
|
|
342
|
+
type: MessageType.SYNC,
|
|
343
|
+
payload: this.store.getState(),
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
for (const uid in this.sockets) {
|
|
347
|
+
this.sockets[uid].write(msg);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
ip2banGCStop() {
|
|
351
|
+
clearInterval(this.ip2banGC);
|
|
352
|
+
}
|
|
353
|
+
cleanup() {
|
|
354
|
+
if (this.unsubscribe) {
|
|
355
|
+
this.unsubscribe();
|
|
356
|
+
}
|
|
357
|
+
this.ip2banGCStop();
|
|
358
|
+
delete this.store.allsock[this.uid];
|
|
359
|
+
}
|
|
360
|
+
close() {
|
|
361
|
+
return new Promise((resolve) => {
|
|
362
|
+
// Prevent the automatic restart handler from creating a new server
|
|
363
|
+
this.shouldAutoRestart = false;
|
|
364
|
+
// Close all connected sockets first
|
|
365
|
+
Object.values(this.sockets).forEach((socket) => {
|
|
366
|
+
if (socket) {
|
|
367
|
+
try {
|
|
368
|
+
this.closeSocket(socket);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// ignore
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
// Clear sockets registry
|
|
376
|
+
Object.keys(this.sockets).forEach((key) => {
|
|
377
|
+
delete this.sockets[key];
|
|
378
|
+
});
|
|
379
|
+
// Stop IP ban garbage collector
|
|
380
|
+
if (this.ip2banGC) {
|
|
381
|
+
clearInterval(this.ip2banGC);
|
|
382
|
+
}
|
|
383
|
+
// Unsubscribe from store updates
|
|
384
|
+
if (this.unsubscribe) {
|
|
385
|
+
try {
|
|
386
|
+
this.unsubscribe();
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
// ignore
|
|
390
|
+
}
|
|
391
|
+
this.unsubscribe = undefined;
|
|
392
|
+
}
|
|
393
|
+
// Finally close the server and resolve when closed
|
|
394
|
+
if (this.server && typeof this.server.close === "function") {
|
|
395
|
+
try {
|
|
396
|
+
this.server.close(() => resolve());
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
// fallback resolve
|
|
400
|
+
resolve();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
resolve();
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Store, Action } from "redux";
|
|
2
|
+
export declare enum SerializationMode {
|
|
3
|
+
JSON = "json",
|
|
4
|
+
PROTOOBJECT = "protoobject"
|
|
5
|
+
}
|
|
6
|
+
export interface ReduxClusterConfig {
|
|
7
|
+
serializationMode?: SerializationMode;
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
mode?: SyncMode;
|
|
10
|
+
syncMode?: "ipc" | "tcp";
|
|
11
|
+
role?: Role[];
|
|
12
|
+
server?: ServerSettings;
|
|
13
|
+
client?: ClientSettings;
|
|
14
|
+
backup?: BackupSettings;
|
|
15
|
+
stderr?: ErrorHandler;
|
|
16
|
+
resync?: number;
|
|
17
|
+
}
|
|
18
|
+
export type ProtoObjectState = any;
|
|
19
|
+
export declare enum MessageType {
|
|
20
|
+
CONN_STATUS = "REDUX_CLUSTER_CONNSTATUS",
|
|
21
|
+
MSG_TO_WORKER = "REDUX_CLUSTER_MSGTOWORKER",
|
|
22
|
+
MSG_TO_MASTER = "REDUX_CLUSTER_MSGTOMASTER",
|
|
23
|
+
SOCKET_AUTH = "REDUX_CLUSTER_SOCKET_AUTH",
|
|
24
|
+
SOCKET_AUTH_STATE = "REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
25
|
+
START = "REDUX_CLUSTER_START",
|
|
26
|
+
SYNC = "REDUX_CLUSTER_SYNC"
|
|
27
|
+
}
|
|
28
|
+
export interface BaseMessage {
|
|
29
|
+
_msg: MessageType;
|
|
30
|
+
_hash: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ConnectionStatusMessage extends BaseMessage {
|
|
33
|
+
_msg: MessageType.CONN_STATUS;
|
|
34
|
+
_connected: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface ActionToWorkerMessage extends BaseMessage {
|
|
37
|
+
_msg: MessageType.MSG_TO_WORKER;
|
|
38
|
+
_action: Action;
|
|
39
|
+
}
|
|
40
|
+
export interface ActionToMasterMessage extends BaseMessage {
|
|
41
|
+
_msg: MessageType.MSG_TO_MASTER;
|
|
42
|
+
_action: Action;
|
|
43
|
+
}
|
|
44
|
+
export interface SocketAuthMessage extends BaseMessage {
|
|
45
|
+
_msg: MessageType.SOCKET_AUTH;
|
|
46
|
+
_login: string;
|
|
47
|
+
_password: string;
|
|
48
|
+
}
|
|
49
|
+
export interface SocketAuthStateMessage extends BaseMessage {
|
|
50
|
+
_msg: MessageType.SOCKET_AUTH_STATE;
|
|
51
|
+
_value: boolean;
|
|
52
|
+
_banned?: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface StartMessage extends BaseMessage {
|
|
55
|
+
_msg: MessageType.START;
|
|
56
|
+
}
|
|
57
|
+
export type ClusterMessage = ConnectionStatusMessage | ActionToWorkerMessage | ActionToMasterMessage | SocketAuthMessage | SocketAuthStateMessage | StartMessage;
|
|
58
|
+
export type SyncMode = "action" | "snapshot";
|
|
59
|
+
export type Role = "master" | "worker" | "server" | "client";
|
|
60
|
+
export interface ServerSettings {
|
|
61
|
+
host?: string;
|
|
62
|
+
port?: number;
|
|
63
|
+
path?: string;
|
|
64
|
+
logins?: Record<string, string>;
|
|
65
|
+
}
|
|
66
|
+
export interface ClientSettings {
|
|
67
|
+
host?: string;
|
|
68
|
+
port?: number;
|
|
69
|
+
path?: string;
|
|
70
|
+
login?: string;
|
|
71
|
+
password?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface BackupSettings {
|
|
74
|
+
path: string;
|
|
75
|
+
key?: string;
|
|
76
|
+
timeout?: number;
|
|
77
|
+
count?: number;
|
|
78
|
+
}
|
|
79
|
+
export interface ReduxClusterStore<S = any, A extends Action = Action> extends Store<S, A> {
|
|
80
|
+
readonly RCHash: string;
|
|
81
|
+
readonly version: string;
|
|
82
|
+
readonly homepage: string;
|
|
83
|
+
readonly role: Role[];
|
|
84
|
+
connected: boolean;
|
|
85
|
+
mode: SyncMode;
|
|
86
|
+
resync: number;
|
|
87
|
+
stderr: (message: string) => void;
|
|
88
|
+
readonly config: ReduxClusterConfig;
|
|
89
|
+
createServer(settings?: ServerSettings): ClusterServer;
|
|
90
|
+
createClient(settings?: ClientSettings): ClusterClient;
|
|
91
|
+
backup(settings: BackupSettings): Promise<boolean>;
|
|
92
|
+
sendtoall(message?: ClusterMessage): void;
|
|
93
|
+
sendtoallsock(message?: ClusterMessage): void;
|
|
94
|
+
registerClass?(name: string, classConstructor: any): void;
|
|
95
|
+
getRegisteredClasses?(): string[];
|
|
96
|
+
}
|
|
97
|
+
export interface ClusterServer {
|
|
98
|
+
readonly uid: string;
|
|
99
|
+
readonly sockets: Record<string, any>;
|
|
100
|
+
readonly database: Record<string, string>;
|
|
101
|
+
readonly ip2ban: Record<string, {
|
|
102
|
+
time: number;
|
|
103
|
+
count: number;
|
|
104
|
+
}>;
|
|
105
|
+
sendtoall(message?: ClusterMessage): void;
|
|
106
|
+
ip2banGCStop(): void;
|
|
107
|
+
}
|
|
108
|
+
export interface ClusterClient {
|
|
109
|
+
login?: string;
|
|
110
|
+
password?: string;
|
|
111
|
+
}
|
|
112
|
+
export interface ClusterSocket {
|
|
113
|
+
uid: string;
|
|
114
|
+
writeNEW: (data: any) => boolean;
|
|
115
|
+
write(data: any): boolean;
|
|
116
|
+
on(event: string, listener: (...args: any[]) => void): any;
|
|
117
|
+
pipe(...args: any[]): any;
|
|
118
|
+
end(): void;
|
|
119
|
+
remoteAddress?: string;
|
|
120
|
+
}
|
|
121
|
+
export type HasherFunction = (data: string, algorithm?: string) => string | undefined;
|
|
122
|
+
export type EncrypterFunction = (data: string, pass: string) => string;
|
|
123
|
+
export type DecrypterFunction = (data: string, pass: string) => string;
|
|
124
|
+
export type ErrorHandler = (message: string) => void;
|
|
125
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Serialization modes
|
|
2
|
+
export var SerializationMode;
|
|
3
|
+
(function (SerializationMode) {
|
|
4
|
+
SerializationMode["JSON"] = "json";
|
|
5
|
+
SerializationMode["PROTOOBJECT"] = "protoobject";
|
|
6
|
+
})(SerializationMode || (SerializationMode = {}));
|
|
7
|
+
// Message types for IPC communication
|
|
8
|
+
export var MessageType;
|
|
9
|
+
(function (MessageType) {
|
|
10
|
+
MessageType["CONN_STATUS"] = "REDUX_CLUSTER_CONNSTATUS";
|
|
11
|
+
MessageType["MSG_TO_WORKER"] = "REDUX_CLUSTER_MSGTOWORKER";
|
|
12
|
+
MessageType["MSG_TO_MASTER"] = "REDUX_CLUSTER_MSGTOMASTER";
|
|
13
|
+
MessageType["SOCKET_AUTH"] = "REDUX_CLUSTER_SOCKET_AUTH";
|
|
14
|
+
MessageType["SOCKET_AUTH_STATE"] = "REDUX_CLUSTER_SOCKET_AUTHSTATE";
|
|
15
|
+
MessageType["START"] = "REDUX_CLUSTER_START";
|
|
16
|
+
MessageType["SYNC"] = "REDUX_CLUSTER_SYNC";
|
|
17
|
+
})(MessageType || (MessageType = {}));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as crypto from "crypto";
|
|
2
|
+
import { Transform } from "stream";
|
|
3
|
+
import { SerializationMode } from "../types";
|
|
4
|
+
export declare function hasher(input: string): string;
|
|
5
|
+
export declare function createCipher(key: string): crypto.Cipher;
|
|
6
|
+
export declare function createDecipher(key: string, iv: Buffer): crypto.Decipher;
|
|
7
|
+
export declare function encrypter(data: string, password: string): string;
|
|
8
|
+
export declare function decrypter(encryptedData: string, password: string): string;
|
|
9
|
+
export declare function deepClone(obj: any): any;
|
|
10
|
+
export declare function universalClone(obj: any, mode: SerializationMode, _classRegistry?: Map<string, any>): any;
|
|
11
|
+
export declare function protoObjectClone(obj: any): any;
|
|
12
|
+
export declare function universalSerialize(obj: any, mode: SerializationMode, classRegistry?: Map<string, any>): string;
|
|
13
|
+
export declare function universalDeserialize(str: string, mode: SerializationMode, classRegistry?: Map<string, any>): any;
|
|
14
|
+
export declare function serializeProtoObject(obj: any, classRegistry?: Map<string, any>): string;
|
|
15
|
+
export declare function deserializeProtoObject(str: string, classRegistry?: Map<string, any>): any;
|
|
16
|
+
export declare function createClassRegistry(): Map<string, any>;
|
|
17
|
+
export declare function createObjectStreamParser(): Transform | null;
|
|
18
|
+
export declare function createObjectStreamStringifier(): Transform | null;
|
|
19
|
+
export declare function createObjectStream(): Transform | null;
|
|
20
|
+
export declare function createDeserializationStream(mode: SerializationMode, classRegistry?: Map<string, any>): Transform;
|
|
21
|
+
export declare function createSerializationStream(mode: SerializationMode, classRegistry?: Map<string, any>): Transform;
|
|
22
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAoD7C,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5C;AAGD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAGvD;AAGD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC,QAAQ,CAEvE;AAGD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAShE;AAGD,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWzE;AAGD,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAKvC;AAGD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,iBAAiB,EACvB,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,GAAG,CAKL;AAGD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAsC9C;AAGD,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,iBAAiB,EACvB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,MAAM,CAMR;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,iBAAiB,EACvB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,GAAG,CAML;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,GAAG,EACR,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,MAAM,CAmCR;AAGD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,GAAG,CAqCL;AAGD,wBAAgB,mBAAmB,IAAI,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAEtD;AAKD,wBAAgB,wBAAwB,IAAI,SAAS,GAAG,IAAI,CAK3D;AAGD,wBAAgB,6BAA6B,IAAI,SAAS,GAAG,IAAI,CAKhE;AAGD,wBAAgB,kBAAkB,IAAI,SAAS,GAAG,IAAI,CAErD;AAGD,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,iBAAiB,EACvB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,SAAS,CAuBX;AAGD,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,iBAAiB,EACvB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,SAAS,CAuBX"}
|