jappixmini: spare global js namespace
[friendica-addons.git/.git] / jappixmini / lib.js
1 function jappixmini_addon_xor(str1, str2) {
2     if (str1.length != str2.length) throw "not same length";
3
4     var encoded = "";
5
6     for (var i=0; i<str1.length;i++) {
7         var a = str1.charCodeAt(i);
8         var b = str2.charCodeAt(i);
9         var c = a ^ b;
10
11         encoded += String.fromCharCode(c);
12     }
13
14     return encoded;
15 }
16
17 function jappixmini_addon_set_client_secret(password) {
18         if (!password) return;
19
20         var salt1 = "h8doCRekWto0njyQohKpdx6BN0UTyC6N";
21         var salt2 = "jdX8OwFC1kWAq3s9uOyAcE8g3UNNO5t3";
22
23         var client_secret1 = str_sha1(salt1+password);
24         var client_secret2 = str_sha1(salt2+password);
25         var client_secret = client_secret1 + client_secret2;
26
27         setPersistent('jappix-mini', 'client-secret', client_secret);
28         console.log("client secret set");
29 }
30
31 function jappixmini_addon_get_client_secret(callback) {
32         var client_secret = getPersistent('jappix-mini', 'client-secret');
33         if (client_secret===null) {
34                 var div = document.getElementById("#jappixmini-password-query-div");
35
36                 if (!div) {
37                         div = $('<div id="jappixmini-password-query-div" style="position:fixed;padding:1em;background-color:#F00;color:#fff;top:50px;left:50px;">Retype your Friendica password for chatting:<br></div>');
38
39                         var input = $('<input type="password" id="jappixmini-password-query-input">')
40                         div.append(input);
41
42                         var button = $('<input type="button" value="OK" id="jappixmini-password-query-button">');
43                         div.append(button);
44
45                         $("body").append(div);
46                 }
47
48                 button.click(function(){
49                         var password = $("#jappixmini-password-query-input").val();
50                         jappixmini_addon_set_client_secret(password);
51                         div.remove();
52
53                         var client_secret = getPersistent('jappix-mini', 'client-secret');
54                         callback(client_secret);
55                 });
56         }
57         else {
58                 callback(client_secret);
59         }
60 }
61
62 function jappixmini_addon_encrypt_password(password, callback) {
63         jappixmini_addon_get_client_secret(function(client_secret){
64                 // add \0 to password until it has the same length as secret
65                 if (password.length>client_secret.length-1) throw "password too long";
66                 while (password.length<client_secret.length) {
67                         password += "\0";
68                 }
69
70                 // xor password with secret
71                 var encrypted_password = jappixmini_addon_xor(client_secret, password);
72
73                 encrypted_password = encodeURI(encrypted_password)
74                 callback(encrypted_password);
75         });
76 }
77
78 function jappixmini_addon_decrypt_password(encrypted_password, callback) {
79         encrypted_password = decodeURI(encrypted_password);
80
81         jappixmini_addon_get_client_secret(function(client_secret){
82                 // xor password with secret
83                 var password = jappixmini_addon_xor(client_secret, encrypted_password);
84
85                 // remove \0
86                 var first_null = password.indexOf("\0")
87                 if (first_null==-1) throw "Decrypted password does not contain \\0";
88                 password = password.substr(0, first_null);
89
90                 callback(password);
91         });
92 }
93
94 function jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubscribe) {
95         // listen for subscriptions
96         con.registerHandler('presence',function(presence){
97                 var type = presence.getType();
98                 if (type != "subscribe") return;
99
100                 var from = fullXID(getStanzaFrom(presence));
101                 var xid = bareXID(from);
102                 var pstatus = presence.getStatus();
103
104                 var approve;
105
106                 if (autoapprove && contacts[xid]!==undefined) {
107                         // approve known address
108                         approve = true;
109                         console.log("Approve known Friendica contact "+xid+".");
110                 }
111                 else if (autoapprove && pstatus && pstatus.indexOf("Friendica")!=-1) {
112                         // Unknown address claims to be a Friendica contact.
113                         // This is probably because the other side knows our
114                         // address, but we do not know the other side yet.
115                         // But it's only a matter of time, so wait - do not
116                         // approve yet and do not annoy the user by asking.
117                         approve = false;
118                         console.log("Do not approve unknown Friendica contact "+xid+" - wait instead.");
119                 }
120                 else {
121                         // In all other cases, ask the user.
122                         var message = "Accept "+xid+" for chat?";
123                         if (pstatus) message += "\n\nStatus:\n"+pstatus;
124                         approve = confirm(message);
125
126                         // do not ask any more
127                         if (!approve) sendSubscribe(xid, "unsubscribed");
128                 }
129
130                 if (approve) {
131                         var name = contacts[xid];
132                         if (!name) name = xid;
133
134                         acceptSubscribe(xid, name);
135                         console.log("Accepted "+xid+" for chat.");
136                 }
137         });
138
139         // autosubscribe
140         if (!autosubscribe) return;
141
142         var stored_hash = getPersistent("jappix-mini", "contacts-hash");
143         var contacts_changed = (stored_hash != contacts_hash); // stored_hash gets updated later if everything was successful
144         if (!contacts_changed) return;
145
146         console.log("Start autosubscribe.");
147
148         var get_roster = new JSJaCIQ();
149         get_roster.setType('get');
150         get_roster.setQuery(NS_ROSTER);
151
152         con.send(get_roster, function(iq){
153                 var handleXML = iq.getQuery();
154
155                 // filter out contacts that are already in the roster
156                 $(handleXML).find('item').each(function() {
157                         var node = $(this);
158                         var xid = node.attr("jid");
159                         var name = node.attr("name");
160                         var subscription = node.attr("subscription");
161
162                         // ignore accounts that are not in the list
163                         if (contacts[xid]===undefined) return;
164
165                         // add to Friendica group if necessary
166                         var groups = [];
167                         node.find('group').each(function() {
168                                 var group_text = $(this).text();
169                                 if (group_text) groups.push(group_text);
170                         });
171
172                         if ($.inArray("Friendica", groups)==-1) {
173                                 groups.push("Friendica");
174                                 sendRoster(xid, null, null, groups);
175                                 console.log("Added "+xid+" to Friendica group.");
176                         }
177
178                         // authorize if necessary
179                         if (subscription=="to") {
180                                 sendSubscribe(xid, 'subscribed');
181                                 console.log("Authorized "+xid+" automatically.");
182                         }
183
184                         // remove from list
185                         delete contacts[xid];
186                 });
187
188                 // go through remaining contacts
189                 for (var xid in contacts) {if(!contacts.hasOwnProperty(xid)) continue;
190                         // subscribe
191                         var presence = new JSJaCPresence();
192                         presence.setTo(xid);
193                         presence.setType("subscribe");
194
195                         // must contain the word "~Friendica" so the other side knows
196                         // how to handle this
197                         presence.setStatus("I'm "+MINI_NICKNAME+" from ~Friendica.\n[machine-generated message]");
198
199                         con.send(presence);
200                         console.log("Subscribed to "+xid+" automatically.");
201
202                         // add to roster
203                         var iq = new JSJaCIQ();
204                         iq.setType('set');
205                         var iqQuery = iq.setQuery(NS_ROSTER);
206                         var item = iqQuery.appendChild(iq.buildNode('item', {'xmlns': NS_ROSTER, 'jid': xid}));
207                         item.setAttribute('name', contacts[xid]);
208                         item.appendChild(iq.buildNode('group', {'xmlns': NS_ROSTER}, "Friendica"));
209                         con.send(iq);
210                         console.log("Added "+xid+" to roster.");
211                 }
212
213                 setPersistent("jappix-mini", "contacts-hash", contacts_hash);
214                 console.log("Autosubscribe done.");
215         });
216
217 }
218
219 function jappixmini_addon_subscribe() {
220         if (!window.con) {
221                 alert("Not connected.");
222                 return;
223         }
224
225         var xid = prompt("Jabber address");
226         sendSubscribe(xid, "subscribe");
227 }
228
229 function jappixmini_addon_start(server, username, proxy, bosh, encrypted, password, nickname, contacts, contacts_hash, autoapprove, autosubscribe) {
230     var handler = function(password){
231         // check if settings have changed, reinitialize jappix mini if this is the case
232         var settings_identifier = str_sha1(server);
233         settings_identifier += str_sha1(username);
234         settings_identifier += str_sha1(proxy);
235         settings_identifier += str_sha1(bosh);
236         settings_identifier += str_sha1(password);
237         settings_identifier += str_sha1(nickname);
238
239         var saved_identifier = getDB("jappix-mini", "settings-identifier");
240         if (saved_identifier != settings_identifier) {
241             disconnectMini();
242             removeDB('jappix-mini', 'dom');
243             removePersistent("jappix-mini", "contacts-hash");
244         }
245         setDB("jappix-mini", "settings-identifier", settings_identifier);
246
247         // set HOST_BOSH
248         if (proxy)
249             HOST_BOSH = proxy+"?host_bosh="+encodeURI(bosh);
250         else
251             HOST_BOSH = bosh;
252
253         // start jappix mini
254         MINI_NICKNAME = nickname;
255         LOCK_HOST = "off";
256         launchMini(true, false, server, username, password);
257
258         // increase priority over other Jabber clients - does not seem to work?
259         var priority = 101;
260         presenceMini(null,null,priority);
261
262         jappixmini_manage_roster(contacts, contacts_hash, autoapprove, autosubscribe)
263     }
264
265     // decrypt password if necessary
266     if (encrypted)
267         jappixmini_addon_decrypt_password(password, handler);
268     else
269         handler(password);
270 }