// Copyright 2020-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ConversationAttributesType, MessageAttributesType, SenderKeyInfoType, } from '../model-types.d'; import type { StoredJob } from '../jobs/types'; import type { ReactionType } from '../types/Reactions'; import type { ConversationColorType, CustomColorType } from '../types/Colors'; import type { StorageAccessType } from '../types/Storage.d'; import type { AttachmentType } from '../types/Attachment'; import type { BodyRangesType, BytesToStrings } from '../types/Util'; import type { QualifiedAddressStringType } from '../types/QualifiedAddress'; import type { UUIDStringType } from '../types/UUID'; import type { BadgeType } from '../badges/types'; import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration'; import type { LoggerType } from '../types/Logging'; import type { ReadStatus } from '../messages/MessageReadStatus'; export type AttachmentDownloadJobTypeType = | 'long-message' | 'attachment' | 'preview' | 'contact' | 'quote' | 'sticker'; export type AttachmentDownloadJobType = { attachment: AttachmentType; attempts: number; id: string; index: number; messageId: string; pending: number; timestamp: number; type: AttachmentDownloadJobTypeType; }; export type MessageMetricsType = { id: string; received_at: number; sent_at: number; }; export type ConversationMetricsType = { oldest?: MessageMetricsType; newest?: MessageMetricsType; oldestUnseen?: MessageMetricsType; totalUnseen: number; }; export type ConversationType = ConversationAttributesType; export type EmojiType = { shortName: string; lastUsage: number; }; export type IdentityKeyType = { firstUse: boolean; id: UUIDStringType | `conversation:${string}`; nonblockingApproval: boolean; publicKey: Uint8Array; timestamp: number; verified: number; }; export type StoredIdentityKeyType = { firstUse: boolean; id: UUIDStringType | `conversation:${string}`; nonblockingApproval: boolean; publicKey: string; timestamp: number; verified: number; }; export type IdentityKeyIdType = IdentityKeyType['id']; export type ItemKeyType = keyof StorageAccessType; export type AllItemsType = Partial; export type StoredAllItemsType = Partial>; export type ItemType = { id: K; value: StorageAccessType[K]; }; export type StoredItemType = { id: K; value: BytesToStrings; }; export type MessageType = MessageAttributesType; export type MessageTypeUnhydrated = { json: string; }; export type PreKeyType = { id: `${UUIDStringType}:${number}`; keyId: number; ourUuid: UUIDStringType; privateKey: Uint8Array; publicKey: Uint8Array; }; export type StoredPreKeyType = { id: `${UUIDStringType}:${number}`; keyId: number; ourUuid: UUIDStringType; privateKey: string; publicKey: string; }; export type PreKeyIdType = PreKeyType['id']; export type ServerSearchResultMessageType = { json: string; snippet: string; }; export type ClientSearchResultMessageType = MessageType & { json: string; bodyRanges: BodyRangesType; snippet: string; }; export type SentProtoType = { contentHint: number; proto: Uint8Array; timestamp: number; urgent: boolean; hasPniSignatureMessage: boolean; }; export type SentProtoWithMessageIdsType = SentProtoType & { messageIds: Array; }; export type SentRecipientsType = Record>; export type SentMessagesType = Array; // These two are for test only export type SentRecipientsDBType = { payloadId: number; recipientUuid: string; deviceId: number; }; export type SentMessageDBType = { payloadId: number; messageId: string; }; export type SenderKeyType = { // Primary key id: `${QualifiedAddressStringType}--${string}`; // These two are combined into one string to give us the final id senderId: string; distributionId: string; // Raw data to serialize/deserialize into signal-client SenderKeyRecord data: Uint8Array; lastUpdatedDate: number; }; export type SenderKeyIdType = SenderKeyType['id']; export type SessionType = { id: QualifiedAddressStringType; ourUuid: UUIDStringType; uuid: UUIDStringType; conversationId: string; deviceId: number; record: string; version?: number; }; export type SessionIdType = SessionType['id']; export type SignedPreKeyType = { confirmed: boolean; created_at: number; ourUuid: UUIDStringType; id: `${UUIDStringType}:${number}`; keyId: number; privateKey: Uint8Array; publicKey: Uint8Array; }; export type StoredSignedPreKeyType = { confirmed: boolean; created_at: number; ourUuid: UUIDStringType; id: `${UUIDStringType}:${number}`; keyId: number; privateKey: string; publicKey: string; }; export type SignedPreKeyIdType = SignedPreKeyType['id']; export type StickerType = Readonly<{ id: number; packId: string; emoji?: string; isCoverOnly: boolean; lastUsed?: number; path: string; width: number; height: number; }>; export const StickerPackStatuses = [ 'known', 'ephemeral', 'downloaded', 'installed', 'pending', 'error', ] as const; export type StickerPackStatusType = typeof StickerPackStatuses[number]; export type StorageServiceFieldsType = Readonly<{ storageID?: string; storageVersion?: number; storageUnknownFields?: Uint8Array | null; storageNeedsSync: boolean; }>; export type InstalledStickerPackType = Readonly<{ id: string; key: string; uninstalledAt?: undefined; position?: number | null; }> & StorageServiceFieldsType; export type UninstalledStickerPackType = Readonly<{ id: string; key?: undefined; uninstalledAt: number; position?: undefined; }> & StorageServiceFieldsType; export type StickerPackInfoType = | InstalledStickerPackType | UninstalledStickerPackType; export type StickerPackType = InstalledStickerPackType & Readonly<{ attemptedStatus?: 'downloaded' | 'installed' | 'ephemeral'; author: string; coverStickerId: number; createdAt: number; downloadAttempts: number; installedAt?: number; lastUsed?: number; status: StickerPackStatusType; stickerCount: number; stickers: Record; title: string; }>; export type UnprocessedType = { id: string; timestamp: number; receivedAtCounter: number | null; version: number; attempts: number; envelope?: string; messageAgeSec?: number; source?: string; sourceUuid?: UUIDStringType; sourceDevice?: number; destinationUuid?: string; updatedPni?: string; serverGuid?: string; serverTimestamp?: number; decrypted?: string; urgent?: boolean; story?: boolean; }; export type UnprocessedUpdateType = { source?: string; sourceUuid?: UUIDStringType; sourceDevice?: number; serverGuid?: string; serverTimestamp?: number; decrypted?: string; }; export type ConversationMessageStatsType = { activity?: MessageType; preview?: MessageType; hasUserInitiatedMessages: boolean; }; export type DeleteSentProtoRecipientOptionsType = Readonly<{ timestamp: number; recipientUuid: string; deviceId: number; }>; export type DeleteSentProtoRecipientResultType = Readonly<{ successfulPhoneNumberShares: ReadonlyArray; }>; export type StoryDistributionType = Readonly<{ id: UUIDStringType; name: string; deletedAtTimestamp?: number; allowsReplies: boolean; isBlockList: boolean; senderKeyInfo: SenderKeyInfoType | undefined; }> & StorageServiceFieldsType; export type StoryDistributionMemberType = Readonly<{ listId: UUIDStringType; uuid: UUIDStringType; }>; export type StoryDistributionWithMembersType = Readonly< { members: Array; } & StoryDistributionType >; export type StoryReadType = Readonly<{ authorId: UUIDStringType; conversationId: string; storyId: string; storyReadDate: number; }>; export type ReactionResultType = Pick< ReactionType, 'targetAuthorUuid' | 'targetTimestamp' | 'messageId' > & { rowid: number }; export type GetUnreadByConversationAndMarkReadResultType = Array< { originalReadStatus: ReadStatus | undefined } & Pick< MessageType, | 'id' | 'source' | 'sourceUuid' | 'sent_at' | 'type' | 'readStatus' | 'seenStatus' > >; export type GetConversationRangeCenteredOnMessageResultType = Readonly<{ older: Array; newer: Array; metrics: ConversationMetricsType; }>; export type MessageAttachmentsCursorType = Readonly<{ done: boolean; runId: string; count: number; }>; export type GetKnownMessageAttachmentsResultType = Readonly<{ cursor: MessageAttachmentsCursorType; attachments: ReadonlyArray; }>; export type GetAllStoriesResultType = ReadonlyArray< MessageType & { hasReplies: boolean; hasRepliesFromSelf: boolean; } >; export type DataInterface = { close: () => Promise; removeDB: () => Promise; removeIndexedDBFiles: () => Promise; removeIdentityKeyById: (id: IdentityKeyIdType) => Promise; removeAllIdentityKeys: () => Promise; removePreKeyById: (id: PreKeyIdType) => Promise; removePreKeysByUuid: (uuid: UUIDStringType) => Promise; removeAllPreKeys: () => Promise; removeSignedPreKeyById: (id: SignedPreKeyIdType) => Promise; removeSignedPreKeysByUuid: (uuid: UUIDStringType) => Promise; removeAllSignedPreKeys: () => Promise; removeAllItems: () => Promise; removeItemById: (id: ItemKeyType) => Promise; createOrUpdateSenderKey: (key: SenderKeyType) => Promise; getSenderKeyById: (id: SenderKeyIdType) => Promise; removeAllSenderKeys: () => Promise; getAllSenderKeys: () => Promise>; removeSenderKeyById: (id: SenderKeyIdType) => Promise; insertSentProto: ( proto: SentProtoType, options: { recipients: SentRecipientsType; messageIds: SentMessagesType; } ) => Promise; deleteSentProtosOlderThan: (timestamp: number) => Promise; deleteSentProtoByMessageId: (messageId: string) => Promise; insertProtoRecipients: (options: { id: number; recipientUuid: string; deviceIds: Array; }) => Promise; deleteSentProtoRecipient: ( options: | DeleteSentProtoRecipientOptionsType | ReadonlyArray ) => Promise; getSentProtoByRecipient: (options: { now: number; recipientUuid: string; timestamp: number; }) => Promise; removeAllSentProtos: () => Promise; getAllSentProtos: () => Promise>; // Test-only _getAllSentProtoRecipients: () => Promise>; _getAllSentProtoMessageIds: () => Promise>; createOrUpdateSession: (data: SessionType) => Promise; createOrUpdateSessions: (array: Array) => Promise; commitDecryptResult(options: { senderKeys: Array; sessions: Array; unprocessed: Array; }): Promise; bulkAddSessions: (array: Array) => Promise; removeSessionById: (id: SessionIdType) => Promise; removeSessionsByConversation: (conversationId: string) => Promise; removeSessionsByUUID: (uuid: UUIDStringType) => Promise; removeAllSessions: () => Promise; getAllSessions: () => Promise>; eraseStorageServiceStateFromConversations: () => Promise; getConversationCount: () => Promise; saveConversation: (data: ConversationType) => Promise; saveConversations: (array: Array) => Promise; getConversationById: (id: string) => Promise; // updateConversation is a normal data method on Server, a sync batch-add on Client updateConversations: (array: Array) => Promise; // removeConversation handles either one id or an array on Server, and one id on Client _removeAllConversations: () => Promise; updateAllConversationColors: ( conversationColor?: ConversationColorType, customColorData?: { id: string; value: CustomColorType; } ) => Promise; removeAllProfileKeyCredentials: () => Promise; getAllConversations: () => Promise>; getAllConversationIds: () => Promise>; getAllGroupsInvolvingUuid: ( id: UUIDStringType ) => Promise>; // searchMessages is JSON on server, full message on Client // searchMessagesInConversation is JSON on server, full message on Client getMessageCount: (conversationId?: string) => Promise; getStoryCount: (conversationId: string) => Promise; saveMessage: ( data: MessageType, options: { jobToInsert?: StoredJob; forceSave?: boolean; ourUuid: UUIDStringType; } ) => Promise; saveMessages: ( arrayOfMessages: ReadonlyArray, options: { forceSave?: boolean; ourUuid: UUIDStringType } ) => Promise; removeMessage: (id: string) => Promise; removeMessages: (ids: Array) => Promise; getTotalUnreadForConversation: ( conversationId: string, options: { storyId: UUIDStringType | undefined; includeStoryReplies: boolean; } ) => Promise; getUnreadByConversationAndMarkRead: (options: { conversationId: string; includeStoryReplies: boolean; newestUnreadAt: number; now?: number; readAt?: number; storyId?: UUIDStringType; }) => Promise; getUnreadReactionsAndMarkRead: (options: { conversationId: string; newestUnreadAt: number; storyId?: UUIDStringType; }) => Promise>; markReactionAsRead: ( targetAuthorUuid: string, targetTimestamp: number ) => Promise; removeReactionFromConversation: (reaction: { emoji: string; fromId: string; targetAuthorUuid: string; targetTimestamp: number; }) => Promise; addReaction: (reactionObj: ReactionType) => Promise; _getAllReactions: () => Promise>; _removeAllReactions: () => Promise; getMessageBySender: (options: { source: string; sourceUuid: UUIDStringType; sourceDevice: number; sent_at: number; }) => Promise; getMessageById: (id: string) => Promise; getMessagesById: (messageIds: Array) => Promise>; _getAllMessages: () => Promise>; _removeAllMessages: () => Promise; getAllMessageIds: () => Promise>; getMessagesBySentAt: (sentAt: number) => Promise>; getExpiredMessages: () => Promise>; getMessagesUnexpectedlyMissingExpirationStartTimestamp: () => Promise< Array >; getSoonestMessageExpiry: () => Promise; getNextTapToViewMessageTimestampToAgeOut: () => Promise; getTapToViewMessagesNeedingErase: () => Promise>; // getOlderMessagesByConversation is JSON on server, full message on Client getAllStories: (options: { conversationId?: string; sourceUuid?: UUIDStringType; }) => Promise; // getNewerMessagesByConversation is JSON on server, full message on Client getMessageMetricsForConversation: ( conversationId: string, options: { storyId?: UUIDStringType; includeStoryReplies: boolean; } ) => Promise; // getConversationRangeCenteredOnMessage is JSON on server, full message on client getConversationMessageStats: (options: { conversationId: string; includeStoryReplies: boolean; ourUuid: UUIDStringType; }) => Promise; getLastConversationMessage(options: { conversationId: string; }): Promise; hasGroupCallHistoryMessage: ( conversationId: string, eraId: string ) => Promise; migrateConversationMessages: ( obsoleteId: string, currentId: string ) => Promise; getUnprocessedCount: () => Promise; getAllUnprocessedAndIncrementAttempts: () => Promise>; updateUnprocessedWithData: ( id: string, data: UnprocessedUpdateType ) => Promise; updateUnprocessedsWithData: ( array: Array<{ id: string; data: UnprocessedUpdateType }> ) => Promise; getUnprocessedById: (id: string) => Promise; removeUnprocessed: (id: string | Array) => Promise; removeAllUnprocessed: () => Promise; getAttachmentDownloadJobById: ( id: string ) => Promise; getNextAttachmentDownloadJobs: ( limit?: number, options?: { timestamp?: number } ) => Promise>; saveAttachmentDownloadJob: (job: AttachmentDownloadJobType) => Promise; resetAttachmentDownloadPending: () => Promise; setAttachmentDownloadJobPending: ( id: string, pending: boolean ) => Promise; removeAttachmentDownloadJob: (id: string) => Promise; removeAllAttachmentDownloadJobs: () => Promise; createOrUpdateStickerPack: (pack: StickerPackType) => Promise; updateStickerPackStatus: ( id: string, status: StickerPackStatusType, options?: { timestamp: number } ) => Promise; updateStickerPackInfo: (info: StickerPackInfoType) => Promise; createOrUpdateSticker: (sticker: StickerType) => Promise; updateStickerLastUsed: ( packId: string, stickerId: number, lastUsed: number ) => Promise; addStickerPackReference: (messageId: string, packId: string) => Promise; deleteStickerPackReference: ( messageId: string, packId: string ) => Promise | undefined>; getStickerCount: () => Promise; deleteStickerPack: (packId: string) => Promise>; getAllStickerPacks: () => Promise>; addUninstalledStickerPack: ( pack: UninstalledStickerPackType ) => Promise; removeUninstalledStickerPack: (packId: string) => Promise; getInstalledStickerPacks: () => Promise>; getUninstalledStickerPacks: () => Promise>; installStickerPack: (packId: string, timestamp: number) => Promise; uninstallStickerPack: (packId: string, timestamp: number) => Promise; getStickerPackInfo: ( packId: string ) => Promise; getAllStickers: () => Promise>; getRecentStickers: (options?: { limit?: number; }) => Promise>; clearAllErrorStickerPackAttempts: () => Promise; updateEmojiUsage: (shortName: string, timeUsed?: number) => Promise; getRecentEmojis: (limit?: number) => Promise>; getAllBadges(): Promise>; updateOrCreateBadges(badges: ReadonlyArray): Promise; badgeImageFileDownloaded(url: string, localPath: string): Promise; _getAllStoryDistributions(): Promise>; _getAllStoryDistributionMembers(): Promise< Array >; _deleteAllStoryDistributions(): Promise; createNewStoryDistribution( distribution: StoryDistributionWithMembersType ): Promise; getAllStoryDistributionsWithMembers(): Promise< Array >; getStoryDistributionWithMembers( id: string ): Promise; modifyStoryDistribution(distribution: StoryDistributionType): Promise; modifyStoryDistributionMembers( listId: string, options: { toAdd: Array; toRemove: Array; } ): Promise; modifyStoryDistributionWithMembers( distribution: StoryDistributionType, options: { toAdd: Array; toRemove: Array; } ): Promise; deleteStoryDistribution(id: UUIDStringType): Promise; _getAllStoryReads(): Promise>; _deleteAllStoryReads(): Promise; addNewStoryRead(read: StoryReadType): Promise; getLastStoryReadsForAuthor(options: { authorId: UUIDStringType; conversationId?: UUIDStringType; limit?: number; }): Promise>; countStoryReadsByConversation(conversationId: string): Promise; removeAll: () => Promise; removeAllConfiguration: (type?: RemoveAllConfiguration) => Promise; getMessagesNeedingUpgrade: ( limit: number, options: { maxVersion: number } ) => Promise>; getMessagesWithVisualMediaAttachments: ( conversationId: string, options: { limit: number } ) => Promise>; getMessagesWithFileAttachments: ( conversationId: string, options: { limit: number } ) => Promise>; getMessageServerGuidsForSpam: ( conversationId: string ) => Promise>; getJobsInQueue(queueType: string): Promise>; insertJob(job: Readonly): Promise; deleteJob(id: string): Promise; wasGroupCallRingPreviouslyCanceled(ringId: bigint): Promise; processGroupCallRingCancellation(ringId: bigint): Promise; cleanExpiredGroupCallRingCancellations(): Promise; getMaxMessageCounter(): Promise; getStatisticsForLogging(): Promise>; }; export type ServerInterface = DataInterface & { // Differing signature on client/server updateConversation: (data: ConversationType) => Promise; removeConversation: (id: Array | string) => Promise; searchMessages: ( query: string, options?: { limit?: number } ) => Promise>; searchMessagesInConversation: ( query: string, conversationId: string, options?: { limit?: number } ) => Promise>; getOlderMessagesByConversation: ( conversationId: string, options: { includeStoryReplies: boolean; limit?: number; messageId?: string; receivedAt?: number; sentAt?: number; storyId: string | undefined; } ) => Promise>; getNewerMessagesByConversation: ( conversationId: string, options: { includeStoryReplies: boolean; limit?: number; receivedAt?: number; sentAt?: number; storyId: UUIDStringType | undefined; } ) => Promise>; getConversationRangeCenteredOnMessage: (options: { conversationId: string; includeStoryReplies: boolean; limit?: number; messageId: string; receivedAt: number; sentAt?: number; storyId: UUIDStringType | undefined; }) => Promise< GetConversationRangeCenteredOnMessageResultType >; createOrUpdateIdentityKey: (data: StoredIdentityKeyType) => Promise; getIdentityKeyById: ( id: IdentityKeyIdType ) => Promise; bulkAddIdentityKeys: (array: Array) => Promise; getAllIdentityKeys: () => Promise>; createOrUpdatePreKey: (data: StoredPreKeyType) => Promise; getPreKeyById: (id: PreKeyIdType) => Promise; bulkAddPreKeys: (array: Array) => Promise; getAllPreKeys: () => Promise>; createOrUpdateSignedPreKey: (data: StoredSignedPreKeyType) => Promise; getSignedPreKeyById: ( id: SignedPreKeyIdType ) => Promise; bulkAddSignedPreKeys: (array: Array) => Promise; getAllSignedPreKeys: () => Promise>; createOrUpdateItem( data: StoredItemType ): Promise; getItemById( id: K ): Promise | undefined>; getAllItems: () => Promise; // Server-only initialize: (options: { configDir: string; key: string; logger: LoggerType; }) => Promise; initializeRenderer: (options: { configDir: string; key: string; }) => Promise; getKnownMessageAttachments: ( cursor?: MessageAttachmentsCursorType ) => Promise; finishGetKnownMessageAttachments: ( cursor: MessageAttachmentsCursorType ) => Promise; getKnownConversationAttachments: () => Promise>; removeKnownStickers: ( allStickers: ReadonlyArray ) => Promise>; removeKnownDraftAttachments: ( allStickers: ReadonlyArray ) => Promise>; getAllBadgeImageFileLocalPaths: () => Promise>; }; // Differing signature on client/server export type ClientExclusiveInterface = { // Differing signature on client/server updateConversation: (data: ConversationType) => void; removeConversation: (id: string) => Promise; searchMessages: ( query: string, options?: { limit?: number } ) => Promise>; searchMessagesInConversation: ( query: string, conversationId: string, options?: { limit?: number } ) => Promise>; getOlderMessagesByConversation: ( conversationId: string, options: { includeStoryReplies: boolean; limit?: number; messageId?: string; receivedAt?: number; sentAt?: number; storyId: string | undefined; } ) => Promise>; getNewerMessagesByConversation: ( conversationId: string, options: { includeStoryReplies: boolean; limit?: number; receivedAt?: number; sentAt?: number; storyId: UUIDStringType | undefined; } ) => Promise>; getConversationRangeCenteredOnMessage: (options: { conversationId: string; includeStoryReplies: boolean; limit?: number; messageId: string; receivedAt: number; sentAt?: number; storyId: UUIDStringType | undefined; }) => Promise>; createOrUpdateIdentityKey: (data: IdentityKeyType) => Promise; getIdentityKeyById: ( id: IdentityKeyIdType ) => Promise; bulkAddIdentityKeys: (array: Array) => Promise; getAllIdentityKeys: () => Promise>; createOrUpdatePreKey: (data: PreKeyType) => Promise; getPreKeyById: (id: PreKeyIdType) => Promise; bulkAddPreKeys: (array: Array) => Promise; getAllPreKeys: () => Promise>; createOrUpdateSignedPreKey: (data: SignedPreKeyType) => Promise; getSignedPreKeyById: ( id: SignedPreKeyIdType ) => Promise; bulkAddSignedPreKeys: (array: Array) => Promise; getAllSignedPreKeys: () => Promise>; createOrUpdateItem(data: ItemType): Promise; getItemById(id: K): Promise | undefined>; getAllItems: () => Promise; // Client-side only shutdown: () => Promise; removeAllMessagesInConversation: ( conversationId: string, options: { logId: string; } ) => Promise; removeOtherData: () => Promise; cleanupOrphanedAttachments: () => Promise; ensureFilePermissions: () => Promise; // 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; startInRendererProcess: (isTesting?: boolean) => Promise; }; export type ClientInterface = DataInterface & ClientExclusiveInterface;