From 29b068a1c4e8854449bb62c3a47ef7fb5a71d8c4 Mon Sep 17 00:00:00 2001 From: hippoz Date: Thu, 3 Dec 2020 23:14:49 +0200 Subject: [PATCH] add a little library for interacting with the gateway, will make it also interact with http apis soon! --- libbrainlet/gatewayconnection.js | 84 ++++++++++++++++++++ libbrainlet/index.js | 84 ++++++++++++++++++++ libbrainlet/package-lock.json | 128 +++++++++++++++++++++++++++++++ libbrainlet/package.json | 16 ++++ libbrainlet/test.js | 18 +++++ 5 files changed, 330 insertions(+) create mode 100644 libbrainlet/gatewayconnection.js create mode 100644 libbrainlet/index.js create mode 100644 libbrainlet/package-lock.json create mode 100644 libbrainlet/package.json create mode 100644 libbrainlet/test.js diff --git a/libbrainlet/gatewayconnection.js b/libbrainlet/gatewayconnection.js new file mode 100644 index 0000000..e818ec5 --- /dev/null +++ b/libbrainlet/gatewayconnection.js @@ -0,0 +1,84 @@ +const io = require('socket.io-client'); +const EventEmitter = require('events'); + +class GatewayConnection extends EventEmitter { + constructor(url) { + super(); + this.isConnected = false; + this.socket = null; + this.url = url; + } +} + +GatewayConnection.prototype.disconnect = function() { + if (this.socket) this.socket.disconnect(); + this.socket = null; + this.isConnected = false; +}; + +GatewayConnection.prototype.connect = function(token) { + console.log('[*] [gateway] [handshake] Trying to connect to gateway'); + this.socket = io(`${this.url}/gateway`, { + query: { + token + }, + transports: ['websocket'] + }); + + this.socket.on('connect', () => { + this.socket.once('hello', (debugInfo) => { + console.log('[*] [gateway] [handshake] Got hello from server, sending yoo...', debugInfo); + this.socket.emit('yoo'); + this.isConnected = true; + this.debugInfo = debugInfo; + this.emit('connect', { message: 'CONNECT_RECEIVED_HELLO' }); + console.log('[*] [gateway] [handshake] Assuming that server received yoo and that connection is completed.'); + }); + }) + + this.socket.on('error', (e) => { + console.log('[E] [gateway] Gateway error', e); + this.isConnected = false; + this.socket = null; + this.emit('disconnect', { message: 'DISCONNECT_ERR' }); + }); + this.socket.on('disconnectNotification', (e) => { + console.log('[E] [gateway] Received disconnect notfication', e); + this.isConnected = false; + this.socket = null; + this.emit('disconnect', { message: 'DISCONNECT_NOTIF', reason: e }); + }); + this.socket.on('disconnect', (e) => { + console.log('[E] [gateway] Disconnected from gateway: ', e); + this.isConnected = false; + this.emit('disconnect', { message: 'DISCONNECT', reason: e }); + }); + + this.socket.on('message', (e) => this.emit('message', e)); + this.socket.on('refreshClient', (e) => this.emit('refreshClient', e)); + this.socket.on('clientListUpdate', (e) => this.emit('clientListUpdate', e)); +}; + +GatewayConnection.prototype.sendMessage = function(categoryId, content) { + if (!this.isConnected) return 1; + if (content.length >= 2000) return 1; + + this.socket.emit('message', { + category: { + _id: categoryId + }, + content + }); +}; + +GatewayConnection.prototype.subscribeToCategoryChat = function(categoryId) { + if (!this.isConnected) return; + + const request = [categoryId]; + + console.log('[*] [gateway] Subscribing to channel(s)', request); + + this.socket.emit('subscribe', request); +}; + +module.exports = GatewayConnection; \ No newline at end of file diff --git a/libbrainlet/index.js b/libbrainlet/index.js new file mode 100644 index 0000000..ef30b5b --- /dev/null +++ b/libbrainlet/index.js @@ -0,0 +1,84 @@ +const GatewayConnection = require('./gatewayconnection'); +const fetch = require('node-fetch'); + +class Client { + constructor(url, config={}) { + this.token = null; + this.user = null; + this.isLoggedIn = false; + + this.url = url; + this.config = config; + + this.gateway = null; + } +} + +Client.prototype.gatewayConnect = async function() { + if (!this.token) throw new Error('Attempt to connect to gateway without being authenticated'); + + this.gateway = new GatewayConnection(this.url); + this.gateway.connect(this.token); +}; + +Client.prototype.gatewayDisconnect = async function() { + if (this.gateway) this.gateway.disconnect(); + this.gateway = null; +}; + +Client.prototype.sendAuthenticatedRequest = async function(endpoint, options) { + if (!this.token) throw new Error('Attempt to send authenticated request without being authenticated'); + + options.headers = { + cookie: `token=${this.token}`, + ...options.headers + }; + + let res; + let json; + let isOK = false; + + try { + res = await fetch(`${this.url}${endpoint}`, options); + json = await res.json(); + } catch(e) { + throw new Error(`Request to ${endpoint} failed with error`, e); + } + + if (res.ok && !json.error) { + isOK = true; + } else { + if (this.config.throwErrors) { + throw new Error(`Request to ${endpoint} failed with status ${res.status}`); + } + } + + return { res, json, isOK }; +}; + +Client.prototype.setToken = async function(token) { + this.token = token; + + const { json, isOK } = await this.sendAuthenticatedRequest('/api/v1/users/current/info', { + method: 'GET', + headers: { + 'Accept': 'application/json', + } + }); + + if (!isOK) throw new Error('Failed to get user info for setToken'); + + this.user = json.user; + this.userLoggedIn = true; + + console.log(`[*] Logged in as ${this.user.username}`); +}; + +Client.prototype.unsetToken = async function() { + this.token = null; + this.user = null; + this.gatewayDisconnect(); + this.userLoggedIn = false; +}; + +module.exports = Client; \ No newline at end of file diff --git a/libbrainlet/package-lock.json b/libbrainlet/package-lock.json new file mode 100644 index 0000000..51c5ef8 --- /dev/null +++ b/libbrainlet/package-lock.json @@ -0,0 +1,128 @@ +{ + "name": "libbrainlet", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==" + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "engine.io-client": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.4.tgz", + "integrity": "sha512-and4JRvjv+BQ4WBLopYUFePxju3ms3aBRk0XjaLdh/t9TKv2LCKtKKWFRoRzIfUZsu3U38FcYqNLuXhfS16vqw==", + "requires": { + "base64-arraybuffer": "0.1.4", + "component-emitter": "~1.3.0", + "debug": "~4.1.0", + "engine.io-parser": "~4.0.1", + "has-cors": "1.1.0", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~7.2.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + } + }, + "engine.io-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.1.tgz", + "integrity": "sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg==" + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "parseqs": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" + }, + "parseuri": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" + }, + "socket.io-client": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.3.tgz", + "integrity": "sha512-kwCJAKb6JMqE9ZYXg78Dgt8rYLSwtJ/g/LJqpb/pOTFRZMSr1cKAsCaisHZ+IBwKHBY7DYOOkjtkHqseY3ZLpw==", + "requires": { + "@types/component-emitter": "^1.2.10", + "backo2": "1.0.2", + "component-bind": "1.0.0", + "component-emitter": "~1.3.0", + "debug": "~4.1.0", + "engine.io-client": "~4.0.0", + "parseuri": "0.0.6", + "socket.io-parser": "~4.0.1" + } + }, + "socket.io-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.2.tgz", + "integrity": "sha512-Bs3IYHDivwf+bAAuW/8xwJgIiBNtlvnjYRc4PbXgniLmcP1BrakBoq/QhO24rgtgW7VZ7uAaswRGxutUnlAK7g==", + "requires": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.1.0" + } + }, + "ws": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==" + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + } + } +} diff --git a/libbrainlet/package.json b/libbrainlet/package.json new file mode 100644 index 0000000..48e55e1 --- /dev/null +++ b/libbrainlet/package.json @@ -0,0 +1,16 @@ +{ + "name": "libbrainlet", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "node-fetch": "^2.6.1", + "socket.io-client": "^3.0.3" + } +} diff --git a/libbrainlet/test.js b/libbrainlet/test.js new file mode 100644 index 0000000..fa65387 --- /dev/null +++ b/libbrainlet/test.js @@ -0,0 +1,18 @@ +const Client = require('./index'); + +const main = async () => { + const client = new Client('http://localhost:3000', { + throwErrors: true + }); + + await client.setToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2MDcwMjc5MzIsImV4cCI6MTYwNzAzODczMn0.9sHWxfPetp5efm11kaJP8wzsFfWjntVJ6COdqKGEuX4'); + + await client.gatewayConnect(); + + client.gateway.on('connect', () => { + client.gateway.subscribeToCategoryChat('5fc829314e96e00725c17fd8'); + client.gateway.on('message', console.log); + }); +}; + +main(); \ No newline at end of file