diff --git a/ts/test-both/util/iterables_test.ts b/ts/test-both/util/iterables_test.ts index a1fb9101e..82a77c4ef 100644 --- a/ts/test-both/util/iterables_test.ts +++ b/ts/test-both/util/iterables_test.ts @@ -4,7 +4,14 @@ import { assert } from 'chai'; import * as sinon from 'sinon'; -import { isIterable, size, filter, map, take } from '../../util/iterables'; +import { + concat, + filter, + isIterable, + map, + size, + take, +} from '../../util/iterables'; describe('iterable utilities', () => { describe('isIterable', () => { @@ -82,6 +89,72 @@ describe('iterable utilities', () => { }); }); + describe('concat', () => { + it('returns an empty iterable when passed nothing', () => { + assert.deepEqual([...concat()], []); + }); + + it('returns an empty iterable when passed empty iterables', () => { + assert.deepEqual([...concat([])], []); + assert.deepEqual([...concat(new Set())], []); + assert.deepEqual([...concat(new Set(), [], new Map())], []); + }); + + it('concatenates multiple iterables', () => { + const everyNumber = { + *[Symbol.iterator]() { + for (let i = 4; true; i += 1) { + yield i; + } + }, + }; + + const result = concat([1, 2], new Set([3]), [], everyNumber); + const iterator = result[Symbol.iterator](); + + assert.deepEqual(iterator.next(), { value: 1, done: false }); + assert.deepEqual(iterator.next(), { value: 2, done: false }); + assert.deepEqual(iterator.next(), { value: 3, done: false }); + assert.deepEqual(iterator.next(), { value: 4, done: false }); + assert.deepEqual(iterator.next(), { value: 5, done: false }); + assert.deepEqual(iterator.next(), { value: 6, done: false }); + assert.deepEqual(iterator.next(), { value: 7, done: false }); + }); + + it("doesn't start the iterable until the last minute", () => { + const oneTwoThree = { + [Symbol.iterator]: sinon.fake(() => { + let n = 0; + return { + next() { + if (n > 3) { + return { done: true }; + } + n += 1; + return { value: n, done: false }; + }, + }; + }), + }; + + const result = concat([1, 2], oneTwoThree); + const iterator = result[Symbol.iterator](); + + sinon.assert.notCalled(oneTwoThree[Symbol.iterator]); + + iterator.next(); + sinon.assert.notCalled(oneTwoThree[Symbol.iterator]); + iterator.next(); + sinon.assert.notCalled(oneTwoThree[Symbol.iterator]); + + iterator.next(); + sinon.assert.calledOnce(oneTwoThree[Symbol.iterator]); + + iterator.next(); + sinon.assert.calledOnce(oneTwoThree[Symbol.iterator]); + }); + }); + describe('filter', () => { it('returns an empty iterable when passed an empty iterable', () => { const fn = sinon.fake(); diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index e68436f20..df53d70d6 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -48,6 +48,7 @@ import { LinkPreviewImage, LinkPreviewMetadata, } from '../linkPreviews/linkPreviewFetch'; +import { concat } from '../util/iterables'; function stringToArrayBuffer(str: string): ArrayBuffer { if (typeof str !== 'string') { @@ -1706,10 +1707,12 @@ export default class MessageSender { isNotMe = r => r !== myE164; } - const blockedIdentifiers = new Set([ - ...window.storage.getBlockedUuids(), - ...window.storage.getBlockedNumbers(), - ]); + const blockedIdentifiers = new Set( + concat( + window.storage.getBlockedUuids(), + window.storage.getBlockedNumbers() + ) + ); const recipients = groupMembers.filter( recipient => isNotMe(recipient) && !blockedIdentifiers.has(recipient) diff --git a/ts/util/iterables.ts b/ts/util/iterables.ts index 1664d592d..68d4df294 100644 --- a/ts/util/iterables.ts +++ b/ts/util/iterables.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only /* eslint-disable max-classes-per-file */ +/* eslint-disable no-restricted-syntax */ export function isIterable(value: unknown): value is Iterable { return ( @@ -28,6 +29,22 @@ export function size(iterable: Iterable): number { return result; } +export function concat( + ...iterables: ReadonlyArray> +): Iterable { + return new ConcatIterable(iterables); +} + +class ConcatIterable implements Iterable { + constructor(private readonly iterables: ReadonlyArray>) {} + + *[Symbol.iterator](): Iterator { + for (const iterable of this.iterables) { + yield* iterable; + } + } +} + export function filter( iterable: Iterable, predicate: (value: T) => value is S