diff --git a/.eslint/rules/type-alias-readonlydeep.js b/.eslint/rules/type-alias-readonlydeep.js new file mode 100644 index 000000000000..1e316eeee440 --- /dev/null +++ b/.eslint/rules/type-alias-readonlydeep.js @@ -0,0 +1,58 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +function isReadOnlyDeep(node, scope) { + if (node.type !== 'TSTypeReference') { + return false; + } + + let reference = scope.references.find(reference => { + return reference.identifier === node.typeName; + }); + + let variable = reference.resolved; + if (variable == null) { + return false; + } + + let defs = variable.defs; + if (defs.length !== 1) { + return false; + } + + let [def] = defs; + + return ( + def.type === 'ImportBinding' && + def.parent.type === 'ImportDeclaration' && + def.parent.source.type === 'Literal' && + def.parent.source.value === 'type-fest' + ); +} + +/** @type {import("eslint").Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + hasSuggestions: false, + fixable: false, + schema: [], + }, + create(context) { + return { + TSTypeAliasDeclaration(node) { + let scope = context.getScope(node); + + if (isReadOnlyDeep(node.typeAnnotation, scope)) { + return; + } + + context.report({ + node: node.id, + message: + 'Type aliases must be wrapped with ReadonlyDeep from type-fest', + }); + }, + }; + }, +}; diff --git a/.eslint/rules/type-alias-readonlydeep.test.js b/.eslint/rules/type-alias-readonlydeep.test.js new file mode 100644 index 000000000000..7f81d1b20afa --- /dev/null +++ b/.eslint/rules/type-alias-readonlydeep.test.js @@ -0,0 +1,79 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +const rule = require('./type-alias-readonlydeep'); +const RuleTester = require('eslint').RuleTester; + +// avoid triggering mocha's global leak detection +require('@typescript-eslint/parser'); + +const ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + }, +}); + +ruleTester.run('type-alias-readonlydeep', rule, { + valid: [ + { + code: `import type { ReadonlyDeep } from "type-fest"; type Foo = ReadonlyDeep<{}>`, + }, + { + code: `import { ReadonlyDeep } from "type-fest"; type Foo = ReadonlyDeep<{}>`, + }, + ], + invalid: [ + { + code: `type Foo = {}`, + errors: [ + { + message: + 'Type aliases must be wrapped with ReadonlyDeep from type-fest', + type: 'Identifier', + }, + ], + }, + { + code: `type Foo = Bar<{}>`, + errors: [ + { + message: + 'Type aliases must be wrapped with ReadonlyDeep from type-fest', + type: 'Identifier', + }, + ], + }, + { + code: `type Foo = ReadonlyDeep<{}>`, + errors: [ + { + message: + 'Type aliases must be wrapped with ReadonlyDeep from type-fest', + type: 'Identifier', + }, + ], + }, + { + code: `interface ReadonlyDeep {}; type Foo = ReadonlyDeep<{}>`, + errors: [ + { + message: + 'Type aliases must be wrapped with ReadonlyDeep from type-fest', + type: 'Identifier', + }, + ], + }, + { + code: `import type { ReadonlyDeep } from "foo"; type Foo = ReadonlyDeep<{}>`, + errors: [ + { + message: + 'Type aliases must be wrapped with ReadonlyDeep from type-fest', + type: 'Identifier', + }, + ], + }, + ], +}); diff --git a/.eslintrc.js b/.eslintrc.js index 0bfbdbf56236..b1bf52c27d2d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -274,6 +274,12 @@ module.exports = { 'react/no-array-index-key': 'off', }, }, + { + files: ['ts/state/ducks/**/*.ts'], + rules: { + 'local-rules/type-alias-readonlydeep': 'error', + }, + }, ], rules: { diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index a05eb2b52d45..024468235909 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -2480,6 +2480,10 @@ Signal Desktop makes use of the following open source projects. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +## type-fest + + License: (MIT OR CC0-1.0) + ## typeface-inter Copyright (c) 2016-2020 The Inter Project Authors. diff --git a/eslint-local-rules.js b/eslint-local-rules.js index ca387946eddd..619440009a5b 100644 --- a/eslint-local-rules.js +++ b/eslint-local-rules.js @@ -4,4 +4,5 @@ module.exports = { 'valid-i18n-keys': require('./.eslint/rules/valid-i18n-keys'), + 'type-alias-readonlydeep': require('./.eslint/rules/type-alias-readonlydeep'), }; diff --git a/package.json b/package.json index c226d3466acc..9c535380ab4f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "test-release": "node ts/scripts/test-release.js", "test-node": "electron-mocha --timeout 10000 --file test/setup-test-node.js --recursive test/modules ts/test-node ts/test-both", "test-mock": "mocha ts/test-mock/**/*_test.js", - "test-eslint": "mocha .eslint/rules/**/*.test.js", + "test-eslint": "mocha .eslint/rules/**/*.test.js --ignore-leaks", "test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/modules ts/test-node ts/test-both", "test-lint-intl": "ts-node ./build/intl-linter/linter.ts --test", "eslint": "eslint --cache . --max-warnings 0", @@ -174,6 +174,7 @@ "semver": "5.4.1", "split2": "4.0.0", "testcheck": "1.0.0-rc.2", + "type-fest": "3.5.0", "typeface-inter": "3.18.1", "uuid": "3.3.2", "websocket": "1.0.34", diff --git a/ts/components/Lightbox.tsx b/ts/components/Lightbox.tsx index 89759fcfb082..870e15009674 100644 --- a/ts/components/Lightbox.tsx +++ b/ts/components/Lightbox.tsx @@ -9,6 +9,7 @@ import { createPortal } from 'react-dom'; import { noop } from 'lodash'; import { useSpring, animated, to } from '@react-spring/web'; +import type { ReadonlyDeep } from 'type-fest'; import type { ConversationType, SaveAttachmentActionCreatorType, @@ -29,7 +30,7 @@ export type PropsType = { getConversation?: (id: string) => ConversationType; i18n: LocalizerType; isViewOnce?: boolean; - media: ReadonlyArray; + media: ReadonlyArray>; saveAttachment: SaveAttachmentActionCreatorType; selectedIndex?: number; toggleForwardMessageModal: (messageId: string) => unknown; @@ -682,7 +683,7 @@ function LightboxHeader({ }: { getConversation: (id: string) => ConversationType; i18n: LocalizerType; - message: MediaItemMessageType; + message: ReadonlyDeep; }): JSX.Element { const conversation = getConversation(message.conversationId); diff --git a/ts/components/conversation/Timeline.tsx b/ts/components/conversation/Timeline.tsx index 4144e2775c00..812ccac72df5 100644 --- a/ts/components/conversation/Timeline.tsx +++ b/ts/components/conversation/Timeline.tsx @@ -7,6 +7,7 @@ import type { ReactChild, ReactNode, RefObject } from 'react'; import React from 'react'; import Measure from 'react-measure'; +import type { ReadonlyDeep } from 'type-fest'; import { ScrollDownButton } from './ScrollDownButton'; import type { LocalizerType, ThemeType } from '../../types/Util'; @@ -49,7 +50,7 @@ const MIN_ROW_HEIGHT = 18; const SCROLL_DOWN_BUTTON_THRESHOLD = 8; const LOAD_NEWER_THRESHOLD = 5; -export type WarningType = +export type WarningType = ReadonlyDeep< | { type: ContactSpoofingType.DirectConversationWithSameTitle; safeConversation: ConversationType; @@ -58,7 +59,8 @@ export type WarningType = type: ContactSpoofingType.MultipleGroupMembersWithSameTitle; acknowledgedGroupNameCollisions: GroupNameCollisionsWithIdsByTitle; groupNameCollisions: GroupNameCollisionsWithIdsByTitle; - }; + } +>; export type ContactSpoofingReviewPropType = | { @@ -139,7 +141,7 @@ export type PropsActionsType = { // From Backbone acknowledgeGroupMemberNameCollisions: ( conversationId: string, - groupNameCollisions: Readonly + groupNameCollisions: ReadonlyDeep ) => void; clearInvitedUuidsForNewlyCreatedGroup: () => void; clearSelectedMessage: () => unknown; diff --git a/ts/components/conversation/conversation-details/ConversationDetailsMediaList.tsx b/ts/components/conversation/conversation-details/ConversationDetailsMediaList.tsx index fd3ba6e2b1b2..85c4b6728523 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsMediaList.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsMediaList.tsx @@ -3,6 +3,7 @@ import React from 'react'; +import type { ReadonlyDeep } from 'type-fest'; import type { LocalizerType } from '../../../types/Util'; import type { MediaItemType } from '../../../types/MediaItem'; @@ -19,7 +20,7 @@ export type Props = { showAllMedia: () => void; showLightboxWithMedia: ( selectedAttachmentPath: string | undefined, - media: ReadonlyArray + media: ReadonlyArray> ) => void; }; diff --git a/ts/components/conversation/media-gallery/MediaGridItem.tsx b/ts/components/conversation/media-gallery/MediaGridItem.tsx index 95f4a43d59b7..cecafd6eeb6b 100644 --- a/ts/components/conversation/media-gallery/MediaGridItem.tsx +++ b/ts/components/conversation/media-gallery/MediaGridItem.tsx @@ -4,6 +4,7 @@ import React from 'react'; import classNames from 'classnames'; +import type { ReadonlyDeep } from 'type-fest'; import { isImageTypeSupported, isVideoTypeSupported, @@ -13,7 +14,7 @@ import type { MediaItemType } from '../../../types/MediaItem'; import * as log from '../../../logging/log'; export type Props = { - mediaItem: MediaItemType; + mediaItem: ReadonlyDeep; onClick?: () => void; i18n: LocalizerType; }; diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index caf254818f45..3006885d2470 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -14,6 +14,7 @@ import { batch as batchDispatch } from 'react-redux'; import { v4 as generateGuid } from 'uuid'; import PQueue from 'p-queue'; +import type { ReadonlyDeep } from 'type-fest'; import type { ConversationAttributesType, ConversationLastProfileType, @@ -5512,7 +5513,7 @@ export class ConversationModel extends window.Backbone } acknowledgeGroupMemberNameCollisions( - groupNameCollisions: Readonly + groupNameCollisions: ReadonlyDeep ): void { this.set('acknowledgedGroupNameCollisions', groupNameCollisions); window.Signal.Data.updateConversation(this.attributes); diff --git a/ts/state/ducks/accounts.ts b/ts/state/ducks/accounts.ts index bf61e3d8e4a0..9755c4541b18 100644 --- a/ts/state/ducks/accounts.ts +++ b/ts/state/ducks/accounts.ts @@ -3,6 +3,7 @@ import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import * as Errors from '../../types/errors'; import * as log from '../../logging/log'; @@ -16,21 +17,21 @@ import type { NoopActionType } from './noop'; // State -export type AccountsStateType = { +export type AccountsStateType = ReadonlyDeep<{ accounts: Record; -}; +}>; // Actions -type AccountUpdateActionType = { +type AccountUpdateActionType = ReadonlyDeep<{ type: 'accounts/UPDATE'; payload: { phoneNumber: string; uuid?: UUIDStringType; }; -}; +}>; -export type AccountsActionType = AccountUpdateActionType; +export type AccountsActionType = ReadonlyDeep; // Action Creators diff --git a/ts/state/ducks/app.ts b/ts/state/ducks/app.ts index b84ea5a16c2a..4824259ccf7d 100644 --- a/ts/state/ducks/app.ts +++ b/ts/state/ducks/app.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { StateType as RootStateType } from '../reducer'; import * as log from '../../logging/log'; @@ -14,10 +15,10 @@ export enum AppViewType { Standalone = 'Standalone', } -export type AppStateType = { +export type AppStateType = ReadonlyDeep<{ appView: AppViewType; hasInitialLoadCompleted: boolean; -}; +}>; // Actions @@ -26,27 +27,28 @@ const OPEN_INBOX = 'app/OPEN_INBOX'; const OPEN_INSTALLER = 'app/OPEN_INSTALLER'; const OPEN_STANDALONE = 'app/OPEN_STANDALONE'; -type InitialLoadCompleteActionType = { +type InitialLoadCompleteActionType = ReadonlyDeep<{ type: typeof INITIAL_LOAD_COMPLETE; -}; +}>; -type OpenInboxActionType = { +type OpenInboxActionType = ReadonlyDeep<{ type: typeof OPEN_INBOX; -}; +}>; -type OpenInstallerActionType = { +type OpenInstallerActionType = ReadonlyDeep<{ type: typeof OPEN_INSTALLER; -}; +}>; -type OpenStandaloneActionType = { +type OpenStandaloneActionType = ReadonlyDeep<{ type: typeof OPEN_STANDALONE; -}; +}>; -export type AppActionType = +export type AppActionType = ReadonlyDeep< | InitialLoadCompleteActionType | OpenInboxActionType | OpenInstallerActionType - | OpenStandaloneActionType; + | OpenStandaloneActionType +>; export const actions = { initialLoadComplete, diff --git a/ts/state/ducks/audioPlayer.ts b/ts/state/ducks/audioPlayer.ts index 4f7fdd7448ca..1b022b5cd099 100644 --- a/ts/state/ducks/audioPlayer.ts +++ b/ts/state/ducks/audioPlayer.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; import { Sound } from '../../util/Sound'; @@ -35,25 +36,25 @@ import { getMessagePropStatus } from '../selectors/message'; // State -export type ActiveAudioPlayerStateType = { - readonly playing: boolean; - readonly currentTime: number; - readonly playbackRate: number; - readonly duration: number; -}; +export type ActiveAudioPlayerStateType = ReadonlyDeep<{ + playing: boolean; + currentTime: number; + playbackRate: number; + duration: number; +}>; -export type AudioPlayerStateType = { - readonly active: +export type AudioPlayerStateType = ReadonlyDeep<{ + active: | (ActiveAudioPlayerStateType & { id: string; context: string }) | undefined; -}; +}>; // Actions /** * Sets the current "active" message audio for a particular rendering "context" */ -export type SetMessageAudioAction = { +export type SetMessageAudioAction = ReadonlyDeep<{ type: 'audioPlayer/SET_MESSAGE_AUDIO'; payload: | { @@ -63,39 +64,40 @@ export type SetMessageAudioAction = { duration: number; } | undefined; -}; +}>; -type SetPlaybackRate = { +type SetPlaybackRate = ReadonlyDeep<{ type: 'audioPlayer/SET_PLAYBACK_RATE'; payload: number; -}; +}>; -type SetIsPlayingAction = { +type SetIsPlayingAction = ReadonlyDeep<{ type: 'audioPlayer/SET_IS_PLAYING'; payload: boolean; -}; +}>; -type CurrentTimeUpdated = { +type CurrentTimeUpdated = ReadonlyDeep<{ type: 'audioPlayer/CURRENT_TIME_UPDATED'; payload: number; -}; +}>; -type MessageAudioEnded = { +type MessageAudioEnded = ReadonlyDeep<{ type: 'audioPlayer/MESSAGE_AUDIO_ENDED'; -}; +}>; -type DurationChanged = { +type DurationChanged = ReadonlyDeep<{ type: 'audioPlayer/DURATION_CHANGED'; payload: number; -}; +}>; -type AudioPlayerActionType = +type AudioPlayerActionType = ReadonlyDeep< | SetMessageAudioAction | SetIsPlayingAction | SetPlaybackRate | MessageAudioEnded | CurrentTimeUpdated - | DurationChanged; + | DurationChanged +>; // Action Creators diff --git a/ts/state/ducks/audioRecorder.ts b/ts/state/ducks/audioRecorder.ts index c1151c689413..6b66e3eef25c 100644 --- a/ts/state/ducks/audioRecorder.ts +++ b/ts/state/ducks/audioRecorder.ts @@ -3,6 +3,7 @@ import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import * as log from '../../logging/log'; import type { InMemoryAttachmentDraftType } from '../../types/Attachment'; import { SignalService as Proto } from '../../protobuf'; @@ -28,10 +29,10 @@ export enum RecordingState { Idle = 'idle', } -export type AudioPlayerStateType = { - readonly recordingState: RecordingState; - readonly errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType; -}; +export type AudioPlayerStateType = ReadonlyDeep<{ + recordingState: RecordingState; + errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType; +}>; // Actions @@ -41,33 +42,34 @@ const ERROR_RECORDING = 'audioRecorder/ERROR_RECORDING'; const NOW_RECORDING = 'audioRecorder/NOW_RECORDING'; const START_RECORDING = 'audioRecorder/START_RECORDING'; -type CancelRecordingAction = { +type CancelRecordingAction = ReadonlyDeep<{ type: typeof CANCEL_RECORDING; payload: undefined; -}; -type CompleteRecordingAction = { +}>; +type CompleteRecordingAction = ReadonlyDeep<{ type: typeof COMPLETE_RECORDING; payload: undefined; -}; -type ErrorRecordingAction = { +}>; +type ErrorRecordingAction = ReadonlyDeep<{ type: typeof ERROR_RECORDING; payload: ErrorDialogAudioRecorderType; -}; -type StartRecordingAction = { +}>; +type StartRecordingAction = ReadonlyDeep<{ type: typeof START_RECORDING; payload: undefined; -}; -type NowRecordingAction = { +}>; +type NowRecordingAction = ReadonlyDeep<{ type: typeof NOW_RECORDING; payload: undefined; -}; +}>; -type AudioPlayerActionType = +type AudioPlayerActionType = ReadonlyDeep< | CancelRecordingAction | CompleteRecordingAction | ErrorRecordingAction | NowRecordingAction - | StartRecordingAction; + | StartRecordingAction +>; // Action Creators diff --git a/ts/state/ducks/badges.ts b/ts/state/ducks/badges.ts index 19a4e08cf3c7..db4156d0888a 100644 --- a/ts/state/ducks/badges.ts +++ b/ts/state/ducks/badges.ts @@ -3,6 +3,7 @@ import type { ThunkAction } from 'redux-thunk'; import { mapValues } from 'lodash'; +import type { ReadonlyDeep } from 'type-fest'; import type { StateType as RootStateType } from '../reducer'; import type { BadgeType, BadgeImageType } from '../../badges/types'; import { getOwn } from '../../util/getOwn'; @@ -22,27 +23,27 @@ import { badgeImageFileDownloader } from '../../badges/badgeImageFileDownloader' // State -export type BadgesStateType = { +export type BadgesStateType = ReadonlyDeep<{ byId: Record; -}; +}>; // Actions const IMAGE_FILE_DOWNLOADED = 'badges/IMAGE_FILE_DOWNLOADED'; const UPDATE_OR_CREATE = 'badges/UPDATE_OR_CREATE'; -type ImageFileDownloadedActionType = { +type ImageFileDownloadedActionType = ReadonlyDeep<{ type: typeof IMAGE_FILE_DOWNLOADED; payload: { url: string; localPath: string; }; -}; +}>; -type UpdateOrCreateActionType = { +type UpdateOrCreateActionType = ReadonlyDeep<{ type: typeof UPDATE_OR_CREATE; - payload: ReadonlyArray; -}; + payload: Array; +}>; // Action creators diff --git a/ts/state/ducks/calling.ts b/ts/state/ducks/calling.ts index b389d57d203f..bd9967435dff 100644 --- a/ts/state/ducks/calling.ts +++ b/ts/state/ducks/calling.ts @@ -9,6 +9,7 @@ import { openSystemPreferences, } from 'mac-screen-capture-permissions'; import { has, omit } from 'lodash'; +import type { ReadonlyDeep } from 'type-fest'; import { getOwn } from '../../util/getOwn'; import * as Errors from '../../types/errors'; import { getPlatform } from '../selectors/user'; @@ -57,14 +58,15 @@ import MessageSender from '../../textsecure/SendMessage'; // State -export type GroupCallPeekInfoType = { +export type GroupCallPeekInfoType = ReadonlyDeep<{ uuids: Array; creatorUuid?: UUIDStringType; eraId?: string; maxDevices: number; deviceCount: number; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type GroupCallParticipantInfoType = { uuid: UUIDStringType; demuxId: number; @@ -76,6 +78,7 @@ export type GroupCallParticipantInfoType = { videoAspectRatio: number; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type DirectCallStateType = { callMode: CallMode.Direct; conversationId: string; @@ -87,7 +90,7 @@ export type DirectCallStateType = { hasRemoteVideo?: boolean; }; -type GroupCallRingStateType = +type GroupCallRingStateType = ReadonlyDeep< | { ringId?: undefined; ringerUuid?: undefined; @@ -95,8 +98,10 @@ type GroupCallRingStateType = | { ringId: bigint; ringerUuid: UUIDStringType; - }; + } +>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type GroupCallStateType = { callMode: CallMode.Group; conversationId: string; @@ -107,6 +112,7 @@ export type GroupCallStateType = { remoteAudioLevels?: Map; } & GroupCallRingStateType; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type ActiveCallStateType = { conversationId: string; hasLocalAudio: boolean; @@ -124,21 +130,23 @@ export type ActiveCallStateType = { showParticipantsList: boolean; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type CallsByConversationType = { [conversationId: string]: DirectCallStateType | GroupCallStateType; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type CallingStateType = MediaDeviceSettings & { callsByConversation: CallsByConversationType; activeCallState?: ActiveCallStateType; }; -export type AcceptCallType = { +export type AcceptCallType = ReadonlyDeep<{ conversationId: string; asVideoCall: boolean; -}; +}>; -export type CallStateChangeType = { +export type CallStateChangeType = ReadonlyDeep<{ remoteUserId: string; // TODO: Remove callId: string; // TODO: Remove conversationId: string; @@ -148,21 +156,22 @@ export type CallStateChangeType = { isIncoming: boolean; isVideoCall: boolean; title: string; -}; +}>; -export type CancelCallType = { +export type CancelCallType = ReadonlyDeep<{ conversationId: string; -}; +}>; -type CancelIncomingGroupCallRingType = { +type CancelIncomingGroupCallRingType = ReadonlyDeep<{ conversationId: string; ringId: bigint; -}; +}>; -export type DeclineCallType = { +export type DeclineCallType = ReadonlyDeep<{ conversationId: string; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type GroupCallStateChangeArgumentType = { connectionState: GroupCallConnectionState; conversationId: string; @@ -173,77 +182,81 @@ type GroupCallStateChangeArgumentType = { remoteParticipants: Array; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type GroupCallStateChangeActionPayloadType = GroupCallStateChangeArgumentType & { ourUuid: UUIDStringType; }; -type HangUpActionPayloadType = { +type HangUpActionPayloadType = ReadonlyDeep<{ conversationId: string; -}; +}>; -type KeyChangedType = { +type KeyChangedType = ReadonlyDeep<{ uuid: UUIDStringType; -}; +}>; -export type KeyChangeOkType = { +export type KeyChangeOkType = ReadonlyDeep<{ conversationId: string; -}; +}>; -export type IncomingDirectCallType = { +export type IncomingDirectCallType = ReadonlyDeep<{ conversationId: string; isVideoCall: boolean; -}; +}>; -type IncomingGroupCallType = { +type IncomingGroupCallType = ReadonlyDeep<{ conversationId: string; ringId: bigint; ringerUuid: UUIDStringType; -}; +}>; -type PeekNotConnectedGroupCallType = { +type PeekNotConnectedGroupCallType = ReadonlyDeep<{ conversationId: string; -}; +}>; -type StartDirectCallType = { +type StartDirectCallType = ReadonlyDeep<{ conversationId: string; hasLocalAudio: boolean; hasLocalVideo: boolean; -}; +}>; -export type StartCallType = StartDirectCallType & { - callMode: CallMode.Direct | CallMode.Group; -}; +export type StartCallType = ReadonlyDeep< + StartDirectCallType & { + callMode: CallMode.Direct | CallMode.Group; + } +>; -export type RemoteVideoChangeType = { +export type RemoteVideoChangeType = ReadonlyDeep<{ conversationId: string; hasVideo: boolean; -}; +}>; -type RemoteSharingScreenChangeType = { +type RemoteSharingScreenChangeType = ReadonlyDeep<{ conversationId: string; isSharingScreen: boolean; -}; +}>; -export type SetLocalAudioType = { +export type SetLocalAudioType = ReadonlyDeep<{ enabled: boolean; -}; +}>; -export type SetLocalVideoType = { +export type SetLocalVideoType = ReadonlyDeep<{ enabled: boolean; -}; +}>; -export type SetGroupCallVideoRequestType = { +export type SetGroupCallVideoRequestType = ReadonlyDeep<{ conversationId: string; resolutions: Array; speakerHeight: number; -}; +}>; -export type StartCallingLobbyType = { +export type StartCallingLobbyType = ReadonlyDeep<{ conversationId: string; isVideoCall: boolean; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type StartCallingLobbyPayloadType = | { callMode: CallMode.Direct; @@ -263,10 +276,12 @@ type StartCallingLobbyPayloadType = remoteParticipants: Array; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type SetLocalPreviewType = { element: React.RefObject | undefined; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type SetRendererCanvasType = { element: React.RefObject | undefined; }; @@ -441,76 +456,79 @@ const TOGGLE_SPEAKER_VIEW = 'calling/TOGGLE_SPEAKER_VIEW'; const SWITCH_TO_PRESENTATION_VIEW = 'calling/SWITCH_TO_PRESENTATION_VIEW'; const SWITCH_FROM_PRESENTATION_VIEW = 'calling/SWITCH_FROM_PRESENTATION_VIEW'; -type AcceptCallPendingActionType = { +type AcceptCallPendingActionType = ReadonlyDeep<{ type: 'calling/ACCEPT_CALL_PENDING'; payload: AcceptCallType; -}; +}>; -type CancelCallActionType = { +type CancelCallActionType = ReadonlyDeep<{ type: 'calling/CANCEL_CALL'; -}; +}>; -type CancelIncomingGroupCallRingActionType = { +type CancelIncomingGroupCallRingActionType = ReadonlyDeep<{ type: 'calling/CANCEL_INCOMING_GROUP_CALL_RING'; payload: CancelIncomingGroupCallRingType; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type StartCallingLobbyActionType = { type: 'calling/START_CALLING_LOBBY'; payload: StartCallingLobbyPayloadType; }; -type CallStateChangeFulfilledActionType = { +type CallStateChangeFulfilledActionType = ReadonlyDeep<{ type: 'calling/CALL_STATE_CHANGE_FULFILLED'; payload: CallStateChangeType; -}; +}>; -type ChangeIODeviceFulfilledActionType = { +type ChangeIODeviceFulfilledActionType = ReadonlyDeep<{ type: 'calling/CHANGE_IO_DEVICE_FULFILLED'; payload: ChangeIODevicePayloadType; -}; +}>; -type CloseNeedPermissionScreenActionType = { +type CloseNeedPermissionScreenActionType = ReadonlyDeep<{ type: 'calling/CLOSE_NEED_PERMISSION_SCREEN'; payload: null; -}; +}>; -type DeclineCallActionType = { +type DeclineCallActionType = ReadonlyDeep<{ type: 'calling/DECLINE_DIRECT_CALL'; payload: DeclineCallType; -}; +}>; -type GroupCallAudioLevelsChangeActionPayloadType = Readonly<{ +type GroupCallAudioLevelsChangeActionPayloadType = ReadonlyDeep<{ conversationId: string; localAudioLevel: number; remoteDeviceStates: ReadonlyArray<{ audioLevel: number; demuxId: number }>; }>; -type GroupCallAudioLevelsChangeActionType = { +type GroupCallAudioLevelsChangeActionType = ReadonlyDeep<{ type: 'calling/GROUP_CALL_AUDIO_LEVELS_CHANGE'; payload: GroupCallAudioLevelsChangeActionPayloadType; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type GroupCallStateChangeActionType = { type: 'calling/GROUP_CALL_STATE_CHANGE'; payload: GroupCallStateChangeActionPayloadType; }; -type HangUpActionType = { +type HangUpActionType = ReadonlyDeep<{ type: 'calling/HANG_UP'; payload: HangUpActionPayloadType; -}; +}>; -type IncomingDirectCallActionType = { +type IncomingDirectCallActionType = ReadonlyDeep<{ type: 'calling/INCOMING_DIRECT_CALL'; payload: IncomingDirectCallType; -}; +}>; -type IncomingGroupCallActionType = { +type IncomingGroupCallActionType = ReadonlyDeep<{ type: 'calling/INCOMING_GROUP_CALL'; payload: IncomingGroupCallType; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type KeyChangedActionType = { type: 'calling/MARK_CALL_UNTRUSTED'; payload: { @@ -518,101 +536,104 @@ type KeyChangedActionType = { }; }; -type KeyChangeOkActionType = { +type KeyChangeOkActionType = ReadonlyDeep<{ type: 'calling/MARK_CALL_TRUSTED'; payload: null; -}; +}>; -type OutgoingCallActionType = { +type OutgoingCallActionType = ReadonlyDeep<{ type: 'calling/OUTGOING_CALL'; payload: StartDirectCallType; -}; +}>; -export type PeekGroupCallFulfilledActionType = { +export type PeekGroupCallFulfilledActionType = ReadonlyDeep<{ type: 'calling/PEEK_GROUP_CALL_FULFILLED'; payload: { conversationId: string; peekInfo: GroupCallPeekInfoType; }; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type RefreshIODevicesActionType = { type: 'calling/REFRESH_IO_DEVICES'; payload: MediaDeviceSettings; }; -type RemoteSharingScreenChangeActionType = { +type RemoteSharingScreenChangeActionType = ReadonlyDeep<{ type: 'calling/REMOTE_SHARING_SCREEN_CHANGE'; payload: RemoteSharingScreenChangeType; -}; +}>; -type RemoteVideoChangeActionType = { +type RemoteVideoChangeActionType = ReadonlyDeep<{ type: 'calling/REMOTE_VIDEO_CHANGE'; payload: RemoteVideoChangeType; -}; +}>; -type ReturnToActiveCallActionType = { +type ReturnToActiveCallActionType = ReadonlyDeep<{ type: 'calling/RETURN_TO_ACTIVE_CALL'; -}; +}>; -type SetLocalAudioActionType = { +type SetLocalAudioActionType = ReadonlyDeep<{ type: 'calling/SET_LOCAL_AUDIO_FULFILLED'; payload: SetLocalAudioType; -}; +}>; -type SetLocalVideoFulfilledActionType = { +type SetLocalVideoFulfilledActionType = ReadonlyDeep<{ type: 'calling/SET_LOCAL_VIDEO_FULFILLED'; payload: SetLocalVideoType; -}; +}>; -type SetPresentingFulfilledActionType = { +type SetPresentingFulfilledActionType = ReadonlyDeep<{ type: 'calling/SET_PRESENTING'; payload?: PresentedSource; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type SetPresentingSourcesActionType = { type: 'calling/SET_PRESENTING_SOURCES'; payload: Array; }; -type SetOutgoingRingActionType = { +type SetOutgoingRingActionType = ReadonlyDeep<{ type: 'calling/SET_OUTGOING_RING'; payload: boolean; -}; +}>; -type StartDirectCallActionType = { +type StartDirectCallActionType = ReadonlyDeep<{ type: 'calling/START_DIRECT_CALL'; payload: StartDirectCallType; -}; +}>; -type ToggleNeedsScreenRecordingPermissionsActionType = { +type ToggleNeedsScreenRecordingPermissionsActionType = ReadonlyDeep<{ type: 'calling/TOGGLE_NEEDS_SCREEN_RECORDING_PERMISSIONS'; -}; +}>; -type ToggleParticipantsActionType = { +type ToggleParticipantsActionType = ReadonlyDeep<{ type: 'calling/TOGGLE_PARTICIPANTS'; -}; +}>; -type TogglePipActionType = { +type TogglePipActionType = ReadonlyDeep<{ type: 'calling/TOGGLE_PIP'; -}; +}>; -type ToggleSettingsActionType = { +type ToggleSettingsActionType = ReadonlyDeep<{ type: 'calling/TOGGLE_SETTINGS'; -}; +}>; -type ToggleSpeakerViewActionType = { +type ToggleSpeakerViewActionType = ReadonlyDeep<{ type: 'calling/TOGGLE_SPEAKER_VIEW'; -}; +}>; -type SwitchToPresentationViewActionType = { +type SwitchToPresentationViewActionType = ReadonlyDeep<{ type: 'calling/SWITCH_TO_PRESENTATION_VIEW'; -}; +}>; -type SwitchFromPresentationViewActionType = { +type SwitchFromPresentationViewActionType = ReadonlyDeep<{ type: 'calling/SWITCH_FROM_PRESENTATION_VIEW'; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type CallingActionType = | AcceptCallPendingActionType | CancelCallActionType @@ -1545,7 +1566,7 @@ export const actions = { toggleSpeakerView, }; -export type ActionsType = typeof actions; +export type ActionsType = ReadonlyDeep; // Reducer diff --git a/ts/state/ducks/composer.ts b/ts/state/ducks/composer.ts index 3c5b87c52bfc..0f76a238425d 100644 --- a/ts/state/ducks/composer.ts +++ b/ts/state/ducks/composer.ts @@ -6,6 +6,7 @@ import path from 'path'; import { debounce } from 'lodash'; import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { AddLinkPreviewActionType, RemoveLinkPreviewActionType, @@ -86,7 +87,7 @@ import { drop } from '../../util/drop'; import { strictAssert } from '../../util/assert'; // State - +// eslint-disable-next-line local-rules/type-alias-readonlydeep type ComposerStateByConversationType = { attachments: ReadonlyArray; focusCounter: number; @@ -98,11 +99,13 @@ type ComposerStateByConversationType = { shouldSendHighQualityAttachments?: boolean; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type QuotedMessageType = Pick< MessageAttributesType, 'conversationId' | 'quote' >; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type ComposerStateType = { conversations: Record; }; @@ -134,14 +137,15 @@ const SET_HIGH_QUALITY_SETTING = 'composer/SET_HIGH_QUALITY_SETTING'; const SET_QUOTED_MESSAGE = 'composer/SET_QUOTED_MESSAGE'; const SET_COMPOSER_DISABLED = 'composer/SET_COMPOSER_DISABLED'; -type AddPendingAttachmentActionType = { +type AddPendingAttachmentActionType = ReadonlyDeep<{ type: typeof ADD_PENDING_ATTACHMENT; payload: { conversationId: string; attachment: AttachmentDraftType; }; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type ReplaceAttachmentsActionType = { type: typeof REPLACE_ATTACHMENTS; payload: { @@ -150,36 +154,37 @@ export type ReplaceAttachmentsActionType = { }; }; -export type ResetComposerActionType = { +export type ResetComposerActionType = ReadonlyDeep<{ type: typeof RESET_COMPOSER; payload: { conversationId: string; }; -}; +}>; -type SetComposerDisabledStateActionType = { +type SetComposerDisabledStateActionType = ReadonlyDeep<{ type: typeof SET_COMPOSER_DISABLED; payload: { conversationId: string; value: boolean; }; -}; +}>; -export type SetFocusActionType = { +export type SetFocusActionType = ReadonlyDeep<{ type: typeof SET_FOCUS; payload: { conversationId: string; }; -}; +}>; -type SetHighQualitySettingActionType = { +type SetHighQualitySettingActionType = ReadonlyDeep<{ type: typeof SET_HIGH_QUALITY_SETTING; payload: { conversationId: string; value: boolean; }; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type SetQuotedMessageActionType = { type: typeof SET_QUOTED_MESSAGE; payload: { @@ -188,6 +193,7 @@ export type SetQuotedMessageActionType = { }; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type ComposerActionType = | AddLinkPreviewActionType | AddPendingAttachmentActionType diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index cf97b790ce8d..1ee05a319dac 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -12,6 +12,7 @@ import { without, } from 'lodash'; +import type { ReadonlyDeep } from 'type-fest'; import type { AttachmentType } from '../../types/Attachment'; import type { StateType as RootStateType } from '../reducer'; import * as groups from '../../groups'; @@ -152,27 +153,31 @@ import { // State -export type DBConversationType = { +export type DBConversationType = ReadonlyDeep<{ id: string; activeAt?: number; lastMessage?: string | null; type: string; -}; +}>; export const InteractionModes = ['mouse', 'keyboard'] as const; -export type InteractionModeType = typeof InteractionModes[number]; +export type InteractionModeType = ReadonlyDeep; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type MessageType = MessageAttributesType & { interactionType?: InteractionModeType; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type MessageWithUIFieldsType = MessageAttributesType & { displayLimit?: number; }; export const ConversationTypes = ['direct', 'group'] as const; -export type ConversationTypeType = typeof ConversationTypes[number]; +export type ConversationTypeType = ReadonlyDeep< + typeof ConversationTypes[number] +>; -export type LastMessageType = Readonly< +export type LastMessageType = ReadonlyDeep< | { status?: LastMessageStatus; text: string; @@ -182,154 +187,161 @@ export type LastMessageType = Readonly< | { deletedForEveryone: true } >; -export type ConversationType = { - id: string; - uuid?: UUIDStringType; - pni?: UUIDStringType; - e164?: string; - name?: string; - systemGivenName?: string; - systemFamilyName?: string; - familyName?: string; - firstName?: string; - profileName?: string; - username?: string; - about?: string; - aboutText?: string; - aboutEmoji?: string; - avatars?: ReadonlyArray; - avatarPath?: string; - avatarHash?: string; - profileAvatarPath?: string; - unblurredAvatarPath?: string; - areWeAdmin?: boolean; - areWePending?: boolean; - areWePendingApproval?: boolean; - canChangeTimer?: boolean; - canEditGroupInfo?: boolean; - canAddNewMembers?: boolean; - color?: AvatarColorType; - conversationColor?: ConversationColorType; - customColor?: CustomColorType; - customColorId?: string; - discoveredUnregisteredAt?: number; - hideStory?: boolean; - isArchived?: boolean; - isBlocked?: boolean; - isGroupV1AndDisabled?: boolean; - isPinned?: boolean; - isUntrusted?: boolean; - isVerified?: boolean; - activeAt?: number; - timestamp?: number; - inboxPosition?: number; - left?: boolean; - lastMessage?: LastMessageType; - markedUnread?: boolean; - phoneNumber?: string; - membersCount?: number; - hasMessages?: boolean; - accessControlAddFromInviteLink?: number; - accessControlAttributes?: number; - accessControlMembers?: number; - announcementsOnly?: boolean; - announcementsOnlyReady?: boolean; - expireTimer?: DurationInSeconds; - memberships?: ReadonlyArray<{ - uuid: UUIDStringType; - isAdmin: boolean; - }>; - pendingMemberships?: ReadonlyArray<{ - uuid: UUIDStringType; - addedByUserId?: UUIDStringType; - }>; - pendingApprovalMemberships?: ReadonlyArray<{ - uuid: UUIDStringType; - }>; - bannedMemberships?: ReadonlyArray; - muteExpiresAt?: number; - dontNotifyForMentionsIfMuted?: boolean; - isMe: boolean; - lastUpdated?: number; - // This is used by the CompositionInput for @mentions - sortedGroupMembers?: ReadonlyArray; - title: string; - titleNoDefault?: string; - searchableTitle?: string; - unreadCount?: number; - isSelected?: boolean; - isFetchingUUID?: boolean; - typingContactId?: string; - recentMediaItems?: ReadonlyArray; - profileSharing?: boolean; +export type ConversationType = ReadonlyDeep< + { + id: string; + uuid?: UUIDStringType; + pni?: UUIDStringType; + e164?: string; + name?: string; + systemGivenName?: string; + systemFamilyName?: string; + familyName?: string; + firstName?: string; + profileName?: string; + username?: string; + about?: string; + aboutText?: string; + aboutEmoji?: string; + avatars?: ReadonlyArray; + avatarPath?: string; + avatarHash?: string; + profileAvatarPath?: string; + unblurredAvatarPath?: string; + areWeAdmin?: boolean; + areWePending?: boolean; + areWePendingApproval?: boolean; + canChangeTimer?: boolean; + canEditGroupInfo?: boolean; + canAddNewMembers?: boolean; + color?: AvatarColorType; + conversationColor?: ConversationColorType; + customColor?: CustomColorType; + customColorId?: string; + discoveredUnregisteredAt?: number; + hideStory?: boolean; + isArchived?: boolean; + isBlocked?: boolean; + isGroupV1AndDisabled?: boolean; + isPinned?: boolean; + isUntrusted?: boolean; + isVerified?: boolean; + activeAt?: number; + timestamp?: number; + inboxPosition?: number; + left?: boolean; + lastMessage?: LastMessageType; + markedUnread?: boolean; + phoneNumber?: string; + membersCount?: number; + hasMessages?: boolean; + accessControlAddFromInviteLink?: number; + accessControlAttributes?: number; + accessControlMembers?: number; + announcementsOnly?: boolean; + announcementsOnlyReady?: boolean; + expireTimer?: DurationInSeconds; + memberships?: ReadonlyArray<{ + uuid: UUIDStringType; + isAdmin: boolean; + }>; + pendingMemberships?: ReadonlyArray<{ + uuid: UUIDStringType; + addedByUserId?: UUIDStringType; + }>; + pendingApprovalMemberships?: ReadonlyArray<{ + uuid: UUIDStringType; + }>; + bannedMemberships?: ReadonlyArray; + muteExpiresAt?: number; + dontNotifyForMentionsIfMuted?: boolean; + isMe: boolean; + lastUpdated?: number; + // This is used by the CompositionInput for @mentions + sortedGroupMembers?: ReadonlyArray; + title: string; + titleNoDefault?: string; + searchableTitle?: string; + unreadCount?: number; + isSelected?: boolean; + isFetchingUUID?: boolean; + typingContactId?: string; + recentMediaItems?: ReadonlyArray; + profileSharing?: boolean; - shouldShowDraft?: boolean; - draftText?: string; - draftBodyRanges?: DraftBodyRangesType; - draftPreview?: string; + shouldShowDraft?: boolean; + draftText?: string; + draftBodyRanges?: DraftBodyRangesType; + draftPreview?: string; - sharedGroupNames: ReadonlyArray; - groupDescription?: string; - groupVersion?: 1 | 2; - groupId?: string; - groupLink?: string; - messageRequestsEnabled?: boolean; - acceptedMessageRequest: boolean; - secretParams?: string; - publicParams?: string; - profileKey?: string; - voiceNotePlaybackRate?: number; + sharedGroupNames: ReadonlyArray; + groupDescription?: string; + groupVersion?: 1 | 2; + groupId?: string; + groupLink?: string; + messageRequestsEnabled?: boolean; + acceptedMessageRequest: boolean; + secretParams?: string; + publicParams?: string; + profileKey?: string; + voiceNotePlaybackRate?: number; - badges: ReadonlyArray< + badges: ReadonlyArray< + | { + id: string; + } + | { + id: string; + expiresAt: number; + isVisible: boolean; + } + >; + } & ( | { - id: string; + type: 'direct'; + storySendMode?: undefined; + acknowledgedGroupNameCollisions?: undefined; } | { - id: string; - expiresAt: number; - isVisible: boolean; + type: 'group'; + storySendMode: StorySendMode; + acknowledgedGroupNameCollisions: GroupNameCollisionsWithIdsByTitle; } - >; -} & ( - | { - type: 'direct'; - storySendMode?: undefined; - acknowledgedGroupNameCollisions?: undefined; - } - | { - type: 'group'; - storySendMode: StorySendMode; - acknowledgedGroupNameCollisions: GroupNameCollisionsWithIdsByTitle; - } -); -export type ProfileDataType = { - firstName: string; -} & Pick; + ) +>; +export type ProfileDataType = ReadonlyDeep< + { + firstName: string; + } & Pick +>; -export type ConversationLookupType = { +export type ConversationLookupType = ReadonlyDeep<{ [key: string]: ConversationType; -}; -export type CustomError = Error & { - identifier?: string; - number?: string; -}; +}>; +export type CustomError = ReadonlyDeep< + Error & { + identifier?: string; + number?: string; + } +>; -type MessagePointerType = { +type MessagePointerType = ReadonlyDeep<{ id: string; received_at: number; sent_at?: number; -}; -type MessageMetricsType = { +}>; +type MessageMetricsType = ReadonlyDeep<{ newest?: MessagePointerType; oldest?: MessagePointerType; oldestUnseen?: MessagePointerType; totalUnseen: number; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type MessageLookupType = { [key: string]: MessageWithUIFieldsType; }; -export type ConversationMessageType = { +export type ConversationMessageType = ReadonlyDeep<{ isNearBottom?: boolean; messageChangeCounter: number; messageIds: ReadonlyArray; @@ -337,13 +349,13 @@ export type ConversationMessageType = { metrics: MessageMetricsType; scrollToMessageId?: string; scrollToMessageCounter: number; -}; +}>; -export type MessagesByConversationType = { +export type MessagesByConversationType = ReadonlyDeep<{ [key: string]: ConversationMessageType | undefined; -}; +}>; -export type PreJoinConversationType = { +export type PreJoinConversationType = ReadonlyDeep<{ avatar?: { loading?: boolean; url?: string; @@ -352,9 +364,9 @@ export type PreJoinConversationType = { memberCount: number; title: string; approvalRequired: boolean; -}; +}>; -type ComposerGroupCreationState = { +type ComposerGroupCreationState = ReadonlyDeep<{ groupAvatar: undefined | Uint8Array; groupName: string; groupExpireTimer: DurationInSeconds; @@ -362,13 +374,13 @@ type ComposerGroupCreationState = { recommendedGroupSizeModalState: OneTimeModalState; selectedConversationIds: ReadonlyArray; userAvatarData: ReadonlyArray; -}; +}>; -type DistributionVerificationData = { - uuidsNeedingVerification: ReadonlyArray; -}; +type DistributionVerificationData = ReadonlyDeep<{ + uuidsNeedingVerification: Array; +}>; -export type ConversationVerificationData = +export type ConversationVerificationData = ReadonlyDeep< | { type: ConversationVerificationState.PendingVerification; uuidsNeedingVerification: ReadonlyArray; @@ -378,14 +390,14 @@ export type ConversationVerificationData = | { type: ConversationVerificationState.VerificationCancelled; canceledAt: number; - }; - -type VerificationDataByConversation = Record< - string, - ConversationVerificationData + } >; -type ComposerStateType = +type VerificationDataByConversation = ReadonlyDeep< + Record +>; + +type ComposerStateType = ReadonlyDeep< | { step: ComposerStep.StartDirectConversation; searchTerm: string; @@ -403,9 +415,10 @@ type ComposerStateType = ( | { isCreating: false; hasError: boolean } | { isCreating: true; hasError: false } - )); + )) +>; -type ContactSpoofingReviewStateType = +type ContactSpoofingReviewStateType = ReadonlyDeep< | { type: ContactSpoofingType.DirectConversationWithSameTitle; safeConversationId: string; @@ -413,9 +426,11 @@ type ContactSpoofingReviewStateType = | { type: ContactSpoofingType.MultipleGroupMembersWithSameTitle; groupConversationId: string; - }; + } +>; -export type ConversationsStateType = { +// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME +export type ConversationsStateType = Readonly<{ preJoinConversation?: PreJoinConversationType; invitedUuidsForNewlyCreatedGroup?: ReadonlyArray; conversationLookup: ConversationLookupType; @@ -444,7 +459,7 @@ export type ConversationsStateType = { // Note: it's very important that both of these locations are always kept up to date messagesLookup: MessageLookupType; messagesByConversation: MessagesByConversationType; -}; +}>; // Helpers @@ -502,35 +517,37 @@ export const SET_VOICE_NOTE_PLAYBACK_RATE = 'conversations/SET_VOICE_NOTE_PLAYBACK_RATE'; export const CONVERSATION_UNLOADED = 'CONVERSATION_UNLOADED'; -export type CancelVerificationDataByConversationActionType = { +export type CancelVerificationDataByConversationActionType = ReadonlyDeep<{ type: typeof CANCEL_CONVERSATION_PENDING_VERIFICATION; payload: { canceledAt: number; }; -}; -type ClearGroupCreationErrorActionType = { type: 'CLEAR_GROUP_CREATION_ERROR' }; -type ClearInvitedUuidsForNewlyCreatedGroupActionType = { +}>; +type ClearGroupCreationErrorActionType = ReadonlyDeep<{ + type: 'CLEAR_GROUP_CREATION_ERROR'; +}>; +type ClearInvitedUuidsForNewlyCreatedGroupActionType = ReadonlyDeep<{ type: 'CLEAR_INVITED_UUIDS_FOR_NEWLY_CREATED_GROUP'; -}; -type ClearVerificationDataByConversationActionType = { +}>; +type ClearVerificationDataByConversationActionType = ReadonlyDeep<{ type: typeof CLEAR_CONVERSATIONS_PENDING_VERIFICATION; -}; -type ClearCancelledVerificationActionType = { +}>; +type ClearCancelledVerificationActionType = ReadonlyDeep<{ type: typeof CLEAR_CANCELLED_VERIFICATION; payload: { conversationId: string; }; -}; -type CloseContactSpoofingReviewActionType = { +}>; +type CloseContactSpoofingReviewActionType = ReadonlyDeep<{ type: 'CLOSE_CONTACT_SPOOFING_REVIEW'; -}; -type CloseMaximumGroupSizeModalActionType = { +}>; +type CloseMaximumGroupSizeModalActionType = ReadonlyDeep<{ type: 'CLOSE_MAXIMUM_GROUP_SIZE_MODAL'; -}; -type CloseRecommendedGroupSizeModalActionType = { +}>; +type CloseRecommendedGroupSizeModalActionType = ReadonlyDeep<{ type: 'CLOSE_RECOMMENDED_GROUP_SIZE_MODAL'; -}; -type ColorsChangedActionType = { +}>; +type ColorsChangedActionType = ReadonlyDeep<{ type: typeof COLORS_CHANGED; payload: { conversationColor?: ConversationColorType; @@ -539,41 +556,41 @@ type ColorsChangedActionType = { value: CustomColorType; }; }; -}; -type ColorSelectedPayloadType = { +}>; +type ColorSelectedPayloadType = ReadonlyDeep<{ conversationId: string; conversationColor?: ConversationColorType; customColorData?: { id: string; value: CustomColorType; }; -}; -export type ColorSelectedActionType = { +}>; +export type ColorSelectedActionType = ReadonlyDeep<{ type: typeof COLOR_SELECTED; payload: ColorSelectedPayloadType; -}; -type ComposeDeleteAvatarActionType = { +}>; +type ComposeDeleteAvatarActionType = ReadonlyDeep<{ type: typeof COMPOSE_REMOVE_AVATAR; payload: AvatarDataType; -}; -type ComposeReplaceAvatarsActionType = { +}>; +type ComposeReplaceAvatarsActionType = ReadonlyDeep<{ type: typeof COMPOSE_REPLACE_AVATAR; payload: { curr: AvatarDataType; prev?: AvatarDataType; }; -}; -type ComposeSaveAvatarActionType = { +}>; +type ComposeSaveAvatarActionType = ReadonlyDeep<{ type: typeof COMPOSE_ADD_AVATAR; payload: AvatarDataType; -}; -type CustomColorRemovedActionType = { +}>; +type CustomColorRemovedActionType = ReadonlyDeep<{ type: typeof CUSTOM_COLOR_REMOVED; payload: { colorId: string; }; -}; -type DiscardMessagesActionType = { +}>; +type DiscardMessagesActionType = ReadonlyDeep<{ type: typeof DISCARD_MESSAGES; payload: Readonly< | { @@ -582,71 +599,72 @@ type DiscardMessagesActionType = { } | { conversationId: string; numberToKeepAtTop: number } >; -}; -type SetPreJoinConversationActionType = { +}>; +type SetPreJoinConversationActionType = ReadonlyDeep<{ type: 'SET_PRE_JOIN_CONVERSATION'; payload: { data: PreJoinConversationType | undefined; }; -}; +}>; -type ConversationAddedActionType = { +type ConversationAddedActionType = ReadonlyDeep<{ type: 'CONVERSATION_ADDED'; payload: { id: string; data: ConversationType; }; -}; -export type ConversationChangedActionType = { +}>; +export type ConversationChangedActionType = ReadonlyDeep<{ type: 'CONVERSATION_CHANGED'; payload: { id: string; data: ConversationType; }; -}; -export type ConversationRemovedActionType = { +}>; +export type ConversationRemovedActionType = ReadonlyDeep<{ type: 'CONVERSATION_REMOVED'; payload: { id: string; }; -}; -export type ConversationUnloadedActionType = { +}>; +export type ConversationUnloadedActionType = ReadonlyDeep<{ type: typeof CONVERSATION_UNLOADED; payload: { conversationId: string; }; -}; -type CreateGroupPendingActionType = { +}>; +type CreateGroupPendingActionType = ReadonlyDeep<{ type: 'CREATE_GROUP_PENDING'; -}; -type CreateGroupFulfilledActionType = { +}>; +type CreateGroupFulfilledActionType = ReadonlyDeep<{ type: 'CREATE_GROUP_FULFILLED'; payload: { invitedUuids: ReadonlyArray; }; -}; -type CreateGroupRejectedActionType = { +}>; +type CreateGroupRejectedActionType = ReadonlyDeep<{ type: 'CREATE_GROUP_REJECTED'; -}; -export type RemoveAllConversationsActionType = { +}>; +export type RemoveAllConversationsActionType = ReadonlyDeep<{ type: 'CONVERSATIONS_REMOVE_ALL'; payload: null; -}; -export type MessageSelectedActionType = { +}>; +export type MessageSelectedActionType = ReadonlyDeep<{ type: 'MESSAGE_SELECTED'; payload: { messageId: string; conversationId: string; }; -}; -type ConversationStoppedByMissingVerificationActionType = { +}>; +type ConversationStoppedByMissingVerificationActionType = ReadonlyDeep<{ type: typeof CONVERSATION_STOPPED_BY_MISSING_VERIFICATION; payload: { conversationId: string; distributionId?: string; untrustedUuids: ReadonlyArray; }; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME export type MessageChangedActionType = { type: typeof MESSAGE_CHANGED; payload: { @@ -655,22 +673,23 @@ export type MessageChangedActionType = { data: MessageAttributesType; }; }; -export type MessageDeletedActionType = { +export type MessageDeletedActionType = ReadonlyDeep<{ type: typeof MESSAGE_DELETED; payload: { id: string; conversationId: string; }; -}; -export type MessageExpandedActionType = { +}>; +export type MessageExpandedActionType = ReadonlyDeep<{ type: 'MESSAGE_EXPANDED'; payload: { id: string; displayLimit: number; }; -}; +}>; -export type MessagesAddedActionType = { +// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME +export type MessagesAddedActionType = Readonly<{ type: 'MESSAGES_ADDED'; payload: { conversationId: string; @@ -679,27 +698,28 @@ export type MessagesAddedActionType = { isNewMessage: boolean; messages: ReadonlyArray; }; -}; +}>; -export type MessageExpiredActionType = { +export type MessageExpiredActionType = ReadonlyDeep<{ type: typeof MESSAGE_EXPIRED; payload: { id: string; }; -}; +}>; -export type RepairNewestMessageActionType = { +export type RepairNewestMessageActionType = ReadonlyDeep<{ type: 'REPAIR_NEWEST_MESSAGE'; payload: { conversationId: string; }; -}; -export type RepairOldestMessageActionType = { +}>; +export type RepairOldestMessageActionType = ReadonlyDeep<{ type: 'REPAIR_OLDEST_MESSAGE'; payload: { conversationId: string; }; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type MessagesResetActionType = { type: 'MESSAGES_RESET'; payload: { @@ -712,132 +732,135 @@ export type MessagesResetActionType = { unboundedFetch: boolean; }; }; -export type SetMessageLoadingStateActionType = { +export type SetMessageLoadingStateActionType = ReadonlyDeep<{ type: 'SET_MESSAGE_LOADING_STATE'; payload: { conversationId: string; messageLoadingState: undefined | TimelineMessageLoadingState; }; -}; -export type SetIsNearBottomActionType = { +}>; +export type SetIsNearBottomActionType = ReadonlyDeep<{ type: 'SET_NEAR_BOTTOM'; payload: { conversationId: string; isNearBottom: boolean; }; -}; -export type ScrollToMessageActionType = { +}>; +export type ScrollToMessageActionType = ReadonlyDeep<{ type: 'SCROLL_TO_MESSAGE'; payload: { conversationId: string; messageId: string; }; -}; -export type ClearSelectedMessageActionType = { +}>; +export type ClearSelectedMessageActionType = ReadonlyDeep<{ type: 'CLEAR_SELECTED_MESSAGE'; payload: null; -}; -export type ClearUnreadMetricsActionType = { +}>; +export type ClearUnreadMetricsActionType = ReadonlyDeep<{ type: 'CLEAR_UNREAD_METRICS'; payload: { conversationId: string; }; -}; -export type SelectedConversationChangedActionType = { +}>; +export type SelectedConversationChangedActionType = ReadonlyDeep<{ type: typeof SELECTED_CONVERSATION_CHANGED; payload: { conversationId?: string; messageId?: string; switchToAssociatedView?: boolean; }; -}; -type ReviewGroupMemberNameCollisionActionType = { +}>; +type ReviewGroupMemberNameCollisionActionType = ReadonlyDeep<{ type: 'REVIEW_GROUP_MEMBER_NAME_COLLISION'; payload: { groupConversationId: string; }; -}; -type ReviewMessageRequestNameCollisionActionType = { +}>; +type ReviewMessageRequestNameCollisionActionType = ReadonlyDeep<{ type: 'REVIEW_MESSAGE_REQUEST_NAME_COLLISION'; payload: { safeConversationId: string; }; -}; -type ShowInboxActionType = { +}>; +type ShowInboxActionType = ReadonlyDeep<{ type: 'SHOW_INBOX'; payload: null; -}; -export type ShowArchivedConversationsActionType = { +}>; +export type ShowArchivedConversationsActionType = ReadonlyDeep<{ type: 'SHOW_ARCHIVED_CONVERSATIONS'; payload: null; -}; -type SetComposeGroupAvatarActionType = { +}>; +type SetComposeGroupAvatarActionType = ReadonlyDeep<{ type: 'SET_COMPOSE_GROUP_AVATAR'; payload: { groupAvatar: undefined | Uint8Array }; -}; -type SetComposeGroupNameActionType = { +}>; +type SetComposeGroupNameActionType = ReadonlyDeep<{ type: 'SET_COMPOSE_GROUP_NAME'; payload: { groupName: string }; -}; -type SetComposeGroupExpireTimerActionType = { +}>; +type SetComposeGroupExpireTimerActionType = ReadonlyDeep<{ type: 'SET_COMPOSE_GROUP_EXPIRE_TIMER'; payload: { groupExpireTimer: DurationInSeconds }; -}; -type SetComposeSearchTermActionType = { +}>; +type SetComposeSearchTermActionType = ReadonlyDeep<{ type: 'SET_COMPOSE_SEARCH_TERM'; payload: { searchTerm: string }; -}; -type SetIsFetchingUUIDActionType = { +}>; +type SetIsFetchingUUIDActionType = ReadonlyDeep<{ type: 'SET_IS_FETCHING_UUID'; payload: { identifier: UUIDFetchStateKeyType; isFetching: boolean; }; -}; -type SetRecentMediaItemsActionType = { +}>; +type SetRecentMediaItemsActionType = ReadonlyDeep<{ type: 'SET_RECENT_MEDIA_ITEMS'; payload: { id: string; recentMediaItems: ReadonlyArray; }; -}; -type ToggleComposeEditingAvatarActionType = { +}>; +type ToggleComposeEditingAvatarActionType = ReadonlyDeep<{ type: typeof COMPOSE_TOGGLE_EDITING_AVATAR; -}; -type StartComposingActionType = { +}>; +type StartComposingActionType = ReadonlyDeep<{ type: 'START_COMPOSING'; -}; -type ShowChooseGroupMembersActionType = { +}>; +type ShowChooseGroupMembersActionType = ReadonlyDeep<{ type: 'SHOW_CHOOSE_GROUP_MEMBERS'; -}; -type StartSettingGroupMetadataActionType = { +}>; +type StartSettingGroupMetadataActionType = ReadonlyDeep<{ type: 'START_SETTING_GROUP_METADATA'; -}; -export type ToggleConversationInChooseMembersActionType = { +}>; +export type ToggleConversationInChooseMembersActionType = ReadonlyDeep<{ type: 'TOGGLE_CONVERSATION_IN_CHOOSE_MEMBERS'; payload: { conversationId: string; maxRecommendedGroupSize: number; maxGroupSize: number; }; -}; -type PushPanelActionType = { +}>; + +// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME +type PushPanelActionType = Readonly<{ type: typeof PUSH_PANEL; payload: PanelRenderType; -}; -type PopPanelActionType = { +}>; +type PopPanelActionType = ReadonlyDeep<{ type: typeof POP_PANEL; payload: null; -}; +}>; -type ReplaceAvatarsActionType = { +type ReplaceAvatarsActionType = ReadonlyDeep<{ type: typeof REPLACE_AVATARS; payload: { conversationId: string; avatars: ReadonlyArray; }; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME export type ConversationActionType = | CancelVerificationDataByConversationActionType | ClearCancelledVerificationActionType @@ -1109,7 +1132,7 @@ function onMoveToInbox(conversationId: string): ShowToastActionType { function acknowledgeGroupMemberNameCollisions( conversationId: string, - groupNameCollisions: Readonly + groupNameCollisions: ReadonlyDeep ): NoopActionType { const conversation = window.ConversationController.get(conversationId); if (!conversation) { @@ -1907,10 +1930,10 @@ function kickOffAttachmentDownload( }; } -type AttachmentOptions = { +type AttachmentOptions = ReadonlyDeep<{ messageId: string; attachment: AttachmentType; -}; +}>; function markAttachmentAsCorrupted( options: AttachmentOptions @@ -2512,13 +2535,14 @@ function reviewMessageRequestNameCollision( return { type: 'REVIEW_MESSAGE_REQUEST_NAME_COLLISION', payload }; } -export type MessageResetOptionsType = Readonly<{ +// eslint-disable-next-line local-rules/type-alias-readonlydeep +export type MessageResetOptionsType = { conversationId: string; messages: ReadonlyArray; metrics: MessageMetricsType; scrollToMessageId?: string; unboundedFetch?: boolean; -}>; +}; function messagesReset({ conversationId, @@ -2583,9 +2607,9 @@ function setIsFetchingUUID( }; } -export type PushPanelForConversationActionType = ( - panel: PanelRequestType -) => unknown; +export type PushPanelForConversationActionType = ReadonlyDeep< + (panel: PanelRequestType) => unknown +>; function pushPanelForConversation( panel: PanelRequestType @@ -2619,7 +2643,7 @@ function pushPanelForConversation( }; } -export type PopPanelForConversationActionType = () => unknown; +export type PopPanelForConversationActionType = ReadonlyDeep<() => unknown>; function popPanelForConversation(): ThunkAction< void, @@ -3016,11 +3040,9 @@ function loadRecentMediaItems( }; } -export type SaveAttachmentActionCreatorType = ( - attachment: AttachmentType, - timestamp?: number, - index?: number -) => unknown; +export type SaveAttachmentActionCreatorType = ReadonlyDeep< + (attachment: AttachmentType, timestamp?: number, index?: number) => unknown +>; function saveAttachment( attachment: AttachmentType, @@ -3475,14 +3497,14 @@ function showInbox(): ShowInboxActionType { }; } -type ShowConversationArgsType = { +type ShowConversationArgsType = ReadonlyDeep<{ conversationId?: string; messageId?: string; switchToAssociatedView?: boolean; -}; -export type ShowConversationType = ( - options: ShowConversationArgsType -) => unknown; +}>; +export type ShowConversationType = ReadonlyDeep< + (options: ShowConversationArgsType) => unknown +>; function showConversation({ conversationId, @@ -3858,10 +3880,12 @@ function getVerificationDataForConversation({ } // Return same data, and we do nothing. Return undefined, and we'll delete the list. -type DistributionVisitor = ( - id: string, - data: DistributionVerificationData -) => DistributionVerificationData | undefined; +type DistributionVisitor = ReadonlyDeep< + ( + id: string, + data: DistributionVerificationData + ) => DistributionVerificationData | undefined +>; function visitListsInVerificationData( existing: VerificationDataByConversation, diff --git a/ts/state/ducks/crashReports.ts b/ts/state/ducks/crashReports.ts index 574865e07da8..14dffc949f97 100644 --- a/ts/state/ducks/crashReports.ts +++ b/ts/state/ducks/crashReports.ts @@ -1,6 +1,7 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import type { ReadonlyDeep } from 'type-fest'; import * as log from '../../logging/log'; import { showToast } from '../../util/showToast'; import * as Errors from '../../types/errors'; @@ -10,10 +11,10 @@ import type { PromiseAction } from '../util'; // State -export type CrashReportsStateType = { +export type CrashReportsStateType = ReadonlyDeep<{ count: number; isPending: boolean; -}; +}>; // Actions @@ -21,15 +22,16 @@ const SET_COUNT = 'crashReports/SET_COUNT'; const UPLOAD = 'crashReports/UPLOAD'; const ERASE = 'crashReports/ERASE'; -type SetCrashReportCountActionType = { +type SetCrashReportCountActionType = ReadonlyDeep<{ type: typeof SET_COUNT; payload: number; -}; +}>; -type CrashReportsActionType = +type CrashReportsActionType = ReadonlyDeep< | SetCrashReportCountActionType | PromiseAction - | PromiseAction; + | PromiseAction +>; // Action Creators diff --git a/ts/state/ducks/emojis.ts b/ts/state/ducks/emojis.ts index 7d054f5abd51..e3fad33e80bf 100644 --- a/ts/state/ducks/emojis.ts +++ b/ts/state/ducks/emojis.ts @@ -3,6 +3,7 @@ import { take, uniq } from 'lodash'; import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { EmojiPickDataType } from '../../components/emoji/EmojiPicker'; import dataInterface from '../../sql/Client'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; @@ -12,18 +13,18 @@ const { updateEmojiUsage } = dataInterface; // State -export type EmojisStateType = { - readonly recents: Array; -}; +export type EmojisStateType = ReadonlyDeep<{ + recents: Array; +}>; // Actions -type UseEmojiAction = { +type UseEmojiAction = ReadonlyDeep<{ type: 'emojis/USE_EMOJI'; payload: string; -}; +}>; -type EmojisActionType = UseEmojiAction; +type EmojisActionType = ReadonlyDeep; // Action Creators @@ -64,8 +65,8 @@ function getEmptyState(): EmojisStateType { } export function reducer( - state: Readonly = getEmptyState(), - action: Readonly + state: EmojisStateType = getEmptyState(), + action: EmojisActionType ): EmojisStateType { if (action.type === 'emojis/USE_EMOJI') { const { payload } = action; diff --git a/ts/state/ducks/expiration.ts b/ts/state/ducks/expiration.ts index 649da49ecb21..7d2f64d5ce25 100644 --- a/ts/state/ducks/expiration.ts +++ b/ts/state/ducks/expiration.ts @@ -1,22 +1,25 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import type { ReadonlyDeep } from 'type-fest'; + // State -export type ExpirationStateType = { +export type ExpirationStateType = ReadonlyDeep<{ hasExpired: boolean; -}; +}>; // Actions const HYDRATE_EXPIRATION_STATUS = 'expiration/HYDRATE_EXPIRATION_STATUS'; -type HyrdateExpirationStatusActionType = { +type HyrdateExpirationStatusActionType = ReadonlyDeep<{ type: 'expiration/HYDRATE_EXPIRATION_STATUS'; payload: boolean; -}; +}>; -export type ExpirationActionType = HyrdateExpirationStatusActionType; +export type ExpirationActionType = + ReadonlyDeep; // Action Creators diff --git a/ts/state/ducks/globalModals.ts b/ts/state/ducks/globalModals.ts index 9f404b5ef637..4a3917174b18 100644 --- a/ts/state/ducks/globalModals.ts +++ b/ts/state/ducks/globalModals.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { ExplodePromiseResultType } from '../../util/explodePromise'; import type { GroupV2PendingMemberType } from '../../model-types.d'; import type { PropsForMessage } from '../selectors/message'; @@ -21,24 +22,23 @@ import { getGroupMigrationMembers } from '../../groups'; // State -export type ForwardMessagePropsType = Omit< - PropsForMessage, - 'renderingContext' | 'menu' | 'contextMenu' +export type ForwardMessagePropsType = ReadonlyDeep< + Omit >; -export type SafetyNumberChangedBlockingDataType = Readonly<{ +export type SafetyNumberChangedBlockingDataType = ReadonlyDeep<{ promiseUuid: UUIDStringType; source?: SafetyNumberChangeSource; }>; -type MigrateToGV2PropsType = { +type MigrateToGV2PropsType = ReadonlyDeep<{ areWeInvited: boolean; conversationId: string; - droppedMemberIds: ReadonlyArray; + droppedMemberIds: Array; hasMigrated: boolean; - invitedMemberIds: ReadonlyArray; -}; + invitedMemberIds: Array; +}>; -export type GlobalModalsStateType = Readonly<{ +export type GlobalModalsStateType = ReadonlyDeep<{ addUserToAnotherGroupModalContactId?: string; contactModalState?: ContactModalStateType; errorModalProps?: { @@ -90,12 +90,12 @@ const SHOW_ERROR_MODAL = 'globalModals/SHOW_ERROR_MODAL'; const CLOSE_SHORTCUT_GUIDE_MODAL = 'globalModals/CLOSE_SHORTCUT_GUIDE_MODAL'; const SHOW_SHORTCUT_GUIDE_MODAL = 'globalModals/SHOW_SHORTCUT_GUIDE_MODAL'; -export type ContactModalStateType = { +export type ContactModalStateType = ReadonlyDeep<{ contactId: string; conversationId?: string; -}; +}>; -export type UserNotFoundModalStateType = +export type UserNotFoundModalStateType = ReadonlyDeep< | { type: 'phoneNumber'; phoneNumber: string; @@ -103,119 +103,120 @@ export type UserNotFoundModalStateType = | { type: 'username'; username: string; - }; + } +>; -type HideContactModalActionType = { +type HideContactModalActionType = ReadonlyDeep<{ type: typeof HIDE_CONTACT_MODAL; -}; +}>; -type ShowContactModalActionType = { +type ShowContactModalActionType = ReadonlyDeep<{ type: typeof SHOW_CONTACT_MODAL; payload: ContactModalStateType; -}; +}>; -type HideWhatsNewModalActionType = { +type HideWhatsNewModalActionType = ReadonlyDeep<{ type: typeof HIDE_WHATS_NEW_MODAL; -}; +}>; -type ShowWhatsNewModalActionType = { +type ShowWhatsNewModalActionType = ReadonlyDeep<{ type: typeof SHOW_WHATS_NEW_MODAL; -}; +}>; -type HideUserNotFoundModalActionType = { +type HideUserNotFoundModalActionType = ReadonlyDeep<{ type: typeof HIDE_UUID_NOT_FOUND_MODAL; -}; +}>; -export type ShowUserNotFoundModalActionType = { +export type ShowUserNotFoundModalActionType = ReadonlyDeep<{ type: typeof SHOW_UUID_NOT_FOUND_MODAL; payload: UserNotFoundModalStateType; -}; +}>; -type ToggleForwardMessageModalActionType = { +type ToggleForwardMessageModalActionType = ReadonlyDeep<{ type: typeof TOGGLE_FORWARD_MESSAGE_MODAL; payload: ForwardMessagePropsType | undefined; -}; +}>; -type ToggleProfileEditorActionType = { +type ToggleProfileEditorActionType = ReadonlyDeep<{ type: typeof TOGGLE_PROFILE_EDITOR; -}; +}>; -export type ToggleProfileEditorErrorActionType = { +export type ToggleProfileEditorErrorActionType = ReadonlyDeep<{ type: typeof TOGGLE_PROFILE_EDITOR_ERROR; -}; +}>; -type ToggleSafetyNumberModalActionType = { +type ToggleSafetyNumberModalActionType = ReadonlyDeep<{ type: typeof TOGGLE_SAFETY_NUMBER_MODAL; payload: string | undefined; -}; +}>; -type ToggleAddUserToAnotherGroupModalActionType = { +type ToggleAddUserToAnotherGroupModalActionType = ReadonlyDeep<{ type: typeof TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL; payload: string | undefined; -}; +}>; -type ToggleSignalConnectionsModalActionType = { +type ToggleSignalConnectionsModalActionType = ReadonlyDeep<{ type: typeof TOGGLE_SIGNAL_CONNECTIONS_MODAL; -}; +}>; -type ShowStoriesSettingsActionType = { +type ShowStoriesSettingsActionType = ReadonlyDeep<{ type: typeof SHOW_STORIES_SETTINGS; -}; +}>; -type HideStoriesSettingsActionType = { +type HideStoriesSettingsActionType = ReadonlyDeep<{ type: typeof HIDE_STORIES_SETTINGS; -}; +}>; -type StartMigrationToGV2ActionType = { +type StartMigrationToGV2ActionType = ReadonlyDeep<{ type: typeof SHOW_GV2_MIGRATION_DIALOG; payload: MigrateToGV2PropsType; -}; +}>; -type CloseGV2MigrationDialogActionType = { +type CloseGV2MigrationDialogActionType = ReadonlyDeep<{ type: typeof CLOSE_GV2_MIGRATION_DIALOG; -}; +}>; -export type ShowSendAnywayDialogActionType = { +export type ShowSendAnywayDialogActionType = ReadonlyDeep<{ type: typeof SHOW_SEND_ANYWAY_DIALOG; payload: SafetyNumberChangedBlockingDataType & { untrustedByConversation: RecipientsByConversation; }; -}; +}>; -type HideSendAnywayDialogActiontype = { +type HideSendAnywayDialogActiontype = ReadonlyDeep<{ type: typeof HIDE_SEND_ANYWAY_DIALOG; -}; +}>; -export type ShowStickerPackPreviewActionType = { +export type ShowStickerPackPreviewActionType = ReadonlyDeep<{ type: typeof SHOW_STICKER_PACK_PREVIEW; payload: string; -}; +}>; -type CloseStickerPackPreviewActionType = { +type CloseStickerPackPreviewActionType = ReadonlyDeep<{ type: typeof CLOSE_STICKER_PACK_PREVIEW; -}; +}>; -type CloseErrorModalActionType = { +type CloseErrorModalActionType = ReadonlyDeep<{ type: typeof CLOSE_ERROR_MODAL; -}; +}>; -type ShowErrorModalActionType = { +type ShowErrorModalActionType = ReadonlyDeep<{ type: typeof SHOW_ERROR_MODAL; payload: { description?: string; title?: string; }; -}; +}>; -type CloseShortcutGuideModalActionType = { +type CloseShortcutGuideModalActionType = ReadonlyDeep<{ type: typeof CLOSE_SHORTCUT_GUIDE_MODAL; -}; +}>; -type ShowShortcutGuideModalActionType = { +type ShowShortcutGuideModalActionType = ReadonlyDeep<{ type: typeof SHOW_SHORTCUT_GUIDE_MODAL; -}; +}>; -export type GlobalModalsActionType = +export type GlobalModalsActionType = ReadonlyDeep< | StartMigrationToGV2ActionType | CloseGV2MigrationDialogActionType | HideContactModalActionType @@ -239,7 +240,8 @@ export type GlobalModalsActionType = | ToggleProfileEditorErrorActionType | ToggleSafetyNumberModalActionType | ToggleAddUserToAnotherGroupModalActionType - | ToggleSignalConnectionsModalActionType; + | ToggleSignalConnectionsModalActionType +>; // Action Creators diff --git a/ts/state/ducks/items.ts b/ts/state/ducks/items.ts index 0618fa9f362c..474ec5c91ee0 100644 --- a/ts/state/ducks/items.ts +++ b/ts/state/ducks/items.ts @@ -4,6 +4,7 @@ import { omit } from 'lodash'; import { v4 as getGuid } from 'uuid'; import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { StateType as RootStateType } from '../reducer'; import * as storageShim from '../../shims/storage'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; @@ -23,60 +24,61 @@ import type { ConfigMapType as RemoteConfigType } from '../../RemoteConfig'; // State -export type ItemsStateType = { - readonly universalExpireTimer?: number; +export type ItemsStateType = ReadonlyDeep<{ + universalExpireTimer?: number; - readonly [key: string]: unknown; + [key: string]: unknown; - readonly remoteConfig?: RemoteConfigType; + remoteConfig?: RemoteConfigType; // This property should always be set and this is ensured in background.ts - readonly defaultConversationColor?: DefaultConversationColorType; + defaultConversationColor?: DefaultConversationColorType; - readonly customColors?: CustomColorsItemType; + customColors?: CustomColorsItemType; - readonly preferredLeftPaneWidth?: number; + preferredLeftPaneWidth?: number; - readonly preferredReactionEmoji?: ReadonlyArray; + preferredReactionEmoji?: Array; - readonly areWeASubscriber?: boolean; -}; + areWeASubscriber?: boolean; +}>; // Actions -type ItemPutAction = { +type ItemPutAction = ReadonlyDeep<{ type: 'items/PUT'; payload: null; -}; +}>; -type ItemPutExternalAction = { +type ItemPutExternalAction = ReadonlyDeep<{ type: 'items/PUT_EXTERNAL'; payload: { key: string; value: unknown; }; -}; +}>; -type ItemRemoveAction = { +type ItemRemoveAction = ReadonlyDeep<{ type: 'items/REMOVE'; payload: null; -}; +}>; -type ItemRemoveExternalAction = { +type ItemRemoveExternalAction = ReadonlyDeep<{ type: 'items/REMOVE_EXTERNAL'; payload: string; -}; +}>; -type ItemsResetAction = { +type ItemsResetAction = ReadonlyDeep<{ type: 'items/RESET'; -}; +}>; -export type ItemsActionType = +export type ItemsActionType = ReadonlyDeep< | ItemPutAction | ItemPutExternalAction | ItemRemoveAction | ItemRemoveExternalAction - | ItemsResetAction; + | ItemsResetAction +>; // Action Creators diff --git a/ts/state/ducks/lightbox.ts b/ts/state/ducks/lightbox.ts index 17e2a0ab8a9b..6751d500b0d2 100644 --- a/ts/state/ducks/lightbox.ts +++ b/ts/state/ducks/lightbox.ts @@ -3,6 +3,7 @@ import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { AttachmentType } from '../../types/Attachment'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import type { MediaItemType } from '../../types/MediaItem'; @@ -34,6 +35,7 @@ import { import { showStickerPackPreview } from './globalModals'; import { useBoundActions } from '../../hooks/useBoundActions'; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type LightboxStateType = | { isShowingLightbox: false; @@ -41,26 +43,28 @@ export type LightboxStateType = | { isShowingLightbox: true; isViewOnce: boolean; - media: ReadonlyArray; + media: ReadonlyArray>; selectedAttachmentPath: string | undefined; }; const CLOSE_LIGHTBOX = 'lightbox/CLOSE'; const SHOW_LIGHTBOX = 'lightbox/SHOW'; -type CloseLightboxActionType = { +type CloseLightboxActionType = ReadonlyDeep<{ type: typeof CLOSE_LIGHTBOX; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type ShowLightboxActionType = { type: typeof SHOW_LIGHTBOX; payload: { isViewOnce: boolean; - media: ReadonlyArray; + media: ReadonlyArray>; selectedAttachmentPath: string | undefined; }; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type LightboxActionType = | CloseLightboxActionType | MessageChangedActionType @@ -100,7 +104,7 @@ function closeLightbox(): ThunkAction< function showLightboxWithMedia( selectedAttachmentPath: string | undefined, - media: ReadonlyArray + media: ReadonlyArray> ): ShowLightboxActionType { return { type: SHOW_LIGHTBOX, diff --git a/ts/state/ducks/linkPreviews.ts b/ts/state/ducks/linkPreviews.ts index 6dc177379f94..b9a4d5e92f69 100644 --- a/ts/state/ducks/linkPreviews.ts +++ b/ts/state/ducks/linkPreviews.ts @@ -3,6 +3,7 @@ import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import type { LinkPreviewType } from '../../types/message/LinkPreviews'; import type { MaybeGrabLinkPreviewOptionsType } from '../../types/LinkPreview'; @@ -16,35 +17,35 @@ import { useBoundActions } from '../../hooks/useBoundActions'; // State -export type LinkPreviewsStateType = { - readonly linkPreview?: LinkPreviewType; - readonly source?: LinkPreviewSourceType; -}; +export type LinkPreviewsStateType = ReadonlyDeep<{ + linkPreview?: LinkPreviewType; + source?: LinkPreviewSourceType; +}>; // Actions export const ADD_PREVIEW = 'linkPreviews/ADD_PREVIEW'; export const REMOVE_PREVIEW = 'linkPreviews/REMOVE_PREVIEW'; -export type AddLinkPreviewActionType = { +export type AddLinkPreviewActionType = ReadonlyDeep<{ type: 'linkPreviews/ADD_PREVIEW'; payload: { conversationId?: string; linkPreview: LinkPreviewType; source: LinkPreviewSourceType; }; -}; +}>; -export type RemoveLinkPreviewActionType = { +export type RemoveLinkPreviewActionType = ReadonlyDeep<{ type: 'linkPreviews/REMOVE_PREVIEW'; payload: { conversationId?: string; }; -}; +}>; -type LinkPreviewsActionType = - | AddLinkPreviewActionType - | RemoveLinkPreviewActionType; +type LinkPreviewsActionType = ReadonlyDeep< + AddLinkPreviewActionType | RemoveLinkPreviewActionType +>; // Action Creators diff --git a/ts/state/ducks/mediaGallery.ts b/ts/state/ducks/mediaGallery.ts index 49159f5d2e7c..3d9e26884118 100644 --- a/ts/state/ducks/mediaGallery.ts +++ b/ts/state/ducks/mediaGallery.ts @@ -27,6 +27,7 @@ import { isDownloading, hasFailed } from '../../types/Attachment'; import { isNotNil } from '../../util/isNotNil'; import { useBoundActions } from '../../hooks/useBoundActions'; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type MediaType = { path: string; objectURL: string; @@ -44,6 +45,7 @@ type MediaType = { }; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type MediaGalleryStateType = { documents: Array; media: Array; @@ -51,6 +53,7 @@ export type MediaGalleryStateType = { const LOAD_MEDIA_ITEMS = 'mediaGallery/LOAD_MEDIA_ITEMS'; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type LoadMediaItemslActionType = { type: typeof LOAD_MEDIA_ITEMS; payload: { @@ -59,6 +62,7 @@ type LoadMediaItemslActionType = { }; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type MediaGalleryActionType = | ConversationUnloadedActionType | LoadMediaItemslActionType diff --git a/ts/state/ducks/network.ts b/ts/state/ducks/network.ts index 66d2f8ba8ff2..2eab8ef99f12 100644 --- a/ts/state/ducks/network.ts +++ b/ts/state/ducks/network.ts @@ -1,18 +1,19 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import type { ReadonlyDeep } from 'type-fest'; import { SocketStatus } from '../../types/SocketStatus'; import { trigger } from '../../shims/events'; import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation'; // State -export type NetworkStateType = { +export type NetworkStateType = ReadonlyDeep<{ isOnline: boolean; socketStatus: SocketStatus; withinConnectingGracePeriod: boolean; challengeStatus: 'required' | 'pending' | 'idle'; -}; +}>; // Actions @@ -21,36 +22,37 @@ const CLOSE_CONNECTING_GRACE_PERIOD = 'network/CLOSE_CONNECTING_GRACE_PERIOD'; const RELINK_DEVICE = 'network/RELINK_DEVICE'; const SET_CHALLENGE_STATUS = 'network/SET_CHALLENGE_STATUS'; -export type CheckNetworkStatusPayloadType = { +export type CheckNetworkStatusPayloadType = ReadonlyDeep<{ isOnline: boolean; socketStatus: SocketStatus; -}; +}>; -type CheckNetworkStatusAction = { +type CheckNetworkStatusAction = ReadonlyDeep<{ type: 'network/CHECK_NETWORK_STATUS'; payload: CheckNetworkStatusPayloadType; -}; +}>; -type CloseConnectingGracePeriodActionType = { +type CloseConnectingGracePeriodActionType = ReadonlyDeep<{ type: 'network/CLOSE_CONNECTING_GRACE_PERIOD'; -}; +}>; -type RelinkDeviceActionType = { +type RelinkDeviceActionType = ReadonlyDeep<{ type: 'network/RELINK_DEVICE'; -}; +}>; -type SetChallengeStatusActionType = { +type SetChallengeStatusActionType = ReadonlyDeep<{ type: 'network/SET_CHALLENGE_STATUS'; payload: { challengeStatus: NetworkStateType['challengeStatus']; }; -}; +}>; -export type NetworkActionType = +export type NetworkActionType = ReadonlyDeep< | CheckNetworkStatusAction | CloseConnectingGracePeriodActionType | RelinkDeviceActionType - | SetChallengeStatusActionType; + | SetChallengeStatusActionType +>; // Action Creators diff --git a/ts/state/ducks/noop.ts b/ts/state/ducks/noop.ts index dfa6f38de5df..9ed4ffe1f9ce 100644 --- a/ts/state/ducks/noop.ts +++ b/ts/state/ducks/noop.ts @@ -1,10 +1,12 @@ // Copyright 2019 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -export type NoopActionType = { +import type { ReadonlyDeep } from 'type-fest'; + +export type NoopActionType = ReadonlyDeep<{ type: 'NOOP'; payload: null; -}; +}>; export function noopAction(): NoopActionType { return { diff --git a/ts/state/ducks/preferredReactions.ts b/ts/state/ducks/preferredReactions.ts index 683d660d1016..dfc1758bf864 100644 --- a/ts/state/ducks/preferredReactions.ts +++ b/ts/state/ducks/preferredReactions.ts @@ -3,6 +3,7 @@ import type { ThunkAction } from 'redux-thunk'; import { omit } from 'lodash'; +import type { ReadonlyDeep } from 'type-fest'; import * as log from '../../logging/log'; import * as Errors from '../../types/errors'; import { replaceIndex } from '../../util/replaceIndex'; @@ -16,7 +17,7 @@ import { convertShortName } from '../../components/emoji/lib'; // State -export type PreferredReactionsStateType = { +export type PreferredReactionsStateType = ReadonlyDeep<{ customizePreferredReactionsModal?: { draftPreferredReactions: Array; originalPreferredReactions: Array; @@ -25,7 +26,7 @@ export type PreferredReactionsStateType = { | { isSaving: true; hadSaveError: false } | { isSaving: false; hadSaveError: boolean } ); -}; +}>; // Actions @@ -46,45 +47,47 @@ const SAVE_PREFERRED_REACTIONS_REJECTED = const SELECT_DRAFT_EMOJI_TO_BE_REPLACED = 'preferredReactions/SELECT_DRAFT_EMOJI_TO_BE_REPLACED'; -type CancelCustomizePreferredReactionsModalActionType = { +type CancelCustomizePreferredReactionsModalActionType = ReadonlyDeep<{ type: typeof CANCEL_CUSTOMIZE_PREFERRED_REACTIONS_MODAL; -}; +}>; -type DeselectDraftEmojiActionType = { type: typeof DESELECT_DRAFT_EMOJI }; +type DeselectDraftEmojiActionType = ReadonlyDeep<{ + type: typeof DESELECT_DRAFT_EMOJI; +}>; -type OpenCustomizePreferredReactionsModalActionType = { +type OpenCustomizePreferredReactionsModalActionType = ReadonlyDeep<{ type: typeof OPEN_CUSTOMIZE_PREFERRED_REACTIONS_MODAL; payload: { originalPreferredReactions: Array; }; -}; +}>; -type ReplaceSelectedDraftEmojiActionType = { +type ReplaceSelectedDraftEmojiActionType = ReadonlyDeep<{ type: typeof REPLACE_SELECTED_DRAFT_EMOJI; payload: string; -}; +}>; -type ResetDraftEmojiActionType = { +type ResetDraftEmojiActionType = ReadonlyDeep<{ type: typeof RESET_DRAFT_EMOJI; payload: { skinTone: number }; -}; +}>; -type SavePreferredReactionsFulfilledActionType = { +type SavePreferredReactionsFulfilledActionType = ReadonlyDeep<{ type: typeof SAVE_PREFERRED_REACTIONS_FULFILLED; -}; +}>; -type SavePreferredReactionsPendingActionType = { +type SavePreferredReactionsPendingActionType = ReadonlyDeep<{ type: typeof SAVE_PREFERRED_REACTIONS_PENDING; -}; +}>; -type SavePreferredReactionsRejectedActionType = { +type SavePreferredReactionsRejectedActionType = ReadonlyDeep<{ type: typeof SAVE_PREFERRED_REACTIONS_REJECTED; -}; +}>; -type SelectDraftEmojiToBeReplacedActionType = { +type SelectDraftEmojiToBeReplacedActionType = ReadonlyDeep<{ type: typeof SELECT_DRAFT_EMOJI_TO_BE_REPLACED; payload: number; -}; +}>; // Action creators diff --git a/ts/state/ducks/safetyNumber.ts b/ts/state/ducks/safetyNumber.ts index f0f6f8dd2b49..1884c231cbb3 100644 --- a/ts/state/ducks/safetyNumber.ts +++ b/ts/state/ducks/safetyNumber.ts @@ -1,6 +1,7 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import type { ReadonlyDeep } from 'type-fest'; import { generateSecurityNumberBlock } from '../../util/safetyNumber'; import type { ConversationType } from './conversations'; import { @@ -10,17 +11,17 @@ import { import * as log from '../../logging/log'; import * as Errors from '../../types/errors'; -export type SafetyNumberContactType = { +export type SafetyNumberContactType = ReadonlyDeep<{ safetyNumber: string; safetyNumberChanged?: boolean; verificationDisabled: boolean; -}; +}>; -export type SafetyNumberStateType = { +export type SafetyNumberStateType = ReadonlyDeep<{ contacts: { [key: string]: SafetyNumberContactType; }; -}; +}>; const GENERATE = 'safetyNumber/GENERATE'; const GENERATE_FULFILLED = 'safetyNumber/GENERATE_FULFILLED'; @@ -28,51 +29,52 @@ const TOGGLE_VERIFIED = 'safetyNumber/TOGGLE_VERIFIED'; const TOGGLE_VERIFIED_FULFILLED = 'safetyNumber/TOGGLE_VERIFIED_FULFILLED'; const TOGGLE_VERIFIED_PENDING = 'safetyNumber/TOGGLE_VERIFIED_PENDING'; -type GenerateAsyncActionType = { +type GenerateAsyncActionType = ReadonlyDeep<{ contact: ConversationType; safetyNumber: string; -}; +}>; -type GenerateActionType = { +type GenerateActionType = ReadonlyDeep<{ type: 'safetyNumber/GENERATE'; payload: Promise; -}; +}>; -type GenerateFulfilledActionType = { +type GenerateFulfilledActionType = ReadonlyDeep<{ type: 'safetyNumber/GENERATE_FULFILLED'; payload: GenerateAsyncActionType; -}; +}>; -type ToggleVerifiedAsyncActionType = { +type ToggleVerifiedAsyncActionType = ReadonlyDeep<{ contact: ConversationType; safetyNumber?: string; safetyNumberChanged?: boolean; -}; +}>; -type ToggleVerifiedActionType = { +type ToggleVerifiedActionType = ReadonlyDeep<{ type: 'safetyNumber/TOGGLE_VERIFIED'; payload: { data: { contact: ConversationType }; promise: Promise; }; -}; +}>; -type ToggleVerifiedPendingActionType = { +type ToggleVerifiedPendingActionType = ReadonlyDeep<{ type: 'safetyNumber/TOGGLE_VERIFIED_PENDING'; payload: ToggleVerifiedAsyncActionType; -}; +}>; -type ToggleVerifiedFulfilledActionType = { +type ToggleVerifiedFulfilledActionType = ReadonlyDeep<{ type: 'safetyNumber/TOGGLE_VERIFIED_FULFILLED'; payload: ToggleVerifiedAsyncActionType; -}; +}>; -export type SafetyNumberActionType = +export type SafetyNumberActionType = ReadonlyDeep< | GenerateActionType | GenerateFulfilledActionType | ToggleVerifiedActionType | ToggleVerifiedPendingActionType - | ToggleVerifiedFulfilledActionType; + | ToggleVerifiedFulfilledActionType +>; function generate(contact: ConversationType): GenerateActionType { return { diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 961bf600e608..54c9768a5341 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -4,6 +4,7 @@ import type { ThunkAction, ThunkDispatch } from 'redux-thunk'; import { debounce, omit, reject } from 'lodash'; +import type { ReadonlyDeep } from 'type-fest'; import type { StateType as RootStateType } from '../reducer'; import { cleanSearchTerm } from '../../util/cleanSearchTerm'; import { filterAndSortConversationsByRecent } from '../../util/filterAndSortConversations'; @@ -43,15 +44,17 @@ const { // State -export type MessageSearchResultType = MessageType & { - snippet?: string; -}; +export type MessageSearchResultType = ReadonlyDeep< + MessageType & { + snippet?: string; + } +>; -export type MessageSearchResultLookupType = { +export type MessageSearchResultLookupType = ReadonlyDeep<{ [id: string]: MessageSearchResultType; -}; +}>; -export type SearchStateType = { +export type SearchStateType = ReadonlyDeep<{ startSearchCounter: number; searchConversationId?: string; contactIds: Array; @@ -64,49 +67,49 @@ export type SearchStateType = { // Loading state discussionsLoading: boolean; messagesLoading: boolean; -}; +}>; // Actions -type SearchMessagesResultsFulfilledActionType = { +type SearchMessagesResultsFulfilledActionType = ReadonlyDeep<{ type: 'SEARCH_MESSAGES_RESULTS_FULFILLED'; payload: { messages: Array; query: string; }; -}; -type SearchDiscussionsResultsFulfilledActionType = { +}>; +type SearchDiscussionsResultsFulfilledActionType = ReadonlyDeep<{ type: 'SEARCH_DISCUSSIONS_RESULTS_FULFILLED'; payload: { conversationIds: Array; contactIds: Array; query: string; }; -}; -type UpdateSearchTermActionType = { +}>; +type UpdateSearchTermActionType = ReadonlyDeep<{ type: 'SEARCH_UPDATE'; payload: { query: string; }; -}; -type StartSearchActionType = { +}>; +type StartSearchActionType = ReadonlyDeep<{ type: 'SEARCH_START'; payload: null; -}; -type ClearSearchActionType = { +}>; +type ClearSearchActionType = ReadonlyDeep<{ type: 'SEARCH_CLEAR'; payload: null; -}; -type ClearConversationSearchActionType = { +}>; +type ClearConversationSearchActionType = ReadonlyDeep<{ type: 'CLEAR_CONVERSATION_SEARCH'; payload: null; -}; -type SearchInConversationActionType = { +}>; +type SearchInConversationActionType = ReadonlyDeep<{ type: 'SEARCH_IN_CONVERSATION'; payload: { searchConversationId: string }; -}; +}>; -export type SearchActionType = +export type SearchActionType = ReadonlyDeep< | SearchMessagesResultsFulfilledActionType | SearchDiscussionsResultsFulfilledActionType | UpdateSearchTermActionType @@ -118,7 +121,8 @@ export type SearchActionType = | RemoveAllConversationsActionType | SelectedConversationChangedActionType | ShowArchivedConversationsActionType - | ConversationUnloadedActionType; + | ConversationUnloadedActionType +>; // Action Creators diff --git a/ts/state/ducks/stickers.ts b/ts/state/ducks/stickers.ts index c2d5d79ba6c0..389f0bb2ae11 100644 --- a/ts/state/ducks/stickers.ts +++ b/ts/state/ducks/stickers.ts @@ -3,6 +3,7 @@ import type { Dictionary } from 'lodash'; import { omit, reject } from 'lodash'; +import type { ReadonlyDeep } from 'type-fest'; import type { StickerPackStatusType, StickerType as StickerDBType, @@ -24,23 +25,23 @@ const { getRecentStickers, updateStickerLastUsed } = dataInterface; // State -export type StickersStateType = { - readonly installedPack: string | null; - readonly packs: Dictionary; - readonly recentStickers: Array; - readonly blessedPacks: Dictionary; -}; +export type StickersStateType = ReadonlyDeep<{ + installedPack: string | null; + packs: Dictionary; + recentStickers: Array; + blessedPacks: Dictionary; +}>; // These are for the React components -export type StickerType = { - readonly id: number; - readonly packId: string; - readonly emoji?: string; - readonly url: string; -}; +export type StickerType = ReadonlyDeep<{ + id: number; + packId: string; + emoji?: string; + url: string; +}>; -export type StickerPackType = Readonly<{ +export type StickerPackType = ReadonlyDeep<{ id: string; key: string; title: string; @@ -56,76 +57,76 @@ export type StickerPackType = Readonly<{ // Actions -type StickerPackAddedAction = { +type StickerPackAddedAction = ReadonlyDeep<{ type: 'stickers/STICKER_PACK_ADDED'; payload: StickerPackDBType; -}; +}>; -type StickerAddedAction = { +type StickerAddedAction = ReadonlyDeep<{ type: 'stickers/STICKER_ADDED'; payload: StickerDBType; -}; +}>; -type InstallStickerPackPayloadType = { +type InstallStickerPackPayloadType = ReadonlyDeep<{ packId: string; fromSync: boolean; status: 'installed'; installedAt: number; recentStickers: Array; -}; -type InstallStickerPackAction = { +}>; +type InstallStickerPackAction = ReadonlyDeep<{ type: 'stickers/INSTALL_STICKER_PACK'; payload: Promise; -}; -type InstallStickerPackFulfilledAction = { +}>; +type InstallStickerPackFulfilledAction = ReadonlyDeep<{ type: 'stickers/INSTALL_STICKER_PACK_FULFILLED'; payload: InstallStickerPackPayloadType; -}; -type ClearInstalledStickerPackAction = { +}>; +type ClearInstalledStickerPackAction = ReadonlyDeep<{ type: 'stickers/CLEAR_INSTALLED_STICKER_PACK'; -}; +}>; -type UninstallStickerPackPayloadType = { +type UninstallStickerPackPayloadType = ReadonlyDeep<{ packId: string; fromSync: boolean; status: 'downloaded'; installedAt?: undefined; recentStickers: Array; -}; -type UninstallStickerPackAction = { +}>; +type UninstallStickerPackAction = ReadonlyDeep<{ type: 'stickers/UNINSTALL_STICKER_PACK'; payload: Promise; -}; -type UninstallStickerPackFulfilledAction = { +}>; +type UninstallStickerPackFulfilledAction = ReadonlyDeep<{ type: 'stickers/UNINSTALL_STICKER_PACK_FULFILLED'; payload: UninstallStickerPackPayloadType; -}; +}>; -type StickerPackUpdatedAction = { +type StickerPackUpdatedAction = ReadonlyDeep<{ type: 'stickers/STICKER_PACK_UPDATED'; payload: { packId: string; patch: Partial }; -}; +}>; -type StickerPackRemovedAction = { +type StickerPackRemovedAction = ReadonlyDeep<{ type: 'stickers/REMOVE_STICKER_PACK'; payload: string; -}; +}>; -type UseStickerPayloadType = { +type UseStickerPayloadType = ReadonlyDeep<{ packId: string; stickerId: number; time: number; -}; -type UseStickerAction = { +}>; +type UseStickerAction = ReadonlyDeep<{ type: 'stickers/USE_STICKER'; payload: Promise; -}; -type UseStickerFulfilledAction = { +}>; +type UseStickerFulfilledAction = ReadonlyDeep<{ type: 'stickers/USE_STICKER_FULFILLED'; payload: UseStickerPayloadType; -}; +}>; -export type StickersActionType = +export type StickersActionType = ReadonlyDeep< | ClearInstalledStickerPackAction | StickerAddedAction | StickerPackAddedAction @@ -134,7 +135,8 @@ export type StickersActionType = | StickerPackUpdatedAction | StickerPackRemovedAction | UseStickerFulfilledAction - | NoopActionType; + | NoopActionType +>; // Action Creators diff --git a/ts/state/ducks/stories.ts b/ts/state/ducks/stories.ts index 30906f50b495..f39e50850145 100644 --- a/ts/state/ducks/stories.ts +++ b/ts/state/ducks/stories.ts @@ -4,6 +4,7 @@ import type { ThunkAction, ThunkDispatch } from 'redux-thunk'; import { isEqual, pick } from 'lodash'; +import type { ReadonlyDeep } from 'type-fest'; import * as Errors from '../../types/errors'; import type { AttachmentType } from '../../types/Attachment'; import type { DraftBodyRangesType } from '../../types/Util'; @@ -60,44 +61,46 @@ import { SHOW_TOAST } from './toast'; import { ToastType } from '../../types/Toast'; import type { ShowToastActionType } from './toast'; -export type StoryDataType = { - attachment?: AttachmentType; - hasReplies?: boolean; - hasRepliesFromSelf?: boolean; - messageId: string; - startedDownload?: boolean; -} & Pick< - MessageAttributesType, - | 'canReplyToStory' - | 'conversationId' - | 'deletedForEveryone' - | 'reactions' - | 'readAt' - | 'readStatus' - | 'sendStateByConversationId' - | 'source' - | 'sourceUuid' - | 'storyDistributionListId' - | 'timestamp' - | 'type' - | 'storyRecipientsVersion' -> & { - // don't want the fields to be optional as in MessageAttributesType - expireTimer: DurationInSeconds | undefined; - expirationStartTimestamp: number | undefined; - sourceDevice: number; - }; +export type StoryDataType = ReadonlyDeep< + { + attachment?: AttachmentType; + hasReplies?: boolean; + hasRepliesFromSelf?: boolean; + messageId: string; + startedDownload?: boolean; + } & Pick< + MessageAttributesType, + | 'canReplyToStory' + | 'conversationId' + | 'deletedForEveryone' + | 'reactions' + | 'readAt' + | 'readStatus' + | 'sendStateByConversationId' + | 'source' + | 'sourceUuid' + | 'storyDistributionListId' + | 'timestamp' + | 'type' + | 'storyRecipientsVersion' + > & { + // don't want the fields to be optional as in MessageAttributesType + expireTimer: DurationInSeconds | undefined; + expirationStartTimestamp: number | undefined; + sourceDevice: number; + } +>; -export type SelectedStoryDataType = { +export type SelectedStoryDataType = ReadonlyDeep<{ currentIndex: number; messageId: string; numStories: number; storyViewMode: StoryViewModeType; unviewedStoryConversationIdsSorted: Array; viewTarget?: StoryViewTargetType; -}; +}>; -export type AddStoryData = +export type AddStoryData = ReadonlyDeep< | { type: 'Media'; file: File; @@ -107,8 +110,10 @@ export type AddStoryData = type: 'Text'; sending?: boolean; } - | undefined; + | undefined +>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type RecipientsByConversation = Record< string, // conversationId { @@ -125,6 +130,7 @@ export type RecipientsByConversation = Record< // State +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type StoriesStateType = Readonly<{ addStoryData: AddStoryData; hasAllStoriesUnmuted: boolean; @@ -157,20 +163,21 @@ const SET_ADD_STORY_DATA = 'stories/SET_ADD_STORY_DATA'; const SET_STORY_SENDING = 'stories/SET_STORY_SENDING'; const SET_HAS_ALL_STORIES_UNMUTED = 'stories/SET_HAS_ALL_STORIES_UNMUTED'; -type DOEStoryActionType = { +type DOEStoryActionType = ReadonlyDeep<{ type: typeof DOE_STORY; payload: string; -}; +}>; -type ListMembersVerified = { +type ListMembersVerified = ReadonlyDeep<{ type: typeof LIST_MEMBERS_VERIFIED; payload: { conversationId: string; distributionId: string | undefined; uuids: Array; }; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep type LoadStoryRepliesActionType = { type: typeof LOAD_STORY_REPLIES; payload: { @@ -179,62 +186,63 @@ type LoadStoryRepliesActionType = { }; }; -type MarkStoryReadActionType = { +type MarkStoryReadActionType = ReadonlyDeep<{ type: typeof MARK_STORY_READ; payload: { messageId: string; readAt: number; }; -}; +}>; -type QueueStoryDownloadActionType = { +type QueueStoryDownloadActionType = ReadonlyDeep<{ type: typeof QUEUE_STORY_DOWNLOAD; payload: string; -}; +}>; -type SendStoryModalOpenStateChanged = { +type SendStoryModalOpenStateChanged = ReadonlyDeep<{ type: typeof SEND_STORY_MODAL_OPEN_STATE_CHANGED; payload: number | undefined; -}; +}>; -type StoryChangedActionType = { +type StoryChangedActionType = ReadonlyDeep<{ type: typeof STORY_CHANGED; payload: StoryDataType; -}; +}>; -type ToggleViewActionType = { +type ToggleViewActionType = ReadonlyDeep<{ type: typeof TOGGLE_VIEW; -}; +}>; -type ViewStoryActionType = { +type ViewStoryActionType = ReadonlyDeep<{ type: typeof VIEW_STORY; payload: SelectedStoryDataType | undefined; -}; +}>; -type StoryReplyDeletedActionType = { +type StoryReplyDeletedActionType = ReadonlyDeep<{ type: typeof STORY_REPLY_DELETED; payload: string; -}; +}>; -type RemoveAllStoriesActionType = { +type RemoveAllStoriesActionType = ReadonlyDeep<{ type: typeof REMOVE_ALL_STORIES; -}; +}>; -type SetAddStoryDataType = { +type SetAddStoryDataType = ReadonlyDeep<{ type: typeof SET_ADD_STORY_DATA; payload: AddStoryData; -}; +}>; -type SetStorySendingType = { +type SetStorySendingType = ReadonlyDeep<{ type: typeof SET_STORY_SENDING; payload: boolean; -}; +}>; -type SetHasAllStoriesUnmutedType = { +type SetHasAllStoriesUnmutedType = ReadonlyDeep<{ type: typeof SET_HAS_ALL_STORIES_UNMUTED; payload: boolean; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type StoriesActionType = | DOEStoryActionType | ListMembersVerified @@ -817,11 +825,13 @@ const getSelectedStoryDataForConversationId = ( }; }; -export type ViewUserStoriesActionCreatorType = (opts: { - conversationId: string; - storyViewMode?: StoryViewModeType; - viewTarget?: StoryViewTargetType; -}) => unknown; +export type ViewUserStoriesActionCreatorType = ReadonlyDeep< + (opts: { + conversationId: string; + storyViewMode?: StoryViewModeType; + viewTarget?: StoryViewTargetType; + }) => unknown +>; const viewUserStories: ViewUserStoriesActionCreatorType = ({ conversationId, @@ -886,7 +896,7 @@ function removeAllStories(): RemoveAllStoriesActionType { }; } -type ViewStoryOptionsType = +type ViewStoryOptionsType = ReadonlyDeep< | { closeViewer: true; } @@ -895,15 +905,18 @@ type ViewStoryOptionsType = storyViewMode: StoryViewModeType; viewDirection?: StoryViewDirectionType; viewTarget?: StoryViewTargetType; - }; + } +>; -export type ViewStoryActionCreatorType = ( - opts: ViewStoryOptionsType -) => unknown; +export type ViewStoryActionCreatorType = ReadonlyDeep< + (opts: ViewStoryOptionsType) => unknown +>; -export type DispatchableViewStoryType = ( - opts: ViewStoryOptionsType -) => ThunkAction; +export type DispatchableViewStoryType = ReadonlyDeep< + ( + opts: ViewStoryOptionsType + ) => ThunkAction +>; const viewStory: ViewStoryActionCreatorType = ( opts diff --git a/ts/state/ducks/storyDistributionLists.ts b/ts/state/ducks/storyDistributionLists.ts index 8254a94d9edd..0ffeba5ece29 100644 --- a/ts/state/ducks/storyDistributionLists.ts +++ b/ts/state/ducks/storyDistributionLists.ts @@ -4,6 +4,7 @@ import { omit } from 'lodash'; import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { StateType as RootStateType } from '../reducer'; import type { StoryDistributionWithMembersType } from '../../sql/Interface'; import type { UUIDStringType } from '../../types/UUID'; @@ -19,18 +20,18 @@ import { useBoundActions } from '../../hooks/useBoundActions'; // State -export type StoryDistributionListDataType = { +export type StoryDistributionListDataType = ReadonlyDeep<{ id: UUIDStringType; deletedAtTimestamp?: number; name: string; allowsReplies: boolean; isBlockList: boolean; memberUuids: Array; -}; +}>; -export type StoryDistributionListStateType = { +export type StoryDistributionListStateType = ReadonlyDeep<{ distributionLists: Array; -}; +}>; // Actions @@ -43,65 +44,65 @@ export const MODIFY_LIST = 'storyDistributionLists/MODIFY_LIST'; const RESET_MY_STORIES = 'storyDistributionLists/RESET_MY_STORIES'; export const VIEWERS_CHANGED = 'storyDistributionLists/VIEWERS_CHANGED'; -type AllowRepliesChangedActionType = { +type AllowRepliesChangedActionType = ReadonlyDeep<{ type: typeof ALLOW_REPLIES_CHANGED; payload: { listId: string; allowsReplies: boolean; }; -}; +}>; -type CreateListActionType = { +type CreateListActionType = ReadonlyDeep<{ type: typeof CREATE_LIST; payload: StoryDistributionListDataType; -}; +}>; -type DeleteListActionType = { +type DeleteListActionType = ReadonlyDeep<{ type: typeof DELETE_LIST; payload: { listId: string; deletedAtTimestamp: number; }; -}; +}>; -type HideMyStoriesFromActionType = { +type HideMyStoriesFromActionType = ReadonlyDeep<{ type: typeof HIDE_MY_STORIES_FROM; payload: Array; -}; +}>; -type ModifyDistributionListType = Omit< - StoryDistributionListDataType, - 'memberUuids' -> & { - membersToAdd: Array; - membersToRemove: Array; -}; +type ModifyDistributionListType = ReadonlyDeep< + Omit & { + membersToAdd: Array; + membersToRemove: Array; + } +>; -export type ModifyListActionType = { +export type ModifyListActionType = ReadonlyDeep<{ type: typeof MODIFY_LIST; payload: ModifyDistributionListType; -}; +}>; -type ResetMyStoriesActionType = { +type ResetMyStoriesActionType = ReadonlyDeep<{ type: typeof RESET_MY_STORIES; -}; +}>; -type ViewersChangedActionType = { +type ViewersChangedActionType = ReadonlyDeep<{ type: typeof VIEWERS_CHANGED; payload: { listId: string; memberUuids: Array; }; -}; +}>; -export type StoryDistributionListsActionType = +export type StoryDistributionListsActionType = ReadonlyDeep< | AllowRepliesChangedActionType | CreateListActionType | DeleteListActionType | HideMyStoriesFromActionType | ModifyListActionType | ResetMyStoriesActionType - | ViewersChangedActionType; + | ViewersChangedActionType +>; // Action Creators @@ -503,7 +504,7 @@ export function getEmptyState(): StoryDistributionListStateType { } function replaceDistributionListData( - distributionLists: Array, + distributionLists: ReadonlyArray, listId: string, getNextDistributionListData: ( list: StoryDistributionListDataType diff --git a/ts/state/ducks/toast.ts b/ts/state/ducks/toast.ts index c452d2f084f1..fec39499f4ee 100644 --- a/ts/state/ducks/toast.ts +++ b/ts/state/ducks/toast.ts @@ -3,6 +3,7 @@ import { ipcRenderer } from 'electron'; +import type { ReadonlyDeep } from 'type-fest'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import type { NoopActionType } from './noop'; import type { ReplacementValuesType } from '../../types/Util'; @@ -11,6 +12,7 @@ import type { ToastType } from '../../types/Toast'; // State +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type ToastStateType = { toast?: { toastType: ToastType; @@ -23,10 +25,11 @@ export type ToastStateType = { const HIDE_TOAST = 'toast/HIDE_TOAST'; export const SHOW_TOAST = 'toast/SHOW_TOAST'; -type HideToastActionType = { +type HideToastActionType = ReadonlyDeep<{ type: typeof HIDE_TOAST; -}; +}>; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type ShowToastActionType = { type: typeof SHOW_TOAST; payload: { @@ -35,6 +38,7 @@ export type ShowToastActionType = { }; }; +// eslint-disable-next-line local-rules/type-alias-readonlydeep export type ToastActionType = HideToastActionType | ShowToastActionType; // Action Creators @@ -53,10 +57,12 @@ function openFileInFolder(target: string): NoopActionType { }; } -export type ShowToastActionCreatorType = ( - toastType: ToastType, - parameters?: ReplacementValuesType -) => ShowToastActionType; +export type ShowToastActionCreatorType = ReadonlyDeep< + ( + toastType: ToastType, + parameters?: ReplacementValuesType + ) => ShowToastActionType +>; export const showToast: ShowToastActionCreatorType = ( toastType, diff --git a/ts/state/ducks/updates.ts b/ts/state/ducks/updates.ts index 5a02f95735ef..1ba2b409f4d4 100644 --- a/ts/state/ducks/updates.ts +++ b/ts/state/ducks/updates.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import * as updateIpc from '../../shims/updateIpc'; import { DialogType } from '../../types/Dialogs'; import { DAY } from '../../util/durations'; @@ -9,14 +10,14 @@ import type { StateType as RootStateType } from '../reducer'; // State -export type UpdatesStateType = { +export type UpdatesStateType = ReadonlyDeep<{ dialogType: DialogType; didSnooze: boolean; downloadSize?: number; downloadedSize?: number; showEventsCount: number; version?: string; -}; +}>; // Actions @@ -26,43 +27,44 @@ const SNOOZE_UPDATE = 'updates/SNOOZE_UPDATE'; const START_UPDATE = 'updates/START_UPDATE'; const UNSNOOZE_UPDATE = 'updates/UNSNOOZE_UPDATE'; -export type UpdateDialogOptionsType = { +export type UpdateDialogOptionsType = ReadonlyDeep<{ downloadSize?: number; downloadedSize?: number; version?: string; -}; +}>; -type DismissDialogActionType = { +type DismissDialogActionType = ReadonlyDeep<{ type: typeof DISMISS_DIALOG; -}; +}>; -export type ShowUpdateDialogActionType = { +export type ShowUpdateDialogActionType = ReadonlyDeep<{ type: typeof SHOW_UPDATE_DIALOG; payload: { dialogType: DialogType; otherState: UpdateDialogOptionsType; }; -}; +}>; -type SnoozeUpdateActionType = { +type SnoozeUpdateActionType = ReadonlyDeep<{ type: typeof SNOOZE_UPDATE; -}; +}>; -type StartUpdateActionType = { +type StartUpdateActionType = ReadonlyDeep<{ type: typeof START_UPDATE; -}; +}>; -type UnsnoozeUpdateActionType = { +type UnsnoozeUpdateActionType = ReadonlyDeep<{ type: typeof UNSNOOZE_UPDATE; payload: DialogType; -}; +}>; -export type UpdatesActionType = +export type UpdatesActionType = ReadonlyDeep< | DismissDialogActionType | ShowUpdateDialogActionType | SnoozeUpdateActionType | StartUpdateActionType - | UnsnoozeUpdateActionType; + | UnsnoozeUpdateActionType +>; // Action Creators diff --git a/ts/state/ducks/user.ts b/ts/state/ducks/user.ts index 58944282bcff..2f510a0ffd1d 100644 --- a/ts/state/ducks/user.ts +++ b/ts/state/ducks/user.ts @@ -1,6 +1,7 @@ // Copyright 2019 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import type { ReadonlyDeep } from 'type-fest'; import { trigger } from '../../shims/events'; import type { LocaleMessagesType } from '../../types/I18N'; @@ -13,7 +14,8 @@ import { ThemeType } from '../../types/Util'; // State -export type UserStateType = { +// eslint-disable-next-line local-rules/type-alias-readonlydeep +export type UserStateType = Readonly<{ attachmentsPath: string; i18n: LocalizerType; interactionMode: 'mouse' | 'keyboard'; @@ -33,11 +35,11 @@ export type UserStateType = { tempPath: string; theme: ThemeType; version: string; -}; +}>; // Actions -type UserChangedActionType = { +type UserChangedActionType = ReadonlyDeep<{ type: 'USER_CHANGED'; payload: { ourConversationId?: string; @@ -52,9 +54,9 @@ type UserChangedActionType = { isMainWindowFullScreen?: boolean; menuOptions?: MenuOptionsType; }; -}; +}>; -export type UserActionType = UserChangedActionType; +export type UserActionType = ReadonlyDeep; // Action Creators diff --git a/ts/state/ducks/username.ts b/ts/state/ducks/username.ts index 2e4dee3f896f..c4b58dba6e61 100644 --- a/ts/state/ducks/username.ts +++ b/ts/state/ducks/username.ts @@ -3,6 +3,7 @@ import type { ThunkAction } from 'redux-thunk'; +import type { ReadonlyDeep } from 'type-fest'; import type { UsernameReservationType } from '../../types/Username'; import { ReserveUsernameError } from '../../types/Username'; import * as usernameServices from '../../services/username'; @@ -27,14 +28,14 @@ import { showToast } from './toast'; import { ToastType } from '../../types/Toast'; import type { ToastActionType } from './toast'; -export type UsernameReservationStateType = Readonly<{ +export type UsernameReservationStateType = ReadonlyDeep<{ state: UsernameReservationState; reservation?: UsernameReservationType; error?: UsernameReservationError; abortController?: AbortController; }>; -export type UsernameStateType = Readonly<{ +export type UsernameStateType = ReadonlyDeep<{ // ProfileEditor editState: UsernameEditState; @@ -52,44 +53,51 @@ const RESERVE_USERNAME = 'username/RESERVE_USERNAME'; const CONFIRM_USERNAME = 'username/CONFIRM_USERNAME'; const DELETE_USERNAME = 'username/DELETE_USERNAME'; -type SetUsernameEditStateActionType = { +type SetUsernameEditStateActionType = ReadonlyDeep<{ type: typeof SET_USERNAME_EDIT_STATE; payload: { editState: UsernameEditState; }; -}; +}>; -type OpenUsernameReservationModalActionType = { +type OpenUsernameReservationModalActionType = ReadonlyDeep<{ type: typeof OPEN_USERNAME_RESERVATION_MODAL; -}; +}>; -type CloseUsernameReservationModalActionType = { +type CloseUsernameReservationModalActionType = ReadonlyDeep<{ type: typeof CLOSE_USERNAME_RESERVATION_MODAL; -}; +}>; -type SetUsernameReservationErrorActionType = { +type SetUsernameReservationErrorActionType = ReadonlyDeep<{ type: typeof SET_USERNAME_RESERVATION_ERROR; payload: { error: UsernameReservationError | undefined; }; -}; +}>; -type ReserveUsernameActionType = PromiseAction< - typeof RESERVE_USERNAME, - ReserveUsernameResultType | undefined, - { abortController: AbortController } +type ReserveUsernameActionType = ReadonlyDeep< + PromiseAction< + typeof RESERVE_USERNAME, + ReserveUsernameResultType | undefined, + { abortController: AbortController } + > +>; +type ConfirmUsernameActionType = ReadonlyDeep< + PromiseAction +>; +type DeleteUsernameActionType = ReadonlyDeep< + PromiseAction >; -type ConfirmUsernameActionType = PromiseAction; -type DeleteUsernameActionType = PromiseAction; -export type UsernameActionType = +export type UsernameActionType = ReadonlyDeep< | SetUsernameEditStateActionType | OpenUsernameReservationModalActionType | CloseUsernameReservationModalActionType | SetUsernameReservationErrorActionType | ReserveUsernameActionType | ConfirmUsernameActionType - | DeleteUsernameActionType; + | DeleteUsernameActionType +>; export const actions = { setUsernameEditState, @@ -133,7 +141,7 @@ export function setUsernameReservationError( const INPUT_DELAY_MS = 500; -export type ReserveUsernameOptionsType = Readonly<{ +export type ReserveUsernameOptionsType = ReadonlyDeep<{ doReserveUsername?: typeof usernameServices.reserveUsername; delay?: number; }>; @@ -194,7 +202,7 @@ export function reserveUsername( }; } -export type ConfirmUsernameOptionsType = Readonly<{ +export type ConfirmUsernameOptionsType = ReadonlyDeep<{ doConfirmUsername?: typeof usernameServices.confirmUsername; }>; @@ -221,7 +229,7 @@ export function confirmUsername({ }; } -export type DeleteUsernameOptionsType = Readonly<{ +export type DeleteUsernameOptionsType = ReadonlyDeep<{ doDeleteUsername?: typeof usernameServices.deleteUsername; // Only for testing diff --git a/ts/state/selectors/lightbox.ts b/ts/state/selectors/lightbox.ts index 49c0478b2dd2..64a483161281 100644 --- a/ts/state/selectors/lightbox.ts +++ b/ts/state/selectors/lightbox.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { createSelector } from 'reselect'; +import type { ReadonlyDeep } from 'type-fest'; import type { MediaItemType } from '../../types/MediaItem'; import type { StateType } from '../reducer'; import type { LightboxStateType } from '../ducks/lightbox'; @@ -36,6 +37,6 @@ export const getSelectedIndex = createSelector( export const getMedia = createSelector( getLightboxState, - (state): ReadonlyArray => + (state): ReadonlyArray> => state.isShowingLightbox ? state.media : [] ); diff --git a/ts/state/selectors/stickers.ts b/ts/state/selectors/stickers.ts index de19e8a67509..d196ff09b885 100644 --- a/ts/state/selectors/stickers.ts +++ b/ts/state/selectors/stickers.ts @@ -125,7 +125,7 @@ export const getRecentStickers = createSelector( getStickersPath, getTempPath, ( - recents: Array, + recents: ReadonlyArray, packs: Dictionary, stickersPath: string, tempPath: string diff --git a/ts/state/smart/AllMedia.tsx b/ts/state/smart/AllMedia.tsx index 60aa86d4f482..55a7080d4d36 100644 --- a/ts/state/smart/AllMedia.tsx +++ b/ts/state/smart/AllMedia.tsx @@ -8,6 +8,7 @@ import { MediaGallery } from '../../components/conversation/media-gallery/MediaG import { getMediaGalleryState } from '../selectors/mediaGallery'; import { useConversationsActions } from '../ducks/conversations'; import { useLightboxActions } from '../ducks/lightbox'; + import { useMediaGalleryActions } from '../ducks/mediaGallery'; export type PropsType = { diff --git a/ts/state/smart/Lightbox.tsx b/ts/state/smart/Lightbox.tsx index 6eba3dd326fa..b1e0ea99a039 100644 --- a/ts/state/smart/Lightbox.tsx +++ b/ts/state/smart/Lightbox.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { useSelector } from 'react-redux'; +import type { ReadonlyDeep } from 'type-fest'; import type { GetConversationByIdType } from '../selectors/conversations'; import type { LocalizerType } from '../../types/Util'; import type { MediaItemType } from '../../types/MediaItem'; @@ -33,7 +34,10 @@ export function SmartLightbox(): JSX.Element | null { const isShowingLightbox = useSelector(shouldShowLightbox); const isViewOnce = useSelector(getIsViewOnce); - const media = useSelector>(getMedia); + const media = useSelector< + StateType, + ReadonlyArray> + >(getMedia); const selectedIndex = useSelector(getSelectedIndex); if (!isShowingLightbox) { diff --git a/ts/state/smart/Timeline.tsx b/ts/state/smart/Timeline.tsx index ec9a69589a92..a2aef61e8d5b 100644 --- a/ts/state/smart/Timeline.tsx +++ b/ts/state/smart/Timeline.tsx @@ -6,6 +6,7 @@ import type { RefObject } from 'react'; import React from 'react'; import { connect } from 'react-redux'; +import type { ReadonlyDeep } from 'type-fest'; import { mapDispatchToProps } from '../actions'; import type { ContactSpoofingReviewPropType, @@ -103,7 +104,7 @@ function renderTypingBubble(id: string): JSX.Element { } const getWarning = ( - conversation: Readonly, + conversation: ReadonlyDeep, state: Readonly ): undefined | TimelineWarningType => { switch (conversation.type) { diff --git a/ts/util/groupMemberNameCollisions.ts b/ts/util/groupMemberNameCollisions.ts index 3f43e52b88c9..a9ffd9b8afc1 100644 --- a/ts/util/groupMemberNameCollisions.ts +++ b/ts/util/groupMemberNameCollisions.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { mapValues, pickBy } from 'lodash'; +import type { ReadonlyDeep } from 'type-fest'; import { groupBy, map, filter } from './iterables'; import { getOwn } from './getOwn'; import type { ConversationType } from '../state/ducks/conversations'; @@ -51,8 +52,8 @@ export function getCollisionsFromMemberships( * haven't dismissed. */ export const hasUnacknowledgedCollisions = ( - previous: Readonly, - current: Readonly + previous: ReadonlyDeep, + current: ReadonlyDeep ): boolean => Object.entries(current).some(([title, currentIds]) => { const previousIds = new Set(getOwn(previous, title) || []); @@ -60,7 +61,7 @@ export const hasUnacknowledgedCollisions = ( }); export const invertIdsByTitle = ( - idsByTitle: Readonly + idsByTitle: ReadonlyDeep ): GroupNameCollisionsWithTitlesById => { const result: GroupNameCollisionsWithTitlesById = Object.create(null); Object.entries(idsByTitle).forEach(([title, ids]) => { diff --git a/tsconfig.json b/tsconfig.json index 5466415bc029..dfe7ebea527f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "lib": [ "dom", // Required to access `window` "dom.iterable", - "es2020" + "es2022" ], "incremental": true, // "allowJs": true, // Allow javascript files to be compiled. diff --git a/yarn.lock b/yarn.lock index 0717d0afad11..fa76837851ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17726,6 +17726,11 @@ type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.5.0.tgz#df7b2ef54ea775163c56d087b33e901ce9d657f7" + integrity sha512-bI3zRmZC8K0tUz1HjbIOAGQwR2CoPQG68N5IF7gm0LBl8QSNXzkmaWnkWccCUL5uG9mCsp4sBwC8SBrNSISWew== + type-fest@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"