redux-cluster-ws 1.4.0 → 2.0.1

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.
Files changed (54) hide show
  1. package/FUNDING.yml +7 -0
  2. package/LICENSE +21 -21
  3. package/README.md +394 -90
  4. package/dist/cjs/client.d.ts +43 -0
  5. package/dist/cjs/client.js +276 -0
  6. package/dist/cjs/client.js.map +1 -0
  7. package/dist/cjs/index.d.ts +4 -0
  8. package/dist/cjs/index.js +25 -0
  9. package/dist/cjs/index.js.map +1 -0
  10. package/dist/cjs/package.json +1 -0
  11. package/dist/cjs/server.d.ts +31 -0
  12. package/dist/cjs/server.js +295 -0
  13. package/dist/cjs/server.js.map +1 -0
  14. package/dist/cjs/types.d.ts +83 -0
  15. package/dist/cjs/types.js +3 -0
  16. package/dist/cjs/types.js.map +1 -0
  17. package/dist/cjs/utils.d.ts +17 -0
  18. package/dist/cjs/utils.js +75 -0
  19. package/dist/cjs/utils.js.map +1 -0
  20. package/dist/esm/client.d.ts +43 -0
  21. package/dist/esm/client.js +282 -0
  22. package/dist/esm/client.js.map +1 -0
  23. package/dist/esm/index.d.ts +4 -0
  24. package/dist/esm/index.js +5 -0
  25. package/dist/esm/index.js.map +1 -0
  26. package/dist/esm/server.d.ts +31 -0
  27. package/dist/esm/server.js +299 -0
  28. package/dist/esm/server.js.map +1 -0
  29. package/dist/esm/types.d.ts +83 -0
  30. package/dist/esm/types.js +2 -0
  31. package/dist/esm/types.js.map +1 -0
  32. package/dist/esm/utils.d.ts +17 -0
  33. package/dist/esm/utils.js +69 -0
  34. package/dist/esm/utils.js.map +1 -0
  35. package/eslint.config.js +143 -0
  36. package/examples/browser-example.cjs +350 -0
  37. package/examples/browser.html +255 -0
  38. package/examples/client.cjs +155 -0
  39. package/examples/cross-library-browser.html +655 -0
  40. package/examples/cross-library-client.cjs +190 -0
  41. package/examples/cross-library-server.cjs +213 -0
  42. package/examples/server.cjs +96 -0
  43. package/package.json +96 -18
  44. package/client.js +0 -192
  45. package/index.js +0 -12
  46. package/server.js +0 -173
  47. package/test.auto.proc1.js +0 -100
  48. package/test.auto.proc2.js +0 -74
  49. package/test.visual.client.html +0 -37
  50. package/test.visual.client.js +0 -63
  51. package/test.visual.server.js +0 -66
  52. package/umd/ReduxCluster.js +0 -18
  53. package/webpack-src.js +0 -6
  54. 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,4 @@
1
+ export { server } from "./server.js";
2
+ export { client, createStore } from "./client.js";
3
+ export * from "./types.js";
4
+ export * from "./utils.js";
@@ -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