2 // Copyright 2012 "Leberwurscht" <leberwurscht@hoegners.de>
4 // This file is dual-licensed under the MIT license (see MIT.txt) and the AGPL license (see jappix/COPYING).
7 function jappixmini_addon_xor(str1, str2) {
8 if (str1.length != str2.length) throw "not same length";
12 for (var i=0; i<str1.length;i++) {
13 var a = str1.charCodeAt(i);
14 var b = str2.charCodeAt(i);
17 encoded += String.fromCharCode(c);
23 function jappixmini_addon_set_client_secret(password) {
24 if (!password) return;
26 var salt1 = "h8doCRekWto0njyQohKpdx6BN0UTyC6N";
27 var salt2 = "jdX8OwFC1kWAq3s9uOyAcE8g3UNNO5t3";
29 var client_secret1 = str_sha1(salt1+password);
30 var client_secret2 = str_sha1(salt2+password);
31 var client_secret = client_secret1 + client_secret2;
33 setPersistent('jappix-mini', 'client-secret', client_secret);
34 console.log("client secret set");
37 function jappixmini_addon_get_client_secret(callback) {
38 var client_secret = getPersistent('jappix-mini', 'client-secret');
39 if (client_secret===null) {
40 var div = document.getElementById("#jappixmini-password-query-div");
43 div = $('<div id="jappixmini-password-query-div" style="position:fixed;padding:1em;background-color:#F00;color:#fff;top:50px;left:650px;">Retype your Friendica password for chatting:<br></div>');
45 var input = $('<input type="password" id="jappixmini-password-query-input">')
48 var button = $('<input type="button" value="OK" id="jappixmini-password-query-button">');
51 $("body").append(div);
54 button.click(function(){
55 var password = $("#jappixmini-password-query-input").val();
56 jappixmini_addon_set_client_secret(password);
59 var client_secret = getPersistent('jappix-mini', 'client-secret');
60 callback(client_secret);
64 callback(client_secret);
68 function jappixmini_addon_encrypt_password(password, callback) {
69 jappixmini_addon_get_client_secret(function(client_secret){
70 // add \0 to password until it has the same length as secret
71 if (password.length>client_secret.length-1) throw "password too long";
72 while (password.length<client_secret.length) {
76 // xor password with secret
77 var encrypted_password = jappixmini_addon_xor(client_secret, password);
79 encrypted_password = encodeURI(encrypted_password)
80 callback(encrypted_password);
84 function jappixmini_addon_decrypt_password(encrypted_password, callback) {
85 encrypted_password = decodeURI(encrypted_password);
87 jappixmini_addon_get_client_secret(function(client_secret){
88 // xor password with secret
89 var password = jappixmini_addon_xor(client_secret, encrypted_password);
92 var first_null = password.indexOf("\0")
93 if (first_null==-1) throw "Decrypted password does not contain \\0";
94 password = password.substr(0, first_null);
100 function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubscribe) {
101 // listen for subscriptions
102 con.registerHandler('presence',function(presence){
103 var type = presence.getType();
104 if (type != "subscribe") return;
106 var from = fullXID(getStanzaFrom(presence));
107 var xid = bareXID(from);
108 var pstatus = presence.getStatus();
112 if (autoapprove && contacts[xid]!==undefined) {
113 // approve known address
115 console.log("Approve known Friendica contact "+xid+".");
117 else if (autoapprove && pstatus && pstatus.indexOf("Friendica")!=-1) {
118 // Unknown address claims to be a Friendica contact.
119 // This is probably because the other side knows our
120 // address, but we do not know the other side yet.
121 // But it's only a matter of time, so wait - do not
122 // approve yet and do not annoy the user by asking.
124 console.log("Do not approve unknown Friendica contact "+xid+" - wait instead.");
127 // In all other cases, ask the user.
128 var message = "Accept "+xid+" for chat?";
129 if (pstatus) message += "\n\nStatus:\n"+pstatus;
130 approve = confirm(message);
132 // do not ask any more
133 if (!approve) sendSubscribe(xid, "unsubscribed");
137 var name = contacts[xid];
138 if (!name) name = xid;
140 acceptSubscribe(xid, name);
141 console.log("Accepted "+xid+" ("+name+") for chat.");
146 if (!autosubscribe) return;
148 var stored_hash = getPersistent("jappix-mini", "contacts-hash");
149 var contacts_changed = (stored_hash != contacts_hash); // stored_hash gets updated later if everything was successful
150 if (!contacts_changed) return;
152 console.log("Start autosubscribe.");
154 var get_roster = new JSJaCIQ();
155 get_roster.setType('get');
156 get_roster.setQuery(NS_ROSTER);
158 con.send(get_roster, function(iq){
159 var handleXML = iq.getQuery();
161 // filter out contacts that are already in the roster
162 $(handleXML).find('item').each(function() {
164 var xid = node.attr("jid");
165 var name = node.attr("name");
166 var subscription = node.attr("subscription");
168 // ignore accounts that are not in the list
169 if (contacts[xid]===undefined) return;
171 // add to Friendica group or change name if necessary
173 var group_missing = false;
174 node.find('group').each(function() {
175 var group_text = $(this).text();
176 if (group_text) groups.push(group_text);
178 if ($.inArray("Friendica", groups)==-1) {
179 group_missing = true;
180 groups.push("Friendica");
183 if (group_missing || name!=contacts[xid]) {
184 sendRoster(xid, null, contacts[xid], groups);
185 console.log("Added "+xid+" to Friendica group and set name to "+contacts[xid]+".");
188 // authorize if necessary
189 if (subscription=="to") {
190 sendSubscribe(xid, 'subscribed');
191 console.log("Authorized "+xid+" automatically.");
195 delete contacts[xid];
198 // go through remaining contacts
199 for (var xid in contacts) {if(!contacts.hasOwnProperty(xid)) continue;
201 var presence = new JSJaCPresence();
203 presence.setType("subscribe");
205 // must contain the word "~Friendica" so the other side knows
206 // how to handle this
207 presence.setStatus("I'm "+MINI_NICKNAME+" from ~Friendica.\n[machine-generated message]");
210 console.log("Subscribed to "+xid+" automatically.");
213 var iq = new JSJaCIQ();
215 var iqQuery = iq.setQuery(NS_ROSTER);
216 var item = iqQuery.appendChild(iq.buildNode('item', {'xmlns': NS_ROSTER, 'jid': xid}));
217 item.setAttribute('name', contacts[xid]);
218 item.appendChild(iq.buildNode('group', {'xmlns': NS_ROSTER}, "Friendica"));
220 console.log("Added "+xid+" ("+contacts[xid]+") to roster.");
223 setPersistent("jappix-mini", "contacts-hash", contacts_hash);
224 console.log("Autosubscribe done.");
229 function jappixmini_addon_subscribe() {
231 alert("Not connected.");
235 var xid = prompt("Jabber address");
236 sendSubscribe(xid, "subscribe");
239 function jappixmini_addon_start(server, username, proxy, bosh, encrypted, password, nickname, contacts, contacts_hash, autoapprove, autosubscribe, groupchats) {
240 var handler = function(password){
241 // check if settings have changed, reinitialize jappix mini if this is the case
242 var settings_identifier = str_sha1(server);
243 settings_identifier += str_sha1(username);
244 settings_identifier += str_sha1(proxy);
245 settings_identifier += str_sha1(bosh);
246 settings_identifier += str_sha1(password);
247 settings_identifier += str_sha1(nickname);
249 var saved_identifier = getDB("jappix-mini", "settings-identifier");
250 if (saved_identifier != settings_identifier) {
252 removeDB('jappix-mini', 'dom');
253 removePersistent("jappix-mini", "contacts-hash");
255 setDB("jappix-mini", "settings-identifier", settings_identifier);
259 HOST_BOSH = proxy+"?host_bosh="+encodeURI(bosh);
264 MINI_GROUPCHATS = groupchats;
265 MINI_NICKNAME = nickname;
267 launchMini(true, false, server, username, password);
269 // increase priority over other Jabber clients - does not seem to work?
271 presenceMini(null,null,priority);
273 jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubscribe)
276 // decrypt password if necessary
278 jappixmini_addon_decrypt_password(password, handler);