diff --git a/js/backup.js b/js/backup.js
index 3e0717199a..7643634333 100644
--- a/js/backup.js
+++ b/js/backup.js
@@ -119,7 +119,7 @@
_.each(storeNames, function(storeName) {
var transaction = idb_db.transaction(storeNames, 'readwrite');
transaction.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'exportToJsonFile transaction error (store: ' + storeName + ')',
transaction.error,
reject
@@ -133,7 +133,7 @@
var request = store.openCursor();
var count = 0;
request.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'exportToJsonFile request error (store: ' + storeNames + ')',
request.error,
reject
@@ -184,16 +184,6 @@
});
}
- function handleDOMException(prefix, error, reject) {
- console.log(
- prefix + ':',
- error && error.name,
- error && error.message,
- error && error.code
- );
- reject(error || new Error(prefix));
- }
-
function eliminateClientConfigInBackup(data, path) {
var cleaned = _.pick(data, 'conversations', 'groups');
console.log('Writing configuration-free backup file back to disk');
@@ -261,7 +251,7 @@
var transaction = idb_db.transaction(storeNames, 'readwrite');
transaction.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'importFromJsonString transaction error',
transaction.error,
reject
@@ -321,7 +311,7 @@
}
};
request.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'importFromJsonString request error (store: ' + storeName + ')',
request.error,
reject
@@ -338,27 +328,6 @@
});
}
- function openDatabase() {
- var migrations = Whisper.Database.migrations;
- var version = migrations[migrations.length - 1].version;
- var DBOpenRequest = window.indexedDB.open('signal', version);
-
- return new Promise(function(resolve, reject) {
- // these two event handlers act on the IDBDatabase object,
- // when the database is opened successfully, or not
- DBOpenRequest.onerror = reject;
- DBOpenRequest.onsuccess = function() {
- resolve(DBOpenRequest.result);
- };
-
- // This event handles the event whereby a new version of
- // the database needs to be created Either one has not
- // been created before, or a new version number has been
- // submitted via the window.indexedDB.open line above
- DBOpenRequest.onupgradeneeded = reject;
- });
- }
-
function createDirectory(parent, name) {
return new Promise(function(resolve, reject) {
var sanitized = sanitizeFileName(name);
@@ -492,7 +461,7 @@
return new Promise(function(resolve, reject) {
var transaction = idb_db.transaction('messages', 'readwrite');
transaction.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'exportConversation transaction error (conversation: ' + name + ')',
transaction.error,
reject
@@ -514,7 +483,7 @@
stream.write('{"messages":[');
request.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'exportConversation request error (conversation: ' + name + ')',
request.error,
reject
@@ -608,7 +577,7 @@
return new Promise(function(resolve, reject) {
var transaction = idb_db.transaction('conversations', 'readwrite');
transaction.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'exportConversations transaction error',
transaction.error,
reject
@@ -622,7 +591,7 @@
var store = transaction.objectStore('conversations');
var request = store.openCursor();
request.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'exportConversations request error',
request.error,
reject
@@ -716,7 +685,7 @@
var transaction = idb_db.transaction('messages', 'readwrite');
transaction.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'saveAllMessages transaction error',
transaction.error,
reject
@@ -744,7 +713,7 @@
}
};
request.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'saveAllMessages request error',
request.error,
reject
@@ -879,7 +848,7 @@
return new Promise(function(resolve, reject) {
var transaction = idb_db.transaction(storeName, 'readwrite');
transaction.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'assembleLookup(' + storeName + ') transaction error',
transaction.error,
reject
@@ -893,7 +862,7 @@
var store = transaction.objectStore(storeName);
var request = store.openCursor();
request.onerror = function(e) {
- handleDOMException(
+ Whisper.Database.handleDOMException(
'assembleLookup(' + storeName + ') request error',
request.error,
reject
@@ -912,59 +881,6 @@
});
}
- function clearAllStores(idb_db) {
- return clearStores(idb_db);
- }
-
- function clearStores(idb_db, names) {
- return new Promise(function(resolve, reject) {
- var storeNames = names || idb_db.objectStoreNames;
- console.log('Clearing these indexeddb stores:', storeNames);
- var transaction = idb_db.transaction(storeNames, 'readwrite');
-
- var finished = false;
- var finish = function(via) {
- console.log('clearing all stores done via', via);
- if (finished) {
- resolve();
- }
- finished = true;
- };
-
- transaction.oncomplete = finish.bind(null, 'transaction complete');
- transaction.onerror = function(e) {
- handleDOMException(
- 'clearStores transaction error',
- transaction.error,
- reject
- );
- };
-
- var count = 0;
- _.forEach(storeNames, function(storeName) {
- var store = transaction.objectStore(storeName);
- var request = store.clear();
-
- request.onsuccess = function() {
- count += 1;
- console.log('Done clearing store', storeName);
-
- if (count >= storeNames.length) {
- console.log('Done clearing all indexeddb stores');
- return finish('clears complete');
- }
- };
-
- request.onerror = function(e) {
- handleDOMException(
- 'clearStores request error',
- request.error,
- reject
- );
- };
- });
- });
- }
function getTimestamp() {
return moment().format('YYYY MMM Do [at] h.mm.ss a');
@@ -972,16 +888,6 @@
// directories returned and taken by backup/import are all string paths
Whisper.Backup = {
- clearDatabase: function() {
- return openDatabase().then(function(idb_db) {
- return clearAllStores(idb_db);
- });
- },
- clearStores: function(storeNames) {
- return openDatabase().then(function(idb_db) {
- return clearStores(idb_db, storeNames);
- });
- },
getDirectoryForExport: function() {
var options = {
title: i18n('exportChooserTitle'),
@@ -992,7 +898,7 @@
exportToDirectory: function(directory, options) {
var dir;
var idb;
- return openDatabase().then(function(idb_db) {
+ return Whisper.Database.open().then(function(idb_db) {
idb = idb_db;
var name = 'Signal Export ' + getTimestamp();
return createDirectory(directory, name);
@@ -1025,7 +931,7 @@
options = options || {};
var idb, nonMessageResult;
- return openDatabase().then(function(idb_db) {
+ return Whisper.Database.open().then(function(idb_db) {
idb = idb_db;
return Promise.all([
@@ -1054,7 +960,6 @@
});
},
// for testing
- handleDOMException,
sanitizeFileName,
trimFileName,
getAttachmentFileName,
diff --git a/js/database.js b/js/database.js
index f10cd9b0a6..84c4332f22 100644
--- a/js/database.js
+++ b/js/database.js
@@ -1,4 +1,5 @@
/* global Whisper: false */
+/* global Backbone: false */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
@@ -10,6 +11,114 @@
window.Whisper.Database.id = window.Whisper.Database.id || 'signal';
window.Whisper.Database.nolog = true;
+ Whisper.Database.handleDOMException = (prefix, error, reject) => {
+ console.log(
+ `${prefix}:`,
+ error && error.name,
+ error && error.message,
+ error && error.code
+ );
+ reject(error || new Error(prefix));
+ };
+
+ function clearStores(db, names) {
+ return new Promise(((resolve, reject) => {
+ const storeNames = names || db.objectStoreNames;
+ console.log('Clearing these indexeddb stores:', storeNames);
+ const transaction = db.transaction(storeNames, 'readwrite');
+
+ let finished = false;
+ const finish = (via) => {
+ console.log('clearing all stores done via', via);
+ if (finished) {
+ resolve();
+ }
+ finished = true;
+ };
+
+ transaction.oncomplete = finish.bind(null, 'transaction complete');
+ transaction.onerror = () => {
+ Whisper.Database.handleDOMException(
+ 'clearStores transaction error',
+ transaction.error,
+ reject
+ );
+ };
+
+ let count = 0;
+ storeNames.forEach((storeName) => {
+ const store = transaction.objectStore(storeName);
+ const request = store.clear();
+
+ request.onsuccess = () => {
+ count += 1;
+ console.log('Done clearing store', storeName);
+
+ if (count >= storeNames.length) {
+ console.log('Done clearing all indexeddb stores');
+ finish('clears complete');
+ }
+ };
+
+ request.onerror = () => {
+ Whisper.Database.handleDOMException(
+ 'clearStores request error',
+ request.error,
+ reject
+ );
+ };
+ });
+ }));
+ }
+
+ Whisper.Database.open = () => {
+ const { migrations } = Whisper.Database;
+ const { version } = migrations[migrations.length - 1];
+ const DBOpenRequest = window.indexedDB.open(Whisper.Database.id, version);
+
+ return new Promise(((resolve, reject) => {
+ // these two event handlers act on the IDBDatabase object,
+ // when the database is opened successfully, or not
+ DBOpenRequest.onerror = reject;
+ DBOpenRequest.onsuccess = () => resolve(DBOpenRequest.result);
+
+ // This event handles the event whereby a new version of
+ // the database needs to be created Either one has not
+ // been created before, or a new version number has been
+ // submitted via the window.indexedDB.open line above
+ DBOpenRequest.onupgradeneeded = reject;
+ }));
+ };
+
+ Whisper.Database.clear = async () => {
+ const db = await Whisper.Database.open();
+ return clearStores(db);
+ };
+
+ Whisper.Database.clearStores = async (storeNames) => {
+ const db = await Whisper.Database.open();
+ return clearStores(db, storeNames);
+ };
+
+ Whisper.Database.close = () => window.wrapDeferred(Backbone.sync('closeall'));
+
+ Whisper.Database.drop = () =>
+ new Promise(((resolve, reject) => {
+ const request = window.indexedDB.deleteDatabase(Whisper.Database.id);
+
+ request.onblocked = () => {
+ reject(new Error('Error deleting database: Blocked.'));
+ };
+ request.onupgradeneeded = () => {
+ reject(new Error('Error deleting database: Upgrade needed.'));
+ };
+ request.onerror = () => {
+ reject(new Error('Error deleting database.'));
+ };
+
+ request.onsuccess = resolve;
+ }));
+
Whisper.Database.migrations = [
{
version: '12.0',
diff --git a/js/modules/logs.js b/js/modules/logs.js
new file mode 100644
index 0000000000..de49e469fb
--- /dev/null
+++ b/js/modules/logs.js
@@ -0,0 +1,19 @@
+const { ipcRenderer } = require('electron');
+
+/* eslint-env node */
+
+module.exports = {
+ deleteAll,
+};
+
+function deleteAll() {
+ return new Promise((resolve, reject) => {
+ ipcRenderer.once('delete-all-logs-complete', resolve);
+
+ setTimeout(() => {
+ reject(new Error('Request to delete all logs timed out'));
+ }, 5000);
+
+ ipcRenderer.send('delete-all-logs');
+ });
+}
diff --git a/js/signal_protocol_store.js b/js/signal_protocol_store.js
index 83541955fe..cfa3d057d3 100644
--- a/js/signal_protocol_store.js
+++ b/js/signal_protocol_store.js
@@ -860,14 +860,14 @@
ConversationController.reset(); // conversations store
// Then, the entire database:
- return window.Whisper.Backup.clearDatabase();
+ return Whisper.Database.clear();
},
removeAllConfiguration: function() {
// First the in-memory cache for the items store:
window.storage.reset();
// Then anything in the database that isn't a message/conversation/group:
- return window.Whisper.Backup.clearStores([
+ return Whisper.Database.clearStores([
'items',
'identityKeys',
'sessions',
diff --git a/js/views/import_view.js b/js/views/import_view.js
index 92752a945b..f95e14f83a 100644
--- a/js/views/import_view.js
+++ b/js/views/import_view.js
@@ -35,7 +35,7 @@
return storage.put(IMPORT_LOCATION, location);
},
reset: function() {
- return Whisper.Backup.clearDatabase();
+ return Whisper.Database.clear();
}
};
@@ -129,7 +129,7 @@
// Wait for prior database interaction to complete
this.pending = this.pending.then(function() {
// For resilience to interruption, clear database both before and on failure
- return Whisper.Backup.clearDatabase();
+ return Whisper.Import.reset();
}).then(function() {
return Promise.all([
Whisper.Import.start(),
@@ -153,7 +153,7 @@
this.state = null;
this.render();
- return Whisper.Backup.clearDatabase();
+ return Whisper.Import.reset();
}.bind(this));
},
finishLightImport: function(directory) {
diff --git a/js/views/settings_view.js b/js/views/settings_view.js
index 0aac296293..723a437a15 100644
--- a/js/views/settings_view.js
+++ b/js/views/settings_view.js
@@ -141,7 +141,7 @@
this.step = CLEAR_DATA_STEPS.DELETING;
this.render();
- window.wrapDeferred(Backbone.sync('closeall')).then(function() {
+ Whisper.Database.close().then(function() {
console.log('All database connections closed. Starting delete.');
this.clearAllData();
}.bind(this), function(error) {
@@ -150,44 +150,18 @@
}.bind(this));
},
clearAllData: function() {
- var finishCount = 0;
- var finish = function() {
- finishCount += 1;
- console.log('Deletion complete, finishCount is now', finishCount);
- if (finishCount > 1) {
- console.log('Deletion complete! Restarting now...');
- window.restart();
- }
- };
-
- var request = window.indexedDB.deleteDatabase('signal');
-
- // None of the three of these should happen, since we close all database
- // connections first. However, testing indicates that even if one of these
- // handlers fires, the database is still deleted on restart.
- request.onblocked = function(event) {
- console.log('Error deleting database: Blocked.');
- finish();
- };
- request.onupgradeneeded = function(event) {
- console.log('Error deleting database: Upgrade needed.');
- finish();
- };
- request.onerror = function(event) {
- console.log('Error deleting database.');
- finish();
- };
-
- request.onsuccess = function(event) {
- console.log('Database deleted successfully.');
- finish();
- };
-
- Whisper.events.once('deleteAllLogsComplete', function() {
- console.log('Log deleted successfully.');
- finish();
+ Promise.all([
+ Signal.Logs.deleteAll(),
+ Whisper.Database.drop(),
+ ]).then(function() {
+ window.restart();
+ }, function(error) {
+ console.log(
+ 'Something went wrong deleting all data:',
+ error && error.stack ? error.stack : error
+ );
+ window.restart();
});
- window.deleteAllLogs();
},
render_attributes: function() {
return {
diff --git a/preload.js b/preload.js
index 80c33f6114..7ce82a33bd 100644
--- a/preload.js
+++ b/preload.js
@@ -43,18 +43,10 @@
ipc.send('update-tray-icon', unreadCount);
};
- window.deleteAllLogs = function() {
- ipc.send('delete-all-logs');
- }
-
ipc.on('debug-log', function() {
Whisper.events.trigger('showDebugLog');
});
- ipc.on('delete-all-logs-complete', function() {
- Whisper.events.trigger('deleteAllLogsComplete');
- });
-
ipc.on('set-up-with-import', function() {
Whisper.events.trigger('setupWithImport');
});
@@ -115,6 +107,8 @@
// ES2015+ modules
window.Signal = window.Signal || {};
window.Signal.OS = require('./js/modules/os');
+ window.Signal.Logs = require('./js/modules/logs');
+
window.Signal.Types = window.Signal.Types || {};
window.Signal.Types.Attachment = require('./js/modules/types/attachment');
window.Signal.Types.Errors = require('./js/modules/types/errors');
diff --git a/test/backup_test.js b/test/backup_test.js
index 89054583ac..fadae90bdd 100644
--- a/test/backup_test.js
+++ b/test/backup_test.js
@@ -1,37 +1,6 @@
'use strict';
describe('Backup', function() {
- describe('handleDOMException', function() {
- it('handles null, still calls reject', function() {
- var called = 0;
- var reject = function() {
- called += 1;
- };
- var error = null;
- var prefix = 'something';
-
- Whisper.Backup.handleDOMException(prefix, error, reject);
-
- assert.strictEqual(called, 1);
- });
-
- it('handles object code and message', function() {
- var called = 0;
- var reject = function() {
- called += 1;
- };
- var error = {
- code: 4,
- message: 'some cryptic error',
- };
- var prefix = 'something';
-
- Whisper.Backup.handleDOMException(prefix, error, reject);
-
- assert.strictEqual(called, 1);
- });
- });
-
describe('sanitizeFileName', function() {
it('leaves a basic string alone', function() {
var initial = 'Hello, how are you #5 (\'fine\' + great).jpg';
diff --git a/test/database_test.js b/test/database_test.js
new file mode 100644
index 0000000000..7d67ad3563
--- /dev/null
+++ b/test/database_test.js
@@ -0,0 +1,34 @@
+'use strict';
+
+describe('Database', function() {
+ describe('handleDOMException', function() {
+ it('handles null, still calls reject', function() {
+ var called = 0;
+ var reject = function() {
+ called += 1;
+ };
+ var error = null;
+ var prefix = 'something';
+
+ Whisper.Database.handleDOMException(prefix, error, reject);
+
+ assert.strictEqual(called, 1);
+ });
+
+ it('handles object code and message', function() {
+ var called = 0;
+ var reject = function() {
+ called += 1;
+ };
+ var error = {
+ code: 4,
+ message: 'some cryptic error',
+ };
+ var prefix = 'something';
+
+ Whisper.Database.handleDOMException(prefix, error, reject);
+
+ assert.strictEqual(called, 1);
+ });
+ });
+});
diff --git a/test/index.html b/test/index.html
index 52a3b74bfc..2c6c101e7e 100644
--- a/test/index.html
+++ b/test/index.html
@@ -640,6 +640,7 @@
+