<commit_before>/**
 * @author <NAME> <<EMAIL>>
 * @copyright Interactive Solutions
 */

import * as socket from 'socket.io';
import * as http from 'http';
import * as _ from 'lodash';

import {EventEmitter} from "events";
import {UserApi} from "../api/user";
import {SocketConnection} from "./socket-connection";
import {UserEntity} from "../user/user";
import {UserCollection} from "../user/user";

export interface SocketServerConfig {
  events:string[];
  maxAuthenticateAttempts?:number;
}

interface AuthenticationFailedSocket extends SocketIO.Socket {
  attempts:number;
}

export class SocketServer extends EventEmitter {

  private server:SocketIO.Server;
  private _connections:{[key:number]:SocketConnection[]} = {};

  constructor(private userApi:UserApi,
              private users:UserCollection,
              private config:SocketServerConfig,
              httpServer:http.Server) {
    super();
    this.server = socket.listen(httpServer);

    this.initSocketServer();
  }

  get connections():SocketConnection[] {
    return _.flattenDeep<SocketConnection>(_.values<SocketConnection[]>(this._connections));
  }

  close():void {
    this.removeAllListeners();

    this.connections.forEach((s:SocketConnection) => {
      s.socket.emit('hermes:shutdown');
      s.socket.disconnect(true);
      s.destroy();
    });
  }

  notify(user:number, event:string, payload:any):void {
    if (this._connections[user]) {
      var connections = this._connections[user];

      connections.forEach((c:SocketConnection) => c.socket.emit(event, payload));
    }
  }

  notifyUsers(users:number[], event:string, payload:any):void {
    users.forEach((u:number) => this.notify(u, event, payload));
  }

  private initSocketServer():void {
    this.server.sockets.on('connection', this.onConnection.bind(this));
  }

  private onConnection(socket:SocketIO.Socket):void {
    socket.once('user:authenticate', this.onUserAuthenticate.bind(this, socket));
  }

  private onUserAuthenticate(socket:SocketIO.Socket, accessToken:string):void {
    this
      .userApi
      .authenticate(accessToken)
      .then(this.onAuthenticationSuccess.bind(this, socket))
      .catch(this.onAuthenticationFailed.bind(this, socket));
  }

  private onAuthenticationSuccess(socket:SocketIO.Socket, u:UserEntity):void {
    var user = this.users.has(u.id) ? this.users.get(u.id) : u;

    var connection = new SocketConnection(socket, user, this.config.events);
    connection.once('connection:closed', this.onConnectionClosed.bind(this, connection));

    if (!this._connections[user.id]) {
      this._connections[user.id] = [];

      this.users.add(user);
      this.emit('new:user', connection);
    } else {
      this.emit('new:socket', connection);
    }

    this._connections[user.id].push(connection);

    this.notifyAuthenticationResult(socket, true);
  }

  private onAuthenticationFailed(socket:AuthenticationFailedSocket):void {
    socket.attempts = socket.attempts ? ++socket.attempts : 1;

    if (this.config.maxAuthenticateAttempts && socket.attempts > this.config.maxAuthenticateAttempts - 1) {
      this.notifyAuthenticationResult(socket, false);
      socket.emit('socket:closed', {reason: 'Maximum authentication attempts reached'});
      socket.disconnect(true);

      return;
    }

    socket.once('user:authenticate', this.onUserAuthenticate.bind(this, socket));

    this.notifyAuthenticationResult(socket, false);
  }

  private notifyAuthenticationResult(socket:SocketIO.Socket, success:boolean) {
    this.emit('user:authenticated', {success: success});
    socket.emit('user:authenticated', {success: success});
  }

  private onConnectionClosed(connection:SocketConnection) {
    var user = connection.user;

    if (!this._connections[user.id]) {
      return;
    }

    this._connections[user.id] = _.reject(this._connections[user.id], (s:SocketConnection) => s === connection);
    connection.destroy();

    var socket = connection.socket;

    if (socket.connected) {
      socket.once('user:authenticate', this.onUserAuthenticate.bind(this));
    }

    if (this._connections[user.id].length === 0) {
      delete this._connections[user.id];
    }
  }
}<commit_msg>Fixed an issue with socket being undefined on user authenticate.<commit_after>/**
 * @author <NAME> <<EMAIL>>
 * @copyright Interactive Solutions
 */

