Disable forward for messages with embedded contact

This commit is contained in:
Scott Nonnenberg 2022-04-11 13:57:44 -07:00 committed by GitHub
parent 6d816d01ad
commit 7f89f6162f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 442 additions and 185 deletions

View File

@ -188,6 +188,7 @@ function initializeMigrations({
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);
@ -248,6 +249,7 @@ function initializeMigrations({
getAbsoluteStickerPath,
getAbsoluteTempPath,
loadAttachmentData,
loadContactData,
loadMessage: MessageType.createAttachmentLoader(loadAttachmentData),
loadPreviewData,
loadQuoteData,

View File

@ -562,6 +562,42 @@ exports.loadQuoteData = loadAttachmentData => {
};
};
exports.loadContactData = loadAttachmentData => {
if (!isFunction(loadAttachmentData)) {
throw new TypeError('loadContactData: loadAttachmentData is required');
}
return async contact => {
if (!contact) {
return null;
}
return Promise.all(
contact.map(async item => {
if (
!item ||
!item.avatar ||
!item.avatar.avatar ||
!item.avatar.avatar.path
) {
return item;
}
return {
...item,
avatar: {
...item.avatar,
avatar: {
...item.avatar.avatar,
...(await loadAttachmentData(item.avatar.avatar)),
},
},
};
})
);
};
};
exports.loadPreviewData = loadAttachmentData => {
if (!isFunction(loadAttachmentData)) {
throw new TypeError('loadPreviewData: loadAttachmentData is required');

View File

@ -48,6 +48,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
doForwardMessage: action('doForwardMessage'),
getPreferredBadge: () => undefined,
i18n,
hasContact: Boolean(overrideProps.hasContact),
isSticker: Boolean(overrideProps.isSticker),
linkPreview: overrideProps.linkPreview,
messageBody: text('messageBody', overrideProps.messageBody || ''),
@ -75,6 +76,10 @@ story.add('a sticker', () => {
return <ForwardMessageModal {...useProps({ isSticker: true })} />;
});
story.add('with a contact', () => {
return <ForwardMessageModal {...useProps({ hasContact: true })} />;
});
story.add('link preview', () => {
return (
<ForwardMessageModal

View File

@ -48,6 +48,7 @@ export type DataPropsType = {
linkPreview?: LinkPreviewType
) => void;
getPreferredBadge: PreferredBadgeSelectorType;
hasContact: boolean;
i18n: LocalizerType;
isSticker: boolean;
linkPreview?: LinkPreviewType;
@ -79,6 +80,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
candidateConversations,
doForwardMessage,
getPreferredBadge,
hasContact,
i18n,
isSticker,
linkPreview,
@ -110,7 +112,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
const [messageBodyText, setMessageBodyText] = useState(messageBody || '');
const [cannotMessage, setCannotMessage] = useState(false);
const isMessageEditable = !isSticker;
const isMessageEditable = !isSticker && !hasContact;
const hasSelectedMaximumNumberOfContacts =
selectedContacts.length >= MAX_FORWARD;
@ -142,6 +144,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
hasContactsSelected &&
(Boolean(messageBodyText) ||
isSticker ||
hasContact ||
(attachmentsToForward && attachmentsToForward.length));
const forwardMessage = React.useCallback(() => {

View File

@ -1710,6 +1710,7 @@ export class Message extends React.PureComponent<Props, State> {
const {
attachments,
canDownload,
contact,
canReact,
canReply,
canRetry,
@ -1729,7 +1730,7 @@ export class Message extends React.PureComponent<Props, State> {
text,
} = this.props;
const canForward = !isTapToView && !deletedForEveryone;
const canForward = !isTapToView && !deletedForEveryone && !contact;
const multipleAttachments = attachments && attachments.length > 1;
const shouldShowAdditional =

View File

@ -14,7 +14,10 @@ import { handleMessageSend } from '../../util/handleMessageSend';
import type { CallbackResultType } from '../../textsecure/Types.d';
import { isSent } from '../../messages/MessageSendState';
import { isOutgoing } from '../../state/selectors/message';
import type { AttachmentType } from '../../textsecure/SendMessage';
import type {
AttachmentType,
ContactWithHydratedAvatar,
} from '../../textsecure/SendMessage';
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
import type { BodyRangesType, StoryContextType } from '../../types/Util';
import type { WhatIsThis } from '../../window.d';
@ -131,6 +134,7 @@ export async function sendNormalMessage(
const {
attachments,
body,
contact,
deletedForEveryoneTimestamp,
expireTimer,
mentions,
@ -148,11 +152,12 @@ export async function sendNormalMessage(
const dataMessage = await window.textsecure.messaging.getDataMessage({
attachments,
body,
contact,
deletedForEveryoneTimestamp,
expireTimer,
groupV2: conversation.getGroupV2Info({
members: recipientIdentifiersWithoutMe,
}),
deletedForEveryoneTimestamp,
expireTimer,
preview,
profileKey,
quote,
@ -188,6 +193,7 @@ export async function sendNormalMessage(
contentHint: ContentHint.RESENDABLE,
groupSendOptions: {
attachments,
contact,
deletedForEveryoneTimestamp,
expireTimer,
groupV1: conversation.getGroupV1Info(
@ -237,21 +243,22 @@ export async function sendNormalMessage(
log.info('sending direct message');
innerPromise = window.textsecure.messaging.sendMessageToIdentifier({
attachments,
contact,
contentHint: ContentHint.RESENDABLE,
deletedForEveryoneTimestamp,
expireTimer,
groupId: undefined,
identifier: recipientIdentifiersWithoutMe[0],
messageText: body,
attachments,
quote,
preview,
sticker,
reaction: undefined,
deletedForEveryoneTimestamp,
timestamp: messageTimestamp,
expireTimer,
contentHint: ContentHint.RESENDABLE,
groupId: undefined,
profileKey,
options: sendOptions,
preview,
profileKey,
quote,
reaction: undefined,
sticker,
storyContext,
timestamp: messageTimestamp,
});
}
@ -380,6 +387,7 @@ async function getMessageSendData({
}>): Promise<{
attachments: Array<AttachmentType>;
body: undefined | string;
contact?: Array<ContactWithHydratedAvatar>;
deletedForEveryoneTimestamp: undefined | number;
expireTimer: undefined | number;
mentions: undefined | BodyRangesType;
@ -391,6 +399,7 @@ async function getMessageSendData({
}> {
const {
loadAttachmentData,
loadContactData,
loadPreviewData,
loadQuoteData,
loadStickerData,
@ -413,13 +422,15 @@ async function getMessageSendData({
const storyId = message.get('storyId');
const [attachmentsWithData, preview, quote, sticker, storyMessage] =
const [attachmentsWithData, contact, preview, quote, sticker, storyMessage] =
await Promise.all([
// We don't update the caches here because (1) we expect the caches to be populated
// on initial send, so they should be there in the 99% case (2) if you're retrying
// a failed message across restarts, we don't touch the cache for simplicity. If
// sends are failing, let's not add the complication of a cache.
Promise.all((message.get('attachments') ?? []).map(loadAttachmentData)),
message.cachedOutgoingContactData ||
loadContactData(message.get('contact')),
message.cachedOutgoingPreviewData ||
loadPreviewData(message.get('preview')),
message.cachedOutgoingQuoteData || loadQuoteData(message.get('quote')),
@ -439,6 +450,7 @@ async function getMessageSendData({
return {
attachments,
body,
contact,
deletedForEveryoneTimestamp: message.get('deletedForEveryoneTimestamp'),
expireTimer: message.get('expireTimer'),
mentions: message.get('bodyRanges'),

View File

@ -29,6 +29,7 @@ import * as Conversation from '../types/Conversation';
import * as Stickers from '../types/Stickers';
import { CapabilityError } from '../types/errors';
import type {
ContactWithHydratedAvatar,
GroupV1InfoType,
GroupV2InfoType,
StickerType,
@ -3834,7 +3835,11 @@ export class ConversationModel extends window.Backbone
},
};
this.enqueueMessageForSend(undefined, [], undefined, [], sticker);
this.enqueueMessageForSend({
body: undefined,
attachments: [],
sticker,
});
window.reduxActions.stickers.useSticker(packId, stickerId);
}
@ -3925,12 +3930,23 @@ export class ConversationModel extends window.Backbone
}
async enqueueMessageForSend(
body: string | undefined,
attachments: Array<AttachmentType>,
quote?: QuotedMessageType,
preview?: Array<LinkPreviewType>,
sticker?: StickerType,
mentions?: BodyRangesType,
{
attachments,
body,
contact,
mentions,
preview,
quote,
sticker,
}: {
attachments: Array<AttachmentType>;
body: string | undefined;
contact?: Array<ContactWithHydratedAvatar>;
mentions?: BodyRangesType;
preview?: Array<LinkPreviewType>;
quote?: QuotedMessageType;
sticker?: StickerType;
},
{
dontClearDraft,
sendHQImages,
@ -4000,6 +4016,7 @@ export class ConversationModel extends window.Backbone
type: 'outgoing',
body,
conversationId: this.id,
contact,
quote,
preview,
attachments: attachmentsToSend,
@ -4031,6 +4048,7 @@ export class ConversationModel extends window.Backbone
const model = new window.Whisper.Message(attributes);
const message = window.MessageController.register(model.id, model);
message.cachedOutgoingContactData = contact;
message.cachedOutgoingPreviewData = preview;
message.cachedOutgoingQuoteData = quote;
message.cachedOutgoingStickerData = sticker;

View File

@ -151,6 +151,7 @@ import type { ConversationQueueJobData } from '../jobs/conversationJobQueue';
import { getMessageById } from '../messages/getMessageById';
import { shouldDownloadStory } from '../util/shouldDownloadStory';
import { shouldShowStoriesView } from '../state/selectors/stories';
import type { ContactWithHydratedAvatar } from '../textsecure/SendMessage';
/* eslint-disable camelcase */
/* eslint-disable more/no-then */
@ -188,6 +189,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
syncPromise?: Promise<CallbackResultType | void>;
cachedOutgoingContactData?: Array<ContactWithHydratedAvatar>;
cachedOutgoingPreviewData?: Array<LinkPreviewType>;
cachedOutgoingQuoteData?: WhatIsThis;

View File

@ -230,12 +230,11 @@ function replyToStory(
if (conversation) {
conversation.enqueueMessageForSend(
messageBody,
[],
undefined,
undefined,
undefined,
mentions,
{
body: messageBody,
attachments: [],
mentions,
},
{
storyId: story.messageId,
timestamp,

View File

@ -24,6 +24,7 @@ export type SmartForwardMessageModalProps = {
attachments?: Array<AttachmentType>,
linkPreview?: LinkPreviewType
) => void;
hasContact: boolean;
isSticker: boolean;
messageBody?: string;
onClose: () => void;
@ -42,6 +43,7 @@ const mapStateToProps = (
const {
attachments,
doForwardMessage,
hasContact,
isSticker,
messageBody,
onClose,
@ -59,6 +61,7 @@ const mapStateToProps = (
candidateConversations,
doForwardMessage,
getPreferredBadge: getPreferredBadgeSelector(state),
hasContact,
i18n: getIntl(state),
isSticker,
linkPreview,

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-nested-ternary */
/* eslint-disable more/no-then */
/* eslint-disable no-bitwise */
/* eslint-disable max-classes-per-file */
@ -69,6 +68,12 @@ import type { SendTypesType } from '../util/handleMessageSend';
import { shouldSaveProto, sendTypesEnum } from '../util/handleMessageSend';
import { SignalService as Proto } from '../protobuf';
import * as log from '../logging/log';
import type { Avatar, EmbeddedContactType } from '../types/EmbeddedContact';
import {
numberToPhoneType,
numberToEmailType,
numberToAddressType,
} from '../types/EmbeddedContact';
export type SendMetadataType = {
[identifier: string]: {
@ -172,9 +177,16 @@ function makeAttachmentSendReady(
};
}
export type ContactWithHydratedAvatar = EmbeddedContactType & {
avatar?: Avatar & {
attachmentPointer?: Proto.IAttachmentPointer;
};
};
export type MessageOptionsType = {
attachments?: ReadonlyArray<AttachmentType> | null;
body?: string;
contact?: Array<ContactWithHydratedAvatar>;
expireTimer?: number;
flags?: number;
group?: {
@ -197,21 +209,22 @@ export type MessageOptionsType = {
};
export type GroupSendOptionsType = {
attachments?: Array<AttachmentType>;
contact?: Array<ContactWithHydratedAvatar>;
deletedForEveryoneTimestamp?: number;
expireTimer?: number;
flags?: number;
groupV2?: GroupV2InfoType;
groupCallUpdate?: GroupCallUpdateType;
groupV1?: GroupV1InfoType;
groupV2?: GroupV2InfoType;
mentions?: BodyRangesType;
messageText?: string;
preview?: ReadonlyArray<LinkPreviewType>;
profileKey?: Uint8Array;
quote?: QuoteType;
reaction?: ReactionType;
sticker?: StickerType;
deletedForEveryoneTimestamp?: number;
timestamp: number;
mentions?: BodyRangesType;
groupCallUpdate?: GroupCallUpdateType;
storyContext?: StoryContextType;
timestamp: number;
};
class Message {
@ -219,6 +232,8 @@ class Message {
body?: string;
contact?: Array<ContactWithHydratedAvatar>;
expireTimer?: number;
flags?: number;
@ -261,6 +276,7 @@ class Message {
constructor(options: MessageOptionsType) {
this.attachments = options.attachments || [];
this.body = options.body;
this.contact = options.contact;
this.expireTimer = options.expireTimer;
this.flags = options.flags;
this.group = options.group;
@ -403,6 +419,74 @@ class Message {
return item;
});
}
if (Array.isArray(this.contact)) {
proto.contact = this.contact.map(contact => {
const contactProto = new Proto.DataMessage.Contact();
if (contact.name) {
const nameProto: Proto.DataMessage.Contact.IName = {
givenName: contact.name.givenName,
familyName: contact.name.familyName,
prefix: contact.name.prefix,
suffix: contact.name.suffix,
middleName: contact.name.middleName,
displayName: contact.name.displayName,
};
contactProto.name = new Proto.DataMessage.Contact.Name(nameProto);
}
if (Array.isArray(contact.number)) {
contactProto.number = contact.number.map(number => {
const numberProto: Proto.DataMessage.Contact.IPhone = {
value: number.value,
type: numberToPhoneType(number.type),
label: number.label,
};
return new Proto.DataMessage.Contact.Phone(numberProto);
});
}
if (Array.isArray(contact.email)) {
contactProto.email = contact.email.map(email => {
const emailProto: Proto.DataMessage.Contact.IEmail = {
value: email.value,
type: numberToEmailType(email.type),
label: email.label,
};
return new Proto.DataMessage.Contact.Email(emailProto);
});
}
if (Array.isArray(contact.address)) {
contactProto.address = contact.address.map(address => {
const addressProto: Proto.DataMessage.Contact.IPostalAddress = {
type: numberToAddressType(address.type),
label: address.label,
street: address.street,
pobox: address.pobox,
neighborhood: address.neighborhood,
city: address.city,
region: address.region,
postcode: address.postcode,
country: address.country,
};
return new Proto.DataMessage.Contact.PostalAddress(addressProto);
});
}
if (contact.avatar && contact.avatar.attachmentPointer) {
const avatarProto = new Proto.DataMessage.Contact.Avatar();
avatarProto.avatar = contact.avatar.attachmentPointer;
avatarProto.isProfile = Boolean(contact.avatar.isProfile);
contactProto.avatar = avatarProto;
}
if (contact.organization) {
contactProto.organization = contact.organization;
}
return contactProto;
});
}
if (this.quote) {
const { QuotedAttachment } = Proto.DataMessage.Quote;
const { BodyRange, Quote } = Proto.DataMessage;
@ -559,14 +643,17 @@ export default class MessageSender {
}
async makeAttachmentPointer(
attachment: Readonly<AttachmentType>
attachment: Readonly<
Partial<AttachmentType> &
Pick<AttachmentType, 'data' | 'size' | 'contentType'>
>
): Promise<Proto.IAttachmentPointer> {
assert(
typeof attachment === 'object' && attachment !== null,
'Got null attachment in `makeAttachmentPointer`'
);
const { data, size } = attachment;
const { data, size, contentType } = attachment;
if (!(data instanceof Uint8Array)) {
throw new Error(
`makeAttachmentPointer: data was a '${typeof data}' instead of Uint8Array`
@ -577,6 +664,11 @@ export default class MessageSender {
`makeAttachmentPointer: Size ${size} did not match data.byteLength ${data.byteLength}`
);
}
if (typeof contentType !== 'string') {
throw new Error(
`makeAttachmentPointer: contentType ${contentType} was not a string`
);
}
const padded = this.getPaddedAttachment(data);
const key = getRandomBytes(64);
@ -589,7 +681,7 @@ export default class MessageSender {
proto.cdnId = Long.fromString(id);
proto.contentType = attachment.contentType;
proto.key = key;
proto.size = attachment.size;
proto.size = data.byteLength;
proto.digest = result.digest;
if (attachment.fileName) {
@ -615,22 +707,20 @@ export default class MessageSender {
}
async uploadAttachments(message: Message): Promise<void> {
await Promise.all(
message.attachments.map(attachment =>
this.makeAttachmentPointer(attachment)
)
)
.then(attachmentPointers => {
// eslint-disable-next-line no-param-reassign
message.attachmentPointers = attachmentPointers;
})
.catch(error => {
if (error instanceof HTTPError) {
throw new MessageError(message, error);
} else {
throw error;
}
});
try {
// eslint-disable-next-line no-param-reassign
message.attachmentPointers = await Promise.all(
message.attachments.map(attachment =>
this.makeAttachmentPointer(attachment)
)
);
} catch (error) {
if (error instanceof HTTPError) {
throw new MessageError(message, error);
} else {
throw error;
}
}
}
async uploadLinkPreviews(message: Message): Promise<void> {
@ -687,32 +777,68 @@ export default class MessageSender {
}
}
async uploadThumbnails(message: Message): Promise<void> {
const makePointer = this.makeAttachmentPointer.bind(this);
const { quote } = message;
if (!quote || !quote.attachments || quote.attachments.length === 0) {
async uploadContactAvatar(message: Message): Promise<void> {
const { contact } = message;
if (!contact || contact.length === 0) {
return;
}
await Promise.all(
quote.attachments.map((attachment: QuoteAttachmentType) => {
if (!attachment.thumbnail) {
return null;
}
try {
await Promise.all(
contact.map(async (item: ContactWithHydratedAvatar) => {
const itemAvatar = item?.avatar;
const avatar = itemAvatar?.avatar;
if (!itemAvatar || !avatar || !avatar.data) {
return;
}
const attachment = makeAttachmentSendReady(avatar);
if (!attachment) {
return;
}
return makePointer(attachment.thumbnail).then(pointer => {
// eslint-disable-next-line no-param-reassign
attachment.attachmentPointer = pointer;
});
})
).catch(error => {
itemAvatar.attachmentPointer = await this.makeAttachmentPointer(
attachment
);
})
);
} catch (error) {
if (error instanceof HTTPError) {
throw new MessageError(message, error);
} else {
throw error;
}
});
}
}
async uploadThumbnails(message: Message): Promise<void> {
const { quote } = message;
if (!quote || !quote.attachments || quote.attachments.length === 0) {
return;
}
try {
await Promise.all(
quote.attachments.map(async (attachment: QuoteAttachmentType) => {
if (!attachment.thumbnail) {
return;
}
// eslint-disable-next-line no-param-reassign
attachment.attachmentPointer = await this.makeAttachmentPointer(
attachment.thumbnail
);
})
);
} catch (error) {
if (error instanceof HTTPError) {
throw new MessageError(message, error);
} else {
throw error;
}
}
}
// Proto assembly
@ -742,6 +868,7 @@ export default class MessageSender {
const message = new Message(attributes);
await Promise.all([
this.uploadAttachments(message),
this.uploadContactAvatar(message),
this.uploadThumbnails(message),
this.uploadLinkPreviews(message),
this.uploadSticker(message),
@ -789,6 +916,7 @@ export default class MessageSender {
): MessageOptionsType {
const {
attachments,
contact,
deletedForEveryoneTimestamp,
expireTimer,
flags,
@ -839,6 +967,7 @@ export default class MessageSender {
return {
attachments,
body: messageText,
contact,
deletedForEveryoneTimestamp,
expireTimer,
flags,
@ -883,33 +1012,25 @@ export default class MessageSender {
groupId: string | undefined;
options?: SendOptionsType;
}>): Promise<CallbackResultType> {
const message = new Message(messageOptions);
const message = await this.getHydratedMessage(messageOptions);
return Promise.all([
this.uploadAttachments(message),
this.uploadThumbnails(message),
this.uploadLinkPreviews(message),
this.uploadSticker(message),
]).then(
async (): Promise<CallbackResultType> =>
new Promise((resolve, reject) => {
this.sendMessageProto({
callback: (res: CallbackResultType) => {
if (res.errors && res.errors.length > 0) {
reject(new SendMessageProtoError(res));
} else {
resolve(res);
}
},
contentHint,
groupId,
options,
proto: message.toProto(),
recipients: message.recipients || [],
timestamp: message.timestamp,
});
})
);
return new Promise((resolve, reject) => {
this.sendMessageProto({
callback: (res: CallbackResultType) => {
if (res.errors && res.errors.length > 0) {
reject(new SendMessageProtoError(res));
} else {
resolve(res);
}
},
contentHint,
groupId,
options,
proto: message.toProto(),
recipients: message.recipients || [],
timestamp: message.timestamp,
});
});
}
sendMessageProto({
@ -1033,52 +1154,55 @@ export default class MessageSender {
// You might wonder why this takes a groupId. models/messages.resend() can send a group
// message to just one person.
async sendMessageToIdentifier({
attachments,
contact,
contentHint,
deletedForEveryoneTimestamp,
expireTimer,
groupId,
identifier,
messageText,
attachments,
quote,
preview,
sticker,
reaction,
deletedForEveryoneTimestamp,
timestamp,
expireTimer,
contentHint,
groupId,
profileKey,
options,
preview,
profileKey,
quote,
reaction,
sticker,
storyContext,
timestamp,
}: Readonly<{
attachments: ReadonlyArray<AttachmentType> | undefined;
contact?: Array<ContactWithHydratedAvatar>;
contentHint: number;
deletedForEveryoneTimestamp: number | undefined;
expireTimer: number | undefined;
groupId: string | undefined;
identifier: string;
messageText: string | undefined;
attachments: ReadonlyArray<AttachmentType> | undefined;
quote?: QuoteType;
preview?: ReadonlyArray<LinkPreviewType> | undefined;
sticker?: StickerType;
reaction?: ReactionType;
deletedForEveryoneTimestamp: number | undefined;
timestamp: number;
expireTimer: number | undefined;
contentHint: number;
groupId: string | undefined;
profileKey?: Uint8Array;
storyContext?: StoryContextType;
options?: SendOptionsType;
preview?: ReadonlyArray<LinkPreviewType> | undefined;
profileKey?: Uint8Array;
quote?: QuoteType;
reaction?: ReactionType;
sticker?: StickerType;
storyContext?: StoryContextType;
timestamp: number;
}>): Promise<CallbackResultType> {
return this.sendMessage({
messageOptions: {
recipients: [identifier],
body: messageText,
timestamp,
attachments,
quote,
preview,
sticker,
reaction,
body: messageText,
contact,
deletedForEveryoneTimestamp,
expireTimer,
preview,
profileKey,
quote,
reaction,
recipients: [identifier],
sticker,
storyContext,
timestamp,
},
contentHint,
groupId,

View File

@ -83,6 +83,51 @@ const DEFAULT_PHONE_TYPE = Proto.DataMessage.Contact.Phone.Type.HOME;
const DEFAULT_EMAIL_TYPE = Proto.DataMessage.Contact.Email.Type.HOME;
const DEFAULT_ADDRESS_TYPE = Proto.DataMessage.Contact.PostalAddress.Type.HOME;
export function numberToPhoneType(
type: number
): Proto.DataMessage.Contact.Phone.Type {
if (type === Proto.DataMessage.Contact.Phone.Type.MOBILE) {
return type;
}
if (type === Proto.DataMessage.Contact.Phone.Type.WORK) {
return type;
}
if (type === Proto.DataMessage.Contact.Phone.Type.CUSTOM) {
return type;
}
return DEFAULT_PHONE_TYPE;
}
export function numberToEmailType(
type: number
): Proto.DataMessage.Contact.Email.Type {
if (type === Proto.DataMessage.Contact.Email.Type.MOBILE) {
return type;
}
if (type === Proto.DataMessage.Contact.Email.Type.WORK) {
return type;
}
if (type === Proto.DataMessage.Contact.Email.Type.CUSTOM) {
return type;
}
return DEFAULT_EMAIL_TYPE;
}
export function numberToAddressType(
type: number
): Proto.DataMessage.Contact.PostalAddress.Type {
if (type === Proto.DataMessage.Contact.PostalAddress.Type.WORK) {
return type;
}
if (type === Proto.DataMessage.Contact.PostalAddress.Type.CUSTOM) {
return type;
}
return DEFAULT_ADDRESS_TYPE;
}
export function embeddedContactSelector(
contact: EmbeddedContactType,
options: {

View File

@ -136,6 +136,7 @@ const {
getAbsoluteAttachmentPath,
getAbsoluteTempPath,
loadAttachmentData,
loadContactData,
loadPreviewData,
loadStickerData,
openFileInFolder,
@ -1284,34 +1285,37 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
}
const attachments = getAttachmentsForMessage(message.attributes);
const doForwardMessage = async (
conversationIds: Array<string>,
messageBody?: string,
includedAttachments?: Array<AttachmentType>,
linkPreview?: LinkPreviewType
) => {
try {
const didForwardSuccessfully = await this.maybeForwardMessage(
message,
conversationIds,
messageBody,
includedAttachments,
linkPreview
);
if (didForwardSuccessfully && this.forwardMessageModal) {
this.forwardMessageModal.remove();
this.forwardMessageModal = undefined;
}
} catch (err) {
log.warn('doForwardMessage', err && err.stack ? err.stack : err);
}
};
this.forwardMessageModal = new Whisper.ReactWrapperView({
JSX: window.Signal.State.Roots.createForwardMessageModal(
window.reduxStore,
{
attachments,
doForwardMessage: async (
conversationIds: Array<string>,
messageBody?: string,
includedAttachments?: Array<AttachmentType>,
linkPreview?: LinkPreviewType
) => {
try {
const didForwardSuccessfully = await this.maybeForwardMessage(
message,
conversationIds,
messageBody,
includedAttachments,
linkPreview
);
if (didForwardSuccessfully && this.forwardMessageModal) {
this.forwardMessageModal.remove();
this.forwardMessageModal = undefined;
}
} catch (err) {
log.warn('doForwardMessage', err && err.stack ? err.stack : err);
}
},
doForwardMessage,
hasContact: Boolean(message.get('contact')),
isSticker: Boolean(message.get('sticker')),
messageBody: message.getRawText(),
onClose: () => {
@ -1433,6 +1437,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
const timestamp = baseTimestamp + offset;
if (conversation) {
const sticker = message.get('sticker');
const contact = message.get('contact');
if (sticker) {
const stickerWithData = await loadStickerData(sticker);
const stickerNoPath = stickerWithData
@ -1446,12 +1452,21 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
: undefined;
conversation.enqueueMessageForSend(
undefined, // body
[],
undefined, // quote
[],
stickerNoPath,
undefined, // BodyRanges
{
body: undefined,
attachments: [],
sticker: stickerNoPath,
},
{ ...sendMessageOptions, timestamp }
);
} else if (contact) {
const contactWithHydratedAvatar = await loadContactData(contact);
conversation.enqueueMessageForSend(
{
body: undefined,
attachments: [],
contact: contactWithHydratedAvatar,
},
{ ...sendMessageOptions, timestamp }
);
} else {
@ -1472,12 +1487,11 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
);
conversation.enqueueMessageForSend(
messageBody || undefined,
attachmentsToSend,
undefined, // quote
preview,
undefined, // sticker
undefined, // BodyRanges
{
body: messageBody || undefined,
attachments: attachmentsToSend,
preview,
},
{ ...sendMessageOptions, timestamp }
);
}
@ -2885,12 +2899,13 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
log.info('Send pre-checks took', sendDelta, 'milliseconds');
model.enqueueMessageForSend(
message,
attachments,
this.quote,
this.getLinkPreviewForSend(message),
undefined, // sticker
mentions,
{
body: message,
attachments,
quote: this.quote,
preview: this.getLinkPreviewForSend(message),
mentions,
},
{
sendHQImages,
timestamp,

25
ts/window.d.ts vendored
View File

@ -3,20 +3,17 @@
// Captures the globals put in place by preload.js, background.js and others
import { DeepPartial, Store } from 'redux';
import { Store } from 'redux';
import * as Backbone from 'backbone';
import * as Underscore from 'underscore';
import moment from 'moment';
import PQueue from 'p-queue/dist';
import { Ref } from 'react';
import { imageToBlurHash } from './util/imageToBlurHash';
import type { ParsedUrlQuery } from 'querystring';
import * as Util from './util';
import {
ConversationModelCollectionType,
MessageModelCollectionType,
MessageAttributesType,
ReactionAttributesType,
} from './model-types.d';
import { TextSecureType } from './textsecure.d';
import { Storage } from './textsecure/Storage';
@ -32,11 +29,8 @@ import * as Curve from './Curve';
import * as RemoteConfig from './RemoteConfig';
import * as OS from './OS';
import { getEnvironment } from './environment';
import * as zkgroup from './util/zkgroup';
import { LocalizerType, BodyRangesType, BodyRangeType } from './types/Util';
import * as EmbeddedContact from './types/EmbeddedContact';
import { LocalizerType } from './types/Util';
import type { Receipt } from './types/Receipt';
import * as Errors from './types/errors';
import { ConversationController } from './ConversationController';
import { ReduxActions } from './state/types';
import { createStore } from './state/createStore';
@ -71,13 +65,11 @@ import * as stickersDuck from './state/ducks/stickers';
import * as conversationsSelectors from './state/selectors/conversations';
import * as searchSelectors from './state/selectors/search';
import AccountManager from './textsecure/AccountManager';
import { SendOptionsType } from './textsecure/SendMessage';
import { ContactWithHydratedAvatar } from './textsecure/SendMessage';
import Data from './sql/Client';
import { UserMessage } from './types/Message';
import { PhoneNumberFormat } from 'google-libphonenumber';
import { MessageModel } from './models/messages';
import { ConversationModel } from './models/conversations';
import { combineNames } from './util';
import { BatcherType } from './util/batcher';
import { AttachmentList } from './components/conversation/AttachmentList';
import { ChatColorPicker } from './components/ChatColorPicker';
@ -93,14 +85,12 @@ import { Quote } from './components/conversation/Quote';
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
import { WhatsNewLink } from './components/WhatsNewLink';
import { MIMEType } from './types/MIME';
import { DownloadedAttachmentType } from './types/Attachment';
import { ElectronLocaleType } from './util/mapToSupportLocale';
import { SignalProtocolStore } from './SignalProtocolStore';
import { StartupQueue } from './util/StartupQueue';
import { SocketStatus } from './types/SocketStatus';
import SyncRequest from './textsecure/SyncRequest';
import { ConversationColorType, CustomColorType } from './types/Colors';
import { MessageController } from './util/MessageController';
import { StateType } from './state/reducer';
import { SystemTraySetting } from './types/SystemTraySetting';
@ -108,15 +98,13 @@ import { UUID } from './types/UUID';
import { Address } from './types/Address';
import { QualifiedAddress } from './types/QualifiedAddress';
import { CI } from './CI';
import { IPCEventsType, IPCEventsValuesType } from './util/createIPCEvents';
import { IPCEventsType } from './util/createIPCEvents';
import { ConversationView } from './views/conversation_view';
import type { SignalContextType } from './windows/context';
import { GroupV2Change } from './components/conversation/GroupV2Change';
import type { EmbeddedContactType } from './types/EmbeddedContact';
export { Long } from 'long';
type TaskResultType = any;
export type WhatIsThis = any;
// Synced with the type in ts/shims/showConfirmationDialog
@ -311,6 +299,9 @@ declare global {
}
>;
loadQuoteData: (quote: unknown) => WhatIsThis;
loadContactData: (
contact?: Array<EmbeddedContactType>
) => Promise<Array<ContactWithHydratedAvatar> | undefined>;
loadPreviewData: (preview: unknown) => WhatIsThis;
loadStickerData: (sticker: unknown) => WhatIsThis;
readStickerData: (path: string) => Promise<Uint8Array>;