Merge remote-tracking branch 'upstream/clearnet' into fix-integration-tests

This commit is contained in:
Audric Ackermann 2020-08-07 10:00:37 +10:00
commit 58dd064e93
No known key found for this signature in database
GPG key ID: 999F434D76324AD4
29 changed files with 414 additions and 225 deletions

View file

@ -9,6 +9,7 @@ on:
- clearnet - clearnet
- github-actions - github-actions
- refactor-ts-react - refactor-ts-react
- clean-en-translation
jobs: jobs:
build: build:

View file

@ -83,10 +83,20 @@
"message": "Löschen", "message": "Löschen",
"description": "Edit menu command to remove the selected text" "description": "Edit menu command to remove the selected text"
}, },
"editProfileModalTitle": {
"message": "Profil",
"description": "Title for the Edit Profile modal"
},
"editMenuSelectAll": { "editMenuSelectAll": {
"message": "Alles auswählen", "message": "Alles auswählen",
"description": "Edit menu comand to select all of the text in selected text box" "description": "Edit menu comand to select all of the text in selected text box"
}, },
"newSession": {
"message": "Neue Session"
},
"addContact": {
"message": "Kontakt hinzufügen"
},
"editMenuStartSpeaking": { "editMenuStartSpeaking": {
"message": "Sprachausgabe starten", "message": "Sprachausgabe starten",
"description": "Edit menu item under 'speech' to start dictation" "description": "Edit menu item under 'speech' to start dictation"
@ -147,6 +157,9 @@
"message": "Wird geladen …", "message": "Wird geladen …",
"description": "Message shown on the loading screen before we've loaded any messages" "description": "Message shown on the loading screen before we've loaded any messages"
}, },
"or": {
"message": "oder"
},
"optimizingApplication": { "optimizingApplication": {
"message": "Anwendung wird optimiert …", "message": "Anwendung wird optimiert …",
"description": "Message shown on the loading screen while we are doing application optimizations" "description": "Message shown on the loading screen while we are doing application optimizations"
@ -549,10 +562,90 @@
} }
} }
}, },
"copiedPublicKey": {
"message": "In Zwischenablage kopiert",
"description": "A toast message telling the user that the key was copied"
},
"copiedChatId": {
"message": "In Zwischenablage kopiert",
"description": "A toast message telling the user that the key was copied"
},
"sessionResetOngoing": {
"message": "Verschlüsselung neu gestartet",
"description": "your secure session is currently being reset, waiting for the reset acknowledgment."
},
"blockUser": {
"message": "Blockieren"
},
"unblockUser": {
"message": "Freigeben"
},
"members": {
"message": "$count$ mitglied",
"placeholders": {
"count": {
"content": "$1",
"example": "26"
}
}
},
"noContactsForGroup": {
"message": "Sie haben noch keine Kontakte."
},
"getStarted": {
"message": "Loslegen"
},
"createAccount": {
"message": "Konto Erstellen"
},
"signIn": {
"message": "Einloggen"
},
"beginYourSession": {
"message": "Beginnen<br />Sie<br />Ihre<br />Session."
},
"continue": {
"message": "Fortsetzen"
},
"welcomeToYourSession": {
"message": "Willkommen bei Session"
},
"generateSessionID": {
"message": "Session ID erstellen"
},
"yourUniqueSessionID": {
"message": "Das ist Ihre Session ID."
},
"allUsersAreRandomly...": {
"message": "Ihre Session ID ist die eindeutige Adresse, unter der Personen Sie über Session kontaktieren können. Ihre Session ID ist nicht mit Ihrer realen Identität verbunden, völlig anonym und von Natur aus privat."
},
"leaveOpenGroupConfirmation": {
"message": "Möchtest du wirklich diese Gruppe verlassen??",
"description": "Confirmation dialog text that tells the user what will happen if they leave the public channel."
},
"leaveClosedGroupConfirmation": {
"message": "Möchtest du wirklich diese Gruppe verlassen?",
"description": "Confirmation dialog text that tells the user what will happen if they leave the closed group."
},
"copyPublicKey": {
"message": "Kopieren",
"description": "Button action that the user can click to copy their public keys"
},
"copyChatId": {
"message": "Kopieren"
},
"deleteContact": {
"message": "Kontakt löschen",
"description": "Confirmation dialog title that asks the user if they really wish to delete the contact. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone."
},
"oneNonImageAtATimeToast": { "oneNonImageAtATimeToast": {
"message": "Mehrere Anhänge je Nachricht sind ausschließlich bei Bildern erlaubt.", "message": "Mehrere Anhänge je Nachricht sind ausschließlich bei Bildern erlaubt.",
"description": "An error popup when the user has attempted to add an attachment" "description": "An error popup when the user has attempted to add an attachment"
}, },
"editGroupName": {
"message": "Gruppe bearbeiten",
"description": "Button action that the user can click to edit a group name (closed)"
},
"cannotMixImageAndNonImageAttachments": { "cannotMixImageAndNonImageAttachments": {
"message": "Du kannst Bilder nicht gemeinsam mit anderen Anhängen in einer Nachricht kombinieren.", "message": "Du kannst Bilder nicht gemeinsam mit anderen Anhängen in einer Nachricht kombinieren.",
"description": "An error popup when the user has attempted to add an attachment" "description": "An error popup when the user has attempted to add an attachment"
@ -739,6 +832,24 @@
"message": "Auf Nachricht antworten", "message": "Auf Nachricht antworten",
"description": "Shown in triple-dot menu next to message to allow user to start crafting a message with a quotation" "description": "Shown in triple-dot menu next to message to allow user to start crafting a message with a quotation"
}, },
"enterOpenGroupURL": {
"message": "Gruppen-URL öffnen"
},
"newClosedGroup": {
"message": "Neue geschlossene Gruppe"
},
"createClosedGroupNamePrompt": {
"message": "Gruppenname"
},
"createClosedGroupPlaceholder": {
"message": "Geben Sie einen Gruppennamen ein."
},
"addChannelDescription": {
"message": "Geben Sie eine offene Gruppen-URL ein."
},
"next": {
"message": "Weiter"
},
"originalMessageNotFound": { "originalMessageNotFound": {
"message": "Originalnachricht nicht gefunden", "message": "Originalnachricht nicht gefunden",
"description": "Shown in quote if reference message was not found as message was initially downloaded and processed" "description": "Shown in quote if reference message was not found as message was initially downloaded and processed"
@ -802,7 +913,7 @@
"description": "Shown on MacOS if running on a read-only volume and we cannot update" "description": "Shown on MacOS if running on a read-only volume and we cannot update"
}, },
"ok": { "ok": {
"message": "Okay", "message": "Ok",
"description": "" "description": ""
}, },
"cancel": { "cancel": {
@ -907,6 +1018,14 @@
"message": "Soll diese Unterhaltung unwiderruflich gelöscht werden?", "message": "Soll diese Unterhaltung unwiderruflich gelöscht werden?",
"description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." "description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone."
}, },
"deleteAccount": {
"message": "Alle Daten löschen",
"description": "Text for button in settings view to delete account"
},
"deleteAccountWarningSub": {
"message": "Dadurch werden Ihre Nachrichten, Sessions und Kontakte dauerhaft gelöscht.",
"description": "Warning for account deletion in settings view"
},
"sessionEnded": { "sessionEnded": {
"message": "Verschlüsselung zurückgesetzt", "message": "Verschlüsselung zurückgesetzt",
"description": "This is a past tense, informational message. In other words, your secure session has been reset." "description": "This is a past tense, informational message. In other words, your secure session has been reset."
@ -1117,10 +1236,60 @@
"message": "Erfahre mehr über das Verifizieren von Sicherheitsnummern", "message": "Erfahre mehr über das Verifizieren von Sicherheitsnummern",
"description": "Text that links to a support article on verifying safety numbers" "description": "Text that links to a support article on verifying safety numbers"
}, },
"appearanceSettingsTitle": {
"message": "Darstellung"
},
"privacySettingsTitle": {
"message": "Datenschutz"
},
"notificationsSettingsTitle": {
"message": "Benachrichtigungen"
},
"devicesSettingsTitle": {
"message": "Geräte"
},
"linkPreviewsTitle": {
"message": "Link-Vorschauen senden",
"description": "Option to control creation and send of link previews in setting screen"
},
"linkPreviewDescription": {
"message": "Vorschauen werden unterstützt für Links von Imgur, Instagram, Pinterest, Reddit und YouTube",
"description": "Description shown for the Link Preview option "
},
"expiredWarning": { "expiredWarning": {
"message": "Diese Version von Signal Desktop ist veraltet. Bitte führe eine Aktualisierung auf die aktuellste Version durch, um weiterhin Nachrichten austauschen zu können.", "message": "Diese Version von Signal Desktop ist veraltet. Bitte führe eine Aktualisierung auf die aktuellste Version durch, um weiterhin Nachrichten austauschen zu können.",
"description": "Warning notification that this version of the app has expired" "description": "Warning notification that this version of the app has expired"
}, },
"readReceiptSettingDescription": {
"message": "Lesebestätigungen aktivieren",
"description": "Description of the read receipts setting"
},
"readReceiptSettingTitle": {
"message": "Lesebestätigungen",
"description": "Title of the read receipts setting"
},
"typingIndicatorsSettingDescription": {
"message": "sehen und teilen, wann Nachrichten eingetippt werden",
"description": "Description of the typing indicators setting"
},
"typingIndicatorsSettingTitle": {
"message": "Tipp-Indikatoren",
"description": "Title of the typing indicators setting"
},
"inviteContacts": {
"message": "Freunde einladen"
},
"settingsHeader": {
"message": "Einstellungen"
},
"leaveOpenGroup": {
"message": "Gruppe öffnen verlassen",
"description": "Button action that the user can click to leave the group"
},
"leaveClosedGroup": {
"message": "Geschlossene Gruppe verlassen",
"description": "Button action that the user can click to leave the group"
},
"upgrade": { "upgrade": {
"message": "Aktualisieren", "message": "Aktualisieren",
"description": "Label text for button to upgrade the app to the latest version" "description": "Label text for button to upgrade the app to the latest version"
@ -1513,6 +1682,24 @@
} }
} }
}, },
"joinOpenGroup": {
"message": "Offener Gruppe beitreten"
},
"yourSessionID": {
"message": "Ihre Session ID"
},
"createClosedGroup": {
"message": "Neue geschlossene Gruppe"
},
"searchForAKeyPhrase": {
"message": "Nach Unterhaltungen, Kontakten und Nachrichten suchen"
},
"enterSessionID": {
"message": "Session ID eingeben"
},
"usersCanShareTheir...": {
"message": "Benutzer können ihre Session ID freigeben, indem sie in ihren Einstellungen auf \"Session ID freigeben\" tippen oder ihren QR-Code freigeben."
},
"joinedTheGroup": { "joinedTheGroup": {
"message": "$name$ ist der Gruppe beigetreten", "message": "$name$ ist der Gruppe beigetreten",
"description": "Shown in the conversation history when a single person joins the group", "description": "Shown in the conversation history when a single person joins the group",

View file

@ -596,8 +596,7 @@
"description": "Shown to separate the types of search results" "description": "Shown to separate the types of search results"
}, },
"settingsHeader": { "settingsHeader": {
"message": "Settings", "message": "Settings"
"description": "Shown to separate the types of search results"
}, },
"welcomeToSession": { "welcomeToSession": {
"message": "Welcome to Session" "message": "Welcome to Session"
@ -723,9 +722,6 @@
"cancel": { "cancel": {
"message": "Cancel" "message": "Cancel"
}, },
"copy": {
"message": "Copy"
},
"close": { "close": {
"message": "Close" "message": "Close"
}, },
@ -913,11 +909,7 @@
"message": "Permanently delete the messages in this conversation?", "message": "Permanently delete the messages in this conversation?",
"description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone." "description": "Confirmation dialog text that asks the user if they really wish to delete the conversation. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone."
}, },
"deletePublicChannel": { "leaveOpenGroupConfirmation": {
"message": "Leave Channel",
"description": "Confirmation dialog title that asks the user if they really wish to delete a public channel. Answer buttons use the strings 'ok' and 'cancel'. The deletion is permanent, i.e. it cannot be undone."
},
"deletePublicChannelConfirmation": {
"message": "Leave this Open Group?", "message": "Leave this Open Group?",
"description": "Confirmation dialog text that tells the user what will happen if they leave the public channel." "description": "Confirmation dialog text that tells the user what will happen if they leave the public channel."
}, },
@ -1051,7 +1043,7 @@
"description": "Header for general options on the settings screen" "description": "Header for general options on the settings screen"
}, },
"linkPreviewsTitle": { "linkPreviewsTitle": {
"message": "Link Previews", "message": "Send Link Previews",
"description": "Option to control creation and send of link previews in setting screen" "description": "Option to control creation and send of link previews in setting screen"
}, },
"linkPreviewDescription": { "linkPreviewDescription": {
@ -1116,22 +1108,6 @@
"message": "Typing Indicators", "message": "Typing Indicators",
"description": "Title of the typing indicators setting" "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": "Youre seeing this because you have a secondary device linked to your Session ID. To improve reliability and stability, weve decided to temporarily disable Sessions multi-device functionality. Device linking has been disabled, and existing secondary clients will be erased on <span style=\"color:#00f782\">August 6th</span>.</br></br>To read more about this change, visit the Session FAQ at <a href=\"https://getsession.org/faq\">getsession.org/faq</a>.",
"description": "Description of why multi device is disabled on app start for a primary device"
},
"multiDeviceDisabledTemporaryDescriptionSecondary": {
"message": "Youre seeing this because this is a secondary device in a multi-device setup. To improve reliability and stability, weve decided to temporarily disable Sessions multi-device functionality. Device linking has been disabled, and existing secondary clients will be erased on <span style=\"color:#00f782\">August 6th</span>.</br></br>To read more about this change, visit the Session FAQ at <a href=\"https://getsession.org/faq\">getsession.org/faq</a>.",
"description": "Description of why multi device is disabled on app start for a secondary device"
},
"messageTTL": { "messageTTL": {
"message": "Message TTL", "message": "Message TTL",
"description": "Title of the Message TTL setting" "description": "Title of the Message TTL setting"
@ -1470,6 +1446,9 @@
"unlinked": { "unlinked": {
"message": "Unlinked" "message": "Unlinked"
}, },
"successUnlinked": {
"message": "Your device was unlinked successfully"
},
"relink": { "relink": {
"message": "Relink" "message": "Relink"
}, },
@ -1607,10 +1586,6 @@
"copyChatId": { "copyChatId": {
"message": "Copy Chat ID" "message": "Copy Chat ID"
}, },
"leaveGroup": {
"message": "Leave Group",
"description": "Button action that the user can click to leave the group"
},
"leaveOpenGroup": { "leaveOpenGroup": {
"message": "Leave Open Group", "message": "Leave Open Group",
"description": "Button action that the user can click to leave the group" "description": "Button action that the user can click to leave the group"
@ -2009,15 +1984,9 @@
"enterSessionID": { "enterSessionID": {
"message": "Enter Session ID" "message": "Enter Session ID"
}, },
"pasteSessionIDRecipient": {
"message": "Enter a Session ID"
},
"usersCanShareTheir...": { "usersCanShareTheir...": {
"message": "Users can share their Session ID from their account settings, or by sharing their QR code." "message": "Users can share their Session ID from their account settings, or by sharing their QR code."
}, },
"searchByIDOrDisplayName": {
"message": "Search by ID # or Display Name"
},
"message": { "message": {
"message": "Message" "message": "Message"
}, },
@ -2030,9 +1999,6 @@
"createGroup": { "createGroup": {
"message": "Create Group" "message": "Create Group"
}, },
"yourPublicKey": {
"message": "Your Session ID"
},
"appearanceSettingsTitle": { "appearanceSettingsTitle": {
"message": "Appearance" "message": "Appearance"
}, },
@ -2042,7 +2008,7 @@
"privacySettingsTitle": { "privacySettingsTitle": {
"message": "Privacy" "message": "Privacy"
}, },
"notificationSettingsTitle": { "notificationsSettingsTitle": {
"message": "Notifications" "message": "Notifications"
}, },
"devicesSettingsTitle": { "devicesSettingsTitle": {
@ -2063,9 +2029,6 @@
} }
} }
}, },
"addChannel": {
"message": "Join Open Group"
},
"joinOpenGroup": { "joinOpenGroup": {
"message": "Join Open Group" "message": "Join Open Group"
}, },
@ -2087,7 +2050,7 @@
"closedGroupCreatedToastTitle": { "closedGroupCreatedToastTitle": {
"message": "Group created successfully" "message": "Group created successfully"
}, },
"enterChannelURL": { "enterOpenGroupURL": {
"message": "Enter Open Group URL" "message": "Enter Open Group URL"
}, },
"channelUrlPlaceholder": { "channelUrlPlaceholder": {

View file

@ -810,6 +810,7 @@ const LOKI_SCHEMA_VERSIONS = [
updateToLokiSchemaVersion4, updateToLokiSchemaVersion4,
updateToLokiSchemaVersion5, updateToLokiSchemaVersion5,
updateToLokiSchemaVersion6, updateToLokiSchemaVersion6,
updateToLokiSchemaVersion7,
]; ];
async function updateToLokiSchemaVersion1(currentVersion, instance) { async function updateToLokiSchemaVersion1(currentVersion, instance) {
@ -1027,6 +1028,30 @@ async function updateToLokiSchemaVersion6(currentVersion, instance) {
console.log('updateToLokiSchemaVersion6: success!'); 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) { async function updateLokiSchema(instance) {
const result = await instance.get( const result = await instance.get(
"SELECT name FROM sqlite_master WHERE type = 'table' AND name='loki_schema';" "SELECT name FROM sqlite_master WHERE type = 'table' AND name='loki_schema';"

View file

@ -129,6 +129,52 @@
// of preload.js processing // of preload.js processing
window.setImmediate = window.nodeSetImmediate; 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 { IdleDetector, MessageDataMigrator } = Signal.Workflow;
const { const {
mandatoryMessageUpgrade, mandatoryMessageUpgrade,
@ -151,6 +197,20 @@
window.log.info('background page reloaded'); window.log.info('background page reloaded');
window.log.info('environment:', window.getEnvironment()); 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 idleDetector;
let initialLoadComplete = false; let initialLoadComplete = false;
@ -300,6 +360,12 @@
storage.put('primaryDevicePubKey', textsecure.storage.user.getNumber()); 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 // These make key operations available to IPC handlers created in preload.js
window.Events = { window.Events = {
getThemeSetting: () => 'dark', // storage.get('theme-setting', 'dark') getThemeSetting: () => 'dark', // storage.get('theme-setting', 'dark')
@ -754,76 +820,9 @@
.toString(36) .toString(36)
.substring(3); .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 >> // Get memberlist. This function is not accurate >>
// window.getMemberList = window.lokiPublicChatAPI.getListOfMembers(); // 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 = () => { window.toggleTheme = () => {
const theme = window.Events.getThemeSetting(); const theme = window.Events.getThemeSetting();
const updatedTheme = theme === 'dark' ? 'light' : 'dark'; const updatedTheme = theme === 'dark' ? 'light' : 'dark';

View file

@ -137,6 +137,6 @@ window.onerror = (message, script, line, col, error) => {
window.addEventListener('unhandledrejection', rejectionEvent => { window.addEventListener('unhandledrejection', rejectionEvent => {
const error = rejectionEvent.reason; const error = rejectionEvent.reason;
const errorInfo = error && error.stack ? error.stack : JSON.stringify(error); const errorInfo = error && error.stack ? error.stack : error;
window.log.error(`Top-level unhandled promise rejection: ${errorInfo}`); window.log.error('Top-level unhandled promise rejection:', errorInfo);
}); });

View file

@ -2210,16 +2210,20 @@
return; return;
} }
const profileKeyBuffer = window.Signal.Crypto.base64ToArrayBuffer( try {
profileKey const profileKeyBuffer = window.Signal.Crypto.base64ToArrayBuffer(
); profileKey
const accessKeyBuffer = await window.Signal.Crypto.deriveAccessKey( );
profileKeyBuffer const accessKeyBuffer = await window.Signal.Crypto.deriveAccessKey(
); profileKeyBuffer
const accessKey = window.Signal.Crypto.arrayBufferToBase64( );
accessKeyBuffer const accessKey = window.Signal.Crypto.arrayBufferToBase64(
); accessKeyBuffer
this.set({ accessKey }); );
this.set({ accessKey });
} catch (e) {
window.log.warn(`Failed to derive access key for ${this.id}`);
}
}, },
async upgradeMessages(messages) { async upgradeMessages(messages) {
@ -2327,8 +2331,8 @@
let message = i18n('deleteContactConfirmation'); let message = i18n('deleteContactConfirmation');
if (this.isPublic()) { if (this.isPublic()) {
title = i18n('deletePublicChannel'); title = i18n('leaveOpenGroup');
message = i18n('deletePublicChannelConfirmation'); message = i18n('leaveOpenGroupConfirmation');
} else if (this.isClosedGroup()) { } else if (this.isClosedGroup()) {
title = i18n('leaveClosedGroup'); title = i18n('leaveClosedGroup');
message = i18n('leaveClosedGroupConfirmation'); message = i18n('leaveClosedGroupConfirmation');

View file

@ -1710,8 +1710,9 @@ class LokiPublicChannelAPI {
sigString += [...attachmentAnnotations, ...previewAnnotations] sigString += [...attachmentAnnotations, ...previewAnnotations]
.map(data => data.id || data.image.id) .map(data => data.id || data.image.id)
.sort() .sort()
.join(); .join('');
sigString += sigVer; sigString += sigVer;
return dcodeIO.ByteBuffer.wrap(sigString, 'utf8').toArrayBuffer(); return dcodeIO.ByteBuffer.wrap(sigString, 'utf8').toArrayBuffer();
} }

View file

@ -218,6 +218,10 @@ class LokiHomeServerInstance extends LokiFileServerInstance {
} }
async updateOurDeviceMapping() { async updateOurDeviceMapping() {
if (!window.lokiFeatureFlags.useMultiDevice) {
return undefined;
}
const isPrimary = !storage.get('isSecondaryDevice'); const isPrimary = !storage.get('isSecondaryDevice');
const authorisations = await window.libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations( const authorisations = await window.libsession.Protocols.MultiDeviceProtocol.getPairingAuthorisations(
this.ourKey this.ourKey

View file

@ -238,8 +238,8 @@
let message = i18n('deleteContactConfirmation'); let message = i18n('deleteContactConfirmation');
if (groupConvo.isPublic()) { if (groupConvo.isPublic()) {
title = i18n('deletePublicChannel'); title = i18n('leaveOpenGroup');
message = i18n('deletePublicChannelConfirmation'); message = i18n('leaveOpenGroupConfirmation');
} else if (groupConvo.isClosedGroup()) { } else if (groupConvo.isClosedGroup()) {
title = i18n('leaveClosedGroup'); title = i18n('leaveClosedGroup');
message = i18n('leaveClosedGroupConfirmation'); message = i18n('leaveClosedGroupConfirmation');

View file

@ -763,9 +763,8 @@ app.on('ready', async () => {
logger = logging.getLogger(); logger = logging.getLogger();
logger.info('app ready'); logger.info('app ready');
logger.info(`starting version ${packageJson.version}`); logger.info(`starting version ${packageJson.version}`);
if (!locale) { if (!locale) {
const appLocale = process.env.NODE_ENV === 'test' ? 'en' : 'en'; // app.getLocale(); // FIXME reenable once we have translated our files const appLocale = process.env.NODE_ENV === 'test' ? 'en' : app.getLocale();
locale = loadLocale({ appLocale, logger }); locale = loadLocale({ appLocale, logger });
} }

View file

@ -2,7 +2,7 @@
"name": "session-messenger-desktop", "name": "session-messenger-desktop",
"productName": "Session", "productName": "Session",
"description": "Private messaging from your desktop", "description": "Private messaging from your desktop",
"version": "1.2.0", "version": "1.2.1",
"license": "GPL-3.0", "license": "GPL-3.0",
"author": { "author": {
"name": "Loki Project", "name": "Loki Project",

View file

@ -456,6 +456,7 @@ window.lokiFeatureFlags = {
enableSenderKeys: false, enableSenderKeys: false,
onionRequestHops: 3, onionRequestHops: 3,
debugMessageLogs: process.env.ENABLE_MESSAGE_LOGS, debugMessageLogs: process.env.ENABLE_MESSAGE_LOGS,
useMultiDevice: false,
}; };
// eslint-disable-next-line no-extend-native,func-names // eslint-disable-next-line no-extend-native,func-names
@ -492,6 +493,7 @@ if (config.environment.includes('test-integration')) {
useFileOnionRequests: false, useFileOnionRequests: false,
debugMessageLogs: true, debugMessageLogs: true,
enableSenderKeys: true, enableSenderKeys: true,
useMultiDevice: false,
}; };
/* eslint-disable global-require, import/no-extraneous-dependencies */ /* eslint-disable global-require, import/no-extraneous-dependencies */
window.sinon = require('sinon'); window.sinon = require('sinon');
@ -505,3 +507,26 @@ const {
} = require('./ts/util/blockedNumberController'); } = require('./ts/util/blockedNumberController');
window.BlockedNumberController = 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();
};

View file

@ -209,7 +209,7 @@ textarea {
border-radius: 2px; border-radius: 2px;
height: 33px; height: 33px;
padding: 0px 18px; padding: 0px 18px;
line-height: 33px; // line-height: 33px;
font-size: $session-font-sm; font-size: $session-font-sm;
} }

View file

@ -273,10 +273,6 @@ $session-compose-margin: 20px;
} }
} }
h4 {
text-transform: uppercase;
}
.white-border { .white-border {
width: $session-left-pane-width; width: $session-left-pane-width;
position: relative; position: relative;
@ -339,6 +335,12 @@ $session-compose-margin: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-grow: 1; flex-grow: 1;
.left-pane-contact-bottom-buttons .session-button {
vertical-align: middle;
white-space: normal;
text-align: center;
}
} }
.session-left-pane-section-content { .session-left-pane-section-content {
@ -382,6 +384,7 @@ $session-compose-margin: 20px;
flex-grow: 1; flex-grow: 1;
font-size: $session-font-sm; font-size: $session-font-sm;
font-family: $session-font-default; font-family: $session-font-default;
text-overflow: ellipsis;
&:focus { &:focus {
outline: none !important; outline: none !important;
@ -424,7 +427,7 @@ $session-compose-margin: 20px;
.session-button.square-outline.square.danger { .session-button.square-outline.square.danger {
flex-grow: 1; flex-grow: 1;
height: 50px; height: 50px;
line-height: 50px; // line-height: 50px;
@at-root .light-theme #{&} { @at-root .light-theme #{&} {
border: 1px solid $session-shade-15; border: 1px solid $session-shade-15;
@ -609,7 +612,7 @@ $session-compose-margin: 20px;
flex-grow: 1; flex-grow: 1;
border: 1px solid $session-shade-8; border: 1px solid $session-shade-8;
height: 50px; height: 50px;
line-height: 50px; // line-height: 50px;
} }
} }
} }

View file

@ -122,7 +122,7 @@ export class EditProfileDialog extends React.Component<Props, State> {
{viewDefault || viewQR ? ( {viewDefault || viewQR ? (
<SessionButton <SessionButton
text={window.i18n('copy')} text={window.i18n('editMenuCopy')}
buttonType={SessionButtonType.BrandOutline} buttonType={SessionButtonType.BrandOutline}
buttonColor={SessionButtonColor.Green} buttonColor={SessionButtonColor.Green}
onClick={() => { onClick={() => {

View file

@ -2,12 +2,7 @@ import React from 'react';
import { Avatar } from '../Avatar'; import { Avatar } from '../Avatar';
import { Colors, LocalizerType } from '../../types/Util'; import { Colors, LocalizerType } from '../../types/Util';
import { import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu';
ContextMenu,
ContextMenuTrigger,
MenuItem,
SubMenu,
} from 'react-contextmenu';
import { import {
SessionIconButton, SessionIconButton,

View file

@ -65,13 +65,13 @@ export class MediaGallery extends React.Component<Props, State> {
<div className="module-media-gallery"> <div className="module-media-gallery">
<div className="module-media-gallery__tab-container"> <div className="module-media-gallery__tab-container">
<Tab <Tab
label="Media" label={window.i18n('media')}
type="media" type="media"
isSelected={selectedTab === 'media'} isSelected={selectedTab === 'media'}
onSelect={this.handleTabSelect} onSelect={this.handleTabSelect}
/> />
<Tab <Tab
label="Documents" label={window.i18n('documents')}
type="documents" type="documents"
isSelected={selectedTab === 'documents'} isSelected={selectedTab === 'documents'}
onSelect={this.handleTabSelect} onSelect={this.handleTabSelect}

View file

@ -59,48 +59,6 @@ export class ActionsPanel extends React.Component<Props, State> {
}, },
'refreshAvatarCallback' '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);
} }
); );
} }

View file

@ -219,13 +219,13 @@ export class LeftPaneSettingSection extends React.Component<Props, State> {
}, },
{ {
id: SessionSettingCategory.Notifications, id: SessionSettingCategory.Notifications,
title: window.i18n('notificationSettingsTitle'), title: window.i18n('notificationsSettingsTitle'),
hidden: false, hidden: false,
}, },
{ {
id: SessionSettingCategory.Devices, id: SessionSettingCategory.Devices,
title: window.i18n('devicesSettingsTitle'), title: window.i18n('devicesSettingsTitle'),
hidden: isSecondaryDevice, hidden: !window.lokiFeatureFlags.useMultiDevice || isSecondaryDevice,
}, },
]; ];
} }

View file

@ -557,9 +557,12 @@ export class RegistrationTabs extends React.Component<{}, State> {
SessionButtonType.BrandOutline, SessionButtonType.BrandOutline,
SessionButtonColor.Green SessionButtonColor.Green
)} )}
{/*<h4>{or}</h4>*/} {window.lokiFeatureFlags.useMultiDevice && (
{/* FIXME enable back to allow linking of device <>
this.renderLinkDeviceToExistingAccountButton() */} <h4>{or}</h4>
{this.renderLinkDeviceToExistingAccountButton()}
</>
)}
</div> </div>
); );
} }
@ -584,9 +587,12 @@ export class RegistrationTabs extends React.Component<{}, State> {
return ( return (
<div> <div>
{this.renderContinueYourSessionButton()} {this.renderContinueYourSessionButton()}
{/*<h4>{or}</h4>*/} {window.lokiFeatureFlags.useMultiDevice && (
{/* FIXME enable back to allow linking of device <>
this.renderLinkDeviceToExistingAccountButton()*/} <h4>{or}</h4>
{this.renderLinkDeviceToExistingAccountButton()}
</>
)}
</div> </div>
); );
} }

View file

@ -134,20 +134,20 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
buttonText = window.i18n('next'); buttonText = window.i18n('next');
descriptionLong = window.i18n('usersCanShareTheir...'); descriptionLong = window.i18n('usersCanShareTheir...');
subtitle = window.i18n('enterSessionID'); subtitle = window.i18n('enterSessionID');
placeholder = window.i18n('pasteSessionIDRecipient'); placeholder = window.i18n('enterSessionID');
break; break;
case 'contact': case 'contact':
title = window.i18n('addContact'); title = window.i18n('addContact');
buttonText = window.i18n('next'); buttonText = window.i18n('next');
descriptionLong = window.i18n('usersCanShareTheir...'); descriptionLong = window.i18n('usersCanShareTheir...');
subtitle = window.i18n('enterSessionID'); subtitle = window.i18n('enterSessionID');
placeholder = window.i18n('pasteSessionIDRecipient'); placeholder = window.i18n('enterSessionID');
break; break;
case 'open-group': case 'open-group':
title = window.i18n('addChannel'); title = window.i18n('joinOpenGroup');
buttonText = window.i18n('joinChannel'); buttonText = window.i18n('joinOpenGroup');
descriptionLong = window.i18n('addChannelDescription'); descriptionLong = window.i18n('addChannelDescription');
subtitle = window.i18n('enterChannelURL'); subtitle = window.i18n('enterOpenGroupURL');
placeholder = window.i18n('channelUrlPlaceholder'); placeholder = window.i18n('channelUrlPlaceholder');
break; break;
case 'closed-group': case 'closed-group':
@ -239,13 +239,13 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
<UserSearchDropdown <UserSearchDropdown
searchTerm={searchTerm || ''} searchTerm={searchTerm || ''}
updateSearch={updateSearch} updateSearch={updateSearch}
placeholder={window.i18n('searchByIDOrDisplayName')} placeholder={window.i18n('searchForAKeyPhrase')}
searchResults={searchResults} searchResults={searchResults}
/> />
)} )}
{isAddContactView && ( {isAddContactView && (
<PillDivider text={window.i18n('yourPublicKey')} /> <PillDivider text={window.i18n('yourSessionID')} />
)} )}
{isAddContactView && ( {isAddContactView && (

View file

@ -238,6 +238,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
showLinkDeviceButton={!shouldRenderPasswordLock} showLinkDeviceButton={!shouldRenderPasswordLock}
category={category} category={category}
isSecondaryDevice={isSecondaryDevice} isSecondaryDevice={isSecondaryDevice}
categoryTitle={window.i18n(`${category}SettingsTitle`)}
/> />
<div className="session-settings-view"> <div className="session-settings-view">
@ -646,7 +647,7 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
id: 'no-linked-device', id: 'no-linked-device',
title: noPairedDeviceText, title: noPairedDeviceText,
type: undefined, type: undefined,
description: window.i18n('multiDeviceDisabledTemporary'), description: '',
category: SessionSettingCategory.Devices, category: SessionSettingCategory.Devices,
content: {}, content: {},
comparisonValue: undefined, comparisonValue: undefined,

View file

@ -12,6 +12,7 @@ interface Props extends SettingsViewProps {
showLinkDeviceButton: boolean | null; showLinkDeviceButton: boolean | null;
// isSecondaryDevice is used to just disable the linkDeviceButton when we are already a secondary device // isSecondaryDevice is used to just disable the linkDeviceButton when we are already a secondary device
isSecondaryDevice: boolean; isSecondaryDevice: boolean;
categoryTitle: string;
} }
export class SettingsHeader extends React.Component<Props, any> { export class SettingsHeader extends React.Component<Props, any> {
@ -65,23 +66,12 @@ export class SettingsHeader extends React.Component<Props, any> {
} }
public render() { public render() {
const { category } = this.props; const { category, categoryTitle } = this.props;
const { disableLinkDeviceButton } = this.state; const { disableLinkDeviceButton } = this.state;
const categoryString = String(category);
const categoryTitlePrefix =
categoryString[0].toUpperCase() + categoryString.substr(1);
// Remove 's' on the end to keep words in singular form
const categoryTitle =
categoryTitlePrefix[categoryTitlePrefix.length - 1] === 's'
? `${categoryTitlePrefix.slice(0, -1)} Settings`
: `${categoryTitlePrefix} Settings`;
const showSearch = false; const showSearch = false;
const showAddDevice = false; const showAddDevice =
/* FIXME enable back to allow linking of device
const showAddDevice =
category === SessionSettingCategory.Devices && category === SessionSettingCategory.Devices &&
this.props.showLinkDeviceButton; this.props.showLinkDeviceButton;
*/
return ( return (
<div className="session-settings-header"> <div className="session-settings-header">

View file

@ -87,6 +87,14 @@ export async function handlePairingAuthorisationMessage(
pairingAuthorisation: SignalService.IPairingAuthorisationMessage, pairingAuthorisation: SignalService.IPairingAuthorisationMessage,
dataMessage: SignalService.IDataMessage | undefined | null dataMessage: SignalService.IDataMessage | undefined | null
): Promise<void> { ): Promise<void> {
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 { secondaryDevicePubKey, grantSignature } = pairingAuthorisation;
const isGrant = const isGrant =
grantSignature && grantSignature &&

View file

@ -28,6 +28,11 @@ export class MultiDeviceProtocol {
public static async fetchPairingAuthorisationsIfNeeded( public static async fetchPairingAuthorisationsIfNeeded(
device: PubKey device: PubKey
): Promise<void> { ): Promise<void> {
// 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 // This return here stops an infinite loop when we get all our other devices
const ourKey = await UserUtil.getCurrentDevicePubKey(); const ourKey = await UserUtil.getCurrentDevicePubKey();
if (!ourKey || device.key === ourKey) { if (!ourKey || device.key === ourKey) {

View file

@ -141,9 +141,7 @@ export function getDeleteContactMenuItem(
): JSX.Element | null { ): JSX.Element | null {
if (showDeleteContact(isMe, isClosable, isGroup, isPublic, isRss)) { if (showDeleteContact(isMe, isClosable, isGroup, isPublic, isRss)) {
if (isPublic) { if (isPublic) {
return ( return <MenuItem onClick={action}>{i18n('leaveOpenGroup')}</MenuItem>;
<MenuItem onClick={action}>{i18n('deletePublicChannel')}</MenuItem>
);
} }
return <MenuItem onClick={action}>{i18n('deleteContact')}</MenuItem>; return <MenuItem onClick={action}>{i18n('deleteContact')}</MenuItem>;
} }
@ -159,7 +157,7 @@ export function getLeaveGroupMenuItem(
i18n: LocalizerType i18n: LocalizerType
): JSX.Element | null { ): JSX.Element | null {
if (showLeaveGroup(isKickedFromGroup, isGroup, isPublic, isRss)) { if (showLeaveGroup(isKickedFromGroup, isGroup, isPublic, isRss)) {
return <MenuItem onClick={action}>{i18n('leaveGroup')}</MenuItem>; return <MenuItem onClick={action}>{i18n('leaveClosedGroup')}</MenuItem>;
} }
return null; return null;
} }

View file

@ -25,6 +25,13 @@ function generateFakeAuthorisations(
describe('MultiDeviceProtocol', () => { describe('MultiDeviceProtocol', () => {
const sandbox = sinon.createSandbox(); const sandbox = sinon.createSandbox();
beforeEach(() => {
// Enable multidevice for tests
TestUtils.stubWindow('lokiFeatureFlags', {
useMultiDevice: true,
});
});
afterEach(() => { afterEach(() => {
TestUtils.restoreStubs(); TestUtils.restoreStubs();
sandbox.restore(); sandbox.restore();

12
ts/window.d.ts vendored
View file

@ -51,7 +51,17 @@ declare global {
libloki: Libloki; libloki: Libloki;
libsignal: LibsignalProtocol; libsignal: LibsignalProtocol;
log: any; 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; lokiFileServerAPI: LokiFileServerInstance;
lokiMessageAPI: LokiMessageInterface; lokiMessageAPI: LokiMessageInterface;
lokiPublicChatAPI: LokiPublicChatFactoryInterface; lokiPublicChatAPI: LokiPublicChatFactoryInterface;