Adjust some types
This commit is contained in:
parent
9ce4b8977d
commit
d7307934bc
15 changed files with 223 additions and 175 deletions
|
@ -21,11 +21,11 @@ import type {
|
||||||
MessageAttributesType,
|
MessageAttributesType,
|
||||||
ConversationAttributesType,
|
ConversationAttributesType,
|
||||||
ReactionAttributesType,
|
ReactionAttributesType,
|
||||||
|
ValidateConversationType,
|
||||||
} from './model-types.d';
|
} from './model-types.d';
|
||||||
import * as Bytes from './Bytes';
|
import * as Bytes from './Bytes';
|
||||||
import * as Timers from './Timers';
|
import * as Timers from './Timers';
|
||||||
import * as indexedDb from './indexeddb';
|
import * as indexedDb from './indexeddb';
|
||||||
import type { WhatIsThis } from './window.d';
|
|
||||||
import type { MenuOptionsType } from './types/menu';
|
import type { MenuOptionsType } from './types/menu';
|
||||||
import type { Receipt } from './types/Receipt';
|
import type { Receipt } from './types/Receipt';
|
||||||
import { SocketStatus } from './types/SocketStatus';
|
import { SocketStatus } from './types/SocketStatus';
|
||||||
|
@ -133,8 +133,7 @@ import { onRetryRequest, onDecryptionError } from './util/handleRetry';
|
||||||
import { themeChanged } from './shims/themeChanged';
|
import { themeChanged } from './shims/themeChanged';
|
||||||
import { createIPCEvents } from './util/createIPCEvents';
|
import { createIPCEvents } from './util/createIPCEvents';
|
||||||
import { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
|
import { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
|
||||||
import { isValidUuid, UUIDKind } from './types/UUID';
|
import { isValidUuid, UUIDKind, UUID } from './types/UUID';
|
||||||
import type { UUID } from './types/UUID';
|
|
||||||
import * as log from './logging/log';
|
import * as log from './logging/log';
|
||||||
import { loadRecentEmojis } from './util/loadRecentEmojis';
|
import { loadRecentEmojis } from './util/loadRecentEmojis';
|
||||||
import { deleteAllLogs } from './util/deleteAllLogs';
|
import { deleteAllLogs } from './util/deleteAllLogs';
|
||||||
|
@ -155,6 +154,7 @@ import { conversationJobQueue } from './jobs/conversationJobQueue';
|
||||||
import { SeenStatus } from './MessageSeenStatus';
|
import { SeenStatus } from './MessageSeenStatus';
|
||||||
import MessageSender from './textsecure/SendMessage';
|
import MessageSender from './textsecure/SendMessage';
|
||||||
import type AccountManager from './textsecure/AccountManager';
|
import type AccountManager from './textsecure/AccountManager';
|
||||||
|
import { validateConversation } from './util/validateConversation';
|
||||||
|
|
||||||
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
||||||
|
|
||||||
|
@ -1227,7 +1227,7 @@ export async function startApp(): Promise<void> {
|
||||||
window.reduxActions.user.userChanged({ menuOptions: options });
|
window.reduxActions.user.userChanged({ menuOptions: options });
|
||||||
});
|
});
|
||||||
|
|
||||||
let shortcutGuideView: WhatIsThis | null = null;
|
let shortcutGuideView: ReactWrapperView | null = null;
|
||||||
|
|
||||||
window.showKeyboardShortcuts = () => {
|
window.showKeyboardShortcuts = () => {
|
||||||
if (!shortcutGuideView) {
|
if (!shortcutGuideView) {
|
||||||
|
@ -2724,12 +2724,13 @@ export async function startApp(): Promise<void> {
|
||||||
function onContactReceived(ev: ContactEvent) {
|
function onContactReceived(ev: ContactEvent) {
|
||||||
const details = ev.contactDetails;
|
const details = ev.contactDetails;
|
||||||
|
|
||||||
const c = new window.Whisper.Conversation({
|
const partialConversation: ValidateConversationType = {
|
||||||
e164: details.number,
|
e164: details.number,
|
||||||
uuid: details.uuid,
|
uuid: UUID.fromString(details.uuid),
|
||||||
type: 'private',
|
type: 'private',
|
||||||
} as Partial<ConversationAttributesType> as WhatIsThis);
|
};
|
||||||
const validationError = c.validate();
|
|
||||||
|
const validationError = validateConversation(partialConversation);
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
log.error(
|
log.error(
|
||||||
'Invalid contact received:',
|
'Invalid contact received:',
|
||||||
|
@ -3188,7 +3189,8 @@ export async function startApp(): Promise<void> {
|
||||||
.filter(isNotNil);
|
.filter(isNotNil);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new window.Whisper.Message({
|
const partialMessage: MessageAttributesType = {
|
||||||
|
id: UUID.generate().toString(),
|
||||||
canReplyToStory: data.message.isStory
|
canReplyToStory: data.message.isStory
|
||||||
? data.message.canReplyToStory
|
? data.message.canReplyToStory
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -3211,7 +3213,9 @@ export async function startApp(): Promise<void> {
|
||||||
type: data.message.isStory ? 'story' : 'outgoing',
|
type: data.message.isStory ? 'story' : 'outgoing',
|
||||||
storyDistributionListId: data.storyDistributionListId,
|
storyDistributionListId: data.storyDistributionListId,
|
||||||
unidentifiedDeliveries,
|
unidentifiedDeliveries,
|
||||||
} as Partial<MessageAttributesType> as WhatIsThis);
|
};
|
||||||
|
|
||||||
|
return new window.Whisper.Message(partialMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Works with 'sent' and 'message' data sent from MessageReceiver, with a little massage
|
// Works with 'sent' and 'message' data sent from MessageReceiver, with a little massage
|
||||||
|
@ -3446,7 +3450,8 @@ export async function startApp(): Promise<void> {
|
||||||
Boolean(data.receivedAtCounter),
|
Boolean(data.receivedAtCounter),
|
||||||
`Did not receive receivedAtCounter for message: ${data.timestamp}`
|
`Did not receive receivedAtCounter for message: ${data.timestamp}`
|
||||||
);
|
);
|
||||||
return new window.Whisper.Message({
|
const partialMessage: MessageAttributesType = {
|
||||||
|
id: UUID.generate().toString(),
|
||||||
canReplyToStory: data.message.isStory
|
canReplyToStory: data.message.isStory
|
||||||
? data.message.canReplyToStory
|
? data.message.canReplyToStory
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -3460,11 +3465,14 @@ export async function startApp(): Promise<void> {
|
||||||
serverTimestamp: data.serverTimestamp,
|
serverTimestamp: data.serverTimestamp,
|
||||||
source: data.source,
|
source: data.source,
|
||||||
sourceDevice: data.sourceDevice,
|
sourceDevice: data.sourceDevice,
|
||||||
sourceUuid: data.sourceUuid,
|
sourceUuid: data.sourceUuid
|
||||||
|
? UUID.fromString(data.sourceUuid)
|
||||||
|
: undefined,
|
||||||
timestamp: data.timestamp,
|
timestamp: data.timestamp,
|
||||||
type: data.message.isStory ? 'story' : 'incoming',
|
type: data.message.isStory ? 'story' : 'incoming',
|
||||||
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
|
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
|
||||||
} as Partial<MessageAttributesType> as WhatIsThis);
|
};
|
||||||
|
return new window.Whisper.Message(partialMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns `false` if this message isn't a group call message.
|
// Returns `false` if this message isn't a group call message.
|
||||||
|
|
|
@ -20,8 +20,9 @@ import type {
|
||||||
} from '../../textsecure/SendMessage';
|
} from '../../textsecure/SendMessage';
|
||||||
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
||||||
import type { BodyRangesType, StoryContextType } from '../../types/Util';
|
import type { BodyRangesType, StoryContextType } from '../../types/Util';
|
||||||
import type { WhatIsThis } from '../../window.d';
|
|
||||||
import type { LoggerType } from '../../types/Logging';
|
import type { LoggerType } from '../../types/Logging';
|
||||||
|
import type { StickerWithHydratedData } from '../../types/Stickers';
|
||||||
|
import type { QuotedMessageType } from '../../model-types.d';
|
||||||
import type {
|
import type {
|
||||||
ConversationQueueJobBundle,
|
ConversationQueueJobBundle,
|
||||||
NormalMessageSendJobData,
|
NormalMessageSendJobData,
|
||||||
|
@ -31,6 +32,7 @@ import { handleMultipleSendErrors } from './handleMultipleSendErrors';
|
||||||
import { ourProfileKeyService } from '../../services/ourProfileKey';
|
import { ourProfileKeyService } from '../../services/ourProfileKey';
|
||||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||||
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
||||||
|
import { sendToGroup } from '../../util/sendToGroup';
|
||||||
|
|
||||||
export async function sendNormalMessage(
|
export async function sendNormalMessage(
|
||||||
conversation: ConversationModel,
|
conversation: ConversationModel,
|
||||||
|
@ -207,7 +209,7 @@ export async function sendNormalMessage(
|
||||||
innerPromise = conversation.queueJob(
|
innerPromise = conversation.queueJob(
|
||||||
'conversationQueue/sendNormalMessage',
|
'conversationQueue/sendNormalMessage',
|
||||||
abortSignal =>
|
abortSignal =>
|
||||||
window.Signal.Util.sendToGroup({
|
sendToGroup({
|
||||||
abortSignal,
|
abortSignal,
|
||||||
contentHint: ContentHint.RESENDABLE,
|
contentHint: ContentHint.RESENDABLE,
|
||||||
groupSendOptions: {
|
groupSendOptions: {
|
||||||
|
@ -428,8 +430,8 @@ async function getMessageSendData({
|
||||||
mentions: undefined | BodyRangesType;
|
mentions: undefined | BodyRangesType;
|
||||||
messageTimestamp: number;
|
messageTimestamp: number;
|
||||||
preview: Array<LinkPreviewType>;
|
preview: Array<LinkPreviewType>;
|
||||||
quote: WhatIsThis;
|
quote: QuotedMessageType | null;
|
||||||
sticker: WhatIsThis;
|
sticker: StickerWithHydratedData | undefined;
|
||||||
storyContext?: StoryContextType;
|
storyContext?: StoryContextType;
|
||||||
}> {
|
}> {
|
||||||
const {
|
const {
|
||||||
|
|
18
ts/model-types.d.ts
vendored
18
ts/model-types.d.ts
vendored
|
@ -17,7 +17,11 @@ import { ReadStatus } from './messages/MessageReadStatus';
|
||||||
import { SendStateByConversationId } from './messages/MessageSendState';
|
import { SendStateByConversationId } from './messages/MessageSendState';
|
||||||
import { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions';
|
import { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions';
|
||||||
import { ConversationColorType } from './types/Colors';
|
import { ConversationColorType } from './types/Colors';
|
||||||
import { AttachmentDraftType, AttachmentType } from './types/Attachment';
|
import {
|
||||||
|
AttachmentDraftType,
|
||||||
|
AttachmentType,
|
||||||
|
ThumbnailType,
|
||||||
|
} from './types/Attachment';
|
||||||
import { EmbeddedContactType } from './types/EmbeddedContact';
|
import { EmbeddedContactType } from './types/EmbeddedContact';
|
||||||
import { SignalService as Proto } from './protobuf';
|
import { SignalService as Proto } from './protobuf';
|
||||||
import { AvatarDataType } from './types/Avatar';
|
import { AvatarDataType } from './types/Avatar';
|
||||||
|
@ -30,11 +34,10 @@ import { SeenStatus } from './MessageSeenStatus';
|
||||||
import { GiftBadgeStates } from './components/conversation/Message';
|
import { GiftBadgeStates } from './components/conversation/Message';
|
||||||
import { LinkPreviewType } from './types/message/LinkPreviews';
|
import { LinkPreviewType } from './types/message/LinkPreviews';
|
||||||
|
|
||||||
|
import type { ProcessedQuoteAttachment } from './textsecure/Types.d';
|
||||||
import type { StickerType } from './types/Stickers';
|
import type { StickerType } from './types/Stickers';
|
||||||
import { MIMEType } from './types/MIME';
|
import { MIMEType } from './types/MIME';
|
||||||
|
|
||||||
export type WhatIsThis = any;
|
|
||||||
|
|
||||||
export type LastMessageStatus =
|
export type LastMessageStatus =
|
||||||
| 'paused'
|
| 'paused'
|
||||||
| 'error'
|
| 'error'
|
||||||
|
@ -73,7 +76,9 @@ export type QuotedAttachment = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QuotedMessageType = {
|
export type QuotedMessageType = {
|
||||||
attachments: Array<WhatIsThis /* QuotedAttachment */>;
|
// TODO DESKTOP-3826
|
||||||
|
// eslint-disable-next-line no-explicit-any
|
||||||
|
attachments: Array<any>;
|
||||||
// `author` is an old attribute that holds the author's E164. We shouldn't use it for
|
// `author` is an old attribute that holds the author's E164. We shouldn't use it for
|
||||||
// new messages, but old messages might have this attribute.
|
// new messages, but old messages might have this attribute.
|
||||||
author?: string;
|
author?: string;
|
||||||
|
@ -245,6 +250,11 @@ export type ConversationLastProfileType = Readonly<{
|
||||||
profileKeyVersion: string;
|
profileKeyVersion: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type ValidateConversationType = Pick<
|
||||||
|
ConversationAttributesType,
|
||||||
|
'e164' | 'uuid' | 'type' | 'groupId'
|
||||||
|
>;
|
||||||
|
|
||||||
export type ConversationAttributesType = {
|
export type ConversationAttributesType = {
|
||||||
accessKey?: string | null;
|
accessKey?: string | null;
|
||||||
addedBy?: string;
|
addedBy?: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2020-2022 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { compact, isNumber, throttle, debounce } from 'lodash';
|
import { compact, has, isNumber, throttle, debounce } from 'lodash';
|
||||||
import { batch as batchDispatch } from 'react-redux';
|
import { batch as batchDispatch } from 'react-redux';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { v4 as generateGuid } from 'uuid';
|
import { v4 as generateGuid } from 'uuid';
|
||||||
|
@ -15,28 +15,24 @@ import type {
|
||||||
QuotedMessageType,
|
QuotedMessageType,
|
||||||
SenderKeyInfoType,
|
SenderKeyInfoType,
|
||||||
VerificationOptions,
|
VerificationOptions,
|
||||||
WhatIsThis,
|
|
||||||
} from '../model-types.d';
|
} from '../model-types.d';
|
||||||
import { getInitials } from '../util/getInitials';
|
import { getInitials } from '../util/getInitials';
|
||||||
import { normalizeUuid } from '../util/normalizeUuid';
|
import { normalizeUuid } from '../util/normalizeUuid';
|
||||||
import {
|
import { getRegionCodeForNumber } from '../util/libphonenumberUtil';
|
||||||
getRegionCodeForNumber,
|
|
||||||
parseNumber,
|
|
||||||
} from '../util/libphonenumberUtil';
|
|
||||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||||
|
import type { AttachmentType, ThumbnailType } from '../types/Attachment';
|
||||||
import { toDayMillis } from '../util/timestamp';
|
import { toDayMillis } from '../util/timestamp';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
|
||||||
import { isGIF } from '../types/Attachment';
|
import { isGIF } from '../types/Attachment';
|
||||||
import type { CallHistoryDetailsType } from '../types/Calling';
|
import type { CallHistoryDetailsType } from '../types/Calling';
|
||||||
import { CallMode } from '../types/Calling';
|
import { CallMode } from '../types/Calling';
|
||||||
import * as EmbeddedContact from '../types/EmbeddedContact';
|
import * as EmbeddedContact from '../types/EmbeddedContact';
|
||||||
import * as Conversation from '../types/Conversation';
|
import * as Conversation from '../types/Conversation';
|
||||||
|
import type { StickerType, StickerWithHydratedData } from '../types/Stickers';
|
||||||
import * as Stickers from '../types/Stickers';
|
import * as Stickers from '../types/Stickers';
|
||||||
import type {
|
import type {
|
||||||
ContactWithHydratedAvatar,
|
ContactWithHydratedAvatar,
|
||||||
GroupV1InfoType,
|
GroupV1InfoType,
|
||||||
GroupV2InfoType,
|
GroupV2InfoType,
|
||||||
StickerType,
|
|
||||||
} from '../textsecure/SendMessage';
|
} from '../textsecure/SendMessage';
|
||||||
import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
|
import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
|
||||||
import MessageSender from '../textsecure/SendMessage';
|
import MessageSender from '../textsecure/SendMessage';
|
||||||
|
@ -58,7 +54,7 @@ import { sniffImageMimeType } from '../util/sniffImageMimeType';
|
||||||
import { isValidE164 } from '../util/isValidE164';
|
import { isValidE164 } from '../util/isValidE164';
|
||||||
import type { MIMEType } from '../types/MIME';
|
import type { MIMEType } from '../types/MIME';
|
||||||
import { IMAGE_JPEG, IMAGE_GIF, IMAGE_WEBP } from '../types/MIME';
|
import { IMAGE_JPEG, IMAGE_GIF, IMAGE_WEBP } from '../types/MIME';
|
||||||
import { UUID, isValidUuid, UUIDKind } from '../types/UUID';
|
import { UUID, UUIDKind } from '../types/UUID';
|
||||||
import type { UUIDStringType } from '../types/UUID';
|
import type { UUIDStringType } from '../types/UUID';
|
||||||
import { deriveAccessKey, decryptProfileName, decryptProfile } from '../Crypto';
|
import { deriveAccessKey, decryptProfileName, decryptProfile } from '../Crypto';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
|
@ -125,6 +121,7 @@ import { SeenStatus } from '../MessageSeenStatus';
|
||||||
import { getConversationIdForLogging } from '../util/idForLogging';
|
import { getConversationIdForLogging } from '../util/idForLogging';
|
||||||
import { getSendTarget } from '../util/getSendTarget';
|
import { getSendTarget } from '../util/getSendTarget';
|
||||||
import { getRecipients } from '../util/getRecipients';
|
import { getRecipients } from '../util/getRecipients';
|
||||||
|
import { validateConversation } from '../util/validateConversation';
|
||||||
|
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
|
@ -3389,71 +3386,7 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
override validate(attributes = this.attributes): string | null {
|
override validate(attributes = this.attributes): string | null {
|
||||||
const required = ['type'];
|
return validateConversation(attributes);
|
||||||
const missing = window._.filter(required, attr => !attributes[attr]);
|
|
||||||
if (missing.length) {
|
|
||||||
return `Conversation must have ${missing}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attributes.type !== 'private' && attributes.type !== 'group') {
|
|
||||||
return `Invalid conversation type: ${attributes.type}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const atLeastOneOf = ['e164', 'uuid', 'groupId'];
|
|
||||||
const hasAtLeastOneOf =
|
|
||||||
window._.filter(atLeastOneOf, attr => attributes[attr]).length > 0;
|
|
||||||
|
|
||||||
if (!hasAtLeastOneOf) {
|
|
||||||
return 'Missing one of e164, uuid, or groupId';
|
|
||||||
}
|
|
||||||
|
|
||||||
const error = this.validateNumber() || this.validateUuid();
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateNumber(): string | null {
|
|
||||||
if (isDirectConversation(this.attributes) && this.get('e164')) {
|
|
||||||
const regionCode = window.storage.get('regionCode');
|
|
||||||
if (!regionCode) {
|
|
||||||
throw new Error('No region code');
|
|
||||||
}
|
|
||||||
const number = parseNumber(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
this.get('e164')!,
|
|
||||||
regionCode
|
|
||||||
);
|
|
||||||
if (number.isValidNumber) {
|
|
||||||
this.set({ e164: number.e164 });
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let errorMessage: undefined | string;
|
|
||||||
if (number.error instanceof Error) {
|
|
||||||
errorMessage = number.error.message;
|
|
||||||
} else if (typeof number.error === 'string') {
|
|
||||||
errorMessage = number.error;
|
|
||||||
}
|
|
||||||
return errorMessage || 'Invalid phone number';
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateUuid(): string | null {
|
|
||||||
if (isDirectConversation(this.attributes) && this.get('uuid')) {
|
|
||||||
if (isValidUuid(this.get('uuid'))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Invalid UUID';
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
queueJob<T>(
|
queueJob<T>(
|
||||||
|
@ -3643,10 +3576,16 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
async getQuoteAttachment(
|
async getQuoteAttachment(
|
||||||
attachments?: Array<WhatIsThis>,
|
attachments?: Array<AttachmentType>,
|
||||||
preview?: Array<WhatIsThis>,
|
preview?: Array<LinkPreviewType>,
|
||||||
sticker?: WhatIsThis
|
sticker?: StickerType
|
||||||
): Promise<WhatIsThis> {
|
): Promise<
|
||||||
|
Array<{
|
||||||
|
contentType: MIMEType;
|
||||||
|
fileName: string | null;
|
||||||
|
thumbnail: ThumbnailType | null;
|
||||||
|
}>
|
||||||
|
> {
|
||||||
if (attachments && attachments.length) {
|
if (attachments && attachments.length) {
|
||||||
const attachmentsToUse = Array.from(take(attachments, 1));
|
const attachmentsToUse = Array.from(take(attachments, 1));
|
||||||
const isGIFQuote = isGIF(attachmentsToUse);
|
const isGIFQuote = isGIF(attachmentsToUse);
|
||||||
|
@ -3673,7 +3612,9 @@ export class ConversationModel extends window.Backbone
|
||||||
thumbnail: thumbnail
|
thumbnail: thumbnail
|
||||||
? {
|
? {
|
||||||
...(await loadAttachmentData(thumbnail)),
|
...(await loadAttachmentData(thumbnail)),
|
||||||
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
|
objectUrl: thumbnail.path
|
||||||
|
? getAbsoluteAttachmentPath(thumbnail.path)
|
||||||
|
: undefined,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
};
|
};
|
||||||
|
@ -3708,7 +3649,9 @@ export class ConversationModel extends window.Backbone
|
||||||
thumbnail: image
|
thumbnail: image
|
||||||
? {
|
? {
|
||||||
...(await loadAttachmentData(image)),
|
...(await loadAttachmentData(image)),
|
||||||
objectUrl: getAbsoluteAttachmentPath(image.path),
|
objectUrl: image.path
|
||||||
|
? getAbsoluteAttachmentPath(image.path)
|
||||||
|
: undefined,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
};
|
};
|
||||||
|
@ -3727,7 +3670,7 @@ export class ConversationModel extends window.Backbone
|
||||||
fileName: null,
|
fileName: null,
|
||||||
thumbnail: {
|
thumbnail: {
|
||||||
...(await loadAttachmentData(sticker.data)),
|
...(await loadAttachmentData(sticker.data)),
|
||||||
objectUrl: getAbsoluteAttachmentPath(path),
|
objectUrl: path ? getAbsoluteAttachmentPath(path) : undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -3797,7 +3740,7 @@ export class ConversationModel extends window.Backbone
|
||||||
contentType = IMAGE_WEBP;
|
contentType = IMAGE_WEBP;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sticker = {
|
const sticker: StickerWithHydratedData = {
|
||||||
packId,
|
packId,
|
||||||
stickerId,
|
stickerId,
|
||||||
packKey: key,
|
packKey: key,
|
||||||
|
@ -3867,7 +3810,7 @@ export class ConversationModel extends window.Backbone
|
||||||
mentions?: BodyRangesType;
|
mentions?: BodyRangesType;
|
||||||
preview?: Array<LinkPreviewType>;
|
preview?: Array<LinkPreviewType>;
|
||||||
quote?: QuotedMessageType;
|
quote?: QuotedMessageType;
|
||||||
sticker?: StickerType;
|
sticker?: StickerWithHydratedData;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dontClearDraft,
|
dontClearDraft,
|
||||||
|
@ -5489,8 +5432,8 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
reset(...args: Array<WhatIsThis>) {
|
reset(models?: Array<ConversationModel>, options?: Backbone.Silenceable) {
|
||||||
window.Backbone.Collection.prototype.reset.apply(this, args as WhatIsThis);
|
window.Backbone.Collection.prototype.reset.call(this, models, options);
|
||||||
this.resetLookups();
|
this.resetLookups();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -5534,8 +5477,14 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({
|
||||||
this._byGroupId = Object.create(null);
|
this._byGroupId = Object.create(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
add(data: WhatIsThis | Array<WhatIsThis>) {
|
add(
|
||||||
let hydratedData;
|
data:
|
||||||
|
| ConversationModel
|
||||||
|
| ConversationAttributesType
|
||||||
|
| Array<ConversationModel>
|
||||||
|
| Array<ConversationAttributesType>
|
||||||
|
) {
|
||||||
|
let hydratedData: Array<ConversationModel> | ConversationModel;
|
||||||
|
|
||||||
// First, we need to ensure that the data we're working with is Conversation models
|
// First, we need to ensure that the data we're working with is Conversation models
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
|
@ -5544,16 +5493,20 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({
|
||||||
const item = data[i];
|
const item = data[i];
|
||||||
|
|
||||||
// We create a new model if it's not already a model
|
// We create a new model if it's not already a model
|
||||||
if (!item.get) {
|
if (has(item, 'get')) {
|
||||||
hydratedData.push(new window.Whisper.Conversation(item));
|
hydratedData.push(item as ConversationModel);
|
||||||
} else {
|
} else {
|
||||||
hydratedData.push(item);
|
hydratedData.push(
|
||||||
|
new window.Whisper.Conversation(item as ConversationAttributesType)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!data.get) {
|
} else if (has(data, 'get')) {
|
||||||
hydratedData = new window.Whisper.Conversation(data);
|
hydratedData = data as ConversationModel;
|
||||||
} else {
|
} else {
|
||||||
hydratedData = data;
|
hydratedData = new window.Whisper.Conversation(
|
||||||
|
data as ConversationAttributesType
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, we update our lookups first to prevent infinite loops on the 'add' event
|
// Next, we update our lookups first to prevent infinite loops on the 'add' event
|
||||||
|
@ -5562,7 +5515,9 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({
|
||||||
);
|
);
|
||||||
|
|
||||||
// Lastly, we fire off the add events related to this change
|
// Lastly, we fire off the add events related to this change
|
||||||
window.Backbone.Collection.prototype.add.call(this, hydratedData);
|
// Go home Backbone, you're drunk.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
window.Backbone.Collection.prototype.add.call(this, hydratedData as any);
|
||||||
|
|
||||||
return hydratedData;
|
return hydratedData;
|
||||||
},
|
},
|
||||||
|
@ -5583,7 +5538,7 @@ window.Whisper.ConversationCollection = window.Backbone.Collection.extend({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
comparator(m: WhatIsThis) {
|
comparator(m: ConversationModel) {
|
||||||
return -(m.get('active_at') || 0);
|
return -(m.get('active_at') || 0);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,6 @@ import type {
|
||||||
MessageAttributesType,
|
MessageAttributesType,
|
||||||
MessageReactionType,
|
MessageReactionType,
|
||||||
QuotedMessageType,
|
QuotedMessageType,
|
||||||
WhatIsThis,
|
|
||||||
} from '../model-types.d';
|
} from '../model-types.d';
|
||||||
import {
|
import {
|
||||||
filter,
|
filter,
|
||||||
|
@ -46,7 +45,10 @@ import * as reactionUtil from '../reactions/util';
|
||||||
import * as Stickers from '../types/Stickers';
|
import * as Stickers from '../types/Stickers';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import * as EmbeddedContact from '../types/EmbeddedContact';
|
import * as EmbeddedContact from '../types/EmbeddedContact';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type {
|
||||||
|
AttachmentType,
|
||||||
|
AttachmentWithHydratedData,
|
||||||
|
} from '../types/Attachment';
|
||||||
import { isImage, isVideo } from '../types/Attachment';
|
import { isImage, isVideo } from '../types/Attachment';
|
||||||
import * as Attachment from '../types/Attachment';
|
import * as Attachment from '../types/Attachment';
|
||||||
import { stringToMIMEType } from '../types/MIME';
|
import { stringToMIMEType } from '../types/MIME';
|
||||||
|
@ -158,6 +160,8 @@ import { isNewReactionReplacingPrevious } from '../reactions/util';
|
||||||
import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
|
import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
|
||||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||||
import { downloadAttachment } from '../util/downloadAttachment';
|
import { downloadAttachment } from '../util/downloadAttachment';
|
||||||
|
import type { DeleteModel } from '../messageModifiers/Deletes';
|
||||||
|
import type { StickerWithHydratedData } from '../types/Stickers';
|
||||||
|
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
|
|
||||||
|
@ -176,9 +180,14 @@ const { getTextWithMentions, GoogleChrome } = window.Signal.Util;
|
||||||
const { getMessageBySender } = window.Signal.Data;
|
const { getMessageBySender } = window.Signal.Data;
|
||||||
|
|
||||||
export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
static getLongMessageAttachment: (
|
static getLongMessageAttachment: (opts: {
|
||||||
attachment: typeof window.WhatIsThis
|
attachments: Array<AttachmentWithHydratedData>;
|
||||||
) => typeof window.WhatIsThis;
|
body?: string;
|
||||||
|
now: number;
|
||||||
|
}) => {
|
||||||
|
body?: string;
|
||||||
|
attachments: Array<AttachmentWithHydratedData>;
|
||||||
|
};
|
||||||
|
|
||||||
CURRENT_PROTOCOL_VERSION?: number;
|
CURRENT_PROTOCOL_VERSION?: number;
|
||||||
|
|
||||||
|
@ -198,9 +207,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
cachedOutgoingPreviewData?: Array<LinkPreviewType>;
|
cachedOutgoingPreviewData?: Array<LinkPreviewType>;
|
||||||
|
|
||||||
cachedOutgoingQuoteData?: WhatIsThis;
|
cachedOutgoingQuoteData?: QuotedMessageType;
|
||||||
|
|
||||||
cachedOutgoingStickerData?: WhatIsThis;
|
cachedOutgoingStickerData?: StickerWithHydratedData;
|
||||||
|
|
||||||
override initialize(attributes: unknown): void {
|
override initialize(attributes: unknown): void {
|
||||||
if (_.isObject(attributes)) {
|
if (_.isObject(attributes)) {
|
||||||
|
@ -1910,7 +1919,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
quote.attachments = [
|
quote.attachments = [
|
||||||
{
|
{
|
||||||
contentType: 'image/jpeg',
|
contentType: MIME.IMAGE_JPEG,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
@ -1942,7 +1951,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
quote.text = originalMessage.get('body');
|
quote.text = originalMessage.get('body');
|
||||||
if (firstAttachment) {
|
if (firstAttachment) {
|
||||||
firstAttachment.thumbnail = undefined;
|
firstAttachment.thumbnail = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -2336,7 +2345,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageId = UUID.generate().toString();
|
const messageId = message.get('id') || UUID.generate().toString();
|
||||||
|
|
||||||
// Send delivery receipts, but only for incoming sealed sender messages
|
// Send delivery receipts, but only for incoming sealed sender messages
|
||||||
// and not for messages from unaccepted conversations
|
// and not for messages from unaccepted conversations
|
||||||
|
@ -2379,7 +2388,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
const urls = LinkPreview.findLinks(dataMessage.body || '');
|
const urls = LinkPreview.findLinks(dataMessage.body || '');
|
||||||
const incomingPreview = dataMessage.preview || [];
|
const incomingPreview = dataMessage.preview || [];
|
||||||
const preview = incomingPreview.filter(
|
const preview = incomingPreview.filter(
|
||||||
(item: typeof window.WhatIsThis) =>
|
(item: LinkPreviewType) =>
|
||||||
(item.image || item.title) &&
|
(item.image || item.title) &&
|
||||||
urls.includes(item.url) &&
|
urls.includes(item.url) &&
|
||||||
LinkPreview.shouldPreviewHref(item.url)
|
LinkPreview.shouldPreviewHref(item.url)
|
||||||
|
@ -3215,7 +3224,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDeleteForEveryone(
|
async handleDeleteForEveryone(
|
||||||
del: typeof window.WhatIsThis,
|
del: DeleteModel,
|
||||||
shouldPersist = true
|
shouldPersist = true
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
log.info('Handling DOE.', {
|
log.info('Handling DOE.', {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { getThemeType } from '../util/getThemeType';
|
||||||
|
|
||||||
export function themeChanged(): void {
|
export function themeChanged(): void {
|
||||||
if (window.reduxActions && window.reduxActions.user) {
|
if (window.reduxActions && window.reduxActions.user) {
|
||||||
const theme = window.Events.getThemeSetting();
|
const theme = getThemeType();
|
||||||
window.reduxActions.user.userChanged({
|
window.reduxActions.user.userChanged({ theme });
|
||||||
theme: theme === 'system' ? window.systemTheme : theme,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ type MigrationsModuleType = {
|
||||||
getAbsoluteStickerPath: (path: string) => string;
|
getAbsoluteStickerPath: (path: string) => string;
|
||||||
getAbsoluteTempPath: (path: string) => string;
|
getAbsoluteTempPath: (path: string) => string;
|
||||||
loadAttachmentData: (
|
loadAttachmentData: (
|
||||||
attachment: AttachmentType
|
attachment: Pick<AttachmentType, 'data' | 'path'>
|
||||||
) => Promise<AttachmentWithHydratedData>;
|
) => Promise<AttachmentWithHydratedData>;
|
||||||
loadContactData: (
|
loadContactData: (
|
||||||
contact: Array<EmbeddedContactType> | undefined
|
contact: Array<EmbeddedContactType> | undefined
|
||||||
|
|
|
@ -32,6 +32,7 @@ import type { MenuOptionsType } from '../types/menu';
|
||||||
import { UUIDKind } from '../types/UUID';
|
import { UUIDKind } from '../types/UUID';
|
||||||
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
|
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
|
||||||
import type { MainWindowStatsType } from '../windows/context';
|
import type { MainWindowStatsType } from '../windows/context';
|
||||||
|
import { getThemeType } from '../util/getThemeType';
|
||||||
|
|
||||||
export function getInitialState({
|
export function getInitialState({
|
||||||
badges,
|
badges,
|
||||||
|
@ -63,8 +64,7 @@ export function getInitialState({
|
||||||
window.ConversationController.getOurConversationId();
|
window.ConversationController.getOurConversationId();
|
||||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||||
|
|
||||||
const themeSetting = window.Events.getThemeSetting();
|
const theme = getThemeType();
|
||||||
const theme = themeSetting === 'system' ? window.systemTheme : themeSetting;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts: accounts(),
|
accounts: accounts(),
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
SenderKeyDistributionMessage,
|
SenderKeyDistributionMessage,
|
||||||
} from '@signalapp/libsignal-client';
|
} from '@signalapp/libsignal-client';
|
||||||
|
|
||||||
|
import type { QuotedMessageType } from '../model-types.d';
|
||||||
import { GLOBAL_ZONE } from '../SignalProtocolStore';
|
import { GLOBAL_ZONE } from '../SignalProtocolStore';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||||
|
@ -110,15 +111,6 @@ export type StickerType = StickerWithHydratedData & {
|
||||||
attachmentPointer?: Proto.IAttachmentPointer;
|
attachmentPointer?: Proto.IAttachmentPointer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QuoteType = {
|
|
||||||
attachments?: Array<AttachmentType>;
|
|
||||||
authorUuid?: string;
|
|
||||||
bodyRanges?: BodyRangesType;
|
|
||||||
id?: number;
|
|
||||||
isGiftBadge?: boolean;
|
|
||||||
text?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ReactionType = {
|
export type ReactionType = {
|
||||||
emoji?: string;
|
emoji?: string;
|
||||||
remove?: boolean;
|
remove?: boolean;
|
||||||
|
@ -192,9 +184,9 @@ export type MessageOptionsType = {
|
||||||
needsSync?: boolean;
|
needsSync?: boolean;
|
||||||
preview?: ReadonlyArray<LinkPreviewType>;
|
preview?: ReadonlyArray<LinkPreviewType>;
|
||||||
profileKey?: Uint8Array;
|
profileKey?: Uint8Array;
|
||||||
quote?: QuoteType;
|
quote?: QuotedMessageType | null;
|
||||||
recipients: ReadonlyArray<string>;
|
recipients: ReadonlyArray<string>;
|
||||||
sticker?: StickerType;
|
sticker?: StickerWithHydratedData;
|
||||||
reaction?: ReactionType;
|
reaction?: ReactionType;
|
||||||
deletedForEveryoneTimestamp?: number;
|
deletedForEveryoneTimestamp?: number;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
@ -215,9 +207,9 @@ export type GroupSendOptionsType = {
|
||||||
messageText?: string;
|
messageText?: string;
|
||||||
preview?: ReadonlyArray<LinkPreviewType>;
|
preview?: ReadonlyArray<LinkPreviewType>;
|
||||||
profileKey?: Uint8Array;
|
profileKey?: Uint8Array;
|
||||||
quote?: QuoteType;
|
quote?: QuotedMessageType | null;
|
||||||
reaction?: ReactionType;
|
reaction?: ReactionType;
|
||||||
sticker?: StickerType;
|
sticker?: StickerWithHydratedData;
|
||||||
storyContext?: StoryContextType;
|
storyContext?: StoryContextType;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
};
|
};
|
||||||
|
@ -246,7 +238,7 @@ class Message {
|
||||||
|
|
||||||
profileKey?: Uint8Array;
|
profileKey?: Uint8Array;
|
||||||
|
|
||||||
quote?: QuoteType;
|
quote?: QuotedMessageType | null;
|
||||||
|
|
||||||
recipients: ReadonlyArray<string>;
|
recipients: ReadonlyArray<string>;
|
||||||
|
|
||||||
|
@ -1195,9 +1187,9 @@ export default class MessageSender {
|
||||||
options?: SendOptionsType;
|
options?: SendOptionsType;
|
||||||
preview?: ReadonlyArray<LinkPreviewType> | undefined;
|
preview?: ReadonlyArray<LinkPreviewType> | undefined;
|
||||||
profileKey?: Uint8Array;
|
profileKey?: Uint8Array;
|
||||||
quote?: QuoteType;
|
quote?: QuotedMessageType | null;
|
||||||
reaction?: ReactionType;
|
reaction?: ReactionType;
|
||||||
sticker?: StickerType;
|
sticker?: StickerWithHydratedData;
|
||||||
storyContext?: StoryContextType;
|
storyContext?: StoryContextType;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
urgent: boolean;
|
urgent: boolean;
|
||||||
|
|
|
@ -168,13 +168,10 @@ export type AttachmentDraftType =
|
||||||
size: number;
|
size: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ThumbnailType = {
|
export type ThumbnailType = Pick<
|
||||||
height?: number;
|
AttachmentType,
|
||||||
width?: number;
|
'height' | 'width' | 'url' | 'contentType' | 'path' | 'data'
|
||||||
url?: string;
|
> & {
|
||||||
contentType: MIME.MIMEType;
|
|
||||||
path?: string;
|
|
||||||
data?: Uint8Array;
|
|
||||||
// Only used when quote needed to make an in-memory thumbnail
|
// Only used when quote needed to make an in-memory thumbnail
|
||||||
objectUrl?: string;
|
objectUrl?: string;
|
||||||
};
|
};
|
||||||
|
@ -236,7 +233,7 @@ export async function migrateDataToFileSystem(
|
||||||
// Over time, we can expand this definition to become more narrow, e.g. require certain
|
// Over time, we can expand this definition to become more narrow, e.g. require certain
|
||||||
// fields, etc.
|
// fields, etc.
|
||||||
export function isValid(
|
export function isValid(
|
||||||
rawAttachment?: AttachmentType
|
rawAttachment?: Pick<AttachmentType, 'data' | 'path'>
|
||||||
): rawAttachment is AttachmentType {
|
): rawAttachment is AttachmentType {
|
||||||
// NOTE: We cannot use `_.isPlainObject` because `rawAttachment` is
|
// NOTE: We cannot use `_.isPlainObject` because `rawAttachment` is
|
||||||
// deserialized by protobuf:
|
// deserialized by protobuf:
|
||||||
|
@ -396,14 +393,14 @@ export function hasData(attachment: AttachmentType): boolean {
|
||||||
|
|
||||||
export function loadData(
|
export function loadData(
|
||||||
readAttachmentData: (path: string) => Promise<Uint8Array>
|
readAttachmentData: (path: string) => Promise<Uint8Array>
|
||||||
): (attachment: AttachmentType) => Promise<AttachmentWithHydratedData> {
|
): (
|
||||||
|
attachment: Pick<AttachmentType, 'data' | 'path'>
|
||||||
|
) => Promise<AttachmentWithHydratedData> {
|
||||||
if (!is.function_(readAttachmentData)) {
|
if (!is.function_(readAttachmentData)) {
|
||||||
throw new TypeError("'readAttachmentData' must be a function");
|
throw new TypeError("'readAttachmentData' must be a function");
|
||||||
}
|
}
|
||||||
|
|
||||||
return async (
|
return async attachment => {
|
||||||
attachment: AttachmentType
|
|
||||||
): Promise<AttachmentWithHydratedData> => {
|
|
||||||
if (!isValid(attachment)) {
|
if (!isValid(attachment)) {
|
||||||
throw new TypeError("'attachment' is not valid");
|
throw new TypeError("'attachment' is not valid");
|
||||||
}
|
}
|
||||||
|
|
|
@ -651,7 +651,7 @@ export const processNewSticker = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
type LoadAttachmentType = (
|
type LoadAttachmentType = (
|
||||||
attachment: AttachmentType
|
attachment: Pick<AttachmentType, 'data' | 'path'>
|
||||||
) => Promise<AttachmentWithHydratedData>;
|
) => Promise<AttachmentWithHydratedData>;
|
||||||
|
|
||||||
export const createAttachmentLoader = (
|
export const createAttachmentLoader = (
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import type { StorageAccessType } from './Storage.d';
|
import type { StorageAccessType } from './Storage.d';
|
||||||
|
|
||||||
export const themeSettingSchema = z.enum(['system', 'light', 'dark']);
|
export const themeSettingSchema = z.enum(['system', 'light', 'dark']);
|
||||||
|
|
18
ts/util/getThemeType.ts
Normal file
18
ts/util/getThemeType.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { ThemeType } from '../types/Util';
|
||||||
|
|
||||||
|
export function getThemeType(): ThemeType {
|
||||||
|
const themeSetting = window.Events.getThemeSetting();
|
||||||
|
|
||||||
|
if (themeSetting === 'light') {
|
||||||
|
return ThemeType.light;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeSetting === 'dark') {
|
||||||
|
return ThemeType.dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.systemTheme;
|
||||||
|
}
|
62
ts/util/validateConversation.ts
Normal file
62
ts/util/validateConversation.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { ValidateConversationType } from '../model-types.d';
|
||||||
|
import { isDirectConversation } from './whatTypeOfConversation';
|
||||||
|
import { parseNumber } from './libphonenumberUtil';
|
||||||
|
import { isValidUuid } from '../types/UUID';
|
||||||
|
|
||||||
|
export function validateConversation(
|
||||||
|
attributes: ValidateConversationType
|
||||||
|
): string | null {
|
||||||
|
if (attributes.type !== 'private' && attributes.type !== 'group') {
|
||||||
|
return `Invalid conversation type: ${attributes.type}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attributes.e164 && !attributes.uuid && !attributes.groupId) {
|
||||||
|
return 'Missing one of e164, uuid, or groupId';
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = validateNumber(attributes) || validateUuid(attributes);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateNumber(attributes: ValidateConversationType): string | null {
|
||||||
|
if (isDirectConversation(attributes) && attributes.e164) {
|
||||||
|
const regionCode = window.storage.get('regionCode');
|
||||||
|
if (!regionCode) {
|
||||||
|
throw new Error('No region code');
|
||||||
|
}
|
||||||
|
const number = parseNumber(attributes.e164, regionCode);
|
||||||
|
if (number.isValidNumber) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let errorMessage: undefined | string;
|
||||||
|
if (number.error instanceof Error) {
|
||||||
|
errorMessage = number.error.message;
|
||||||
|
} else if (typeof number.error === 'string') {
|
||||||
|
errorMessage = number.error;
|
||||||
|
}
|
||||||
|
return errorMessage || 'Invalid phone number';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateUuid(attributes: ValidateConversationType): string | null {
|
||||||
|
if (isDirectConversation(attributes) && attributes.uuid) {
|
||||||
|
if (isValidUuid(attributes.uuid)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Invalid UUID';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
14
ts/window.d.ts
vendored
14
ts/window.d.ts
vendored
|
@ -98,8 +98,6 @@ import { RendererConfigType } from './types/RendererConfig';
|
||||||
|
|
||||||
export { Long } from 'long';
|
export { Long } from 'long';
|
||||||
|
|
||||||
export type WhatIsThis = any;
|
|
||||||
|
|
||||||
// Synced with the type in ts/shims/showConfirmationDialog
|
// Synced with the type in ts/shims/showConfirmationDialog
|
||||||
// we are duplicating it here because that file cannot import/export.
|
// we are duplicating it here because that file cannot import/export.
|
||||||
type ConfirmationDialogViewProps = {
|
type ConfirmationDialogViewProps = {
|
||||||
|
@ -253,8 +251,6 @@ declare global {
|
||||||
};
|
};
|
||||||
WebAudioRecorder: typeof WebAudioRecorderClass;
|
WebAudioRecorder: typeof WebAudioRecorderClass;
|
||||||
|
|
||||||
WhatIsThis: WhatIsThis;
|
|
||||||
|
|
||||||
addSetupMenuItems: () => void;
|
addSetupMenuItems: () => void;
|
||||||
attachmentDownloadQueue: Array<MessageModel> | undefined;
|
attachmentDownloadQueue: Array<MessageModel> | undefined;
|
||||||
startupProcessingQueue: StartupQueue | undefined;
|
startupProcessingQueue: StartupQueue | undefined;
|
||||||
|
@ -305,7 +301,7 @@ declare global {
|
||||||
nodeSetImmediate: typeof setImmediate;
|
nodeSetImmediate: typeof setImmediate;
|
||||||
onFullScreenChange: (fullScreen: boolean, maximized: boolean) => void;
|
onFullScreenChange: (fullScreen: boolean, maximized: boolean) => void;
|
||||||
platform: string;
|
platform: string;
|
||||||
preloadedImages: Array<WhatIsThis>;
|
preloadedImages: Array<HTMLImageElement>;
|
||||||
reduxActions: ReduxActions;
|
reduxActions: ReduxActions;
|
||||||
reduxStore: Store<StateType>;
|
reduxStore: Store<StateType>;
|
||||||
restart: () => void;
|
restart: () => void;
|
||||||
|
@ -315,14 +311,14 @@ declare global {
|
||||||
shutdown: () => void;
|
shutdown: () => void;
|
||||||
showDebugLog: () => void;
|
showDebugLog: () => void;
|
||||||
sendChallengeRequest: (request: IPCChallengeRequest) => void;
|
sendChallengeRequest: (request: IPCChallengeRequest) => void;
|
||||||
setAutoHideMenuBar: (value: WhatIsThis) => void;
|
setAutoHideMenuBar: (value: boolean) => void;
|
||||||
setBadgeCount: (count: number) => void;
|
setBadgeCount: (count: number) => void;
|
||||||
setMenuBarVisibility: (value: WhatIsThis) => void;
|
setMenuBarVisibility: (value: boolean) => void;
|
||||||
updateSystemTraySetting: (value: SystemTraySetting) => void;
|
updateSystemTraySetting: (value: SystemTraySetting) => void;
|
||||||
showConfirmationDialog: (options: ConfirmationDialogViewProps) => void;
|
showConfirmationDialog: (options: ConfirmationDialogViewProps) => void;
|
||||||
showKeyboardShortcuts: () => void;
|
showKeyboardShortcuts: () => void;
|
||||||
storage: Storage;
|
storage: Storage;
|
||||||
systemTheme: WhatIsThis;
|
systemTheme: ThemeType;
|
||||||
textsecure: typeof textsecure;
|
textsecure: typeof textsecure;
|
||||||
titleBarDoubleClick: () => void;
|
titleBarDoubleClick: () => void;
|
||||||
updateTrayIcon: (count: number) => void;
|
updateTrayIcon: (count: number) => void;
|
||||||
|
@ -341,7 +337,7 @@ declare global {
|
||||||
WebAPI: WebAPIConnectType;
|
WebAPI: WebAPIConnectType;
|
||||||
Whisper: WhisperType;
|
Whisper: WhisperType;
|
||||||
|
|
||||||
getServerTrustRoot: () => WhatIsThis;
|
getServerTrustRoot: () => string;
|
||||||
readyForUpdates: () => void;
|
readyForUpdates: () => void;
|
||||||
logAppLoadedEvent?: (options: { processedCount?: number }) => void;
|
logAppLoadedEvent?: (options: { processedCount?: number }) => void;
|
||||||
logAuthenticatedConnect?: () => void;
|
logAuthenticatedConnect?: () => void;
|
||||||
|
|
Loading…
Reference in a new issue