Replace MessageController with MessageCache
This commit is contained in:
parent
ba1a8aad09
commit
7d35216fda
73 changed files with 2237 additions and 1229 deletions
10
.eslintrc.js
10
.eslintrc.js
|
@ -20,6 +20,16 @@ const rules = {
|
|||
'brace-style': ['error', '1tbs', { allowSingleLine: false }],
|
||||
curly: ['error', 'all'],
|
||||
|
||||
// Immer support
|
||||
'no-param-reassign': [
|
||||
'error',
|
||||
{
|
||||
props: true,
|
||||
ignorePropertyModificationsForRegex: ['^draft'],
|
||||
ignorePropertyModificationsFor: ['acc', 'ctx', 'context'],
|
||||
},
|
||||
],
|
||||
|
||||
// Always use === and !== except when directly comparing to null
|
||||
// (which only will equal null or undefined)
|
||||
eqeqeq: ['error', 'always', { null: 'never' }],
|
||||
|
|
|
@ -776,8 +776,9 @@ async function createWindow() {
|
|||
}
|
||||
|
||||
const startInTray =
|
||||
isTestEnvironment(getEnvironment()) ||
|
||||
(await systemTraySettingCache.get()) ===
|
||||
SystemTraySetting.MinimizeToAndStartInSystemTray;
|
||||
SystemTraySetting.MinimizeToAndStartInSystemTray;
|
||||
|
||||
const visibleOnAnyScreen = some(screen.getAllDisplays(), display => {
|
||||
if (
|
||||
|
@ -2882,6 +2883,10 @@ async function showStickerCreatorWindow() {
|
|||
}
|
||||
|
||||
if (isTestEnvironment(getEnvironment())) {
|
||||
ipc.handle('ci:test-electron:debug', async (_event, info) => {
|
||||
process.stdout.write(`ci:test-electron:debug=${JSON.stringify(info)}\n`);
|
||||
});
|
||||
|
||||
ipc.handle('ci:test-electron:done', async (_event, info) => {
|
||||
if (!process.env.TEST_QUIT_ON_COMPLETE) {
|
||||
return;
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright 2018 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// For reference: https://github.com/airbnb/javascript
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
mocha: true,
|
||||
browser: true,
|
||||
},
|
||||
|
||||
globals: {
|
||||
check: true,
|
||||
gen: true,
|
||||
},
|
||||
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
|
||||
rules: {
|
||||
// We still get the value of this rule, it just allows for dev deps
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
},
|
||||
],
|
||||
|
||||
// We want to keep each test structured the same, even if its contents are tiny
|
||||
'arrow-body-style': 'off',
|
||||
},
|
||||
};
|
13
test/test.js
13
test/test.js
|
@ -1,8 +1,6 @@
|
|||
// Copyright 2014 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* global Whisper, _, Backbone */
|
||||
|
||||
/*
|
||||
* global helpers for tests
|
||||
*/
|
||||
|
@ -18,20 +16,21 @@ function deleteIndexedDB() {
|
|||
});
|
||||
}
|
||||
|
||||
window.Events = {
|
||||
getThemeSetting: () => 'light',
|
||||
};
|
||||
|
||||
/* Delete the database before running any tests */
|
||||
before(async () => {
|
||||
window.testUtilities.installMessageController();
|
||||
|
||||
await window.testUtilities.initialize();
|
||||
await deleteIndexedDB();
|
||||
await window.testUtilities.initializeMessageCounter();
|
||||
await window.Signal.Data.removeAll();
|
||||
await window.storage.fetch();
|
||||
});
|
||||
|
||||
window.textsecure.storage.protocol = window.getSignalProtocolStore();
|
||||
|
||||
window.testUtilities.prepareTests();
|
||||
delete window.testUtilities.prepareTests;
|
||||
window.textsecure.storage.protocol = window.getSignalProtocolStore();
|
||||
|
||||
!(function () {
|
||||
const passed = [];
|
||||
|
|
7
ts/CI.ts
7
ts/CI.ts
|
@ -120,7 +120,12 @@ export function getCI(deviceName: string): CIType {
|
|||
[sentAt]
|
||||
);
|
||||
return messages.map(
|
||||
m => window.MessageController.register(m.id, m).attributes
|
||||
m =>
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
m.id,
|
||||
m,
|
||||
'CI.getMessagesBySentAt'
|
||||
).attributes
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import type {
|
|||
ConversationRenderInfoType,
|
||||
} from './model-types.d';
|
||||
import type { ConversationModel } from './models/conversations';
|
||||
import type { MessageModel } from './models/messages';
|
||||
|
||||
import dataInterface from './sql/Client';
|
||||
import * as log from './logging/log';
|
||||
|
@ -1127,13 +1126,11 @@ export class ConversationController {
|
|||
});
|
||||
}
|
||||
|
||||
log.warn(`${logId}: Update cached messages in MessageController`);
|
||||
window.MessageController.update((message: MessageModel) => {
|
||||
if (message.get('conversationId') === obsoleteId) {
|
||||
message.set({ conversationId: currentId });
|
||||
}
|
||||
log.warn(`${logId}: Update cached messages in MessageCache`);
|
||||
window.MessageCache.replaceAllObsoleteConversationIds({
|
||||
conversationId: currentId,
|
||||
obsoleteId,
|
||||
});
|
||||
|
||||
log.warn(`${logId}: Update messages table`);
|
||||
await migrateConversationMessages(obsoleteId, currentId);
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import { webFrame } from 'electron';
|
||||
import { isNumber, throttle, groupBy } from 'lodash';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { render } from 'react-dom';
|
||||
import { batch as batchDispatch } from 'react-redux';
|
||||
import PQueue from 'p-queue';
|
||||
|
@ -108,7 +107,6 @@ import { AppViewType } from './state/ducks/app';
|
|||
import type { BadgesStateType } from './state/ducks/badges';
|
||||
import { areAnyCallsActiveOrRinging } from './state/selectors/calling';
|
||||
import { badgeImageFileDownloader } from './badges/badgeImageFileDownloader';
|
||||
import { actionCreators } from './state/actions';
|
||||
import * as Deletes from './messageModifiers/Deletes';
|
||||
import type { EditAttributesType } from './messageModifiers/Edits';
|
||||
import * as Edits from './messageModifiers/Edits';
|
||||
|
@ -154,7 +152,6 @@ import { startInteractionMode } from './services/InteractionMode';
|
|||
import type { MainWindowStatsType } from './windows/context';
|
||||
import { ReactionSource } from './reactions/ReactionSource';
|
||||
import { singleProtoJobQueue } from './jobs/singleProtoJobQueue';
|
||||
import { getInitialState } from './state/getInitialState';
|
||||
import {
|
||||
conversationJobQueue,
|
||||
conversationQueueJobEnum,
|
||||
|
@ -164,6 +161,7 @@ import MessageSender from './textsecure/SendMessage';
|
|||
import type AccountManager from './textsecure/AccountManager';
|
||||
import { onStoryRecipientUpdate } from './util/onStoryRecipientUpdate';
|
||||
import { flushAttachmentDownloadQueue } from './util/attachmentDownloadQueue';
|
||||
import { initializeRedux } from './state/initializeRedux';
|
||||
import { StartupQueue } from './util/StartupQueue';
|
||||
import { showConfirmationDialog } from './util/showConfirmationDialog';
|
||||
import { onCallEventSync } from './util/onCallEventSync';
|
||||
|
@ -1151,7 +1149,7 @@ export async function startApp(): Promise<void> {
|
|||
Errors.toLogFormat(error)
|
||||
);
|
||||
} finally {
|
||||
initializeRedux({ mainWindowStats, menuOptions });
|
||||
setupAppState({ mainWindowStats, menuOptions });
|
||||
drop(start());
|
||||
window.Signal.Services.initializeNetworkObserver(
|
||||
window.reduxActions.network
|
||||
|
@ -1173,89 +1171,24 @@ export async function startApp(): Promise<void> {
|
|||
}
|
||||
});
|
||||
|
||||
function initializeRedux({
|
||||
function setupAppState({
|
||||
mainWindowStats,
|
||||
menuOptions,
|
||||
}: {
|
||||
mainWindowStats: MainWindowStatsType;
|
||||
menuOptions: MenuOptionsType;
|
||||
}) {
|
||||
// Here we set up a full redux store with initial state for our LeftPane Root
|
||||
const convoCollection = window.getConversations();
|
||||
const initialState = getInitialState({
|
||||
badges: initialBadgesState,
|
||||
initializeRedux({
|
||||
callsHistory: getCallsHistoryForRedux(),
|
||||
initialBadgesState,
|
||||
mainWindowStats,
|
||||
menuOptions,
|
||||
stories: getStoriesForRedux(),
|
||||
storyDistributionLists: getDistributionListsForRedux(),
|
||||
callsHistory: getCallsHistoryForRedux(),
|
||||
});
|
||||
|
||||
const store = window.Signal.State.createStore(initialState);
|
||||
window.reduxStore = store;
|
||||
|
||||
// Binding these actions to our redux store and exposing them allows us to update
|
||||
// redux when things change in the backbone world.
|
||||
window.reduxActions = {
|
||||
accounts: bindActionCreators(actionCreators.accounts, store.dispatch),
|
||||
app: bindActionCreators(actionCreators.app, store.dispatch),
|
||||
audioPlayer: bindActionCreators(
|
||||
actionCreators.audioPlayer,
|
||||
store.dispatch
|
||||
),
|
||||
audioRecorder: bindActionCreators(
|
||||
actionCreators.audioRecorder,
|
||||
store.dispatch
|
||||
),
|
||||
badges: bindActionCreators(actionCreators.badges, store.dispatch),
|
||||
callHistory: bindActionCreators(
|
||||
actionCreators.callHistory,
|
||||
store.dispatch
|
||||
),
|
||||
calling: bindActionCreators(actionCreators.calling, store.dispatch),
|
||||
composer: bindActionCreators(actionCreators.composer, store.dispatch),
|
||||
conversations: bindActionCreators(
|
||||
actionCreators.conversations,
|
||||
store.dispatch
|
||||
),
|
||||
crashReports: bindActionCreators(
|
||||
actionCreators.crashReports,
|
||||
store.dispatch
|
||||
),
|
||||
inbox: bindActionCreators(actionCreators.inbox, store.dispatch),
|
||||
emojis: bindActionCreators(actionCreators.emojis, store.dispatch),
|
||||
expiration: bindActionCreators(actionCreators.expiration, store.dispatch),
|
||||
globalModals: bindActionCreators(
|
||||
actionCreators.globalModals,
|
||||
store.dispatch
|
||||
),
|
||||
items: bindActionCreators(actionCreators.items, store.dispatch),
|
||||
lightbox: bindActionCreators(actionCreators.lightbox, store.dispatch),
|
||||
linkPreviews: bindActionCreators(
|
||||
actionCreators.linkPreviews,
|
||||
store.dispatch
|
||||
),
|
||||
mediaGallery: bindActionCreators(
|
||||
actionCreators.mediaGallery,
|
||||
store.dispatch
|
||||
),
|
||||
network: bindActionCreators(actionCreators.network, store.dispatch),
|
||||
safetyNumber: bindActionCreators(
|
||||
actionCreators.safetyNumber,
|
||||
store.dispatch
|
||||
),
|
||||
search: bindActionCreators(actionCreators.search, store.dispatch),
|
||||
stickers: bindActionCreators(actionCreators.stickers, store.dispatch),
|
||||
stories: bindActionCreators(actionCreators.stories, store.dispatch),
|
||||
storyDistributionLists: bindActionCreators(
|
||||
actionCreators.storyDistributionLists,
|
||||
store.dispatch
|
||||
),
|
||||
toast: bindActionCreators(actionCreators.toast, store.dispatch),
|
||||
updates: bindActionCreators(actionCreators.updates, store.dispatch),
|
||||
user: bindActionCreators(actionCreators.user, store.dispatch),
|
||||
username: bindActionCreators(actionCreators.username, store.dispatch),
|
||||
};
|
||||
// Here we set up a full redux store with initial state for our LeftPane Root
|
||||
const convoCollection = window.getConversations();
|
||||
|
||||
const {
|
||||
conversationAdded,
|
||||
|
|
18
ts/groups.ts
18
ts/groups.ts
|
@ -2008,8 +2008,12 @@ export async function createGroupV2(
|
|||
forceSave: true,
|
||||
ourAci,
|
||||
});
|
||||
const model = new window.Whisper.Message(createdTheGroupMessage);
|
||||
window.MessageController.register(model.id, model);
|
||||
let model = new window.Whisper.Message(createdTheGroupMessage);
|
||||
model = window.MessageCache.__DEPRECATED$register(
|
||||
model.id,
|
||||
model,
|
||||
'createGroupV2'
|
||||
);
|
||||
conversation.trigger('newmessage', model);
|
||||
|
||||
if (expireTimer) {
|
||||
|
@ -3371,7 +3375,7 @@ async function appendChangeMessages(
|
|||
|
||||
let newMessages = 0;
|
||||
for (const changeMessage of mergedMessages) {
|
||||
const existing = window.MessageController.getById(changeMessage.id);
|
||||
const existing = window.MessageCache.__DEPRECATED$getById(changeMessage.id);
|
||||
|
||||
// Update existing message
|
||||
if (existing) {
|
||||
|
@ -3383,8 +3387,12 @@ async function appendChangeMessages(
|
|||
continue;
|
||||
}
|
||||
|
||||
const model = new window.Whisper.Message(changeMessage);
|
||||
window.MessageController.register(model.id, model);
|
||||
let model = new window.Whisper.Message(changeMessage);
|
||||
model = window.MessageCache.__DEPRECATED$register(
|
||||
model.id,
|
||||
model,
|
||||
'appendChangeMessages'
|
||||
);
|
||||
conversation.trigger('newmessage', model);
|
||||
newMessages += 1;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import { getUntrustedConversationServiceIds } from './getUntrustedConversationSe
|
|||
import { handleMessageSend } from '../../util/handleMessageSend';
|
||||
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import { isNotNil } from '../../util/isNotNil';
|
||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||
import type { MessageModel } from '../../models/messages';
|
||||
|
@ -59,7 +59,7 @@ export async function sendDeleteForEveryone(
|
|||
|
||||
const logId = `sendDeleteForEveryone(${conversation.idForLogging()}, ${messageId})`;
|
||||
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
log.error(`${logId}: Failed to fetch message. Failing job.`);
|
||||
return;
|
||||
|
|
|
@ -20,7 +20,7 @@ import { getUntrustedConversationServiceIds } from './getUntrustedConversationSe
|
|||
import { handleMessageSend } from '../../util/handleMessageSend';
|
||||
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import { isNotNil } from '../../util/isNotNil';
|
||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||
import type { MessageModel } from '../../models/messages';
|
||||
|
@ -45,7 +45,7 @@ export async function sendDeleteStoryForEveryone(
|
|||
|
||||
const logId = `sendDeleteStoryForEveryone(${storyId})`;
|
||||
|
||||
const message = await getMessageById(storyId);
|
||||
const message = await __DEPRECATED$getMessageById(storyId);
|
||||
if (!message) {
|
||||
log.error(`${logId}: Failed to fetch message. Failing job.`);
|
||||
return;
|
||||
|
|
|
@ -7,7 +7,7 @@ import PQueue from 'p-queue';
|
|||
import * as Errors from '../../types/errors';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import type { MessageModel } from '../../models/messages';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import type { ConversationModel } from '../../models/conversations';
|
||||
import { isGroup, isGroupV2, isMe } from '../../util/whatTypeOfConversation';
|
||||
import { getSendOptions } from '../../util/getSendOptions';
|
||||
|
@ -72,7 +72,7 @@ export async function sendNormalMessage(
|
|||
const { Message } = window.Signal.Types;
|
||||
|
||||
const { messageId, revision, editedMessageTimestamp } = data;
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
log.info(
|
||||
`message ${messageId} was not found, maybe because it was deleted. Giving up on sending it`
|
||||
|
@ -551,7 +551,7 @@ async function getMessageSendData({
|
|||
uploadMessagePreviews(message, uploadQueue),
|
||||
uploadMessageQuote(message, uploadQueue),
|
||||
uploadMessageSticker(message, uploadQueue),
|
||||
storyId ? getMessageById(storyId) : undefined,
|
||||
storyId ? __DEPRECATED$getMessageById(storyId) : undefined,
|
||||
]);
|
||||
|
||||
// Save message after uploading attachments
|
||||
|
|
|
@ -14,7 +14,7 @@ import type { ConversationModel } from '../../models/conversations';
|
|||
|
||||
import * as reactionUtil from '../../reactions/util';
|
||||
import { isSent, SendStatus } from '../../messages/MessageSendState';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import { isIncoming } from '../../messages/helpers';
|
||||
import {
|
||||
isMe,
|
||||
|
@ -60,7 +60,7 @@ export async function sendReaction(
|
|||
const ourConversationId =
|
||||
window.ConversationController.getOurConversationIdOrThrow();
|
||||
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
log.info(
|
||||
`message ${messageId} was not found, maybe because it was deleted. Giving up on sending its reactions`
|
||||
|
@ -334,7 +334,11 @@ export async function sendReaction(
|
|||
});
|
||||
|
||||
void conversation.addSingleMessage(
|
||||
window.MessageController.register(reactionMessage.id, reactionMessage)
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
reactionMessage.id,
|
||||
reactionMessage,
|
||||
'sendReaction'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -391,7 +391,7 @@ async function _getMessageById(
|
|||
id: string,
|
||||
messageId: string
|
||||
): Promise<MessageModel | undefined> {
|
||||
const message = window.MessageController.getById(messageId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||
|
||||
if (message) {
|
||||
return message;
|
||||
|
@ -408,7 +408,11 @@ async function _getMessageById(
|
|||
}
|
||||
|
||||
strictAssert(messageId === messageAttributes.id, 'message id mismatch');
|
||||
return window.MessageController.register(messageId, messageAttributes);
|
||||
return window.MessageCache.__DEPRECATED$register(
|
||||
messageId,
|
||||
messageAttributes,
|
||||
'AttachmentDownloads._getMessageById'
|
||||
);
|
||||
}
|
||||
|
||||
async function _finishJob(
|
||||
|
|
|
@ -85,9 +85,10 @@ export async function onDelete(del: DeleteAttributesType): Promise<void> {
|
|||
return;
|
||||
}
|
||||
|
||||
const message = window.MessageController.register(
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
targetMessage.id,
|
||||
targetMessage
|
||||
targetMessage,
|
||||
'Deletes.onDelete'
|
||||
);
|
||||
|
||||
await deleteForEveryone(message, del);
|
||||
|
|
|
@ -106,9 +106,10 @@ export async function onEdit(edit: EditAttributesType): Promise<void> {
|
|||
return;
|
||||
}
|
||||
|
||||
const message = window.MessageController.register(
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
targetMessage.id,
|
||||
targetMessage
|
||||
targetMessage,
|
||||
'Edits.onEdit'
|
||||
);
|
||||
|
||||
await handleEditMessage(message.attributes, edit);
|
||||
|
|
|
@ -95,7 +95,11 @@ async function getTargetMessage(
|
|||
(isOutgoing(item) || isStory(item)) && sourceId === item.conversationId
|
||||
);
|
||||
if (message) {
|
||||
return window.MessageController.register(message.id, message);
|
||||
return window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
message,
|
||||
'MessageReceipts.getTargetMessage 1'
|
||||
);
|
||||
}
|
||||
|
||||
const groups = await window.Signal.Data.getAllGroupsInvolvingServiceId(
|
||||
|
@ -113,7 +117,11 @@ async function getTargetMessage(
|
|||
return null;
|
||||
}
|
||||
|
||||
return window.MessageController.register(target.id, target);
|
||||
return window.MessageCache.__DEPRECATED$register(
|
||||
target.id,
|
||||
target,
|
||||
'MessageReceipts.getTargetMessage 2'
|
||||
);
|
||||
}
|
||||
|
||||
const wasDeliveredWithSealedSender = (
|
||||
|
@ -376,7 +384,11 @@ export async function onReceipt(
|
|||
|
||||
await Promise.all(
|
||||
targetMessages.map(msg => {
|
||||
const model = window.MessageController.register(msg.id, msg);
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
msg.id,
|
||||
msg,
|
||||
'MessageReceipts.onReceipt'
|
||||
);
|
||||
return updateMessageSendState(receipt, model);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -196,9 +196,10 @@ export async function onReaction(
|
|||
return;
|
||||
}
|
||||
|
||||
const message = window.MessageController.register(
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
targetMessage.id,
|
||||
targetMessage
|
||||
targetMessage,
|
||||
'Reactions.onReaction'
|
||||
);
|
||||
|
||||
// Use the generated message in ts/background.ts to create a message
|
||||
|
|
|
@ -125,7 +125,11 @@ export async function onSync(sync: ReadSyncAttributesType): Promise<void> {
|
|||
|
||||
notificationService.removeBy({ messageId: found.id });
|
||||
|
||||
const message = window.MessageController.register(found.id, found);
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
found.id,
|
||||
found,
|
||||
'ReadSyncs.onSync'
|
||||
);
|
||||
const readAt = Math.min(sync.readAt, Date.now());
|
||||
const newestSentAt = sync.timestamp;
|
||||
|
||||
|
|
|
@ -97,7 +97,11 @@ export async function onSync(
|
|||
return;
|
||||
}
|
||||
|
||||
const message = window.MessageController.register(found.id, found);
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
found.id,
|
||||
found,
|
||||
'ViewOnceOpenSyncs.onSync'
|
||||
);
|
||||
await message.markViewOnceMessageViewed({ fromSync: true });
|
||||
|
||||
viewOnceSyncs.delete(sync.timestamp);
|
||||
|
|
|
@ -99,7 +99,11 @@ export async function onSync(sync: ViewSyncAttributesType): Promise<void> {
|
|||
|
||||
notificationService.removeBy({ messageId: found.id });
|
||||
|
||||
const message = window.MessageController.register(found.id, found);
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
found.id,
|
||||
found,
|
||||
'ViewSyncs.onSync'
|
||||
);
|
||||
let didChangeMessage = false;
|
||||
|
||||
if (message.get('readStatus') !== ReadStatus.Viewed) {
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
import * as log from '../logging/log';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import * as Errors from '../types/errors';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
|
||||
export async function getMessageById(
|
||||
export async function __DEPRECATED$getMessageById(
|
||||
messageId: string
|
||||
): Promise<MessageModel | undefined> {
|
||||
const message = window.MessageController.getById(messageId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
|
@ -28,5 +28,9 @@ export async function getMessageById(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
return window.MessageController.register(found.id, found);
|
||||
return window.MessageCache.__DEPRECATED$register(
|
||||
found.id,
|
||||
found,
|
||||
'__DEPRECATED$getMessageById'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ export async function getMessagesById(
|
|||
const messageIdsToLookUpInDatabase: Array<string> = [];
|
||||
|
||||
for (const messageId of messageIds) {
|
||||
const message = window.MessageController.getById(messageId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||
if (message) {
|
||||
messagesFromMemory.push(message);
|
||||
} else {
|
||||
|
@ -39,7 +39,11 @@ export async function getMessagesById(
|
|||
// We use `window.Whisper.Message` instead of `MessageModel` here to avoid a circular
|
||||
// import.
|
||||
const message = new window.Whisper.Message(rawMessage);
|
||||
return window.MessageController.register(message.id, message);
|
||||
return window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
message,
|
||||
'getMessagesById'
|
||||
);
|
||||
});
|
||||
|
||||
return [...messagesFromMemory, ...messagesFromDatabase];
|
||||
|
|
5
ts/model-types.d.ts
vendored
5
ts/model-types.d.ts
vendored
|
@ -1,15 +1,12 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import * as Backbone from 'backbone';
|
||||
|
||||
import type { GroupV2ChangeType } from './groups';
|
||||
import type { DraftBodyRanges, RawBodyRange } from './types/BodyRange';
|
||||
import type { CustomColorType, ConversationColorType } from './types/Colors';
|
||||
import type { SendMessageChallengeData } from './textsecure/Errors';
|
||||
import type { MessageModel } from './models/messages';
|
||||
import type { ConversationModel } from './models/conversations';
|
||||
import type { ProfileNameChangeType } from './util/getStringForProfileChange';
|
||||
import type { CapabilitiesType } from './textsecure/WebAPI';
|
||||
|
@ -503,5 +500,3 @@ export type ShallowChallengeError = CustomError & {
|
|||
export declare class ConversationModelCollectionType extends Backbone.Collection<ConversationModel> {
|
||||
resetLookups(): void;
|
||||
}
|
||||
|
||||
export declare class MessageModelCollectionType extends Backbone.Collection<MessageModel> {}
|
||||
|
|
|
@ -160,6 +160,7 @@ import { getQuoteAttachment } from '../util/makeQuote';
|
|||
import { deriveProfileKeyVersion } from '../util/zkgroup';
|
||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||
import OS from '../util/os/osMain';
|
||||
import { getMessageAuthorText } from '../util/getMessageAuthorText';
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
window.Whisper = window.Whisper || {};
|
||||
|
@ -1765,7 +1766,13 @@ export class ConversationModel extends window.Backbone
|
|||
): Promise<Array<MessageModel>> {
|
||||
const result = messages
|
||||
.filter(message => Boolean(message.id))
|
||||
.map(message => window.MessageController.register(message.id, message));
|
||||
.map(message =>
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
message,
|
||||
'cleanModels'
|
||||
)
|
||||
);
|
||||
|
||||
const eliminated = messages.length - result.length;
|
||||
if (eliminated > 0) {
|
||||
|
@ -2078,7 +2085,11 @@ export class ConversationModel extends window.Backbone
|
|||
// eslint-disable-next-line no-await-in-loop
|
||||
await Promise.all(
|
||||
readMessages.map(async m => {
|
||||
const registered = window.MessageController.register(m.id, m);
|
||||
const registered = window.MessageCache.__DEPRECATED$register(
|
||||
m.id,
|
||||
m,
|
||||
'handleReadAndDownloadAttachments'
|
||||
);
|
||||
const shouldSave = await registered.queueAttachmentDownloads();
|
||||
if (shouldSave) {
|
||||
await window.Signal.Data.saveMessage(registered.attributes, {
|
||||
|
@ -2824,12 +2835,13 @@ export class ConversationModel extends window.Backbone
|
|||
const id = await window.Signal.Data.saveMessage(message, {
|
||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||
});
|
||||
const model = window.MessageController.register(
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
id,
|
||||
new window.Whisper.Message({
|
||||
...message,
|
||||
id,
|
||||
})
|
||||
}),
|
||||
'addChatSessionRefreshed'
|
||||
);
|
||||
|
||||
this.trigger('newmessage', model);
|
||||
|
@ -2868,12 +2880,13 @@ export class ConversationModel extends window.Backbone
|
|||
const id = await window.Signal.Data.saveMessage(message, {
|
||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||
});
|
||||
const model = window.MessageController.register(
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
id,
|
||||
new window.Whisper.Message({
|
||||
...message,
|
||||
id,
|
||||
})
|
||||
}),
|
||||
'addDeliveryIssue'
|
||||
);
|
||||
|
||||
this.trigger('newmessage', model);
|
||||
|
@ -2922,9 +2935,10 @@ export class ConversationModel extends window.Backbone
|
|||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||
forceSave: true,
|
||||
});
|
||||
const model = window.MessageController.register(
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
new window.Whisper.Message(message)
|
||||
new window.Whisper.Message(message),
|
||||
'addKeyChange'
|
||||
);
|
||||
|
||||
const isUntrusted = await this.isUntrusted();
|
||||
|
@ -3001,12 +3015,13 @@ export class ConversationModel extends window.Backbone
|
|||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||
forceSave: true,
|
||||
});
|
||||
const model = window.MessageController.register(
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
id,
|
||||
new window.Whisper.Message({
|
||||
...message,
|
||||
id,
|
||||
})
|
||||
}),
|
||||
'addConversationMerge'
|
||||
);
|
||||
|
||||
this.trigger('newmessage', model);
|
||||
|
@ -3052,9 +3067,10 @@ export class ConversationModel extends window.Backbone
|
|||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||
forceSave: true,
|
||||
});
|
||||
const model = window.MessageController.register(
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
new window.Whisper.Message(message)
|
||||
new window.Whisper.Message(message),
|
||||
'addVerifiedChange'
|
||||
);
|
||||
|
||||
this.trigger('newmessage', model);
|
||||
|
@ -3093,12 +3109,13 @@ export class ConversationModel extends window.Backbone
|
|||
const id = await window.Signal.Data.saveMessage(message, {
|
||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||
});
|
||||
const model = window.MessageController.register(
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
id,
|
||||
new window.Whisper.Message({
|
||||
...message,
|
||||
id,
|
||||
})
|
||||
}),
|
||||
'addProfileChange'
|
||||
);
|
||||
|
||||
this.trigger('newmessage', model);
|
||||
|
@ -3139,12 +3156,13 @@ export class ConversationModel extends window.Backbone
|
|||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||
}
|
||||
);
|
||||
const model = window.MessageController.register(
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
id,
|
||||
new window.Whisper.Message({
|
||||
...(message as MessageAttributesType),
|
||||
id,
|
||||
})
|
||||
}),
|
||||
'addNotification'
|
||||
);
|
||||
|
||||
this.trigger('newmessage', model);
|
||||
|
@ -3224,7 +3242,7 @@ export class ConversationModel extends window.Backbone
|
|||
`maybeRemoveUniversalTimer(${this.idForLogging()}): removed notification`
|
||||
);
|
||||
|
||||
const message = window.MessageController.getById(notificationId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(notificationId);
|
||||
if (message) {
|
||||
await window.Signal.Data.removeMessage(message.id);
|
||||
}
|
||||
|
@ -3261,7 +3279,7 @@ export class ConversationModel extends window.Backbone
|
|||
`maybeClearContactRemoved(${this.idForLogging()}): removed notification`
|
||||
);
|
||||
|
||||
const message = window.MessageController.getById(notificationId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(notificationId);
|
||||
if (message) {
|
||||
await window.Signal.Data.removeMessage(message.id);
|
||||
}
|
||||
|
@ -3643,7 +3661,7 @@ export class ConversationModel extends window.Backbone
|
|||
draftBodyRanges: [],
|
||||
draftTimestamp: null,
|
||||
quotedMessageId: undefined,
|
||||
lastMessageAuthor: message.getAuthorText(),
|
||||
lastMessageAuthor: getMessageAuthorText(message.attributes),
|
||||
lastMessageBodyRanges: message.get('bodyRanges'),
|
||||
lastMessage:
|
||||
notificationData?.text || message.getNotificationText() || '',
|
||||
|
@ -3795,7 +3813,11 @@ export class ConversationModel extends window.Backbone
|
|||
});
|
||||
|
||||
const model = new window.Whisper.Message(attributes);
|
||||
const message = window.MessageController.register(model.id, model);
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
model.id,
|
||||
model,
|
||||
'enqueueMessageForSend'
|
||||
);
|
||||
message.cachedOutgoingContactData = contact;
|
||||
|
||||
// Attach path to preview images so that sendNormalMessage can use them to
|
||||
|
@ -3985,17 +4007,22 @@ export class ConversationModel extends window.Backbone
|
|||
let previewMessage: MessageModel | undefined;
|
||||
let activityMessage: MessageModel | undefined;
|
||||
|
||||
// Register the message with MessageController so that if it already exists
|
||||
// Register the message with MessageCache so that if it already exists
|
||||
// in memory we use that data instead of the data from the db which may
|
||||
// be out of date.
|
||||
if (preview) {
|
||||
previewMessage = window.MessageController.register(preview.id, preview);
|
||||
previewMessage = window.MessageCache.__DEPRECATED$register(
|
||||
preview.id,
|
||||
preview,
|
||||
'previewMessage'
|
||||
);
|
||||
}
|
||||
|
||||
if (activity) {
|
||||
activityMessage = window.MessageController.register(
|
||||
activityMessage = window.MessageCache.__DEPRECATED$register(
|
||||
activity.id,
|
||||
activity
|
||||
activity,
|
||||
'activityMessage'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4027,7 +4054,7 @@ export class ConversationModel extends window.Backbone
|
|||
notificationData?.text || previewMessage?.getNotificationText() || '',
|
||||
lastMessageBodyRanges: notificationData?.bodyRanges,
|
||||
lastMessagePrefix: notificationData?.emoji,
|
||||
lastMessageAuthor: previewMessage?.getAuthorText(),
|
||||
lastMessageAuthor: getMessageAuthorText(previewMessage?.attributes),
|
||||
lastMessageStatus:
|
||||
(previewMessage
|
||||
? getMessagePropStatus(previewMessage.attributes, ourConversationId)
|
||||
|
@ -4394,7 +4421,11 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
model.set({ id });
|
||||
|
||||
const message = window.MessageController.register(id, model);
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
id,
|
||||
model,
|
||||
'updateExpirationTimer'
|
||||
);
|
||||
|
||||
void this.addSingleMessage(message);
|
||||
void this.updateUnread();
|
||||
|
|
|
@ -27,11 +27,10 @@ import type { DeleteAttributesType } from '../messageModifiers/Deletes';
|
|||
import type { SentEventData } from '../textsecure/messageReceiverEvents';
|
||||
import { isNotNil } from '../util/isNotNil';
|
||||
import { isNormalNumber } from '../util/isNormalNumber';
|
||||
import { softAssert, strictAssert } from '../util/assert';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { hydrateStoryContext } from '../util/hydrateStoryContext';
|
||||
import { drop } from '../util/drop';
|
||||
import { dropNull } from '../util/dropNull';
|
||||
import type { ConversationModel } from './conversations';
|
||||
import { getCallingNotificationText } from '../util/callingNotification';
|
||||
import type {
|
||||
ProcessedDataMessage,
|
||||
ProcessedQuote,
|
||||
|
@ -39,7 +38,6 @@ import type {
|
|||
CallbackResultType,
|
||||
} from '../textsecure/Types.d';
|
||||
import { SendMessageProtoError } from '../textsecure/Errors';
|
||||
import * as expirationTimer from '../util/expirationTimer';
|
||||
import { getUserLanguages } from '../util/userLanguages';
|
||||
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||
import { copyCdnFields } from '../util/attachments';
|
||||
|
@ -49,15 +47,11 @@ import type { ServiceIdString } from '../types/ServiceId';
|
|||
import { normalizeServiceId } from '../types/ServiceId';
|
||||
import { isAciString } from '../util/isAciString';
|
||||
import * as reactionUtil from '../reactions/util';
|
||||
import * as Stickers from '../types/Stickers';
|
||||
import * as Errors from '../types/errors';
|
||||
import * as EmbeddedContact from '../types/EmbeddedContact';
|
||||
import type { AttachmentType } from '../types/Attachment';
|
||||
import { isImage, isVideo } from '../types/Attachment';
|
||||
import * as Attachment from '../types/Attachment';
|
||||
import { stringToMIMEType } from '../types/MIME';
|
||||
import * as MIME from '../types/MIME';
|
||||
import * as GroupChange from '../groupChange';
|
||||
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||
import type { SendStateByConversationId } from '../messages/MessageSendState';
|
||||
import {
|
||||
|
@ -79,12 +73,9 @@ import {
|
|||
} from '../util/whatTypeOfConversation';
|
||||
import { handleMessageSend } from '../util/handleMessageSend';
|
||||
import { getSendOptions } from '../util/getSendOptions';
|
||||
import { findAndFormatContact } from '../util/findAndFormatContact';
|
||||
import { modifyTargetMessage } from '../util/modifyTargetMessage';
|
||||
import {
|
||||
getAttachmentsForMessage,
|
||||
getMessagePropStatus,
|
||||
getPropsForCallHistory,
|
||||
hasErrors,
|
||||
isCallHistory,
|
||||
isChatSessionRefreshed,
|
||||
|
@ -106,14 +97,9 @@ import {
|
|||
isUnsupportedMessage,
|
||||
isVerifiedChange,
|
||||
isConversationMerge,
|
||||
extractHydratedMentions,
|
||||
} from '../state/selectors/message';
|
||||
import {
|
||||
isInCall,
|
||||
getCallSelector,
|
||||
getActiveCall,
|
||||
} from '../state/selectors/calling';
|
||||
import type { ReactionAttributesType } from '../messageModifiers/Reactions';
|
||||
import { isInCall } from '../state/selectors/calling';
|
||||
import { ReactionSource } from '../reactions/ReactionSource';
|
||||
import * as LinkPreview from '../types/LinkPreview';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
|
@ -138,9 +124,7 @@ import {
|
|||
isCustomError,
|
||||
messageHasPaymentEvent,
|
||||
isQuoteAMatch,
|
||||
getPaymentEventNotificationText,
|
||||
} from '../messages/helpers';
|
||||
import type { ReplacementValuesType } from '../types/I18N';
|
||||
import { viewOnceOpenJobQueue } from '../jobs/viewOnceOpenJobQueue';
|
||||
import { getMessageIdForLogging } from '../util/idForLogging';
|
||||
import { hasAttachmentDownloads } from '../util/hasAttachmentDownloads';
|
||||
|
@ -148,33 +132,29 @@ import { queueAttachmentDownloads } from '../util/queueAttachmentDownloads';
|
|||
import { findStoryMessages } from '../util/findStoryMessage';
|
||||
import { getStoryDataFromMessageAttributes } from '../services/storyLoader';
|
||||
import type { ConversationQueueJobData } from '../jobs/conversationJobQueue';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { shouldDownloadStory } from '../util/shouldDownloadStory';
|
||||
import type { EmbeddedContactWithHydratedAvatar } from '../types/EmbeddedContact';
|
||||
import { SeenStatus } from '../MessageSeenStatus';
|
||||
import { isNewReactionReplacingPrevious } from '../reactions/util';
|
||||
import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
|
||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||
import type { StickerWithHydratedData } from '../types/Stickers';
|
||||
import { getStringForConversationMerge } from '../util/getStringForConversationMerge';
|
||||
import {
|
||||
addToAttachmentDownloadQueue,
|
||||
shouldUseAttachmentDownloadQueue,
|
||||
} from '../util/attachmentDownloadQueue';
|
||||
import { getTitleNoDefault, getNumber } from '../util/getTitle';
|
||||
import dataInterface from '../sql/Client';
|
||||
import { getQuoteBodyText } from '../util/getQuoteBodyText';
|
||||
import { shouldReplyNotifyUser } from '../util/shouldReplyNotifyUser';
|
||||
import { isConversationAccepted } from '../util/isConversationAccepted';
|
||||
import type { RawBodyRange } from '../types/BodyRange';
|
||||
import { BodyRange, applyRangesForText } from '../types/BodyRange';
|
||||
import { getStringForProfileChange } from '../util/getStringForProfileChange';
|
||||
import { BodyRange } from '../types/BodyRange';
|
||||
import {
|
||||
queueUpdateMessage,
|
||||
saveNewMessageBatcher,
|
||||
} from '../util/messageBatcher';
|
||||
import { getCallHistorySelector } from '../state/selectors/callHistory';
|
||||
import { getConversationSelector } from '../state/selectors/conversations';
|
||||
import { getSenderIdentifier } from '../util/getSenderIdentifier';
|
||||
import { getNotificationDataForMessage } from '../util/getNotificationDataForMessage';
|
||||
import { getNotificationTextForMessage } from '../util/getNotificationTextForMessage';
|
||||
import { getMessageAuthorText } from '../util/getMessageAuthorText';
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
|
@ -212,9 +192,17 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
cachedOutgoingStickerData?: StickerWithHydratedData;
|
||||
|
||||
public registerLocations: Set<string>;
|
||||
|
||||
constructor(attributes: MessageAttributesType) {
|
||||
super(attributes);
|
||||
|
||||
if (!this.id && attributes.id) {
|
||||
this.id = attributes.id;
|
||||
}
|
||||
|
||||
this.registerLocations = new Set();
|
||||
|
||||
// Note that we intentionally don't use `initialize()` method because it
|
||||
// isn't compatible with esnext output of esbuild.
|
||||
if (isObject(attributes)) {
|
||||
|
@ -266,6 +254,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
return;
|
||||
}
|
||||
|
||||
window.MessageCache.setAttributes({
|
||||
messageId: this.id,
|
||||
messageAttributes: this.attributes,
|
||||
skipSaveToDatabase: true,
|
||||
});
|
||||
|
||||
const { storyChanged } = window.reduxActions.stories;
|
||||
|
||||
if (isStory(this.attributes)) {
|
||||
|
@ -293,19 +287,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
}
|
||||
|
||||
getSenderIdentifier(): string {
|
||||
const sentAt = this.get('sent_at');
|
||||
const source = this.get('source');
|
||||
const sourceServiceId = this.get('sourceServiceId');
|
||||
const sourceDevice = this.get('sourceDevice');
|
||||
|
||||
// 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}`;
|
||||
return getSenderIdentifier(this.attributes);
|
||||
}
|
||||
|
||||
getReceivedAt(): number {
|
||||
|
@ -345,63 +327,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
shouldSave?: boolean;
|
||||
} = {}
|
||||
): Promise<void> {
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const storyId = this.get('storyId');
|
||||
if (!storyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = this.get('storyReplyContext');
|
||||
// We'll continue trying to get the attachment as long as the message still exists
|
||||
if (context && (context.attachment?.url || !context.messageId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message =
|
||||
inMemoryMessage === undefined
|
||||
? (await getMessageById(storyId))?.attributes
|
||||
: inMemoryMessage;
|
||||
|
||||
if (!message) {
|
||||
const conversation = this.getConversation();
|
||||
softAssert(
|
||||
conversation && isDirectConversation(conversation.attributes),
|
||||
'hydrateStoryContext: Not a type=direct conversation'
|
||||
);
|
||||
this.set({
|
||||
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: '',
|
||||
},
|
||||
});
|
||||
if (shouldSave) {
|
||||
await window.Signal.Data.saveMessage(this.attributes, { ourAci });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const attachments = getAttachmentsForMessage({ ...message });
|
||||
let attachment: AttachmentType | undefined = attachments?.[0];
|
||||
if (attachment && !attachment.url && !attachment.textAttachment) {
|
||||
attachment = undefined;
|
||||
}
|
||||
|
||||
const { sourceServiceId: authorAci } = message;
|
||||
strictAssert(isAciString(authorAci), 'Story message from pni');
|
||||
this.set({
|
||||
storyReplyContext: {
|
||||
attachment: omit(attachment, 'screenshotData'),
|
||||
authorAci,
|
||||
messageId: message.id,
|
||||
},
|
||||
});
|
||||
if (shouldSave) {
|
||||
await window.Signal.Data.saveMessage(this.attributes, { ourAci });
|
||||
}
|
||||
await hydrateStoryContext(this.id, inMemoryMessage, { shouldSave });
|
||||
}
|
||||
|
||||
// Dependencies of prop-generation functions
|
||||
|
@ -414,486 +340,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
text: string;
|
||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||
} {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const attributes: MessageAttributesType = this.attributes;
|
||||
|
||||
if (isDeliveryIssue(attributes)) {
|
||||
return {
|
||||
emoji: '⚠️',
|
||||
text: window.i18n('icu:DeliveryIssue--preview'),
|
||||
};
|
||||
}
|
||||
|
||||
if (isConversationMerge(attributes)) {
|
||||
const conversation = this.getConversation();
|
||||
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 change = this.get('profileChange');
|
||||
const changedId = this.get('changedId');
|
||||
const changedContact = findAndFormatContact(changedId);
|
||||
if (!change) {
|
||||
throw new Error('getNotificationData: profileChange was missing!');
|
||||
}
|
||||
|
||||
return {
|
||||
text: getStringForProfileChange(change, changedContact, window.i18n),
|
||||
};
|
||||
}
|
||||
|
||||
if (isGroupV2Change(attributes)) {
|
||||
const change = this.get('groupV2Change');
|
||||
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 = this.get('attachments') || [];
|
||||
|
||||
if (isTapToView(attributes)) {
|
||||
if (this.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 groupUpdate = this.get('group_update');
|
||||
const fromContact = getContact(this.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: this.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 = (this.get('body') || '').trim();
|
||||
const bodyRanges = this.get('bodyRanges') || [];
|
||||
|
||||
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 stickerData = this.get('sticker');
|
||||
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)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const { expireTimer } = this.get('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 identifier = this.get('key_changed');
|
||||
const conversation = window.ConversationController.get(identifier);
|
||||
return {
|
||||
text: window.i18n('icu:safetyNumberChangedGroup', {
|
||||
name: conversation ? conversation.getTitle() : '',
|
||||
}),
|
||||
};
|
||||
}
|
||||
const contacts = this.get('contact');
|
||||
if (contacts && contacts.length) {
|
||||
return {
|
||||
text:
|
||||
EmbeddedContact.getName(contacts[0]) ||
|
||||
window.i18n('icu:unknownContact'),
|
||||
emoji: '👤',
|
||||
};
|
||||
}
|
||||
|
||||
const giftBadge = this.get('giftBadge');
|
||||
if (giftBadge) {
|
||||
const emoji = '✨';
|
||||
|
||||
if (isOutgoing(this.attributes)) {
|
||||
const toContact = window.ConversationController.get(
|
||||
this.attributes.conversationId
|
||||
);
|
||||
const recipient =
|
||||
toContact?.getTitle() ?? window.i18n('icu:unknownContact');
|
||||
return {
|
||||
emoji,
|
||||
text: window.i18n('icu:message--donation--preview--sent', {
|
||||
recipient,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const fromContact = getContact(this.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: '' };
|
||||
}
|
||||
|
||||
getAuthorText(): string | undefined {
|
||||
// if it's outgoing, it must be self-authored
|
||||
const selfAuthor = isOutgoing(this.attributes)
|
||||
? window.i18n('icu:you')
|
||||
: undefined;
|
||||
|
||||
// if it's not selfAuthor and there's no incoming contact,
|
||||
// it might be a group notification, so we return undefined
|
||||
return selfAuthor ?? this.getIncomingContact()?.getTitle({ isShort: true });
|
||||
return getNotificationDataForMessage(this.attributes);
|
||||
}
|
||||
|
||||
getNotificationText(): string {
|
||||
const { text, emoji } = this.getNotificationData();
|
||||
const { attributes } = this;
|
||||
|
||||
const conversation = this.getConversation();
|
||||
|
||||
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 name = this.getConversation()?.get('profileName');
|
||||
|
||||
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 || '';
|
||||
return getNotificationTextForMessage(this.attributes);
|
||||
}
|
||||
|
||||
// General
|
||||
|
@ -921,14 +372,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
this.set(attributes);
|
||||
}
|
||||
|
||||
getNameForNumber(number: string): string {
|
||||
const conversation = window.ConversationController.get(number);
|
||||
if (!conversation) {
|
||||
return number;
|
||||
}
|
||||
return conversation.getTitle();
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
await cleanupMessage(this.attributes);
|
||||
}
|
||||
|
@ -1041,7 +484,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
`doubleCheckMissingQuoteReference/${logId}: missing story reference`
|
||||
);
|
||||
|
||||
const message = window.MessageController.getById(storyId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(storyId);
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
@ -1068,7 +511,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
log.info(
|
||||
`doubleCheckMissingQuoteReference/${logId}: Verifying reference to ${sentAt}`
|
||||
);
|
||||
const inMemoryMessages = window.MessageController.filterBySentAt(
|
||||
const inMemoryMessages = window.MessageCache.__DEPRECATED$filterBySentAt(
|
||||
Number(sentAt)
|
||||
);
|
||||
let matchingMessage = find(inMemoryMessages, message =>
|
||||
|
@ -1082,7 +525,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
isQuoteAMatch(item, this.get('conversationId'), quote)
|
||||
);
|
||||
if (found) {
|
||||
matchingMessage = window.MessageController.register(found.id, found);
|
||||
matchingMessage = window.MessageCache.__DEPRECATED$register(
|
||||
found.id,
|
||||
found,
|
||||
'doubleCheckMissingQuoteReference'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1292,21 +739,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
this.set(markRead(this.attributes, readAt, options));
|
||||
}
|
||||
|
||||
getIncomingContact(): ConversationModel | undefined | null {
|
||||
if (!isIncoming(this.attributes)) {
|
||||
return null;
|
||||
}
|
||||
const sourceServiceId = this.get('sourceServiceId');
|
||||
if (!sourceServiceId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return window.ConversationController.getOrCreate(
|
||||
sourceServiceId,
|
||||
'private'
|
||||
);
|
||||
}
|
||||
|
||||
async retrySend(): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const conversation = this.getConversation()!;
|
||||
|
@ -1971,7 +1403,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
messageId: '',
|
||||
};
|
||||
|
||||
const inMemoryMessages = window.MessageController.filterBySentAt(id);
|
||||
const inMemoryMessages =
|
||||
window.MessageCache.__DEPRECATED$filterBySentAt(id);
|
||||
const matchingMessage = find(inMemoryMessages, item =>
|
||||
isQuoteAMatch(item.attributes, conversationId, result)
|
||||
);
|
||||
|
@ -1992,7 +1425,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
return result;
|
||||
}
|
||||
|
||||
queryMessage = window.MessageController.register(found.id, found);
|
||||
queryMessage = window.MessageCache.__DEPRECATED$register(
|
||||
found.id,
|
||||
found,
|
||||
'copyFromQuotedMessage'
|
||||
);
|
||||
}
|
||||
|
||||
if (queryMessage) {
|
||||
|
@ -2146,7 +1583,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
// 3. in rare cases, an incoming message can be retried, though it will
|
||||
// still go through one of the previous two codepaths
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const message = this;
|
||||
let message: MessageModel = this;
|
||||
const source = message.get('source');
|
||||
const sourceServiceId = message.get('sourceServiceId');
|
||||
const type = message.get('type');
|
||||
|
@ -2164,9 +1601,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
log.info(`${idLog}: starting processing in queue`);
|
||||
|
||||
// First, check for duplicates. If we find one, stop processing here.
|
||||
const inMemoryMessage = window.MessageController.findBySender(
|
||||
const inMemoryMessage = window.MessageCache.findBySender(
|
||||
this.getSenderIdentifier()
|
||||
)?.attributes;
|
||||
);
|
||||
if (inMemoryMessage) {
|
||||
log.info(`${idLog}: cache hit`, this.getSenderIdentifier());
|
||||
} else {
|
||||
|
@ -2197,9 +1634,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
`${idLog}: Updating message ${message.idForLogging()} with received transcript`
|
||||
);
|
||||
|
||||
const toUpdate = window.MessageController.register(
|
||||
const toUpdate = window.MessageCache.__DEPRECATED$register(
|
||||
existingMessage.id,
|
||||
existingMessage
|
||||
existingMessage,
|
||||
'handleDataMessage/outgoing/toUpdate'
|
||||
);
|
||||
|
||||
const unidentifiedDeliveriesSet = new Set<string>(
|
||||
|
@ -2594,6 +2032,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
const ourPni = window.textsecure.storage.user.getCheckedPni();
|
||||
const ourServiceIds: Set<ServiceIdString> = new Set([ourAci, ourPni]);
|
||||
|
||||
window.MessageCache.toMessageAttributes(this.attributes);
|
||||
message.set({
|
||||
id: messageId,
|
||||
attachments: dataMessage.attachments,
|
||||
|
@ -2786,12 +2225,16 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
) {
|
||||
conversation.set({
|
||||
lastMessage: message.getNotificationText(),
|
||||
lastMessageAuthor: message.getAuthorText(),
|
||||
lastMessageAuthor: getMessageAuthorText(message.attributes),
|
||||
timestamp: message.get('sent_at'),
|
||||
});
|
||||
}
|
||||
|
||||
window.MessageController.register(message.id, message);
|
||||
message = window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
message,
|
||||
'handleDataMessage/message'
|
||||
);
|
||||
conversation.incrementMessageCount();
|
||||
|
||||
// If we sent a message in a given conversation, unarchive it!
|
||||
|
@ -2892,7 +2335,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
const isFirstRun = false;
|
||||
await this.modifyTargetMessage(conversation, isFirstRun);
|
||||
|
||||
if (await shouldReplyNotifyUser(this, conversation)) {
|
||||
if (await shouldReplyNotifyUser(this.attributes, conversation)) {
|
||||
await conversation.notify(this);
|
||||
}
|
||||
|
||||
|
@ -3042,9 +2485,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
timestamp: reaction.timestamp,
|
||||
});
|
||||
|
||||
const messageToAdd = window.MessageController.register(
|
||||
const messageToAdd = window.MessageCache.__DEPRECATED$register(
|
||||
generatedMessage.id,
|
||||
generatedMessage
|
||||
generatedMessage,
|
||||
'generatedMessage'
|
||||
);
|
||||
if (isDirectConversation(targetConversation.attributes)) {
|
||||
await targetConversation.addSingleMessage(messageToAdd);
|
||||
|
@ -3063,7 +2507,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
'handleReaction: notifying for story reaction to ' +
|
||||
`${getMessageIdForLogging(storyMessage)} from someone else`
|
||||
);
|
||||
if (await shouldReplyNotifyUser(messageToAdd, targetConversation)) {
|
||||
if (
|
||||
await shouldReplyNotifyUser(
|
||||
messageToAdd.attributes,
|
||||
targetConversation
|
||||
)
|
||||
) {
|
||||
drop(targetConversation.notify(messageToAdd));
|
||||
}
|
||||
}
|
||||
|
@ -3188,9 +2637,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
});
|
||||
|
||||
void conversation.addSingleMessage(
|
||||
window.MessageController.register(
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
generatedMessage.id,
|
||||
generatedMessage
|
||||
generatedMessage,
|
||||
'generatedMessage2'
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -3285,14 +2735,3 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
}
|
||||
|
||||
window.Whisper.Message = MessageModel;
|
||||
|
||||
window.Whisper.MessageCollection = window.Backbone.Collection.extend({
|
||||
model: window.Whisper.Message,
|
||||
comparator(left: Readonly<MessageModel>, right: Readonly<MessageModel>) {
|
||||
if (left.get('received_at') === right.get('received_at')) {
|
||||
return (left.get('sent_at') || 0) - (right.get('sent_at') || 0);
|
||||
}
|
||||
|
||||
return (left.get('received_at') || 0) - (right.get('received_at') || 0);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import { v4 as generateUuid } from 'uuid';
|
|||
|
||||
import type { ReactionAttributesType } from '../messageModifiers/Reactions';
|
||||
import { ReactionSource } from './ReactionSource';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||
import { getSourceServiceId, isStory } from '../messages/helpers';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { isDirectConversation } from '../util/whatTypeOfConversation';
|
||||
|
@ -26,7 +26,7 @@ export async function enqueueReactionForSend({
|
|||
messageId: string;
|
||||
remove: boolean;
|
||||
}>): Promise<void> {
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
strictAssert(message, 'enqueueReactionForSend: no message found');
|
||||
|
||||
const targetAuthorAci = getSourceServiceId(message.attributes);
|
||||
|
|
|
@ -31,6 +31,16 @@ try {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
const debugMatch = stdout.matchAll(/ci:test-electron:debug=(.*)?\n/g);
|
||||
Array.from(debugMatch).forEach(info => {
|
||||
try {
|
||||
const args = JSON.parse(info[1]);
|
||||
console.log('DEBUG:', args);
|
||||
} catch {
|
||||
// this section intentionally left blank
|
||||
}
|
||||
});
|
||||
|
||||
const match = stdout.match(/ci:test-electron:done=(.*)?\n/);
|
||||
|
||||
if (!match) {
|
||||
|
|
410
ts/services/MessageCache.ts
Normal file
410
ts/services/MessageCache.ts
Normal file
|
@ -0,0 +1,410 @@
|
|||
// Copyright 2019 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import * as Errors from '../types/errors';
|
||||
import * as log from '../logging/log';
|
||||
import { drop } from '../util/drop';
|
||||
import { getEnvironment, Environment } from '../environment';
|
||||
import { getMessageConversation } from '../util/getMessageConversation';
|
||||
import { getMessageModelLogger } from '../util/MessageModelLogger';
|
||||
import { getSenderIdentifier } from '../util/getSenderIdentifier';
|
||||
import { isNotNil } from '../util/isNotNil';
|
||||
import { map } from '../util/iterables';
|
||||
import { softAssert, strictAssert } from '../util/assert';
|
||||
|
||||
export class MessageCache {
|
||||
private state = {
|
||||
messages: new Map<string, MessageAttributesType>(),
|
||||
messageIdsBySender: new Map<string, string>(),
|
||||
messageIdsBySentAt: new Map<number, Array<string>>(),
|
||||
lastAccessedAt: new Map<string, number>(),
|
||||
};
|
||||
|
||||
// Stores the models so that __DEPRECATED$register always returns the existing
|
||||
// copy instead of a new model.
|
||||
private modelCache = new Map<string, MessageModel>();
|
||||
|
||||
// Synchronously access a message's attributes from internal cache. Will
|
||||
// return undefined if the message does not exist in memory.
|
||||
public accessAttributes(
|
||||
messageId: string
|
||||
): Readonly<MessageAttributesType> | undefined {
|
||||
const messageAttributes = this.state.messages.get(messageId);
|
||||
return messageAttributes
|
||||
? this.freezeAttributes(messageAttributes)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
// Synchronously access a message's attributes from internal cache. Throws
|
||||
// if the message does not exist in memory.
|
||||
public accessAttributesOrThrow(
|
||||
source: string,
|
||||
messageId: string
|
||||
): Readonly<MessageAttributesType> {
|
||||
const messageAttributes = this.accessAttributes(messageId);
|
||||
strictAssert(
|
||||
messageAttributes,
|
||||
`MessageCache.accessAttributesOrThrow/${source}: no message`
|
||||
);
|
||||
return messageAttributes;
|
||||
}
|
||||
|
||||
// Evicts messages from the message cache if they have not been accessed past
|
||||
// the expiry time.
|
||||
public deleteExpiredMessages(expiryTime: number): void {
|
||||
const now = Date.now();
|
||||
|
||||
for (const [messageId, messageAttributes] of this.state.messages) {
|
||||
const timeLastAccessed = this.state.lastAccessedAt.get(messageId) ?? 0;
|
||||
const conversation = getMessageConversation(messageAttributes);
|
||||
|
||||
const state = window.reduxStore.getState();
|
||||
const selectedId = state?.conversations?.selectedConversationId;
|
||||
const inActiveConversation =
|
||||
conversation && selectedId && conversation.id === selectedId;
|
||||
|
||||
if (now - timeLastAccessed > expiryTime && !inActiveConversation) {
|
||||
this.__DEPRECATED$unregister(messageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finds a message in the cache by sender identifier
|
||||
public findBySender(
|
||||
senderIdentifier: string
|
||||
): Readonly<MessageAttributesType> | undefined {
|
||||
const id = this.state.messageIdsBySender.get(senderIdentifier);
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.accessAttributes(id);
|
||||
}
|
||||
|
||||
public replaceAllObsoleteConversationIds({
|
||||
conversationId,
|
||||
obsoleteId,
|
||||
}: {
|
||||
conversationId: string;
|
||||
obsoleteId: string;
|
||||
}): void {
|
||||
for (const [messageId, messageAttributes] of this.state.messages) {
|
||||
if (messageAttributes.conversationId !== obsoleteId) {
|
||||
continue;
|
||||
}
|
||||
this.setAttributes({
|
||||
messageId,
|
||||
messageAttributes: { conversationId },
|
||||
skipSaveToDatabase: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Find the message's attributes whether in memory or in the database.
|
||||
// Refresh the attributes in the cache if they exist. Throw if we cannot find
|
||||
// a matching message.
|
||||
public async resolveAttributes(
|
||||
source: string,
|
||||
messageId: string
|
||||
): Promise<Readonly<MessageAttributesType>> {
|
||||
const inMemoryMessageAttributes = this.accessAttributes(messageId);
|
||||
|
||||
if (inMemoryMessageAttributes) {
|
||||
return inMemoryMessageAttributes;
|
||||
}
|
||||
|
||||
let messageAttributesFromDatabase: MessageAttributesType | undefined;
|
||||
try {
|
||||
messageAttributesFromDatabase = await window.Signal.Data.getMessageById(
|
||||
messageId
|
||||
);
|
||||
} catch (err: unknown) {
|
||||
log.error(
|
||||
`MessageCache.resolveAttributes(${messageId}): db error ${Errors.toLogFormat(
|
||||
err
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
strictAssert(
|
||||
messageAttributesFromDatabase,
|
||||
`MessageCache.resolveAttributes/${source}: no message`
|
||||
);
|
||||
|
||||
return this.freezeAttributes(messageAttributesFromDatabase);
|
||||
}
|
||||
|
||||
// Updates a message's attributes and saves the message to cache and to the
|
||||
// database. Option to skip the save to the database.
|
||||
public setAttributes({
|
||||
messageId,
|
||||
messageAttributes: partialMessageAttributes,
|
||||
skipSaveToDatabase = false,
|
||||
}: {
|
||||
messageId: string;
|
||||
messageAttributes: Partial<MessageAttributesType>;
|
||||
skipSaveToDatabase: boolean;
|
||||
}): void {
|
||||
let messageAttributes = this.accessAttributes(messageId);
|
||||
|
||||
softAssert(messageAttributes, 'could not find message attributes');
|
||||
if (!messageAttributes) {
|
||||
// We expect message attributes to be defined in cache if one is trying to
|
||||
// set new attributes. In the case that the attributes are missing in cache
|
||||
// we'll add whatever we currently have to cache as a defensive measure so
|
||||
// that the code continues to work properly downstream. The softAssert above
|
||||
// that logs/debugger should be addressed upstream immediately by ensuring
|
||||
// that message is in cache.
|
||||
const partiallyCachedMessage = {
|
||||
id: messageId,
|
||||
...partialMessageAttributes,
|
||||
} as MessageAttributesType;
|
||||
|
||||
this.addMessageToCache(partiallyCachedMessage);
|
||||
messageAttributes = partiallyCachedMessage;
|
||||
}
|
||||
|
||||
this.state.messageIdsBySender.delete(
|
||||
getSenderIdentifier(messageAttributes)
|
||||
);
|
||||
|
||||
const nextMessageAttributes = {
|
||||
...messageAttributes,
|
||||
...partialMessageAttributes,
|
||||
};
|
||||
|
||||
const { id, sent_at: sentAt } = nextMessageAttributes;
|
||||
const previousIdsBySentAt = this.state.messageIdsBySentAt.get(sentAt);
|
||||
|
||||
let nextIdsBySentAtSet: Set<string>;
|
||||
if (previousIdsBySentAt) {
|
||||
nextIdsBySentAtSet = new Set(previousIdsBySentAt);
|
||||
nextIdsBySentAtSet.add(id);
|
||||
} else {
|
||||
nextIdsBySentAtSet = new Set([id]);
|
||||
}
|
||||
|
||||
this.state.messages.set(id, nextMessageAttributes);
|
||||
this.state.lastAccessedAt.set(id, Date.now());
|
||||
this.state.messageIdsBySender.set(
|
||||
getSenderIdentifier(messageAttributes),
|
||||
id
|
||||
);
|
||||
|
||||
this.markModelStale(nextMessageAttributes);
|
||||
|
||||
if (window.reduxActions) {
|
||||
window.reduxActions.conversations.messageChanged(
|
||||
messageId,
|
||||
nextMessageAttributes.conversationId,
|
||||
nextMessageAttributes
|
||||
);
|
||||
}
|
||||
|
||||
if (skipSaveToDatabase) {
|
||||
return;
|
||||
}
|
||||
drop(
|
||||
window.Signal.Data.saveMessage(messageAttributes, {
|
||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// When you already have the message attributes from the db and want to
|
||||
// ensure that they're added to the cache. The latest attributes from cache
|
||||
// are returned if they exist, if not the attributes passed in are returned.
|
||||
public toMessageAttributes(
|
||||
messageAttributes: MessageAttributesType
|
||||
): Readonly<MessageAttributesType> {
|
||||
this.addMessageToCache(messageAttributes);
|
||||
|
||||
const nextMessageAttributes = this.state.messages.get(messageAttributes.id);
|
||||
strictAssert(
|
||||
nextMessageAttributes,
|
||||
`MessageCache.toMessageAttributes: no message for id ${messageAttributes.id}`
|
||||
);
|
||||
|
||||
if (getEnvironment() === Environment.Development) {
|
||||
return Object.freeze(cloneDeep(nextMessageAttributes));
|
||||
}
|
||||
return nextMessageAttributes;
|
||||
}
|
||||
|
||||
static install(): MessageCache {
|
||||
const instance = new MessageCache();
|
||||
window.MessageCache = instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private addMessageToCache(messageAttributes: MessageAttributesType): void {
|
||||
if (!messageAttributes.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.messages.has(messageAttributes.id)) {
|
||||
this.state.lastAccessedAt.set(messageAttributes.id, Date.now());
|
||||
return;
|
||||
}
|
||||
|
||||
const { id, sent_at: sentAt } = messageAttributes;
|
||||
const previousIdsBySentAt = this.state.messageIdsBySentAt.get(sentAt);
|
||||
|
||||
let nextIdsBySentAtSet: Set<string>;
|
||||
if (previousIdsBySentAt) {
|
||||
nextIdsBySentAtSet = new Set(previousIdsBySentAt);
|
||||
nextIdsBySentAtSet.add(id);
|
||||
} else {
|
||||
nextIdsBySentAtSet = new Set([id]);
|
||||
}
|
||||
|
||||
this.state.messages.set(messageAttributes.id, { ...messageAttributes });
|
||||
this.state.lastAccessedAt.set(messageAttributes.id, Date.now());
|
||||
this.state.messageIdsBySentAt.set(sentAt, Array.from(nextIdsBySentAtSet));
|
||||
this.state.messageIdsBySender.set(
|
||||
getSenderIdentifier(messageAttributes),
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
private freezeAttributes(
|
||||
messageAttributes: MessageAttributesType
|
||||
): Readonly<MessageAttributesType> {
|
||||
this.addMessageToCache(messageAttributes);
|
||||
|
||||
if (getEnvironment() === Environment.Development) {
|
||||
return Object.freeze(cloneDeep(messageAttributes));
|
||||
}
|
||||
return messageAttributes;
|
||||
}
|
||||
|
||||
private removeMessage(messageId: string): void {
|
||||
const messageAttributes = this.state.messages.get(messageId);
|
||||
if (!messageAttributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { id, sent_at: sentAt } = messageAttributes;
|
||||
const nextIdsBySentAtSet =
|
||||
new Set(this.state.messageIdsBySentAt.get(sentAt)) || new Set();
|
||||
|
||||
nextIdsBySentAtSet.delete(id);
|
||||
|
||||
if (nextIdsBySentAtSet.size) {
|
||||
this.state.messageIdsBySentAt.set(sentAt, Array.from(nextIdsBySentAtSet));
|
||||
} else {
|
||||
this.state.messageIdsBySentAt.delete(sentAt);
|
||||
}
|
||||
|
||||
this.state.messages.delete(messageId);
|
||||
this.state.lastAccessedAt.delete(messageId);
|
||||
this.state.messageIdsBySender.delete(
|
||||
getSenderIdentifier(messageAttributes)
|
||||
);
|
||||
}
|
||||
|
||||
// Deprecated methods below
|
||||
|
||||
// Adds the message into the cache and eturns a Proxy that resembles
|
||||
// a MessageModel
|
||||
public __DEPRECATED$register(
|
||||
id: string,
|
||||
data: MessageModel | MessageAttributesType,
|
||||
location: string
|
||||
): MessageModel {
|
||||
if (!id || !data) {
|
||||
throw new Error(
|
||||
'MessageCache.__DEPRECATED$register: Got falsey id or message'
|
||||
);
|
||||
}
|
||||
|
||||
const existing = this.__DEPRECATED$getById(id);
|
||||
|
||||
if (existing) {
|
||||
this.addMessageToCache(existing.attributes);
|
||||
return existing;
|
||||
}
|
||||
|
||||
const modelProxy = this.toModel(data);
|
||||
const messageAttributes = 'attributes' in data ? data.attributes : data;
|
||||
this.addMessageToCache(messageAttributes);
|
||||
modelProxy.registerLocations.add(location);
|
||||
|
||||
return modelProxy;
|
||||
}
|
||||
|
||||
// Deletes the message from our cache
|
||||
public __DEPRECATED$unregister(id: string): void {
|
||||
const model = this.modelCache.get(id);
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeMessage(id);
|
||||
this.modelCache.delete(id);
|
||||
}
|
||||
|
||||
// Finds a message in the cache by Id
|
||||
public __DEPRECATED$getById(id: string): MessageModel | undefined {
|
||||
const data = this.state.messages.get(id);
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.toModel(data);
|
||||
}
|
||||
|
||||
// Finds a message in the cache by sentAt/timestamp
|
||||
public __DEPRECATED$filterBySentAt(sentAt: number): Iterable<MessageModel> {
|
||||
const items = this.state.messageIdsBySentAt.get(sentAt) ?? [];
|
||||
const attrs = items.map(id => this.accessAttributes(id)).filter(isNotNil);
|
||||
return map(attrs, data => this.toModel(data));
|
||||
}
|
||||
|
||||
// Marks cached model as "should be stale" to discourage continued use.
|
||||
// The model's attributes are directly updated so that the model is in sync
|
||||
// with the in-memory attributes.
|
||||
private markModelStale(messageAttributes: MessageAttributesType): void {
|
||||
const { id } = messageAttributes;
|
||||
const model = this.modelCache.get(id);
|
||||
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
model.attributes = { ...messageAttributes };
|
||||
|
||||
if (getEnvironment() === Environment.Development) {
|
||||
log.warn('MessageCache: stale model', {
|
||||
cid: model.cid,
|
||||
locations: Array.from(model.registerLocations).join('+'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a proxy object for MessageModel which logs usage in development
|
||||
// so that we're able to migrate off of models
|
||||
private toModel(
|
||||
messageAttributes: MessageAttributesType | MessageModel
|
||||
): MessageModel {
|
||||
const existingModel = this.modelCache.get(messageAttributes.id);
|
||||
|
||||
if (existingModel) {
|
||||
return existingModel;
|
||||
}
|
||||
|
||||
const model =
|
||||
'attributes' in messageAttributes
|
||||
? messageAttributes
|
||||
: new window.Whisper.Message(messageAttributes);
|
||||
|
||||
const proxy = getMessageModelLogger(model);
|
||||
|
||||
this.modelCache.set(messageAttributes.id, proxy);
|
||||
|
||||
return proxy;
|
||||
}
|
||||
}
|
|
@ -33,9 +33,10 @@ class ExpiringMessagesDeletionService {
|
|||
const inMemoryMessages: Array<MessageModel> = [];
|
||||
|
||||
messages.forEach(dbMessage => {
|
||||
const message = window.MessageController.register(
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
dbMessage.id,
|
||||
dbMessage
|
||||
dbMessage,
|
||||
'destroyExpiredMessages'
|
||||
);
|
||||
messageIds.push(message.id);
|
||||
inMemoryMessages.push(message);
|
||||
|
|
17
ts/services/messageStateCleanup.ts
Normal file
17
ts/services/messageStateCleanup.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as durations from '../util/durations';
|
||||
import { isEnabled } from '../RemoteConfig';
|
||||
import { MessageCache } from './MessageCache';
|
||||
|
||||
const TEN_MINUTES = 10 * durations.MINUTE;
|
||||
|
||||
export function initMessageCleanup(): void {
|
||||
setInterval(
|
||||
() => window.MessageCache.deleteExpiredMessages(TEN_MINUTES),
|
||||
isEnabled('desktop.messageCleanup') ? TEN_MINUTES : durations.HOUR
|
||||
);
|
||||
|
||||
MessageCache.install();
|
||||
}
|
|
@ -15,7 +15,11 @@ async function eraseTapToViewMessages() {
|
|||
await window.Signal.Data.getTapToViewMessagesNeedingErase();
|
||||
await Promise.all(
|
||||
messages.map(async fromDB => {
|
||||
const message = window.MessageController.register(fromDB.id, fromDB);
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
fromDB.id,
|
||||
fromDB,
|
||||
'eraseTapToViewMessages'
|
||||
);
|
||||
|
||||
window.SignalContext.log.info(
|
||||
'eraseTapToViewMessages: erasing message contents',
|
||||
|
|
|
@ -18,8 +18,6 @@ import { ConfirmationDialog } from './components/ConfirmationDialog';
|
|||
import { createApp } from './state/roots/createApp';
|
||||
import { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
|
||||
|
||||
import { createStore } from './state/createStore';
|
||||
|
||||
// Types
|
||||
import * as TypesAttachment from './types/Attachment';
|
||||
import * as VisualAttachment from './types/VisualAttachment';
|
||||
|
@ -379,7 +377,6 @@ export const setup = (options: {
|
|||
};
|
||||
|
||||
const State = {
|
||||
createStore,
|
||||
Roots,
|
||||
};
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ import { resolveAttachmentDraftData } from '../../util/resolveAttachmentDraftDat
|
|||
import { resolveDraftAttachmentOnDisk } from '../../util/resolveDraftAttachmentOnDisk';
|
||||
import { shouldShowInvalidMessageToast } from '../../util/shouldShowInvalidMessageToast';
|
||||
import { writeDraftAttachment } from '../../util/writeDraftAttachment';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import { canReply } from '../selectors/message';
|
||||
import { getContactId } from '../../messages/helpers';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
|
@ -747,7 +747,9 @@ export function setQuoteByMessageId(
|
|||
return;
|
||||
}
|
||||
|
||||
const message = messageId ? await getMessageById(messageId) : undefined;
|
||||
const message = messageId
|
||||
? await __DEPRECATED$getMessageById(messageId)
|
||||
: undefined;
|
||||
const state = getState();
|
||||
|
||||
if (
|
||||
|
|
|
@ -137,7 +137,7 @@ import {
|
|||
buildUpdateAttributesChange,
|
||||
initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2,
|
||||
} from '../../groups';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import type { PanelRenderType, PanelRequestType } from '../../types/Panels';
|
||||
import type { ConversationQueueJobData } from '../../jobs/conversationJobQueue';
|
||||
import { isOlderThan } from '../../util/timestamp';
|
||||
|
@ -1329,7 +1329,7 @@ function markMessageRead(
|
|||
return;
|
||||
}
|
||||
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`markMessageRead: failed to load message ${messageId}`);
|
||||
}
|
||||
|
@ -1674,7 +1674,7 @@ function deleteMessages({
|
|||
|
||||
await Promise.all(
|
||||
messageIds.map(async messageId => {
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`deleteMessages: Message ${messageId} missing!`);
|
||||
}
|
||||
|
@ -1778,7 +1778,7 @@ function setMessageToEdit(
|
|||
return;
|
||||
}
|
||||
|
||||
const message = (await getMessageById(messageId))?.attributes;
|
||||
const message = (await __DEPRECATED$getMessageById(messageId))?.attributes;
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
@ -1855,7 +1855,7 @@ function generateNewGroupLink(
|
|||
* replace it with an actual action that fits in with the redux approach.
|
||||
*/
|
||||
export const markViewed = (messageId: string): void => {
|
||||
const message = window.MessageController.getById(messageId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`markViewed: Message ${messageId} missing!`);
|
||||
}
|
||||
|
@ -2126,7 +2126,7 @@ function kickOffAttachmentDownload(
|
|||
options: Readonly<{ messageId: string }>
|
||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||
return async dispatch => {
|
||||
const message = await getMessageById(options.messageId);
|
||||
const message = await __DEPRECATED$getMessageById(options.messageId);
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`kickOffAttachmentDownload: Message ${options.messageId} missing!`
|
||||
|
@ -2158,7 +2158,7 @@ function markAttachmentAsCorrupted(
|
|||
options: AttachmentOptions
|
||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||
return async dispatch => {
|
||||
const message = await getMessageById(options.messageId);
|
||||
const message = await __DEPRECATED$getMessageById(options.messageId);
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`markAttachmentAsCorrupted: Message ${options.messageId} missing!`
|
||||
|
@ -2177,7 +2177,7 @@ function openGiftBadge(
|
|||
messageId: string
|
||||
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
|
||||
return async dispatch => {
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`openGiftBadge: Message ${messageId} missing!`);
|
||||
}
|
||||
|
@ -2197,7 +2197,7 @@ function retryMessageSend(
|
|||
messageId: string
|
||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||
return async dispatch => {
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`retryMessageSend: Message ${messageId} missing!`);
|
||||
}
|
||||
|
@ -2214,7 +2214,7 @@ export function copyMessageText(
|
|||
messageId: string
|
||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||
return async dispatch => {
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`copy: Message ${messageId} missing!`);
|
||||
}
|
||||
|
@ -2233,7 +2233,7 @@ export function retryDeleteForEveryone(
|
|||
messageId: string
|
||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||
return async dispatch => {
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`retryDeleteForEveryone: Message ${messageId} missing!`);
|
||||
}
|
||||
|
@ -2737,7 +2737,7 @@ function conversationStoppedByMissingVerification(payload: {
|
|||
};
|
||||
}
|
||||
|
||||
function messageChanged(
|
||||
export function messageChanged(
|
||||
id: string,
|
||||
conversationId: string,
|
||||
data: MessageAttributesType
|
||||
|
@ -2962,7 +2962,7 @@ function pushPanelForConversation(
|
|||
|
||||
const message =
|
||||
state.conversations.messagesLookup[messageId] ||
|
||||
(await getMessageById(messageId))?.attributes;
|
||||
(await __DEPRECATED$getMessageById(messageId))?.attributes;
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
'pushPanelForConversation: could not find message for MessageDetails'
|
||||
|
@ -3038,7 +3038,7 @@ function deleteMessagesForEveryone(
|
|||
await Promise.all(
|
||||
messageIds.map(async messageId => {
|
||||
try {
|
||||
const message = window.MessageController.getById(messageId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`deleteMessageForEveryone: Message ${messageId} missing!`
|
||||
|
@ -3394,7 +3394,11 @@ function loadRecentMediaItems(
|
|||
|
||||
// Cache these messages in memory to ensure Lightbox can find them
|
||||
messages.forEach(message => {
|
||||
window.MessageController.register(message.id, message);
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
message,
|
||||
'loadRecentMediaItems'
|
||||
);
|
||||
});
|
||||
|
||||
const recentMediaItems = messages
|
||||
|
@ -3492,7 +3496,7 @@ export function saveAttachmentFromMessage(
|
|||
providedAttachment?: AttachmentType
|
||||
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
|
||||
return async (dispatch, getState) => {
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`saveAttachmentFromMessage: Message ${messageId} missing!`
|
||||
|
@ -3585,7 +3589,7 @@ export function scrollToMessage(
|
|||
throw new Error('scrollToMessage: No conversation found');
|
||||
}
|
||||
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`scrollToMessage: failed to load message ${messageId}`);
|
||||
}
|
||||
|
@ -3599,7 +3603,7 @@ export function scrollToMessage(
|
|||
|
||||
let isInMemory = true;
|
||||
|
||||
if (!window.MessageController.getById(messageId)) {
|
||||
if (!window.MessageCache.__DEPRECATED$getById(messageId)) {
|
||||
isInMemory = false;
|
||||
}
|
||||
|
||||
|
@ -4009,7 +4013,7 @@ function onConversationOpened(
|
|||
conversation.onOpenStart();
|
||||
|
||||
if (messageId) {
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
|
||||
if (message) {
|
||||
drop(conversation.loadAndScroll(messageId));
|
||||
|
@ -4138,7 +4142,7 @@ function showArchivedConversations(): ShowArchivedConversationsActionType {
|
|||
}
|
||||
|
||||
function doubleCheckMissingQuoteReference(messageId: string): NoopActionType {
|
||||
const message = window.MessageController.getById(messageId);
|
||||
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||
if (message) {
|
||||
void message.doubleCheckMissingQuoteReference();
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import * as Errors from '../../types/errors';
|
|||
import * as SingleServePromise from '../../services/singleServePromise';
|
||||
import * as Stickers from '../../types/Stickers';
|
||||
import * as log from '../../logging/log';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { getMessagePropsSelector } from '../selectors/message';
|
||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||
import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper';
|
||||
|
@ -559,15 +558,12 @@ function toggleForwardMessagesModal(
|
|||
|
||||
const messagesProps = await Promise.all(
|
||||
messageIds.map(async messageId => {
|
||||
const message = await getMessageById(messageId);
|
||||
const messageAttributes = await window.MessageCache.resolveAttributes(
|
||||
'toggleForwardMessagesModal',
|
||||
messageId
|
||||
);
|
||||
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`toggleForwardMessagesModal: no message found for ${messageId}`
|
||||
);
|
||||
}
|
||||
|
||||
const attachments = message.get('attachments') ?? [];
|
||||
const { attachments = [] } = messageAttributes;
|
||||
|
||||
if (!attachments.every(isDownloaded)) {
|
||||
dispatch(
|
||||
|
@ -576,7 +572,7 @@ function toggleForwardMessagesModal(
|
|||
}
|
||||
|
||||
const messagePropsSelector = getMessagePropsSelector(getState());
|
||||
const messageProps = messagePropsSelector(message.attributes);
|
||||
const messageProps = messagePropsSelector(messageAttributes);
|
||||
|
||||
return messageProps;
|
||||
})
|
||||
|
@ -765,14 +761,10 @@ function showEditHistoryModal(
|
|||
messageId: string
|
||||
): ThunkAction<void, RootStateType, unknown, ShowEditHistoryModalActionType> {
|
||||
return async dispatch => {
|
||||
const message = await getMessageById(messageId);
|
||||
|
||||
if (!message) {
|
||||
log.warn('showEditHistoryModal: no message found');
|
||||
return;
|
||||
}
|
||||
|
||||
const messageAttributes = message.attributes;
|
||||
const messageAttributes = await window.MessageCache.resolveAttributes(
|
||||
'showEditHistoryModal',
|
||||
messageId
|
||||
);
|
||||
const nextEditHistoryMessages =
|
||||
copyOverMessageAttributesIntoEditHistory(messageAttributes);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import type { ShowToastActionType } from './toast';
|
|||
import type { StateType as RootStateType } from '../reducer';
|
||||
|
||||
import * as log from '../../logging/log';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import { isGIF } from '../../types/Attachment';
|
||||
import {
|
||||
|
@ -137,7 +137,7 @@ function showLightboxForViewOnceMedia(
|
|||
return async dispatch => {
|
||||
log.info('showLightboxForViewOnceMedia: attempting to display message');
|
||||
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`showLightboxForViewOnceMedia: Message ${messageId} missing!`
|
||||
|
@ -232,7 +232,7 @@ function showLightbox(opts: {
|
|||
return async (dispatch, getState) => {
|
||||
const { attachment, messageId } = opts;
|
||||
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`showLightbox: Message ${messageId} missing!`);
|
||||
}
|
||||
|
@ -373,7 +373,7 @@ function showLightboxForAdjacentMessage(
|
|||
sent_at: sentAt,
|
||||
} = media.message;
|
||||
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
if (!message) {
|
||||
log.warn('showLightboxForAdjacentMessage: original message is gone');
|
||||
dispatch({
|
||||
|
|
|
@ -101,7 +101,11 @@ function loadMediaItems(
|
|||
await Promise.all(
|
||||
rawMedia.map(async message => {
|
||||
const { schemaVersion } = message;
|
||||
const model = window.MessageController.register(message.id, message);
|
||||
const model = window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
message,
|
||||
'loadMediaItems'
|
||||
);
|
||||
|
||||
if (schemaVersion && schemaVersion < VERSION_NEEDED_FOR_DISPLAY) {
|
||||
const upgradedMsgAttributes = await upgradeMessageSchema(message);
|
||||
|
|
|
@ -36,7 +36,7 @@ import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUnti
|
|||
import { deleteStoryForEveryone as doDeleteStoryForEveryone } from '../../util/deleteStoryForEveryone';
|
||||
import { deleteGroupStoryReplyForEveryone as doDeleteGroupStoryReplyForEveryone } from '../../util/deleteGroupStoryReplyForEveryone';
|
||||
import { enqueueReactionForSend } from '../../reactions/enqueueReactionForSend';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import { markOnboardingStoryAsRead } from '../../util/markOnboardingStoryAsRead';
|
||||
import { markViewed } from '../../services/MessageUpdater';
|
||||
import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
|
||||
|
@ -387,7 +387,7 @@ function markStoryRead(
|
|||
return;
|
||||
}
|
||||
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
|
||||
if (!message) {
|
||||
log.warn(`markStoryRead: no message found ${messageId}`);
|
||||
|
@ -520,7 +520,7 @@ function queueStoryDownload(
|
|||
return;
|
||||
}
|
||||
|
||||
const message = await getMessageById(storyId);
|
||||
const message = await __DEPRECATED$getMessageById(storyId);
|
||||
|
||||
if (message) {
|
||||
// We want to ensure that we re-hydrate the story reply context with the
|
||||
|
@ -1395,7 +1395,7 @@ function removeAllContactStories(
|
|||
const messages = (
|
||||
await Promise.all(
|
||||
messageIds.map(async messageId => {
|
||||
const message = await getMessageById(messageId);
|
||||
const message = await __DEPRECATED$getMessageById(messageId);
|
||||
|
||||
if (!message) {
|
||||
log.warn(`${logId}: no message found ${messageId}`);
|
||||
|
|
98
ts/state/initializeRedux.ts
Normal file
98
ts/state/initializeRedux.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { bindActionCreators } from 'redux';
|
||||
import type { BadgesStateType } from './ducks/badges';
|
||||
import type { CallHistoryDetails } from '../types/CallDisposition';
|
||||
import type { MainWindowStatsType } from '../windows/context';
|
||||
import type { MenuOptionsType } from '../types/menu';
|
||||
import type { StoryDataType } from './ducks/stories';
|
||||
import type { StoryDistributionListDataType } from './ducks/storyDistributionLists';
|
||||
import { actionCreators } from './actions';
|
||||
import { createStore } from './createStore';
|
||||
import { getInitialState } from './getInitialState';
|
||||
|
||||
export function initializeRedux({
|
||||
callsHistory,
|
||||
initialBadgesState,
|
||||
mainWindowStats,
|
||||
menuOptions,
|
||||
stories,
|
||||
storyDistributionLists,
|
||||
}: {
|
||||
callsHistory: ReadonlyArray<CallHistoryDetails>;
|
||||
initialBadgesState: BadgesStateType;
|
||||
mainWindowStats: MainWindowStatsType;
|
||||
menuOptions: MenuOptionsType;
|
||||
stories: Array<StoryDataType>;
|
||||
storyDistributionLists: Array<StoryDistributionListDataType>;
|
||||
}): void {
|
||||
const initialState = getInitialState({
|
||||
badges: initialBadgesState,
|
||||
callsHistory,
|
||||
mainWindowStats,
|
||||
menuOptions,
|
||||
stories,
|
||||
storyDistributionLists,
|
||||
});
|
||||
|
||||
const store = createStore(initialState);
|
||||
window.reduxStore = store;
|
||||
|
||||
// Binding these actions to our redux store and exposing them allows us to update
|
||||
// redux when things change in the backbone world.
|
||||
window.reduxActions = {
|
||||
accounts: bindActionCreators(actionCreators.accounts, store.dispatch),
|
||||
app: bindActionCreators(actionCreators.app, store.dispatch),
|
||||
audioPlayer: bindActionCreators(actionCreators.audioPlayer, store.dispatch),
|
||||
audioRecorder: bindActionCreators(
|
||||
actionCreators.audioRecorder,
|
||||
store.dispatch
|
||||
),
|
||||
badges: bindActionCreators(actionCreators.badges, store.dispatch),
|
||||
callHistory: bindActionCreators(actionCreators.callHistory, store.dispatch),
|
||||
calling: bindActionCreators(actionCreators.calling, store.dispatch),
|
||||
composer: bindActionCreators(actionCreators.composer, store.dispatch),
|
||||
conversations: bindActionCreators(
|
||||
actionCreators.conversations,
|
||||
store.dispatch
|
||||
),
|
||||
crashReports: bindActionCreators(
|
||||
actionCreators.crashReports,
|
||||
store.dispatch
|
||||
),
|
||||
inbox: bindActionCreators(actionCreators.inbox, store.dispatch),
|
||||
emojis: bindActionCreators(actionCreators.emojis, store.dispatch),
|
||||
expiration: bindActionCreators(actionCreators.expiration, store.dispatch),
|
||||
globalModals: bindActionCreators(
|
||||
actionCreators.globalModals,
|
||||
store.dispatch
|
||||
),
|
||||
items: bindActionCreators(actionCreators.items, store.dispatch),
|
||||
lightbox: bindActionCreators(actionCreators.lightbox, store.dispatch),
|
||||
linkPreviews: bindActionCreators(
|
||||
actionCreators.linkPreviews,
|
||||
store.dispatch
|
||||
),
|
||||
mediaGallery: bindActionCreators(
|
||||
actionCreators.mediaGallery,
|
||||
store.dispatch
|
||||
),
|
||||
network: bindActionCreators(actionCreators.network, store.dispatch),
|
||||
safetyNumber: bindActionCreators(
|
||||
actionCreators.safetyNumber,
|
||||
store.dispatch
|
||||
),
|
||||
search: bindActionCreators(actionCreators.search, store.dispatch),
|
||||
stickers: bindActionCreators(actionCreators.stickers, store.dispatch),
|
||||
stories: bindActionCreators(actionCreators.stories, store.dispatch),
|
||||
storyDistributionLists: bindActionCreators(
|
||||
actionCreators.storyDistributionLists,
|
||||
store.dispatch
|
||||
),
|
||||
toast: bindActionCreators(actionCreators.toast, store.dispatch),
|
||||
updates: bindActionCreators(actionCreators.updates, store.dispatch),
|
||||
user: bindActionCreators(actionCreators.user, store.dispatch),
|
||||
username: bindActionCreators(actionCreators.username, store.dispatch),
|
||||
};
|
||||
}
|
|
@ -19,7 +19,6 @@ import {
|
|||
} from '../selectors/conversations';
|
||||
import { getIntl, getTheme, getRegionCode } from '../selectors/user';
|
||||
import { getLinkPreview } from '../selectors/linkPreviews';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import type {
|
||||
ForwardMessageData,
|
||||
|
@ -36,6 +35,8 @@ import { SmartCompositionTextArea } from './CompositionTextArea';
|
|||
import { useToastActions } from '../ducks/toast';
|
||||
import { hydrateRanges } from '../../types/BodyRange';
|
||||
import { isDownloaded } from '../../types/Attachment';
|
||||
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
|
||||
function toMessageForwardDraft(
|
||||
props: ForwardMessagePropsType,
|
||||
|
@ -119,10 +120,10 @@ function SmartForwardMessagesModalInner({
|
|||
try {
|
||||
const messages = await Promise.all(
|
||||
finalDrafts.map(async (draft): Promise<ForwardMessageData> => {
|
||||
const message = await getMessageById(draft.originalMessageId);
|
||||
if (message == null) {
|
||||
throw new Error('No message found');
|
||||
}
|
||||
const message = await __DEPRECATED$getMessageById(
|
||||
draft.originalMessageId
|
||||
);
|
||||
strictAssert(message, 'no message found');
|
||||
return {
|
||||
draft,
|
||||
originalMessage: message.attributes,
|
||||
|
|
|
@ -83,7 +83,11 @@ describe('Conversations', () => {
|
|||
forceSave: true,
|
||||
ourAci,
|
||||
});
|
||||
message = window.MessageController.register(message.id, message);
|
||||
message = window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
message,
|
||||
'test'
|
||||
);
|
||||
await window.Signal.Data.updateConversation(conversation.attributes);
|
||||
await conversation.updateLastMessage();
|
||||
|
||||
|
|
|
@ -5,17 +5,30 @@ import { assert } from 'chai';
|
|||
import * as sinon from 'sinon';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import type { AttachmentType } from '../../types/Attachment';
|
||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||
import type { ConversationModel } from '../../models/conversations';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import type { MessageModel } from '../../models/messages';
|
||||
import type { RawBodyRange } from '../../types/BodyRange';
|
||||
import type { StorageAccessType } from '../../types/Storage.d';
|
||||
import type { WebAPIType } from '../../textsecure/WebAPI';
|
||||
import MessageSender from '../../textsecure/SendMessage';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import { SendStatus } from '../../messages/MessageSendState';
|
||||
import MessageSender from '../../textsecure/SendMessage';
|
||||
import type { WebAPIType } from '../../textsecure/WebAPI';
|
||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||
import type { StorageAccessType } from '../../types/Storage.d';
|
||||
import { generateAci } from '../../types/ServiceId';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
import { generateAci } from '../../types/ServiceId';
|
||||
import { getContact } from '../../messages/helpers';
|
||||
import type { ConversationModel } from '../../models/conversations';
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import {
|
||||
APPLICATION_JSON,
|
||||
AUDIO_MP3,
|
||||
IMAGE_GIF,
|
||||
IMAGE_PNG,
|
||||
LONG_MESSAGE,
|
||||
TEXT_ATTACHMENT,
|
||||
VIDEO_MP4,
|
||||
} from '../../types/MIME';
|
||||
|
||||
describe('Message', () => {
|
||||
const STORAGE_KEYS_TO_RESTORE: Array<keyof StorageAccessType> = [
|
||||
|
@ -28,7 +41,7 @@ describe('Message', () => {
|
|||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
const attributes = {
|
||||
type: 'outgoing',
|
||||
type: 'outgoing' as const,
|
||||
body: 'hi',
|
||||
conversationId: 'foo',
|
||||
attachments: [],
|
||||
|
@ -39,12 +52,12 @@ describe('Message', () => {
|
|||
const me = '+14155555556';
|
||||
const ourServiceId = generateAci();
|
||||
|
||||
function createMessage(attrs: { [key: string]: unknown }) {
|
||||
const messages = new window.Whisper.MessageCollection();
|
||||
return messages.add({
|
||||
received_at: Date.now(),
|
||||
function createMessage(attrs: Partial<MessageAttributesType>): MessageModel {
|
||||
return new window.Whisper.Message({
|
||||
id: generateUuid(),
|
||||
...attrs,
|
||||
});
|
||||
received_at: Date.now(),
|
||||
} as MessageAttributesType);
|
||||
}
|
||||
|
||||
function createMessageAndGetNotificationData(attrs: {
|
||||
|
@ -214,14 +227,12 @@ describe('Message', () => {
|
|||
|
||||
describe('getContact', () => {
|
||||
it('gets outgoing contact', () => {
|
||||
const messages = new window.Whisper.MessageCollection();
|
||||
const message = messages.add(attributes);
|
||||
const message = createMessage(attributes);
|
||||
assert.exists(getContact(message.attributes));
|
||||
});
|
||||
|
||||
it('gets incoming contact', () => {
|
||||
const messages = new window.Whisper.MessageCollection();
|
||||
const message = messages.add({
|
||||
const message = createMessage({
|
||||
type: 'incoming',
|
||||
source,
|
||||
});
|
||||
|
@ -287,7 +298,8 @@ describe('Message', () => {
|
|||
isErased: false,
|
||||
attachments: [
|
||||
{
|
||||
contentType: 'image/png',
|
||||
contentType: IMAGE_PNG,
|
||||
size: 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
@ -302,7 +314,8 @@ describe('Message', () => {
|
|||
isErased: false,
|
||||
attachments: [
|
||||
{
|
||||
contentType: 'video/mp4',
|
||||
contentType: VIDEO_MP4,
|
||||
size: 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
@ -317,7 +330,8 @@ describe('Message', () => {
|
|||
isErased: false,
|
||||
attachments: [
|
||||
{
|
||||
contentType: 'text/plain',
|
||||
contentType: LONG_MESSAGE,
|
||||
size: 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
@ -482,7 +496,7 @@ describe('Message', () => {
|
|||
createMessageAndGetNotificationData({
|
||||
type: 'incoming',
|
||||
source,
|
||||
flags: true,
|
||||
flags: 1,
|
||||
}),
|
||||
{ text: i18n('icu:sessionEnded') }
|
||||
);
|
||||
|
@ -493,17 +507,26 @@ describe('Message', () => {
|
|||
createMessageAndGetNotificationData({
|
||||
type: 'incoming',
|
||||
source,
|
||||
errors: [{}],
|
||||
errors: [new Error()],
|
||||
}),
|
||||
{ text: i18n('icu:incomingError') }
|
||||
);
|
||||
});
|
||||
|
||||
const attachmentTestCases = [
|
||||
const attachmentTestCases: Array<{
|
||||
title: string;
|
||||
attachment: AttachmentType;
|
||||
expectedResult: {
|
||||
text: string;
|
||||
emoji: string;
|
||||
bodyRanges?: Array<RawBodyRange>;
|
||||
};
|
||||
}> = [
|
||||
{
|
||||
title: 'GIF',
|
||||
attachment: {
|
||||
contentType: 'image/gif',
|
||||
contentType: IMAGE_GIF,
|
||||
size: 0,
|
||||
},
|
||||
expectedResult: {
|
||||
text: 'GIF',
|
||||
|
@ -514,7 +537,8 @@ describe('Message', () => {
|
|||
{
|
||||
title: 'photo',
|
||||
attachment: {
|
||||
contentType: 'image/png',
|
||||
contentType: IMAGE_PNG,
|
||||
size: 0,
|
||||
},
|
||||
expectedResult: {
|
||||
text: 'Photo',
|
||||
|
@ -525,7 +549,8 @@ describe('Message', () => {
|
|||
{
|
||||
title: 'video',
|
||||
attachment: {
|
||||
contentType: 'video/mp4',
|
||||
contentType: VIDEO_MP4,
|
||||
size: 0,
|
||||
},
|
||||
expectedResult: {
|
||||
text: 'Video',
|
||||
|
@ -536,8 +561,9 @@ describe('Message', () => {
|
|||
{
|
||||
title: 'voice message',
|
||||
attachment: {
|
||||
contentType: 'audio/ogg',
|
||||
contentType: AUDIO_MP3,
|
||||
flags: Proto.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||
size: 0,
|
||||
},
|
||||
expectedResult: {
|
||||
text: 'Voice Message',
|
||||
|
@ -548,8 +574,9 @@ describe('Message', () => {
|
|||
{
|
||||
title: 'audio message',
|
||||
attachment: {
|
||||
contentType: 'audio/ogg',
|
||||
fileName: 'audio.ogg',
|
||||
contentType: AUDIO_MP3,
|
||||
fileName: 'audio.mp3',
|
||||
size: 0,
|
||||
},
|
||||
expectedResult: {
|
||||
text: 'Audio Message',
|
||||
|
@ -560,7 +587,8 @@ describe('Message', () => {
|
|||
{
|
||||
title: 'plain text',
|
||||
attachment: {
|
||||
contentType: 'text/plain',
|
||||
contentType: LONG_MESSAGE,
|
||||
size: 0,
|
||||
},
|
||||
expectedResult: {
|
||||
text: 'File',
|
||||
|
@ -571,7 +599,8 @@ describe('Message', () => {
|
|||
{
|
||||
title: 'unspecified-type',
|
||||
attachment: {
|
||||
contentType: null,
|
||||
contentType: APPLICATION_JSON,
|
||||
size: 0,
|
||||
},
|
||||
expectedResult: {
|
||||
text: 'File',
|
||||
|
@ -600,7 +629,8 @@ describe('Message', () => {
|
|||
attachments: [
|
||||
attachment,
|
||||
{
|
||||
contentType: 'text/html',
|
||||
contentType: TEXT_ATTACHMENT,
|
||||
size: 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
@ -671,7 +701,8 @@ describe('Message', () => {
|
|||
source,
|
||||
attachments: [
|
||||
{
|
||||
contentType: 'image/png',
|
||||
contentType: IMAGE_PNG,
|
||||
size: 0,
|
||||
},
|
||||
],
|
||||
}).getNotificationText(),
|
||||
|
@ -699,7 +730,8 @@ describe('Message', () => {
|
|||
source,
|
||||
attachments: [
|
||||
{
|
||||
contentType: 'image/png',
|
||||
contentType: IMAGE_PNG,
|
||||
size: 0,
|
||||
},
|
||||
],
|
||||
}).getNotificationText(),
|
||||
|
@ -708,26 +740,3 @@ describe('Message', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('MessageCollection', () => {
|
||||
it('should be ordered oldest to newest', () => {
|
||||
const messages = new window.Whisper.MessageCollection();
|
||||
// Timestamps
|
||||
const today = Date.now();
|
||||
const tomorrow = today + 12345;
|
||||
|
||||
// Add threads
|
||||
messages.add({ received_at: today });
|
||||
messages.add({ received_at: tomorrow });
|
||||
|
||||
const { models } = messages;
|
||||
const firstTimestamp = models[0].get('received_at');
|
||||
const secondTimestamp = models[1].get('received_at');
|
||||
|
||||
// Compare timestamps
|
||||
assert(typeof firstTimestamp === 'number');
|
||||
assert(typeof secondTimestamp === 'number');
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
assert(firstTimestamp! < secondTimestamp!);
|
||||
});
|
||||
});
|
||||
|
|
382
ts/test-electron/services/MessageCache_test.ts
Normal file
382
ts/test-electron/services/MessageCache_test.ts
Normal file
|
@ -0,0 +1,382 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import { MessageModel } from '../../models/messages';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
|
||||
import { MessageCache } from '../../services/MessageCache';
|
||||
|
||||
describe('MessageCache', () => {
|
||||
describe('filterBySentAt', () => {
|
||||
it('returns an empty iterable if no messages match', () => {
|
||||
const mc = new MessageCache();
|
||||
|
||||
assert.isEmpty([...mc.__DEPRECATED$filterBySentAt(123)]);
|
||||
});
|
||||
|
||||
it('returns all messages that match the timestamp', () => {
|
||||
const mc = new MessageCache();
|
||||
|
||||
let message1 = new MessageModel({
|
||||
conversationId: 'xyz',
|
||||
body: 'message1',
|
||||
id: uuid(),
|
||||
received_at: 1,
|
||||
sent_at: 1234,
|
||||
timestamp: 9999,
|
||||
type: 'incoming',
|
||||
});
|
||||
let message2 = new MessageModel({
|
||||
conversationId: 'xyz',
|
||||
body: 'message2',
|
||||
id: uuid(),
|
||||
received_at: 2,
|
||||
sent_at: 1234,
|
||||
timestamp: 9999,
|
||||
type: 'outgoing',
|
||||
});
|
||||
const message3 = new MessageModel({
|
||||
conversationId: 'xyz',
|
||||
body: 'message3',
|
||||
id: uuid(),
|
||||
received_at: 3,
|
||||
sent_at: 5678,
|
||||
timestamp: 9999,
|
||||
type: 'outgoing',
|
||||
});
|
||||
|
||||
message1 = mc.__DEPRECATED$register(message1.id, message1, 'test');
|
||||
message2 = mc.__DEPRECATED$register(message2.id, message2, 'test');
|
||||
// We deliberately register this message twice for testing.
|
||||
message2 = mc.__DEPRECATED$register(message2.id, message2, 'test');
|
||||
mc.__DEPRECATED$register(message3.id, message3, 'test');
|
||||
|
||||
const filteredMessages = Array.from(
|
||||
mc.__DEPRECATED$filterBySentAt(1234)
|
||||
).map(x => x.attributes);
|
||||
|
||||
assert.deepEqual(
|
||||
filteredMessages,
|
||||
[message1.attributes, message2.attributes],
|
||||
'first'
|
||||
);
|
||||
|
||||
mc.__DEPRECATED$unregister(message2.id);
|
||||
|
||||
const filteredMessages2 = Array.from(
|
||||
mc.__DEPRECATED$filterBySentAt(1234)
|
||||
).map(x => x.attributes);
|
||||
|
||||
assert.deepEqual(filteredMessages2, [message1.attributes], 'second');
|
||||
});
|
||||
});
|
||||
|
||||
describe('__DEPRECATED$register: syncing with backbone', () => {
|
||||
it('backbone to redux', () => {
|
||||
const message1 = new MessageModel({
|
||||
conversationId: 'xyz',
|
||||
id: uuid(),
|
||||
body: 'test1',
|
||||
received_at: 1,
|
||||
sent_at: Date.now(),
|
||||
timestamp: Date.now(),
|
||||
type: 'outgoing',
|
||||
});
|
||||
const messageFromController = window.MessageCache.__DEPRECATED$register(
|
||||
message1.id,
|
||||
message1,
|
||||
'test'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
message1,
|
||||
messageFromController,
|
||||
'same objects from mc.__DEPRECATED$register'
|
||||
);
|
||||
|
||||
const messageById = window.MessageCache.__DEPRECATED$getById(message1.id);
|
||||
|
||||
assert.strictEqual(message1, messageById, 'same objects from mc.getById');
|
||||
|
||||
const messageInCache = window.MessageCache.accessAttributes(message1.id);
|
||||
strictAssert(messageInCache, 'no message found');
|
||||
assert.deepEqual(
|
||||
message1.attributes,
|
||||
messageInCache,
|
||||
'same attributes as in cache'
|
||||
);
|
||||
|
||||
message1.set({ body: 'test2' });
|
||||
assert.equal(message1.attributes.body, 'test2', 'message model updated');
|
||||
assert.equal(
|
||||
messageById?.attributes.body,
|
||||
'test2',
|
||||
'old reference from messageById was updated'
|
||||
);
|
||||
assert.equal(
|
||||
messageInCache.body,
|
||||
'test1',
|
||||
'old cache reference not updated'
|
||||
);
|
||||
|
||||
const newMessageById = window.MessageCache.__DEPRECATED$getById(
|
||||
message1.id
|
||||
);
|
||||
assert.deepEqual(
|
||||
message1.attributes,
|
||||
newMessageById?.attributes,
|
||||
'same attributes from mc.getById (2)'
|
||||
);
|
||||
|
||||
const newMessageInCache = window.MessageCache.accessAttributes(
|
||||
message1.id
|
||||
);
|
||||
strictAssert(newMessageInCache, 'no message found');
|
||||
assert.deepEqual(
|
||||
message1.attributes,
|
||||
newMessageInCache,
|
||||
'same attributes as in cache (2)'
|
||||
);
|
||||
});
|
||||
|
||||
it('redux to backbone (working with models)', () => {
|
||||
const message = new MessageModel({
|
||||
conversationId: 'xyz',
|
||||
id: uuid(),
|
||||
body: 'test1',
|
||||
received_at: 1,
|
||||
sent_at: Date.now(),
|
||||
timestamp: Date.now(),
|
||||
type: 'outgoing',
|
||||
});
|
||||
|
||||
window.MessageCache.toMessageAttributes(message.attributes);
|
||||
|
||||
const messageFromController = window.MessageCache.__DEPRECATED$register(
|
||||
message.id,
|
||||
message,
|
||||
'test'
|
||||
);
|
||||
|
||||
assert.notStrictEqual(
|
||||
message,
|
||||
messageFromController,
|
||||
'mc.__DEPRECATED$register returns existing but it is not the same reference'
|
||||
);
|
||||
assert.deepEqual(
|
||||
message.attributes,
|
||||
messageFromController.attributes,
|
||||
'mc.__DEPRECATED$register returns existing and is the same attributes'
|
||||
);
|
||||
|
||||
messageFromController.set({ body: 'test2' });
|
||||
|
||||
assert.notEqual(
|
||||
message.get('body'),
|
||||
messageFromController.get('body'),
|
||||
'new model is not equal to old model'
|
||||
);
|
||||
|
||||
const messageInCache = window.MessageCache.accessAttributes(message.id);
|
||||
strictAssert(messageInCache, 'no message found');
|
||||
assert.equal(
|
||||
messageFromController.get('body'),
|
||||
messageInCache.body,
|
||||
'new update is in cache'
|
||||
);
|
||||
|
||||
assert.isUndefined(
|
||||
messageFromController.get('storyReplyContext'),
|
||||
'storyReplyContext is undefined'
|
||||
);
|
||||
|
||||
window.MessageCache.setAttributes({
|
||||
messageId: message.id,
|
||||
messageAttributes: {
|
||||
storyReplyContext: {
|
||||
attachment: undefined,
|
||||
authorAci: undefined,
|
||||
messageId: 'test123',
|
||||
},
|
||||
},
|
||||
skipSaveToDatabase: true,
|
||||
});
|
||||
|
||||
// This works because we refresh the model whenever an attribute changes
|
||||
// but this should log a warning.
|
||||
assert.equal(
|
||||
messageFromController.get('storyReplyContext')?.messageId,
|
||||
'test123',
|
||||
'storyReplyContext was updated (stale model)'
|
||||
);
|
||||
|
||||
const newMessageFromController =
|
||||
window.MessageCache.__DEPRECATED$register(message.id, message, 'test');
|
||||
|
||||
assert.equal(
|
||||
newMessageFromController.get('storyReplyContext')?.messageId,
|
||||
'test123',
|
||||
'storyReplyContext was updated (not stale)'
|
||||
);
|
||||
});
|
||||
|
||||
it('redux to backbone (working with attributes)', () => {
|
||||
it('sets the attributes and returns a fresh copy', () => {
|
||||
const mc = new MessageCache();
|
||||
|
||||
const messageAttributes: MessageAttributesType = {
|
||||
conversationId: uuid(),
|
||||
id: uuid(),
|
||||
received_at: 1,
|
||||
sent_at: Date.now(),
|
||||
timestamp: Date.now(),
|
||||
type: 'incoming',
|
||||
};
|
||||
|
||||
const messageModel = mc.__DEPRECATED$register(
|
||||
messageAttributes.id,
|
||||
messageAttributes,
|
||||
'test/updateAttributes'
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
messageAttributes,
|
||||
messageModel.attributes,
|
||||
'initial attributes matches message model'
|
||||
);
|
||||
|
||||
const proposedStoryReplyContext = {
|
||||
attachment: undefined,
|
||||
authorAci: undefined,
|
||||
messageId: 'test123',
|
||||
};
|
||||
|
||||
assert.notDeepEqual(
|
||||
messageModel.attributes.storyReplyContext,
|
||||
proposedStoryReplyContext,
|
||||
'attributes were changed outside of the message model'
|
||||
);
|
||||
|
||||
mc.setAttributes({
|
||||
messageId: messageAttributes.id,
|
||||
messageAttributes: {
|
||||
storyReplyContext: proposedStoryReplyContext,
|
||||
},
|
||||
skipSaveToDatabase: true,
|
||||
});
|
||||
|
||||
const nextMessageAttributes = mc.accessAttributesOrThrow(
|
||||
'test',
|
||||
messageAttributes.id
|
||||
);
|
||||
|
||||
assert.notDeepEqual(
|
||||
messageAttributes,
|
||||
nextMessageAttributes,
|
||||
'initial attributes are stale'
|
||||
);
|
||||
assert.notDeepEqual(
|
||||
messageAttributes.storyReplyContext,
|
||||
proposedStoryReplyContext,
|
||||
'initial attributes are stale 2'
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
nextMessageAttributes.storyReplyContext,
|
||||
proposedStoryReplyContext,
|
||||
'fresh attributes match what was proposed'
|
||||
);
|
||||
assert.notStrictEqual(
|
||||
nextMessageAttributes.storyReplyContext,
|
||||
proposedStoryReplyContext,
|
||||
'fresh attributes are not the same reference as proposed attributes'
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
messageModel.attributes,
|
||||
nextMessageAttributes,
|
||||
'model was updated'
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
messageModel.get('storyReplyContext')?.messageId,
|
||||
'test123',
|
||||
'storyReplyContext in model is set correctly'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('accessAttributes', () => {
|
||||
it('gets the attributes if they exist', () => {
|
||||
const mc = new MessageCache();
|
||||
|
||||
const messageAttributes: MessageAttributesType = {
|
||||
conversationId: uuid(),
|
||||
id: uuid(),
|
||||
received_at: 1,
|
||||
sent_at: Date.now(),
|
||||
timestamp: Date.now(),
|
||||
type: 'incoming',
|
||||
};
|
||||
|
||||
mc.toMessageAttributes(messageAttributes);
|
||||
|
||||
const accessAttributes = mc.accessAttributes(messageAttributes.id);
|
||||
|
||||
assert.deepEqual(
|
||||
accessAttributes,
|
||||
messageAttributes,
|
||||
'attributes returned have the same values'
|
||||
);
|
||||
assert.notStrictEqual(
|
||||
accessAttributes,
|
||||
messageAttributes,
|
||||
'attributes returned are not the same references'
|
||||
);
|
||||
|
||||
const undefinedMessage = mc.accessAttributes(uuid());
|
||||
assert.isUndefined(undefinedMessage, 'access did not find message');
|
||||
});
|
||||
});
|
||||
|
||||
describe('accessAttributesOrThrow', () => {
|
||||
it('accesses the attributes or throws if they do not exist', () => {
|
||||
const mc = new MessageCache();
|
||||
|
||||
const messageAttributes: MessageAttributesType = {
|
||||
conversationId: uuid(),
|
||||
id: uuid(),
|
||||
received_at: 1,
|
||||
sent_at: Date.now(),
|
||||
timestamp: Date.now(),
|
||||
type: 'incoming',
|
||||
};
|
||||
|
||||
mc.toMessageAttributes(messageAttributes);
|
||||
|
||||
const accessAttributes = mc.accessAttributesOrThrow(
|
||||
'tests.1',
|
||||
messageAttributes.id
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
accessAttributes,
|
||||
messageAttributes,
|
||||
'attributes returned have the same values'
|
||||
);
|
||||
assert.notStrictEqual(
|
||||
accessAttributes,
|
||||
messageAttributes,
|
||||
'attributes returned are not the same references'
|
||||
);
|
||||
|
||||
assert.throws(() => {
|
||||
mc.accessAttributesOrThrow('tests.2', uuid());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -861,7 +861,11 @@ describe('both/state/ducks/stories', () => {
|
|||
const storyId = generateUuid();
|
||||
const messageAttributes = getStoryMessage(storyId);
|
||||
|
||||
window.MessageController.register(storyId, messageAttributes);
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
storyId,
|
||||
messageAttributes,
|
||||
'test'
|
||||
);
|
||||
|
||||
const dispatch = sinon.spy();
|
||||
await queueStoryDownload(storyId)(dispatch, getEmptyRootState, null);
|
||||
|
@ -883,7 +887,11 @@ describe('both/state/ducks/stories', () => {
|
|||
],
|
||||
};
|
||||
|
||||
window.MessageController.register(storyId, messageAttributes);
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
storyId,
|
||||
messageAttributes,
|
||||
'test'
|
||||
);
|
||||
|
||||
const dispatch = sinon.spy();
|
||||
await queueStoryDownload(storyId)(dispatch, getEmptyRootState, null);
|
||||
|
@ -905,7 +913,11 @@ describe('both/state/ducks/stories', () => {
|
|||
],
|
||||
};
|
||||
|
||||
window.MessageController.register(storyId, messageAttributes);
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
storyId,
|
||||
messageAttributes,
|
||||
'test'
|
||||
);
|
||||
|
||||
const dispatch = sinon.spy();
|
||||
await queueStoryDownload(storyId)(dispatch, getEmptyRootState, null);
|
||||
|
@ -947,7 +959,11 @@ describe('both/state/ducks/stories', () => {
|
|||
},
|
||||
});
|
||||
|
||||
window.MessageController.register(storyId, messageAttributes);
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
storyId,
|
||||
messageAttributes,
|
||||
'test'
|
||||
);
|
||||
|
||||
const dispatch = sinon.spy();
|
||||
await queueStoryDownload(storyId)(dispatch, getState, null);
|
||||
|
@ -1004,7 +1020,11 @@ describe('both/state/ducks/stories', () => {
|
|||
},
|
||||
});
|
||||
|
||||
window.MessageController.register(storyId, messageAttributes);
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
storyId,
|
||||
messageAttributes,
|
||||
'test'
|
||||
);
|
||||
|
||||
const dispatch = sinon.spy();
|
||||
await queueStoryDownload(storyId)(dispatch, getState, null);
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { MessageModel } from '../../models/messages';
|
||||
|
||||
import { MessageController } from '../../util/MessageController';
|
||||
|
||||
describe('MessageController', () => {
|
||||
describe('filterBySentAt', () => {
|
||||
it('returns an empty iterable if no messages match', () => {
|
||||
const mc = new MessageController();
|
||||
|
||||
assert.isEmpty([...mc.filterBySentAt(123)]);
|
||||
});
|
||||
|
||||
it('returns all messages that match the timestamp', () => {
|
||||
const mc = new MessageController();
|
||||
const message1 = new MessageModel({
|
||||
conversationId: 'xyz',
|
||||
id: 'abc',
|
||||
received_at: 1,
|
||||
sent_at: 1234,
|
||||
timestamp: 9999,
|
||||
type: 'incoming',
|
||||
});
|
||||
const message2 = new MessageModel({
|
||||
conversationId: 'xyz',
|
||||
id: 'def',
|
||||
received_at: 2,
|
||||
sent_at: 1234,
|
||||
timestamp: 9999,
|
||||
type: 'outgoing',
|
||||
});
|
||||
const message3 = new MessageModel({
|
||||
conversationId: 'xyz',
|
||||
id: 'ignored',
|
||||
received_at: 3,
|
||||
sent_at: 5678,
|
||||
timestamp: 9999,
|
||||
type: 'outgoing',
|
||||
});
|
||||
mc.register(message1.id, message1);
|
||||
mc.register(message2.id, message2);
|
||||
// We deliberately register this message twice for testing.
|
||||
mc.register(message2.id, message2);
|
||||
mc.register(message3.id, message3);
|
||||
|
||||
assert.sameMembers([...mc.filterBySentAt(1234)], [message1, message2]);
|
||||
|
||||
mc.unregister(message2.id);
|
||||
|
||||
assert.sameMembers([...mc.filterBySentAt(1234)], [message1]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -13,7 +13,7 @@ import { generateStoryDistributionId } from '../../types/StoryDistributionId';
|
|||
import type { App } from '../playwright';
|
||||
import { Bootstrap } from '../bootstrap';
|
||||
|
||||
export const debug = createDebug('mock:test:edit');
|
||||
export const debug = createDebug('mock:test:stories');
|
||||
|
||||
const IdentifierType = Proto.ManifestRecord.Identifier.Type;
|
||||
|
||||
|
@ -236,7 +236,10 @@ describe('story/messaging', function unknownContacts() {
|
|||
}
|
||||
const sentAt = new Date(time).valueOf();
|
||||
|
||||
debug('Contact sends reply to group story');
|
||||
debug('Contact sends reply to group story', {
|
||||
story: sentAt,
|
||||
reply: sentAt + 1,
|
||||
});
|
||||
await contacts[0].sendRaw(
|
||||
desktop,
|
||||
{
|
||||
|
|
|
@ -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) {
|
||||
|
|
16
ts/window.d.ts
vendored
16
ts/window.d.ts
vendored
|
@ -9,10 +9,7 @@ import type PQueue from 'p-queue/dist';
|
|||
import type { assert } from 'chai';
|
||||
import type { PhoneNumber, PhoneNumberFormat } from 'google-libphonenumber';
|
||||
|
||||
import type {
|
||||
ConversationModelCollectionType,
|
||||
MessageModelCollectionType,
|
||||
} from './model-types.d';
|
||||
import type { ConversationModelCollectionType } from './model-types.d';
|
||||
import type { textsecure } from './textsecure';
|
||||
import type { Storage } from './textsecure/Storage';
|
||||
import type {
|
||||
|
@ -33,7 +30,6 @@ import type { LocalizerType, ThemeType } from './types/Util';
|
|||
import type { Receipt } from './types/Receipt';
|
||||
import type { ConversationController } from './ConversationController';
|
||||
import type { ReduxActions } from './state/types';
|
||||
import type { createStore } from './state/createStore';
|
||||
import type { createApp } from './state/roots/createApp';
|
||||
import type Data from './sql/Client';
|
||||
import type { MessageModel } from './models/messages';
|
||||
|
@ -43,7 +39,7 @@ import type { ConfirmationDialog } from './components/ConfirmationDialog';
|
|||
import type { SignalProtocolStore } from './SignalProtocolStore';
|
||||
import type { SocketStatus } from './types/SocketStatus';
|
||||
import type SyncRequest from './textsecure/SyncRequest';
|
||||
import type { MessageController } from './util/MessageController';
|
||||
import type { MessageCache } from './services/MessageCache';
|
||||
import type { StateType } from './state/reducer';
|
||||
import type { SystemTraySetting } from './types/SystemTraySetting';
|
||||
import type { Address } from './types/Address';
|
||||
|
@ -164,7 +160,6 @@ export type SignalCoreType = {
|
|||
};
|
||||
OS: OSType;
|
||||
State: {
|
||||
createStore: typeof createStore;
|
||||
Roots: {
|
||||
createApp: typeof createApp;
|
||||
};
|
||||
|
@ -235,7 +230,7 @@ declare global {
|
|||
ConversationController: ConversationController;
|
||||
Events: IPCEventsType;
|
||||
FontFace: typeof FontFace;
|
||||
MessageController: MessageController;
|
||||
MessageCache: MessageCache;
|
||||
SignalProtocolStore: typeof SignalProtocolStore;
|
||||
WebAPI: WebAPIConnectType;
|
||||
Whisper: WhisperType;
|
||||
|
@ -277,10 +272,10 @@ declare global {
|
|||
RETRY_DELAY: boolean;
|
||||
assert: typeof assert;
|
||||
testUtilities: {
|
||||
debug: (info: unknown) => void;
|
||||
initialize: () => Promise<void>;
|
||||
onComplete: (info: unknown) => void;
|
||||
prepareTests: () => void;
|
||||
installMessageController: () => void;
|
||||
initializeMessageCounter: () => Promise<void>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -308,7 +303,6 @@ export type WhisperType = {
|
|||
Conversation: typeof ConversationModel;
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
Message: typeof MessageModel;
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
|
||||
deliveryReceiptQueue: PQueue;
|
||||
deliveryReceiptBatcher: BatcherType<Receipt>;
|
||||
|
|
|
@ -10,18 +10,49 @@ import { sync } from 'fast-glob';
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { getSignalProtocolStore } from '../../SignalProtocolStore';
|
||||
import { MessageController } from '../../util/MessageController';
|
||||
import { initMessageCleanup } from '../../services/messageStateCleanup';
|
||||
import { initializeMessageCounter } from '../../util/incrementMessageCounter';
|
||||
import { initializeRedux } from '../../state/initializeRedux';
|
||||
import * as Stickers from '../../types/Stickers';
|
||||
|
||||
window.assert = assert;
|
||||
|
||||
// This is a hack to let us run TypeScript tests in the renderer process. See the
|
||||
// code in `test/index.html`.
|
||||
// code in `test/test.js`.
|
||||
|
||||
window.testUtilities = {
|
||||
debug(info) {
|
||||
return ipc.invoke('ci:test-electron:debug', info);
|
||||
},
|
||||
|
||||
onComplete(info) {
|
||||
return ipc.invoke('ci:test-electron:done', info);
|
||||
},
|
||||
|
||||
async initialize() {
|
||||
initMessageCleanup();
|
||||
await initializeMessageCounter();
|
||||
await Stickers.load();
|
||||
|
||||
initializeRedux({
|
||||
callsHistory: [],
|
||||
initialBadgesState: { byId: {} },
|
||||
mainWindowStats: {
|
||||
isFullScreen: false,
|
||||
isMaximized: false,
|
||||
},
|
||||
menuOptions: {
|
||||
development: false,
|
||||
devTools: false,
|
||||
includeSetup: false,
|
||||
isProduction: false,
|
||||
platform: 'test',
|
||||
},
|
||||
stories: [],
|
||||
storyDistributionLists: [],
|
||||
});
|
||||
},
|
||||
|
||||
prepareTests() {
|
||||
console.log('Preparing tests...');
|
||||
sync('../../test-{both,electron}/**/*_test.js', {
|
||||
|
@ -29,12 +60,6 @@ window.testUtilities = {
|
|||
cwd: __dirname,
|
||||
}).forEach(require);
|
||||
},
|
||||
installMessageController() {
|
||||
MessageController.install();
|
||||
},
|
||||
initializeMessageCounter() {
|
||||
return initializeMessageCounter();
|
||||
},
|
||||
};
|
||||
|
||||
window.getSignalProtocolStore = getSignalProtocolStore;
|
||||
|
|
|
@ -13,11 +13,11 @@ import './phase3-post-signal';
|
|||
import './phase4-test';
|
||||
import '../../backbone/reliable_trigger';
|
||||
|
||||
import type { CdsLookupOptionsType } from '../../textsecure/WebAPI';
|
||||
import type { FeatureFlagType } from '../../window.d';
|
||||
import type { StorageAccessType } from '../../types/Storage.d';
|
||||
import type { CdsLookupOptionsType } from '../../textsecure/WebAPI';
|
||||
import { start as startConversationController } from '../../ConversationController';
|
||||
import { MessageController } from '../../util/MessageController';
|
||||
import { initMessageCleanup } from '../../services/messageStateCleanup';
|
||||
import { Environment, getEnvironment } from '../../environment';
|
||||
import { isProduction } from '../../util/version';
|
||||
import { ipcInvoke } from '../../sql/channels';
|
||||
|
@ -43,7 +43,7 @@ if (window.SignalContext.config.proxyUrl) {
|
|||
}
|
||||
|
||||
window.Whisper.events = clone(window.Backbone.Events);
|
||||
MessageController.install();
|
||||
initMessageCleanup();
|
||||
startConversationController();
|
||||
|
||||
if (!isProduction(window.SignalContext.getVersion())) {
|
||||
|
@ -51,7 +51,8 @@ if (!isProduction(window.SignalContext.getVersion())) {
|
|||
cdsLookup: (options: CdsLookupOptionsType) =>
|
||||
window.textsecure.server?.cdsLookup(options),
|
||||
getConversation: (id: string) => window.ConversationController.get(id),
|
||||
getMessageById: (id: string) => window.MessageController.getById(id),
|
||||
getMessageById: (id: string) =>
|
||||
window.MessageCache.__DEPRECATED$getById(id),
|
||||
getReduxState: () => window.reduxStore.getState(),
|
||||
getSfuUrl: () => window.Signal.Services.calling._sfuUrl,
|
||||
getStorageItem: (name: keyof StorageAccessType) => window.storage.get(name),
|
||||
|
|
Loading…
Add table
Reference in a new issue