redux-cluster-ws 2.0.1 → 2.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redux-cluster-ws",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "WebSocket client/server wrapper for redux-cluster with TypeScript support",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -76,6 +76,11 @@
76
76
  "url": "https://github.com/siarheidudko/redux-cluster-ws/issues"
77
77
  },
78
78
  "homepage": "https://github.com/siarheidudko/redux-cluster-ws#readme",
79
+ "files": [
80
+ "dist",
81
+ "README.md",
82
+ "LICENSE"
83
+ ],
79
84
  "funding": [
80
85
  {
81
86
  "type": "individual",
@@ -96,7 +101,7 @@
96
101
  ],
97
102
  "dependencies": {
98
103
  "@sergdudko/objectstream": "^3.2.26",
99
- "protoobject": "^1.1.30",
104
+ "protoobject": "^2.0.0",
100
105
  "redux": "^5.0.1",
101
106
  "redux-cluster": "^2.0.2"
102
107
  },
package/FUNDING.yml DELETED
@@ -1,7 +0,0 @@
1
- patreon: dudko_dev
2
- custom:
3
- [
4
- "http://dudko.dev/donate",
5
- "https://paypal.me/dudkodev",
6
- "https://www.buymeacoffee.com/dudko.dev",
7
- ]
package/eslint.config.js DELETED
@@ -1,143 +0,0 @@
1
- import js from "@eslint/js";
2
- import tsPlugin from "@typescript-eslint/eslint-plugin";
3
- import tsParser from "@typescript-eslint/parser";
4
-
5
- export default [
6
- js.configs.recommended,
7
- {
8
- ignores: ["*.md", "**/*.md"], // Ignore Markdown files
9
- },
10
- {
11
- files: ["src/**/*.ts"],
12
- languageOptions: {
13
- parser: tsParser,
14
- parserOptions: {
15
- ecmaVersion: 2020,
16
- sourceType: "module",
17
- project: "./tsconfig.json",
18
- },
19
- globals: {
20
- // Node.js globals
21
- global: "readonly",
22
- process: "readonly",
23
- console: "readonly",
24
- Buffer: "readonly",
25
- require: "readonly",
26
- module: "readonly",
27
- exports: "readonly",
28
- __dirname: "readonly",
29
- __filename: "readonly",
30
- setTimeout: "readonly",
31
- clearTimeout: "readonly",
32
- setInterval: "readonly",
33
- clearInterval: "readonly",
34
- setImmediate: "readonly",
35
- clearImmediate: "readonly",
36
- structuredClone: "readonly",
37
- // TypeScript globals
38
- NodeJS: "readonly",
39
- BufferEncoding: "readonly",
40
- },
41
- },
42
- plugins: {
43
- "@typescript-eslint": tsPlugin,
44
- },
45
- rules: {
46
- ...tsPlugin.configs.recommended.rules,
47
- "@typescript-eslint/no-explicit-any": "off",
48
- "@typescript-eslint/no-unused-vars": [
49
- "error",
50
- { argsIgnorePattern: "^_" },
51
- ],
52
- "@typescript-eslint/explicit-function-return-type": "off",
53
- "@typescript-eslint/explicit-module-boundary-types": "off",
54
- "@typescript-eslint/no-empty-function": "off",
55
- "no-case-declarations": "off",
56
- "no-undef": "off", // TypeScript handles this
57
- },
58
- },
59
- {
60
- files: ["tests/**/*.cjs", "*.test.cjs", "test.*.cjs"],
61
- languageOptions: {
62
- parser: tsParser,
63
- parserOptions: {
64
- ecmaVersion: 2020,
65
- sourceType: "script",
66
- },
67
- globals: {
68
- // Node.js globals
69
- global: "readonly",
70
- process: "readonly",
71
- console: "readonly",
72
- Buffer: "readonly",
73
- require: "readonly",
74
- module: "readonly",
75
- exports: "readonly",
76
- __dirname: "readonly",
77
- __filename: "readonly",
78
- setTimeout: "readonly",
79
- clearTimeout: "readonly",
80
- setInterval: "readonly",
81
- clearInterval: "readonly",
82
- setImmediate: "readonly",
83
- clearImmediate: "readonly",
84
- structuredClone: "readonly",
85
- },
86
- },
87
- plugins: {
88
- "@typescript-eslint": tsPlugin,
89
- },
90
- rules: {
91
- ...tsPlugin.configs.recommended.rules,
92
- "@typescript-eslint/no-explicit-any": "off",
93
- "no-case-declarations": "off",
94
- "no-undef": "off",
95
- // Allow console in test files
96
- "no-console": "off",
97
- // Allow require in CommonJS
98
- "@typescript-eslint/no-var-requires": "off",
99
- },
100
- },
101
- {
102
- files: ["examples/**/*.cjs", "*.cjs"],
103
- languageOptions: {
104
- parser: tsParser,
105
- parserOptions: {
106
- ecmaVersion: 2020,
107
- sourceType: "script",
108
- },
109
- globals: {
110
- // Node.js globals
111
- global: "readonly",
112
- process: "readonly",
113
- console: "readonly",
114
- Buffer: "readonly",
115
- require: "readonly",
116
- module: "readonly",
117
- exports: "readonly",
118
- __dirname: "readonly",
119
- __filename: "readonly",
120
- setTimeout: "readonly",
121
- clearTimeout: "readonly",
122
- setInterval: "readonly",
123
- clearInterval: "readonly",
124
- setImmediate: "readonly",
125
- clearImmediate: "readonly",
126
- structuredClone: "readonly",
127
- },
128
- },
129
- plugins: {
130
- "@typescript-eslint": tsPlugin,
131
- },
132
- rules: {
133
- ...tsPlugin.configs.recommended.rules,
134
- "@typescript-eslint/no-explicit-any": "off",
135
- "no-case-declarations": "off",
136
- "no-undef": "off",
137
- // Allow console in examples
138
- "no-console": "off",
139
- // Allow require in CommonJS
140
- "@typescript-eslint/no-var-requires": "off",
141
- },
142
- },
143
- ];
@@ -1,350 +0,0 @@
1
- /**
2
- * Redux-Cluster-WS Browser Example JavaScript
3
- *
4
- * This script demonstrates how to use redux-cluster-ws in a browser environment.
5
- * It creates a simple counter application that synchronizes with a WebSocket server.
6
- */
7
-
8
- // Store instance
9
- let store = null;
10
- let wsClient = null;
11
-
12
- // Simple Redux implementation for browser (minimal version)
13
- function createSimpleRedux(reducer) {
14
- let state = reducer(undefined, {});
15
- let listeners = [];
16
-
17
- return {
18
- getState: () => state,
19
- dispatch: (action) => {
20
- state = reducer(state, action);
21
- listeners.forEach((listener) => listener());
22
- return action;
23
- },
24
- subscribe: (listener) => {
25
- listeners.push(listener);
26
- return () => {
27
- listeners = listeners.filter((l) => l !== listener);
28
- };
29
- },
30
- };
31
- }
32
-
33
- // Counter reducer (same as server)
34
- function counterReducer(state = { count: 0, lastUpdate: null }, action) {
35
- switch (action.type) {
36
- case "INCREMENT":
37
- return {
38
- count: state.count + 1,
39
- lastUpdate: new Date().toISOString(),
40
- };
41
-
42
- case "DECREMENT":
43
- return {
44
- count: state.count - 1,
45
- lastUpdate: new Date().toISOString(),
46
- };
47
-
48
- case "RESET":
49
- return {
50
- count: 0,
51
- lastUpdate: new Date().toISOString(),
52
- };
53
-
54
- default:
55
- return state;
56
- }
57
- }
58
-
59
- // Simple hash function (simplified version of redux-cluster-ws hasher)
60
- function simpleHash(str) {
61
- let hash = 0;
62
- for (let i = 0; i < str.length; i++) {
63
- const char = str.charCodeAt(i);
64
- hash = (hash << 5) - hash + char;
65
- hash = hash & hash;
66
- }
67
- return hash.toString(16);
68
- }
69
-
70
- // WebSocket Client implementation
71
- class BrowserWSClient {
72
- constructor(store, config) {
73
- this.store = store;
74
- this.config = config;
75
- this.authenticated = false;
76
- this.login = simpleHash(`REDUX_CLUSTER${config.login}`);
77
- this.password = simpleHash(`REDUX_CLUSTER${config.password}`);
78
- this.originalDispatch = store.dispatch;
79
-
80
- // Override store dispatch
81
- this.store.dispatch = this.dispatch.bind(this);
82
- this.store.connected = false;
83
- this.store.RCHash = "browser-example-hash";
84
- }
85
-
86
- connect() {
87
- const url = `${this.config.host}:${this.config.port}/redux-cluster-${this.store.RCHash}`;
88
- log(`Connecting to: ${url}`, "info");
89
-
90
- try {
91
- this.ws = new WebSocket(url);
92
-
93
- this.ws.onopen = () => {
94
- log("WebSocket connected", "success");
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
-
103
- this.ws.onmessage = (event) => {
104
- try {
105
- const message = JSON.parse(event.data);
106
- this.handleMessage(message);
107
- } catch (error) {
108
- log(`Message parse error: ${error.message}`, "error");
109
- }
110
- };
111
-
112
- this.ws.onclose = () => {
113
- log("WebSocket disconnected", "warning");
114
- this.authenticated = false;
115
- this.store.connected = false;
116
- updateUI();
117
- };
118
-
119
- this.ws.onerror = (error) => {
120
- log(`WebSocket error: ${error}`, "error");
121
- this.authenticated = false;
122
- this.store.connected = false;
123
- updateUI();
124
- };
125
- } catch (error) {
126
- log(`Connection error: ${error.message}`, "error");
127
- this.store.connected = false;
128
- updateUI();
129
- }
130
- }
131
-
132
- handleMessage(message) {
133
- if (message._hash !== this.store.RCHash) {
134
- return;
135
- }
136
-
137
- switch (message._msg) {
138
- case "REDUX_CLUSTER_MSGTOWORKER":
139
- if (message._action) {
140
- log(`Received action: ${message._action.type}`, "info");
141
- this.originalDispatch(message._action);
142
- }
143
- break;
144
-
145
- case "REDUX_CLUSTER_SOCKET_AUTHSTATE":
146
- if (message._value === true) {
147
- this.authenticated = true;
148
- this.store.connected = true;
149
- log("Authentication successful", "success");
150
- updateUI();
151
-
152
- // Request initial sync
153
- this.sendMessage({
154
- _msg: "REDUX_CLUSTER_START",
155
- _hash: this.store.RCHash,
156
- });
157
- } else {
158
- this.authenticated = false;
159
- this.store.connected = false;
160
-
161
- if (message._banned) {
162
- log("Authentication failed: IP banned", "error");
163
- } else {
164
- log("Authentication failed: Invalid credentials", "error");
165
- }
166
- updateUI();
167
- }
168
- break;
169
- }
170
- }
171
-
172
- dispatch(action) {
173
- try {
174
- if (
175
- this.ws &&
176
- this.ws.readyState === WebSocket.OPEN &&
177
- this.authenticated
178
- ) {
179
- log(`Sending action: ${action.type}`, "info");
180
- this.sendMessage({
181
- _msg: "REDUX_CLUSTER_MSGTOMASTER",
182
- _hash: this.store.RCHash,
183
- _action: action,
184
- });
185
- } else {
186
- log("Cannot dispatch: not connected or not authenticated", "warning");
187
- }
188
- } catch (error) {
189
- log(`Dispatch error: ${error.message}`, "error");
190
- }
191
- return action;
192
- }
193
-
194
- sendMessage(message) {
195
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
196
- this.ws.send(JSON.stringify(message));
197
- }
198
- }
199
-
200
- disconnect() {
201
- if (this.ws) {
202
- this.ws.close();
203
- }
204
- this.store.dispatch = this.originalDispatch;
205
- }
206
- }
207
-
208
- // Logging function
209
- function log(message, type = "info") {
210
- const logEl = document.getElementById("log");
211
- const timestamp = new Date().toLocaleTimeString();
212
- const entry = document.createElement("div");
213
- entry.className = `log-entry log-${type}`;
214
- entry.textContent = `[${timestamp}] ${message}`;
215
-
216
- logEl.appendChild(entry);
217
- logEl.scrollTop = logEl.scrollHeight;
218
-
219
- // Keep only last 50 entries
220
- while (logEl.children.length > 50) {
221
- logEl.removeChild(logEl.firstChild);
222
- }
223
- }
224
-
225
- // UI update function
226
- function updateUI() {
227
- const statusEl = document.getElementById("status");
228
- const connectBtn = document.getElementById("connectBtn");
229
- const counterEl = document.getElementById("counterValue");
230
- const lastUpdateEl = document.getElementById("lastUpdate");
231
- const buttons = ["incBtn", "decBtn", "resetBtn"];
232
-
233
- if (store && store.connected) {
234
- statusEl.textContent = "Connected";
235
- statusEl.className = "status connected";
236
- connectBtn.textContent = "Disconnect";
237
-
238
- // Enable action buttons
239
- buttons.forEach((id) => {
240
- document.getElementById(id).disabled = false;
241
- });
242
-
243
- // Update counter display
244
- const state = store.getState();
245
- counterEl.textContent = state.count;
246
- lastUpdateEl.textContent = state.lastUpdate
247
- ? `Last updated: ${new Date(state.lastUpdate).toLocaleString()}`
248
- : "Never updated";
249
- } else if (store) {
250
- statusEl.textContent = "Connecting...";
251
- statusEl.className = "status connecting";
252
- connectBtn.textContent = "Cancel";
253
-
254
- // Disable action buttons
255
- buttons.forEach((id) => {
256
- document.getElementById(id).disabled = true;
257
- });
258
- } else {
259
- statusEl.textContent = "Disconnected";
260
- statusEl.className = "status disconnected";
261
- connectBtn.textContent = "Connect";
262
-
263
- // Disable action buttons
264
- buttons.forEach((id) => {
265
- document.getElementById(id).disabled = true;
266
- });
267
-
268
- counterEl.textContent = "0";
269
- lastUpdateEl.textContent = "Never updated";
270
- }
271
- }
272
-
273
- // Connection toggle function
274
- function toggleConnection() {
275
- if (store && store.connected) {
276
- // Disconnect
277
- log("Disconnecting...", "info");
278
- if (wsClient) {
279
- wsClient.disconnect();
280
- }
281
- store = null;
282
- wsClient = null;
283
- updateUI();
284
- } else if (store && !store.connected) {
285
- // Cancel connection attempt
286
- if (wsClient) {
287
- wsClient.disconnect();
288
- }
289
- store = null;
290
- wsClient = null;
291
- updateUI();
292
- } else {
293
- // Connect
294
- const host = document.getElementById("host").value.trim();
295
- const port = document.getElementById("port").value.trim();
296
- const login = document.getElementById("login").value.trim();
297
- const password = document.getElementById("password").value.trim();
298
-
299
- if (!host || !port || !login || !password) {
300
- log("Please fill in all connection fields", "error");
301
- return;
302
- }
303
-
304
- log("Creating store and connecting...", "info");
305
-
306
- // Create store
307
- store = createSimpleRedux(counterReducer);
308
-
309
- // Subscribe to state changes
310
- store.subscribe(() => {
311
- updateUI();
312
- });
313
-
314
- // Create WebSocket client
315
- wsClient = new BrowserWSClient(store, {
316
- host: host,
317
- port: parseInt(port),
318
- login: login,
319
- password: password,
320
- });
321
-
322
- updateUI();
323
- wsClient.connect();
324
- }
325
- }
326
-
327
- // Action functions
328
- function increment() {
329
- if (store && store.connected) {
330
- store.dispatch({ type: "INCREMENT" });
331
- }
332
- }
333
-
334
- function decrement() {
335
- if (store && store.connected) {
336
- store.dispatch({ type: "DECREMENT" });
337
- }
338
- }
339
-
340
- function reset() {
341
- if (store && store.connected) {
342
- store.dispatch({ type: "RESET" });
343
- }
344
- }
345
-
346
- // Initialize UI on page load
347
- document.addEventListener("DOMContentLoaded", () => {
348
- log("Browser example loaded", "success");
349
- updateUI();
350
- });