Lesterpig/openparty

View on GitHub
public/js/controller.js

Summary

Maintainability
B
6 hrs
Test Coverage
angular.module('openparty', [
    'btford.socket-io',
    'ngSanitize',
    'luegg.directives', //for scrollGlue directive
    'ngAudio'
]).
filter('parseUrlFilter', function () { // the "linky" filter is not suitable due to the html sanitization
  var urlPattern = /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/gi;
  return function (text, target) {
    return text.replace(urlPattern, '<a target="' + target + '" href="$&">$&</a>');
  };
}).
factory('socket', function (socketFactory) {
  return socketFactory();
}).
controller('controller', ['$scope', 'socket', '$interval', 'ngAudio', 'crypto', function ($scope, socket, $interval, ngAudio, crypto) {

  // Server data (served on login)

  $scope.gametypes = {};

  // Live data

  $scope.users    = {lobby: 0, playing: 0};
  $scope.lastPing = 0;

  // Game Status

  $scope.status           = 0;
  $scope.rooms            = [];
  $scope.joinedRoom       = null;
  $scope.selectedRoom     = null;
  $scope.localParams      = [];
  $scope.isMaster         = false;
  $scope.showMasterParams = false;

  // Game Chats

  $scope.preChat             = '';
  $scope.gameChat            = '';
  $scope.channels            = {};
  $scope.manualChannelChange = false;

  // Timer

  $scope.timer         = null;
  $scope.timerEnd      = null;
  $scope.remainingTime = {min: '--', sec: '--'};

  // Actions

  $scope.actions       = {};
  $scope.actionsValues = {};

  // Local data

  $scope.playersInfos = {};
  $scope.authUsername = localStorage['PGP_Username'];
  $scope.username     = $scope.authUsername || (window.location.hash ? window.location.hash.substr(1) : '');
  $scope.audio        = {};
  $scope.mute         = localStorage['mute'];
  var availableThemes = ['default', 'darkly'];
  $scope.theme        = +localStorage['theme'] || 0;
  $scope.overlay      = '';

  if($scope.mute === 'on') {
    ngAudio.mute();
  }

  // Header

  var baseTitle = document.title;

  $scope.updateTitle = function() {
    var output = baseTitle;
    if($scope.joinedRoom) {
      if($scope.joinedRoom.started) {
        output = '❱ ' + output;
      }
      else {
        output = '[' + $scope.joinedRoom.players.length + '/' + $scope.joinedRoom.size +'] ' + output;
      }
    }
    document.title = output;
  };

  $scope.printHelp = function(help) {
    $scope.roomHelpMessage = help;
  };

  $scope.getPlayerInfos  = function(player) {
    if(!$scope.playersInfos[player.username]) {
      return player.username;
    }
    return $scope.playersInfos[player.username];
  };

  $scope.changeSelectedRoom = function(id) {
    $scope.selectedRoom = id;
    $scope.invalidRoomPassword = false;
  };

  $scope.changeTheme = function(n) {
    if(n === undefined) {
      localStorage['theme'] = $scope.theme = ($scope.theme + 1) % availableThemes.length;
    }
    // Doing a raw update to optimize refresh
    document.getElementById('stylesheet').href = 'css/bootstrap.' + availableThemes[$scope.theme] + '.min.css';
  };
  $scope.changeTheme($scope.theme); // Initial call

  // UP
  $scope.loginSubmit = function() {
    var req = {username: $scope.username, password: $scope.password};

    if($scope.authUsername !== $scope.username) {
      $scope.authUsername = '';
    }

    if($scope.authUsername) {
      crypto.getChallengeResponse($scope.challenge).then(function(data) {
        req.key = data.key;
        req.message = data.message;
        socket.emit('login', req);
      }, function() {
        socket.emit('login', req);
      });
    } else {
      socket.emit('login', req);
    }
  };

  $scope.logout = function() {
    socket.emit('logout');
  };

  $scope.createRoom = function() {
    socket.emit('createRoom', {name: $scope.roomName, type: $scope.roomType, password: $scope.roomPassword});
  };

  $scope.leaveRoom = function() {
    socket.emit('leaveRoom');
  };

  $scope.startRoom = function() {
    socket.emit('startRoom');
  };

  $scope.joinRoom = function(id, password) {
    $scope.invalidRoomPassword = false;
    socket.emit('joinRoom', {id: id, password: password});
  };

  $scope.kickPlayer = function(username) {
    socket.emit('kickPlayer', username);
  };

  $scope.incSize = function(inc) {
    socket.emit('setRoomSize', $scope.joinedRoom.size + inc);
  };

  $scope.changeParameter = function(parameter, value) {
    socket.emit('setRoomParameter', {parameter: parameter, value: value});
  };

  $scope.keyChat = function(e, preChat) {
    if(e.keyCode === 13)
      $scope.sendMessage(preChat);
  };

  $scope.sendMessage = function(preChat) {
    if(preChat) {
      socket.emit('sendMessage', { channel: 'preChat', message: $scope.chatMessage });
    } else {
      socket.emit('sendMessage', { channel: $scope.selectedChannel, message: $scope.chatMessage });
    }
  };

  $scope.executeAction = function(action) {
    var data = {action: action};
    if($scope.actions[action].type !== 'button') {
      data.value = $scope.actionsValues[action];
    }
    socket.emit('executeAction', data);
  };

  $scope.register = function() {
   $scope.registering = true;
   crypto.generateKey($scope.username).then(function() {
    $scope.registered = true;
   });
  };

  $scope.unregister = function() {
    if(confirm('Confirm?')) {
      delete localStorage['PGP_Username'];
      $scope.overlay = $scope.authUsername = '';
    }
  };

  $interval(function() {
    $scope.lastPing = new Date().getTime();
    socket.emit('o-ping');
  }, 10000);

  // DISCONNECTION MANAGEMENT

  socket.on('emergencyMessage', function(msg) {
    $scope.emergencyMessage = msg;
  });

  socket.on('disconnect', function() {
    if(!$scope.disableWarning)
      $scope.status = -1;
  });

  socket.on('reconnect', function() {
    window.location.reload();
  });

  window.onbeforeunload = function() {
    $scope.disableWarning = true;
  };

  // DOWN

  socket.on('o-pong', function() {
    $scope.ping = new Date().getTime() - $scope.lastPing;
  });

  socket.on('userCount', function(c) {
    $scope.users = c;
  });

  socket.on('challenge', function(c) {
    $scope.challenge = c;
  });

  socket.on('reconnectInvitation', function(username) {
    $scope.username = username;
    $scope.loginSubmit();
  });

  socket.on('loginResult', function(o) {
    if(o.err) {
      $scope.loginError = 'has-error';
      $scope.loginErrorMessage = o.err;
      return;
    }

    // Write username in URL for further use
    window.location = window.location.origin + '/#' + $scope.username;

    $scope.gametypes = o.gametypes;
    for(var i in o.gametypes) {
      $scope.roomType = i; break;
    }
    $scope.status = 1;
    $scope.username = o.username;
    socket.emit('getRooms');
  });


  /** GLOBAL LOBBY FUNCTIONS **/

  socket.on('roomCreated', function(room) {
    $scope.rooms.push(room);
    $scope.updateTitle();
  });

  socket.on('roomRemoved', function(id) {
    for(var i = 0; i < $scope.rooms.length; i++) {
      if($scope.rooms[i].id === id) {
        $scope.rooms.splice(i, 1);
        return;
      }
    }
  });

  socket.on('roomUpdated', function(room) {

    for(var i = 0; i < $scope.rooms.length; i++) {
      if($scope.rooms[i].id === room.id) {
        $scope.rooms[i] = room;
        if(room.started) {
          $scope.rooms.splice(i,1); // remove from lobby
        }
        break;
      }
    }

    if($scope.joinedRoom) {
      if($scope.joinedRoom.id === room.id) {
        $scope.joinedRoom = room;
        updateLocalParameters(room);
        $scope.updateTitle();
      }
    }
  });

  socket.on('roomsList', function(rooms) {
    $scope.rooms = rooms;
  });

  /** PERSONNAL LOBBY FUNCTIONS **/

  socket.on('roomJoined', function(room) {
    $scope.joinedRoom = room;
    $scope.isMaster = false;
    $scope.preChat = '';
    updateLocalParameters(room);
    $scope.updateTitle();

    // Preload default sounds
    if(room.gameplay.sounds){
      room.gameplay.sounds.forEach(function(s){
        $scope.audio[s.id] = ngAudio.load(s.path);
      });
    }
  });

  socket.on('invalidRoomPassword', function() {
    $scope.invalidRoomPassword = true;
  });

  socket.on('roomLeft', function() {
    $scope.joinedRoom = null;
    $scope.isMaster   = false;
    $scope.preChat    = '';
    $scope.gameChat   = '';
    $scope.gameInfo = '';
    $scope.status     = 1;
    $scope.updateTitle();
  });

  /** CHAT **/

  socket.on('messageSent', function() {
    $scope.chatMessage = '';
  });

  socket.on('chatMessage', function(data) {

    if(data.sender)
      data.sender = getDate() + ' <strong>' + data.sender + '</strong> : ';
    else
      data.sender = '';

    if(!$scope.joinedRoom || !$scope.joinedRoom.started && !data.lobby)
      $scope.preChat += data.sender + data.message + '\n';
    else if(!data.lobby)
      $scope.gameChat += data.sender + data.message + '<br />';
  });

  socket.on('setAllowedChannels', function(data) {

    var channelsArray = [];
    for(var i in data) {
      data[i].id = i;
      channelsArray.push(data[i]);
    }
    channelsArray.sort(function(a, b) {
      if(!a.p)
        a.p = 0;
      if(!b.p)
        b.p = 0;
      return b.p - a.p; // reversed by priority
    });

    $scope.channels = channelsArray;

    if(!data[$scope.selectedChannel] || !$scope.manualChannelChange) {
      $scope.manualChannelChange = false;
      if(channelsArray.length > 0)
        $scope.selectedChannel = channelsArray[0].id; // the highest priority is selected by default
      else
        $scope.selectedChannel = null; // no channel available
    }

  });

  socket.on('clearChat', function() {
    $scope.gameChat = '';
    $scope.preChat  = '';
  });

  /** ROOM EVENTS */

  socket.on('gameStarted', function(room) {
    $scope.status = 2;
    $scope.playersInfos = {};
    $scope.updateTitle();
  });

  socket.on('setGameInfo', function(data) {
    $scope.gameInfo = data;
  });

  socket.on('setTimer', function(data) {
    $interval.cancel($scope.timer);
    $scope.timerEnd = data;
    $scope.timer = $interval($scope.writeTimer, 200);
    $scope.writeTimer();
  });

  socket.on('clearTimer', function(data) {
    $scope.remainingTime.min = '--';
    $scope.remainingTime.sec = '--';
    $interval.cancel($scope.timer);
  });

  socket.on('setAvailableActions', function(data) {
    $scope.actions = data;
    for(var action in $scope.actions) {
      if($scope.actions[action].type === 'select') {
        if($scope.actions[action].options.safeChoices.length >= 1)
          $scope.actionsValues[action] = $scope.actions[action].options.safeChoices[0];
      }
    }
  });

  socket.on('playerInfo', function(data) {
    $scope.playersInfos[data.username] = data.value;
  });

  /** Audio management */

  socket.on('preloadSound', function(data) {
    if(!$scope.audio[data.id]){
      $scope.audio[data.id] = ngAudio.load(data.path);
    }
  });

  socket.on('playSound', function(data) {
    if($scope.mute === 'on'){
      return;
    }

    if(!data.id){
      data = {id: data};
    }

    if(!$scope.audio[data.id]) {
      $scope.audio[data.id] = ngAudio.play(data.path);
    } else {
      $scope.audio[data.id].play();
    }
    var sound = $scope.audio[data.id];

    sound.pausing = false;
    if(data.loop !== undefined)
      sound.loop = data.loop;
    if(data.volume !== undefined)
      sound.setVolume(data.volume);
  });

  socket.on('stopSound', function(data) {
    if(!data.id){
      data = {id: data};
    }
    if($scope.audio[data.id]){
      $scope.audio[data.id].stop();
    }
  });

  $scope.writeTimer = function() {
    var now = new Date().getTime();
    var time = Math.round(($scope.timerEnd - now) / 1000);

    if(time === Infinity) {
      $scope.remainingTime.min = '--';
      $scope.remainingTime.sec = '--';
      $interval.cancel($scope.timer);
      return;
    }

    var min = Math.floor(time / 60);
    var sec = time % 60;

    if(min < 10) min = '0' + min;
    if(sec < 10) sec = '0' + sec;

    $scope.remainingTime.min = min;
    $scope.remainingTime.sec = sec;

  };

  $scope.toggleMute = function() {
    $scope.mute = ($scope.mute === 'on' ? 'off' : 'on');
    localStorage['mute'] = $scope.mute;
  };

  /** Authentication management **/

  var fingerprints = localStorage['fingerprints'];
  fingerprints = fingerprints ? JSON.parse(fingerprints) : {};

  // TODO This function is to be called many times. We should find a way to cache result in $scope or somewhere else...
  $scope.checkAuth = function(player) {
    if(player.username === $scope.username || !player.authentication)
      return 'none';

    var saved = fingerprints[player.authentication.username];
    if(!saved)
      return 'unknown';

    if(player.authentication.fingerprint === saved)
      return 'ok';

    return 'ko';
  };

  $scope.askFriend = function(player, remove) {
    $scope.friend  = player.authentication;
    $scope.overlay = remove ? 'rmFriend' : 'addFriend';
  };

  $scope.printIdentity = function() {
    $scope.exportData = crypto.exportData();
    $scope.overlay = 'registrationInfo';
  };

  $scope.setFriend = function(remove) {
    fingerprints[$scope.friend.username] = remove ? '' : $scope.friend.fingerprint;
    localStorage['fingerprints'] = JSON.stringify(fingerprints);
    $scope.overlay = false;
  };

  $scope.import = crypto.importData;

  /** PRIVATE **/

  function updateLocalParameters(room) {
    for(var i = 0; i < room.gameplay.parameters.length; i++) {
      $scope.localParams[i] = room.gameplay.parameters[i].value;
    }
    if(!room.players[0])
      return;
    if(room.players[0].username === $scope.username) {
      $scope.isMaster = true;
    }
  }

  function getDate() {
    var date = new Date();
    var h    = date.getHours();
    var i    = date.getMinutes();
    var j    = date.getSeconds();

    if(h < 10) h = '0' + h;
    if(i < 10) i = '0' + i;
    if(j < 10) j = '0' + j;

    return '['+h+':'+i+':'+j+']';
  }


}]);