code refactoring

This commit is contained in:
Rodolphe Breard 2013-02-27 15:37:08 +01:00
parent e78c5f919e
commit 5ee9143b71
12 changed files with 445 additions and 246 deletions

22
background.js Normal file
View file

@ -0,0 +1,22 @@
//
// Copyright (c) 2012 Rodolphe Breard
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('chromesoul.html', {
'width': 800,
'height': 500
});
});

View file

@ -18,7 +18,6 @@
</div>
<div id="main-ctn">
<p id="pre-conf-pannel">You haven't configured chromesoul yet, you should go to the configuration pannel.</p>
<div id="config-pannel">
<input type="text" placeholder="Login" id="login" class="opt" value=""><br>
<input type="password" placeholder="Password (SOCKS)" id="pwd_socks" class="opt" value=""><br>
@ -33,13 +32,14 @@
</div>
<script type="text/javascript" src="third-party/md5-min.js"></script>
<script type="text/javascript" src="third-party/ab-str.js"></script>
<script type="text/javascript" src="lib/txt_socket.js"></script>
<script type="text/javascript" src="lib/client.js"></script>
<script type="text/javascript" src="lib/ns_client.js"></script>
<script type="text/javascript" src="lib/avatars.js"></script>
<script type="text/javascript" src="lib/options.js"></script>
<script type="text/javascript" src="lib/tab.nsui.js"></script>
<script type="text/javascript" src="lib/contacts.nsui.js"></script>
<script type="text/javascript" src="lib/nsui.js"></script>
<script type="text/javascript" src="start.js"></script>
<script type="text/javascript" src="chromesoul.js"></script>
</body>
</html>

View file

@ -1,22 +1,21 @@
//
// Copyright (c) 2012 Rodolphe Breard
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
var chromesoul = (function() {
var chromesoul = {
"opts": new OptionsManager(),
"ui": new Nsui(),
"contacts": new ContactList(),
"client": new Client(),
"avatars": new AvatarManager()
};
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('chromesoul.html', {
'width': 400,
'height': 500
});
return (window.chromesoul = window.$cs = chromesoul);
})();
$cs.opts.init();
$cs.ui.init();
$cs.client.init(new NsClient(), $cs.ui);
$cs.contacts.init();
$cs.ui.setReconnect(function() {
$cs.client.disconnect();
$cs.client.connect();
});

124
lib/client.js Normal file
View file

@ -0,0 +1,124 @@
//
// Copyright (c) 2013 Rodolphe Breard
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
var Client = function() {
this.socket = null;
this.is_connected = false;
this.client = null;
this.ui = null;
this.status = null;
this.contacts = [];
};
Client.prototype.createSocket = function() {
if (this.socket === null) {
this.socket = new TxtSocket();
this.socket.onError = (function(elem) {
return function() {
elem.is_connected = false;
elem.connect();
};
})(this);
}
};
Client.prototype.connect = function() {
if (!this.is_connected) {
this.createSocket();
this.socket.connect(this.client.host, this.client.port, (function(elem) {
return function() {
elem.client.connect(elem, function() {
if (elem.is_connected) {
elem.daemonize();
elem.changeStatus();
} else {
elem.disconnect();
}
});
};
})(this));
}
}
Client.prototype.disconnect = function() {
if (this.is_connected) {
this.is_connected = false;
this.client.disconnect(this);
this.socket.disconnect();
this.socket = null;
}
this.changeStatus(this.client.status_disconnected);
};
Client.prototype.reconnect = function() {
this.disconnect();
this.connect();
};
Client.prototype.daemonize = function() {
this.socket.read((function(elem) {
return function(data) {
elem.client.recv(elem, data);
elem.daemonize();
};
})(this));
};
Client.prototype.changeStatus = function(status) {
if (typeof status === 'undefined') {
status = this.client.default_status;
}
if (status !== this.status) {
this.status = status;
this.updateStatus();
}
};
Client.prototype.updateStatus = function() {
if (this.status !== null) {
this.client.changeStatus(this);
}
};
Client.prototype.addContact = function(name) {
if (this.is_connected) {
this.client.addContact(this, name);
}
};
Client.prototype.rmContact = function(name) {
if (this.is_connected) {
this.client.rmContact(this, name);
}
};
Client.prototype.speak = function(to, msg) {
if (this.is_connected) {
this.client.speak(this, to, msg);
}
};
Client.prototype.init = function(client, ui) {
this.client = client;
this.ui = ui;
this.connect();
setInterval((function(elem) {
return function() {
elem.connect();
};
})(this), 10000);
};

