UNPKG

dlovely-websocket

Version:

WebSocket For Dlovely

353 lines (352 loc) 13.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AppConnection = void 0; const crypto_1 = __importDefault(require("crypto")); const events_1 = require("events"); const common_1 = require("./common"); const frame = __importStar(require("./frame")); const InStream_1 = require("./InStream"); const OutStream_1 = require("./OutStream"); class AppConnection extends events_1.EventEmitter { token; server; socket; key; headers = {}; protocol; protocols; constructor(token, socket, server, onConnect) { super(); this.token = token; this.socket = socket; this.server = server; this.readyState = this.CONNECTING; this.socket.on('readable', () => this.doRead()); this.socket.on('error', err => this.emit('error', err)); const onclose = () => { if (this.readyState === this.CONNECTING || this.readyState === this.OPEN) { this.emit('close', 1006, ''); } this.readyState = this.CLOSED; if (this.frameBuffer instanceof InStream_1.InStream) { this.frameBuffer.end(); this.frameBuffer = ''; } if (this.outStream instanceof OutStream_1.OutStream) { this.outStream.end(); this.outStream = ''; } }; this.socket.once('close', onclose); this.socket.once('finish', onclose); if (onConnect) this.once('connect', onConnect); } extraParams = {}; sendSign(sign, data, callback) { if (this.readyState === this.OPEN) { if (!this.outStream) { common_1.toast.log(`向连接[${this.token}]传出:${sign}${data ? typeof data === 'string' ? `\t${data}` : Object.entries(data) .map(val => `\r\n${val[0]}:\t|${val[1]}`) .join('') : ''}`); return this.socket.write(frame.createTextFrame(JSON.stringify({ sign, data }), false), callback); } return this.emit('error', new Error('在发送完二进制帧之前,无法发送标签帧')); } return this.emit('error', new Error('无法向未打开的连接进行写入')); } sendText(str, callback) { if (this.readyState === this.OPEN) { if (!this.outStream) { common_1.toast.log(`向连接[${this.token}]传出:${str}`); return this.socket.write(frame.createTextFrame(str, false), callback); } return this.emit('error', new Error('在发送完二进制帧之前,无法发送文本帧')); } return this.emit('error', new Error('无法向未打开的连接进行写入')); } sendBinary(data, callback) { if (this.readyState === this.OPEN) { if (!this.outStream) { common_1.toast.log(`向连接[${this.token}]传出:二进制流`, data); return this.socket.write(frame.createBinaryFrame(data, false, true, true), callback); } return this.emit('error', new Error('在发送完之前的二进制帧之前,无法发送更多的二进制帧')); } return this.emit('error', new Error('无法向未打开的连接进行写入')); } send(data, callback) { if (typeof data === 'string') { this.sendText(data, callback); } else if (Buffer.isBuffer(data)) { this.sendBinary(data, callback); } else { throw new TypeError('数据应该是字符串或Buffer实例'); } } close(code, reason) { if (this.readyState === this.OPEN) { this.socket.write(frame.createCloseFrame(code, reason, false)); this.readyState = this.CLOSING; } else if (this.readyState !== this.CLOSED) { this.socket.end(); this.readyState = this.CLOSED; } this.emit('close', code, reason); } buffer = Buffer.alloc(0); doRead() { const buffer = this.socket.read(); if (!buffer) return; this.buffer = Buffer.concat([this.buffer, buffer], this.buffer.length + buffer.length); if (this.readyState === this.CONNECTING && !this.readHandshake()) return; if (this.readyState !== this.CLOSED) { let temp; while ((temp = this.extractFrame()) === true) { } if (temp === false) this.close(1002); else if (this.buffer.length > AppConnection.maxBufferLength) this.close(1009); } } readHandshake() { if (this.buffer.length > AppConnection.maxBufferLength) { this.socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); return false; } let i, found = false; for (i = 0; i < this.buffer.length - 3; i++) { if (this.buffer[i] === 13 && this.buffer[i + 2] === 13 && this.buffer[i + 1] === 10 && this.buffer[i + 3] === 10) { found = true; break; } } if (!found) return false; const data = this.buffer .slice(0, i + 4) .toString() .split('\r\n'); if (this.answerHandshake(data)) { this.buffer = this.buffer.slice(i + 4); this.readyState = this.OPEN; this.emit('connect'); return true; } else { this.socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); return false; } } answerHandshake(lines) { if (lines.length < 6) return false; if (!lines[0].match(/^GET (.+) HTTP\/\d\.\d$/i)) return false; this.readHeaders(lines); for (const head of [ 'host', 'sec-websocket-key', 'upgrade', 'connection' ]) { if (!this.headers[head]) return false; } if (this.headers.upgrade?.toLowerCase() !== 'websocket' || this.headers.connection ?.toLowerCase() .split(/\s*,\s*/) .indexOf('upgrade') === -1) return false; if (this.headers['sec-websocket-version'] !== '13') return false; this.key = this.headers['sec-websocket-key']; if (this.headers['sec-websocket-protocol']) { this.protocols = this.headers['sec-websocket-protocol'] ?.split(',') .map(each => each.trim()); if (this.server?._selectProtocol) { this.protocol = this.server._selectProtocol(this, this.protocols); } } const sha1 = crypto_1.default.createHash('sha1'); sha1.end(this.key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'); const key = sha1.read().toString('base64'); const headers = { upgrade: 'websocket', connection: 'Upgrade', 'sec-websocket-accept': key }; this.protocol && (headers['sec-websocket-protocol'] = this.protocol); this.socket.write(buildRequest('HTTP/1.1 101 Switching Protocols', headers)); return true; } readHeaders(lines) { lines.forEach(line => { const match = line.match(/^([a-z-]+): (.+)$/i); match && (this.headers[match[1].toLowerCase()] = match[2]); }); } frameBuffer = ''; outStream = ''; extractFrame() { if (this.buffer.length < 2) return; let B = this.buffer[0]; const HB = B >> 4; if (HB % 8) return false; const fin = HB === 8; const opcode = B % 16; if (![0, 1, 2, 8, 9, 10].includes(opcode)) return false; if (opcode >= 8 && !fin) return false; B = this.buffer[1]; const hasMask = B >> 7; if (this.server && !hasMask) return false; let len = B % 128; let start = hasMask ? 6 : 2; if (this.buffer.length < start + len) return; if (len === 126) { len = this.buffer.readUInt16BE(2); start += 2; } else if (len === 127) { len = this.buffer.readUInt32BE(2) * Math.pow(2, 32) + this.buffer.readUInt32BE(6); start += 8; } if (this.buffer.length < start + len) return; const payload = this.buffer.slice(start, start + len); if (hasMask) { const mask = this.buffer.slice(start - 4, start); for (let i = 0; i < payload.length; i++) { payload[i] ^= mask[i % 4]; } } this.buffer = this.buffer.slice(start + len); return this.processFrame(fin, opcode, payload); } processFrame(fin, opcode, payload) { if (opcode === 8) { if (this.readyState === this.CLOSING) this.socket.end(); else if (this.readyState === this.OPEN) this.processCloseFrame(payload); return true; } else if (opcode === 9) { if (this.readyState === this.OPEN) this.socket.write(frame.createPongFrame(payload.toString(), false)); return true; } else if (opcode === 10) { this.emit('pong', payload.toString()); return true; } if (this.readyState !== this.OPEN) return true; if (opcode === 0 && !this.frameBuffer) return false; else if (opcode !== 0 && this.frameBuffer) return false; if (typeof this.frameBuffer === 'string') { opcode = 1; const payload_string = payload.toString(); this.frameBuffer = this.frameBuffer ? this.frameBuffer + payload_string : payload_string; if (fin) { this.emit('text', this.frameBuffer); this.frameBuffer = ''; } } else { opcode = 2; if (!this.frameBuffer) { this.frameBuffer = new InStream_1.InStream(); this.emit('binary', this.frameBuffer); } this.frameBuffer.addData(payload); if (fin) { this.frameBuffer.end(); this.frameBuffer = ''; } } return true; } processCloseFrame(payload) { let code, reason; if (payload.length >= 2) { code = payload.readUInt16BE(0); reason = payload.slice(2).toString(); } else { code = 1005; reason = ''; } this.socket.write(frame.createCloseFrame(code, reason, false)); this.readyState = this.CLOSED; this.emit('close', code, reason); } readyState; CONNECTING = 0; OPEN = 1; CLOSING = 2; CLOSED = 3; static binaryFragmentation = 512 * 1024; static maxBufferLength = 2 * 1024 * 1024; } exports.AppConnection = AppConnection; function buildRequest(requestLine, headers) { let headerString = requestLine + '\r\n'; for (const [key, val] of Object.entries(headers)) { headerString += key + ': ' + val + '\r\n'; } return headerString + '\r\n'; }