jakeoverall/codeworks-starter

View on GitHub
lib/Channels/Channel.ts

Summary

Maintainability
A
1 hr
Test Coverage
import {Socket, Server} from 'socket.io'
import { SessionUser } from '../Middleware/Authorize'
import { ErrorUnAuthorized, ErrorBadRequest } from "../Errors/Errors";

export const COMMANDS = {
  GROUPMESSAGE: "GROUPMESSAGE",
  CHANNELMESSAGE: "CHANNELMESSAGE",
  PRIVATEMESSAGE: "PRIVATEMESSAGE",
  SELFMESSAGE: "SELFMESSAGE",
  JOINGROUP: "JOINGROUP",
  LEAVEGROUP: "LEAVEGROUP",
  USERJOINED: "USERJOINED",
  USERLEFT: "USERLEFT",
  USERDISCONNECTED: "USERDISCONNECTED",
  ERROR: "ERROR"
}

const COMMANDMETHODS = {
  GROUPMESSAGE: "SendToGroup",
  CHANNELMESSAGE: "SendToChannel",
  PRIVATEMESSAGE: "SendToConnectionId",
  SELFMESSAGE: "SendToMe",
  JOINGROUP: "JoinGroup",
  LEAVEGROUP: "LeaveGroup"
}

export class Channel {
  channelName: string;
  io: Server;
  name: string
  constructor(channelName: string, io: Server) {
    if (!channelName || !io) { throw new Error("Unable to create channel you must provide a channelName and instance of io") }
    this.channelName = "/" + channelName
    this.io = io
  }

  /**
   * This method will be called for each socket that joins the channel
   * to keep the appropriate context of this you may need to bind the
   * context  -> socket.on("EVENT", LocalMethod.bind(context))
   * @param context 
   * @param socket 
   */
  init(context: Channel, socket: Socket): void {
    throw new Error("SUPER CHANNEL INIT WAS CALLED")
  }

  OnConnected(channel) {
    this.io.of(this.channelName).on('connection', (socket) => {
      let user = socket.request.user;
      socket.emit(this.channelName, `JOINED ${this.channelName}`)
      this.io.of(this.channelName).emit(COMMANDS.USERJOINED, { socketId: socket.id, user })

      for (let command in COMMANDMETHODS) {
        socket.on(command, (payload) => {
          let method = COMMANDMETHODS[command]
          this[method](socket, payload)
        })
      }
      socket.on("disconnect", () => this.OnDisconnected(user))
      channel.init(this, socket)
    });
  }

  OnDisconnected(user: any) {
    this.io.of(this.channelName).emit(COMMANDS.USERDISCONNECTED, user);
  }

  SendToMe(socket: Socket, payload: any) {
    try {
      socket.emit(COMMANDS.SELFMESSAGE, payload)
    } catch (err) {
      this.SendError(socket, COMMANDS.SELFMESSAGE, err)
    }
  }

  SendToConnectionId(socket: Socket, payload: any) {
    let connectionId = payload.connectionId
    if (!connectionId) {
      return this.SendError(
        socket,
        COMMANDS.PRIVATEMESSAGE,
        new Error("Invalid Payload must provide a connectionId")
      )
    }
    this.io.to(connectionId).emit(COMMANDS.PRIVATEMESSAGE, payload)
  }

  SendToChannel(socket: Socket, payload: any) {
    this.io.of(this.channelName).emit(COMMANDS.CHANNELMESSAGE, payload)
  }

  SendToGroup(socket: Socket, payload: any) {
    if (!payload.groupName || !payload.data) {
      return this.SendError(socket, COMMANDS.GROUPMESSAGE, new ErrorBadRequest("Payload must specify groupName && data"))
    }
    let group = this.channelName + ":" + payload.groupName
    if (!socket.rooms[group]) { return }
    this.io.of(this.channelName).to(group).emit(COMMANDS.GROUPMESSAGE, payload)
  }

  JoinGroup(socket: Socket, groupName: string) {
    try {
      let group = this.channelName + ":" + groupName
      if (socket.rooms[group]) { return }
      socket.join(group)
      this.io.of(this.channelName).to(group).emit(COMMANDS.USERJOINED, socket.request['user'])
    } catch (err) {
      return this.SendError(socket, COMMANDS.JOINGROUP, err)
    }
  }

  LeaveGroup(socket: Socket, groupName: string) {
    try {
      let group = this.channelName + ":" + groupName
      if (!socket.rooms[group]) { return }
      socket.leave(group)
      this.io.of(this.channelName).to(group).emit(COMMANDS.USERLEFT, socket.request['user'])
    } catch (err) {
      return this.SendError(socket, COMMANDS.LEAVEGROUP, err)
    }
  }

  SendError(socket: Socket, command: string, err: Error) {
    try {
      socket.emit(COMMANDS.ERROR, { ...err, message: err.message, channel: this.channelName, command })
    } catch (err) {
      console.error(err);
    }
  }

  Authorize(role: string | number, socket: Socket) {
    try {
      if (!socket.request['authService'].hasAccess(role)) {
        throw new ErrorUnAuthorized()
      }
      return true
    } catch (err) {
      this.SendError(socket, "Access Denied", err)
      return false
    }
  }
}