From 052a8e65e2755307221c8de8fc82dd108184b11e Mon Sep 17 00:00:00 2001 From: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com> Date: Fri, 25 Feb 2022 12:37:15 -0600 Subject: [PATCH] Add "clean up timer if necessary" utility --- app/main.ts | 5 +- ts/IdleDetector.ts | 9 ++-- ts/challenge.ts | 9 ++-- ts/components/CallingToastManager.tsx | 9 ++-- ts/components/DialogNetworkStatus.tsx | 7 ++- ts/components/Toast.tsx | 7 ++- ts/components/conversation/ExpireTimer.tsx | 7 ++- ts/components/conversation/Message.tsx | 19 +++---- .../conversation/MessageTimestamp.tsx | 5 +- ts/components/conversation/Timeline.tsx | 21 +++----- ts/messageModifiers/AttachmentDownloads.ts | 15 +++--- ts/models/conversations.ts | 27 ++++------ ts/services/calling.ts | 7 ++- .../util/clearTimeoutIfNecessary_test.ts | 51 +++++++++++++++++++ ts/textsecure/MessageReceiver.ts | 7 ++- ts/textsecure/RotateSignedPreKeyListener.ts | 7 ++- ts/textsecure/TaskWithTimeout.ts | 9 ++-- ts/util/batcher.ts | 9 ++-- ts/util/clearTimeoutIfNecessary.ts | 10 ++++ ts/util/getStreamWithTimeout.ts | 9 ++-- ts/util/longRunningTaskWrapper.ts | 15 +++--- ts/util/waitBatcher.ts | 15 +++--- ts/util/waitForOnline.ts | 8 +-- 23 files changed, 150 insertions(+), 137 deletions(-) create mode 100644 ts/test-both/util/clearTimeoutIfNecessary_test.ts create mode 100644 ts/util/clearTimeoutIfNecessary.ts diff --git a/app/main.ts b/app/main.ts index 282bf95bf..43207f9be 100644 --- a/app/main.ts +++ b/app/main.ts @@ -84,6 +84,7 @@ import { parseSignalHttpsLink, rewriteSignalHrefsIfNecessary, } from '../ts/util/sgnlHref'; +import { clearTimeoutIfNecessary } from '../ts/util/clearTimeoutIfNecessary'; import { toggleMaximizedBrowserWindow } from '../ts/util/toggleMaximizedBrowserWindow'; import { getTitleBarVisibility, @@ -1698,9 +1699,7 @@ async function requestShutdown() { if (error) { return reject(error); } - if (timeout) { - clearTimeout(timeout); - } + clearTimeoutIfNecessary(timeout); resolve(); }); diff --git a/ts/IdleDetector.ts b/ts/IdleDetector.ts index 2a5d74344..2c371231d 100644 --- a/ts/IdleDetector.ts +++ b/ts/IdleDetector.ts @@ -1,8 +1,9 @@ -// Copyright 2018-2021 Signal Messenger, LLC +// Copyright 2018-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import EventEmitter from 'events'; import * as log from './logging/log'; +import { clearTimeoutIfNecessary } from './util/clearTimeoutIfNecessary'; const POLL_INTERVAL_MS = 5 * 1000; const IDLE_THRESHOLD_MS = 20; @@ -31,10 +32,8 @@ export class IdleDetector extends EventEmitter { delete this.handle; } - if (this.timeoutId) { - clearTimeout(this.timeoutId); - delete this.timeoutId; - } + clearTimeoutIfNecessary(this.timeoutId); + delete this.timeoutId; } private scheduleNextCallback() { diff --git a/ts/challenge.ts b/ts/challenge.ts index bd9171e68..c0c521b2f 100644 --- a/ts/challenge.ts +++ b/ts/challenge.ts @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // `ChallengeHandler` is responsible for: @@ -17,6 +17,7 @@ import { assert } from './util/assert'; import { isNotNil } from './util/isNotNil'; import { isOlderThan } from './util/timestamp'; import { parseRetryAfter } from './util/parseRetryAfter'; +import { clearTimeoutIfNecessary } from './util/clearTimeoutIfNecessary'; import { getEnvironment, Environment } from './environment'; import type { StorageInterface } from './types/Storage.d'; import { HTTPError } from './textsecure/Errors'; @@ -250,7 +251,7 @@ export class ChallengeHandler { const waitTime = Math.max(0, error.retryAfter - Date.now()); const oldTimer = this.retryTimers.get(message.id); if (oldTimer) { - clearTimeout(oldTimer); + clearTimeoutIfNecessary(oldTimer); } this.retryTimers.set( message.id, @@ -305,9 +306,7 @@ export class ChallengeHandler { const timer = this.retryTimers.get(message.id); this.retryTimers.delete(message.id); - if (timer) { - clearTimeout(timer); - } + clearTimeoutIfNecessary(timer); await this.persist(); } diff --git a/ts/components/CallingToastManager.tsx b/ts/components/CallingToastManager.tsx index bb53ec6ff..815fada48 100644 --- a/ts/components/CallingToastManager.tsx +++ b/ts/components/CallingToastManager.tsx @@ -6,6 +6,7 @@ import type { ActiveCallType } from '../types/Calling'; import { CallMode, GroupCallConnectionState } from '../types/Calling'; import type { ConversationType } from '../state/ducks/conversations'; import type { LocalizerType } from '../types/Util'; +import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { CallingToast, DEFAULT_LIFETIME } from './CallingToast'; type PropsType = { @@ -126,9 +127,7 @@ export const CallingToastManager: React.FC = props => { useEffect(() => { if (toast) { if (toast.type === 'dismissable') { - if (timeoutRef && timeoutRef.current) { - clearTimeout(timeoutRef.current); - } + clearTimeoutIfNecessary(timeoutRef.current); timeoutRef.current = setTimeout(dismissToast, DEFAULT_LIFETIME); } @@ -136,9 +135,7 @@ export const CallingToastManager: React.FC = props => { } return () => { - if (timeoutRef && timeoutRef.current) { - clearTimeout(timeoutRef.current); - } + clearTimeoutIfNecessary(timeoutRef.current); }; }, [dismissToast, setToastMessage, timeoutRef, toast]); diff --git a/ts/components/DialogNetworkStatus.tsx b/ts/components/DialogNetworkStatus.tsx index 6de3e0dba..d4af87ad1 100644 --- a/ts/components/DialogNetworkStatus.tsx +++ b/ts/components/DialogNetworkStatus.tsx @@ -1,4 +1,4 @@ -// Copyright 2020-2021 Signal Messenger, LLC +// Copyright 2020-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useEffect } from 'react'; @@ -9,6 +9,7 @@ import type { LocalizerType } from '../types/Util'; import { SocketStatus } from '../types/SocketStatus'; import type { NetworkStateType } from '../state/ducks/network'; import type { WidthBreakpoint } from './_util'; +import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; const FIVE_SECONDS = 5 * 1000; @@ -44,9 +45,7 @@ export const DialogNetworkStatus = ({ } return () => { - if (timeout) { - clearTimeout(timeout); - } + clearTimeoutIfNecessary(timeout); }; }, [hasNetworkDialog, isConnecting, setIsConnecting]); diff --git a/ts/components/Toast.tsx b/ts/components/Toast.tsx index d3c3c15ae..916687a96 100644 --- a/ts/components/Toast.tsx +++ b/ts/components/Toast.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { KeyboardEvent, MouseEvent, ReactNode } from 'react'; @@ -6,6 +6,7 @@ import React, { memo, useEffect } from 'react'; import classNames from 'classnames'; import { createPortal } from 'react-dom'; import { useRestoreFocus } from '../hooks/useRestoreFocus'; +import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; export type PropsType = { autoDismissDisabled?: boolean; @@ -54,9 +55,7 @@ export const Toast = memo( const timeoutId = setTimeout(onClose, timeout); return () => { - if (timeoutId) { - clearTimeout(timeoutId); - } + clearTimeoutIfNecessary(timeoutId); }; }, [autoDismissDisabled, onClose, root, timeout]); diff --git a/ts/components/conversation/ExpireTimer.tsx b/ts/components/conversation/ExpireTimer.tsx index 3eb2a0ac9..786fd2e7c 100644 --- a/ts/components/conversation/ExpireTimer.tsx +++ b/ts/components/conversation/ExpireTimer.tsx @@ -1,10 +1,11 @@ -// Copyright 2018-2021 Signal Messenger, LLC +// Copyright 2018-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; import classNames from 'classnames'; import { getIncrement, getTimerBucket } from '../../util/timer'; +import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary'; export type Props = { withImageNoCaption?: boolean; @@ -40,9 +41,7 @@ export class ExpireTimer extends React.Component { } public override componentWillUnmount(): void { - if (this.interval) { - clearInterval(this.interval); - } + clearTimeoutIfNecessary(this.interval); } public override render(): JSX.Element { diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index a2120f84a..0731425f1 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -1,4 +1,4 @@ -// Copyright 2018-2021 Signal Messenger, LLC +// Copyright 2018-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { RefObject } from 'react'; @@ -58,6 +58,7 @@ import { import type { EmbeddedContactType } from '../../types/EmbeddedContact'; import { getIncrement } from '../../util/timer'; +import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary'; import { isFileDangerous } from '../../util/isFileDangerous'; import { missingCaseError } from '../../util/missingCaseError'; import type { @@ -451,18 +452,10 @@ export class Message extends React.PureComponent { } public override componentWillUnmount(): void { - if (this.selectedTimeout) { - clearTimeout(this.selectedTimeout); - } - if (this.expirationCheckInterval) { - clearInterval(this.expirationCheckInterval); - } - if (this.expiredTimeout) { - clearTimeout(this.expiredTimeout); - } - if (this.deleteForEveryoneTimeout) { - clearTimeout(this.deleteForEveryoneTimeout); - } + clearTimeoutIfNecessary(this.selectedTimeout); + clearTimeoutIfNecessary(this.expirationCheckInterval); + clearTimeoutIfNecessary(this.expiredTimeout); + clearTimeoutIfNecessary(this.deleteForEveryoneTimeout); this.toggleReactionViewer(true); this.toggleReactionPicker(true); } diff --git a/ts/components/conversation/MessageTimestamp.tsx b/ts/components/conversation/MessageTimestamp.tsx index 25f4227de..dbc94bb29 100644 --- a/ts/components/conversation/MessageTimestamp.tsx +++ b/ts/components/conversation/MessageTimestamp.tsx @@ -6,6 +6,7 @@ import classNames from 'classnames'; import moment from 'moment'; import { formatTime } from '../../util/timestamp'; +import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary'; import type { LocalizerType } from '../../types/Util'; @@ -42,9 +43,7 @@ export class MessageTimestamp extends React.Component { } public override componentWillUnmount(): void { - if (this.interval) { - clearInterval(this.interval); - } + clearTimeoutIfNecessary(this.interval); } public override render(): JSX.Element | null { diff --git a/ts/components/conversation/Timeline.tsx b/ts/components/conversation/Timeline.tsx index 0aa83268d..29acf2b9a 100644 --- a/ts/components/conversation/Timeline.tsx +++ b/ts/components/conversation/Timeline.tsx @@ -18,6 +18,7 @@ import type { PreferredBadgeSelectorType } from '../../state/selectors/badges'; import { assert } from '../../util/assert'; import { missingCaseError } from '../../util/missingCaseError'; import { createRefMerger } from '../../util/refMerger'; +import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary'; import { WidthBreakpoint } from '../_util'; import type { PropsActions as MessageActionsType } from './Message'; @@ -483,9 +484,7 @@ export class Timeline extends React.PureComponent { } this.setState({ hasRecentlyScrolled: true }); - if (this.hasRecentlyScrolledTimeout) { - clearTimeout(this.hasRecentlyScrolledTimeout); - } + clearTimeoutIfNecessary(this.hasRecentlyScrolledTimeout); this.hasRecentlyScrolledTimeout = setTimeout(() => { this.setState({ hasRecentlyScrolled: false }); }, 3000); @@ -549,10 +548,8 @@ export class Timeline extends React.PureComponent { const atTop = scrollTop <= AT_TOP_THRESHOLD; const loadCountdownStart = atTop && !haveOldest ? Date.now() : undefined; - if (this.loadCountdownTimeout) { - clearTimeout(this.loadCountdownTimeout); - this.loadCountdownTimeout = null; - } + clearTimeoutIfNecessary(this.loadCountdownTimeout); + this.loadCountdownTimeout = null; if (isNumber(loadCountdownStart)) { this.loadCountdownTimeout = setTimeout( this.loadOlderMessages, @@ -746,10 +743,8 @@ export class Timeline extends React.PureComponent { const { haveOldest, isLoadingMessages, items, loadOlderMessages } = this.props; - if (this.loadCountdownTimeout) { - clearTimeout(this.loadCountdownTimeout); - this.loadCountdownTimeout = null; - } + clearTimeoutIfNecessary(this.loadCountdownTimeout); + this.loadCountdownTimeout = null; if (isLoadingMessages || haveOldest || !items || items.length < 1) { return; @@ -962,9 +957,7 @@ export class Timeline extends React.PureComponent { window.unregisterForActive(this.updateWithVisibleRows); - if (delayedPeekTimeout) { - clearTimeout(delayedPeekTimeout); - } + clearTimeoutIfNecessary(delayedPeekTimeout); } public override componentDidUpdate( diff --git a/ts/messageModifiers/AttachmentDownloads.ts b/ts/messageModifiers/AttachmentDownloads.ts index 01e4dedfb..89a4e330b 100644 --- a/ts/messageModifiers/AttachmentDownloads.ts +++ b/ts/messageModifiers/AttachmentDownloads.ts @@ -1,4 +1,4 @@ -// Copyright 2019-2021 Signal Messenger, LLC +// Copyright 2019-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { isNumber, omit } from 'lodash'; @@ -6,6 +6,7 @@ import { v4 as getGuid } from 'uuid'; import dataInterface from '../sql/Client'; import * as durations from '../util/durations'; +import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { downloadAttachment } from '../util/downloadAttachment'; import * as Bytes from '../Bytes'; import type { @@ -67,10 +68,8 @@ export async function stop(): Promise { logger.info('attachment_downloads/stop: disabling'); } enabled = false; - if (timeout) { - clearTimeout(timeout); - timeout = null; - } + clearTimeoutIfNecessary(timeout); + timeout = null; } export async function addJob( @@ -115,10 +114,8 @@ export async function addJob( } async function _tick(): Promise { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } + clearTimeoutIfNecessary(timeout); + timeout = null; _maybeStartJob(); timeout = setTimeout(_tick, TICK_INTERVAL); diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index fe4270e71..ff908932b 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -18,6 +18,7 @@ import type { } from '../model-types.d'; import { getInitials } from '../util/getInitials'; import { normalizeUuid } from '../util/normalizeUuid'; +import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import type { AttachmentType } from '../types/Attachment'; import { isGIF } from '../types/Attachment'; import type { CallHistoryDetailsType } from '../types/Calling'; @@ -1032,9 +1033,7 @@ export class ConversationModel extends window.Backbone } setTypingRefreshTimer(): void { - if (this.typingRefreshTimer) { - clearTimeout(this.typingRefreshTimer); - } + clearTimeoutIfNecessary(this.typingRefreshTimer); this.typingRefreshTimer = setTimeout( this.onTypingRefreshTimeout.bind(this), 10 * 1000 @@ -1050,9 +1049,7 @@ export class ConversationModel extends window.Backbone } setTypingPauseTimer(): void { - if (this.typingPauseTimer) { - clearTimeout(this.typingPauseTimer); - } + clearTimeoutIfNecessary(this.typingPauseTimer); this.typingPauseTimer = setTimeout( this.onTypingPauseTimeout.bind(this), 3 * 1000 @@ -1067,14 +1064,10 @@ export class ConversationModel extends window.Backbone } clearTypingTimers(): void { - if (this.typingPauseTimer) { - clearTimeout(this.typingPauseTimer); - this.typingPauseTimer = null; - } - if (this.typingRefreshTimer) { - clearTimeout(this.typingRefreshTimer); - this.typingRefreshTimer = null; - } + clearTimeoutIfNecessary(this.typingPauseTimer); + this.typingPauseTimer = null; + clearTimeoutIfNecessary(this.typingRefreshTimer); + this.typingRefreshTimer = null; } async fetchLatestGroupV2Data( @@ -4914,10 +4907,8 @@ export class ConversationModel extends window.Backbone } startMuteTimer({ viaStorageServiceSync = false } = {}): void { - if (this.muteTimer !== undefined) { - clearTimeout(this.muteTimer); - this.muteTimer = undefined; - } + clearTimeoutIfNecessary(this.muteTimer); + this.muteTimer = undefined; const muteExpiresAt = this.get('muteExpiresAt'); if (isNumber(muteExpiresAt) && muteExpiresAt < Number.MAX_SAFE_INTEGER) { diff --git a/ts/services/calling.ts b/ts/services/calling.ts index ef168f61c..ae5f9cf62 100644 --- a/ts/services/calling.ts +++ b/ts/services/calling.ts @@ -75,6 +75,7 @@ import { dropNull, shallowDropNull } from '../util/dropNull'; import { getOwn } from '../util/getOwn'; import { isNormalNumber } from '../util/isNormalNumber'; import * as durations from '../util/durations'; +import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { handleMessageSend } from '../util/handleMessageSend'; import { fetchMembershipProof, getMembershipList } from '../groups'; import { wrapWithSyncMessageSend } from '../util/wrapWithSyncMessageSend'; @@ -1228,10 +1229,8 @@ export class CallingClass { } private stopDeviceReselectionTimer() { - if (this.deviceReselectionTimer) { - clearInterval(this.deviceReselectionTimer); - this.deviceReselectionTimer = undefined; - } + clearTimeoutIfNecessary(this.deviceReselectionTimer); + this.deviceReselectionTimer = undefined; } private mediaDeviceSettingsEqual( diff --git a/ts/test-both/util/clearTimeoutIfNecessary_test.ts b/ts/test-both/util/clearTimeoutIfNecessary_test.ts new file mode 100644 index 000000000..75a9ba235 --- /dev/null +++ b/ts/test-both/util/clearTimeoutIfNecessary_test.ts @@ -0,0 +1,51 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import * as sinon from 'sinon'; + +import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary'; + +describe('clearTimeoutIfNecessary', () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('does nothing if passed `null` or `undefined`', () => { + sandbox.stub(global, 'clearTimeout'); + sandbox.stub(global, 'clearInterval'); + + clearTimeoutIfNecessary(undefined); + clearTimeoutIfNecessary(null); + + sinon.assert.notCalled(global.clearTimeout as sinon.SinonSpy); + sinon.assert.notCalled(global.clearInterval as sinon.SinonSpy); + }); + + it('clears timeouts', async () => { + const clock = sandbox.useFakeTimers(); + const fn = sinon.fake(); + const timeout = setTimeout(fn, 123); + + clearTimeoutIfNecessary(timeout); + + await clock.tickAsync(9999); + sinon.assert.notCalled(fn); + }); + + it('clears intervals', async () => { + const clock = sandbox.useFakeTimers(); + const fn = sinon.fake(); + const interval = setInterval(fn, 123); + + clearTimeoutIfNecessary(interval); + + await clock.tickAsync(9999); + sinon.assert.notCalled(fn); + }); +}); diff --git a/ts/textsecure/MessageReceiver.ts b/ts/textsecure/MessageReceiver.ts index 93acb9813..5b9e4459e 100644 --- a/ts/textsecure/MessageReceiver.ts +++ b/ts/textsecure/MessageReceiver.ts @@ -45,6 +45,7 @@ import { dropNull } from '../util/dropNull'; import { normalizeUuid } from '../util/normalizeUuid'; import { normalizeNumber } from '../util/normalizeNumber'; import { parseIntOrThrow } from '../util/parseIntOrThrow'; +import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { Zone } from '../util/Zone'; import { deriveMasterKeyFromGroupV1 } from '../Crypto'; import type { DownloadedAttachmentType } from '../types/Attachment'; @@ -756,10 +757,8 @@ export default class MessageReceiver } private clearRetryTimeout(): void { - if (this.retryCachedTimeout) { - clearInterval(this.retryCachedTimeout); - this.retryCachedTimeout = undefined; - } + clearTimeoutIfNecessary(this.retryCachedTimeout); + this.retryCachedTimeout = undefined; } private maybeScheduleRetryTimeout(): void { diff --git a/ts/textsecure/RotateSignedPreKeyListener.ts b/ts/textsecure/RotateSignedPreKeyListener.ts index 55a2f065a..6990e65cc 100644 --- a/ts/textsecure/RotateSignedPreKeyListener.ts +++ b/ts/textsecure/RotateSignedPreKeyListener.ts @@ -1,7 +1,8 @@ -// Copyright 2017-2020 Signal Messenger, LLC +// Copyright 2017-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as durations from '../util/durations'; +import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { UUIDKind } from '../types/UUID'; import * as log from '../logging/log'; @@ -38,9 +39,7 @@ export class RotateSignedPreKeyListener { waitTime = 0; } - if (this.timeout !== undefined) { - clearTimeout(this.timeout); - } + clearTimeoutIfNecessary(this.timeout); this.timeout = setTimeout(() => this.runWhenOnline(), waitTime); } diff --git a/ts/textsecure/TaskWithTimeout.ts b/ts/textsecure/TaskWithTimeout.ts index 3bb1c2cf4..8890f0368 100644 --- a/ts/textsecure/TaskWithTimeout.ts +++ b/ts/textsecure/TaskWithTimeout.ts @@ -1,7 +1,8 @@ -// Copyright 2020 Signal Messenger, LLC +// Copyright 2020-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as durations from '../util/durations'; +import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { explodePromise } from '../util/explodePromise'; import { toLogFormat } from '../types/errors'; import * as log from '../logging/log'; @@ -66,10 +67,8 @@ export default function createTaskWithTimeout>( }; const stopTimer = () => { - if (timer) { - clearTimeout(timer); - timer = undefined; - } + clearTimeoutIfNecessary(timer); + timer = undefined; }; const entry: TaskType = { diff --git a/ts/util/batcher.ts b/ts/util/batcher.ts index 07c75b486..cc7e37dea 100644 --- a/ts/util/batcher.ts +++ b/ts/util/batcher.ts @@ -1,10 +1,11 @@ -// Copyright 2019-2021 Signal Messenger, LLC +// Copyright 2019-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import PQueue from 'p-queue'; import { sleep } from './sleep'; import * as log from '../logging/log'; +import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary'; declare global { // We want to extend `window`'s properties, so we need an interface. @@ -51,10 +52,8 @@ export function createBatcher( }); function _kickBatchOff() { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } + clearTimeoutIfNecessary(timeout); + timeout = null; const itemsRef = items; items = []; diff --git a/ts/util/clearTimeoutIfNecessary.ts b/ts/util/clearTimeoutIfNecessary.ts new file mode 100644 index 000000000..01e25db23 --- /dev/null +++ b/ts/util/clearTimeoutIfNecessary.ts @@ -0,0 +1,10 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +export function clearTimeoutIfNecessary( + timeout: undefined | null | ReturnType +): void { + if (timeout) { + clearTimeout(timeout); + } +} diff --git a/ts/util/getStreamWithTimeout.ts b/ts/util/getStreamWithTimeout.ts index a58be8fcb..00390bf90 100644 --- a/ts/util/getStreamWithTimeout.ts +++ b/ts/util/getStreamWithTimeout.ts @@ -1,9 +1,10 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { Readable } from 'stream'; import * as Bytes from '../Bytes'; +import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary'; import { explodePromise } from './explodePromise'; export type OptionsType = Readonly<{ @@ -25,10 +26,8 @@ export function getStreamWithTimeout( let timer: NodeJS.Timeout | undefined; const clearTimer = () => { - if (timer !== undefined) { - clearTimeout(timer); - timer = undefined; - } + clearTimeoutIfNecessary(timer); + timer = undefined; }; const reset = () => { diff --git a/ts/util/longRunningTaskWrapper.ts b/ts/util/longRunningTaskWrapper.ts index dda31988c..10fd831fd 100644 --- a/ts/util/longRunningTaskWrapper.ts +++ b/ts/util/longRunningTaskWrapper.ts @@ -1,7 +1,8 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as log from '../logging/log'; +import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary'; export async function longRunningTaskWrapper({ name, @@ -39,10 +40,8 @@ export async function longRunningTaskWrapper({ const result = await task(); log.info(`longRunningTaskWrapper/${idLog}: Task completed successfully`); - if (progressTimeout) { - clearTimeout(progressTimeout); - progressTimeout = undefined; - } + clearTimeoutIfNecessary(progressTimeout); + progressTimeout = undefined; if (progressView) { const now = Date.now(); if (spinnerStart && now - spinnerStart < ONE_SECOND) { @@ -62,10 +61,8 @@ export async function longRunningTaskWrapper({ error && error.stack ? error.stack : error ); - if (progressTimeout) { - clearTimeout(progressTimeout); - progressTimeout = undefined; - } + clearTimeoutIfNecessary(progressTimeout); + progressTimeout = undefined; if (progressView) { progressView.remove(); progressView = undefined; diff --git a/ts/util/waitBatcher.ts b/ts/util/waitBatcher.ts index fcb3c5333..9d9cfd213 100644 --- a/ts/util/waitBatcher.ts +++ b/ts/util/waitBatcher.ts @@ -1,10 +1,11 @@ -// Copyright 2019-2021 Signal Messenger, LLC +// Copyright 2019-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import PQueue from 'p-queue'; import { sleep } from './sleep'; import * as log from '../logging/log'; +import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary'; declare global { // We want to extend `window`'s properties, so we need an interface. @@ -119,10 +120,8 @@ export function createWaitBatcher( }, options.wait); } if (items.length >= options.maxSize) { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } + clearTimeoutIfNecessary(timeout); + timeout = null; _kickBatchOff(); } @@ -159,10 +158,8 @@ export function createWaitBatcher( `Flushing start ${options.name} for waitBatcher ` + `items.length=${items.length}` ); - if (timeout) { - clearTimeout(timeout); - timeout = null; - } + clearTimeoutIfNecessary(timeout); + timeout = null; while (anyPending()) { // eslint-disable-next-line no-await-in-loop diff --git a/ts/util/waitForOnline.ts b/ts/util/waitForOnline.ts index f8770b82c..8bf9bfcac 100644 --- a/ts/util/waitForOnline.ts +++ b/ts/util/waitForOnline.ts @@ -1,6 +1,8 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary'; + export function waitForOnline( navigator: Readonly<{ onLine: boolean }>, onlineEventTarget: EventTarget, @@ -23,9 +25,7 @@ export function waitForOnline( const cleanup = () => { onlineEventTarget.removeEventListener('online', listener); - if (typeof timeoutId === 'number') { - clearTimeout(timeoutId); - } + clearTimeoutIfNecessary(timeoutId); }; onlineEventTarget.addEventListener('online', listener);