Merge pull request #1137 from Bilb/integration-test-logs-rebased

Integration test logs
pull/1158/head
Mikunj Varsani 5 years ago committed by GitHub
commit 35ee455bbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -30,3 +30,4 @@ ts/**/*.js
# Libloki specific files
libloki/test/components.js
libloki/modules/mnemonic.js
session-file-server/**

@ -24,6 +24,15 @@ jobs:
- name: Checkout git repo
uses: actions/checkout@v2
- name: Pull git submodules
run: git submodule update --init
- name: Install file server dependency
run: |
cd session-file-server
yarn install;
cd -
- name: Install node
uses: actions/setup-node@v1
with:

3
.gitignore vendored

@ -37,3 +37,6 @@ ts/protobuf/*.d.ts
# Ctags
tags
proxy.key
proxy.pub

3
.gitmodules vendored

@ -0,0 +1,3 @@
[submodule "session-file-server"]
path = session-file-server
url = https://github.com/loki-project/session-file-server/

@ -54,3 +54,4 @@ stylesheets/_intlTelInput.scss
# Coverage
coverage/**
.nyc_output/**
session-file-server/**

@ -94,9 +94,9 @@ There are a few scripts which you can use:
```
yarn start - Start development
yarn start-multi - Start second instance of development
MULTI=1 yarn start - Start second instance of development
yarn start-prod - Start production but in development mode
yarn start-prod-multi - Start another instance of production
MULTI=1 yarn start-prod - Start another instance of production
```
For more than 2 clients, you may run the above command with `NODE_APP_INSTANCE` set before them.

@ -1124,11 +1124,6 @@
"message": " Type your message",
"description": "Placeholder text in the message entry field"
},
"secondaryDeviceDefaultFR": {
"message": "Please accept to enable messages to be synced across devices",
"description":
"Placeholder text in the message entry field when it is disabled because a secondary device conversation is visible"
},
"sendMessageDisabledSecondary": {
"message":
"This pubkey belongs to a secondary device. You should never see this message",

@ -1540,6 +1540,8 @@ async function getGrantAuthorisationsForPrimaryPubKey(primaryDevicePubKey) {
async function createOrUpdatePairingAuthorisation(data) {
const { primaryDevicePubKey, secondaryDevicePubKey, grantSignature } = data;
// remove any existing authorisation for this pubkey (we allow only one secondary device for now)
await removePairingAuthorisationForPrimaryPubKey(primaryDevicePubKey);
await db.run(
`INSERT OR REPLACE INTO ${PAIRING_AUTHORISATIONS_TABLE} (
@ -1562,6 +1564,15 @@ async function createOrUpdatePairingAuthorisation(data) {
);
}
async function removePairingAuthorisationForPrimaryPubKey(pubKey) {
await db.run(
`DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE primaryDevicePubKey = $primaryDevicePubKey;`,
{
$primaryDevicePubKey: pubKey,
}
);
}
async function removePairingAuthorisationForSecondaryPubKey(pubKey) {
await db.run(
`DELETE FROM ${PAIRING_AUTHORISATIONS_TABLE} WHERE secondaryDevicePubKey = $secondaryDevicePubKey;`,

@ -18,18 +18,16 @@ describe('Add friends', function() {
const app1Props = {
mnemonic: common.TEST_MNEMONIC1,
displayName: common.TEST_DISPLAY_NAME1,
stubSnode: true,
};
const app2Props = {
mnemonic: common.TEST_MNEMONIC2,
displayName: common.TEST_DISPLAY_NAME2,
stubSnode: true,
};
[app, app2] = await Promise.all([
common.startAndStub(app1Props),
common.startAndStub2(app2Props),
common.startAndStubN(app2Props, 2),
]);
});
@ -116,5 +114,31 @@ describe('Add friends', function() {
ConversationPage.acceptedFriendRequestMessage,
5000
);
// app trigger the friend request logic first
const aliceLogs = await app.client.getRenderProcessLogs();
const bobLogs = await app2.client.getRenderProcessLogs();
await common.logsContains(
aliceLogs,
`Sending undefined:friend-request message to ${common.TEST_PUBKEY2}`
);
await common.logsContains(
bobLogs,
`Received a NORMAL_FRIEND_REQUEST from source: ${
common.TEST_PUBKEY1
}, primarySource: ${common.TEST_PUBKEY1},`
);
await common.logsContains(
bobLogs,
`Sending incoming-friend-request-accept:onlineBroadcast message to ${
common.TEST_PUBKEY1
}`
);
await common.logsContains(
aliceLogs,
`Sending outgoing-friend-request-accepted:onlineBroadcast message to ${
common.TEST_PUBKEY2
}`
);
});
});

@ -25,9 +25,6 @@ describe('Closed groups', function() {
});
it('closedGroup: can create a closed group with a friend and send/receive a message', async () => {
await app.client.element(ConversationPage.globeButtonSection).click();
await app.client.element(ConversationPage.createClosedGroupButton).click();
const useSenderKeys = false;
// create group and add new friend

@ -36,19 +36,19 @@ module.exports = {
'faxed mechanic mocked agony unrest loincloth pencil eccentric boyfriend oasis speedy ribbon faxed',
TEST_PUBKEY1:
'0552b85a43fb992f6bdb122a5a379505a0b99a16f0628ab8840249e2a60e12a413',
TEST_DISPLAY_NAME1: 'integration_tester_1',
TEST_DISPLAY_NAME1: 'tester_Alice',
TEST_MNEMONIC2:
'guide inbound jerseys bays nouns basin sulking awkward stockpile ostrich ascend pylons ascend',
TEST_PUBKEY2:
'054e1ca8681082dbd9aad1cf6fc89a32254e15cba50c75b5a73ac10a0b96bcbd2a',
TEST_DISPLAY_NAME2: 'integration_tester_2',
TEST_DISPLAY_NAME2: 'tester_Bob',
TEST_MNEMONIC3:
'alpine lukewarm oncoming blender kiwi fuel lobster upkeep vogue simplest gasp fully simplest',
TEST_PUBKEY3:
'05f8662b6e83da5a31007cc3ded44c601f191e07999acb6db2314a896048d9036c',
TEST_DISPLAY_NAME3: 'integration_tester_3',
TEST_DISPLAY_NAME3: 'tester_Charlie',
/* ************** OPEN GROUPS ****************** */
VALID_GROUP_URL: 'https://chat.getsession.org',
@ -191,20 +191,11 @@ module.exports = {
async startAndStub({
mnemonic,
displayName,
stubSnode = false,
stubOpenGroups = false,
env = 'test-integration-session',
}) {
const app = await this.startAndAssureCleanedApp(env);
if (stubSnode) {
await this.startStubSnodeServer();
this.stubSnodeCalls(app);
}
if (stubOpenGroups) {
this.stubOpenGroupsCalls(app);
}
await this.startStubSnodeServer();
if (mnemonic && displayName) {
await this.restoreFromMnemonic(app, mnemonic, displayName);
@ -333,6 +324,11 @@ module.exports = {
async addFriendToNewClosedGroup(members, useSenderKeys) {
const [app, ...others] = members;
await app.client
.element(ConversationPage.conversationButtonSection)
.click();
await app.client.element(ConversationPage.createClosedGroupButton).click();
await this.setValueWrapper(
app,
ConversationPage.closedGroupNameTextarea,
@ -427,7 +423,7 @@ module.exports = {
);
},
async linkApp2ToApp(app1, app2) {
async linkApp2ToApp(app1, app2, app1Pubkey) {
// app needs to be logged in as user1 and app2 needs to be logged out
// start the pairing dialog for the first app
await app1.client.element(SettingsPage.settingsButtonSection).click();
@ -452,20 +448,19 @@ module.exports = {
await this.setValueWrapper(
app2,
RegistrationPage.textareaLinkDevicePubkey,
this.TEST_PUBKEY1
app1Pubkey
);
await app2.client.element(RegistrationPage.linkDeviceTriggerButton).click();
await app1.client.waitForExist(RegistrationPage.toastWrapper, 7000);
let secretWordsapp1 = await app1.client
.element(RegistrationPage.secretToastDescription)
await app1.client.waitForExist(SettingsPage.secretWordsTextInDialog, 7000);
const secretWordsapp1 = await app1.client
.element(SettingsPage.secretWordsTextInDialog)
.getText();
secretWordsapp1 = secretWordsapp1.split(': ')[1];
await app2.client.waitForExist(RegistrationPage.toastWrapper, 6000);
await app2.client
.element(RegistrationPage.secretToastDescription)
.getText()
.should.eventually.be.equal(secretWordsapp1);
await app1.client.element(ConversationPage.allowPairingButton).click();
await app1.client.element(ConversationPage.okButton).click();
// validate device paired in settings list with correct secrets
@ -488,7 +483,7 @@ module.exports = {
// validate primary pubkey of app2 is the same that in app1
await app2.webContents
.executeJavaScript("window.storage.get('primaryDevicePubKey')")
.should.eventually.be.equal(this.TEST_PUBKEY1);
.should.eventually.be.equal(app1Pubkey);
},
async triggerUnlinkApp2FromApp(app1, app2) {
@ -496,9 +491,9 @@ module.exports = {
await app2.client.isExisting(RegistrationPage.conversationListContainer)
.should.eventually.be.true;
await app1.client.element(ConversationPage.settingsButtonSection).click();
await app1.client.element(SettingsPage.settingsButtonSection).click();
await app1.client
.element(ConversationPage.settingsRowWithText('Devices'))
.element(SettingsPage.settingsRowWithText('Devices'))
.click();
await app1.client.isExisting(ConversationPage.linkDeviceButtonDisabled)
.should.eventually.be.true;
@ -508,7 +503,7 @@ module.exports = {
await app1.client.waitForExist(
ConversationPage.noPairedDeviceMessage,
2000
5000
);
await app1.client.element(ConversationPage.linkDeviceButton).isEnabled()
.should.eventually.be.true;
@ -563,27 +558,6 @@ module.exports = {
generateSendMessageText: () =>
`Test message from integration tests ${Date.now()}`,
stubOpenGroupsCalls: app1 => {
app1.webContents.executeJavaScript(
'window.LokiAppDotNetServerAPI = window.StubAppDotNetAPI;'
);
},
stubSnodeCalls(app1) {
app1.webContents.executeJavaScript(
'window.LokiMessageAPI = window.StubMessageAPI;'
);
app1.webContents.executeJavaScript(
'window.LokiSnodeAPI = window.StubLokiSnodeAPI;'
);
},
logsContainsString: async (app1, str) => {
const logs = JSON.stringify(await app1.client.getRenderProcessLogs());
return logs.includes(str);
},
async startStubSnodeServer() {
if (!this.stubSnode) {
this.messages = {};
@ -595,6 +569,7 @@ module.exports = {
console.warn('NO PUBKEY');
response.writeHead(400, { 'Content-Type': 'text/html' });
response.end();
return;
}
if (request.method === 'POST') {
@ -631,12 +606,57 @@ module.exports = {
response.end();
}
});
this.startLocalFileServer();
this.stubSnode.listen(STUB_SNODE_SERVER_PORT);
} else {
this.messages = {};
}
},
async startLocalFileServer() {
if (!this.fileServer) {
// be sure to run `git submodule update --init && cd session-file-server && yarn install; cd -`
// eslint-disable-next-line global-require
this.fileServer = require('../session-file-server/app');
}
},
async joinOpenGroup(app, openGroupUrl, name) {
await app.client
.element(ConversationPage.conversationButtonSection)
.click();
await app.client.element(ConversationPage.joinOpenGroupButton).click();
await this.setValueWrapper(
app,
ConversationPage.openGroupInputUrl,
openGroupUrl
);
await app.client
.element(ConversationPage.openGroupInputUrl)
.getValue()
.should.eventually.equal(openGroupUrl);
await app.client.element(ConversationPage.joinOpenGroupButton).click();
// validate session loader is shown
await app.client.isExisting(ConversationPage.sessionLoader).should
.eventually.be.true;
// account for slow home internet connection delays...
await app.client.waitForExist(
ConversationPage.sessionToastJoinOpenGroupSuccess,
60 * 1000
);
// validate overlay is closed
await app.client.isExisting(ConversationPage.leftPaneOverlay).should
.eventually.be.false;
// validate open chat has been added
await app.client.isExisting(
ConversationPage.rowOpenGroupConversationName(name)
).should.eventually.be.true;
},
async stopStubSnodeServer() {
if (this.stubSnode) {
this.stubSnode.close();
@ -644,6 +664,32 @@ module.exports = {
}
},
/**
* Search for a string in logs
* @param {*} app the render logs to search in
* @param {*} str the string to search (not regex)
* Note: getRenderProcessLogs() clears the app logs each calls.
*/
async logsContains(renderLogs, str, count = undefined) {
const foundLines = renderLogs.filter(log => log.message.includes(str));
// eslint-disable-next-line no-unused-expressions
chai.expect(
foundLines.length > 0,
`'${str}' not found in logs but was expected`
).to.be.true;
if (count) {
// eslint-disable-next-line no-unused-expressions
chai
.expect(
foundLines.length,
`'${str}' found but not the correct number of times`
)
.to.be.equal(count);
}
},
// async killStubSnodeServer() {
// return new Promise(resolve => {
// exec(

@ -13,6 +13,7 @@ require('./link_device_test');
require('./closed_group_test');
require('./message_functions_test');
require('./settings_test');
require('./message_sync_test');
require('./sender_keys_test');
before(async () => {

@ -18,12 +18,9 @@ describe('Link Device', function() {
const app1Props = {
mnemonic: common.TEST_MNEMONIC1,
displayName: common.TEST_DISPLAY_NAME1,
stubSnode: true,
};
const app2Props = {
stubSnode: true,
};
const app2Props = {};
[app, app2] = await Promise.all([
common.startAndStub(app1Props),
@ -37,12 +34,39 @@ describe('Link Device', function() {
});
it('linkDevice: link two desktop devices', async () => {
await common.linkApp2ToApp(app, app2);
await common.linkApp2ToApp(app, app2, common.TEST_PUBKEY1);
});
it('linkDevice: unlink two devices', async () => {
await common.linkApp2ToApp(app, app2);
await common.linkApp2ToApp(app, app2, common.TEST_PUBKEY1);
await common.timeout(1000);
await common.triggerUnlinkApp2FromApp(app, app2);
});
it('linkDevice:sync no groups, closed group, nor open groups', async () => {
await common.linkApp2ToApp(app, app2, common.TEST_PUBKEY1);
await common.timeout(10000);
// get logs at this stage (getRenderProcessLogs() clears the app logs)
const secondaryRenderLogs = await app2.client.getRenderProcessLogs();
// pairing request message sent from secondary to primary pubkey
await common.logsContains(
secondaryRenderLogs,
`Sending pairing-request:pairing-request message to ${
common.TEST_PUBKEY1
}`
);
const primaryRenderLogs = await app.client.getRenderProcessLogs();
// primary grant pairing request
await common.logsContains(
primaryRenderLogs,
'Sending pairing-request:pairing-request message to OUR SECONDARY PUBKEY'
);
// no friends, no closed groups, no open groups. we should see those message sync in the log
await common.logsContains(primaryRenderLogs, 'No closed group to sync.', 1);
await common.logsContains(primaryRenderLogs, 'No open groups to sync', 1);
await common.logsContains(primaryRenderLogs, 'No contacts to sync.', 1);
});
});

@ -4,7 +4,7 @@
/* eslint-disable import/no-extraneous-dependencies */
const path = require('path');
const { after, before, describe, it } = require('mocha');
const { afterEach, beforeEach, describe, it } = require('mocha');
const common = require('./common');
const ConversationPage = require('./page-objects/conversation.page');
@ -14,25 +14,22 @@ describe('Message Functions', function() {
this.timeout(60000);
this.slow(15000);
before(async () => {
beforeEach(async () => {
await common.killallElectron();
await common.stopStubSnodeServer();
[app, app2] = await common.startAppsAsFriends();
});
after(async () => {
afterEach(async () => {
await common.stopApp(app);
await common.killallElectron();
await common.stopStubSnodeServer();
});
it('can send attachment', async () => {
await app.client.element(ConversationPage.globeButtonSection).click();
await app.client.element(ConversationPage.createClosedGroupButton).click();
// create group and add new friend
await common.addFriendToNewClosedGroup([app, app2]);
await common.addFriendToNewClosedGroup([app, app2], false);
// send attachment from app1 to closed group
const fileLocation = path.join(__dirname, 'test_attachment');
@ -53,6 +50,8 @@ describe('Message Functions', function() {
});
it('can delete message', async () => {
// create group and add new friend
await common.addFriendToNewClosedGroup([app, app2], false);
const messageText = 'delete_me';
await common.sendMessage(app, messageText);
@ -71,7 +70,7 @@ describe('Message Functions', function() {
.click();
await app.client.element(ConversationPage.deleteMessageCtxButton).click();
// delete messaage from modal
// delete message from modal
await app.client.waitForExist(
ConversationPage.deleteMessageModalButton,
5000

@ -1,49 +1,146 @@
/* eslint-disable func-names */
/* eslint-disable import/no-extraneous-dependencies */
const { afterEach, beforeEach, describe, it } = require('mocha');
const { after, before, describe, it } = require('mocha');
const common = require('./common');
describe('Message Syncing', function() {
let app;
let app2;
let Alice1;
let Bob1;
let Alice2;
this.timeout(60000);
this.slow(15000);
beforeEach(async () => {
// this test suite builds a complex usecase over several tests,
// so you need to run all of those tests together (running only one might fail)
before(async () => {
await common.killallElectron();
await common.stopStubSnodeServer();
const app1Props = {
mnemonic: common.TEST_MNEMONIC1,
displayName: common.TEST_DISPLAY_NAME1,
stubSnode: true,
};
const app2Props = {
mnemonic: common.TEST_MNEMONIC2,
displayName: common.TEST_DISPLAY_NAME2,
stubSnode: true,
};
[app, app2] = await Promise.all([
common.startAndStub(app1Props),
common.startAndStubN(app2Props, 2),
]);
const alice2Props = {};
[Alice1, Bob1] = await common.startAppsAsFriends(); // Alice and Bob are friends
await common.addFriendToNewClosedGroup([Alice1, Bob1], false);
await common.joinOpenGroup(
Alice1,
common.VALID_GROUP_URL,
common.VALID_GROUP_NAME
);
Alice2 = await common.startAndStubN(alice2Props, 4); // Alice secondary, just start the app for now. no linking
});
afterEach(async () => {
after(async () => {
await common.killallElectron();
await common.stopStubSnodeServer();
});
it('message syncing between linked devices', async () => {
await common.linkApp2ToApp(app, app2);
});
it('message syncing with 1 friend, 1 closed group, 1 open group', async () => {
// Alice1 has:
// * no linked device
// * Bob is a friend
// * one open group
// * one closed group with Bob inside
// Bob1 has:
// * no linked device
// * Alice as a friend
// * one open group with Alice
// Linking Alice2 to Alice1
// alice2 should trigger auto FR with bob1 as it's one of her friend
// and alice2 should trigger a SESSION_REQUEST with bob1 as he is in a closed group with her
await common.linkApp2ToApp(Alice1, Alice2, common.TEST_PUBKEY1);
await common.timeout(25000);
// validate pubkey of app2 is the set
const alice2Pubkey = await Alice2.webContents.executeJavaScript(
'window.textsecure.storage.user.getNumber()'
);
alice2Pubkey.should.have.lengthOf(66);
const alice1Logs = await Alice1.client.getRenderProcessLogs();
const bob1Logs = await Bob1.client.getRenderProcessLogs();
const alice2Logs = await Alice2.client.getRenderProcessLogs();
// validate primary alice
await common.logsContains(
alice1Logs,
'Sending closed-group-sync-send:outgoing message to OUR SECONDARY PUBKEY',
1
);
await common.logsContains(
alice1Logs,
'Sending open-group-sync-send:outgoing message to OUR SECONDARY PUBKEY',
1
);
await common.logsContains(
alice1Logs,
'Sending contact-sync-send:outgoing message to OUR SECONDARY PUBKEY',
1
);
// validate secondary alice
// what is expected is
// alice2 receives group sync, contact sync and open group sync
// alice2 triggers session request with closed group members and autoFR with contact sync received
// once autoFR is auto-accepted, alice2 trigger contact sync
await common.logsContains(
alice2Logs,
'Got sync group message with group id',
1
);
await common.logsContains(
alice2Logs,
'Received GROUP_SYNC with open groups: [chat.getsession.org]',
1
);
await common.logsContains(
alice2Logs,
`Sending auto-friend-request:friend-request message to ${
common.TEST_PUBKEY2
}`,
1
);
await common.logsContains(
alice2Logs,
`Sending session-request:friend-request message to ${
common.TEST_PUBKEY2
}`,
1
);
await common.logsContains(
alice2Logs,
`Sending contact-sync-send:outgoing message to OUR_PRIMARY_PUBKEY`,
1
);
it('unlink two devices', async () => {
await common.linkApp2ToApp(app, app2);
await common.timeout(1000);
await common.triggerUnlinkApp2FromApp(app, app2);
// validate primary bob
// what is expected is
// bob1 receives session request from alice2
// bob1 accept auto fr by sending a bg message
// once autoFR is auto-accepted, alice2 trigger contact sync
await common.logsContains(
bob1Logs,
`Received SESSION_REQUEST from source: ${alice2Pubkey}`,
1
);
await common.logsContains(
bob1Logs,
`Received AUTO_FRIEND_REQUEST from source: ${alice2Pubkey}`,
1
);
await common.logsContains(
bob1Logs,
`Sending auto-friend-accept:onlineBroadcast message to ${alice2Pubkey}`,
1
);
// be sure only one autoFR accept was sent (even if multi device, we need to reply to that specific device only)
await common.logsContains(
bob1Logs,
`Sending auto-friend-accept:onlineBroadcast message to`,
1
);
});
});

@ -7,7 +7,7 @@ const ConversationPage = require('./page-objects/conversation.page');
describe('Open groups', function() {
let app;
this.timeout(30000);
this.timeout(40000);
this.slow(15000);
beforeEach(async () => {
@ -15,7 +15,6 @@ describe('Open groups', function() {
const login = {
mnemonic: common.TEST_MNEMONIC1,
displayName: common.TEST_DISPLAY_NAME1,
stubOpenGroups: true,
};
app = await common.startAndStub(login);
});
@ -24,46 +23,25 @@ describe('Open groups', function() {
await common.killallElectron();
});
// reduce code duplication to get the initial join
async function joinOpenGroup(url, name) {
await app.client.element(ConversationPage.globeButtonSection).click();
await app.client.element(ConversationPage.joinOpenGroupButton).click();
await common.setValueWrapper(app, ConversationPage.openGroupInputUrl, url);
await app.client
.element(ConversationPage.openGroupInputUrl)
.getValue()
.should.eventually.equal(url);
await app.client.element(ConversationPage.joinOpenGroupButton).click();
// validate session loader is shown
await app.client.isExisting(ConversationPage.sessionLoader).should
.eventually.be.true;
// account for slow home internet connection delays...
await app.client.waitForExist(
ConversationPage.sessionToastJoinOpenGroupSuccess,
60 * 1000
);
// validate overlay is closed
await app.client.isExisting(ConversationPage.leftPaneOverlay).should
.eventually.be.false;
// validate open chat has been added
await app.client.isExisting(
ConversationPage.rowOpenGroupConversationName(name)
).should.eventually.be.true;
}
it('openGroup: works with valid open group url', async () => {
await joinOpenGroup(common.VALID_GROUP_URL, common.VALID_GROUP_NAME);
await common.joinOpenGroup(
app,
common.VALID_GROUP_URL,
common.VALID_GROUP_NAME
);
});
it('openGroup: cannot join two times the same open group', async () => {
await joinOpenGroup(common.VALID_GROUP_URL2, common.VALID_GROUP_NAME2);
await common.joinOpenGroup(
app,
common.VALID_GROUP_URL2,
common.VALID_GROUP_NAME2
);
// adding a second time the same open group
await app.client.element(ConversationPage.globeButtonSection).click();
await app.client
.element(ConversationPage.conversationButtonSection)
.click();
await app.client.element(ConversationPage.joinOpenGroupButton).click();
await common.setValueWrapper(
@ -88,7 +66,9 @@ describe('Open groups', function() {
it('openGroup: can send message to open group', async () => {
// join dev-chat group
await app.client.element(ConversationPage.globeButtonSection).click();
await app.client
.element(ConversationPage.conversationButtonSection)
.click();
await app.client.element(ConversationPage.joinOpenGroupButton).click();
await common.setValueWrapper(

@ -40,8 +40,6 @@ module.exports = {
'//*[contains(@class, "session-modal")]//div[contains(string(), "Delete") and contains(@class, "session-button")]',
// channels
globeButtonSection:
'//*[contains(@class,"session-icon-button") and .//*[contains(@class, "globe")]]',
joinOpenGroupButton: commonPage.divRoleButtonWithText('Join Open Group'),
openGroupInputUrl: commonPage.textAreaWithPlaceholder('chat.getsession.org'),
sessionToastJoinOpenGroupSuccess: commonPage.toastWithText(

@ -17,4 +17,7 @@ module.exports = {
// Confirm is a boolean. Selects confirmation input
passwordSetModalInput: _confirm =>
`//input[@id = 'password-modal-input${_confirm ? '-confirm' : ''}']`,
secretWordsTextInDialog:
'//div[@class="device-pairing-dialog__secret-words"]/div[@class="subtle"]',
};

@ -5,6 +5,7 @@
const { afterEach, beforeEach, describe, it } = require('mocha');
const common = require('./common');
const SettingsPage = require('./page-objects/settings.page');
const RegistrationPage = require('./page-objects/registration.page');
const ConversationPage = require('./page-objects/conversation.page');
@ -104,7 +105,6 @@ describe('Window Test and Login', function() {
const login = {
mnemonic: common.TEST_MNEMONIC1,
displayName: common.TEST_DISPLAY_NAME1,
stubOpenGroups: true,
};
app = await common.startAndStub(login);
@ -117,7 +117,7 @@ describe('Window Test and Login', function() {
.executeJavaScript("window.storage.get('primaryDevicePubKey')")
.should.eventually.be.equal(common.TEST_PUBKEY1);
// delete account
await app.client.element(ConversationPage.settingsButtonSection).click();
await app.client.element(SettingsPage.settingsButtonSection).click();
await app.client.element(ConversationPage.deleteAccountButton).click();
await app.client.isExisting(ConversationPage.descriptionDeleteAccount)
.should.eventually.be.true;

@ -39,15 +39,12 @@ async function makeFriendsPlusMessage(app, [app2, pubkey]) {
);
// Click away so we can call this function again
await app.client.element(ConversationPage.globeButtonSection).click();
await app.client.element(ConversationPage.conversationButtonSection).click();
}
async function testTwoMembers() {
const [app, app2] = await common.startAppsAsFriends();
await app.client.element(ConversationPage.globeButtonSection).click();
await app.client.element(ConversationPage.createClosedGroupButton).click();
const useSenderKeys = true;
// create group and add new friend
@ -106,9 +103,6 @@ async function testThreeMembers() {
const useSenderKeys = true;
await app1.client.element(ConversationPage.globeButtonSection).click();
await app1.client.element(ConversationPage.createClosedGroupButton).click();
// 3. Add all three to the group
await common.addFriendToNewClosedGroup([app1, app2, app3], useSenderKeys);

@ -27,7 +27,6 @@ describe('Settings', function() {
const appProps = {
mnemonic: common.TEST_MNEMONIC1,
displayName: common.TEST_DISPLAY_NAME1,
stubSnode: true,
};
app = await common.startAndStub(appProps);

@ -0,0 +1,9 @@
/* eslint-disable class-methods-use-this */
class StubSnodeAPI {
async refreshSwarmNodesForPubKey() {
return [];
}
}
module.exports = StubSnodeAPI;

@ -646,7 +646,7 @@
active: true,
expireTimer: 0,
avatar: '',
is_medium_group: true,
is_medium_group: false,
},
confirm: () => {},
};
@ -854,6 +854,7 @@
window.friends.friendRequestStatusEnum.friends
);
textsecure.messaging.sendGroupSyncMessage([convo]);
appView.openConversation(groupId, {});
};
@ -1444,9 +1445,11 @@
// TODO: we should ensure the message was sent and retry automatically if not
await libloki.api.sendUnpairingMessageToSecondary(pubKey);
// Remove all traces of the device
ConversationController.deleteContact(pubKey);
Whisper.events.trigger('refreshLinkedDeviceList');
callback();
setTimeout(() => {
ConversationController.deleteContact(pubKey);
Whisper.events.trigger('refreshLinkedDeviceList');
callback();
}, 1000);
});
}
@ -1773,7 +1776,6 @@
const details = ev.contactDetails;
const id = details.number;
libloki.api.debug.logContactSync(
'Got sync contact message with',
id,
@ -1828,12 +1830,21 @@
await conversation.setSecondaryStatus(true, ourPrimaryKey);
}
if (conversation.isFriendRequestStatusNoneOrExpired()) {
libloki.api.sendAutoFriendRequestMessage(conversation.id);
} else {
// Accept any pending friend requests if there are any
conversation.onAcceptFriendRequest({ blockSync: true });
}
const otherDevices = await libloki.storage.getPairedDevicesFor(id);
const devices = [id, ...otherDevices];
const deviceConversations = await Promise.all(
devices.map(d =>
ConversationController.getOrCreateAndWait(d, 'private')
)
);
deviceConversations.forEach(device => {
if (device.isFriendRequestStatusNoneOrExpired()) {
libloki.api.sendAutoFriendRequestMessage(device.id);
} else {
// Accept any pending friend requests if there are any
device.onAcceptFriendRequest({ blockSync: true });
}
});
if (details.profileKey) {
const profileKey = window.Signal.Crypto.arrayBufferToBase64(

@ -253,22 +253,26 @@
// Friend request message conmfirmations (Accept / Decline) are always
// sent to the primary device conversation
const messages = await window.Signal.Data.getMessagesByConversation(
this.id,
this.getPrimaryDevicePubKey(),
{
limit: 1,
limit: 5,
MessageCollection: Whisper.MessageCollection,
type: 'friend-request',
}
);
const lastMessageModel = messages.at(0);
if (lastMessageModel) {
lastMessageModel.acceptFriendRequest();
let lastMessage = null;
messages.forEach(m => {
m.acceptFriendRequest();
lastMessage = m;
});
if (lastMessage) {
await this.markRead();
window.Whisper.events.trigger(
'showConversation',
this.id,
lastMessageModel.id
lastMessage.id
);
}
},
@ -978,7 +982,7 @@
Conversation: Whisper.Conversation,
});
},
async respondToAllFriendRequests(options) {
async updateAllFriendRequestsMessages(options) {
const { response, status, direction = null } = options;
// Ignore if no response supplied
if (!response) {
@ -1032,8 +1036,8 @@
})
);
},
async respondToAllPendingFriendRequests(options) {
return this.respondToAllFriendRequests({
async updateAllPendingFriendRequestsMessages(options) {
return this.updateAllFriendRequestsMessages({
...options,
status: 'pending',
});
@ -1048,7 +1052,7 @@
// We have declined an incoming friend request
async onDeclineFriendRequest() {
this.setFriendRequestStatus(FriendRequestStatusEnum.none);
await this.respondToAllPendingFriendRequests({
await this.updateAllPendingFriendRequestsMessages({
response: 'declined',
direction: 'incoming',
});
@ -1062,13 +1066,16 @@
},
// We have accepted an incoming friend request
async onAcceptFriendRequest(options = {}) {
if (this.get('type') !== Message.PRIVATE) {
return;
}
if (this.unlockTimer) {
clearTimeout(this.unlockTimer);
}
if (this.hasReceivedFriendRequest()) {
this.setFriendRequestStatus(FriendRequestStatusEnum.friends, options);
await this.respondToAllFriendRequests({
await this.updateAllFriendRequestsMessages({
response: 'accepted',
direction: 'incoming',
status: ['pending', 'expired'],
@ -1078,6 +1085,12 @@
window.textsecure.OutgoingMessage.DebugMessageType
.INCOMING_FR_ACCEPTED
);
} else if (this.isFriendRequestStatusNoneOrExpired()) {
// send AFR if we haven't sent a message before
const autoFrMessage = textsecure.OutgoingMessage.buildAutoFriendRequestMessage(
this.id
);
await autoFrMessage.sendToNumber(this.id, false);
}
},
// Our outgoing friend request has been accepted
@ -1090,7 +1103,7 @@
}
if (this.hasSentFriendRequest()) {
this.setFriendRequestStatus(FriendRequestStatusEnum.friends);
await this.respondToAllFriendRequests({
await this.updateAllFriendRequestsMessages({
response: 'accepted',
status: ['pending', 'expired'],
});
@ -1122,7 +1135,7 @@
}
// Change any pending outgoing friend requests to expired
await this.respondToAllPendingFriendRequests({
await this.updateAllPendingFriendRequestsMessages({
response: 'expired',
direction: 'outgoing',
});
@ -1135,7 +1148,7 @@
await Promise.all([
this.setFriendRequestStatus(FriendRequestStatusEnum.friends),
// Accept all outgoing FR
this.respondToAllPendingFriendRequests({
this.updateAllPendingFriendRequestsMessages({
direction: 'outgoing',
response: 'accepted',
}),

@ -415,21 +415,21 @@
},
async acceptFriendRequest() {
const primaryDevicePubKey = this.attributes.conversationId;
if (this.get('friendStatus') !== 'pending') {
return;
}
const allDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey(
primaryDevicePubKey
const devicePubKey = this.get('conversationId');
const otherDevices = await libloki.storage.getPairedDevicesFor(
devicePubKey
);
const allDevices = [devicePubKey, ...otherDevices];
// Set profile name to primary conversation
let profileName;
const allConversationsWithUser = allDevices.map(d =>
ConversationController.get(d)
);
const allConversationsWithUser = allDevices
.map(d => ConversationController.get(d))
.filter(c => Boolean(c));
allConversationsWithUser.forEach(conversation => {
// If we somehow received an old friend request (e.g. after having restored
// from seed, we won't be able to accept it, we should initiate our own
@ -447,6 +447,9 @@
// If you don't have a profile name for this device, and profileName is set,
// add profileName to conversation.
const primaryDevicePubKey =
(await window.Signal.Data.getPrimaryDeviceFor(devicePubKey)) ||
devicePubKey;
const primaryConversation = allConversationsWithUser.find(
c => c.id === primaryDevicePubKey
);
@ -471,13 +474,23 @@
if (this.get('friendStatus') !== 'pending') {
return;
}
const conversation = this.getConversation();
this.set({ friendStatus: 'declined' });
await window.Signal.Data.saveMessage(this.attributes, {
Message: Whisper.Message,
});
conversation.onDeclineFriendRequest();
const devicePubKey = this.attributes.conversationId;
const otherDevices = await libloki.storage.getPairedDevicesFor(
devicePubKey
);
const allDevices = [devicePubKey, ...otherDevices];
const allConversationsWithUser = allDevices
.map(d => ConversationController.get(d))
.filter(c => Boolean(c));
allConversationsWithUser.forEach(conversation => {
conversation.onDeclineFriendRequest();
});
},
getPropsForFriendRequest() {
const friendStatus = this.get('friendStatus') || 'pending';
@ -2083,6 +2096,7 @@
return false;
},
async handleSessionRequest(source, confirm) {
window.console.log(`Received SESSION_REQUEST from source: ${source}`);
window.libloki.api.sendSessionEstablishedMessage(source);
confirm();
},
@ -2234,11 +2248,10 @@
return null;
}
}
const conversation = conversationPrimary;
return conversation.queueJob(async () => {
return conversationPrimary.queueJob(async () => {
window.log.info(
`Starting handleDataMessage for message ${message.idForLogging()} in conversation ${conversation.idForLogging()}`
`Starting handleDataMessage for message ${message.idForLogging()} in conversation ${conversationPrimary.idForLogging()}`
);
const GROUP_TYPES = textsecure.protobuf.GroupContext.Type;
const type = message.get('type');
@ -2251,7 +2264,7 @@
try {
const now = new Date().getTime();
let attributes = {
...conversation.attributes,
...conversationPrimary.attributes,
};
if (dataMessage.group) {
@ -2269,18 +2282,18 @@
};
groupUpdate =
conversation.changedAttributes(
conversationPrimary.changedAttributes(
_.pick(dataMessage.group, 'name', 'avatar')
) || {};
const addedMembers = _.difference(
attributes.members,
conversation.get('members')
conversationPrimary.get('members')
);
if (addedMembers.length > 0) {
groupUpdate.joined = addedMembers;
}
if (conversation.get('left')) {
if (conversationPrimary.get('left')) {
// TODO: Maybe we shouldn't assume this message adds us:
// we could maybe still get this message by mistake
window.log.warn('re-added to a left group');
@ -2294,7 +2307,7 @@
// Check if anyone got kicked:
const removedMembers = _.difference(
conversation.get('members'),
conversationPrimary.get('members'),
attributes.members
);
@ -2316,7 +2329,7 @@
groupUpdate = { left: source };
}
attributes.members = _.without(
conversation.get('members'),
conversationPrimary.get('members'),
source
);
}
@ -2349,7 +2362,7 @@
attachments: dataMessage.attachments,
body: dataMessage.body,
contact: dataMessage.contact,
conversationId: conversation.id,
conversationId: conversationPrimary.id,
decrypted_at: now,
errors: [],
flags: dataMessage.flags,
@ -2363,7 +2376,7 @@
if (type === 'outgoing') {
const receipts = Whisper.DeliveryReceipts.forMessage(
conversation,
conversationPrimary,
message
);
receipts.forEach(receipt =>
@ -2376,10 +2389,10 @@
);
}
attributes.active_at = now;
conversation.set(attributes);
conversationPrimary.set(attributes);
// Re-enable typing if re-joined the group
conversation.updateTextInputState();
conversationPrimary.updateTextInputState();
if (message.isExpirationTimerUpdate()) {
message.set({
@ -2388,7 +2401,7 @@
expireTimer: dataMessage.expireTimer,
},
});
conversation.set({ expireTimer: dataMessage.expireTimer });
conversationPrimary.set({ expireTimer: dataMessage.expireTimer });
} else if (dataMessage.expireTimer) {
message.set({ expireTimer: dataMessage.expireTimer });
}
@ -2400,7 +2413,7 @@
message.isExpirationTimerUpdate() || expireTimer;
if (shouldLogExpireTimerChange) {
window.log.info("Update conversation 'expireTimer'", {
id: conversation.idForLogging(),
id: conversationPrimary.idForLogging(),
expireTimer,
source: 'handleDataMessage',
});
@ -2408,8 +2421,11 @@
if (!message.isEndSession()) {
if (dataMessage.expireTimer) {
if (dataMessage.expireTimer !== conversation.get('expireTimer')) {
conversation.updateExpirationTimer(
if (
dataMessage.expireTimer !==
conversationPrimary.get('expireTimer')
) {
conversationPrimary.updateExpirationTimer(
dataMessage.expireTimer,
source,
message.get('received_at'),
@ -2419,18 +2435,18 @@
);
}
} else if (
conversation.get('expireTimer') &&
conversationPrimary.get('expireTimer') &&
// We only turn off timers if it's not a group update
!message.isGroupUpdate()
) {
conversation.updateExpirationTimer(
conversationPrimary.updateExpirationTimer(
null,
source,
message.get('received_at')
);
}
} else {
const endSessionType = conversation.isSessionResetReceived()
const endSessionType = conversationPrimary.isSessionResetReceived()
? 'ongoing'
: 'done';
this.set({ endSessionType });
@ -2462,11 +2478,11 @@
message.attributes.body &&
message.attributes.body.indexOf(`@${ourNumber}`) !== -1
) {
conversation.set({ mentionedUs: true });
conversationPrimary.set({ mentionedUs: true });
}
conversation.set({
unreadCount: conversation.get('unreadCount') + 1,
conversationPrimary.set({
unreadCount: conversationPrimary.get('unreadCount') + 1,
isArchived: false,
});
}
@ -2474,7 +2490,7 @@
if (type === 'outgoing') {
const reads = Whisper.ReadReceipts.forMessage(
conversation,
conversationPrimary,
message
);
if (reads.length) {
@ -2485,39 +2501,35 @@
}
// A sync'd message to ourself is automatically considered read and delivered
if (conversation.isMe()) {
if (conversationPrimary.isMe()) {
message.set({
read_by: conversation.getRecipients(),
delivered_to: conversation.getRecipients(),
read_by: conversationPrimary.getRecipients(),
delivered_to: conversationPrimary.getRecipients(),
});
}
message.set({ recipients: conversation.getRecipients() });
message.set({ recipients: conversationPrimary.getRecipients() });
}
const conversationTimestamp = conversation.get('timestamp');
const conversationTimestamp = conversationPrimary.get('timestamp');
if (
!conversationTimestamp ||
message.get('sent_at') > conversationTimestamp
) {
conversation.lastMessage = message.getNotificationText();
conversation.set({
conversationPrimary.lastMessage = message.getNotificationText();
conversationPrimary.set({
timestamp: message.get('sent_at'),
});
}
const sendingDeviceConversation = await ConversationController.getOrCreateAndWait(
source,
'private'
);
if (dataMessage.profileKey) {
const profileKey = dataMessage.profileKey.toString('base64');
if (source === textsecure.storage.user.getNumber()) {
conversation.set({ profileSharing: true });
} else if (conversation.isPrivate()) {
conversation.setProfileKey(profileKey);
conversationPrimary.set({ profileSharing: true });
} else if (conversationPrimary.isPrivate()) {
conversationPrimary.setProfileKey(profileKey);
} else {
sendingDeviceConversation.setProfileKey(profileKey);
conversationOrigin.setProfileKey(profileKey);
}
}
@ -2544,8 +2556,8 @@
and that user just sent us a friend request.
*/
const isFriend = sendingDeviceConversation.isFriend();
const hasSentFriendRequest = sendingDeviceConversation.hasSentFriendRequest();
const isFriend = conversationOrigin.isFriend();
const hasSentFriendRequest = conversationOrigin.hasSentFriendRequest();
autoAccept = isFriend || hasSentFriendRequest;
if (autoAccept) {
@ -2559,16 +2571,17 @@
if (isFriend) {
window.Whisper.events.trigger('endSession', source);
} else if (hasSentFriendRequest) {
await sendingDeviceConversation.onFriendRequestAccepted();
await conversationOrigin.onFriendRequestAccepted();
} else {
await sendingDeviceConversation.onFriendRequestReceived();
await conversationOrigin.onFriendRequestReceived();
}
} else if (message.get('type') !== 'outgoing') {
// Ignore 'outgoing' messages because they are sync messages
await sendingDeviceConversation.onFriendRequestAccepted();
await conversationOrigin.onFriendRequestAccepted();
}
}
// We need to map the original message source to the primary device
if (source !== ourNumber) {
message.set({ source: primarySource });
}
@ -2586,11 +2599,11 @@
await window.Signal.Data.updateConversation(
conversationId,
conversation.attributes,
conversationPrimary.attributes,
{ Conversation: Whisper.Conversation }
);
conversation.trigger('newmessage', message);
conversationPrimary.trigger('newmessage', message);
try {
// We go to the database here because, between the message save above and
@ -2628,9 +2641,9 @@
if (message.get('unread')) {
// Need to do this here because the conversation has already changed states
if (autoAccept) {
await conversation.notifyFriendRequest(source, 'accepted');
await conversationPrimary.notifyFriendRequest(source, 'accepted');
} else {
await conversation.notify(message);
await conversationPrimary.notify(message);
}
}

@ -1,4 +1,4 @@
/* global log, libloki, process, window */
/* global log, libloki, window */
/* global storage: false */
/* global Signal: false */
/* global log: false */
@ -20,13 +20,8 @@ class LokiFileServerInstance {
// LokiAppDotNetAPI (base) should not know about LokiFileServer.
async establishConnection(serverUrl, options) {
// why don't we extend this?
if (process.env.USE_STUBBED_NETWORK) {
// eslint-disable-next-line global-require
const StubAppDotNetAPI = require('../../integration_test/stubs/stub_app_dot_net_api.js');
this._server = new StubAppDotNetAPI(this.ourKey, serverUrl);
} else {
this._server = new LokiAppDotNetAPI(this.ourKey, serverUrl);
}
this._server = new LokiAppDotNetAPI(this.ourKey, serverUrl);
// make sure pubKey & pubKeyHex are set in _server
this.pubKey = this._server.getPubKeyForUrl();

@ -67,7 +67,7 @@ const makeOnionRequest = async (
for (let i = firstPos; i > -1; i -= 1) {
let dest;
const relayingToFinalDestination = i === 0; // if last position
const relayingToFinalDestination = i === firstPos; // if last position
if (relayingToFinalDestination && finalRelayOptions) {
dest = {

@ -21,9 +21,7 @@
const debugFlags = DebugFlagsEnum.ALL;
const debugLogFn = (...args) => {
// eslint-disable-next-line no-constant-condition
if (true) {
// process.env.NODE_ENV.includes('test-integration') ||
if (window.lokiFeatureFlags.debugMessageLogs) {
window.console.warn(...args);
}
};
@ -47,7 +45,7 @@
}
function logContactSync(...args) {
if (debugFlags & DebugFlagsEnum.GROUP_CONTACT_MESSAGES) {
if (debugFlags & DebugFlagsEnum.CONTACT_SYNC_MESSAGES) {
debugLogFn(...args);
}
}
@ -91,38 +89,22 @@
const message = textsecure.OutgoingMessage.buildSessionEstablishedMessage(
pubKey
);
await message.sendToNumber(pubKey);
await message.sendToNumber(pubKey, false);
}
async function sendBackgroundMessage(pubKey, debugMessageType) {
const primaryPubKey = await getPrimaryDevicePubkey(pubKey);
if (primaryPubKey !== pubKey) {
// if we got the secondary device pubkey first,
// call ourself again with the primary device pubkey
await sendBackgroundMessage(primaryPubKey, debugMessageType);
return;
}
const backgroundMessage = textsecure.OutgoingMessage.buildBackgroundMessage(
pubKey,
debugMessageType
);
await backgroundMessage.sendToNumber(pubKey);
await backgroundMessage.sendToNumber(pubKey, false);
}
async function sendAutoFriendRequestMessage(pubKey) {
const primaryPubKey = await getPrimaryDevicePubkey(pubKey);
if (primaryPubKey !== pubKey) {
// if we got the secondary device pubkey first,
// call ourself again with the primary device pubkey
await sendAutoFriendRequestMessage(primaryPubKey);
return;
}
const autoFrMessage = textsecure.OutgoingMessage.buildAutoFriendRequestMessage(
pubKey
);
await autoFrMessage.sendToNumber(pubKey);
await autoFrMessage.sendToNumber(pubKey, false);
}
function createPairingAuthorisationProtoMessage({
@ -158,7 +140,7 @@
const unpairingMessage = textsecure.OutgoingMessage.buildUnpairingMessage(
pubKey
);
return unpairingMessage.sendToNumber(pubKey);
return unpairingMessage.sendToNumber(pubKey, false);
}
// Serialise as <Element0.length><Element0><Element1.length><Element1>...
// This is an implementation of the reciprocal of contacts_parser.js
@ -298,7 +280,7 @@
callback
);
pairingRequestMessage.sendToNumber(recipientPubKey);
pairingRequestMessage.sendToNumber(recipientPubKey, false);
});
return p;
}
@ -348,6 +330,7 @@
createContactSyncProtoMessage,
createGroupSyncProtoMessage,
createOpenGroupsSyncProtoMessage,
getPrimaryDevicePubkey,
debug,
};
})();

@ -619,10 +619,13 @@
}
);
// Send sync messages
const conversations = window.getConversations().models;
textsecure.messaging.sendContactSyncMessage(conversations);
textsecure.messaging.sendGroupSyncMessage(conversations);
textsecure.messaging.sendOpenGroupsSyncMessage(conversations);
// bad hack to send sync messages when secondary device is ready to process them
setTimeout(async () => {
const conversations = window.getConversations().models;
await textsecure.messaging.sendGroupSyncMessage(conversations);
await textsecure.messaging.sendOpenGroupsSyncMessage(conversations);
await textsecure.messaging.sendContactSyncMessage(conversations);
}, 5000);
},
validatePubKeyHex(pubKey) {
const c = new Whisper.Conversation({

@ -101,6 +101,9 @@
NUM_CONCURRENT_CONNECTIONS,
stopPolling,
messages => {
if (this.calledStop) {
return; // don't handle those messages
}
connected = true;
messages.forEach(message => {
this.handleMessage(message.data, {
@ -127,6 +130,9 @@
// Exhausted all our snodes urls, trying again later from scratch
setTimeout(() => {
if (this.calledStop) {
return; // don't restart
}
window.log.info(
`http-resource: Exhausted all our snodes urls, trying again in ${EXHAUSTED_SNODES_RETRY_DELAY /
1000}s from scratch`

@ -59,7 +59,7 @@ function MessageReceiver(username, password, signalingKey, options = {}) {
openGroupBound = true;
}
} else {
window.log.error('Can not handle open group data, API is not available');
window.log.warn('Can not handle open group data, API is not available');
}
}
@ -929,33 +929,34 @@ MessageReceiver.prototype.extend({
await this.handleEndSession(destination);
}
if (msg.mediumGroupUpdate) {
await this.handleMediumGroupUpdate(envelope, msg.mediumGroupUpdate);
return;
}
// if (msg.mediumGroupUpdate) {
// await this.handleMediumGroupUpdate(envelope, msg.mediumGroupUpdate);
// return;
// }
const message = await this.processDecrypted(envelope, msg);
const groupId = message.group && message.group.id;
const isBlocked = this.isGroupBlocked(groupId);
const primaryDevicePubKey = window.storage.get('primaryDevicePubKey');
const isMe =
envelope.source === textsecure.storage.user.getNumber() ||
envelope.source === primaryDevicePubKey;
const isLeavingGroup = Boolean(
message.group &&
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
);
if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
window.log.warn(
`Message ${this.getEnvelopeId(
envelope
)} ignored; destined for blocked group`
);
this.removeFromCache(envelope);
return;
}
// const groupId = message.group && message.group.id;
// const isBlocked = this.isGroupBlocked(groupId);
//
// const isMe =
// envelope.source === textsecure.storage.user.getNumber() ||
// envelope.source === primaryDevicePubKey;
// const isLeavingGroup = Boolean(
// message.group &&
// message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
// );
// if (groupId && isBlocked && !(isMe && isLeavingGroup)) {
// window.log.warn(
// `Message ${this.getEnvelopeId(
// envelope
// )} ignored; destined for blocked group`
// );
// this.removeFromCache(envelope);
// return;
// }
// handle profileKey and avatar updates
if (envelope.source === primaryDevicePubKey) {
@ -1675,7 +1676,7 @@ MessageReceiver.prototype.extend({
const ourNumber = textsecure.storage.user.getNumber();
const ourPrimaryNumber = window.storage.get('primaryDevicePubKey');
const ourOtherDevices = await libloki.storage.getAllDevicePubKeysForPrimaryPubKey(
window.storage.get('primaryDevicePubKey')
ourPrimaryNumber
);
const ourDevices = new Set([
ourNumber,
@ -1861,6 +1862,7 @@ MessageReceiver.prototype.extend({
isBlocked(number) {
return textsecure.storage.get('blocked', []).indexOf(number) >= 0;
},
cleanAttachment(attachment) {
return {
..._.omit(attachment, 'thumbnail'),

@ -6,7 +6,6 @@
libloki,
StringView,
lokiMessageAPI,
i18n,
log
*/
@ -218,9 +217,17 @@ OutgoingMessage.prototype = {
this.errors[this.errors.length] = error;
this.numberCompleted();
},
reloadDevicesAndSend(primaryPubKey) {
reloadDevicesAndSend(primaryPubKey, multiDevice = true) {
const ourNumber = textsecure.storage.user.getNumber();
if (!multiDevice) {
if (primaryPubKey === ourNumber) {
return Promise.resolve();
}
return this.doSendMessage(primaryPubKey, [primaryPubKey]);
}
return (
libloki.storage
.getAllDevicePubKeysForPrimaryPubKey(primaryPubKey)
@ -352,7 +359,7 @@ OutgoingMessage.prototype = {
const updatedDevices = await getStaleDeviceIdsForNumber(devicePubKey);
const keysFound = await this.getKeysForNumber(devicePubKey, updatedDevices);
let isMultiDeviceRequest = false;
// let isMultiDeviceRequest = false;
let thisDeviceMessageType = this.messageType;
if (
thisDeviceMessageType !== 'pairing-request' &&
@ -373,7 +380,7 @@ OutgoingMessage.prototype = {
// - We haven't received a friend request from this device
// - We haven't sent a friend request recently
if (conversation.friendRequestTimerIsExpired()) {
isMultiDeviceRequest = true;
// isMultiDeviceRequest = true;
thisDeviceMessageType = 'friend-request';
} else {
// Throttle automated friend requests
@ -415,27 +422,11 @@ OutgoingMessage.prototype = {
window.log.info('attaching prekeys to outgoing message');
}
let messageBuffer;
let logDetails;
if (isMultiDeviceRequest) {
const tempMessage = new textsecure.protobuf.Content();
const tempDataMessage = new textsecure.protobuf.DataMessage();
tempDataMessage.body = i18n('secondaryDeviceDefaultFR');
if (this.message.dataMessage && this.message.dataMessage.profile) {
tempDataMessage.profile = this.message.dataMessage.profile;
}
tempMessage.preKeyBundleMessage = this.message.preKeyBundleMessage;
tempMessage.dataMessage = tempDataMessage;
messageBuffer = tempMessage.toArrayBuffer();
logDetails = {
tempMessage,
};
} else {
messageBuffer = this.message.toArrayBuffer();
logDetails = {
message: this.message,
};
}
const messageBuffer = this.message.toArrayBuffer();
const logDetails = {
message: this.message,
};
const messageTypeStr = this.debugMessageType;
const ourPubKey = textsecure.storage.user.getNumber();
@ -492,6 +483,7 @@ OutgoingMessage.prototype = {
plaintext,
pubKey,
isSessionRequest,
isFriendRequest,
enableFallBackEncryption,
} = clearMessage;
// Session doesn't use the deviceId scheme, it's always 1.
@ -537,7 +529,7 @@ OutgoingMessage.prototype = {
sourceDevice,
content,
pubKey,
isFriendRequest: enableFallBackEncryption,
isFriendRequest,
isSessionRequest,
};
},
@ -706,14 +698,14 @@ OutgoingMessage.prototype = {
return promise;
},
sendToNumber(number) {
sendToNumber(number, multiDevice = true) {
let conversation;
try {
conversation = ConversationController.get(number);
} catch (e) {
// do nothing
}
return this.reloadDevicesAndSend(number).catch(error => {
return this.reloadDevicesAndSend(number, multiDevice).catch(error => {
conversation.resetPendingSend();
if (error.message === 'Identity key changed') {
// eslint-disable-next-line no-param-reassign
@ -738,14 +730,15 @@ OutgoingMessage.prototype = {
OutgoingMessage.buildAutoFriendRequestMessage = function buildAutoFriendRequestMessage(
pubKey
) {
const dataMessage = new textsecure.protobuf.DataMessage({});
const body = 'Please accept to enable messages to be synced across devices';
const dataMessage = new textsecure.protobuf.DataMessage({ body });
const content = new textsecure.protobuf.Content({
dataMessage,
});
const options = {
messageType: 'onlineBroadcast',
messageType: 'friend-request',
debugMessageType: DebugMessageType.AUTO_FR_REQUEST,
};
// Send a empty message with information about how to contact us directly
@ -765,9 +758,10 @@ OutgoingMessage.buildSessionRequestMessage = function buildSessionRequestMessage
) {
const body =
'(If you see this message, you must be using an out-of-date client)';
const flags = textsecure.protobuf.DataMessage.Flags.SESSION_REQUEST;
const dataMessage = new textsecure.protobuf.DataMessage({ body, flags });
const dataMessage = new textsecure.protobuf.DataMessage({ flags, body });
const content = new textsecure.protobuf.Content({
dataMessage,

@ -664,16 +664,58 @@ MessageSender.prototype = {
if (!primaryDeviceKey) {
return Promise.resolve();
}
// Extract required contacts information out of conversations
const sessionContacts = conversations.filter(
c => c.isPrivate() && !c.isSecondaryDevice() && c.isFriend()
// first get all friends with primary devices
const sessionContactsPrimary =
conversations.filter(
c =>
c.isPrivate() &&
!c.isOurLocalDevice() &&
c.isFriend() &&
!c.get('secondaryStatus')
) || [];
// then get all friends with secondary devices
let sessionContactsSecondary = conversations.filter(
c =>
c.isPrivate() &&
!c.isOurLocalDevice() &&
c.isFriend() &&
c.get('secondaryStatus')
);
// then morph all secondary conversation to their primary
sessionContactsSecondary =
(await Promise.all(
// eslint-disable-next-line arrow-body-style
sessionContactsSecondary.map(async c => {
return window.ConversationController.getOrCreateAndWait(
c.getPrimaryDevicePubKey(),
'private'
);
})
)) || [];
// filter out our primary pubkey if it was added.
sessionContactsSecondary = sessionContactsSecondary.filter(
c => c.id !== primaryDeviceKey
);
if (sessionContacts.length === 0) {
const contactsSet = new Set([
...sessionContactsPrimary,
...sessionContactsSecondary,
]);
if (contactsSet.size === 0) {
window.console.info('No contacts to sync.');
return Promise.resolve();
}
libloki.api.debug.logContactSync('Triggering contact sync message with:', [
...contactsSet,
]);
// We need to sync across 3 contacts at a time
// This is to avoid hitting storage server limit
const chunked = _.chunk(sessionContacts, 3);
const chunked = _.chunk([...contactsSet], 3);
const syncMessages = await Promise.all(
chunked.map(c => libloki.api.createContactSyncProtoMessage(c))
);

@ -15,13 +15,9 @@
"main": "main.js",
"scripts": {
"postinstall": "electron-builder install-app-deps && rimraf node_modules/dtrace-provider",
"start": "electron .",
"start-multi": "cross-env NODE_APP_INSTANCE=1 electron .",
"start-multi2": "cross-env NODE_APP_INSTANCE=2 electron .",
"start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod electron .",
"start-prod-multi": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod1 electron .",
"start-swarm-test": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=1 electron .",
"start-swarm-test-2": "cross-env NODE_ENV=swarm-testing NODE_APP_INSTANCE=2 electron .",
"start": "cross-env NODE_APP_INSTANCE=$MULTI electron .",
"start-prod": "cross-env NODE_ENV=production NODE_APP_INSTANCE=devprod$MULTI electron .",
"start-swarm-test": "cross-env NODE_ENV=production NODE_APP_INSTANCE=$MULTI electron .",
"grunt": "grunt",
"icon-gen": "electron-icon-maker --input=images/icon_1024.png --output=./build",
"generate": "yarn icon-gen && yarn grunt",
@ -36,7 +32,6 @@
"test-loki-view": "NODE_ENV=test-loki yarn run start",
"test-electron": "yarn grunt test",
"test-integration": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js",
"test-integration-parts": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'registration' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'openGroup' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'addFriends' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'linkDevice' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'closedGroup'",
"test-medium-groups": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'senderkeys'",
"test-node": "mocha --recursive --exit test/app test/modules ts/test libloki/test/node",
"eslint": "eslint --cache .",

@ -56,6 +56,8 @@ if (
process.env.NODE_ENV.includes('test-integration')
) {
window.electronRequire = require;
// during test-integration, file server is started on localhost
window.getDefaultFileServer = () => 'http://127.0.0.1:7070';
}
window.isBeforeVersion = (toCheck, baseVersion) => {
@ -164,7 +166,7 @@ window.open = () => null;
window.eval = global.eval = () => null;
window.drawAttention = () => {
// window.log.info('draw attention');
// window.log.debug('draw attention');
ipc.send('draw-attention');
};
window.showWindow = () => {
@ -337,17 +339,31 @@ window.lokiSnodeAPI = new LokiSnodeAPI({
localUrl: config.localUrl,
});
window.LokiMessageAPI = require('./js/modules/loki_message_api');
if (process.env.USE_STUBBED_NETWORK) {
window.StubMessageAPI = require('./integration_test/stubs/stub_message_api');
window.StubAppDotNetApi = require('./integration_test/stubs/stub_app_dot_net_api');
window.StubLokiSnodeAPI = require('./integration_test/stubs/stub_loki_snode_api');
const StubMessageAPI = require('./integration_test/stubs/stub_message_api');
window.LokiMessageAPI = StubMessageAPI;
const StubAppDotNetAPI = require('./integration_test/stubs/stub_app_dot_net_api');
window.LokiAppDotNetServerAPI = StubAppDotNetAPI;
const StubSnodeAPI = require('./integration_test/stubs/stub_snode_api');
window.lokiSnodeAPI = new StubSnodeAPI({
serverUrl: config.serverUrl,
localUrl: config.localUrl,
});
} else {
window.lokiSnodeAPI = new LokiSnodeAPI({
serverUrl: config.serverUrl,
localUrl: config.localUrl,
});
window.LokiMessageAPI = require('./js/modules/loki_message_api');
window.LokiAppDotNetServerAPI = require('./js/modules/loki_app_dot_net_api');
}
window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');
window.LokiAppDotNetServerAPI = require('./js/modules/loki_app_dot_net_api');
window.LokiFileServerAPI = require('./js/modules/loki_file_server_api');
window.LokiRssAPI = require('./js/modules/loki_rss_api');
@ -433,6 +449,7 @@ window.lokiFeatureFlags = {
useFileOnionRequests: false,
enableSenderKeys: false,
onionRequestHops: 3,
debugMessageLogs: process.env.ENABLE_MESSAGE_LOGS,
};
// eslint-disable-next-line no-extend-native,func-names
@ -443,7 +460,8 @@ Promise.prototype.ignore = function() {
if (
config.environment.includes('test') &&
!config.environment.includes('swarm-testing')
!config.environment.includes('swarm-testing') &&
!config.environment.includes('test-integration')
) {
const isWindows = process.platform === 'win32';
/* eslint-disable global-require, import/no-extraneous-dependencies */
@ -465,5 +483,8 @@ if (config.environment.includes('test-integration')) {
multiDeviceUnpairing: true,
privateGroupChats: true,
useSnodeProxy: !process.env.USE_STUBBED_NETWORK,
useOnionRequests: false,
debugMessageLogs: true,
enableSenderKeys: true,
};
}

@ -0,0 +1 @@
Subproject commit 52b77bf3039aec88b3900e8a7ed6e62d30a4d0d4

@ -535,6 +535,7 @@ $session-compose-margin: 20px;
&-section {
display: flex;
flex-direction: column;
flex: 1;
}
&-category-list-item {

@ -114,6 +114,7 @@ export class LeftPaneSectionHeader extends React.Component<Props, State> {
count={notificationCount}
size={NotificationCountSize.ON_HEADER}
onClick={this.props.buttonClicked}
key="notification-count" // we can only have one of those here
/>
</div>
);

@ -147,7 +147,9 @@ export const _getLeftPaneLists = (
}
if (conversation.hasSentFriendRequest) {
if (!conversation.isFriend) {
allSentFriendsRequest.push(conversation);
if (!conversation.isSecondary) {
allSentFriendsRequest.push(conversation);
}
}
}

@ -4836,6 +4836,13 @@ in-publish@^2.0.0:
resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c"
integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==
indent-string@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
dependencies:
repeating "^2.0.0"
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
@ -9193,6 +9200,11 @@ slice-ansi@1.0.0:
dependencies:
is-fullwidth-code-point "^2.0.0"
slide@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=
snapdragon-node@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@ -9650,6 +9662,13 @@ strip-eof@^1.0.0:
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
strip-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=
dependencies:
get-stdin "^4.0.1"
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"

Loading…
Cancel
Save