├── Portfolio Website ├── script.js ├── index.html └── styles.css ├── Real-Time-Chat-App ├── chat-app │ ├── node_modules │ │ ├── ws │ │ │ ├── browser.js │ │ │ ├── wrapper.mjs │ │ │ ├── index.js │ │ │ ├── lib │ │ │ │ ├── constants.js │ │ │ │ ├── limiter.js │ │ │ │ ├── subprotocol.js │ │ │ │ ├── buffer-util.js │ │ │ │ ├── validation.js │ │ │ │ ├── stream.js │ │ │ │ ├── extension.js │ │ │ │ ├── event-target.js │ │ │ │ ├── permessage-deflate.js │ │ │ │ ├── websocket-server.js │ │ │ │ ├── sender.js │ │ │ │ ├── receiver.js │ │ │ │ └── websocket.js │ │ │ ├── LICENSE │ │ │ ├── package.json │ │ │ └── README.md │ │ └── .package-lock.json │ ├── package.json │ ├── package-lock.json │ └── server.js ├── index.html ├── script.js └── styles.css ├── E-Commerce Page ├── script.js ├── index.html └── styles.css ├── Todo App ├── index.html ├── script.js └── styles.css ├── Simple Calculator ├── script.js ├── index.html └── styles.css ├── Tic Tac Game ├── index.html ├── styles.css └── script.js └── Weather App ├── index.html ├── styles.css └── script.js /Portfolio Website/script.js: -------------------------------------------------------------------------------- 1 | // Add Javascript Functionality Here -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function () { 4 | throw new Error( 5 | 'ws does not work in the browser. Browser clients must use the native ' + 6 | 'WebSocket object' 7 | ); 8 | }; 9 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app", 3 | "version": "1.0.0", 4 | "main": "server.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "start": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "description": "", 13 | "dependencies": { 14 | "ws": "^8.18.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/wrapper.mjs: -------------------------------------------------------------------------------- 1 | import createWebSocketStream from './lib/stream.js'; 2 | import Receiver from './lib/receiver.js'; 3 | import Sender from './lib/sender.js'; 4 | import WebSocket from './lib/websocket.js'; 5 | import WebSocketServer from './lib/websocket-server.js'; 6 | 7 | export { createWebSocketStream, Receiver, Sender, WebSocket, WebSocketServer }; 8 | export default WebSocket; 9 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const WebSocket = require('./lib/websocket'); 4 | 5 | WebSocket.createWebSocketStream = require('./lib/stream'); 6 | WebSocket.Server = require('./lib/websocket-server'); 7 | WebSocket.Receiver = require('./lib/receiver'); 8 | WebSocket.Sender = require('./lib/sender'); 9 | 10 | WebSocket.WebSocket = WebSocket; 11 | WebSocket.WebSocketServer = WebSocket.Server; 12 | 13 | module.exports = WebSocket; 14 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments']; 4 | const hasBlob = typeof Blob !== 'undefined'; 5 | 6 | if (hasBlob) BINARY_TYPES.push('blob'); 7 | 8 | module.exports = { 9 | BINARY_TYPES, 10 | EMPTY_BUFFER: Buffer.alloc(0), 11 | GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 12 | hasBlob, 13 | kForOnEventAttribute: Symbol('kIsForOnEventAttribute'), 14 | kListener: Symbol('kListener'), 15 | kStatusCode: Symbol('status-code'), 16 | kWebSocket: Symbol('websocket'), 17 | NOOP: () => {} 18 | }; 19 | -------------------------------------------------------------------------------- /E-Commerce Page/script.js: -------------------------------------------------------------------------------- 1 | // Change main product image based on thumbnail click 2 | function changeImage(imageSrc) { 3 | document.getElementById('main-image').src = imageSrc; 4 | } 5 | 6 | // Increase quantity 7 | function increaseQuantity() { 8 | const quantityInput = document.getElementById('quantity'); 9 | quantityInput.value = parseInt(quantityInput.value) + 1; 10 | } 11 | 12 | // Decrease quantity 13 | function decreaseQuantity() { 14 | const quantityInput = document.getElementById('quantity'); 15 | if (parseInt(quantityInput.value) > 1) { 16 | quantityInput.value = parseInt(quantityInput.value) - 1; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Todo App/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Styled To-Do List 7 | 8 | 9 | 10 |
11 |

Todo List

12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Simple Calculator/script.js: -------------------------------------------------------------------------------- 1 | let currentExpression = ''; 2 | 3 | function appendNumber(number) { 4 | currentExpression += number; 5 | document.getElementById('result').value = currentExpression; 6 | } 7 | 8 | function appendOperator(operator) { 9 | currentExpression += ` ${operator} `; 10 | document.getElementById('result').value = currentExpression; 11 | } 12 | 13 | function calculateResult() { 14 | try { 15 | currentExpression = eval(currentExpression.replace('÷', '/').replace('×', '*')); 16 | document.getElementById('result').value = currentExpression; 17 | } catch { 18 | document.getElementById('result').value = 'Error'; 19 | } 20 | } 21 | 22 | function clearResult() { 23 | currentExpression = ''; 24 | document.getElementById('result').value = ''; 25 | } 26 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Real-Time Chat 7 | 8 | 9 | 10 |
11 |
12 |

Chat Room

