diff --git a/package-lock.json b/package-lock.json index 5ce4564..f85465f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,11 @@ "@types/node": "*" } }, + "@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==" + }, "@types/express": { "version": "4.17.12", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz", @@ -118,6 +123,11 @@ "@types/node": "*" } }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==" + }, "@types/ws": { "version": "7.4.5", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.5.tgz", @@ -434,6 +444,15 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1006,6 +1025,11 @@ "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -1482,6 +1506,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 4f0d340..3056090 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,12 @@ "author": "", "license": "ISC", "dependencies": { + "@types/cors": "^2.8.10", + "@types/uuid": "^8.3.0", "@types/ws": "^7.4.5", + "cors": "^2.8.5", "express": "^4.17.1", + "uuid": "^8.3.2", "ws": "^7.5.0" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 96ac0e4..65e7dcb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,27 +1,34 @@ import express, { Request, Response } from "express"; import * as http from 'http'; import WebSocket from "ws" +import { encodeText } from "./rtmt/byte_util"; +import { encode } from "./rtmt/encode_decode_message"; +import { RTMTWS } from "./rtmt/rtmt_websocket"; +import cors from "cors" +import { generateKey } from "./key_factory"; const app = express(); +app.use(cors()) + const server = http.createServer(app); -const ws = new WebSocket.Server({server}) - -ws.on("connection", (ws : WebSocket)=>{ - ws.on("message", (message : string)=>{ - console.log('received: %s', message); - ws.send(`Hello, you sent -> ${message}`); - }) - ws.send('Hi there, I am a WebSocket server'); -}) - app.get("/", (req: Request, res: Response) => { res.send("Hello World"); }); +app.get("/register/", (req: Request, res: Response) => { + res.send(generateKey()); + }); + const port = process.env.PORT || 5000 server.listen(port, () => { console.log(`Server started on port ${port} :)`); }) + +const rtmt = new RTMTWS() + +rtmt.initWebSocket(server, ()=>{ + +}) \ No newline at end of file diff --git a/src/key_factory.ts b/src/key_factory.ts new file mode 100644 index 0000000..fd05c2f --- /dev/null +++ b/src/key_factory.ts @@ -0,0 +1,6 @@ +import { v4 as uuidv4 } from 'uuid'; + + +export const generateKey = ()=>{ + return uuidv4(); +} \ No newline at end of file diff --git a/src/mancala.ts b/src/mancala.ts new file mode 100644 index 0000000..3a9cd7d --- /dev/null +++ b/src/mancala.ts @@ -0,0 +1,8 @@ +type User = { + id : Number, +} + +type Game = { + user1 : User, + user2 : User, +} \ No newline at end of file diff --git a/src/rtmt/byte_util.ts b/src/rtmt/byte_util.ts new file mode 100644 index 0000000..e803ada --- /dev/null +++ b/src/rtmt/byte_util.ts @@ -0,0 +1,14 @@ +import { Bytes } from "./rtmt" +var util= require('util'); + +const textEncoder = new util.TextEncoder() +const textDecoder = new util.TextDecoder("utf-8") + +export function encodeText(text : string) { + const bytes = textEncoder.encode(text) + return bytes +} + +export function decodeText(bytes : Bytes) { + return textDecoder.decode(bytes) +} \ No newline at end of file diff --git a/src/rtmt/encode_decode_message.ts b/src/rtmt/encode_decode_message.ts new file mode 100644 index 0000000..119c323 --- /dev/null +++ b/src/rtmt/encode_decode_message.ts @@ -0,0 +1,58 @@ +import { decodeText, encodeText } from "./byte_util"; +import { Bytes } from "./rtmt"; + +const headerLenght = 4 + +export type Message = { + channel : string, + data : Bytes, +} +// +// channel is string, message is byte array +// +export function encode(message : Message) { + const {channel, data} = message + const channelLenght = channel.length + const messageLenght = data.length + const totalLenght = headerLenght + channelLenght + messageLenght + + const buffer = new ArrayBuffer(totalLenght); + const view = new DataView(buffer); + + view.setUint32(0, channelLenght); + + const channelBytes = encodeText(channel) + + let count = headerLenght + channelBytes.forEach((byte : any)=>{ + view.setUint8(count, byte) + count++ + }) + + for (const byte of data) { + view.setUint8(count, byte) + count++ + } + + return buffer +} + +// +// return { channel : string, message : byte array} +// +export function decode(bytes : Bytes) : Message { + const view = new DataView(bytes.buffer); + + const channelLenght = view.getUint32(0) + + + const channel = decodeText( + bytes.slice(headerLenght, headerLenght + channelLenght)) + + const message = bytes.slice(headerLenght + channelLenght) + + return { + "channel": channel, + "data": message, + } +} diff --git a/src/rtmt/rtmt.ts b/src/rtmt/rtmt.ts new file mode 100644 index 0000000..fa83f10 --- /dev/null +++ b/src/rtmt/rtmt.ts @@ -0,0 +1,7 @@ +export type Bytes = UInt8Array +export type OnMessage = (clientID : string, message : Bytes) => any + +export interface RTMT{ + sendMessage : (clientID : string, channel : string, message : Bytes) => any + listenMessage : (channel : string, callback : OnMessage) => any +} \ No newline at end of file diff --git a/src/rtmt/rtmt_websocket.ts b/src/rtmt/rtmt_websocket.ts new file mode 100644 index 0000000..0e96386 --- /dev/null +++ b/src/rtmt/rtmt_websocket.ts @@ -0,0 +1,81 @@ +import { decode, encode } from "./encode_decode_message" +import { Bytes, OnMessage, RTMT } from "./rtmt" + +import WebSocket from "ws" +import * as http from 'http'; + +export class RTMTWS implements RTMT { + + private messageChannels: Map + + private wsServer: WebSocket.Server | null + + public clients = new Map() + + constructor() { + this.messageChannels = new Map() + this.wsServer = null + } + + initWebSocket(server: http.Server, onopen: () => any) { + const wsServer = new WebSocket.Server({ server }) + this.clients = new Map() + + wsServer.on("connection", (ws: WebSocket, req: Request) => { + console.log(req.url); + const regexResult = req.url.split(RegExp("\/\?userKey=")); + const clientID = regexResult[1] + console.log(regexResult); + this.clients.set(clientID, ws) + ws.on("message", (messageBytes: Bytes) => { + console.log('received: %s', messageBytes); + this.onWebSocketMessage("0", messageBytes) + }) + + ws.on("close", (code: number, reason: string) => { + console.log("WS Closed! code : " + code + " reason : " + reason); + this.clients.delete(clientID) + }) + + ws.on("error", (err: Error) => { + console.log("WS Closed with error! error : " + err.message); + this.clients.delete(clientID) + }) + }) + + } + + sendMessage(clientID: string, channel: string, message: Bytes) { + if (this.wsServer) { + const client = this.clients.get(clientID) + if (client) { + const data = encode({ channel: channel, data: message }) + console.log("(RTMT) Sending message to channel " + channel); + client.send(data) + } else { + console.log("Client not connected!"); + } + } else { + console.log('ws is undefined') + } + } + + listenMessage(channel: string, callback: OnMessage) { + this.messageChannels.set(channel, callback) + } + + onWebSocketMessage(clientID: string, messageBytes: Bytes) { + const message = decode(messageBytes) + this.onMessage(clientID, message.channel, message.data) + + } + + onMessage(clientID: string, channel: string, message: Int8Array) { + const callback = this.messageChannels.get(channel) + if (callback) { + callback(clientID, message) + } else { + console.log("Channel callback not found! " + channel); + } + } +} \ No newline at end of file