diff --git a/js/modules/settings.js b/js/modules/settings.js index d99bfdeb9..a4c609ccd 100644 --- a/js/modules/settings.js +++ b/js/modules/settings.js @@ -6,6 +6,8 @@ const LAST_PROCESSED_INDEX_KEY = 'attachmentMigration_lastProcessedIndex'; const IS_MIGRATION_COMPLETE_KEY = 'attachmentMigration_isComplete'; // Public API +exports.READ_RECEIPT_CONFIGURATION_SYNC = 'read-receipt-configuration-sync'; + exports.getAttachmentMigrationLastProcessedIndex = connection => exports._getItem(connection, LAST_PROCESSED_INDEX_KEY); diff --git a/js/modules/startup.js b/js/modules/startup.js new file mode 100644 index 000000000..fc60861de --- /dev/null +++ b/js/modules/startup.js @@ -0,0 +1,55 @@ +const is = require('@sindresorhus/is'); + +const Errors = require('./types/errors'); +const Settings = require('./settings'); + + +exports.syncReadReceiptConfiguration = async ({ + deviceId, + sendRequestConfigurationSyncMessage, + storage, +}) => { + if (!is.string(deviceId)) { + throw new TypeError('"deviceId" is required'); + } + + if (!is.function(sendRequestConfigurationSyncMessage)) { + throw new TypeError('"sendRequestConfigurationSyncMessage" is required'); + } + + if (!is.object(storage)) { + throw new TypeError('"storage" is required'); + } + + const isPrimaryDevice = deviceId === '1'; + if (isPrimaryDevice) { + return { + status: 'skipped', + reason: 'isPrimaryDevice', + }; + } + + const settingName = Settings.READ_RECEIPT_CONFIGURATION_SYNC; + const hasPreviouslySynced = Boolean(storage.get(settingName)); + if (hasPreviouslySynced) { + return { + status: 'skipped', + reason: 'hasPreviouslySynced', + }; + } + + try { + await sendRequestConfigurationSyncMessage(); + storage.put(settingName, true); + } catch (error) { + return { + status: 'error', + reason: 'failedToSendSyncMessage', + error: Errors.toLogFormat(error), + }; + } + + return { + status: 'complete', + }; +}; diff --git a/preload.js b/preload.js index 4c998af96..70938c04d 100644 --- a/preload.js +++ b/preload.js @@ -146,6 +146,7 @@ window.Signal.Migrations.Migrations1DatabaseWithoutAttachmentData = window.Signal.Migrations.upgradeMessageSchema = upgradeMessageSchema; window.Signal.OS = require('./js/modules/os'); window.Signal.Settings = require('./js/modules/settings'); +window.Signal.Startup = require('./js/modules/startup'); window.Signal.Types = {}; window.Signal.Types.Attachment = Attachment; diff --git a/test/modules/startup_test.js b/test/modules/startup_test.js new file mode 100644 index 000000000..d39aab3c0 --- /dev/null +++ b/test/modules/startup_test.js @@ -0,0 +1,123 @@ +const sinon = require('sinon'); +const { assert } = require('chai'); + +const Startup = require('../../js/modules/startup'); + + +describe('Startup', () => { + const sandbox = sinon.createSandbox(); + + describe('syncReadReceiptConfiguration', () => { + afterEach(() => { + sandbox.restore(); + }); + + it('should complete if user hasn’t previously synced', async () => { + const deviceId = '2'; + const sendRequestConfigurationSyncMessage = sandbox.spy(); + const storagePutSpy = sandbox.spy(); + const storage = { + get(name) { + if (name !== 'read-receipt-configuration-sync') { + return true; + } + + return false; + }, + put: storagePutSpy, + }; + + const expected = { + status: 'complete', + }; + + const actual = await Startup.syncReadReceiptConfiguration({ + deviceId, + sendRequestConfigurationSyncMessage, + storage, + }); + + assert.deepEqual(actual, expected); + assert.equal(sendRequestConfigurationSyncMessage.callCount, 1); + assert.equal(storagePutSpy.callCount, 1); + assert(storagePutSpy.calledWith('read-receipt-configuration-sync', true)); + }); + + it('should be skipped if this is the primary device', async () => { + const deviceId = '1'; + const sendRequestConfigurationSyncMessage = () => {}; + const storage = {}; + + const expected = { + status: 'skipped', + reason: 'isPrimaryDevice', + }; + + const actual = await Startup.syncReadReceiptConfiguration({ + deviceId, + sendRequestConfigurationSyncMessage, + storage, + }); + + assert.deepEqual(actual, expected); + }); + + it('should be skipped if user has previously synced', async () => { + const deviceId = '2'; + const sendRequestConfigurationSyncMessage = () => {}; + const storage = { + get(name) { + if (name !== 'read-receipt-configuration-sync') { + return false; + } + + return true; + }, + }; + + const expected = { + status: 'skipped', + reason: 'hasPreviouslySynced', + }; + + const actual = await Startup.syncReadReceiptConfiguration({ + deviceId, + sendRequestConfigurationSyncMessage, + storage, + }); + + assert.deepEqual(actual, expected); + }); + + it('should return error if sending of sync request fails', async () => { + const deviceId = '2'; + + const sendRequestConfigurationSyncMessage = sandbox.stub(); + sendRequestConfigurationSyncMessage.rejects(new Error('boom')); + + const storagePutSpy = sandbox.spy(); + const storage = { + get(name) { + if (name !== 'read-receipt-configuration-sync') { + return true; + } + + return false; + }, + put: storagePutSpy, + }; + + const actual = await Startup.syncReadReceiptConfiguration({ + deviceId, + sendRequestConfigurationSyncMessage, + storage, + }); + + assert.equal(actual.status, 'error'); + assert.include(actual.error, 'boom'); + + assert.equal(sendRequestConfigurationSyncMessage.callCount, 1); + assert.equal(storagePutSpy.callCount, 0); + }); + }); +});