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