No Backbone in data layer; server/client interfaces are now similar

This commit is contained in:
Scott Nonnenberg 2021-12-10 14:51:54 -08:00 committed by GitHub
parent 064bbfe97a
commit 34fd945f83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 573 additions and 1021 deletions

View file

@ -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

View file

@ -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>;
};

View file

@ -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(() => {