Skip to content

Commit 27b1ff0

Browse files
committed
Initial commit
0 parents  commit 27b1ff0

23 files changed

+4602
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.env
2+
node_modules
3+
dist
4+
.vscode

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

.prettierrc

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"semi": true,
3+
"singleQuote": true,
4+
"trailingComma": "all",
5+
"printWidth": 120,
6+
"tabWidth": 4,
7+
"useTabs": false
8+
}

jest.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
transform: {
4+
"^.+\\.ts$": "ts-jest"
5+
},
6+
};

package.json

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "rvsc-server",
3+
"version": "1.0.0",
4+
"description": "Server for Remote Visual Studio Code.",
5+
"scripts": {
6+
"build": "webpack",
7+
"test": "jest",
8+
"start": "yarn build && node dist/server.js"
9+
},
10+
"main": "dist/server.js",
11+
"author": "Exortions",
12+
"license": "MIT",
13+
"private": true,
14+
"dependencies": {
15+
"axios": "^0.27.2",
16+
"body-parser": "^1.20.0",
17+
"chalk": "^5.0.1",
18+
"cors": "^2.8.5",
19+
"express": "^4.18.1",
20+
"fs-extra": "^10.1.0",
21+
"jsonwebtoken": "^8.5.1",
22+
"lodash": "^4.17.21",
23+
"mongoose": "^6.6.3",
24+
"socket.io": "^4.5.2",
25+
"socket.io-client": "^4.5.2",
26+
"uuid": "^9.0.0"
27+
},
28+
"devDependencies": {
29+
"@types/express": "^4.17.14",
30+
"@types/jest": "^29.1.1",
31+
"@types/jsonwebtoken": "^8.5.9",
32+
"@types/node": "^18.7.23",
33+
"@types/socket.io": "^3.0.2",
34+
"@types/socket.io-client": "^3.0.0",
35+
"@types/supertest": "^2.0.12",
36+
"@types/uuid": "^8.3.4",
37+
"dotenv-webpack": "^8.0.1",
38+
"jest": "^29.1.2",
39+
"supertest": "^6.2.4",
40+
"ts-jest": "^29.0.3",
41+
"ts-loader": "^9.4.1",
42+
"typescript": "^4.8.4",
43+
"webpack": "^5.74.0",
44+
"webpack-cli": "^4.10.0",
45+
"webpack-node-externals": "^3.0.0"
46+
}
47+
}

src/database/database.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import mongoose from 'mongoose';
2+
3+
export async function connect(): Promise<boolean> {
4+
const uri: string | undefined = process.env.MONGO_URI;
5+
6+
if (!uri) throw new Error('MONGO_URI is not defined');
7+
8+
try {
9+
await mongoose.connect(uri);
10+
11+
return true;
12+
} catch (err) {
13+
return false;
14+
}
15+
}

src/models/Session.ts

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import mongoose, { Schema } from 'mongoose';
2+
3+
const SessionSchema = new Schema({
4+
sid: {
5+
type: String,
6+
required: true,
7+
unique: true,
8+
},
9+
password: {
10+
type: String,
11+
required: true,
12+
},
13+
expires: {
14+
type: Date,
15+
required: true,
16+
},
17+
users_connected: [
18+
{
19+
user: {
20+
type: String,
21+
required: true,
22+
},
23+
permission: {
24+
type: String,
25+
required: true,
26+
validate: {
27+
validator: (v: string) => {
28+
return v === 'admin' || v === 'editor' || v === 'viewer';
29+
},
30+
},
31+
},
32+
},
33+
],
34+
});
35+
36+
export type UserPermission = 'viewer' | 'editor' | 'admin';
37+
38+
export type SessionLike = mongoose.Document & {
39+
sid: string;
40+
password: string;
41+
expires: Date;
42+
users_connected: { user: string; permission: UserPermission }[];
43+
};
44+
45+
export type SessionAuth = {
46+
sid: string;
47+
password: string;
48+
};
49+
50+
const Session = mongoose.model<SessionLike>('Session', SessionSchema);
51+
52+
export async function validate(session: SessionAuth): Promise<boolean> {
53+
const found = Session.findOne({ sid: session.sid, password: session.password });
54+
55+
return found !== null && found !== undefined;
56+
}
57+
58+
export async function find(session: SessionAuth): Promise<SessionLike | null> {
59+
if (!(await validate(session))) return null;
60+
61+
const found = await Session.findOne({ sid: session.sid, password: session.password });
62+
63+
return found;
64+
}
65+
66+
export async function create(session: SessionAuth & { expires: Date }): Promise<SessionLike> {
67+
const created = new Session(session);
68+
69+
await created.save();
70+
71+
return created;
72+
}
73+
74+
export async function remove(session: SessionAuth): Promise<boolean> {
75+
if (!(await validate(session))) return false;
76+
77+
await Session.deleteOne({ sid: session.sid, password: session.password });
78+
79+
return true;
80+
}
81+
82+
export async function add_user(
83+
session: SessionAuth,
84+
user: { user: string; permission: UserPermission },
85+
): Promise<boolean> {
86+
if (!(await validate(session))) return false;
87+
88+
const found = await Session.findOne({ sid: session.sid, password: session.password });
89+
90+
if (found === null || !found) return false;
91+
92+
found.users_connected.push(user);
93+
94+
await found.save();
95+
96+
return true;
97+
}
98+
99+
export async function remove_user(session: SessionAuth, user: string): Promise<boolean> {
100+
if (!(await validate(session))) return false;
101+
102+
const found = await Session.findOne({ sid: session.sid, password: session.password });
103+
104+
if (found === null || !found) return false;
105+
106+
found.users_connected = found.users_connected.filter((u) => u.user !== user);
107+
108+
await found.save();
109+
110+
return true;
111+
}
112+
113+
export async function get_users(session: SessionAuth): Promise<{ user: string; permission: UserPermission }[] | null> {
114+
if (!(await validate(session))) return null;
115+
116+
const found = await Session.findOne({ sid: session.sid, password: session.password });
117+
118+
if (found === null || !found || !found.users_connected) return null;
119+
120+
return found.users_connected;
121+
}
122+
123+
export default Session;

src/server.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { Request, Response } from 'express';
2+
// @ts-ignore - weird error, not recognizing socket.io types
3+
import { Server } from 'socket.io';
4+
import parser from 'body-parser';
5+
import express from 'express';
6+
import cors from 'cors';
7+
8+
// modules
9+
import { connect } from './database/database';
10+
import loadTokens from './token/tokens';
11+
import socket from './socket/socket';
12+
import SocketEvent from './socket/SocketEvent';
13+
14+
// events
15+
import DeleteSessionEvent from './socket/events/session/delete-session';
16+
import CreateSessionEvent from './socket/events/session/create-session';
17+
import UserConnectEvent from './socket/events/user/user-connect';
18+
import GenerateTokenEvent from './socket/events/token/generate';
19+
import UserKickEvent from './socket/events/user/kick-user';
20+
import DisconnectEvent from './socket/events/disconnect';
21+
22+
const app: express.Application = express();
23+
const port: number = Number(process.env.PORT) || 3000;
24+
25+
app.use(parser.urlencoded({ extended: true }));
26+
app.use(parser.json());
27+
app.use(cors({ origin: '*' }));
28+
29+
loadTokens(app);
30+
31+
app.get('/', (_req: Request, res: Response) => {
32+
res.json({
33+
hello: 'world',
34+
});
35+
});
36+
37+
let connected: boolean = false;
38+
let io: Server;
39+
let serv: any;
40+
41+
(async () => {
42+
const events: SocketEvent<any>[] = [];
43+
44+
events.push(new DeleteSessionEvent());
45+
events.push(new CreateSessionEvent());
46+
events.push(new UserConnectEvent());
47+
events.push(new GenerateTokenEvent());
48+
events.push(new UserKickEvent());
49+
events.push(new DisconnectEvent());
50+
51+
io = await socket(events);
52+
53+
connected = await connect();
54+
55+
if (!connected) {
56+
console.error('Could not connect to database');
57+
process.exit(1);
58+
} else {
59+
console.log('Connected to database');
60+
}
61+
62+
if (require.main === module) {
63+
serv = app.listen(port, () => {
64+
console.log(`Started server on port ${port}`);
65+
});
66+
}
67+
})();
68+
69+
export default app;
70+
export { io };
71+
export function stopExpress(): void {
72+
if (serv) serv.close();
73+
}

src/socket/SocketEvent.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// @ts-ignore - weird error, not recognizing socket.io types
2+
import { Socket, Server } from 'socket.io';
3+
4+
export default class SocketEvent<T> {
5+
private _name: string;
6+
private _callback: (data: T) => void;
7+
private _socket: Socket | null;
8+
private _server: Server | null;
9+
10+
constructor(name: string, callback: (data: T) => void, socket?: Socket) {
11+
this._name = name;
12+
this._callback = callback;
13+
this._socket = socket || null;
14+
}
15+
16+
public fire(socket: Socket, server: Server): (data: T) => void {
17+
this._socket = socket;
18+
this._server = server;
19+
20+
return this._callback;
21+
}
22+
23+
get name(): string {
24+
return this._name;
25+
}
26+
27+
get callback(): (data: string) => void {
28+
return (d: string) => {
29+
this._callback(JSON.parse(d));
30+
};
31+
}
32+
33+
set socket(socket: Socket | null) {
34+
this._socket = socket;
35+
}
36+
37+
get socket(): Socket | null {
38+
return this._socket;
39+
}
40+
41+
set server(server: Server | null) {
42+
this._server = server;
43+
}
44+
45+
get server(): Server | null {
46+
return this._server;
47+
}
48+
}

src/socket/events/disconnect.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import SocketEvent from '../SocketEvent';
2+
3+
export default class DisconnectEvent extends SocketEvent<{}> {
4+
constructor() {
5+
super('disconnect', (_data: {}) => {
6+
if (!this.socket) return;
7+
8+
console.log(`Socket disconnected: ${this.socket.id}`);
9+
10+
this.socket.removeAllListeners();
11+
});
12+
}
13+
}

0 commit comments

Comments
 (0)