View file

@ -76,6 +76,7 @@ ContactList.prototype.addContact = function(name) {
this.save();
this.insertContact(infos);
}
$cs.client.addContact(name);
};
ContactList.prototype.rmContact = function(name) {
@ -86,6 +87,10 @@ ContactList.prototype.rmContact = function(name) {
}
};
ContactList.prototype.changeContactStatus = function(name, status) {
console.log(name + ' changed his status to ' + status);
};
ContactList.prototype.save = function() {
var i, data = {"contact_list": []};

View file

@ -15,46 +15,180 @@
//
var NsClient = function() {
this.state = "actif";
this.allowed_statuses = ["actif", "away", "idle", "lock"];
this.is_connected = false;
this.socket = null;
this.actions = {};
this.host = 'ns-server.epita.fr';
this.port = 4242;
this.default_status = 'actif';
this.status_disconnected = 'disconnected';
this.actions_enabled = ['ping', 'msg', 'status', 'login_out', 'who'];
this.actions.ping = {};
this.actions.ping.is = function(msg) {
return msg.substr(0, 5) === "ping ";
this.action = {};
this.action.ping = function(client, str) {
if (str.substr(0, 5) !== 'ping ') {
return false;
}
client.socket.write(str, function(inf) {});
return true;
};
this.actions.ping.act = (function(elem) {
return function(msg) {
chrome.socket.write(elem.socket, str2ab(msg), function(w_inf) {});
this.action.msg = (function(elem) {
return function(client, str) {
var mo = {}, mch = /user_cmd (\d+):user:.*?:(.*?)@(.*?):.*?:(.*?):(.*?) \| msg ([^ ]*)/.exec(str);
if (mch === null) {
return false;
}
mo.socket = mch[1];
mo.login = mch[2];
mo.host = mch[3];
mo.location = elem.msgDecode(mch[4]);
mo.group = mch[5];
mo.message = elem.msgDecode(mch[6]);
$cs.ui.addContentToTab(mo.login, mo); // TODO: FIX ME!
return true;
};
})(this);
this.actions.msg = {};
this.actions.msg.exp = /user_cmd (\d+):user:.*?:(.*?)@(.*?):.*?:(.*?):(.*?) \| msg ([^ ]*)/;
this.actions.msg.is = (function(exp) {
return function(msg) {
return exp.exec(msg) !== null;
};
})(this.actions.msg.exp);
this.actions.msg.act = (function(elem) {
return function(msg) {
var mo = {};
this.action.status = (function(elem) {
return function(client, str) {
var mch = /user_cmd (\d+):user:.*?:(.*?)@(.*?):.*?:(.*?):(.*?) \| state ([^ ]*):(\d+)/.exec(str);
mch = elem.actions.msg.exp.exec(msg);
if (mch !== null) {
mo.socket = mch[1];
mo.login = mch[2];
mo.host = mch[3];
mo.location = elem.msgDecode(mch[4]);
mo.group = mch[5];
mo.message = elem.msgDecode(mch[6]);
if (mch === null) {
return false;
}
$cs.ui.addContentToTab(mo.login, mo);
}
$cs.contacts.changeContactStatus(mch[2], this.msgDecode(mch[6])); // TODO: FIX ME!
return true;
};
})(this);
this.action.login_out = function(client, str) {
var status = null, mch = /user_cmd (\d+):user:.*?:(.*?)@(.*?):.*?:(.*?):(.*?) \| (login|logout)/.exec(str);
if (mch === null) {
return false;
}
if (mch[6] === 'login') {
status = 'actif';
}
$cs.contacts.changeContactStatus(mch[2], status); // TODO: FIX ME!
return true;
};
this.action.who = (function(elem) {
return function(client, str) {
var mch = /user_cmd (\d+):user:.*?:(.*?)@(.*?):.*?:(.*?):(.*?) \| who (\d+) (.*?) (.*?) (\d+) (\d+) (\d+) (\d+) (.*?) (.*?) (.*?) (.*?):(.*) (.*)/.exec(str);
if (mch === null) {
return false;
}
$cs.contacts.changeContactStatus(mch[7], this.msgDecode(mch[16])); // TODO: FIX ME!
return true;
};
})(this);
};
NsClient.prototype.connect = function(client, callback) {
var login, pwd_socks;
if (!client.is_connected) {
login = $cs.opts.get('login');
pwd_socks = $cs.opts.get('pwd_socks');
if (login !== null && pwd_socks !== null) {
client.socket.read(function(data) {
var auth = '';
data = data.split(' ');
auth = 'ext_user_log ';
auth += login + ' ';
auth += hex_md5(data[2] + '-' + data[3] + '/' + data[4] + pwd_socks) + ' ';
auth += 'chromesoul chromesoul\n';
client.socket.write('auth_ag ext_user none none\n', function(inf) {
client.socket.read(function(data) {
client.socket.write(auth, function(inf) {
client.socket.read(function(data) {
if (data === 'rep 002 -- cmd end') {
client.is_connected = true;
console.info('connected to the netsoul server');
} else {
console.error('authentication failure');
}
callback();
});
});
});
});
});
} else {
callback();
}
} else {
callback();
}
};
NsClient.prototype.disconnect = function(client) {
var msg = 'user_cmd msg_user exit\n';
client.socket.write(msg, function() {
client.socket.disconnect();
});
};
NsClient.prototype.changeStatus = function(client) {
if (client.is_connected) {
status_msg = 'user_cmd state ';
status_msg += this.msgEncode(client.status) + ':';
status_msg += Math.round(new Date().getTime() / 1000) + '\n';
client.socket.write(status_msg, function(inf) {
$cs.ui.onUserStatusChange(client.status);
});
} else {
$cs.ui.onUserStatusChange(client.status);
}
};
NsClient.prototype.recv = function(client, data) {
console.log('Debug: ' + data); // TODO: REMOVE ME!
for (var i = this.actions_enabled.length - 1; i >= 0; --i) {
if (this.action[this.actions_enabled[i]](client, data)) {
break;
}
}
};
NsClient.prototype.addContact = function(client, name) {
// TODO
};
NsClient.prototype.rmContact = function(client, name) {
// TODO
};
NsClient.prototype.speak = function(client, to, msg) {
msg = 'user_cmd msg_user ' + to + ' msg ' + this.msgEncode(msg) + '\n';
client.socket.write(msg, function(inf) {});
};
NsClient.prototype.addContact = function(client, name) {
var msg = 'user_cmd watch_log_user {' + name + '}\n';
client.socket.write(msg, function(inf) {
var msg = 'user_cmd who {' + name + '}\n';
console.log('stalking ' + name + ': ' + msg);
client.socket.write(msg, function(inf) {});
});
};
NsClient.prototype.rmContact = function(client, name) {
};
NsClient.prototype.replacePairs = function(str, pairs) {
@ -68,150 +202,18 @@ NsClient.prototype.replacePairs = function(str, pairs) {
NsClient.prototype.msgDecode = function(msg) {
return this.replacePairs(unescape(msg), {
"@": /%40/g,
"*": /%2A/g,
"/": /%2F/g,
"+": /%2B/g
'@': /%40/g,
'*': /%2A/g,
'/': /%2F/g,
'+': /%2B/g
});
};
NsClient.prototype.msgEncode = function(msg) {
return this.replacePairs(escape(msg), {
"%40": /@/g,
"%2A": /\*/g,
"%2F": /\//g,
"%2B": /\+/g,
'%40': /@/g,
'%2A': /\*/g,
'%2F': /\//g,
'%2B': /\+/g,
});
};
NsClient.prototype.connect = function() {
var login, pwd_socks;
if (!this.is_connected) {
login = $cs.opts.get('login');
pwd_socks = $cs.opts.get('pwd_socks');
if (login !== null && pwd_socks !== null) {
chrome.socket.create('tcp', {}, (function(elem) {
return function(sock_inf) {
elem.socket = sock_inf.socketId;
chrome.socket.connect(elem.socket, "ns-server.epita.fr", 4242, function(res) {
chrome.socket.read(elem.socket, null, function(rd_inf) {
if (rd_inf.resultCode > 0) {
var data = ab2str(rd_inf.data).split(' '),
auth = "ext_user_log ";
auth += login + " ";
auth += hex_md5(data[2] + "-" + data[3] + "/" + data[4] + pwd_socks) + " ";
auth += "chromesoul chromesoul\n";
chrome.socket.write(elem.socket, str2ab("auth_ag ext_user none none\n"), function(w_inf) {
chrome.socket.read(elem.socket, null, function(rd_inf) {
if (rd_inf.resultCode > 0) {
chrome.socket.write(elem.socket, str2ab(auth), function(w_inf) {
chrome.socket.read(elem.socket, null, function(rd_inf) {
if (rd_inf.resultCode > 0) {
elem.is_connected = true;
console.info("connected to the netsoul server");
elem.updateStatus();
elem.daemonize();
}
});
});
}
});
});
}
});
});
};
})(this));
}
}
};
NsClient.prototype.disconnect = function() {
if (this.is_connected) {
chrome.socket.disconnect(this.socket);
this.is_connected = false;
console.info("disconnected");
this.updateStatus();
}
};
NsClient.prototype.daemonize = function() {
if (this.is_connected) {
chrome.socket.read(this.socket, (function(elem) {
return function(rd_inf) {
if (rd_inf.resultCode > 0) {
var at, data = ab2str(rd_inf.data);
for (at in elem.actions) {
if (elem.actions[at].is(data)) {
elem.actions[at].act(data);
break ;
}
}
elem.daemonize();
} else {
/*
* A read error is the only way to know if the remote peer has disconnected.
* See <http://developer.chrome.com/apps/socket.html> for more informations.
*/
elem.disconnect();
console.info('connection lost, reconnecting...');
}
};
})(this));
} else {
console.error("unable to daemonize: not connected");
}
};
NsClient.prototype.sendMessage = function(to, message) {
var msg = "user_cmd msg_user " + to + " msg " + this.msgEncode(message) + "\n";
chrome.socket.write(this.socket, str2ab(msg), function(w_inf) {});
};
NsClient.prototype.updateStatus = function() {
var status = "disconnected", status_msg = "";
if (this.is_connected) {
status_msg = "user_cmd state ";
status = this.state;
status_msg += this.state + ":";
status_msg += Math.round(new Date().getTime() / 1000) + "\n";
chrome.socket.write(this.socket, str2ab(status_msg), function(w_inf) {});
}
$cs.ui.onUserStatusChange(status);
};
NsClient.prototype.changeStatus = function(new_status) {
if (this.is_connected) {
if (this.allowed_statuses.indexOf(new_status) !== -1) {
this.status = new_status;
} else {
console.warn("invalid status: " + new_status);
}
} else {
console.warn("not connected");
}
};
NsClient.prototype.init = function() {
var status_update = function(elem) {
return function() {
elem.updateStatus();
};
},
connect = function(elem) {
return function() {
if (!elem.is_connected) {
elem.connect();
}
};
};
status_update(this).apply();
setTimeout(connect(this), 500);
setInterval(connect(this), 10000);
setInterval(status_update(this), 600000);
};

View file

@ -20,7 +20,10 @@ var Nsui = function() {
};
Nsui.prototype.setReconnect = function(func) {
document.getElementById("user-status").addEventListener("click", func, false);
document.getElementById("user-status").addEventListener("click", function() {
func();
$cs.contacts.restore();
}, false);
};
Nsui.prototype.onUserStatusChange = function(new_status) {
@ -179,7 +182,7 @@ Nsui.prototype.sanitizeText = function(str) {
};
Nsui.prototype.showContent = function(part_id) {
var i, ctn_lst = ["config-pannel", "chat-pannel", "pre-conf-pannel"];
var i, ctn_lst = ["config-pannel", "chat-pannel"];
for (i = ctn_lst.length - 1; i>= 0; --i) {
if (ctn_lst[i] === part_id) {
@ -229,9 +232,4 @@ Nsui.prototype.init = function() {
})(this);
this.showContent("chat-pannel");
setTimeout(function() {
if ($cs.opts.get("login") === null || $cs.opts.get("pwd_socks") === null) {
$cs.ui.showContent("pre-conf-pannel");
}
}, 600);
};

View file

@ -73,7 +73,7 @@ Tab.prototype.initBodyElement = function() {
this.value = "";
if (typeof $cs.client !== "undefined" && $cs.opts.get("enable_msg")) {
$cs.client.sendMessage(elem.name, msg);
$cs.client.speak(elem.name, msg);
$cs.ui.addContentToTab(elem.name, {"message": msg});
} else {
console.error("chromesoul client not found");

91
lib/txt_socket.js Normal file
View file

@ -0,0 +1,91 @@
0000263103
0//
// Copyright (c) 2013 Rodolphe Breard
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
var TxtSocket = function() {
this.socket_id = null;
this.buffer = '';
this.onError = function(infos) {};
};
TxtSocket.prototype.connect = function(host, port, callback) {
chrome.socket.create('tcp', {}, (function(elem) {
return function(inf) {
elem.socket_id = inf.socketId;
chrome.socket.connect(elem.socket_id, host, port, callback);
};
})(this));
};
TxtSocket.prototype.disconnect = function() {
if (this.socket_id !== null) {
chrome.socket.disconnect(this.socket_id);
chrome.socket.destroy(this.socket_id);
}
};
TxtSocket.prototype.read = function(callback) {
var tmp = '', offset = this.buffer.indexOf("\n");
if (offset === -1) {
chrome.socket.read(this.socket_id, (function(elem) {
return function(rd_inf) {
if (rd_inf.resultCode > 0) {
elem.buffer += elem.ab2str(rd_inf.data);
elem.read(callback);
} else {
elem.throwError({code: rd_inf.resultCode});
}
};
})(this));
} else {
tmp = this.buffer.substr(0, offset);
this.buffer = this.buffer.substr(offset + 1);
callback(tmp);
}
};
TxtSocket.prototype.write = function(str, callback) {
chrome.socket.write(this.socket_id, this.str2ab(str), (function(elem) {
return function(w_inf) {
if (w_inf.bytesWritten >= 0) {
callback(w_inf);
} else {
elem.throwError({code: w_inf.bytesWritten});
}
};
})(this));
};
TxtSocket.prototype.throwError = function(infos) {
console.error('socket error ' + infos.code + ', shutting it down');
this.disconnect();
this.onError(infos);
};
TxtSocket.prototype.ab2str = function(buff) {
return String.fromCharCode.apply(null, new Uint8Array(buff));
};
TxtSocket.prototype.str2ab = function(str) {
var i = 0, buff = new ArrayBuffer(str.length), buff_v = new Uint8Array(buff);
for (i = str.length - 1; i >= 0; --i) {
buff_v[i] = str.charCodeAt(i);
}
return buff;
}

View file

@ -1,6 +1,6 @@
{
"name": "Chromesoul",
"version": "0.4.9",
"version": "0.5.0",
"minimum_chrome_version": "25",
"manifest_version": 2,
"offline_enabled": false,
@ -11,7 +11,7 @@
},
"app": {
"background": {
"scripts": ["chromesoul.js"]
"scripts": ["background.js"]
}
},
"permissions": [

View file

@ -1,21 +0,0 @@
var chromesoul = (function() {
var chromesoul = {
"opts": new OptionsManager(),
"ui": new Nsui(),
"contacts": new ContactList(),
"client": new NsClient(),
"avatars": new AvatarManager()
};
return (window.chromesoul = window.$cs = chromesoul);
})();
$cs.opts.init();
$cs.ui.init();
$cs.client.init();
$cs.contacts.init();
$cs.ui.setReconnect(function() {
$cs.client.disconnect();
$cs.client.connect();
});

21
third-party/ab-str.js vendored
View file

@ -1,21 +0,0 @@
//
// Renato Mangini
// http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
// http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
//
// Edited by Rodolphe Breard
// - Using Uint8Array instead of Uint16Array
//
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length);
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}