diff --git a/background.html b/background.html
index b0e186ba..6d10b280 100644
--- a/background.html
+++ b/background.html
@@ -29,7 +29,10 @@
-
+
+
+
+
diff --git a/index.html b/index.html
index f4b71d80..50c7bf94 100644
--- a/index.html
+++ b/index.html
@@ -138,6 +138,9 @@
+
+
+
diff --git a/js/helpers.js b/js/helpers.js
index 5375dfcc..47e4df3e 100644
--- a/js/helpers.js
+++ b/js/helpers.js
@@ -205,268 +205,6 @@ window.textsecure.throwHumanError = function(error, type, humanError) {
throw e;
}
-/************************************************
- *** Utilities to store data in local storage ***
- ************************************************/
-window.textsecure.storage = function() {
- var self = {};
-
- /*****************************
- *** Base Storage Routines ***
- *****************************/
- self.putEncrypted = function(key, value) {
- //TODO
- if (value === undefined)
- throw new Error("Tried to store undefined");
- localStorage.setItem("e" + key, textsecure.utils.jsonThing(value));
- }
-
- self.getEncrypted = function(key, defaultValue) {
- //TODO
- var value = localStorage.getItem("e" + key);
- if (value === null)
- return defaultValue;
- return JSON.parse(value);
- }
-
- self.removeEncrypted = function(key) {
- localStorage.removeItem("e" + key);
- }
-
- self.putUnencrypted = function(key, value) {
- if (value === undefined)
- throw new Error("Tried to store undefined");
- localStorage.setItem("u" + key, textsecure.utils.jsonThing(value));
- }
-
- self.getUnencrypted = function(key, defaultValue) {
- var value = localStorage.getItem("u" + key);
- if (value === null)
- return defaultValue;
- return JSON.parse(value);
- }
-
- self.removeUnencrypted = function(key) {
- localStorage.removeItem("u" + key);
- }
-
- /**********************
- *** Device Storage ***
- **********************/
- self.devices = function() {
- var self = {};
-
- var internalSaveDeviceObject = function(deviceObject, onlyKeys) {
- if (deviceObject.identityKey === undefined || deviceObject.encodedNumber === undefined)
- throw new Error("Tried to store invalid deviceObject");
-
- var number = textsecure.utils.unencodeNumber(deviceObject.encodedNumber)[0];
- var map = textsecure.storage.getEncrypted("devices" + number);
-
- if (map === undefined)
- map = { devices: [deviceObject], identityKey: deviceObject.identityKey };
- else if (map.identityKey != getString(deviceObject.identityKey))
- throw new Error("Identity key changed");
- else {
- var updated = false;
- for (var i in map.devices) {
- if (map.devices[i].encodedNumber == deviceObject.encodedNumber) {
- if (!onlyKeys)
- map.devices[i] = deviceObject;
- else {
- map.devices[i].preKey = deviceObject.preKey;
- map.devices[i].preKeyId = deviceObject.preKeyId;
- map.devices[i].signedKey = deviceObject.signedKey;
- map.devices[i].signedKeyId = deviceObject.signedKeyId;
- map.devices[i].registrationId = deviceObject.registrationId;
- }
- updated = true;
- }
- }
-
- if (!updated)
- map.devices.push(deviceObject);
- }
-
- textsecure.storage.putEncrypted("devices" + number, map);
- }
-
- self.saveDeviceObject = function(deviceObject) {
- return internalSaveDeviceObject(deviceObject, false);
- }
-
- self.saveKeysToDeviceObject = function(deviceObject) {
- return internalSaveDeviceObject(deviceObject, true);
- }
-
- self.getDeviceObjectsForNumber = function(number) {
- var map = textsecure.storage.getEncrypted("devices" + number);
- return map === undefined ? [] : map.devices;
- }
-
- self.getDeviceObject = function(encodedNumber) {
- var number = textsecure.utils.unencodeNumber(encodedNumber);
- var devices = self.getDeviceObjectsForNumber(number[0]);
- if (devices === undefined)
- return undefined;
-
- for (var i in devices)
- if (devices[i].encodedNumber == encodedNumber)
- return devices[i];
-
- return undefined;
- }
-
- self.removeDeviceIdsForNumber = function(number, deviceIdsToRemove) {
- var map = textsecure.storage.getEncrypted("devices" + number);
- if (map === undefined)
- throw new Error("Tried to remove device for unknown number");
-
- var newDevices = [];
- var devicesRemoved = 0;
- for (var i in map.devices) {
- var keep = true;
- for (var j in deviceIdsToRemove)
- if (map.devices[i].encodedNumber == number + "." + deviceIdsToRemove[j])
- keep = false;
-
- if (keep)
- newDevices.push(map.devices[i]);
- else
- devicesRemoved++;
- }
-
- if (devicesRemoved != deviceIdsToRemove.length)
- throw new Error("Tried to remove unknown device");
- }
-
- return self;
- }();
-
- /*********************
- *** Group Storage ***
- *********************/
- self.groups = function() {
- var self = {};
-
- var addGroupToNumber = function(groupId, number) {
- var membership = textsecure.storage.getEncrypted("groupMembership" + number, [groupId]);
- if (membership.indexOf(groupId) < 0)
- membership.push(groupId);
- textsecure.storage.putEncrypted("groupMembership" + number, membership);
- }
-
- var removeGroupFromNumber = function(groupId, number) {
- var membership = textsecure.storage.getEncrypted("groupMembership" + number, [groupId]);
- membership = membership.filter(function(group) { return group != groupId; });
- if (membership.length == 0)
- textsecure.storage.removeEncrypted("groupMembership" + number);
- else
- textsecure.storage.putEncrypted("groupMembership" + number, membership);
- }
-
- self.getGroupListForNumber = function(number) {
- return textsecure.storage.getEncrypted("groupMembership" + number, []);
- }
-
- self.createNewGroup = function(numbers, groupId) {
- if (groupId !== undefined && textsecure.storage.getEncrypted("group" + groupId) !== undefined) {
- throw new Error("Tried to recreate group");
- }
-
- while (groupId === undefined || textsecure.storage.getEncrypted("group" + groupId) !== undefined) {
- groupId = getString(textsecure.crypto.getRandomBytes(16));
- }
-
- var me = textsecure.utils.unencodeNumber(textsecure.storage.getUnencrypted("number_id"))[0];
- var haveMe = false;
- var finalNumbers = [];
- for (var i in numbers) {
- var number = libphonenumber.util.verifyNumber(numbers[i]);
- if (number == me)
- haveMe = true;
- if (finalNumbers.indexOf(number) < 0) {
- finalNumbers.push(number);
- addGroupToNumber(groupId, number);
- }
- }
-
- if (!haveMe)
- finalNumbers.push(me);
-
- textsecure.storage.putEncrypted("group" + groupId, {numbers: finalNumbers});
-
- return {id: groupId, numbers: finalNumbers};
- }
-
- self.getNumbers = function(groupId) {
- var group = textsecure.storage.getEncrypted("group" + groupId);
- if (group === undefined)
- return undefined;
-
- return group.numbers;
- }
-
- self.removeNumber = function(groupId, number) {
- var group = textsecure.storage.getEncrypted("group" + groupId);
- if (group === undefined)
- return undefined;
-
- try {
- number = libphonenumber.util.verifyNumber(number);
- } catch (e) {
- return group.numbers;
- }
-
- var me = textsecure.utils.unencodeNumber(textsecure.storage.getUnencrypted("number_id"))[0];
- if (number == me)
- throw new Error("Cannot remove ourselves from a group, leave the group instead");
-
- var i = group.numbers.indexOf(number);
- if (i > -1) {
- group.numbers.slice(i, 1);
- textsecure.storage.putEncrypted("group" + groupId, group);
- removeGroupFromNumber(groupId, number);
- }
-
- return group.numbers;
- }
-
- self.addNumbers = function(groupId, numbers) {
- var group = textsecure.storage.getEncrypted("group" + groupId);
- if (group === undefined)
- return undefined;
-
- for (var i in numbers) {
- var number = libphonenumber.util.verifyNumber(numbers[i]);
- if (group.numbers.indexOf(number) < 0) {
- group.numbers.push(number);
- addGroupToNumber(groupId, number);
- }
- }
-
- textsecure.storage.putEncrypted("group" + groupId, group);
- return group.numbers;
- }
-
- self.deleteGroup = function(groupId) {
- textsecure.storage.removeEncrypted("group" + groupId);
- }
-
- self.getGroup = function(groupId) {
- var group = textsecure.storage.getEncrypted("group" + groupId);
- if (group === undefined)
- return undefined;
-
- return { id: groupId, numbers: group.numbers }; //TODO: avatar/name tracking
- }
-
- return self;
- }();
-
- return self;
-}();
-
/**********************
*** NaCL Interface ***
**********************/
diff --git a/js/storage.js b/js/storage.js
new file mode 100644
index 00000000..cae05ad1
--- /dev/null
+++ b/js/storage.js
@@ -0,0 +1,69 @@
+/* vim: ts=4:sw=4
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+'use strict';
+
+;(function() {
+
+ /************************************************
+ *** Utilities to store data in local storage ***
+ ************************************************/
+ window.textsecure = window.textsecure || {};
+ window.textsecure.storage = window.textsecure.storage || {};
+
+ window.textsecure.storage = {
+
+ /*****************************
+ *** Base Storage Routines ***
+ *****************************/
+ putEncrypted: function(key, value) {
+ //TODO
+ if (value === undefined)
+ throw new Error("Tried to store undefined");
+ localStorage.setItem("e" + key, textsecure.utils.jsonThing(value));
+ },
+
+ getEncrypted: function(key, defaultValue) {
+ //TODO
+ var value = localStorage.getItem("e" + key);
+ if (value === null)
+ return defaultValue;
+ return JSON.parse(value);
+ },
+
+ removeEncrypted: function(key) {
+ localStorage.removeItem("e" + key);
+ },
+
+ putUnencrypted: function(key, value) {
+ if (value === undefined)
+ throw new Error("Tried to store undefined");
+ localStorage.setItem("u" + key, textsecure.utils.jsonThing(value));
+ },
+
+ getUnencrypted: function(key, defaultValue) {
+ var value = localStorage.getItem("u" + key);
+ if (value === null)
+ return defaultValue;
+ return JSON.parse(value);
+ },
+
+ removeUnencrypted: function(key) {
+ localStorage.removeItem("u" + key);
+ }
+ };
+})();
+
diff --git a/js/storage/devices.js b/js/storage/devices.js
new file mode 100644
index 00000000..2fd86e7d
--- /dev/null
+++ b/js/storage/devices.js
@@ -0,0 +1,111 @@
+/* vim: ts=4:sw=4
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+'use strict';
+
+;(function() {
+ /**********************
+ *** Device Storage ***
+ **********************/
+ window.textsecure = window.textsecure || {};
+ window.textsecure.storage = window.textsecure.storage || {};
+
+ window.textsecure.storage.devices = {
+ saveDeviceObject: function(deviceObject) {
+ return internalSaveDeviceObject(deviceObject, false);
+ },
+
+ saveKeysToDeviceObject: function(deviceObject) {
+ return internalSaveDeviceObject(deviceObject, true);
+ },
+
+ getDeviceObjectsForNumber: function(number) {
+ var map = textsecure.storage.getEncrypted("devices" + number);
+ return map === undefined ? [] : map.devices;
+ },
+
+ getDeviceObject: function(encodedNumber) {
+ var number = textsecure.utils.unencodeNumber(encodedNumber);
+ var devices = textsecure.storage.devices.getDeviceObjectsForNumber(number[0]);
+ if (devices === undefined)
+ return undefined;
+
+ for (var i in devices)
+ if (devices[i].encodedNumber == encodedNumber)
+ return devices[i];
+
+ return undefined;
+ },
+
+ removeDeviceIdsForNumber: function(number, deviceIdsToRemove) {
+ var map = textsecure.storage.getEncrypted("devices" + number);
+ if (map === undefined)
+ throw new Error("Tried to remove device for unknown number");
+
+ var newDevices = [];
+ var devicesRemoved = 0;
+ for (var i in map.devices) {
+ var keep = true;
+ for (var j in deviceIdsToRemove)
+ if (map.devices[i].encodedNumber == number + "." + deviceIdsToRemove[j])
+ keep = false;
+
+ if (keep)
+ newDevices.push(map.devices[i]);
+ else
+ devicesRemoved++;
+ }
+
+ if (devicesRemoved != deviceIdsToRemove.length)
+ throw new Error("Tried to remove unknown device");
+ }
+ };
+
+ var internalSaveDeviceObject = function(deviceObject, onlyKeys) {
+ if (deviceObject.identityKey === undefined || deviceObject.encodedNumber === undefined)
+ throw new Error("Tried to store invalid deviceObject");
+
+ var number = textsecure.utils.unencodeNumber(deviceObject.encodedNumber)[0];
+ var map = textsecure.storage.getEncrypted("devices" + number);
+
+ if (map === undefined)
+ map = { devices: [deviceObject], identityKey: deviceObject.identityKey };
+ else if (map.identityKey != getString(deviceObject.identityKey))
+ throw new Error("Identity key changed");
+ else {
+ var updated = false;
+ for (var i in map.devices) {
+ if (map.devices[i].encodedNumber == deviceObject.encodedNumber) {
+ if (!onlyKeys)
+ map.devices[i] = deviceObject;
+ else {
+ map.devices[i].preKey = deviceObject.preKey;
+ map.devices[i].preKeyId = deviceObject.preKeyId;
+ map.devices[i].signedKey = deviceObject.signedKey;
+ map.devices[i].signedKeyId = deviceObject.signedKeyId;
+ map.devices[i].registrationId = deviceObject.registrationId;
+ }
+ updated = true;
+ }
+ }
+
+ if (!updated)
+ map.devices.push(deviceObject);
+ }
+
+ textsecure.storage.putEncrypted("devices" + number, map);
+ };
+})();
diff --git a/js/storage/groups.js b/js/storage/groups.js
new file mode 100644
index 00000000..3de9c24b
--- /dev/null
+++ b/js/storage/groups.js
@@ -0,0 +1,140 @@
+/* vim: ts=4:sw=4
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+'use strict';
+
+;(function() {
+ /*********************
+ *** Group Storage ***
+ *********************/
+ window.textsecure = window.textsecure || {};
+ window.textsecure.storage = window.textsecure.storage || {};
+
+ window.textsecure.storage.groups = {
+ getGroupListForNumber: function(number) {
+ return textsecure.storage.getEncrypted("groupMembership" + number, []);
+ },
+
+ createNewGroup: function(numbers, groupId) {
+ if (groupId !== undefined && textsecure.storage.getEncrypted("group" + groupId) !== undefined) {
+ throw new Error("Tried to recreate group");
+ }
+
+ while (groupId === undefined || textsecure.storage.getEncrypted("group" + groupId) !== undefined) {
+ groupId = getString(textsecure.crypto.getRandomBytes(16));
+ }
+
+ var me = textsecure.utils.unencodeNumber(textsecure.storage.getUnencrypted("number_id"))[0];
+ var haveMe = false;
+ var finalNumbers = [];
+ for (var i in numbers) {
+ var number = libphonenumber.util.verifyNumber(numbers[i]);
+ if (number == me)
+ haveMe = true;
+ if (finalNumbers.indexOf(number) < 0) {
+ finalNumbers.push(number);
+ addGroupToNumber(groupId, number);
+ }
+ }
+
+ if (!haveMe)
+ finalNumbers.push(me);
+
+ textsecure.storage.putEncrypted("group" + groupId, {numbers: finalNumbers});
+
+ return {id: groupId, numbers: finalNumbers};
+ },
+
+ getNumbers: function(groupId) {
+ var group = textsecure.storage.getEncrypted("group" + groupId);
+ if (group === undefined)
+ return undefined;
+
+ return group.numbers;
+ },
+
+ removeNumber: function(groupId, number) {
+ var group = textsecure.storage.getEncrypted("group" + groupId);
+ if (group === undefined)
+ return undefined;
+
+ try {
+ number = libphonenumber.util.verifyNumber(number);
+ } catch (e) {
+ return group.numbers;
+ }
+
+ var me = textsecure.utils.unencodeNumber(textsecure.storage.getUnencrypted("number_id"))[0];
+ if (number == me)
+ throw new Error("Cannot remove ourselves from a group, leave the group instead");
+
+ var i = group.numbers.indexOf(number);
+ if (i > -1) {
+ group.numbers.slice(i, 1);
+ textsecure.storage.putEncrypted("group" + groupId, group);
+ removeGroupFromNumber(groupId, number);
+ }
+
+ return group.numbers;
+ },
+
+ addNumbers: function(groupId, numbers) {
+ var group = textsecure.storage.getEncrypted("group" + groupId);
+ if (group === undefined)
+ return undefined;
+
+ for (var i in numbers) {
+ var number = libphonenumber.util.verifyNumber(numbers[i]);
+ if (group.numbers.indexOf(number) < 0) {
+ group.numbers.push(number);
+ addGroupToNumber(groupId, number);
+ }
+ }
+
+ textsecure.storage.putEncrypted("group" + groupId, group);
+ return group.numbers;
+ },
+
+ deleteGroup: function(groupId) {
+ textsecure.storage.removeEncrypted("group" + groupId);
+ },
+
+ getGroup: function(groupId) {
+ var group = textsecure.storage.getEncrypted("group" + groupId);
+ if (group === undefined)
+ return undefined;
+
+ return { id: groupId, numbers: group.numbers }; //TODO: avatar/name tracking
+ }
+ };
+
+ var addGroupToNumber = function(groupId, number) {
+ var membership = textsecure.storage.getEncrypted("groupMembership" + number, [groupId]);
+ if (membership.indexOf(groupId) < 0)
+ membership.push(groupId);
+ textsecure.storage.putEncrypted("groupMembership" + number, membership);
+ }
+
+ var removeGroupFromNumber = function(groupId, number) {
+ var membership = textsecure.storage.getEncrypted("groupMembership" + number, [groupId]);
+ membership = membership.filter(function(group) { return group != groupId; });
+ if (membership.length == 0)
+ textsecure.storage.removeEncrypted("groupMembership" + number);
+ else
+ textsecure.storage.putEncrypted("groupMembership" + number, membership);
+ }
+
+})();
diff --git a/options.html b/options.html
index 96118128..6d63f2fe 100644
--- a/options.html
+++ b/options.html
@@ -106,6 +106,9 @@
+
+
+
diff --git a/test.html b/test.html
index bfe95511..84096ddc 100644
--- a/test.html
+++ b/test.html
@@ -47,6 +47,9 @@
+
+
+
diff --git a/test/test_views.html b/test/test_views.html
index 6ced2533..29af628b 100644
--- a/test/test_views.html
+++ b/test/test_views.html
@@ -127,6 +127,9 @@
+
+
+