redux-cluster-ws 1.3.1 → 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/FUNDING.yml +7 -0
- package/LICENSE +21 -21
- package/README.md +394 -90
- package/dist/cjs/client.d.ts +43 -0
- package/dist/cjs/client.js +276 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/server.d.ts +31 -0
- package/dist/cjs/server.js +295 -0
- package/dist/cjs/server.js.map +1 -0
- package/dist/cjs/types.d.ts +83 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/utils.d.ts +17 -0
- package/dist/cjs/utils.js +75 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/esm/client.d.ts +43 -0
- package/dist/esm/client.js +282 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/server.d.ts +31 -0
- package/dist/esm/server.js +299 -0
- package/dist/esm/server.js.map +1 -0
- package/dist/esm/types.d.ts +83 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/utils.d.ts +17 -0
- package/dist/esm/utils.js +69 -0
- package/dist/esm/utils.js.map +1 -0
- package/eslint.config.js +143 -0
- package/examples/browser-example.cjs +350 -0
- package/examples/browser.html +255 -0
- package/examples/client.cjs +155 -0
- package/examples/cross-library-browser.html +655 -0
- package/examples/cross-library-client.cjs +190 -0
- package/examples/cross-library-server.cjs +213 -0
- package/examples/server.cjs +96 -0
- package/package.json +96 -23
- package/client.js +0 -192
- package/index.js +0 -12
- package/server.js +0 -175
- package/test.auto.proc1.js +0 -100
- package/test.auto.proc2.js +0 -74
- package/test.visual.client.html +0 -37
- package/test.visual.client.js +0 -63
- package/test.visual.server.js +0 -66
- package/umd/ReduxCluster.js +0 -18
- package/webpack-src.js +0 -6
- package/webpack.config.js +0 -25
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReduxClusterWSClientWrapper = exports.ReduxCluster = void 0;
|
|
4
|
+
exports.createStore = createStore;
|
|
5
|
+
exports.client = client;
|
|
6
|
+
const redux_1 = require("redux");
|
|
7
|
+
const utils_js_1 = require("./utils.js");
|
|
8
|
+
// WebSocket compatibility for both browser and Node.js
|
|
9
|
+
const WebSocketClass = (() => {
|
|
10
|
+
if (typeof window !== "undefined" && window.WebSocket) {
|
|
11
|
+
return window.WebSocket;
|
|
12
|
+
}
|
|
13
|
+
if (typeof global !== "undefined" && global.WebSocket) {
|
|
14
|
+
return global.WebSocket;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
// Use dynamic import for Node.js environments
|
|
18
|
+
const wsModule = eval("require")("ws");
|
|
19
|
+
return wsModule;
|
|
20
|
+
}
|
|
21
|
+
catch (_a) {
|
|
22
|
+
throw new Error("WebSocket implementation not found");
|
|
23
|
+
}
|
|
24
|
+
})();
|
|
25
|
+
class ReduxCluster {
|
|
26
|
+
constructor(reducer) {
|
|
27
|
+
this.stderr = console.error;
|
|
28
|
+
this.role = [];
|
|
29
|
+
this.mode = "action";
|
|
30
|
+
this.connected = false;
|
|
31
|
+
this.resync = 1000;
|
|
32
|
+
this.dispatch = (action) => {
|
|
33
|
+
return this.store.dispatch(action);
|
|
34
|
+
};
|
|
35
|
+
this.altReducer = reducer;
|
|
36
|
+
this.RCHash = (0, utils_js_1.hasher)(reducer.name);
|
|
37
|
+
// Get version and homepage from package.json (would need to be injected in real implementation)
|
|
38
|
+
this.version = "2.0.0";
|
|
39
|
+
this.homepage = "https://github.com/siarheidudko/redux-cluster-ws";
|
|
40
|
+
// Check for duplicate reducer names
|
|
41
|
+
if (ReduxCluster.reducers[reducer.name]) {
|
|
42
|
+
throw new Error("Please don't use a reducer with the same name!");
|
|
43
|
+
}
|
|
44
|
+
ReduxCluster.reducers[reducer.name] = this.RCHash;
|
|
45
|
+
// Get default state
|
|
46
|
+
try {
|
|
47
|
+
const defaultState = this.altReducer(undefined, {});
|
|
48
|
+
if (typeof defaultState === "object" && defaultState !== null) {
|
|
49
|
+
this.defaultState = defaultState;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
throw new Error("The returned value is not an object.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (_a) {
|
|
56
|
+
this.defaultState = {};
|
|
57
|
+
}
|
|
58
|
+
// Create new reducer that handles sync actions
|
|
59
|
+
const newReducer = (state = this.defaultState, action) => {
|
|
60
|
+
if (action.type === "REDUX_CLUSTER_SYNC") {
|
|
61
|
+
return (0, utils_js_1.deepClone)(action.payload);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return this.altReducer(state, action);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
// Create Redux store
|
|
68
|
+
this.store = (0, redux_1.createStore)(newReducer);
|
|
69
|
+
}
|
|
70
|
+
// Redux Store interface implementation
|
|
71
|
+
getState() {
|
|
72
|
+
return this.store.getState();
|
|
73
|
+
}
|
|
74
|
+
subscribe(listener) {
|
|
75
|
+
return this.store.subscribe(listener);
|
|
76
|
+
}
|
|
77
|
+
replaceReducer(_nextReducer) {
|
|
78
|
+
throw new Error("replaceReducer is not supported in ReduxCluster");
|
|
79
|
+
}
|
|
80
|
+
[Symbol.observable]() {
|
|
81
|
+
return this.store[Symbol.observable]();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.ReduxCluster = ReduxCluster;
|
|
85
|
+
ReduxCluster.reducers = {};
|
|
86
|
+
class ReduxClusterWSClientWrapper {
|
|
87
|
+
constructor(store, config) {
|
|
88
|
+
this.authenticated = false;
|
|
89
|
+
this.reconnect = () => {
|
|
90
|
+
try {
|
|
91
|
+
const url = this.buildWebSocketURL();
|
|
92
|
+
this.ws = new WebSocketClass(url);
|
|
93
|
+
if (this.ws) {
|
|
94
|
+
this.ws.onopen = () => {
|
|
95
|
+
this.sendMessage({
|
|
96
|
+
_msg: "REDUX_CLUSTER_SOCKET_AUTH",
|
|
97
|
+
_hash: this.store.RCHash,
|
|
98
|
+
_login: this.login,
|
|
99
|
+
_password: this.password,
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
this.ws.onmessage = (event) => {
|
|
103
|
+
try {
|
|
104
|
+
const message = JSON.parse(event.data);
|
|
105
|
+
this.handleMessage(message);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
this.store.stderr(`ReduxCluster.createWSClient message parse error: ${error}`);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
this.ws.onclose = () => {
|
|
112
|
+
this.authenticated = false;
|
|
113
|
+
this.store.connected = false;
|
|
114
|
+
this.scheduleReconnect();
|
|
115
|
+
};
|
|
116
|
+
this.ws.onerror = (error) => {
|
|
117
|
+
this.store.stderr(`ReduxCluster.createWSClient connection error: ${error}`);
|
|
118
|
+
this.authenticated = false;
|
|
119
|
+
this.store.connected = false;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
this.store.stderr(`ReduxCluster.createWSClient client error: ${error}`);
|
|
125
|
+
this.store.connected = false;
|
|
126
|
+
this.scheduleReconnect();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
this.handleMessage = (message) => {
|
|
130
|
+
var _a;
|
|
131
|
+
if (message._hash !== this.store.RCHash) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
switch (message._msg) {
|
|
135
|
+
case "REDUX_CLUSTER_MSGTOWORKER":
|
|
136
|
+
if (message._action) {
|
|
137
|
+
this.originalDispatch(message._action);
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
case "REDUX_CLUSTER_SOCKET_AUTHSTATE":
|
|
141
|
+
if (message._value === true) {
|
|
142
|
+
this.authenticated = true;
|
|
143
|
+
this.store.connected = true;
|
|
144
|
+
this.sendMessage({
|
|
145
|
+
_msg: "REDUX_CLUSTER_START",
|
|
146
|
+
_hash: this.store.RCHash,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
this.authenticated = false;
|
|
151
|
+
this.store.connected = false;
|
|
152
|
+
if (message._banned) {
|
|
153
|
+
this.store.stderr("Your IP is locked for 3 hours");
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
this.store.stderr("Authorization failed");
|
|
157
|
+
}
|
|
158
|
+
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
this.dispatch = (action) => {
|
|
164
|
+
try {
|
|
165
|
+
if (this.ws && this.ws.readyState === 1 && this.authenticated) {
|
|
166
|
+
// WebSocket.OPEN = 1
|
|
167
|
+
this.sendMessage({
|
|
168
|
+
_msg: "REDUX_CLUSTER_MSGTOMASTER",
|
|
169
|
+
_hash: this.store.RCHash,
|
|
170
|
+
_action: action,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
this.store.stderr("WebSocket is not connected or not authenticated");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
this.store.stderr(`ReduxCluster.createWSClient write error: ${error}`);
|
|
179
|
+
}
|
|
180
|
+
return action;
|
|
181
|
+
};
|
|
182
|
+
this.sendMessage = (message) => {
|
|
183
|
+
if (this.ws && this.ws.readyState === 1) {
|
|
184
|
+
// WebSocket.OPEN = 1
|
|
185
|
+
this.ws.send(JSON.stringify(message));
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
this.scheduleReconnect = () => {
|
|
189
|
+
if (this.reconnectTimer) {
|
|
190
|
+
clearTimeout(this.reconnectTimer);
|
|
191
|
+
}
|
|
192
|
+
this.reconnectTimer = setTimeout(() => {
|
|
193
|
+
this.reconnect();
|
|
194
|
+
}, this.config.reconnectInterval);
|
|
195
|
+
};
|
|
196
|
+
this.destroy = () => {
|
|
197
|
+
if (this.reconnectTimer) {
|
|
198
|
+
clearTimeout(this.reconnectTimer);
|
|
199
|
+
}
|
|
200
|
+
if (this.ws) {
|
|
201
|
+
this.ws.close();
|
|
202
|
+
}
|
|
203
|
+
// Restore original dispatch
|
|
204
|
+
this.store.dispatch = this.originalDispatch;
|
|
205
|
+
};
|
|
206
|
+
this.store = store;
|
|
207
|
+
this.config = {
|
|
208
|
+
port: 10002,
|
|
209
|
+
reconnectInterval: 10000,
|
|
210
|
+
timeout: 30000,
|
|
211
|
+
...config,
|
|
212
|
+
};
|
|
213
|
+
// Validate config
|
|
214
|
+
if (!config.host) {
|
|
215
|
+
throw new Error("Config requires server address!");
|
|
216
|
+
}
|
|
217
|
+
if (!config.login) {
|
|
218
|
+
throw new Error("Config requires login for server authorization!");
|
|
219
|
+
}
|
|
220
|
+
if (!config.password) {
|
|
221
|
+
throw new Error("Config requires password for server authorization!");
|
|
222
|
+
}
|
|
223
|
+
// Setup authentication
|
|
224
|
+
this.login = (0, utils_js_1.hasher)(`REDUX_CLUSTER${config.login}`);
|
|
225
|
+
this.password = (0, utils_js_1.hasher)(`REDUX_CLUSTER${config.password}`);
|
|
226
|
+
// Override dispatch method
|
|
227
|
+
this.originalDispatch = this.store.dispatch;
|
|
228
|
+
this.store.dispatch = this.dispatch.bind(this);
|
|
229
|
+
// Add client role
|
|
230
|
+
if (!this.store.role.includes("client")) {
|
|
231
|
+
this.store.role.push("client");
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
throw new Error("One storage cannot be connected to two servers at the same time.");
|
|
235
|
+
}
|
|
236
|
+
this.store.connected = false;
|
|
237
|
+
this.reconnect();
|
|
238
|
+
}
|
|
239
|
+
buildWebSocketURL() {
|
|
240
|
+
const protocol = this.config.host.toLowerCase().includes("https://")
|
|
241
|
+
? "wss:"
|
|
242
|
+
: "ws:";
|
|
243
|
+
const host = this.config.host.replace(/^(https?|wss?):\/\//, "");
|
|
244
|
+
const path = `/redux-cluster-${this.store.RCHash}`;
|
|
245
|
+
return `${protocol}//${host}:${this.config.port}${path}`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
exports.ReduxClusterWSClientWrapper = ReduxClusterWSClientWrapper;
|
|
249
|
+
function createStore(reducer) {
|
|
250
|
+
const reduxCluster = new ReduxCluster(reducer);
|
|
251
|
+
reduxCluster.createWSClient = (config) => {
|
|
252
|
+
return new ReduxClusterWSClientWrapper(reduxCluster, config);
|
|
253
|
+
};
|
|
254
|
+
return reduxCluster;
|
|
255
|
+
}
|
|
256
|
+
function client(store) {
|
|
257
|
+
// Add createWSClient method to existing redux-cluster store
|
|
258
|
+
if (!store.createWSClient) {
|
|
259
|
+
// Get the current reducer
|
|
260
|
+
const originalReducer = store._reducer || ((state) => state);
|
|
261
|
+
// Create a new reducer that handles REDUX_CLUSTER_SYNC
|
|
262
|
+
const syncReducer = (state, action) => {
|
|
263
|
+
if (action.type === "REDUX_CLUSTER_SYNC") {
|
|
264
|
+
return (0, utils_js_1.deepClone)(action.payload);
|
|
265
|
+
}
|
|
266
|
+
return originalReducer(state, action);
|
|
267
|
+
};
|
|
268
|
+
// Replace the reducer
|
|
269
|
+
store.replaceReducer(syncReducer);
|
|
270
|
+
store.createWSClient = (config) => {
|
|
271
|
+
return new ReduxClusterWSClientWrapper(store, config);
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return store;
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.createStore = exports.client = exports.server = void 0;
|
|
18
|
+
var server_js_1 = require("./server.js");
|
|
19
|
+
Object.defineProperty(exports, "server", { enumerable: true, get: function () { return server_js_1.server; } });
|
|
20
|
+
var client_js_1 = require("./client.js");
|
|
21
|
+
Object.defineProperty(exports, "client", { enumerable: true, get: function () { return client_js_1.client; } });
|
|
22
|
+
Object.defineProperty(exports, "createStore", { enumerable: true, get: function () { return client_js_1.createStore; } });
|
|
23
|
+
__exportStar(require("./types.js"), exports);
|
|
24
|
+
__exportStar(require("./utils.js"), exports);
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,yCAAqC;AAA5B,mGAAA,MAAM,OAAA;AACf,yCAAkD;AAAzC,mGAAA,MAAM,OAAA;AAAE,wGAAA,WAAW,OAAA;AAC5B,6CAA2B;AAC3B,6CAA2B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { WSServerConfig, ReduxClusterStore, ReduxClusterMessage, WSConnection, AuthDatabase, IPBanDatabase, ReduxClusterWSServer } from "./types.js";
|
|
2
|
+
export declare class ReduxClusterWSServerWrapper implements ReduxClusterWSServer {
|
|
3
|
+
store: ReduxClusterStore;
|
|
4
|
+
uid: string;
|
|
5
|
+
connections: Map<string, WSConnection>;
|
|
6
|
+
authDatabase: AuthDatabase;
|
|
7
|
+
ipBanDatabase: IPBanDatabase;
|
|
8
|
+
config: WSServerConfig;
|
|
9
|
+
server?: any;
|
|
10
|
+
wss?: any;
|
|
11
|
+
unsubscribe?: () => void;
|
|
12
|
+
private readonly IP_BAN_ATTEMPTS;
|
|
13
|
+
private readonly IP_BAN_TIMEOUT;
|
|
14
|
+
private readonly CONNECTION_TIMEOUT;
|
|
15
|
+
private gcInterval?;
|
|
16
|
+
constructor(store: ReduxClusterStore);
|
|
17
|
+
private createWSServer;
|
|
18
|
+
private handleConnection;
|
|
19
|
+
private handleMessage;
|
|
20
|
+
private handleAuthentication;
|
|
21
|
+
sendToAll: (message?: ReduxClusterMessage) => void;
|
|
22
|
+
private sendMessage;
|
|
23
|
+
private extractIP;
|
|
24
|
+
private isIPBanned;
|
|
25
|
+
private recordFailedAttempt;
|
|
26
|
+
private clearIPBan;
|
|
27
|
+
private removeConnection;
|
|
28
|
+
private startGarbageCollection;
|
|
29
|
+
destroy: () => void;
|
|
30
|
+
}
|
|
31
|
+
export declare function server<S = any>(store: ReduxClusterStore<S>): ReduxClusterStore<S>;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReduxClusterWSServerWrapper = void 0;
|
|
4
|
+
exports.server = server;
|
|
5
|
+
const http_1 = require("http");
|
|
6
|
+
const https_1 = require("https");
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const utils_js_1 = require("./utils.js");
|
|
9
|
+
// Import WebSocket server for Node.js
|
|
10
|
+
const ws_1 = require("ws");
|
|
11
|
+
class ReduxClusterWSServerWrapper {
|
|
12
|
+
constructor(store) {
|
|
13
|
+
this.IP_BAN_ATTEMPTS = 15;
|
|
14
|
+
this.IP_BAN_TIMEOUT = 10800000; // 3 hours
|
|
15
|
+
this.CONNECTION_TIMEOUT = 30000;
|
|
16
|
+
this.createWSServer = (config) => {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
this.config = { ...this.config, ...config };
|
|
19
|
+
// Setup authentication database
|
|
20
|
+
if (this.config.logins) {
|
|
21
|
+
for (const [login, password] of Object.entries(this.config.logins)) {
|
|
22
|
+
const hashedLogin = (0, utils_js_1.hasher)(`REDUX_CLUSTER${login}`);
|
|
23
|
+
const hashedPassword = (0, utils_js_1.hasher)(`REDUX_CLUSTER${password}`);
|
|
24
|
+
this.authDatabase[hashedLogin] = hashedPassword;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Start garbage collection for banned IPs
|
|
28
|
+
this.startGarbageCollection();
|
|
29
|
+
// Create HTTP/HTTPS server if not provided
|
|
30
|
+
if (!this.config.server) {
|
|
31
|
+
if (((_a = this.config.ssl) === null || _a === void 0 ? void 0 : _a.key) && ((_b = this.config.ssl) === null || _b === void 0 ? void 0 : _b.cert)) {
|
|
32
|
+
const ssl = {
|
|
33
|
+
key: (0, fs_1.readFileSync)(this.config.ssl.key),
|
|
34
|
+
cert: (0, fs_1.readFileSync)(this.config.ssl.cert) +
|
|
35
|
+
(this.config.ssl.ca ? "\n" + (0, fs_1.readFileSync)(this.config.ssl.ca) : ""),
|
|
36
|
+
};
|
|
37
|
+
this.server = (0, https_1.createServer)(ssl);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.server = (0, http_1.createServer)();
|
|
41
|
+
}
|
|
42
|
+
this.server.setTimeout(this.CONNECTION_TIMEOUT);
|
|
43
|
+
this.server.listen(this.config.port, this.config.host);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this.server = this.config.server;
|
|
47
|
+
}
|
|
48
|
+
// Create WebSocket server
|
|
49
|
+
this.wss = new ws_1.WebSocketServer({
|
|
50
|
+
server: this.server,
|
|
51
|
+
path: `/redux-cluster-${this.store.RCHash}`,
|
|
52
|
+
});
|
|
53
|
+
this.wss.on("connection", this.handleConnection.bind(this));
|
|
54
|
+
// Subscribe to store changes
|
|
55
|
+
this.unsubscribe = this.store.subscribe(() => {
|
|
56
|
+
if (this.store.mode === "snapshot") {
|
|
57
|
+
this.sendToAll();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
// For action mode, override dispatch to send individual actions
|
|
61
|
+
if (this.store.mode === "action") {
|
|
62
|
+
const originalDispatch = this.store.dispatch;
|
|
63
|
+
this.store.dispatch = (action) => {
|
|
64
|
+
const result = originalDispatch(action);
|
|
65
|
+
// Send the action to all connected clients
|
|
66
|
+
if (action.type !== "@@INIT" && action.type !== "REDUX_CLUSTER_SYNC") {
|
|
67
|
+
this.sendToAll({
|
|
68
|
+
_msg: "REDUX_CLUSTER_MSGTOWORKER",
|
|
69
|
+
_hash: this.store.RCHash,
|
|
70
|
+
_action: action,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Add server role
|
|
77
|
+
if (!this.store.role.includes("server")) {
|
|
78
|
+
this.store.role.push("server");
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
this.handleConnection = (ws, req) => {
|
|
82
|
+
const connectionId = (0, utils_js_1.generateUID)();
|
|
83
|
+
const ip = this.extractIP(req);
|
|
84
|
+
const processedIP = (0, utils_js_1.replacer)(ip, true);
|
|
85
|
+
// Check if IP is banned
|
|
86
|
+
if (this.isIPBanned(processedIP)) {
|
|
87
|
+
this.sendMessage(ws, {
|
|
88
|
+
_msg: "REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
89
|
+
_hash: this.store.RCHash,
|
|
90
|
+
_value: false,
|
|
91
|
+
_banned: true,
|
|
92
|
+
});
|
|
93
|
+
ws.close();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const connection = {
|
|
97
|
+
id: connectionId,
|
|
98
|
+
ws,
|
|
99
|
+
authenticated: false,
|
|
100
|
+
ip: processedIP,
|
|
101
|
+
};
|
|
102
|
+
this.connections.set(connectionId, connection);
|
|
103
|
+
ws.on("message", (data) => {
|
|
104
|
+
try {
|
|
105
|
+
const message = JSON.parse(data.toString());
|
|
106
|
+
this.handleMessage(connectionId, message);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
this.store.stderr(`ReduxCluster.createWSServer message parse error: ${error}`);
|
|
110
|
+
this.removeConnection(connectionId);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
ws.on("close", () => {
|
|
114
|
+
this.removeConnection(connectionId);
|
|
115
|
+
});
|
|
116
|
+
ws.on("error", (error) => {
|
|
117
|
+
this.store.stderr(`ReduxCluster.createWSServer connection error: ${error.message}`);
|
|
118
|
+
this.removeConnection(connectionId);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
this.handleMessage = (connectionId, message) => {
|
|
122
|
+
const connection = this.connections.get(connectionId);
|
|
123
|
+
if (!connection || message._hash !== this.store.RCHash) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
switch (message._msg) {
|
|
127
|
+
case "REDUX_CLUSTER_SOCKET_AUTH":
|
|
128
|
+
this.handleAuthentication(connectionId, message);
|
|
129
|
+
break;
|
|
130
|
+
case "REDUX_CLUSTER_START":
|
|
131
|
+
if (connection.authenticated) {
|
|
132
|
+
this.sendMessage(connection.ws, {
|
|
133
|
+
_msg: "REDUX_CLUSTER_MSGTOWORKER",
|
|
134
|
+
_hash: this.store.RCHash,
|
|
135
|
+
_action: {
|
|
136
|
+
type: "REDUX_CLUSTER_SYNC",
|
|
137
|
+
payload: this.store.getState(),
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
case "REDUX_CLUSTER_MSGTOMASTER":
|
|
143
|
+
if (connection.authenticated && message._action) {
|
|
144
|
+
if (message._action.type === "REDUX_CLUSTER_SYNC") {
|
|
145
|
+
throw new Error("Please don't use REDUX_CLUSTER_SYNC action type!");
|
|
146
|
+
}
|
|
147
|
+
this.store.dispatch(message._action);
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
this.handleAuthentication = (connectionId, message) => {
|
|
153
|
+
const connection = this.connections.get(connectionId);
|
|
154
|
+
if (!connection)
|
|
155
|
+
return;
|
|
156
|
+
const { _login, _password } = message;
|
|
157
|
+
if (_login &&
|
|
158
|
+
_password &&
|
|
159
|
+
this.authDatabase[_login] &&
|
|
160
|
+
this.authDatabase[_login] === _password) {
|
|
161
|
+
// Authentication successful
|
|
162
|
+
connection.authenticated = true;
|
|
163
|
+
this.clearIPBan(connection.ip);
|
|
164
|
+
this.sendMessage(connection.ws, {
|
|
165
|
+
_msg: "REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
166
|
+
_hash: this.store.RCHash,
|
|
167
|
+
_value: true,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Authentication failed
|
|
172
|
+
this.recordFailedAttempt(connection.ip);
|
|
173
|
+
this.sendMessage(connection.ws, {
|
|
174
|
+
_msg: "REDUX_CLUSTER_SOCKET_AUTHSTATE",
|
|
175
|
+
_hash: this.store.RCHash,
|
|
176
|
+
_value: false,
|
|
177
|
+
});
|
|
178
|
+
this.removeConnection(connectionId);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
this.sendToAll = (message) => {
|
|
182
|
+
const msg = message || {
|
|
183
|
+
_msg: "REDUX_CLUSTER_MSGTOWORKER",
|
|
184
|
+
_hash: this.store.RCHash,
|
|
185
|
+
_action: { type: "REDUX_CLUSTER_SYNC", payload: this.store.getState() },
|
|
186
|
+
};
|
|
187
|
+
for (const connection of this.connections.values()) {
|
|
188
|
+
if (connection.authenticated && connection.ws.readyState === 1) {
|
|
189
|
+
// WebSocket.OPEN = 1
|
|
190
|
+
try {
|
|
191
|
+
this.sendMessage(connection.ws, msg);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
this.store.stderr(`ReduxCluster.createWSServer write error: ${error}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
this.sendMessage = (ws, message) => {
|
|
200
|
+
if (ws.readyState === 1) {
|
|
201
|
+
// WebSocket.OPEN = 1
|
|
202
|
+
ws.send(JSON.stringify(message));
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
this.extractIP = (req) => {
|
|
206
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
207
|
+
if (typeof forwarded === "string") {
|
|
208
|
+
return forwarded.split(",")[0].trim();
|
|
209
|
+
}
|
|
210
|
+
if (Array.isArray(forwarded)) {
|
|
211
|
+
return forwarded[0];
|
|
212
|
+
}
|
|
213
|
+
return req.socket.remoteAddress || "127.0.0.1";
|
|
214
|
+
};
|
|
215
|
+
this.isIPBanned = (ip) => {
|
|
216
|
+
const banInfo = this.ipBanDatabase[ip];
|
|
217
|
+
if (!banInfo)
|
|
218
|
+
return false;
|
|
219
|
+
const isTimedOut = banInfo.time + this.IP_BAN_TIMEOUT < Date.now();
|
|
220
|
+
if (isTimedOut) {
|
|
221
|
+
delete this.ipBanDatabase[ip];
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
return banInfo.count >= this.IP_BAN_ATTEMPTS;
|
|
225
|
+
};
|
|
226
|
+
this.recordFailedAttempt = (ip) => {
|
|
227
|
+
const existing = this.ipBanDatabase[ip];
|
|
228
|
+
let count = 0;
|
|
229
|
+
if (existing) {
|
|
230
|
+
const isTimedOut = existing.time + this.IP_BAN_TIMEOUT < Date.now();
|
|
231
|
+
count = isTimedOut ? 0 : existing.count;
|
|
232
|
+
}
|
|
233
|
+
this.ipBanDatabase[ip] = {
|
|
234
|
+
time: Date.now(),
|
|
235
|
+
count: count + 1,
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
this.clearIPBan = (ip) => {
|
|
239
|
+
delete this.ipBanDatabase[ip];
|
|
240
|
+
};
|
|
241
|
+
this.removeConnection = (connectionId) => {
|
|
242
|
+
const connection = this.connections.get(connectionId);
|
|
243
|
+
if (connection) {
|
|
244
|
+
try {
|
|
245
|
+
connection.ws.close();
|
|
246
|
+
}
|
|
247
|
+
catch (_a) {
|
|
248
|
+
// Ignore errors when closing
|
|
249
|
+
}
|
|
250
|
+
this.connections.delete(connectionId);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
this.startGarbageCollection = () => {
|
|
254
|
+
this.gcInterval = setInterval(() => {
|
|
255
|
+
const now = Date.now();
|
|
256
|
+
for (const [ip, banInfo] of Object.entries(this.ipBanDatabase)) {
|
|
257
|
+
if (banInfo.time + this.IP_BAN_TIMEOUT < now) {
|
|
258
|
+
delete this.ipBanDatabase[ip];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}, 60000); // Run every minute
|
|
262
|
+
};
|
|
263
|
+
this.destroy = () => {
|
|
264
|
+
if (this.gcInterval) {
|
|
265
|
+
clearInterval(this.gcInterval);
|
|
266
|
+
}
|
|
267
|
+
if (this.unsubscribe) {
|
|
268
|
+
this.unsubscribe();
|
|
269
|
+
}
|
|
270
|
+
for (const connection of this.connections.values()) {
|
|
271
|
+
this.removeConnection(connection.id);
|
|
272
|
+
}
|
|
273
|
+
if (this.wss) {
|
|
274
|
+
this.wss.close();
|
|
275
|
+
}
|
|
276
|
+
if (this.server && this.server.close) {
|
|
277
|
+
this.server.close();
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
this.store = store;
|
|
281
|
+
this.uid = (0, utils_js_1.generateUID)();
|
|
282
|
+
this.connections = new Map();
|
|
283
|
+
this.authDatabase = {};
|
|
284
|
+
this.ipBanDatabase = {};
|
|
285
|
+
this.config = { host: "0.0.0.0", port: 10002 };
|
|
286
|
+
// Attach createWSServer method to store
|
|
287
|
+
store.createWSServer = this.createWSServer.bind(this);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
exports.ReduxClusterWSServerWrapper = ReduxClusterWSServerWrapper;
|
|
291
|
+
function server(store) {
|
|
292
|
+
new ReduxClusterWSServerWrapper(store);
|
|
293
|
+
return store;
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=server.js.map
|