You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
720 lines
27 KiB
720 lines
27 KiB
(function() {
|
|
'use strict';
|
|
|
|
var weechat = angular.module('weechat');
|
|
|
|
weechat.factory('connection',
|
|
['$rootScope', '$log', 'handlers', 'models', 'settings', 'ngWebsockets', 'utils', function($rootScope,
|
|
$log,
|
|
handlers,
|
|
models,
|
|
settings,
|
|
ngWebsockets,
|
|
utils) {
|
|
|
|
var protocol = new weeChat.Protocol();
|
|
|
|
var connectionData = [];
|
|
var reconnectTimer;
|
|
|
|
// Global connection lock to prevent multiple connections from being opened
|
|
var locked = false;
|
|
|
|
// Takes care of the connection and websocket hooks
|
|
var connect = function (host, port, path, passwd, ssl, useTotp, totp, noCompression, successCallback, failCallback) {
|
|
$rootScope.passwordError = false;
|
|
$rootScope.oldWeechatError = false;
|
|
$rootScope.hashAlgorithemDisagree = false;
|
|
connectionData = [host, port, path, passwd, ssl, noCompression];
|
|
var proto = ssl ? 'wss' : 'ws';
|
|
// If host is an IPv6 literal wrap it in brackets
|
|
if (host.indexOf(":") !== -1 && host[0] !== "[" && host[host.length-1] !== "]") {
|
|
host = "[" + host + "]";
|
|
}
|
|
var url = proto + "://" + host + ":" + port + "/" + path;
|
|
$log.debug('Connecting to URL: ', url);
|
|
|
|
|
|
var weechatIsPre2_9 = false;
|
|
var onopen = function () {
|
|
|
|
var _performHandshake = function() {
|
|
return new Promise(function(resolve) {
|
|
|
|
// First a handshake is sent to determine authentication method
|
|
// This is only supported for weechat >= 2.9
|
|
// If after 'a while' weechat does not respond
|
|
// stop waiting for the handshake and assume it's an old version
|
|
// This time is debatable, high latency connections may wrongfully
|
|
// think weechat is an older version. This time is purposfully set
|
|
// too high, this time should be reduced if determined the weechat
|
|
// is lower than 2.9
|
|
// This time also includes the time it takes to generate the hash
|
|
const WAIT_TIME_OLD_WEECHAT = 2000; //ms
|
|
|
|
// Wait long enough to assume we are on a version < 2.9
|
|
var handShakeTimeout = setTimeout(function () {
|
|
weechatIsPre2_9 = true;
|
|
console.log('Weechat\'s version is assumed to be < 2.9');
|
|
resolve();
|
|
}, WAIT_TIME_OLD_WEECHAT);
|
|
|
|
// Or wait for a response from the handshake
|
|
ngWebsockets.send(
|
|
weeChat.Protocol.formatHandshake({
|
|
password_hash_algo: "pbkdf2+sha512", compression: noCompression ? 'off' : 'zlib'
|
|
})
|
|
).then(function (message){
|
|
clearTimeout(handShakeTimeout);
|
|
resolve(message);
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
var _askTotp = function (useTotp) {
|
|
return new Promise(function(resolve) {
|
|
|
|
// If weechat is < 2.9 the totp will be a setting (checkbox)
|
|
// Otherwise the handshake will specify it
|
|
if ( useTotp ) {
|
|
// Ask the user to input his TOTP
|
|
var totp = prompt("Please enter your TOTP Token");
|
|
resolve (totp);
|
|
} else {
|
|
// User does not use TOTP, don't ask
|
|
resolve(null);
|
|
}
|
|
|
|
})
|
|
}
|
|
|
|
// Helper methods for initialization commands
|
|
// This method is used to initialize weechat < 2.9
|
|
var _initializeConnectionPre29 = function(passwd, totp) {
|
|
|
|
// This is not secure, this has to be specifically allowed with a setting
|
|
// Otherwise an attacker could persuade the client to send it's password
|
|
// Or due to latency the client could think weechat was an older version
|
|
if (!settings.allowPlaintextAuthentication)
|
|
{
|
|
$rootScope.oldWeechatError = true;
|
|
$rootScope.$emit('relayDisconnect');
|
|
$rootScope.$digest() // Have to do this otherwise change detection doesn't see the error.
|
|
throw new Error('Plainttext authentication not allowed.');
|
|
}
|
|
|
|
// Escape comma in password (#937)
|
|
passwd = passwd.replace(',', '\\,');
|
|
|
|
ngWebsockets.send(
|
|
weeChat.Protocol.formatInitPre29({
|
|
password: passwd,
|
|
compression: noCompression ? 'off' : 'zlib',
|
|
totp: totp
|
|
})
|
|
);
|
|
|
|
// Wait a little bit until the init is sent
|
|
return new Promise(function(resolve) {
|
|
setTimeout(() => resolve(), 5);
|
|
})
|
|
|
|
};
|
|
|
|
// Helper methods for initialization commands
|
|
// This method is used to initialize weechat >= 2.9
|
|
var salt;
|
|
var _initializeConnection29 = function(passwd, nonce, iterations, totp) {
|
|
|
|
return window.crypto.subtle.importKey(
|
|
|
|
'raw',
|
|
utils.stringToUTF8Array(passwd),
|
|
{name: 'PBKDF2'},//{name: 'HMAC', hash: 'SHA-512'},
|
|
false,
|
|
['deriveBits']
|
|
|
|
).then( function (key) {
|
|
|
|
salt = utils.concatenateTypedArray(utils.concatenateTypedArray(nonce, new Uint8Array([0x3A])), window.crypto.getRandomValues(new Uint8Array(16))); //nonce:cnonce, 3A is a ':' in ASCII
|
|
return window.crypto.subtle.deriveBits(
|
|
{
|
|
name: 'PBKDF2',
|
|
hash: 'SHA-512',
|
|
salt: salt,
|
|
iterations: iterations,
|
|
},
|
|
key, //your key from generateKey or importKey
|
|
512
|
|
);
|
|
|
|
}).then( function (hash) {
|
|
|
|
|
|
ngWebsockets.send(
|
|
weeChat.Protocol.formatInit29(
|
|
'pbkdf2+sha512:' + utils.bytetoHexString(salt) + ':100000:' + utils.bytetoHexString(hash),
|
|
totp
|
|
)
|
|
);
|
|
|
|
// Wait a little bit until the init is sent
|
|
return new Promise(function(resolve) {
|
|
|
|
setTimeout(() => resolve(), 5);
|
|
|
|
})
|
|
|
|
});
|
|
|
|
};
|
|
|
|
var _requestHotlist = function() {
|
|
return ngWebsockets.send(
|
|
weeChat.Protocol.formatHdata({
|
|
path: "hotlist:gui_hotlist(*)",
|
|
keys: []
|
|
})
|
|
);
|
|
};
|
|
|
|
var _requestBufferInfos = function() {
|
|
return ngWebsockets.send(
|
|
weeChat.Protocol.formatHdata({
|
|
path: 'buffer:gui_buffers(*)',
|
|
keys: ['local_variables,notify,number,full_name,short_name,title,hidden,type']
|
|
})
|
|
);
|
|
};
|
|
|
|
var _requestSync = function() {
|
|
return ngWebsockets.send(
|
|
weeChat.Protocol.formatSync({})
|
|
);
|
|
};
|
|
|
|
var _parseWeechatTimeFormat = function() {
|
|
// helper function to get a custom delimiter span
|
|
var _timeDelimiter = function(delim) {
|
|
return "'<span class=\"cof-chat_time_delimiters cob-chat_time_delimiters coa-chat_time_delimiters\">" + delim + "</span>'";
|
|
};
|
|
|
|
// Fetch the buffer time format from weechat
|
|
var timeFormat = models.wconfig['weechat.look.buffer_time_format'];
|
|
|
|
// Weechat uses strftime, with time specifiers such as %I:%M:%S for 12h time
|
|
// The time formatter we use, AngularJS' date filter, uses a different format
|
|
// Where %I:%M:%S would be represented as hh:mm:ss
|
|
// Here, we detect what format the user has set in Weechat and slot it into
|
|
// one of four formats, (short|long) (12|24)-hour time
|
|
var angularFormat = "";
|
|
|
|
var timeDelimiter = _timeDelimiter(":");
|
|
|
|
var left12 = "hh" + timeDelimiter + "mm";
|
|
var right12 = "' 'a";
|
|
|
|
var short12 = left12 + right12;
|
|
var long12 = left12 + timeDelimiter + "ss" + right12;
|
|
|
|
var short24 = "HH" + timeDelimiter + "mm";
|
|
var long24 = short24 + timeDelimiter + "ss";
|
|
|
|
if (timeFormat.indexOf("%H") > -1 ||
|
|
timeFormat.indexOf("%k") > -1) {
|
|
// 24h time detected
|
|
if (timeFormat.indexOf("%S") > -1) {
|
|
// show seconds
|
|
angularFormat = long24;
|
|
} else {
|
|
// don't show seconds
|
|
angularFormat = short24;
|
|
}
|
|
} else if (timeFormat.indexOf("%I") > -1 ||
|
|
timeFormat.indexOf("%l") > -1 ||
|
|
timeFormat.indexOf("%p") > -1 ||
|
|
timeFormat.indexOf("%P") > -1) {
|
|
// 12h time detected
|
|
if (timeFormat.indexOf("%S") > -1) {
|
|
// show seconds
|
|
angularFormat = long12;
|
|
} else {
|
|
// don't show seconds
|
|
angularFormat = short12;
|
|
}
|
|
} else if (timeFormat.indexOf("%r") > -1) {
|
|
// strftime doesn't have an equivalent for short12???
|
|
angularFormat = long12;
|
|
} else if (timeFormat.indexOf("%T") > -1) {
|
|
angularFormat = long24;
|
|
} else if (timeFormat.indexOf("%R") > -1) {
|
|
angularFormat = short24;
|
|
} else {
|
|
angularFormat = short24;
|
|
}
|
|
|
|
// Assemble date format
|
|
var date_components = [];
|
|
|
|
// Check for day of month in time format
|
|
var day_pos = Math.max(timeFormat.indexOf("%d"),
|
|
timeFormat.indexOf("%e"));
|
|
date_components.push([day_pos, "dd"]);
|
|
|
|
// month of year?
|
|
var month_pos = timeFormat.indexOf("%m");
|
|
date_components.push([month_pos, "MM"]);
|
|
|
|
// year as well?
|
|
var year_pos = Math.max(timeFormat.indexOf("%y"),
|
|
timeFormat.indexOf("%Y"));
|
|
if (timeFormat.indexOf("%y") > -1) {
|
|
date_components.push([year_pos, "yy"]);
|
|
} else if (timeFormat.indexOf("%Y") > -1) {
|
|
date_components.push([year_pos, "yyyy"]);
|
|
}
|
|
|
|
// if there is a date, assemble it in the right order
|
|
date_components.sort();
|
|
var format_array = [];
|
|
for (var i = 0; i < date_components.length; i++) {
|
|
if (date_components[i][0] == -1) continue;
|
|
format_array.push(date_components[i][1]);
|
|
}
|
|
if (format_array.length > 0) {
|
|
// TODO: parse delimiter as well? For now, use '/' as it is
|
|
// more common internationally than '-'
|
|
var date_format = format_array.join(_timeDelimiter("/"));
|
|
angularFormat = date_format + _timeDelimiter(" ") + angularFormat;
|
|
}
|
|
|
|
$rootScope.angularTimeFormat = angularFormat;
|
|
};
|
|
|
|
var passwordMethod
|
|
var totpRequested;
|
|
var nonce;
|
|
var iterations;
|
|
|
|
_performHandshake().then(
|
|
|
|
//Wait for weechat to respond or handshake times out
|
|
function (message)
|
|
{
|
|
// Do nothing if the handshake was received
|
|
// after concluding weechat was an old version
|
|
// TODO maybe warn the user here
|
|
if(weechatIsPre2_9) {
|
|
return;
|
|
}
|
|
|
|
passwordMethod = message.objects[0].content.password_hash_algo;
|
|
totpRequested = message.objects[0].content.totp === 'on' ? true : false;
|
|
nonce = utils.hexStringToByte(message.objects[0].content.nonce);
|
|
iterations = message.objects[0].content.password_hash_iterations;
|
|
|
|
if(passwordMethod != "pbkdf2+sha512")
|
|
{
|
|
$rootScope.hashAlgorithemDisagree = true;
|
|
$rootScope.$emit('relayDisconnect');
|
|
$rootScope.$digest() // Have to do this otherwise change detection doesn't see the error.
|
|
throw new Error('No password hash algorithem returned.');
|
|
}
|
|
|
|
}
|
|
|
|
).then( function() {
|
|
|
|
if(weechatIsPre2_9)
|
|
{
|
|
// Ask the user for the TOTP token if this is enabled
|
|
return _askTotp(useTotp)
|
|
.then( function (totp) {
|
|
return _initializeConnectionPre29(passwd, totp)
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
// Weechat version >= 2.9
|
|
return _askTotp(totpRequested)
|
|
.then( function(totp) {
|
|
return _initializeConnection29(passwd, nonce, iterations, totp)
|
|
})
|
|
|
|
}
|
|
|
|
}).then( function(){
|
|
|
|
// The Init was sent, weechat will not respond
|
|
// Wait until either the connection closes
|
|
// Or try to send version and see if weechat responds
|
|
return ngWebsockets.send(
|
|
weeChat.Protocol.formatInfo({
|
|
name: 'version'
|
|
})
|
|
);
|
|
|
|
}).then( function(version) {
|
|
|
|
// From now on we are assumed initialized
|
|
// We don't know for sure because weechat does not respond
|
|
// All we know is the socket wasn't closed afer waiting a little bit
|
|
console.log('Succesfully connected');
|
|
$rootScope.waseverconnected = true;
|
|
handlers.handleVersionInfo(version);
|
|
|
|
// Send all the other commands required for initialization
|
|
_requestBufferInfos().then(function(bufinfo) {
|
|
handlers.handleBufferInfo(bufinfo);
|
|
});
|
|
|
|
_requestHotlist().then(function(hotlist) {
|
|
handlers.handleHotlistInfo(hotlist);
|
|
|
|
});
|
|
if (settings.hotlistsync) {
|
|
// Schedule hotlist syncing every so often so that this
|
|
// client will have unread counts (mostly) in sync with
|
|
// other clients or terminal usage directly.
|
|
setInterval(function() {
|
|
if ($rootScope.connected) {
|
|
_requestHotlist().then(function(hotlist) {
|
|
handlers.handleHotlistInfo(hotlist);
|
|
|
|
});
|
|
}
|
|
}, 60000); // Sync hotlist every 60 second
|
|
}
|
|
|
|
// Fetch weechat time format for displaying timestamps
|
|
fetchConfValue('weechat.look.buffer_time_format',
|
|
function() {
|
|
// Will set models.wconfig['weechat.look.buffer_time_format']
|
|
_parseWeechatTimeFormat();
|
|
});
|
|
|
|
// Fetch nick completion config
|
|
fetchConfValue('weechat.completion.nick_completer');
|
|
fetchConfValue('weechat.completion.nick_add_space');
|
|
|
|
_requestSync();
|
|
$log.info("Connected to relay");
|
|
$rootScope.connected = true;
|
|
if (successCallback) {
|
|
successCallback();
|
|
}
|
|
|
|
},
|
|
|
|
//Sending version failed
|
|
function() {
|
|
handleWrongPassword();
|
|
});
|
|
};
|
|
|
|
var onmessage = function() {
|
|
|
|
};
|
|
|
|
var onclose = function (evt) {
|
|
/*
|
|
* Handles websocket disconnection
|
|
*/
|
|
$log.info("Disconnected from relay");
|
|
$rootScope.$emit('relayDisconnect');
|
|
locked = false;
|
|
if ($rootScope.userdisconnect || !$rootScope.waseverconnected) {
|
|
handleClose(evt);
|
|
$rootScope.userdisconnect = false;
|
|
} else {
|
|
reconnect(evt);
|
|
}
|
|
handleWrongPassword();
|
|
};
|
|
|
|
var handleClose = function (evt) {
|
|
if (ssl && evt && evt.code === 1006) {
|
|
// A password error doesn't trigger onerror, but certificate issues do. Check time of last error.
|
|
if (typeof $rootScope.lastError !== "undefined" && (Date.now() - $rootScope.lastError) < 1000) {
|
|
// abnormal disconnect by client, most likely ssl error
|
|
$rootScope.sslError = true;
|
|
$rootScope.$apply();
|
|
}
|
|
}
|
|
};
|
|
|
|
var handleWrongPassword = function() {
|
|
// Connection got closed, lets check if we ever was connected successfully
|
|
if (!$rootScope.waseverconnected && !$rootScope.errorMessage && !$rootScope.oldWeechatError && !$rootScope.hashAlgorithemDisagree) {
|
|
$rootScope.passwordError = true;
|
|
$rootScope.$apply();
|
|
}
|
|
};
|
|
|
|
var onerror = function (evt) {
|
|
/*
|
|
* Handles cases when connection issues come from
|
|
* the relay.
|
|
*/
|
|
$log.error("Relay error", evt);
|
|
locked = false; // release connection lock
|
|
$rootScope.lastError = Date.now();
|
|
|
|
if (evt.type === "error" && this.readyState !== 1) {
|
|
ngWebsockets.failCallbacks('error');
|
|
$rootScope.errorMessage = true;
|
|
}
|
|
};
|
|
|
|
if (locked) {
|
|
// We already have an open connection
|
|
$log.debug("Aborting connection (lock in use)");
|
|
}
|
|
// Kinda need a compare-and-swap here...
|
|
locked = true;
|
|
|
|
try {
|
|
ngWebsockets.connect(url,
|
|
protocol,
|
|
{
|
|
'binaryType': "arraybuffer",
|
|
'onopen': onopen,
|
|
'onclose': onclose,
|
|
'onmessage': onmessage,
|
|
'onerror': onerror
|
|
});
|
|
} catch(e) {
|
|
locked = false;
|
|
$log.debug("Websocket caught DOMException:", e);
|
|
$rootScope.lastError = Date.now();
|
|
$rootScope.errorMessage = true;
|
|
$rootScope.securityError = true;
|
|
$rootScope.$emit('relayDisconnect');
|
|
|
|
if (failCallback) {
|
|
failCallback();
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
var attemptReconnect = function (bufferId, timeout) {
|
|
// won't work if totp is mandatory
|
|
if (settings.useTotp)
|
|
{
|
|
$log.info('Not reconnecting because totp will be expired.');
|
|
return;
|
|
}
|
|
|
|
$log.info('Attempting to reconnect...');
|
|
var d = connectionData;
|
|
connect(d[0], d[1], d[2], d[3], d[4], false, "", d[5], function() {
|
|
$rootScope.reconnecting = false;
|
|
// on success, update active buffer
|
|
models.setActiveBuffer(bufferId);
|
|
$log.info('Sucessfully reconnected to relay');
|
|
}, function() {
|
|
// on failure, schedule another attempt
|
|
if (timeout >= 600000) {
|
|
// If timeout is ten minutes or more, give up
|
|
$log.info('Failed to reconnect, giving up');
|
|
handleClose();
|
|
} else {
|
|
$log.info('Failed to reconnect, scheduling next attempt in', timeout/1000, 'seconds');
|
|
// Clear previous timer, if exists
|
|
if (reconnectTimer !== undefined) {
|
|
clearTimeout(reconnectTimer);
|
|
}
|
|
reconnectTimer = setTimeout(function() {
|
|
// exponential timeout increase
|
|
attemptReconnect(bufferId, timeout * 1.5);
|
|
}, timeout);
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
var reconnect = function (evt) {
|
|
if (connectionData.length < 5) {
|
|
// something is wrong
|
|
$log.error('Cannot reconnect, connection information is missing');
|
|
return;
|
|
}
|
|
|
|
// reinitialise everything, clear all buffers
|
|
// TODO: this can be further extended in the future by looking
|
|
// at the last line in ever buffer and request more buffers from
|
|
// WeeChat based on that
|
|
models.reinitialize();
|
|
$rootScope.reconnecting = true;
|
|
// Have to do this to get the reconnect banner to show
|
|
$rootScope.$apply();
|
|
|
|
var bufferId = models.getActiveBuffer().id,
|
|
timeout = 3000; // start with a three-second timeout
|
|
|
|
reconnectTimer = setTimeout(function() {
|
|
attemptReconnect(bufferId, timeout);
|
|
}, timeout);
|
|
};
|
|
|
|
var disconnect = function() {
|
|
$log.info('Disconnecting from relay');
|
|
$rootScope.userdisconnect = true;
|
|
ngWebsockets.send(weeChat.Protocol.formatQuit());
|
|
// In case the backend doesn't repond we will close from our end
|
|
var closeTimer = setTimeout(function() {
|
|
ngWebsockets.disconnect();
|
|
// We pretend we are not connected anymore
|
|
// The connection can time out on its own
|
|
ngWebsockets.failCallbacks('disconnection');
|
|
$rootScope.connected = false;
|
|
locked = false; // release the connection lock
|
|
$rootScope.$emit('relayDisconnect');
|
|
$rootScope.$apply();
|
|
});
|
|
};
|
|
|
|
/*
|
|
* Format and send a weechat message
|
|
*
|
|
* @returns the angular promise
|
|
*/
|
|
var sendMessage = function(message) {
|
|
ngWebsockets.send(weeChat.Protocol.formatInput({
|
|
buffer: models.getActiveBufferReference(),
|
|
data: message
|
|
}));
|
|
};
|
|
|
|
var sendCoreCommand = function(command) {
|
|
ngWebsockets.send(weeChat.Protocol.formatInput({
|
|
buffer: 'core.weechat',
|
|
data: command
|
|
}));
|
|
};
|
|
|
|
var sendHotlistClear = function() {
|
|
if (models.version[0] >= 1) {
|
|
// WeeChat >= 1 supports clearing hotlist with this command
|
|
sendMessage('/buffer set hotlist -1');
|
|
// Also move read marker
|
|
sendMessage('/input set_unread_current_buffer');
|
|
} else {
|
|
// If user wants to sync hotlist with weechat
|
|
// we will send a /buffer bufferName command every time
|
|
// the user switches a buffer. This will ensure that notifications
|
|
// are cleared in the buffer the user switches to
|
|
sendCoreCommand('/buffer ' + models.getActiveBuffer().fullName);
|
|
}
|
|
};
|
|
|
|
var sendHotlistClearAll = function() {
|
|
sendMessage("/input hotlist_clear");
|
|
};
|
|
|
|
var requestNicklist = function(bufferId, callback) {
|
|
// Prevent requesting nicklist for all buffers if bufferId is invalid
|
|
if (!bufferId) {
|
|
return;
|
|
}
|
|
ngWebsockets.send(
|
|
weeChat.Protocol.formatNicklist({
|
|
buffer: "0x"+bufferId
|
|
})
|
|
).then(function(nicklist) {
|
|
handlers.handleNicklist(nicklist);
|
|
if (callback !== undefined) {
|
|
callback();
|
|
}
|
|
});
|
|
};
|
|
|
|
var fetchConfValue = function(name, callback) {
|
|
ngWebsockets.send(
|
|
weeChat.Protocol.formatInfolist({
|
|
name: "option",
|
|
pointer: 0,
|
|
args: name
|
|
})
|
|
).then(function(i) {
|
|
handlers.handleConfValue(i);
|
|
if (callback !== undefined) {
|
|
callback();
|
|
}
|
|
});
|
|
};
|
|
|
|
var fetchMoreLines = function(numLines) {
|
|
$log.debug('Fetching ', numLines, ' lines');
|
|
var buffer = models.getActiveBuffer();
|
|
if (numLines === undefined) {
|
|
// Math.max(undefined, *) = NaN -> need a number here
|
|
numLines = 0;
|
|
}
|
|
// Calculate number of lines to fetch, at least as many as the parameter
|
|
numLines = Math.max(numLines, buffer.requestedLines * 2);
|
|
|
|
// Indicator that we are loading lines, hides "load more lines" link
|
|
$rootScope.loadingLines = true;
|
|
// Send hdata request to fetch lines for this particular buffer
|
|
return ngWebsockets.send(
|
|
weeChat.Protocol.formatHdata({
|
|
// "0x" is important, otherwise it won't work
|
|
path: "buffer:0x" + buffer.id + "/own_lines/last_line(-" + numLines + ")/data",
|
|
keys: []
|
|
})
|
|
).then(function(lineinfo) {
|
|
//XXX move to handlers?
|
|
// delete old lines and add new ones
|
|
var oldLength = buffer.lines.length;
|
|
// whether we already had all unread lines
|
|
var hadAllUnreadLines = buffer.lastSeen >= 0;
|
|
|
|
// clear the old lines
|
|
buffer.lines.length = 0;
|
|
// We need to set the number of requested lines to 0 here, because parsing a line
|
|
// increments it. This is needed to also count newly arriving lines while we're
|
|
// already connected.
|
|
buffer.requestedLines = 0;
|
|
// Count number of lines recieved
|
|
var linesReceivedCount = lineinfo.objects[0].content.length;
|
|
|
|
// Parse the lines
|
|
handlers.handleLineInfo(lineinfo, true);
|
|
|
|
// Correct the read marker for the lines that were counted twice
|
|
buffer.lastSeen -= oldLength;
|
|
|
|
// We requested more lines than we got, no more lines.
|
|
if (linesReceivedCount < numLines) {
|
|
buffer.allLinesFetched = true;
|
|
}
|
|
$rootScope.loadingLines = false;
|
|
|
|
// Only scroll to read marker if we didn't have all unread lines previously, but have them now
|
|
var scrollToReadmarker = !hadAllUnreadLines && buffer.lastSeen >= 0;
|
|
// Scroll to correct position
|
|
$rootScope.scrollWithBuffer(scrollToReadmarker, true);
|
|
});
|
|
};
|
|
|
|
|
|
return {
|
|
connect: connect,
|
|
disconnect: disconnect,
|
|
sendMessage: sendMessage,
|
|
sendCoreCommand: sendCoreCommand,
|
|
sendHotlistClear: sendHotlistClear,
|
|
sendHotlistClearAll: sendHotlistClearAll,
|
|
fetchMoreLines: fetchMoreLines,
|
|
requestNicklist: requestNicklist,
|
|
attemptReconnect: attemptReconnect
|
|
};
|
|
}]);
|
|
})();
|
|
|