diff --git a/example/example.html b/example/example.html new file mode 100644 index 0000000..71d3a2b --- /dev/null +++ b/example/example.html @@ -0,0 +1,28 @@ + + + + + + Wormhole test + + + + + + + \ No newline at end of file diff --git a/example/example.js b/example/example.js new file mode 100644 index 0000000..bf83724 --- /dev/null +++ b/example/example.js @@ -0,0 +1,3 @@ +const Wormhole = require('../index'); + +const wormhole = new Wormhole({ urls: [ '/hello' ] }); \ No newline at end of file diff --git a/index.js b/index.js index e69de29..c843d41 100644 --- a/index.js +++ b/index.js @@ -0,0 +1,58 @@ +const http = require('http'); + +const { createLog } = require('./lib/logger'); +const handshake = require('./lib/handshake'); +const constants = require('./lib/constants'); +const Socket = require('./lib/Socket'); + +const handshakeLog = createLog([ 'Wormhole', 'Handshake' ]); + +class Wormhole { + constructor({ urls=[ '/bruh' ], port=8080 }) { + this._urls = urls; + this._port = port; + + this._httpServer = http.createServer((req, res) => { + if (req.method === 'GET' && req.url && this._urls.includes(req.url)) { + handshakeLog(`Got connection request to ${req.url} on port ${this._port}`); + + const failConnection = (status=400) => { + res.writeHead(status); + res.end(); + + console.trace(); + }; + + const websocketKey = req.headers['sec-websocket-key']; + const upgradeHeader = req.headers['upgrade']; + const websocketVersion = req.headers['sec-websocket-version']; + + if (upgradeHeader !== constants.upgradeHeaderRequirement) return failConnection(); + if (websocketVersion !== constants.websocketVersionRequirement) return failConnection(); + + const websocketAccept = handshake.generateWebsocketAcceptValue(websocketKey); + + handshakeLog(websocketKey, websocketAccept); + + if (websocketAccept) { + res.writeHead(101, { + 'Upgrade': 'websocket', + 'Connection': 'Upgrade', + 'Sec-WebSocket-Accept': websocketAccept + }); + res.end(); + + const socket = new Socket({ socket: res.socket, initalState: 'CONNECTED' }); + + return true; + } + + return failConnection(); + } + }); + + this._httpServer.listen(port); + } +} + +module.exports = Wormhole; \ No newline at end of file diff --git a/lib/Socket.js b/lib/Socket.js new file mode 100644 index 0000000..4e248a3 --- /dev/null +++ b/lib/Socket.js @@ -0,0 +1,12 @@ +class Socket { + constructor({ initialState='CONNECTING', socket }) { + this._state = initialState; + this._socket = socket; + + this._socket.on('data', (e) => { + console.log(e.toString()); + }); + } +} + +module.exports = Socket; \ No newline at end of file diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 0000000..1830419 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,5 @@ +module.exports = { + handshakeGUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', + upgradeHeaderRequirement: 'websocket', + websocketVersionRequirement: '13' +} \ No newline at end of file diff --git a/lib/handshake.js b/lib/handshake.js new file mode 100644 index 0000000..f6abb61 --- /dev/null +++ b/lib/handshake.js @@ -0,0 +1,17 @@ +const crypto = require('crypto'); + +const { handshakeGUID } = require('./constants'); + +const generateWebsocketAcceptValue = (websocketKey) => { + if (typeof websocketKey !== 'string' || typeof handshakeGUID !== 'string') { + // TODO: maybe throw error? + return; + } + + const concatenated = websocketKey + handshakeGUID; + const sha1HashedInBase64 = crypto.createHash('sha1').update(concatenated, 'binary').digest('base64'); + + return sha1HashedInBase64; +}; + +module.exports = { generateWebsocketAcceptValue }; \ No newline at end of file diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..9dad21c --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,16 @@ +const logDebug = true; + +module.exports = { + createLog: (components) => (...args) => { + let compString = ''; + for (const i in components) { + const component = components[i]; + if (i >= (components.length - 1)) { + compString += `[${component}]`; + } else { + compString += `[${component}] `; + } + } + logDebug && console.log(compString, ...args); + } +}; \ No newline at end of file