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 = ``;
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 = ``;
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 | [](https://www.npmjs.com/package/ws)
4 | [](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)
5 | [](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 |
--------------------------------------------------------------------------------