Signal-Desktop/ts/signal.ts

494 lines
16 KiB
TypeScript

// Copyright 2018-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// The idea with this file is to make it webpackable for the style guide
import * as Crypto from './Crypto';
import * as Curve from './Curve';
import { start as conversationControllerStart } from './ConversationController';
import Data from './sql/Client';
import * as Groups from './groups';
import * as OS from './OS';
import * as RemoteConfig from './RemoteConfig';
import * as Util from './util';
// Components
import { AttachmentList } from './components/conversation/AttachmentList';
import { ChatColorPicker } from './components/ChatColorPicker';
import { ConfirmationDialog } from './components/ConfirmationDialog';
import { ContactModal } from './components/conversation/ContactModal';
import { Emojify } from './components/conversation/Emojify';
import { MessageDetail } from './components/conversation/MessageDetail';
import { Quote } from './components/conversation/Quote';
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
// State
import { createChatColorPicker } from './state/roots/createChatColorPicker';
import { createConversationDetails } from './state/roots/createConversationDetails';
import { createApp } from './state/roots/createApp';
import { createGroupLinkManagement } from './state/roots/createGroupLinkManagement';
import { createGroupV1MigrationModal } from './state/roots/createGroupV1MigrationModal';
import { createGroupV2JoinModal } from './state/roots/createGroupV2JoinModal';
import { createMessageDetail } from './state/roots/createMessageDetail';
import { createConversationNotificationsSettings } from './state/roots/createConversationNotificationsSettings';
import { createGroupV2Permissions } from './state/roots/createGroupV2Permissions';
import { createPendingInvites } from './state/roots/createPendingInvites';
import { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
import { createStickerManager } from './state/roots/createStickerManager';
import { createStickerPreviewModal } from './state/roots/createStickerPreviewModal';
import { createShortcutGuideModal } from './state/roots/createShortcutGuideModal';
import { createStore } from './state/createStore';
import * as appDuck from './state/ducks/app';
import * as callingDuck from './state/ducks/calling';
import * as conversationsDuck from './state/ducks/conversations';
import * as emojisDuck from './state/ducks/emojis';
import * as expirationDuck from './state/ducks/expiration';
import * as itemsDuck from './state/ducks/items';
import * as linkPreviewsDuck from './state/ducks/linkPreviews';
import * as networkDuck from './state/ducks/network';
import * as searchDuck from './state/ducks/search';
import * as stickersDuck from './state/ducks/stickers';
import * as updatesDuck from './state/ducks/updates';
import * as userDuck from './state/ducks/user';
import * as conversationsSelectors from './state/selectors/conversations';
import * as searchSelectors from './state/selectors/search';
// Types
import * as TypesAttachment from './types/Attachment';
import * as VisualAttachment from './types/VisualAttachment';
import * as MessageType from './types/Message2';
import { UUID } from './types/UUID';
import { Address } from './types/Address';
import { QualifiedAddress } from './types/QualifiedAddress';
// Processes / Services
import { initializeGroupCredentialFetcher } from './services/groupCredentialFetcher';
import { initializeNetworkObserver } from './services/networkObserver';
import { initializeUpdateListener } from './services/updateListener';
import { calling } from './services/calling';
import {
enableStorageService,
eraseAllStorageServiceState,
runStorageServiceSyncJob,
storageServiceUploadJob,
} from './services/storage';
import type { LoggerType } from './types/Logging';
import type {
AttachmentType,
AttachmentWithHydratedData,
} from './types/Attachment';
import type { MessageAttributesType, QuotedMessageType } from './model-types.d';
import type { SignalCoreType } from './window.d';
import type { EmbeddedContactType } from './types/EmbeddedContact';
import type { ContactWithHydratedAvatar } from './textsecure/SendMessage';
import type { LinkPreviewType } from './types/message/LinkPreviews';
import type { StickerType, StickerWithHydratedData } from './types/Stickers';
type MigrationsModuleType = {
attachmentsPath: string;
copyIntoAttachmentsDirectory: (
path: string
) => Promise<{ path: string; size: number }>;
copyIntoTempDirectory: (
path: string
) => Promise<{ path: string; size: number }>;
deleteAttachmentData: (path: string) => Promise<void>;
deleteAvatar: (path: string) => Promise<void>;
deleteDraftFile: (path: string) => Promise<void>;
deleteExternalMessageFiles: (
attributes: MessageAttributesType
) => Promise<void>;
deleteSticker: (path: string) => Promise<void>;
deleteTempFile: (path: string) => Promise<void>;
doesAttachmentExist: (path: string) => Promise<boolean>;
getAbsoluteAttachmentPath: (path: string) => string;
getAbsoluteAvatarPath: (src: string) => string;
getAbsoluteBadgeImageFilePath: (path: string) => string;
getAbsoluteDraftPath: (path: string) => string;
getAbsoluteStickerPath: (path: string) => string;
getAbsoluteTempPath: (path: string) => string;
loadAttachmentData: (
attachment: Pick<AttachmentType, 'data' | 'path'>
) => Promise<AttachmentWithHydratedData>;
loadContactData: (
contact: Array<EmbeddedContactType> | undefined
) => Promise<Array<ContactWithHydratedAvatar> | undefined>;
loadMessage: (
message: MessageAttributesType
) => Promise<MessageAttributesType>;
loadPreviewData: (
preview: Array<LinkPreviewType> | undefined
) => Promise<Array<LinkPreviewType>>;
loadQuoteData: (
quote: QuotedMessageType | null | undefined
) => Promise<QuotedMessageType | null>;
loadStickerData: (
sticker: StickerType | undefined
) => Promise<StickerWithHydratedData | undefined>;
openFileInFolder: (target: string) => Promise<void>;
readAttachmentData: (path: string) => Promise<Uint8Array>;
readDraftData: (path: string) => Promise<Uint8Array>;
readStickerData: (path: string) => Promise<Uint8Array>;
readTempData: (path: string) => Promise<Uint8Array>;
saveAttachmentToDisk: (options: {
data: Uint8Array;
name: string;
}) => Promise<null | { fullPath: string; name: string }>;
processNewAttachment: (attachment: AttachmentType) => Promise<AttachmentType>;
processNewSticker: (stickerData: Uint8Array) => Promise<{
path: string;
width: number;
height: number;
}>;
processNewEphemeralSticker: (stickerData: Uint8Array) => Promise<{
path: string;
width: number;
height: number;
}>;
upgradeMessageSchema: (
attributes: MessageAttributesType,
options?: { maxVersion?: number }
) => Promise<MessageAttributesType>;
writeMessageAttachments: (
message: MessageAttributesType
) => Promise<MessageAttributesType>;
writeNewAttachmentData: (data: Uint8Array) => Promise<string>;
writeNewDraftData: (data: Uint8Array) => Promise<string>;
writeNewAvatarData: (data: Uint8Array) => Promise<string>;
writeNewBadgeImageFileData: (data: Uint8Array) => Promise<string>;
};
export function initializeMigrations({
getRegionCode,
Attachments,
Type,
VisualType,
logger,
userDataPath,
}: {
getRegionCode: () => string | undefined;
Attachments: AttachmentsModuleType;
Type: typeof TypesAttachment;
VisualType: typeof VisualAttachment;
logger: LoggerType;
userDataPath: string;
}): MigrationsModuleType {
if (!Attachments) {
throw new Error('initializeMigrations: Missing provided attachments!');
}
const {
createAbsolutePathGetter,
createReader,
createWriterForExisting,
createWriterForNew,
createDoesExist,
getAvatarsPath,
getDraftPath,
getPath,
getStickersPath,
getBadgesPath,
getTempPath,
openFileInFolder,
saveAttachmentToDisk,
} = Attachments;
const {
getImageDimensions,
makeImageThumbnail,
makeObjectUrl,
makeVideoScreenshot,
revokeObjectUrl,
} = VisualType;
const attachmentsPath = getPath(userDataPath);
const readAttachmentData = createReader(attachmentsPath);
const loadAttachmentData = Type.loadData(readAttachmentData);
const loadContactData = MessageType.loadContactData(loadAttachmentData);
const loadPreviewData = MessageType.loadPreviewData(loadAttachmentData);
const loadQuoteData = MessageType.loadQuoteData(loadAttachmentData);
const loadStickerData = MessageType.loadStickerData(loadAttachmentData);
const getAbsoluteAttachmentPath = createAbsolutePathGetter(attachmentsPath);
const deleteOnDisk = Attachments.createDeleter(attachmentsPath);
const writeExistingAttachmentData = createWriterForExisting(attachmentsPath);
const writeNewAttachmentData = createWriterForNew(attachmentsPath);
const copyIntoAttachmentsDirectory =
Attachments.copyIntoAttachmentsDirectory(attachmentsPath);
const doesAttachmentExist = createDoesExist(attachmentsPath);
const stickersPath = getStickersPath(userDataPath);
const writeNewStickerData = createWriterForNew(stickersPath);
const getAbsoluteStickerPath = createAbsolutePathGetter(stickersPath);
const deleteSticker = Attachments.createDeleter(stickersPath);
const readStickerData = createReader(stickersPath);
const badgesPath = getBadgesPath(userDataPath);
const getAbsoluteBadgeImageFilePath = createAbsolutePathGetter(badgesPath);
const writeNewBadgeImageFileData = createWriterForNew(badgesPath, '.svg');
const tempPath = getTempPath(userDataPath);
const getAbsoluteTempPath = createAbsolutePathGetter(tempPath);
const writeNewTempData = createWriterForNew(tempPath);
const deleteTempFile = Attachments.createDeleter(tempPath);
const readTempData = createReader(tempPath);
const copyIntoTempDirectory =
Attachments.copyIntoAttachmentsDirectory(tempPath);
const draftPath = getDraftPath(userDataPath);
const getAbsoluteDraftPath = createAbsolutePathGetter(draftPath);
const writeNewDraftData = createWriterForNew(draftPath);
const deleteDraftFile = Attachments.createDeleter(draftPath);
const readDraftData = createReader(draftPath);
const avatarsPath = getAvatarsPath(userDataPath);
const getAbsoluteAvatarPath = createAbsolutePathGetter(avatarsPath);
const writeNewAvatarData = createWriterForNew(avatarsPath);
const deleteAvatar = Attachments.createDeleter(avatarsPath);
return {
attachmentsPath,
copyIntoAttachmentsDirectory,
copyIntoTempDirectory,
deleteAttachmentData: deleteOnDisk,
deleteAvatar,
deleteDraftFile,
deleteExternalMessageFiles: MessageType.deleteAllExternalFiles({
deleteAttachmentData: Type.deleteData(deleteOnDisk),
deleteOnDisk,
}),
deleteSticker,
deleteTempFile,
doesAttachmentExist,
getAbsoluteAttachmentPath,
getAbsoluteAvatarPath,
getAbsoluteBadgeImageFilePath,
getAbsoluteDraftPath,
getAbsoluteStickerPath,
getAbsoluteTempPath,
loadAttachmentData,
loadContactData,
loadMessage: MessageType.createAttachmentLoader(loadAttachmentData),
loadPreviewData,
loadQuoteData,
loadStickerData,
openFileInFolder,
readAttachmentData,
readDraftData,
readStickerData,
readTempData,
saveAttachmentToDisk,
processNewAttachment: (attachment: AttachmentType) =>
MessageType.processNewAttachment(attachment, {
writeNewAttachmentData,
getAbsoluteAttachmentPath,
makeObjectUrl,
revokeObjectUrl,
getImageDimensions,
makeImageThumbnail,
makeVideoScreenshot,
logger,
}),
processNewSticker: (stickerData: Uint8Array) =>
MessageType.processNewSticker(stickerData, {
writeNewStickerData,
getAbsoluteStickerPath,
getImageDimensions,
logger,
}),
processNewEphemeralSticker: (stickerData: Uint8Array) =>
MessageType.processNewSticker(stickerData, {
writeNewStickerData: writeNewTempData,
getAbsoluteStickerPath: getAbsoluteTempPath,
getImageDimensions,
logger,
}),
upgradeMessageSchema: (
message: MessageAttributesType,
options: { maxVersion?: number } = {}
) => {
const { maxVersion } = options;
return MessageType.upgradeSchema(message, {
writeNewAttachmentData,
getRegionCode,
getAbsoluteAttachmentPath,
makeObjectUrl,
revokeObjectUrl,
getImageDimensions,
makeImageThumbnail,
makeVideoScreenshot,
logger,
maxVersion,
getAbsoluteStickerPath,
writeNewStickerData,
});
},
writeMessageAttachments: MessageType.createAttachmentDataWriter({
writeExistingAttachmentData,
logger,
}),
writeNewAttachmentData: createWriterForNew(attachmentsPath),
writeNewAvatarData,
writeNewDraftData,
writeNewBadgeImageFileData,
};
}
type StringGetterType = (basePath: string) => string;
type AttachmentsModuleType = {
getAvatarsPath: StringGetterType;
getBadgesPath: StringGetterType;
getDraftPath: StringGetterType;
getPath: StringGetterType;
getStickersPath: StringGetterType;
getTempPath: StringGetterType;
getUpdateCachePath: StringGetterType;
createDeleter: (root: string) => (relativePath: string) => Promise<void>;
createReader: (root: string) => (relativePath: string) => Promise<Uint8Array>;
getRelativePath: (name: string) => string;
createName: (suffix?: string) => string;
copyIntoAttachmentsDirectory: (
root: string
) => (sourcePath: string) => Promise<{ path: string; size: number }>;
createWriterForNew: (
root: string,
suffix?: string
) => (bytes: Uint8Array) => Promise<string>;
createWriterForExisting: (
root: string
) => (options: { data?: Uint8Array; path?: string }) => Promise<string>;
createAbsolutePathGetter: (
rootPath: string
) => (relativePath: string) => string;
createDoesExist: (root: string) => (relativePath: string) => Promise<boolean>;
openFileInFolder: (target: string) => Promise<void>;
saveAttachmentToDisk: ({
data,
name,
}: {
data: Uint8Array;
name: string;
}) => Promise<null | { fullPath: string; name: string }>;
};
export const setup = (options: {
Attachments: AttachmentsModuleType;
getRegionCode: () => string | undefined;
logger: LoggerType;
userDataPath: string;
}): SignalCoreType => {
const { Attachments, getRegionCode, logger, userDataPath } = options;
const Migrations = initializeMigrations({
getRegionCode,
Attachments,
Type: TypesAttachment,
VisualType: VisualAttachment,
logger,
userDataPath,
});
const Components = {
AttachmentList,
ChatColorPicker,
ConfirmationDialog,
ContactModal,
Emojify,
MessageDetail,
Quote,
StagedLinkPreview,
DisappearingTimeDialog,
};
const Roots = {
createApp,
createChatColorPicker,
createConversationDetails,
createGroupLinkManagement,
createGroupV1MigrationModal,
createGroupV2JoinModal,
createGroupV2Permissions,
createMessageDetail,
createConversationNotificationsSettings,
createPendingInvites,
createSafetyNumberViewer,
createShortcutGuideModal,
createStickerManager,
createStickerPreviewModal,
};
const Ducks = {
app: appDuck,
calling: callingDuck,
conversations: conversationsDuck,
emojis: emojisDuck,
expiration: expirationDuck,
items: itemsDuck,
linkPreviews: linkPreviewsDuck,
network: networkDuck,
updates: updatesDuck,
user: userDuck,
search: searchDuck,
stickers: stickersDuck,
};
const Selectors = {
conversations: conversationsSelectors,
search: searchSelectors,
};
const Services = {
calling,
enableStorageService,
eraseAllStorageServiceState,
initializeGroupCredentialFetcher,
initializeNetworkObserver,
initializeUpdateListener,
runStorageServiceSyncJob,
storageServiceUploadJob,
};
const State = {
createStore,
Roots,
Ducks,
Selectors,
};
const Types = {
Message: MessageType,
// Mostly for debugging
UUID,
Address,
QualifiedAddress,
};
return {
Components,
Crypto,
Curve,
// Note: used in test/index.html, and not type-checked!
conversationControllerStart,
Data,
Groups,
Migrations,
OS,
RemoteConfig,
Services,
State,
Types,
Util,
};
};