From d7307934bc46973c2f7148bf4b2d894af2387207 Mon Sep 17 00:00:00 2001 From: Josh Perez <60019601+josh-signal@users.noreply.github.com> Date: Tue, 12 Jul 2022 20:37:21 -0400 Subject: [PATCH] Adjust some types --- ts/background.ts | 34 ++++--- ts/jobs/helpers/sendNormalMessage.ts | 10 +- ts/model-types.d.ts | 18 +++- ts/models/conversations.ts | 145 +++++++++------------------ ts/models/messages.ts | 33 +++--- ts/shims/themeChanged.ts | 10 +- ts/signal.ts | 2 +- ts/state/getInitialState.ts | 4 +- ts/textsecure/SendMessage.ts | 24 ++--- ts/types/Attachment.ts | 21 ++-- ts/types/Message2.ts | 2 +- ts/types/StorageUIKeys.ts | 1 - ts/util/getThemeType.ts | 18 ++++ ts/util/validateConversation.ts | 62 ++++++++++++ ts/window.d.ts | 14 +-- 15 files changed, 223 insertions(+), 175 deletions(-) create mode 100644 ts/util/getThemeType.ts create mode 100644 ts/util/validateConversation.ts diff --git a/ts/background.ts b/ts/background.ts index bf23cbc9f..6ba99361a 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -21,11 +21,11 @@ import type { MessageAttributesType, ConversationAttributesType, ReactionAttributesType, + ValidateConversationType, } from './model-types.d'; import * as Bytes from './Bytes'; import * as Timers from './Timers'; import * as indexedDb from './indexeddb'; -import type { WhatIsThis } from './window.d'; import type { MenuOptionsType } from './types/menu'; import type { Receipt } from './types/Receipt'; import { SocketStatus } from './types/SocketStatus'; @@ -133,8 +133,7 @@ import { onRetryRequest, onDecryptionError } from './util/handleRetry'; import { themeChanged } from './shims/themeChanged'; import { createIPCEvents } from './util/createIPCEvents'; import { RemoveAllConfiguration } from './types/RemoveAllConfiguration'; -import { isValidUuid, UUIDKind } from './types/UUID'; -import type { UUID } from './types/UUID'; +import { isValidUuid, UUIDKind, UUID } from './types/UUID'; import * as log from './logging/log'; import { loadRecentEmojis } from './util/loadRecentEmojis'; import { deleteAllLogs } from './util/deleteAllLogs'; @@ -155,6 +154,7 @@ import { conversationJobQueue } from './jobs/conversationJobQueue'; import { SeenStatus } from './MessageSeenStatus'; import MessageSender from './textsecure/SendMessage'; import type AccountManager from './textsecure/AccountManager'; +import { validateConversation } from './util/validateConversation'; const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000; @@ -1227,7 +1227,7 @@ export async function startApp(): Promise { window.reduxActions.user.userChanged({ menuOptions: options }); }); - let shortcutGuideView: WhatIsThis | null = null; + let shortcutGuideView: ReactWrapperView | null = null; window.showKeyboardShortcuts = () => { if (!shortcutGuideView) { @@ -2724,12 +2724,13 @@ export async function startApp(): Promise { function onContactReceived(ev: ContactEvent) { const details = ev.contactDetails; - const c = new window.Whisper.Conversation({ + const partialConversation: ValidateConversationType = { e164: details.number, - uuid: details.uuid, + uuid: UUID.fromString(details.uuid), type: 'private', - } as Partial as WhatIsThis); - const validationError = c.validate(); + }; + + const validationError = validateConversation(partialConversation); if (validationError) { log.error( 'Invalid contact received:', @@ -3188,7 +3189,8 @@ export async function startApp(): Promise { .filter(isNotNil); } - return new window.Whisper.Message({ + const partialMessage: MessageAttributesType = { + id: UUID.generate().toString(), canReplyToStory: data.message.isStory ? data.message.canReplyToStory : undefined, @@ -3211,7 +3213,9 @@ export async function startApp(): Promise { type: data.message.isStory ? 'story' : 'outgoing', storyDistributionListId: data.storyDistributionListId, unidentifiedDeliveries, - } as Partial as WhatIsThis); + }; + + return new window.Whisper.Message(partialMessage); } // Works with 'sent' and 'message' data sent from MessageReceiver, with a little massage @@ -3446,7 +3450,8 @@ export async function startApp(): Promise { Boolean(data.receivedAtCounter), `Did not receive receivedAtCounter for message: ${data.timestamp}` ); - return new window.Whisper.Message({ + const partialMessage: MessageAttributesType = { + id: UUID.generate().toString(), canReplyToStory: data.message.isStory ? data.message.canReplyToStory : undefined, @@ -3460,11 +3465,14 @@ export async function startApp(): Promise { serverTimestamp: data.serverTimestamp, source: data.source, sourceDevice: data.sourceDevice, - sourceUuid: data.sourceUuid, + sourceUuid: data.sourceUuid + ? UUID.fromString(data.sourceUuid) + : undefined, timestamp: data.timestamp, type: data.message.isStory ? 'story' : 'incoming', unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived, - } as Partial as WhatIsThis); + }; + return new window.Whisper.Message(partialMessage); } // Returns `false` if this message isn't a group call message. diff --git a/ts/jobs/helpers/sendNormalMessage.ts b/ts/jobs/helpers/sendNormalMessage.ts index 8333a79ed..c963f01e9 100644 --- a/ts/jobs/helpers/sendNormalMessage.ts +++ b/ts/jobs/helpers/sendNormalMessage.ts @@ -20,8 +20,9 @@ import type { } from '../../textsecure/SendMessage'; import type { LinkPreviewType } from '../../types/message/LinkPreviews'; import type { BodyRangesType, StoryContextType } from '../../types/Util'; -import type { WhatIsThis } from '../../window.d'; import type { LoggerType } from '../../types/Logging'; +import type { StickerWithHydratedData } from '../../types/Stickers'; +import type { QuotedMessageType } from '../../model-types.d'; import type { ConversationQueueJobBundle, NormalMessageSendJobData, @@ -31,6 +32,7 @@ import { handleMultipleSendErrors } from './handleMultipleSendErrors'; import { ourProfileKeyService } from '../../services/ourProfileKey'; import { isConversationUnregistered } from '../../util/isConversationUnregistered'; import { isConversationAccepted } from '../../util/isConversationAccepted'; +import { sendToGroup } from '../../util/sendToGroup'; export async function sendNormalMessage( conversation: ConversationModel, @@ -207,7 +209,7 @@ export async function sendNormalMessage( innerPromise = conversation.queueJob( 'conversationQueue/sendNormalMessage', abortSignal => - window.Signal.Util.sendToGroup({ + sendToGroup({ abortSignal, contentHint: ContentHint.RESENDABLE, groupSendOptions: { @@ -428,8 +430,8 @@ async function getMessageSendData({ mentions: undefined | BodyRangesType; messageTimestamp: number; preview: Array; - quote: WhatIsThis; - sticker: WhatIsThis; + quote: QuotedMessageType | null; + sticker: StickerWithHydratedData | undefined; storyContext?: StoryContextType; }> { const { diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index bf690385b..f21ee910f 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -17,7 +17,11 @@ import { ReadStatus } from './messages/MessageReadStatus'; import { SendStateByConversationId } from './messages/MessageSendState'; import { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions'; import { ConversationColorType } from './types/Colors'; -import { AttachmentDraftType, AttachmentType } from './types/Attachment'; +import { + AttachmentDraftType, + AttachmentType, + ThumbnailType, +} from './types/Attachment'; import { EmbeddedContactType } from './types/EmbeddedContact'; import { SignalService as Proto } from './protobuf'; import { AvatarDataType } from './types/Avatar'; @@ -30,11 +34,10 @@ import { SeenStatus } from './MessageSeenStatus'; import { GiftBadgeStates } from './components/conversation/Message'; import { LinkPreviewType } from './types/message/LinkPreviews'; +import type { ProcessedQuoteAttachment } from './textsecure/Types.d'; import type { StickerType } from './types/Stickers'; import { MIMEType } from './types/MIME'; -export type WhatIsThis = any; - export type LastMessageStatus = | 'paused' | 'error' @@ -73,7 +76,9 @@ export type QuotedAttachment = { }; export type QuotedMessageType = { - attachments: Array; + // TODO DESKTOP-3826 + // eslint-disable-next-line no-explicit-any + attachments: Array; // `author` is an old attribute that holds the author's E164. We shouldn't use it for // new messages, but old messages might have this attribute. author?: string; @@ -245,6 +250,11 @@ export type ConversationLastProfileType = Readonly<{ profileKeyVersion: string; }>; +export type ValidateConversationType = Pick< + ConversationAttributesType, + 'e164' | 'uuid' | 'type' | 'groupId' +>; + export type ConversationAttributesType = { accessKey?: string | null; addedBy?: string; diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 0927559fd..863149865 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -1,7 +1,7 @@ // Copyright 2020-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { compact, isNumber, throttle, debounce } from 'lodash'; +import { compact, has, isNumber, throttle, debounce } from 'lodash'; import { batch as batchDispatch } from 'react-redux'; import PQueue from 'p-queue'; import { v4 as generateGuid } from 'uuid'; @@ -15,28 +15,24 @@ import type { QuotedMessageType, SenderKeyInfoType, VerificationOptions, - WhatIsThis, } from '../model-types.d'; import { getInitials } from '../util/getInitials'; import { normalizeUuid } from '../util/normalizeUuid'; -import { - getRegionCodeForNumber, - parseNumber, -} from '../util/libphonenumberUtil'; +import { getRegionCodeForNumber } from '../util/libphonenumberUtil'; import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; +import type { AttachmentType, ThumbnailType } from '../types/Attachment'; import { toDayMillis } from '../util/timestamp'; -import type { AttachmentType } from '../types/Attachment'; import { isGIF } from '../types/Attachment'; import type { CallHistoryDetailsType } from '../types/Calling'; import { CallMode } from '../types/Calling'; import * as EmbeddedContact from '../types/EmbeddedContact'; import * as Conversation from '../types/Conversation'; +import type { StickerType, StickerWithHydratedData } from '../types/Stickers'; import * as Stickers from '../types/Stickers'; import type { ContactWithHydratedAvatar, GroupV1InfoType, GroupV2InfoType, - StickerType, } from '../textsecure/SendMessage'; import createTaskWithTimeout from '../textsecure/TaskWithTimeout'; import MessageSender from '../textsecure/SendMessage'; @@ -58,7 +54,7 @@ import { sniffImageMimeType } from '../util/sniffImageMimeType'; import { isValidE164 } from '../util/isValidE164'; import type { MIMEType } from '../types/MIME'; import { IMAGE_JPEG, IMAGE_GIF, IMAGE_WEBP } from '../types/MIME'; -import { UUID, isValidUuid, UUIDKind } from '../types/UUID'; +import { UUID, UUIDKind } from '../types/UUID'; import type { UUIDStringType } from '../types/UUID'; import { deriveAccessKey, decryptProfileName, decryptProfile } from '../Crypto'; import * as Bytes from '../Bytes'; @@ -125,6 +121,7 @@ import { SeenStatus } from '../MessageSeenStatus'; import { getConversationIdForLogging } from '../util/idForLogging'; import { getSendTarget } from '../util/getSendTarget'; import { getRecipients } from '../util/getRecipients'; +import { validateConversation } from '../util/validateConversation'; /* eslint-disable more/no-then */ window.Whisper = window.Whisper || {}; @@ -3389,71 +3386,7 @@ export class ConversationModel extends window.Backbone } override validate(attributes = this.attributes): string | null { - const required = ['type']; - const missing = window._.filter(required, attr => !attributes[attr]); - if (missing.length) { - return `Conversation must have ${missing}`; - } - - if (attributes.type !== 'private' && attributes.type !== 'group') { - return `Invalid conversation type: ${attributes.type}`; - } - - const atLeastOneOf = ['e164', 'uuid', 'groupId']; - const hasAtLeastOneOf = - window._.filter(atLeastOneOf, attr => attributes[attr]).length > 0; - - if (!hasAtLeastOneOf) { - return 'Missing one of e164, uuid, or groupId'; - } - - const error = this.validateNumber() || this.validateUuid(); - - if (error) { - return error; - } - - return null; - } - - validateNumber(): string | null { - if (isDirectConversation(this.attributes) && this.get('e164')) { - const regionCode = window.storage.get('regionCode'); - if (!regionCode) { - throw new Error('No region code'); - } - const number = parseNumber( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.get('e164')!, - regionCode - ); - if (number.isValidNumber) { - this.set({ e164: number.e164 }); - return null; - } - - let errorMessage: undefined | string; - if (number.error instanceof Error) { - errorMessage = number.error.message; - } else if (typeof number.error === 'string') { - errorMessage = number.error; - } - return errorMessage || 'Invalid phone number'; - } - - return null; - } - - validateUuid(): string | null { - if (isDirectConversation(this.attributes) && this.get('uuid')) { - if (isValidUuid(this.get('uuid'))) { - return null; - } - - return 'Invalid UUID'; - } - - return null; + return validateConversation(attributes); } queueJob( @@ -3643,10 +3576,16 @@ export class ConversationModel extends window.Backbone } async getQuoteAttachment( - attachments?: Array, - preview?: Array, - sticker?: WhatIsThis - ): Promise { + attachments?: Array, + preview?: Array, + sticker?: StickerType + ): Promise< + Array<{ + contentType: MIMEType; + fileName: string | null; + thumbnail: ThumbnailType | null; + }> + > { if (attachments && attachments.length) { const attachmentsToUse = Array.from(take(attachments, 1)); const isGIFQuote = isGIF(attachmentsToUse); @@ -3673,7 +3612,9 @@ export class ConversationModel extends window.Backbone thumbnail: thumbnail ? { ...(await loadAttachmentData(thumbnail)), - objectUrl: getAbsoluteAttachmentPath(thumbnail.path), + objectUrl: thumbnail.path + ? getAbsoluteAttachmentPath(thumbnail.path) + : undefined, } : null, }; @@ -3708,7 +3649,9 @@ export class ConversationModel extends window.Backbone thumbnail: image ? { ...(await loadAttachmentData(image)), - objectUrl: getAbsoluteAttachmentPath(image.path), + objectUrl: image.path + ? getAbsoluteAttachmentPath(image.path) + : undefined, } : null, }; @@ -3727,7 +3670,7 @@ export class ConversationModel extends window.Backbone fileName: null, thumbnail: { ...(await loadAttachmentData(sticker.data)), - objectUrl: getAbsoluteAttachmentPath(path), + objectUrl: path ? getAbsoluteAttachmentPath(path) : undefined, }, }, ]; @@ -3797,7 +3740,7 @@ export class ConversationModel extends window.Backbone contentType = IMAGE_WEBP; } - const sticker = { + const sticker: StickerWithHydratedData = { packId, stickerId, packKey: key, @@ -3867,7 +3810,7 @@ export class ConversationModel extends window.Backbone mentions?: BodyRangesType; preview?: Array; quote?: QuotedMessageType; - sticker?: StickerType; + sticker?: StickerWithHydratedData; }, { dontClearDraft, @@ -5489,8 +5432,8 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({ ); }, - reset(...args: Array) { - window.Backbone.Collection.prototype.reset.apply(this, args as WhatIsThis); + reset(models?: Array, options?: Backbone.Silenceable) { + window.Backbone.Collection.prototype.reset.call(this, models, options); this.resetLookups(); }, @@ -5534,8 +5477,14 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({ this._byGroupId = Object.create(null); }, - add(data: WhatIsThis | Array) { - let hydratedData; + add( + data: + | ConversationModel + | ConversationAttributesType + | Array + | Array + ) { + let hydratedData: Array | ConversationModel; // First, we need to ensure that the data we're working with is Conversation models if (Array.isArray(data)) { @@ -5544,16 +5493,20 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({ const item = data[i]; // We create a new model if it's not already a model - if (!item.get) { - hydratedData.push(new window.Whisper.Conversation(item)); + if (has(item, 'get')) { + hydratedData.push(item as ConversationModel); } else { - hydratedData.push(item); + hydratedData.push( + new window.Whisper.Conversation(item as ConversationAttributesType) + ); } } - } else if (!data.get) { - hydratedData = new window.Whisper.Conversation(data); + } else if (has(data, 'get')) { + hydratedData = data as ConversationModel; } else { - hydratedData = data; + hydratedData = new window.Whisper.Conversation( + data as ConversationAttributesType + ); } // Next, we update our lookups first to prevent infinite loops on the 'add' event @@ -5562,7 +5515,9 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({ ); // Lastly, we fire off the add events related to this change - window.Backbone.Collection.prototype.add.call(this, hydratedData); + // Go home Backbone, you're drunk. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + window.Backbone.Collection.prototype.add.call(this, hydratedData as any); return hydratedData; }, @@ -5583,7 +5538,7 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({ ); }, - comparator(m: WhatIsThis) { + comparator(m: ConversationModel) { return -(m.get('active_at') || 0); }, }); diff --git a/ts/models/messages.ts b/ts/models/messages.ts index d4e570223..a901db1f6 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -8,7 +8,6 @@ import type { MessageAttributesType, MessageReactionType, QuotedMessageType, - WhatIsThis, } from '../model-types.d'; import { filter, @@ -46,7 +45,10 @@ import * as reactionUtil from '../reactions/util'; import * as Stickers from '../types/Stickers'; import * as Errors from '../types/errors'; import * as EmbeddedContact from '../types/EmbeddedContact'; -import type { AttachmentType } from '../types/Attachment'; +import type { + AttachmentType, + AttachmentWithHydratedData, +} from '../types/Attachment'; import { isImage, isVideo } from '../types/Attachment'; import * as Attachment from '../types/Attachment'; import { stringToMIMEType } from '../types/MIME'; @@ -158,6 +160,8 @@ import { isNewReactionReplacingPrevious } from '../reactions/util'; import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer'; import { GiftBadgeStates } from '../components/conversation/Message'; import { downloadAttachment } from '../util/downloadAttachment'; +import type { DeleteModel } from '../messageModifiers/Deletes'; +import type { StickerWithHydratedData } from '../types/Stickers'; /* eslint-disable more/no-then */ @@ -176,9 +180,14 @@ const { getTextWithMentions, GoogleChrome } = window.Signal.Util; const { getMessageBySender } = window.Signal.Data; export class MessageModel extends window.Backbone.Model { - static getLongMessageAttachment: ( - attachment: typeof window.WhatIsThis - ) => typeof window.WhatIsThis; + static getLongMessageAttachment: (opts: { + attachments: Array; + body?: string; + now: number; + }) => { + body?: string; + attachments: Array; + }; CURRENT_PROTOCOL_VERSION?: number; @@ -198,9 +207,9 @@ export class MessageModel extends window.Backbone.Model { cachedOutgoingPreviewData?: Array; - cachedOutgoingQuoteData?: WhatIsThis; + cachedOutgoingQuoteData?: QuotedMessageType; - cachedOutgoingStickerData?: WhatIsThis; + cachedOutgoingStickerData?: StickerWithHydratedData; override initialize(attributes: unknown): void { if (_.isObject(attributes)) { @@ -1910,7 +1919,7 @@ export class MessageModel extends window.Backbone.Model { // eslint-disable-next-line no-param-reassign quote.attachments = [ { - contentType: 'image/jpeg', + contentType: MIME.IMAGE_JPEG, }, ]; // eslint-disable-next-line no-param-reassign @@ -1942,7 +1951,7 @@ export class MessageModel extends window.Backbone.Model { // eslint-disable-next-line no-param-reassign quote.text = originalMessage.get('body'); if (firstAttachment) { - firstAttachment.thumbnail = undefined; + firstAttachment.thumbnail = null; } if ( @@ -2336,7 +2345,7 @@ export class MessageModel extends window.Backbone.Model { return; } - const messageId = UUID.generate().toString(); + const messageId = message.get('id') || UUID.generate().toString(); // Send delivery receipts, but only for incoming sealed sender messages // and not for messages from unaccepted conversations @@ -2379,7 +2388,7 @@ export class MessageModel extends window.Backbone.Model { const urls = LinkPreview.findLinks(dataMessage.body || ''); const incomingPreview = dataMessage.preview || []; const preview = incomingPreview.filter( - (item: typeof window.WhatIsThis) => + (item: LinkPreviewType) => (item.image || item.title) && urls.includes(item.url) && LinkPreview.shouldPreviewHref(item.url) @@ -3215,7 +3224,7 @@ export class MessageModel extends window.Backbone.Model { } async handleDeleteForEveryone( - del: typeof window.WhatIsThis, + del: DeleteModel, shouldPersist = true ): Promise { log.info('Handling DOE.', { diff --git a/ts/shims/themeChanged.ts b/ts/shims/themeChanged.ts index 226eaf822..13a8951ce 100644 --- a/ts/shims/themeChanged.ts +++ b/ts/shims/themeChanged.ts @@ -1,11 +1,11 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { getThemeType } from '../util/getThemeType'; + export function themeChanged(): void { if (window.reduxActions && window.reduxActions.user) { - const theme = window.Events.getThemeSetting(); - window.reduxActions.user.userChanged({ - theme: theme === 'system' ? window.systemTheme : theme, - }); + const theme = getThemeType(); + window.reduxActions.user.userChanged({ theme }); } } diff --git a/ts/signal.ts b/ts/signal.ts index cae847d13..758ec4a1b 100644 --- a/ts/signal.ts +++ b/ts/signal.ts @@ -113,7 +113,7 @@ type MigrationsModuleType = { getAbsoluteStickerPath: (path: string) => string; getAbsoluteTempPath: (path: string) => string; loadAttachmentData: ( - attachment: AttachmentType + attachment: Pick ) => Promise; loadContactData: ( contact: Array | undefined diff --git a/ts/state/getInitialState.ts b/ts/state/getInitialState.ts index c5e90f65f..25975e851 100644 --- a/ts/state/getInitialState.ts +++ b/ts/state/getInitialState.ts @@ -32,6 +32,7 @@ import type { MenuOptionsType } from '../types/menu'; import { UUIDKind } from '../types/UUID'; import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis'; import type { MainWindowStatsType } from '../windows/context'; +import { getThemeType } from '../util/getThemeType'; export function getInitialState({ badges, @@ -63,8 +64,7 @@ export function getInitialState({ window.ConversationController.getOurConversationId(); const ourDeviceId = window.textsecure.storage.user.getDeviceId(); - const themeSetting = window.Events.getThemeSetting(); - const theme = themeSetting === 'system' ? window.systemTheme : themeSetting; + const theme = getThemeType(); return { accounts: accounts(), diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index f2c07bbd2..b84a5e312 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -14,6 +14,7 @@ import { SenderKeyDistributionMessage, } from '@signalapp/libsignal-client'; +import type { QuotedMessageType } from '../model-types.d'; import { GLOBAL_ZONE } from '../SignalProtocolStore'; import { assert } from '../util/assert'; import { parseIntOrThrow } from '../util/parseIntOrThrow'; @@ -110,15 +111,6 @@ export type StickerType = StickerWithHydratedData & { attachmentPointer?: Proto.IAttachmentPointer; }; -export type QuoteType = { - attachments?: Array; - authorUuid?: string; - bodyRanges?: BodyRangesType; - id?: number; - isGiftBadge?: boolean; - text?: string; -}; - export type ReactionType = { emoji?: string; remove?: boolean; @@ -192,9 +184,9 @@ export type MessageOptionsType = { needsSync?: boolean; preview?: ReadonlyArray; profileKey?: Uint8Array; - quote?: QuoteType; + quote?: QuotedMessageType | null; recipients: ReadonlyArray; - sticker?: StickerType; + sticker?: StickerWithHydratedData; reaction?: ReactionType; deletedForEveryoneTimestamp?: number; timestamp: number; @@ -215,9 +207,9 @@ export type GroupSendOptionsType = { messageText?: string; preview?: ReadonlyArray; profileKey?: Uint8Array; - quote?: QuoteType; + quote?: QuotedMessageType | null; reaction?: ReactionType; - sticker?: StickerType; + sticker?: StickerWithHydratedData; storyContext?: StoryContextType; timestamp: number; }; @@ -246,7 +238,7 @@ class Message { profileKey?: Uint8Array; - quote?: QuoteType; + quote?: QuotedMessageType | null; recipients: ReadonlyArray; @@ -1195,9 +1187,9 @@ export default class MessageSender { options?: SendOptionsType; preview?: ReadonlyArray | undefined; profileKey?: Uint8Array; - quote?: QuoteType; + quote?: QuotedMessageType | null; reaction?: ReactionType; - sticker?: StickerType; + sticker?: StickerWithHydratedData; storyContext?: StoryContextType; timestamp: number; urgent: boolean; diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index ad21d1e9b..33651f8a0 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -168,13 +168,10 @@ export type AttachmentDraftType = size: number; }; -export type ThumbnailType = { - height?: number; - width?: number; - url?: string; - contentType: MIME.MIMEType; - path?: string; - data?: Uint8Array; +export type ThumbnailType = Pick< + AttachmentType, + 'height' | 'width' | 'url' | 'contentType' | 'path' | 'data' +> & { // Only used when quote needed to make an in-memory thumbnail objectUrl?: string; }; @@ -236,7 +233,7 @@ export async function migrateDataToFileSystem( // Over time, we can expand this definition to become more narrow, e.g. require certain // fields, etc. export function isValid( - rawAttachment?: AttachmentType + rawAttachment?: Pick ): rawAttachment is AttachmentType { // NOTE: We cannot use `_.isPlainObject` because `rawAttachment` is // deserialized by protobuf: @@ -396,14 +393,14 @@ export function hasData(attachment: AttachmentType): boolean { export function loadData( readAttachmentData: (path: string) => Promise -): (attachment: AttachmentType) => Promise { +): ( + attachment: Pick +) => Promise { if (!is.function_(readAttachmentData)) { throw new TypeError("'readAttachmentData' must be a function"); } - return async ( - attachment: AttachmentType - ): Promise => { + return async attachment => { if (!isValid(attachment)) { throw new TypeError("'attachment' is not valid"); } diff --git a/ts/types/Message2.ts b/ts/types/Message2.ts index 18fc2edbb..0602f5713 100644 --- a/ts/types/Message2.ts +++ b/ts/types/Message2.ts @@ -651,7 +651,7 @@ export const processNewSticker = async ( }; type LoadAttachmentType = ( - attachment: AttachmentType + attachment: Pick ) => Promise; export const createAttachmentLoader = ( diff --git a/ts/types/StorageUIKeys.ts b/ts/types/StorageUIKeys.ts index ac5bb3591..df7d12b10 100644 --- a/ts/types/StorageUIKeys.ts +++ b/ts/types/StorageUIKeys.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { z } from 'zod'; - import type { StorageAccessType } from './Storage.d'; export const themeSettingSchema = z.enum(['system', 'light', 'dark']); diff --git a/ts/util/getThemeType.ts b/ts/util/getThemeType.ts new file mode 100644 index 000000000..e745801a8 --- /dev/null +++ b/ts/util/getThemeType.ts @@ -0,0 +1,18 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { ThemeType } from '../types/Util'; + +export function getThemeType(): ThemeType { + const themeSetting = window.Events.getThemeSetting(); + + if (themeSetting === 'light') { + return ThemeType.light; + } + + if (themeSetting === 'dark') { + return ThemeType.dark; + } + + return window.systemTheme; +} diff --git a/ts/util/validateConversation.ts b/ts/util/validateConversation.ts new file mode 100644 index 000000000..63e0a0f83 --- /dev/null +++ b/ts/util/validateConversation.ts @@ -0,0 +1,62 @@ +// Copyright 2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { ValidateConversationType } from '../model-types.d'; +import { isDirectConversation } from './whatTypeOfConversation'; +import { parseNumber } from './libphonenumberUtil'; +import { isValidUuid } from '../types/UUID'; + +export function validateConversation( + attributes: ValidateConversationType +): string | null { + if (attributes.type !== 'private' && attributes.type !== 'group') { + return `Invalid conversation type: ${attributes.type}`; + } + + if (!attributes.e164 && !attributes.uuid && !attributes.groupId) { + return 'Missing one of e164, uuid, or groupId'; + } + + const error = validateNumber(attributes) || validateUuid(attributes); + + if (error) { + return error; + } + + return null; +} + +function validateNumber(attributes: ValidateConversationType): string | null { + if (isDirectConversation(attributes) && attributes.e164) { + const regionCode = window.storage.get('regionCode'); + if (!regionCode) { + throw new Error('No region code'); + } + const number = parseNumber(attributes.e164, regionCode); + if (number.isValidNumber) { + return null; + } + + let errorMessage: undefined | string; + if (number.error instanceof Error) { + errorMessage = number.error.message; + } else if (typeof number.error === 'string') { + errorMessage = number.error; + } + return errorMessage || 'Invalid phone number'; + } + + return null; +} + +function validateUuid(attributes: ValidateConversationType): string | null { + if (isDirectConversation(attributes) && attributes.uuid) { + if (isValidUuid(attributes.uuid)) { + return null; + } + + return 'Invalid UUID'; + } + + return null; +} diff --git a/ts/window.d.ts b/ts/window.d.ts index fc4b6362a..5b3db6b83 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -98,8 +98,6 @@ import { RendererConfigType } from './types/RendererConfig'; export { Long } from 'long'; -export type WhatIsThis = any; - // Synced with the type in ts/shims/showConfirmationDialog // we are duplicating it here because that file cannot import/export. type ConfirmationDialogViewProps = { @@ -253,8 +251,6 @@ declare global { }; WebAudioRecorder: typeof WebAudioRecorderClass; - WhatIsThis: WhatIsThis; - addSetupMenuItems: () => void; attachmentDownloadQueue: Array | undefined; startupProcessingQueue: StartupQueue | undefined; @@ -305,7 +301,7 @@ declare global { nodeSetImmediate: typeof setImmediate; onFullScreenChange: (fullScreen: boolean, maximized: boolean) => void; platform: string; - preloadedImages: Array; + preloadedImages: Array; reduxActions: ReduxActions; reduxStore: Store; restart: () => void; @@ -315,14 +311,14 @@ declare global { shutdown: () => void; showDebugLog: () => void; sendChallengeRequest: (request: IPCChallengeRequest) => void; - setAutoHideMenuBar: (value: WhatIsThis) => void; + setAutoHideMenuBar: (value: boolean) => void; setBadgeCount: (count: number) => void; - setMenuBarVisibility: (value: WhatIsThis) => void; + setMenuBarVisibility: (value: boolean) => void; updateSystemTraySetting: (value: SystemTraySetting) => void; showConfirmationDialog: (options: ConfirmationDialogViewProps) => void; showKeyboardShortcuts: () => void; storage: Storage; - systemTheme: WhatIsThis; + systemTheme: ThemeType; textsecure: typeof textsecure; titleBarDoubleClick: () => void; updateTrayIcon: (count: number) => void; @@ -341,7 +337,7 @@ declare global { WebAPI: WebAPIConnectType; Whisper: WhisperType; - getServerTrustRoot: () => WhatIsThis; + getServerTrustRoot: () => string; readyForUpdates: () => void; logAppLoadedEvent?: (options: { processedCount?: number }) => void; logAuthenticatedConnect?: () => void;