'use strict';
var EventEmitter = require('events').EventEmitter;
var Events = require('./events.js').Events;
var guidManager = require('../utils/guids.js');
var contactsEndpoint = require('../endpoints/contacts-v1.js');
var conversationsEndpoint = require('../endpoints/conversations-v1.js');
var loginEndpoint = require('../endpoints/logins-v1.js');
var groupsEndpoints = require('../endpoints/groups-v1.js');
var notificationsEndpoint = require('../endpoints/notifications-v1.js');
var sessionsEndpoint = require('../endpoints/sessions-v1.js');
var usersModule = require('./users.js');
var winston = require('winston');
var serverModule = require('./servers.js');
var conversationsModule = require('./conversations.js');
var notificationsModule = require('./notifications.js');
/**
* @class Client
* @description [Client]{@link Client} class is the main application class, it's the core of the library
that makes all works together. A [Client]{@link Client} represent a user connection to curse servers,
it handles the main events that occurs at run time and expose them to a third party application.
The [Client]{@link Client} class extends the [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) class.
*
* @property {Map} servers Regroup all the servers fetched when the client starts.
This map is filled only when using the client.run method. The keys are the servers IDs and the values are
instances of the [Server]{@link Server} class.
* @property {Map} channels Regroup all the channels fetched from all the servers when the client starts. This map is filled
only when using the client.run method. The keys are the channels IDs and the values are instances of the [Channel]{@link Channel} class.
* @property {Map} conversations Regroup all the conversations that the client encounters during its run time.
The keys are the conversations IDs and the values are instances of the [Conversation]{@link Conversation} class.
* @property {Map} users Regroup all the users that the client encounters during its run time.
The keys are the users IDs and the values are instances of the [User]{@link User} class.
* @property {number} clientID Curse ID of the connected client.
This is very helpful for example to check the ID of the for notificationMessages and ignore self sended messages.
* @property {string} username Curse username of the connected client.
*/
class Client extends EventEmitter {
constructor(debugLevel){
super();
if(debugLevel === undefined){
debugLevel = 'info';
}
//Change console settings for the logger
winston.remove(winston.transports.Console)
winston.add(winston.transports.Console, {
level: debugLevel,
prettyPrint: true,
colorize: true,
silent: false,
timestamp: true
});
this._loginSession;
this._notifier;
this._loginRequest = {login: "", password: ""};
this._connected = false;
this.machineKey = guidManager.getMachineKey();
this.token;
this._tokenExpires;
this._tokenRenewAfter;
this._timeGapToServer;
this._readySent = false;
this.servers = new Map();
this.channels = new Map();
this.conversations = new Map();
this.users = new Map();
this.clientID;
this.friendList = []; //Please don't use them until we can manage friends at user level.
}
/**
* @description Connects the client to the curse API endpoints.
This function will not get any server information ready, and will neither start the notifier from the notification
module making impossible to receive and send new messages. **For general use the [Client.run]{@link Client#run} function.**
* @param {string} login Your Curse login name
* @param {string} password Your Curse login password
* @param {Function} callback Facultative arg, callback: (errors) => {}.
This function can take an argument errors that is null or undefined when function ends correctly.
*/
login(login, password, callback){
//Define an empty void if no callback specified
if(callback === undefined){
callback = _ => {};
}
this._loginRequest = new loginEndpoint.LoginRequest(login, password);
var self = this; //Make object avalaible for callbacks
loginEndpoint.login(this._loginRequest, function(errors, answer){
if(errors === null){
self._loginSession = answer['Session'];
self.token = answer['Session']['Token'];
self._tokenExpires = answer['Session']['Expires'];
self._tokenRenewAfter = answer['Session']['RenewAfter'];
self.clientID = answer['Session']['UserID'];
// ! ABOUT timeGapToServer: careful with the signs +/- you're using
self._timeGapToServer = Date.now() - answer['Timestamp'];
self._connected = true;
//Renew session automatically
self._renewSessionTimeout = setTimeout(function(){
self._renewSession();
}, (self._tokenRenewAfter - Date.now()) + self._timeGapToServer);
winston.log('info', 'Client.login', 'Succesfully connected to REST Curse API.');
self.emit(Events.CONNECTED);
callback(null);
} else {
winston.log('error', 'Client.login:', 'Status:', errors);
callback(errors);
}
});
}
/*
* Renew the token for all the rest api request before the rest session exprire (renew is automatically scheduled)
*/
_renewSession(){
clearTimeout(this._renewSessionTimeout);
var self = this;
loginEndpoint.loginRenew(this.token, function(errors, answer){
if(errors === null){
var data = answer.content;
winston.log('debug', 'Client._renewSession', 'Successful token renew');
self.token = data.Token;
self._tokenExpires = data.Expires;
self._tokenRenewAfter = data.RenewAfter;
self._renewSessionTimeout = setTimeout(function(){
self._renewSession();
}, (self._tokenRenewAfter - Date.now()) + self._timeGapToServer);
} else {
winston.log('error', 'Client._renewSession', 'Couldn\'t renew the token will try again in 5minutes', errors);
self._renewSessionTimeout = setTimeout(function(){
self._renewSession();
}, 300000);
}
});
}
/*
* Fill up the servers and friendList properties (erasing existing, DO NOT use it to update the server infos)
* (The erasing thing is not true anymore but it's still not a good idea to do so, we'll get some change later..).
*/
_loadContacts(callback){
//Define an empty void if no callback specified
if(callback === undefined){
callback = _ => {};
}
var self = this;
contactsEndpoint.contacts(this.token, function(errors, answer){
if(errors === null){
for (let groupAnswer of answer['Groups']){
//Take only servers
if(groupAnswer.GroupType == groupsEndpoints.GroupType.Large){
//Check that server is not already existing
if(self.servers.has(groupAnswer.GroupID) == false){
var server = new serverModule.Server(groupAnswer.GroupID, self);
}
}
}
self.friendList = answer['Friends']; //Thoses friends are not useable yet (who needs friends ?)
callback(null);
} else {
callback(errors);
}
});
}
get username(){
if(this._connected){
return this._loginSession.Username;
} else {
return undefined;
}
}
/**
* @description All-in-one function that makes the client to work seemlessly.
The [Client]{@link Client} class will emit the *ready* event when the client is connected and ready.
* @param {string} login Your Curse login name
* @param {string} password Your Curse login password
* @example
* var client = new cursejs.Client;
*
* //Internal use of the client
* client.on('ready', function(){
*
* //My own code...
*
* });
*
* //Start the client after defining the events handler
* client.run('login', 'password');
*/
run(login, password){
var self = this;
this._notifier = new notificationsModule.Notifier(this);
this.on(Events.CONNECTED, function(){
self._loadContacts(function(errors){
if(errors === null){
self._notifier.start();
}
else {
winston.log('error', 'Client.run', 'Cannot get contacts', errors);
}
});
});
this.login(login, password);
}
_ready(){
var serversReady = true;
//check servers are ready
for (let server of this.servers.values()){
if(!server._ready){
serversReady = false;
break;
}
}
// Everything is ready
if(this._notifier._ready && serversReady && !this._readySent){
this._readySent = true;
this.emit(Events.READY);
}
}
/**
* @description Send a message in a conversation.
* @param {Conversation} conversation Conversation
* @param {string} content Message content
* @param {Function} callback Facultative arg, callback: (errors) => {}.
This function can take an argument errors that is null or undefined when function ends correctly.
*/
sendMessage(conversation, content, callback){
//Define an empty void if no callback specified
if(callback === undefined){
callback = _ => {};
}
conversation.sendMessage(content, callback);
}
/**
* @description Join a server using a specified invite code.
* @param {string} inviteCode Invitation code
* @param {Function} callback Function callback
* @example
* client.redeemInvitation(myInvitationCode, function(errors){
* if(errors === null){
* //Code when server have succesfully been joined
* }
* });
*/
redeemInvitation(inviteCode, callback){
//Define an empty void if no callback specified
if(callback === undefined){
callback = _ => {};
}
var self = this
groupsEndpoints.getInvitationDetails(inviteCode, this.token, function(errors, answer){
if(errors === null){
var invitDetails = answer.content;
groupsEndpoints.joinInvitation(inviteCode, self.token, function(errors, _){
if(errors === null){
if(self.servers.has(invitDetails.GroupID) == false &&
invitDetails.GroupType == groupsEndpoints.GroupType.Large){
var server = new serverModule.Server(invitDetails.GroupID, self);
}
callback();
} else {
callback(errors);
}
});
}
else {
callback(errors);
}
})
}
/**
* @description Get a [User]{@link User} object from its ID.
* @param {number} userID ID of the curse user
* @return {User} Corresponding User object
* @example
* var myUser = client.getUser(myUserID);
*/
getUser(userID){
if(userID === 0){
return null;
}
if(this.users.has(userID)){
return this.users.get(userID);
}
else {
return new usersModule.User(userID, this);
}
}
/**
* @description Closes the [Client]{@link Client} by ending the notifier connection and returning
the run function.
*/
close(){
this._notifier.close();
winston.log('info', 'Client.close', 'Notifier connection closed.');
clearTimeout(this._renewSessionTimeout);
//clean client internal stuff
this._loginSession = undefined;
this.token = undefined;
this._tokenExpires = undefined;
this._tokenRenewAfter = undefined;
this.clientID = undefined;
this._timeGapToServer = undefined;
this._connected = false;
this._readySent = false;
this._notifier = undefined;
}
getServerTime(){
//Comment warning: if you're going to use that, then there is something not right,
//try to adjust your timestamp before starting using this function.
//TODO Calculate and return server timestamp
}
}
exports.Client = Client;