├── .gitignore
├── Dockerfile
├── index.html
├── package-lock.json
├── package.json
├── script.js
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the official Node.js image
2 | FROM node:18
3 |
4 | # Create and set the working directory
5 | WORKDIR /app
6 |
7 | # Copy the package.json and package-lock.json
8 | COPY package*.json ./
9 |
10 | # Install the dependencies
11 | RUN npm install
12 |
13 | # Copy the rest of the application code
14 | COPY . .
15 |
16 | # Expose the port the application runs on
17 | EXPOSE 8080
18 |
19 | # Run the application
20 | CMD ["npm", "start"]
21 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | WebSocket Checkboxes
7 |
119 |
120 |
121 |
122 |
123 |
Total Connections: 0
124 |
Total Active Users: 0
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "websocker-checkboxes",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "websocker-checkboxes",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "ws": "^8.18.0",
13 | "zlib": "^1.0.5"
14 | }
15 | },
16 | "node_modules/ws": {
17 | "version": "8.18.0",
18 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
19 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
20 | "engines": {
21 | "node": ">=10.0.0"
22 | },
23 | "peerDependencies": {
24 | "bufferutil": "^4.0.1",
25 | "utf-8-validate": ">=5.0.2"
26 | },
27 | "peerDependenciesMeta": {
28 | "bufferutil": {
29 | "optional": true
30 | },
31 | "utf-8-validate": {
32 | "optional": true
33 | }
34 | }
35 | },
36 | "node_modules/zlib": {
37 | "version": "1.0.5",
38 | "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz",
39 | "integrity": "sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w==",
40 | "hasInstallScript": true,
41 | "engines": {
42 | "node": ">=0.2.0"
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "websocker-checkboxes",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node server.js",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "ws": "^8.18.0",
15 | "zlib": "^1.0.5"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | const grid = document.getElementById('checkbox-grid');
2 | const totalConnectionsElement = document.getElementById('total-connections');
3 | const activeUsersElement = document.getElementById('active-users');
4 | const connectBtn = document.getElementById('connect-btn');
5 | const usernameInput = document.getElementById('username-input');
6 | const usernameDisplay = document.getElementById('username-display');
7 | const timerElement = document.createElement('div'); // Timer display
8 |
9 | let checkboxes = new Array(100000).fill(false);
10 | let ws;
11 | let countdownInterval;
12 |
13 | const CHECKBOX_BATCH_SIZE = 500; // Number of checkboxes to load at a time
14 | let loadedCheckboxes = 0; // Track how many checkboxes have been loaded
15 | let isConnected = false; // Track connection status
16 |
17 | // Automatically connect to WebSocket to get initial stats and checkbox state
18 | connectToWebSocket();
19 |
20 | function connectToWebSocket() {
21 | const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
22 | const host = window.location.host;
23 | const wsUrl = `${protocol}://${host}/`;
24 | ws = new WebSocket(wsUrl);
25 |
26 | ws.onopen = () => {
27 | console.log('WebSocket connection opened');
28 | };
29 |
30 | ws.onmessage = (event) => {
31 | const data = JSON.parse(event.data);
32 | if (Array.isArray(data)) {
33 | // Received checkbox state array
34 | checkboxes = data;
35 | renderCheckboxes();
36 | } else if (data.totalConnections !== undefined && data.activeUsers !== undefined) {
37 | // Received stats update
38 | totalConnectionsElement.textContent = data.totalConnections;
39 | activeUsersElement.textContent = data.activeUsers;
40 | } else if (data.index !== undefined && data.checked !== undefined) {
41 | // Received individual checkbox update
42 | checkboxes[data.index] = data.checked;
43 | updateCheckbox(data.index, data.checked);
44 | }
45 | };
46 |
47 | ws.onclose = () => {
48 | console.log('WebSocket connection closed');
49 | connectBtn.style.display = 'inline-block';
50 | grid.style.display = 'none';
51 | timerElement.textContent = ''; // Clear the timer display
52 | isConnected = false; // Reset connection status
53 | disableCheckboxes(); // Disable checkboxes on disconnect
54 | };
55 |
56 | ws.onerror = (error) => {
57 | console.error('WebSocket error:', error);
58 | };
59 | }
60 |
61 | function startConnection() {
62 | grid.style.display = 'grid';
63 | connectBtn.style.display = 'none';
64 | usernameDisplay.appendChild(timerElement); // Add the timer to the username display
65 | startTimer();
66 | isConnected = true; // Set connection status to true
67 | enableCheckboxes(); // Enable checkboxes after connection
68 | }
69 |
70 | function handleCheckboxChange(index) {
71 | if (!isConnected) return; // Prevent interaction if not connected
72 | const newCheckedState = !checkboxes[index];
73 | checkboxes[index] = newCheckedState;
74 | ws.send(JSON.stringify({ index, checked: newCheckedState }));
75 | }
76 |
77 | function renderCheckboxes() {
78 | const fragment = document.createDocumentFragment();
79 | for (let i = loadedCheckboxes; i < Math.min(checkboxes.length, loadedCheckboxes + CHECKBOX_BATCH_SIZE); i++) {
80 | const checkbox = document.createElement('input');
81 | checkbox.type = 'checkbox';
82 | checkbox.id = `checkbox-${i}`;
83 | checkbox.className = 'checkbox';
84 | checkbox.checked = checkboxes[i];
85 | checkbox.disabled = !isConnected; // Disable or enable based on connection status
86 | checkbox.onchange = () => handleCheckboxChange(i);
87 | fragment.appendChild(checkbox);
88 | }
89 | grid.appendChild(fragment);
90 | loadedCheckboxes += CHECKBOX_BATCH_SIZE;
91 | }
92 |
93 | function updateCheckbox(index, checked) {
94 | const checkbox = document.getElementById(`checkbox-${index}`);
95 | if (checkbox) {
96 | checkbox.checked = checked;
97 | }
98 | }
99 |
100 | function startTimer() {
101 | let timeLeft = 60;
102 | timerElement.textContent = `Time remaining: ${timeLeft}s`;
103 | countdownInterval = setInterval(() => {
104 | timeLeft--;
105 | timerElement.textContent = `Time remaining: ${timeLeft}s`;
106 | if (timeLeft <= 0) {
107 | clearInterval(countdownInterval);
108 | ws.close();
109 | disableCheckboxes(); // Disable all checkboxes when the timer ends
110 | }
111 | }, 1000);
112 | }
113 |
114 | function enableCheckboxes() {
115 | document.querySelectorAll('.checkbox').forEach(checkbox => {
116 | checkbox.disabled = false;
117 | });
118 | }
119 |
120 | function disableCheckboxes() {
121 | document.querySelectorAll('.checkbox').forEach(checkbox => {
122 | checkbox.disabled = true;
123 | });
124 | grid.style.display = 'none'; // Hide the grid after disconnection
125 | connectBtn.style.display = 'inline-block'; // Show the connect button again
126 | }
127 |
128 | // Lazy loading: load more checkboxes as the user scrolls
129 | window.addEventListener('scroll', () => {
130 | if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
131 | renderCheckboxes();
132 | }
133 | });
134 |
135 | usernameInput.addEventListener('input', () => {
136 | const username = usernameInput.value.trim();
137 | connectBtn.disabled = username === '';
138 | usernameDisplay.textContent = username ? `Welcome, ${username}` : '';
139 | });
140 |
141 | connectBtn.addEventListener('click', () => {
142 | startConnection();
143 | });
144 |
145 | // Initial rendering of checkboxes
146 | renderCheckboxes();
147 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const WebSocket = require('ws');
2 | const http = require('http');
3 | const fs = require('fs');
4 | const path = require('path');
5 | const zlib = require('zlib');
6 |
7 | // Create HTTP server to serve the static files (HTML, JS, CSS)
8 | const server = http.createServer((req, res) => {
9 | let filePath = '.' + req.url;
10 | if (filePath === './') {
11 | filePath = './index.html';
12 | }
13 |
14 | const extname = String(path.extname(filePath)).toLowerCase();
15 | const mimeTypes = {
16 | '.html': 'text/html',
17 | '.js': 'application/javascript',
18 | '.css': 'text/css',
19 | '.json': 'application/json',
20 | '.png': 'image/png',
21 | '.jpg': 'image/jpg',
22 | '.gif': 'image/gif',
23 | '.svg': 'image/svg+xml',
24 | '.wav': 'audio/wav',
25 | '.mp4': 'video/mp4',
26 | '.woff': 'application/font-woff',
27 | '.ttf': 'application/font-ttf',
28 | '.eot': 'application/vnd.ms-fontobject',
29 | '.otf': 'application/font-otf',
30 | '.wasm': 'application/wasm'
31 | };
32 |
33 | const contentType = mimeTypes[extname] || 'application/octet-stream';
34 |
35 | fs.readFile(filePath, (error, content) => {
36 | if (error) {
37 | if (error.code == 'ENOENT') {
38 | fs.readFile('./404.html', (error, content) => {
39 | res.writeHead(404, { 'Content-Type': 'text/html' });
40 | res.end(content, 'utf-8');
41 | });
42 | } else {
43 | res.writeHead(500);
44 | res.end('Sorry, there was an error: ' + error.code + ' ..\n');
45 | }
46 | } else {
47 | res.writeHead(200, { 'Content-Type': contentType });
48 | res.end(content, 'utf-8');
49 | }
50 | });
51 | });
52 |
53 | // Create WebSocket server on top of HTTP server
54 | const wss = new WebSocket.Server({ server });
55 |
56 | let totalConnections = 0;
57 | let activeUsers = 0;
58 | let checkboxState = new Array(100000).fill(false);
59 |
60 | const userActionLimits = {}; // Store user actions count and timestamps
61 |
62 | wss.on('connection', (ws) => {
63 | totalConnections++;
64 | activeUsers++;
65 | broadcastStats();
66 |
67 | // Send initial checkbox state to new connection
68 | ws.send(JSON.stringify(checkboxState));
69 | ws.send(JSON.stringify({ totalConnections, activeUsers }));
70 |
71 | ws.on('message', (message) => {
72 | const data = JSON.parse(message);
73 | const userId = ws._socket.remoteAddress; // Example user identifier
74 | const currentTime = Date.now();
75 |
76 | if (!userActionLimits[userId]) {
77 | userActionLimits[userId] = { count: 0, lastActionTime: currentTime };
78 | }
79 |
80 | const timeSinceLastAction = currentTime - userActionLimits[userId].lastActionTime;
81 |
82 | if (timeSinceLastAction > 1000) { // Reset count after 1 second
83 | userActionLimits[userId].count = 0;
84 | userActionLimits[userId].lastActionTime = currentTime;
85 | }
86 |
87 | if (userActionLimits[userId].count >= 5) {
88 | ws.send(JSON.stringify({ error: "Rate limit exceeded" }));
89 | return;
90 | }
91 |
92 | userActionLimits[userId].count += 1;
93 |
94 | if (data.index !== undefined && data.checked !== undefined) {
95 | checkboxState[data.index] = data.checked;
96 | broadcastCheckboxState(data);
97 | }
98 | });
99 |
100 | ws.on('close', () => {
101 | activeUsers--;
102 | broadcastStats();
103 | });
104 | });
105 |
106 | function broadcastStats() {
107 | const stats = { totalConnections, activeUsers };
108 | wss.clients.forEach((client) => {
109 | if (client.readyState === WebSocket.OPEN) {
110 | client.send(JSON.stringify(stats));
111 | }
112 | });
113 | }
114 |
115 | function broadcastCheckboxState(data) {
116 | wss.clients.forEach((client) => {
117 | if (client.readyState === WebSocket.OPEN) {
118 | client.send(JSON.stringify(data));
119 | }
120 | });
121 | }
122 |
123 | // Listen on port 8080
124 | const PORT = 8080;
125 | server.listen(PORT, () => {
126 | console.log(`Server is listening on port ${PORT}`);
127 | });
128 |
--------------------------------------------------------------------------------