diff --git a/patches/@types+backbone+1.4.5.patch b/patches/@types+backbone+1.4.5.patch index 737ea1ec29bd..883b82deeeb2 100644 --- a/patches/@types+backbone+1.4.5.patch +++ b/patches/@types+backbone+1.4.5.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@types/backbone/index.d.ts b/node_modules/@types/backbone/index.d.ts -index a172230..b85ab4c 100644 +index a172230..6f6de9f 100644 --- a/node_modules/@types/backbone/index.d.ts +++ b/node_modules/@types/backbone/index.d.ts -@@ -218,7 +218,7 @@ declare namespace Backbone { +@@ -218,14 +218,14 @@ declare namespace Backbone { * E - Extensions to the model constructor options. You can accept additional constructor options * by listing them in the E parameter. */ @@ -11,3 +11,11 @@ index a172230..b85ab4c 100644 /** * Do not use, prefer TypeScript's extend functionality. + **/ + public static extend(properties: any, classProperties?: any): any; + +- attributes: any; ++ attributes: T; + changed: any[]; + cidPrefix: string; + cid: string; diff --git a/ts/groups.ts b/ts/groups.ts index 6129f558b25c..93803d473a2a 100644 --- a/ts/groups.ts +++ b/ts/groups.ts @@ -358,6 +358,12 @@ export async function getPreJoinGroupInfo( export function buildGroupLink(conversation: ConversationModel): string { const { masterKey, groupInviteLinkPassword } = conversation.attributes; + strictAssert(masterKey, 'buildGroupLink requires the master key!'); + strictAssert( + groupInviteLinkPassword, + 'buildGroupLink requires the groupInviteLinkPassword!' + ); + const bytes = Proto.GroupInviteLink.encode({ v1Contents: { groupMasterKey: Bytes.fromBase64(masterKey), diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index 6a3793380692..54958290e257 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -290,7 +290,7 @@ export type ConversationAttributesType = { discoveredUnregisteredAt?: number; firstUnregisteredAt?: number; draftChanged?: boolean; - draftAttachments?: Array; + draftAttachments?: ReadonlyArray; draftBodyRanges?: DraftBodyRangesType; draftTimestamp?: number | null; hideStory?: boolean; @@ -315,7 +315,7 @@ export type ConversationAttributesType = { quotedMessageId?: string | null; sealedSender?: unknown; sentMessageCount?: number; - sharedGroupNames?: Array; + sharedGroupNames?: ReadonlyArray; voiceNotePlaybackRate?: number; id: string; diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 58d7ba7354d7..74e653ad47b5 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -1686,7 +1686,7 @@ export class ConversationModel extends window.Backbone const { attributes } = message; const { schemaVersion } = attributes; - if (schemaVersion < Message.VERSION_NEEDED_FOR_DISPLAY) { + if ((schemaVersion || 0) < Message.VERSION_NEEDED_FOR_DISPLAY) { // Yep, we really do want to wait for each of these // eslint-disable-next-line no-await-in-loop const upgradedMessage = await upgradeMessageSchema(attributes); diff --git a/ts/models/messages.ts b/ts/models/messages.ts index 80236b79e502..155ff7537259 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -2558,7 +2558,9 @@ export class MessageModel extends window.Backbone.Model { storyId: storyQuote?.id, }; - const dataMessage = await upgradeMessageSchema(withQuoteReference); + // There are type conflicts between ModelAttributesType and protos passed in here + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const dataMessage = await upgradeMessageSchema(withQuoteReference as any); const isGroupStoryReply = isGroup(conversation.attributes) && dataMessage.storyId; @@ -2712,7 +2714,18 @@ export class MessageModel extends window.Backbone.Model { }; } - attributes.avatar = avatar; + if (!avatar) { + attributes.avatar = avatar; + } else { + const { url, path } = avatar; + strictAssert(url, 'Avatar needs url'); + strictAssert(path, 'Avatar needs path'); + attributes.avatar = { + url, + path, + ...avatar, + }; + } pendingGroupUpdate.avatarUpdated = true; } else { diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts index 8c1d3edb78ac..94313bd47690 100644 --- a/ts/sql/Interface.ts +++ b/ts/sql/Interface.ts @@ -506,9 +506,9 @@ export type DataInterface = { _getAllReactions: () => Promise>; _removeAllReactions: () => Promise; getMessageBySender: (options: { - source: string; - sourceUuid: UUIDStringType; - sourceDevice: number; + source?: string; + sourceUuid?: UUIDStringType; + sourceDevice?: number; sent_at: number; }) => Promise; getMessageById: (id: string) => Promise; diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index 0840bdbd0530..6f25502b1a02 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -2131,9 +2131,9 @@ async function getMessageBySender({ sourceDevice, sent_at, }: { - source: string; - sourceUuid: UUIDStringType; - sourceDevice: number; + source?: string; + sourceUuid?: UUIDStringType; + sourceDevice?: number; sent_at: number; }): Promise { const db = getInstance(); @@ -2147,9 +2147,9 @@ async function getMessageBySender({ LIMIT 2; ` ).all({ - source, - sourceUuid, - sourceDevice, + source: source || null, + sourceUuid: sourceUuid || null, + sourceDevice: sourceDevice || null, sent_at, }); diff --git a/ts/state/ducks/composer.ts b/ts/state/ducks/composer.ts index 54224213739b..7953007f235d 100644 --- a/ts/state/ducks/composer.ts +++ b/ts/state/ducks/composer.ts @@ -539,7 +539,7 @@ function sendStickerMessage( // next in-memory store. function getAttachmentsFromConversationModel( conversationId: string -): Array { +): ReadonlyArray { const conversation = window.ConversationController.get(conversationId); return conversation?.get('draftAttachments') || []; } diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 9ce06f0ba60f..fea9e93ec351 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -1646,6 +1646,7 @@ export const markViewed = (messageId: string): void => { message.set(messageUpdaterMarkViewed(message.attributes, Date.now())); if (isIncoming(message.attributes)) { + const convoAttributes = message.getConversation()?.attributes; drop( viewedReceiptsJobQueue.add({ viewedReceipt: { @@ -1653,9 +1654,9 @@ export const markViewed = (messageId: string): void => { senderE164, senderUuid, timestamp, - isDirectConversation: isDirectConversation( - message.getConversation()?.attributes - ), + isDirectConversation: convoAttributes + ? isDirectConversation(convoAttributes) + : true, }, }) ); diff --git a/ts/state/ducks/stories.ts b/ts/state/ducks/stories.ts index d5fd172e8da9..30906f50b495 100644 --- a/ts/state/ducks/stories.ts +++ b/ts/state/ducks/stories.ts @@ -24,7 +24,7 @@ import dataInterface from '../../sql/Client'; import { ReadStatus } from '../../messages/MessageReadStatus'; import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog'; import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories'; -import { assertDev } from '../../util/assert'; +import { assertDev, strictAssert } from '../../util/assert'; import { drop } from '../../util/drop'; import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUntilConversationsAreVerified'; import { deleteStoryForEveryone as doDeleteStoryForEveryone } from '../../util/deleteStoryForEveryone'; @@ -371,6 +371,11 @@ function markStoryRead( log.warn(`markStoryRead: no message found ${messageId}`); return; } + const authorId = message.attributes.sourceUuid; + strictAssert( + authorId, + 'markStoryRead: The message needs a sender to mark it read!' + ); const isSignalOnboardingStory = message.get('sourceUuid') === SIGNAL_ACI; @@ -404,7 +409,7 @@ function markStoryRead( } await dataInterface.addNewStoryRead({ - authorId: message.attributes.sourceUuid, + authorId, conversationId: message.attributes.conversationId, storyId: messageId, storyReadDate, diff --git a/ts/util/expirationTimer.ts b/ts/util/expirationTimer.ts index cad73ef27e9a..593050e3701e 100644 --- a/ts/util/expirationTimer.ts +++ b/ts/util/expirationTimer.ts @@ -81,8 +81,8 @@ export function calculateExpirationTimestamp({ expireTimer, expirationStartTimestamp, }: { - expireTimer: DurationInSeconds | undefined; - expirationStartTimestamp: number | undefined | null; + expireTimer?: DurationInSeconds | null; + expirationStartTimestamp?: number | null; }): number | undefined { return isNumber(expirationStartTimestamp) && isNumber(expireTimer) ? expirationStartTimestamp + DurationInSeconds.toMillis(expireTimer)