Add `durations` utility for computing durations

This commit is contained in:
Evan Hahn 2021-08-26 09:10:58 -05:00 committed by GitHub
parent c6aa668a9b
commit f86f753df9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 99 additions and 95 deletions

View File

@ -19,6 +19,7 @@ import { getTitleBarVisibility, TitleBarVisibility } from './types/Settings';
import { SocketStatus } from './types/SocketStatus';
import { DEFAULT_CONVERSATION_COLOR } from './types/Colors';
import { ChallengeHandler } from './challenge';
import * as durations from './util/durations';
import { isWindowDragElement } from './util/isWindowDragElement';
import { assert, strictAssert } from './util/assert';
import { dropNull } from './util/dropNull';
@ -2261,7 +2262,7 @@ export async function startApp(): Promise<void> {
window.SignalContext.nativeThemeListener.subscribe(themeChanged);
const FIVE_MINUTES = 5 * 60 * 1000;
const FIVE_MINUTES = 5 * durations.MINUTE;
// Note: once this function returns, there still might be messages being processed on
// a given conversation's queue. But we have processed all events from the websocket.

View File

@ -23,6 +23,7 @@ import dataInterface from './sql/Client';
import { toWebSafeBase64, fromWebSafeBase64 } from './util/webSafeBase64';
import { assert, strictAssert } from './util/assert';
import { isMoreRecentThan } from './util/timestamp';
import * as durations from './util/durations';
import { normalizeUuid } from './util/normalizeUuid';
import { dropNull } from './util/dropNull';
import {
@ -1252,9 +1253,8 @@ export async function modifyGroupV2({
);
}
const ONE_MINUTE = 1000 * 60;
const startTime = Date.now();
const timeoutTime = startTime + ONE_MINUTE;
const timeoutTime = startTime + durations.MINUTE;
const MAX_ATTEMPTS = 5;
@ -2723,7 +2723,7 @@ type MaybeUpdatePropsType = {
force?: boolean;
};
const FIVE_MINUTES = 1000 * 60 * 5;
const FIVE_MINUTES = 5 * durations.MINUTE;
export async function waitThenMaybeUpdateGroup(
options: MaybeUpdatePropsType,

View File

@ -4,7 +4,7 @@
/* eslint-disable class-methods-use-this */
import * as z from 'zod';
import * as moment from 'moment';
import * as durations from '../util/durations';
import type { LoggerType } from '../logging/log';
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
import { runReadOrViewSyncJob } from './helpers/runReadOrViewSyncJob';
@ -12,7 +12,7 @@ import { runReadOrViewSyncJob } from './helpers/runReadOrViewSyncJob';
import { JobQueue } from './JobQueue';
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
const MAX_RETRY_TIME = moment.duration(1, 'day').asMilliseconds();
const MAX_RETRY_TIME = durations.DAY;
const readSyncJobDataSchema = z.object({
readSyncs: z.array(

View File

@ -3,7 +3,7 @@
/* eslint-disable class-methods-use-this */
import * as z from 'zod';
import * as moment from 'moment';
import * as durations from '../util/durations';
import { strictAssert } from '../util/assert';
import { waitForOnline } from '../util/waitForOnline';
import { isDone as isDeviceLinked } from '../util/registration';
@ -16,7 +16,7 @@ import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
import { parseIntWithFallback } from '../util/parseIntWithFallback';
import type { WebAPIType } from '../textsecure/WebAPI';
const RETRY_WAIT_TIME = moment.duration(1, 'minute').asMilliseconds();
const RETRY_WAIT_TIME = durations.MINUTE;
const RETRYABLE_4XX_FAILURE_STATUSES = new Set([
404,
408,

View File

@ -4,7 +4,7 @@
/* eslint-disable class-methods-use-this */
import * as z from 'zod';
import * as moment from 'moment';
import * as durations from '../util/durations';
import type { LoggerType } from '../logging/log';
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
import { runReadOrViewSyncJob } from './helpers/runReadOrViewSyncJob';
@ -12,7 +12,7 @@ import { runReadOrViewSyncJob } from './helpers/runReadOrViewSyncJob';
import { JobQueue } from './JobQueue';
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
const MAX_RETRY_TIME = moment.duration(1, 'day').asMilliseconds();
const MAX_RETRY_TIME = durations.DAY;
const viewSyncJobDataSchema = z.object({
viewSyncs: z.array(

View File

@ -4,7 +4,7 @@
/* eslint-disable class-methods-use-this */
import { z } from 'zod';
import * as moment from 'moment';
import * as durations from '../util/durations';
import type { LoggerType } from '../logging/log';
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
import { commonShouldJobContinue } from './helpers/commonShouldJobContinue';
@ -14,7 +14,7 @@ import { JobQueue } from './JobQueue';
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
import { handleCommonJobRequestError } from './helpers/handleCommonJobRequestError';
const MAX_RETRY_TIME = moment.duration(1, 'day').asMilliseconds();
const MAX_RETRY_TIME = durations.DAY;
const viewedReceiptsJobDataSchema = z.object({
viewedReceipt: z.object({

View File

@ -5,6 +5,7 @@ import { isNumber, omit } from 'lodash';
import { v4 as getGuid } from 'uuid';
import dataInterface from '../sql/Client';
import * as durations from '../util/durations';
import { downloadAttachment } from '../util/downloadAttachment';
import { stringFromBytes } from '../Crypto';
import {
@ -28,15 +29,12 @@ const {
const MAX_ATTACHMENT_JOB_PARALLELISM = 3;
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const TICK_INTERVAL = MINUTE;
const TICK_INTERVAL = durations.MINUTE;
const RETRY_BACKOFF: Record<number, number> = {
1: 30 * SECOND,
2: 30 * MINUTE,
3: 6 * HOUR,
1: 30 * durations.SECOND,
2: 30 * durations.MINUTE,
3: 6 * durations.HOUR,
};
let enabled = false;

View File

@ -56,6 +56,7 @@ import { sendReadReceiptsFor } from '../util/sendReadReceiptsFor';
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
import { ReadStatus } from '../messages/MessageReadStatus';
import { SendStatus } from '../messages/MessageSendState';
import * as durations from '../util/durations';
import {
concat,
filter,
@ -106,8 +107,8 @@ const {
} = window.Signal.Migrations;
const { addStickerPackReference } = window.Signal.Data;
const THREE_HOURS = 3 * 60 * 60 * 1000;
const FIVE_MINUTES = 1000 * 60 * 5;
const THREE_HOURS = durations.HOUR * 3;
const FIVE_MINUTES = durations.MINUTE * 5;
const JOB_REPORTING_THRESHOLD_MS = 25;

View File

@ -34,7 +34,6 @@ import {
BandwidthMode,
} from 'ringrtc';
import { uniqBy, noop } from 'lodash';
import * as moment from 'moment';
import {
ActionsType as UxActionsType,
@ -63,6 +62,7 @@ import {
import { assert } from '../util/assert';
import { dropNull, shallowDropNull } from '../util/dropNull';
import { getOwn } from '../util/getOwn';
import * as durations from '../util/durations';
import { handleMessageSend } from '../util/handleMessageSend';
import {
fetchMembershipProof,
@ -99,9 +99,7 @@ const RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD: Map<
[HttpMethod.Delete, 'DELETE'],
]);
const CLEAN_EXPIRED_GROUP_CALL_RINGS_INTERVAL = moment
.duration(10, 'minutes')
.asMilliseconds();
const CLEAN_EXPIRED_GROUP_CALL_RINGS_INTERVAL = 10 * durations.MINUTE;
// We send group call update messages to tell other clients to peek, which triggers
// notifications, timeline messages, big green "Join" buttons, and so on. This enum

View File

@ -11,6 +11,7 @@ import {
} from '../util/zkgroup';
import { GroupCredentialType } from '../textsecure/WebAPI';
import * as durations from '../util/durations';
import { BackOff } from '../util/BackOff';
import { sleep } from '../util/sleep';
@ -26,13 +27,8 @@ type NextCredentialsType = {
tomorrow: GroupCredentialType;
};
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
function getTodayInEpoch() {
return Math.floor(Date.now() / DAY);
return Math.floor(Date.now() / durations.DAY);
}
let started = false;
@ -48,15 +44,17 @@ export async function initializeGroupCredentialFetcher(): Promise<void> {
// Because we fetch eight days of credentials at a time, we really only need to run
// this about once a week. But there's no problem running it more often; it will do
// nothing if no new credentials are needed, and will only request needed credentials.
await runWithRetry(maybeFetchNewCredentials, { scheduleAnother: 4 * HOUR });
await runWithRetry(maybeFetchNewCredentials, {
scheduleAnother: 4 * durations.HOUR,
});
}
const BACKOFF_TIMEOUTS = [
SECOND,
5 * SECOND,
30 * SECOND,
2 * MINUTE,
5 * MINUTE,
durations.SECOND,
5 * durations.SECOND,
30 * durations.SECOND,
2 * durations.MINUTE,
5 * durations.MINUTE,
];
export async function runWithRetry(

View File

@ -26,6 +26,7 @@ import {
} from './storageRecordOps';
import { ConversationModel } from '../models/conversations';
import { strictAssert } from '../util/assert';
import * as durations from '../util/durations';
import { BackOff } from '../util/BackOff';
import { handleMessageSend } from '../util/handleMessageSend';
import { storageJobQueue } from '../util/JobQueue';
@ -60,18 +61,19 @@ const validRecordTypes = new Set([
4, // ACCOUNT
]);
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const backOff = new BackOff([
SECOND,
5 * SECOND,
30 * SECOND,
2 * MINUTE,
5 * MINUTE,
durations.SECOND,
5 * durations.SECOND,
30 * durations.SECOND,
2 * durations.MINUTE,
5 * durations.MINUTE,
]);
const conflictBackOff = new BackOff([SECOND, 5 * SECOND, 30 * SECOND]);
const conflictBackOff = new BackOff([
durations.SECOND,
5 * durations.SECOND,
30 * durations.SECOND,
]);
function redactStorageID(storageID: string): string {
return storageID.substring(0, 3);
@ -1112,7 +1114,7 @@ async function upload(fromSync = false): Promise<void> {
if (uploadBucket.length >= 3) {
const [firstMostRecentWrite] = uploadBucket;
if (isMoreRecentThan(5 * MINUTE, firstMostRecentWrite)) {
if (isMoreRecentThan(5 * durations.MINUTE, firstMostRecentWrite)) {
throw new Error(
'storageService.uploadManifest: too many writes too soon.'
);

View File

@ -29,6 +29,7 @@ import { arrayBufferToBase64, base64ToArrayBuffer } from '../Crypto';
import { CURRENT_SCHEMA_VERSION } from '../../js/modules/types/message';
import { createBatcher } from '../util/batcher';
import { assert } from '../util/assert';
import * as durations from '../util/durations';
import { cleanDataForIpc } from './cleanDataForIpc';
import { ReactionType } from '../types/Reactions';
import { ConversationColorType, CustomColorType } from '../types/Colors';
@ -82,7 +83,7 @@ if (ipcRenderer && ipcRenderer.setMaxListeners) {
window.log.warn('sql/Client: ipcRenderer is not available!');
}
const DATABASE_UPDATE_TIMEOUT = 2 * 60 * 1000; // two minutes
const DATABASE_UPDATE_TIMEOUT = 2 * durations.MINUTE;
const MIN_TRACE_DURATION = 10;

View File

@ -13,7 +13,6 @@ import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import SQL, { Database, Statement } from 'better-sqlite3';
import pProps from 'p-props';
import * as moment from 'moment';
import { v4 as generateUUID } from 'uuid';
import {
@ -41,6 +40,7 @@ import { dropNull } from '../util/dropNull';
import { isNormalNumber } from '../util/isNormalNumber';
import { isNotNil } from '../util/isNotNil';
import { parseIntOrThrow } from '../util/parseIntOrThrow';
import * as durations from '../util/durations';
import { formatCountForLogging } from '../logging/formatCountForLogging';
import { ConversationColorType, CustomColorType } from '../types/Colors';
import { ProcessGroupCallRingRequestResult } from '../types/Calling';
@ -5965,7 +5965,7 @@ async function processGroupCallRingCancelation(ringId: bigint): Promise<void> {
// This age, in milliseconds, should be longer than any group call ring duration. Beyond
// that, it doesn't really matter what the value is.
const MAX_GROUP_CALL_RING_AGE = moment.duration(30, 'minutes').asMilliseconds();
const MAX_GROUP_CALL_RING_AGE = 30 * durations.MINUTE;
async function cleanExpiredGroupCallRings(): Promise<void> {
const db = getInstance();

View File

@ -1,18 +1,14 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as durations from '../../util/durations';
export type TestExpireTimer = Readonly<{ value: number; label: string }>;
const SECOND = 1;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const WEEK = 7 * DAY;
export const EXPIRE_TIMERS: ReadonlyArray<TestExpireTimer> = [
{ value: 42 * SECOND, label: '42 seconds' },
{ value: 5 * MINUTE, label: '5 minutes' },
{ value: 1 * HOUR, label: '1 hour' },
{ value: 6 * DAY, label: '6 days' },
{ value: 3 * WEEK, label: '3 weeks' },
{ value: 42 * durations.SECOND, label: '42 seconds' },
{ value: 5 * durations.MINUTE, label: '5 minutes' },
{ value: 1 * durations.HOUR, label: '1 hour' },
{ value: 6 * durations.DAY, label: '6 days' },
{ value: 3 * durations.WEEK, label: '3 weeks' },
];

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import * as moment from 'moment';
import * as durations from '../../util/durations';
import {
exponentialBackoffSleepTime,
@ -20,7 +20,7 @@ describe('exponential backoff utilities', () => {
});
it('plateaus at a maximum after 15 attempts', () => {
const maximum = moment.duration(15, 'minutes').asMilliseconds();
const maximum = 15 * durations.MINUTE;
for (let attempt = 16; attempt < 100; attempt += 1) {
assert.strictEqual(exponentialBackoffSleepTime(attempt), maximum);
}
@ -39,8 +39,7 @@ describe('exponential backoff utilities', () => {
it('returns 110 attempts for 1 day', () => {
// This is a test case that is lifted from iOS's codebase.
const oneDay = moment.duration(24, 'hours').asMilliseconds();
assert.strictEqual(exponentialBackoffMaxAttempts(oneDay), 110);
assert.strictEqual(exponentialBackoffMaxAttempts(durations.DAY), 110);
});
});
});

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import * as durations from '../../util/durations';
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
@ -33,12 +34,10 @@ describe('isConversationUnregistered', () => {
});
it('returns false if passed a time more than 6 hours ago', () => {
const oneMinute = 1000 * 60;
const sixHours = 1000 * 60 * 60 * 6;
assert.isFalse(
isConversationUnregistered({
discoveredUnregisteredAt: Date.now() - sixHours - oneMinute,
discoveredUnregisteredAt:
Date.now() - 6 * durations.HOUR - durations.MINUTE,
})
);
assert.isFalse(

View File

@ -8,6 +8,7 @@ import { assert } from 'chai';
import * as sinon from 'sinon';
import { v4 as uuid } from 'uuid';
import Long from 'long';
import * as durations from '../../util/durations';
import * as Bytes from '../../Bytes';
import { typedArrayToArrayBuffer } from '../../Crypto';
import { SenderCertificateMode } from '../../textsecure/OutgoingMessage';
@ -18,7 +19,7 @@ import { SenderCertificateService } from '../../services/senderCertificate';
import SenderCertificate = Proto.SenderCertificate;
describe('SenderCertificateService', () => {
const FIFTEEN_MINUTES = 15 * 60 * 1000;
const FIFTEEN_MINUTES = 15 * durations.MINUTE;
let fakeValidCertificate: SenderCertificate;
let fakeValidCertificateExpiry: number;

View File

@ -14,6 +14,7 @@ import { strictAssert } from '../util/assert';
import { explodePromise } from '../util/explodePromise';
import { BackOff, FIBONACCI_TIMEOUTS } from '../util/BackOff';
import { getUserAgent } from '../util/getUserAgent';
import * as durations from '../util/durations';
import { sleep } from '../util/sleep';
import { SocketStatus } from '../types/SocketStatus';
import * as Errors from '../types/errors';
@ -30,9 +31,9 @@ import { WebAPICredentials, IRequestHandler } from './Types.d';
// TODO: remove once we move away from ArrayBuffers
const FIXMEU8 = Uint8Array;
const TEN_SECONDS = 1000 * 10;
const TEN_SECONDS = 10 * durations.SECOND;
const FIVE_MINUTES = 5 * 60 * 1000;
const FIVE_MINUTES = 5 * durations.MINUTE;
export type SocketManagerOptions = Readonly<{
url: string;

View File

@ -1,12 +1,14 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as durations from '../util/durations';
export default function createTaskWithTimeout<T, Args extends Array<unknown>>(
task: (...args: Args) => Promise<T>,
id: string,
options: { timeout?: number } = {}
): (...args: Args) => Promise<T> {
const timeout = options.timeout || 1000 * 60 * 2; // two minutes
const timeout = options.timeout || 2 * durations.MINUTE;
const errorForStack = new Error('for stack');

View File

@ -30,6 +30,7 @@ import { z } from 'zod';
import Long from 'long';
import { assert, strictAssert } from '../util/assert';
import * as durations from '../util/durations';
import { getUserAgent } from '../util/getUserAgent';
import { toWebSafeBase64 } from '../util/webSafeBase64';
import { SocketStatus } from '../types/SocketStatus';
@ -266,7 +267,7 @@ function _validateResponse(response: any, schema: any) {
return true;
}
const FIVE_MINUTES = 1000 * 60 * 5;
const FIVE_MINUTES = 5 * durations.MINUTE;
type AgentCacheType = {
[name: string]: {

View File

@ -27,6 +27,7 @@ import { connection as WebSocket, IMessage } from 'websocket';
import EventTarget, { EventHandler } from './EventTarget';
import * as durations from '../util/durations';
import { dropNull } from '../util/dropNull';
import { isOlderThan } from '../util/timestamp';
import { strictAssert } from '../util/assert';
@ -34,7 +35,7 @@ import { normalizeNumber } from '../util/normalizeNumber';
import * as Errors from '../types/errors';
import { SignalService as Proto } from '../protobuf';
const THIRTY_SECONDS = 30 * 1000;
const THIRTY_SECONDS = 30 * durations.SECOND;
const MAX_MESSAGE_SIZE = 64 * 1024;
@ -385,7 +386,7 @@ export type KeepAliveOptionsType = {
const KEEPALIVE_INTERVAL_MS = 55000; // 55 seconds + 5 seconds for closing the
// socket above.
const MAX_KEEPALIVE_INTERVAL_MS = 300 * 1000; // 5 minutes
const MAX_KEEPALIVE_INTERVAL_MS = 5 * durations.MINUTE;
class KeepAlive {
private keepAliveTimer: NodeJS.Timeout | undefined;

View File

@ -21,14 +21,13 @@ import {
setUpdateListener,
UpdaterInterface,
} from './common';
import * as durations from '../util/durations';
import { LoggerType } from '../types/Logging';
import { hexToBinary, verifySignature } from './signature';
import { markShouldQuit } from '../../app/window_state';
import { DialogType } from '../types/Dialogs';
const SECOND = 1000;
const MINUTE = SECOND * 60;
const INTERVAL = MINUTE * 30;
const INTERVAL = 30 * durations.MINUTE;
export async function start(
getMainWindow: () => BrowserWindow,

View File

@ -19,6 +19,7 @@ import {
setUpdateListener,
UpdaterInterface,
} from './common';
import * as durations from '../util/durations';
import { LoggerType } from '../types/Logging';
import { hexToBinary, verifySignature } from './signature';
import { markShouldQuit } from '../../app/window_state';
@ -27,9 +28,7 @@ import { DialogType } from '../types/Dialogs';
const readdir = pify(readdirCallback);
const unlink = pify(unlinkCallback);
const SECOND = 1000;
const MINUTE = SECOND * 60;
const INTERVAL = MINUTE * 30;
const INTERVAL = 30 * durations.MINUTE;
let fileName: string;
let version: string;

View File

@ -2,13 +2,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { MessageModel } from '../models/messages';
import * as durations from './durations';
import { map, filter } from './iterables';
import { isNotNil } from './isNotNil';
const SECOND = 1000;
const MINUTE = SECOND * 60;
const FIVE_MINUTES = MINUTE * 5;
const HOUR = MINUTE * 60;
const FIVE_MINUTES = 5 * durations.MINUTE;
type LookupItemType = {
timestamp: number;
@ -121,6 +119,6 @@ export class MessageController {
}
startCleanupInterval(): NodeJS.Timeout | number {
return setInterval(this.cleanup.bind(this), HOUR);
return setInterval(this.cleanup.bind(this), durations.HOUR);
}
}

View File

@ -28,6 +28,7 @@ import * as universalExpireTimer from './universalExpireTimer';
import { PhoneNumberDiscoverability } from './phoneNumberDiscoverability';
import { PhoneNumberSharingMode } from './phoneNumberSharingMode';
import { assert } from './assert';
import * as durations from './durations';
import { isPhoneNumberSharingEnabled } from './isPhoneNumberSharingEnabled';
type ThemeType = 'light' | 'dark' | 'system';
@ -333,7 +334,7 @@ export function createIPCEvents(
isPrimary: () => window.textsecure.storage.user.getDeviceId() === 1,
syncRequest: () =>
new Promise<void>((resolve, reject) => {
const FIVE_MINUTES = 5 * 60 * 60 * 1000;
const FIVE_MINUTES = 5 * durations.MINUTE;
const syncRequest = window.getSyncRequest(FIVE_MINUTES);
syncRequest.addEventListener('success', () => resolve());
syncRequest.addEventListener('timeout', () =>

8
ts/util/durations.ts Normal file
View File

@ -0,0 +1,8 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
export const SECOND = 1000;
export const MINUTE = SECOND * 60;
export const HOUR = MINUTE * 60;
export const DAY = HOUR * 24;
export const WEEK = DAY * 7;

View File

@ -1,10 +1,10 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as moment from 'moment';
import * as durations from './durations';
const BACKOFF_FACTOR = 1.9;
const MAX_BACKOFF = moment.duration(15, 'minutes').asMilliseconds();
const MAX_BACKOFF = 15 * durations.MINUTE;
/**
* For a given attempt, how long should we sleep (in milliseconds)?

View File

@ -1,7 +1,7 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import moment from 'moment';
import * as durations from './durations';
import { LocalizerType } from '../types/Util';
import { getMutedUntilText } from './getMutedUntilText';
import { isMuted } from './isMuted';
@ -32,19 +32,19 @@ export function getMuteOptions(
: []),
{
name: i18n('muteHour'),
value: moment.duration(1, 'hour').as('milliseconds'),
value: durations.HOUR,
},
{
name: i18n('muteEightHours'),
value: moment.duration(8, 'hour').as('milliseconds'),
value: 8 * durations.HOUR,
},
{
name: i18n('muteDay'),
value: moment.duration(1, 'day').as('milliseconds'),
value: durations.DAY,
},
{
name: i18n('muteWeek'),
value: moment.duration(1, 'week').as('milliseconds'),
value: durations.WEEK,
},
{
name: i18n('muteAlways'),