Add multi device v2 support
parent
64737a89d7
commit
b88ea110e8
@ -0,0 +1,80 @@
|
||||
import { createOrUpdateItem, getItemById } from '../../../js/modules/data';
|
||||
import { getMessageQueue } from '..';
|
||||
import { ConversationController } from '../conversations';
|
||||
import { getCurrentConfigurationMessage } from './Messages';
|
||||
import { RawMessage } from '../types';
|
||||
import { DAYS } from './Number';
|
||||
|
||||
const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp';
|
||||
|
||||
const getLastSyncTimestampFromDb = async (): Promise<number | undefined> =>
|
||||
(await getItemById(ITEM_ID_LAST_SYNC_TIMESTAMP))?.value;
|
||||
|
||||
const writeLastSyncTimestampToDb = async (timestamp: number) =>
|
||||
createOrUpdateItem({ id: ITEM_ID_LAST_SYNC_TIMESTAMP, value: timestamp });
|
||||
|
||||
export const syncConfigurationIfNeeded = async () => {
|
||||
const lastSyncedTimestamp = (await getLastSyncTimestampFromDb()) || 0;
|
||||
const now = Date.now();
|
||||
|
||||
// if the last sync was less than 2 days before, return early.
|
||||
if (Math.abs(now - lastSyncedTimestamp) < DAYS * 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allConvos = ConversationController.getInstance().getConversations();
|
||||
const configMessage = await getCurrentConfigurationMessage(allConvos);
|
||||
try {
|
||||
window.log.info('syncConfigurationIfNeeded with', configMessage);
|
||||
|
||||
await getMessageQueue().sendSyncMessage(configMessage);
|
||||
} catch (e) {
|
||||
window.log.warn(
|
||||
'Caught an error while sending our ConfigurationMessage:',
|
||||
e
|
||||
);
|
||||
// we do return early so that next time we use the old timestamp again
|
||||
// and so try again to trigger a sync
|
||||
return;
|
||||
}
|
||||
await writeLastSyncTimestampToDb(now);
|
||||
};
|
||||
|
||||
export const forceSyncConfigurationNowIfNeeded = async (
|
||||
waitForMessageSent = false
|
||||
) => {
|
||||
const allConvos = ConversationController.getInstance().getConversations();
|
||||
const configMessage = await getCurrentConfigurationMessage(allConvos);
|
||||
window.log.info('forceSyncConfigurationNowIfNeeded with', configMessage);
|
||||
|
||||
const waitForMessageSentEvent = new Promise(resolve => {
|
||||
const ourResolver = (message: any) => {
|
||||
if (message.identifier === configMessage.identifier) {
|
||||
getMessageQueue().events.off('sendSuccess', ourResolver);
|
||||
getMessageQueue().events.off('sendFail', ourResolver);
|
||||
resolve(true);
|
||||
}
|
||||
};
|
||||
getMessageQueue().events.on('sendSuccess', ourResolver);
|
||||
getMessageQueue().events.on('sendFail', ourResolver);
|
||||
});
|
||||
|
||||
try {
|
||||
// this just adds the message to the sending queue.
|
||||
// if waitForMessageSent is set, we need to effectively wait until then
|
||||
await Promise.all([
|
||||
getMessageQueue().sendSyncMessage(configMessage),
|
||||
waitForMessageSentEvent,
|
||||
]);
|
||||
} catch (e) {
|
||||
window.log.warn(
|
||||
'Caught an error while sending our ConfigurationMessage:',
|
||||
e
|
||||
);
|
||||
}
|
||||
if (!waitForMessageSent) {
|
||||
return;
|
||||
}
|
||||
|
||||
return waitForMessageSentEvent;
|
||||
};
|
@ -0,0 +1,141 @@
|
||||
import { expect } from 'chai';
|
||||
import { ECKeyPair } from '../../../../receiver/keypairs';
|
||||
|
||||
import {
|
||||
ConfigurationMessage,
|
||||
ConfigurationMessageClosedGroup,
|
||||
} from '../../../../session/messages/outgoing/content/ConfigurationMessage';
|
||||
import { PubKey } from '../../../../session/types';
|
||||
import { TestUtils } from '../../../test-utils';
|
||||
|
||||
describe('ConfigurationMessage', () => {
|
||||
it('throw if closed group is not set', () => {
|
||||
const activeClosedGroups = null as any;
|
||||
const params = {
|
||||
activeClosedGroups,
|
||||
activeOpenGroups: [],
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
expect(() => new ConfigurationMessage(params)).to.throw(
|
||||
'closed group must be set'
|
||||
);
|
||||
});
|
||||
|
||||
it('throw if open group is not set', () => {
|
||||
const activeOpenGroups = null as any;
|
||||
const params = {
|
||||
activeClosedGroups: [],
|
||||
activeOpenGroups,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
expect(() => new ConfigurationMessage(params)).to.throw(
|
||||
'open group must be set'
|
||||
);
|
||||
});
|
||||
|
||||
describe('ConfigurationMessageClosedGroup', () => {
|
||||
it('throw if closed group has no encryptionkeypair', () => {
|
||||
const member = TestUtils.generateFakePubKey().key;
|
||||
const params = {
|
||||
publicKey: TestUtils.generateFakePubKey().key,
|
||||
name: 'groupname',
|
||||
members: [member],
|
||||
admins: [member],
|
||||
encryptionKeyPair: undefined as any,
|
||||
};
|
||||
|
||||
expect(() => new ConfigurationMessageClosedGroup(params)).to.throw(
|
||||
'Encryption key pair looks invalid'
|
||||
);
|
||||
});
|
||||
|
||||
it('throw if closed group has invalid encryptionkeypair', () => {
|
||||
const member = TestUtils.generateFakePubKey().key;
|
||||
const params = {
|
||||
publicKey: TestUtils.generateFakePubKey().key,
|
||||
name: 'groupname',
|
||||
members: [member],
|
||||
admins: [member],
|
||||
encryptionKeyPair: new ECKeyPair(new Uint8Array(), new Uint8Array()),
|
||||
};
|
||||
|
||||
expect(() => new ConfigurationMessageClosedGroup(params)).to.throw(
|
||||
'Encryption key pair looks invalid'
|
||||
);
|
||||
});
|
||||
|
||||
it('throw if closed group has invalid pubkey', () => {
|
||||
const member = TestUtils.generateFakePubKey().key;
|
||||
const params = {
|
||||
publicKey: 'invalidpubkey',
|
||||
name: 'groupname',
|
||||
members: [member],
|
||||
admins: [member],
|
||||
encryptionKeyPair: TestUtils.generateFakeECKeyPair(),
|
||||
};
|
||||
|
||||
expect(() => new ConfigurationMessageClosedGroup(params)).to.throw();
|
||||
});
|
||||
|
||||
it('throw if closed group has invalid name', () => {
|
||||
const member = TestUtils.generateFakePubKey().key;
|
||||
const params = {
|
||||
publicKey: TestUtils.generateFakePubKey().key,
|
||||
name: '',
|
||||
members: [member],
|
||||
admins: [member],
|
||||
encryptionKeyPair: TestUtils.generateFakeECKeyPair(),
|
||||
};
|
||||
|
||||
expect(() => new ConfigurationMessageClosedGroup(params)).to.throw(
|
||||
'name must be set'
|
||||
);
|
||||
});
|
||||
|
||||
it('throw if members is empty', () => {
|
||||
const member = TestUtils.generateFakePubKey().key;
|
||||
const params = {
|
||||
publicKey: TestUtils.generateFakePubKey().key,
|
||||
name: 'groupname',
|
||||
members: [],
|
||||
admins: [member],
|
||||
encryptionKeyPair: TestUtils.generateFakeECKeyPair(),
|
||||
};
|
||||
|
||||
expect(() => new ConfigurationMessageClosedGroup(params)).to.throw(
|
||||
'members must be set'
|
||||
);
|
||||
});
|
||||
|
||||
it('throw if admins is empty', () => {
|
||||
const member = TestUtils.generateFakePubKey().key;
|
||||
const params = {
|
||||
publicKey: TestUtils.generateFakePubKey().key,
|
||||
name: 'groupname',
|
||||
members: [member],
|
||||
admins: [],
|
||||
encryptionKeyPair: TestUtils.generateFakeECKeyPair(),
|
||||
};
|
||||
|
||||
expect(() => new ConfigurationMessageClosedGroup(params)).to.throw(
|
||||
'admins must be set'
|
||||
);
|
||||
});
|
||||
|
||||
it('throw if some admins are not members', () => {
|
||||
const member = TestUtils.generateFakePubKey().key;
|
||||
const admin = TestUtils.generateFakePubKey().key;
|
||||
const params = {
|
||||
publicKey: TestUtils.generateFakePubKey().key,
|
||||
name: 'groupname',
|
||||
members: [member],
|
||||
admins: [admin],
|
||||
encryptionKeyPair: TestUtils.generateFakeECKeyPair(),
|
||||
};
|
||||
|
||||
expect(() => new ConfigurationMessageClosedGroup(params)).to.throw(
|
||||
'some admins are not members'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,31 @@
|
||||
import chai from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { ConversationController } from '../../../../session/conversations';
|
||||
import * as MessageUtils from '../../../../session/utils/Messages';
|
||||
import { syncConfigurationIfNeeded } from '../../../../session/utils/syncUtils';
|
||||
import { TestUtils } from '../../../test-utils';
|
||||
import { restoreStubs } from '../../../test-utils/utils';
|
||||
// tslint:disable-next-line: no-require-imports no-var-requires
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
describe('SyncUtils', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
restoreStubs();
|
||||
});
|
||||
|
||||
describe('syncConfigurationIfNeeded', () => {
|
||||
it('sync if last sync undefined', async () => {
|
||||
// TestUtils.stubData('getItemById').resolves(undefined);
|
||||
// sandbox.stub(ConversationController.getInstance(), 'getConversations').returns([]);
|
||||
// const getCurrentConfigurationMessageSpy = sandbox.spy(MessageUtils, 'getCurrentConfigurationMessage');
|
||||
// await syncConfigurationIfNeeded();
|
||||
// expect(getCurrentConfigurationMessageSpy.callCount).equal(1);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue