diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 4aaad9806..ececbfe68 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1302,22 +1302,6 @@ "message": "Typing Indicators", "description": "Title of the typing indicators setting" }, - "multiDeviceDisabledTemporary": { - "message": "MultiDevice disabled temporarily", - "description": "Description of why multi device is disabled" - }, - "multiDeviceDisabledTemporaryTitle": { - "message": "Changes to Multi-device", - "description": "Description of why multi device is disabled on app start" - }, - "multiDeviceDisabledTemporaryDescriptionPrimary": { - "message": "You’re seeing this because you have a secondary device linked to your Session ID. To improve reliability and stability, we’ve decided to temporarily disable Session’s multi-device functionality. Device linking has been disabled, and existing secondary clients will be erased on August 6th.

To read more about this change, visit the Session FAQ at getsession.org/faq.", - "description": "Description of why multi device is disabled on app start for a primary device" - }, - "multiDeviceDisabledTemporaryDescriptionSecondary": { - "message": "You’re seeing this because this is a secondary device in a multi-device setup. To improve reliability and stability, we’ve decided to temporarily disable Session’s multi-device functionality. Device linking has been disabled, and existing secondary clients will be erased on August 6th.

To read more about this change, visit the Session FAQ at getsession.org/faq.", - "description": "Description of why multi device is disabled on app start for a secondary device" - }, "messageTTL": { "message": "Message TTL", "description": "Title of the Message TTL setting" @@ -1752,6 +1736,9 @@ "unlinked": { "message": "Unlinked" }, + "successUnlinked": { + "message": "Your device was unlinked successfully" + }, "relink": { "message": "Relink" }, diff --git a/app/sql.js b/app/sql.js index 086100c5b..301956921 100644 --- a/app/sql.js +++ b/app/sql.js @@ -810,6 +810,7 @@ const LOKI_SCHEMA_VERSIONS = [ updateToLokiSchemaVersion4, updateToLokiSchemaVersion5, updateToLokiSchemaVersion6, + updateToLokiSchemaVersion7, ]; async function updateToLokiSchemaVersion1(currentVersion, instance) { @@ -1027,6 +1028,30 @@ async function updateToLokiSchemaVersion6(currentVersion, instance) { console.log('updateToLokiSchemaVersion6: success!'); } +async function updateToLokiSchemaVersion7(currentVersion, instance) { + if (currentVersion >= 7) { + return; + } + + console.log('updateToLokiSchemaVersion7: starting...'); + + await instance.run('BEGIN TRANSACTION;'); + + // Remove multi device data + await instance.run('DELETE FROM pairingAuthorisations;'); + + await instance.run( + `INSERT INTO loki_schema ( + version + ) values ( + 7 + );` + ); + + await instance.run('COMMIT TRANSACTION;'); + console.log('updateToLokiSchemaVersion7: success!'); +} + async function updateLokiSchema(instance) { const result = await instance.get( "SELECT name FROM sqlite_master WHERE type = 'table' AND name='loki_schema';" diff --git a/js/background.js b/js/background.js index 67c556030..bece78657 100644 --- a/js/background.js +++ b/js/background.js @@ -129,6 +129,52 @@ // of preload.js processing window.setImmediate = window.nodeSetImmediate; + window.toasts = new Map(); + window.pushToast = options => { + // Setting toasts with the same ID can be used to prevent identical + // toasts from appearing at once (stacking). + // If toast already exists, it will be reloaded (updated) + + const params = { + title: options.title, + id: options.id || window.generateID(), + description: options.description || '', + type: options.type || '', + icon: options.icon || '', + shouldFade: options.shouldFade, + }; + + // Give all toasts an ID. User may define. + let currentToast; + const toastID = params.id; + const toast = !!toastID && window.toasts.get(toastID); + if (toast) { + currentToast = window.toasts.get(toastID); + currentToast.update(params); + } else { + // Make new Toast + window.toasts.set( + toastID, + new Whisper.SessionToastView({ + el: $('body'), + }) + ); + + currentToast = window.toasts.get(toastID); + currentToast.render(); + currentToast.update(params); + } + + // Remove some toasts if too many exist + const maxToasts = 6; + while (window.toasts.size > maxToasts) { + const finalToastID = window.toasts.keys().next().value; + window.toasts.get(finalToastID).fadeToast(); + } + + return toastID; + }; + const { IdleDetector, MessageDataMigrator } = Signal.Workflow; const { mandatoryMessageUpgrade, @@ -151,6 +197,20 @@ window.log.info('background page reloaded'); window.log.info('environment:', window.getEnvironment()); + const restartReason = localStorage.getItem('restart-reason'); + window.log.info('restartReason:', restartReason); + + if (restartReason === 'unlink') { + setTimeout(() => { + localStorage.removeItem('restart-reason'); + + window.pushToast({ + title: window.i18n('successUnlinked'), + type: 'info', + id: '123', + }); + }, 2000); + } let idleDetector; let initialLoadComplete = false; @@ -300,6 +360,12 @@ storage.put('primaryDevicePubKey', textsecure.storage.user.getNumber()); } + // 4th August 2020 - Force wipe of secondary devices as multi device is being disabled. + if (storage.get('isSecondaryDevice')) { + await window.deleteAccount('unlink'); + return; + } + // These make key operations available to IPC handlers created in preload.js window.Events = { getThemeSetting: () => 'dark', // storage.get('theme-setting', 'dark') @@ -754,76 +820,9 @@ .toString(36) .substring(3); - window.toasts = new Map(); - window.pushToast = options => { - // Setting toasts with the same ID can be used to prevent identical - // toasts from appearing at once (stacking). - // If toast already exists, it will be reloaded (updated) - - const params = { - title: options.title, - id: options.id || window.generateID(), - description: options.description || '', - type: options.type || '', - icon: options.icon || '', - shouldFade: options.shouldFade, - }; - - // Give all toasts an ID. User may define. - let currentToast; - const toastID = params.id; - const toast = !!toastID && window.toasts.get(toastID); - if (toast) { - currentToast = window.toasts.get(toastID); - currentToast.update(params); - } else { - // Make new Toast - window.toasts.set( - toastID, - new Whisper.SessionToastView({ - el: $('body'), - }) - ); - - currentToast = window.toasts.get(toastID); - currentToast.render(); - currentToast.update(params); - } - - // Remove some toasts if too many exist - const maxToasts = 6; - while (window.toasts.size > maxToasts) { - const finalToastID = window.toasts.keys().next().value; - window.toasts.get(finalToastID).fadeToast(); - } - - return toastID; - }; - // Get memberlist. This function is not accurate >> // window.getMemberList = window.lokiPublicChatAPI.getListOfMembers(); - window.deleteAccount = async () => { - try { - window.log.info('Deleting everything!'); - - const { Logs } = window.Signal; - await Logs.deleteAll(); - - await window.Signal.Data.removeAll(); - await window.Signal.Data.close(); - await window.Signal.Data.removeDB(); - - await window.Signal.Data.removeOtherData(); - } catch (error) { - window.log.error( - 'Something went wrong deleting all data:', - error && error.stack ? error.stack : error - ); - } - window.restart(); - }; - window.toggleTheme = () => { const theme = window.Events.getThemeSetting(); const updatedTheme = theme === 'dark' ? 'light' : 'dark'; diff --git a/js/logging.js b/js/logging.js index e675e44ef..0c7cb1186 100644 --- a/js/logging.js +++ b/js/logging.js @@ -137,6 +137,6 @@ window.onerror = (message, script, line, col, error) => { window.addEventListener('unhandledrejection', rejectionEvent => { const error = rejectionEvent.reason; - const errorInfo = error && error.stack ? error.stack : JSON.stringify(error); - window.log.error(`Top-level unhandled promise rejection: ${errorInfo}`); + const errorInfo = error && error.stack ? error.stack : error; + window.log.error('Top-level unhandled promise rejection:', errorInfo); }); diff --git a/js/models/conversations.js b/js/models/conversations.js index 6ecf74d29..ab9db964d 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -2210,16 +2210,20 @@ return; } - const profileKeyBuffer = window.Signal.Crypto.base64ToArrayBuffer( - profileKey - ); - const accessKeyBuffer = await window.Signal.Crypto.deriveAccessKey( - profileKeyBuffer - ); - const accessKey = window.Signal.Crypto.arrayBufferToBase64( - accessKeyBuffer - ); - this.set({ accessKey }); + try { + const profileKeyBuffer = window.Signal.Crypto.base64ToArrayBuffer( + profileKey + ); + const accessKeyBuffer = await window.Signal.Crypto.deriveAccessKey( + profileKeyBuffer + ); + const accessKey = window.Signal.Crypto.arrayBufferToBase64( + accessKeyBuffer + ); + this.set({ accessKey }); + } catch (e) { + window.log.warn(`Failed to derive access key for ${this.id}`); + } }, async upgradeMessages(messages) { diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 526f1b44f..ae606fb35 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -1710,8 +1710,9 @@ class LokiPublicChannelAPI { sigString += [...attachmentAnnotations, ...previewAnnotations] .map(data => data.id || data.image.id) .sort() - .join(); + .join(''); sigString += sigVer; + return dcodeIO.ByteBuffer.wrap(sigString, 'utf8').toArrayBuffer(); } diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index 8956c7f21..9287875c9 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -218,6 +218,10 @@ class LokiHomeServerInstance extends LokiFileServerInstance { } async updateOurDeviceMapping() { + if (!window.lokiFeatureFlags.useMultiDevice) { + return undefined; + } + const isPrimary = !storage.get('isSecondaryDevice'); const authorisations = await window.libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations( this.ourKey diff --git a/package.json b/package.json index 5552f5c11..72e8330ff 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "session-messenger-desktop", "productName": "Session", "description": "Private messaging from your desktop", - "version": "1.2.0", + "version": "1.2.1", "license": "GPL-3.0", "author": { "name": "Loki Project", diff --git a/preload.js b/preload.js index bf4471ec0..92204da6e 100644 --- a/preload.js +++ b/preload.js @@ -456,6 +456,7 @@ window.lokiFeatureFlags = { enableSenderKeys: false, onionRequestHops: 3, debugMessageLogs: process.env.ENABLE_MESSAGE_LOGS, + useMultiDevice: false, }; // eslint-disable-next-line no-extend-native,func-names @@ -492,6 +493,7 @@ if (config.environment.includes('test-integration')) { useFileOnionRequests: false, debugMessageLogs: true, enableSenderKeys: true, + useMultiDevice: false, }; } @@ -502,3 +504,26 @@ const { } = require('./ts/util/blockedNumberController'); window.BlockedNumberController = BlockedNumberController; + +window.deleteAccount = async reason => { + try { + window.log.info('Deleting everything!'); + + const { Logs } = window.Signal; + await Logs.deleteAll(); + + await window.Signal.Data.removeAll(); + await window.Signal.Data.close(); + await window.Signal.Data.removeDB(); + + await window.Signal.Data.removeOtherData(); + // 'unlink' => toast will be shown on app restart + window.localStorage.setItem('restart-reason', reason); + } catch (error) { + window.log.error( + 'Something went wrong deleting all data:', + error && error.stack ? error.stack : error + ); + } + window.restart(); +}; diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index 484a3b89f..84d653bfa 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -59,48 +59,6 @@ export class ActionsPanel extends React.Component { }, 'refreshAvatarCallback' ); - setTimeout(async () => { - const disabledMultiDeviceCountDb = await getItemById( - 'disabledMultiDeviceCount' - ); - const disabledMultiDeviceCount = - Number(disabledMultiDeviceCountDb?.value) || 0; - const data = { - id: 'disabledMultiDeviceCount', - value: String(disabledMultiDeviceCount + 1), - }; - await createOrUpdateItem(data); - if (disabledMultiDeviceCount % 5 !== 0) { - return; - } - const currentDevice = await UserUtil.getCurrentDevicePubKey(); - if (!currentDevice) { - return; - } - const secondaryDevices = await MultiDeviceProtocol.getSecondaryDevices( - currentDevice - ); - const isSecondary = - secondaryDevices.find(s => s.key === currentDevice) || - !!window.textsecure.storage.get('isSecondaryDevice'); - - const hasMultipleDevices = - (await MultiDeviceProtocol.getOurDevices()).length > 1; - const primaryWithSecondary = !isSecondary && hasMultipleDevices; - - if (!primaryWithSecondary && !isSecondary) { - return; - } - - const opts = { - hideCancel: true, - title: window.i18n('multiDeviceDisabledTemporaryTitle'), - message: primaryWithSecondary - ? window.i18n('multiDeviceDisabledTemporaryDescriptionPrimary') - : window.i18n('multiDeviceDisabledTemporaryDescriptionSecondary'), - }; - window.Whisper.events.trigger('showConfirmationDialog', opts); - }, 1000); } ); } diff --git a/ts/components/session/LeftPaneSettingSection.tsx b/ts/components/session/LeftPaneSettingSection.tsx index 7d729e498..cecb27ad8 100644 --- a/ts/components/session/LeftPaneSettingSection.tsx +++ b/ts/components/session/LeftPaneSettingSection.tsx @@ -225,7 +225,7 @@ export class LeftPaneSettingSection extends React.Component { { id: SessionSettingCategory.Devices, title: window.i18n('devicesSettingsTitle'), - hidden: isSecondaryDevice, + hidden: !window.lokiFeatureFlags.useMultiDevice || isSecondaryDevice, }, ]; } diff --git a/ts/components/session/RegistrationTabs.tsx b/ts/components/session/RegistrationTabs.tsx index 4690a3d34..71fa3aac7 100644 --- a/ts/components/session/RegistrationTabs.tsx +++ b/ts/components/session/RegistrationTabs.tsx @@ -557,9 +557,12 @@ export class RegistrationTabs extends React.Component<{}, State> { SessionButtonType.BrandOutline, SessionButtonColor.Green )} - {/*

{or}

*/} - {/* FIXME enable back to allow linking of device - this.renderLinkDeviceToExistingAccountButton() */} + {window.lokiFeatureFlags.useMultiDevice && ( + <> +

{or}

+ {this.renderLinkDeviceToExistingAccountButton()} + + )} ); } @@ -584,9 +587,12 @@ export class RegistrationTabs extends React.Component<{}, State> { return (
{this.renderContinueYourSessionButton()} - {/*

{or}

*/} - {/* FIXME enable back to allow linking of device - this.renderLinkDeviceToExistingAccountButton()*/} + {window.lokiFeatureFlags.useMultiDevice && ( + <> +

{or}

+ {this.renderLinkDeviceToExistingAccountButton()} + + )}
); } diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index f60459ea8..3b4076da0 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -644,7 +644,7 @@ export class SettingsView extends React.Component { id: 'no-linked-device', title: noPairedDeviceText, type: undefined, - description: window.i18n('multiDeviceDisabledTemporary'), + description: '', category: SessionSettingCategory.Devices, content: {}, comparisonValue: undefined, diff --git a/ts/components/session/settings/SessionSettingsHeader.tsx b/ts/components/session/settings/SessionSettingsHeader.tsx index b1e33393a..21e663e96 100644 --- a/ts/components/session/settings/SessionSettingsHeader.tsx +++ b/ts/components/session/settings/SessionSettingsHeader.tsx @@ -74,12 +74,9 @@ export class SettingsHeader extends React.Component { ? `${categoryTitlePrefix.slice(0, -1)} Settings` : `${categoryTitlePrefix} Settings`; const showSearch = false; - const showAddDevice = false; - /* FIXME enable back to allow linking of device - const showAddDevice = + const showAddDevice = category === SessionSettingCategory.Devices && this.props.showLinkDeviceButton; - */ return (
diff --git a/ts/receiver/multidevice.ts b/ts/receiver/multidevice.ts index 6dd9142b6..c1c69261d 100644 --- a/ts/receiver/multidevice.ts +++ b/ts/receiver/multidevice.ts @@ -87,6 +87,14 @@ export async function handlePairingAuthorisationMessage( pairingAuthorisation: SignalService.IPairingAuthorisationMessage, dataMessage: SignalService.IDataMessage | undefined | null ): Promise { + if (!window.lokiFeatureFlags.useMultiDevice) { + window.log.info( + `Received a pairing authorisation message from ${envelope.source} while multi device is disabled.` + ); + await removeFromCache(envelope); + return; + } + const { secondaryDevicePubKey, grantSignature } = pairingAuthorisation; const isGrant = grantSignature && diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts index 2ae330040..3e3a2320a 100644 --- a/ts/session/protocols/MultiDeviceProtocol.ts +++ b/ts/session/protocols/MultiDeviceProtocol.ts @@ -28,6 +28,11 @@ export class MultiDeviceProtocol { public static async fetchPairingAuthorisationsIfNeeded( device: PubKey ): Promise { + // Disable fetching if we don't want to use multi device + if (!window.lokiFeatureFlags.useMultiDevice) { + return; + } + // This return here stops an infinite loop when we get all our other devices const ourKey = await UserUtil.getCurrentDevicePubKey(); if (!ourKey || device.key === ourKey) { diff --git a/ts/test/session/protocols/MultiDeviceProtocol_test.ts b/ts/test/session/protocols/MultiDeviceProtocol_test.ts index e1d248121..1a5dd8220 100644 --- a/ts/test/session/protocols/MultiDeviceProtocol_test.ts +++ b/ts/test/session/protocols/MultiDeviceProtocol_test.ts @@ -25,6 +25,13 @@ function generateFakeAuthorisations( describe('MultiDeviceProtocol', () => { const sandbox = sinon.createSandbox(); + beforeEach(() => { + // Enable multidevice for tests + TestUtils.stubWindow('lokiFeatureFlags', { + useMultiDevice: true, + }); + }); + afterEach(() => { TestUtils.restoreStubs(); sandbox.restore(); diff --git a/ts/window.d.ts b/ts/window.d.ts index f6d15c2d6..d6b245677 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -51,7 +51,17 @@ declare global { libloki: Libloki; libsignal: LibsignalProtocol; log: any; - lokiFeatureFlags: any; + lokiFeatureFlags: { + multiDeviceUnpairing: boolean; + privateGroupChats: boolean; + useSnodeProxy: boolean; + useOnionRequests: boolean; + useFileOnionRequests: boolean; + enableSenderKeys: boolean; + onionRequestHops: number; + debugMessageLogs: boolean; + useMultiDevice: boolean; + }; lokiFileServerAPI: LokiFileServerInstance; lokiMessageAPI: LokiMessageInterface; lokiPublicChatAPI: LokiPublicChatFactoryInterface;