Replace MessageController with MessageCache
This commit is contained in:
@@ -1,143 +0,0 @@
|
||||
// Copyright 2019 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import * as durations from './durations';
|
||||
import * as log from '../logging/log';
|
||||
import { map, filter } from './iterables';
|
||||
import { isNotNil } from './isNotNil';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import { isEnabled } from '../RemoteConfig';
|
||||
|
||||
const FIVE_MINUTES = 5 * durations.MINUTE;
|
||||
|
||||
type LookupItemType = {
|
||||
timestamp: number;
|
||||
message: MessageModel;
|
||||
};
|
||||
type LookupType = Record<string, LookupItemType>;
|
||||
|
||||
export class MessageController {
|
||||
private messageLookup: LookupType = Object.create(null);
|
||||
|
||||
private msgIDsBySender = new Map<string, string>();
|
||||
|
||||
private msgIDsBySentAt = new Map<number, Set<string>>();
|
||||
|
||||
static install(): MessageController {
|
||||
const instance = new MessageController();
|
||||
window.MessageController = instance;
|
||||
|
||||
instance.startCleanupInterval();
|
||||
return instance;
|
||||
}
|
||||
|
||||
register(
|
||||
id: string,
|
||||
data: MessageModel | MessageAttributesType
|
||||
): MessageModel {
|
||||
if (!id || !data) {
|
||||
throw new Error('MessageController.register: Got falsey id or message');
|
||||
}
|
||||
|
||||
const existing = this.messageLookup[id];
|
||||
if (existing) {
|
||||
this.messageLookup[id] = {
|
||||
message: existing.message,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
return existing.message;
|
||||
}
|
||||
|
||||
const message =
|
||||
'attributes' in data ? data : new window.Whisper.Message(data);
|
||||
this.messageLookup[id] = {
|
||||
message,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
const sentAt = message.get('sent_at');
|
||||
const previousIdsBySentAt = this.msgIDsBySentAt.get(sentAt);
|
||||
if (previousIdsBySentAt) {
|
||||
previousIdsBySentAt.add(id);
|
||||
} else {
|
||||
this.msgIDsBySentAt.set(sentAt, new Set([id]));
|
||||
}
|
||||
|
||||
this.msgIDsBySender.set(message.getSenderIdentifier(), id);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
unregister(id: string): void {
|
||||
const { message } = this.messageLookup[id] || {};
|
||||
if (message) {
|
||||
this.msgIDsBySender.delete(message.getSenderIdentifier());
|
||||
|
||||
const sentAt = message.get('sent_at');
|
||||
const idsBySentAt = this.msgIDsBySentAt.get(sentAt) || new Set();
|
||||
idsBySentAt.delete(id);
|
||||
if (!idsBySentAt.size) {
|
||||
this.msgIDsBySentAt.delete(sentAt);
|
||||
}
|
||||
}
|
||||
delete this.messageLookup[id];
|
||||
}
|
||||
|
||||
cleanup(): void {
|
||||
const messages = Object.values(this.messageLookup);
|
||||
const now = Date.now();
|
||||
|
||||
for (let i = 0, max = messages.length; i < max; i += 1) {
|
||||
const { message, timestamp } = messages[i];
|
||||
const conversation = message.getConversation();
|
||||
|
||||
const state = window.reduxStore.getState();
|
||||
const selectedId = state?.conversations?.selectedConversationId;
|
||||
const inActiveConversation =
|
||||
conversation && selectedId && conversation.id === selectedId;
|
||||
|
||||
if (now - timestamp > FIVE_MINUTES && !inActiveConversation) {
|
||||
this.unregister(message.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getById(id: string): MessageModel | undefined {
|
||||
const existing = this.messageLookup[id];
|
||||
return existing && existing.message ? existing.message : undefined;
|
||||
}
|
||||
|
||||
filterBySentAt(sentAt: number): Iterable<MessageModel> {
|
||||
const ids = this.msgIDsBySentAt.get(sentAt) || [];
|
||||
const maybeMessages = map(ids, id => this.getById(id));
|
||||
return filter(maybeMessages, isNotNil);
|
||||
}
|
||||
|
||||
findBySender(sender: string): MessageModel | undefined {
|
||||
const id = this.msgIDsBySender.get(sender);
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
return this.getById(id);
|
||||
}
|
||||
|
||||
update(predicate: (message: MessageModel) => void): void {
|
||||
const values = Object.values(this.messageLookup);
|
||||
log.info(
|
||||
`MessageController.update: About to process ${values.length} messages`
|
||||
);
|
||||
values.forEach(({ message }) => predicate(message));
|
||||
}
|
||||
|
||||
_get(): LookupType {
|
||||
return this.messageLookup;
|
||||
}
|
||||
|
||||
startCleanupInterval(): NodeJS.Timeout | number {
|
||||
return setInterval(
|
||||
this.cleanup.bind(this),
|
||||
isEnabled('desktop.messageCleanup') ? FIVE_MINUTES : durations.HOUR
|
||||
);
|
||||
}
|
||||
}
|
55
ts/util/MessageModelLogger.ts
Normal file
55
ts/util/MessageModelLogger.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import * as log from '../logging/log';
|
||||
import { getEnvironment, Environment } from '../environment';
|
||||
|
||||
export function getMessageModelLogger(model: MessageModel): MessageModel {
|
||||
const { id } = model;
|
||||
|
||||
if (getEnvironment() !== Environment.Development) {
|
||||
return model;
|
||||
}
|
||||
|
||||
const proxyHandler: ProxyHandler<MessageModel> = {
|
||||
get(target: MessageModel, property: keyof MessageModel) {
|
||||
// Allowed set of attributes & methods
|
||||
if (property === 'attributes') {
|
||||
return model.attributes;
|
||||
}
|
||||
|
||||
if (property === 'id') {
|
||||
return id;
|
||||
}
|
||||
|
||||
if (property === 'get') {
|
||||
return model.get.bind(model);
|
||||
}
|
||||
|
||||
if (property === 'set') {
|
||||
return model.set.bind(model);
|
||||
}
|
||||
|
||||
if (property === 'registerLocations') {
|
||||
return target.registerLocations;
|
||||
}
|
||||
|
||||
// Disallowed set of methods & attributes
|
||||
|
||||
log.warn(`MessageModelLogger: model.${property}`, new Error().stack);
|
||||
|
||||
if (typeof target[property] === 'function') {
|
||||
return target[property].bind(target);
|
||||
}
|
||||
|
||||
if (typeof target[property] !== 'undefined') {
|
||||
return target[property];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
|
||||
return new Proxy(model, proxyHandler);
|
||||
}
|
@@ -883,12 +883,13 @@ async function saveCallHistory(
|
||||
});
|
||||
log.info('saveCallHistory: Saved call history message:', id);
|
||||
|
||||
const model = window.MessageController.register(
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
id,
|
||||
new window.Whisper.Message({
|
||||
...message,
|
||||
id,
|
||||
})
|
||||
}),
|
||||
'callDisposition'
|
||||
);
|
||||
|
||||
if (callHistory.direction === CallDirection.Outgoing) {
|
||||
@@ -986,7 +987,7 @@ export async function clearCallHistoryDataAndSync(): Promise<void> {
|
||||
const messageIds = await window.Signal.Data.clearCallHistory(timestamp);
|
||||
|
||||
messageIds.forEach(messageId => {
|
||||
const message = window.MessageController.getById(messageId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||
const conversation = message?.getConversation();
|
||||
if (message == null || conversation == null) {
|
||||
return;
|
||||
@@ -996,7 +997,7 @@ export async function clearCallHistoryDataAndSync(): Promise<void> {
|
||||
message.get('conversationId')
|
||||
);
|
||||
conversation.debouncedUpdateLastMessage();
|
||||
window.MessageController.unregister(messageId);
|
||||
window.MessageCache.__DEPRECATED$unregister(messageId);
|
||||
});
|
||||
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
@@ -25,7 +25,7 @@ export function cleanupMessageFromMemory(message: MessageAttributesType): void {
|
||||
const parentConversation = window.ConversationController.get(conversationId);
|
||||
parentConversation?.debouncedUpdateLastMessage();
|
||||
|
||||
window.MessageController.unregister(id);
|
||||
window.MessageCache.__DEPRECATED$unregister(id);
|
||||
}
|
||||
|
||||
async function cleanupStoryReplies(
|
||||
@@ -72,9 +72,10 @@ async function cleanupStoryReplies(
|
||||
// Cleanup all group replies
|
||||
await Promise.all(
|
||||
replies.map(reply => {
|
||||
const replyMessageModel = window.MessageController.register(
|
||||
const replyMessageModel = window.MessageCache.__DEPRECATED$register(
|
||||
reply.id,
|
||||
reply
|
||||
reply,
|
||||
'cleanupStoryReplies/group'
|
||||
);
|
||||
return replyMessageModel.eraseContents();
|
||||
})
|
||||
@@ -83,7 +84,11 @@ async function cleanupStoryReplies(
|
||||
// Refresh the storyReplyContext data for 1:1 conversations
|
||||
await Promise.all(
|
||||
replies.map(async reply => {
|
||||
const model = window.MessageController.register(reply.id, reply);
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
reply.id,
|
||||
reply,
|
||||
'cleanupStoryReplies/1:1'
|
||||
);
|
||||
model.unset('storyReplyContext');
|
||||
await model.hydrateStoryContext(story, { shouldSave: true });
|
||||
})
|
||||
|
@@ -3,13 +3,13 @@
|
||||
|
||||
import { DAY } from './durations';
|
||||
import { sendDeleteForEveryoneMessage } from './sendDeleteForEveryoneMessage';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
export async function deleteGroupStoryReplyForEveryone(
|
||||
replyMessageId: string
|
||||
): Promise<void> {
|
||||
const messageModel = await getMessageById(replyMessageId);
|
||||
const messageModel = await __DEPRECATED$getMessageById(replyMessageId);
|
||||
|
||||
if (!messageModel) {
|
||||
log.warn(
|
||||
|
@@ -19,7 +19,7 @@ import {
|
||||
import { onStoryRecipientUpdate } from './onStoryRecipientUpdate';
|
||||
import { sendDeleteForEveryoneMessage } from './sendDeleteForEveryoneMessage';
|
||||
import { isGroupV2 } from './whatTypeOfConversation';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||
import { strictAssert } from './assert';
|
||||
import { repeat, zipObject } from './iterables';
|
||||
import { isOlderThan } from './timestamp';
|
||||
@@ -46,7 +46,7 @@ export async function deleteStoryForEveryone(
|
||||
}
|
||||
|
||||
const logId = `deleteStoryForEveryone(${story.messageId})`;
|
||||
const message = await getMessageById(story.messageId);
|
||||
const message = await __DEPRECATED$getMessageById(story.messageId);
|
||||
if (!message) {
|
||||
throw new Error('Story not found');
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as log from '../logging/log';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { calculateExpirationTimestamp } from './expirationTimer';
|
||||
import { DAY } from './durations';
|
||||
|
||||
@@ -16,23 +15,23 @@ export async function findAndDeleteOnboardingStoryIfExists(): Promise<void> {
|
||||
}
|
||||
|
||||
const hasExpired = await (async () => {
|
||||
for (const id of existingOnboardingStoryMessageIds) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const message = await getMessageById(id);
|
||||
if (!message) {
|
||||
continue;
|
||||
}
|
||||
const [storyId] = existingOnboardingStoryMessageIds;
|
||||
try {
|
||||
const messageAttributes = await window.MessageCache.resolveAttributes(
|
||||
'findAndDeleteOnboardingStoryIfExists',
|
||||
storyId
|
||||
);
|
||||
|
||||
const expires = calculateExpirationTimestamp(message.attributes) ?? 0;
|
||||
const expires = calculateExpirationTimestamp(messageAttributes) ?? 0;
|
||||
|
||||
const now = Date.now();
|
||||
const isExpired = expires < now;
|
||||
const needsRepair = expires > now + 2 * DAY;
|
||||
|
||||
return isExpired || needsRepair;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
})();
|
||||
|
||||
if (!hasExpired) {
|
||||
|
@@ -31,7 +31,8 @@ export async function findStoryMessages(
|
||||
const ourConversationId =
|
||||
window.ConversationController.getOurConversationIdOrThrow();
|
||||
|
||||
const inMemoryMessages = window.MessageController.filterBySentAt(sentAt);
|
||||
const inMemoryMessages =
|
||||
window.MessageCache.__DEPRECATED$filterBySentAt(sentAt);
|
||||
const matchingMessages = [
|
||||
...filter(inMemoryMessages, item =>
|
||||
isStoryAMatch(
|
||||
@@ -60,7 +61,11 @@ export async function findStoryMessages(
|
||||
}
|
||||
|
||||
const result = found.map(attributes =>
|
||||
window.MessageController.register(attributes.id, attributes)
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
attributes.id,
|
||||
attributes,
|
||||
'findStoryMessages'
|
||||
)
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
50
ts/util/getMessageAuthorText.ts
Normal file
50
ts/util/getMessageAuthorText.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type {
|
||||
ConversationAttributesType,
|
||||
MessageAttributesType,
|
||||
} from '../model-types.d';
|
||||
import { isIncoming, isOutgoing } from '../state/selectors/message';
|
||||
import { getTitle } from './getTitle';
|
||||
|
||||
function getIncomingContact(
|
||||
messageAttributes: MessageAttributesType
|
||||
): ConversationAttributesType | undefined {
|
||||
if (!isIncoming(messageAttributes)) {
|
||||
return undefined;
|
||||
}
|
||||
const { sourceServiceId } = messageAttributes;
|
||||
if (!sourceServiceId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return window.ConversationController.getOrCreate(sourceServiceId, 'private')
|
||||
.attributes;
|
||||
}
|
||||
|
||||
export function getMessageAuthorText(
|
||||
messageAttributes?: MessageAttributesType
|
||||
): string | undefined {
|
||||
if (!messageAttributes) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// if it's outgoing, it must be self-authored
|
||||
const selfAuthor = isOutgoing(messageAttributes)
|
||||
? window.i18n('icu:you')
|
||||
: undefined;
|
||||
|
||||
if (selfAuthor) {
|
||||
return selfAuthor;
|
||||
}
|
||||
|
||||
const incomingContact = getIncomingContact(messageAttributes);
|
||||
if (incomingContact) {
|
||||
return getTitle(incomingContact, { isShort: true });
|
||||
}
|
||||
|
||||
// if it's not selfAuthor and there's no incoming contact,
|
||||
// it might be a group notification, so we return undefined
|
||||
return undefined;
|
||||
}
|
13
ts/util/getMessageConversation.ts
Normal file
13
ts/util/getMessageConversation.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import type { ConversationModel } from '../models/conversations';
|
||||
|
||||
export function getMessageConversation({
|
||||
conversationId,
|
||||
}: Pick<MessageAttributesType, 'conversationId'>):
|
||||
| ConversationModel
|
||||
| undefined {
|
||||
return window.ConversationController.get(conversationId);
|
||||
}
|
452
ts/util/getNotificationDataForMessage.ts
Normal file
452
ts/util/getNotificationDataForMessage.ts
Normal file
@@ -0,0 +1,452 @@
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RawBodyRange } from '../types/BodyRange';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import type { ReplacementValuesType } from '../types/I18N';
|
||||
import * as Attachment from '../types/Attachment';
|
||||
import * as EmbeddedContact from '../types/EmbeddedContact';
|
||||
import * as GroupChange from '../groupChange';
|
||||
import * as MIME from '../types/MIME';
|
||||
import * as Stickers from '../types/Stickers';
|
||||
import * as expirationTimer from './expirationTimer';
|
||||
import * as log from '../logging/log';
|
||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||
import { dropNull } from './dropNull';
|
||||
import { getCallHistorySelector } from '../state/selectors/callHistory';
|
||||
import { getCallSelector, getActiveCall } from '../state/selectors/calling';
|
||||
import { getCallingNotificationText } from './callingNotification';
|
||||
import { getConversationSelector } from '../state/selectors/conversations';
|
||||
import { getStringForConversationMerge } from './getStringForConversationMerge';
|
||||
import { getStringForProfileChange } from './getStringForProfileChange';
|
||||
import { getTitleNoDefault, getNumber } from './getTitle';
|
||||
import { findAndFormatContact } from './findAndFormatContact';
|
||||
import { isMe } from './whatTypeOfConversation';
|
||||
import { strictAssert } from './assert';
|
||||
import {
|
||||
getPropsForCallHistory,
|
||||
hasErrors,
|
||||
isCallHistory,
|
||||
isChatSessionRefreshed,
|
||||
isDeliveryIssue,
|
||||
isEndSession,
|
||||
isExpirationTimerUpdate,
|
||||
isGroupUpdate,
|
||||
isGroupV1Migration,
|
||||
isGroupV2Change,
|
||||
isIncoming,
|
||||
isKeyChange,
|
||||
isOutgoing,
|
||||
isProfileChange,
|
||||
isTapToView,
|
||||
isUnsupportedMessage,
|
||||
isConversationMerge,
|
||||
} from '../state/selectors/message';
|
||||
import {
|
||||
getContact,
|
||||
messageHasPaymentEvent,
|
||||
getPaymentEventNotificationText,
|
||||
} from '../messages/helpers';
|
||||
|
||||
function getNameForNumber(e164: string): string {
|
||||
const conversation = window.ConversationController.get(e164);
|
||||
if (!conversation) {
|
||||
return e164;
|
||||
}
|
||||
return conversation.getTitle();
|
||||
}
|
||||
|
||||
export function getNotificationDataForMessage(
|
||||
attributes: MessageAttributesType
|
||||
): {
|
||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||
emoji?: string;
|
||||
text: string;
|
||||
} {
|
||||
if (isDeliveryIssue(attributes)) {
|
||||
return {
|
||||
emoji: '⚠️',
|
||||
text: window.i18n('icu:DeliveryIssue--preview'),
|
||||
};
|
||||
}
|
||||
|
||||
if (isConversationMerge(attributes)) {
|
||||
const conversation = window.ConversationController.get(
|
||||
attributes.conversationId
|
||||
);
|
||||
strictAssert(
|
||||
conversation,
|
||||
'getNotificationData/isConversationMerge/conversation'
|
||||
);
|
||||
strictAssert(
|
||||
attributes.conversationMerge,
|
||||
'getNotificationData/isConversationMerge/conversationMerge'
|
||||
);
|
||||
|
||||
return {
|
||||
text: getStringForConversationMerge({
|
||||
obsoleteConversationTitle: getTitleNoDefault(
|
||||
attributes.conversationMerge.renderInfo
|
||||
),
|
||||
obsoleteConversationNumber: getNumber(
|
||||
attributes.conversationMerge.renderInfo
|
||||
),
|
||||
conversationTitle: conversation.getTitle(),
|
||||
i18n: window.i18n,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (isChatSessionRefreshed(attributes)) {
|
||||
return {
|
||||
emoji: '🔁',
|
||||
text: window.i18n('icu:ChatRefresh--notification'),
|
||||
};
|
||||
}
|
||||
|
||||
if (isUnsupportedMessage(attributes)) {
|
||||
return {
|
||||
text: window.i18n('icu:message--getDescription--unsupported-message'),
|
||||
};
|
||||
}
|
||||
|
||||
if (isGroupV1Migration(attributes)) {
|
||||
return {
|
||||
text: window.i18n('icu:GroupV1--Migration--was-upgraded'),
|
||||
};
|
||||
}
|
||||
|
||||
if (isProfileChange(attributes)) {
|
||||
const { profileChange: change, changedId } = attributes;
|
||||
const changedContact = findAndFormatContact(changedId);
|
||||
if (!change) {
|
||||
throw new Error('getNotificationData: profileChange was missing!');
|
||||
}
|
||||
|
||||
return {
|
||||
text: getStringForProfileChange(change, changedContact, window.i18n),
|
||||
};
|
||||
}
|
||||
|
||||
if (isGroupV2Change(attributes)) {
|
||||
const { groupV2Change: change } = attributes;
|
||||
strictAssert(
|
||||
change,
|
||||
'getNotificationData: isGroupV2Change true, but no groupV2Change!'
|
||||
);
|
||||
|
||||
const changes = GroupChange.renderChange<string>(change, {
|
||||
i18n: window.i18n,
|
||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||
ourPni: window.textsecure.storage.user.getCheckedPni(),
|
||||
renderContact: (conversationId: string) => {
|
||||
const conversation = window.ConversationController.get(conversationId);
|
||||
return conversation
|
||||
? conversation.getTitle()
|
||||
: window.i18n('icu:unknownContact');
|
||||
},
|
||||
renderString: (
|
||||
key: string,
|
||||
_i18n: unknown,
|
||||
components: ReplacementValuesType<string | number> | undefined
|
||||
) => {
|
||||
// eslint-disable-next-line local-rules/valid-i18n-keys
|
||||
return window.i18n(key, components);
|
||||
},
|
||||
});
|
||||
|
||||
return { text: changes.map(({ text }) => text).join(' ') };
|
||||
}
|
||||
|
||||
if (messageHasPaymentEvent(attributes)) {
|
||||
const sender = findAndFormatContact(attributes.sourceServiceId);
|
||||
const conversation = findAndFormatContact(attributes.conversationId);
|
||||
return {
|
||||
text: getPaymentEventNotificationText(
|
||||
attributes.payment,
|
||||
sender.title,
|
||||
conversation.title,
|
||||
sender.isMe,
|
||||
window.i18n
|
||||
),
|
||||
emoji: '💳',
|
||||
};
|
||||
}
|
||||
|
||||
const { attachments = [] } = attributes;
|
||||
|
||||
if (isTapToView(attributes)) {
|
||||
if (attributes.isErased) {
|
||||
return {
|
||||
text: window.i18n('icu:message--getDescription--disappearing-media'),
|
||||
};
|
||||
}
|
||||
|
||||
if (Attachment.isImage(attachments)) {
|
||||
return {
|
||||
text: window.i18n('icu:message--getDescription--disappearing-photo'),
|
||||
emoji: '📷',
|
||||
};
|
||||
}
|
||||
if (Attachment.isVideo(attachments)) {
|
||||
return {
|
||||
text: window.i18n('icu:message--getDescription--disappearing-video'),
|
||||
emoji: '🎥',
|
||||
};
|
||||
}
|
||||
// There should be an image or video attachment, but we have a fallback just in
|
||||
// case.
|
||||
return { text: window.i18n('icu:mediaMessage'), emoji: '📎' };
|
||||
}
|
||||
|
||||
if (isGroupUpdate(attributes)) {
|
||||
const { group_update: groupUpdate } = attributes;
|
||||
const fromContact = getContact(attributes);
|
||||
const messages = [];
|
||||
if (!groupUpdate) {
|
||||
throw new Error('getNotificationData: Missing group_update');
|
||||
}
|
||||
|
||||
if (groupUpdate.left === 'You') {
|
||||
return { text: window.i18n('icu:youLeftTheGroup') };
|
||||
}
|
||||
if (groupUpdate.left) {
|
||||
return {
|
||||
text: window.i18n('icu:leftTheGroup', {
|
||||
name: getNameForNumber(groupUpdate.left),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (!fromContact) {
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
if (isMe(fromContact.attributes)) {
|
||||
messages.push(window.i18n('icu:youUpdatedTheGroup'));
|
||||
} else {
|
||||
messages.push(
|
||||
window.i18n('icu:updatedTheGroup', {
|
||||
name: fromContact.getTitle(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (groupUpdate.joined && groupUpdate.joined.length) {
|
||||
const joinedContacts = groupUpdate.joined.map(item =>
|
||||
window.ConversationController.getOrCreate(item, 'private')
|
||||
);
|
||||
const joinedWithoutMe = joinedContacts.filter(
|
||||
contact => !isMe(contact.attributes)
|
||||
);
|
||||
|
||||
if (joinedContacts.length > 1) {
|
||||
messages.push(
|
||||
window.i18n('icu:multipleJoinedTheGroup', {
|
||||
names: joinedWithoutMe
|
||||
.map(contact => contact.getTitle())
|
||||
.join(', '),
|
||||
})
|
||||
);
|
||||
|
||||
if (joinedWithoutMe.length < joinedContacts.length) {
|
||||
messages.push(window.i18n('icu:youJoinedTheGroup'));
|
||||
}
|
||||
} else {
|
||||
const joinedContact = window.ConversationController.getOrCreate(
|
||||
groupUpdate.joined[0],
|
||||
'private'
|
||||
);
|
||||
if (isMe(joinedContact.attributes)) {
|
||||
messages.push(window.i18n('icu:youJoinedTheGroup'));
|
||||
} else {
|
||||
messages.push(
|
||||
window.i18n('icu:joinedTheGroup', {
|
||||
name: joinedContacts[0].getTitle(),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (groupUpdate.name) {
|
||||
messages.push(
|
||||
window.i18n('icu:titleIsNow', {
|
||||
name: groupUpdate.name,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (groupUpdate.avatarUpdated) {
|
||||
messages.push(window.i18n('icu:updatedGroupAvatar'));
|
||||
}
|
||||
|
||||
return { text: messages.join(' ') };
|
||||
}
|
||||
if (isEndSession(attributes)) {
|
||||
return { text: window.i18n('icu:sessionEnded') };
|
||||
}
|
||||
if (isIncoming(attributes) && hasErrors(attributes)) {
|
||||
return { text: window.i18n('icu:incomingError') };
|
||||
}
|
||||
|
||||
const { body: untrimmedBody = '', bodyRanges = [] } = attributes;
|
||||
const body = untrimmedBody.trim();
|
||||
|
||||
if (attachments.length) {
|
||||
// This should never happen but we want to be extra-careful.
|
||||
const attachment = attachments[0] || {};
|
||||
const { contentType } = attachment;
|
||||
|
||||
if (contentType === MIME.IMAGE_GIF || Attachment.isGIF(attachments)) {
|
||||
return {
|
||||
bodyRanges,
|
||||
emoji: '🎡',
|
||||
text: body || window.i18n('icu:message--getNotificationText--gif'),
|
||||
};
|
||||
}
|
||||
if (Attachment.isImage(attachments)) {
|
||||
return {
|
||||
bodyRanges,
|
||||
emoji: '📷',
|
||||
text: body || window.i18n('icu:message--getNotificationText--photo'),
|
||||
};
|
||||
}
|
||||
if (Attachment.isVideo(attachments)) {
|
||||
return {
|
||||
bodyRanges,
|
||||
emoji: '🎥',
|
||||
text: body || window.i18n('icu:message--getNotificationText--video'),
|
||||
};
|
||||
}
|
||||
if (Attachment.isVoiceMessage(attachment)) {
|
||||
return {
|
||||
bodyRanges,
|
||||
emoji: '🎤',
|
||||
text:
|
||||
body ||
|
||||
window.i18n('icu:message--getNotificationText--voice-message'),
|
||||
};
|
||||
}
|
||||
if (Attachment.isAudio(attachments)) {
|
||||
return {
|
||||
bodyRanges,
|
||||
emoji: '🔈',
|
||||
text:
|
||||
body ||
|
||||
window.i18n('icu:message--getNotificationText--audio-message'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
bodyRanges,
|
||||
text: body || window.i18n('icu:message--getNotificationText--file'),
|
||||
emoji: '📎',
|
||||
};
|
||||
}
|
||||
|
||||
const { sticker: stickerData } = attributes;
|
||||
if (stickerData) {
|
||||
const emoji =
|
||||
Stickers.getSticker(stickerData.packId, stickerData.stickerId)?.emoji ||
|
||||
stickerData?.emoji;
|
||||
|
||||
if (!emoji) {
|
||||
log.warn('Unable to get emoji for sticker');
|
||||
}
|
||||
return {
|
||||
text: window.i18n('icu:message--getNotificationText--stickers'),
|
||||
emoji: dropNull(emoji),
|
||||
};
|
||||
}
|
||||
|
||||
if (isCallHistory(attributes)) {
|
||||
const state = window.reduxStore.getState();
|
||||
const callingNotification = getPropsForCallHistory(attributes, {
|
||||
callSelector: getCallSelector(state),
|
||||
activeCall: getActiveCall(state),
|
||||
callHistorySelector: getCallHistorySelector(state),
|
||||
conversationSelector: getConversationSelector(state),
|
||||
});
|
||||
if (callingNotification) {
|
||||
const text = getCallingNotificationText(callingNotification, window.i18n);
|
||||
if (text != null) {
|
||||
return {
|
||||
text,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
log.error("This call history message doesn't have valid call history");
|
||||
}
|
||||
if (isExpirationTimerUpdate(attributes)) {
|
||||
const { expireTimer } = attributes.expirationTimerUpdate ?? {};
|
||||
if (!expireTimer) {
|
||||
return { text: window.i18n('icu:disappearingMessagesDisabled') };
|
||||
}
|
||||
|
||||
return {
|
||||
text: window.i18n('icu:timerSetTo', {
|
||||
time: expirationTimer.format(window.i18n, expireTimer),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (isKeyChange(attributes)) {
|
||||
const { key_changed: identifier } = attributes;
|
||||
const conversation = window.ConversationController.get(identifier);
|
||||
return {
|
||||
text: window.i18n('icu:safetyNumberChangedGroup', {
|
||||
name: conversation ? conversation.getTitle() : '',
|
||||
}),
|
||||
};
|
||||
}
|
||||
const { contact: contacts } = attributes;
|
||||
if (contacts && contacts.length) {
|
||||
return {
|
||||
text:
|
||||
EmbeddedContact.getName(contacts[0]) ||
|
||||
window.i18n('icu:unknownContact'),
|
||||
emoji: '👤',
|
||||
};
|
||||
}
|
||||
|
||||
const { giftBadge } = attributes;
|
||||
if (giftBadge) {
|
||||
const emoji = '✨';
|
||||
|
||||
if (isOutgoing(attributes)) {
|
||||
const toContact = window.ConversationController.get(
|
||||
attributes.conversationId
|
||||
);
|
||||
const recipient =
|
||||
toContact?.getTitle() ?? window.i18n('icu:unknownContact');
|
||||
return {
|
||||
emoji,
|
||||
text: window.i18n('icu:message--donation--preview--sent', {
|
||||
recipient,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const fromContact = getContact(attributes);
|
||||
const sender = fromContact?.getTitle() ?? window.i18n('icu:unknownContact');
|
||||
return {
|
||||
emoji,
|
||||
text:
|
||||
giftBadge.state === GiftBadgeStates.Unopened
|
||||
? window.i18n('icu:message--donation--preview--unopened', {
|
||||
sender,
|
||||
})
|
||||
: window.i18n('icu:message--donation--preview--redeemed'),
|
||||
};
|
||||
}
|
||||
|
||||
if (body) {
|
||||
return {
|
||||
text: body,
|
||||
bodyRanges,
|
||||
};
|
||||
}
|
||||
|
||||
return { text: '' };
|
||||
}
|
88
ts/util/getNotificationTextForMessage.ts
Normal file
88
ts/util/getNotificationTextForMessage.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import { BodyRange, applyRangesForText } from '../types/BodyRange';
|
||||
import { extractHydratedMentions } from '../state/selectors/message';
|
||||
import { findAndFormatContact } from './findAndFormatContact';
|
||||
import { getNotificationDataForMessage } from './getNotificationDataForMessage';
|
||||
import { isConversationAccepted } from './isConversationAccepted';
|
||||
import { strictAssert } from './assert';
|
||||
|
||||
export function getNotificationTextForMessage(
|
||||
attributes: MessageAttributesType
|
||||
): string {
|
||||
const { text, emoji } = getNotificationDataForMessage(attributes);
|
||||
|
||||
const conversation = window.ConversationController.get(
|
||||
attributes.conversationId
|
||||
);
|
||||
|
||||
strictAssert(
|
||||
conversation != null,
|
||||
'Conversation not found in ConversationController'
|
||||
);
|
||||
|
||||
if (!isConversationAccepted(conversation.attributes)) {
|
||||
return window.i18n('icu:message--getNotificationText--messageRequest');
|
||||
}
|
||||
|
||||
if (attributes.storyReaction) {
|
||||
if (attributes.type === 'outgoing') {
|
||||
const { profileName: name } = conversation.attributes;
|
||||
|
||||
if (!name) {
|
||||
return window.i18n(
|
||||
'icu:Quote__story-reaction-notification--outgoing--nameless',
|
||||
{
|
||||
emoji: attributes.storyReaction.emoji,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return window.i18n('icu:Quote__story-reaction-notification--outgoing', {
|
||||
emoji: attributes.storyReaction.emoji,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
if (
|
||||
attributes.type === 'incoming' &&
|
||||
attributes.storyReaction.targetAuthorAci === ourAci
|
||||
) {
|
||||
return window.i18n('icu:Quote__story-reaction-notification--incoming', {
|
||||
emoji: attributes.storyReaction.emoji,
|
||||
});
|
||||
}
|
||||
|
||||
if (!window.Signal.OS.isLinux()) {
|
||||
return attributes.storyReaction.emoji;
|
||||
}
|
||||
|
||||
return window.i18n('icu:Quote__story-reaction--single');
|
||||
}
|
||||
|
||||
const mentions =
|
||||
extractHydratedMentions(attributes, {
|
||||
conversationSelector: findAndFormatContact,
|
||||
}) || [];
|
||||
const spoilers = (attributes.bodyRanges || []).filter(
|
||||
range =>
|
||||
BodyRange.isFormatting(range) && range.style === BodyRange.Style.SPOILER
|
||||
) as Array<BodyRange<BodyRange.Formatting>>;
|
||||
const modifiedText = applyRangesForText({ text, mentions, spoilers });
|
||||
|
||||
// Linux emoji support is mixed, so we disable it. (Note that this doesn't touch
|
||||
// the `text`, which can contain emoji.)
|
||||
const shouldIncludeEmoji = Boolean(emoji) && !window.Signal.OS.isLinux();
|
||||
if (shouldIncludeEmoji) {
|
||||
return window.i18n('icu:message--getNotificationText--text-with-emoji', {
|
||||
text: modifiedText,
|
||||
emoji,
|
||||
});
|
||||
}
|
||||
|
||||
return modifiedText || '';
|
||||
}
|
23
ts/util/getSenderIdentifier.ts
Normal file
23
ts/util/getSenderIdentifier.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
|
||||
export function getSenderIdentifier({
|
||||
sent_at: sentAt,
|
||||
source,
|
||||
sourceServiceId,
|
||||
sourceDevice,
|
||||
}: Pick<
|
||||
MessageAttributesType,
|
||||
'sent_at' | 'source' | 'sourceServiceId' | 'sourceDevice'
|
||||
>): string {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const conversation = window.ConversationController.lookupOrCreate({
|
||||
e164: source,
|
||||
serviceId: sourceServiceId,
|
||||
reason: 'MessageModel.getSenderIdentifier',
|
||||
})!;
|
||||
|
||||
return `${conversation?.id}.${sourceDevice}-${sentAt}`;
|
||||
}
|
@@ -89,9 +89,10 @@ export async function handleEditMessage(
|
||||
return;
|
||||
}
|
||||
|
||||
const mainMessageModel = window.MessageController.register(
|
||||
const mainMessageModel = window.MessageCache.__DEPRECATED$register(
|
||||
mainMessage.id,
|
||||
mainMessage
|
||||
mainMessage,
|
||||
'handleEditMessage'
|
||||
);
|
||||
|
||||
// Pull out the edit history from the main message. If this is the first edit
|
||||
|
89
ts/util/hydrateStoryContext.ts
Normal file
89
ts/util/hydrateStoryContext.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import omit from 'lodash/omit';
|
||||
import type { AttachmentType } from '../types/Attachment';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import { getAttachmentsForMessage } from '../state/selectors/message';
|
||||
import { isAciString } from './isAciString';
|
||||
import { isDirectConversation } from './whatTypeOfConversation';
|
||||
import { softAssert, strictAssert } from './assert';
|
||||
|
||||
export async function hydrateStoryContext(
|
||||
messageId: string,
|
||||
storyMessageParam?: MessageAttributesType,
|
||||
{
|
||||
shouldSave,
|
||||
}: {
|
||||
shouldSave?: boolean;
|
||||
} = {}
|
||||
): Promise<void> {
|
||||
const messageAttributes = await window.MessageCache.resolveAttributes(
|
||||
'hydrateStoryContext',
|
||||
messageId
|
||||
);
|
||||
|
||||
const { storyId } = messageAttributes;
|
||||
if (!storyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { storyReplyContext: context } = messageAttributes;
|
||||
// We'll continue trying to get the attachment as long as the message still exists
|
||||
if (context && (context.attachment?.url || !context.messageId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const storyMessage =
|
||||
storyMessageParam === undefined
|
||||
? await window.MessageCache.resolveAttributes(
|
||||
'hydrateStoryContext/story',
|
||||
storyId
|
||||
)
|
||||
: window.MessageCache.toMessageAttributes(storyMessageParam);
|
||||
|
||||
if (!storyMessage) {
|
||||
const conversation = window.ConversationController.get(
|
||||
messageAttributes.conversationId
|
||||
);
|
||||
softAssert(
|
||||
conversation && isDirectConversation(conversation.attributes),
|
||||
'hydrateStoryContext: Not a type=direct conversation'
|
||||
);
|
||||
window.MessageCache.setAttributes({
|
||||
messageId,
|
||||
messageAttributes: {
|
||||
storyReplyContext: {
|
||||
attachment: undefined,
|
||||
// This is ok to do because story replies only show in 1:1 conversations
|
||||
// so the story that was quoted should be from the same conversation.
|
||||
authorAci: conversation?.getAci(),
|
||||
// No messageId = referenced story not found
|
||||
messageId: '',
|
||||
},
|
||||
},
|
||||
skipSaveToDatabase: !shouldSave,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const attachments = getAttachmentsForMessage({ ...storyMessage });
|
||||
let attachment: AttachmentType | undefined = attachments?.[0];
|
||||
if (attachment && !attachment.url && !attachment.textAttachment) {
|
||||
attachment = undefined;
|
||||
}
|
||||
|
||||
const { sourceServiceId: authorAci } = storyMessage;
|
||||
strictAssert(isAciString(authorAci), 'Story message from pni');
|
||||
window.MessageCache.setAttributes({
|
||||
messageId,
|
||||
messageAttributes: {
|
||||
storyReplyContext: {
|
||||
attachment: omit(attachment, 'screenshotData'),
|
||||
authorAci,
|
||||
messageId: storyMessage.id,
|
||||
},
|
||||
},
|
||||
skipSaveToDatabase: !shouldSave,
|
||||
});
|
||||
}
|
@@ -1351,27 +1351,6 @@
|
||||
"updated": "2020-10-13T18:36:57.012Z",
|
||||
"reasonDetail": "necessary for quill"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/quill/dist/quill.js",
|
||||
"line": " // this.container.innerHTML = html.replace(/\\>\\r?\\n +\\</g, '><'); // Remove spaces between tags",
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2023-05-17T01:41:49.734Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/quill/dist/quill.js",
|
||||
"line": " // this.container.innerHTML = '';",
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2023-05-17T01:41:49.734Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/quill/dist/quill.js",
|
||||
"line": " // this.container.innerHTML = '';",
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2023-05-17T01:41:49.734Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/quill/dist/quill.js",
|
||||
@@ -1468,6 +1447,27 @@
|
||||
"updated": "2020-10-13T18:36:57.012Z",
|
||||
"reasonDetail": "necessary for quill"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/quill/dist/quill.js",
|
||||
"line": " // this.container.innerHTML = html.replace(/\\>\\r?\\n +\\</g, '><'); // Remove spaces between tags",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2023-09-28T00:50:24.377Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/quill/dist/quill.js",
|
||||
"line": " // this.container.innerHTML = '';",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2023-09-28T00:50:24.377Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/quill/dist/quill.js",
|
||||
"line": " // this.container.innerHTML = '';",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2023-09-28T00:50:24.377Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/quill/dist/quill.min.js",
|
||||
|
@@ -102,7 +102,9 @@ export async function markConversationRead(
|
||||
|
||||
const allReadMessagesSync = allUnreadMessages
|
||||
.map(messageSyncData => {
|
||||
const message = window.MessageController.getById(messageSyncData.id);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(
|
||||
messageSyncData.id
|
||||
);
|
||||
// we update the in-memory MessageModel with the fresh database call data
|
||||
if (message) {
|
||||
message.set(omit(messageSyncData, 'originalReadStatus'));
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as log from '../logging/log';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||
import { isNotNil } from './isNotNil';
|
||||
import { DurationInSeconds } from './durations';
|
||||
import { markViewed } from '../services/MessageUpdater';
|
||||
@@ -19,7 +19,7 @@ export async function markOnboardingStoryAsRead(): Promise<boolean> {
|
||||
}
|
||||
|
||||
const messages = await Promise.all(
|
||||
existingOnboardingStoryMessageIds.map(getMessageById)
|
||||
existingOnboardingStoryMessageIds.map(__DEPRECATED$getMessageById)
|
||||
);
|
||||
|
||||
const storyReadDate = Date.now();
|
||||
|
@@ -161,7 +161,11 @@ export async function onStoryRecipientUpdate(
|
||||
return true;
|
||||
}
|
||||
|
||||
const message = window.MessageController.register(item.id, item);
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
item.id,
|
||||
item,
|
||||
'onStoryRecipientUpdate'
|
||||
);
|
||||
|
||||
const sendStateConversationIds = new Set(
|
||||
Object.keys(nextSendStateByConversationId)
|
||||
|
@@ -30,7 +30,7 @@ import type { StickerType } from '../types/Stickers';
|
||||
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
||||
import { isNotNil } from './isNotNil';
|
||||
|
||||
type ReturnType = {
|
||||
export type MessageAttachmentsDownloadedType = {
|
||||
bodyAttachment?: AttachmentType;
|
||||
attachments: Array<AttachmentType>;
|
||||
editHistory?: Array<EditHistoryType>;
|
||||
@@ -45,7 +45,7 @@ type ReturnType = {
|
||||
// count then you'll also have to modify ./hasAttachmentsDownloads
|
||||
export async function queueAttachmentDownloads(
|
||||
message: MessageAttributesType
|
||||
): Promise<ReturnType | undefined> {
|
||||
): Promise<MessageAttachmentsDownloadedType | undefined> {
|
||||
const attachmentsToQueue = message.attachments || [];
|
||||
const messageId = message.id;
|
||||
const idForLogging = getMessageIdForLogging(message);
|
||||
|
@@ -15,7 +15,7 @@ import {
|
||||
getConversationIdForLogging,
|
||||
getMessageIdForLogging,
|
||||
} from './idForLogging';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||
import { getRecipientConversationIds } from './getRecipientConversationIds';
|
||||
import { getRecipients } from './getRecipients';
|
||||
import { repeat, zipObject } from './iterables';
|
||||
@@ -35,7 +35,7 @@ export async function sendDeleteForEveryoneMessage(
|
||||
timestamp: targetTimestamp,
|
||||
id: messageId,
|
||||
} = options;
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error('sendDeleteForEveryoneMessage: Cannot find message!');
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@ import {
|
||||
import { concat, filter, map, repeat, zipObject, find } from './iterables';
|
||||
import { getConversationIdForLogging } from './idForLogging';
|
||||
import { isQuoteAMatch } from '../messages/helpers';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||
import { handleEditMessage } from './handleEditMessage';
|
||||
import { incrementMessageCounter } from './incrementMessageCounter';
|
||||
import { isGroupV1 } from './whatTypeOfConversation';
|
||||
@@ -64,7 +64,7 @@ export async function sendEditedMessage(
|
||||
conversation.attributes
|
||||
)})`;
|
||||
|
||||
const targetMessage = await getMessageById(targetMessageId);
|
||||
const targetMessage = await __DEPRECATED$getMessageById(targetMessageId);
|
||||
strictAssert(targetMessage, 'could not find message to edit');
|
||||
|
||||
if (isGroupV1(conversation.attributes)) {
|
||||
|
@@ -311,7 +311,11 @@ export async function sendStoryMessage(
|
||||
await Promise.all(
|
||||
distributionListMessages.map(messageAttributes => {
|
||||
const model = new window.Whisper.Message(messageAttributes);
|
||||
const message = window.MessageController.register(model.id, model);
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
model.id,
|
||||
model,
|
||||
'sendStoryMessage'
|
||||
);
|
||||
|
||||
void ourConversation.addSingleMessage(model, { isJustSent: true });
|
||||
|
||||
@@ -361,7 +365,11 @@ export async function sendStoryMessage(
|
||||
},
|
||||
async jobToInsert => {
|
||||
const model = new window.Whisper.Message(messageAttributes);
|
||||
const message = window.MessageController.register(model.id, model);
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
model.id,
|
||||
model,
|
||||
'sendStoryMessage'
|
||||
);
|
||||
|
||||
const conversation = message.getConversation();
|
||||
void conversation?.addSingleMessage(model, { isJustSent: true });
|
||||
|
@@ -2,22 +2,24 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ConversationModel } from '../models/conversations';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import * as log from '../logging/log';
|
||||
import dataInterface from '../sql/Client';
|
||||
import { isGroup } from './whatTypeOfConversation';
|
||||
import { isMessageUnread } from './isMessageUnread';
|
||||
|
||||
export async function shouldReplyNotifyUser(
|
||||
message: MessageModel,
|
||||
messageAttributes: Readonly<
|
||||
Pick<MessageAttributesType, 'readStatus' | 'storyId'>
|
||||
>,
|
||||
conversation: ConversationModel
|
||||
): Promise<boolean> {
|
||||
// Don't notify if the message has already been read
|
||||
if (!isMessageUnread(message.attributes)) {
|
||||
if (!isMessageUnread(messageAttributes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const storyId = message.get('storyId');
|
||||
const { storyId } = messageAttributes;
|
||||
|
||||
// If this is not a reply to a story, always notify.
|
||||
if (storyId == null) {
|
||||
|
Reference in New Issue
Block a user