No Backbone in data layer; server/client interfaces are now similar
This commit is contained in:
parent
064bbfe97a
commit
34fd945f83
31 changed files with 573 additions and 1021 deletions
|
@ -11,6 +11,7 @@ import type {
|
|||
ConversationAttributesTypeType,
|
||||
} from './model-types.d';
|
||||
import type { ConversationModel } from './models/conversations';
|
||||
import { getContactId } from './messages/helpers';
|
||||
import { maybeDeriveGroupV2Id } from './groups';
|
||||
import { assert } from './util/assert';
|
||||
import { map, reduce } from './util/iterables';
|
||||
|
@ -676,9 +677,7 @@ export class ConversationController {
|
|||
log.warn(
|
||||
'combineConversations: Delete the obsolete conversation from the database'
|
||||
);
|
||||
await removeConversation(obsoleteId, {
|
||||
Conversation: window.Whisper.Conversation,
|
||||
});
|
||||
await removeConversation(obsoleteId);
|
||||
|
||||
log.warn('combineConversations: Update messages table');
|
||||
await migrateConversationMessages(obsoleteId, currentId);
|
||||
|
@ -714,13 +713,11 @@ export class ConversationController {
|
|||
targetFromId: string,
|
||||
targetTimestamp: number
|
||||
): Promise<ConversationModel | null | undefined> {
|
||||
const messages = await getMessagesBySentAt(targetTimestamp, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
});
|
||||
const targetMessage = messages.find(m => m.getContactId() === targetFromId);
|
||||
const messages = await getMessagesBySentAt(targetTimestamp);
|
||||
const targetMessage = messages.find(m => getContactId(m) === targetFromId);
|
||||
|
||||
if (targetMessage) {
|
||||
return targetMessage.getConversation();
|
||||
return this.get(targetMessage.conversationId);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -729,9 +726,7 @@ export class ConversationController {
|
|||
async getAllGroupsInvolvingUuid(
|
||||
uuid: UUID
|
||||
): Promise<Array<ConversationModel>> {
|
||||
const groups = await getAllGroupsInvolvingUuid(uuid.toString(), {
|
||||
ConversationCollection: window.Whisper.ConversationCollection,
|
||||
});
|
||||
const groups = await getAllGroupsInvolvingUuid(uuid.toString());
|
||||
return groups.map(group => {
|
||||
const existing = this.get(group.id);
|
||||
if (existing) {
|
||||
|
@ -767,13 +762,11 @@ export class ConversationController {
|
|||
}
|
||||
|
||||
try {
|
||||
const collection = await getAllConversations({
|
||||
ConversationCollection: window.Whisper.ConversationCollection,
|
||||
});
|
||||
const collection = await getAllConversations();
|
||||
|
||||
// Get rid of temporary conversations
|
||||
const temporaryConversations = collection.filter(conversation =>
|
||||
Boolean(conversation.get('isTemporary'))
|
||||
Boolean(conversation.isTemporary)
|
||||
);
|
||||
|
||||
if (temporaryConversations.length) {
|
||||
|
@ -788,16 +781,14 @@ export class ConversationController {
|
|||
});
|
||||
queue.addAll(
|
||||
temporaryConversations.map(item => async () => {
|
||||
await removeConversation(item.id, {
|
||||
Conversation: window.Whisper.Conversation,
|
||||
});
|
||||
await removeConversation(item.id);
|
||||
})
|
||||
);
|
||||
await queue.onIdle();
|
||||
|
||||
// Hydrate the final set of conversations
|
||||
this._conversations.add(
|
||||
collection.filter(conversation => !conversation.get('isTemporary'))
|
||||
collection.filter(conversation => !conversation.isTemporary)
|
||||
);
|
||||
|
||||
this._initialFetchComplete = true;
|
||||
|
|
|
@ -44,6 +44,7 @@ import { routineProfileRefresh } from './routineProfileRefresh';
|
|||
import { isMoreRecentThan, isOlderThan, toDayMillis } from './util/timestamp';
|
||||
import { isValidReactionEmoji } from './reactions/isValidReactionEmoji';
|
||||
import type { ConversationModel } from './models/conversations';
|
||||
import { getContact } from './messages/helpers';
|
||||
import { getMessageById } from './messages/getMessageById';
|
||||
import { createBatcher } from './util/batcher';
|
||||
import { updateConversationsWithUuidLookup } from './updateConversationsWithUuidLookup';
|
||||
|
@ -2841,7 +2842,7 @@ export async function startApp(): Promise<void> {
|
|||
isIncoming(message.attributes) &&
|
||||
!message.get('unidentifiedDeliveryReceived')
|
||||
) {
|
||||
const sender = message.getContact();
|
||||
const sender = getContact(message.attributes);
|
||||
|
||||
if (!sender) {
|
||||
throw new Error('MessageModel has no sender.');
|
||||
|
|
|
@ -318,9 +318,7 @@ export async function joinViaLink(hash: string): Promise<void> {
|
|||
window.ConversationController.dangerouslyRemoveById(
|
||||
tempConversation.id
|
||||
);
|
||||
await window.Signal.Data.removeConversation(tempConversation.id, {
|
||||
Conversation: window.Whisper.Conversation,
|
||||
});
|
||||
await window.Signal.Data.removeConversation(tempConversation.id);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
|
|
@ -196,9 +196,7 @@ async function _runJob(job?: AttachmentDownloadJobType): Promise<void> {
|
|||
|
||||
const found =
|
||||
window.MessageController.getById(messageId) ||
|
||||
(await getMessageById(messageId, {
|
||||
Message: window.Whisper.Message,
|
||||
}));
|
||||
(await getMessageById(messageId));
|
||||
if (!found) {
|
||||
logger.error('_runJob: Source message not found, deleting job');
|
||||
await _finishJob(null, id);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import { Collection, Model } from 'backbone';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import { getContactId } from '../messages/helpers';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
type DeleteAttributesType = {
|
||||
|
@ -30,7 +31,7 @@ export class Deletes extends Collection<DeleteModel> {
|
|||
const matchingDeletes = this.filter(item => {
|
||||
return (
|
||||
item.get('targetSentTimestamp') === message.get('sent_at') &&
|
||||
item.get('fromId') === message.getContactId()
|
||||
item.get('fromId') === getContactId(message.attributes)
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -68,14 +69,11 @@ export class Deletes extends Collection<DeleteModel> {
|
|||
log.info('Handling DOE for', del.get('targetSentTimestamp'));
|
||||
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(
|
||||
del.get('targetSentTimestamp'),
|
||||
{
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}
|
||||
del.get('targetSentTimestamp')
|
||||
);
|
||||
|
||||
const targetMessage = messages.find(
|
||||
m => del.get('fromId') === m.getContactId()
|
||||
m => del.get('fromId') === getContactId(m)
|
||||
);
|
||||
|
||||
if (!targetMessage) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Collection, Model } from 'backbone';
|
|||
|
||||
import type { ConversationModel } from '../models/conversations';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import type { MessageModelCollectionType } from '../model-types.d';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import { isOutgoing } from '../state/selectors/message';
|
||||
import { isDirectConversation } from '../util/whatTypeOfConversation';
|
||||
import { getOwn } from '../util/getOwn';
|
||||
|
@ -60,32 +60,25 @@ const deleteSentProtoBatcher = createWaitBatcher({
|
|||
async function getTargetMessage(
|
||||
sourceId: string,
|
||||
sourceUuid: UUIDStringType,
|
||||
messages: MessageModelCollectionType
|
||||
messages: ReadonlyArray<MessageAttributesType>
|
||||
): Promise<MessageModel | null> {
|
||||
if (messages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const message = messages.find(
|
||||
item =>
|
||||
isOutgoing(item.attributes) && sourceId === item.get('conversationId')
|
||||
item => isOutgoing(item) && sourceId === item.conversationId
|
||||
);
|
||||
if (message) {
|
||||
return window.MessageController.register(message.id, message);
|
||||
}
|
||||
|
||||
const groups = await window.Signal.Data.getAllGroupsInvolvingUuid(
|
||||
sourceUuid,
|
||||
{
|
||||
ConversationCollection: window.Whisper.ConversationCollection,
|
||||
}
|
||||
);
|
||||
const groups = await window.Signal.Data.getAllGroupsInvolvingUuid(sourceUuid);
|
||||
|
||||
const ids = groups.pluck('id');
|
||||
const ids = groups.map(item => item.id);
|
||||
ids.push(sourceId);
|
||||
|
||||
const target = messages.find(
|
||||
item =>
|
||||
isOutgoing(item.attributes) && ids.includes(item.get('conversationId'))
|
||||
item => isOutgoing(item) && ids.includes(item.conversationId)
|
||||
);
|
||||
if (!target) {
|
||||
return null;
|
||||
|
@ -147,10 +140,7 @@ export class MessageReceipts extends Collection<MessageReceiptModel> {
|
|||
|
||||
try {
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(
|
||||
messageSentAt,
|
||||
{
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}
|
||||
messageSentAt
|
||||
);
|
||||
|
||||
const message = await getTargetMessage(
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import { Collection, Model } from 'backbone';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import { getContactId, getContact } from '../messages/helpers';
|
||||
import { isOutgoing } from '../state/selectors/message';
|
||||
import type { ReactionAttributesType } from '../model-types.d';
|
||||
import * as log from '../logging/log';
|
||||
|
@ -35,7 +36,7 @@ export class Reactions extends Collection<ReactionModel> {
|
|||
}
|
||||
}
|
||||
|
||||
const senderId = message.getContactId();
|
||||
const senderId = getContactId(message.attributes);
|
||||
const sentAt = message.get('sent_at');
|
||||
const reactionsBySource = this.filter(re => {
|
||||
const targetSenderId = window.ConversationController.ensureContactIds({
|
||||
|
@ -87,15 +88,12 @@ export class Reactions extends Collection<ReactionModel> {
|
|||
log.info('Handling reaction for', reaction.get('targetTimestamp'));
|
||||
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(
|
||||
reaction.get('targetTimestamp'),
|
||||
{
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}
|
||||
reaction.get('targetTimestamp')
|
||||
);
|
||||
// Message is fetched inside the conversation queue so we have the
|
||||
// most recent data
|
||||
const targetMessage = messages.find(m => {
|
||||
const contact = m.getContact();
|
||||
const contact = getContact(m);
|
||||
|
||||
if (!contact) {
|
||||
return false;
|
||||
|
|
|
@ -80,19 +80,16 @@ export class ReadSyncs extends Collection {
|
|||
async onSync(sync: ReadSyncModel): Promise<void> {
|
||||
try {
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(
|
||||
sync.get('timestamp'),
|
||||
{
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}
|
||||
sync.get('timestamp')
|
||||
);
|
||||
|
||||
const found = messages.find(item => {
|
||||
const senderId = window.ConversationController.ensureContactIds({
|
||||
e164: item.get('source'),
|
||||
uuid: item.get('sourceUuid'),
|
||||
e164: item.source,
|
||||
uuid: item.sourceUuid,
|
||||
});
|
||||
|
||||
return isIncoming(item.attributes) && senderId === sync.get('senderId');
|
||||
return isIncoming(item) && senderId === sync.get('senderId');
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
|
|
|
@ -57,16 +57,13 @@ export class ViewOnceOpenSyncs extends Collection<ViewOnceOpenSyncModel> {
|
|||
async onSync(sync: ViewOnceOpenSyncModel): Promise<void> {
|
||||
try {
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(
|
||||
sync.get('timestamp'),
|
||||
{
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}
|
||||
sync.get('timestamp')
|
||||
);
|
||||
|
||||
const found = messages.find(item => {
|
||||
const itemSourceUuid = item.get('sourceUuid');
|
||||
const itemSourceUuid = item.sourceUuid;
|
||||
const syncSourceUuid = sync.get('sourceUuid');
|
||||
const itemSource = item.get('source');
|
||||
const itemSource = item.source;
|
||||
const syncSource = sync.get('source');
|
||||
|
||||
return Boolean(
|
||||
|
|
|
@ -58,19 +58,16 @@ export class ViewSyncs extends Collection {
|
|||
async onSync(sync: ViewSyncModel): Promise<void> {
|
||||
try {
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(
|
||||
sync.get('timestamp'),
|
||||
{
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}
|
||||
sync.get('timestamp')
|
||||
);
|
||||
|
||||
const found = messages.find(item => {
|
||||
const senderId = window.ConversationController.ensureContactIds({
|
||||
e164: item.get('source'),
|
||||
uuid: item.get('sourceUuid'),
|
||||
e164: item.source,
|
||||
uuid: item.sourceUuid,
|
||||
});
|
||||
|
||||
return isIncoming(item.attributes) && senderId === sync.get('senderId');
|
||||
return isIncoming(item) && senderId === sync.get('senderId');
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
|
|
|
@ -2,21 +2,21 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as log from '../logging/log';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import * as Errors from '../types/errors';
|
||||
|
||||
export async function getMessageById(
|
||||
messageId: string
|
||||
): Promise<MessageModel | undefined> {
|
||||
let message = window.MessageController.getById(messageId);
|
||||
const message = window.MessageController.getById(messageId);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
|
||||
let found: MessageAttributesType | undefined;
|
||||
try {
|
||||
message = await window.Signal.Data.getMessageById(messageId, {
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
found = await window.Signal.Data.getMessageById(messageId);
|
||||
} catch (err: unknown) {
|
||||
log.error(
|
||||
`failed to load message with id ${messageId} ` +
|
||||
|
@ -24,10 +24,9 @@ export async function getMessageById(
|
|||
);
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
if (!found) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
message = window.MessageController.register(message.id, message);
|
||||
return message;
|
||||
return window.MessageController.register(found.id, found);
|
||||
}
|
||||
|
|
103
ts/messages/helpers.ts
Normal file
103
ts/messages/helpers.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as log from '../logging/log';
|
||||
import type { ConversationModel } from '../models/conversations';
|
||||
import type {
|
||||
CustomError,
|
||||
MessageAttributesType,
|
||||
QuotedMessageType,
|
||||
} from '../model-types.d';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import { isIncoming, isOutgoing } from '../state/selectors/message';
|
||||
|
||||
export function isQuoteAMatch(
|
||||
message: MessageAttributesType | null | undefined,
|
||||
conversationId: string,
|
||||
quote: QuotedMessageType
|
||||
): message is MessageAttributesType {
|
||||
if (!message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { authorUuid, id } = quote;
|
||||
const authorConversationId = window.ConversationController.ensureContactIds({
|
||||
e164: 'author' in quote ? quote.author : undefined,
|
||||
uuid: authorUuid,
|
||||
});
|
||||
|
||||
return (
|
||||
message.sent_at === id &&
|
||||
message.conversationId === conversationId &&
|
||||
getContactId(message) === authorConversationId
|
||||
);
|
||||
}
|
||||
|
||||
export function getContactId(
|
||||
message: MessageAttributesType
|
||||
): string | undefined {
|
||||
const source = getSource(message);
|
||||
const sourceUuid = getSourceUuid(message);
|
||||
|
||||
if (!source && !sourceUuid) {
|
||||
return window.ConversationController.getOurConversationId();
|
||||
}
|
||||
|
||||
return window.ConversationController.ensureContactIds({
|
||||
e164: source,
|
||||
uuid: sourceUuid,
|
||||
});
|
||||
}
|
||||
|
||||
export function getContact(
|
||||
message: MessageAttributesType
|
||||
): ConversationModel | undefined {
|
||||
const id = getContactId(message);
|
||||
return window.ConversationController.get(id);
|
||||
}
|
||||
|
||||
export function getSource(message: MessageAttributesType): string | undefined {
|
||||
if (isIncoming(message)) {
|
||||
return message.source;
|
||||
}
|
||||
if (!isOutgoing(message)) {
|
||||
log.warn('Message.getSource: Called for non-incoming/non-outgoing message');
|
||||
}
|
||||
|
||||
return window.textsecure.storage.user.getNumber();
|
||||
}
|
||||
|
||||
export function getSourceDevice(
|
||||
message: MessageAttributesType
|
||||
): string | number | undefined {
|
||||
const { sourceDevice } = message;
|
||||
|
||||
if (isIncoming(message)) {
|
||||
return sourceDevice;
|
||||
}
|
||||
if (!isOutgoing(message)) {
|
||||
log.warn(
|
||||
'Message.getSourceDevice: Called for non-incoming/non-outgoing message'
|
||||
);
|
||||
}
|
||||
|
||||
return sourceDevice || window.textsecure.storage.user.getDeviceId();
|
||||
}
|
||||
|
||||
export function getSourceUuid(
|
||||
message: MessageAttributesType
|
||||
): UUIDStringType | undefined {
|
||||
if (isIncoming(message)) {
|
||||
return message.sourceUuid;
|
||||
}
|
||||
if (!isOutgoing(message)) {
|
||||
log.warn(
|
||||
'Message.getSourceUuid: Called for non-incoming/non-outgoing message'
|
||||
);
|
||||
}
|
||||
|
||||
return window.textsecure.storage.user.getUuid()?.toString();
|
||||
}
|
||||
|
||||
export const isCustomError = (e: unknown): e is CustomError =>
|
||||
e instanceof Error;
|
|
@ -9,7 +9,6 @@ import type {
|
|||
ConversationModelCollectionType,
|
||||
LastMessageStatus,
|
||||
MessageAttributesType,
|
||||
MessageModelCollectionType,
|
||||
QuotedMessageType,
|
||||
SenderKeyInfoType,
|
||||
VerificationOptions,
|
||||
|
@ -38,6 +37,7 @@ import type {
|
|||
CustomColorType,
|
||||
} from '../types/Colors';
|
||||
import type { MessageModel } from './messages';
|
||||
import { getContact } from '../messages/helpers';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { isMuted } from '../util/isMuted';
|
||||
import { isConversationSMSOnly } from '../util/isConversationSMSOnly';
|
||||
|
@ -1305,12 +1305,6 @@ export class ConversationModel extends window.Backbone
|
|||
});
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
await Conversation.deleteExternalFiles(this.attributes, {
|
||||
deleteAttachmentData,
|
||||
});
|
||||
}
|
||||
|
||||
async onNewMessage(message: MessageModel): Promise<void> {
|
||||
const uuid = message.get('sourceUuid');
|
||||
const e164 = message.get('source');
|
||||
|
@ -1407,13 +1401,11 @@ export class ConversationModel extends window.Backbone
|
|||
let scrollToLatestUnread = true;
|
||||
|
||||
if (newestMessageId) {
|
||||
const newestInMemoryMessage = await getMessageById(newestMessageId, {
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
const newestInMemoryMessage = await getMessageById(newestMessageId);
|
||||
if (newestInMemoryMessage) {
|
||||
// If newest in-memory message is unread, scrolling down would mean going to
|
||||
// the very bottom, not the oldest unread.
|
||||
if (isMessageUnread(newestInMemoryMessage.attributes)) {
|
||||
if (isMessageUnread(newestInMemoryMessage)) {
|
||||
scrollToLatestUnread = false;
|
||||
}
|
||||
} else {
|
||||
|
@ -1443,7 +1435,6 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
const messages = await getOlderMessagesByConversation(conversationId, {
|
||||
limit: MESSAGE_LOAD_CHUNK_SIZE,
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
});
|
||||
|
||||
const cleaned: Array<MessageModel> = await this.cleanModels(messages);
|
||||
|
@ -1481,23 +1472,20 @@ export class ConversationModel extends window.Backbone
|
|||
const finish = this.setInProgressFetch();
|
||||
|
||||
try {
|
||||
const message = await getMessageById(oldestMessageId, {
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
const message = await getMessageById(oldestMessageId);
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`loadOlderMessages: failed to load message ${oldestMessageId}`
|
||||
);
|
||||
}
|
||||
|
||||
const receivedAt = message.get('received_at');
|
||||
const sentAt = message.get('sent_at');
|
||||
const receivedAt = message.received_at;
|
||||
const sentAt = message.sent_at;
|
||||
const models = await getOlderMessagesByConversation(conversationId, {
|
||||
receivedAt,
|
||||
sentAt,
|
||||
messageId: oldestMessageId,
|
||||
limit: MESSAGE_LOAD_CHUNK_SIZE,
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
});
|
||||
|
||||
if (models.length < 1) {
|
||||
|
@ -1533,22 +1521,19 @@ export class ConversationModel extends window.Backbone
|
|||
const finish = this.setInProgressFetch();
|
||||
|
||||
try {
|
||||
const message = await getMessageById(newestMessageId, {
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
const message = await getMessageById(newestMessageId);
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`loadNewerMessages: failed to load message ${newestMessageId}`
|
||||
);
|
||||
}
|
||||
|
||||
const receivedAt = message.get('received_at');
|
||||
const sentAt = message.get('sent_at');
|
||||
const receivedAt = message.received_at;
|
||||
const sentAt = message.sent_at;
|
||||
const models = await getNewerMessagesByConversation(conversationId, {
|
||||
receivedAt,
|
||||
sentAt,
|
||||
limit: MESSAGE_LOAD_CHUNK_SIZE,
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
});
|
||||
|
||||
if (models.length < 1) {
|
||||
|
@ -1587,33 +1572,29 @@ export class ConversationModel extends window.Backbone
|
|||
const finish = this.setInProgressFetch();
|
||||
|
||||
try {
|
||||
const message = await getMessageById(messageId, {
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
const message = await getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`loadMoreAndScroll: failed to load message ${messageId}`
|
||||
);
|
||||
}
|
||||
|
||||
const receivedAt = message.get('received_at');
|
||||
const sentAt = message.get('sent_at');
|
||||
const receivedAt = message.received_at;
|
||||
const sentAt = message.sent_at;
|
||||
const older = await getOlderMessagesByConversation(conversationId, {
|
||||
limit: MESSAGE_LOAD_CHUNK_SIZE,
|
||||
receivedAt,
|
||||
sentAt,
|
||||
messageId,
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
});
|
||||
const newer = await getNewerMessagesByConversation(conversationId, {
|
||||
limit: MESSAGE_LOAD_CHUNK_SIZE,
|
||||
receivedAt,
|
||||
sentAt,
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
});
|
||||
const metrics = await getMessageMetricsForConversation(conversationId);
|
||||
|
||||
const all = [...older.models, message, ...newer.models];
|
||||
const all = [...older, message, ...newer];
|
||||
|
||||
const cleaned: Array<MessageModel> = await this.cleanModels(all);
|
||||
const scrollToMessageId =
|
||||
|
@ -1636,19 +1617,18 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
async cleanModels(
|
||||
collection: MessageModelCollectionType | Array<MessageModel>
|
||||
messages: ReadonlyArray<MessageAttributesType>
|
||||
): Promise<Array<MessageModel>> {
|
||||
const result = collection
|
||||
.filter((message: MessageModel) => Boolean(message.id))
|
||||
.map((message: MessageModel) =>
|
||||
window.MessageController.register(message.id, message)
|
||||
);
|
||||
const result = messages
|
||||
.filter(message => Boolean(message.id))
|
||||
.map(message => window.MessageController.register(message.id, message));
|
||||
|
||||
const eliminated = collection.length - result.length;
|
||||
const eliminated = messages.length - result.length;
|
||||
if (eliminated > 0) {
|
||||
log.warn(`cleanModels: Eliminated ${eliminated} messages without an id`);
|
||||
}
|
||||
|
||||
let upgraded = 0;
|
||||
for (let max = result.length, i = 0; i < max; i += 1) {
|
||||
const message = result[i];
|
||||
const { attributes } = message;
|
||||
|
@ -1661,8 +1641,12 @@ export class ConversationModel extends window.Backbone
|
|||
message.set(upgradedMessage);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await window.Signal.Data.saveMessage(upgradedMessage);
|
||||
upgraded += 1;
|
||||
}
|
||||
}
|
||||
if (upgraded > 0) {
|
||||
log.warn(`cleanModels: Upgraded schema of ${upgraded} messages`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1972,18 +1956,17 @@ export class ConversationModel extends window.Backbone
|
|||
): Promise<void> {
|
||||
const { isLocalAction } = options;
|
||||
|
||||
let messages: MessageModelCollectionType | undefined;
|
||||
let messages: Array<MessageAttributesType> | undefined;
|
||||
do {
|
||||
const first = messages ? messages.first() : undefined;
|
||||
const first = messages ? messages[0] : undefined;
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
messages = await window.Signal.Data.getOlderMessagesByConversation(
|
||||
this.get('id'),
|
||||
{
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 100,
|
||||
receivedAt: first ? first.get('received_at') : undefined,
|
||||
sentAt: first ? first.get('sent_at') : undefined,
|
||||
receivedAt: first ? first.received_at : undefined,
|
||||
sentAt: first ? first.sent_at : undefined,
|
||||
messageId: first ? first.id : undefined,
|
||||
}
|
||||
);
|
||||
|
@ -1992,9 +1975,7 @@ export class ConversationModel extends window.Backbone
|
|||
return;
|
||||
}
|
||||
|
||||
const readMessages = messages.filter(
|
||||
m => !hasErrors(m.attributes) && isIncoming(m.attributes)
|
||||
);
|
||||
const readMessages = messages.filter(m => !hasErrors(m) && isIncoming(m));
|
||||
|
||||
if (isLocalAction) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
|
@ -2002,9 +1983,9 @@ export class ConversationModel extends window.Backbone
|
|||
window.storage,
|
||||
readMessages.map(m => ({
|
||||
messageId: m.id,
|
||||
senderE164: m.get('source'),
|
||||
senderUuid: m.get('sourceUuid'),
|
||||
timestamp: m.get('sent_at'),
|
||||
senderE164: m.source,
|
||||
senderUuid: m.sourceUuid,
|
||||
timestamp: m.sent_at,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
@ -3209,10 +3190,7 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
const message = window.MessageController.getById(notificationId);
|
||||
if (message) {
|
||||
message.cleanup();
|
||||
window.Signal.Data.removeMessage(message.id, {
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
window.Signal.Data.removeMessage(message.id);
|
||||
}
|
||||
|
||||
if (this.get('expireTimer') || forceRemove) {
|
||||
|
@ -3628,7 +3606,7 @@ export class ConversationModel extends window.Backbone
|
|||
async makeQuote(quotedMessage: MessageModel): Promise<QuotedMessageType> {
|
||||
const { getName } = EmbeddedContact;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const contact = quotedMessage.getContact()!;
|
||||
const contact = getContact(quotedMessage.attributes)!;
|
||||
const attachments = quotedMessage.get('attachments');
|
||||
const preview = quotedMessage.get('preview');
|
||||
const sticker = quotedMessage.get('sticker');
|
||||
|
@ -4082,7 +4060,6 @@ export class ConversationModel extends window.Backbone
|
|||
const lastMessages = await window.Signal.Data.getLastConversationMessages({
|
||||
conversationId,
|
||||
ourUuid,
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
|
||||
// This runs as a job to avoid race conditions
|
||||
|
@ -4090,22 +4067,21 @@ export class ConversationModel extends window.Backbone
|
|||
this.maybeSetPendingUniversalTimer(lastMessages.hasUserInitiatedMessages)
|
||||
);
|
||||
|
||||
let { preview: previewMessage, activity: activityMessage } = lastMessages;
|
||||
const { preview, activity } = lastMessages;
|
||||
let previewMessage: MessageModel | undefined;
|
||||
let activityMessage: MessageModel | undefined;
|
||||
|
||||
// Register the message with MessageController so that if it already exists
|
||||
// in memory we use that data instead of the data from the db which may
|
||||
// be out of date.
|
||||
if (previewMessage) {
|
||||
previewMessage = window.MessageController.register(
|
||||
previewMessage.id,
|
||||
previewMessage
|
||||
);
|
||||
if (preview) {
|
||||
previewMessage = window.MessageController.register(preview.id, preview);
|
||||
}
|
||||
|
||||
if (activityMessage) {
|
||||
if (activity) {
|
||||
activityMessage = window.MessageController.register(
|
||||
activityMessage.id,
|
||||
activityMessage
|
||||
activity.id,
|
||||
activity
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4748,9 +4724,7 @@ export class ConversationModel extends window.Backbone
|
|||
this.deriveProfileKeyVersionIfNeeded(),
|
||||
]);
|
||||
|
||||
window.Signal.Data.updateConversation(this.attributes, {
|
||||
Conversation: window.Whisper.Conversation,
|
||||
});
|
||||
window.Signal.Data.updateConversation(this.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4814,7 +4788,6 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
await window.Signal.Data.removeAllMessagesInConversation(this.id, {
|
||||
logId: this.idForLogging(),
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5090,7 +5063,7 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
const sender = reaction
|
||||
? window.ConversationController.get(reaction.get('fromId'))
|
||||
: message.getContact();
|
||||
: getContact(message.attributes);
|
||||
const senderName = sender
|
||||
? sender.getTitle()
|
||||
: window.i18n('unknownContact');
|
||||
|
|
|
@ -41,11 +41,9 @@ import * as expirationTimer from '../util/expirationTimer';
|
|||
|
||||
import type { ReactionType } from '../types/Reactions';
|
||||
import { UUID, UUIDKind } from '../types/UUID';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import * as reactionUtil from '../reactions/util';
|
||||
import {
|
||||
copyStickerToAttachments,
|
||||
deletePackReference,
|
||||
savePackMetadata,
|
||||
getStickerPackStatus,
|
||||
} from '../types/Stickers';
|
||||
|
@ -134,6 +132,16 @@ import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
|||
import * as log from '../logging/log';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { computeHash } from '../Crypto';
|
||||
import { cleanupMessage, deleteMessageData } from '../util/cleanup';
|
||||
import {
|
||||
getContact,
|
||||
getContactId,
|
||||
getSource,
|
||||
getSourceDevice,
|
||||
getSourceUuid,
|
||||
isCustomError,
|
||||
isQuoteAMatch,
|
||||
} from '../messages/helpers';
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint-disable more/no-then */
|
||||
|
@ -148,36 +156,10 @@ declare const _: typeof window._;
|
|||
window.Whisper = window.Whisper || {};
|
||||
|
||||
const { Message: TypedMessage } = window.Signal.Types;
|
||||
const { deleteExternalMessageFiles, upgradeMessageSchema } =
|
||||
window.Signal.Migrations;
|
||||
const { upgradeMessageSchema } = window.Signal.Migrations;
|
||||
const { getTextWithMentions, GoogleChrome } = window.Signal.Util;
|
||||
|
||||
const { addStickerPackReference, getMessageBySender } = window.Signal.Data;
|
||||
|
||||
export function isQuoteAMatch(
|
||||
message: MessageModel | null | undefined,
|
||||
conversationId: string,
|
||||
quote: QuotedMessageType
|
||||
): message is MessageModel {
|
||||
if (!message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { authorUuid, id } = quote;
|
||||
const authorConversationId = window.ConversationController.ensureContactIds({
|
||||
e164: 'author' in quote ? quote.author : undefined,
|
||||
uuid: authorUuid,
|
||||
});
|
||||
|
||||
return (
|
||||
message.get('sent_at') === id &&
|
||||
message.get('conversationId') === conversationId &&
|
||||
message.getContactId() === authorConversationId
|
||||
);
|
||||
}
|
||||
|
||||
const isCustomError = (e: unknown): e is CustomError => e instanceof Error;
|
||||
|
||||
export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||
static getLongMessageAttachment: (
|
||||
attachment: typeof window.WhatIsThis
|
||||
|
@ -307,7 +289,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
let conversationIds: Array<string>;
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
if (isIncoming(this.attributes)) {
|
||||
conversationIds = [this.getContactId()!];
|
||||
conversationIds = [getContactId(this.attributes)!];
|
||||
} else if (!isEmpty(sendStateByConversationId)) {
|
||||
if (isMessageJustForMe(sendStateByConversationId, ourConversationId)) {
|
||||
conversationIds = [ourConversationId];
|
||||
|
@ -516,7 +498,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
if (isGroupUpdate(attributes)) {
|
||||
const groupUpdate = this.get('group_update');
|
||||
const fromContact = this.getContact();
|
||||
const fromContact = getContact(this.attributes);
|
||||
const messages = [];
|
||||
if (!groupUpdate) {
|
||||
throw new Error('getNotificationData: Missing group_update');
|
||||
|
@ -749,8 +731,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
// General
|
||||
idForLogging(): string {
|
||||
const account = this.getSourceUuid() || this.getSource();
|
||||
const device = this.getSourceDevice();
|
||||
const account =
|
||||
getSourceUuid(this.attributes) || getSource(this.attributes);
|
||||
const device = getSourceDevice(this.attributes);
|
||||
const timestamp = this.get('sent_at');
|
||||
|
||||
return `${account}.${device} ${timestamp}`;
|
||||
|
@ -785,29 +768,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
window.reduxActions?.conversations?.messageDeleted(
|
||||
this.id,
|
||||
this.get('conversationId')
|
||||
);
|
||||
|
||||
this.getConversation()?.debouncedUpdateLastMessage?.();
|
||||
|
||||
window.MessageController.unregister(this.id);
|
||||
await this.deleteData();
|
||||
await cleanupMessage(this.attributes);
|
||||
}
|
||||
|
||||
async deleteData(): Promise<void> {
|
||||
await deleteExternalMessageFiles(this.attributes);
|
||||
|
||||
const sticker = this.get('sticker');
|
||||
if (!sticker) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { packId } = sticker;
|
||||
if (packId) {
|
||||
await deletePackReference(this.id, packId);
|
||||
}
|
||||
await deleteMessageData(this.attributes);
|
||||
}
|
||||
|
||||
isValidTapToView(): boolean {
|
||||
|
@ -875,8 +840,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
await this.eraseContents();
|
||||
|
||||
if (!fromSync) {
|
||||
const sender = this.getSource();
|
||||
const senderUuid = this.getSourceUuid();
|
||||
const sender = getSource(this.attributes);
|
||||
const senderUuid = getSourceUuid(this.attributes);
|
||||
|
||||
if (senderUuid === undefined) {
|
||||
throw new Error('senderUuid is undefined');
|
||||
|
@ -929,7 +894,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
Number(sentAt)
|
||||
);
|
||||
const matchingMessage = find(inMemoryMessages, message =>
|
||||
isQuoteAMatch(message, this.get('conversationId'), quote)
|
||||
isQuoteAMatch(message.attributes, this.get('conversationId'), quote)
|
||||
);
|
||||
if (!matchingMessage) {
|
||||
log.info(
|
||||
|
@ -1078,66 +1043,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
return unidentifiedDeliveriesSet.has(contactId);
|
||||
}
|
||||
|
||||
getSource(): string | undefined {
|
||||
if (isIncoming(this.attributes)) {
|
||||
return this.get('source');
|
||||
}
|
||||
if (!isOutgoing(this.attributes)) {
|
||||
log.warn(
|
||||
'Message.getSource: Called for non-incoming/non-outoing message'
|
||||
);
|
||||
}
|
||||
|
||||
return window.textsecure.storage.user.getNumber();
|
||||
}
|
||||
|
||||
getSourceDevice(): string | number | undefined {
|
||||
const sourceDevice = this.get('sourceDevice');
|
||||
|
||||
if (isIncoming(this.attributes)) {
|
||||
return sourceDevice;
|
||||
}
|
||||
if (!isOutgoing(this.attributes)) {
|
||||
log.warn(
|
||||
'Message.getSourceDevice: Called for non-incoming/non-outoing message'
|
||||
);
|
||||
}
|
||||
|
||||
return sourceDevice || window.textsecure.storage.user.getDeviceId();
|
||||
}
|
||||
|
||||
getSourceUuid(): UUIDStringType | undefined {
|
||||
if (isIncoming(this.attributes)) {
|
||||
return this.get('sourceUuid');
|
||||
}
|
||||
if (!isOutgoing(this.attributes)) {
|
||||
log.warn(
|
||||
'Message.getSourceUuid: Called for non-incoming/non-outoing message'
|
||||
);
|
||||
}
|
||||
|
||||
return window.textsecure.storage.user.getUuid()?.toString();
|
||||
}
|
||||
|
||||
getContactId(): string | undefined {
|
||||
const source = this.getSource();
|
||||
const sourceUuid = this.getSourceUuid();
|
||||
|
||||
if (!source && !sourceUuid) {
|
||||
return window.ConversationController.getOurConversationId();
|
||||
}
|
||||
|
||||
return window.ConversationController.ensureContactIds({
|
||||
e164: source,
|
||||
uuid: sourceUuid,
|
||||
});
|
||||
}
|
||||
|
||||
getContact(): ConversationModel | undefined {
|
||||
const id = this.getContactId();
|
||||
return window.ConversationController.get(id);
|
||||
}
|
||||
|
||||
async saveErrors(
|
||||
providedErrors: Error | Array<Error>,
|
||||
options: { skipSave?: boolean } = {}
|
||||
|
@ -2106,7 +2011,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
const inMemoryMessages = window.MessageController.filterBySentAt(id);
|
||||
const matchingMessage = find(inMemoryMessages, item =>
|
||||
isQuoteAMatch(item, conversationId, result)
|
||||
isQuoteAMatch(item.attributes, conversationId, result)
|
||||
);
|
||||
|
||||
let queryMessage: undefined | MessageModel;
|
||||
|
@ -2115,10 +2020,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
queryMessage = matchingMessage;
|
||||
} else {
|
||||
log.info('copyFromQuotedMessage: db lookup needed', id);
|
||||
const collection = await window.Signal.Data.getMessagesBySentAt(id, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
});
|
||||
const found = collection.find(item =>
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(id);
|
||||
const found = messages.find(item =>
|
||||
isQuoteAMatch(item, conversationId, result)
|
||||
);
|
||||
|
||||
|
@ -2256,7 +2159,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
const conversationId = message.get('conversationId');
|
||||
const GROUP_TYPES = Proto.GroupContext.Type;
|
||||
|
||||
const fromContact = this.getContact();
|
||||
const fromContact = getContact(this.attributes);
|
||||
if (fromContact) {
|
||||
fromContact.setRegistered();
|
||||
}
|
||||
|
@ -2281,10 +2184,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
);
|
||||
}
|
||||
const existingMessage =
|
||||
inMemoryMessage ||
|
||||
(await getMessageBySender(this.attributes, {
|
||||
Message: window.Whisper.Message,
|
||||
}));
|
||||
inMemoryMessage || (await getMessageBySender(this.attributes));
|
||||
const isUpdate = Boolean(data && data.isRecipientUpdate);
|
||||
|
||||
if (existingMessage && type === 'incoming') {
|
||||
|
@ -2702,7 +2602,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
if (conversation.get('left')) {
|
||||
log.warn('re-added to a left group');
|
||||
attributes.left = false;
|
||||
conversation.set({ addedBy: message.getContactId() });
|
||||
conversation.set({ addedBy: getContactId(message.attributes) });
|
||||
}
|
||||
} else if (dataMessage.group.type === GROUP_TYPES.QUIT) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { ReactionModel } from '../messageModifiers/Reactions';
|
||||
import { ReactionSource } from './ReactionSource';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { getSourceUuid } from '../messages/helpers';
|
||||
import { strictAssert } from '../util/assert';
|
||||
|
||||
export async function enqueueReactionForSend({
|
||||
|
@ -18,7 +19,7 @@ export async function enqueueReactionForSend({
|
|||
const message = await getMessageById(messageId);
|
||||
strictAssert(message, 'enqueueReactionForSend: no message found');
|
||||
|
||||
const targetAuthorUuid = message.getSourceUuid();
|
||||
const targetAuthorUuid = getSourceUuid(message.attributes);
|
||||
strictAssert(
|
||||
targetAuthorUuid,
|
||||
`enqueueReactionForSend: message ${message.idForLogging()} had no source UUID`
|
||||
|
|
235
ts/sql/Client.ts
235
ts/sql/Client.ts
|
@ -26,6 +26,7 @@ import {
|
|||
uniq,
|
||||
} from 'lodash';
|
||||
|
||||
import { deleteExternalFiles } from '../types/Conversation';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { CURRENT_SCHEMA_VERSION } from '../../js/modules/types/message';
|
||||
import { createBatcher } from '../util/batcher';
|
||||
|
@ -40,12 +41,9 @@ import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration';
|
|||
import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
import type {
|
||||
ConversationModelCollectionType,
|
||||
MessageModelCollectionType,
|
||||
} from '../model-types.d';
|
||||
import type { StoredJob } from '../jobs/types';
|
||||
import { formatJobForInsert } from '../jobs/formatJobForInsert';
|
||||
import { cleanupMessage } from '../util/cleanup';
|
||||
|
||||
import type {
|
||||
AttachmentDownloadJobType,
|
||||
|
@ -54,18 +52,17 @@ import type {
|
|||
ClientSearchResultMessageType,
|
||||
ConversationType,
|
||||
DeleteSentProtoRecipientOptionsType,
|
||||
IdentityKeyType,
|
||||
IdentityKeyIdType,
|
||||
IdentityKeyType,
|
||||
ItemKeyType,
|
||||
ItemType,
|
||||
LastConversationMessagesType,
|
||||
MessageType,
|
||||
MessageTypeUnhydrated,
|
||||
PreKeyType,
|
||||
PreKeyIdType,
|
||||
SearchResultMessageType,
|
||||
SenderKeyType,
|
||||
PreKeyType,
|
||||
SenderKeyIdType,
|
||||
SenderKeyType,
|
||||
SentMessageDBType,
|
||||
SentMessagesType,
|
||||
SentProtoType,
|
||||
|
@ -73,15 +70,16 @@ import type {
|
|||
SentRecipientsDBType,
|
||||
SentRecipientsType,
|
||||
ServerInterface,
|
||||
SessionType,
|
||||
ServerSearchResultMessageType,
|
||||
SessionIdType,
|
||||
SignedPreKeyType,
|
||||
SessionType,
|
||||
SignedPreKeyIdType,
|
||||
SignedPreKeyType,
|
||||
StickerPackStatusType,
|
||||
StickerPackType,
|
||||
StickerType,
|
||||
StoryDistributionType,
|
||||
StoryDistributionMemberType,
|
||||
StoryDistributionType,
|
||||
StoryDistributionWithMembersType,
|
||||
StoryReadType,
|
||||
UnprocessedType,
|
||||
|
@ -89,8 +87,6 @@ import type {
|
|||
} from './Interface';
|
||||
import Server from './Server';
|
||||
import { isCorruptionError } from './errors';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import type { ConversationModel } from '../models/conversations';
|
||||
|
||||
// We listen to a lot of events on ipc, often on the same channel. This prevents
|
||||
// any warnings that might be sent to the console in that case.
|
||||
|
@ -160,16 +156,16 @@ const dataInterface: ClientInterface = {
|
|||
|
||||
createOrUpdateSignedPreKey,
|
||||
getSignedPreKeyById,
|
||||
getAllSignedPreKeys,
|
||||
bulkAddSignedPreKeys,
|
||||
removeSignedPreKeyById,
|
||||
removeAllSignedPreKeys,
|
||||
getAllSignedPreKeys,
|
||||
|
||||
createOrUpdateItem,
|
||||
getItemById,
|
||||
getAllItems,
|
||||
removeItemById,
|
||||
removeAllItems,
|
||||
getAllItems,
|
||||
|
||||
createOrUpdateSenderKey,
|
||||
getSenderKeyById,
|
||||
|
@ -197,6 +193,7 @@ const dataInterface: ClientInterface = {
|
|||
removeAllSessions,
|
||||
getAllSessions,
|
||||
|
||||
eraseStorageServiceStateFromConversations,
|
||||
getConversationCount,
|
||||
saveConversation,
|
||||
saveConversations,
|
||||
|
@ -206,7 +203,6 @@ const dataInterface: ClientInterface = {
|
|||
removeConversation,
|
||||
updateAllConversationColors,
|
||||
|
||||
eraseStorageServiceStateFromConversations,
|
||||
getAllConversations,
|
||||
getAllConversationIds,
|
||||
getAllPrivateConversations,
|
||||
|
@ -229,10 +225,11 @@ const dataInterface: ClientInterface = {
|
|||
addReaction,
|
||||
_getAllReactions,
|
||||
_removeAllReactions,
|
||||
|
||||
getMessageBySender,
|
||||
getMessageById,
|
||||
getMessagesById,
|
||||
_getAllMessages,
|
||||
_removeAllMessages,
|
||||
getAllMessageIds,
|
||||
getMessagesBySentAt,
|
||||
getExpiredMessages,
|
||||
|
@ -243,8 +240,8 @@ const dataInterface: ClientInterface = {
|
|||
getOlderMessagesByConversation,
|
||||
getOlderStories,
|
||||
getNewerMessagesByConversation,
|
||||
getLastConversationMessages,
|
||||
getMessageMetricsForConversation,
|
||||
getLastConversationMessages,
|
||||
hasGroupCallHistoryMessage,
|
||||
migrateConversationMessages,
|
||||
|
||||
|
@ -263,13 +260,13 @@ const dataInterface: ClientInterface = {
|
|||
removeAttachmentDownloadJob,
|
||||
removeAllAttachmentDownloadJobs,
|
||||
|
||||
getStickerCount,
|
||||
createOrUpdateStickerPack,
|
||||
updateStickerPackStatus,
|
||||
createOrUpdateSticker,
|
||||
updateStickerLastUsed,
|
||||
addStickerPackReference,
|
||||
deleteStickerPackReference,
|
||||
getStickerCount,
|
||||
deleteStickerPack,
|
||||
getAllStickerPacks,
|
||||
getAllStickers,
|
||||
|
@ -317,11 +314,6 @@ const dataInterface: ClientInterface = {
|
|||
|
||||
getStatisticsForLogging,
|
||||
|
||||
// Test-only
|
||||
|
||||
_getAllMessages,
|
||||
_removeAllMessages,
|
||||
|
||||
// Client-side only
|
||||
|
||||
shutdown,
|
||||
|
@ -335,7 +327,6 @@ const dataInterface: ClientInterface = {
|
|||
|
||||
startInRendererProcess,
|
||||
goBackToMainProcess,
|
||||
_removeConversations,
|
||||
_jobs,
|
||||
};
|
||||
|
||||
|
@ -971,17 +962,8 @@ async function saveConversations(array: Array<ConversationType>) {
|
|||
await channels.saveConversations(array);
|
||||
}
|
||||
|
||||
async function getConversationById(
|
||||
id: string,
|
||||
{ Conversation }: { Conversation: typeof ConversationModel }
|
||||
) {
|
||||
const data = await channels.getConversationById(id);
|
||||
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new Conversation(data);
|
||||
async function getConversationById(id: string) {
|
||||
return channels.getConversationById(id);
|
||||
}
|
||||
|
||||
const updateConversationBatcher = createBatcher<ConversationType>({
|
||||
|
@ -1015,40 +997,25 @@ async function updateConversations(array: Array<ConversationType>) {
|
|||
await channels.updateConversations(cleaned);
|
||||
}
|
||||
|
||||
async function removeConversation(
|
||||
id: string,
|
||||
{ Conversation }: { Conversation: typeof ConversationModel }
|
||||
) {
|
||||
const existing = await getConversationById(id, { Conversation });
|
||||
async function removeConversation(id: string) {
|
||||
const existing = await getConversationById(id);
|
||||
|
||||
// Note: It's important to have a fully database-hydrated model to delete here because
|
||||
// it needs to delete all associated on-disk files along with the database delete.
|
||||
if (existing) {
|
||||
await channels.removeConversation(id);
|
||||
await existing.cleanup();
|
||||
await deleteExternalFiles(existing, {
|
||||
deleteAttachmentData: window.Signal.Migrations.deleteAttachmentData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this method will not clean up external files, just delete from SQL
|
||||
async function _removeConversations(ids: Array<string>) {
|
||||
await channels.removeConversation(ids);
|
||||
}
|
||||
|
||||
async function eraseStorageServiceStateFromConversations() {
|
||||
await channels.eraseStorageServiceStateFromConversations();
|
||||
}
|
||||
|
||||
async function getAllConversations({
|
||||
ConversationCollection,
|
||||
}: {
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
}): Promise<ConversationModelCollectionType> {
|
||||
const conversations = await channels.getAllConversations();
|
||||
|
||||
const collection = new ConversationCollection();
|
||||
collection.add(conversations);
|
||||
|
||||
return collection;
|
||||
async function getAllConversations() {
|
||||
return channels.getAllConversations();
|
||||
}
|
||||
|
||||
async function getAllConversationIds() {
|
||||
|
@ -1057,33 +1024,12 @@ async function getAllConversationIds() {
|
|||
return ids;
|
||||
}
|
||||
|
||||
async function getAllPrivateConversations({
|
||||
ConversationCollection,
|
||||
}: {
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
}) {
|
||||
const conversations = await channels.getAllPrivateConversations();
|
||||
|
||||
const collection = new ConversationCollection();
|
||||
collection.add(conversations);
|
||||
|
||||
return collection;
|
||||
async function getAllPrivateConversations() {
|
||||
return channels.getAllPrivateConversations();
|
||||
}
|
||||
|
||||
async function getAllGroupsInvolvingUuid(
|
||||
uuid: UUIDStringType,
|
||||
{
|
||||
ConversationCollection,
|
||||
}: {
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
}
|
||||
) {
|
||||
const conversations = await channels.getAllGroupsInvolvingUuid(uuid);
|
||||
|
||||
const collection = new ConversationCollection();
|
||||
collection.add(conversations);
|
||||
|
||||
return collection;
|
||||
async function getAllGroupsInvolvingUuid(uuid: UUIDStringType) {
|
||||
return channels.getAllGroupsInvolvingUuid(uuid);
|
||||
}
|
||||
|
||||
async function searchConversations(query: string) {
|
||||
|
@ -1093,7 +1039,7 @@ async function searchConversations(query: string) {
|
|||
}
|
||||
|
||||
function handleSearchMessageJSON(
|
||||
messages: Array<SearchResultMessageType>
|
||||
messages: Array<ServerSearchResultMessageType>
|
||||
): Array<ClientSearchResultMessageType> {
|
||||
return messages.map(message => ({
|
||||
json: message.json,
|
||||
|
@ -1163,17 +1109,14 @@ async function saveMessages(
|
|||
window.Whisper.TapToViewMessagesListener.update();
|
||||
}
|
||||
|
||||
async function removeMessage(
|
||||
id: string,
|
||||
{ Message }: { Message: typeof MessageModel }
|
||||
) {
|
||||
const message = await getMessageById(id, { Message });
|
||||
async function removeMessage(id: string) {
|
||||
const message = await getMessageById(id);
|
||||
|
||||
// Note: It's important to have a fully database-hydrated model to delete here because
|
||||
// it needs to delete all associated on-disk files along with the database delete.
|
||||
if (message) {
|
||||
await channels.removeMessage(id);
|
||||
await message.cleanup();
|
||||
await cleanupMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1182,16 +1125,8 @@ async function removeMessages(ids: Array<string>) {
|
|||
await channels.removeMessages(ids);
|
||||
}
|
||||
|
||||
async function getMessageById(
|
||||
id: string,
|
||||
{ Message }: { Message: typeof MessageModel }
|
||||
) {
|
||||
const message = await channels.getMessageById(id);
|
||||
if (!message) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new Message(message);
|
||||
async function getMessageById(id: string) {
|
||||
return channels.getMessageById(id);
|
||||
}
|
||||
|
||||
async function getMessagesById(messageIds: Array<string>) {
|
||||
|
@ -1202,14 +1137,8 @@ async function getMessagesById(messageIds: Array<string>) {
|
|||
}
|
||||
|
||||
// For testing only
|
||||
async function _getAllMessages({
|
||||
MessageCollection,
|
||||
}: {
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
}) {
|
||||
const messages = await channels._getAllMessages();
|
||||
|
||||
return new MessageCollection(messages);
|
||||
async function _getAllMessages() {
|
||||
return channels._getAllMessages();
|
||||
}
|
||||
async function _removeAllMessages() {
|
||||
await channels._removeAllMessages();
|
||||
|
@ -1221,31 +1150,23 @@ async function getAllMessageIds() {
|
|||
return ids;
|
||||
}
|
||||
|
||||
async function getMessageBySender(
|
||||
{
|
||||
source,
|
||||
sourceUuid,
|
||||
sourceDevice,
|
||||
sent_at,
|
||||
}: {
|
||||
source: string;
|
||||
sourceUuid: string;
|
||||
sourceDevice: number;
|
||||
sent_at: number;
|
||||
},
|
||||
{ Message }: { Message: typeof MessageModel }
|
||||
) {
|
||||
const messages = await channels.getMessageBySender({
|
||||
async function getMessageBySender({
|
||||
source,
|
||||
sourceUuid,
|
||||
sourceDevice,
|
||||
sent_at,
|
||||
}: {
|
||||
source: string;
|
||||
sourceUuid: string;
|
||||
sourceDevice: number;
|
||||
sent_at: number;
|
||||
}) {
|
||||
return channels.getMessageBySender({
|
||||
source,
|
||||
sourceUuid,
|
||||
sourceDevice,
|
||||
sent_at,
|
||||
});
|
||||
if (!messages || !messages.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Message(messages[0]);
|
||||
}
|
||||
|
||||
async function getTotalUnreadForConversation(
|
||||
|
@ -1299,7 +1220,9 @@ async function _removeAllReactions() {
|
|||
await channels._removeAllReactions();
|
||||
}
|
||||
|
||||
function handleMessageJSON(messages: Array<MessageTypeUnhydrated>) {
|
||||
function handleMessageJSON(
|
||||
messages: Array<MessageTypeUnhydrated>
|
||||
): Array<MessageType> {
|
||||
return messages.map(message => JSON.parse(message.json));
|
||||
}
|
||||
|
||||
|
@ -1307,14 +1230,12 @@ async function getOlderMessagesByConversation(
|
|||
conversationId: string,
|
||||
{
|
||||
limit = 100,
|
||||
MessageCollection,
|
||||
messageId,
|
||||
receivedAt = Number.MAX_VALUE,
|
||||
sentAt = Number.MAX_VALUE,
|
||||
storyId,
|
||||
}: {
|
||||
limit?: number;
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
messageId?: string;
|
||||
receivedAt?: number;
|
||||
sentAt?: number;
|
||||
|
@ -1332,7 +1253,7 @@ async function getOlderMessagesByConversation(
|
|||
}
|
||||
);
|
||||
|
||||
return new MessageCollection(handleMessageJSON(messages));
|
||||
return handleMessageJSON(messages);
|
||||
}
|
||||
async function getOlderStories(options: {
|
||||
conversationId?: string;
|
||||
|
@ -1348,13 +1269,11 @@ async function getNewerMessagesByConversation(
|
|||
conversationId: string,
|
||||
{
|
||||
limit = 100,
|
||||
MessageCollection,
|
||||
receivedAt = 0,
|
||||
sentAt = 0,
|
||||
storyId,
|
||||
}: {
|
||||
limit?: number;
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
receivedAt?: number;
|
||||
sentAt?: number;
|
||||
storyId?: UUIDStringType;
|
||||
|
@ -1370,16 +1289,14 @@ async function getNewerMessagesByConversation(
|
|||
}
|
||||
);
|
||||
|
||||
return new MessageCollection(handleMessageJSON(messages));
|
||||
return handleMessageJSON(messages);
|
||||
}
|
||||
async function getLastConversationMessages({
|
||||
conversationId,
|
||||
ourUuid,
|
||||
Message,
|
||||
}: {
|
||||
conversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
Message: typeof MessageModel;
|
||||
}): Promise<LastConversationMessagesType> {
|
||||
const { preview, activity, hasUserInitiatedMessages } =
|
||||
await channels.getLastConversationMessages({
|
||||
|
@ -1388,8 +1305,8 @@ async function getLastConversationMessages({
|
|||
});
|
||||
|
||||
return {
|
||||
preview: preview ? new Message(preview) : undefined,
|
||||
activity: activity ? new Message(activity) : undefined,
|
||||
preview,
|
||||
activity,
|
||||
hasUserInitiatedMessages,
|
||||
};
|
||||
}
|
||||
|
@ -1421,10 +1338,8 @@ async function removeAllMessagesInConversation(
|
|||
conversationId: string,
|
||||
{
|
||||
logId,
|
||||
MessageCollection,
|
||||
}: {
|
||||
logId: string;
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
}
|
||||
) {
|
||||
let messages;
|
||||
|
@ -1437,21 +1352,22 @@ async function removeAllMessagesInConversation(
|
|||
// time so we don't use too much memory.
|
||||
messages = await getOlderMessagesByConversation(conversationId, {
|
||||
limit: chunkSize,
|
||||
MessageCollection,
|
||||
});
|
||||
|
||||
if (!messages.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = messages.map((message: MessageModel) => message.id);
|
||||
const ids = messages.map(message => message.id);
|
||||
|
||||
log.info(`removeAllMessagesInConversation/${logId}: Cleanup...`);
|
||||
// Note: It's very important that these models are fully hydrated because
|
||||
// we need to delete all associated on-disk files along with the database delete.
|
||||
const queue = new window.PQueue({ concurrency: 3, timeout: 1000 * 60 * 2 });
|
||||
queue.addAll(
|
||||
messages.map((message: MessageModel) => async () => message.cleanup())
|
||||
messages.map(
|
||||
(message: MessageType) => async () => cleanupMessage(message)
|
||||
)
|
||||
);
|
||||
await queue.onIdle();
|
||||
|
||||
|
@ -1460,25 +1376,12 @@ async function removeAllMessagesInConversation(
|
|||
} while (messages.length > 0);
|
||||
}
|
||||
|
||||
async function getMessagesBySentAt(
|
||||
sentAt: number,
|
||||
{
|
||||
MessageCollection,
|
||||
}: { MessageCollection: typeof MessageModelCollectionType }
|
||||
) {
|
||||
const messages = await channels.getMessagesBySentAt(sentAt);
|
||||
|
||||
return new MessageCollection(messages);
|
||||
async function getMessagesBySentAt(sentAt: number) {
|
||||
return channels.getMessagesBySentAt(sentAt);
|
||||
}
|
||||
|
||||
async function getExpiredMessages({
|
||||
MessageCollection,
|
||||
}: {
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
}) {
|
||||
const messages = await channels.getExpiredMessages();
|
||||
|
||||
return new MessageCollection(messages);
|
||||
async function getExpiredMessages() {
|
||||
return channels.getExpiredMessages();
|
||||
}
|
||||
|
||||
function getMessagesUnexpectedlyMissingExpirationStartTimestamp() {
|
||||
|
@ -1492,14 +1395,8 @@ function getSoonestMessageExpiry() {
|
|||
async function getNextTapToViewMessageTimestampToAgeOut() {
|
||||
return channels.getNextTapToViewMessageTimestampToAgeOut();
|
||||
}
|
||||
async function getTapToViewMessagesNeedingErase({
|
||||
MessageCollection,
|
||||
}: {
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
}) {
|
||||
const messages = await channels.getTapToViewMessagesNeedingErase();
|
||||
|
||||
return new MessageCollection(messages);
|
||||
async function getTapToViewMessagesNeedingErase() {
|
||||
return channels.getTapToViewMessagesNeedingErase();
|
||||
}
|
||||
|
||||
// Unprocessed
|
||||
|
|
|
@ -6,13 +6,9 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type {
|
||||
ConversationAttributesType,
|
||||
ConversationModelCollectionType,
|
||||
MessageAttributesType,
|
||||
MessageModelCollectionType,
|
||||
SenderKeyInfoType,
|
||||
} from '../model-types.d';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import type { ConversationModel } from '../models/conversations';
|
||||
import type { StoredJob } from '../jobs/types';
|
||||
import type { ReactionType } from '../types/Reactions';
|
||||
import type { ConversationColorType, CustomColorType } from '../types/Colors';
|
||||
|
@ -91,7 +87,7 @@ export type PreKeyType = {
|
|||
publicKey: Uint8Array;
|
||||
};
|
||||
export type PreKeyIdType = PreKeyType['id'];
|
||||
export type SearchResultMessageType = {
|
||||
export type ServerSearchResultMessageType = {
|
||||
json: string;
|
||||
snippet: string;
|
||||
};
|
||||
|
@ -222,18 +218,12 @@ export type UnprocessedUpdateType = {
|
|||
decrypted?: string;
|
||||
};
|
||||
|
||||
export type LastConversationMessagesServerType = {
|
||||
export type LastConversationMessagesType = {
|
||||
activity?: MessageType;
|
||||
preview?: MessageType;
|
||||
hasUserInitiatedMessages: boolean;
|
||||
};
|
||||
|
||||
export type LastConversationMessagesType = {
|
||||
activity?: MessageModel;
|
||||
preview?: MessageModel;
|
||||
hasUserInitiatedMessages: boolean;
|
||||
};
|
||||
|
||||
export type DeleteSentProtoRecipientOptionsType = Readonly<{
|
||||
timestamp: number;
|
||||
recipientUuid: string;
|
||||
|
@ -353,15 +343,33 @@ export type DataInterface = {
|
|||
getConversationCount: () => Promise<number>;
|
||||
saveConversation: (data: ConversationType) => Promise<void>;
|
||||
saveConversations: (array: Array<ConversationType>) => Promise<void>;
|
||||
getConversationById: (id: string) => Promise<ConversationType | undefined>;
|
||||
// updateConversation is a normal data method on Server, a sync batch-add on Client
|
||||
updateConversations: (array: Array<ConversationType>) => Promise<void>;
|
||||
// removeConversation handles either one id or an array on Server, and one id on Client
|
||||
updateAllConversationColors: (
|
||||
conversationColor?: ConversationColorType,
|
||||
customColorData?: {
|
||||
id: string;
|
||||
value: CustomColorType;
|
||||
}
|
||||
) => Promise<void>;
|
||||
|
||||
getAllConversations: () => Promise<Array<ConversationType>>;
|
||||
getAllConversationIds: () => Promise<Array<string>>;
|
||||
getAllPrivateConversations: () => Promise<Array<ConversationType>>;
|
||||
getAllGroupsInvolvingUuid: (
|
||||
id: UUIDStringType
|
||||
) => Promise<Array<ConversationType>>;
|
||||
|
||||
searchConversations: (
|
||||
query: string,
|
||||
options?: { limit?: number }
|
||||
) => Promise<Array<ConversationType>>;
|
||||
// searchMessages is JSON on server, full message on Client
|
||||
// searchMessagesInConversation is JSON on server, full message on Client
|
||||
|
||||
getMessagesById: (messageIds: Array<string>) => Promise<Array<MessageType>>;
|
||||
getMessageCount: (conversationId?: string) => Promise<number>;
|
||||
saveMessage: (
|
||||
data: MessageType,
|
||||
options?: {
|
||||
|
@ -373,30 +381,8 @@ export type DataInterface = {
|
|||
arrayOfMessages: Array<MessageType>,
|
||||
options?: { forceSave?: boolean }
|
||||
) => Promise<void>;
|
||||
getMessageCount: (conversationId?: string) => Promise<number>;
|
||||
getAllMessageIds: () => Promise<Array<string>>;
|
||||
getOlderStories: (options: {
|
||||
conversationId?: string;
|
||||
limit?: number;
|
||||
receivedAt?: number;
|
||||
sentAt?: number;
|
||||
sourceUuid?: string;
|
||||
}) => Promise<Array<MessageType>>;
|
||||
getMessageMetricsForConversation: (
|
||||
conversationId: string,
|
||||
storyId?: UUIDStringType
|
||||
) => Promise<ConversationMetricsType>;
|
||||
hasGroupCallHistoryMessage: (
|
||||
conversationId: string,
|
||||
eraId: string
|
||||
) => Promise<boolean>;
|
||||
migrateConversationMessages: (
|
||||
obsoleteId: string,
|
||||
currentId: string
|
||||
) => Promise<void>;
|
||||
getNextTapToViewMessageTimestampToAgeOut: () => Promise<undefined | number>;
|
||||
_removeAllMessages: () => Promise<void>;
|
||||
|
||||
removeMessage: (id: string) => Promise<void>;
|
||||
removeMessages: (ids: Array<string>) => Promise<void>;
|
||||
getTotalUnreadForConversation: (
|
||||
conversationId: string,
|
||||
storyId?: UUIDStringType
|
||||
|
@ -433,6 +419,50 @@ export type DataInterface = {
|
|||
addReaction: (reactionObj: ReactionType) => Promise<void>;
|
||||
_getAllReactions: () => Promise<Array<ReactionType>>;
|
||||
_removeAllReactions: () => Promise<void>;
|
||||
getMessageBySender: (options: {
|
||||
source: string;
|
||||
sourceUuid: string;
|
||||
sourceDevice: number;
|
||||
sent_at: number;
|
||||
}) => Promise<MessageType | undefined>;
|
||||
getMessageById: (id: string) => Promise<MessageType | undefined>;
|
||||
getMessagesById: (messageIds: Array<string>) => Promise<Array<MessageType>>;
|
||||
_getAllMessages: () => Promise<Array<MessageType>>;
|
||||
_removeAllMessages: () => Promise<void>;
|
||||
getAllMessageIds: () => Promise<Array<string>>;
|
||||
getMessagesBySentAt: (sentAt: number) => Promise<Array<MessageType>>;
|
||||
getExpiredMessages: () => Promise<Array<MessageType>>;
|
||||
getMessagesUnexpectedlyMissingExpirationStartTimestamp: () => Promise<
|
||||
Array<MessageType>
|
||||
>;
|
||||
getSoonestMessageExpiry: () => Promise<undefined | number>;
|
||||
getNextTapToViewMessageTimestampToAgeOut: () => Promise<undefined | number>;
|
||||
getTapToViewMessagesNeedingErase: () => Promise<Array<MessageType>>;
|
||||
// getOlderMessagesByConversation is JSON on server, full message on Client
|
||||
getOlderStories: (options: {
|
||||
conversationId?: string;
|
||||
limit?: number;
|
||||
receivedAt?: number;
|
||||
sentAt?: number;
|
||||
sourceUuid?: string;
|
||||
}) => Promise<Array<MessageType>>;
|
||||
// getNewerMessagesByConversation is JSON on server, full message on Client
|
||||
getMessageMetricsForConversation: (
|
||||
conversationId: string,
|
||||
storyId?: UUIDStringType
|
||||
) => Promise<ConversationMetricsType>;
|
||||
getLastConversationMessages: (options: {
|
||||
conversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
}) => Promise<LastConversationMessagesType>;
|
||||
hasGroupCallHistoryMessage: (
|
||||
conversationId: string,
|
||||
eraId: string
|
||||
) => Promise<boolean>;
|
||||
migrateConversationMessages: (
|
||||
obsoleteId: string,
|
||||
currentId: string
|
||||
) => Promise<void>;
|
||||
|
||||
getUnprocessedCount: () => Promise<number>;
|
||||
getAllUnprocessed: () => Promise<Array<UnprocessedType>>;
|
||||
|
@ -452,11 +482,11 @@ export type DataInterface = {
|
|||
options?: { timestamp?: number }
|
||||
) => Promise<Array<AttachmentDownloadJobType>>;
|
||||
saveAttachmentDownloadJob: (job: AttachmentDownloadJobType) => Promise<void>;
|
||||
resetAttachmentDownloadPending: () => Promise<void>;
|
||||
setAttachmentDownloadJobPending: (
|
||||
id: string,
|
||||
pending: boolean
|
||||
) => Promise<void>;
|
||||
resetAttachmentDownloadPending: () => Promise<void>;
|
||||
removeAttachmentDownloadJob: (id: string) => Promise<void>;
|
||||
removeAllAttachmentDownloadJobs: () => Promise<void>;
|
||||
|
||||
|
@ -541,10 +571,6 @@ export type DataInterface = {
|
|||
getMessageServerGuidsForSpam: (
|
||||
conversationId: string
|
||||
) => Promise<Array<string>>;
|
||||
getMessagesUnexpectedlyMissingExpirationStartTimestamp: () => Promise<
|
||||
Array<MessageType>
|
||||
>;
|
||||
getSoonestMessageExpiry: () => Promise<undefined | number>;
|
||||
|
||||
getJobsInQueue(queueType: string): Promise<Array<StoredJob>>;
|
||||
insertJob(job: Readonly<StoredJob>): Promise<void>;
|
||||
|
@ -556,42 +582,27 @@ export type DataInterface = {
|
|||
processGroupCallRingCancelation(ringId: bigint): Promise<void>;
|
||||
cleanExpiredGroupCallRings(): Promise<void>;
|
||||
|
||||
updateAllConversationColors: (
|
||||
conversationColor?: ConversationColorType,
|
||||
customColorData?: {
|
||||
id: string;
|
||||
value: CustomColorType;
|
||||
}
|
||||
) => Promise<void>;
|
||||
|
||||
getMaxMessageCounter(): Promise<number | undefined>;
|
||||
|
||||
getStatisticsForLogging(): Promise<Record<string, string>>;
|
||||
};
|
||||
|
||||
// The reason for client/server divergence is the need to inject Backbone models and
|
||||
// collections into data calls so those are the objects returned. This was necessary in
|
||||
// July 2018 when creating the Data API as a drop-in replacement for previous database
|
||||
// requests via ORM.
|
||||
|
||||
// Note: It is extremely important that items are duplicated between these two. Client.js
|
||||
// loops over all of its local functions to generate the server-side IPC-based API.
|
||||
|
||||
export type ServerInterface = DataInterface & {
|
||||
getAllConversations: () => Promise<Array<ConversationType>>;
|
||||
getAllGroupsInvolvingUuid: (
|
||||
id: UUIDStringType
|
||||
) => Promise<Array<ConversationType>>;
|
||||
getAllPrivateConversations: () => Promise<Array<ConversationType>>;
|
||||
getConversationById: (id: string) => Promise<ConversationType | undefined>;
|
||||
getExpiredMessages: () => Promise<Array<MessageType>>;
|
||||
getMessageById: (id: string) => Promise<MessageType | undefined>;
|
||||
getMessageBySender: (options: {
|
||||
source: string;
|
||||
sourceUuid: string;
|
||||
sourceDevice: number;
|
||||
sent_at: number;
|
||||
}) => Promise<Array<MessageType>>;
|
||||
getMessagesBySentAt: (sentAt: number) => Promise<Array<MessageType>>;
|
||||
// Differing signature on client/server
|
||||
|
||||
updateConversation: (data: ConversationType) => Promise<void>;
|
||||
removeConversation: (id: Array<string> | string) => Promise<void>;
|
||||
|
||||
searchMessages: (
|
||||
query: string,
|
||||
options?: { limit?: number }
|
||||
) => Promise<Array<ServerSearchResultMessageType>>;
|
||||
searchMessagesInConversation: (
|
||||
query: string,
|
||||
conversationId: string,
|
||||
options?: { limit?: number }
|
||||
) => Promise<Array<ServerSearchResultMessageType>>;
|
||||
|
||||
getOlderMessagesByConversation: (
|
||||
conversationId: string,
|
||||
options?: {
|
||||
|
@ -611,38 +622,15 @@ export type ServerInterface = DataInterface & {
|
|||
storyId?: UUIDStringType;
|
||||
}
|
||||
) => Promise<Array<MessageTypeUnhydrated>>;
|
||||
getLastConversationMessages: (options: {
|
||||
conversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
}) => Promise<LastConversationMessagesServerType>;
|
||||
getTapToViewMessagesNeedingErase: () => Promise<Array<MessageType>>;
|
||||
removeConversation: (id: Array<string> | string) => Promise<void>;
|
||||
removeMessage: (id: string) => Promise<void>;
|
||||
removeMessages: (ids: Array<string>) => Promise<void>;
|
||||
searchMessages: (
|
||||
query: string,
|
||||
options?: { limit?: number }
|
||||
) => Promise<Array<SearchResultMessageType>>;
|
||||
searchMessagesInConversation: (
|
||||
query: string,
|
||||
conversationId: string,
|
||||
options?: { limit?: number }
|
||||
) => Promise<Array<SearchResultMessageType>>;
|
||||
updateConversation: (data: ConversationType) => Promise<void>;
|
||||
|
||||
// For testing only
|
||||
_getAllMessages: () => Promise<Array<MessageType>>;
|
||||
|
||||
// Server-only
|
||||
|
||||
getCorruptionLog: () => string;
|
||||
|
||||
initialize: (options: {
|
||||
configDir: string;
|
||||
key: string;
|
||||
logger: LoggerType;
|
||||
}) => Promise<void>;
|
||||
|
||||
initializeRenderer: (options: {
|
||||
configDir: string;
|
||||
key: string;
|
||||
|
@ -659,83 +647,11 @@ export type ServerInterface = DataInterface & {
|
|||
};
|
||||
|
||||
export type ClientInterface = DataInterface & {
|
||||
getAllConversations: (options: {
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
}) => Promise<ConversationModelCollectionType>;
|
||||
getAllGroupsInvolvingUuid: (
|
||||
id: UUIDStringType,
|
||||
options: {
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
}
|
||||
) => Promise<ConversationModelCollectionType>;
|
||||
getAllPrivateConversations: (options: {
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
}) => Promise<ConversationModelCollectionType>;
|
||||
getConversationById: (
|
||||
id: string,
|
||||
options: { Conversation: typeof ConversationModel }
|
||||
) => Promise<ConversationModel | undefined>;
|
||||
getExpiredMessages: (options: {
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
}) => Promise<MessageModelCollectionType>;
|
||||
getMessageById: (
|
||||
id: string,
|
||||
options: { Message: typeof MessageModel }
|
||||
) => Promise<MessageModel | undefined>;
|
||||
getMessageBySender: (
|
||||
data: {
|
||||
source: string;
|
||||
sourceUuid: string;
|
||||
sourceDevice: number;
|
||||
sent_at: number;
|
||||
},
|
||||
options: { Message: typeof MessageModel }
|
||||
) => Promise<MessageModel | null>;
|
||||
getMessagesBySentAt: (
|
||||
sentAt: number,
|
||||
options: { MessageCollection: typeof MessageModelCollectionType }
|
||||
) => Promise<MessageModelCollectionType>;
|
||||
getOlderMessagesByConversation: (
|
||||
conversationId: string,
|
||||
options: {
|
||||
limit?: number;
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
messageId?: string;
|
||||
receivedAt?: number;
|
||||
sentAt?: number;
|
||||
storyId?: UUIDStringType;
|
||||
}
|
||||
) => Promise<MessageModelCollectionType>;
|
||||
getNewerMessagesByConversation: (
|
||||
conversationId: string,
|
||||
options: {
|
||||
limit?: number;
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
receivedAt?: number;
|
||||
sentAt?: number;
|
||||
storyId?: UUIDStringType;
|
||||
}
|
||||
) => Promise<MessageModelCollectionType>;
|
||||
getLastConversationMessages: (options: {
|
||||
conversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
Message: typeof MessageModel;
|
||||
}) => Promise<LastConversationMessagesType>;
|
||||
getTapToViewMessagesNeedingErase: (options: {
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
}) => Promise<MessageModelCollectionType>;
|
||||
removeConversation: (
|
||||
id: string,
|
||||
options: { Conversation: typeof ConversationModel }
|
||||
) => Promise<void>;
|
||||
removeMessage: (
|
||||
id: string,
|
||||
options: { Message: typeof MessageModel }
|
||||
) => Promise<void>;
|
||||
removeMessages: (
|
||||
ids: Array<string>,
|
||||
options: { Message: typeof MessageModel }
|
||||
) => Promise<void>;
|
||||
// Differing signature on client/server
|
||||
|
||||
updateConversation: (data: ConversationType) => void;
|
||||
removeConversation: (id: string) => Promise<void>;
|
||||
|
||||
searchMessages: (
|
||||
query: string,
|
||||
options?: { limit?: number }
|
||||
|
@ -745,13 +661,26 @@ export type ClientInterface = DataInterface & {
|
|||
conversationId: string,
|
||||
options?: { limit?: number }
|
||||
) => Promise<Array<ClientSearchResultMessageType>>;
|
||||
updateConversation: (data: ConversationType, extra?: unknown) => void;
|
||||
|
||||
// Test-only
|
||||
|
||||
_getAllMessages: (options: {
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
}) => Promise<MessageModelCollectionType>;
|
||||
getOlderMessagesByConversation: (
|
||||
conversationId: string,
|
||||
options: {
|
||||
limit?: number;
|
||||
messageId?: string;
|
||||
receivedAt?: number;
|
||||
sentAt?: number;
|
||||
storyId?: UUIDStringType;
|
||||
}
|
||||
) => Promise<Array<MessageAttributesType>>;
|
||||
getNewerMessagesByConversation: (
|
||||
conversationId: string,
|
||||
options: {
|
||||
limit?: number;
|
||||
receivedAt?: number;
|
||||
sentAt?: number;
|
||||
storyId?: UUIDStringType;
|
||||
}
|
||||
) => Promise<Array<MessageAttributesType>>;
|
||||
|
||||
// Client-side only
|
||||
|
||||
|
@ -760,21 +689,16 @@ export type ClientInterface = DataInterface & {
|
|||
conversationId: string,
|
||||
options: {
|
||||
logId: string;
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
}
|
||||
) => Promise<void>;
|
||||
removeOtherData: () => Promise<void>;
|
||||
cleanupOrphanedAttachments: () => Promise<void>;
|
||||
ensureFilePermissions: () => Promise<void>;
|
||||
|
||||
// Client-side only, and test-only
|
||||
|
||||
_removeConversations: (ids: Array<string>) => Promise<void>;
|
||||
_jobs: { [id: string]: ClientJobType };
|
||||
|
||||
// These are defined on the server-only and used in the client to determine
|
||||
// whether we should use IPC to use the database in the main process or
|
||||
// use the db already running in the renderer.
|
||||
// To decide whether to use IPC to use the database in the main process or
|
||||
// use the db already running in the renderer.
|
||||
goBackToMainProcess: () => Promise<void>;
|
||||
startInRendererProcess: (isTesting?: boolean) => Promise<void>;
|
||||
};
|
||||
|
|
|
@ -80,13 +80,13 @@ import type {
|
|||
IdentityKeyType,
|
||||
ItemKeyType,
|
||||
ItemType,
|
||||
LastConversationMessagesServerType,
|
||||
LastConversationMessagesType,
|
||||
MessageMetricsType,
|
||||
MessageType,
|
||||
MessageTypeUnhydrated,
|
||||
PreKeyIdType,
|
||||
PreKeyType,
|
||||
SearchResultMessageType,
|
||||
ServerSearchResultMessageType,
|
||||
SenderKeyIdType,
|
||||
SenderKeyType,
|
||||
SentMessageDBType,
|
||||
|
@ -152,16 +152,16 @@ const dataInterface: ServerInterface = {
|
|||
|
||||
createOrUpdateSignedPreKey,
|
||||
getSignedPreKeyById,
|
||||
getAllSignedPreKeys,
|
||||
bulkAddSignedPreKeys,
|
||||
removeSignedPreKeyById,
|
||||
removeAllSignedPreKeys,
|
||||
getAllSignedPreKeys,
|
||||
|
||||
createOrUpdateItem,
|
||||
getItemById,
|
||||
getAllItems,
|
||||
removeItemById,
|
||||
removeAllItems,
|
||||
getAllItems,
|
||||
|
||||
createOrUpdateSenderKey,
|
||||
getSenderKeyById,
|
||||
|
@ -189,6 +189,7 @@ const dataInterface: ServerInterface = {
|
|||
removeAllSessions,
|
||||
getAllSessions,
|
||||
|
||||
eraseStorageServiceStateFromConversations,
|
||||
getConversationCount,
|
||||
saveConversation,
|
||||
saveConversations,
|
||||
|
@ -196,12 +197,12 @@ const dataInterface: ServerInterface = {
|
|||
updateConversation,
|
||||
updateConversations,
|
||||
removeConversation,
|
||||
eraseStorageServiceStateFromConversations,
|
||||
updateAllConversationColors,
|
||||
|
||||
getAllConversations,
|
||||
getAllConversationIds,
|
||||
getAllPrivateConversations,
|
||||
getAllGroupsInvolvingUuid,
|
||||
updateAllConversationColors,
|
||||
|
||||
searchConversations,
|
||||
searchMessages,
|
||||
|
@ -250,8 +251,8 @@ const dataInterface: ServerInterface = {
|
|||
|
||||
getNextAttachmentDownloadJobs,
|
||||
saveAttachmentDownloadJob,
|
||||
setAttachmentDownloadJobPending,
|
||||
resetAttachmentDownloadPending,
|
||||
setAttachmentDownloadJobPending,
|
||||
removeAttachmentDownloadJob,
|
||||
removeAllAttachmentDownloadJobs,
|
||||
|
||||
|
@ -1557,7 +1558,7 @@ async function searchConversations(
|
|||
async function searchMessages(
|
||||
query: string,
|
||||
params: { limit?: number; conversationId?: string } = {}
|
||||
): Promise<Array<SearchResultMessageType>> {
|
||||
): Promise<Array<ServerSearchResultMessageType>> {
|
||||
const { limit = 500, conversationId } = params;
|
||||
|
||||
const db = getInstance();
|
||||
|
@ -1663,7 +1664,7 @@ async function searchMessagesInConversation(
|
|||
query: string,
|
||||
conversationId: string,
|
||||
{ limit = 100 }: { limit?: number } = {}
|
||||
): Promise<Array<SearchResultMessageType>> {
|
||||
): Promise<Array<ServerSearchResultMessageType>> {
|
||||
return searchMessages(query, { conversationId, limit });
|
||||
}
|
||||
|
||||
|
@ -2024,7 +2025,7 @@ async function getMessageBySender({
|
|||
sourceUuid: string;
|
||||
sourceDevice: number;
|
||||
sent_at: number;
|
||||
}): Promise<Array<MessageType>> {
|
||||
}): Promise<MessageType | undefined> {
|
||||
const db = getInstance();
|
||||
const rows: JSONRows = prepare(
|
||||
db,
|
||||
|
@ -2032,7 +2033,8 @@ async function getMessageBySender({
|
|||
SELECT json FROM messages WHERE
|
||||
(source = $source OR sourceUuid = $sourceUuid) AND
|
||||
sourceDevice = $sourceDevice AND
|
||||
sent_at = $sent_at;
|
||||
sent_at = $sent_at
|
||||
LIMIT 2;
|
||||
`
|
||||
).all({
|
||||
source,
|
||||
|
@ -2041,7 +2043,20 @@ async function getMessageBySender({
|
|||
sent_at,
|
||||
});
|
||||
|
||||
return rows.map(row => jsonToObject(row.json));
|
||||
if (rows.length > 1) {
|
||||
log.warn('getMessageBySender: More than one message found for', {
|
||||
sent_at,
|
||||
source,
|
||||
sourceUuid,
|
||||
sourceDevice,
|
||||
});
|
||||
}
|
||||
|
||||
if (rows.length < 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return jsonToObject(rows[0].json);
|
||||
}
|
||||
|
||||
async function getUnreadByConversationAndMarkRead({
|
||||
|
@ -2605,7 +2620,7 @@ async function getLastConversationMessages({
|
|||
}: {
|
||||
conversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
}): Promise<LastConversationMessagesServerType> {
|
||||
}): Promise<LastConversationMessagesType> {
|
||||
const db = getInstance();
|
||||
|
||||
return db.transaction(() => {
|
||||
|
|
|
@ -187,7 +187,7 @@ export type GetContactOptions = Pick<
|
|||
'conversationSelector' | 'ourConversationId' | 'ourNumber' | 'ourUuid'
|
||||
>;
|
||||
|
||||
function getContactId(
|
||||
export function getContactId(
|
||||
message: MessageWithUIFieldsType,
|
||||
{
|
||||
conversationSelector,
|
||||
|
|
|
@ -12,6 +12,7 @@ import type { CallbackResultType } from '../../textsecure/Types.d';
|
|||
import type { StorageAccessType } from '../../types/Storage.d';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
import { getContact } from '../../messages/helpers';
|
||||
|
||||
describe('Message', () => {
|
||||
const STORAGE_KEYS_TO_RESTORE: Array<keyof StorageAccessType> = [
|
||||
|
@ -204,7 +205,7 @@ describe('Message', () => {
|
|||
it('gets outgoing contact', () => {
|
||||
const messages = new window.Whisper.MessageCollection();
|
||||
const message = messages.add(attributes);
|
||||
message.getContact();
|
||||
assert.exists(getContact(message.attributes));
|
||||
});
|
||||
|
||||
it('gets incoming contact', () => {
|
||||
|
@ -213,7 +214,7 @@ describe('Message', () => {
|
|||
type: 'incoming',
|
||||
source,
|
||||
});
|
||||
message.getContact();
|
||||
assert.exists(getContact(message.attributes));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -28,12 +28,7 @@ describe('sql/allMedia', () => {
|
|||
|
||||
describe('getMessagesWithVisualMediaAttachments', () => {
|
||||
it('returns messages matching with visual attachments', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -69,12 +64,7 @@ describe('sql/allMedia', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const searchResults = await getMessagesWithVisualMediaAttachments(
|
||||
conversationId,
|
||||
|
@ -85,12 +75,7 @@ describe('sql/allMedia', () => {
|
|||
});
|
||||
|
||||
it('excludes stories and story replies', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -129,12 +114,7 @@ describe('sql/allMedia', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const searchResults = await getMessagesWithVisualMediaAttachments(
|
||||
conversationId,
|
||||
|
@ -147,12 +127,7 @@ describe('sql/allMedia', () => {
|
|||
|
||||
describe('getMessagesWithFileAttachments', () => {
|
||||
it('returns messages matching with visual attachments', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -188,12 +163,7 @@ describe('sql/allMedia', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const searchResults = await getMessagesWithFileAttachments(
|
||||
conversationId,
|
||||
|
@ -204,12 +174,7 @@ describe('sql/allMedia', () => {
|
|||
});
|
||||
|
||||
it('excludes stories and story replies', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -248,12 +213,7 @@ describe('sql/allMedia', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const searchResults = await getMessagesWithFileAttachments(
|
||||
conversationId,
|
||||
|
|
|
@ -27,12 +27,7 @@ describe('sql/fullTextSearch', () => {
|
|||
});
|
||||
|
||||
it('returns messages matching query', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -66,12 +61,7 @@ describe('sql/fullTextSearch', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const searchResults = await searchMessages('unique');
|
||||
assert.lengthOf(searchResults, 1);
|
||||
|
@ -87,12 +77,7 @@ describe('sql/fullTextSearch', () => {
|
|||
});
|
||||
|
||||
it('excludes messages with isViewOnce = true', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -128,12 +113,7 @@ describe('sql/fullTextSearch', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const searchResults = await searchMessages('unique');
|
||||
assert.lengthOf(searchResults, 1);
|
||||
|
@ -148,12 +128,7 @@ describe('sql/fullTextSearch', () => {
|
|||
});
|
||||
|
||||
it('excludes messages with storyId !== null', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -189,12 +164,7 @@ describe('sql/fullTextSearch', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const searchResults = await searchMessages('unique');
|
||||
assert.lengthOf(searchResults, 1);
|
||||
|
|
|
@ -34,12 +34,7 @@ describe('sql/markRead', () => {
|
|||
});
|
||||
|
||||
it('properly finds and reads unread messages in current conversation', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const start = Date.now();
|
||||
const readAt = start + 20;
|
||||
|
@ -125,12 +120,7 @@ describe('sql/markRead', () => {
|
|||
}
|
||||
);
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
7
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 7);
|
||||
assert.strictEqual(
|
||||
await getTotalUnreadForConversation(conversationId),
|
||||
3,
|
||||
|
@ -179,12 +169,7 @@ describe('sql/markRead', () => {
|
|||
});
|
||||
|
||||
it('properly finds and reads unread messages in story', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const start = Date.now();
|
||||
const readAt = start + 20;
|
||||
|
@ -276,12 +261,7 @@ describe('sql/markRead', () => {
|
|||
}
|
||||
);
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
7
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 7);
|
||||
|
||||
const markedRead = await getUnreadByConversationAndMarkRead({
|
||||
conversationId,
|
||||
|
@ -311,12 +291,7 @@ describe('sql/markRead', () => {
|
|||
});
|
||||
|
||||
it('properly starts disappearing message timer, even if message is already read', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const start = Date.now();
|
||||
const readAt = start + 20;
|
||||
|
@ -388,12 +363,7 @@ describe('sql/markRead', () => {
|
|||
2,
|
||||
'unread count'
|
||||
);
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
5
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 5);
|
||||
|
||||
const markedRead = await getUnreadByConversationAndMarkRead({
|
||||
conversationId,
|
||||
|
@ -413,21 +383,21 @@ describe('sql/markRead', () => {
|
|||
'unread count'
|
||||
);
|
||||
|
||||
const allMessages = await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
});
|
||||
const allMessages = await _getAllMessages();
|
||||
const sorted = allMessages.sort(
|
||||
(left, right) => left.timestamp - right.timestamp
|
||||
);
|
||||
|
||||
// Ascending order, since it's sorted by MessageCollection
|
||||
assert.strictEqual(allMessages.at(1).id, message2.id);
|
||||
assert.strictEqual(sorted[1].id, message2.id, 'checking message 2');
|
||||
assert.isAtMost(
|
||||
allMessages.at(1).get('expirationStartTimestamp') ?? Infinity,
|
||||
sorted[1].expirationStartTimestamp ?? Infinity,
|
||||
Date.now(),
|
||||
'checking message 2 expirationStartTimestamp'
|
||||
);
|
||||
|
||||
assert.strictEqual(allMessages.at(3).id, message4.id, 'checking message 4');
|
||||
assert.strictEqual(sorted[3].id, message4.id, 'checking message 4');
|
||||
assert.isAtMost(
|
||||
allMessages.at(3).get('expirationStartTimestamp') ?? Infinity,
|
||||
sorted[3].expirationStartTimestamp ?? Infinity,
|
||||
Date.now(),
|
||||
'checking message 4 expirationStartTimestamp'
|
||||
);
|
||||
|
@ -490,12 +460,7 @@ describe('sql/markRead', () => {
|
|||
await saveMessages([message1, message2, message3, message4, message5], {
|
||||
forceSave: true,
|
||||
});
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
5
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 5);
|
||||
|
||||
const reaction1: ReactionType = {
|
||||
conversationId,
|
||||
|
@ -642,12 +607,7 @@ describe('sql/markRead', () => {
|
|||
await saveMessages([message1, message2, message3, message4, message5], {
|
||||
forceSave: true,
|
||||
});
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
5
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 5);
|
||||
|
||||
const reaction1: ReactionType = {
|
||||
conversationId,
|
||||
|
|
|
@ -124,7 +124,7 @@ describe('sql/sendLog', () => {
|
|||
|
||||
assert.strictEqual(actual.timestamp, proto.timestamp);
|
||||
|
||||
await removeMessage(id, { Message: window.Whisper.Message });
|
||||
await removeMessage(id);
|
||||
|
||||
assert.lengthOf(await getAllSentProtos(), 0);
|
||||
});
|
||||
|
|
|
@ -23,12 +23,7 @@ describe('sql/stories', () => {
|
|||
|
||||
describe('getOlderStories', () => {
|
||||
it('returns N most recent stories overall, or in converation, or by author', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -89,12 +84,7 @@ describe('sql/stories', () => {
|
|||
forceSave: true,
|
||||
});
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
5
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 5);
|
||||
|
||||
const stories = await getOlderStories({
|
||||
limit: 5,
|
||||
|
@ -155,12 +145,7 @@ describe('sql/stories', () => {
|
|||
});
|
||||
|
||||
it('returns N stories older than provided receivedAt/sentAt', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const start = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -214,12 +199,7 @@ describe('sql/stories', () => {
|
|||
forceSave: true,
|
||||
});
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
5
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 5);
|
||||
|
||||
const stories = await getOlderStories({
|
||||
receivedAt: story4.received_at,
|
||||
|
|
|
@ -30,12 +30,7 @@ describe('sql/timelineFetches', () => {
|
|||
|
||||
describe('getOlderMessagesByConversation', () => {
|
||||
it('returns N most recent messages', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -92,30 +87,20 @@ describe('sql/timelineFetches', () => {
|
|||
forceSave: true,
|
||||
});
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
5
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 5);
|
||||
|
||||
const messages = await getOlderMessagesByConversation(conversationId, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 5,
|
||||
});
|
||||
assert.lengthOf(messages, 2);
|
||||
// They are not in DESC order because MessageCollection is sorting them
|
||||
assert.strictEqual(messages.at(0).attributes.id, message1.id);
|
||||
assert.strictEqual(messages.at(1).attributes.id, message2.id);
|
||||
|
||||
// Fetched with DESC query, but with reverse() call afterwards
|
||||
assert.strictEqual(messages[0].id, message1.id);
|
||||
assert.strictEqual(messages[1].id, message2.id);
|
||||
});
|
||||
|
||||
it('returns N most recent messages for a given story', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -152,29 +137,18 @@ describe('sql/timelineFetches', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const messages = await getOlderMessagesByConversation(conversationId, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 5,
|
||||
storyId,
|
||||
});
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.strictEqual(messages.at(0).attributes.id, message2.id);
|
||||
assert.strictEqual(messages[0].id, message2.id);
|
||||
});
|
||||
|
||||
it('returns N messages older than provided received_at', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const target = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -208,30 +182,19 @@ describe('sql/timelineFetches', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const messages = await getOlderMessagesByConversation(conversationId, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 5,
|
||||
receivedAt: target,
|
||||
sentAt: target,
|
||||
});
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.strictEqual(messages.at(0).attributes.id, message1.id);
|
||||
assert.strictEqual(messages[0].id, message1.id);
|
||||
});
|
||||
|
||||
it('returns N older messages with received_at, lesser sent_at', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const target = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -265,33 +228,23 @@ describe('sql/timelineFetches', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const messages = await getOlderMessagesByConversation(conversationId, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 5,
|
||||
receivedAt: target,
|
||||
sentAt: target,
|
||||
});
|
||||
|
||||
assert.lengthOf(messages, 2);
|
||||
// They are not in DESC order because MessageCollection is sorting them
|
||||
assert.strictEqual(messages.at(0).attributes.id, message1.id);
|
||||
assert.strictEqual(messages.at(1).attributes.id, message2.id);
|
||||
|
||||
// Fetched with DESC query, but with reverse() call afterwards
|
||||
assert.strictEqual(messages[0].id, message1.id, 'checking message 1');
|
||||
assert.strictEqual(messages[1].id, message2.id, 'checking message 2');
|
||||
});
|
||||
|
||||
it('returns N older messages, same received_at/sent_at but excludes messageId', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const target = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -325,15 +278,9 @@ describe('sql/timelineFetches', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const messages = await getOlderMessagesByConversation(conversationId, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 5,
|
||||
receivedAt: target,
|
||||
sentAt: target,
|
||||
|
@ -341,18 +288,13 @@ describe('sql/timelineFetches', () => {
|
|||
});
|
||||
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.strictEqual(messages.at(0).attributes.id, message1.id);
|
||||
assert.strictEqual(messages[0].id, message1.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNewerMessagesByConversation', () => {
|
||||
it('returns N oldest messages with no parameters', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -409,30 +351,19 @@ describe('sql/timelineFetches', () => {
|
|||
forceSave: true,
|
||||
});
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
5
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 5);
|
||||
|
||||
const messages = await getNewerMessagesByConversation(conversationId, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
assert.lengthOf(messages, 2);
|
||||
assert.strictEqual(messages.at(0).attributes.id, message4.id);
|
||||
assert.strictEqual(messages.at(1).attributes.id, message5.id);
|
||||
assert.strictEqual(messages[0].id, message4.id, 'checking message 4');
|
||||
assert.strictEqual(messages[1].id, message5.id, 'checking message 5');
|
||||
});
|
||||
|
||||
it('returns N oldest messages for a given story with no parameters', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const now = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -469,30 +400,19 @@ describe('sql/timelineFetches', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const messages = await getNewerMessagesByConversation(conversationId, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 5,
|
||||
storyId,
|
||||
});
|
||||
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.strictEqual(messages.at(0).attributes.id, message2.id);
|
||||
assert.strictEqual(messages[0].id, message2.id);
|
||||
});
|
||||
|
||||
it('returns N messages newer than provided received_at', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const target = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -526,30 +446,19 @@ describe('sql/timelineFetches', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const messages = await getNewerMessagesByConversation(conversationId, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 5,
|
||||
receivedAt: target,
|
||||
sentAt: target,
|
||||
});
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.strictEqual(messages.at(0).attributes.id, message3.id);
|
||||
assert.strictEqual(messages[0].id, message3.id);
|
||||
});
|
||||
|
||||
it('returns N newer messages with same received_at, greater sent_at', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const target = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -583,15 +492,9 @@ describe('sql/timelineFetches', () => {
|
|||
|
||||
await saveMessages([message1, message2, message3], { forceSave: true });
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
3
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 3);
|
||||
|
||||
const messages = await getNewerMessagesByConversation(conversationId, {
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
limit: 5,
|
||||
receivedAt: target,
|
||||
sentAt: target,
|
||||
|
@ -599,19 +502,14 @@ describe('sql/timelineFetches', () => {
|
|||
|
||||
assert.lengthOf(messages, 2);
|
||||
// They are not in DESC order because MessageCollection is sorting them
|
||||
assert.strictEqual(messages.at(0).attributes.id, message2.id);
|
||||
assert.strictEqual(messages.at(1).attributes.id, message3.id);
|
||||
assert.strictEqual(messages[0].id, message2.id);
|
||||
assert.strictEqual(messages[1].id, message3.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessageMetricsForConversation', () => {
|
||||
it('returns metrics properly for story and non-story timelines', async () => {
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
0
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 0);
|
||||
|
||||
const target = Date.now();
|
||||
const conversationId = getUuid();
|
||||
|
@ -710,12 +608,7 @@ describe('sql/timelineFetches', () => {
|
|||
{ forceSave: true }
|
||||
);
|
||||
|
||||
assert.lengthOf(
|
||||
await _getAllMessages({
|
||||
MessageCollection: window.Whisper.MessageCollection,
|
||||
}),
|
||||
8
|
||||
);
|
||||
assert.lengthOf(await _getAllMessages(), 8);
|
||||
|
||||
const metricsInTimeline = await getMessageMetricsForConversation(
|
||||
conversationId
|
||||
|
|
|
@ -11,8 +11,6 @@ import { SignalProtocolStore } from '../../SignalProtocolStore';
|
|||
import type { ConversationModel } from '../../models/conversations';
|
||||
import * as KeyChangeListener from '../../textsecure/KeyChangeListener';
|
||||
|
||||
const { Whisper } = window;
|
||||
|
||||
describe('KeyChangeListener', () => {
|
||||
let oldNumberId: string | undefined;
|
||||
let oldUuidId: string | undefined;
|
||||
|
@ -71,11 +69,8 @@ describe('KeyChangeListener', () => {
|
|||
afterEach(async () => {
|
||||
await window.Signal.Data.removeAllMessagesInConversation(convo.id, {
|
||||
logId: uuidWithKeyChange,
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
});
|
||||
await window.Signal.Data.removeConversation(convo.id, {
|
||||
Conversation: Whisper.Conversation,
|
||||
});
|
||||
await window.Signal.Data.removeConversation(convo.id);
|
||||
|
||||
await store.removeIdentityKey(new UUID(uuidWithKeyChange));
|
||||
});
|
||||
|
@ -107,11 +102,8 @@ describe('KeyChangeListener', () => {
|
|||
afterEach(async () => {
|
||||
await window.Signal.Data.removeAllMessagesInConversation(groupConvo.id, {
|
||||
logId: uuidWithKeyChange,
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
});
|
||||
await window.Signal.Data.removeConversation(groupConvo.id, {
|
||||
Conversation: Whisper.Conversation,
|
||||
});
|
||||
await window.Signal.Data.removeConversation(groupConvo.id);
|
||||
});
|
||||
|
||||
it('generates a key change notice in the group conversation with this contact', done => {
|
||||
|
|
|
@ -5,6 +5,7 @@ import type { MessageModel } from '../models/messages';
|
|||
import * as durations from './durations';
|
||||
import { map, filter } from './iterables';
|
||||
import { isNotNil } from './isNotNil';
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
|
||||
const FIVE_MINUTES = 5 * durations.MINUTE;
|
||||
|
||||
|
@ -29,9 +30,12 @@ export class MessageController {
|
|||
return instance;
|
||||
}
|
||||
|
||||
register(id: string, message: MessageModel): MessageModel {
|
||||
if (!id || !message) {
|
||||
return message;
|
||||
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];
|
||||
|
@ -43,6 +47,8 @@ export class MessageController {
|
|||
return existing.message;
|
||||
}
|
||||
|
||||
const message =
|
||||
'attributes' in data ? data : new window.Whisper.Message(data);
|
||||
this.messageLookup[id] = {
|
||||
message,
|
||||
timestamp: Date.now(),
|
||||
|
|
36
ts/util/cleanup.ts
Normal file
36
ts/util/cleanup.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { MessageAttributesType } from '../model-types.d';
|
||||
import { deletePackReference } from '../types/Stickers';
|
||||
|
||||
export async function cleanupMessage(
|
||||
message: MessageAttributesType
|
||||
): Promise<void> {
|
||||
const { id, conversationId } = message;
|
||||
|
||||
window.reduxActions?.conversations.messageDeleted(id, conversationId);
|
||||
|
||||
const parentConversation = window.ConversationController.get(conversationId);
|
||||
parentConversation?.debouncedUpdateLastMessage?.();
|
||||
|
||||
window.MessageController.unregister(id);
|
||||
|
||||
await deleteMessageData(message);
|
||||
}
|
||||
|
||||
export async function deleteMessageData(
|
||||
message: MessageAttributesType
|
||||
): Promise<void> {
|
||||
await window.Signal.Migrations.deleteExternalMessageFiles(message);
|
||||
|
||||
const { sticker } = message;
|
||||
if (!sticker) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { packId } = sticker;
|
||||
if (packId) {
|
||||
await deletePackReference(message.id, packId);
|
||||
}
|
||||
}
|
|
@ -347,9 +347,7 @@ async function getRetryConversation({
|
|||
}
|
||||
|
||||
const [messageId] = messageIds;
|
||||
const message = await window.Signal.Data.getMessageById(messageId, {
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
const message = await window.Signal.Data.getMessageById(messageId);
|
||||
if (!message) {
|
||||
log.warn(
|
||||
`getRetryConversation/${logId}: Unable to find message ${messageId}`
|
||||
|
@ -358,7 +356,7 @@ async function getRetryConversation({
|
|||
return window.ConversationController.get(requestGroupId);
|
||||
}
|
||||
|
||||
const conversationId = message.get('conversationId');
|
||||
const { conversationId } = message;
|
||||
return window.ConversationController.get(conversationId);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import type {
|
|||
MessageAttributesType as MediaItemMessageType,
|
||||
} from '../types/MediaItem';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import { getContactId } from '../messages/helpers';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { maybeParseUrl } from '../util/url';
|
||||
import { enqueueReactionForSend } from '../reactions/enqueueReactionForSend';
|
||||
|
@ -447,14 +448,12 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
const { authorId, sentAt } = options;
|
||||
|
||||
const conversationId = this.model.id;
|
||||
const messages = await getMessagesBySentAt(sentAt, {
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
});
|
||||
const messages = await getMessagesBySentAt(sentAt);
|
||||
const message = messages.find(item =>
|
||||
Boolean(
|
||||
item.get('conversationId') === conversationId &&
|
||||
item.conversationId === conversationId &&
|
||||
authorId &&
|
||||
item.getContactId() === authorId
|
||||
getContactId(item) === authorId
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -471,14 +470,12 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
return;
|
||||
}
|
||||
|
||||
const message = await getMessageById(messageId, {
|
||||
Message: Whisper.Message,
|
||||
});
|
||||
const message = await getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`markMessageRead: failed to load message ${messageId}`);
|
||||
}
|
||||
|
||||
await this.model.markRead(message.get('received_at'));
|
||||
await this.model.markRead(message.received_at);
|
||||
};
|
||||
|
||||
const createMessageRequestResponseHandler =
|
||||
|
@ -883,9 +880,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
}
|
||||
|
||||
async scrollToMessage(messageId: string): Promise<void> {
|
||||
const message = await getMessageById(messageId, {
|
||||
Message: Whisper.Message,
|
||||
});
|
||||
const message = await getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`scrollToMessage: failed to load message ${messageId}`);
|
||||
}
|
||||
|
@ -1201,9 +1196,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
|
||||
async onOpened(messageId: string): Promise<void> {
|
||||
if (messageId) {
|
||||
const message = await getMessageById(messageId, {
|
||||
Message: Whisper.Message,
|
||||
});
|
||||
const message = await getMessageById(messageId);
|
||||
|
||||
if (message) {
|
||||
this.model.loadAndScroll(messageId);
|
||||
|
@ -1254,16 +1247,14 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
if (!messageFromCache) {
|
||||
log.info('showForwardMessageModal: Fetching message from database');
|
||||
}
|
||||
const message =
|
||||
messageFromCache ||
|
||||
(await window.Signal.Data.getMessageById(messageId, {
|
||||
Message: window.Whisper.Message,
|
||||
}));
|
||||
const found =
|
||||
messageFromCache || (await window.Signal.Data.getMessageById(messageId));
|
||||
|
||||
if (!message) {
|
||||
if (!found) {
|
||||
throw new Error(`showForwardMessageModal: Message ${messageId} missing!`);
|
||||
}
|
||||
|
||||
const message = window.MessageController.register(found.id, found);
|
||||
const attachments = getAttachmentsForMessage(message.attributes);
|
||||
|
||||
this.forwardMessageModal = new Whisper.ReactWrapperView({
|
||||
|
@ -1911,10 +1902,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
message: window.i18n('deleteWarning'),
|
||||
okText: window.i18n('delete'),
|
||||
resolve: () => {
|
||||
window.Signal.Data.removeMessage(message.id, {
|
||||
Message: Whisper.Message,
|
||||
});
|
||||
message.cleanup();
|
||||
window.Signal.Data.removeMessage(message.id);
|
||||
if (isOutgoing(message.attributes)) {
|
||||
this.model.decrementSentMessageCount();
|
||||
} else {
|
||||
|
@ -2676,18 +2664,16 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
}
|
||||
|
||||
async setQuoteMessage(messageId: null | string): Promise<void> {
|
||||
const { model }: { model: ConversationModel } = this;
|
||||
|
||||
const message: MessageModel | undefined = messageId
|
||||
? await getMessageById(messageId, {
|
||||
Message: Whisper.Message,
|
||||
})
|
||||
const { model } = this;
|
||||
const found = messageId ? await getMessageById(messageId) : undefined;
|
||||
const message = found
|
||||
? window.MessageController.register(found.id, found)
|
||||
: undefined;
|
||||
|
||||
if (
|
||||
message &&
|
||||
found &&
|
||||
!canReply(
|
||||
message.attributes,
|
||||
found,
|
||||
window.ConversationController.getOurConversationIdOrThrow(),
|
||||
findAndFormatContact
|
||||
)
|
||||
|
@ -2724,18 +2710,11 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
}
|
||||
|
||||
if (message) {
|
||||
const quotedMessage = window.MessageController.register(
|
||||
message.id,
|
||||
message
|
||||
);
|
||||
this.quotedMessage = quotedMessage;
|
||||
this.quotedMessage = message;
|
||||
this.quote = await model.makeQuote(this.quotedMessage);
|
||||
|
||||
if (quotedMessage) {
|
||||
this.quote = await model.makeQuote(this.quotedMessage);
|
||||
|
||||
this.enableMessageField();
|
||||
this.focusMessageField();
|
||||
}
|
||||
this.enableMessageField();
|
||||
this.focusMessageField();
|
||||
}
|
||||
|
||||
this.renderQuotedMessage();
|
||||
|
|
Loading…
Reference in a new issue