Disable forward for messages with embedded contact
This commit is contained in:
parent
6d816d01ad
commit
7f89f6162f
|
@ -188,6 +188,7 @@ function initializeMigrations({
|
||||||
const attachmentsPath = getPath(userDataPath);
|
const attachmentsPath = getPath(userDataPath);
|
||||||
const readAttachmentData = createReader(attachmentsPath);
|
const readAttachmentData = createReader(attachmentsPath);
|
||||||
const loadAttachmentData = Type.loadData(readAttachmentData);
|
const loadAttachmentData = Type.loadData(readAttachmentData);
|
||||||
|
const loadContactData = MessageType.loadContactData(loadAttachmentData);
|
||||||
const loadPreviewData = MessageType.loadPreviewData(loadAttachmentData);
|
const loadPreviewData = MessageType.loadPreviewData(loadAttachmentData);
|
||||||
const loadQuoteData = MessageType.loadQuoteData(loadAttachmentData);
|
const loadQuoteData = MessageType.loadQuoteData(loadAttachmentData);
|
||||||
const loadStickerData = MessageType.loadStickerData(loadAttachmentData);
|
const loadStickerData = MessageType.loadStickerData(loadAttachmentData);
|
||||||
|
@ -248,6 +249,7 @@ function initializeMigrations({
|
||||||
getAbsoluteStickerPath,
|
getAbsoluteStickerPath,
|
||||||
getAbsoluteTempPath,
|
getAbsoluteTempPath,
|
||||||
loadAttachmentData,
|
loadAttachmentData,
|
||||||
|
loadContactData,
|
||||||
loadMessage: MessageType.createAttachmentLoader(loadAttachmentData),
|
loadMessage: MessageType.createAttachmentLoader(loadAttachmentData),
|
||||||
loadPreviewData,
|
loadPreviewData,
|
||||||
loadQuoteData,
|
loadQuoteData,
|
||||||
|
|
|
@ -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 => {
|
exports.loadPreviewData = loadAttachmentData => {
|
||||||
if (!isFunction(loadAttachmentData)) {
|
if (!isFunction(loadAttachmentData)) {
|
||||||
throw new TypeError('loadPreviewData: loadAttachmentData is required');
|
throw new TypeError('loadPreviewData: loadAttachmentData is required');
|
||||||
|
|
|
@ -48,6 +48,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
doForwardMessage: action('doForwardMessage'),
|
doForwardMessage: action('doForwardMessage'),
|
||||||
getPreferredBadge: () => undefined,
|
getPreferredBadge: () => undefined,
|
||||||
i18n,
|
i18n,
|
||||||
|
hasContact: Boolean(overrideProps.hasContact),
|
||||||
isSticker: Boolean(overrideProps.isSticker),
|
isSticker: Boolean(overrideProps.isSticker),
|
||||||
linkPreview: overrideProps.linkPreview,
|
linkPreview: overrideProps.linkPreview,
|
||||||
messageBody: text('messageBody', overrideProps.messageBody || ''),
|
messageBody: text('messageBody', overrideProps.messageBody || ''),
|
||||||
|
@ -75,6 +76,10 @@ story.add('a sticker', () => {
|
||||||
return <ForwardMessageModal {...useProps({ isSticker: true })} />;
|
return <ForwardMessageModal {...useProps({ isSticker: true })} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
story.add('with a contact', () => {
|
||||||
|
return <ForwardMessageModal {...useProps({ hasContact: true })} />;
|
||||||
|
});
|
||||||
|
|
||||||
story.add('link preview', () => {
|
story.add('link preview', () => {
|
||||||
return (
|
return (
|
||||||
<ForwardMessageModal
|
<ForwardMessageModal
|
||||||
|
|
|
@ -48,6 +48,7 @@ export type DataPropsType = {
|
||||||
linkPreview?: LinkPreviewType
|
linkPreview?: LinkPreviewType
|
||||||
) => void;
|
) => void;
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
getPreferredBadge: PreferredBadgeSelectorType;
|
||||||
|
hasContact: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isSticker: boolean;
|
isSticker: boolean;
|
||||||
linkPreview?: LinkPreviewType;
|
linkPreview?: LinkPreviewType;
|
||||||
|
@ -79,6 +80,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
candidateConversations,
|
candidateConversations,
|
||||||
doForwardMessage,
|
doForwardMessage,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
|
hasContact,
|
||||||
i18n,
|
i18n,
|
||||||
isSticker,
|
isSticker,
|
||||||
linkPreview,
|
linkPreview,
|
||||||
|
@ -110,7 +112,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
const [messageBodyText, setMessageBodyText] = useState(messageBody || '');
|
const [messageBodyText, setMessageBodyText] = useState(messageBody || '');
|
||||||
const [cannotMessage, setCannotMessage] = useState(false);
|
const [cannotMessage, setCannotMessage] = useState(false);
|
||||||
|
|
||||||
const isMessageEditable = !isSticker;
|
const isMessageEditable = !isSticker && !hasContact;
|
||||||
|
|
||||||
const hasSelectedMaximumNumberOfContacts =
|
const hasSelectedMaximumNumberOfContacts =
|
||||||
selectedContacts.length >= MAX_FORWARD;
|
selectedContacts.length >= MAX_FORWARD;
|
||||||
|
@ -142,6 +144,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
hasContactsSelected &&
|
hasContactsSelected &&
|
||||||
(Boolean(messageBodyText) ||
|
(Boolean(messageBodyText) ||
|
||||||
isSticker ||
|
isSticker ||
|
||||||
|
hasContact ||
|
||||||
(attachmentsToForward && attachmentsToForward.length));
|
(attachmentsToForward && attachmentsToForward.length));
|
||||||
|
|
||||||
const forwardMessage = React.useCallback(() => {
|
const forwardMessage = React.useCallback(() => {
|
||||||
|
|
|
@ -1710,6 +1710,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
canDownload,
|
canDownload,
|
||||||
|
contact,
|
||||||
canReact,
|
canReact,
|
||||||
canReply,
|
canReply,
|
||||||
canRetry,
|
canRetry,
|
||||||
|
@ -1729,7 +1730,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
text,
|
text,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const canForward = !isTapToView && !deletedForEveryone;
|
const canForward = !isTapToView && !deletedForEveryone && !contact;
|
||||||
const multipleAttachments = attachments && attachments.length > 1;
|
const multipleAttachments = attachments && attachments.length > 1;
|
||||||
|
|
||||||
const shouldShowAdditional =
|
const shouldShowAdditional =
|
||||||
|
|
|
@ -14,7 +14,10 @@ import { handleMessageSend } from '../../util/handleMessageSend';
|
||||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||||
import { isSent } from '../../messages/MessageSendState';
|
import { isSent } from '../../messages/MessageSendState';
|
||||||
import { isOutgoing } from '../../state/selectors/message';
|
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 { 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 { WhatIsThis } from '../../window.d';
|
||||||
|
@ -131,6 +134,7 @@ export async function sendNormalMessage(
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
body,
|
body,
|
||||||
|
contact,
|
||||||
deletedForEveryoneTimestamp,
|
deletedForEveryoneTimestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
mentions,
|
mentions,
|
||||||
|
@ -148,11 +152,12 @@ export async function sendNormalMessage(
|
||||||
const dataMessage = await window.textsecure.messaging.getDataMessage({
|
const dataMessage = await window.textsecure.messaging.getDataMessage({
|
||||||
attachments,
|
attachments,
|
||||||
body,
|
body,
|
||||||
|
contact,
|
||||||
|
deletedForEveryoneTimestamp,
|
||||||
|
expireTimer,
|
||||||
groupV2: conversation.getGroupV2Info({
|
groupV2: conversation.getGroupV2Info({
|
||||||
members: recipientIdentifiersWithoutMe,
|
members: recipientIdentifiersWithoutMe,
|
||||||
}),
|
}),
|
||||||
deletedForEveryoneTimestamp,
|
|
||||||
expireTimer,
|
|
||||||
preview,
|
preview,
|
||||||
profileKey,
|
profileKey,
|
||||||
quote,
|
quote,
|
||||||
|
@ -188,6 +193,7 @@ export async function sendNormalMessage(
|
||||||
contentHint: ContentHint.RESENDABLE,
|
contentHint: ContentHint.RESENDABLE,
|
||||||
groupSendOptions: {
|
groupSendOptions: {
|
||||||
attachments,
|
attachments,
|
||||||
|
contact,
|
||||||
deletedForEveryoneTimestamp,
|
deletedForEveryoneTimestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
groupV1: conversation.getGroupV1Info(
|
groupV1: conversation.getGroupV1Info(
|
||||||
|
@ -237,21 +243,22 @@ export async function sendNormalMessage(
|
||||||
|
|
||||||
log.info('sending direct message');
|
log.info('sending direct message');
|
||||||
innerPromise = window.textsecure.messaging.sendMessageToIdentifier({
|
innerPromise = window.textsecure.messaging.sendMessageToIdentifier({
|
||||||
|
attachments,
|
||||||
|
contact,
|
||||||
|
contentHint: ContentHint.RESENDABLE,
|
||||||
|
deletedForEveryoneTimestamp,
|
||||||
|
expireTimer,
|
||||||
|
groupId: undefined,
|
||||||
identifier: recipientIdentifiersWithoutMe[0],
|
identifier: recipientIdentifiersWithoutMe[0],
|
||||||
messageText: body,
|
messageText: body,
|
||||||
attachments,
|
|
||||||
quote,
|
|
||||||
preview,
|
|
||||||
sticker,
|
|
||||||
reaction: undefined,
|
|
||||||
deletedForEveryoneTimestamp,
|
|
||||||
timestamp: messageTimestamp,
|
|
||||||
expireTimer,
|
|
||||||
contentHint: ContentHint.RESENDABLE,
|
|
||||||
groupId: undefined,
|
|
||||||
profileKey,
|
|
||||||
options: sendOptions,
|
options: sendOptions,
|
||||||
|
preview,
|
||||||
|
profileKey,
|
||||||
|
quote,
|
||||||
|
reaction: undefined,
|
||||||
|
sticker,
|
||||||
storyContext,
|
storyContext,
|
||||||
|
timestamp: messageTimestamp,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,6 +387,7 @@ async function getMessageSendData({
|
||||||
}>): Promise<{
|
}>): Promise<{
|
||||||
attachments: Array<AttachmentType>;
|
attachments: Array<AttachmentType>;
|
||||||
body: undefined | string;
|
body: undefined | string;
|
||||||
|
contact?: Array<ContactWithHydratedAvatar>;
|
||||||
deletedForEveryoneTimestamp: undefined | number;
|
deletedForEveryoneTimestamp: undefined | number;
|
||||||
expireTimer: undefined | number;
|
expireTimer: undefined | number;
|
||||||
mentions: undefined | BodyRangesType;
|
mentions: undefined | BodyRangesType;
|
||||||
|
@ -391,6 +399,7 @@ async function getMessageSendData({
|
||||||
}> {
|
}> {
|
||||||
const {
|
const {
|
||||||
loadAttachmentData,
|
loadAttachmentData,
|
||||||
|
loadContactData,
|
||||||
loadPreviewData,
|
loadPreviewData,
|
||||||
loadQuoteData,
|
loadQuoteData,
|
||||||
loadStickerData,
|
loadStickerData,
|
||||||
|
@ -413,13 +422,15 @@ async function getMessageSendData({
|
||||||
|
|
||||||
const storyId = message.get('storyId');
|
const storyId = message.get('storyId');
|
||||||
|
|
||||||
const [attachmentsWithData, preview, quote, sticker, storyMessage] =
|
const [attachmentsWithData, contact, preview, quote, sticker, storyMessage] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// We don't update the caches here because (1) we expect the caches to be populated
|
// 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
|
// 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
|
// 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.
|
// sends are failing, let's not add the complication of a cache.
|
||||||
Promise.all((message.get('attachments') ?? []).map(loadAttachmentData)),
|
Promise.all((message.get('attachments') ?? []).map(loadAttachmentData)),
|
||||||
|
message.cachedOutgoingContactData ||
|
||||||
|
loadContactData(message.get('contact')),
|
||||||
message.cachedOutgoingPreviewData ||
|
message.cachedOutgoingPreviewData ||
|
||||||
loadPreviewData(message.get('preview')),
|
loadPreviewData(message.get('preview')),
|
||||||
message.cachedOutgoingQuoteData || loadQuoteData(message.get('quote')),
|
message.cachedOutgoingQuoteData || loadQuoteData(message.get('quote')),
|
||||||
|
@ -439,6 +450,7 @@ async function getMessageSendData({
|
||||||
return {
|
return {
|
||||||
attachments,
|
attachments,
|
||||||
body,
|
body,
|
||||||
|
contact,
|
||||||
deletedForEveryoneTimestamp: message.get('deletedForEveryoneTimestamp'),
|
deletedForEveryoneTimestamp: message.get('deletedForEveryoneTimestamp'),
|
||||||
expireTimer: message.get('expireTimer'),
|
expireTimer: message.get('expireTimer'),
|
||||||
mentions: message.get('bodyRanges'),
|
mentions: message.get('bodyRanges'),
|
||||||
|
|
|
@ -29,6 +29,7 @@ import * as Conversation from '../types/Conversation';
|
||||||
import * as Stickers from '../types/Stickers';
|
import * as Stickers from '../types/Stickers';
|
||||||
import { CapabilityError } from '../types/errors';
|
import { CapabilityError } from '../types/errors';
|
||||||
import type {
|
import type {
|
||||||
|
ContactWithHydratedAvatar,
|
||||||
GroupV1InfoType,
|
GroupV1InfoType,
|
||||||
GroupV2InfoType,
|
GroupV2InfoType,
|
||||||
StickerType,
|
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);
|
window.reduxActions.stickers.useSticker(packId, stickerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3925,12 +3930,23 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
async enqueueMessageForSend(
|
async enqueueMessageForSend(
|
||||||
body: string | undefined,
|
{
|
||||||
attachments: Array<AttachmentType>,
|
attachments,
|
||||||
quote?: QuotedMessageType,
|
body,
|
||||||
preview?: Array<LinkPreviewType>,
|
contact,
|
||||||
sticker?: StickerType,
|
mentions,
|
||||||
mentions?: BodyRangesType,
|
preview,
|
||||||
|
quote,
|
||||||
|
sticker,
|
||||||
|
}: {
|
||||||
|
attachments: Array<AttachmentType>;
|
||||||
|
body: string | undefined;
|
||||||
|
contact?: Array<ContactWithHydratedAvatar>;
|
||||||
|
mentions?: BodyRangesType;
|
||||||
|
preview?: Array<LinkPreviewType>;
|
||||||
|
quote?: QuotedMessageType;
|
||||||
|
sticker?: StickerType;
|
||||||
|
},
|
||||||
{
|
{
|
||||||
dontClearDraft,
|
dontClearDraft,
|
||||||
sendHQImages,
|
sendHQImages,
|
||||||
|
@ -4000,6 +4016,7 @@ export class ConversationModel extends window.Backbone
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
body,
|
body,
|
||||||
conversationId: this.id,
|
conversationId: this.id,
|
||||||
|
contact,
|
||||||
quote,
|
quote,
|
||||||
preview,
|
preview,
|
||||||
attachments: attachmentsToSend,
|
attachments: attachmentsToSend,
|
||||||
|
@ -4031,6 +4048,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
const model = new window.Whisper.Message(attributes);
|
const model = new window.Whisper.Message(attributes);
|
||||||
const message = window.MessageController.register(model.id, model);
|
const message = window.MessageController.register(model.id, model);
|
||||||
|
message.cachedOutgoingContactData = contact;
|
||||||
message.cachedOutgoingPreviewData = preview;
|
message.cachedOutgoingPreviewData = preview;
|
||||||
message.cachedOutgoingQuoteData = quote;
|
message.cachedOutgoingQuoteData = quote;
|
||||||
message.cachedOutgoingStickerData = sticker;
|
message.cachedOutgoingStickerData = sticker;
|
||||||
|
|
|
@ -151,6 +151,7 @@ import type { ConversationQueueJobData } from '../jobs/conversationJobQueue';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
import { getMessageById } from '../messages/getMessageById';
|
||||||
import { shouldDownloadStory } from '../util/shouldDownloadStory';
|
import { shouldDownloadStory } from '../util/shouldDownloadStory';
|
||||||
import { shouldShowStoriesView } from '../state/selectors/stories';
|
import { shouldShowStoriesView } from '../state/selectors/stories';
|
||||||
|
import type { ContactWithHydratedAvatar } from '../textsecure/SendMessage';
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
|
@ -188,6 +189,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
syncPromise?: Promise<CallbackResultType | void>;
|
syncPromise?: Promise<CallbackResultType | void>;
|
||||||
|
|
||||||
|
cachedOutgoingContactData?: Array<ContactWithHydratedAvatar>;
|
||||||
|
|
||||||
cachedOutgoingPreviewData?: Array<LinkPreviewType>;
|
cachedOutgoingPreviewData?: Array<LinkPreviewType>;
|
||||||
|
|
||||||
cachedOutgoingQuoteData?: WhatIsThis;
|
cachedOutgoingQuoteData?: WhatIsThis;
|
||||||
|
|
|
@ -230,12 +230,11 @@ function replyToStory(
|
||||||
|
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
conversation.enqueueMessageForSend(
|
conversation.enqueueMessageForSend(
|
||||||
messageBody,
|
{
|
||||||
[],
|
body: messageBody,
|
||||||
undefined,
|
attachments: [],
|
||||||
undefined,
|
mentions,
|
||||||
undefined,
|
},
|
||||||
mentions,
|
|
||||||
{
|
{
|
||||||
storyId: story.messageId,
|
storyId: story.messageId,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|
|
@ -24,6 +24,7 @@ export type SmartForwardMessageModalProps = {
|
||||||
attachments?: Array<AttachmentType>,
|
attachments?: Array<AttachmentType>,
|
||||||
linkPreview?: LinkPreviewType
|
linkPreview?: LinkPreviewType
|
||||||
) => void;
|
) => void;
|
||||||
|
hasContact: boolean;
|
||||||
isSticker: boolean;
|
isSticker: boolean;
|
||||||
messageBody?: string;
|
messageBody?: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
@ -42,6 +43,7 @@ const mapStateToProps = (
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
doForwardMessage,
|
doForwardMessage,
|
||||||
|
hasContact,
|
||||||
isSticker,
|
isSticker,
|
||||||
messageBody,
|
messageBody,
|
||||||
onClose,
|
onClose,
|
||||||
|
@ -59,6 +61,7 @@ const mapStateToProps = (
|
||||||
candidateConversations,
|
candidateConversations,
|
||||||
doForwardMessage,
|
doForwardMessage,
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||||
|
hasContact,
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
isSticker,
|
isSticker,
|
||||||
linkPreview,
|
linkPreview,
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
/* eslint-disable more/no-then */
|
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
|
@ -69,6 +68,12 @@ import type { SendTypesType } from '../util/handleMessageSend';
|
||||||
import { shouldSaveProto, sendTypesEnum } from '../util/handleMessageSend';
|
import { shouldSaveProto, sendTypesEnum } from '../util/handleMessageSend';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
|
import type { Avatar, EmbeddedContactType } from '../types/EmbeddedContact';
|
||||||
|
import {
|
||||||
|
numberToPhoneType,
|
||||||
|
numberToEmailType,
|
||||||
|
numberToAddressType,
|
||||||
|
} from '../types/EmbeddedContact';
|
||||||
|
|
||||||
export type SendMetadataType = {
|
export type SendMetadataType = {
|
||||||
[identifier: string]: {
|
[identifier: string]: {
|
||||||
|
@ -172,9 +177,16 @@ function makeAttachmentSendReady(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ContactWithHydratedAvatar = EmbeddedContactType & {
|
||||||
|
avatar?: Avatar & {
|
||||||
|
attachmentPointer?: Proto.IAttachmentPointer;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type MessageOptionsType = {
|
export type MessageOptionsType = {
|
||||||
attachments?: ReadonlyArray<AttachmentType> | null;
|
attachments?: ReadonlyArray<AttachmentType> | null;
|
||||||
body?: string;
|
body?: string;
|
||||||
|
contact?: Array<ContactWithHydratedAvatar>;
|
||||||
expireTimer?: number;
|
expireTimer?: number;
|
||||||
flags?: number;
|
flags?: number;
|
||||||
group?: {
|
group?: {
|
||||||
|
@ -197,21 +209,22 @@ export type MessageOptionsType = {
|
||||||
};
|
};
|
||||||
export type GroupSendOptionsType = {
|
export type GroupSendOptionsType = {
|
||||||
attachments?: Array<AttachmentType>;
|
attachments?: Array<AttachmentType>;
|
||||||
|
contact?: Array<ContactWithHydratedAvatar>;
|
||||||
|
deletedForEveryoneTimestamp?: number;
|
||||||
expireTimer?: number;
|
expireTimer?: number;
|
||||||
flags?: number;
|
flags?: number;
|
||||||
groupV2?: GroupV2InfoType;
|
groupCallUpdate?: GroupCallUpdateType;
|
||||||
groupV1?: GroupV1InfoType;
|
groupV1?: GroupV1InfoType;
|
||||||
|
groupV2?: GroupV2InfoType;
|
||||||
|
mentions?: BodyRangesType;
|
||||||
messageText?: string;
|
messageText?: string;
|
||||||
preview?: ReadonlyArray<LinkPreviewType>;
|
preview?: ReadonlyArray<LinkPreviewType>;
|
||||||
profileKey?: Uint8Array;
|
profileKey?: Uint8Array;
|
||||||
quote?: QuoteType;
|
quote?: QuoteType;
|
||||||
reaction?: ReactionType;
|
reaction?: ReactionType;
|
||||||
sticker?: StickerType;
|
sticker?: StickerType;
|
||||||
deletedForEveryoneTimestamp?: number;
|
|
||||||
timestamp: number;
|
|
||||||
mentions?: BodyRangesType;
|
|
||||||
groupCallUpdate?: GroupCallUpdateType;
|
|
||||||
storyContext?: StoryContextType;
|
storyContext?: StoryContextType;
|
||||||
|
timestamp: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
|
@ -219,6 +232,8 @@ class Message {
|
||||||
|
|
||||||
body?: string;
|
body?: string;
|
||||||
|
|
||||||
|
contact?: Array<ContactWithHydratedAvatar>;
|
||||||
|
|
||||||
expireTimer?: number;
|
expireTimer?: number;
|
||||||
|
|
||||||
flags?: number;
|
flags?: number;
|
||||||
|
@ -261,6 +276,7 @@ class Message {
|
||||||
constructor(options: MessageOptionsType) {
|
constructor(options: MessageOptionsType) {
|
||||||
this.attachments = options.attachments || [];
|
this.attachments = options.attachments || [];
|
||||||
this.body = options.body;
|
this.body = options.body;
|
||||||
|
this.contact = options.contact;
|
||||||
this.expireTimer = options.expireTimer;
|
this.expireTimer = options.expireTimer;
|
||||||
this.flags = options.flags;
|
this.flags = options.flags;
|
||||||
this.group = options.group;
|
this.group = options.group;
|
||||||
|
@ -403,6 +419,74 @@ class Message {
|
||||||
return item;
|
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) {
|
if (this.quote) {
|
||||||
const { QuotedAttachment } = Proto.DataMessage.Quote;
|
const { QuotedAttachment } = Proto.DataMessage.Quote;
|
||||||
const { BodyRange, Quote } = Proto.DataMessage;
|
const { BodyRange, Quote } = Proto.DataMessage;
|
||||||
|
@ -559,14 +643,17 @@ export default class MessageSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
async makeAttachmentPointer(
|
async makeAttachmentPointer(
|
||||||
attachment: Readonly<AttachmentType>
|
attachment: Readonly<
|
||||||
|
Partial<AttachmentType> &
|
||||||
|
Pick<AttachmentType, 'data' | 'size' | 'contentType'>
|
||||||
|
>
|
||||||
): Promise<Proto.IAttachmentPointer> {
|
): Promise<Proto.IAttachmentPointer> {
|
||||||
assert(
|
assert(
|
||||||
typeof attachment === 'object' && attachment !== null,
|
typeof attachment === 'object' && attachment !== null,
|
||||||
'Got null attachment in `makeAttachmentPointer`'
|
'Got null attachment in `makeAttachmentPointer`'
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, size } = attachment;
|
const { data, size, contentType } = attachment;
|
||||||
if (!(data instanceof Uint8Array)) {
|
if (!(data instanceof Uint8Array)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`makeAttachmentPointer: data was a '${typeof data}' instead of Uint8Array`
|
`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}`
|
`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 padded = this.getPaddedAttachment(data);
|
||||||
const key = getRandomBytes(64);
|
const key = getRandomBytes(64);
|
||||||
|
@ -589,7 +681,7 @@ export default class MessageSender {
|
||||||
proto.cdnId = Long.fromString(id);
|
proto.cdnId = Long.fromString(id);
|
||||||
proto.contentType = attachment.contentType;
|
proto.contentType = attachment.contentType;
|
||||||
proto.key = key;
|
proto.key = key;
|
||||||
proto.size = attachment.size;
|
proto.size = data.byteLength;
|
||||||
proto.digest = result.digest;
|
proto.digest = result.digest;
|
||||||
|
|
||||||
if (attachment.fileName) {
|
if (attachment.fileName) {
|
||||||
|
@ -615,22 +707,20 @@ export default class MessageSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadAttachments(message: Message): Promise<void> {
|
async uploadAttachments(message: Message): Promise<void> {
|
||||||
await Promise.all(
|
try {
|
||||||
message.attachments.map(attachment =>
|
// eslint-disable-next-line no-param-reassign
|
||||||
this.makeAttachmentPointer(attachment)
|
message.attachmentPointers = 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) {
|
||||||
.catch(error => {
|
throw new MessageError(message, error);
|
||||||
if (error instanceof HTTPError) {
|
} else {
|
||||||
throw new MessageError(message, error);
|
throw error;
|
||||||
} else {
|
}
|
||||||
throw error;
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadLinkPreviews(message: Message): Promise<void> {
|
async uploadLinkPreviews(message: Message): Promise<void> {
|
||||||
|
@ -687,32 +777,68 @@ export default class MessageSender {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadThumbnails(message: Message): Promise<void> {
|
async uploadContactAvatar(message: Message): Promise<void> {
|
||||||
const makePointer = this.makeAttachmentPointer.bind(this);
|
const { contact } = message;
|
||||||
const { quote } = message;
|
if (!contact || contact.length === 0) {
|
||||||
|
|
||||||
if (!quote || !quote.attachments || quote.attachments.length === 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(
|
try {
|
||||||
quote.attachments.map((attachment: QuoteAttachmentType) => {
|
await Promise.all(
|
||||||
if (!attachment.thumbnail) {
|
contact.map(async (item: ContactWithHydratedAvatar) => {
|
||||||
return null;
|
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
|
// eslint-disable-next-line no-param-reassign
|
||||||
attachment.attachmentPointer = pointer;
|
itemAvatar.attachmentPointer = await this.makeAttachmentPointer(
|
||||||
});
|
attachment
|
||||||
})
|
);
|
||||||
).catch(error => {
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
if (error instanceof HTTPError) {
|
if (error instanceof HTTPError) {
|
||||||
throw new MessageError(message, error);
|
throw new MessageError(message, error);
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
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
|
// Proto assembly
|
||||||
|
@ -742,6 +868,7 @@ export default class MessageSender {
|
||||||
const message = new Message(attributes);
|
const message = new Message(attributes);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.uploadAttachments(message),
|
this.uploadAttachments(message),
|
||||||
|
this.uploadContactAvatar(message),
|
||||||
this.uploadThumbnails(message),
|
this.uploadThumbnails(message),
|
||||||
this.uploadLinkPreviews(message),
|
this.uploadLinkPreviews(message),
|
||||||
this.uploadSticker(message),
|
this.uploadSticker(message),
|
||||||
|
@ -789,6 +916,7 @@ export default class MessageSender {
|
||||||
): MessageOptionsType {
|
): MessageOptionsType {
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
|
contact,
|
||||||
deletedForEveryoneTimestamp,
|
deletedForEveryoneTimestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
flags,
|
flags,
|
||||||
|
@ -839,6 +967,7 @@ export default class MessageSender {
|
||||||
return {
|
return {
|
||||||
attachments,
|
attachments,
|
||||||
body: messageText,
|
body: messageText,
|
||||||
|
contact,
|
||||||
deletedForEveryoneTimestamp,
|
deletedForEveryoneTimestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
flags,
|
flags,
|
||||||
|
@ -883,33 +1012,25 @@ export default class MessageSender {
|
||||||
groupId: string | undefined;
|
groupId: string | undefined;
|
||||||
options?: SendOptionsType;
|
options?: SendOptionsType;
|
||||||
}>): Promise<CallbackResultType> {
|
}>): Promise<CallbackResultType> {
|
||||||
const message = new Message(messageOptions);
|
const message = await this.getHydratedMessage(messageOptions);
|
||||||
|
|
||||||
return Promise.all([
|
return new Promise((resolve, reject) => {
|
||||||
this.uploadAttachments(message),
|
this.sendMessageProto({
|
||||||
this.uploadThumbnails(message),
|
callback: (res: CallbackResultType) => {
|
||||||
this.uploadLinkPreviews(message),
|
if (res.errors && res.errors.length > 0) {
|
||||||
this.uploadSticker(message),
|
reject(new SendMessageProtoError(res));
|
||||||
]).then(
|
} else {
|
||||||
async (): Promise<CallbackResultType> =>
|
resolve(res);
|
||||||
new Promise((resolve, reject) => {
|
}
|
||||||
this.sendMessageProto({
|
},
|
||||||
callback: (res: CallbackResultType) => {
|
contentHint,
|
||||||
if (res.errors && res.errors.length > 0) {
|
groupId,
|
||||||
reject(new SendMessageProtoError(res));
|
options,
|
||||||
} else {
|
proto: message.toProto(),
|
||||||
resolve(res);
|
recipients: message.recipients || [],
|
||||||
}
|
timestamp: message.timestamp,
|
||||||
},
|
});
|
||||||
contentHint,
|
});
|
||||||
groupId,
|
|
||||||
options,
|
|
||||||
proto: message.toProto(),
|
|
||||||
recipients: message.recipients || [],
|
|
||||||
timestamp: message.timestamp,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessageProto({
|
sendMessageProto({
|
||||||
|
@ -1033,52 +1154,55 @@ export default class MessageSender {
|
||||||
// You might wonder why this takes a groupId. models/messages.resend() can send a group
|
// You might wonder why this takes a groupId. models/messages.resend() can send a group
|
||||||
// message to just one person.
|
// message to just one person.
|
||||||
async sendMessageToIdentifier({
|
async sendMessageToIdentifier({
|
||||||
|
attachments,
|
||||||
|
contact,
|
||||||
|
contentHint,
|
||||||
|
deletedForEveryoneTimestamp,
|
||||||
|
expireTimer,
|
||||||
|
groupId,
|
||||||
identifier,
|
identifier,
|
||||||
messageText,
|
messageText,
|
||||||
attachments,
|
|
||||||
quote,
|
|
||||||
preview,
|
|
||||||
sticker,
|
|
||||||
reaction,
|
|
||||||
deletedForEveryoneTimestamp,
|
|
||||||
timestamp,
|
|
||||||
expireTimer,
|
|
||||||
contentHint,
|
|
||||||
groupId,
|
|
||||||
profileKey,
|
|
||||||
options,
|
options,
|
||||||
|
preview,
|
||||||
|
profileKey,
|
||||||
|
quote,
|
||||||
|
reaction,
|
||||||
|
sticker,
|
||||||
storyContext,
|
storyContext,
|
||||||
|
timestamp,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
|
attachments: ReadonlyArray<AttachmentType> | undefined;
|
||||||
|
contact?: Array<ContactWithHydratedAvatar>;
|
||||||
|
contentHint: number;
|
||||||
|
deletedForEveryoneTimestamp: number | undefined;
|
||||||
|
expireTimer: number | undefined;
|
||||||
|
groupId: string | undefined;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
messageText: string | undefined;
|
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;
|
options?: SendOptionsType;
|
||||||
|
preview?: ReadonlyArray<LinkPreviewType> | undefined;
|
||||||
|
profileKey?: Uint8Array;
|
||||||
|
quote?: QuoteType;
|
||||||
|
reaction?: ReactionType;
|
||||||
|
sticker?: StickerType;
|
||||||
|
storyContext?: StoryContextType;
|
||||||
|
timestamp: number;
|
||||||
}>): Promise<CallbackResultType> {
|
}>): Promise<CallbackResultType> {
|
||||||
return this.sendMessage({
|
return this.sendMessage({
|
||||||
messageOptions: {
|
messageOptions: {
|
||||||
recipients: [identifier],
|
|
||||||
body: messageText,
|
|
||||||
timestamp,
|
|
||||||
attachments,
|
attachments,
|
||||||
quote,
|
body: messageText,
|
||||||
preview,
|
contact,
|
||||||
sticker,
|
|
||||||
reaction,
|
|
||||||
deletedForEveryoneTimestamp,
|
deletedForEveryoneTimestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
|
preview,
|
||||||
profileKey,
|
profileKey,
|
||||||
|
quote,
|
||||||
|
reaction,
|
||||||
|
recipients: [identifier],
|
||||||
|
sticker,
|
||||||
storyContext,
|
storyContext,
|
||||||
|
timestamp,
|
||||||
},
|
},
|
||||||
contentHint,
|
contentHint,
|
||||||
groupId,
|
groupId,
|
||||||
|
|
|
@ -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_EMAIL_TYPE = Proto.DataMessage.Contact.Email.Type.HOME;
|
||||||
const DEFAULT_ADDRESS_TYPE = Proto.DataMessage.Contact.PostalAddress.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(
|
export function embeddedContactSelector(
|
||||||
contact: EmbeddedContactType,
|
contact: EmbeddedContactType,
|
||||||
options: {
|
options: {
|
||||||
|
|
|
@ -136,6 +136,7 @@ const {
|
||||||
getAbsoluteAttachmentPath,
|
getAbsoluteAttachmentPath,
|
||||||
getAbsoluteTempPath,
|
getAbsoluteTempPath,
|
||||||
loadAttachmentData,
|
loadAttachmentData,
|
||||||
|
loadContactData,
|
||||||
loadPreviewData,
|
loadPreviewData,
|
||||||
loadStickerData,
|
loadStickerData,
|
||||||
openFileInFolder,
|
openFileInFolder,
|
||||||
|
@ -1284,34 +1285,37 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
}
|
}
|
||||||
const attachments = getAttachmentsForMessage(message.attributes);
|
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({
|
this.forwardMessageModal = new Whisper.ReactWrapperView({
|
||||||
JSX: window.Signal.State.Roots.createForwardMessageModal(
|
JSX: window.Signal.State.Roots.createForwardMessageModal(
|
||||||
window.reduxStore,
|
window.reduxStore,
|
||||||
{
|
{
|
||||||
attachments,
|
attachments,
|
||||||
doForwardMessage: async (
|
doForwardMessage,
|
||||||
conversationIds: Array<string>,
|
hasContact: Boolean(message.get('contact')),
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isSticker: Boolean(message.get('sticker')),
|
isSticker: Boolean(message.get('sticker')),
|
||||||
messageBody: message.getRawText(),
|
messageBody: message.getRawText(),
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
|
@ -1433,6 +1437,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
const timestamp = baseTimestamp + offset;
|
const timestamp = baseTimestamp + offset;
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
const sticker = message.get('sticker');
|
const sticker = message.get('sticker');
|
||||||
|
const contact = message.get('contact');
|
||||||
|
|
||||||
if (sticker) {
|
if (sticker) {
|
||||||
const stickerWithData = await loadStickerData(sticker);
|
const stickerWithData = await loadStickerData(sticker);
|
||||||
const stickerNoPath = stickerWithData
|
const stickerNoPath = stickerWithData
|
||||||
|
@ -1446,12 +1452,21 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
conversation.enqueueMessageForSend(
|
conversation.enqueueMessageForSend(
|
||||||
undefined, // body
|
{
|
||||||
[],
|
body: undefined,
|
||||||
undefined, // quote
|
attachments: [],
|
||||||
[],
|
sticker: stickerNoPath,
|
||||||
stickerNoPath,
|
},
|
||||||
undefined, // BodyRanges
|
{ ...sendMessageOptions, timestamp }
|
||||||
|
);
|
||||||
|
} else if (contact) {
|
||||||
|
const contactWithHydratedAvatar = await loadContactData(contact);
|
||||||
|
conversation.enqueueMessageForSend(
|
||||||
|
{
|
||||||
|
body: undefined,
|
||||||
|
attachments: [],
|
||||||
|
contact: contactWithHydratedAvatar,
|
||||||
|
},
|
||||||
{ ...sendMessageOptions, timestamp }
|
{ ...sendMessageOptions, timestamp }
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1472,12 +1487,11 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
);
|
);
|
||||||
|
|
||||||
conversation.enqueueMessageForSend(
|
conversation.enqueueMessageForSend(
|
||||||
messageBody || undefined,
|
{
|
||||||
attachmentsToSend,
|
body: messageBody || undefined,
|
||||||
undefined, // quote
|
attachments: attachmentsToSend,
|
||||||
preview,
|
preview,
|
||||||
undefined, // sticker
|
},
|
||||||
undefined, // BodyRanges
|
|
||||||
{ ...sendMessageOptions, timestamp }
|
{ ...sendMessageOptions, timestamp }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2885,12 +2899,13 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
log.info('Send pre-checks took', sendDelta, 'milliseconds');
|
log.info('Send pre-checks took', sendDelta, 'milliseconds');
|
||||||
|
|
||||||
model.enqueueMessageForSend(
|
model.enqueueMessageForSend(
|
||||||
message,
|
{
|
||||||
attachments,
|
body: message,
|
||||||
this.quote,
|
attachments,
|
||||||
this.getLinkPreviewForSend(message),
|
quote: this.quote,
|
||||||
undefined, // sticker
|
preview: this.getLinkPreviewForSend(message),
|
||||||
mentions,
|
mentions,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
sendHQImages,
|
sendHQImages,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|
|
@ -3,20 +3,17 @@
|
||||||
|
|
||||||
// Captures the globals put in place by preload.js, background.js and others
|
// 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 Backbone from 'backbone';
|
||||||
import * as Underscore from 'underscore';
|
import * as Underscore from 'underscore';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import PQueue from 'p-queue/dist';
|
import PQueue from 'p-queue/dist';
|
||||||
import { Ref } from 'react';
|
import { Ref } from 'react';
|
||||||
import { imageToBlurHash } from './util/imageToBlurHash';
|
import { imageToBlurHash } from './util/imageToBlurHash';
|
||||||
import type { ParsedUrlQuery } from 'querystring';
|
|
||||||
import * as Util from './util';
|
import * as Util from './util';
|
||||||
import {
|
import {
|
||||||
ConversationModelCollectionType,
|
ConversationModelCollectionType,
|
||||||
MessageModelCollectionType,
|
MessageModelCollectionType,
|
||||||
MessageAttributesType,
|
|
||||||
ReactionAttributesType,
|
|
||||||
} from './model-types.d';
|
} from './model-types.d';
|
||||||
import { TextSecureType } from './textsecure.d';
|
import { TextSecureType } from './textsecure.d';
|
||||||
import { Storage } from './textsecure/Storage';
|
import { Storage } from './textsecure/Storage';
|
||||||
|
@ -32,11 +29,8 @@ import * as Curve from './Curve';
|
||||||
import * as RemoteConfig from './RemoteConfig';
|
import * as RemoteConfig from './RemoteConfig';
|
||||||
import * as OS from './OS';
|
import * as OS from './OS';
|
||||||
import { getEnvironment } from './environment';
|
import { getEnvironment } from './environment';
|
||||||
import * as zkgroup from './util/zkgroup';
|
import { LocalizerType } from './types/Util';
|
||||||
import { LocalizerType, BodyRangesType, BodyRangeType } from './types/Util';
|
|
||||||
import * as EmbeddedContact from './types/EmbeddedContact';
|
|
||||||
import type { Receipt } from './types/Receipt';
|
import type { Receipt } from './types/Receipt';
|
||||||
import * as Errors from './types/errors';
|
|
||||||
import { ConversationController } from './ConversationController';
|
import { ConversationController } from './ConversationController';
|
||||||
import { ReduxActions } from './state/types';
|
import { ReduxActions } from './state/types';
|
||||||
import { createStore } from './state/createStore';
|
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 conversationsSelectors from './state/selectors/conversations';
|
||||||
import * as searchSelectors from './state/selectors/search';
|
import * as searchSelectors from './state/selectors/search';
|
||||||
import AccountManager from './textsecure/AccountManager';
|
import AccountManager from './textsecure/AccountManager';
|
||||||
import { SendOptionsType } from './textsecure/SendMessage';
|
import { ContactWithHydratedAvatar } from './textsecure/SendMessage';
|
||||||
import Data from './sql/Client';
|
import Data from './sql/Client';
|
||||||
import { UserMessage } from './types/Message';
|
|
||||||
import { PhoneNumberFormat } from 'google-libphonenumber';
|
import { PhoneNumberFormat } from 'google-libphonenumber';
|
||||||
import { MessageModel } from './models/messages';
|
import { MessageModel } from './models/messages';
|
||||||
import { ConversationModel } from './models/conversations';
|
import { ConversationModel } from './models/conversations';
|
||||||
import { combineNames } from './util';
|
|
||||||
import { BatcherType } from './util/batcher';
|
import { BatcherType } from './util/batcher';
|
||||||
import { AttachmentList } from './components/conversation/AttachmentList';
|
import { AttachmentList } from './components/conversation/AttachmentList';
|
||||||
import { ChatColorPicker } from './components/ChatColorPicker';
|
import { ChatColorPicker } from './components/ChatColorPicker';
|
||||||
|
@ -93,14 +85,12 @@ import { Quote } from './components/conversation/Quote';
|
||||||
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
|
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
|
||||||
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
|
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
|
||||||
import { WhatsNewLink } from './components/WhatsNewLink';
|
import { WhatsNewLink } from './components/WhatsNewLink';
|
||||||
import { MIMEType } from './types/MIME';
|
|
||||||
import { DownloadedAttachmentType } from './types/Attachment';
|
import { DownloadedAttachmentType } from './types/Attachment';
|
||||||
import { ElectronLocaleType } from './util/mapToSupportLocale';
|
import { ElectronLocaleType } from './util/mapToSupportLocale';
|
||||||
import { SignalProtocolStore } from './SignalProtocolStore';
|
import { SignalProtocolStore } from './SignalProtocolStore';
|
||||||
import { StartupQueue } from './util/StartupQueue';
|
import { StartupQueue } from './util/StartupQueue';
|
||||||
import { SocketStatus } from './types/SocketStatus';
|
import { SocketStatus } from './types/SocketStatus';
|
||||||
import SyncRequest from './textsecure/SyncRequest';
|
import SyncRequest from './textsecure/SyncRequest';
|
||||||
import { ConversationColorType, CustomColorType } from './types/Colors';
|
|
||||||
import { MessageController } from './util/MessageController';
|
import { MessageController } from './util/MessageController';
|
||||||
import { StateType } from './state/reducer';
|
import { StateType } from './state/reducer';
|
||||||
import { SystemTraySetting } from './types/SystemTraySetting';
|
import { SystemTraySetting } from './types/SystemTraySetting';
|
||||||
|
@ -108,15 +98,13 @@ import { UUID } from './types/UUID';
|
||||||
import { Address } from './types/Address';
|
import { Address } from './types/Address';
|
||||||
import { QualifiedAddress } from './types/QualifiedAddress';
|
import { QualifiedAddress } from './types/QualifiedAddress';
|
||||||
import { CI } from './CI';
|
import { CI } from './CI';
|
||||||
import { IPCEventsType, IPCEventsValuesType } from './util/createIPCEvents';
|
import { IPCEventsType } from './util/createIPCEvents';
|
||||||
import { ConversationView } from './views/conversation_view';
|
import { ConversationView } from './views/conversation_view';
|
||||||
import type { SignalContextType } from './windows/context';
|
import type { SignalContextType } from './windows/context';
|
||||||
import { GroupV2Change } from './components/conversation/GroupV2Change';
|
import type { EmbeddedContactType } from './types/EmbeddedContact';
|
||||||
|
|
||||||
export { Long } from 'long';
|
export { Long } from 'long';
|
||||||
|
|
||||||
type TaskResultType = any;
|
|
||||||
|
|
||||||
export type WhatIsThis = any;
|
export type WhatIsThis = any;
|
||||||
|
|
||||||
// Synced with the type in ts/shims/showConfirmationDialog
|
// Synced with the type in ts/shims/showConfirmationDialog
|
||||||
|
@ -311,6 +299,9 @@ declare global {
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
loadQuoteData: (quote: unknown) => WhatIsThis;
|
loadQuoteData: (quote: unknown) => WhatIsThis;
|
||||||
|
loadContactData: (
|
||||||
|
contact?: Array<EmbeddedContactType>
|
||||||
|
) => Promise<Array<ContactWithHydratedAvatar> | undefined>;
|
||||||
loadPreviewData: (preview: unknown) => WhatIsThis;
|
loadPreviewData: (preview: unknown) => WhatIsThis;
|
||||||
loadStickerData: (sticker: unknown) => WhatIsThis;
|
loadStickerData: (sticker: unknown) => WhatIsThis;
|
||||||
readStickerData: (path: string) => Promise<Uint8Array>;
|
readStickerData: (path: string) => Promise<Uint8Array>;
|
||||||
|
|
Loading…
Reference in New Issue