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 }],
|
'brace-style': ['error', '1tbs', { allowSingleLine: false }],
|
||||||
curly: ['error', 'all'],
|
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
|
// Always use === and !== except when directly comparing to null
|
||||||
// (which only will equal null or undefined)
|
// (which only will equal null or undefined)
|
||||||
eqeqeq: ['error', 'always', { null: 'never' }],
|
eqeqeq: ['error', 'always', { null: 'never' }],
|
||||||
|
|
|
@ -776,8 +776,9 @@ async function createWindow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const startInTray =
|
const startInTray =
|
||||||
|
isTestEnvironment(getEnvironment()) ||
|
||||||
(await systemTraySettingCache.get()) ===
|
(await systemTraySettingCache.get()) ===
|
||||||
SystemTraySetting.MinimizeToAndStartInSystemTray;
|
SystemTraySetting.MinimizeToAndStartInSystemTray;
|
||||||
|
|
||||||
const visibleOnAnyScreen = some(screen.getAllDisplays(), display => {
|
const visibleOnAnyScreen = some(screen.getAllDisplays(), display => {
|
||||||
if (
|
if (
|
||||||
|
@ -2882,6 +2883,10 @@ async function showStickerCreatorWindow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTestEnvironment(getEnvironment())) {
|
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) => {
|
ipc.handle('ci:test-electron:done', async (_event, info) => {
|
||||||
if (!process.env.TEST_QUIT_ON_COMPLETE) {
|
if (!process.env.TEST_QUIT_ON_COMPLETE) {
|
||||||
return;
|
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
|
// Copyright 2014 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* global Whisper, _, Backbone */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* global helpers for tests
|
* global helpers for tests
|
||||||
*/
|
*/
|
||||||
|
@ -18,20 +16,21 @@ function deleteIndexedDB() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.Events = {
|
||||||
|
getThemeSetting: () => 'light',
|
||||||
|
};
|
||||||
|
|
||||||
/* Delete the database before running any tests */
|
/* Delete the database before running any tests */
|
||||||
before(async () => {
|
before(async () => {
|
||||||
window.testUtilities.installMessageController();
|
await window.testUtilities.initialize();
|
||||||
|
|
||||||
await deleteIndexedDB();
|
await deleteIndexedDB();
|
||||||
await window.testUtilities.initializeMessageCounter();
|
|
||||||
await window.Signal.Data.removeAll();
|
await window.Signal.Data.removeAll();
|
||||||
await window.storage.fetch();
|
await window.storage.fetch();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.textsecure.storage.protocol = window.getSignalProtocolStore();
|
|
||||||
|
|
||||||
window.testUtilities.prepareTests();
|
window.testUtilities.prepareTests();
|
||||||
delete window.testUtilities.prepareTests;
|
delete window.testUtilities.prepareTests;
|
||||||
|
window.textsecure.storage.protocol = window.getSignalProtocolStore();
|
||||||
|
|
||||||
!(function () {
|
!(function () {
|
||||||
const passed = [];
|
const passed = [];
|
||||||
|
|
7
ts/CI.ts
7
ts/CI.ts
|
@ -120,7 +120,12 @@ export function getCI(deviceName: string): CIType {
|
||||||
[sentAt]
|
[sentAt]
|
||||||
);
|
);
|
||||||
return messages.map(
|
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,
|
ConversationRenderInfoType,
|
||||||
} from './model-types.d';
|
} from './model-types.d';
|
||||||
import type { ConversationModel } from './models/conversations';
|
import type { ConversationModel } from './models/conversations';
|
||||||
import type { MessageModel } from './models/messages';
|
|
||||||
|
|
||||||
import dataInterface from './sql/Client';
|
import dataInterface from './sql/Client';
|
||||||
import * as log from './logging/log';
|
import * as log from './logging/log';
|
||||||
|
@ -1127,13 +1126,11 @@ export class ConversationController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
log.warn(`${logId}: Update cached messages in MessageController`);
|
log.warn(`${logId}: Update cached messages in MessageCache`);
|
||||||
window.MessageController.update((message: MessageModel) => {
|
window.MessageCache.replaceAllObsoleteConversationIds({
|
||||||
if (message.get('conversationId') === obsoleteId) {
|
conversationId: currentId,
|
||||||
message.set({ conversationId: currentId });
|
obsoleteId,
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
log.warn(`${logId}: Update messages table`);
|
log.warn(`${logId}: Update messages table`);
|
||||||
await migrateConversationMessages(obsoleteId, currentId);
|
await migrateConversationMessages(obsoleteId, currentId);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import { webFrame } from 'electron';
|
import { webFrame } from 'electron';
|
||||||
import { isNumber, throttle, groupBy } from 'lodash';
|
import { isNumber, throttle, groupBy } from 'lodash';
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import { batch as batchDispatch } from 'react-redux';
|
import { batch as batchDispatch } from 'react-redux';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
|
@ -108,7 +107,6 @@ import { AppViewType } from './state/ducks/app';
|
||||||
import type { BadgesStateType } from './state/ducks/badges';
|
import type { BadgesStateType } from './state/ducks/badges';
|
||||||
import { areAnyCallsActiveOrRinging } from './state/selectors/calling';
|
import { areAnyCallsActiveOrRinging } from './state/selectors/calling';
|
||||||
import { badgeImageFileDownloader } from './badges/badgeImageFileDownloader';
|
import { badgeImageFileDownloader } from './badges/badgeImageFileDownloader';
|
||||||
import { actionCreators } from './state/actions';
|
|
||||||
import * as Deletes from './messageModifiers/Deletes';
|
import * as Deletes from './messageModifiers/Deletes';
|
||||||
import type { EditAttributesType } from './messageModifiers/Edits';
|
import type { EditAttributesType } from './messageModifiers/Edits';
|
||||||
import * as Edits 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 type { MainWindowStatsType } from './windows/context';
|
||||||
import { ReactionSource } from './reactions/ReactionSource';
|
import { ReactionSource } from './reactions/ReactionSource';
|
||||||
import { singleProtoJobQueue } from './jobs/singleProtoJobQueue';
|
import { singleProtoJobQueue } from './jobs/singleProtoJobQueue';
|
||||||
import { getInitialState } from './state/getInitialState';
|
|
||||||
import {
|
import {
|
||||||
conversationJobQueue,
|
conversationJobQueue,
|
||||||
conversationQueueJobEnum,
|
conversationQueueJobEnum,
|
||||||
|
@ -164,6 +161,7 @@ import MessageSender from './textsecure/SendMessage';
|
||||||
import type AccountManager from './textsecure/AccountManager';
|
import type AccountManager from './textsecure/AccountManager';
|
||||||
import { onStoryRecipientUpdate } from './util/onStoryRecipientUpdate';
|
import { onStoryRecipientUpdate } from './util/onStoryRecipientUpdate';
|
||||||
import { flushAttachmentDownloadQueue } from './util/attachmentDownloadQueue';
|
import { flushAttachmentDownloadQueue } from './util/attachmentDownloadQueue';
|
||||||
|
import { initializeRedux } from './state/initializeRedux';
|
||||||
import { StartupQueue } from './util/StartupQueue';
|
import { StartupQueue } from './util/StartupQueue';
|
||||||
import { showConfirmationDialog } from './util/showConfirmationDialog';
|
import { showConfirmationDialog } from './util/showConfirmationDialog';
|
||||||
import { onCallEventSync } from './util/onCallEventSync';
|
import { onCallEventSync } from './util/onCallEventSync';
|
||||||
|
@ -1151,7 +1149,7 @@ export async function startApp(): Promise<void> {
|
||||||
Errors.toLogFormat(error)
|
Errors.toLogFormat(error)
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
initializeRedux({ mainWindowStats, menuOptions });
|
setupAppState({ mainWindowStats, menuOptions });
|
||||||
drop(start());
|
drop(start());
|
||||||
window.Signal.Services.initializeNetworkObserver(
|
window.Signal.Services.initializeNetworkObserver(
|
||||||
window.reduxActions.network
|
window.reduxActions.network
|
||||||
|
@ -1173,89 +1171,24 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function initializeRedux({
|
function setupAppState({
|
||||||
mainWindowStats,
|
mainWindowStats,
|
||||||
menuOptions,
|
menuOptions,
|
||||||
}: {
|
}: {
|
||||||
mainWindowStats: MainWindowStatsType;
|
mainWindowStats: MainWindowStatsType;
|
||||||
menuOptions: MenuOptionsType;
|
menuOptions: MenuOptionsType;
|
||||||
}) {
|
}) {
|
||||||
// Here we set up a full redux store with initial state for our LeftPane Root
|
initializeRedux({
|
||||||
const convoCollection = window.getConversations();
|
callsHistory: getCallsHistoryForRedux(),
|
||||||
const initialState = getInitialState({
|
initialBadgesState,
|
||||||
badges: initialBadgesState,
|
|
||||||
mainWindowStats,
|
mainWindowStats,
|
||||||
menuOptions,
|
menuOptions,
|
||||||
stories: getStoriesForRedux(),
|
stories: getStoriesForRedux(),
|
||||||
storyDistributionLists: getDistributionListsForRedux(),
|
storyDistributionLists: getDistributionListsForRedux(),
|
||||||
callsHistory: getCallsHistoryForRedux(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = window.Signal.State.createStore(initialState);
|
// Here we set up a full redux store with initial state for our LeftPane Root
|
||||||
window.reduxStore = store;
|
const convoCollection = window.getConversations();
|
||||||
|
|
||||||
// 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),
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
conversationAdded,
|
conversationAdded,
|
||||||
|
|
18
ts/groups.ts
18
ts/groups.ts
|
@ -2008,8 +2008,12 @@ export async function createGroupV2(
|
||||||
forceSave: true,
|
forceSave: true,
|
||||||
ourAci,
|
ourAci,
|
||||||
});
|
});
|
||||||
const model = new window.Whisper.Message(createdTheGroupMessage);
|
let model = new window.Whisper.Message(createdTheGroupMessage);
|
||||||
window.MessageController.register(model.id, model);
|
model = window.MessageCache.__DEPRECATED$register(
|
||||||
|
model.id,
|
||||||
|
model,
|
||||||
|
'createGroupV2'
|
||||||
|
);
|
||||||
conversation.trigger('newmessage', model);
|
conversation.trigger('newmessage', model);
|
||||||
|
|
||||||
if (expireTimer) {
|
if (expireTimer) {
|
||||||
|
@ -3371,7 +3375,7 @@ async function appendChangeMessages(
|
||||||
|
|
||||||
let newMessages = 0;
|
let newMessages = 0;
|
||||||
for (const changeMessage of mergedMessages) {
|
for (const changeMessage of mergedMessages) {
|
||||||
const existing = window.MessageController.getById(changeMessage.id);
|
const existing = window.MessageCache.__DEPRECATED$getById(changeMessage.id);
|
||||||
|
|
||||||
// Update existing message
|
// Update existing message
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
@ -3383,8 +3387,12 @@ async function appendChangeMessages(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = new window.Whisper.Message(changeMessage);
|
let model = new window.Whisper.Message(changeMessage);
|
||||||
window.MessageController.register(model.id, model);
|
model = window.MessageCache.__DEPRECATED$register(
|
||||||
|
model.id,
|
||||||
|
model,
|
||||||
|
'appendChangeMessages'
|
||||||
|
);
|
||||||
conversation.trigger('newmessage', model);
|
conversation.trigger('newmessage', model);
|
||||||
newMessages += 1;
|
newMessages += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { getUntrustedConversationServiceIds } from './getUntrustedConversationSe
|
||||||
import { handleMessageSend } from '../../util/handleMessageSend';
|
import { handleMessageSend } from '../../util/handleMessageSend';
|
||||||
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
||||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||||
import { isNotNil } from '../../util/isNotNil';
|
import { isNotNil } from '../../util/isNotNil';
|
||||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||||
import type { MessageModel } from '../../models/messages';
|
import type { MessageModel } from '../../models/messages';
|
||||||
|
@ -59,7 +59,7 @@ export async function sendDeleteForEveryone(
|
||||||
|
|
||||||
const logId = `sendDeleteForEveryone(${conversation.idForLogging()}, ${messageId})`;
|
const logId = `sendDeleteForEveryone(${conversation.idForLogging()}, ${messageId})`;
|
||||||
|
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.error(`${logId}: Failed to fetch message. Failing job.`);
|
log.error(`${logId}: Failed to fetch message. Failing job.`);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { getUntrustedConversationServiceIds } from './getUntrustedConversationSe
|
||||||
import { handleMessageSend } from '../../util/handleMessageSend';
|
import { handleMessageSend } from '../../util/handleMessageSend';
|
||||||
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
||||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||||
import { isNotNil } from '../../util/isNotNil';
|
import { isNotNil } from '../../util/isNotNil';
|
||||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||||
import type { MessageModel } from '../../models/messages';
|
import type { MessageModel } from '../../models/messages';
|
||||||
|
@ -45,7 +45,7 @@ export async function sendDeleteStoryForEveryone(
|
||||||
|
|
||||||
const logId = `sendDeleteStoryForEveryone(${storyId})`;
|
const logId = `sendDeleteStoryForEveryone(${storyId})`;
|
||||||
|
|
||||||
const message = await getMessageById(storyId);
|
const message = await __DEPRECATED$getMessageById(storyId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.error(`${logId}: Failed to fetch message. Failing job.`);
|
log.error(`${logId}: Failed to fetch message. Failing job.`);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import PQueue from 'p-queue';
|
||||||
import * as Errors from '../../types/errors';
|
import * as Errors from '../../types/errors';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import type { MessageModel } from '../../models/messages';
|
import type { MessageModel } from '../../models/messages';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||||
import type { ConversationModel } from '../../models/conversations';
|
import type { ConversationModel } from '../../models/conversations';
|
||||||
import { isGroup, isGroupV2, isMe } from '../../util/whatTypeOfConversation';
|
import { isGroup, isGroupV2, isMe } from '../../util/whatTypeOfConversation';
|
||||||
import { getSendOptions } from '../../util/getSendOptions';
|
import { getSendOptions } from '../../util/getSendOptions';
|
||||||
|
@ -72,7 +72,7 @@ export async function sendNormalMessage(
|
||||||
const { Message } = window.Signal.Types;
|
const { Message } = window.Signal.Types;
|
||||||
|
|
||||||
const { messageId, revision, editedMessageTimestamp } = data;
|
const { messageId, revision, editedMessageTimestamp } = data;
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.info(
|
log.info(
|
||||||
`message ${messageId} was not found, maybe because it was deleted. Giving up on sending it`
|
`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),
|
uploadMessagePreviews(message, uploadQueue),
|
||||||
uploadMessageQuote(message, uploadQueue),
|
uploadMessageQuote(message, uploadQueue),
|
||||||
uploadMessageSticker(message, uploadQueue),
|
uploadMessageSticker(message, uploadQueue),
|
||||||
storyId ? getMessageById(storyId) : undefined,
|
storyId ? __DEPRECATED$getMessageById(storyId) : undefined,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Save message after uploading attachments
|
// Save message after uploading attachments
|
||||||
|
|
|
@ -14,7 +14,7 @@ import type { ConversationModel } from '../../models/conversations';
|
||||||
|
|
||||||
import * as reactionUtil from '../../reactions/util';
|
import * as reactionUtil from '../../reactions/util';
|
||||||
import { isSent, SendStatus } from '../../messages/MessageSendState';
|
import { isSent, SendStatus } from '../../messages/MessageSendState';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||||
import { isIncoming } from '../../messages/helpers';
|
import { isIncoming } from '../../messages/helpers';
|
||||||
import {
|
import {
|
||||||
isMe,
|
isMe,
|
||||||
|
@ -60,7 +60,7 @@ export async function sendReaction(
|
||||||
const ourConversationId =
|
const ourConversationId =
|
||||||
window.ConversationController.getOurConversationIdOrThrow();
|
window.ConversationController.getOurConversationIdOrThrow();
|
||||||
|
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.info(
|
log.info(
|
||||||
`message ${messageId} was not found, maybe because it was deleted. Giving up on sending its reactions`
|
`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(
|
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,
|
id: string,
|
||||||
messageId: string
|
messageId: string
|
||||||
): Promise<MessageModel | undefined> {
|
): Promise<MessageModel | undefined> {
|
||||||
const message = window.MessageController.getById(messageId);
|
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
return message;
|
return message;
|
||||||
|
@ -408,7 +408,11 @@ async function _getMessageById(
|
||||||
}
|
}
|
||||||
|
|
||||||
strictAssert(messageId === messageAttributes.id, 'message id mismatch');
|
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(
|
async function _finishJob(
|
||||||
|
|
|
@ -85,9 +85,10 @@ export async function onDelete(del: DeleteAttributesType): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = window.MessageController.register(
|
const message = window.MessageCache.__DEPRECATED$register(
|
||||||
targetMessage.id,
|
targetMessage.id,
|
||||||
targetMessage
|
targetMessage,
|
||||||
|
'Deletes.onDelete'
|
||||||
);
|
);
|
||||||
|
|
||||||
await deleteForEveryone(message, del);
|
await deleteForEveryone(message, del);
|
||||||
|
|
|
@ -106,9 +106,10 @@ export async function onEdit(edit: EditAttributesType): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = window.MessageController.register(
|
const message = window.MessageCache.__DEPRECATED$register(
|
||||||
targetMessage.id,
|
targetMessage.id,
|
||||||
targetMessage
|
targetMessage,
|
||||||
|
'Edits.onEdit'
|
||||||
);
|
);
|
||||||
|
|
||||||
await handleEditMessage(message.attributes, edit);
|
await handleEditMessage(message.attributes, edit);
|
||||||
|
|
|
@ -95,7 +95,11 @@ async function getTargetMessage(
|
||||||
(isOutgoing(item) || isStory(item)) && sourceId === item.conversationId
|
(isOutgoing(item) || isStory(item)) && sourceId === item.conversationId
|
||||||
);
|
);
|
||||||
if (message) {
|
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(
|
const groups = await window.Signal.Data.getAllGroupsInvolvingServiceId(
|
||||||
|
@ -113,7 +117,11 @@ async function getTargetMessage(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return window.MessageController.register(target.id, target);
|
return window.MessageCache.__DEPRECATED$register(
|
||||||
|
target.id,
|
||||||
|
target,
|
||||||
|
'MessageReceipts.getTargetMessage 2'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const wasDeliveredWithSealedSender = (
|
const wasDeliveredWithSealedSender = (
|
||||||
|
@ -376,7 +384,11 @@ export async function onReceipt(
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
targetMessages.map(msg => {
|
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);
|
return updateMessageSendState(receipt, model);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -196,9 +196,10 @@ export async function onReaction(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = window.MessageController.register(
|
const message = window.MessageCache.__DEPRECATED$register(
|
||||||
targetMessage.id,
|
targetMessage.id,
|
||||||
targetMessage
|
targetMessage,
|
||||||
|
'Reactions.onReaction'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use the generated message in ts/background.ts to create a message
|
// 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 });
|
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 readAt = Math.min(sync.readAt, Date.now());
|
||||||
const newestSentAt = sync.timestamp;
|
const newestSentAt = sync.timestamp;
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,11 @@ export async function onSync(
|
||||||
return;
|
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 });
|
await message.markViewOnceMessageViewed({ fromSync: true });
|
||||||
|
|
||||||
viewOnceSyncs.delete(sync.timestamp);
|
viewOnceSyncs.delete(sync.timestamp);
|
||||||
|
|
|
@ -99,7 +99,11 @@ export async function onSync(sync: ViewSyncAttributesType): Promise<void> {
|
||||||
|
|
||||||
notificationService.removeBy({ messageId: found.id });
|
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;
|
let didChangeMessage = false;
|
||||||
|
|
||||||
if (message.get('readStatus') !== ReadStatus.Viewed) {
|
if (message.get('readStatus') !== ReadStatus.Viewed) {
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
|
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import type { MessageAttributesType } from '../model-types.d';
|
import type { MessageAttributesType } from '../model-types.d';
|
||||||
import type { MessageModel } from '../models/messages';
|
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
|
import type { MessageModel } from '../models/messages';
|
||||||
|
|
||||||
export async function getMessageById(
|
export async function __DEPRECATED$getMessageById(
|
||||||
messageId: string
|
messageId: string
|
||||||
): Promise<MessageModel | undefined> {
|
): Promise<MessageModel | undefined> {
|
||||||
const message = window.MessageController.getById(messageId);
|
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||||
if (message) {
|
if (message) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
@ -28,5 +28,9 @@ export async function getMessageById(
|
||||||
return undefined;
|
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> = [];
|
const messageIdsToLookUpInDatabase: Array<string> = [];
|
||||||
|
|
||||||
for (const messageId of messageIds) {
|
for (const messageId of messageIds) {
|
||||||
const message = window.MessageController.getById(messageId);
|
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||||
if (message) {
|
if (message) {
|
||||||
messagesFromMemory.push(message);
|
messagesFromMemory.push(message);
|
||||||
} else {
|
} else {
|
||||||
|
@ -39,7 +39,11 @@ export async function getMessagesById(
|
||||||
// We use `window.Whisper.Message` instead of `MessageModel` here to avoid a circular
|
// We use `window.Whisper.Message` instead of `MessageModel` here to avoid a circular
|
||||||
// import.
|
// import.
|
||||||
const message = new window.Whisper.Message(rawMessage);
|
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];
|
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
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable max-classes-per-file */
|
|
||||||
|
|
||||||
import * as Backbone from 'backbone';
|
import * as Backbone from 'backbone';
|
||||||
|
|
||||||
import type { GroupV2ChangeType } from './groups';
|
import type { GroupV2ChangeType } from './groups';
|
||||||
import type { DraftBodyRanges, RawBodyRange } from './types/BodyRange';
|
import type { DraftBodyRanges, RawBodyRange } from './types/BodyRange';
|
||||||
import type { CustomColorType, ConversationColorType } from './types/Colors';
|
import type { CustomColorType, ConversationColorType } from './types/Colors';
|
||||||
import type { SendMessageChallengeData } from './textsecure/Errors';
|
import type { SendMessageChallengeData } from './textsecure/Errors';
|
||||||
import type { MessageModel } from './models/messages';
|
|
||||||
import type { ConversationModel } from './models/conversations';
|
import type { ConversationModel } from './models/conversations';
|
||||||
import type { ProfileNameChangeType } from './util/getStringForProfileChange';
|
import type { ProfileNameChangeType } from './util/getStringForProfileChange';
|
||||||
import type { CapabilitiesType } from './textsecure/WebAPI';
|
import type { CapabilitiesType } from './textsecure/WebAPI';
|
||||||
|
@ -503,5 +500,3 @@ export type ShallowChallengeError = CustomError & {
|
||||||
export declare class ConversationModelCollectionType extends Backbone.Collection<ConversationModel> {
|
export declare class ConversationModelCollectionType extends Backbone.Collection<ConversationModel> {
|
||||||
resetLookups(): void;
|
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 { deriveProfileKeyVersion } from '../util/zkgroup';
|
||||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||||
import OS from '../util/os/osMain';
|
import OS from '../util/os/osMain';
|
||||||
|
import { getMessageAuthorText } from '../util/getMessageAuthorText';
|
||||||
|
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
|
@ -1765,7 +1766,13 @@ export class ConversationModel extends window.Backbone
|
||||||
): Promise<Array<MessageModel>> {
|
): Promise<Array<MessageModel>> {
|
||||||
const result = messages
|
const result = messages
|
||||||
.filter(message => Boolean(message.id))
|
.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;
|
const eliminated = messages.length - result.length;
|
||||||
if (eliminated > 0) {
|
if (eliminated > 0) {
|
||||||
|
@ -2078,7 +2085,11 @@ export class ConversationModel extends window.Backbone
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
readMessages.map(async m => {
|
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();
|
const shouldSave = await registered.queueAttachmentDownloads();
|
||||||
if (shouldSave) {
|
if (shouldSave) {
|
||||||
await window.Signal.Data.saveMessage(registered.attributes, {
|
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, {
|
const id = await window.Signal.Data.saveMessage(message, {
|
||||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||||
});
|
});
|
||||||
const model = window.MessageController.register(
|
const model = window.MessageCache.__DEPRECATED$register(
|
||||||
id,
|
id,
|
||||||
new window.Whisper.Message({
|
new window.Whisper.Message({
|
||||||
...message,
|
...message,
|
||||||
id,
|
id,
|
||||||
})
|
}),
|
||||||
|
'addChatSessionRefreshed'
|
||||||
);
|
);
|
||||||
|
|
||||||
this.trigger('newmessage', model);
|
this.trigger('newmessage', model);
|
||||||
|
@ -2868,12 +2880,13 @@ export class ConversationModel extends window.Backbone
|
||||||
const id = await window.Signal.Data.saveMessage(message, {
|
const id = await window.Signal.Data.saveMessage(message, {
|
||||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||||
});
|
});
|
||||||
const model = window.MessageController.register(
|
const model = window.MessageCache.__DEPRECATED$register(
|
||||||
id,
|
id,
|
||||||
new window.Whisper.Message({
|
new window.Whisper.Message({
|
||||||
...message,
|
...message,
|
||||||
id,
|
id,
|
||||||
})
|
}),
|
||||||
|
'addDeliveryIssue'
|
||||||
);
|
);
|
||||||
|
|
||||||
this.trigger('newmessage', model);
|
this.trigger('newmessage', model);
|
||||||
|
@ -2922,9 +2935,10 @@ export class ConversationModel extends window.Backbone
|
||||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||||
forceSave: true,
|
forceSave: true,
|
||||||
});
|
});
|
||||||
const model = window.MessageController.register(
|
const model = window.MessageCache.__DEPRECATED$register(
|
||||||
message.id,
|
message.id,
|
||||||
new window.Whisper.Message(message)
|
new window.Whisper.Message(message),
|
||||||
|
'addKeyChange'
|
||||||
);
|
);
|
||||||
|
|
||||||
const isUntrusted = await this.isUntrusted();
|
const isUntrusted = await this.isUntrusted();
|
||||||
|
@ -3001,12 +3015,13 @@ export class ConversationModel extends window.Backbone
|
||||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||||
forceSave: true,
|
forceSave: true,
|
||||||
});
|
});
|
||||||
const model = window.MessageController.register(
|
const model = window.MessageCache.__DEPRECATED$register(
|
||||||
id,
|
id,
|
||||||
new window.Whisper.Message({
|
new window.Whisper.Message({
|
||||||
...message,
|
...message,
|
||||||
id,
|
id,
|
||||||
})
|
}),
|
||||||
|
'addConversationMerge'
|
||||||
);
|
);
|
||||||
|
|
||||||
this.trigger('newmessage', model);
|
this.trigger('newmessage', model);
|
||||||
|
@ -3052,9 +3067,10 @@ export class ConversationModel extends window.Backbone
|
||||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||||
forceSave: true,
|
forceSave: true,
|
||||||
});
|
});
|
||||||
const model = window.MessageController.register(
|
const model = window.MessageCache.__DEPRECATED$register(
|
||||||
message.id,
|
message.id,
|
||||||
new window.Whisper.Message(message)
|
new window.Whisper.Message(message),
|
||||||
|
'addVerifiedChange'
|
||||||
);
|
);
|
||||||
|
|
||||||
this.trigger('newmessage', model);
|
this.trigger('newmessage', model);
|
||||||
|
@ -3093,12 +3109,13 @@ export class ConversationModel extends window.Backbone
|
||||||
const id = await window.Signal.Data.saveMessage(message, {
|
const id = await window.Signal.Data.saveMessage(message, {
|
||||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||||
});
|
});
|
||||||
const model = window.MessageController.register(
|
const model = window.MessageCache.__DEPRECATED$register(
|
||||||
id,
|
id,
|
||||||
new window.Whisper.Message({
|
new window.Whisper.Message({
|
||||||
...message,
|
...message,
|
||||||
id,
|
id,
|
||||||
})
|
}),
|
||||||
|
'addProfileChange'
|
||||||
);
|
);
|
||||||
|
|
||||||
this.trigger('newmessage', model);
|
this.trigger('newmessage', model);
|
||||||
|
@ -3139,12 +3156,13 @@ export class ConversationModel extends window.Backbone
|
||||||
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const model = window.MessageController.register(
|
const model = window.MessageCache.__DEPRECATED$register(
|
||||||
id,
|
id,
|
||||||
new window.Whisper.Message({
|
new window.Whisper.Message({
|
||||||
...(message as MessageAttributesType),
|
...(message as MessageAttributesType),
|
||||||
id,
|
id,
|
||||||
})
|
}),
|
||||||
|
'addNotification'
|
||||||
);
|
);
|
||||||
|
|
||||||
this.trigger('newmessage', model);
|
this.trigger('newmessage', model);
|
||||||
|
@ -3224,7 +3242,7 @@ export class ConversationModel extends window.Backbone
|
||||||
`maybeRemoveUniversalTimer(${this.idForLogging()}): removed notification`
|
`maybeRemoveUniversalTimer(${this.idForLogging()}): removed notification`
|
||||||
);
|
);
|
||||||
|
|
||||||
const message = window.MessageController.getById(notificationId);
|
const message = window.MessageCache.__DEPRECATED$getById(notificationId);
|
||||||
if (message) {
|
if (message) {
|
||||||
await window.Signal.Data.removeMessage(message.id);
|
await window.Signal.Data.removeMessage(message.id);
|
||||||
}
|
}
|
||||||
|
@ -3261,7 +3279,7 @@ export class ConversationModel extends window.Backbone
|
||||||
`maybeClearContactRemoved(${this.idForLogging()}): removed notification`
|
`maybeClearContactRemoved(${this.idForLogging()}): removed notification`
|
||||||
);
|
);
|
||||||
|
|
||||||
const message = window.MessageController.getById(notificationId);
|
const message = window.MessageCache.__DEPRECATED$getById(notificationId);
|
||||||
if (message) {
|
if (message) {
|
||||||
await window.Signal.Data.removeMessage(message.id);
|
await window.Signal.Data.removeMessage(message.id);
|
||||||
}
|
}
|
||||||
|
@ -3643,7 +3661,7 @@ export class ConversationModel extends window.Backbone
|
||||||
draftBodyRanges: [],
|
draftBodyRanges: [],
|
||||||
draftTimestamp: null,
|
draftTimestamp: null,
|
||||||
quotedMessageId: undefined,
|
quotedMessageId: undefined,
|
||||||
lastMessageAuthor: message.getAuthorText(),
|
lastMessageAuthor: getMessageAuthorText(message.attributes),
|
||||||
lastMessageBodyRanges: message.get('bodyRanges'),
|
lastMessageBodyRanges: message.get('bodyRanges'),
|
||||||
lastMessage:
|
lastMessage:
|
||||||
notificationData?.text || message.getNotificationText() || '',
|
notificationData?.text || message.getNotificationText() || '',
|
||||||
|
@ -3795,7 +3813,11 @@ export class ConversationModel extends window.Backbone
|
||||||
});
|
});
|
||||||
|
|
||||||
const model = new window.Whisper.Message(attributes);
|
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;
|
message.cachedOutgoingContactData = contact;
|
||||||
|
|
||||||
// Attach path to preview images so that sendNormalMessage can use them to
|
// 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 previewMessage: MessageModel | undefined;
|
||||||
let activityMessage: 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
|
// in memory we use that data instead of the data from the db which may
|
||||||
// be out of date.
|
// be out of date.
|
||||||
if (preview) {
|
if (preview) {
|
||||||
previewMessage = window.MessageController.register(preview.id, preview);
|
previewMessage = window.MessageCache.__DEPRECATED$register(
|
||||||
|
preview.id,
|
||||||
|
preview,
|
||||||
|
'previewMessage'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity) {
|
if (activity) {
|
||||||
activityMessage = window.MessageController.register(
|
activityMessage = window.MessageCache.__DEPRECATED$register(
|
||||||
activity.id,
|
activity.id,
|
||||||
activity
|
activity,
|
||||||
|
'activityMessage'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4027,7 +4054,7 @@ export class ConversationModel extends window.Backbone
|
||||||
notificationData?.text || previewMessage?.getNotificationText() || '',
|
notificationData?.text || previewMessage?.getNotificationText() || '',
|
||||||
lastMessageBodyRanges: notificationData?.bodyRanges,
|
lastMessageBodyRanges: notificationData?.bodyRanges,
|
||||||
lastMessagePrefix: notificationData?.emoji,
|
lastMessagePrefix: notificationData?.emoji,
|
||||||
lastMessageAuthor: previewMessage?.getAuthorText(),
|
lastMessageAuthor: getMessageAuthorText(previewMessage?.attributes),
|
||||||
lastMessageStatus:
|
lastMessageStatus:
|
||||||
(previewMessage
|
(previewMessage
|
||||||
? getMessagePropStatus(previewMessage.attributes, ourConversationId)
|
? getMessagePropStatus(previewMessage.attributes, ourConversationId)
|
||||||
|
@ -4394,7 +4421,11 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
model.set({ id });
|
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.addSingleMessage(message);
|
||||||
void this.updateUnread();
|
void this.updateUnread();
|
||||||
|
|
|
@ -27,11 +27,10 @@ import type { DeleteAttributesType } from '../messageModifiers/Deletes';
|
||||||
import type { SentEventData } from '../textsecure/messageReceiverEvents';
|
import type { SentEventData } from '../textsecure/messageReceiverEvents';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import { isNormalNumber } from '../util/isNormalNumber';
|
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 { drop } from '../util/drop';
|
||||||
import { dropNull } from '../util/dropNull';
|
|
||||||
import type { ConversationModel } from './conversations';
|
import type { ConversationModel } from './conversations';
|
||||||
import { getCallingNotificationText } from '../util/callingNotification';
|
|
||||||
import type {
|
import type {
|
||||||
ProcessedDataMessage,
|
ProcessedDataMessage,
|
||||||
ProcessedQuote,
|
ProcessedQuote,
|
||||||
|
@ -39,7 +38,6 @@ import type {
|
||||||
CallbackResultType,
|
CallbackResultType,
|
||||||
} from '../textsecure/Types.d';
|
} from '../textsecure/Types.d';
|
||||||
import { SendMessageProtoError } from '../textsecure/Errors';
|
import { SendMessageProtoError } from '../textsecure/Errors';
|
||||||
import * as expirationTimer from '../util/expirationTimer';
|
|
||||||
import { getUserLanguages } from '../util/userLanguages';
|
import { getUserLanguages } from '../util/userLanguages';
|
||||||
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||||
import { copyCdnFields } from '../util/attachments';
|
import { copyCdnFields } from '../util/attachments';
|
||||||
|
@ -49,15 +47,11 @@ import type { ServiceIdString } from '../types/ServiceId';
|
||||||
import { normalizeServiceId } from '../types/ServiceId';
|
import { normalizeServiceId } from '../types/ServiceId';
|
||||||
import { isAciString } from '../util/isAciString';
|
import { isAciString } from '../util/isAciString';
|
||||||
import * as reactionUtil from '../reactions/util';
|
import * as reactionUtil from '../reactions/util';
|
||||||
import * as Stickers from '../types/Stickers';
|
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import * as EmbeddedContact from '../types/EmbeddedContact';
|
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
import { isImage, isVideo } from '../types/Attachment';
|
import { isImage, isVideo } from '../types/Attachment';
|
||||||
import * as Attachment from '../types/Attachment';
|
|
||||||
import { stringToMIMEType } from '../types/MIME';
|
import { stringToMIMEType } from '../types/MIME';
|
||||||
import * as MIME from '../types/MIME';
|
import * as MIME from '../types/MIME';
|
||||||
import * as GroupChange from '../groupChange';
|
|
||||||
import { ReadStatus } from '../messages/MessageReadStatus';
|
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||||
import type { SendStateByConversationId } from '../messages/MessageSendState';
|
import type { SendStateByConversationId } from '../messages/MessageSendState';
|
||||||
import {
|
import {
|
||||||
|
@ -79,12 +73,9 @@ import {
|
||||||
} from '../util/whatTypeOfConversation';
|
} from '../util/whatTypeOfConversation';
|
||||||
import { handleMessageSend } from '../util/handleMessageSend';
|
import { handleMessageSend } from '../util/handleMessageSend';
|
||||||
import { getSendOptions } from '../util/getSendOptions';
|
import { getSendOptions } from '../util/getSendOptions';
|
||||||
import { findAndFormatContact } from '../util/findAndFormatContact';
|
|
||||||
import { modifyTargetMessage } from '../util/modifyTargetMessage';
|
import { modifyTargetMessage } from '../util/modifyTargetMessage';
|
||||||
import {
|
import {
|
||||||
getAttachmentsForMessage,
|
|
||||||
getMessagePropStatus,
|
getMessagePropStatus,
|
||||||
getPropsForCallHistory,
|
|
||||||
hasErrors,
|
hasErrors,
|
||||||
isCallHistory,
|
isCallHistory,
|
||||||
isChatSessionRefreshed,
|
isChatSessionRefreshed,
|
||||||
|
@ -106,14 +97,9 @@ import {
|
||||||
isUnsupportedMessage,
|
isUnsupportedMessage,
|
||||||
isVerifiedChange,
|
isVerifiedChange,
|
||||||
isConversationMerge,
|
isConversationMerge,
|
||||||
extractHydratedMentions,
|
|
||||||
} from '../state/selectors/message';
|
} from '../state/selectors/message';
|
||||||
import {
|
|
||||||
isInCall,
|
|
||||||
getCallSelector,
|
|
||||||
getActiveCall,
|
|
||||||
} from '../state/selectors/calling';
|
|
||||||
import type { ReactionAttributesType } from '../messageModifiers/Reactions';
|
import type { ReactionAttributesType } from '../messageModifiers/Reactions';
|
||||||
|
import { isInCall } from '../state/selectors/calling';
|
||||||
import { ReactionSource } from '../reactions/ReactionSource';
|
import { ReactionSource } from '../reactions/ReactionSource';
|
||||||
import * as LinkPreview from '../types/LinkPreview';
|
import * as LinkPreview from '../types/LinkPreview';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
@ -138,9 +124,7 @@ import {
|
||||||
isCustomError,
|
isCustomError,
|
||||||
messageHasPaymentEvent,
|
messageHasPaymentEvent,
|
||||||
isQuoteAMatch,
|
isQuoteAMatch,
|
||||||
getPaymentEventNotificationText,
|
|
||||||
} from '../messages/helpers';
|
} from '../messages/helpers';
|
||||||
import type { ReplacementValuesType } from '../types/I18N';
|
|
||||||
import { viewOnceOpenJobQueue } from '../jobs/viewOnceOpenJobQueue';
|
import { viewOnceOpenJobQueue } from '../jobs/viewOnceOpenJobQueue';
|
||||||
import { getMessageIdForLogging } from '../util/idForLogging';
|
import { getMessageIdForLogging } from '../util/idForLogging';
|
||||||
import { hasAttachmentDownloads } from '../util/hasAttachmentDownloads';
|
import { hasAttachmentDownloads } from '../util/hasAttachmentDownloads';
|
||||||
|
@ -148,33 +132,29 @@ import { queueAttachmentDownloads } from '../util/queueAttachmentDownloads';
|
||||||
import { findStoryMessages } from '../util/findStoryMessage';
|
import { findStoryMessages } from '../util/findStoryMessage';
|
||||||
import { getStoryDataFromMessageAttributes } from '../services/storyLoader';
|
import { getStoryDataFromMessageAttributes } from '../services/storyLoader';
|
||||||
import type { ConversationQueueJobData } from '../jobs/conversationJobQueue';
|
import type { ConversationQueueJobData } from '../jobs/conversationJobQueue';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
|
||||||
import { shouldDownloadStory } from '../util/shouldDownloadStory';
|
import { shouldDownloadStory } from '../util/shouldDownloadStory';
|
||||||
import type { EmbeddedContactWithHydratedAvatar } from '../types/EmbeddedContact';
|
import type { EmbeddedContactWithHydratedAvatar } from '../types/EmbeddedContact';
|
||||||
import { SeenStatus } from '../MessageSeenStatus';
|
import { SeenStatus } from '../MessageSeenStatus';
|
||||||
import { isNewReactionReplacingPrevious } from '../reactions/util';
|
import { isNewReactionReplacingPrevious } from '../reactions/util';
|
||||||
import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
|
import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
|
||||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
|
||||||
import type { StickerWithHydratedData } from '../types/Stickers';
|
import type { StickerWithHydratedData } from '../types/Stickers';
|
||||||
import { getStringForConversationMerge } from '../util/getStringForConversationMerge';
|
|
||||||
import {
|
import {
|
||||||
addToAttachmentDownloadQueue,
|
addToAttachmentDownloadQueue,
|
||||||
shouldUseAttachmentDownloadQueue,
|
shouldUseAttachmentDownloadQueue,
|
||||||
} from '../util/attachmentDownloadQueue';
|
} from '../util/attachmentDownloadQueue';
|
||||||
import { getTitleNoDefault, getNumber } from '../util/getTitle';
|
|
||||||
import dataInterface from '../sql/Client';
|
import dataInterface from '../sql/Client';
|
||||||
import { getQuoteBodyText } from '../util/getQuoteBodyText';
|
import { getQuoteBodyText } from '../util/getQuoteBodyText';
|
||||||
import { shouldReplyNotifyUser } from '../util/shouldReplyNotifyUser';
|
import { shouldReplyNotifyUser } from '../util/shouldReplyNotifyUser';
|
||||||
import { isConversationAccepted } from '../util/isConversationAccepted';
|
|
||||||
import type { RawBodyRange } from '../types/BodyRange';
|
import type { RawBodyRange } from '../types/BodyRange';
|
||||||
import { BodyRange, applyRangesForText } from '../types/BodyRange';
|
import { BodyRange } from '../types/BodyRange';
|
||||||
import { getStringForProfileChange } from '../util/getStringForProfileChange';
|
|
||||||
import {
|
import {
|
||||||
queueUpdateMessage,
|
queueUpdateMessage,
|
||||||
saveNewMessageBatcher,
|
saveNewMessageBatcher,
|
||||||
} from '../util/messageBatcher';
|
} from '../util/messageBatcher';
|
||||||
import { getCallHistorySelector } from '../state/selectors/callHistory';
|
import { getSenderIdentifier } from '../util/getSenderIdentifier';
|
||||||
import { getConversationSelector } from '../state/selectors/conversations';
|
import { getNotificationDataForMessage } from '../util/getNotificationDataForMessage';
|
||||||
|
import { getNotificationTextForMessage } from '../util/getNotificationTextForMessage';
|
||||||
|
import { getMessageAuthorText } from '../util/getMessageAuthorText';
|
||||||
|
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
|
|
||||||
|
@ -212,9 +192,17 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
cachedOutgoingStickerData?: StickerWithHydratedData;
|
cachedOutgoingStickerData?: StickerWithHydratedData;
|
||||||
|
|
||||||
|
public registerLocations: Set<string>;
|
||||||
|
|
||||||
constructor(attributes: MessageAttributesType) {
|
constructor(attributes: MessageAttributesType) {
|
||||||
super(attributes);
|
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
|
// Note that we intentionally don't use `initialize()` method because it
|
||||||
// isn't compatible with esnext output of esbuild.
|
// isn't compatible with esnext output of esbuild.
|
||||||
if (isObject(attributes)) {
|
if (isObject(attributes)) {
|
||||||
|
@ -266,6 +254,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.MessageCache.setAttributes({
|
||||||
|
messageId: this.id,
|
||||||
|
messageAttributes: this.attributes,
|
||||||
|
skipSaveToDatabase: true,
|
||||||
|
});
|
||||||
|
|
||||||
const { storyChanged } = window.reduxActions.stories;
|
const { storyChanged } = window.reduxActions.stories;
|
||||||
|
|
||||||
if (isStory(this.attributes)) {
|
if (isStory(this.attributes)) {
|
||||||
|
@ -293,19 +287,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
getSenderIdentifier(): string {
|
getSenderIdentifier(): string {
|
||||||
const sentAt = this.get('sent_at');
|
return getSenderIdentifier(this.attributes);
|
||||||
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}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getReceivedAt(): number {
|
getReceivedAt(): number {
|
||||||
|
@ -345,63 +327,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
shouldSave?: boolean;
|
shouldSave?: boolean;
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
await hydrateStoryContext(this.id, inMemoryMessage, { shouldSave });
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dependencies of prop-generation functions
|
// Dependencies of prop-generation functions
|
||||||
|
@ -414,486 +340,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
text: string;
|
text: string;
|
||||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||||
} {
|
} {
|
||||||
// eslint-disable-next-line prefer-destructuring
|
return getNotificationDataForMessage(this.attributes);
|
||||||
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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getNotificationText(): string {
|
getNotificationText(): string {
|
||||||
const { text, emoji } = this.getNotificationData();
|
return getNotificationTextForMessage(this.attributes);
|
||||||
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 || '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// General
|
// General
|
||||||
|
@ -921,14 +372,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
this.set(attributes);
|
this.set(attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNameForNumber(number: string): string {
|
|
||||||
const conversation = window.ConversationController.get(number);
|
|
||||||
if (!conversation) {
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
return conversation.getTitle();
|
|
||||||
}
|
|
||||||
|
|
||||||
async cleanup(): Promise<void> {
|
async cleanup(): Promise<void> {
|
||||||
await cleanupMessage(this.attributes);
|
await cleanupMessage(this.attributes);
|
||||||
}
|
}
|
||||||
|
@ -1041,7 +484,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
`doubleCheckMissingQuoteReference/${logId}: missing story reference`
|
`doubleCheckMissingQuoteReference/${logId}: missing story reference`
|
||||||
);
|
);
|
||||||
|
|
||||||
const message = window.MessageController.getById(storyId);
|
const message = window.MessageCache.__DEPRECATED$getById(storyId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1068,7 +511,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
log.info(
|
log.info(
|
||||||
`doubleCheckMissingQuoteReference/${logId}: Verifying reference to ${sentAt}`
|
`doubleCheckMissingQuoteReference/${logId}: Verifying reference to ${sentAt}`
|
||||||
);
|
);
|
||||||
const inMemoryMessages = window.MessageController.filterBySentAt(
|
const inMemoryMessages = window.MessageCache.__DEPRECATED$filterBySentAt(
|
||||||
Number(sentAt)
|
Number(sentAt)
|
||||||
);
|
);
|
||||||
let matchingMessage = find(inMemoryMessages, message =>
|
let matchingMessage = find(inMemoryMessages, message =>
|
||||||
|
@ -1082,7 +525,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
isQuoteAMatch(item, this.get('conversationId'), quote)
|
isQuoteAMatch(item, this.get('conversationId'), quote)
|
||||||
);
|
);
|
||||||
if (found) {
|
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));
|
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> {
|
async retrySend(): Promise<void> {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const conversation = this.getConversation()!;
|
const conversation = this.getConversation()!;
|
||||||
|
@ -1971,7 +1403,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
messageId: '',
|
messageId: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const inMemoryMessages = window.MessageController.filterBySentAt(id);
|
const inMemoryMessages =
|
||||||
|
window.MessageCache.__DEPRECATED$filterBySentAt(id);
|
||||||
const matchingMessage = find(inMemoryMessages, item =>
|
const matchingMessage = find(inMemoryMessages, item =>
|
||||||
isQuoteAMatch(item.attributes, conversationId, result)
|
isQuoteAMatch(item.attributes, conversationId, result)
|
||||||
);
|
);
|
||||||
|
@ -1992,7 +1425,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
queryMessage = window.MessageController.register(found.id, found);
|
queryMessage = window.MessageCache.__DEPRECATED$register(
|
||||||
|
found.id,
|
||||||
|
found,
|
||||||
|
'copyFromQuotedMessage'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queryMessage) {
|
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
|
// 3. in rare cases, an incoming message can be retried, though it will
|
||||||
// still go through one of the previous two codepaths
|
// still go through one of the previous two codepaths
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
const message = this;
|
let message: MessageModel = this;
|
||||||
const source = message.get('source');
|
const source = message.get('source');
|
||||||
const sourceServiceId = message.get('sourceServiceId');
|
const sourceServiceId = message.get('sourceServiceId');
|
||||||
const type = message.get('type');
|
const type = message.get('type');
|
||||||
|
@ -2164,9 +1601,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
log.info(`${idLog}: starting processing in queue`);
|
log.info(`${idLog}: starting processing in queue`);
|
||||||
|
|
||||||
// First, check for duplicates. If we find one, stop processing here.
|
// First, check for duplicates. If we find one, stop processing here.
|
||||||
const inMemoryMessage = window.MessageController.findBySender(
|
const inMemoryMessage = window.MessageCache.findBySender(
|
||||||
this.getSenderIdentifier()
|
this.getSenderIdentifier()
|
||||||
)?.attributes;
|
);
|
||||||
if (inMemoryMessage) {
|
if (inMemoryMessage) {
|
||||||
log.info(`${idLog}: cache hit`, this.getSenderIdentifier());
|
log.info(`${idLog}: cache hit`, this.getSenderIdentifier());
|
||||||
} else {
|
} else {
|
||||||
|
@ -2197,9 +1634,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
`${idLog}: Updating message ${message.idForLogging()} with received transcript`
|
`${idLog}: Updating message ${message.idForLogging()} with received transcript`
|
||||||
);
|
);
|
||||||
|
|
||||||
const toUpdate = window.MessageController.register(
|
const toUpdate = window.MessageCache.__DEPRECATED$register(
|
||||||
existingMessage.id,
|
existingMessage.id,
|
||||||
existingMessage
|
existingMessage,
|
||||||
|
'handleDataMessage/outgoing/toUpdate'
|
||||||
);
|
);
|
||||||
|
|
||||||
const unidentifiedDeliveriesSet = new Set<string>(
|
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 ourPni = window.textsecure.storage.user.getCheckedPni();
|
||||||
const ourServiceIds: Set<ServiceIdString> = new Set([ourAci, ourPni]);
|
const ourServiceIds: Set<ServiceIdString> = new Set([ourAci, ourPni]);
|
||||||
|
|
||||||
|
window.MessageCache.toMessageAttributes(this.attributes);
|
||||||
message.set({
|
message.set({
|
||||||
id: messageId,
|
id: messageId,
|
||||||
attachments: dataMessage.attachments,
|
attachments: dataMessage.attachments,
|
||||||
|
@ -2786,12 +2225,16 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
) {
|
) {
|
||||||
conversation.set({
|
conversation.set({
|
||||||
lastMessage: message.getNotificationText(),
|
lastMessage: message.getNotificationText(),
|
||||||
lastMessageAuthor: message.getAuthorText(),
|
lastMessageAuthor: getMessageAuthorText(message.attributes),
|
||||||
timestamp: message.get('sent_at'),
|
timestamp: message.get('sent_at'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.MessageController.register(message.id, message);
|
message = window.MessageCache.__DEPRECATED$register(
|
||||||
|
message.id,
|
||||||
|
message,
|
||||||
|
'handleDataMessage/message'
|
||||||
|
);
|
||||||
conversation.incrementMessageCount();
|
conversation.incrementMessageCount();
|
||||||
|
|
||||||
// If we sent a message in a given conversation, unarchive it!
|
// 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;
|
const isFirstRun = false;
|
||||||
await this.modifyTargetMessage(conversation, isFirstRun);
|
await this.modifyTargetMessage(conversation, isFirstRun);
|
||||||
|
|
||||||
if (await shouldReplyNotifyUser(this, conversation)) {
|
if (await shouldReplyNotifyUser(this.attributes, conversation)) {
|
||||||
await conversation.notify(this);
|
await conversation.notify(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3042,9 +2485,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
timestamp: reaction.timestamp,
|
timestamp: reaction.timestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
const messageToAdd = window.MessageController.register(
|
const messageToAdd = window.MessageCache.__DEPRECATED$register(
|
||||||
generatedMessage.id,
|
generatedMessage.id,
|
||||||
generatedMessage
|
generatedMessage,
|
||||||
|
'generatedMessage'
|
||||||
);
|
);
|
||||||
if (isDirectConversation(targetConversation.attributes)) {
|
if (isDirectConversation(targetConversation.attributes)) {
|
||||||
await targetConversation.addSingleMessage(messageToAdd);
|
await targetConversation.addSingleMessage(messageToAdd);
|
||||||
|
@ -3063,7 +2507,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
'handleReaction: notifying for story reaction to ' +
|
'handleReaction: notifying for story reaction to ' +
|
||||||
`${getMessageIdForLogging(storyMessage)} from someone else`
|
`${getMessageIdForLogging(storyMessage)} from someone else`
|
||||||
);
|
);
|
||||||
if (await shouldReplyNotifyUser(messageToAdd, targetConversation)) {
|
if (
|
||||||
|
await shouldReplyNotifyUser(
|
||||||
|
messageToAdd.attributes,
|
||||||
|
targetConversation
|
||||||
|
)
|
||||||
|
) {
|
||||||
drop(targetConversation.notify(messageToAdd));
|
drop(targetConversation.notify(messageToAdd));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3188,9 +2637,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
});
|
});
|
||||||
|
|
||||||
void conversation.addSingleMessage(
|
void conversation.addSingleMessage(
|
||||||
window.MessageController.register(
|
window.MessageCache.__DEPRECATED$register(
|
||||||
generatedMessage.id,
|
generatedMessage.id,
|
||||||
generatedMessage
|
generatedMessage,
|
||||||
|
'generatedMessage2'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3285,14 +2735,3 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Whisper.Message = MessageModel;
|
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 type { ReactionAttributesType } from '../messageModifiers/Reactions';
|
||||||
import { ReactionSource } from './ReactionSource';
|
import { ReactionSource } from './ReactionSource';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||||
import { getSourceServiceId, isStory } from '../messages/helpers';
|
import { getSourceServiceId, isStory } from '../messages/helpers';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
import { isDirectConversation } from '../util/whatTypeOfConversation';
|
import { isDirectConversation } from '../util/whatTypeOfConversation';
|
||||||
|
@ -26,7 +26,7 @@ export async function enqueueReactionForSend({
|
||||||
messageId: string;
|
messageId: string;
|
||||||
remove: boolean;
|
remove: boolean;
|
||||||
}>): Promise<void> {
|
}>): Promise<void> {
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
strictAssert(message, 'enqueueReactionForSend: no message found');
|
strictAssert(message, 'enqueueReactionForSend: no message found');
|
||||||
|
|
||||||
const targetAuthorAci = getSourceServiceId(message.attributes);
|
const targetAuthorAci = getSourceServiceId(message.attributes);
|
||||||
|
|
|
@ -31,6 +31,16 @@ try {
|
||||||
process.exit(1);
|
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/);
|
const match = stdout.match(/ci:test-electron:done=(.*)?\n/);
|
||||||
|
|
||||||
if (!match) {
|
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> = [];
|
const inMemoryMessages: Array<MessageModel> = [];
|
||||||
|
|
||||||
messages.forEach(dbMessage => {
|
messages.forEach(dbMessage => {
|
||||||
const message = window.MessageController.register(
|
const message = window.MessageCache.__DEPRECATED$register(
|
||||||
dbMessage.id,
|
dbMessage.id,
|
||||||
dbMessage
|
dbMessage,
|
||||||
|
'destroyExpiredMessages'
|
||||||
);
|
);
|
||||||
messageIds.push(message.id);
|
messageIds.push(message.id);
|
||||||
inMemoryMessages.push(message);
|
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 window.Signal.Data.getTapToViewMessagesNeedingErase();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
messages.map(async fromDB => {
|
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(
|
window.SignalContext.log.info(
|
||||||
'eraseTapToViewMessages: erasing message contents',
|
'eraseTapToViewMessages: erasing message contents',
|
||||||
|
|
|
@ -18,8 +18,6 @@ import { ConfirmationDialog } from './components/ConfirmationDialog';
|
||||||
import { createApp } from './state/roots/createApp';
|
import { createApp } from './state/roots/createApp';
|
||||||
import { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
|
import { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
|
||||||
|
|
||||||
import { createStore } from './state/createStore';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import * as TypesAttachment from './types/Attachment';
|
import * as TypesAttachment from './types/Attachment';
|
||||||
import * as VisualAttachment from './types/VisualAttachment';
|
import * as VisualAttachment from './types/VisualAttachment';
|
||||||
|
@ -379,7 +377,6 @@ export const setup = (options: {
|
||||||
};
|
};
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
createStore,
|
|
||||||
Roots,
|
Roots,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ import { resolveAttachmentDraftData } from '../../util/resolveAttachmentDraftDat
|
||||||
import { resolveDraftAttachmentOnDisk } from '../../util/resolveDraftAttachmentOnDisk';
|
import { resolveDraftAttachmentOnDisk } from '../../util/resolveDraftAttachmentOnDisk';
|
||||||
import { shouldShowInvalidMessageToast } from '../../util/shouldShowInvalidMessageToast';
|
import { shouldShowInvalidMessageToast } from '../../util/shouldShowInvalidMessageToast';
|
||||||
import { writeDraftAttachment } from '../../util/writeDraftAttachment';
|
import { writeDraftAttachment } from '../../util/writeDraftAttachment';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||||
import { canReply } from '../selectors/message';
|
import { canReply } from '../selectors/message';
|
||||||
import { getContactId } from '../../messages/helpers';
|
import { getContactId } from '../../messages/helpers';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
|
@ -747,7 +747,9 @@ export function setQuoteByMessageId(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = messageId ? await getMessageById(messageId) : undefined;
|
const message = messageId
|
||||||
|
? await __DEPRECATED$getMessageById(messageId)
|
||||||
|
: undefined;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -137,7 +137,7 @@ import {
|
||||||
buildUpdateAttributesChange,
|
buildUpdateAttributesChange,
|
||||||
initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2,
|
initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2,
|
||||||
} from '../../groups';
|
} from '../../groups';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||||
import type { PanelRenderType, PanelRequestType } from '../../types/Panels';
|
import type { PanelRenderType, PanelRequestType } from '../../types/Panels';
|
||||||
import type { ConversationQueueJobData } from '../../jobs/conversationJobQueue';
|
import type { ConversationQueueJobData } from '../../jobs/conversationJobQueue';
|
||||||
import { isOlderThan } from '../../util/timestamp';
|
import { isOlderThan } from '../../util/timestamp';
|
||||||
|
@ -1329,7 +1329,7 @@ function markMessageRead(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`markMessageRead: failed to load message ${messageId}`);
|
throw new Error(`markMessageRead: failed to load message ${messageId}`);
|
||||||
}
|
}
|
||||||
|
@ -1674,7 +1674,7 @@ function deleteMessages({
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
messageIds.map(async messageId => {
|
messageIds.map(async messageId => {
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`deleteMessages: Message ${messageId} missing!`);
|
throw new Error(`deleteMessages: Message ${messageId} missing!`);
|
||||||
}
|
}
|
||||||
|
@ -1778,7 +1778,7 @@ function setMessageToEdit(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = (await getMessageById(messageId))?.attributes;
|
const message = (await __DEPRECATED$getMessageById(messageId))?.attributes;
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1855,7 +1855,7 @@ function generateNewGroupLink(
|
||||||
* replace it with an actual action that fits in with the redux approach.
|
* replace it with an actual action that fits in with the redux approach.
|
||||||
*/
|
*/
|
||||||
export const markViewed = (messageId: string): void => {
|
export const markViewed = (messageId: string): void => {
|
||||||
const message = window.MessageController.getById(messageId);
|
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`markViewed: Message ${messageId} missing!`);
|
throw new Error(`markViewed: Message ${messageId} missing!`);
|
||||||
}
|
}
|
||||||
|
@ -2126,7 +2126,7 @@ function kickOffAttachmentDownload(
|
||||||
options: Readonly<{ messageId: string }>
|
options: Readonly<{ messageId: string }>
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const message = await getMessageById(options.messageId);
|
const message = await __DEPRECATED$getMessageById(options.messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`kickOffAttachmentDownload: Message ${options.messageId} missing!`
|
`kickOffAttachmentDownload: Message ${options.messageId} missing!`
|
||||||
|
@ -2158,7 +2158,7 @@ function markAttachmentAsCorrupted(
|
||||||
options: AttachmentOptions
|
options: AttachmentOptions
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const message = await getMessageById(options.messageId);
|
const message = await __DEPRECATED$getMessageById(options.messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`markAttachmentAsCorrupted: Message ${options.messageId} missing!`
|
`markAttachmentAsCorrupted: Message ${options.messageId} missing!`
|
||||||
|
@ -2177,7 +2177,7 @@ function openGiftBadge(
|
||||||
messageId: string
|
messageId: string
|
||||||
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
|
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`openGiftBadge: Message ${messageId} missing!`);
|
throw new Error(`openGiftBadge: Message ${messageId} missing!`);
|
||||||
}
|
}
|
||||||
|
@ -2197,7 +2197,7 @@ function retryMessageSend(
|
||||||
messageId: string
|
messageId: string
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`retryMessageSend: Message ${messageId} missing!`);
|
throw new Error(`retryMessageSend: Message ${messageId} missing!`);
|
||||||
}
|
}
|
||||||
|
@ -2214,7 +2214,7 @@ export function copyMessageText(
|
||||||
messageId: string
|
messageId: string
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`copy: Message ${messageId} missing!`);
|
throw new Error(`copy: Message ${messageId} missing!`);
|
||||||
}
|
}
|
||||||
|
@ -2233,7 +2233,7 @@ export function retryDeleteForEveryone(
|
||||||
messageId: string
|
messageId: string
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`retryDeleteForEveryone: Message ${messageId} missing!`);
|
throw new Error(`retryDeleteForEveryone: Message ${messageId} missing!`);
|
||||||
}
|
}
|
||||||
|
@ -2737,7 +2737,7 @@ function conversationStoppedByMissingVerification(payload: {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function messageChanged(
|
export function messageChanged(
|
||||||
id: string,
|
id: string,
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
data: MessageAttributesType
|
data: MessageAttributesType
|
||||||
|
@ -2962,7 +2962,7 @@ function pushPanelForConversation(
|
||||||
|
|
||||||
const message =
|
const message =
|
||||||
state.conversations.messagesLookup[messageId] ||
|
state.conversations.messagesLookup[messageId] ||
|
||||||
(await getMessageById(messageId))?.attributes;
|
(await __DEPRECATED$getMessageById(messageId))?.attributes;
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'pushPanelForConversation: could not find message for MessageDetails'
|
'pushPanelForConversation: could not find message for MessageDetails'
|
||||||
|
@ -3038,7 +3038,7 @@ function deleteMessagesForEveryone(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
messageIds.map(async messageId => {
|
messageIds.map(async messageId => {
|
||||||
try {
|
try {
|
||||||
const message = window.MessageController.getById(messageId);
|
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`deleteMessageForEveryone: Message ${messageId} missing!`
|
`deleteMessageForEveryone: Message ${messageId} missing!`
|
||||||
|
@ -3394,7 +3394,11 @@ function loadRecentMediaItems(
|
||||||
|
|
||||||
// Cache these messages in memory to ensure Lightbox can find them
|
// Cache these messages in memory to ensure Lightbox can find them
|
||||||
messages.forEach(message => {
|
messages.forEach(message => {
|
||||||
window.MessageController.register(message.id, message);
|
window.MessageCache.__DEPRECATED$register(
|
||||||
|
message.id,
|
||||||
|
message,
|
||||||
|
'loadRecentMediaItems'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const recentMediaItems = messages
|
const recentMediaItems = messages
|
||||||
|
@ -3492,7 +3496,7 @@ export function saveAttachmentFromMessage(
|
||||||
providedAttachment?: AttachmentType
|
providedAttachment?: AttachmentType
|
||||||
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
|
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`saveAttachmentFromMessage: Message ${messageId} missing!`
|
`saveAttachmentFromMessage: Message ${messageId} missing!`
|
||||||
|
@ -3585,7 +3589,7 @@ export function scrollToMessage(
|
||||||
throw new Error('scrollToMessage: No conversation found');
|
throw new Error('scrollToMessage: No conversation found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`scrollToMessage: failed to load message ${messageId}`);
|
throw new Error(`scrollToMessage: failed to load message ${messageId}`);
|
||||||
}
|
}
|
||||||
|
@ -3599,7 +3603,7 @@ export function scrollToMessage(
|
||||||
|
|
||||||
let isInMemory = true;
|
let isInMemory = true;
|
||||||
|
|
||||||
if (!window.MessageController.getById(messageId)) {
|
if (!window.MessageCache.__DEPRECATED$getById(messageId)) {
|
||||||
isInMemory = false;
|
isInMemory = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4009,7 +4013,7 @@ function onConversationOpened(
|
||||||
conversation.onOpenStart();
|
conversation.onOpenStart();
|
||||||
|
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
drop(conversation.loadAndScroll(messageId));
|
drop(conversation.loadAndScroll(messageId));
|
||||||
|
@ -4138,7 +4142,7 @@ function showArchivedConversations(): ShowArchivedConversationsActionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
function doubleCheckMissingQuoteReference(messageId: string): NoopActionType {
|
function doubleCheckMissingQuoteReference(messageId: string): NoopActionType {
|
||||||
const message = window.MessageController.getById(messageId);
|
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||||
if (message) {
|
if (message) {
|
||||||
void message.doubleCheckMissingQuoteReference();
|
void message.doubleCheckMissingQuoteReference();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import * as Errors from '../../types/errors';
|
||||||
import * as SingleServePromise from '../../services/singleServePromise';
|
import * as SingleServePromise from '../../services/singleServePromise';
|
||||||
import * as Stickers from '../../types/Stickers';
|
import * as Stickers from '../../types/Stickers';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
|
||||||
import { getMessagePropsSelector } from '../selectors/message';
|
import { getMessagePropsSelector } from '../selectors/message';
|
||||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||||
import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper';
|
import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper';
|
||||||
|
@ -559,15 +558,12 @@ function toggleForwardMessagesModal(
|
||||||
|
|
||||||
const messagesProps = await Promise.all(
|
const messagesProps = await Promise.all(
|
||||||
messageIds.map(async messageId => {
|
messageIds.map(async messageId => {
|
||||||
const message = await getMessageById(messageId);
|
const messageAttributes = await window.MessageCache.resolveAttributes(
|
||||||
|
'toggleForwardMessagesModal',
|
||||||
|
messageId
|
||||||
|
);
|
||||||
|
|
||||||
if (!message) {
|
const { attachments = [] } = messageAttributes;
|
||||||
throw new Error(
|
|
||||||
`toggleForwardMessagesModal: no message found for ${messageId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachments = message.get('attachments') ?? [];
|
|
||||||
|
|
||||||
if (!attachments.every(isDownloaded)) {
|
if (!attachments.every(isDownloaded)) {
|
||||||
dispatch(
|
dispatch(
|
||||||
|
@ -576,7 +572,7 @@ function toggleForwardMessagesModal(
|
||||||
}
|
}
|
||||||
|
|
||||||
const messagePropsSelector = getMessagePropsSelector(getState());
|
const messagePropsSelector = getMessagePropsSelector(getState());
|
||||||
const messageProps = messagePropsSelector(message.attributes);
|
const messageProps = messagePropsSelector(messageAttributes);
|
||||||
|
|
||||||
return messageProps;
|
return messageProps;
|
||||||
})
|
})
|
||||||
|
@ -765,14 +761,10 @@ function showEditHistoryModal(
|
||||||
messageId: string
|
messageId: string
|
||||||
): ThunkAction<void, RootStateType, unknown, ShowEditHistoryModalActionType> {
|
): ThunkAction<void, RootStateType, unknown, ShowEditHistoryModalActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const message = await getMessageById(messageId);
|
const messageAttributes = await window.MessageCache.resolveAttributes(
|
||||||
|
'showEditHistoryModal',
|
||||||
if (!message) {
|
messageId
|
||||||
log.warn('showEditHistoryModal: no message found');
|
);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const messageAttributes = message.attributes;
|
|
||||||
const nextEditHistoryMessages =
|
const nextEditHistoryMessages =
|
||||||
copyOverMessageAttributesIntoEditHistory(messageAttributes);
|
copyOverMessageAttributesIntoEditHistory(messageAttributes);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import type { ShowToastActionType } from './toast';
|
||||||
import type { StateType as RootStateType } from '../reducer';
|
import type { StateType as RootStateType } from '../reducer';
|
||||||
|
|
||||||
import * as log from '../../logging/log';
|
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 type { MessageAttributesType } from '../../model-types.d';
|
||||||
import { isGIF } from '../../types/Attachment';
|
import { isGIF } from '../../types/Attachment';
|
||||||
import {
|
import {
|
||||||
|
@ -137,7 +137,7 @@ function showLightboxForViewOnceMedia(
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
log.info('showLightboxForViewOnceMedia: attempting to display message');
|
log.info('showLightboxForViewOnceMedia: attempting to display message');
|
||||||
|
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`showLightboxForViewOnceMedia: Message ${messageId} missing!`
|
`showLightboxForViewOnceMedia: Message ${messageId} missing!`
|
||||||
|
@ -232,7 +232,7 @@ function showLightbox(opts: {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const { attachment, messageId } = opts;
|
const { attachment, messageId } = opts;
|
||||||
|
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error(`showLightbox: Message ${messageId} missing!`);
|
throw new Error(`showLightbox: Message ${messageId} missing!`);
|
||||||
}
|
}
|
||||||
|
@ -373,7 +373,7 @@ function showLightboxForAdjacentMessage(
|
||||||
sent_at: sentAt,
|
sent_at: sentAt,
|
||||||
} = media.message;
|
} = media.message;
|
||||||
|
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.warn('showLightboxForAdjacentMessage: original message is gone');
|
log.warn('showLightboxForAdjacentMessage: original message is gone');
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -101,7 +101,11 @@ function loadMediaItems(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
rawMedia.map(async message => {
|
rawMedia.map(async message => {
|
||||||
const { schemaVersion } = 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) {
|
if (schemaVersion && schemaVersion < VERSION_NEEDED_FOR_DISPLAY) {
|
||||||
const upgradedMsgAttributes = await upgradeMessageSchema(message);
|
const upgradedMsgAttributes = await upgradeMessageSchema(message);
|
||||||
|
|
|
@ -36,7 +36,7 @@ import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUnti
|
||||||
import { deleteStoryForEveryone as doDeleteStoryForEveryone } from '../../util/deleteStoryForEveryone';
|
import { deleteStoryForEveryone as doDeleteStoryForEveryone } from '../../util/deleteStoryForEveryone';
|
||||||
import { deleteGroupStoryReplyForEveryone as doDeleteGroupStoryReplyForEveryone } from '../../util/deleteGroupStoryReplyForEveryone';
|
import { deleteGroupStoryReplyForEveryone as doDeleteGroupStoryReplyForEveryone } from '../../util/deleteGroupStoryReplyForEveryone';
|
||||||
import { enqueueReactionForSend } from '../../reactions/enqueueReactionForSend';
|
import { enqueueReactionForSend } from '../../reactions/enqueueReactionForSend';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||||
import { markOnboardingStoryAsRead } from '../../util/markOnboardingStoryAsRead';
|
import { markOnboardingStoryAsRead } from '../../util/markOnboardingStoryAsRead';
|
||||||
import { markViewed } from '../../services/MessageUpdater';
|
import { markViewed } from '../../services/MessageUpdater';
|
||||||
import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
|
import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
|
||||||
|
@ -387,7 +387,7 @@ function markStoryRead(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.warn(`markStoryRead: no message found ${messageId}`);
|
log.warn(`markStoryRead: no message found ${messageId}`);
|
||||||
|
@ -520,7 +520,7 @@ function queueStoryDownload(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = await getMessageById(storyId);
|
const message = await __DEPRECATED$getMessageById(storyId);
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
// We want to ensure that we re-hydrate the story reply context with the
|
// We want to ensure that we re-hydrate the story reply context with the
|
||||||
|
@ -1395,7 +1395,7 @@ function removeAllContactStories(
|
||||||
const messages = (
|
const messages = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
messageIds.map(async messageId => {
|
messageIds.map(async messageId => {
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
log.warn(`${logId}: no message found ${messageId}`);
|
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';
|
} from '../selectors/conversations';
|
||||||
import { getIntl, getTheme, getRegionCode } from '../selectors/user';
|
import { getIntl, getTheme, getRegionCode } from '../selectors/user';
|
||||||
import { getLinkPreview } from '../selectors/linkPreviews';
|
import { getLinkPreview } from '../selectors/linkPreviews';
|
||||||
import { getMessageById } from '../../messages/getMessageById';
|
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import type {
|
import type {
|
||||||
ForwardMessageData,
|
ForwardMessageData,
|
||||||
|
@ -36,6 +35,8 @@ import { SmartCompositionTextArea } from './CompositionTextArea';
|
||||||
import { useToastActions } from '../ducks/toast';
|
import { useToastActions } from '../ducks/toast';
|
||||||
import { hydrateRanges } from '../../types/BodyRange';
|
import { hydrateRanges } from '../../types/BodyRange';
|
||||||
import { isDownloaded } from '../../types/Attachment';
|
import { isDownloaded } from '../../types/Attachment';
|
||||||
|
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
||||||
|
import { strictAssert } from '../../util/assert';
|
||||||
|
|
||||||
function toMessageForwardDraft(
|
function toMessageForwardDraft(
|
||||||
props: ForwardMessagePropsType,
|
props: ForwardMessagePropsType,
|
||||||
|
@ -119,10 +120,10 @@ function SmartForwardMessagesModalInner({
|
||||||
try {
|
try {
|
||||||
const messages = await Promise.all(
|
const messages = await Promise.all(
|
||||||
finalDrafts.map(async (draft): Promise<ForwardMessageData> => {
|
finalDrafts.map(async (draft): Promise<ForwardMessageData> => {
|
||||||
const message = await getMessageById(draft.originalMessageId);
|
const message = await __DEPRECATED$getMessageById(
|
||||||
if (message == null) {
|
draft.originalMessageId
|
||||||
throw new Error('No message found');
|
);
|
||||||
}
|
strictAssert(message, 'no message found');
|
||||||
return {
|
return {
|
||||||
draft,
|
draft,
|
||||||
originalMessage: message.attributes,
|
originalMessage: message.attributes,
|
||||||
|
|
|
@ -83,7 +83,11 @@ describe('Conversations', () => {
|
||||||
forceSave: true,
|
forceSave: true,
|
||||||
ourAci,
|
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 window.Signal.Data.updateConversation(conversation.attributes);
|
||||||
await conversation.updateLastMessage();
|
await conversation.updateLastMessage();
|
||||||
|
|
||||||
|
|
|
@ -5,17 +5,30 @@ import { assert } from 'chai';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import { v4 as generateUuid } from 'uuid';
|
import { v4 as generateUuid } from 'uuid';
|
||||||
|
|
||||||
import { 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 enMessages from '../../../_locales/en/messages.json';
|
||||||
import { SendStatus } from '../../messages/MessageSendState';
|
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 { SignalService as Proto } from '../../protobuf';
|
||||||
|
import { generateAci } from '../../types/ServiceId';
|
||||||
import { getContact } from '../../messages/helpers';
|
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', () => {
|
describe('Message', () => {
|
||||||
const STORAGE_KEYS_TO_RESTORE: Array<keyof StorageAccessType> = [
|
const STORAGE_KEYS_TO_RESTORE: Array<keyof StorageAccessType> = [
|
||||||
|
@ -28,7 +41,7 @@ describe('Message', () => {
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
const attributes = {
|
const attributes = {
|
||||||
type: 'outgoing',
|
type: 'outgoing' as const,
|
||||||
body: 'hi',
|
body: 'hi',
|
||||||
conversationId: 'foo',
|
conversationId: 'foo',
|
||||||
attachments: [],
|
attachments: [],
|
||||||
|
@ -39,12 +52,12 @@ describe('Message', () => {
|
||||||
const me = '+14155555556';
|
const me = '+14155555556';
|
||||||
const ourServiceId = generateAci();
|
const ourServiceId = generateAci();
|
||||||
|
|
||||||
function createMessage(attrs: { [key: string]: unknown }) {
|
function createMessage(attrs: Partial<MessageAttributesType>): MessageModel {
|
||||||
const messages = new window.Whisper.MessageCollection();
|
return new window.Whisper.Message({
|
||||||
return messages.add({
|
id: generateUuid(),
|
||||||
received_at: Date.now(),
|
|
||||||
...attrs,
|
...attrs,
|
||||||
});
|
received_at: Date.now(),
|
||||||
|
} as MessageAttributesType);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMessageAndGetNotificationData(attrs: {
|
function createMessageAndGetNotificationData(attrs: {
|
||||||
|
@ -214,14 +227,12 @@ describe('Message', () => {
|
||||||
|
|
||||||
describe('getContact', () => {
|
describe('getContact', () => {
|
||||||
it('gets outgoing contact', () => {
|
it('gets outgoing contact', () => {
|
||||||
const messages = new window.Whisper.MessageCollection();
|
const message = createMessage(attributes);
|
||||||
const message = messages.add(attributes);
|
|
||||||
assert.exists(getContact(message.attributes));
|
assert.exists(getContact(message.attributes));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets incoming contact', () => {
|
it('gets incoming contact', () => {
|
||||||
const messages = new window.Whisper.MessageCollection();
|
const message = createMessage({
|
||||||
const message = messages.add({
|
|
||||||
type: 'incoming',
|
type: 'incoming',
|
||||||
source,
|
source,
|
||||||
});
|
});
|
||||||
|
@ -287,7 +298,8 @@ describe('Message', () => {
|
||||||
isErased: false,
|
isErased: false,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
contentType: 'image/png',
|
contentType: IMAGE_PNG,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -302,7 +314,8 @@ describe('Message', () => {
|
||||||
isErased: false,
|
isErased: false,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
contentType: 'video/mp4',
|
contentType: VIDEO_MP4,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -317,7 +330,8 @@ describe('Message', () => {
|
||||||
isErased: false,
|
isErased: false,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
contentType: 'text/plain',
|
contentType: LONG_MESSAGE,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -482,7 +496,7 @@ describe('Message', () => {
|
||||||
createMessageAndGetNotificationData({
|
createMessageAndGetNotificationData({
|
||||||
type: 'incoming',
|
type: 'incoming',
|
||||||
source,
|
source,
|
||||||
flags: true,
|
flags: 1,
|
||||||
}),
|
}),
|
||||||
{ text: i18n('icu:sessionEnded') }
|
{ text: i18n('icu:sessionEnded') }
|
||||||
);
|
);
|
||||||
|
@ -493,17 +507,26 @@ describe('Message', () => {
|
||||||
createMessageAndGetNotificationData({
|
createMessageAndGetNotificationData({
|
||||||
type: 'incoming',
|
type: 'incoming',
|
||||||
source,
|
source,
|
||||||
errors: [{}],
|
errors: [new Error()],
|
||||||
}),
|
}),
|
||||||
{ text: i18n('icu:incomingError') }
|
{ text: i18n('icu:incomingError') }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const attachmentTestCases = [
|
const attachmentTestCases: Array<{
|
||||||
|
title: string;
|
||||||
|
attachment: AttachmentType;
|
||||||
|
expectedResult: {
|
||||||
|
text: string;
|
||||||
|
emoji: string;
|
||||||
|
bodyRanges?: Array<RawBodyRange>;
|
||||||
|
};
|
||||||
|
}> = [
|
||||||
{
|
{
|
||||||
title: 'GIF',
|
title: 'GIF',
|
||||||
attachment: {
|
attachment: {
|
||||||
contentType: 'image/gif',
|
contentType: IMAGE_GIF,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
expectedResult: {
|
expectedResult: {
|
||||||
text: 'GIF',
|
text: 'GIF',
|
||||||
|
@ -514,7 +537,8 @@ describe('Message', () => {
|
||||||
{
|
{
|
||||||
title: 'photo',
|
title: 'photo',
|
||||||
attachment: {
|
attachment: {
|
||||||
contentType: 'image/png',
|
contentType: IMAGE_PNG,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
expectedResult: {
|
expectedResult: {
|
||||||
text: 'Photo',
|
text: 'Photo',
|
||||||
|
@ -525,7 +549,8 @@ describe('Message', () => {
|
||||||
{
|
{
|
||||||
title: 'video',
|
title: 'video',
|
||||||
attachment: {
|
attachment: {
|
||||||
contentType: 'video/mp4',
|
contentType: VIDEO_MP4,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
expectedResult: {
|
expectedResult: {
|
||||||
text: 'Video',
|
text: 'Video',
|
||||||
|
@ -536,8 +561,9 @@ describe('Message', () => {
|
||||||
{
|
{
|
||||||
title: 'voice message',
|
title: 'voice message',
|
||||||
attachment: {
|
attachment: {
|
||||||
contentType: 'audio/ogg',
|
contentType: AUDIO_MP3,
|
||||||
flags: Proto.AttachmentPointer.Flags.VOICE_MESSAGE,
|
flags: Proto.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
expectedResult: {
|
expectedResult: {
|
||||||
text: 'Voice Message',
|
text: 'Voice Message',
|
||||||
|
@ -548,8 +574,9 @@ describe('Message', () => {
|
||||||
{
|
{
|
||||||
title: 'audio message',
|
title: 'audio message',
|
||||||
attachment: {
|
attachment: {
|
||||||
contentType: 'audio/ogg',
|
contentType: AUDIO_MP3,
|
||||||
fileName: 'audio.ogg',
|
fileName: 'audio.mp3',
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
expectedResult: {
|
expectedResult: {
|
||||||
text: 'Audio Message',
|
text: 'Audio Message',
|
||||||
|
@ -560,7 +587,8 @@ describe('Message', () => {
|
||||||
{
|
{
|
||||||
title: 'plain text',
|
title: 'plain text',
|
||||||
attachment: {
|
attachment: {
|
||||||
contentType: 'text/plain',
|
contentType: LONG_MESSAGE,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
expectedResult: {
|
expectedResult: {
|
||||||
text: 'File',
|
text: 'File',
|
||||||
|
@ -571,7 +599,8 @@ describe('Message', () => {
|
||||||
{
|
{
|
||||||
title: 'unspecified-type',
|
title: 'unspecified-type',
|
||||||
attachment: {
|
attachment: {
|
||||||
contentType: null,
|
contentType: APPLICATION_JSON,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
expectedResult: {
|
expectedResult: {
|
||||||
text: 'File',
|
text: 'File',
|
||||||
|
@ -600,7 +629,8 @@ describe('Message', () => {
|
||||||
attachments: [
|
attachments: [
|
||||||
attachment,
|
attachment,
|
||||||
{
|
{
|
||||||
contentType: 'text/html',
|
contentType: TEXT_ATTACHMENT,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -671,7 +701,8 @@ describe('Message', () => {
|
||||||
source,
|
source,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
contentType: 'image/png',
|
contentType: IMAGE_PNG,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}).getNotificationText(),
|
}).getNotificationText(),
|
||||||
|
@ -699,7 +730,8 @@ describe('Message', () => {
|
||||||
source,
|
source,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
contentType: 'image/png',
|
contentType: IMAGE_PNG,
|
||||||
|
size: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}).getNotificationText(),
|
}).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 storyId = generateUuid();
|
||||||
const messageAttributes = getStoryMessage(storyId);
|
const messageAttributes = getStoryMessage(storyId);
|
||||||
|
|
||||||
window.MessageController.register(storyId, messageAttributes);
|
window.MessageCache.__DEPRECATED$register(
|
||||||
|
storyId,
|
||||||
|
messageAttributes,
|
||||||
|
'test'
|
||||||
|
);
|
||||||
|
|
||||||
const dispatch = sinon.spy();
|
const dispatch = sinon.spy();
|
||||||
await queueStoryDownload(storyId)(dispatch, getEmptyRootState, null);
|
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();
|
const dispatch = sinon.spy();
|
||||||
await queueStoryDownload(storyId)(dispatch, getEmptyRootState, null);
|
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();
|
const dispatch = sinon.spy();
|
||||||
await queueStoryDownload(storyId)(dispatch, getEmptyRootState, null);
|
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();
|
const dispatch = sinon.spy();
|
||||||
await queueStoryDownload(storyId)(dispatch, getState, null);
|
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();
|
const dispatch = sinon.spy();
|
||||||
await queueStoryDownload(storyId)(dispatch, getState, null);
|
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 type { App } from '../playwright';
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
|
|
||||||
export const debug = createDebug('mock:test:edit');
|
export const debug = createDebug('mock:test:stories');
|
||||||
|
|
||||||
const IdentifierType = Proto.ManifestRecord.Identifier.Type;
|
const IdentifierType = Proto.ManifestRecord.Identifier.Type;
|
||||||
|
|
||||||
|
@ -236,7 +236,10 @@ describe('story/messaging', function unknownContacts() {
|
||||||
}
|
}
|
||||||
const sentAt = new Date(time).valueOf();
|
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(
|
await contacts[0].sendRaw(
|
||||||
desktop,
|
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);
|
log.info('saveCallHistory: Saved call history message:', id);
|
||||||
|
|
||||||
const model = window.MessageController.register(
|
const model = window.MessageCache.__DEPRECATED$register(
|
||||||
id,
|
id,
|
||||||
new window.Whisper.Message({
|
new window.Whisper.Message({
|
||||||
...message,
|
...message,
|
||||||
id,
|
id,
|
||||||
})
|
}),
|
||||||
|
'callDisposition'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (callHistory.direction === CallDirection.Outgoing) {
|
if (callHistory.direction === CallDirection.Outgoing) {
|
||||||
|
@ -986,7 +987,7 @@ export async function clearCallHistoryDataAndSync(): Promise<void> {
|
||||||
const messageIds = await window.Signal.Data.clearCallHistory(timestamp);
|
const messageIds = await window.Signal.Data.clearCallHistory(timestamp);
|
||||||
|
|
||||||
messageIds.forEach(messageId => {
|
messageIds.forEach(messageId => {
|
||||||
const message = window.MessageController.getById(messageId);
|
const message = window.MessageCache.__DEPRECATED$getById(messageId);
|
||||||
const conversation = message?.getConversation();
|
const conversation = message?.getConversation();
|
||||||
if (message == null || conversation == null) {
|
if (message == null || conversation == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -996,7 +997,7 @@ export async function clearCallHistoryDataAndSync(): Promise<void> {
|
||||||
message.get('conversationId')
|
message.get('conversationId')
|
||||||
);
|
);
|
||||||
conversation.debouncedUpdateLastMessage();
|
conversation.debouncedUpdateLastMessage();
|
||||||
window.MessageController.unregister(messageId);
|
window.MessageCache.__DEPRECATED$unregister(messageId);
|
||||||
});
|
});
|
||||||
|
|
||||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function cleanupMessageFromMemory(message: MessageAttributesType): void {
|
||||||
const parentConversation = window.ConversationController.get(conversationId);
|
const parentConversation = window.ConversationController.get(conversationId);
|
||||||
parentConversation?.debouncedUpdateLastMessage();
|
parentConversation?.debouncedUpdateLastMessage();
|
||||||
|
|
||||||
window.MessageController.unregister(id);
|
window.MessageCache.__DEPRECATED$unregister(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanupStoryReplies(
|
async function cleanupStoryReplies(
|
||||||
|
@ -72,9 +72,10 @@ async function cleanupStoryReplies(
|
||||||
// Cleanup all group replies
|
// Cleanup all group replies
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
replies.map(reply => {
|
replies.map(reply => {
|
||||||
const replyMessageModel = window.MessageController.register(
|
const replyMessageModel = window.MessageCache.__DEPRECATED$register(
|
||||||
reply.id,
|
reply.id,
|
||||||
reply
|
reply,
|
||||||
|
'cleanupStoryReplies/group'
|
||||||
);
|
);
|
||||||
return replyMessageModel.eraseContents();
|
return replyMessageModel.eraseContents();
|
||||||
})
|
})
|
||||||
|
@ -83,7 +84,11 @@ async function cleanupStoryReplies(
|
||||||
// Refresh the storyReplyContext data for 1:1 conversations
|
// Refresh the storyReplyContext data for 1:1 conversations
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
replies.map(async reply => {
|
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');
|
model.unset('storyReplyContext');
|
||||||
await model.hydrateStoryContext(story, { shouldSave: true });
|
await model.hydrateStoryContext(story, { shouldSave: true });
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
|
|
||||||
import { DAY } from './durations';
|
import { DAY } from './durations';
|
||||||
import { sendDeleteForEveryoneMessage } from './sendDeleteForEveryoneMessage';
|
import { sendDeleteForEveryoneMessage } from './sendDeleteForEveryoneMessage';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
|
|
||||||
export async function deleteGroupStoryReplyForEveryone(
|
export async function deleteGroupStoryReplyForEveryone(
|
||||||
replyMessageId: string
|
replyMessageId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const messageModel = await getMessageById(replyMessageId);
|
const messageModel = await __DEPRECATED$getMessageById(replyMessageId);
|
||||||
|
|
||||||
if (!messageModel) {
|
if (!messageModel) {
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
import { onStoryRecipientUpdate } from './onStoryRecipientUpdate';
|
import { onStoryRecipientUpdate } from './onStoryRecipientUpdate';
|
||||||
import { sendDeleteForEveryoneMessage } from './sendDeleteForEveryoneMessage';
|
import { sendDeleteForEveryoneMessage } from './sendDeleteForEveryoneMessage';
|
||||||
import { isGroupV2 } from './whatTypeOfConversation';
|
import { isGroupV2 } from './whatTypeOfConversation';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||||
import { strictAssert } from './assert';
|
import { strictAssert } from './assert';
|
||||||
import { repeat, zipObject } from './iterables';
|
import { repeat, zipObject } from './iterables';
|
||||||
import { isOlderThan } from './timestamp';
|
import { isOlderThan } from './timestamp';
|
||||||
|
@ -46,7 +46,7 @@ export async function deleteStoryForEveryone(
|
||||||
}
|
}
|
||||||
|
|
||||||
const logId = `deleteStoryForEveryone(${story.messageId})`;
|
const logId = `deleteStoryForEveryone(${story.messageId})`;
|
||||||
const message = await getMessageById(story.messageId);
|
const message = await __DEPRECATED$getMessageById(story.messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error('Story not found');
|
throw new Error('Story not found');
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
|
||||||
import { calculateExpirationTimestamp } from './expirationTimer';
|
import { calculateExpirationTimestamp } from './expirationTimer';
|
||||||
import { DAY } from './durations';
|
import { DAY } from './durations';
|
||||||
|
|
||||||
|
@ -16,23 +15,23 @@ export async function findAndDeleteOnboardingStoryIfExists(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasExpired = await (async () => {
|
const hasExpired = await (async () => {
|
||||||
for (const id of existingOnboardingStoryMessageIds) {
|
const [storyId] = existingOnboardingStoryMessageIds;
|
||||||
// eslint-disable-next-line no-await-in-loop
|
try {
|
||||||
const message = await getMessageById(id);
|
const messageAttributes = await window.MessageCache.resolveAttributes(
|
||||||
if (!message) {
|
'findAndDeleteOnboardingStoryIfExists',
|
||||||
continue;
|
storyId
|
||||||
}
|
);
|
||||||
|
|
||||||
const expires = calculateExpirationTimestamp(message.attributes) ?? 0;
|
const expires = calculateExpirationTimestamp(messageAttributes) ?? 0;
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const isExpired = expires < now;
|
const isExpired = expires < now;
|
||||||
const needsRepair = expires > now + 2 * DAY;
|
const needsRepair = expires > now + 2 * DAY;
|
||||||
|
|
||||||
return isExpired || needsRepair;
|
return isExpired || needsRepair;
|
||||||
|
} catch {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (!hasExpired) {
|
if (!hasExpired) {
|
||||||
|
|
|
@ -31,7 +31,8 @@ export async function findStoryMessages(
|
||||||
const ourConversationId =
|
const ourConversationId =
|
||||||
window.ConversationController.getOurConversationIdOrThrow();
|
window.ConversationController.getOurConversationIdOrThrow();
|
||||||
|
|
||||||
const inMemoryMessages = window.MessageController.filterBySentAt(sentAt);
|
const inMemoryMessages =
|
||||||
|
window.MessageCache.__DEPRECATED$filterBySentAt(sentAt);
|
||||||
const matchingMessages = [
|
const matchingMessages = [
|
||||||
...filter(inMemoryMessages, item =>
|
...filter(inMemoryMessages, item =>
|
||||||
isStoryAMatch(
|
isStoryAMatch(
|
||||||
|
@ -60,7 +61,11 @@ export async function findStoryMessages(
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = found.map(attributes =>
|
const result = found.map(attributes =>
|
||||||
window.MessageController.register(attributes.id, attributes)
|
window.MessageCache.__DEPRECATED$register(
|
||||||
|
attributes.id,
|
||||||
|
attributes,
|
||||||
|
'findStoryMessages'
|
||||||
|
)
|
||||||
);
|
);
|
||||||
return result;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainMessageModel = window.MessageController.register(
|
const mainMessageModel = window.MessageCache.__DEPRECATED$register(
|
||||||
mainMessage.id,
|
mainMessage.id,
|
||||||
mainMessage
|
mainMessage,
|
||||||
|
'handleEditMessage'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Pull out the edit history from the main message. If this is the first edit
|
// 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",
|
"updated": "2020-10-13T18:36:57.012Z",
|
||||||
"reasonDetail": "necessary for quill"
|
"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",
|
"rule": "DOM-innerHTML",
|
||||||
"path": "node_modules/quill/dist/quill.js",
|
"path": "node_modules/quill/dist/quill.js",
|
||||||
|
@ -1468,6 +1447,27 @@
|
||||||
"updated": "2020-10-13T18:36:57.012Z",
|
"updated": "2020-10-13T18:36:57.012Z",
|
||||||
"reasonDetail": "necessary for quill"
|
"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",
|
"rule": "DOM-innerHTML",
|
||||||
"path": "node_modules/quill/dist/quill.min.js",
|
"path": "node_modules/quill/dist/quill.min.js",
|
||||||
|
|
|
@ -102,7 +102,9 @@ export async function markConversationRead(
|
||||||
|
|
||||||
const allReadMessagesSync = allUnreadMessages
|
const allReadMessagesSync = allUnreadMessages
|
||||||
.map(messageSyncData => {
|
.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
|
// we update the in-memory MessageModel with the fresh database call data
|
||||||
if (message) {
|
if (message) {
|
||||||
message.set(omit(messageSyncData, 'originalReadStatus'));
|
message.set(omit(messageSyncData, 'originalReadStatus'));
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||||
import { isNotNil } from './isNotNil';
|
import { isNotNil } from './isNotNil';
|
||||||
import { DurationInSeconds } from './durations';
|
import { DurationInSeconds } from './durations';
|
||||||
import { markViewed } from '../services/MessageUpdater';
|
import { markViewed } from '../services/MessageUpdater';
|
||||||
|
@ -19,7 +19,7 @@ export async function markOnboardingStoryAsRead(): Promise<boolean> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = await Promise.all(
|
const messages = await Promise.all(
|
||||||
existingOnboardingStoryMessageIds.map(getMessageById)
|
existingOnboardingStoryMessageIds.map(__DEPRECATED$getMessageById)
|
||||||
);
|
);
|
||||||
|
|
||||||
const storyReadDate = Date.now();
|
const storyReadDate = Date.now();
|
||||||
|
|
|
@ -161,7 +161,11 @@ export async function onStoryRecipientUpdate(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = window.MessageController.register(item.id, item);
|
const message = window.MessageCache.__DEPRECATED$register(
|
||||||
|
item.id,
|
||||||
|
item,
|
||||||
|
'onStoryRecipientUpdate'
|
||||||
|
);
|
||||||
|
|
||||||
const sendStateConversationIds = new Set(
|
const sendStateConversationIds = new Set(
|
||||||
Object.keys(nextSendStateByConversationId)
|
Object.keys(nextSendStateByConversationId)
|
||||||
|
|
|
@ -30,7 +30,7 @@ import type { StickerType } from '../types/Stickers';
|
||||||
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
||||||
import { isNotNil } from './isNotNil';
|
import { isNotNil } from './isNotNil';
|
||||||
|
|
||||||
type ReturnType = {
|
export type MessageAttachmentsDownloadedType = {
|
||||||
bodyAttachment?: AttachmentType;
|
bodyAttachment?: AttachmentType;
|
||||||
attachments: Array<AttachmentType>;
|
attachments: Array<AttachmentType>;
|
||||||
editHistory?: Array<EditHistoryType>;
|
editHistory?: Array<EditHistoryType>;
|
||||||
|
@ -45,7 +45,7 @@ type ReturnType = {
|
||||||
// count then you'll also have to modify ./hasAttachmentsDownloads
|
// count then you'll also have to modify ./hasAttachmentsDownloads
|
||||||
export async function queueAttachmentDownloads(
|
export async function queueAttachmentDownloads(
|
||||||
message: MessageAttributesType
|
message: MessageAttributesType
|
||||||
): Promise<ReturnType | undefined> {
|
): Promise<MessageAttachmentsDownloadedType | undefined> {
|
||||||
const attachmentsToQueue = message.attachments || [];
|
const attachmentsToQueue = message.attachments || [];
|
||||||
const messageId = message.id;
|
const messageId = message.id;
|
||||||
const idForLogging = getMessageIdForLogging(message);
|
const idForLogging = getMessageIdForLogging(message);
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
getConversationIdForLogging,
|
getConversationIdForLogging,
|
||||||
getMessageIdForLogging,
|
getMessageIdForLogging,
|
||||||
} from './idForLogging';
|
} from './idForLogging';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||||
import { getRecipientConversationIds } from './getRecipientConversationIds';
|
import { getRecipientConversationIds } from './getRecipientConversationIds';
|
||||||
import { getRecipients } from './getRecipients';
|
import { getRecipients } from './getRecipients';
|
||||||
import { repeat, zipObject } from './iterables';
|
import { repeat, zipObject } from './iterables';
|
||||||
|
@ -35,7 +35,7 @@ export async function sendDeleteForEveryoneMessage(
|
||||||
timestamp: targetTimestamp,
|
timestamp: targetTimestamp,
|
||||||
id: messageId,
|
id: messageId,
|
||||||
} = options;
|
} = options;
|
||||||
const message = await getMessageById(messageId);
|
const message = await __DEPRECATED$getMessageById(messageId);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error('sendDeleteForEveryoneMessage: Cannot find message!');
|
throw new Error('sendDeleteForEveryoneMessage: Cannot find message!');
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {
|
||||||
import { concat, filter, map, repeat, zipObject, find } from './iterables';
|
import { concat, filter, map, repeat, zipObject, find } from './iterables';
|
||||||
import { getConversationIdForLogging } from './idForLogging';
|
import { getConversationIdForLogging } from './idForLogging';
|
||||||
import { isQuoteAMatch } from '../messages/helpers';
|
import { isQuoteAMatch } from '../messages/helpers';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||||
import { handleEditMessage } from './handleEditMessage';
|
import { handleEditMessage } from './handleEditMessage';
|
||||||
import { incrementMessageCounter } from './incrementMessageCounter';
|
import { incrementMessageCounter } from './incrementMessageCounter';
|
||||||
import { isGroupV1 } from './whatTypeOfConversation';
|
import { isGroupV1 } from './whatTypeOfConversation';
|
||||||
|
@ -64,7 +64,7 @@ export async function sendEditedMessage(
|
||||||
conversation.attributes
|
conversation.attributes
|
||||||
)})`;
|
)})`;
|
||||||
|
|
||||||
const targetMessage = await getMessageById(targetMessageId);
|
const targetMessage = await __DEPRECATED$getMessageById(targetMessageId);
|
||||||
strictAssert(targetMessage, 'could not find message to edit');
|
strictAssert(targetMessage, 'could not find message to edit');
|
||||||
|
|
||||||
if (isGroupV1(conversation.attributes)) {
|
if (isGroupV1(conversation.attributes)) {
|
||||||
|
|
|
@ -311,7 +311,11 @@ export async function sendStoryMessage(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
distributionListMessages.map(messageAttributes => {
|
distributionListMessages.map(messageAttributes => {
|
||||||
const model = new window.Whisper.Message(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 });
|
void ourConversation.addSingleMessage(model, { isJustSent: true });
|
||||||
|
|
||||||
|
@ -361,7 +365,11 @@ export async function sendStoryMessage(
|
||||||
},
|
},
|
||||||
async jobToInsert => {
|
async jobToInsert => {
|
||||||
const model = new window.Whisper.Message(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'
|
||||||
|
);
|
||||||
|
|
||||||
const conversation = message.getConversation();
|
const conversation = message.getConversation();
|
||||||
void conversation?.addSingleMessage(model, { isJustSent: true });
|
void conversation?.addSingleMessage(model, { isJustSent: true });
|
||||||
|
|
|
@ -2,22 +2,24 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ConversationModel } from '../models/conversations';
|
import type { ConversationModel } from '../models/conversations';
|
||||||
import type { MessageModel } from '../models/messages';
|
import type { MessageAttributesType } from '../model-types.d';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import dataInterface from '../sql/Client';
|
import dataInterface from '../sql/Client';
|
||||||
import { isGroup } from './whatTypeOfConversation';
|
import { isGroup } from './whatTypeOfConversation';
|
||||||
import { isMessageUnread } from './isMessageUnread';
|
import { isMessageUnread } from './isMessageUnread';
|
||||||
|
|
||||||
export async function shouldReplyNotifyUser(
|
export async function shouldReplyNotifyUser(
|
||||||
message: MessageModel,
|
messageAttributes: Readonly<
|
||||||
|
Pick<MessageAttributesType, 'readStatus' | 'storyId'>
|
||||||
|
>,
|
||||||
conversation: ConversationModel
|
conversation: ConversationModel
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// Don't notify if the message has already been read
|
// Don't notify if the message has already been read
|
||||||
if (!isMessageUnread(message.attributes)) {
|
if (!isMessageUnread(messageAttributes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storyId = message.get('storyId');
|
const { storyId } = messageAttributes;
|
||||||
|
|
||||||
// If this is not a reply to a story, always notify.
|
// If this is not a reply to a story, always notify.
|
||||||
if (storyId == null) {
|
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 { assert } from 'chai';
|
||||||
import type { PhoneNumber, PhoneNumberFormat } from 'google-libphonenumber';
|
import type { PhoneNumber, PhoneNumberFormat } from 'google-libphonenumber';
|
||||||
|
|
||||||
import type {
|
import type { ConversationModelCollectionType } from './model-types.d';
|
||||||
ConversationModelCollectionType,
|
|
||||||
MessageModelCollectionType,
|
|
||||||
} from './model-types.d';
|
|
||||||
import type { textsecure } from './textsecure';
|
import type { textsecure } from './textsecure';
|
||||||
import type { Storage } from './textsecure/Storage';
|
import type { Storage } from './textsecure/Storage';
|
||||||
import type {
|
import type {
|
||||||
|
@ -33,7 +30,6 @@ import type { LocalizerType, ThemeType } from './types/Util';
|
||||||
import type { Receipt } from './types/Receipt';
|
import type { Receipt } from './types/Receipt';
|
||||||
import type { ConversationController } from './ConversationController';
|
import type { ConversationController } from './ConversationController';
|
||||||
import type { ReduxActions } from './state/types';
|
import type { ReduxActions } from './state/types';
|
||||||
import type { createStore } from './state/createStore';
|
|
||||||
import type { createApp } from './state/roots/createApp';
|
import type { createApp } from './state/roots/createApp';
|
||||||
import type Data from './sql/Client';
|
import type Data from './sql/Client';
|
||||||
import type { MessageModel } from './models/messages';
|
import type { MessageModel } from './models/messages';
|
||||||
|
@ -43,7 +39,7 @@ import type { ConfirmationDialog } from './components/ConfirmationDialog';
|
||||||
import type { SignalProtocolStore } from './SignalProtocolStore';
|
import type { SignalProtocolStore } from './SignalProtocolStore';
|
||||||
import type { SocketStatus } from './types/SocketStatus';
|
import type { SocketStatus } from './types/SocketStatus';
|
||||||
import type SyncRequest from './textsecure/SyncRequest';
|
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 { StateType } from './state/reducer';
|
||||||
import type { SystemTraySetting } from './types/SystemTraySetting';
|
import type { SystemTraySetting } from './types/SystemTraySetting';
|
||||||
import type { Address } from './types/Address';
|
import type { Address } from './types/Address';
|
||||||
|
@ -164,7 +160,6 @@ export type SignalCoreType = {
|
||||||
};
|
};
|
||||||
OS: OSType;
|
OS: OSType;
|
||||||
State: {
|
State: {
|
||||||
createStore: typeof createStore;
|
|
||||||
Roots: {
|
Roots: {
|
||||||
createApp: typeof createApp;
|
createApp: typeof createApp;
|
||||||
};
|
};
|
||||||
|
@ -235,7 +230,7 @@ declare global {
|
||||||
ConversationController: ConversationController;
|
ConversationController: ConversationController;
|
||||||
Events: IPCEventsType;
|
Events: IPCEventsType;
|
||||||
FontFace: typeof FontFace;
|
FontFace: typeof FontFace;
|
||||||
MessageController: MessageController;
|
MessageCache: MessageCache;
|
||||||
SignalProtocolStore: typeof SignalProtocolStore;
|
SignalProtocolStore: typeof SignalProtocolStore;
|
||||||
WebAPI: WebAPIConnectType;
|
WebAPI: WebAPIConnectType;
|
||||||
Whisper: WhisperType;
|
Whisper: WhisperType;
|
||||||
|
@ -277,10 +272,10 @@ declare global {
|
||||||
RETRY_DELAY: boolean;
|
RETRY_DELAY: boolean;
|
||||||
assert: typeof assert;
|
assert: typeof assert;
|
||||||
testUtilities: {
|
testUtilities: {
|
||||||
|
debug: (info: unknown) => void;
|
||||||
|
initialize: () => Promise<void>;
|
||||||
onComplete: (info: unknown) => void;
|
onComplete: (info: unknown) => void;
|
||||||
prepareTests: () => void;
|
prepareTests: () => void;
|
||||||
installMessageController: () => void;
|
|
||||||
initializeMessageCounter: () => Promise<void>;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +303,6 @@ export type WhisperType = {
|
||||||
Conversation: typeof ConversationModel;
|
Conversation: typeof ConversationModel;
|
||||||
ConversationCollection: typeof ConversationModelCollectionType;
|
ConversationCollection: typeof ConversationModelCollectionType;
|
||||||
Message: typeof MessageModel;
|
Message: typeof MessageModel;
|
||||||
MessageCollection: typeof MessageModelCollectionType;
|
|
||||||
|
|
||||||
deliveryReceiptQueue: PQueue;
|
deliveryReceiptQueue: PQueue;
|
||||||
deliveryReceiptBatcher: BatcherType<Receipt>;
|
deliveryReceiptBatcher: BatcherType<Receipt>;
|
||||||
|
|
|
@ -10,18 +10,49 @@ import { sync } from 'fast-glob';
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
|
||||||
import { getSignalProtocolStore } from '../../SignalProtocolStore';
|
import { getSignalProtocolStore } from '../../SignalProtocolStore';
|
||||||
import { MessageController } from '../../util/MessageController';
|
import { initMessageCleanup } from '../../services/messageStateCleanup';
|
||||||
import { initializeMessageCounter } from '../../util/incrementMessageCounter';
|
import { initializeMessageCounter } from '../../util/incrementMessageCounter';
|
||||||
|
import { initializeRedux } from '../../state/initializeRedux';
|
||||||
|
import * as Stickers from '../../types/Stickers';
|
||||||
|
|
||||||
window.assert = assert;
|
window.assert = assert;
|
||||||
|
|
||||||
// This is a hack to let us run TypeScript tests in the renderer process. See the
|
// 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 = {
|
window.testUtilities = {
|
||||||
|
debug(info) {
|
||||||
|
return ipc.invoke('ci:test-electron:debug', info);
|
||||||
|
},
|
||||||
|
|
||||||
onComplete(info) {
|
onComplete(info) {
|
||||||
return ipc.invoke('ci:test-electron:done', 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() {
|
prepareTests() {
|
||||||
console.log('Preparing tests...');
|
console.log('Preparing tests...');
|
||||||
sync('../../test-{both,electron}/**/*_test.js', {
|
sync('../../test-{both,electron}/**/*_test.js', {
|
||||||
|
@ -29,12 +60,6 @@ window.testUtilities = {
|
||||||
cwd: __dirname,
|
cwd: __dirname,
|
||||||
}).forEach(require);
|
}).forEach(require);
|
||||||
},
|
},
|
||||||
installMessageController() {
|
|
||||||
MessageController.install();
|
|
||||||
},
|
|
||||||
initializeMessageCounter() {
|
|
||||||
return initializeMessageCounter();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.getSignalProtocolStore = getSignalProtocolStore;
|
window.getSignalProtocolStore = getSignalProtocolStore;
|
||||||
|
|
|
@ -13,11 +13,11 @@ import './phase3-post-signal';
|
||||||
import './phase4-test';
|
import './phase4-test';
|
||||||
import '../../backbone/reliable_trigger';
|
import '../../backbone/reliable_trigger';
|
||||||
|
|
||||||
|
import type { CdsLookupOptionsType } from '../../textsecure/WebAPI';
|
||||||
import type { FeatureFlagType } from '../../window.d';
|
import type { FeatureFlagType } from '../../window.d';
|
||||||
import type { StorageAccessType } from '../../types/Storage.d';
|
import type { StorageAccessType } from '../../types/Storage.d';
|
||||||
import type { CdsLookupOptionsType } from '../../textsecure/WebAPI';
|
|
||||||
import { start as startConversationController } from '../../ConversationController';
|
import { start as startConversationController } from '../../ConversationController';
|
||||||
import { MessageController } from '../../util/MessageController';
|
import { initMessageCleanup } from '../../services/messageStateCleanup';
|
||||||
import { Environment, getEnvironment } from '../../environment';
|
import { Environment, getEnvironment } from '../../environment';
|
||||||
import { isProduction } from '../../util/version';
|
import { isProduction } from '../../util/version';
|
||||||
import { ipcInvoke } from '../../sql/channels';
|
import { ipcInvoke } from '../../sql/channels';
|
||||||
|
@ -43,7 +43,7 @@ if (window.SignalContext.config.proxyUrl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Whisper.events = clone(window.Backbone.Events);
|
window.Whisper.events = clone(window.Backbone.Events);
|
||||||
MessageController.install();
|
initMessageCleanup();
|
||||||
startConversationController();
|
startConversationController();
|
||||||
|
|
||||||
if (!isProduction(window.SignalContext.getVersion())) {
|
if (!isProduction(window.SignalContext.getVersion())) {
|
||||||
|
@ -51,7 +51,8 @@ if (!isProduction(window.SignalContext.getVersion())) {
|
||||||
cdsLookup: (options: CdsLookupOptionsType) =>
|
cdsLookup: (options: CdsLookupOptionsType) =>
|
||||||
window.textsecure.server?.cdsLookup(options),
|
window.textsecure.server?.cdsLookup(options),
|
||||||
getConversation: (id: string) => window.ConversationController.get(id),
|
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(),
|
getReduxState: () => window.reduxStore.getState(),
|
||||||
getSfuUrl: () => window.Signal.Services.calling._sfuUrl,
|
getSfuUrl: () => window.Signal.Services.calling._sfuUrl,
|
||||||
getStorageItem: (name: keyof StorageAccessType) => window.storage.get(name),
|
getStorageItem: (name: keyof StorageAccessType) => window.storage.get(name),
|
||||||
|
|
Loading…
Add table
Reference in a new issue