add websocket server to backend, client connects to it
This commit is contained in:
parent
98b06181bf
commit
ba74f0a430
|
@ -7,7 +7,7 @@
|
|||
"request": "launch",
|
||||
|
||||
// Debug current file in VSCode
|
||||
"program": "${workspaceFolder}/server/bin/www.js",
|
||||
"program": "${workspaceFolder}/server/app.ts",
|
||||
|
||||
"cwd": "${workspaceFolder}/server/",
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
export { RoomClient } from "./room";
|
||||
|
||||
export const PatchMessageKind: string = "patch";
|
||||
export class PatchMessage {
|
||||
public static readonly Kind = "patch";
|
||||
public readonly kind = PatchMessage.Kind;
|
||||
|
||||
public constructor() {}
|
||||
}
|
||||
|
||||
export const ErrorMessageKind: string = "error";
|
||||
export class ErrorMessage {
|
||||
public static readonly Kind = "error";
|
||||
public readonly kind = ErrorMessage.Kind;
|
||||
|
||||
public constructor(public readonly message: string) {}
|
||||
}
|
||||
|
||||
export type TaggedMessage = PatchMessage | ErrorMessage;
|
|
@ -0,0 +1,46 @@
|
|||
import {
|
||||
ErrorMessage,
|
||||
ErrorMessageKind,
|
||||
PatchMessage,
|
||||
PatchMessageKind,
|
||||
TaggedMessage,
|
||||
} from ".";
|
||||
|
||||
export class RoomClient {
|
||||
public constructor(private sender: (tm: TaggedMessage) => void) {}
|
||||
public handleMessage(messageStr: Buffer | ArrayBuffer | Buffer[]) {
|
||||
try {
|
||||
const message = JSON.parse(messageStr.toString());
|
||||
|
||||
switch (message.kind) {
|
||||
case PatchMessageKind:
|
||||
this.handlePatch(message);
|
||||
case ErrorMessageKind:
|
||||
this.handleError(message);
|
||||
default:
|
||||
console.log(`unhandled message kind: ${message.kind}`);
|
||||
}
|
||||
} catch (e) {
|
||||
this.sendError(`Failed to handle message: ${e}`);
|
||||
console.error(`Failed to handle message '${messageStr}': ${e}`);
|
||||
}
|
||||
}
|
||||
public handleError(error: Error) {
|
||||
console.log(`received error: ${error}`);
|
||||
}
|
||||
public handleClose() {
|
||||
console.log(`received close`);
|
||||
}
|
||||
|
||||
private handlePatch(patch: PatchMessage) {
|
||||
console.log("received patch");
|
||||
}
|
||||
|
||||
private handleErrorMessage(error: ErrorMessage) {
|
||||
console.log(`received error: `);
|
||||
}
|
||||
|
||||
private sendError(message: string) {
|
||||
this.sender(new ErrorMessage(message));
|
||||
}
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
{
|
||||
"name": "graphthing-fe",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@types/cytoscape": "^3.21.0",
|
||||
"typescript": "^5.4.3",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"cytoscape": "^3.28.1",
|
||||
"ts-loader": "^9.5.1"
|
||||
}
|
||||
"name": "graphthing-fe",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@types/cytoscape": "^3.21.0",
|
||||
"typescript": "^5.4.3",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"cytoscape": "^3.28.1",
|
||||
"ts-loader": "^9.5.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import * as cytoscape from "cytoscape";
|
||||
import { RoomClient } from "../../common";
|
||||
|
||||
export class ThingGraph {
|
||||
private cy;
|
||||
private client: RoomClient;
|
||||
private socket: WebSocket;
|
||||
public constructor() {
|
||||
this.cy = cytoscape({
|
||||
container: document.getElementById("cy"),
|
||||
|
@ -17,5 +20,13 @@ export class ThingGraph {
|
|||
},
|
||||
],
|
||||
});
|
||||
|
||||
this.socket = new WebSocket("ws://localhost:3000");
|
||||
this.client = new RoomClient((tm) => {
|
||||
this.socket.send(JSON.stringify(tm));
|
||||
});
|
||||
this.socket.addEventListener("message", (event) => {
|
||||
this.client.handleMessage(event.data.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,9 @@ importers:
|
|||
express:
|
||||
specifier: ^4.19.2
|
||||
version: 4.19.2
|
||||
graphthing-common:
|
||||
specifier: workspace:../common
|
||||
version: link:../common
|
||||
graphthing-fe:
|
||||
specifier: workspace:../frontend
|
||||
version: link:../frontend
|
||||
|
@ -50,6 +53,9 @@ importers:
|
|||
morgan:
|
||||
specifier: ~1.9.1
|
||||
version: 1.9.1
|
||||
nanoid:
|
||||
specifier: ^5.0.6
|
||||
version: 5.0.6
|
||||
pug:
|
||||
specifier: 2.0.0-beta11
|
||||
version: 2.0.0-beta11
|
||||
|
@ -59,6 +65,9 @@ importers:
|
|||
tsx:
|
||||
specifier: ^4.7.1
|
||||
version: 4.7.1
|
||||
ws:
|
||||
specifier: ^8.16.0
|
||||
version: 8.16.0
|
||||
devDependencies:
|
||||
'@types/cookie-parser':
|
||||
specifier: ^1.4.7
|
||||
|
@ -66,6 +75,9 @@ importers:
|
|||
'@types/express':
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
'@types/express-ws':
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4
|
||||
'@types/http-errors':
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
|
@ -78,6 +90,9 @@ importers:
|
|||
'@types/webpack-dev-middleware':
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0(webpack@5.91.0)
|
||||
'@types/ws':
|
||||
specifier: ^8.5.10
|
||||
version: 8.5.10
|
||||
typescript:
|
||||
specifier: ^5.4.3
|
||||
version: 5.4.3
|
||||
|
@ -392,6 +407,14 @@ packages:
|
|||
'@types/send': 0.17.4
|
||||
dev: true
|
||||
|
||||
/@types/express-ws@3.0.4:
|
||||
resolution: {integrity: sha512-Yjj18CaivG5KndgcvzttWe8mPFinPCHJC2wvyQqVzA7hqeufM8EtWMj6mpp5omg3s8XALUexhOu8aXAyi/DyJQ==}
|
||||
dependencies:
|
||||
'@types/express': 4.17.21
|
||||
'@types/express-serve-static-core': 4.17.43
|
||||
'@types/ws': 8.5.10
|
||||
dev: true
|
||||
|
||||
/@types/express@4.17.21:
|
||||
resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==}
|
||||
dependencies:
|
||||
|
@ -462,6 +485,12 @@ packages:
|
|||
- webpack-cli
|
||||
dev: true
|
||||
|
||||
/@types/ws@8.5.10:
|
||||
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
|
||||
dependencies:
|
||||
'@types/node': 20.12.4
|
||||
dev: true
|
||||
|
||||
/@webassemblyjs/ast@1.12.1:
|
||||
resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
|
||||
dependencies:
|
||||
|
@ -1508,6 +1537,12 @@ packages:
|
|||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
dev: false
|
||||
|
||||
/nanoid@5.0.6:
|
||||
resolution: {integrity: sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==}
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/negotiator@0.6.3:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -2254,6 +2289,19 @@ packages:
|
|||
engines: {node: '>=0.4.0'}
|
||||
dev: false
|
||||
|
||||
/ws@8.16.0:
|
||||
resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
dev: false
|
||||
|
|
117
server/app.ts
117
server/app.ts
|
@ -1,54 +1,119 @@
|
|||
import createError from 'http-errors';
|
||||
import express from 'express';
|
||||
import path from 'path';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import logger from 'morgan';
|
||||
import WebSocket from "ws";
|
||||
import path from "path";
|
||||
import cookieParser from "cookie-parser";
|
||||
import logger from "morgan";
|
||||
import http from "http";
|
||||
|
||||
import webpack from 'webpack';
|
||||
import webpackDevMiddleware from 'webpack-dev-middleware';
|
||||
import webpack from "webpack";
|
||||
import webpackDevMiddleware from "webpack-dev-middleware";
|
||||
import { RoomManager } from "./src/rooms";
|
||||
|
||||
let webpackConfig = require("./node_modules/graphthing-fe/webpack.config");
|
||||
const webpackCompiler = webpack(webpackConfig);
|
||||
|
||||
var indexRouter = require('./routes/index');
|
||||
var usersRouter = require('./routes/users');
|
||||
var indexRouter = require("./routes/index");
|
||||
var usersRouter = require("./routes/users");
|
||||
|
||||
var app = express();
|
||||
const port = normalizePort(process.env.PORT || "3000");
|
||||
app.set("port", port);
|
||||
|
||||
const server = http.createServer(app);
|
||||
const wss = new WebSocket.Server({ server });
|
||||
const rooms = new RoomManager(wss);
|
||||
server.listen(port);
|
||||
server.on("error", onError);
|
||||
server.on("listening", onListening);
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'pug');
|
||||
app.set("views", path.join(__dirname, "views"));
|
||||
app.set("view engine", "pug");
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(logger("dev"));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
|
||||
app.use(
|
||||
'/dist/',
|
||||
webpackDevMiddleware(webpackCompiler, {
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
})
|
||||
"/dist/",
|
||||
webpackDevMiddleware(webpackCompiler, {
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
})
|
||||
);
|
||||
|
||||
app.use('/', indexRouter);
|
||||
app.use('/users', usersRouter);
|
||||
app.use("/", indexRouter);
|
||||
app.use("/users", usersRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
next(createError(404));
|
||||
app.use(function (req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
app.use(function (err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get("env") === "development" ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render("error");
|
||||
});
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== "listen") {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === "string" ? "Pipe " + port : "Port " + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case "EACCES":
|
||||
console.error(bind + " requires elevated privileges");
|
||||
process.exit(1);
|
||||
break;
|
||||
case "EADDRINUSE":
|
||||
console.error(bind + " is already in use");
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr?.port;
|
||||
console.log("Listening on " + bind);
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
|
||||
|
|
|
@ -1,35 +1,40 @@
|
|||
{
|
||||
"name": "graphthing",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "tsx ./bin/www",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/http-errors": "^2.0.4",
|
||||
"@types/node": "^20.12.4",
|
||||
"@types/webpack": "^5.28.5",
|
||||
"@types/webpack-dev-middleware": "^5.3.0",
|
||||
"typescript": "^5.4.3",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-middleware": "^7.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"express": "^4.19.2",
|
||||
"graphthing-fe": "workspace:../frontend",
|
||||
"http-errors": "~1.6.3",
|
||||
"morgan": "~1.9.1",
|
||||
"pug": "2.0.0-beta11",
|
||||
"save-dev": "0.0.1-security",
|
||||
"tsx": "^4.7.1"
|
||||
}
|
||||
"name": "graphthing",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "tsx ./bin/www",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-ws": "^3.0.4",
|
||||
"@types/http-errors": "^2.0.4",
|
||||
"@types/node": "^20.12.4",
|
||||
"@types/webpack": "^5.28.5",
|
||||
"@types/webpack-dev-middleware": "^5.3.0",
|
||||
"@types/ws": "^8.5.10",
|
||||
"typescript": "^5.4.3",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-middleware": "^7.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"express": "^4.19.2",
|
||||
"graphthing-fe": "workspace:../frontend",
|
||||
"graphthing-common": "workspace:../common",
|
||||
"http-errors": "~1.6.3",
|
||||
"morgan": "~1.9.1",
|
||||
"nanoid": "^5.0.6",
|
||||
"pug": "2.0.0-beta11",
|
||||
"save-dev": "0.0.1-security",
|
||||
"tsx": "^4.7.1",
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
console.log("hi");
|
|
@ -0,0 +1,33 @@
|
|||
import WebSocket from "ws";
|
||||
import { RoomClient } from "../../common";
|
||||
|
||||
export class RoomManager {
|
||||
private clients: RoomClient[] = [];
|
||||
public constructor(wss: WebSocket.Server) {
|
||||
wss.on("connection", (ws: WebSocket) => {
|
||||
const c = new RoomClient((tm) => {
|
||||
ws.send(JSON.stringify(tm));
|
||||
});
|
||||
ws.on("message", (msg: WebSocket.RawData) => {
|
||||
c.handleMessage(msg);
|
||||
});
|
||||
ws.on("error", (e) => {
|
||||
c.handleError(e);
|
||||
});
|
||||
ws.on("close", () => {
|
||||
c.handleClose();
|
||||
const closingClientIndex = this.clients.indexOf(c);
|
||||
if (closingClientIndex !== -1) {
|
||||
this.clients.splice(closingClientIndex, 1);
|
||||
console.log("removed a client");
|
||||
} else {
|
||||
console.error(
|
||||
`unexpected, closed a client that isn't in the list`
|
||||
);
|
||||
}
|
||||
});
|
||||
this.clients.push(c);
|
||||
console.log("added a client");
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue