166 lines
5.5 KiB
TypeScript
166 lines
5.5 KiB
TypeScript
import * as socketio from "socket.io";
|
|
import { Server } from 'http';
|
|
import { JWT_SECRET, logger } from "../app";
|
|
import { randomString, verifyJWT } from "./crypto";
|
|
import { decode, sign } from "jsonwebtoken";
|
|
import { User } from "../models/user/user.model";
|
|
import { IPhone } from "../models/phone/phone.interface";
|
|
import { IUser } from "../models/user/user.interface";
|
|
import { Phone } from "../models/phone/phone.model";
|
|
|
|
/**
|
|
* This class handles all SocketIO connections.
|
|
*
|
|
* *SocketIO is another layer ontop of WebSockets*
|
|
*/
|
|
export class SocketManager {
|
|
|
|
io: socketio.Server;
|
|
|
|
/**
|
|
* Frontends have limited access to socket.io features. They just sit in connection and wait for any events.
|
|
*/
|
|
frontends: Array<string>;
|
|
|
|
/**
|
|
* A phone has some more privileges. They activly send new data and thus have write access.
|
|
*/
|
|
phones: Array<string>;
|
|
|
|
constructor(httpServer: Server) {
|
|
logger.debug("Preparing real-time communication ...");
|
|
|
|
this.frontends = [];
|
|
this.phones = [];
|
|
|
|
this.io = new socketio.Server();
|
|
this.io.listen(httpServer);
|
|
|
|
this.init();
|
|
}
|
|
|
|
getUserRoom(user: IUser) {
|
|
return `user-${user.id}`;
|
|
}
|
|
|
|
getUserFrontendRoom(user: IUser) {
|
|
return `user-${user.id}-frontend`;
|
|
}
|
|
|
|
getUserPhoneRoom(user: IUser) {
|
|
return `user-${user.id}-phone`;
|
|
}
|
|
|
|
init() {
|
|
this.io.on('connection', socket => {
|
|
socket.on('requestAccess', async data => {
|
|
data = JSON.parse(data);
|
|
|
|
let token: string = data.token;
|
|
let phone: IPhone = data.phone;
|
|
|
|
// If request is faulty or token invalid -> return.
|
|
if (data === undefined || phone === undefined) return;
|
|
if (await !verifyJWT(token)) return;
|
|
|
|
const id = decode(token, { json: true })!.user;
|
|
const user = await User.findById(id);
|
|
|
|
// If user doesn't exist -> return.
|
|
if (user === null) return;
|
|
|
|
const approvalCode = randomString(6, '0123456789');
|
|
|
|
// Create phone
|
|
const newPhone = await Phone.create({
|
|
...phone,
|
|
user,
|
|
approval: {
|
|
code: approvalCode
|
|
}
|
|
});
|
|
|
|
this.io.to(this.getUserRoom(user)).emit('approvePhone', newPhone);
|
|
|
|
// Respond with id so device can later submit correct code.
|
|
socket.emit('requestAccess', { phoneId: newPhone.id });
|
|
|
|
logger.info(`User ${user?.name} requests to connect new phone ${phone.displayName}`);
|
|
});
|
|
|
|
socket.on('submitPairCode', async data => {
|
|
const { phoneId, code } = JSON.parse(data);
|
|
|
|
console.log("Entry:", data, phoneId, code);
|
|
|
|
if (phoneId === undefined || code === undefined) return;
|
|
|
|
const phone = await Phone.findById(phoneId);
|
|
if (phone === null) return;
|
|
|
|
console.log(data, phoneId, code);
|
|
|
|
// If provided code isn't equal with actual code -> Emit event again.
|
|
if (phone.approval.code !== code) {
|
|
console.log(data, phoneId, code);
|
|
socket.emit('submitPairCode', '');
|
|
|
|
return;
|
|
}
|
|
|
|
phone.approval.approvedOn = new Date();
|
|
await phone.save();
|
|
|
|
// We're good. Create JWT token.
|
|
const token = sign({ user: phone.user._id, type: 'phone' }, JWT_SECRET, { expiresIn: '30d' });
|
|
|
|
socket.emit('submitPairCode', token);
|
|
});
|
|
|
|
socket.on('loginFrontend', async (token: string) => {
|
|
if (await verifyJWT(token)) {
|
|
const tokenDecoded = decode(token, { json: true });
|
|
const id = tokenDecoded!.user;
|
|
const type = tokenDecoded!.type;
|
|
const user = await User.findById(id);
|
|
|
|
if (user == null) return;
|
|
if (type != 'frontend') return;
|
|
|
|
|
|
if (this.frontends.indexOf(socket.id) != -1)
|
|
this.frontends.push(socket.id);
|
|
|
|
socket.join(this.getUserRoom(user));
|
|
socket.join(this.getUserFrontendRoom(user));
|
|
|
|
logger.info(`Socket ${socket.id} became a frontend socket.`);
|
|
}
|
|
});
|
|
|
|
socket.on('loginPhone', async (token: string) => {
|
|
if (await verifyJWT(token)) {
|
|
const tokenDecoded = decode(token, { json: true });
|
|
const id = tokenDecoded!.user;
|
|
const type = tokenDecoded!.type;
|
|
const user = await User.findById(id);
|
|
|
|
if (user == null) return;
|
|
if (type != 'phone') return;
|
|
|
|
if (this.frontends.indexOf(socket.id) != -1)
|
|
this.frontends.push(socket.id);
|
|
|
|
socket.join(this.getUserRoom(user));
|
|
socket.join(this.getUserPhoneRoom(user));
|
|
|
|
logger.info(`Socket ${socket.id} became a phone socket.`);
|
|
}
|
|
});
|
|
|
|
logger.info(`New socket connection from ${socket.handshake.address} with id ${socket.id} (total connections: ${this.io.sockets.sockets.size})`);
|
|
socket.emit('test', 'Yay, it works.');
|
|
});
|
|
}
|
|
|
|
} |