Make most message attribute uses readonly

Co-authored-by: Jamie Kyle <jamie@signal.org>
This commit is contained in:
Fedor Indutny
2024-07-24 13:14:11 -07:00
committed by GitHub
parent c619a7b6fd
commit 3555ccc629
53 changed files with 342 additions and 258 deletions
@@ -4,7 +4,10 @@
import type { SafetyNumberChangeSource } from '../components/SafetyNumberChangeDialog';
import * as log from '../logging/log';
import { explodePromise } from './explodePromise';
import type { RecipientsByConversation } from '../state/ducks/stories';
import type {
RecipientsByConversation,
RecipientEntry,
} from '../state/ducks/stories';
import { isNotNil } from './isNotNil';
import type { ServiceIdString } from '../types/ServiceId';
import { waitForAll } from './waitForAll';
@@ -105,7 +108,7 @@ export function filterServiceIds(
byConversation: RecipientsByConversation,
predicate: (serviceId: ServiceIdString) => boolean
): RecipientsByConversation {
const filteredByConversation: RecipientsByConversation = {};
const filteredByConversation: Record<string, RecipientEntry> = {};
Object.entries(byConversation).forEach(
([conversationId, conversationData]) => {
const conversationFiltered = conversationData.serviceIds
+7 -3
View File
@@ -1,14 +1,16 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import { DAY } from './durations';
import { isMoreRecentThan } from './timestamp';
import { isOutgoing } from '../messages/helpers';
export const MESSAGE_MAX_EDIT_COUNT = 10;
export function canEditMessage(message: MessageAttributesType): boolean {
export function canEditMessage(
message: ReadonlyMessageAttributesType
): boolean {
const result =
!message.deletedForEveryone &&
isOutgoing(message) &&
@@ -29,6 +31,8 @@ export function canEditMessage(message: MessageAttributesType): boolean {
return false;
}
export function isWithinMaxEdits(message: MessageAttributesType): boolean {
export function isWithinMaxEdits(
message: ReadonlyMessageAttributesType
): boolean {
return (message.editHistory?.length ?? 0) <= MESSAGE_MAX_EDIT_COUNT;
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { WritableDeep } from 'type-fest';
/**
* Takes a readonly object and returns a writable deep clone of it.
* @see https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
*/
export function deepClone<T>(value: T): WritableDeep<T> {
return structuredClone(value) as WritableDeep<T>;
}
+8 -3
View File
@@ -2,10 +2,15 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { isNumber, sortBy } from 'lodash';
import type { ReadonlyDeep } from 'type-fest';
import { strictAssert } from './assert';
import type { EditHistoryType, MessageAttributesType } from '../model-types';
import type {
EditHistoryType,
MessageAttributesType,
ReadonlyMessageAttributesType,
} from '../model-types';
import type { LoggerType } from '../types/Logging';
// The tricky bit for this function is if we are on our second+ attempt to send a given
@@ -14,7 +19,7 @@ export function getTargetOfThisEditTimestamp({
message,
targetTimestamp,
}: {
message: MessageAttributesType;
message: ReadonlyMessageAttributesType;
targetTimestamp: number;
}): number {
const { timestamp: originalTimestamp, editHistory } = message;
@@ -27,7 +32,7 @@ export function getTargetOfThisEditTimestamp({
});
const mostRecent = sortBy(
sentItems,
(item: EditHistoryType) => item.timestamp
(item: ReadonlyDeep<EditHistoryType>) => item.timestamp
);
const { length } = mostRecent;
+3 -3
View File
@@ -1,7 +1,7 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import type { MessageModel } from '../models/messages';
import type { SignalService as Proto } from '../protobuf';
import type { AciString } from '../types/ServiceId';
@@ -72,12 +72,12 @@ export async function findStoryMessages(
}
function isStoryAMatch(
message: MessageAttributesType | null | undefined,
message: ReadonlyMessageAttributesType | null | undefined,
conversationId: string,
ourConversationId: string,
authorAci: AciString,
sentTimestamp: number
): message is MessageAttributesType {
): message is ReadonlyMessageAttributesType {
if (!message) {
return false;
}
+3 -3
View File
@@ -3,13 +3,13 @@
import type {
ConversationAttributesType,
MessageAttributesType,
ReadonlyMessageAttributesType,
} from '../model-types.d';
import { isIncoming, isOutgoing } from '../state/selectors/message';
import { getTitle } from './getTitle';
function getIncomingContact(
messageAttributes: MessageAttributesType
messageAttributes: ReadonlyMessageAttributesType
): ConversationAttributesType | undefined {
if (!isIncoming(messageAttributes)) {
return undefined;
@@ -24,7 +24,7 @@ function getIncomingContact(
}
export function getMessageAuthorText(
messageAttributes?: MessageAttributesType
messageAttributes?: ReadonlyMessageAttributesType
): string | undefined {
if (!messageAttributes) {
return undefined;
+2 -2
View File
@@ -1,12 +1,12 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import type { ConversationModel } from '../models/conversations';
export function getMessageConversation({
conversationId,
}: Pick<MessageAttributesType, 'conversationId'>):
}: Pick<ReadonlyMessageAttributesType, 'conversationId'>):
| ConversationModel
| undefined {
return window.ConversationController.get(conversationId);
+2 -2
View File
@@ -1,7 +1,7 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import type { LoggerType } from '../types/Logging';
import { assertDev } from './assert';
@@ -16,7 +16,7 @@ export function getMessageSentTimestamp(
sent_at: sentAt,
timestamp,
}: Pick<
MessageAttributesType,
ReadonlyMessageAttributesType,
'editMessageTimestamp' | 'sent_at' | 'timestamp'
>,
{ includeEdits = true, log }: GetMessageSentTimestampOptionsType
+2 -2
View File
@@ -1,13 +1,13 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
export function getMessageSentTimestampSet({
sent_at: sentAt,
editHistory,
}: Pick<
MessageAttributesType,
ReadonlyMessageAttributesType,
'sent_at' | 'editHistory'
>): ReadonlySet<number> {
return new Set([
+2 -2
View File
@@ -1,10 +1,10 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
export function getMessageTimestamp(
message: Pick<MessageAttributesType, 'received_at' | 'received_at_ms'>
message: Pick<ReadonlyMessageAttributesType, 'received_at' | 'received_at_ms'>
): number {
return message.received_at_ms || message.received_at;
}
+5 -3
View File
@@ -1,8 +1,10 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReadonlyDeep } from 'type-fest';
import type { RawBodyRange } from '../types/BodyRange';
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import type { ICUStringMessageParamsByKeyType } from '../types/Util';
import * as Attachment from '../types/Attachment';
import * as EmbeddedContact from '../types/EmbeddedContact';
@@ -64,9 +66,9 @@ function getNameForNumber(e164: string): string {
}
export function getNotificationDataForMessage(
attributes: MessageAttributesType
attributes: ReadonlyMessageAttributesType
): {
bodyRanges?: ReadonlyArray<RawBodyRange>;
bodyRanges?: ReadonlyArray<ReadonlyDeep<RawBodyRange>>;
emoji?: string;
text: string;
} {
+2 -2
View File
@@ -1,7 +1,7 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import { applyRangesToText, hydrateRanges } from '../types/BodyRange';
import { findAndFormatContact } from './findAndFormatContact';
import { getNotificationDataForMessage } from './getNotificationDataForMessage';
@@ -9,7 +9,7 @@ import { isConversationAccepted } from './isConversationAccepted';
import { strictAssert } from './assert';
export function getNotificationTextForMessage(
attributes: MessageAttributesType
attributes: ReadonlyMessageAttributesType
): string {
const { text, emoji, bodyRanges } = getNotificationDataForMessage(attributes);
+2 -2
View File
@@ -1,11 +1,11 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import * as EmbeddedContact from '../types/EmbeddedContact';
export function getQuoteBodyText(
messageAttributes: MessageAttributesType,
messageAttributes: ReadonlyMessageAttributesType,
id: number
): string | undefined {
const storyReactionEmoji = messageAttributes.storyReaction?.emoji;
+7 -1
View File
@@ -3,6 +3,7 @@
import type { ConversationAttributesType } from '../model-types';
import type { RecipientsByConversation } from '../state/ducks/stories';
import type { ServiceIdString } from '../types/ServiceId';
import { getConversationMembers } from './getConversationMembers';
import { isNotNil } from './isNotNil';
@@ -10,7 +11,12 @@ import { isNotNil } from './isNotNil';
export function getRecipientsByConversation(
conversations: Array<ConversationAttributesType>
): RecipientsByConversation {
const recipientsByConversation: RecipientsByConversation = {};
const recipientsByConversation: Record<
string,
{
serviceIds: Array<ServiceIdString>;
}
> = {};
conversations.forEach(attributes => {
recipientsByConversation[attributes.id] = {
+2 -2
View File
@@ -1,7 +1,7 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
export function getSenderIdentifier({
sent_at: sentAt,
@@ -9,7 +9,7 @@ export function getSenderIdentifier({
sourceServiceId,
sourceDevice,
}: Pick<
MessageAttributesType,
ReadonlyMessageAttributesType,
'sent_at' | 'source' | 'sourceServiceId' | 'sourceDevice'
>): string {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+4 -4
View File
@@ -2,13 +2,13 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { partition } from 'lodash';
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import { isLongMessage } from '../types/MIME';
// NOTE: If you're modifying this function then you'll likely also need
// to modify ./queueAttachmentDownloads
export function hasAttachmentDownloads(
message: MessageAttributesType
message: ReadonlyMessageAttributesType
): boolean {
const attachments = message.attachments || [];
@@ -83,7 +83,7 @@ export function hasAttachmentDownloads(
}
function hasPreviewDownloads(
previews: MessageAttributesType['preview']
previews: ReadonlyMessageAttributesType['preview']
): boolean {
return (previews || []).some(item => {
if (!item.image) {
@@ -98,7 +98,7 @@ function hasPreviewDownloads(
}
function hasNormalAttachmentDownloads(
attachments: MessageAttributesType['attachments']
attachments: ReadonlyMessageAttributesType['attachments']
): boolean {
return (attachments || []).some(attachment => {
if (!attachment) {
+2 -2
View File
@@ -3,7 +3,7 @@
import type {
ConversationAttributesType,
MessageAttributesType,
ReadonlyMessageAttributesType,
} from '../model-types.d';
import {
getSource,
@@ -16,7 +16,7 @@ import type { ConversationType } from '../state/ducks/conversations';
export function getMessageIdForLogging(
message: Pick<
MessageAttributesType,
ReadonlyMessageAttributesType,
'type' | 'sourceServiceId' | 'sourceDevice' | 'sent_at'
>
): string {
+2 -2
View File
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { ReadStatus } from '../messages/MessageReadStatus';
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
export const isMessageUnread = (
message: Readonly<Pick<MessageAttributesType, 'readStatus'>>
message: Pick<ReadonlyMessageAttributesType, 'readStatus'>
): boolean => message.readStatus === ReadStatus.Unread;
+2 -2
View File
@@ -1,12 +1,12 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import { DAY } from './durations';
export function isTooOldToModifyMessage(
serverTimestamp: number,
message: MessageAttributesType
message: Pick<ReadonlyMessageAttributesType, 'serverTimestamp' | 'sent_at'>
): boolean {
const messageTimestamp = message.serverTimestamp || message.sent_at || 0;
const delta = Math.abs(serverTimestamp - messageTimestamp);
+25 -20
View File
@@ -1,17 +1,17 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import { createBatcher } from './batcher';
import { createWaitBatcher } from './waitBatcher';
import { DataWriter } from '../sql/Client';
import * as log from '../logging/log';
const updateMessageBatcher = createBatcher<MessageAttributesType>({
const updateMessageBatcher = createBatcher<ReadonlyMessageAttributesType>({
name: 'messageBatcher.updateMessageBatcher',
wait: 75,
maxSize: 50,
processBatch: async (messageAttrs: Array<MessageAttributesType>) => {
processBatch: async (messageAttrs: Array<ReadonlyMessageAttributesType>) => {
log.info('updateMessageBatcher', messageAttrs.length);
// Grab the latest from the cache in case they've changed
@@ -27,7 +27,9 @@ const updateMessageBatcher = createBatcher<MessageAttributesType>({
let shouldBatch = true;
export function queueUpdateMessage(messageAttr: MessageAttributesType): void {
export function queueUpdateMessage(
messageAttr: ReadonlyMessageAttributesType
): void {
if (shouldBatch) {
updateMessageBatcher.add(messageAttr);
} else {
@@ -41,21 +43,24 @@ export function setBatchingStrategy(keepBatching = false): void {
shouldBatch = keepBatching;
}
export const saveNewMessageBatcher = createWaitBatcher<MessageAttributesType>({
name: 'messageBatcher.saveNewMessageBatcher',
wait: 75,
maxSize: 30,
processBatch: async (messageAttrs: Array<MessageAttributesType>) => {
log.info('saveNewMessageBatcher', messageAttrs.length);
export const saveNewMessageBatcher =
createWaitBatcher<ReadonlyMessageAttributesType>({
name: 'messageBatcher.saveNewMessageBatcher',
wait: 75,
maxSize: 30,
processBatch: async (
messageAttrs: Array<ReadonlyMessageAttributesType>
) => {
log.info('saveNewMessageBatcher', messageAttrs.length);
// Grab the latest from the cache in case they've changed
const messagesToSave = messageAttrs.map(
message => window.MessageCache.accessAttributes(message.id) ?? message
);
// Grab the latest from the cache in case they've changed
const messagesToSave = messageAttrs.map(
message => window.MessageCache.accessAttributes(message.id) ?? message
);
await DataWriter.saveMessages(messagesToSave, {
forceSave: true,
ourAci: window.textsecure.storage.user.getCheckedAci(),
});
},
});
await DataWriter.saveMessages(messagesToSave, {
forceSave: true,
ourAci: window.textsecure.storage.user.getCheckedAci(),
});
},
});
+4 -3
View File
@@ -2,15 +2,16 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { ConversationModel } from '../models/conversations';
import type { MessageAttributesType } from '../model-types.d';
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import * as log from '../logging/log';
import { DataReader } from '../sql/Client';
import { isGroup } from './whatTypeOfConversation';
import { isMessageUnread } from './isMessageUnread';
export async function shouldReplyNotifyUser(
messageAttributes: Readonly<
Pick<MessageAttributesType, 'readStatus' | 'storyId'>
messageAttributes: Pick<
ReadonlyMessageAttributesType,
'readStatus' | 'storyId'
>,
conversation: ConversationModel
): Promise<boolean> {