13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/.package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "node_modules/ws": { 8 | "version": "8.18.0", 9 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 10 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 11 | "license": "MIT", 12 | "engines": { 13 | "node": ">=10.0.0" 14 | }, 15 | "peerDependencies": { 16 | "bufferutil": "^4.0.1", 17 | "utf-8-validate": ">=5.0.2" 18 | }, 19 | "peerDependenciesMeta": { 20 | "bufferutil": { 21 | "optional": true 22 | }, 23 | "utf-8-validate": { 24 | "optional": true 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "chat-app", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ws": "^8.18.0" 13 | } 14 | }, 15 | "node_modules/ws": { 16 | "version": "8.18.0", 17 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 18 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 19 | "license": "MIT", 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 | } 37 | } 38 | -------------------------------------------------------------------------------- /Portfolio Website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Portfolio - Web Designer 7 | 8 | 9 | 10 |
11 | 22 |
23 | 24 |
25 |
26 |
27 |

Welcome to My Website

28 |

I'm a Web Developer & a Techinical Writer

29 | Contact Me 30 |
31 |
32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Tic Tac Game/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tic Tac Toe 7 | 8 | 9 | 10 |
11 |

Tic-Tac-Toe

12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |

Player X's Turn

24 | 25 |
26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Einar Otto Stangvik 2 | Copyright (c) 2013 Arnout Kazemier and contributors 3 | Copyright (c) 2016 Luigi Pinca and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/limiter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const kDone = Symbol('kDone'); 4 | const kRun = Symbol('kRun'); 5 | 6 | /** 7 | * A very simple job queue with adjustable concurrency. Adapted from 8 | * https://github.com/STRML/async-limiter 9 | */ 10 | class Limiter { 11 | /** 12 | * Creates a new `Limiter`. 13 | * 14 | * @param {Number} [concurrency=Infinity] The maximum number of jobs allowed 15 | * to run concurrently 16 | */ 17 | constructor(concurrency) { 18 | this[kDone] = () => { 19 | this.pending--; 20 | this[kRun](); 21 | }; 22 | this.concurrency = concurrency || Infinity; 23 | this.jobs = []; 24 | this.pending = 0; 25 | } 26 | 27 | /** 28 | * Adds a job to the queue. 29 | * 30 | * @param {Function} job The job to run 31 | * @public 32 | */ 33 | add(job) { 34 | this.jobs.push(job); 35 | this[kRun](); 36 | } 37 | 38 | /** 39 | * Removes a job from the queue and runs it if possible. 40 | * 41 | * @private 42 | */ 43 | [kRun]() { 44 | if (this.pending === this.concurrency) return; 45 | 46 | if (this.jobs.length) { 47 | const job = this.jobs.shift(); 48 | 49 | this.pending++; 50 | job(this[kDone]); 51 | } 52 | } 53 | } 54 | 55 | module.exports = Limiter; 56 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/script.js: -------------------------------------------------------------------------------- 1 | 2 | const socket = new WebSocket('ws://localhost:8080'); 3 | socket.onmessage = function (event) { 4 | if (event.data instanceof Blob) { 5 | // If the data is a Blob, convert it to text 6 | event.data.text().then(text => { 7 | const chatBox = document.getElementById('chat-box'); 8 | const message = document.createElement('p'); 9 | message.textContent = text; 10 | chatBox.appendChild(message); 11 | 12 | chatBox.scrollTop = chatBox.scrollHeight; // Scroll to the latest message 13 | }); 14 | } else { 15 | // If the data is already a string, just append it 16 | const chatBox = document.getElementById('chat-box'); 17 | const message = document.createElement('p'); 18 | message.textContent = event.data; 19 | chatBox.appendChild(message); 20 | 21 | chatBox.scrollTop = chatBox.scrollHeight; // Scroll to the latest message 22 | } 23 | }; 24 | 25 | function sendMessage() { 26 | const messageInput = document.getElementById('message'); 27 | const message = messageInput.value.trim(); 28 | 29 | if (message !== '') { 30 | socket.send(message); // Send the message to the WebSocket server 31 | messageInput.value = ''; // Clear input field 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Todo App/script.js: -------------------------------------------------------------------------------- 1 | function addTask() { 2 | const taskInput = document.getElementById('taskInput'); 3 | const taskText = taskInput.value.trim(); // Trim whitespace 4 | 5 | // Check if task is empty 6 | if (taskText === '') { 7 | alert('Please enter a task'); 8 | return; 9 | } 10 | 11 | // Create a new task list item 12 | const taskList = document.getElementById('taskList'); 13 | const listItem = document.createElement('li'); 14 | 15 | // Create a span to hold the task text 16 | const taskSpan = document.createElement('span'); 17 | taskSpan.textContent = taskText; 18 | 19 | // Complete Button 20 | const completeButton = document.createElement('button'); 21 | completeButton.textContent = '○'; 22 | completeButton.onclick = () => { 23 | taskSpan.classList.toggle('completed'); // Toggle 'completed' class 24 | }; 25 | 26 | // Remove Button (optional) 27 | const removeButton = document.createElement('button'); 28 | removeButton.textContent = 'Remove'; 29 | removeButton.onclick = () => { 30 | taskList.removeChild(listItem); // Remove task item 31 | }; 32 | 33 | // Append task text and buttons to list item 34 | listItem.appendChild(completeButton); 35 | listItem.appendChild(taskSpan); 36 | listItem.appendChild(removeButton); 37 | 38 | // Append list item to task list 39 | taskList.appendChild(listItem); 40 | 41 | // Clear input after adding task 42 | taskInput.value = ''; 43 | } 44 | -------------------------------------------------------------------------------- /Simple Calculator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Calculator 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Simple Calculator/styles.css: -------------------------------------------------------------------------------- 1 | /* Basic reset */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | /* Body styling */ 9 | body { 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | height: 100vh; 14 | background-color: #f5f5f5; 15 | } 16 | 17 | /* Calculator container */ 18 | .calculator { 19 | background-color: #fff; 20 | padding: 20px; 21 | border-radius: 12px; 22 | box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); 23 | } 24 | 25 | /* Calculator display */ 26 | input { 27 | width: 100%; 28 | font-size: 2rem; 29 | padding: 15px; 30 | border: none; 31 | background-color: #f1f1f1; 32 | text-align: right; 33 | margin-bottom: 20px; 34 | border-radius: 8px; 35 | } 36 | 37 | /* Buttons container */ 38 | .buttons { 39 | display: grid; 40 | grid-template-columns: repeat(4, 1fr); 41 | gap: 10px; 42 | } 43 | 44 | /* Buttons styling */ 45 | button { 46 | font-size: 1.5rem; 47 | padding: 20px; 48 | background-color: #e0e0e0; 49 | border: none; 50 | border-radius: 8px; 51 | cursor: pointer; 52 | transition: background-color 0.3s; 53 | } 54 | 55 | button:hover { 56 | background-color: #cccccc; 57 | } 58 | 59 | /* Number and operator buttons */ 60 | button:nth-child(-n+4) { 61 | background-color: #e0e0e0; /* Gray for operators */ 62 | } 63 | 64 | /* Equal button */ 65 | .equal { 66 | background-color: #6e00ff; /* Purple */ 67 | color: white; 68 | grid-row: span 2; /* Make the "=" button span two rows */ 69 | } 70 | 71 | .equal:hover { 72 | background-color: #5300cc; 73 | } 74 | -------------------------------------------------------------------------------- /Tic Tac Game/styles.css: -------------------------------------------------------------------------------- 1 | /* Basic Reset */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | /* Body Styling */ 9 | body { 10 | font-family: 'Arial', sans-serif; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | height: 100vh; 15 | background-color: #f0f0f0; 16 | } 17 | 18 | /* Container */ 19 | .container { 20 | text-align: center; 21 | background-color: white; 22 | padding: 30px; 23 | border-radius: 15px; 24 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); 25 | } 26 | 27 | /* Heading */ 28 | h1 { 29 | font-size: 2.5rem; 30 | margin-bottom: 20px; 31 | color: #333; 32 | } 33 | 34 | /* Game Board */ 35 | .game-board { 36 | display: grid; 37 | grid-template-columns: repeat(3, 100px); 38 | grid-gap: 10px; 39 | margin-bottom: 20px; 40 | } 41 | 42 | /* Cells */ 43 | .cell { 44 | width: 100px; 45 | height: 100px; 46 | background-color: #f9f9f9; 47 | border: 2px solid #ddd; 48 | font-size: 2.5rem; 49 | display: flex; 50 | justify-content: center; 51 | align-items: center; 52 | cursor: pointer; 53 | transition: background-color 0.3s; 54 | } 55 | 56 | /* Cell hover effect */ 57 | .cell:hover { 58 | background-color: #eaeaea; 59 | } 60 | 61 | /* Status and Reset Button */ 62 | #status { 63 | font-size: 1.5rem; 64 | margin-bottom: 20px; 65 | } 66 | 67 | #resetButton { 68 | padding: 10px 20px; 69 | font-size: 1.2rem; 70 | background-color: #6e00ff; /* Purple */ 71 | color: white; 72 | border: none; 73 | border-radius: 10px; 74 | cursor: pointer; 75 | transition: background-color 0.3s; 76 | } 77 | 78 | #resetButton:hover { 79 | background-color: #5300cc; 80 | } 81 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/subprotocol.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { tokenChars } = require('./validation'); 4 | 5 | /** 6 | * Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names. 7 | * 8 | * @param {String} header The field value of the header 9 | * @return {Set} The subprotocol names 10 | * @public 11 | */ 12 | function parse(header) { 13 | const protocols = new Set(); 14 | let start = -1; 15 | let end = -1; 16 | let i = 0; 17 | 18 | for (i; i < header.length; i++) { 19 | const code = header.charCodeAt(i); 20 | 21 | if (end === -1 && tokenChars[code] === 1) { 22 | if (start === -1) start = i; 23 | } else if ( 24 | i !== 0 && 25 | (code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */ 26 | ) { 27 | if (end === -1 && start !== -1) end = i; 28 | } else if (code === 0x2c /* ',' */) { 29 | if (start === -1) { 30 | throw new SyntaxError(`Unexpected character at index ${i}`); 31 | } 32 | 33 | if (end === -1) end = i; 34 | 35 | const protocol = header.slice(start, end); 36 | 37 | if (protocols.has(protocol)) { 38 | throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`); 39 | } 40 | 41 | protocols.add(protocol); 42 | start = end = -1; 43 | } else { 44 | throw new SyntaxError(`Unexpected character at index ${i}`); 45 | } 46 | } 47 | 48 | if (start === -1 || end !== -1) { 49 | throw new SyntaxError('Unexpected end of input'); 50 | } 51 | 52 | const protocol = header.slice(start, i); 53 | 54 | if (protocols.has(protocol)) { 55 | throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`); 56 | } 57 | 58 | protocols.add(protocol); 59 | return protocols; 60 | } 61 | 62 | module.exports = { parse }; 63 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/server.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require('ws'); 2 | const readline = require('readline'); 3 | 4 | // Create WebSocket server 5 | const server = new WebSocket.Server({ port: 8080 }); 6 | 7 | // Track connected clients 8 | const clients = []; 9 | 10 | server.on('connection', socket => { 11 | console.log('Client connected'); 12 | clients.push(socket); // Add new client to clients array 13 | 14 | // Send a welcome message to the newly connected client 15 | socket.send('Welcome to the chat room!'); 16 | 17 | // Listen for messages from clients 18 | socket.on('message', message => { 19 | console.log(`Received: ${message}`); 20 | // Broadcast the received message to all clients 21 | broadcastMessage(message); 22 | }); 23 | 24 | socket.on('close', () => { 25 | console.log('Client disconnected'); 26 | // Remove the client from the array on disconnect 27 | const index = clients.indexOf(socket); 28 | if (index !== -1) { 29 | clients.splice(index, 1); 30 | } 31 | }); 32 | }); 33 | 34 | // Function to broadcast a message to all clients 35 | function broadcastMessage(message) { 36 | clients.forEach(client => { 37 | if (client.readyState === WebSocket.OPEN) { 38 | client.send(message); 39 | } 40 | }); 41 | } 42 | 43 | // Set up readline interface to read from terminal 44 | const rl = readline.createInterface({ 45 | input: process.stdin, 46 | output: process.stdout 47 | }); 48 | 49 | // Listen for terminal input and broadcast the input to all clients 50 | rl.on('line', (input) => { 51 | console.log(`Sending message from terminal: ${input}`); 52 | broadcastMessage(`Server: ${input}`); 53 | }); 54 | 55 | console.log('WebSocket server is running on ws://localhost:8080'); 56 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/styles.css: -------------------------------------------------------------------------------- 1 | /* Basic Reset */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | /* Body Styling */ 9 | body { 10 | font-family: 'Arial', sans-serif; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | height: 100vh; 15 | background-color: #f0f0f0; 16 | } 17 | 18 | /* Chat Container */ 19 | .chat-container { 20 | width: 400px; 21 | background-color: white; 22 | border-radius: 10px; 23 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); 24 | display: flex; 25 | flex-direction: column; 26 | overflow: hidden; 27 | } 28 | 29 | /* Chat Header */ 30 | .chat-header { 31 | background-color: #6e00ff; /* Purple */ 32 | color: white; 33 | text-align: center; 34 | padding: 15px; 35 | font-size: 1.5rem; 36 | font-weight: bold; 37 | } 38 | 39 | /* Chat Box */ 40 | .chat-box { 41 | flex-grow: 1; 42 | padding: 15px; 43 | overflow-y: auto; 44 | background-color: #fafafa; 45 | } 46 | 47 | .chat-box p { 48 | background-color: #eaeaea; 49 | padding: 10px; 50 | border-radius: 10px; 51 | margin-bottom: 10px; 52 | width: fit-content; 53 | max-width: 80%; 54 | } 55 | 56 | /* Message Input */ 57 | .message-input { 58 | display: flex; 59 | padding: 15px; 60 | background-color: #f7f7f7; 61 | } 62 | 63 | .message-input input { 64 | flex-grow: 1; 65 | padding: 10px; 66 | border-radius: 5px; 67 | border: 1px solid #ddd; 68 | margin-right: 10px; 69 | } 70 | 71 | .message-input button { 72 | background-color: #6e00ff; /* Purple */ 73 | color: white; 74 | border: none; 75 | padding: 10px 20px; 76 | border-radius: 5px; 77 | cursor: pointer; 78 | } 79 | 80 | .message-input button:hover { 81 | background-color: #5300cc; 82 | } 83 | -------------------------------------------------------------------------------- /Tic Tac Game/script.js: -------------------------------------------------------------------------------- 1 | let board = ['', '', '', '', '', '', '', '', '']; 2 | let currentPlayer = 'X'; 3 | let gameActive = true; 4 | 5 | const winningConditions = [ 6 | [0, 1, 2], 7 | [3, 4, 5], 8 | [6, 7, 8], 9 | [0, 3, 6], 10 | [1, 4, 7], 11 | [2, 5, 8], 12 | [0, 4, 8], 13 | [2, 4, 6], 14 | ]; 15 | 16 | function makeMove(index) { 17 | if (board[index] !== '' || !gameActive) { 18 | return; 19 | } 20 | 21 | board[index] = currentPlayer; 22 | document.getElementById(`cell-${index}`).textContent = currentPlayer; 23 | 24 | checkForWinner(); 25 | } 26 | 27 | function checkForWinner() { 28 | let roundWon = false; 29 | 30 | for (let i = 0; i < winningConditions.length; i++) { 31 | const [a, b, c] = winningConditions[i]; 32 | if (board[a] === '' || board[b] === '' || board[c] === '') { 33 | continue; 34 | } 35 | 36 | if (board[a] === board[b] && board[b] === board[c]) { 37 | roundWon = true; 38 | break; 39 | } 40 | } 41 | 42 | if (roundWon) { 43 | document.getElementById('status').textContent = `Player ${currentPlayer} Wins!`; 44 | gameActive = false; 45 | return; 46 | } 47 | 48 | if (!board.includes('')) { 49 | document.getElementById('status').textContent = 'It\'s a Draw!'; 50 | gameActive = false; 51 | return; 52 | } 53 | 54 | currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; 55 | document.getElementById('status').textContent = `Player ${currentPlayer}'s Turn`; 56 | } 57 | 58 | function resetGame() { 59 | board = ['', '', '', '', '', '', '', '', '']; 60 | currentPlayer = 'X'; 61 | gameActive = true; 62 | document.getElementById('status').textContent = 'Player X\'s Turn'; 63 | document.querySelectorAll('.cell').forEach(cell => cell.textContent = ''); 64 | } 65 | -------------------------------------------------------------------------------- /Portfolio Website/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: 'Arial', sans-serif; 9 | } 10 | 11 | header { 12 | position: fixed; 13 | width: 100%; 14 | background-color: transparent; 15 | top: 0; 16 | padding: 20px; 17 | z-index: 100; 18 | } 19 | 20 | nav { 21 | display: flex; 22 | justify-content: space-between; 23 | align-items: center; 24 | max-width: 1200px; 25 | margin: 0 auto; 26 | } 27 | 28 | nav .logo { 29 | font-size: 24px; 30 | font-weight: bold; 31 | color: white; 32 | } 33 | 34 | nav ul { 35 | list-style: none; 36 | display: flex; 37 | gap: 30px; 38 | } 39 | 40 | nav ul li a { 41 | text-decoration: none; 42 | color: white; 43 | font-weight: bold; 44 | } 45 | 46 | .hero { 47 | height: 100vh; 48 | background: url('https://static.vecteezy.com/system/resources/previews/012/105/210/non_2x/abstract-technology-futuristic-digital-square-pattern-with-lighting-glowing-particles-square-elements-on-dark-purple-background-free-vector.jpg') no-repeat center center/cover; 49 | position: relative; 50 | } 51 | 52 | .hero .overlay { 53 | position: absolute; 54 | top: 0; 55 | left: 0; 56 | width: 100%; 57 | height: 100%; 58 | background: rgba(0, 0, 0, 0.5); 59 | } 60 | 61 | .hero .content { 62 | position: absolute; 63 | top: 50%; 64 | left: 50%; 65 | transform: translate(-50%, -50%); 66 | text-align: center; 67 | color: white; 68 | } 69 | 70 | .hero h1 { 71 | font-size: 48px; 72 | margin-bottom: 20px; 73 | } 74 | 75 | .hero h2 { 76 | font-size: 36px; 77 | margin-bottom: 40px; 78 | } 79 | 80 | .btn { 81 | padding: 10px 20px; 82 | background-color: #ff007a; 83 | color: white; 84 | text-decoration: none; 85 | font-weight: bold; 86 | border-radius: 5px; 87 | } 88 | 89 | .btn:hover { 90 | background-color: #e6006f; 91 | } 92 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ws", 3 | "version": "8.18.0", 4 | "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", 5 | "keywords": [ 6 | "HyBi", 7 | "Push", 8 | "RFC-6455", 9 | "WebSocket", 10 | "WebSockets", 11 | "real-time" 12 | ], 13 | "homepage": "https://github.com/websockets/ws", 14 | "bugs": "https://github.com/websockets/ws/issues", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/websockets/ws.git" 18 | }, 19 | "author": "Einar Otto Stangvik (http://2x.io)", 20 | "license": "MIT", 21 | "main": "index.js", 22 | "exports": { 23 | ".": { 24 | "browser": "./browser.js", 25 | "import": "./wrapper.mjs", 26 | "require": "./index.js" 27 | }, 28 | "./package.json": "./package.json" 29 | }, 30 | "browser": "browser.js", 31 | "engines": { 32 | "node": ">=10.0.0" 33 | }, 34 | "files": [ 35 | "browser.js", 36 | "index.js", 37 | "lib/*.js", 38 | "wrapper.mjs" 39 | ], 40 | "scripts": { 41 | "test": "nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js", 42 | "integration": "mocha --throw-deprecation test/*.integration.js", 43 | "lint": "eslint . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\"" 44 | }, 45 | "peerDependencies": { 46 | "bufferutil": "^4.0.1", 47 | "utf-8-validate": ">=5.0.2" 48 | }, 49 | "peerDependenciesMeta": { 50 | "bufferutil": { 51 | "optional": true 52 | }, 53 | "utf-8-validate": { 54 | "optional": true 55 | } 56 | }, 57 | "devDependencies": { 58 | "benchmark": "^2.1.4", 59 | "bufferutil": "^4.0.1", 60 | "eslint": "^9.0.0", 61 | "eslint-config-prettier": "^9.0.0", 62 | "eslint-plugin-prettier": "^5.0.0", 63 | "globals": "^15.0.0", 64 | "mocha": "^8.4.0", 65 | "nyc": "^15.0.0", 66 | "prettier": "^3.0.0", 67 | "utf-8-validate": "^6.0.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Weather App/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Weather App 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 | 16 |
17 |
18 |
19 | 20 |
21 |
22 |

City

23 |

Temperature: --°C

24 |

--

25 |
26 |
27 | 28 |
29 | 30 |
31 |

Wednesday

32 |
33 | 34 |
35 |

21°C

36 |
37 |
38 |

Thursday

39 |
40 | 41 |
42 |

24°C

43 |
44 |
45 |

Friday

46 |
47 | 48 |
49 |

21°C

50 |
51 |
52 |

Saturday

53 |
54 | 55 |
56 |

24°C

57 |
58 |
59 |
60 |
61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Todo App/styles.css: -------------------------------------------------------------------------------- 1 | /* Basic reset */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | /* Body styling */ 9 | body { 10 | font-family: Arial, sans-serif; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | min-height: 100vh; 15 | background-color: #f9f9f9; 16 | } 17 | 18 | /* Container to hold everything */ 19 | .container { 20 | background-color: #fff; 21 | border-radius: 8px; 22 | padding: 40px; 23 | box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); 24 | width: 600px; /* Increased the width */ 25 | } 26 | 27 | /* Heading styling */ 28 | h1 { 29 | font-size: 2.5rem; /* Increased size */ 30 | margin-bottom: 30px; 31 | font-weight: bold; 32 | color: #000; 33 | } 34 | 35 | /* Input container for task entry */ 36 | .input-container { 37 | display: flex; 38 | justify-content: space-between; 39 | align-items: center; 40 | margin-bottom: 25px; /* Increased space below input section */ 41 | } 42 | 43 | /* Input box styling */ 44 | input[type="text"] { 45 | width: 75%; /* Increased width for input box */ 46 | padding: 15px; /* Added padding */ 47 | font-size: 1.2rem; /* Increased font size */ 48 | border-radius: 6px; 49 | border: 1px solid #ddd; 50 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); 51 | } 52 | 53 | /* Add button styling */ 54 | button { 55 | padding: 15px 10px; /* Increased padding */ 56 | background-color: #6e00ff; 57 | color: white; 58 | border: none; 59 | border-radius: 6px; 60 | font-size: 1.2rem; /* Increased font size */ 61 | cursor: pointer; 62 | margin-left: 10px; /* Added space between the button and the input */ 63 | } 64 | 65 | button:hover { 66 | background-color: #5300cc; 67 | } 68 | 69 | /* Task list styling */ 70 | ul { 71 | list-style: none; 72 | padding: 0; 73 | } 74 | 75 | /* Task item styling */ 76 | li { 77 | display: flex; 78 | justify-content: space-between; 79 | align-items: center; 80 | padding: 15px 0; /* Added padding for breathing room */ 81 | border-bottom: 1px solid #ddd; 82 | font-size: 1.3rem; /* Slightly increased font size */ 83 | } 84 | 85 | /* Task completion button styling */ 86 | li button { 87 | background-color: transparent; 88 | color: #6e00ff; 89 | font-size: 1.5rem; /* Increased size for the circle button */ 90 | border: none; 91 | cursor: pointer; 92 | } 93 | 94 | li button:hover { 95 | color: #5300cc; 96 | } 97 | 98 | /* Task text styling */ 99 | .completed { 100 | text-decoration: line-through; 101 | color: #aaa; 102 | } 103 | -------------------------------------------------------------------------------- /E-Commerce Page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sunglasses Product Page 7 | 8 | 9 | 10 |
11 | 12 | 21 |
22 |
🛒
23 |
👤
24 |
25 |
26 | 27 |
28 | 36 | 37 |
38 |

Sleek Sunglasses

39 |

SUNGLASSES COMPANY

40 |

41 | $49.99 42 | $99.99 43 | 50% OFF 44 |

45 |

46 | These sleek and stylish sunglasses are perfect for any occasion. Made with durable materials and offering UV protection, they are both fashionable and functional. 47 |

48 |
49 | 50 | 51 | 52 |
53 | 54 |
55 |
56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Weather App/styles.css: -------------------------------------------------------------------------------- 1 | /* Basic Reset */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | /* Body Styling */ 9 | body { 10 | font-family: 'Arial', sans-serif; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | min-height: 100vh; 15 | background-size: cover; 16 | background-position: center; 17 | background-repeat: no-repeat; 18 | } 19 | 20 | /* Main Container */ 21 | .app-container { 22 | background-color: rgba(255, 255, 255, 0.8); /* Reduced opacity */ 23 | padding: 20px; 24 | border-radius: 15px; 25 | box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); 26 | text-align: center; 27 | width: 100%; 28 | max-width: 600px; 29 | margin: 20px; 30 | } 31 | 32 | /* Search Bar */ 33 | .search-container { 34 | display: flex; 35 | justify-content: center; 36 | margin-bottom: 20px; 37 | } 38 | 39 | input[type="text"] { 40 | padding: 10px; 41 | font-size: 1.2rem; 42 | border-radius: 20px; 43 | border: 1px solid #ddd; 44 | width: 70%; 45 | max-width: 400px; 46 | margin-bottom: 10px; 47 | } 48 | 49 | button { 50 | padding: 10px 20px; 51 | font-size: 1.2rem; 52 | background-color: #6e00ff; 53 | color: white; 54 | border: none; 55 | border-radius: 20px; 56 | margin-left: 10px; 57 | cursor: pointer; 58 | } 59 | 60 | button:hover { 61 | background-color: #5300cc; 62 | } 63 | 64 | /* Weather Information */ 65 | .weather-container { 66 | background-color: rgba(255, 255, 255, 0.85); /* Reduced opacity for weather box */ 67 | padding: 20px; 68 | border-radius: 20px; 69 | box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); 70 | } 71 | 72 | /* Current Weather Section */ 73 | .current-weather { 74 | display: flex; 75 | justify-content: space-between; 76 | align-items: center; 77 | margin-bottom: 20px; 78 | } 79 | 80 | .current-weather .icon { 81 | font-size: 6rem; 82 | } 83 | 84 | .current-weather .info { 85 | text-align: left; 86 | } 87 | 88 | .info h2 { 89 | font-size: 2.5rem; 90 | font-weight: bold; 91 | margin-bottom: 10px; 92 | } 93 | 94 | .info p { 95 | font-size: 1.2rem; 96 | } 97 | 98 | /* Forecast Section */ 99 | .forecast { 100 | display: flex; 101 | justify-content: space-between; 102 | flex-wrap: wrap; 103 | } 104 | 105 | .day { 106 | background-color: rgba(255, 255, 255, 0.9); /* Reduced opacity for forecast cards */ 107 | border-radius: 15px; 108 | padding: 10px; 109 | width: 23%; 110 | margin-bottom: 10px; 111 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); 112 | text-align: center; 113 | } 114 | 115 | .day p { 116 | font-size: 1.1rem; 117 | } 118 | 119 | .day .icon { 120 | font-size: 2rem; 121 | } 122 | 123 | .day .temp { 124 | font-size: 1.2rem; 125 | font-weight: bold; 126 | } 127 | -------------------------------------------------------------------------------- /E-Commerce Page/styles.css: -------------------------------------------------------------------------------- 1 | /* Reset */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | /* Body Styling */ 9 | body { 10 | font-family: 'Arial', sans-serif; 11 | background-color: #f7f7f7; 12 | } 13 | 14 | /* Header */ 15 | header { 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | padding: 20px; 20 | background-color: white; 21 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 22 | } 23 | 24 | .logo { 25 | font-size: 1.5rem; 26 | font-weight: bold; 27 | } 28 | 29 | nav ul { 30 | display: flex; 31 | list-style: none; 32 | gap: 20px; 33 | } 34 | 35 | nav ul li a { 36 | text-decoration: none; 37 | color: #333; 38 | } 39 | 40 | .user-controls { 41 | display: flex; 42 | gap: 15px; 43 | font-size: 1.5rem; 44 | } 45 | 46 | /* Product Container */ 47 | .product-container { 48 | display: flex; 49 | gap: 50px; 50 | padding: 50px; 51 | max-width: 1200px; 52 | margin: 0 auto; 53 | } 54 | 55 | /* Product Gallery */ 56 | .product-gallery { 57 | flex: 1; 58 | } 59 | 60 | .product-gallery img { 61 | width: 100%; 62 | border-radius: 10px; 63 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); 64 | } 65 | 66 | .thumbnails { 67 | display: flex; 68 | gap: 10px; 69 | margin-top: 10px; 70 | } 71 | 72 | .thumbnails img { 73 | width: 80px; 74 | cursor: pointer; 75 | border-radius: 5px; 76 | border: 2px solid transparent; 77 | } 78 | 79 | .thumbnails img:hover, .thumbnails img:focus { 80 | border-color: #6e00ff; /* Purple border on hover */ 81 | } 82 | 83 | /* Product Details */ 84 | .product-details { 85 | flex: 1; 86 | display: flex; 87 | flex-direction: column; 88 | } 89 | 90 | .product-details h1 { 91 | font-size: 2.5rem; 92 | margin-bottom: 10px; 93 | } 94 | 95 | .company { 96 | font-size: 1.1rem; 97 | text-transform: uppercase; 98 | color: #6e00ff; /* Purple */ 99 | margin-bottom: 20px; 100 | } 101 | 102 | .price { 103 | font-size: 1.5rem; 104 | margin-bottom: 20px; 105 | } 106 | 107 | .current-price { 108 | color: #333; 109 | font-weight: bold; 110 | } 111 | 112 | .old-price { 113 | text-decoration: line-through; 114 | margin-left: 10px; 115 | color: #888; 116 | } 117 | 118 | .discount { 119 | color: #6e00ff; /* Purple */ 120 | margin-left: 10px; 121 | } 122 | 123 | .description { 124 | font-size: 1.1rem; 125 | color: #555; 126 | margin-bottom: 20px; 127 | } 128 | 129 | .quantity-selector { 130 | display: flex; 131 | align-items: center; 132 | margin-bottom: 20px; 133 | } 134 | 135 | .quantity-selector button { 136 | background-color: #6e00ff; 137 | color: white; 138 | border: none; 139 | padding: 10px; 140 | font-size: 1.2rem; 141 | cursor: pointer; 142 | } 143 | 144 | .quantity-selector input { 145 | width: 50px; 146 | text-align: center; 147 | font-size: 1.2rem; 148 | margin: 0 10px; 149 | border: 1px solid #ddd; 150 | border-radius: 5px; 151 | } 152 | 153 | .add-to-cart { 154 | background-color: #6e00ff; /* Purple */ 155 | color: white; 156 | border: none; 157 | padding: 15px; 158 | font-size: 1.2rem; 159 | cursor: pointer; 160 | border-radius: 10px; 161 | } 162 | 163 | .add-to-cart:hover { 164 | background-color: #5300cc; /* Darker purple on hover */ 165 | } 166 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/buffer-util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { EMPTY_BUFFER } = require('./constants'); 4 | 5 | const FastBuffer = Buffer[Symbol.species]; 6 | 7 | /** 8 | * Merges an array of buffers into a new buffer. 9 | * 10 | * @param {Buffer[]} list The array of buffers to concat 11 | * @param {Number} totalLength The total length of buffers in the list 12 | * @return {Buffer} The resulting buffer 13 | * @public 14 | */ 15 | function concat(list, totalLength) { 16 | if (list.length === 0) return EMPTY_BUFFER; 17 | if (list.length === 1) return list[0]; 18 | 19 | const target = Buffer.allocUnsafe(totalLength); 20 | let offset = 0; 21 | 22 | for (let i = 0; i < list.length; i++) { 23 | const buf = list[i]; 24 | target.set(buf, offset); 25 | offset += buf.length; 26 | } 27 | 28 | if (offset < totalLength) { 29 | return new FastBuffer(target.buffer, target.byteOffset, offset); 30 | } 31 | 32 | return target; 33 | } 34 | 35 | /** 36 | * Masks a buffer using the given mask. 37 | * 38 | * @param {Buffer} source The buffer to mask 39 | * @param {Buffer} mask The mask to use 40 | * @param {Buffer} output The buffer where to store the result 41 | * @param {Number} offset The offset at which to start writing 42 | * @param {Number} length The number of bytes to mask. 43 | * @public 44 | */ 45 | function _mask(source, mask, output, offset, length) { 46 | for (let i = 0; i < length; i++) { 47 | output[offset + i] = source[i] ^ mask[i & 3]; 48 | } 49 | } 50 | 51 | /** 52 | * Unmasks a buffer using the given mask. 53 | * 54 | * @param {Buffer} buffer The buffer to unmask 55 | * @param {Buffer} mask The mask to use 56 | * @public 57 | */ 58 | function _unmask(buffer, mask) { 59 | for (let i = 0; i < buffer.length; i++) { 60 | buffer[i] ^= mask[i & 3]; 61 | } 62 | } 63 | 64 | /** 65 | * Converts a buffer to an `ArrayBuffer`. 66 | * 67 | * @param {Buffer} buf The buffer to convert 68 | * @return {ArrayBuffer} Converted buffer 69 | * @public 70 | */ 71 | function toArrayBuffer(buf) { 72 | if (buf.length === buf.buffer.byteLength) { 73 | return buf.buffer; 74 | } 75 | 76 | return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length); 77 | } 78 | 79 | /** 80 | * Converts `data` to a `Buffer`. 81 | * 82 | * @param {*} data The data to convert 83 | * @return {Buffer} The buffer 84 | * @throws {TypeError} 85 | * @public 86 | */ 87 | function toBuffer(data) { 88 | toBuffer.readOnly = true; 89 | 90 | if (Buffer.isBuffer(data)) return data; 91 | 92 | let buf; 93 | 94 | if (data instanceof ArrayBuffer) { 95 | buf = new FastBuffer(data); 96 | } else if (ArrayBuffer.isView(data)) { 97 | buf = new FastBuffer(data.buffer, data.byteOffset, data.byteLength); 98 | } else { 99 | buf = Buffer.from(data); 100 | toBuffer.readOnly = false; 101 | } 102 | 103 | return buf; 104 | } 105 | 106 | module.exports = { 107 | concat, 108 | mask: _mask, 109 | toArrayBuffer, 110 | toBuffer, 111 | unmask: _unmask 112 | }; 113 | 114 | /* istanbul ignore else */ 115 | if (!process.env.WS_NO_BUFFER_UTIL) { 116 | try { 117 | const bufferUtil = require('bufferutil'); 118 | 119 | module.exports.mask = function (source, mask, output, offset, length) { 120 | if (length < 48) _mask(source, mask, output, offset, length); 121 | else bufferUtil.mask(source, mask, output, offset, length); 122 | }; 123 | 124 | module.exports.unmask = function (buffer, mask) { 125 | if (buffer.length < 32) _unmask(buffer, mask); 126 | else bufferUtil.unmask(buffer, mask); 127 | }; 128 | } catch (e) { 129 | // Continue regardless of the error. 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Weather App/script.js: -------------------------------------------------------------------------------- 1 | async function getWeather() { 2 | const city = document.getElementById('city').value; 3 | const apiKey = 'YOUR-API-KEY-HERE'; // Replace with your actual API key 4 | const currentWeatherUrl = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`; 5 | const forecastWeatherUrl = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${apiKey}&units=metric`; 6 | 7 | try { 8 | // Fetch current weather data 9 | const currentResponse = await fetch(currentWeatherUrl); 10 | const currentData = await currentResponse.json(); 11 | 12 | // Update current weather section 13 | document.getElementById('cityName').textContent = currentData.name; 14 | document.getElementById('temperature').textContent = `Temperature: ${currentData.main.temp}°C`; 15 | document.getElementById('description').textContent = currentData.weather[0].description; 16 | 17 | const currentIconCode = currentData.weather[0].icon; 18 | const currentIconUrl = `http://openweathermap.org/img/wn/${currentIconCode}@2x.png`; 19 | document.querySelector('.current-weather .icon').innerHTML = `weather icon`; 20 | 21 | // Dynamically change the background based on the weather condition 22 | const weatherCondition = currentData.weather[0].main.toLowerCase(); 23 | changeBackground(weatherCondition); 24 | 25 | // Fetch 5-day forecast data 26 | const forecastResponse = await fetch(forecastWeatherUrl); 27 | const forecastData = await forecastResponse.json(); 28 | 29 | const forecastDays = document.querySelectorAll('.day'); 30 | for (let i = 0; i < forecastDays.length; i++) { 31 | const forecastIndex = i * 8; // Get data for every 24 hours (3-hour intervals * 8 = 1 day) 32 | const forecastDay = forecastData.list[forecastIndex]; 33 | 34 | // Update forecast icons and temperature 35 | const forecastIconCode = forecastDay.weather[0].icon; 36 | const forecastIconUrl = `http://openweathermap.org/img/wn/${forecastIconCode}@2x.png`; 37 | forecastDays[i].querySelector('.icon').innerHTML = `forecast icon`; 38 | forecastDays[i].querySelector('.temp').textContent = `${Math.round(forecastDay.main.temp)}°C`; 39 | } 40 | } catch (error) { 41 | console.error('Error fetching weather data:', error); 42 | } 43 | } 44 | 45 | // Function to change the background based on weather condition 46 | function changeBackground(condition) { 47 | const body = document.body; 48 | 49 | switch (condition) { 50 | case 'clear': 51 | body.style.backgroundImage = "url('https://images.pexels.com/photos/459451/pexels-photo-459451.jpeg?cs=srgb&dl=pexels-pixabay-459451.jpg&fm=jpg')"; 52 | break; 53 | case 'clouds': 54 | body.style.backgroundImage = "url('https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Cloudy_sky_%2826171935906%29.jpg/1024px-Cloudy_sky_%2826171935906%29.jpg')"; 55 | break; 56 | case 'rain': 57 | body.style.backgroundImage = "url('https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Cloudy_sky_%2826171935906%29.jpg/1024px-Cloudy_sky_%2826171935906%29.jpg')"; 58 | break; 59 | case 'snow': 60 | body.style.backgroundImage = "url('https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Cloudy_sky_%2826171935906%29.jpg/1024px-Cloudy_sky_%2826171935906%29.jpg')"; 61 | break; 62 | default: 63 | body.style.backgroundImage = "url('https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Cloudy_sky_%2826171935906%29.jpg/1024px-Cloudy_sky_%2826171935906%29.jpg')"; 64 | break; 65 | } 66 | 67 | body.style.backgroundSize = 'cover'; 68 | body.style.backgroundPosition = 'center'; 69 | body.style.backgroundRepeat = 'no-repeat'; 70 | } 71 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/validation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isUtf8 } = require('buffer'); 4 | 5 | const { hasBlob } = require('./constants'); 6 | 7 | // 8 | // Allowed token characters: 9 | // 10 | // '!', '#', '$', '%', '&', ''', '*', '+', '-', 11 | // '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~' 12 | // 13 | // tokenChars[32] === 0 // ' ' 14 | // tokenChars[33] === 1 // '!' 15 | // tokenChars[34] === 0 // '"' 16 | // ... 17 | // 18 | // prettier-ignore 19 | const tokenChars = [ 20 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 21 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 22 | 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47 23 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 24 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79 25 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95 26 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111 27 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127 28 | ]; 29 | 30 | /** 31 | * Checks if a status code is allowed in a close frame. 32 | * 33 | * @param {Number} code The status code 34 | * @return {Boolean} `true` if the status code is valid, else `false` 35 | * @public 36 | */ 37 | function isValidStatusCode(code) { 38 | return ( 39 | (code >= 1000 && 40 | code <= 1014 && 41 | code !== 1004 && 42 | code !== 1005 && 43 | code !== 1006) || 44 | (code >= 3000 && code <= 4999) 45 | ); 46 | } 47 | 48 | /** 49 | * Checks if a given buffer contains only correct UTF-8. 50 | * Ported from https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c by 51 | * Markus Kuhn. 52 | * 53 | * @param {Buffer} buf The buffer to check 54 | * @return {Boolean} `true` if `buf` contains only correct UTF-8, else `false` 55 | * @public 56 | */ 57 | function _isValidUTF8(buf) { 58 | const len = buf.length; 59 | let i = 0; 60 | 61 | while (i < len) { 62 | if ((buf[i] & 0x80) === 0) { 63 | // 0xxxxxxx 64 | i++; 65 | } else if ((buf[i] & 0xe0) === 0xc0) { 66 | // 110xxxxx 10xxxxxx 67 | if ( 68 | i + 1 === len || 69 | (buf[i + 1] & 0xc0) !== 0x80 || 70 | (buf[i] & 0xfe) === 0xc0 // Overlong 71 | ) { 72 | return false; 73 | } 74 | 75 | i += 2; 76 | } else if ((buf[i] & 0xf0) === 0xe0) { 77 | // 1110xxxx 10xxxxxx 10xxxxxx 78 | if ( 79 | i + 2 >= len || 80 | (buf[i + 1] & 0xc0) !== 0x80 || 81 | (buf[i + 2] & 0xc0) !== 0x80 || 82 | (buf[i] === 0xe0 && (buf[i + 1] & 0xe0) === 0x80) || // Overlong 83 | (buf[i] === 0xed && (buf[i + 1] & 0xe0) === 0xa0) // Surrogate (U+D800 - U+DFFF) 84 | ) { 85 | return false; 86 | } 87 | 88 | i += 3; 89 | } else if ((buf[i] & 0xf8) === 0xf0) { 90 | // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 91 | if ( 92 | i + 3 >= len || 93 | (buf[i + 1] & 0xc0) !== 0x80 || 94 | (buf[i + 2] & 0xc0) !== 0x80 || 95 | (buf[i + 3] & 0xc0) !== 0x80 || 96 | (buf[i] === 0xf0 && (buf[i + 1] & 0xf0) === 0x80) || // Overlong 97 | (buf[i] === 0xf4 && buf[i + 1] > 0x8f) || 98 | buf[i] > 0xf4 // > U+10FFFF 99 | ) { 100 | return false; 101 | } 102 | 103 | i += 4; 104 | } else { 105 | return false; 106 | } 107 | } 108 | 109 | return true; 110 | } 111 | 112 | /** 113 | * Determines whether a value is a `Blob`. 114 | * 115 | * @param {*} value The value to be tested 116 | * @return {Boolean} `true` if `value` is a `Blob`, else `false` 117 | * @private 118 | */ 119 | function isBlob(value) { 120 | return ( 121 | hasBlob && 122 | typeof value === 'object' && 123 | typeof value.arrayBuffer === 'function' && 124 | typeof value.type === 'string' && 125 | typeof value.stream === 'function' && 126 | (value[Symbol.toStringTag] === 'Blob' || 127 | value[Symbol.toStringTag] === 'File') 128 | ); 129 | } 130 | 131 | module.exports = { 132 | isBlob, 133 | isValidStatusCode, 134 | isValidUTF8: _isValidUTF8, 135 | tokenChars 136 | }; 137 | 138 | if (isUtf8) { 139 | module.exports.isValidUTF8 = function (buf) { 140 | return buf.length < 24 ? _isValidUTF8(buf) : isUtf8(buf); 141 | }; 142 | } /* istanbul ignore else */ else if (!process.env.WS_NO_UTF_8_VALIDATE) { 143 | try { 144 | const isValidUTF8 = require('utf-8-validate'); 145 | 146 | module.exports.isValidUTF8 = function (buf) { 147 | return buf.length < 32 ? _isValidUTF8(buf) : isValidUTF8(buf); 148 | }; 149 | } catch (e) { 150 | // Continue regardless of the error. 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Duplex } = require('stream'); 4 | 5 | /** 6 | * Emits the `'close'` event on a stream. 7 | * 8 | * @param {Duplex} stream The stream. 9 | * @private 10 | */ 11 | function emitClose(stream) { 12 | stream.emit('close'); 13 | } 14 | 15 | /** 16 | * The listener of the `'end'` event. 17 | * 18 | * @private 19 | */ 20 | function duplexOnEnd() { 21 | if (!this.destroyed && this._writableState.finished) { 22 | this.destroy(); 23 | } 24 | } 25 | 26 | /** 27 | * The listener of the `'error'` event. 28 | * 29 | * @param {Error} err The error 30 | * @private 31 | */ 32 | function duplexOnError(err) { 33 | this.removeListener('error', duplexOnError); 34 | this.destroy(); 35 | if (this.listenerCount('error') === 0) { 36 | // Do not suppress the throwing behavior. 37 | this.emit('error', err); 38 | } 39 | } 40 | 41 | /** 42 | * Wraps a `WebSocket` in a duplex stream. 43 | * 44 | * @param {WebSocket} ws The `WebSocket` to wrap 45 | * @param {Object} [options] The options for the `Duplex` constructor 46 | * @return {Duplex} The duplex stream 47 | * @public 48 | */ 49 | function createWebSocketStream(ws, options) { 50 | let terminateOnDestroy = true; 51 | 52 | const duplex = new Duplex({ 53 | ...options, 54 | autoDestroy: false, 55 | emitClose: false, 56 | objectMode: false, 57 | writableObjectMode: false 58 | }); 59 | 60 | ws.on('message', function message(msg, isBinary) { 61 | const data = 62 | !isBinary && duplex._readableState.objectMode ? msg.toString() : msg; 63 | 64 | if (!duplex.push(data)) ws.pause(); 65 | }); 66 | 67 | ws.once('error', function error(err) { 68 | if (duplex.destroyed) return; 69 | 70 | // Prevent `ws.terminate()` from being called by `duplex._destroy()`. 71 | // 72 | // - If the `'error'` event is emitted before the `'open'` event, then 73 | // `ws.terminate()` is a noop as no socket is assigned. 74 | // - Otherwise, the error is re-emitted by the listener of the `'error'` 75 | // event of the `Receiver` object. The listener already closes the 76 | // connection by calling `ws.close()`. This allows a close frame to be 77 | // sent to the other peer. If `ws.terminate()` is called right after this, 78 | // then the close frame might not be sent. 79 | terminateOnDestroy = false; 80 | duplex.destroy(err); 81 | }); 82 | 83 | ws.once('close', function close() { 84 | if (duplex.destroyed) return; 85 | 86 | duplex.push(null); 87 | }); 88 | 89 | duplex._destroy = function (err, callback) { 90 | if (ws.readyState === ws.CLOSED) { 91 | callback(err); 92 | process.nextTick(emitClose, duplex); 93 | return; 94 | } 95 | 96 | let called = false; 97 | 98 | ws.once('error', function error(err) { 99 | called = true; 100 | callback(err); 101 | }); 102 | 103 | ws.once('close', function close() { 104 | if (!called) callback(err); 105 | process.nextTick(emitClose, duplex); 106 | }); 107 | 108 | if (terminateOnDestroy) ws.terminate(); 109 | }; 110 | 111 | duplex._final = function (callback) { 112 | if (ws.readyState === ws.CONNECTING) { 113 | ws.once('open', function open() { 114 | duplex._final(callback); 115 | }); 116 | return; 117 | } 118 | 119 | // If the value of the `_socket` property is `null` it means that `ws` is a 120 | // client websocket and the handshake failed. In fact, when this happens, a 121 | // socket is never assigned to the websocket. Wait for the `'error'` event 122 | // that will be emitted by the websocket. 123 | if (ws._socket === null) return; 124 | 125 | if (ws._socket._writableState.finished) { 126 | callback(); 127 | if (duplex._readableState.endEmitted) duplex.destroy(); 128 | } else { 129 | ws._socket.once('finish', function finish() { 130 | // `duplex` is not destroyed here because the `'end'` event will be 131 | // emitted on `duplex` after this `'finish'` event. The EOF signaling 132 | // `null` chunk is, in fact, pushed when the websocket emits `'close'`. 133 | callback(); 134 | }); 135 | ws.close(); 136 | } 137 | }; 138 | 139 | duplex._read = function () { 140 | if (ws.isPaused) ws.resume(); 141 | }; 142 | 143 | duplex._write = function (chunk, encoding, callback) { 144 | if (ws.readyState === ws.CONNECTING) { 145 | ws.once('open', function open() { 146 | duplex._write(chunk, encoding, callback); 147 | }); 148 | return; 149 | } 150 | 151 | ws.send(chunk, callback); 152 | }; 153 | 154 | duplex.on('end', duplexOnEnd); 155 | duplex.on('error', duplexOnError); 156 | return duplex; 157 | } 158 | 159 | module.exports = createWebSocketStream; 160 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/extension.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { tokenChars } = require('./validation'); 4 | 5 | /** 6 | * Adds an offer to the map of extension offers or a parameter to the map of 7 | * parameters. 8 | * 9 | * @param {Object} dest The map of extension offers or parameters 10 | * @param {String} name The extension or parameter name 11 | * @param {(Object|Boolean|String)} elem The extension parameters or the 12 | * parameter value 13 | * @private 14 | */ 15 | function push(dest, name, elem) { 16 | if (dest[name] === undefined) dest[name] = [elem]; 17 | else dest[name].push(elem); 18 | } 19 | 20 | /** 21 | * Parses the `Sec-WebSocket-Extensions` header into an object. 22 | * 23 | * @param {String} header The field value of the header 24 | * @return {Object} The parsed object 25 | * @public 26 | */ 27 | function parse(header) { 28 | const offers = Object.create(null); 29 | let params = Object.create(null); 30 | let mustUnescape = false; 31 | let isEscaping = false; 32 | let inQuotes = false; 33 | let extensionName; 34 | let paramName; 35 | let start = -1; 36 | let code = -1; 37 | let end = -1; 38 | let i = 0; 39 | 40 | for (; i < header.length; i++) { 41 | code = header.charCodeAt(i); 42 | 43 | if (extensionName === undefined) { 44 | if (end === -1 && tokenChars[code] === 1) { 45 | if (start === -1) start = i; 46 | } else if ( 47 | i !== 0 && 48 | (code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */ 49 | ) { 50 | if (end === -1 && start !== -1) end = i; 51 | } else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) { 52 | if (start === -1) { 53 | throw new SyntaxError(`Unexpected character at index ${i}`); 54 | } 55 | 56 | if (end === -1) end = i; 57 | const name = header.slice(start, end); 58 | if (code === 0x2c) { 59 | push(offers, name, params); 60 | params = Object.create(null); 61 | } else { 62 | extensionName = name; 63 | } 64 | 65 | start = end = -1; 66 | } else { 67 | throw new SyntaxError(`Unexpected character at index ${i}`); 68 | } 69 | } else if (paramName === undefined) { 70 | if (end === -1 && tokenChars[code] === 1) { 71 | if (start === -1) start = i; 72 | } else if (code === 0x20 || code === 0x09) { 73 | if (end === -1 && start !== -1) end = i; 74 | } else if (code === 0x3b || code === 0x2c) { 75 | if (start === -1) { 76 | throw new SyntaxError(`Unexpected character at index ${i}`); 77 | } 78 | 79 | if (end === -1) end = i; 80 | push(params, header.slice(start, end), true); 81 | if (code === 0x2c) { 82 | push(offers, extensionName, params); 83 | params = Object.create(null); 84 | extensionName = undefined; 85 | } 86 | 87 | start = end = -1; 88 | } else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) { 89 | paramName = header.slice(start, i); 90 | start = end = -1; 91 | } else { 92 | throw new SyntaxError(`Unexpected character at index ${i}`); 93 | } 94 | } else { 95 | // 96 | // The value of a quoted-string after unescaping must conform to the 97 | // token ABNF, so only token characters are valid. 98 | // Ref: https://tools.ietf.org/html/rfc6455#section-9.1 99 | // 100 | if (isEscaping) { 101 | if (tokenChars[code] !== 1) { 102 | throw new SyntaxError(`Unexpected character at index ${i}`); 103 | } 104 | if (start === -1) start = i; 105 | else if (!mustUnescape) mustUnescape = true; 106 | isEscaping = false; 107 | } else if (inQuotes) { 108 | if (tokenChars[code] === 1) { 109 | if (start === -1) start = i; 110 | } else if (code === 0x22 /* '"' */ && start !== -1) { 111 | inQuotes = false; 112 | end = i; 113 | } else if (code === 0x5c /* '\' */) { 114 | isEscaping = true; 115 | } else { 116 | throw new SyntaxError(`Unexpected character at index ${i}`); 117 | } 118 | } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) { 119 | inQuotes = true; 120 | } else if (end === -1 && tokenChars[code] === 1) { 121 | if (start === -1) start = i; 122 | } else if (start !== -1 && (code === 0x20 || code === 0x09)) { 123 | if (end === -1) end = i; 124 | } else if (code === 0x3b || code === 0x2c) { 125 | if (start === -1) { 126 | throw new SyntaxError(`Unexpected character at index ${i}`); 127 | } 128 | 129 | if (end === -1) end = i; 130 | let value = header.slice(start, end); 131 | if (mustUnescape) { 132 | value = value.replace(/\\/g, ''); 133 | mustUnescape = false; 134 | } 135 | push(params, paramName, value); 136 | if (code === 0x2c) { 137 | push(offers, extensionName, params); 138 | params = Object.create(null); 139 | extensionName = undefined; 140 | } 141 | 142 | paramName = undefined; 143 | start = end = -1; 144 | } else { 145 | throw new SyntaxError(`Unexpected character at index ${i}`); 146 | } 147 | } 148 | } 149 | 150 | if (start === -1 || inQuotes || code === 0x20 || code === 0x09) { 151 | throw new SyntaxError('Unexpected end of input'); 152 | } 153 | 154 | if (end === -1) end = i; 155 | const token = header.slice(start, end); 156 | if (extensionName === undefined) { 157 | push(offers, token, params); 158 | } else { 159 | if (paramName === undefined) { 160 | push(params, token, true); 161 | } else if (mustUnescape) { 162 | push(params, paramName, token.replace(/\\/g, '')); 163 | } else { 164 | push(params, paramName, token); 165 | } 166 | push(offers, extensionName, params); 167 | } 168 | 169 | return offers; 170 | } 171 | 172 | /** 173 | * Builds the `Sec-WebSocket-Extensions` header field value. 174 | * 175 | * @param {Object} extensions The map of extensions and parameters to format 176 | * @return {String} A string representing the given object 177 | * @public 178 | */ 179 | function format(extensions) { 180 | return Object.keys(extensions) 181 | .map((extension) => { 182 | let configurations = extensions[extension]; 183 | if (!Array.isArray(configurations)) configurations = [configurations]; 184 | return configurations 185 | .map((params) => { 186 | return [extension] 187 | .concat( 188 | Object.keys(params).map((k) => { 189 | let values = params[k]; 190 | if (!Array.isArray(values)) values = [values]; 191 | return values 192 | .map((v) => (v === true ? k : `${k}=${v}`)) 193 | .join('; '); 194 | }) 195 | ) 196 | .join('; '); 197 | }) 198 | .join(', '); 199 | }) 200 | .join(', '); 201 | } 202 | 203 | module.exports = { format, parse }; 204 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/event-target.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { kForOnEventAttribute, kListener } = require('./constants'); 4 | 5 | const kCode = Symbol('kCode'); 6 | const kData = Symbol('kData'); 7 | const kError = Symbol('kError'); 8 | const kMessage = Symbol('kMessage'); 9 | const kReason = Symbol('kReason'); 10 | const kTarget = Symbol('kTarget'); 11 | const kType = Symbol('kType'); 12 | const kWasClean = Symbol('kWasClean'); 13 | 14 | /** 15 | * Class representing an event. 16 | */ 17 | class Event { 18 | /** 19 | * Create a new `Event`. 20 | * 21 | * @param {String} type The name of the event 22 | * @throws {TypeError} If the `type` argument is not specified 23 | */ 24 | constructor(type) { 25 | this[kTarget] = null; 26 | this[kType] = type; 27 | } 28 | 29 | /** 30 | * @type {*} 31 | */ 32 | get target() { 33 | return this[kTarget]; 34 | } 35 | 36 | /** 37 | * @type {String} 38 | */ 39 | get type() { 40 | return this[kType]; 41 | } 42 | } 43 | 44 | Object.defineProperty(Event.prototype, 'target', { enumerable: true }); 45 | Object.defineProperty(Event.prototype, 'type', { enumerable: true }); 46 | 47 | /** 48 | * Class representing a close event. 49 | * 50 | * @extends Event 51 | */ 52 | class CloseEvent extends Event { 53 | /** 54 | * Create a new `CloseEvent`. 55 | * 56 | * @param {String} type The name of the event 57 | * @param {Object} [options] A dictionary object that allows for setting 58 | * attributes via object members of the same name 59 | * @param {Number} [options.code=0] The status code explaining why the 60 | * connection was closed 61 | * @param {String} [options.reason=''] A human-readable string explaining why 62 | * the connection was closed 63 | * @param {Boolean} [options.wasClean=false] Indicates whether or not the 64 | * connection was cleanly closed 65 | */ 66 | constructor(type, options = {}) { 67 | super(type); 68 | 69 | this[kCode] = options.code === undefined ? 0 : options.code; 70 | this[kReason] = options.reason === undefined ? '' : options.reason; 71 | this[kWasClean] = options.wasClean === undefined ? false : options.wasClean; 72 | } 73 | 74 | /** 75 | * @type {Number} 76 | */ 77 | get code() { 78 | return this[kCode]; 79 | } 80 | 81 | /** 82 | * @type {String} 83 | */ 84 | get reason() { 85 | return this[kReason]; 86 | } 87 | 88 | /** 89 | * @type {Boolean} 90 | */ 91 | get wasClean() { 92 | return this[kWasClean]; 93 | } 94 | } 95 | 96 | Object.defineProperty(CloseEvent.prototype, 'code', { enumerable: true }); 97 | Object.defineProperty(CloseEvent.prototype, 'reason', { enumerable: true }); 98 | Object.defineProperty(CloseEvent.prototype, 'wasClean', { enumerable: true }); 99 | 100 | /** 101 | * Class representing an error event. 102 | * 103 | * @extends Event 104 | */ 105 | class ErrorEvent extends Event { 106 | /** 107 | * Create a new `ErrorEvent`. 108 | * 109 | * @param {String} type The name of the event 110 | * @param {Object} [options] A dictionary object that allows for setting 111 | * attributes via object members of the same name 112 | * @param {*} [options.error=null] The error that generated this event 113 | * @param {String} [options.message=''] The error message 114 | */ 115 | constructor(type, options = {}) { 116 | super(type); 117 | 118 | this[kError] = options.error === undefined ? null : options.error; 119 | this[kMessage] = options.message === undefined ? '' : options.message; 120 | } 121 | 122 | /** 123 | * @type {*} 124 | */ 125 | get error() { 126 | return this[kError]; 127 | } 128 | 129 | /** 130 | * @type {String} 131 | */ 132 | get message() { 133 | return this[kMessage]; 134 | } 135 | } 136 | 137 | Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true }); 138 | Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true }); 139 | 140 | /** 141 | * Class representing a message event. 142 | * 143 | * @extends Event 144 | */ 145 | class MessageEvent extends Event { 146 | /** 147 | * Create a new `MessageEvent`. 148 | * 149 | * @param {String} type The name of the event 150 | * @param {Object} [options] A dictionary object that allows for setting 151 | * attributes via object members of the same name 152 | * @param {*} [options.data=null] The message content 153 | */ 154 | constructor(type, options = {}) { 155 | super(type); 156 | 157 | this[kData] = options.data === undefined ? null : options.data; 158 | } 159 | 160 | /** 161 | * @type {*} 162 | */ 163 | get data() { 164 | return this[kData]; 165 | } 166 | } 167 | 168 | Object.defineProperty(MessageEvent.prototype, 'data', { enumerable: true }); 169 | 170 | /** 171 | * This provides methods for emulating the `EventTarget` interface. It's not 172 | * meant to be used directly. 173 | * 174 | * @mixin 175 | */ 176 | const EventTarget = { 177 | /** 178 | * Register an event listener. 179 | * 180 | * @param {String} type A string representing the event type to listen for 181 | * @param {(Function|Object)} handler The listener to add 182 | * @param {Object} [options] An options object specifies characteristics about 183 | * the event listener 184 | * @param {Boolean} [options.once=false] A `Boolean` indicating that the 185 | * listener should be invoked at most once after being added. If `true`, 186 | * the listener would be automatically removed when invoked. 187 | * @public 188 | */ 189 | addEventListener(type, handler, options = {}) { 190 | for (const listener of this.listeners(type)) { 191 | if ( 192 | !options[kForOnEventAttribute] && 193 | listener[kListener] === handler && 194 | !listener[kForOnEventAttribute] 195 | ) { 196 | return; 197 | } 198 | } 199 | 200 | let wrapper; 201 | 202 | if (type === 'message') { 203 | wrapper = function onMessage(data, isBinary) { 204 | const event = new MessageEvent('message', { 205 | data: isBinary ? data : data.toString() 206 | }); 207 | 208 | event[kTarget] = this; 209 | callListener(handler, this, event); 210 | }; 211 | } else if (type === 'close') { 212 | wrapper = function onClose(code, message) { 213 | const event = new CloseEvent('close', { 214 | code, 215 | reason: message.toString(), 216 | wasClean: this._closeFrameReceived && this._closeFrameSent 217 | }); 218 | 219 | event[kTarget] = this; 220 | callListener(handler, this, event); 221 | }; 222 | } else if (type === 'error') { 223 | wrapper = function onError(error) { 224 | const event = new ErrorEvent('error', { 225 | error, 226 | message: error.message 227 | }); 228 | 229 | event[kTarget] = this; 230 | callListener(handler, this, event); 231 | }; 232 | } else if (type === 'open') { 233 | wrapper = function onOpen() { 234 | const event = new Event('open'); 235 | 236 | event[kTarget] = this; 237 | callListener(handler, this, event); 238 | }; 239 | } else { 240 | return; 241 | } 242 | 243 | wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute]; 244 | wrapper[kListener] = handler; 245 | 246 | if (options.once) { 247 | this.once(type, wrapper); 248 | } else { 249 | this.on(type, wrapper); 250 | } 251 | }, 252 | 253 | /** 254 | * Remove an event listener. 255 | * 256 | * @param {String} type A string representing the event type to remove 257 | * @param {(Function|Object)} handler The listener to remove 258 | * @public 259 | */ 260 | removeEventListener(type, handler) { 261 | for (const listener of this.listeners(type)) { 262 | if (listener[kListener] === handler && !listener[kForOnEventAttribute]) { 263 | this.removeListener(type, listener); 264 | break; 265 | } 266 | } 267 | } 268 | }; 269 | 270 | module.exports = { 271 | CloseEvent, 272 | ErrorEvent, 273 | Event, 274 | EventTarget, 275 | MessageEvent 276 | }; 277 | 278 | /** 279 | * Call an event listener 280 | * 281 | * @param {(Function|Object)} listener The listener to call 282 | * @param {*} thisArg The value to use as `this`` when calling the listener 283 | * @param {Event} event The event to pass to the listener 284 | * @private 285 | */ 286 | function callListener(listener, thisArg, event) { 287 | if (typeof listener === 'object' && listener.handleEvent) { 288 | listener.handleEvent.call(listener, event); 289 | } else { 290 | listener.call(thisArg, event); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/permessage-deflate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const zlib = require('zlib'); 4 | 5 | const bufferUtil = require('./buffer-util'); 6 | const Limiter = require('./limiter'); 7 | const { kStatusCode } = require('./constants'); 8 | 9 | const FastBuffer = Buffer[Symbol.species]; 10 | const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); 11 | const kPerMessageDeflate = Symbol('permessage-deflate'); 12 | const kTotalLength = Symbol('total-length'); 13 | const kCallback = Symbol('callback'); 14 | const kBuffers = Symbol('buffers'); 15 | const kError = Symbol('error'); 16 | 17 | // 18 | // We limit zlib concurrency, which prevents severe memory fragmentation 19 | // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 20 | // and https://github.com/websockets/ws/issues/1202 21 | // 22 | // Intentionally global; it's the global thread pool that's an issue. 23 | // 24 | let zlibLimiter; 25 | 26 | /** 27 | * permessage-deflate implementation. 28 | */ 29 | class PerMessageDeflate { 30 | /** 31 | * Creates a PerMessageDeflate instance. 32 | * 33 | * @param {Object} [options] Configuration options 34 | * @param {(Boolean|Number)} [options.clientMaxWindowBits] Advertise support 35 | * for, or request, a custom client window size 36 | * @param {Boolean} [options.clientNoContextTakeover=false] Advertise/ 37 | * acknowledge disabling of client context takeover 38 | * @param {Number} [options.concurrencyLimit=10] The number of concurrent 39 | * calls to zlib 40 | * @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the 41 | * use of a custom server window size 42 | * @param {Boolean} [options.serverNoContextTakeover=false] Request/accept 43 | * disabling of server context takeover 44 | * @param {Number} [options.threshold=1024] Size (in bytes) below which 45 | * messages should not be compressed if context takeover is disabled 46 | * @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on 47 | * deflate 48 | * @param {Object} [options.zlibInflateOptions] Options to pass to zlib on 49 | * inflate 50 | * @param {Boolean} [isServer=false] Create the instance in either server or 51 | * client mode 52 | * @param {Number} [maxPayload=0] The maximum allowed message length 53 | */ 54 | constructor(options, isServer, maxPayload) { 55 | this._maxPayload = maxPayload | 0; 56 | this._options = options || {}; 57 | this._threshold = 58 | this._options.threshold !== undefined ? this._options.threshold : 1024; 59 | this._isServer = !!isServer; 60 | this._deflate = null; 61 | this._inflate = null; 62 | 63 | this.params = null; 64 | 65 | if (!zlibLimiter) { 66 | const concurrency = 67 | this._options.concurrencyLimit !== undefined 68 | ? this._options.concurrencyLimit 69 | : 10; 70 | zlibLimiter = new Limiter(concurrency); 71 | } 72 | } 73 | 74 | /** 75 | * @type {String} 76 | */ 77 | static get extensionName() { 78 | return 'permessage-deflate'; 79 | } 80 | 81 | /** 82 | * Create an extension negotiation offer. 83 | * 84 | * @return {Object} Extension parameters 85 | * @public 86 | */ 87 | offer() { 88 | const params = {}; 89 | 90 | if (this._options.serverNoContextTakeover) { 91 | params.server_no_context_takeover = true; 92 | } 93 | if (this._options.clientNoContextTakeover) { 94 | params.client_no_context_takeover = true; 95 | } 96 | if (this._options.serverMaxWindowBits) { 97 | params.server_max_window_bits = this._options.serverMaxWindowBits; 98 | } 99 | if (this._options.clientMaxWindowBits) { 100 | params.client_max_window_bits = this._options.clientMaxWindowBits; 101 | } else if (this._options.clientMaxWindowBits == null) { 102 | params.client_max_window_bits = true; 103 | } 104 | 105 | return params; 106 | } 107 | 108 | /** 109 | * Accept an extension negotiation offer/response. 110 | * 111 | * @param {Array} configurations The extension negotiation offers/reponse 112 | * @return {Object} Accepted configuration 113 | * @public 114 | */ 115 | accept(configurations) { 116 | configurations = this.normalizeParams(configurations); 117 | 118 | this.params = this._isServer 119 | ? this.acceptAsServer(configurations) 120 | : this.acceptAsClient(configurations); 121 | 122 | return this.params; 123 | } 124 | 125 | /** 126 | * Releases all resources used by the extension. 127 | * 128 | * @public 129 | */ 130 | cleanup() { 131 | if (this._inflate) { 132 | this._inflate.close(); 133 | this._inflate = null; 134 | } 135 | 136 | if (this._deflate) { 137 | const callback = this._deflate[kCallback]; 138 | 139 | this._deflate.close(); 140 | this._deflate = null; 141 | 142 | if (callback) { 143 | callback( 144 | new Error( 145 | 'The deflate stream was closed while data was being processed' 146 | ) 147 | ); 148 | } 149 | } 150 | } 151 | 152 | /** 153 | * Accept an extension negotiation offer. 154 | * 155 | * @param {Array} offers The extension negotiation offers 156 | * @return {Object} Accepted configuration 157 | * @private 158 | */ 159 | acceptAsServer(offers) { 160 | const opts = this._options; 161 | const accepted = offers.find((params) => { 162 | if ( 163 | (opts.serverNoContextTakeover === false && 164 | params.server_no_context_takeover) || 165 | (params.server_max_window_bits && 166 | (opts.serverMaxWindowBits === false || 167 | (typeof opts.serverMaxWindowBits === 'number' && 168 | opts.serverMaxWindowBits > params.server_max_window_bits))) || 169 | (typeof opts.clientMaxWindowBits === 'number' && 170 | !params.client_max_window_bits) 171 | ) { 172 | return false; 173 | } 174 | 175 | return true; 176 | }); 177 | 178 | if (!accepted) { 179 | throw new Error('None of the extension offers can be accepted'); 180 | } 181 | 182 | if (opts.serverNoContextTakeover) { 183 | accepted.server_no_context_takeover = true; 184 | } 185 | if (opts.clientNoContextTakeover) { 186 | accepted.client_no_context_takeover = true; 187 | } 188 | if (typeof opts.serverMaxWindowBits === 'number') { 189 | accepted.server_max_window_bits = opts.serverMaxWindowBits; 190 | } 191 | if (typeof opts.clientMaxWindowBits === 'number') { 192 | accepted.client_max_window_bits = opts.clientMaxWindowBits; 193 | } else if ( 194 | accepted.client_max_window_bits === true || 195 | opts.clientMaxWindowBits === false 196 | ) { 197 | delete accepted.client_max_window_bits; 198 | } 199 | 200 | return accepted; 201 | } 202 | 203 | /** 204 | * Accept the extension negotiation response. 205 | * 206 | * @param {Array} response The extension negotiation response 207 | * @return {Object} Accepted configuration 208 | * @private 209 | */ 210 | acceptAsClient(response) { 211 | const params = response[0]; 212 | 213 | if ( 214 | this._options.clientNoContextTakeover === false && 215 | params.client_no_context_takeover 216 | ) { 217 | throw new Error('Unexpected parameter "client_no_context_takeover"'); 218 | } 219 | 220 | if (!params.client_max_window_bits) { 221 | if (typeof this._options.clientMaxWindowBits === 'number') { 222 | params.client_max_window_bits = this._options.clientMaxWindowBits; 223 | } 224 | } else if ( 225 | this._options.clientMaxWindowBits === false || 226 | (typeof this._options.clientMaxWindowBits === 'number' && 227 | params.client_max_window_bits > this._options.clientMaxWindowBits) 228 | ) { 229 | throw new Error( 230 | 'Unexpected or invalid parameter "client_max_window_bits"' 231 | ); 232 | } 233 | 234 | return params; 235 | } 236 | 237 | /** 238 | * Normalize parameters. 239 | * 240 | * @param {Array} configurations The extension negotiation offers/reponse 241 | * @return {Array} The offers/response with normalized parameters 242 | * @private 243 | */ 244 | normalizeParams(configurations) { 245 | configurations.forEach((params) => { 246 | Object.keys(params).forEach((key) => { 247 | let value = params[key]; 248 | 249 | if (value.length > 1) { 250 | throw new Error(`Parameter "${key}" must have only a single value`); 251 | } 252 | 253 | value = value[0]; 254 | 255 | if (key === 'client_max_window_bits') { 256 | if (value !== true) { 257 | const num = +value; 258 | if (!Number.isInteger(num) || num < 8 || num > 15) { 259 | throw new TypeError( 260 | `Invalid value for parameter "${key}": ${value}` 261 | ); 262 | } 263 | value = num; 264 | } else if (!this._isServer) { 265 | throw new TypeError( 266 | `Invalid value for parameter "${key}": ${value}` 267 | ); 268 | } 269 | } else if (key === 'server_max_window_bits') { 270 | const num = +value; 271 | if (!Number.isInteger(num) || num < 8 || num > 15) { 272 | throw new TypeError( 273 | `Invalid value for parameter "${key}": ${value}` 274 | ); 275 | } 276 | value = num; 277 | } else if ( 278 | key === 'client_no_context_takeover' || 279 | key === 'server_no_context_takeover' 280 | ) { 281 | if (value !== true) { 282 | throw new TypeError( 283 | `Invalid value for parameter "${key}": ${value}` 284 | ); 285 | } 286 | } else { 287 | throw new Error(`Unknown parameter "${key}"`); 288 | } 289 | 290 | params[key] = value; 291 | }); 292 | }); 293 | 294 | return configurations; 295 | } 296 | 297 | /** 298 | * Decompress data. Concurrency limited. 299 | * 300 | * @param {Buffer} data Compressed data 301 | * @param {Boolean} fin Specifies whether or not this is the last fragment 302 | * @param {Function} callback Callback 303 | * @public 304 | */ 305 | decompress(data, fin, callback) { 306 | zlibLimiter.add((done) => { 307 | this._decompress(data, fin, (err, result) => { 308 | done(); 309 | callback(err, result); 310 | }); 311 | }); 312 | } 313 | 314 | /** 315 | * Compress data. Concurrency limited. 316 | * 317 | * @param {(Buffer|String)} data Data to compress 318 | * @param {Boolean} fin Specifies whether or not this is the last fragment 319 | * @param {Function} callback Callback 320 | * @public 321 | */ 322 | compress(data, fin, callback) { 323 | zlibLimiter.add((done) => { 324 | this._compress(data, fin, (err, result) => { 325 | done(); 326 | callback(err, result); 327 | }); 328 | }); 329 | } 330 | 331 | /** 332 | * Decompress data. 333 | * 334 | * @param {Buffer} data Compressed data 335 | * @param {Boolean} fin Specifies whether or not this is the last fragment 336 | * @param {Function} callback Callback 337 | * @private 338 | */ 339 | _decompress(data, fin, callback) { 340 | const endpoint = this._isServer ? 'client' : 'server'; 341 | 342 | if (!this._inflate) { 343 | const key = `${endpoint}_max_window_bits`; 344 | const windowBits = 345 | typeof this.params[key] !== 'number' 346 | ? zlib.Z_DEFAULT_WINDOWBITS 347 | : this.params[key]; 348 | 349 | this._inflate = zlib.createInflateRaw({ 350 | ...this._options.zlibInflateOptions, 351 | windowBits 352 | }); 353 | this._inflate[kPerMessageDeflate] = this; 354 | this._inflate[kTotalLength] = 0; 355 | this._inflate[kBuffers] = []; 356 | this._inflate.on('error', inflateOnError); 357 | this._inflate.on('data', inflateOnData); 358 | } 359 | 360 | this._inflate[kCallback] = callback; 361 | 362 | this._inflate.write(data); 363 | if (fin) this._inflate.write(TRAILER); 364 | 365 | this._inflate.flush(() => { 366 | const err = this._inflate[kError]; 367 | 368 | if (err) { 369 | this._inflate.close(); 370 | this._inflate = null; 371 | callback(err); 372 | return; 373 | } 374 | 375 | const data = bufferUtil.concat( 376 | this._inflate[kBuffers], 377 | this._inflate[kTotalLength] 378 | ); 379 | 380 | if (this._inflate._readableState.endEmitted) { 381 | this._inflate.close(); 382 | this._inflate = null; 383 | } else { 384 | this._inflate[kTotalLength] = 0; 385 | this._inflate[kBuffers] = []; 386 | 387 | if (fin && this.params[`${endpoint}_no_context_takeover`]) { 388 | this._inflate.reset(); 389 | } 390 | } 391 | 392 | callback(null, data); 393 | }); 394 | } 395 | 396 | /** 397 | * Compress data. 398 | * 399 | * @param {(Buffer|String)} data Data to compress 400 | * @param {Boolean} fin Specifies whether or not this is the last fragment 401 | * @param {Function} callback Callback 402 | * @private 403 | */ 404 | _compress(data, fin, callback) { 405 | const endpoint = this._isServer ? 'server' : 'client'; 406 | 407 | if (!this._deflate) { 408 | const key = `${endpoint}_max_window_bits`; 409 | const windowBits = 410 | typeof this.params[key] !== 'number' 411 | ? zlib.Z_DEFAULT_WINDOWBITS 412 | : this.params[key]; 413 | 414 | this._deflate = zlib.createDeflateRaw({ 415 | ...this._options.zlibDeflateOptions, 416 | windowBits 417 | }); 418 | 419 | this._deflate[kTotalLength] = 0; 420 | this._deflate[kBuffers] = []; 421 | 422 | this._deflate.on('data', deflateOnData); 423 | } 424 | 425 | this._deflate[kCallback] = callback; 426 | 427 | this._deflate.write(data); 428 | this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { 429 | if (!this._deflate) { 430 | // 431 | // The deflate stream was closed while data was being processed. 432 | // 433 | return; 434 | } 435 | 436 | let data = bufferUtil.concat( 437 | this._deflate[kBuffers], 438 | this._deflate[kTotalLength] 439 | ); 440 | 441 | if (fin) { 442 | data = new FastBuffer(data.buffer, data.byteOffset, data.length - 4); 443 | } 444 | 445 | // 446 | // Ensure that the callback will not be called again in 447 | // `PerMessageDeflate#cleanup()`. 448 | // 449 | this._deflate[kCallback] = null; 450 | 451 | this._deflate[kTotalLength] = 0; 452 | this._deflate[kBuffers] = []; 453 | 454 | if (fin && this.params[`${endpoint}_no_context_takeover`]) { 455 | this._deflate.reset(); 456 | } 457 | 458 | callback(null, data); 459 | }); 460 | } 461 | } 462 | 463 | module.exports = PerMessageDeflate; 464 | 465 | /** 466 | * The listener of the `zlib.DeflateRaw` stream `'data'` event. 467 | * 468 | * @param {Buffer} chunk A chunk of data 469 | * @private 470 | */ 471 | function deflateOnData(chunk) { 472 | this[kBuffers].push(chunk); 473 | this[kTotalLength] += chunk.length; 474 | } 475 | 476 | /** 477 | * The listener of the `zlib.InflateRaw` stream `'data'` event. 478 | * 479 | * @param {Buffer} chunk A chunk of data 480 | * @private 481 | */ 482 | function inflateOnData(chunk) { 483 | this[kTotalLength] += chunk.length; 484 | 485 | if ( 486 | this[kPerMessageDeflate]._maxPayload < 1 || 487 | this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload 488 | ) { 489 | this[kBuffers].push(chunk); 490 | return; 491 | } 492 | 493 | this[kError] = new RangeError('Max payload size exceeded'); 494 | this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'; 495 | this[kError][kStatusCode] = 1009; 496 | this.removeListener('data', inflateOnData); 497 | this.reset(); 498 | } 499 | 500 | /** 501 | * The listener of the `zlib.InflateRaw` stream `'error'` event. 502 | * 503 | * @param {Error} err The emitted error 504 | * @private 505 | */ 506 | function inflateOnError(err) { 507 | // 508 | // There is no need to call `Zlib#close()` as the handle is automatically 509 | // closed when an error is emitted. 510 | // 511 | this[kPerMessageDeflate]._inflate = null; 512 | err[kStatusCode] = 1007; 513 | this[kCallback](err); 514 | } 515 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/README.md: -------------------------------------------------------------------------------- 1 | # ws: a Node.js WebSocket library 2 | 3 | [![Version npm](https://img.shields.io/npm/v/ws.svg?logo=npm)](https://www.npmjs.com/package/ws) 4 | [![CI](https://img.shields.io/github/actions/workflow/status/websockets/ws/ci.yml?branch=master&label=CI&logo=github)](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster) 5 | [![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg?logo=coveralls)](https://coveralls.io/github/websockets/ws) 6 | 7 | ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and 8 | server implementation. 9 | 10 | Passes the quite extensive Autobahn test suite: [server][server-report], 11 | [client][client-report]. 12 | 13 | **Note**: This module does not work in the browser. The client in the docs is a 14 | reference to a backend with the role of a client in the WebSocket communication. 15 | Browser clients must use the native 16 | [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) 17 | object. To make the same code work seamlessly on Node.js and the browser, you 18 | can use one of the many wrappers available on npm, like 19 | [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws). 20 | 21 | ## Table of Contents 22 | 23 | - [Protocol support](#protocol-support) 24 | - [Installing](#installing) 25 | - [Opt-in for performance](#opt-in-for-performance) 26 | - [Legacy opt-in for performance](#legacy-opt-in-for-performance) 27 | - [API docs](#api-docs) 28 | - [WebSocket compression](#websocket-compression) 29 | - [Usage examples](#usage-examples) 30 | - [Sending and receiving text data](#sending-and-receiving-text-data) 31 | - [Sending binary data](#sending-binary-data) 32 | - [Simple server](#simple-server) 33 | - [External HTTP/S server](#external-https-server) 34 | - [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server) 35 | - [Client authentication](#client-authentication) 36 | - [Server broadcast](#server-broadcast) 37 | - [Round-trip time](#round-trip-time) 38 | - [Use the Node.js streams API](#use-the-nodejs-streams-api) 39 | - [Other examples](#other-examples) 40 | - [FAQ](#faq) 41 | - [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client) 42 | - [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections) 43 | - [How to connect via a proxy?](#how-to-connect-via-a-proxy) 44 | - [Changelog](#changelog) 45 | - [License](#license) 46 | 47 | ## Protocol support 48 | 49 | - **HyBi drafts 07-12** (Use the option `protocolVersion: 8`) 50 | - **HyBi drafts 13-17** (Current default, alternatively option 51 | `protocolVersion: 13`) 52 | 53 | ## Installing 54 | 55 | ``` 56 | npm install ws 57 | ``` 58 | 59 | ### Opt-in for performance 60 | 61 | [bufferutil][] is an optional module that can be installed alongside the ws 62 | module: 63 | 64 | ``` 65 | npm install --save-optional bufferutil 66 | ``` 67 | 68 | This is a binary addon that improves the performance of certain operations such 69 | as masking and unmasking the data payload of the WebSocket frames. Prebuilt 70 | binaries are available for the most popular platforms, so you don't necessarily 71 | need to have a C++ compiler installed on your machine. 72 | 73 | To force ws to not use bufferutil, use the 74 | [`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) environment variable. This 75 | can be useful to enhance security in systems where a user can put a package in 76 | the package search path of an application of another user, due to how the 77 | Node.js resolver algorithm works. 78 | 79 | #### Legacy opt-in for performance 80 | 81 | If you are running on an old version of Node.js (prior to v18.14.0), ws also 82 | supports the [utf-8-validate][] module: 83 | 84 | ``` 85 | npm install --save-optional utf-8-validate 86 | ``` 87 | 88 | This contains a binary polyfill for [`buffer.isUtf8()`][]. 89 | 90 | To force ws not to use utf-8-validate, use the 91 | [`WS_NO_UTF_8_VALIDATE`](./doc/ws.md#ws_no_utf_8_validate) environment variable. 92 | 93 | ## API docs 94 | 95 | See [`/doc/ws.md`](./doc/ws.md) for Node.js-like documentation of ws classes and 96 | utility functions. 97 | 98 | ## WebSocket compression 99 | 100 | ws supports the [permessage-deflate extension][permessage-deflate] which enables 101 | the client and server to negotiate a compression algorithm and its parameters, 102 | and then selectively apply it to the data payloads of each WebSocket message. 103 | 104 | The extension is disabled by default on the server and enabled by default on the 105 | client. It adds a significant overhead in terms of performance and memory 106 | consumption so we suggest to enable it only if it is really needed. 107 | 108 | Note that Node.js has a variety of issues with high-performance compression, 109 | where increased concurrency, especially on Linux, can lead to [catastrophic 110 | memory fragmentation][node-zlib-bug] and slow performance. If you intend to use 111 | permessage-deflate in production, it is worthwhile to set up a test 112 | representative of your workload and ensure Node.js/zlib will handle it with 113 | acceptable performance and memory usage. 114 | 115 | Tuning of permessage-deflate can be done via the options defined below. You can 116 | also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly 117 | into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs]. 118 | 119 | See [the docs][ws-server-options] for more options. 120 | 121 | ```js 122 | import WebSocket, { WebSocketServer } from 'ws'; 123 | 124 | const wss = new WebSocketServer({ 125 | port: 8080, 126 | perMessageDeflate: { 127 | zlibDeflateOptions: { 128 | // See zlib defaults. 129 | chunkSize: 1024, 130 | memLevel: 7, 131 | level: 3 132 | }, 133 | zlibInflateOptions: { 134 | chunkSize: 10 * 1024 135 | }, 136 | // Other options settable: 137 | clientNoContextTakeover: true, // Defaults to negotiated value. 138 | serverNoContextTakeover: true, // Defaults to negotiated value. 139 | serverMaxWindowBits: 10, // Defaults to negotiated value. 140 | // Below options specified as default values. 141 | concurrencyLimit: 10, // Limits zlib concurrency for perf. 142 | threshold: 1024 // Size (in bytes) below which messages 143 | // should not be compressed if context takeover is disabled. 144 | } 145 | }); 146 | ``` 147 | 148 | The client will only use the extension if it is supported and enabled on the 149 | server. To always disable the extension on the client, set the 150 | `perMessageDeflate` option to `false`. 151 | 152 | ```js 153 | import WebSocket from 'ws'; 154 | 155 | const ws = new WebSocket('ws://www.host.com/path', { 156 | perMessageDeflate: false 157 | }); 158 | ``` 159 | 160 | ## Usage examples 161 | 162 | ### Sending and receiving text data 163 | 164 | ```js 165 | import WebSocket from 'ws'; 166 | 167 | const ws = new WebSocket('ws://www.host.com/path'); 168 | 169 | ws.on('error', console.error); 170 | 171 | ws.on('open', function open() { 172 | ws.send('something'); 173 | }); 174 | 175 | ws.on('message', function message(data) { 176 | console.log('received: %s', data); 177 | }); 178 | ``` 179 | 180 | ### Sending binary data 181 | 182 | ```js 183 | import WebSocket from 'ws'; 184 | 185 | const ws = new WebSocket('ws://www.host.com/path'); 186 | 187 | ws.on('error', console.error); 188 | 189 | ws.on('open', function open() { 190 | const array = new Float32Array(5); 191 | 192 | for (var i = 0; i < array.length; ++i) { 193 | array[i] = i / 2; 194 | } 195 | 196 | ws.send(array); 197 | }); 198 | ``` 199 | 200 | ### Simple server 201 | 202 | ```js 203 | import { WebSocketServer } from 'ws'; 204 | 205 | const wss = new WebSocketServer({ port: 8080 }); 206 | 207 | wss.on('connection', function connection(ws) { 208 | ws.on('error', console.error); 209 | 210 | ws.on('message', function message(data) { 211 | console.log('received: %s', data); 212 | }); 213 | 214 | ws.send('something'); 215 | }); 216 | ``` 217 | 218 | ### External HTTP/S server 219 | 220 | ```js 221 | import { createServer } from 'https'; 222 | import { readFileSync } from 'fs'; 223 | import { WebSocketServer } from 'ws'; 224 | 225 | const server = createServer({ 226 | cert: readFileSync('/path/to/cert.pem'), 227 | key: readFileSync('/path/to/key.pem') 228 | }); 229 | const wss = new WebSocketServer({ server }); 230 | 231 | wss.on('connection', function connection(ws) { 232 | ws.on('error', console.error); 233 | 234 | ws.on('message', function message(data) { 235 | console.log('received: %s', data); 236 | }); 237 | 238 | ws.send('something'); 239 | }); 240 | 241 | server.listen(8080); 242 | ``` 243 | 244 | ### Multiple servers sharing a single HTTP/S server 245 | 246 | ```js 247 | import { createServer } from 'http'; 248 | import { WebSocketServer } from 'ws'; 249 | 250 | const server = createServer(); 251 | const wss1 = new WebSocketServer({ noServer: true }); 252 | const wss2 = new WebSocketServer({ noServer: true }); 253 | 254 | wss1.on('connection', function connection(ws) { 255 | ws.on('error', console.error); 256 | 257 | // ... 258 | }); 259 | 260 | wss2.on('connection', function connection(ws) { 261 | ws.on('error', console.error); 262 | 263 | // ... 264 | }); 265 | 266 | server.on('upgrade', function upgrade(request, socket, head) { 267 | const { pathname } = new URL(request.url, 'wss://base.url'); 268 | 269 | if (pathname === '/foo') { 270 | wss1.handleUpgrade(request, socket, head, function done(ws) { 271 | wss1.emit('connection', ws, request); 272 | }); 273 | } else if (pathname === '/bar') { 274 | wss2.handleUpgrade(request, socket, head, function done(ws) { 275 | wss2.emit('connection', ws, request); 276 | }); 277 | } else { 278 | socket.destroy(); 279 | } 280 | }); 281 | 282 | server.listen(8080); 283 | ``` 284 | 285 | ### Client authentication 286 | 287 | ```js 288 | import { createServer } from 'http'; 289 | import { WebSocketServer } from 'ws'; 290 | 291 | function onSocketError(err) { 292 | console.error(err); 293 | } 294 | 295 | const server = createServer(); 296 | const wss = new WebSocketServer({ noServer: true }); 297 | 298 | wss.on('connection', function connection(ws, request, client) { 299 | ws.on('error', console.error); 300 | 301 | ws.on('message', function message(data) { 302 | console.log(`Received message ${data} from user ${client}`); 303 | }); 304 | }); 305 | 306 | server.on('upgrade', function upgrade(request, socket, head) { 307 | socket.on('error', onSocketError); 308 | 309 | // This function is not defined on purpose. Implement it with your own logic. 310 | authenticate(request, function next(err, client) { 311 | if (err || !client) { 312 | socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); 313 | socket.destroy(); 314 | return; 315 | } 316 | 317 | socket.removeListener('error', onSocketError); 318 | 319 | wss.handleUpgrade(request, socket, head, function done(ws) { 320 | wss.emit('connection', ws, request, client); 321 | }); 322 | }); 323 | }); 324 | 325 | server.listen(8080); 326 | ``` 327 | 328 | Also see the provided [example][session-parse-example] using `express-session`. 329 | 330 | ### Server broadcast 331 | 332 | A client WebSocket broadcasting to all connected WebSocket clients, including 333 | itself. 334 | 335 | ```js 336 | import WebSocket, { WebSocketServer } from 'ws'; 337 | 338 | const wss = new WebSocketServer({ port: 8080 }); 339 | 340 | wss.on('connection', function connection(ws) { 341 | ws.on('error', console.error); 342 | 343 | ws.on('message', function message(data, isBinary) { 344 | wss.clients.forEach(function each(client) { 345 | if (client.readyState === WebSocket.OPEN) { 346 | client.send(data, { binary: isBinary }); 347 | } 348 | }); 349 | }); 350 | }); 351 | ``` 352 | 353 | A client WebSocket broadcasting to every other connected WebSocket clients, 354 | excluding itself. 355 | 356 | ```js 357 | import WebSocket, { WebSocketServer } from 'ws'; 358 | 359 | const wss = new WebSocketServer({ port: 8080 }); 360 | 361 | wss.on('connection', function connection(ws) { 362 | ws.on('error', console.error); 363 | 364 | ws.on('message', function message(data, isBinary) { 365 | wss.clients.forEach(function each(client) { 366 | if (client !== ws && client.readyState === WebSocket.OPEN) { 367 | client.send(data, { binary: isBinary }); 368 | } 369 | }); 370 | }); 371 | }); 372 | ``` 373 | 374 | ### Round-trip time 375 | 376 | ```js 377 | import WebSocket from 'ws'; 378 | 379 | const ws = new WebSocket('wss://websocket-echo.com/'); 380 | 381 | ws.on('error', console.error); 382 | 383 | ws.on('open', function open() { 384 | console.log('connected'); 385 | ws.send(Date.now()); 386 | }); 387 | 388 | ws.on('close', function close() { 389 | console.log('disconnected'); 390 | }); 391 | 392 | ws.on('message', function message(data) { 393 | console.log(`Round-trip time: ${Date.now() - data} ms`); 394 | 395 | setTimeout(function timeout() { 396 | ws.send(Date.now()); 397 | }, 500); 398 | }); 399 | ``` 400 | 401 | ### Use the Node.js streams API 402 | 403 | ```js 404 | import WebSocket, { createWebSocketStream } from 'ws'; 405 | 406 | const ws = new WebSocket('wss://websocket-echo.com/'); 407 | 408 | const duplex = createWebSocketStream(ws, { encoding: 'utf8' }); 409 | 410 | duplex.on('error', console.error); 411 | 412 | duplex.pipe(process.stdout); 413 | process.stdin.pipe(duplex); 414 | ``` 415 | 416 | ### Other examples 417 | 418 | For a full example with a browser client communicating with a ws server, see the 419 | examples folder. 420 | 421 | Otherwise, see the test cases. 422 | 423 | ## FAQ 424 | 425 | ### How to get the IP address of the client? 426 | 427 | The remote IP address can be obtained from the raw socket. 428 | 429 | ```js 430 | import { WebSocketServer } from 'ws'; 431 | 432 | const wss = new WebSocketServer({ port: 8080 }); 433 | 434 | wss.on('connection', function connection(ws, req) { 435 | const ip = req.socket.remoteAddress; 436 | 437 | ws.on('error', console.error); 438 | }); 439 | ``` 440 | 441 | When the server runs behind a proxy like NGINX, the de-facto standard is to use 442 | the `X-Forwarded-For` header. 443 | 444 | ```js 445 | wss.on('connection', function connection(ws, req) { 446 | const ip = req.headers['x-forwarded-for'].split(',')[0].trim(); 447 | 448 | ws.on('error', console.error); 449 | }); 450 | ``` 451 | 452 | ### How to detect and close broken connections? 453 | 454 | Sometimes, the link between the server and the client can be interrupted in a 455 | way that keeps both the server and the client unaware of the broken state of the 456 | connection (e.g. when pulling the cord). 457 | 458 | In these cases, ping messages can be used as a means to verify that the remote 459 | endpoint is still responsive. 460 | 461 | ```js 462 | import { WebSocketServer } from 'ws'; 463 | 464 | function heartbeat() { 465 | this.isAlive = true; 466 | } 467 | 468 | const wss = new WebSocketServer({ port: 8080 }); 469 | 470 | wss.on('connection', function connection(ws) { 471 | ws.isAlive = true; 472 | ws.on('error', console.error); 473 | ws.on('pong', heartbeat); 474 | }); 475 | 476 | const interval = setInterval(function ping() { 477 | wss.clients.forEach(function each(ws) { 478 | if (ws.isAlive === false) return ws.terminate(); 479 | 480 | ws.isAlive = false; 481 | ws.ping(); 482 | }); 483 | }, 30000); 484 | 485 | wss.on('close', function close() { 486 | clearInterval(interval); 487 | }); 488 | ``` 489 | 490 | Pong messages are automatically sent in response to ping messages as required by 491 | the spec. 492 | 493 | Just like the server example above, your clients might as well lose connection 494 | without knowing it. You might want to add a ping listener on your clients to 495 | prevent that. A simple implementation would be: 496 | 497 | ```js 498 | import WebSocket from 'ws'; 499 | 500 | function heartbeat() { 501 | clearTimeout(this.pingTimeout); 502 | 503 | // Use `WebSocket#terminate()`, which immediately destroys the connection, 504 | // instead of `WebSocket#close()`, which waits for the close timer. 505 | // Delay should be equal to the interval at which your server 506 | // sends out pings plus a conservative assumption of the latency. 507 | this.pingTimeout = setTimeout(() => { 508 | this.terminate(); 509 | }, 30000 + 1000); 510 | } 511 | 512 | const client = new WebSocket('wss://websocket-echo.com/'); 513 | 514 | client.on('error', console.error); 515 | client.on('open', heartbeat); 516 | client.on('ping', heartbeat); 517 | client.on('close', function clear() { 518 | clearTimeout(this.pingTimeout); 519 | }); 520 | ``` 521 | 522 | ### How to connect via a proxy? 523 | 524 | Use a custom `http.Agent` implementation like [https-proxy-agent][] or 525 | [socks-proxy-agent][]. 526 | 527 | ## Changelog 528 | 529 | We're using the GitHub [releases][changelog] for changelog entries. 530 | 531 | ## License 532 | 533 | [MIT](LICENSE) 534 | 535 | [`buffer.isutf8()`]: https://nodejs.org/api/buffer.html#bufferisutf8input 536 | [bufferutil]: https://github.com/websockets/bufferutil 537 | [changelog]: https://github.com/websockets/ws/releases 538 | [client-report]: http://websockets.github.io/ws/autobahn/clients/ 539 | [https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent 540 | [node-zlib-bug]: https://github.com/nodejs/node/issues/8871 541 | [node-zlib-deflaterawdocs]: 542 | https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options 543 | [permessage-deflate]: https://tools.ietf.org/html/rfc7692 544 | [server-report]: http://websockets.github.io/ws/autobahn/servers/ 545 | [session-parse-example]: ./examples/express-session-parse 546 | [socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent 547 | [utf-8-validate]: https://github.com/websockets/utf-8-validate 548 | [ws-server-options]: ./doc/ws.md#new-websocketserveroptions-callback 549 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/websocket-server.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$", "caughtErrors": "none" }] */ 2 | 3 | 'use strict'; 4 | 5 | const EventEmitter = require('events'); 6 | const http = require('http'); 7 | const { Duplex } = require('stream'); 8 | const { createHash } = require('crypto'); 9 | 10 | const extension = require('./extension'); 11 | const PerMessageDeflate = require('./permessage-deflate'); 12 | const subprotocol = require('./subprotocol'); 13 | const WebSocket = require('./websocket'); 14 | const { GUID, kWebSocket } = require('./constants'); 15 | 16 | const keyRegex = /^[+/0-9A-Za-z]{22}==$/; 17 | 18 | const RUNNING = 0; 19 | const CLOSING = 1; 20 | const CLOSED = 2; 21 | 22 | /** 23 | * Class representing a WebSocket server. 24 | * 25 | * @extends EventEmitter 26 | */ 27 | class WebSocketServer extends EventEmitter { 28 | /** 29 | * Create a `WebSocketServer` instance. 30 | * 31 | * @param {Object} options Configuration options 32 | * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether 33 | * any of the `'message'`, `'ping'`, and `'pong'` events can be emitted 34 | * multiple times in the same tick 35 | * @param {Boolean} [options.autoPong=true] Specifies whether or not to 36 | * automatically send a pong in response to a ping 37 | * @param {Number} [options.backlog=511] The maximum length of the queue of 38 | * pending connections 39 | * @param {Boolean} [options.clientTracking=true] Specifies whether or not to 40 | * track clients 41 | * @param {Function} [options.handleProtocols] A hook to handle protocols 42 | * @param {String} [options.host] The hostname where to bind the server 43 | * @param {Number} [options.maxPayload=104857600] The maximum allowed message 44 | * size 45 | * @param {Boolean} [options.noServer=false] Enable no server mode 46 | * @param {String} [options.path] Accept only connections matching this path 47 | * @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable 48 | * permessage-deflate 49 | * @param {Number} [options.port] The port where to bind the server 50 | * @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S 51 | * server to use 52 | * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or 53 | * not to skip UTF-8 validation for text and close messages 54 | * @param {Function} [options.verifyClient] A hook to reject connections 55 | * @param {Function} [options.WebSocket=WebSocket] Specifies the `WebSocket` 56 | * class to use. It must be the `WebSocket` class or class that extends it 57 | * @param {Function} [callback] A listener for the `listening` event 58 | */ 59 | constructor(options, callback) { 60 | super(); 61 | 62 | options = { 63 | allowSynchronousEvents: true, 64 | autoPong: true, 65 | maxPayload: 100 * 1024 * 1024, 66 | skipUTF8Validation: false, 67 | perMessageDeflate: false, 68 | handleProtocols: null, 69 | clientTracking: true, 70 | verifyClient: null, 71 | noServer: false, 72 | backlog: null, // use default (511 as implemented in net.js) 73 | server: null, 74 | host: null, 75 | path: null, 76 | port: null, 77 | WebSocket, 78 | ...options 79 | }; 80 | 81 | if ( 82 | (options.port == null && !options.server && !options.noServer) || 83 | (options.port != null && (options.server || options.noServer)) || 84 | (options.server && options.noServer) 85 | ) { 86 | throw new TypeError( 87 | 'One and only one of the "port", "server", or "noServer" options ' + 88 | 'must be specified' 89 | ); 90 | } 91 | 92 | if (options.port != null) { 93 | this._server = http.createServer((req, res) => { 94 | const body = http.STATUS_CODES[426]; 95 | 96 | res.writeHead(426, { 97 | 'Content-Length': body.length, 98 | 'Content-Type': 'text/plain' 99 | }); 100 | res.end(body); 101 | }); 102 | this._server.listen( 103 | options.port, 104 | options.host, 105 | options.backlog, 106 | callback 107 | ); 108 | } else if (options.server) { 109 | this._server = options.server; 110 | } 111 | 112 | if (this._server) { 113 | const emitConnection = this.emit.bind(this, 'connection'); 114 | 115 | this._removeListeners = addListeners(this._server, { 116 | listening: this.emit.bind(this, 'listening'), 117 | error: this.emit.bind(this, 'error'), 118 | upgrade: (req, socket, head) => { 119 | this.handleUpgrade(req, socket, head, emitConnection); 120 | } 121 | }); 122 | } 123 | 124 | if (options.perMessageDeflate === true) options.perMessageDeflate = {}; 125 | if (options.clientTracking) { 126 | this.clients = new Set(); 127 | this._shouldEmitClose = false; 128 | } 129 | 130 | this.options = options; 131 | this._state = RUNNING; 132 | } 133 | 134 | /** 135 | * Returns the bound address, the address family name, and port of the server 136 | * as reported by the operating system if listening on an IP socket. 137 | * If the server is listening on a pipe or UNIX domain socket, the name is 138 | * returned as a string. 139 | * 140 | * @return {(Object|String|null)} The address of the server 141 | * @public 142 | */ 143 | address() { 144 | if (this.options.noServer) { 145 | throw new Error('The server is operating in "noServer" mode'); 146 | } 147 | 148 | if (!this._server) return null; 149 | return this._server.address(); 150 | } 151 | 152 | /** 153 | * Stop the server from accepting new connections and emit the `'close'` event 154 | * when all existing connections are closed. 155 | * 156 | * @param {Function} [cb] A one-time listener for the `'close'` event 157 | * @public 158 | */ 159 | close(cb) { 160 | if (this._state === CLOSED) { 161 | if (cb) { 162 | this.once('close', () => { 163 | cb(new Error('The server is not running')); 164 | }); 165 | } 166 | 167 | process.nextTick(emitClose, this); 168 | return; 169 | } 170 | 171 | if (cb) this.once('close', cb); 172 | 173 | if (this._state === CLOSING) return; 174 | this._state = CLOSING; 175 | 176 | if (this.options.noServer || this.options.server) { 177 | if (this._server) { 178 | this._removeListeners(); 179 | this._removeListeners = this._server = null; 180 | } 181 | 182 | if (this.clients) { 183 | if (!this.clients.size) { 184 | process.nextTick(emitClose, this); 185 | } else { 186 | this._shouldEmitClose = true; 187 | } 188 | } else { 189 | process.nextTick(emitClose, this); 190 | } 191 | } else { 192 | const server = this._server; 193 | 194 | this._removeListeners(); 195 | this._removeListeners = this._server = null; 196 | 197 | // 198 | // The HTTP/S server was created internally. Close it, and rely on its 199 | // `'close'` event. 200 | // 201 | server.close(() => { 202 | emitClose(this); 203 | }); 204 | } 205 | } 206 | 207 | /** 208 | * See if a given request should be handled by this server instance. 209 | * 210 | * @param {http.IncomingMessage} req Request object to inspect 211 | * @return {Boolean} `true` if the request is valid, else `false` 212 | * @public 213 | */ 214 | shouldHandle(req) { 215 | if (this.options.path) { 216 | const index = req.url.indexOf('?'); 217 | const pathname = index !== -1 ? req.url.slice(0, index) : req.url; 218 | 219 | if (pathname !== this.options.path) return false; 220 | } 221 | 222 | return true; 223 | } 224 | 225 | /** 226 | * Handle a HTTP Upgrade request. 227 | * 228 | * @param {http.IncomingMessage} req The request object 229 | * @param {Duplex} socket The network socket between the server and client 230 | * @param {Buffer} head The first packet of the upgraded stream 231 | * @param {Function} cb Callback 232 | * @public 233 | */ 234 | handleUpgrade(req, socket, head, cb) { 235 | socket.on('error', socketOnError); 236 | 237 | const key = req.headers['sec-websocket-key']; 238 | const upgrade = req.headers.upgrade; 239 | const version = +req.headers['sec-websocket-version']; 240 | 241 | if (req.method !== 'GET') { 242 | const message = 'Invalid HTTP method'; 243 | abortHandshakeOrEmitwsClientError(this, req, socket, 405, message); 244 | return; 245 | } 246 | 247 | if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') { 248 | const message = 'Invalid Upgrade header'; 249 | abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); 250 | return; 251 | } 252 | 253 | if (key === undefined || !keyRegex.test(key)) { 254 | const message = 'Missing or invalid Sec-WebSocket-Key header'; 255 | abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); 256 | return; 257 | } 258 | 259 | if (version !== 8 && version !== 13) { 260 | const message = 'Missing or invalid Sec-WebSocket-Version header'; 261 | abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); 262 | return; 263 | } 264 | 265 | if (!this.shouldHandle(req)) { 266 | abortHandshake(socket, 400); 267 | return; 268 | } 269 | 270 | const secWebSocketProtocol = req.headers['sec-websocket-protocol']; 271 | let protocols = new Set(); 272 | 273 | if (secWebSocketProtocol !== undefined) { 274 | try { 275 | protocols = subprotocol.parse(secWebSocketProtocol); 276 | } catch (err) { 277 | const message = 'Invalid Sec-WebSocket-Protocol header'; 278 | abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); 279 | return; 280 | } 281 | } 282 | 283 | const secWebSocketExtensions = req.headers['sec-websocket-extensions']; 284 | const extensions = {}; 285 | 286 | if ( 287 | this.options.perMessageDeflate && 288 | secWebSocketExtensions !== undefined 289 | ) { 290 | const perMessageDeflate = new PerMessageDeflate( 291 | this.options.perMessageDeflate, 292 | true, 293 | this.options.maxPayload 294 | ); 295 | 296 | try { 297 | const offers = extension.parse(secWebSocketExtensions); 298 | 299 | if (offers[PerMessageDeflate.extensionName]) { 300 | perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]); 301 | extensions[PerMessageDeflate.extensionName] = perMessageDeflate; 302 | } 303 | } catch (err) { 304 | const message = 305 | 'Invalid or unacceptable Sec-WebSocket-Extensions header'; 306 | abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); 307 | return; 308 | } 309 | } 310 | 311 | // 312 | // Optionally call external client verification handler. 313 | // 314 | if (this.options.verifyClient) { 315 | const info = { 316 | origin: 317 | req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], 318 | secure: !!(req.socket.authorized || req.socket.encrypted), 319 | req 320 | }; 321 | 322 | if (this.options.verifyClient.length === 2) { 323 | this.options.verifyClient(info, (verified, code, message, headers) => { 324 | if (!verified) { 325 | return abortHandshake(socket, code || 401, message, headers); 326 | } 327 | 328 | this.completeUpgrade( 329 | extensions, 330 | key, 331 | protocols, 332 | req, 333 | socket, 334 | head, 335 | cb 336 | ); 337 | }); 338 | return; 339 | } 340 | 341 | if (!this.options.verifyClient(info)) return abortHandshake(socket, 401); 342 | } 343 | 344 | this.completeUpgrade(extensions, key, protocols, req, socket, head, cb); 345 | } 346 | 347 | /** 348 | * Upgrade the connection to WebSocket. 349 | * 350 | * @param {Object} extensions The accepted extensions 351 | * @param {String} key The value of the `Sec-WebSocket-Key` header 352 | * @param {Set} protocols The subprotocols 353 | * @param {http.IncomingMessage} req The request object 354 | * @param {Duplex} socket The network socket between the server and client 355 | * @param {Buffer} head The first packet of the upgraded stream 356 | * @param {Function} cb Callback 357 | * @throws {Error} If called more than once with the same socket 358 | * @private 359 | */ 360 | completeUpgrade(extensions, key, protocols, req, socket, head, cb) { 361 | // 362 | // Destroy the socket if the client has already sent a FIN packet. 363 | // 364 | if (!socket.readable || !socket.writable) return socket.destroy(); 365 | 366 | if (socket[kWebSocket]) { 367 | throw new Error( 368 | 'server.handleUpgrade() was called more than once with the same ' + 369 | 'socket, possibly due to a misconfiguration' 370 | ); 371 | } 372 | 373 | if (this._state > RUNNING) return abortHandshake(socket, 503); 374 | 375 | const digest = createHash('sha1') 376 | .update(key + GUID) 377 | .digest('base64'); 378 | 379 | const headers = [ 380 | 'HTTP/1.1 101 Switching Protocols', 381 | 'Upgrade: websocket', 382 | 'Connection: Upgrade', 383 | `Sec-WebSocket-Accept: ${digest}` 384 | ]; 385 | 386 | const ws = new this.options.WebSocket(null, undefined, this.options); 387 | 388 | if (protocols.size) { 389 | // 390 | // Optionally call external protocol selection handler. 391 | // 392 | const protocol = this.options.handleProtocols 393 | ? this.options.handleProtocols(protocols, req) 394 | : protocols.values().next().value; 395 | 396 | if (protocol) { 397 | headers.push(`Sec-WebSocket-Protocol: ${protocol}`); 398 | ws._protocol = protocol; 399 | } 400 | } 401 | 402 | if (extensions[PerMessageDeflate.extensionName]) { 403 | const params = extensions[PerMessageDeflate.extensionName].params; 404 | const value = extension.format({ 405 | [PerMessageDeflate.extensionName]: [params] 406 | }); 407 | headers.push(`Sec-WebSocket-Extensions: ${value}`); 408 | ws._extensions = extensions; 409 | } 410 | 411 | // 412 | // Allow external modification/inspection of handshake headers. 413 | // 414 | this.emit('headers', headers, req); 415 | 416 | socket.write(headers.concat('\r\n').join('\r\n')); 417 | socket.removeListener('error', socketOnError); 418 | 419 | ws.setSocket(socket, head, { 420 | allowSynchronousEvents: this.options.allowSynchronousEvents, 421 | maxPayload: this.options.maxPayload, 422 | skipUTF8Validation: this.options.skipUTF8Validation 423 | }); 424 | 425 | if (this.clients) { 426 | this.clients.add(ws); 427 | ws.on('close', () => { 428 | this.clients.delete(ws); 429 | 430 | if (this._shouldEmitClose && !this.clients.size) { 431 | process.nextTick(emitClose, this); 432 | } 433 | }); 434 | } 435 | 436 | cb(ws, req); 437 | } 438 | } 439 | 440 | module.exports = WebSocketServer; 441 | 442 | /** 443 | * Add event listeners on an `EventEmitter` using a map of 444 | * pairs. 445 | * 446 | * @param {EventEmitter} server The event emitter 447 | * @param {Object.} map The listeners to add 448 | * @return {Function} A function that will remove the added listeners when 449 | * called 450 | * @private 451 | */ 452 | function addListeners(server, map) { 453 | for (const event of Object.keys(map)) server.on(event, map[event]); 454 | 455 | return function removeListeners() { 456 | for (const event of Object.keys(map)) { 457 | server.removeListener(event, map[event]); 458 | } 459 | }; 460 | } 461 | 462 | /** 463 | * Emit a `'close'` event on an `EventEmitter`. 464 | * 465 | * @param {EventEmitter} server The event emitter 466 | * @private 467 | */ 468 | function emitClose(server) { 469 | server._state = CLOSED; 470 | server.emit('close'); 471 | } 472 | 473 | /** 474 | * Handle socket errors. 475 | * 476 | * @private 477 | */ 478 | function socketOnError() { 479 | this.destroy(); 480 | } 481 | 482 | /** 483 | * Close the connection when preconditions are not fulfilled. 484 | * 485 | * @param {Duplex} socket The socket of the upgrade request 486 | * @param {Number} code The HTTP response status code 487 | * @param {String} [message] The HTTP response body 488 | * @param {Object} [headers] Additional HTTP response headers 489 | * @private 490 | */ 491 | function abortHandshake(socket, code, message, headers) { 492 | // 493 | // The socket is writable unless the user destroyed or ended it before calling 494 | // `server.handleUpgrade()` or in the `verifyClient` function, which is a user 495 | // error. Handling this does not make much sense as the worst that can happen 496 | // is that some of the data written by the user might be discarded due to the 497 | // call to `socket.end()` below, which triggers an `'error'` event that in 498 | // turn causes the socket to be destroyed. 499 | // 500 | message = message || http.STATUS_CODES[code]; 501 | headers = { 502 | Connection: 'close', 503 | 'Content-Type': 'text/html', 504 | 'Content-Length': Buffer.byteLength(message), 505 | ...headers 506 | }; 507 | 508 | socket.once('finish', socket.destroy); 509 | 510 | socket.end( 511 | `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` + 512 | Object.keys(headers) 513 | .map((h) => `${h}: ${headers[h]}`) 514 | .join('\r\n') + 515 | '\r\n\r\n' + 516 | message 517 | ); 518 | } 519 | 520 | /** 521 | * Emit a `'wsClientError'` event on a `WebSocketServer` if there is at least 522 | * one listener for it, otherwise call `abortHandshake()`. 523 | * 524 | * @param {WebSocketServer} server The WebSocket server 525 | * @param {http.IncomingMessage} req The request object 526 | * @param {Duplex} socket The socket of the upgrade request 527 | * @param {Number} code The HTTP response status code 528 | * @param {String} message The HTTP response body 529 | * @private 530 | */ 531 | function abortHandshakeOrEmitwsClientError(server, req, socket, code, message) { 532 | if (server.listenerCount('wsClientError')) { 533 | const err = new Error(message); 534 | Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError); 535 | 536 | server.emit('wsClientError', err, socket, req); 537 | } else { 538 | abortHandshake(socket, code, message); 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/sender.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex" }] */ 2 | 3 | 'use strict'; 4 | 5 | const { Duplex } = require('stream'); 6 | const { randomFillSync } = require('crypto'); 7 | 8 | const PerMessageDeflate = require('./permessage-deflate'); 9 | const { EMPTY_BUFFER, kWebSocket, NOOP } = require('./constants'); 10 | const { isBlob, isValidStatusCode } = require('./validation'); 11 | const { mask: applyMask, toBuffer } = require('./buffer-util'); 12 | 13 | const kByteLength = Symbol('kByteLength'); 14 | const maskBuffer = Buffer.alloc(4); 15 | const RANDOM_POOL_SIZE = 8 * 1024; 16 | let randomPool; 17 | let randomPoolPointer = RANDOM_POOL_SIZE; 18 | 19 | const DEFAULT = 0; 20 | const DEFLATING = 1; 21 | const GET_BLOB_DATA = 2; 22 | 23 | /** 24 | * HyBi Sender implementation. 25 | */ 26 | class Sender { 27 | /** 28 | * Creates a Sender instance. 29 | * 30 | * @param {Duplex} socket The connection socket 31 | * @param {Object} [extensions] An object containing the negotiated extensions 32 | * @param {Function} [generateMask] The function used to generate the masking 33 | * key 34 | */ 35 | constructor(socket, extensions, generateMask) { 36 | this._extensions = extensions || {}; 37 | 38 | if (generateMask) { 39 | this._generateMask = generateMask; 40 | this._maskBuffer = Buffer.alloc(4); 41 | } 42 | 43 | this._socket = socket; 44 | 45 | this._firstFragment = true; 46 | this._compress = false; 47 | 48 | this._bufferedBytes = 0; 49 | this._queue = []; 50 | this._state = DEFAULT; 51 | this.onerror = NOOP; 52 | this[kWebSocket] = undefined; 53 | } 54 | 55 | /** 56 | * Frames a piece of data according to the HyBi WebSocket protocol. 57 | * 58 | * @param {(Buffer|String)} data The data to frame 59 | * @param {Object} options Options object 60 | * @param {Boolean} [options.fin=false] Specifies whether or not to set the 61 | * FIN bit 62 | * @param {Function} [options.generateMask] The function used to generate the 63 | * masking key 64 | * @param {Boolean} [options.mask=false] Specifies whether or not to mask 65 | * `data` 66 | * @param {Buffer} [options.maskBuffer] The buffer used to store the masking 67 | * key 68 | * @param {Number} options.opcode The opcode 69 | * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be 70 | * modified 71 | * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the 72 | * RSV1 bit 73 | * @return {(Buffer|String)[]} The framed data 74 | * @public 75 | */ 76 | static frame(data, options) { 77 | let mask; 78 | let merge = false; 79 | let offset = 2; 80 | let skipMasking = false; 81 | 82 | if (options.mask) { 83 | mask = options.maskBuffer || maskBuffer; 84 | 85 | if (options.generateMask) { 86 | options.generateMask(mask); 87 | } else { 88 | if (randomPoolPointer === RANDOM_POOL_SIZE) { 89 | /* istanbul ignore else */ 90 | if (randomPool === undefined) { 91 | // 92 | // This is lazily initialized because server-sent frames must not 93 | // be masked so it may never be used. 94 | // 95 | randomPool = Buffer.alloc(RANDOM_POOL_SIZE); 96 | } 97 | 98 | randomFillSync(randomPool, 0, RANDOM_POOL_SIZE); 99 | randomPoolPointer = 0; 100 | } 101 | 102 | mask[0] = randomPool[randomPoolPointer++]; 103 | mask[1] = randomPool[randomPoolPointer++]; 104 | mask[2] = randomPool[randomPoolPointer++]; 105 | mask[3] = randomPool[randomPoolPointer++]; 106 | } 107 | 108 | skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0; 109 | offset = 6; 110 | } 111 | 112 | let dataLength; 113 | 114 | if (typeof data === 'string') { 115 | if ( 116 | (!options.mask || skipMasking) && 117 | options[kByteLength] !== undefined 118 | ) { 119 | dataLength = options[kByteLength]; 120 | } else { 121 | data = Buffer.from(data); 122 | dataLength = data.length; 123 | } 124 | } else { 125 | dataLength = data.length; 126 | merge = options.mask && options.readOnly && !skipMasking; 127 | } 128 | 129 | let payloadLength = dataLength; 130 | 131 | if (dataLength >= 65536) { 132 | offset += 8; 133 | payloadLength = 127; 134 | } else if (dataLength > 125) { 135 | offset += 2; 136 | payloadLength = 126; 137 | } 138 | 139 | const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset); 140 | 141 | target[0] = options.fin ? options.opcode | 0x80 : options.opcode; 142 | if (options.rsv1) target[0] |= 0x40; 143 | 144 | target[1] = payloadLength; 145 | 146 | if (payloadLength === 126) { 147 | target.writeUInt16BE(dataLength, 2); 148 | } else if (payloadLength === 127) { 149 | target[2] = target[3] = 0; 150 | target.writeUIntBE(dataLength, 4, 6); 151 | } 152 | 153 | if (!options.mask) return [target, data]; 154 | 155 | target[1] |= 0x80; 156 | target[offset - 4] = mask[0]; 157 | target[offset - 3] = mask[1]; 158 | target[offset - 2] = mask[2]; 159 | target[offset - 1] = mask[3]; 160 | 161 | if (skipMasking) return [target, data]; 162 | 163 | if (merge) { 164 | applyMask(data, mask, target, offset, dataLength); 165 | return [target]; 166 | } 167 | 168 | applyMask(data, mask, data, 0, dataLength); 169 | return [target, data]; 170 | } 171 | 172 | /** 173 | * Sends a close message to the other peer. 174 | * 175 | * @param {Number} [code] The status code component of the body 176 | * @param {(String|Buffer)} [data] The message component of the body 177 | * @param {Boolean} [mask=false] Specifies whether or not to mask the message 178 | * @param {Function} [cb] Callback 179 | * @public 180 | */ 181 | close(code, data, mask, cb) { 182 | let buf; 183 | 184 | if (code === undefined) { 185 | buf = EMPTY_BUFFER; 186 | } else if (typeof code !== 'number' || !isValidStatusCode(code)) { 187 | throw new TypeError('First argument must be a valid error code number'); 188 | } else if (data === undefined || !data.length) { 189 | buf = Buffer.allocUnsafe(2); 190 | buf.writeUInt16BE(code, 0); 191 | } else { 192 | const length = Buffer.byteLength(data); 193 | 194 | if (length > 123) { 195 | throw new RangeError('The message must not be greater than 123 bytes'); 196 | } 197 | 198 | buf = Buffer.allocUnsafe(2 + length); 199 | buf.writeUInt16BE(code, 0); 200 | 201 | if (typeof data === 'string') { 202 | buf.write(data, 2); 203 | } else { 204 | buf.set(data, 2); 205 | } 206 | } 207 | 208 | const options = { 209 | [kByteLength]: buf.length, 210 | fin: true, 211 | generateMask: this._generateMask, 212 | mask, 213 | maskBuffer: this._maskBuffer, 214 | opcode: 0x08, 215 | readOnly: false, 216 | rsv1: false 217 | }; 218 | 219 | if (this._state !== DEFAULT) { 220 | this.enqueue([this.dispatch, buf, false, options, cb]); 221 | } else { 222 | this.sendFrame(Sender.frame(buf, options), cb); 223 | } 224 | } 225 | 226 | /** 227 | * Sends a ping message to the other peer. 228 | * 229 | * @param {*} data The message to send 230 | * @param {Boolean} [mask=false] Specifies whether or not to mask `data` 231 | * @param {Function} [cb] Callback 232 | * @public 233 | */ 234 | ping(data, mask, cb) { 235 | let byteLength; 236 | let readOnly; 237 | 238 | if (typeof data === 'string') { 239 | byteLength = Buffer.byteLength(data); 240 | readOnly = false; 241 | } else if (isBlob(data)) { 242 | byteLength = data.size; 243 | readOnly = false; 244 | } else { 245 | data = toBuffer(data); 246 | byteLength = data.length; 247 | readOnly = toBuffer.readOnly; 248 | } 249 | 250 | if (byteLength > 125) { 251 | throw new RangeError('The data size must not be greater than 125 bytes'); 252 | } 253 | 254 | const options = { 255 | [kByteLength]: byteLength, 256 | fin: true, 257 | generateMask: this._generateMask, 258 | mask, 259 | maskBuffer: this._maskBuffer, 260 | opcode: 0x09, 261 | readOnly, 262 | rsv1: false 263 | }; 264 | 265 | if (isBlob(data)) { 266 | if (this._state !== DEFAULT) { 267 | this.enqueue([this.getBlobData, data, false, options, cb]); 268 | } else { 269 | this.getBlobData(data, false, options, cb); 270 | } 271 | } else if (this._state !== DEFAULT) { 272 | this.enqueue([this.dispatch, data, false, options, cb]); 273 | } else { 274 | this.sendFrame(Sender.frame(data, options), cb); 275 | } 276 | } 277 | 278 | /** 279 | * Sends a pong message to the other peer. 280 | * 281 | * @param {*} data The message to send 282 | * @param {Boolean} [mask=false] Specifies whether or not to mask `data` 283 | * @param {Function} [cb] Callback 284 | * @public 285 | */ 286 | pong(data, mask, cb) { 287 | let byteLength; 288 | let readOnly; 289 | 290 | if (typeof data === 'string') { 291 | byteLength = Buffer.byteLength(data); 292 | readOnly = false; 293 | } else if (isBlob(data)) { 294 | byteLength = data.size; 295 | readOnly = false; 296 | } else { 297 | data = toBuffer(data); 298 | byteLength = data.length; 299 | readOnly = toBuffer.readOnly; 300 | } 301 | 302 | if (byteLength > 125) { 303 | throw new RangeError('The data size must not be greater than 125 bytes'); 304 | } 305 | 306 | const options = { 307 | [kByteLength]: byteLength, 308 | fin: true, 309 | generateMask: this._generateMask, 310 | mask, 311 | maskBuffer: this._maskBuffer, 312 | opcode: 0x0a, 313 | readOnly, 314 | rsv1: false 315 | }; 316 | 317 | if (isBlob(data)) { 318 | if (this._state !== DEFAULT) { 319 | this.enqueue([this.getBlobData, data, false, options, cb]); 320 | } else { 321 | this.getBlobData(data, false, options, cb); 322 | } 323 | } else if (this._state !== DEFAULT) { 324 | this.enqueue([this.dispatch, data, false, options, cb]); 325 | } else { 326 | this.sendFrame(Sender.frame(data, options), cb); 327 | } 328 | } 329 | 330 | /** 331 | * Sends a data message to the other peer. 332 | * 333 | * @param {*} data The message to send 334 | * @param {Object} options Options object 335 | * @param {Boolean} [options.binary=false] Specifies whether `data` is binary 336 | * or text 337 | * @param {Boolean} [options.compress=false] Specifies whether or not to 338 | * compress `data` 339 | * @param {Boolean} [options.fin=false] Specifies whether the fragment is the 340 | * last one 341 | * @param {Boolean} [options.mask=false] Specifies whether or not to mask 342 | * `data` 343 | * @param {Function} [cb] Callback 344 | * @public 345 | */ 346 | send(data, options, cb) { 347 | const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; 348 | let opcode = options.binary ? 2 : 1; 349 | let rsv1 = options.compress; 350 | 351 | let byteLength; 352 | let readOnly; 353 | 354 | if (typeof data === 'string') { 355 | byteLength = Buffer.byteLength(data); 356 | readOnly = false; 357 | } else if (isBlob(data)) { 358 | byteLength = data.size; 359 | readOnly = false; 360 | } else { 361 | data = toBuffer(data); 362 | byteLength = data.length; 363 | readOnly = toBuffer.readOnly; 364 | } 365 | 366 | if (this._firstFragment) { 367 | this._firstFragment = false; 368 | if ( 369 | rsv1 && 370 | perMessageDeflate && 371 | perMessageDeflate.params[ 372 | perMessageDeflate._isServer 373 | ? 'server_no_context_takeover' 374 | : 'client_no_context_takeover' 375 | ] 376 | ) { 377 | rsv1 = byteLength >= perMessageDeflate._threshold; 378 | } 379 | this._compress = rsv1; 380 | } else { 381 | rsv1 = false; 382 | opcode = 0; 383 | } 384 | 385 | if (options.fin) this._firstFragment = true; 386 | 387 | const opts = { 388 | [kByteLength]: byteLength, 389 | fin: options.fin, 390 | generateMask: this._generateMask, 391 | mask: options.mask, 392 | maskBuffer: this._maskBuffer, 393 | opcode, 394 | readOnly, 395 | rsv1 396 | }; 397 | 398 | if (isBlob(data)) { 399 | if (this._state !== DEFAULT) { 400 | this.enqueue([this.getBlobData, data, this._compress, opts, cb]); 401 | } else { 402 | this.getBlobData(data, this._compress, opts, cb); 403 | } 404 | } else if (this._state !== DEFAULT) { 405 | this.enqueue([this.dispatch, data, this._compress, opts, cb]); 406 | } else { 407 | this.dispatch(data, this._compress, opts, cb); 408 | } 409 | } 410 | 411 | /** 412 | * Gets the contents of a blob as binary data. 413 | * 414 | * @param {Blob} blob The blob 415 | * @param {Boolean} [compress=false] Specifies whether or not to compress 416 | * the data 417 | * @param {Object} options Options object 418 | * @param {Boolean} [options.fin=false] Specifies whether or not to set the 419 | * FIN bit 420 | * @param {Function} [options.generateMask] The function used to generate the 421 | * masking key 422 | * @param {Boolean} [options.mask=false] Specifies whether or not to mask 423 | * `data` 424 | * @param {Buffer} [options.maskBuffer] The buffer used to store the masking 425 | * key 426 | * @param {Number} options.opcode The opcode 427 | * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be 428 | * modified 429 | * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the 430 | * RSV1 bit 431 | * @param {Function} [cb] Callback 432 | * @private 433 | */ 434 | getBlobData(blob, compress, options, cb) { 435 | this._bufferedBytes += options[kByteLength]; 436 | this._state = GET_BLOB_DATA; 437 | 438 | blob 439 | .arrayBuffer() 440 | .then((arrayBuffer) => { 441 | if (this._socket.destroyed) { 442 | const err = new Error( 443 | 'The socket was closed while the blob was being read' 444 | ); 445 | 446 | // 447 | // `callCallbacks` is called in the next tick to ensure that errors 448 | // that might be thrown in the callbacks behave like errors thrown 449 | // outside the promise chain. 450 | // 451 | process.nextTick(callCallbacks, this, err, cb); 452 | return; 453 | } 454 | 455 | this._bufferedBytes -= options[kByteLength]; 456 | const data = toBuffer(arrayBuffer); 457 | 458 | if (!compress) { 459 | this._state = DEFAULT; 460 | this.sendFrame(Sender.frame(data, options), cb); 461 | this.dequeue(); 462 | } else { 463 | this.dispatch(data, compress, options, cb); 464 | } 465 | }) 466 | .catch((err) => { 467 | // 468 | // `onError` is called in the next tick for the same reason that 469 | // `callCallbacks` above is. 470 | // 471 | process.nextTick(onError, this, err, cb); 472 | }); 473 | } 474 | 475 | /** 476 | * Dispatches a message. 477 | * 478 | * @param {(Buffer|String)} data The message to send 479 | * @param {Boolean} [compress=false] Specifies whether or not to compress 480 | * `data` 481 | * @param {Object} options Options object 482 | * @param {Boolean} [options.fin=false] Specifies whether or not to set the 483 | * FIN bit 484 | * @param {Function} [options.generateMask] The function used to generate the 485 | * masking key 486 | * @param {Boolean} [options.mask=false] Specifies whether or not to mask 487 | * `data` 488 | * @param {Buffer} [options.maskBuffer] The buffer used to store the masking 489 | * key 490 | * @param {Number} options.opcode The opcode 491 | * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be 492 | * modified 493 | * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the 494 | * RSV1 bit 495 | * @param {Function} [cb] Callback 496 | * @private 497 | */ 498 | dispatch(data, compress, options, cb) { 499 | if (!compress) { 500 | this.sendFrame(Sender.frame(data, options), cb); 501 | return; 502 | } 503 | 504 | const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; 505 | 506 | this._bufferedBytes += options[kByteLength]; 507 | this._state = DEFLATING; 508 | perMessageDeflate.compress(data, options.fin, (_, buf) => { 509 | if (this._socket.destroyed) { 510 | const err = new Error( 511 | 'The socket was closed while data was being compressed' 512 | ); 513 | 514 | callCallbacks(this, err, cb); 515 | return; 516 | } 517 | 518 | this._bufferedBytes -= options[kByteLength]; 519 | this._state = DEFAULT; 520 | options.readOnly = false; 521 | this.sendFrame(Sender.frame(buf, options), cb); 522 | this.dequeue(); 523 | }); 524 | } 525 | 526 | /** 527 | * Executes queued send operations. 528 | * 529 | * @private 530 | */ 531 | dequeue() { 532 | while (this._state === DEFAULT && this._queue.length) { 533 | const params = this._queue.shift(); 534 | 535 | this._bufferedBytes -= params[3][kByteLength]; 536 | Reflect.apply(params[0], this, params.slice(1)); 537 | } 538 | } 539 | 540 | /** 541 | * Enqueues a send operation. 542 | * 543 | * @param {Array} params Send operation parameters. 544 | * @private 545 | */ 546 | enqueue(params) { 547 | this._bufferedBytes += params[3][kByteLength]; 548 | this._queue.push(params); 549 | } 550 | 551 | /** 552 | * Sends a frame. 553 | * 554 | * @param {Buffer[]} list The frame to send 555 | * @param {Function} [cb] Callback 556 | * @private 557 | */ 558 | sendFrame(list, cb) { 559 | if (list.length === 2) { 560 | this._socket.cork(); 561 | this._socket.write(list[0]); 562 | this._socket.write(list[1], cb); 563 | this._socket.uncork(); 564 | } else { 565 | this._socket.write(list[0], cb); 566 | } 567 | } 568 | } 569 | 570 | module.exports = Sender; 571 | 572 | /** 573 | * Calls queued callbacks with an error. 574 | * 575 | * @param {Sender} sender The `Sender` instance 576 | * @param {Error} err The error to call the callbacks with 577 | * @param {Function} [cb] The first callback 578 | * @private 579 | */ 580 | function callCallbacks(sender, err, cb) { 581 | if (typeof cb === 'function') cb(err); 582 | 583 | for (let i = 0; i < sender._queue.length; i++) { 584 | const params = sender._queue[i]; 585 | const callback = params[params.length - 1]; 586 | 587 | if (typeof callback === 'function') callback(err); 588 | } 589 | } 590 | 591 | /** 592 | * Handles a `Sender` error. 593 | * 594 | * @param {Sender} sender The `Sender` instance 595 | * @param {Error} err The error 596 | * @param {Function} [cb] The first pending callback 597 | * @private 598 | */ 599 | function onError(sender, err, cb) { 600 | callCallbacks(sender, err, cb); 601 | sender.onerror(err); 602 | } 603 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/receiver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Writable } = require('stream'); 4 | 5 | const PerMessageDeflate = require('./permessage-deflate'); 6 | const { 7 | BINARY_TYPES, 8 | EMPTY_BUFFER, 9 | kStatusCode, 10 | kWebSocket 11 | } = require('./constants'); 12 | const { concat, toArrayBuffer, unmask } = require('./buffer-util'); 13 | const { isValidStatusCode, isValidUTF8 } = require('./validation'); 14 | 15 | const FastBuffer = Buffer[Symbol.species]; 16 | 17 | const GET_INFO = 0; 18 | const GET_PAYLOAD_LENGTH_16 = 1; 19 | const GET_PAYLOAD_LENGTH_64 = 2; 20 | const GET_MASK = 3; 21 | const GET_DATA = 4; 22 | const INFLATING = 5; 23 | const DEFER_EVENT = 6; 24 | 25 | /** 26 | * HyBi Receiver implementation. 27 | * 28 | * @extends Writable 29 | */ 30 | class Receiver extends Writable { 31 | /** 32 | * Creates a Receiver instance. 33 | * 34 | * @param {Object} [options] Options object 35 | * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether 36 | * any of the `'message'`, `'ping'`, and `'pong'` events can be emitted 37 | * multiple times in the same tick 38 | * @param {String} [options.binaryType=nodebuffer] The type for binary data 39 | * @param {Object} [options.extensions] An object containing the negotiated 40 | * extensions 41 | * @param {Boolean} [options.isServer=false] Specifies whether to operate in 42 | * client or server mode 43 | * @param {Number} [options.maxPayload=0] The maximum allowed message length 44 | * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or 45 | * not to skip UTF-8 validation for text and close messages 46 | */ 47 | constructor(options = {}) { 48 | super(); 49 | 50 | this._allowSynchronousEvents = 51 | options.allowSynchronousEvents !== undefined 52 | ? options.allowSynchronousEvents 53 | : true; 54 | this._binaryType = options.binaryType || BINARY_TYPES[0]; 55 | this._extensions = options.extensions || {}; 56 | this._isServer = !!options.isServer; 57 | this._maxPayload = options.maxPayload | 0; 58 | this._skipUTF8Validation = !!options.skipUTF8Validation; 59 | this[kWebSocket] = undefined; 60 | 61 | this._bufferedBytes = 0; 62 | this._buffers = []; 63 | 64 | this._compressed = false; 65 | this._payloadLength = 0; 66 | this._mask = undefined; 67 | this._fragmented = 0; 68 | this._masked = false; 69 | this._fin = false; 70 | this._opcode = 0; 71 | 72 | this._totalPayloadLength = 0; 73 | this._messageLength = 0; 74 | this._fragments = []; 75 | 76 | this._errored = false; 77 | this._loop = false; 78 | this._state = GET_INFO; 79 | } 80 | 81 | /** 82 | * Implements `Writable.prototype._write()`. 83 | * 84 | * @param {Buffer} chunk The chunk of data to write 85 | * @param {String} encoding The character encoding of `chunk` 86 | * @param {Function} cb Callback 87 | * @private 88 | */ 89 | _write(chunk, encoding, cb) { 90 | if (this._opcode === 0x08 && this._state == GET_INFO) return cb(); 91 | 92 | this._bufferedBytes += chunk.length; 93 | this._buffers.push(chunk); 94 | this.startLoop(cb); 95 | } 96 | 97 | /** 98 | * Consumes `n` bytes from the buffered data. 99 | * 100 | * @param {Number} n The number of bytes to consume 101 | * @return {Buffer} The consumed bytes 102 | * @private 103 | */ 104 | consume(n) { 105 | this._bufferedBytes -= n; 106 | 107 | if (n === this._buffers[0].length) return this._buffers.shift(); 108 | 109 | if (n < this._buffers[0].length) { 110 | const buf = this._buffers[0]; 111 | this._buffers[0] = new FastBuffer( 112 | buf.buffer, 113 | buf.byteOffset + n, 114 | buf.length - n 115 | ); 116 | 117 | return new FastBuffer(buf.buffer, buf.byteOffset, n); 118 | } 119 | 120 | const dst = Buffer.allocUnsafe(n); 121 | 122 | do { 123 | const buf = this._buffers[0]; 124 | const offset = dst.length - n; 125 | 126 | if (n >= buf.length) { 127 | dst.set(this._buffers.shift(), offset); 128 | } else { 129 | dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset); 130 | this._buffers[0] = new FastBuffer( 131 | buf.buffer, 132 | buf.byteOffset + n, 133 | buf.length - n 134 | ); 135 | } 136 | 137 | n -= buf.length; 138 | } while (n > 0); 139 | 140 | return dst; 141 | } 142 | 143 | /** 144 | * Starts the parsing loop. 145 | * 146 | * @param {Function} cb Callback 147 | * @private 148 | */ 149 | startLoop(cb) { 150 | this._loop = true; 151 | 152 | do { 153 | switch (this._state) { 154 | case GET_INFO: 155 | this.getInfo(cb); 156 | break; 157 | case GET_PAYLOAD_LENGTH_16: 158 | this.getPayloadLength16(cb); 159 | break; 160 | case GET_PAYLOAD_LENGTH_64: 161 | this.getPayloadLength64(cb); 162 | break; 163 | case GET_MASK: 164 | this.getMask(); 165 | break; 166 | case GET_DATA: 167 | this.getData(cb); 168 | break; 169 | case INFLATING: 170 | case DEFER_EVENT: 171 | this._loop = false; 172 | return; 173 | } 174 | } while (this._loop); 175 | 176 | if (!this._errored) cb(); 177 | } 178 | 179 | /** 180 | * Reads the first two bytes of a frame. 181 | * 182 | * @param {Function} cb Callback 183 | * @private 184 | */ 185 | getInfo(cb) { 186 | if (this._bufferedBytes < 2) { 187 | this._loop = false; 188 | return; 189 | } 190 | 191 | const buf = this.consume(2); 192 | 193 | if ((buf[0] & 0x30) !== 0x00) { 194 | const error = this.createError( 195 | RangeError, 196 | 'RSV2 and RSV3 must be clear', 197 | true, 198 | 1002, 199 | 'WS_ERR_UNEXPECTED_RSV_2_3' 200 | ); 201 | 202 | cb(error); 203 | return; 204 | } 205 | 206 | const compressed = (buf[0] & 0x40) === 0x40; 207 | 208 | if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { 209 | const error = this.createError( 210 | RangeError, 211 | 'RSV1 must be clear', 212 | true, 213 | 1002, 214 | 'WS_ERR_UNEXPECTED_RSV_1' 215 | ); 216 | 217 | cb(error); 218 | return; 219 | } 220 | 221 | this._fin = (buf[0] & 0x80) === 0x80; 222 | this._opcode = buf[0] & 0x0f; 223 | this._payloadLength = buf[1] & 0x7f; 224 | 225 | if (this._opcode === 0x00) { 226 | if (compressed) { 227 | const error = this.createError( 228 | RangeError, 229 | 'RSV1 must be clear', 230 | true, 231 | 1002, 232 | 'WS_ERR_UNEXPECTED_RSV_1' 233 | ); 234 | 235 | cb(error); 236 | return; 237 | } 238 | 239 | if (!this._fragmented) { 240 | const error = this.createError( 241 | RangeError, 242 | 'invalid opcode 0', 243 | true, 244 | 1002, 245 | 'WS_ERR_INVALID_OPCODE' 246 | ); 247 | 248 | cb(error); 249 | return; 250 | } 251 | 252 | this._opcode = this._fragmented; 253 | } else if (this._opcode === 0x01 || this._opcode === 0x02) { 254 | if (this._fragmented) { 255 | const error = this.createError( 256 | RangeError, 257 | `invalid opcode ${this._opcode}`, 258 | true, 259 | 1002, 260 | 'WS_ERR_INVALID_OPCODE' 261 | ); 262 | 263 | cb(error); 264 | return; 265 | } 266 | 267 | this._compressed = compressed; 268 | } else if (this._opcode > 0x07 && this._opcode < 0x0b) { 269 | if (!this._fin) { 270 | const error = this.createError( 271 | RangeError, 272 | 'FIN must be set', 273 | true, 274 | 1002, 275 | 'WS_ERR_EXPECTED_FIN' 276 | ); 277 | 278 | cb(error); 279 | return; 280 | } 281 | 282 | if (compressed) { 283 | const error = this.createError( 284 | RangeError, 285 | 'RSV1 must be clear', 286 | true, 287 | 1002, 288 | 'WS_ERR_UNEXPECTED_RSV_1' 289 | ); 290 | 291 | cb(error); 292 | return; 293 | } 294 | 295 | if ( 296 | this._payloadLength > 0x7d || 297 | (this._opcode === 0x08 && this._payloadLength === 1) 298 | ) { 299 | const error = this.createError( 300 | RangeError, 301 | `invalid payload length ${this._payloadLength}`, 302 | true, 303 | 1002, 304 | 'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH' 305 | ); 306 | 307 | cb(error); 308 | return; 309 | } 310 | } else { 311 | const error = this.createError( 312 | RangeError, 313 | `invalid opcode ${this._opcode}`, 314 | true, 315 | 1002, 316 | 'WS_ERR_INVALID_OPCODE' 317 | ); 318 | 319 | cb(error); 320 | return; 321 | } 322 | 323 | if (!this._fin && !this._fragmented) this._fragmented = this._opcode; 324 | this._masked = (buf[1] & 0x80) === 0x80; 325 | 326 | if (this._isServer) { 327 | if (!this._masked) { 328 | const error = this.createError( 329 | RangeError, 330 | 'MASK must be set', 331 | true, 332 | 1002, 333 | 'WS_ERR_EXPECTED_MASK' 334 | ); 335 | 336 | cb(error); 337 | return; 338 | } 339 | } else if (this._masked) { 340 | const error = this.createError( 341 | RangeError, 342 | 'MASK must be clear', 343 | true, 344 | 1002, 345 | 'WS_ERR_UNEXPECTED_MASK' 346 | ); 347 | 348 | cb(error); 349 | return; 350 | } 351 | 352 | if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16; 353 | else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64; 354 | else this.haveLength(cb); 355 | } 356 | 357 | /** 358 | * Gets extended payload length (7+16). 359 | * 360 | * @param {Function} cb Callback 361 | * @private 362 | */ 363 | getPayloadLength16(cb) { 364 | if (this._bufferedBytes < 2) { 365 | this._loop = false; 366 | return; 367 | } 368 | 369 | this._payloadLength = this.consume(2).readUInt16BE(0); 370 | this.haveLength(cb); 371 | } 372 | 373 | /** 374 | * Gets extended payload length (7+64). 375 | * 376 | * @param {Function} cb Callback 377 | * @private 378 | */ 379 | getPayloadLength64(cb) { 380 | if (this._bufferedBytes < 8) { 381 | this._loop = false; 382 | return; 383 | } 384 | 385 | const buf = this.consume(8); 386 | const num = buf.readUInt32BE(0); 387 | 388 | // 389 | // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned 390 | // if payload length is greater than this number. 391 | // 392 | if (num > Math.pow(2, 53 - 32) - 1) { 393 | const error = this.createError( 394 | RangeError, 395 | 'Unsupported WebSocket frame: payload length > 2^53 - 1', 396 | false, 397 | 1009, 398 | 'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH' 399 | ); 400 | 401 | cb(error); 402 | return; 403 | } 404 | 405 | this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4); 406 | this.haveLength(cb); 407 | } 408 | 409 | /** 410 | * Payload length has been read. 411 | * 412 | * @param {Function} cb Callback 413 | * @private 414 | */ 415 | haveLength(cb) { 416 | if (this._payloadLength && this._opcode < 0x08) { 417 | this._totalPayloadLength += this._payloadLength; 418 | if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) { 419 | const error = this.createError( 420 | RangeError, 421 | 'Max payload size exceeded', 422 | false, 423 | 1009, 424 | 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH' 425 | ); 426 | 427 | cb(error); 428 | return; 429 | } 430 | } 431 | 432 | if (this._masked) this._state = GET_MASK; 433 | else this._state = GET_DATA; 434 | } 435 | 436 | /** 437 | * Reads mask bytes. 438 | * 439 | * @private 440 | */ 441 | getMask() { 442 | if (this._bufferedBytes < 4) { 443 | this._loop = false; 444 | return; 445 | } 446 | 447 | this._mask = this.consume(4); 448 | this._state = GET_DATA; 449 | } 450 | 451 | /** 452 | * Reads data bytes. 453 | * 454 | * @param {Function} cb Callback 455 | * @private 456 | */ 457 | getData(cb) { 458 | let data = EMPTY_BUFFER; 459 | 460 | if (this._payloadLength) { 461 | if (this._bufferedBytes < this._payloadLength) { 462 | this._loop = false; 463 | return; 464 | } 465 | 466 | data = this.consume(this._payloadLength); 467 | 468 | if ( 469 | this._masked && 470 | (this._mask[0] | this._mask[1] | this._mask[2] | this._mask[3]) !== 0 471 | ) { 472 | unmask(data, this._mask); 473 | } 474 | } 475 | 476 | if (this._opcode > 0x07) { 477 | this.controlMessage(data, cb); 478 | return; 479 | } 480 | 481 | if (this._compressed) { 482 | this._state = INFLATING; 483 | this.decompress(data, cb); 484 | return; 485 | } 486 | 487 | if (data.length) { 488 | // 489 | // This message is not compressed so its length is the sum of the payload 490 | // length of all fragments. 491 | // 492 | this._messageLength = this._totalPayloadLength; 493 | this._fragments.push(data); 494 | } 495 | 496 | this.dataMessage(cb); 497 | } 498 | 499 | /** 500 | * Decompresses data. 501 | * 502 | * @param {Buffer} data Compressed data 503 | * @param {Function} cb Callback 504 | * @private 505 | */ 506 | decompress(data, cb) { 507 | const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; 508 | 509 | perMessageDeflate.decompress(data, this._fin, (err, buf) => { 510 | if (err) return cb(err); 511 | 512 | if (buf.length) { 513 | this._messageLength += buf.length; 514 | if (this._messageLength > this._maxPayload && this._maxPayload > 0) { 515 | const error = this.createError( 516 | RangeError, 517 | 'Max payload size exceeded', 518 | false, 519 | 1009, 520 | 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH' 521 | ); 522 | 523 | cb(error); 524 | return; 525 | } 526 | 527 | this._fragments.push(buf); 528 | } 529 | 530 | this.dataMessage(cb); 531 | if (this._state === GET_INFO) this.startLoop(cb); 532 | }); 533 | } 534 | 535 | /** 536 | * Handles a data message. 537 | * 538 | * @param {Function} cb Callback 539 | * @private 540 | */ 541 | dataMessage(cb) { 542 | if (!this._fin) { 543 | this._state = GET_INFO; 544 | return; 545 | } 546 | 547 | const messageLength = this._messageLength; 548 | const fragments = this._fragments; 549 | 550 | this._totalPayloadLength = 0; 551 | this._messageLength = 0; 552 | this._fragmented = 0; 553 | this._fragments = []; 554 | 555 | if (this._opcode === 2) { 556 | let data; 557 | 558 | if (this._binaryType === 'nodebuffer') { 559 | data = concat(fragments, messageLength); 560 | } else if (this._binaryType === 'arraybuffer') { 561 | data = toArrayBuffer(concat(fragments, messageLength)); 562 | } else if (this._binaryType === 'blob') { 563 | data = new Blob(fragments); 564 | } else { 565 | data = fragments; 566 | } 567 | 568 | if (this._allowSynchronousEvents) { 569 | this.emit('message', data, true); 570 | this._state = GET_INFO; 571 | } else { 572 | this._state = DEFER_EVENT; 573 | setImmediate(() => { 574 | this.emit('message', data, true); 575 | this._state = GET_INFO; 576 | this.startLoop(cb); 577 | }); 578 | } 579 | } else { 580 | const buf = concat(fragments, messageLength); 581 | 582 | if (!this._skipUTF8Validation && !isValidUTF8(buf)) { 583 | const error = this.createError( 584 | Error, 585 | 'invalid UTF-8 sequence', 586 | true, 587 | 1007, 588 | 'WS_ERR_INVALID_UTF8' 589 | ); 590 | 591 | cb(error); 592 | return; 593 | } 594 | 595 | if (this._state === INFLATING || this._allowSynchronousEvents) { 596 | this.emit('message', buf, false); 597 | this._state = GET_INFO; 598 | } else { 599 | this._state = DEFER_EVENT; 600 | setImmediate(() => { 601 | this.emit('message', buf, false); 602 | this._state = GET_INFO; 603 | this.startLoop(cb); 604 | }); 605 | } 606 | } 607 | } 608 | 609 | /** 610 | * Handles a control message. 611 | * 612 | * @param {Buffer} data Data to handle 613 | * @return {(Error|RangeError|undefined)} A possible error 614 | * @private 615 | */ 616 | controlMessage(data, cb) { 617 | if (this._opcode === 0x08) { 618 | if (data.length === 0) { 619 | this._loop = false; 620 | this.emit('conclude', 1005, EMPTY_BUFFER); 621 | this.end(); 622 | } else { 623 | const code = data.readUInt16BE(0); 624 | 625 | if (!isValidStatusCode(code)) { 626 | const error = this.createError( 627 | RangeError, 628 | `invalid status code ${code}`, 629 | true, 630 | 1002, 631 | 'WS_ERR_INVALID_CLOSE_CODE' 632 | ); 633 | 634 | cb(error); 635 | return; 636 | } 637 | 638 | const buf = new FastBuffer( 639 | data.buffer, 640 | data.byteOffset + 2, 641 | data.length - 2 642 | ); 643 | 644 | if (!this._skipUTF8Validation && !isValidUTF8(buf)) { 645 | const error = this.createError( 646 | Error, 647 | 'invalid UTF-8 sequence', 648 | true, 649 | 1007, 650 | 'WS_ERR_INVALID_UTF8' 651 | ); 652 | 653 | cb(error); 654 | return; 655 | } 656 | 657 | this._loop = false; 658 | this.emit('conclude', code, buf); 659 | this.end(); 660 | } 661 | 662 | this._state = GET_INFO; 663 | return; 664 | } 665 | 666 | if (this._allowSynchronousEvents) { 667 | this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data); 668 | this._state = GET_INFO; 669 | } else { 670 | this._state = DEFER_EVENT; 671 | setImmediate(() => { 672 | this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data); 673 | this._state = GET_INFO; 674 | this.startLoop(cb); 675 | }); 676 | } 677 | } 678 | 679 | /** 680 | * Builds an error object. 681 | * 682 | * @param {function(new:Error|RangeError)} ErrorCtor The error constructor 683 | * @param {String} message The error message 684 | * @param {Boolean} prefix Specifies whether or not to add a default prefix to 685 | * `message` 686 | * @param {Number} statusCode The status code 687 | * @param {String} errorCode The exposed error code 688 | * @return {(Error|RangeError)} The error 689 | * @private 690 | */ 691 | createError(ErrorCtor, message, prefix, statusCode, errorCode) { 692 | this._loop = false; 693 | this._errored = true; 694 | 695 | const err = new ErrorCtor( 696 | prefix ? `Invalid WebSocket frame: ${message}` : message 697 | ); 698 | 699 | Error.captureStackTrace(err, this.createError); 700 | err.code = errorCode; 701 | err[kStatusCode] = statusCode; 702 | return err; 703 | } 704 | } 705 | 706 | module.exports = Receiver; 707 | -------------------------------------------------------------------------------- /Real-Time-Chat-App/chat-app/node_modules/ws/lib/websocket.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex|Readable$", "caughtErrors": "none" }] */ 2 | 3 | 'use strict'; 4 | 5 | const EventEmitter = require('events'); 6 | const https = require('https'); 7 | const http = require('http'); 8 | const net = require('net'); 9 | const tls = require('tls'); 10 | const { randomBytes, createHash } = require('crypto'); 11 | const { Duplex, Readable } = require('stream'); 12 | const { URL } = require('url'); 13 | 14 | const PerMessageDeflate = require('./permessage-deflate'); 15 | const Receiver = require('./receiver'); 16 | const Sender = require('./sender'); 17 | const { isBlob } = require('./validation'); 18 | 19 | const { 20 | BINARY_TYPES, 21 | EMPTY_BUFFER, 22 | GUID, 23 | kForOnEventAttribute, 24 | kListener, 25 | kStatusCode, 26 | kWebSocket, 27 | NOOP 28 | } = require('./constants'); 29 | const { 30 | EventTarget: { addEventListener, removeEventListener } 31 | } = require('./event-target'); 32 | const { format, parse } = require('./extension'); 33 | const { toBuffer } = require('./buffer-util'); 34 | 35 | const closeTimeout = 30 * 1000; 36 | const kAborted = Symbol('kAborted'); 37 | const protocolVersions = [8, 13]; 38 | const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']; 39 | const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/; 40 | 41 | /** 42 | * Class representing a WebSocket. 43 | * 44 | * @extends EventEmitter 45 | */ 46 | class WebSocket extends EventEmitter { 47 | /** 48 | * Create a new `WebSocket`. 49 | * 50 | * @param {(String|URL)} address The URL to which to connect 51 | * @param {(String|String[])} [protocols] The subprotocols 52 | * @param {Object} [options] Connection options 53 | */ 54 | constructor(address, protocols, options) { 55 | super(); 56 | 57 | this._binaryType = BINARY_TYPES[0]; 58 | this._closeCode = 1006; 59 | this._closeFrameReceived = false; 60 | this._closeFrameSent = false; 61 | this._closeMessage = EMPTY_BUFFER; 62 | this._closeTimer = null; 63 | this._errorEmitted = false; 64 | this._extensions = {}; 65 | this._paused = false; 66 | this._protocol = ''; 67 | this._readyState = WebSocket.CONNECTING; 68 | this._receiver = null; 69 | this._sender = null; 70 | this._socket = null; 71 | 72 | if (address !== null) { 73 | this._bufferedAmount = 0; 74 | this._isServer = false; 75 | this._redirects = 0; 76 | 77 | if (protocols === undefined) { 78 | protocols = []; 79 | } else if (!Array.isArray(protocols)) { 80 | if (typeof protocols === 'object' && protocols !== null) { 81 | options = protocols; 82 | protocols = []; 83 | } else { 84 | protocols = [protocols]; 85 | } 86 | } 87 | 88 | initAsClient(this, address, protocols, options); 89 | } else { 90 | this._autoPong = options.autoPong; 91 | this._isServer = true; 92 | } 93 | } 94 | 95 | /** 96 | * For historical reasons, the custom "nodebuffer" type is used by the default 97 | * instead of "blob". 98 | * 99 | * @type {String} 100 | */ 101 | get binaryType() { 102 | return this._binaryType; 103 | } 104 | 105 | set binaryType(type) { 106 | if (!BINARY_TYPES.includes(type)) return; 107 | 108 | this._binaryType = type; 109 | 110 | // 111 | // Allow to change `binaryType` on the fly. 112 | // 113 | if (this._receiver) this._receiver._binaryType = type; 114 | } 115 | 116 | /** 117 | * @type {Number} 118 | */ 119 | get bufferedAmount() { 120 | if (!this._socket) return this._bufferedAmount; 121 | 122 | return this._socket._writableState.length + this._sender._bufferedBytes; 123 | } 124 | 125 | /** 126 | * @type {String} 127 | */ 128 | get extensions() { 129 | return Object.keys(this._extensions).join(); 130 | } 131 | 132 | /** 133 | * @type {Boolean} 134 | */ 135 | get isPaused() { 136 | return this._paused; 137 | } 138 | 139 | /** 140 | * @type {Function} 141 | */ 142 | /* istanbul ignore next */ 143 | get onclose() { 144 | return null; 145 | } 146 | 147 | /** 148 | * @type {Function} 149 | */ 150 | /* istanbul ignore next */ 151 | get onerror() { 152 | return null; 153 | } 154 | 155 | /** 156 | * @type {Function} 157 | */ 158 | /* istanbul ignore next */ 159 | get onopen() { 160 | return null; 161 | } 162 | 163 | /** 164 | * @type {Function} 165 | */ 166 | /* istanbul ignore next */ 167 | get onmessage() { 168 | return null; 169 | } 170 | 171 | /** 172 | * @type {String} 173 | */ 174 | get protocol() { 175 | return this._protocol; 176 | } 177 | 178 | /** 179 | * @type {Number} 180 | */ 181 | get readyState() { 182 | return this._readyState; 183 | } 184 | 185 | /** 186 | * @type {String} 187 | */ 188 | get url() { 189 | return this._url; 190 | } 191 | 192 | /** 193 | * Set up the socket and the internal resources. 194 | * 195 | * @param {Duplex} socket The network socket between the server and client 196 | * @param {Buffer} head The first packet of the upgraded stream 197 | * @param {Object} options Options object 198 | * @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether 199 | * any of the `'message'`, `'ping'`, and `'pong'` events can be emitted 200 | * multiple times in the same tick 201 | * @param {Function} [options.generateMask] The function used to generate the 202 | * masking key 203 | * @param {Number} [options.maxPayload=0] The maximum allowed message size 204 | * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or 205 | * not to skip UTF-8 validation for text and close messages 206 | * @private 207 | */ 208 | setSocket(socket, head, options) { 209 | const receiver = new Receiver({ 210 | allowSynchronousEvents: options.allowSynchronousEvents, 211 | binaryType: this.binaryType, 212 | extensions: this._extensions, 213 | isServer: this._isServer, 214 | maxPayload: options.maxPayload, 215 | skipUTF8Validation: options.skipUTF8Validation 216 | }); 217 | 218 | const sender = new Sender(socket, this._extensions, options.generateMask); 219 | 220 | this._receiver = receiver; 221 | this._sender = sender; 222 | this._socket = socket; 223 | 224 | receiver[kWebSocket] = this; 225 | sender[kWebSocket] = this; 226 | socket[kWebSocket] = this; 227 | 228 | receiver.on('conclude', receiverOnConclude); 229 | receiver.on('drain', receiverOnDrain); 230 | receiver.on('error', receiverOnError); 231 | receiver.on('message', receiverOnMessage); 232 | receiver.on('ping', receiverOnPing); 233 | receiver.on('pong', receiverOnPong); 234 | 235 | sender.onerror = senderOnError; 236 | 237 | // 238 | // These methods may not be available if `socket` is just a `Duplex`. 239 | // 240 | if (socket.setTimeout) socket.setTimeout(0); 241 | if (socket.setNoDelay) socket.setNoDelay(); 242 | 243 | if (head.length > 0) socket.unshift(head); 244 | 245 | socket.on('close', socketOnClose); 246 | socket.on('data', socketOnData); 247 | socket.on('end', socketOnEnd); 248 | socket.on('error', socketOnError); 249 | 250 | this._readyState = WebSocket.OPEN; 251 | this.emit('open'); 252 | } 253 | 254 | /** 255 | * Emit the `'close'` event. 256 | * 257 | * @private 258 | */ 259 | emitClose() { 260 | if (!this._socket) { 261 | this._readyState = WebSocket.CLOSED; 262 | this.emit('close', this._closeCode, this._closeMessage); 263 | return; 264 | } 265 | 266 | if (this._extensions[PerMessageDeflate.extensionName]) { 267 | this._extensions[PerMessageDeflate.extensionName].cleanup(); 268 | } 269 | 270 | this._receiver.removeAllListeners(); 271 | this._readyState = WebSocket.CLOSED; 272 | this.emit('close', this._closeCode, this._closeMessage); 273 | } 274 | 275 | /** 276 | * Start a closing handshake. 277 | * 278 | * +----------+ +-----------+ +----------+ 279 | * - - -|ws.close()|-->|close frame|-->|ws.close()|- - - 280 | * | +----------+ +-----------+ +----------+ | 281 | * +----------+ +-----------+ | 282 | * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING 283 | * +----------+ +-----------+ | 284 | * | | | +---+ | 285 | * +------------------------+-->|fin| - - - - 286 | * | +---+ | +---+ 287 | * - - - - -|fin|<---------------------+ 288 | * +---+ 289 | * 290 | * @param {Number} [code] Status code explaining why the connection is closing 291 | * @param {(String|Buffer)} [data] The reason why the connection is 292 | * closing 293 | * @public 294 | */ 295 | close(code, data) { 296 | if (this.readyState === WebSocket.CLOSED) return; 297 | if (this.readyState === WebSocket.CONNECTING) { 298 | const msg = 'WebSocket was closed before the connection was established'; 299 | abortHandshake(this, this._req, msg); 300 | return; 301 | } 302 | 303 | if (this.readyState === WebSocket.CLOSING) { 304 | if ( 305 | this._closeFrameSent && 306 | (this._closeFrameReceived || this._receiver._writableState.errorEmitted) 307 | ) { 308 | this._socket.end(); 309 | } 310 | 311 | return; 312 | } 313 | 314 | this._readyState = WebSocket.CLOSING; 315 | this._sender.close(code, data, !this._isServer, (err) => { 316 | // 317 | // This error is handled by the `'error'` listener on the socket. We only 318 | // want to know if the close frame has been sent here. 319 | // 320 | if (err) return; 321 | 322 | this._closeFrameSent = true; 323 | 324 | if ( 325 | this._closeFrameReceived || 326 | this._receiver._writableState.errorEmitted 327 | ) { 328 | this._socket.end(); 329 | } 330 | }); 331 | 332 | setCloseTimer(this); 333 | } 334 | 335 | /** 336 | * Pause the socket. 337 | * 338 | * @public 339 | */ 340 | pause() { 341 | if ( 342 | this.readyState === WebSocket.CONNECTING || 343 | this.readyState === WebSocket.CLOSED 344 | ) { 345 | return; 346 | } 347 | 348 | this._paused = true; 349 | this._socket.pause(); 350 | } 351 | 352 | /** 353 | * Send a ping. 354 | * 355 | * @param {*} [data] The data to send 356 | * @param {Boolean} [mask] Indicates whether or not to mask `data` 357 | * @param {Function} [cb] Callback which is executed when the ping is sent 358 | * @public 359 | */ 360 | ping(data, mask, cb) { 361 | if (this.readyState === WebSocket.CONNECTING) { 362 | throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); 363 | } 364 | 365 | if (typeof data === 'function') { 366 | cb = data; 367 | data = mask = undefined; 368 | } else if (typeof mask === 'function') { 369 | cb = mask; 370 | mask = undefined; 371 | } 372 | 373 | if (typeof data === 'number') data = data.toString(); 374 | 375 | if (this.readyState !== WebSocket.OPEN) { 376 | sendAfterClose(this, data, cb); 377 | return; 378 | } 379 | 380 | if (mask === undefined) mask = !this._isServer; 381 | this._sender.ping(data || EMPTY_BUFFER, mask, cb); 382 | } 383 | 384 | /** 385 | * Send a pong. 386 | * 387 | * @param {*} [data] The data to send 388 | * @param {Boolean} [mask] Indicates whether or not to mask `data` 389 | * @param {Function} [cb] Callback which is executed when the pong is sent 390 | * @public 391 | */ 392 | pong(data, mask, cb) { 393 | if (this.readyState === WebSocket.CONNECTING) { 394 | throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); 395 | } 396 | 397 | if (typeof data === 'function') { 398 | cb = data; 399 | data = mask = undefined; 400 | } else if (typeof mask === 'function') { 401 | cb = mask; 402 | mask = undefined; 403 | } 404 | 405 | if (typeof data === 'number') data = data.toString(); 406 | 407 | if (this.readyState !== WebSocket.OPEN) { 408 | sendAfterClose(this, data, cb); 409 | return; 410 | } 411 | 412 | if (mask === undefined) mask = !this._isServer; 413 | this._sender.pong(data || EMPTY_BUFFER, mask, cb); 414 | } 415 | 416 | /** 417 | * Resume the socket. 418 | * 419 | * @public 420 | */ 421 | resume() { 422 | if ( 423 | this.readyState === WebSocket.CONNECTING || 424 | this.readyState === WebSocket.CLOSED 425 | ) { 426 | return; 427 | } 428 | 429 | this._paused = false; 430 | if (!this._receiver._writableState.needDrain) this._socket.resume(); 431 | } 432 | 433 | /** 434 | * Send a data message. 435 | * 436 | * @param {*} data The message to send 437 | * @param {Object} [options] Options object 438 | * @param {Boolean} [options.binary] Specifies whether `data` is binary or 439 | * text 440 | * @param {Boolean} [options.compress] Specifies whether or not to compress 441 | * `data` 442 | * @param {Boolean} [options.fin=true] Specifies whether the fragment is the 443 | * last one 444 | * @param {Boolean} [options.mask] Specifies whether or not to mask `data` 445 | * @param {Function} [cb] Callback which is executed when data is written out 446 | * @public 447 | */ 448 | send(data, options, cb) { 449 | if (this.readyState === WebSocket.CONNECTING) { 450 | throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); 451 | } 452 | 453 | if (typeof options === 'function') { 454 | cb = options; 455 | options = {}; 456 | } 457 | 458 | if (typeof data === 'number') data = data.toString(); 459 | 460 | if (this.readyState !== WebSocket.OPEN) { 461 | sendAfterClose(this, data, cb); 462 | return; 463 | } 464 | 465 | const opts = { 466 | binary: typeof data !== 'string', 467 | mask: !this._isServer, 468 | compress: true, 469 | fin: true, 470 | ...options 471 | }; 472 | 473 | if (!this._extensions[PerMessageDeflate.extensionName]) { 474 | opts.compress = false; 475 | } 476 | 477 | this._sender.send(data || EMPTY_BUFFER, opts, cb); 478 | } 479 | 480 | /** 481 | * Forcibly close the connection. 482 | * 483 | * @public 484 | */ 485 | terminate() { 486 | if (this.readyState === WebSocket.CLOSED) return; 487 | if (this.readyState === WebSocket.CONNECTING) { 488 | const msg = 'WebSocket was closed before the connection was established'; 489 | abortHandshake(this, this._req, msg); 490 | return; 491 | } 492 | 493 | if (this._socket) { 494 | this._readyState = WebSocket.CLOSING; 495 | this._socket.destroy(); 496 | } 497 | } 498 | } 499 | 500 | /** 501 | * @constant {Number} CONNECTING 502 | * @memberof WebSocket 503 | */ 504 | Object.defineProperty(WebSocket, 'CONNECTING', { 505 | enumerable: true, 506 | value: readyStates.indexOf('CONNECTING') 507 | }); 508 | 509 | /** 510 | * @constant {Number} CONNECTING 511 | * @memberof WebSocket.prototype 512 | */ 513 | Object.defineProperty(WebSocket.prototype, 'CONNECTING', { 514 | enumerable: true, 515 | value: readyStates.indexOf('CONNECTING') 516 | }); 517 | 518 | /** 519 | * @constant {Number} OPEN 520 | * @memberof WebSocket 521 | */ 522 | Object.defineProperty(WebSocket, 'OPEN', { 523 | enumerable: true, 524 | value: readyStates.indexOf('OPEN') 525 | }); 526 | 527 | /** 528 | * @constant {Number} OPEN 529 | * @memberof WebSocket.prototype 530 | */ 531 | Object.defineProperty(WebSocket.prototype, 'OPEN', { 532 | enumerable: true, 533 | value: readyStates.indexOf('OPEN') 534 | }); 535 | 536 | /** 537 | * @constant {Number} CLOSING 538 | * @memberof WebSocket 539 | */ 540 | Object.defineProperty(WebSocket, 'CLOSING', { 541 | enumerable: true, 542 | value: readyStates.indexOf('CLOSING') 543 | }); 544 | 545 | /** 546 | * @constant {Number} CLOSING 547 | * @memberof WebSocket.prototype 548 | */ 549 | Object.defineProperty(WebSocket.prototype, 'CLOSING', { 550 | enumerable: true, 551 | value: readyStates.indexOf('CLOSING') 552 | }); 553 | 554 | /** 555 | * @constant {Number} CLOSED 556 | * @memberof WebSocket 557 | */ 558 | Object.defineProperty(WebSocket, 'CLOSED', { 559 | enumerable: true, 560 | value: readyStates.indexOf('CLOSED') 561 | }); 562 | 563 | /** 564 | * @constant {Number} CLOSED 565 | * @memberof WebSocket.prototype 566 | */ 567 | Object.defineProperty(WebSocket.prototype, 'CLOSED', { 568 | enumerable: true, 569 | value: readyStates.indexOf('CLOSED') 570 | }); 571 | 572 | [ 573 | 'binaryType', 574 | 'bufferedAmount', 575 | 'extensions', 576 | 'isPaused', 577 | 'protocol', 578 | 'readyState', 579 | 'url' 580 | ].forEach((property) => { 581 | Object.defineProperty(WebSocket.prototype, property, { enumerable: true }); 582 | }); 583 | 584 | // 585 | // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes. 586 | // See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface 587 | // 588 | ['open', 'error', 'close', 'message'].forEach((method) => { 589 | Object.defineProperty(WebSocket.prototype, `on${method}`, { 590 | enumerable: true, 591 | get() { 592 | for (const listener of this.listeners(method)) { 593 | if (listener[kForOnEventAttribute]) return listener[kListener]; 594 | } 595 | 596 | return null; 597 | }, 598 | set(handler) { 599 | for (const listener of this.listeners(method)) { 600 | if (listener[kForOnEventAttribute]) { 601 | this.removeListener(method, listener); 602 | break; 603 | } 604 | } 605 | 606 | if (typeof handler !== 'function') return; 607 | 608 | this.addEventListener(method, handler, { 609 | [kForOnEventAttribute]: true 610 | }); 611 | } 612 | }); 613 | }); 614 | 615 | WebSocket.prototype.addEventListener = addEventListener; 616 | WebSocket.prototype.removeEventListener = removeEventListener; 617 | 618 | module.exports = WebSocket; 619 | 620 | /** 621 | * Initialize a WebSocket client. 622 | * 623 | * @param {WebSocket} websocket The client to initialize 624 | * @param {(String|URL)} address The URL to which to connect 625 | * @param {Array} protocols The subprotocols 626 | * @param {Object} [options] Connection options 627 | * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether any 628 | * of the `'message'`, `'ping'`, and `'pong'` events can be emitted multiple 629 | * times in the same tick 630 | * @param {Boolean} [options.autoPong=true] Specifies whether or not to 631 | * automatically send a pong in response to a ping 632 | * @param {Function} [options.finishRequest] A function which can be used to 633 | * customize the headers of each http request before it is sent 634 | * @param {Boolean} [options.followRedirects=false] Whether or not to follow 635 | * redirects 636 | * @param {Function} [options.generateMask] The function used to generate the 637 | * masking key 638 | * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the 639 | * handshake request 640 | * @param {Number} [options.maxPayload=104857600] The maximum allowed message 641 | * size 642 | * @param {Number} [options.maxRedirects=10] The maximum number of redirects 643 | * allowed 644 | * @param {String} [options.origin] Value of the `Origin` or 645 | * `Sec-WebSocket-Origin` header 646 | * @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable 647 | * permessage-deflate 648 | * @param {Number} [options.protocolVersion=13] Value of the 649 | * `Sec-WebSocket-Version` header 650 | * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or 651 | * not to skip UTF-8 validation for text and close messages 652 | * @private 653 | */ 654 | function initAsClient(websocket, address, protocols, options) { 655 | const opts = { 656 | allowSynchronousEvents: true, 657 | autoPong: true, 658 | protocolVersion: protocolVersions[1], 659 | maxPayload: 100 * 1024 * 1024, 660 | skipUTF8Validation: false, 661 | perMessageDeflate: true, 662 | followRedirects: false, 663 | maxRedirects: 10, 664 | ...options, 665 | socketPath: undefined, 666 | hostname: undefined, 667 | protocol: undefined, 668 | timeout: undefined, 669 | method: 'GET', 670 | host: undefined, 671 | path: undefined, 672 | port: undefined 673 | }; 674 | 675 | websocket._autoPong = opts.autoPong; 676 | 677 | if (!protocolVersions.includes(opts.protocolVersion)) { 678 | throw new RangeError( 679 | `Unsupported protocol version: ${opts.protocolVersion} ` + 680 | `(supported versions: ${protocolVersions.join(', ')})` 681 | ); 682 | } 683 | 684 | let parsedUrl; 685 | 686 | if (address instanceof URL) { 687 | parsedUrl = address; 688 | } else { 689 | try { 690 | parsedUrl = new URL(address); 691 | } catch (e) { 692 | throw new SyntaxError(`Invalid URL: ${address}`); 693 | } 694 | } 695 | 696 | if (parsedUrl.protocol === 'http:') { 697 | parsedUrl.protocol = 'ws:'; 698 | } else if (parsedUrl.protocol === 'https:') { 699 | parsedUrl.protocol = 'wss:'; 700 | } 701 | 702 | websocket._url = parsedUrl.href; 703 | 704 | const isSecure = parsedUrl.protocol === 'wss:'; 705 | const isIpcUrl = parsedUrl.protocol === 'ws+unix:'; 706 | let invalidUrlMessage; 707 | 708 | if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) { 709 | invalidUrlMessage = 710 | 'The URL\'s protocol must be one of "ws:", "wss:", ' + 711 | '"http:", "https", or "ws+unix:"'; 712 | } else if (isIpcUrl && !parsedUrl.pathname) { 713 | invalidUrlMessage = "The URL's pathname is empty"; 714 | } else if (parsedUrl.hash) { 715 | invalidUrlMessage = 'The URL contains a fragment identifier'; 716 | } 717 | 718 | if (invalidUrlMessage) { 719 | const err = new SyntaxError(invalidUrlMessage); 720 | 721 | if (websocket._redirects === 0) { 722 | throw err; 723 | } else { 724 | emitErrorAndClose(websocket, err); 725 | return; 726 | } 727 | } 728 | 729 | const defaultPort = isSecure ? 443 : 80; 730 | const key = randomBytes(16).toString('base64'); 731 | const request = isSecure ? https.request : http.request; 732 | const protocolSet = new Set(); 733 | let perMessageDeflate; 734 | 735 | opts.createConnection = 736 | opts.createConnection || (isSecure ? tlsConnect : netConnect); 737 | opts.defaultPort = opts.defaultPort || defaultPort; 738 | opts.port = parsedUrl.port || defaultPort; 739 | opts.host = parsedUrl.hostname.startsWith('[') 740 | ? parsedUrl.hostname.slice(1, -1) 741 | : parsedUrl.hostname; 742 | opts.headers = { 743 | ...opts.headers, 744 | 'Sec-WebSocket-Version': opts.protocolVersion, 745 | 'Sec-WebSocket-Key': key, 746 | Connection: 'Upgrade', 747 | Upgrade: 'websocket' 748 | }; 749 | opts.path = parsedUrl.pathname + parsedUrl.search; 750 | opts.timeout = opts.handshakeTimeout; 751 | 752 | if (opts.perMessageDeflate) { 753 | perMessageDeflate = new PerMessageDeflate( 754 | opts.perMessageDeflate !== true ? opts.perMessageDeflate : {}, 755 | false, 756 | opts.maxPayload 757 | ); 758 | opts.headers['Sec-WebSocket-Extensions'] = format({ 759 | [PerMessageDeflate.extensionName]: perMessageDeflate.offer() 760 | }); 761 | } 762 | if (protocols.length) { 763 | for (const protocol of protocols) { 764 | if ( 765 | typeof protocol !== 'string' || 766 | !subprotocolRegex.test(protocol) || 767 | protocolSet.has(protocol) 768 | ) { 769 | throw new SyntaxError( 770 | 'An invalid or duplicated subprotocol was specified' 771 | ); 772 | } 773 | 774 | protocolSet.add(protocol); 775 | } 776 | 777 | opts.headers['Sec-WebSocket-Protocol'] = protocols.join(','); 778 | } 779 | if (opts.origin) { 780 | if (opts.protocolVersion < 13) { 781 | opts.headers['Sec-WebSocket-Origin'] = opts.origin; 782 | } else { 783 | opts.headers.Origin = opts.origin; 784 | } 785 | } 786 | if (parsedUrl.username || parsedUrl.password) { 787 | opts.auth = `${parsedUrl.username}:${parsedUrl.password}`; 788 | } 789 | 790 | if (isIpcUrl) { 791 | const parts = opts.path.split(':'); 792 | 793 | opts.socketPath = parts[0]; 794 | opts.path = parts[1]; 795 | } 796 | 797 | let req; 798 | 799 | if (opts.followRedirects) { 800 | if (websocket._redirects === 0) { 801 | websocket._originalIpc = isIpcUrl; 802 | websocket._originalSecure = isSecure; 803 | websocket._originalHostOrSocketPath = isIpcUrl 804 | ? opts.socketPath 805 | : parsedUrl.host; 806 | 807 | const headers = options && options.headers; 808 | 809 | // 810 | // Shallow copy the user provided options so that headers can be changed 811 | // without mutating the original object. 812 | // 813 | options = { ...options, headers: {} }; 814 | 815 | if (headers) { 816 | for (const [key, value] of Object.entries(headers)) { 817 | options.headers[key.toLowerCase()] = value; 818 | } 819 | } 820 | } else if (websocket.listenerCount('redirect') === 0) { 821 | const isSameHost = isIpcUrl 822 | ? websocket._originalIpc 823 | ? opts.socketPath === websocket._originalHostOrSocketPath 824 | : false 825 | : websocket._originalIpc 826 | ? false 827 | : parsedUrl.host === websocket._originalHostOrSocketPath; 828 | 829 | if (!isSameHost || (websocket._originalSecure && !isSecure)) { 830 | // 831 | // Match curl 7.77.0 behavior and drop the following headers. These 832 | // headers are also dropped when following a redirect to a subdomain. 833 | // 834 | delete opts.headers.authorization; 835 | delete opts.headers.cookie; 836 | 837 | if (!isSameHost) delete opts.headers.host; 838 | 839 | opts.auth = undefined; 840 | } 841 | } 842 | 843 | // 844 | // Match curl 7.77.0 behavior and make the first `Authorization` header win. 845 | // If the `Authorization` header is set, then there is nothing to do as it 846 | // will take precedence. 847 | // 848 | if (opts.auth && !options.headers.authorization) { 849 | options.headers.authorization = 850 | 'Basic ' + Buffer.from(opts.auth).toString('base64'); 851 | } 852 | 853 | req = websocket._req = request(opts); 854 | 855 | if (websocket._redirects) { 856 | // 857 | // Unlike what is done for the `'upgrade'` event, no early exit is 858 | // triggered here if the user calls `websocket.close()` or 859 | // `websocket.terminate()` from a listener of the `'redirect'` event. This 860 | // is because the user can also call `request.destroy()` with an error 861 | // before calling `websocket.close()` or `websocket.terminate()` and this 862 | // would result in an error being emitted on the `request` object with no 863 | // `'error'` event listeners attached. 864 | // 865 | websocket.emit('redirect', websocket.url, req); 866 | } 867 | } else { 868 | req = websocket._req = request(opts); 869 | } 870 | 871 | if (opts.timeout) { 872 | req.on('timeout', () => { 873 | abortHandshake(websocket, req, 'Opening handshake has timed out'); 874 | }); 875 | } 876 | 877 | req.on('error', (err) => { 878 | if (req === null || req[kAborted]) return; 879 | 880 | req = websocket._req = null; 881 | emitErrorAndClose(websocket, err); 882 | }); 883 | 884 | req.on('response', (res) => { 885 | const location = res.headers.location; 886 | const statusCode = res.statusCode; 887 | 888 | if ( 889 | location && 890 | opts.followRedirects && 891 | statusCode >= 300 && 892 | statusCode < 400 893 | ) { 894 | if (++websocket._redirects > opts.maxRedirects) { 895 | abortHandshake(websocket, req, 'Maximum redirects exceeded'); 896 | return; 897 | } 898 | 899 | req.abort(); 900 | 901 | let addr; 902 | 903 | try { 904 | addr = new URL(location, address); 905 | } catch (e) { 906 | const err = new SyntaxError(`Invalid URL: ${location}`); 907 | emitErrorAndClose(websocket, err); 908 | return; 909 | } 910 | 911 | initAsClient(websocket, addr, protocols, options); 912 | } else if (!websocket.emit('unexpected-response', req, res)) { 913 | abortHandshake( 914 | websocket, 915 | req, 916 | `Unexpected server response: ${res.statusCode}` 917 | ); 918 | } 919 | }); 920 | 921 | req.on('upgrade', (res, socket, head) => { 922 | websocket.emit('upgrade', res); 923 | 924 | // 925 | // The user may have closed the connection from a listener of the 926 | // `'upgrade'` event. 927 | // 928 | if (websocket.readyState !== WebSocket.CONNECTING) return; 929 | 930 | req = websocket._req = null; 931 | 932 | const upgrade = res.headers.upgrade; 933 | 934 | if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') { 935 | abortHandshake(websocket, socket, 'Invalid Upgrade header'); 936 | return; 937 | } 938 | 939 | const digest = createHash('sha1') 940 | .update(key + GUID) 941 | .digest('base64'); 942 | 943 | if (res.headers['sec-websocket-accept'] !== digest) { 944 | abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header'); 945 | return; 946 | } 947 | 948 | const serverProt = res.headers['sec-websocket-protocol']; 949 | let protError; 950 | 951 | if (serverProt !== undefined) { 952 | if (!protocolSet.size) { 953 | protError = 'Server sent a subprotocol but none was requested'; 954 | } else if (!protocolSet.has(serverProt)) { 955 | protError = 'Server sent an invalid subprotocol'; 956 | } 957 | } else if (protocolSet.size) { 958 | protError = 'Server sent no subprotocol'; 959 | } 960 | 961 | if (protError) { 962 | abortHandshake(websocket, socket, protError); 963 | return; 964 | } 965 | 966 | if (serverProt) websocket._protocol = serverProt; 967 | 968 | const secWebSocketExtensions = res.headers['sec-websocket-extensions']; 969 | 970 | if (secWebSocketExtensions !== undefined) { 971 | if (!perMessageDeflate) { 972 | const message = 973 | 'Server sent a Sec-WebSocket-Extensions header but no extension ' + 974 | 'was requested'; 975 | abortHandshake(websocket, socket, message); 976 | return; 977 | } 978 | 979 | let extensions; 980 | 981 | try { 982 | extensions = parse(secWebSocketExtensions); 983 | } catch (err) { 984 | const message = 'Invalid Sec-WebSocket-Extensions header'; 985 | abortHandshake(websocket, socket, message); 986 | return; 987 | } 988 | 989 | const extensionNames = Object.keys(extensions); 990 | 991 | if ( 992 | extensionNames.length !== 1 || 993 | extensionNames[0] !== PerMessageDeflate.extensionName 994 | ) { 995 | const message = 'Server indicated an extension that was not requested'; 996 | abortHandshake(websocket, socket, message); 997 | return; 998 | } 999 | 1000 | try { 1001 | perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]); 1002 | } catch (err) { 1003 | const message = 'Invalid Sec-WebSocket-Extensions header'; 1004 | abortHandshake(websocket, socket, message); 1005 | return; 1006 | } 1007 | 1008 | websocket._extensions[PerMessageDeflate.extensionName] = 1009 | perMessageDeflate; 1010 | } 1011 | 1012 | websocket.setSocket(socket, head, { 1013 | allowSynchronousEvents: opts.allowSynchronousEvents, 1014 | generateMask: opts.generateMask, 1015 | maxPayload: opts.maxPayload, 1016 | skipUTF8Validation: opts.skipUTF8Validation 1017 | }); 1018 | }); 1019 | 1020 | if (opts.finishRequest) { 1021 | opts.finishRequest(req, websocket); 1022 | } else { 1023 | req.end(); 1024 | } 1025 | } 1026 | 1027 | /** 1028 | * Emit the `'error'` and `'close'` events. 1029 | * 1030 | * @param {WebSocket} websocket The WebSocket instance 1031 | * @param {Error} The error to emit 1032 | * @private 1033 | */ 1034 | function emitErrorAndClose(websocket, err) { 1035 | websocket._readyState = WebSocket.CLOSING; 1036 | // 1037 | // The following assignment is practically useless and is done only for 1038 | // consistency. 1039 | // 1040 | websocket._errorEmitted = true; 1041 | websocket.emit('error', err); 1042 | websocket.emitClose(); 1043 | } 1044 | 1045 | /** 1046 | * Create a `net.Socket` and initiate a connection. 1047 | * 1048 | * @param {Object} options Connection options 1049 | * @return {net.Socket} The newly created socket used to start the connection 1050 | * @private 1051 | */ 1052 | function netConnect(options) { 1053 | options.path = options.socketPath; 1054 | return net.connect(options); 1055 | } 1056 | 1057 | /** 1058 | * Create a `tls.TLSSocket` and initiate a connection. 1059 | * 1060 | * @param {Object} options Connection options 1061 | * @return {tls.TLSSocket} The newly created socket used to start the connection 1062 | * @private 1063 | */ 1064 | function tlsConnect(options) { 1065 | options.path = undefined; 1066 | 1067 | if (!options.servername && options.servername !== '') { 1068 | options.servername = net.isIP(options.host) ? '' : options.host; 1069 | } 1070 | 1071 | return tls.connect(options); 1072 | } 1073 | 1074 | /** 1075 | * Abort the handshake and emit an error. 1076 | * 1077 | * @param {WebSocket} websocket The WebSocket instance 1078 | * @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to 1079 | * abort or the socket to destroy 1080 | * @param {String} message The error message 1081 | * @private 1082 | */ 1083 | function abortHandshake(websocket, stream, message) { 1084 | websocket._readyState = WebSocket.CLOSING; 1085 | 1086 | const err = new Error(message); 1087 | Error.captureStackTrace(err, abortHandshake); 1088 | 1089 | if (stream.setHeader) { 1090 | stream[kAborted] = true; 1091 | stream.abort(); 1092 | 1093 | if (stream.socket && !stream.socket.destroyed) { 1094 | // 1095 | // On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if 1096 | // called after the request completed. See 1097 | // https://github.com/websockets/ws/issues/1869. 1098 | // 1099 | stream.socket.destroy(); 1100 | } 1101 | 1102 | process.nextTick(emitErrorAndClose, websocket, err); 1103 | } else { 1104 | stream.destroy(err); 1105 | stream.once('error', websocket.emit.bind(websocket, 'error')); 1106 | stream.once('close', websocket.emitClose.bind(websocket)); 1107 | } 1108 | } 1109 | 1110 | /** 1111 | * Handle cases where the `ping()`, `pong()`, or `send()` methods are called 1112 | * when the `readyState` attribute is `CLOSING` or `CLOSED`. 1113 | * 1114 | * @param {WebSocket} websocket The WebSocket instance 1115 | * @param {*} [data] The data to send 1116 | * @param {Function} [cb] Callback 1117 | * @private 1118 | */ 1119 | function sendAfterClose(websocket, data, cb) { 1120 | if (data) { 1121 | const length = isBlob(data) ? data.size : toBuffer(data).length; 1122 | 1123 | // 1124 | // The `_bufferedAmount` property is used only when the peer is a client and 1125 | // the opening handshake fails. Under these circumstances, in fact, the 1126 | // `setSocket()` method is not called, so the `_socket` and `_sender` 1127 | // properties are set to `null`. 1128 | // 1129 | if (websocket._socket) websocket._sender._bufferedBytes += length; 1130 | else websocket._bufferedAmount += length; 1131 | } 1132 | 1133 | if (cb) { 1134 | const err = new Error( 1135 | `WebSocket is not open: readyState ${websocket.readyState} ` + 1136 | `(${readyStates[websocket.readyState]})` 1137 | ); 1138 | process.nextTick(cb, err); 1139 | } 1140 | } 1141 | 1142 | /** 1143 | * The listener of the `Receiver` `'conclude'` event. 1144 | * 1145 | * @param {Number} code The status code 1146 | * @param {Buffer} reason The reason for closing 1147 | * @private 1148 | */ 1149 | function receiverOnConclude(code, reason) { 1150 | const websocket = this[kWebSocket]; 1151 | 1152 | websocket._closeFrameReceived = true; 1153 | websocket._closeMessage = reason; 1154 | websocket._closeCode = code; 1155 | 1156 | if (websocket._socket[kWebSocket] === undefined) return; 1157 | 1158 | websocket._socket.removeListener('data', socketOnData); 1159 | process.nextTick(resume, websocket._socket); 1160 | 1161 | if (code === 1005) websocket.close(); 1162 | else websocket.close(code, reason); 1163 | } 1164 | 1165 | /** 1166 | * The listener of the `Receiver` `'drain'` event. 1167 | * 1168 | * @private 1169 | */ 1170 | function receiverOnDrain() { 1171 | const websocket = this[kWebSocket]; 1172 | 1173 | if (!websocket.isPaused) websocket._socket.resume(); 1174 | } 1175 | 1176 | /** 1177 | * The listener of the `Receiver` `'error'` event. 1178 | * 1179 | * @param {(RangeError|Error)} err The emitted error 1180 | * @private 1181 | */ 1182 | function receiverOnError(err) { 1183 | const websocket = this[kWebSocket]; 1184 | 1185 | if (websocket._socket[kWebSocket] !== undefined) { 1186 | websocket._socket.removeListener('data', socketOnData); 1187 | 1188 | // 1189 | // On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See 1190 | // https://github.com/websockets/ws/issues/1940. 1191 | // 1192 | process.nextTick(resume, websocket._socket); 1193 | 1194 | websocket.close(err[kStatusCode]); 1195 | } 1196 | 1197 | if (!websocket._errorEmitted) { 1198 | websocket._errorEmitted = true; 1199 | websocket.emit('error', err); 1200 | } 1201 | } 1202 | 1203 | /** 1204 | * The listener of the `Receiver` `'finish'` event. 1205 | * 1206 | * @private 1207 | */ 1208 | function receiverOnFinish() { 1209 | this[kWebSocket].emitClose(); 1210 | } 1211 | 1212 | /** 1213 | * The listener of the `Receiver` `'message'` event. 1214 | * 1215 | * @param {Buffer|ArrayBuffer|Buffer[])} data The message 1216 | * @param {Boolean} isBinary Specifies whether the message is binary or not 1217 | * @private 1218 | */ 1219 | function receiverOnMessage(data, isBinary) { 1220 | this[kWebSocket].emit('message', data, isBinary); 1221 | } 1222 | 1223 | /** 1224 | * The listener of the `Receiver` `'ping'` event. 1225 | * 1226 | * @param {Buffer} data The data included in the ping frame 1227 | * @private 1228 | */ 1229 | function receiverOnPing(data) { 1230 | const websocket = this[kWebSocket]; 1231 | 1232 | if (websocket._autoPong) websocket.pong(data, !this._isServer, NOOP); 1233 | websocket.emit('ping', data); 1234 | } 1235 | 1236 | /** 1237 | * The listener of the `Receiver` `'pong'` event. 1238 | * 1239 | * @param {Buffer} data The data included in the pong frame 1240 | * @private 1241 | */ 1242 | function receiverOnPong(data) { 1243 | this[kWebSocket].emit('pong', data); 1244 | } 1245 | 1246 | /** 1247 | * Resume a readable stream 1248 | * 1249 | * @param {Readable} stream The readable stream 1250 | * @private 1251 | */ 1252 | function resume(stream) { 1253 | stream.resume(); 1254 | } 1255 | 1256 | /** 1257 | * The `Sender` error event handler. 1258 | * 1259 | * @param {Error} The error 1260 | * @private 1261 | */ 1262 | function senderOnError(err) { 1263 | const websocket = this[kWebSocket]; 1264 | 1265 | if (websocket.readyState === WebSocket.CLOSED) return; 1266 | if (websocket.readyState === WebSocket.OPEN) { 1267 | websocket._readyState = WebSocket.CLOSING; 1268 | setCloseTimer(websocket); 1269 | } 1270 | 1271 | // 1272 | // `socket.end()` is used instead of `socket.destroy()` to allow the other 1273 | // peer to finish sending queued data. There is no need to set a timer here 1274 | // because `CLOSING` means that it is already set or not needed. 1275 | // 1276 | this._socket.end(); 1277 | 1278 | if (!websocket._errorEmitted) { 1279 | websocket._errorEmitted = true; 1280 | websocket.emit('error', err); 1281 | } 1282 | } 1283 | 1284 | /** 1285 | * Set a timer to destroy the underlying raw socket of a WebSocket. 1286 | * 1287 | * @param {WebSocket} websocket The WebSocket instance 1288 | * @private 1289 | */ 1290 | function setCloseTimer(websocket) { 1291 | websocket._closeTimer = setTimeout( 1292 | websocket._socket.destroy.bind(websocket._socket), 1293 | closeTimeout 1294 | ); 1295 | } 1296 | 1297 | /** 1298 | * The listener of the socket `'close'` event. 1299 | * 1300 | * @private 1301 | */ 1302 | function socketOnClose() { 1303 | const websocket = this[kWebSocket]; 1304 | 1305 | this.removeListener('close', socketOnClose); 1306 | this.removeListener('data', socketOnData); 1307 | this.removeListener('end', socketOnEnd); 1308 | 1309 | websocket._readyState = WebSocket.CLOSING; 1310 | 1311 | let chunk; 1312 | 1313 | // 1314 | // The close frame might not have been received or the `'end'` event emitted, 1315 | // for example, if the socket was destroyed due to an error. Ensure that the 1316 | // `receiver` stream is closed after writing any remaining buffered data to 1317 | // it. If the readable side of the socket is in flowing mode then there is no 1318 | // buffered data as everything has been already written and `readable.read()` 1319 | // will return `null`. If instead, the socket is paused, any possible buffered 1320 | // data will be read as a single chunk. 1321 | // 1322 | if ( 1323 | !this._readableState.endEmitted && 1324 | !websocket._closeFrameReceived && 1325 | !websocket._receiver._writableState.errorEmitted && 1326 | (chunk = websocket._socket.read()) !== null 1327 | ) { 1328 | websocket._receiver.write(chunk); 1329 | } 1330 | 1331 | websocket._receiver.end(); 1332 | 1333 | this[kWebSocket] = undefined; 1334 | 1335 | clearTimeout(websocket._closeTimer); 1336 | 1337 | if ( 1338 | websocket._receiver._writableState.finished || 1339 | websocket._receiver._writableState.errorEmitted 1340 | ) { 1341 | websocket.emitClose(); 1342 | } else { 1343 | websocket._receiver.on('error', receiverOnFinish); 1344 | websocket._receiver.on('finish', receiverOnFinish); 1345 | } 1346 | } 1347 | 1348 | /** 1349 | * The listener of the socket `'data'` event. 1350 | * 1351 | * @param {Buffer} chunk A chunk of data 1352 | * @private 1353 | */ 1354 | function socketOnData(chunk) { 1355 | if (!this[kWebSocket]._receiver.write(chunk)) { 1356 | this.pause(); 1357 | } 1358 | } 1359 | 1360 | /** 1361 | * The listener of the socket `'end'` event. 1362 | * 1363 | * @private 1364 | */ 1365 | function socketOnEnd() { 1366 | const websocket = this[kWebSocket]; 1367 | 1368 | websocket._readyState = WebSocket.CLOSING; 1369 | websocket._receiver.end(); 1370 | this.end(); 1371 | } 1372 | 1373 | /** 1374 | * The listener of the socket `'error'` event. 1375 | * 1376 | * @private 1377 | */ 1378 | function socketOnError() { 1379 | const websocket = this[kWebSocket]; 1380 | 1381 | this.removeListener('error', socketOnError); 1382 | this.on('error', NOOP); 1383 | 1384 | if (websocket) { 1385 | websocket._readyState = WebSocket.CLOSING; 1386 | this.destroy(); 1387 | } 1388 | } 1389 | --------------------------------------------------------------------------------