Make most message attribute uses readonly
Co-authored-by: Jamie Kyle <jamie@signal.org>
This commit is contained in:
parent
c619a7b6fd
commit
3555ccc629
53 changed files with 342 additions and 258 deletions
21
package-lock.json
generated
21
package-lock.json
generated
|
@ -105,7 +105,7 @@
|
|||
"sanitize.css": "11.0.0",
|
||||
"semver": "5.7.2",
|
||||
"split2": "4.0.0",
|
||||
"type-fest": "3.5.0",
|
||||
"type-fest": "4.23.0",
|
||||
"urlpattern-polyfill": "9.0.0",
|
||||
"uuid": "3.3.2",
|
||||
"uuid-browser": "3.1.0",
|
||||
|
@ -7184,6 +7184,17 @@
|
|||
"uuid": "^8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@signalapp/libsignal-client/node_modules/type-fest": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
|
||||
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@signalapp/libsignal-client/node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
|
@ -34835,11 +34846,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.5.0.tgz",
|
||||
"integrity": "sha512-bI3zRmZC8K0tUz1HjbIOAGQwR2CoPQG68N5IF7gm0LBl8QSNXzkmaWnkWccCUL5uG9mCsp4sBwC8SBrNSISWew==",
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.23.0.tgz",
|
||||
"integrity": "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
|
|
|
@ -187,7 +187,7 @@
|
|||
"sanitize.css": "11.0.0",
|
||||
"semver": "5.7.2",
|
||||
"split2": "4.0.0",
|
||||
"type-fest": "3.5.0",
|
||||
"type-fest": "4.23.0",
|
||||
"urlpattern-polyfill": "9.0.0",
|
||||
"uuid": "3.3.2",
|
||||
"uuid-browser": "3.1.0",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import type {
|
||||
EmbeddedContactType,
|
||||
|
@ -21,7 +22,7 @@ import {
|
|||
import type { LocalizerType } from '../../types/Util';
|
||||
|
||||
export type Props = {
|
||||
contact: EmbeddedContactType;
|
||||
contact: ReadonlyDeep<EmbeddedContactType>;
|
||||
hasSignalAccount: boolean;
|
||||
i18n: LocalizerType;
|
||||
onSendMessage: () => void;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import type { EmbeddedContactType } from '../../types/EmbeddedContact';
|
||||
|
||||
|
@ -14,7 +15,7 @@ import {
|
|||
} from './contactUtil';
|
||||
|
||||
export type Props = {
|
||||
contact: EmbeddedContactType;
|
||||
contact: ReadonlyDeep<EmbeddedContactType>;
|
||||
i18n: LocalizerType;
|
||||
isIncoming: boolean;
|
||||
withContentAbove: boolean;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import type { ReactElement, ReactNode } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { get } from 'lodash';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import * as log from '../../logging/log';
|
||||
import { I18n } from '../I18n';
|
||||
|
@ -27,7 +28,7 @@ import { renderChange } from '../../groupChange';
|
|||
import { Modal } from '../Modal';
|
||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||
|
||||
export type PropsDataType = {
|
||||
export type PropsDataType = ReadonlyDeep<{
|
||||
areWeAdmin: boolean;
|
||||
change: GroupV2ChangeType;
|
||||
conversationId: string;
|
||||
|
@ -39,7 +40,7 @@ export type PropsDataType = {
|
|||
groupName?: string;
|
||||
ourAci: AciString | undefined;
|
||||
ourPni: PniString | undefined;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type PropsActionsType = {
|
||||
blockGroupLinkRequests: (
|
||||
|
|
|
@ -16,6 +16,7 @@ import getDirection from 'direction';
|
|||
import { drop, groupBy, noop, orderBy, take, unescape } from 'lodash';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import type { PreventOverflowModifier } from '@popperjs/core/lib/modifiers/preventOverflow';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import type {
|
||||
ConversationType,
|
||||
|
@ -229,7 +230,7 @@ export type PropsData = {
|
|||
timestamp: number;
|
||||
receivedAtMS?: number;
|
||||
status?: MessageStatusType;
|
||||
contact?: EmbeddedContactType;
|
||||
contact?: ReadonlyDeep<EmbeddedContactType>;
|
||||
author: Pick<
|
||||
ConversationType,
|
||||
| 'acceptedMessageRequest'
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import { Avatar, AvatarBlur } from '../Avatar';
|
||||
import { Spinner } from '../Spinner';
|
||||
|
@ -18,7 +19,7 @@ export function renderAvatar({
|
|||
size,
|
||||
direction,
|
||||
}: {
|
||||
contact: EmbeddedContactType;
|
||||
contact: ReadonlyDeep<EmbeddedContactType>;
|
||||
i18n: LocalizerType;
|
||||
size: 28 | 52 | 80;
|
||||
direction?: 'outgoing' | 'incoming';
|
||||
|
@ -65,7 +66,7 @@ export function renderName({
|
|||
isIncoming,
|
||||
module,
|
||||
}: {
|
||||
contact: EmbeddedContactType;
|
||||
contact: ReadonlyDeep<EmbeddedContactType>;
|
||||
isIncoming: boolean;
|
||||
module: string;
|
||||
}): JSX.Element {
|
||||
|
@ -86,7 +87,7 @@ export function renderContactShorthand({
|
|||
isIncoming,
|
||||
module,
|
||||
}: {
|
||||
contact: EmbeddedContactType;
|
||||
contact: ReadonlyDeep<EmbeddedContactType>;
|
||||
isIncoming: boolean;
|
||||
module: string;
|
||||
}): JSX.Element {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 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 { AttachmentType } from '../../../../types/Attachment';
|
||||
|
||||
export type ItemClickEvent = {
|
||||
message: Pick<MessageAttributesType, 'sent_at'>;
|
||||
message: Pick<ReadonlyMessageAttributesType, 'sent_at'>;
|
||||
attachment: AttachmentType;
|
||||
index: number;
|
||||
type: 'media' | 'documents';
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import type {
|
||||
LocalizerType,
|
||||
ICUStringMessageParamsByKeyType,
|
||||
|
@ -56,7 +57,7 @@ export type RenderChangeResultType<T extends string | JSX.Element> =
|
|||
>;
|
||||
|
||||
export function renderChange<T extends string | JSX.Element>(
|
||||
change: GroupV2ChangeType,
|
||||
change: ReadonlyDeep<GroupV2ChangeType>,
|
||||
options: RenderOptionsType<T>
|
||||
): RenderChangeResultType<T> {
|
||||
const { details, from } = change;
|
||||
|
@ -79,7 +80,7 @@ export function renderChange<T extends string | JSX.Element>(
|
|||
}
|
||||
|
||||
function renderChangeDetail<T extends string | JSX.Element>(
|
||||
detail: GroupV2ChangeDetailType,
|
||||
detail: ReadonlyDeep<GroupV2ChangeDetailType>,
|
||||
options: RenderOptionsType<T>
|
||||
): string | T | ReadonlyArray<string | T> {
|
||||
const {
|
||||
|
|
|
@ -36,10 +36,7 @@ import { copyCdnFields } from '../../util/attachments';
|
|||
import { LONG_MESSAGE } from '../../types/MIME';
|
||||
import { LONG_ATTACHMENT_LIMIT } from '../../types/Message';
|
||||
import type { RawBodyRange } from '../../types/BodyRange';
|
||||
import type {
|
||||
EmbeddedContactWithHydratedAvatar,
|
||||
EmbeddedContactWithUploadedAvatar,
|
||||
} from '../../types/EmbeddedContact';
|
||||
import type { EmbeddedContactWithUploadedAvatar } from '../../types/EmbeddedContact';
|
||||
import type { StoryContextType } from '../../types/Util';
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
import type {
|
||||
|
@ -1058,8 +1055,7 @@ async function uploadMessageContacts(
|
|||
strictAssert(oldContact, `${logId}: Contacts are gone after upload`);
|
||||
|
||||
const newContact = oldContact.map((contact, index) => {
|
||||
const loaded: EmbeddedContactWithHydratedAvatar | undefined =
|
||||
contacts.at(index);
|
||||
const loaded = contacts.at(index);
|
||||
if (!contact.avatar) {
|
||||
strictAssert(
|
||||
loaded?.avatar === undefined,
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import * as log from '../logging/log';
|
||||
import type { ConversationModel } from '../models/conversations';
|
||||
import type {
|
||||
CustomError,
|
||||
MessageAttributesType,
|
||||
ReadonlyMessageAttributesType,
|
||||
QuotedAttachmentType,
|
||||
QuotedMessageType,
|
||||
} from '../model-types.d';
|
||||
|
@ -16,33 +18,36 @@ import type { LocalizerType } from '../types/Util';
|
|||
import { missingCaseError } from '../util/missingCaseError';
|
||||
|
||||
export function isIncoming(
|
||||
message: Pick<MessageAttributesType, 'type'>
|
||||
message: Pick<ReadonlyMessageAttributesType, 'type'>
|
||||
): boolean {
|
||||
return message.type === 'incoming';
|
||||
}
|
||||
|
||||
export function isOutgoing(
|
||||
message: Pick<MessageAttributesType, 'type'>
|
||||
message: Pick<ReadonlyMessageAttributesType, 'type'>
|
||||
): boolean {
|
||||
return message.type === 'outgoing';
|
||||
}
|
||||
|
||||
export function isStory(message: Pick<MessageAttributesType, 'type'>): boolean {
|
||||
export function isStory(
|
||||
message: Pick<ReadonlyMessageAttributesType, 'type'>
|
||||
): boolean {
|
||||
return message.type === 'story';
|
||||
}
|
||||
|
||||
export type MessageAttributesWithPaymentEvent = MessageAttributesType & {
|
||||
payment: AnyPaymentEvent;
|
||||
};
|
||||
export type MessageAttributesWithPaymentEvent = ReadonlyMessageAttributesType &
|
||||
ReadonlyDeep<{
|
||||
payment: AnyPaymentEvent;
|
||||
}>;
|
||||
|
||||
export function messageHasPaymentEvent(
|
||||
message: MessageAttributesType
|
||||
message: ReadonlyMessageAttributesType
|
||||
): message is MessageAttributesWithPaymentEvent {
|
||||
return message.payment != null;
|
||||
}
|
||||
|
||||
export function getPaymentEventNotificationText(
|
||||
payment: AnyPaymentEvent,
|
||||
payment: ReadonlyDeep<AnyPaymentEvent>,
|
||||
senderTitle: string,
|
||||
conversationTitle: string | null,
|
||||
senderIsMe: boolean,
|
||||
|
@ -61,7 +66,7 @@ export function getPaymentEventNotificationText(
|
|||
}
|
||||
|
||||
export function getPaymentEventDescription(
|
||||
payment: AnyPaymentEvent,
|
||||
payment: ReadonlyDeep<AnyPaymentEvent>,
|
||||
senderTitle: string,
|
||||
conversationTitle: string | null,
|
||||
senderIsMe: boolean,
|
||||
|
@ -110,10 +115,10 @@ export function getPaymentEventDescription(
|
|||
}
|
||||
|
||||
export function isQuoteAMatch(
|
||||
message: MessageAttributesType | null | undefined,
|
||||
message: ReadonlyMessageAttributesType | null | undefined,
|
||||
conversationId: string,
|
||||
quote: Pick<QuotedMessageType, 'id' | 'authorAci' | 'author'>
|
||||
): message is MessageAttributesType {
|
||||
quote: ReadonlyDeep<Pick<QuotedMessageType, 'id' | 'authorAci' | 'author'>>
|
||||
): message is ReadonlyMessageAttributesType {
|
||||
if (!message) {
|
||||
return false;
|
||||
}
|
||||
|
@ -142,7 +147,7 @@ export const shouldTryToCopyFromQuotedMessage = ({
|
|||
quoteAttachment,
|
||||
}: {
|
||||
referencedMessageNotFound: boolean;
|
||||
quoteAttachment: QuotedAttachmentType | undefined;
|
||||
quoteAttachment: ReadonlyDeep<QuotedAttachmentType> | undefined;
|
||||
}): boolean => {
|
||||
// If we've tried and can't find the message, try again.
|
||||
if (referencedMessageNotFound === true) {
|
||||
|
@ -163,7 +168,10 @@ export const shouldTryToCopyFromQuotedMessage = ({
|
|||
};
|
||||
|
||||
export function getAuthorId(
|
||||
message: Pick<MessageAttributesType, 'type' | 'source' | 'sourceServiceId'>
|
||||
message: Pick<
|
||||
ReadonlyMessageAttributesType,
|
||||
'type' | 'source' | 'sourceServiceId'
|
||||
>
|
||||
): string | undefined {
|
||||
const source = getSource(message);
|
||||
const sourceServiceId = getSourceServiceId(message);
|
||||
|
@ -181,14 +189,14 @@ export function getAuthorId(
|
|||
}
|
||||
|
||||
export function getAuthor(
|
||||
message: MessageAttributesType
|
||||
message: ReadonlyMessageAttributesType
|
||||
): ConversationModel | undefined {
|
||||
const id = getAuthorId(message);
|
||||
return window.ConversationController.get(id);
|
||||
}
|
||||
|
||||
export function getSource(
|
||||
message: Pick<MessageAttributesType, 'type' | 'source'>
|
||||
message: Pick<ReadonlyMessageAttributesType, 'type' | 'source'>
|
||||
): string | undefined {
|
||||
if (isIncoming(message) || isStory(message)) {
|
||||
return message.source;
|
||||
|
@ -201,7 +209,7 @@ export function getSource(
|
|||
}
|
||||
|
||||
export function getSourceDevice(
|
||||
message: Pick<MessageAttributesType, 'type' | 'sourceDevice'>
|
||||
message: Pick<ReadonlyMessageAttributesType, 'type' | 'sourceDevice'>
|
||||
): string | number | undefined {
|
||||
const { sourceDevice } = message;
|
||||
|
||||
|
@ -218,7 +226,7 @@ export function getSourceDevice(
|
|||
}
|
||||
|
||||
export function getSourceServiceId(
|
||||
message: Pick<MessageAttributesType, 'type' | 'sourceServiceId'>
|
||||
message: Pick<ReadonlyMessageAttributesType, 'type' | 'sourceServiceId'>
|
||||
): ServiceIdString | undefined {
|
||||
if (isIncoming(message) || isStory(message)) {
|
||||
return message.sourceServiceId;
|
||||
|
|
3
ts/model-types.d.ts
vendored
3
ts/model-types.d.ts
vendored
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as Backbone from 'backbone';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import type { GroupV2ChangeType } from './groups';
|
||||
import type { DraftBodyRanges, RawBodyRange } from './types/BodyRange';
|
||||
|
@ -291,6 +292,8 @@ export type MessageAttributesType = {
|
|||
deletedForEveryoneFailed?: boolean;
|
||||
};
|
||||
|
||||
export type ReadonlyMessageAttributesType = ReadonlyDeep<MessageAttributesType>;
|
||||
|
||||
export type ConversationAttributesTypeType = 'private' | 'group';
|
||||
|
||||
export type ConversationLastProfileType = Readonly<{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { pick } from 'lodash';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import type { ReadonlyMessageAttributesType } from '../model-types.d';
|
||||
import type { StoryDataType } from '../state/ducks/stories';
|
||||
import * as durations from '../util/durations';
|
||||
import * as log from '../logging/log';
|
||||
|
@ -28,10 +28,11 @@ export async function loadStories(): Promise<void> {
|
|||
}
|
||||
|
||||
export function getStoryDataFromMessageAttributes(
|
||||
message: MessageAttributesType & {
|
||||
hasReplies?: boolean;
|
||||
hasRepliesFromSelf?: boolean;
|
||||
}
|
||||
message: ReadonlyMessageAttributesType &
|
||||
Readonly<{
|
||||
hasReplies?: boolean;
|
||||
hasRepliesFromSelf?: boolean;
|
||||
}>
|
||||
): StoryDataType | undefined {
|
||||
const { attachments, deletedForEveryone } = message;
|
||||
const unresolvedAttachment = attachments ? attachments[0] : undefined;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
// The idea with this file is to make it webpackable for the style guide
|
||||
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import * as Crypto from './Crypto';
|
||||
import * as Curve from './Curve';
|
||||
import { start as conversationControllerStart } from './ConversationController';
|
||||
|
@ -84,13 +86,13 @@ type MigrationsModuleType = {
|
|||
attachment: Partial<AttachmentType>
|
||||
) => Promise<AttachmentWithHydratedData>;
|
||||
loadContactData: (
|
||||
contact: Array<EmbeddedContactType> | undefined
|
||||
contact: ReadonlyArray<ReadonlyDeep<EmbeddedContactType>> | undefined
|
||||
) => Promise<Array<EmbeddedContactWithHydratedAvatar> | undefined>;
|
||||
loadMessage: (
|
||||
message: MessageAttributesType
|
||||
) => Promise<MessageAttributesType>;
|
||||
loadPreviewData: (
|
||||
preview: Array<LinkPreviewType> | undefined
|
||||
preview: ReadonlyArray<ReadonlyDeep<LinkPreviewType>> | undefined
|
||||
) => Promise<Array<LinkPreviewWithHydratedData>>;
|
||||
loadQuoteData: (
|
||||
quote: QuotedMessageType | null | undefined
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { ipcRenderer as ipc } from 'electron';
|
||||
|
||||
import { groupBy, isTypedArray, last, map, omit } from 'lodash';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import { deleteExternalFiles } from '../types/Conversation';
|
||||
import { update as updateExpiringMessagesService } from '../services/expiringMessagesDeletion';
|
||||
|
@ -222,7 +223,9 @@ function _cleanData(
|
|||
return cleaned;
|
||||
}
|
||||
|
||||
export function _cleanMessageData(data: MessageType): MessageType {
|
||||
export function _cleanMessageData(
|
||||
data: ReadonlyDeep<MessageType>
|
||||
): ReadonlyDeep<MessageType> {
|
||||
const result = { ...data };
|
||||
// Ensure that all messages have the received_at set properly
|
||||
if (!data.received_at) {
|
||||
|
@ -586,7 +589,7 @@ async function searchMessages({
|
|||
// Message
|
||||
|
||||
async function saveMessage(
|
||||
data: MessageType,
|
||||
data: ReadonlyDeep<MessageType>,
|
||||
options: {
|
||||
jobToInsert?: Readonly<StoredJob>;
|
||||
forceSave?: boolean;
|
||||
|
@ -607,7 +610,7 @@ async function saveMessage(
|
|||
}
|
||||
|
||||
async function saveMessages(
|
||||
arrayOfMessages: ReadonlyArray<MessageType>,
|
||||
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
|
||||
options: { forceSave?: boolean; ourAci: AciString }
|
||||
): Promise<Array<string>> {
|
||||
const result = await writableChannel.saveMessages(
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from '@signalapp/better-sqlite3';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import type {
|
||||
ConversationAttributesType,
|
||||
MessageAttributesType,
|
||||
|
@ -730,7 +731,7 @@ type WritableInterface = {
|
|||
deleteAllEndorsementsForGroup: (groupId: string) => void;
|
||||
|
||||
saveMessage: (
|
||||
data: MessageType,
|
||||
data: ReadonlyDeep<MessageType>,
|
||||
options: {
|
||||
jobToInsert?: StoredJob;
|
||||
forceSave?: boolean;
|
||||
|
@ -738,7 +739,7 @@ type WritableInterface = {
|
|||
}
|
||||
) => string;
|
||||
saveMessages: (
|
||||
arrayOfMessages: ReadonlyArray<MessageType>,
|
||||
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
|
||||
options: { forceSave?: boolean; ourAci: AciString }
|
||||
) => Array<string>;
|
||||
|
||||
|
@ -795,14 +796,14 @@ type WritableInterface = {
|
|||
): CallLinkType;
|
||||
migrateConversationMessages: (obsoleteId: string, currentId: string) => void;
|
||||
saveEditedMessage: (
|
||||
mainMessage: MessageType,
|
||||
mainMessage: ReadonlyDeep<MessageType>,
|
||||
ourAci: AciString,
|
||||
opts: EditedMessageType
|
||||
opts: ReadonlyDeep<EditedMessageType>
|
||||
) => void;
|
||||
saveEditedMessages: (
|
||||
mainMessage: MessageType,
|
||||
mainMessage: ReadonlyDeep<MessageType>,
|
||||
ourAci: AciString,
|
||||
history: ReadonlyArray<EditedMessageType>
|
||||
history: ReadonlyArray<ReadonlyDeep<EditedMessageType>>
|
||||
) => void;
|
||||
|
||||
removeSyncTaskById: (id: string) => void;
|
||||
|
@ -1047,13 +1048,13 @@ export type ClientOnlyReadableInterface = ClientInterfaceWrap<{
|
|||
getRecentStoryReplies(
|
||||
storyId: string,
|
||||
options?: GetRecentStoryRepliesOptionsType
|
||||
): Array<MessageAttributesType>;
|
||||
): Array<MessageType>;
|
||||
getOlderMessagesByConversation: (
|
||||
options: AdjacentMessagesByConversationOptionsType
|
||||
) => Array<MessageAttributesType>;
|
||||
) => Array<MessageType>;
|
||||
getNewerMessagesByConversation: (
|
||||
options: AdjacentMessagesByConversationOptionsType
|
||||
) => Array<MessageAttributesType>;
|
||||
) => Array<MessageType>;
|
||||
getConversationRangeCenteredOnMessage: (
|
||||
options: AdjacentMessagesByConversationOptionsType
|
||||
) => GetConversationRangeCenteredOnMessageResultType<MessageType>;
|
||||
|
|
|
@ -11,6 +11,7 @@ import type { Database, Statement } from '@signalapp/better-sqlite3';
|
|||
import SQL from '@signalapp/better-sqlite3';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import type { Dictionary } from 'lodash';
|
||||
import {
|
||||
|
@ -2119,7 +2120,7 @@ export function getAllSyncTasks(db: WritableDB): Array<SyncTaskType> {
|
|||
|
||||
export function saveMessage(
|
||||
db: WritableDB,
|
||||
data: MessageType,
|
||||
data: ReadonlyDeep<MessageType>,
|
||||
options: {
|
||||
alreadyInTransaction?: boolean;
|
||||
forceSave?: boolean;
|
||||
|
@ -2337,7 +2338,7 @@ export function saveMessage(
|
|||
|
||||
function saveMessages(
|
||||
db: WritableDB,
|
||||
arrayOfMessages: ReadonlyArray<MessageType>,
|
||||
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
|
||||
options: { forceSave?: boolean; ourAci: AciString }
|
||||
): Array<string> {
|
||||
return db.transaction(() => {
|
||||
|
@ -6964,9 +6965,9 @@ function removeAllProfileKeyCredentials(db: WritableDB): void {
|
|||
|
||||
function saveEditedMessages(
|
||||
db: WritableDB,
|
||||
mainMessage: MessageType,
|
||||
mainMessage: ReadonlyDeep<MessageType>,
|
||||
ourAci: AciString,
|
||||
history: ReadonlyArray<EditedMessageType>
|
||||
history: ReadonlyArray<ReadonlyDeep<EditedMessageType>>
|
||||
): void {
|
||||
db.transaction(() => {
|
||||
saveMessage(db, mainMessage, {
|
||||
|
@ -6996,9 +6997,9 @@ function saveEditedMessages(
|
|||
|
||||
function saveEditedMessage(
|
||||
db: WritableDB,
|
||||
mainMessage: MessageType,
|
||||
mainMessage: ReadonlyDeep<MessageType>,
|
||||
ourAci: AciString,
|
||||
editedMessage: EditedMessageType
|
||||
editedMessage: ReadonlyDeep<EditedMessageType>
|
||||
): void {
|
||||
return saveEditedMessages(db, mainMessage, ourAci, [editedMessage]);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import { DataReader, DataWriter } from '../../sql/Client';
|
|||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||
import type { DraftBodyRanges } from '../../types/BodyRange';
|
||||
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import type { ReadonlyMessageAttributesType } from '../../model-types.d';
|
||||
import type { NoopActionType } from './noop';
|
||||
import type { ShowToastActionType } from './toast';
|
||||
import type { StateType as RootStateType } from '../reducer';
|
||||
|
@ -104,14 +104,17 @@ type ComposerStateByConversationType = {
|
|||
linkPreviewLoading: boolean;
|
||||
linkPreviewResult?: LinkPreviewType;
|
||||
messageCompositionId: string;
|
||||
quotedMessage?: Pick<MessageAttributesType, 'conversationId' | 'quote'>;
|
||||
quotedMessage?: Pick<
|
||||
ReadonlyMessageAttributesType,
|
||||
'conversationId' | 'quote'
|
||||
>;
|
||||
sendCounter: number;
|
||||
shouldSendHighQualityAttachments?: boolean;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
export type QuotedMessageType = Pick<
|
||||
MessageAttributesType,
|
||||
ReadonlyMessageAttributesType,
|
||||
'conversationId' | 'quote'
|
||||
>;
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ import type {
|
|||
DraftEditMessageType,
|
||||
LastMessageStatus,
|
||||
MessageAttributesType,
|
||||
ReadonlyMessageAttributesType,
|
||||
} from '../../model-types.d';
|
||||
import type {
|
||||
DraftBodyRanges,
|
||||
|
@ -214,18 +215,20 @@ export type InteractionModeType = ReadonlyDeep<
|
|||
>;
|
||||
|
||||
export type MessageTimestamps = ReadonlyDeep<
|
||||
Pick<MessageAttributesType, 'sent_at' | 'received_at'>
|
||||
Pick<ReadonlyMessageAttributesType, 'sent_at' | 'received_at'>
|
||||
>;
|
||||
|
||||
// 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;
|
||||
isSpoilerExpanded?: Record<number, boolean>;
|
||||
};
|
||||
export type MessageType = ReadonlyDeep<
|
||||
ReadonlyMessageAttributesType & {
|
||||
interactionType?: InteractionModeType;
|
||||
}
|
||||
>;
|
||||
export type MessageWithUIFieldsType = ReadonlyDeep<
|
||||
ReadonlyMessageAttributesType & {
|
||||
displayLimit?: number;
|
||||
isSpoilerExpanded?: Record<number, boolean>;
|
||||
}
|
||||
>;
|
||||
|
||||
export const ConversationTypes = ['direct', 'group'] as const;
|
||||
export type ConversationTypeType = ReadonlyDeep<
|
||||
|
@ -420,10 +423,9 @@ type MessageMetricsType = ReadonlyDeep<{
|
|||
totalUnseen: number;
|
||||
}>;
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
export type MessageLookupType = {
|
||||
export type MessageLookupType = ReadonlyDeep<{
|
||||
[key: string]: MessageWithUIFieldsType;
|
||||
};
|
||||
}>;
|
||||
export type ConversationMessageType = ReadonlyDeep<{
|
||||
isNearBottom?: boolean;
|
||||
messageChangeCounter: number;
|
||||
|
@ -510,8 +512,7 @@ type ComposerStateType = ReadonlyDeep<
|
|||
))
|
||||
>;
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME
|
||||
export type ConversationsStateType = Readonly<{
|
||||
export type ConversationsStateType = ReadonlyDeep<{
|
||||
preJoinConversation?: PreJoinConversationType;
|
||||
invitedServiceIdsForNewlyCreatedGroup?: ReadonlyArray<ServiceIdString>;
|
||||
conversationLookup: ConversationLookupType;
|
||||
|
@ -530,7 +531,7 @@ export type ConversationsStateType = Readonly<{
|
|||
stack: ReadonlyArray<PanelRenderType>;
|
||||
watermark: number;
|
||||
};
|
||||
targetedMessageForDetails?: MessageAttributesType;
|
||||
targetedMessageForDetails?: ReadonlyMessageAttributesType;
|
||||
|
||||
lastSelectedMessage: MessageTimestamps | undefined;
|
||||
selectedMessageIds: ReadonlyArray<string> | undefined;
|
||||
|
@ -771,15 +772,14 @@ type ConversationStoppedByMissingVerificationActionType = ReadonlyDeep<{
|
|||
untrustedServiceIds: ReadonlyArray<ServiceIdString>;
|
||||
};
|
||||
}>;
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME
|
||||
export type MessageChangedActionType = {
|
||||
export type MessageChangedActionType = ReadonlyDeep<{
|
||||
type: typeof MESSAGE_CHANGED;
|
||||
payload: {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
data: MessageAttributesType;
|
||||
data: ReadonlyMessageAttributesType;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
export type MessageDeletedActionType = ReadonlyDeep<{
|
||||
type: typeof MESSAGE_DELETED;
|
||||
payload: {
|
||||
|
@ -802,15 +802,14 @@ export type ShowSpoilerActionType = ReadonlyDeep<{
|
|||
};
|
||||
}>;
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME
|
||||
export type MessagesAddedActionType = Readonly<{
|
||||
export type MessagesAddedActionType = ReadonlyDeep<{
|
||||
type: 'MESSAGES_ADDED';
|
||||
payload: {
|
||||
conversationId: string;
|
||||
isActive: boolean;
|
||||
isJustSent: boolean;
|
||||
isNewMessage: boolean;
|
||||
messages: ReadonlyArray<MessageAttributesType>;
|
||||
messages: ReadonlyArray<ReadonlyMessageAttributesType>;
|
||||
};
|
||||
}>;
|
||||
|
||||
|
@ -833,19 +832,18 @@ export type RepairOldestMessageActionType = ReadonlyDeep<{
|
|||
conversationId: string;
|
||||
};
|
||||
}>;
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
export type MessagesResetActionType = {
|
||||
export type MessagesResetActionType = ReadonlyDeep<{
|
||||
type: 'MESSAGES_RESET';
|
||||
payload: {
|
||||
conversationId: string;
|
||||
messages: ReadonlyArray<MessageAttributesType>;
|
||||
messages: ReadonlyArray<ReadonlyMessageAttributesType>;
|
||||
metrics: MessageMetricsType;
|
||||
scrollToMessageId?: string;
|
||||
// The set of provided messages should be trusted, even if it conflicts with metrics,
|
||||
// because we weren't looking for a specific time window of messages with our query.
|
||||
unboundedFetch: boolean;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
export type SetMessageLoadingStateActionType = ReadonlyDeep<{
|
||||
type: 'SET_MESSAGE_LOADING_STATE';
|
||||
payload: {
|
||||
|
@ -957,8 +955,7 @@ export type ToggleConversationInChooseMembersActionType = ReadonlyDeep<{
|
|||
};
|
||||
}>;
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME
|
||||
type PushPanelActionType = Readonly<{
|
||||
type PushPanelActionType = ReadonlyDeep<{
|
||||
type: typeof PUSH_PANEL;
|
||||
payload: PanelRenderType;
|
||||
}>;
|
||||
|
@ -983,7 +980,7 @@ type ReplaceAvatarsActionType = ReadonlyDeep<{
|
|||
};
|
||||
}>;
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep -- FIXME
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
export type ConversationActionType =
|
||||
| CancelVerificationDataByConversationActionType
|
||||
| ClearCancelledVerificationActionType
|
||||
|
@ -2865,7 +2862,7 @@ function conversationStoppedByMissingVerification(payload: {
|
|||
export function messageChanged(
|
||||
id: string,
|
||||
conversationId: string,
|
||||
data: MessageAttributesType
|
||||
data: ReadonlyMessageAttributesType
|
||||
): MessageChangedActionType {
|
||||
return {
|
||||
type: MESSAGE_CHANGED,
|
||||
|
@ -2935,7 +2932,7 @@ function messagesAdded({
|
|||
isActive: boolean;
|
||||
isJustSent: boolean;
|
||||
isNewMessage: boolean;
|
||||
messages: ReadonlyArray<MessageAttributesType>;
|
||||
messages: ReadonlyArray<ReadonlyMessageAttributesType>;
|
||||
}): ThunkAction<void, RootStateType, unknown, MessagesAddedActionType> {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
@ -2990,14 +2987,13 @@ function reviewConversationNameCollision(): ReviewConversationNameCollisionActio
|
|||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
export type MessageResetOptionsType = {
|
||||
export type MessageResetOptionsType = ReadonlyDeep<{
|
||||
conversationId: string;
|
||||
messages: ReadonlyArray<MessageAttributesType>;
|
||||
messages: ReadonlyArray<ReadonlyMessageAttributesType>;
|
||||
metrics: MessageMetricsType;
|
||||
scrollToMessageId?: string;
|
||||
unboundedFetch?: boolean;
|
||||
};
|
||||
}>;
|
||||
|
||||
function messagesReset({
|
||||
conversationId,
|
||||
|
@ -4716,7 +4712,7 @@ function maybeUpdateSelectedMessageForDetails(
|
|||
targetedMessageForDetails,
|
||||
}: {
|
||||
messageId: string;
|
||||
targetedMessageForDetails: MessageAttributesType | undefined;
|
||||
targetedMessageForDetails: ReadonlyMessageAttributesType | undefined;
|
||||
},
|
||||
state: ConversationsStateType
|
||||
): ConversationsStateType {
|
||||
|
|
|
@ -6,7 +6,7 @@ import type { ReadonlyDeep } from 'type-fest';
|
|||
import type { ExplodePromiseResultType } from '../../util/explodePromise';
|
||||
import type {
|
||||
GroupV2PendingMemberType,
|
||||
MessageAttributesType,
|
||||
ReadonlyMessageAttributesType,
|
||||
} from '../../model-types.d';
|
||||
import type {
|
||||
MessageChangedActionType,
|
||||
|
@ -52,7 +52,7 @@ import { linkCallRoute } from '../../util/signalRoutes';
|
|||
// State
|
||||
|
||||
export type EditHistoryMessagesType = ReadonlyDeep<
|
||||
Array<MessageAttributesType>
|
||||
Array<ReadonlyMessageAttributesType>
|
||||
>;
|
||||
export type EditNicknameAndNoteModalPropsType = ReadonlyDeep<{
|
||||
conversationId: string;
|
||||
|
@ -882,7 +882,7 @@ function showShortcutGuideModal(): ShowShortcutGuideModalActionType {
|
|||
}
|
||||
|
||||
function copyOverMessageAttributesIntoEditHistory(
|
||||
messageAttributes: ReadonlyDeep<MessageAttributesType>
|
||||
messageAttributes: ReadonlyDeep<ReadonlyMessageAttributesType>
|
||||
): EditHistoryMessagesType | undefined {
|
||||
if (!messageAttributes.editHistory) {
|
||||
return;
|
||||
|
@ -934,7 +934,7 @@ function closeEditHistoryModal(): CloseEditHistoryModalActionType {
|
|||
|
||||
function copyOverMessageAttributesIntoForwardMessages(
|
||||
messageDrafts: ReadonlyArray<MessageForwardDraft>,
|
||||
attributes: ReadonlyDeep<MessageAttributesType>
|
||||
attributes: ReadonlyDeep<ReadonlyMessageAttributesType>
|
||||
): ReadonlyArray<MessageForwardDraft> {
|
||||
return messageDrafts.map(messageDraft => {
|
||||
if (messageDraft.originalMessageId !== attributes.id) {
|
||||
|
|
|
@ -18,7 +18,7 @@ import type { StateType as RootStateType } from '../reducer';
|
|||
|
||||
import * as log from '../../logging/log';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import type { ReadonlyMessageAttributesType } from '../../model-types.d';
|
||||
import { isGIF } from '../../types/Attachment';
|
||||
import {
|
||||
isImageTypeSupported,
|
||||
|
@ -245,7 +245,7 @@ function showLightboxForViewOnceMedia(
|
|||
}
|
||||
|
||||
function filterValidAttachments(
|
||||
attributes: MessageAttributesType
|
||||
attributes: ReadonlyMessageAttributesType
|
||||
): Array<AttachmentType> {
|
||||
return (attributes.attachments ?? []).filter(
|
||||
item => item.thumbnail && !item.pending && !item.error
|
||||
|
|
|
@ -8,7 +8,7 @@ import type { ReadonlyDeep } from 'type-fest';
|
|||
import * as Errors from '../../types/errors';
|
||||
import type { AttachmentType } from '../../types/Attachment';
|
||||
import type { DraftBodyRanges } from '../../types/BodyRange';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import type { ReadonlyMessageAttributesType } from '../../model-types.d';
|
||||
import type {
|
||||
MessageChangedActionType,
|
||||
MessageDeletedActionType,
|
||||
|
@ -79,7 +79,7 @@ export type StoryDataType = ReadonlyDeep<
|
|||
messageId: string;
|
||||
startedDownload?: boolean;
|
||||
} & Pick<
|
||||
MessageAttributesType,
|
||||
ReadonlyMessageAttributesType,
|
||||
| 'bodyRanges'
|
||||
| 'canReplyToStory'
|
||||
| 'conversationId'
|
||||
|
@ -124,31 +124,33 @@ export type AddStoryData = ReadonlyDeep<
|
|||
| undefined
|
||||
>;
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
export type RecipientsByConversation = Record<
|
||||
string, // conversationId
|
||||
{
|
||||
serviceIds: Array<ServiceIdString>;
|
||||
export type RecipientEntry = ReadonlyDeep<{
|
||||
serviceIds: Array<ServiceIdString>;
|
||||
|
||||
byDistributionId?: Record<
|
||||
StoryDistributionIdString,
|
||||
{
|
||||
serviceIds: Array<ServiceIdString>;
|
||||
}
|
||||
>;
|
||||
}
|
||||
byDistributionId?: Record<
|
||||
StoryDistributionIdString,
|
||||
{
|
||||
serviceIds: Array<ServiceIdString>;
|
||||
}
|
||||
>;
|
||||
}>;
|
||||
|
||||
export type RecipientsByConversation = ReadonlyDeep<
|
||||
Record<
|
||||
string, // conversationId
|
||||
RecipientEntry
|
||||
>
|
||||
>;
|
||||
|
||||
// State
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
export type StoriesStateType = Readonly<{
|
||||
export type StoriesStateType = ReadonlyDeep<{
|
||||
addStoryData: AddStoryData;
|
||||
hasAllStoriesUnmuted: boolean;
|
||||
lastOpenedAtTimestamp: number | undefined;
|
||||
replyState?: Readonly<{
|
||||
messageId: string;
|
||||
replies: Array<MessageAttributesType>;
|
||||
replies: Array<ReadonlyMessageAttributesType>;
|
||||
}>;
|
||||
selectedStoryData?: SelectedStoryDataType;
|
||||
sendStoryModalData?: RecipientsByConversation;
|
||||
|
@ -188,14 +190,13 @@ type ListMembersVerified = ReadonlyDeep<{
|
|||
};
|
||||
}>;
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
type LoadStoryRepliesActionType = {
|
||||
type LoadStoryRepliesActionType = ReadonlyDeep<{
|
||||
type: typeof LOAD_STORY_REPLIES;
|
||||
payload: {
|
||||
messageId: string;
|
||||
replies: Array<MessageAttributesType>;
|
||||
replies: Array<ReadonlyMessageAttributesType>;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
type MarkStoryReadActionType = ReadonlyDeep<{
|
||||
type: typeof MARK_STORY_READ;
|
||||
|
|
|
@ -19,7 +19,7 @@ import type { StateType } from '../reducer';
|
|||
import * as log from '../../logging/log';
|
||||
import { getLocalAttachmentUrl } from '../../util/getLocalAttachmentUrl';
|
||||
import type { MessageWithUIFieldsType } from '../ducks/conversations';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import type { ReadonlyMessageAttributesType } from '../../model-types.d';
|
||||
import { getMessageIdForLogging } from '../../util/idForLogging';
|
||||
import * as Attachment from '../../types/Attachment';
|
||||
import type { ActiveAudioPlayerStateType } from '../ducks/audioPlayer';
|
||||
|
@ -57,7 +57,7 @@ export const selectVoiceNoteTitle = createSelector(
|
|||
(ourNumber, ourAci, ourConversationId, conversationSelector, i18n) => {
|
||||
return (
|
||||
message: Pick<
|
||||
MessageAttributesType,
|
||||
ReadonlyMessageAttributesType,
|
||||
'type' | 'source' | 'sourceServiceId'
|
||||
>
|
||||
) => {
|
||||
|
@ -75,7 +75,7 @@ export const selectVoiceNoteTitle = createSelector(
|
|||
);
|
||||
|
||||
export function extractVoiceNoteForPlayback(
|
||||
message: MessageAttributesType,
|
||||
message: ReadonlyMessageAttributesType,
|
||||
ourConversationId: string | undefined
|
||||
): VoiceNoteForPlayback | undefined {
|
||||
const { type } = message;
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { ReadonlyDeep } from 'type-fest';
|
|||
import type { StateType } from '../reducer';
|
||||
import type {
|
||||
LastMessageStatus,
|
||||
MessageAttributesType,
|
||||
ReadonlyMessageAttributesType,
|
||||
MessageReactionType,
|
||||
QuotedAttachmentType,
|
||||
ShallowChallengeError,
|
||||
|
@ -201,7 +201,7 @@ export function hasErrors(
|
|||
}
|
||||
|
||||
export function getSource(
|
||||
message: Pick<MessageAttributesType, 'type' | 'source'>,
|
||||
message: Pick<ReadonlyMessageAttributesType, 'type' | 'source'>,
|
||||
ourNumber: string | undefined
|
||||
): string | undefined {
|
||||
if (isIncoming(message)) {
|
||||
|
@ -233,7 +233,7 @@ export function getSourceDevice(
|
|||
}
|
||||
|
||||
export function getSourceServiceId(
|
||||
message: Pick<MessageAttributesType, 'type' | 'sourceServiceId'>,
|
||||
message: Pick<ReadonlyMessageAttributesType, 'type' | 'sourceServiceId'>,
|
||||
ourAci: AciString | undefined
|
||||
): ServiceIdString | undefined {
|
||||
if (isIncoming(message)) {
|
||||
|
@ -1523,13 +1523,13 @@ function getPropsForProfileChange(
|
|||
// Message Request Response Event
|
||||
|
||||
export function isMessageRequestResponse(
|
||||
message: MessageAttributesType
|
||||
message: ReadonlyMessageAttributesType
|
||||
): boolean {
|
||||
return message.type === 'message-request-response-event';
|
||||
}
|
||||
|
||||
function getPropsForMessageRequestResponse(
|
||||
message: MessageAttributesType
|
||||
message: ReadonlyMessageAttributesType
|
||||
): MessageRequestResponseNotificationData {
|
||||
const { messageRequestResponseEvent } = message;
|
||||
if (!messageRequestResponseEvent) {
|
||||
|
@ -1805,7 +1805,7 @@ export function getPropsForEmbeddedContact(
|
|||
message: MessageWithUIFieldsType,
|
||||
regionCode: string | undefined,
|
||||
accountSelector: (identifier?: string) => ServiceIdString | undefined
|
||||
): EmbeddedContactType | undefined {
|
||||
): ReadonlyDeep<EmbeddedContactType> | undefined {
|
||||
const contacts = message.contact;
|
||||
if (!contacts || !contacts.length) {
|
||||
return undefined;
|
||||
|
@ -2091,7 +2091,7 @@ export function getLastChallengeError(
|
|||
|
||||
const getTargetedMessageForDetails = (
|
||||
state: StateType
|
||||
): MessageAttributesType | undefined =>
|
||||
): ReadonlyMessageAttributesType | undefined =>
|
||||
state.conversations.targetedMessageForDetails;
|
||||
|
||||
const OUTGOING_KEY_ERROR = 'OutgoingIdentityKeyError';
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import type { ReadonlyMessageAttributesType } from '../../model-types.d';
|
||||
import { EditHistoryMessagesModal } from '../../components/EditHistoryMessagesModal';
|
||||
import { getIntl, getPlatform } from '../selectors/user';
|
||||
import { getMessagePropsSelector } from '../selectors/message';
|
||||
|
@ -31,7 +31,9 @@ export const SmartEditHistoryMessagesModal = memo(
|
|||
|
||||
const editHistoryMessages = useMemo(() => {
|
||||
return messagesAttributes.map(messageAttributes => ({
|
||||
...messagePropsSelector(messageAttributes as MessageAttributesType),
|
||||
...messagePropsSelector(
|
||||
messageAttributes as ReadonlyMessageAttributesType
|
||||
),
|
||||
// Make sure the messages don't get an "edited" badge
|
||||
isEditedMessage: false,
|
||||
// Do not show the same reactions in the message history UI
|
||||
|
|
|
@ -5,6 +5,7 @@ import { assert } from 'chai';
|
|||
import * as sinon from 'sinon';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import { times } from 'lodash';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import { reducer as rootReducer } from '../../../state/reducer';
|
||||
import { noopAction } from '../../../state/ducks/noop';
|
||||
|
@ -60,7 +61,7 @@ import {
|
|||
VIEWERS_CHANGED,
|
||||
} from '../../../state/ducks/storyDistributionLists';
|
||||
import { MY_STORY_ID } from '../../../types/Stories';
|
||||
import type { MessageAttributesType } from '../../../model-types.d';
|
||||
import type { ReadonlyMessageAttributesType } from '../../../model-types.d';
|
||||
|
||||
const {
|
||||
clearGroupCreationError,
|
||||
|
@ -92,8 +93,8 @@ const {
|
|||
function messageChanged(
|
||||
messageId: string,
|
||||
conversationId: string,
|
||||
data: MessageAttributesType
|
||||
): MessageChangedActionType {
|
||||
data: ReadonlyMessageAttributesType
|
||||
): ReadonlyDeep<MessageChangedActionType> {
|
||||
return {
|
||||
type: 'MESSAGE_CHANGED',
|
||||
payload: {
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { omit } from 'lodash';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import type { ReadonlyMessageAttributesType } from '../model-types.d';
|
||||
|
||||
import { isNotNil } from '../util/isNotNil';
|
||||
import {
|
||||
|
@ -147,13 +148,13 @@ export function numberToAddressType(
|
|||
}
|
||||
|
||||
export function embeddedContactSelector(
|
||||
contact: EmbeddedContactType,
|
||||
contact: ReadonlyDeep<EmbeddedContactType>,
|
||||
options: {
|
||||
regionCode?: string;
|
||||
firstNumber?: string;
|
||||
serviceId?: ServiceIdString;
|
||||
}
|
||||
): EmbeddedContactType {
|
||||
): ReadonlyDeep<EmbeddedContactType> {
|
||||
const { firstNumber, serviceId, regionCode } = options;
|
||||
|
||||
let { avatar } = contact;
|
||||
|
@ -189,7 +190,9 @@ export function embeddedContactSelector(
|
|||
};
|
||||
}
|
||||
|
||||
export function getName(contact: EmbeddedContactType): string | undefined {
|
||||
export function getName(
|
||||
contact: ReadonlyDeep<EmbeddedContactType>
|
||||
): string | undefined {
|
||||
const { name, organization } = contact;
|
||||
const displayName = (name && name.displayName) || undefined;
|
||||
const givenName = (name && name.givenName) || undefined;
|
||||
|
@ -206,7 +209,7 @@ export function parseAndWriteAvatar(
|
|||
return async (
|
||||
contact: EmbeddedContactType,
|
||||
context: {
|
||||
message: MessageAttributesType;
|
||||
message: ReadonlyMessageAttributesType;
|
||||
getRegionCode: () => string | undefined;
|
||||
logger: LoggerType;
|
||||
writeNewAttachmentData: (
|
||||
|
@ -280,7 +283,7 @@ function parseContact(
|
|||
return result;
|
||||
}
|
||||
|
||||
function idForLogging(message: MessageAttributesType): string {
|
||||
function idForLogging(message: ReadonlyMessageAttributesType): string {
|
||||
return `${message.source}.${message.sourceDevice} ${message.sent_at}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { orderBy } from 'lodash';
|
||||
import type { MessageAttributesType } from '../model-types';
|
||||
import type { ReadonlyMessageAttributesType } from '../model-types';
|
||||
import {
|
||||
isVoiceMessage,
|
||||
type AttachmentType,
|
||||
|
@ -23,7 +23,7 @@ export type MessageForwardDraft = Readonly<{
|
|||
|
||||
export type ForwardMessageData = Readonly<{
|
||||
// only null for new messages
|
||||
originalMessage: MessageAttributesType | null;
|
||||
originalMessage: ReadonlyMessageAttributesType | null;
|
||||
draft: MessageForwardDraft;
|
||||
}>;
|
||||
|
||||
|
@ -72,7 +72,7 @@ export function sortByMessageOrder<T>(
|
|||
items: ReadonlyArray<T>,
|
||||
getMesssage: (
|
||||
item: T
|
||||
) => Pick<MessageAttributesType, 'sent_at' | 'received_at'> | null
|
||||
) => Pick<ReadonlyMessageAttributesType, 'sent_at' | 'received_at'> | null
|
||||
): Array<T> {
|
||||
return orderBy(
|
||||
items,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// 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 type { AttachmentType } from './Attachment';
|
||||
import type { MIMEType } from './MIME';
|
||||
|
||||
export type MediaItemMessageType = Pick<
|
||||
MessageAttributesType,
|
||||
ReadonlyMessageAttributesType,
|
||||
| 'attachments'
|
||||
| 'conversationId'
|
||||
| 'id'
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { isFunction, isObject } from 'lodash';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import * as Contact from './EmbeddedContact';
|
||||
import type {
|
||||
|
@ -43,6 +44,7 @@ import {
|
|||
AttachmentDisposition,
|
||||
} from '../util/getLocalAttachmentUrl';
|
||||
import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment';
|
||||
import { deepClone } from '../util/deepClone';
|
||||
|
||||
export { hasExpiration } from './Message';
|
||||
|
||||
|
@ -831,14 +833,14 @@ export const loadQuoteData = (
|
|||
export const loadContactData = (
|
||||
loadAttachmentData: LoadAttachmentType
|
||||
): ((
|
||||
contact: Array<EmbeddedContactType> | undefined
|
||||
contact: ReadonlyArray<ReadonlyDeep<EmbeddedContactType>> | undefined
|
||||
) => Promise<Array<EmbeddedContactWithHydratedAvatar> | undefined>) => {
|
||||
if (!isFunction(loadAttachmentData)) {
|
||||
throw new TypeError('loadContactData: loadAttachmentData is required');
|
||||
}
|
||||
|
||||
return async (
|
||||
contact: Array<EmbeddedContactType> | undefined
|
||||
contact: ReadonlyArray<ReadonlyDeep<EmbeddedContactType>> | undefined
|
||||
): Promise<Array<EmbeddedContactWithHydratedAvatar> | undefined> => {
|
||||
if (!contact) {
|
||||
return undefined;
|
||||
|
@ -847,27 +849,23 @@ export const loadContactData = (
|
|||
return Promise.all(
|
||||
contact.map(
|
||||
async (
|
||||
item: EmbeddedContactType
|
||||
item: ReadonlyDeep<EmbeddedContactType>
|
||||
): Promise<EmbeddedContactWithHydratedAvatar> => {
|
||||
if (
|
||||
!item ||
|
||||
!item.avatar ||
|
||||
!item.avatar.avatar ||
|
||||
!item.avatar.avatar.path
|
||||
) {
|
||||
const copy = deepClone(item);
|
||||
if (!copy?.avatar?.avatar?.path) {
|
||||
return {
|
||||
...item,
|
||||
...copy,
|
||||
avatar: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
...copy,
|
||||
avatar: {
|
||||
...item.avatar,
|
||||
...copy.avatar,
|
||||
avatar: {
|
||||
...item.avatar.avatar,
|
||||
...(await loadAttachmentData(item.avatar.avatar)),
|
||||
...copy.avatar.avatar,
|
||||
...(await loadAttachmentData(copy.avatar.avatar)),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -880,13 +878,15 @@ export const loadContactData = (
|
|||
export const loadPreviewData = (
|
||||
loadAttachmentData: LoadAttachmentType
|
||||
): ((
|
||||
preview: Array<LinkPreviewType> | undefined
|
||||
preview: ReadonlyArray<ReadonlyDeep<LinkPreviewType>> | undefined
|
||||
) => Promise<Array<LinkPreviewWithHydratedData>>) => {
|
||||
if (!isFunction(loadAttachmentData)) {
|
||||
throw new TypeError('loadPreviewData: loadAttachmentData is required');
|
||||
}
|
||||
|
||||
return async (preview: Array<LinkPreviewType> | undefined) => {
|
||||
return async (
|
||||
preview: ReadonlyArray<ReadonlyDeep<LinkPreviewType>> | undefined
|
||||
) => {
|
||||
if (!preview || !preview.length) {
|
||||
return [];
|
||||
}
|
||||
|
@ -894,17 +894,19 @@ export const loadPreviewData = (
|
|||
return Promise.all(
|
||||
preview.map(
|
||||
async (item: LinkPreviewType): Promise<LinkPreviewWithHydratedData> => {
|
||||
if (!item.image) {
|
||||
const copy = deepClone(item);
|
||||
|
||||
if (!copy.image) {
|
||||
return {
|
||||
...item,
|
||||
...copy,
|
||||
// Pacify typescript
|
||||
image: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
image: await loadAttachmentData(item.image),
|
||||
...copy,
|
||||
image: await loadAttachmentData(copy.image),
|
||||
};
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
import type { EmbeddedContactType } from './EmbeddedContact';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import type { ReadonlyMessageAttributesType } from '../model-types.d';
|
||||
import type { ServiceIdString } from './ServiceId';
|
||||
|
||||
export enum PanelType {
|
||||
|
@ -19,7 +21,7 @@ export enum PanelType {
|
|||
StickerManager = 'StickerManager',
|
||||
}
|
||||
|
||||
export type PanelRequestType =
|
||||
export type PanelRequestType = ReadonlyDeep<
|
||||
| { type: PanelType.AllMedia }
|
||||
| { type: PanelType.ChatColorEditor }
|
||||
| {
|
||||
|
@ -39,9 +41,10 @@ export type PanelRequestType =
|
|||
| { type: PanelType.GroupV1Members }
|
||||
| { type: PanelType.MessageDetails; args: { messageId: string } }
|
||||
| { type: PanelType.NotificationSettings }
|
||||
| { type: PanelType.StickerManager };
|
||||
| { type: PanelType.StickerManager }
|
||||
>;
|
||||
|
||||
export type PanelRenderType =
|
||||
export type PanelRenderType = ReadonlyDeep<
|
||||
| { type: PanelType.AllMedia }
|
||||
| { type: PanelType.ChatColorEditor }
|
||||
| {
|
||||
|
@ -59,6 +62,10 @@ export type PanelRenderType =
|
|||
| { type: PanelType.GroupLinkManagement }
|
||||
| { type: PanelType.GroupPermissions }
|
||||
| { type: PanelType.GroupV1Members }
|
||||
| { type: PanelType.MessageDetails; args: { message: MessageAttributesType } }
|
||||
| {
|
||||
type: PanelType.MessageDetails;
|
||||
args: { message: ReadonlyMessageAttributesType };
|
||||
}
|
||||
| { type: PanelType.NotificationSettings }
|
||||
| { type: PanelType.StickerManager };
|
||||
| { type: PanelType.StickerManager }
|
||||
>;
|
||||
|
|
|
@ -9,7 +9,7 @@ export enum PaymentEventKind {
|
|||
// Cancellation = 5, -- disabled
|
||||
}
|
||||
|
||||
export type PaymentNotificationEvent = {
|
||||
export type PaymentNotificationEvent = Readonly<{
|
||||
kind: PaymentEventKind.Notification;
|
||||
note: string | null;
|
||||
|
||||
|
@ -17,15 +17,15 @@ export type PaymentNotificationEvent = {
|
|||
transactionDetailsBase64?: string;
|
||||
amountMob?: string;
|
||||
feeMob?: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type PaymentActivationRequestEvent = {
|
||||
export type PaymentActivationRequestEvent = Readonly<{
|
||||
kind: PaymentEventKind.ActivationRequest;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type PaymentActivatedEvent = {
|
||||
export type PaymentActivatedEvent = Readonly<{
|
||||
kind: PaymentEventKind.Activation;
|
||||
};
|
||||
}>;
|
||||
|
||||
export type AnyPaymentEvent =
|
||||
| PaymentNotificationEvent
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
ts/util/deepClone.ts
Normal file
11
ts/util/deepClone.ts
Normal 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>;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
} {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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] = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Reference in a new issue