import * as socket from 'socket.io';
import * as http from 'http';
import * as _ from 'lodash';

import {EventEmitter} from "events";
import {UserApi} from "../api/user";
import {SocketConnection} from "./socket-connection";
import {UserEntity} from "../user/user";
import {UserCollection} from "../user/user";

export interface SocketServerConfig {
  events:string[];
  maxAuthenticateAttempts?:number;
}

interface AuthenticationFailedSocket extends SocketIO.Socket {
  attempts:number;
}

export class SocketServer extends EventEmitter {

  private server:SocketIO.Server;
  private _connections:{[key:number]:SocketConnection[]} = {};

  constructor(private userApi:UserApi,
              private users:UserCollection,
              private config:SocketServerConfig,
              httpServer:http.Server) {
    super();
    this.server = socket.listen(httpServer);

    this.initSocketServer();
  }

  get connections():SocketConnection[] {
    return _.flattenDeep<SocketConnection>(_.values<SocketConnection[]>(this._connections));
  }

  close():void {
    this.removeAllListeners();

    this.connections.forEach((s:SocketConnection) => {
      s.socket.emit('hermes:shutdown');
      s.socket.disconnect(true);
      s.destroy();
    });
  }

  notify(user:number, event:string, payload:any):void {
    if (this._connections[user]) {
      var connections = this._connections[user];

      connections.forEach((c:SocketConnection) => c.socket.emit(event, payload));
    }
  }

  notifyUsers(users:number[], event:string, payload:any):void {
    users.forEach((u:number) => this.notify(u, event, payload));
  }

  private initSocketServer():void {
    this.server.sockets.on('connection', this.onConnection.bind(this));
  }

  private onConnection(socket:SocketIO.Socket):void {
    socket.once('user:authenticate', this.onUserAuthenticate.bind(this, socket));
  }

  private onUserAuthenticate(socket:SocketIO.Socket, accessToken:string):void {
    this
      .userApi
      .authenticate(accessToken)
      .then(this.onAuthenticationSuccess.bind(this, socket))
      .catch(this.onAuthenticationFailed.bind(this, socket));
  }

  private onAuthenticationSuccess(socket:SocketIO.Socket, u:UserEntity):void {
    var user = this.users.has(u.id) ? this.users.get(u.id) : u;

    var connection = new SocketConnection(socket, user, this.config.events);
    connection.once('connection:closed', this.onConnectionClosed.bind(this, connection));

    if (!this._connections[user.id]) {
      this._connections[user.id] = [];

      this.users.add(user);
      this.emit('new:user', connection);
    } else {
      this.emit('new:socket', connection);
    }

    this._connections[user.id].push(connection);

    this.notifyAuthenticationResult(socket, true);
  }

  private onAuthenticationFailed(socket:AuthenticationFailedSocket):void {
    socket.attempts = socket.attempts ? ++socket.attempts : 1;

    if (this.config.maxAuthenticateAttempts && socket.attempts > this.config.maxAuthenticateAttempts - 1) {
      this.notifyAuthenticationResult(socket, false);
      socket.emit('socket:closed', {reason: 'Maximum authentication attempts reached'});
      socket.disconnect(true);

      return;
    }

    socket.once('user:authenticate', this.onUserAuthenticate.bind(this, socket));

    this.notifyAuthenticationResult(socket, false);
  }

  private notifyAuthenticationResult(socket:SocketIO.Socket, success:boolean) {
    this.emit('user:authenticated', {success: success});
    socket.emit('user:authenticated', {success: success});
  }

  private onConnectionClosed(connection:SocketConnection) {
    var user = connection.user;

    if (!this._connections[user.id]) {
      return;
    }

    this._connections[user.id] = _.reject(this._connections[user.id], (s:SocketConnection) => s === connection);
    connection.destroy();

    var socket = connection.socket;

    if (socket.connected) {
      socket.once('user:authenticate', this.onUserAuthenticate.bind(this, socket));
    }

    if (this._connections[user.id].length === 0) {
      delete this._connections[user.id];
    }
  }
}
{'fraction_non_alphanumeric': 0.1407784218620608, 'fraction_numerical': 0.0007098071690524074, 'mean_word_length': 4.136087484811664, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2467556', 'n_tokens_mistral': 2536, 'n_tokens_neox': 2417, 'n_words': 426}