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
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(() => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue