{
name={authorName}
profileName={authorProfileName}
compact={true}
- shouldShowPubkey={Boolean(isPublic)}
+ shouldShowPubkey={false} // never show the pubkey for quoted messages author
/>
)}
diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx
index 5b3d34982..6c32f113d 100644
--- a/ts/components/session/ActionsPanel.tsx
+++ b/ts/components/session/ActionsPanel.tsx
@@ -245,9 +245,7 @@ const doAppStartUp = () => {
debounce(triggerAvatarReUploadIfNeeded, 200);
// TODO: Investigate the case where we reconnect
- const ourKey = UserUtils.getOurPubKeyStrFromCache();
- getSwarmPollingInstance().addPubkey(ourKey);
- getSwarmPollingInstance().start();
+ void getSwarmPollingInstance().start();
};
/**
diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx
index 086c345fd..22df902de 100644
--- a/ts/components/session/conversation/SessionCompositionBox.tsx
+++ b/ts/components/session/conversation/SessionCompositionBox.tsx
@@ -802,12 +802,6 @@ export class SessionCompositionBox extends React.Component {
const { isBlocked, isPrivate, left, isKickedFromGroup } = this.props;
- // deny sending of message if our app version is expired
- if (window.extension.expiredStatus() === true) {
- ToastUtils.pushToastError('expiredWarning', window.i18n('expiredWarning'));
- return;
- }
-
if (isBlocked && isPrivate) {
ToastUtils.pushUnblockToSend();
return;
diff --git a/ts/components/session/icon/Icons.tsx b/ts/components/session/icon/Icons.tsx
index e0c9e7daa..39a7ff3d8 100644
--- a/ts/components/session/icon/Icons.tsx
+++ b/ts/components/session/icon/Icons.tsx
@@ -103,8 +103,12 @@ export const icons = {
ratio: 1,
},
[SessionIconType.Circle]: {
- path: 'M 100, 100m -75, 0a 75,75 0 1,0 150,0a 75,75 0 1,0 -150,0',
- viewBox: '0 0 200 200',
+ path: '\
+ M 0, 50\
+ a 50,50 0 1,1 100,0\
+ a 50,50 0 1,1 -100,0\
+ ',
+ viewBox: '0 0 100 100',
ratio: 1,
},
[SessionIconType.CircleCheck]: {
diff --git a/ts/data/data.ts b/ts/data/data.ts
index cb2826eea..2613be494 100644
--- a/ts/data/data.ts
+++ b/ts/data/data.ts
@@ -1,7 +1,7 @@
import Electron from 'electron';
const { ipcRenderer } = Electron;
-// tslint:disable: function-name no-require-imports no-var-requires one-variable-per-declaration no-void-expression
+// tslint:disable: no-require-imports no-var-requires one-variable-per-declaration no-void-expression
import _ from 'lodash';
import { ConversationCollection, ConversationModel } from '../models/conversation';
diff --git a/ts/interactions/messageInteractions.ts b/ts/interactions/messageInteractions.ts
index 8199e427a..8f7aabcaa 100644
--- a/ts/interactions/messageInteractions.ts
+++ b/ts/interactions/messageInteractions.ts
@@ -134,7 +134,7 @@ export async function removeSenderFromModerator(sender: string, convoId: string)
if (!res) {
window?.log?.warn('failed to remove moderator:', res);
- ToastUtils.pushErrorHappenedWhileRemovingModerator();
+ ToastUtils.pushFailedToRemoveFromModerator();
} else {
window?.log?.info(`${pubKeyToRemove.key} removed from moderators...`);
ToastUtils.pushUserRemovedFromModerators();
@@ -154,7 +154,7 @@ export async function addSenderAsModerator(sender: string, convoId: string) {
if (!res) {
window?.log?.warn('failed to add moderator:', res);
- ToastUtils.pushUserNeedsToHaveJoined();
+ ToastUtils.pushFailedToAddAsModerator();
} else {
window?.log?.info(`${pubKeyToAdd.key} added to moderators...`);
ToastUtils.pushUserAddedToModerators();
diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts
index aa179de25..55b6544c8 100644
--- a/ts/models/conversation.ts
+++ b/ts/models/conversation.ts
@@ -44,6 +44,7 @@ import { NotificationForConvoOption } from '../components/conversation/Conversat
import { useDispatch } from 'react-redux';
import { updateConfirmModal } from '../state/ducks/modalDialog';
import { createTaskWithTimeout } from '../session/utils/TaskWithTimeout';
+import { DURATION, SWARM_POLLING_TIMEOUT } from '../session/constants';
export enum ConversationTypeEnum {
GROUP = 'group',
diff --git a/ts/session/constants.ts b/ts/session/constants.ts
index eebeb4120..df97e93d2 100644
--- a/ts/session/constants.ts
+++ b/ts/session/constants.ts
@@ -17,6 +17,12 @@ export const TTL_DEFAULT = {
TTL_MAX: 14 * DURATION.DAYS,
};
+export const SWARM_POLLING_TIMEOUT = {
+ ACTIVE: DURATION.SECONDS * 5,
+ MEDIUM_ACTIVE: DURATION.SECONDS * 60,
+ INACTIVE: DURATION.MINUTES * 60,
+};
+
export const PROTOCOLS = {
// tslint:disable-next-line: no-http-string
HTTP: 'http:',
diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts
index fdf7faed1..5d249c1b4 100644
--- a/ts/session/conversations/ConversationController.ts
+++ b/ts/session/conversations/ConversationController.ts
@@ -116,7 +116,7 @@ export class ConversationController {
conversation.initialPromise = create();
conversation.initialPromise.then(async () => {
- if (window.inboxStore) {
+ if (window?.inboxStore) {
window.inboxStore?.dispatch(
conversationActions.conversationAdded(conversation.id, conversation.getProps())
);
@@ -242,7 +242,7 @@ export class ConversationController {
window.log.info(`deleteContact !isPrivate, convo removed from DB: ${id}`);
this.conversations.remove(conversation);
- if (window.inboxStore) {
+ if (window?.inboxStore) {
window.inboxStore?.dispatch(conversationActions.conversationRemoved(conversation.id));
window.inboxStore?.dispatch(
conversationActions.conversationChanged(conversation.id, conversation.getProps())
@@ -310,7 +310,7 @@ export class ConversationController {
public reset() {
this._initialPromise = Promise.resolve();
this._initialFetchComplete = false;
- if (window.inboxStore) {
+ if (window?.inboxStore) {
window.inboxStore?.dispatch(conversationActions.removeAllConversations());
}
this.conversations.reset([]);
diff --git a/ts/session/messages/MessageController.ts b/ts/session/messages/MessageController.ts
index aaa7d37a1..c3e276abe 100644
--- a/ts/session/messages/MessageController.ts
+++ b/ts/session/messages/MessageController.ts
@@ -69,7 +69,6 @@ export class MessageController {
});
}
- // tslint:disable-next-line: function-name
public get(identifier: string) {
return this.messageLookup.get(identifier);
}
diff --git a/ts/session/snode_api/swarmPolling.ts b/ts/session/snode_api/swarmPolling.ts
index ea2512e45..87239c427 100644
--- a/ts/session/snode_api/swarmPolling.ts
+++ b/ts/session/snode_api/swarmPolling.ts
@@ -1,5 +1,5 @@
import { PubKey } from '../types';
-import { getSwarmFor } from './snodePool';
+import * as snodePool from './snodePool';
import { retrieveNextMessages } from './SNodeAPI';
import { SignalService } from '../../protobuf';
import * as Receiver from '../../receiver/receiver';
@@ -12,9 +12,10 @@ import {
updateLastHash,
} from '../../../ts/data/data';
-import { StringUtils } from '../../session/utils';
-import { getConversationController } from '../conversations';
+import { StringUtils, UserUtils } from '../../session/utils';
import { ConversationModel } from '../../models/conversation';
+import { DURATION, SWARM_POLLING_TIMEOUT } from '../constants';
+import { getConversationController } from '../conversations';
type PubkeyToHash = { [key: string]: string };
@@ -50,49 +51,133 @@ export const getSwarmPollingInstance = () => {
};
export class SwarmPolling {
- private pubkeys: Array;
- private groupPubkeys: Array;
+ private ourPubkey: PubKey | undefined;
+ private groupPolling: Array<{ pubkey: PubKey; lastPolledTimestamp: number }>;
private readonly lastHashes: { [key: string]: PubkeyToHash };
constructor() {
- this.pubkeys = [];
- this.groupPubkeys = [];
+ this.groupPolling = [];
this.lastHashes = {};
}
- public start(): void {
+ public async start(waitForFirstPoll = false): Promise {
+ this.ourPubkey = UserUtils.getOurPubKeyFromCache();
this.loadGroupIds();
- void this.pollForAllKeys();
+ if (waitForFirstPoll) {
+ await this.TEST_pollForAllKeys();
+ } else {
+ void this.TEST_pollForAllKeys();
+ }
+ }
+
+ /**
+ * Used fo testing only
+ */
+ public TEST_reset() {
+ this.ourPubkey = undefined;
+ this.groupPolling = [];
}
public addGroupId(pubkey: PubKey) {
- if (this.groupPubkeys.findIndex(m => m.key === pubkey.key) === -1) {
+ if (this.groupPolling.findIndex(m => m.pubkey.key === pubkey.key) === -1) {
window?.log?.info('Swarm addGroupId: adding pubkey to polling', pubkey.key);
- this.groupPubkeys.push(pubkey);
+ this.groupPolling.push({ pubkey, lastPolledTimestamp: 0 });
}
}
- public addPubkey(pk: PubKey | string) {
+ public removePubkey(pk: PubKey | string) {
const pubkey = PubKey.cast(pk);
- if (this.pubkeys.findIndex(m => m.key === pubkey.key) === -1) {
- this.pubkeys.push(pubkey);
+ window?.log?.info('Swarm removePubkey: removing pubkey from polling', pubkey.key);
+
+ if (this.ourPubkey && PubKey.cast(pk).isEqual(this.ourPubkey)) {
+ this.ourPubkey = undefined;
}
+ this.groupPolling = this.groupPolling.filter(group => !pubkey.isEqual(group.pubkey));
}
- public removePubkey(pk: PubKey | string) {
- const pubkey = PubKey.cast(pk);
- window?.log?.info('Swarm removePubkey: removing pubkey from polling', pubkey.key);
+ /**
+ * Only public for testing
+ * As of today, we pull closed group pubkeys as follow:
+ * if activeAt is not set, poll only once per hour
+ * if activeAt is less than an hour old, poll every 5 seconds or so
+ * if activeAt is less than a day old, poll every minutes only.
+ * If activeAt is more than a day old, poll only once per hour
+ */
+ public TEST_getPollingTimeout(convoId: PubKey) {
+ const convo = getConversationController().get(convoId.key);
+ if (!convo) {
+ return SWARM_POLLING_TIMEOUT.INACTIVE;
+ }
+ const activeAt = convo.get('active_at');
+ if (!activeAt) {
+ return SWARM_POLLING_TIMEOUT.INACTIVE;
+ }
+
+ const currentTimestamp = Date.now();
- this.pubkeys = this.pubkeys.filter(key => !pubkey.isEqual(key));
- this.groupPubkeys = this.groupPubkeys.filter(key => !pubkey.isEqual(key));
+ // consider that this is an active group if activeAt is less than an hour old
+ if (currentTimestamp - activeAt <= DURATION.HOURS * 1) {
+ return SWARM_POLLING_TIMEOUT.ACTIVE;
+ }
+
+ if (currentTimestamp - activeAt <= DURATION.DAYS * 1) {
+ return SWARM_POLLING_TIMEOUT.MEDIUM_ACTIVE;
+ }
+
+ return SWARM_POLLING_TIMEOUT.INACTIVE;
}
- protected async pollOnceForKey(pubkey: PubKey, isGroup: boolean) {
+ /**
+ * Only public for testing
+ */
+ public async TEST_pollForAllKeys() {
+ // we always poll as often as possible for our pubkey
+ const directPromise = this.ourPubkey
+ ? this.TEST_pollOnceForKey(this.ourPubkey, false)
+ : Promise.resolve();
+
+ const now = Date.now();
+ const groupPromises = this.groupPolling.map(async group => {
+ const convoPollingTimeout = this.TEST_getPollingTimeout(group.pubkey);
+
+ const diff = now - group.lastPolledTimestamp;
+
+ const loggingId =
+ getConversationController()
+ .get(group.pubkey.key)
+ ?.idForLogging() || group.pubkey.key;
+
+ if (diff >= convoPollingTimeout) {
+ (window?.log?.info || console.warn)(
+ `Polling for ${loggingId}; timeout: ${convoPollingTimeout} ; diff: ${diff}`
+ );
+ return this.TEST_pollOnceForKey(group.pubkey, true);
+ }
+ (window?.log?.info || console.warn)(
+ `Not polling for ${loggingId}; timeout: ${convoPollingTimeout} ; diff: ${diff}`
+ );
+
+ return Promise.resolve();
+ });
+ try {
+ await Promise.all(_.concat(directPromise, groupPromises));
+ } catch (e) {
+ (window?.log?.info || console.warn)('pollForAllKeys swallowing exception: ', e);
+ throw e;
+ } finally {
+ setTimeout(this.TEST_pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE);
+ }
+ }
+
+ /**
+ * Only exposed as public for testing
+ */
+ public async TEST_pollOnceForKey(pubkey: PubKey, isGroup: boolean) {
// NOTE: sometimes pubkey is string, sometimes it is object, so
// accept both until this is fixed:
const pkStr = pubkey.key;
- const snodes = await getSwarmFor(pkStr);
+ const snodes = await snodePool.getSwarmFor(pkStr);
// Select nodes for which we already have lastHashes
const alreadyPolled = snodes.filter((n: Snode) => this.lastHashes[n.pubkey_ed25519]);
@@ -123,6 +208,19 @@ export class SwarmPolling {
// Merge results into one list of unique messages
const messages = _.uniqBy(_.flatten(results), (x: any) => x.hash);
+ if (isGroup) {
+ // update the last fetched timestamp
+ this.groupPolling = this.groupPolling.map(group => {
+ if (PubKey.isEqual(pubkey, group.pubkey)) {
+ return {
+ ...group,
+ lastPolledTimestamp: Date.now(),
+ };
+ }
+ return group;
+ });
+ }
+
const newMessages = await this.handleSeenMessages(messages);
newMessages.forEach((m: Message) => {
@@ -133,7 +231,7 @@ export class SwarmPolling {
// Fetches messages for `pubkey` from `node` potentially updating
// the lash hash record
- protected async pollNodeForKey(node: Snode, pubkey: PubKey): Promise> {
+ private async pollNodeForKey(node: Snode, pubkey: PubKey): Promise> {
const edkey = node.pubkey_ed25519;
const pkStr = pubkey.key;
@@ -188,24 +286,6 @@ export class SwarmPolling {
return newMessages;
}
- private async pollForAllKeys() {
- const directPromises = this.pubkeys.map(async pk => {
- return this.pollOnceForKey(pk, false);
- });
-
- const groupPromises = this.groupPubkeys.map(async pk => {
- return this.pollOnceForKey(pk, true);
- });
- try {
- await Promise.all(_.concat(directPromises, groupPromises));
- } catch (e) {
- window?.log?.warn('pollForAllKeys swallowing exception: ', e);
- throw e;
- } finally {
- setTimeout(this.pollForAllKeys.bind(this), 2000);
- }
- }
-
private async updateLastHash(
edkey: string,
pubkey: PubKey,
diff --git a/ts/session/types/PubKey.ts b/ts/session/types/PubKey.ts
index 94ef6f686..63adcd9d9 100644
--- a/ts/session/types/PubKey.ts
+++ b/ts/session/types/PubKey.ts
@@ -152,6 +152,10 @@ export class PubKey {
return key.replace(PubKey.PREFIX_GROUP_TEXTSECURE, '');
}
+ public static isEqual(comparator1: PubKey | string, comparator2: PubKey | string) {
+ return PubKey.cast(comparator1).isEqual(comparator2);
+ }
+
public isEqual(comparator: PubKey | string) {
return comparator instanceof PubKey
? this.key === comparator.key
diff --git a/ts/session/utils/AttachmentsDownload.ts b/ts/session/utils/AttachmentsDownload.ts
index 6ae23e6f8..9df66264e 100644
--- a/ts/session/utils/AttachmentsDownload.ts
+++ b/ts/session/utils/AttachmentsDownload.ts
@@ -90,7 +90,6 @@ export async function addJob(attachment: any, job: any = {}) {
};
}
-// tslint:disable: function-name
async function _tick() {
await _maybeStartJob();
timeout = setTimeout(_tick, TICK_INTERVAL);
diff --git a/ts/session/utils/Toast.tsx b/ts/session/utils/Toast.tsx
index ba562a35b..c90fbb5bf 100644
--- a/ts/session/utils/Toast.tsx
+++ b/ts/session/utils/Toast.tsx
@@ -191,12 +191,12 @@ export function pushOnlyAdminCanRemove() {
);
}
-export function pushUserNeedsToHaveJoined() {
- pushToastWarning(
- 'userNeedsToHaveJoined',
- window.i18n('userNeedsToHaveJoined'),
- window.i18n('userNeedsToHaveJoinedDesc')
- );
+export function pushFailedToAddAsModerator() {
+ pushToastWarning('failedToAddAsModerator', window.i18n('failedToAddAsModerator'));
+}
+
+export function pushFailedToRemoveFromModerator() {
+ pushToastWarning('failedToRemoveFromModerator', window.i18n('failedToRemoveFromModerator'));
}
export function pushUserAddedToModerators() {
@@ -210,11 +210,3 @@ export function pushUserRemovedFromModerators() {
export function pushInvalidPubKey() {
pushToastSuccess('invalidPubKey', window.i18n('invalidPubkeyFormat'));
}
-
-export function pushErrorHappenedWhileRemovingModerator() {
- pushToastError(
- 'errorHappenedWhileRemovingModerator',
- window.i18n('errorHappenedWhileRemovingModerator'),
- window.i18n('errorHappenedWhileRemovingModeratorDesc')
- );
-}
diff --git a/ts/state/ducks/defaultRooms.tsx b/ts/state/ducks/defaultRooms.tsx
index 40dd860e8..ab08894ad 100644
--- a/ts/state/ducks/defaultRooms.tsx
+++ b/ts/state/ducks/defaultRooms.tsx
@@ -33,7 +33,6 @@ const defaultRoomsSlice = createSlice({
},
updateDefaultRoomsInProgress(state, action) {
const inProgress = action.payload as boolean;
- window?.log?.info('fetching default rooms inProgress?', action.payload);
return { ...state, inProgress };
},
updateDefaultBase64RoomData(state, action: PayloadAction) {
diff --git a/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts
new file mode 100644
index 000000000..5ff3eca58
--- /dev/null
+++ b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts
@@ -0,0 +1,307 @@
+// tslint:disable: no-implicit-dependencies max-func-body-length no-unused-expression
+
+import chai from 'chai';
+import Sinon, * as sinon from 'sinon';
+import _, { noop } from 'lodash';
+import { describe } from 'mocha';
+
+import chaiAsPromised from 'chai-as-promised';
+import { TestUtils } from '../../../test-utils';
+import { UserUtils } from '../../../../session/utils';
+import { getConversationController } from '../../../../session/conversations';
+import * as Data from '../../../../../ts/data/data';
+import { getSwarmPollingInstance, SnodePool } from '../../../../session/snode_api';
+import { SwarmPolling } from '../../../../session/snode_api/swarmPolling';
+import { SWARM_POLLING_TIMEOUT } from '../../../../session/constants';
+import {
+ ConversationCollection,
+ ConversationModel,
+ ConversationTypeEnum,
+} from '../../../../models/conversation';
+import { PubKey } from '../../../../session/types';
+// tslint:disable: chai-vague-errors
+
+chai.use(chaiAsPromised as any);
+chai.should();
+
+const { expect } = chai;
+
+// tslint:disable-next-line: max-func-body-length
+describe('SwarmPolling', () => {
+ // Initialize new stubbed cache
+ const sandbox = sinon.createSandbox();
+ const ourPubkey = TestUtils.generateFakePubKey();
+ const ourNumber = ourPubkey.key;
+
+ let pollOnceForKeySpy: Sinon.SinonSpy;
+
+ let swarmPolling: SwarmPolling;
+
+ let clock: Sinon.SinonFakeTimers;
+ beforeEach(async () => {
+ // Utils Stubs
+ sandbox.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(ourNumber);
+
+ sandbox.stub(Data, 'getAllConversations').resolves(new ConversationCollection());
+ sandbox.stub(Data, 'getItemById').resolves();
+ sandbox.stub(Data, 'saveConversation').resolves();
+ sandbox.stub(Data, 'getSwarmNodesForPubkey').resolves();
+ sandbox.stub(SnodePool, 'getSwarmFor').resolves([]);
+ TestUtils.stubWindow('profileImages', { removeImagesNotInArray: noop, hasImage: noop });
+ TestUtils.stubWindow('inboxStore', undefined);
+ const convoController = getConversationController();
+ await convoController.load();
+ getConversationController().getOrCreate(ourPubkey.key, ConversationTypeEnum.PRIVATE);
+
+ swarmPolling = getSwarmPollingInstance();
+ swarmPolling.TEST_reset();
+ pollOnceForKeySpy = sandbox.spy(swarmPolling, 'TEST_pollOnceForKey');
+
+ clock = sinon.useFakeTimers(Date.now());
+ });
+
+ afterEach(() => {
+ TestUtils.restoreStubs();
+ sandbox.restore();
+ getConversationController().reset();
+ clock.restore();
+ });
+
+ describe('getPollingTimeout', () => {
+ it('returns INACTIVE for non existing convo', () => {
+ const fakeConvo = TestUtils.generateFakePubKey();
+
+ expect(swarmPolling.TEST_getPollingTimeout(fakeConvo)).to.eq(SWARM_POLLING_TIMEOUT.INACTIVE);
+ });
+
+ it('returns ACTIVE for convo with less than an hour old activeAt', () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+ convo.set('active_at', Date.now() - 3555);
+ expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq(
+ SWARM_POLLING_TIMEOUT.ACTIVE
+ );
+ });
+
+ it('returns INACTIVE for convo with undefined activeAt', () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+ convo.set('active_at', undefined);
+ expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq(
+ SWARM_POLLING_TIMEOUT.INACTIVE
+ );
+ });
+
+ it('returns MEDIUM_ACTIVE for convo with activeAt of less than a day', () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+ convo.set('active_at', Date.now() - 1000 * 3600 * 23);
+ expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq(
+ SWARM_POLLING_TIMEOUT.MEDIUM_ACTIVE
+ );
+ });
+
+ it('returns INACTIVE for convo with activeAt of more than a day', () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+ convo.set('active_at', Date.now() - 1000 * 3600 * 25);
+ expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq(
+ SWARM_POLLING_TIMEOUT.INACTIVE
+ );
+ });
+ });
+
+ describe('pollForAllKeys', () => {
+ it('does run for our pubkey even if activeAt is really old ', async () => {
+ const convo = getConversationController().getOrCreate(
+ ourNumber,
+ ConversationTypeEnum.PRIVATE
+ );
+ convo.set('active_at', Date.now() - 1000 * 3600 * 25);
+ await swarmPolling.start(true);
+
+ expect(pollOnceForKeySpy.callCount).to.eq(1);
+ expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
+ });
+
+ it('does run for our pubkey even if activeAt is recent ', async () => {
+ const convo = getConversationController().getOrCreate(
+ ourNumber,
+ ConversationTypeEnum.PRIVATE
+ );
+ convo.set('active_at', Date.now());
+ await swarmPolling.start(true);
+
+ expect(pollOnceForKeySpy.callCount).to.eq(1);
+ expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
+ });
+
+ it('does run for group pubkey on start no matter the recent timestamp ', async () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+ convo.set('active_at', Date.now());
+ const groupConvoPubkey = PubKey.cast(convo.id);
+ swarmPolling.addGroupId(groupConvoPubkey);
+ await swarmPolling.start(true);
+
+ // our pubkey will be polled for, hence the 2
+ expect(pollOnceForKeySpy.callCount).to.eq(2);
+ expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
+ expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
+ });
+
+ it('does run for group pubkey on start no matter the old timestamp ', async () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+
+ convo.set('active_at', 1);
+ const groupConvoPubkey = PubKey.cast(convo.id);
+ swarmPolling.addGroupId(groupConvoPubkey);
+ await swarmPolling.start(true);
+
+ // our pubkey will be polled for, hence the 2
+ expect(pollOnceForKeySpy.callCount).to.eq(2);
+ expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
+ expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
+ });
+
+ it('does run for group pubkey on start but not another time if activeAt is old ', async () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+
+ convo.set('active_at', 1);
+ const groupConvoPubkey = PubKey.cast(convo.id);
+ swarmPolling.addGroupId(groupConvoPubkey);
+ await swarmPolling.start(true);
+
+ await swarmPolling.TEST_pollForAllKeys();
+
+ expect(pollOnceForKeySpy.callCount).to.eq(3);
+ expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
+ expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
+ });
+
+ it('does run twice if activeAt less than one hour ', async () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+
+ convo.set('active_at', Date.now());
+ const groupConvoPubkey = PubKey.cast(convo.id);
+ swarmPolling.addGroupId(groupConvoPubkey);
+ await swarmPolling.start(true);
+ clock.tick(6000);
+ // no need to do that as the tick will trigger a call in all cases after 5 secs
+ // await swarmPolling.TEST_pollForAllKeys();
+
+ expect(pollOnceForKeySpy.callCount).to.eq(4);
+ expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
+ expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
+ expect(pollOnceForKeySpy.lastCall.args).to.deep.eq([groupConvoPubkey, true]);
+ });
+
+ it('does run once only if activeAt is more than one hour', async () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+
+ convo.set('active_at', Date.now());
+ const groupConvoPubkey = PubKey.cast(convo.id);
+ swarmPolling.addGroupId(groupConvoPubkey);
+ await swarmPolling.start(true);
+
+ // more than hour old, we should not tick after just 5 seconds
+ convo.set('active_at', Date.now() - 3605 * 1000);
+
+ clock.tick(6000);
+
+ expect(pollOnceForKeySpy.callCount).to.eq(3);
+ expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
+ expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
+ });
+
+ it('does run once if activeAt is more than 1 days old ', async () => {
+ const convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+
+ convo.set('active_at', Date.now());
+ const groupConvoPubkey = PubKey.cast(convo.id);
+ swarmPolling.addGroupId(groupConvoPubkey);
+ await swarmPolling.start(true);
+
+ // more than hour old, we should not tick after just 5 seconds
+ convo.set('active_at', Date.now() - 25 * 3600 * 1000);
+
+ clock.tick(6 * 1000); // active
+
+ expect(pollOnceForKeySpy.callCount).to.eq(3);
+ expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
+ expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
+ });
+
+ describe('multiple runs', () => {
+ let convo: ConversationModel;
+ let groupConvoPubkey: PubKey;
+
+ beforeEach(async () => {
+ convo = getConversationController().getOrCreate(
+ TestUtils.generateFakePubKeyStr(),
+ ConversationTypeEnum.GROUP
+ );
+
+ convo.set('active_at', Date.now());
+ groupConvoPubkey = PubKey.cast(convo.id);
+ swarmPolling.addGroupId(groupConvoPubkey);
+ await swarmPolling.start(true);
+ });
+
+ it('does run twice if activeAt is more than 1 hour old and we tick more than one minute ', async () => {
+ pollOnceForKeySpy.resetHistory();
+ // more than hour old but less than a day, we should tick after just 60 seconds
+ convo.set('active_at', Date.now() - 3605 * 1000);
+
+ clock.tick(61 * 1000); // medium_active
+
+ await swarmPolling.TEST_pollForAllKeys();
+ expect(pollOnceForKeySpy.callCount).to.eq(3);
+ // first two calls are our pubkey
+ expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
+ expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([ourPubkey, false]);
+
+ expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([groupConvoPubkey, true]);
+ });
+
+ it('does run twice if activeAt is more than 1 day old and we tick more than one hour ', async () => {
+ pollOnceForKeySpy.resetHistory();
+ convo.set('active_at', Date.now() - 25 * 3600 * 1000);
+
+ clock.tick(3700 * 1000); // inactive
+
+ await swarmPolling.TEST_pollForAllKeys();
+ expect(pollOnceForKeySpy.callCount).to.eq(3);
+ // first two calls are our pubkey
+ expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
+ expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([ourPubkey, false]);
+ expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([groupConvoPubkey, true]);
+ });
+ });
+ });
+});
diff --git a/ts/test/session/unit/utils/Password.ts b/ts/test/session/unit/utils/Password_test.ts
similarity index 99%
rename from ts/test/session/unit/utils/Password.ts
rename to ts/test/session/unit/utils/Password_test.ts
index a5cca5a39..b1747b0d2 100644
--- a/ts/test/session/unit/utils/Password.ts
+++ b/ts/test/session/unit/utils/Password_test.ts
@@ -40,6 +40,7 @@ describe('Password Util', () => {
'TiJf@lk^jsO^z8MUn%)[Sd~UPQ)ci9CGS@jb<^',
'$u&%{r]apg#G@3dQdCkB_p8)gxhNFr=K&yfM_M8O&2Z.vQyvx',
'bf^OMnYku*iX;{Piw_0zvz',
+ '@@@@/???\\4545',
'#'.repeat(50),
];
valid.forEach(pass => {
diff --git a/ts/test/test-utils/utils/pubkey.ts b/ts/test/test-utils/utils/pubkey.ts
index 04f17bfc6..b73ac6d43 100644
--- a/ts/test/test-utils/utils/pubkey.ts
+++ b/ts/test/test-utils/utils/pubkey.ts
@@ -11,6 +11,15 @@ export function generateFakePubKey(): PubKey {
return new PubKey(pubkeyString);
}
+export function generateFakePubKeyStr(): string {
+ // Generates a mock pubkey for testing
+ const numBytes = PubKey.PUBKEY_LEN / 2 - 1;
+ const hexBuffer = crypto.randomBytes(numBytes).toString('hex');
+ const pubkeyString = `05${hexBuffer}`;
+
+ return pubkeyString;
+}
+
export function generateFakeECKeyPair(): ECKeyPair {
const pubkey = generateFakePubKey().toArray();
const privKey = new Uint8Array(crypto.randomBytes(64));
diff --git a/ts/util/blockedNumberController.ts b/ts/util/blockedNumberController.ts
index abdfc4b06..d16399d5d 100644
--- a/ts/util/blockedNumberController.ts
+++ b/ts/util/blockedNumberController.ts
@@ -1,4 +1,4 @@
-import { createOrUpdateItem, getItemById } from '../../ts/data/data';
+import { createOrUpdateItem, getItemById } from '../data/data';
import { PubKey } from '../session/types';
import { UserUtils } from '../session/utils';
diff --git a/ts/util/passwordUtils.ts b/ts/util/passwordUtils.ts
index d5f5d6f30..d6c12d1e0 100644
--- a/ts/util/passwordUtils.ts
+++ b/ts/util/passwordUtils.ts
@@ -33,7 +33,7 @@ export const validatePassword = (phrase: string) => {
}
// Restrict characters to letters, numbers and symbols
- const characterRegex = /^[a-zA-Z0-9-!()._`~@#$%^&*+=[\]{}|<>,;: ]+$/;
+ const characterRegex = /^[a-zA-Z0-9-!?/\\()._`~@#$%^&*+=[\]{}|<>,;: ]+$/;
if (!characterRegex.test(trimmed)) {
return window?.i18n ? window?.i18n('passwordCharacterError') : ERRORS.CHARACTER;
}
diff --git a/tslint.json b/tslint.json
index 4f804d015..79c9acaf1 100644
--- a/tslint.json
+++ b/tslint.json
@@ -65,10 +65,10 @@
"function-name": [
true,
{
- "function-regex": "^[a-z][\\w\\d]+$",
- "method-regex": "^[a-z][\\w\\d]+$",
- "private-method-regex": "^[a-z][\\w\\d]+$",
- "protected-method-regex": "^[a-z][\\w\\d]+$",
+ "function-regex": "^(TEST_)?(_)?[a-z][\\w\\d]+$",
+ "method-regex": "^(TEST_)?(_)?[a-z][\\w\\d]+$",
+ "private-method-regex": "^(TEST_)?(_)?[a-z][\\w\\d]+$",
+ "protected-method-regex": "^(TEST_)?(_)?[a-z][\\w\\d]+$",
"static-method-regex": "^[a-zA-Z][\\w\\d]+$"
}
],