Process incoming story messages
This commit is contained in:
parent
df7cdfacc7
commit
eb91eb6fec
84 changed files with 4382 additions and 652 deletions
|
@ -43,11 +43,6 @@ import * as expirationTimer from '../util/expirationTimer';
|
|||
import type { ReactionType } from '../types/Reactions';
|
||||
import { UUID, UUIDKind } from '../types/UUID';
|
||||
import * as reactionUtil from '../reactions/util';
|
||||
import {
|
||||
copyStickerToAttachments,
|
||||
savePackMetadata,
|
||||
getStickerPackStatus,
|
||||
} from '../types/Stickers';
|
||||
import * as Stickers from '../types/Stickers';
|
||||
import * as Errors from '../types/errors';
|
||||
import * as EmbeddedContact from '../types/EmbeddedContact';
|
||||
|
@ -99,6 +94,7 @@ import {
|
|||
isKeyChange,
|
||||
isMessageHistoryUnsynced,
|
||||
isOutgoing,
|
||||
isStory,
|
||||
isProfileChange,
|
||||
isTapToView,
|
||||
isUniversalTimerNotification,
|
||||
|
@ -124,7 +120,6 @@ import { ReactionSource } from '../reactions/ReactionSource';
|
|||
import { ReadSyncs } from '../messageModifiers/ReadSyncs';
|
||||
import { ViewSyncs } from '../messageModifiers/ViewSyncs';
|
||||
import { ViewOnceOpenSyncs } from '../messageModifiers/ViewOnceOpenSyncs';
|
||||
import * as AttachmentDownloads from '../messageModifiers/AttachmentDownloads';
|
||||
import * as LinkPreview from '../types/LinkPreview';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import {
|
||||
|
@ -141,13 +136,18 @@ import {
|
|||
getContact,
|
||||
getContactId,
|
||||
getSource,
|
||||
getSourceDevice,
|
||||
getSourceUuid,
|
||||
isCustomError,
|
||||
isQuoteAMatch,
|
||||
} from '../messages/helpers';
|
||||
import type { ReplacementValuesType } from '../types/I18N';
|
||||
import { viewOnceOpenJobQueue } from '../jobs/viewOnceOpenJobQueue';
|
||||
import { getMessageIdForLogging } from '../util/getMessageIdForLogging';
|
||||
import { hasAttachmentDownloads } from '../util/hasAttachmentDownloads';
|
||||
import { queueAttachmentDownloads } from '../util/queueAttachmentDownloads';
|
||||
import { findStoryMessage } from '../util/findStoryMessage';
|
||||
import { isConversationAccepted } from '../util/isConversationAccepted';
|
||||
import { getStoryDataFromMessageAttributes } from '../services/storyLoader';
|
||||
import type { ConversationQueueJobData } from '../jobs/conversationJobQueue';
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -165,7 +165,7 @@ window.Whisper = window.Whisper || {};
|
|||
const { Message: TypedMessage } = window.Signal.Types;
|
||||
const { upgradeMessageSchema } = window.Signal.Migrations;
|
||||
const { getTextWithMentions, GoogleChrome } = window.Signal.Util;
|
||||
const { addStickerPackReference, getMessageBySender } = window.Signal.Data;
|
||||
const { getMessageBySender } = window.Signal.Data;
|
||||
|
||||
export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||
static getLongMessageAttachment: (
|
||||
|
@ -232,6 +232,33 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
// Note: The clone is important for triggering a re-run of selectors
|
||||
messageChanged(this.id, conversationId, { ...this.attributes });
|
||||
}
|
||||
|
||||
const { addStory } = window.reduxActions.stories;
|
||||
|
||||
if (isStory(this.attributes)) {
|
||||
const ourConversationId =
|
||||
window.ConversationController.getOurConversationIdOrThrow();
|
||||
const storyData = getStoryDataFromMessageAttributes(
|
||||
this.attributes,
|
||||
ourConversationId
|
||||
);
|
||||
|
||||
if (!storyData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO DESKTOP-3179
|
||||
// Only add stories to redux if we've downloaded them. This should work
|
||||
// because once we download a story we'll receive another change event
|
||||
// which kicks off this function again.
|
||||
if (Attachment.hasNotDownloaded(storyData.attachment)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is fine to call multiple times since the addStory action only
|
||||
// adds new stories.
|
||||
addStory(storyData);
|
||||
}
|
||||
}
|
||||
|
||||
getSenderIdentifier(): string {
|
||||
|
@ -740,12 +767,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
// General
|
||||
idForLogging(): string {
|
||||
const account =
|
||||
getSourceUuid(this.attributes) || getSource(this.attributes);
|
||||
const device = getSourceDevice(this.attributes);
|
||||
const timestamp = this.get('sent_at');
|
||||
|
||||
return `${account}.${device} ${timestamp}`;
|
||||
return getMessageIdForLogging(this.attributes);
|
||||
}
|
||||
|
||||
override defaults(): Partial<MessageAttributesType> {
|
||||
|
@ -1636,332 +1658,18 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
return getLastChallengeError(this.attributes);
|
||||
}
|
||||
|
||||
// NOTE: If you're modifying this function then you'll likely also need
|
||||
// to modify queueAttachmentDownloads since it contains the logic below
|
||||
hasAttachmentDownloads(): boolean {
|
||||
const attachments = this.get('attachments') || [];
|
||||
|
||||
const [longMessageAttachments, normalAttachments] = _.partition(
|
||||
attachments,
|
||||
attachment => MIME.isLongMessage(attachment.contentType)
|
||||
);
|
||||
|
||||
if (longMessageAttachments.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasNormalAttachments = normalAttachments.some(attachment => {
|
||||
if (!attachment) {
|
||||
return false;
|
||||
}
|
||||
// We've already downloaded this!
|
||||
if (attachment.path) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (hasNormalAttachments) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const previews = this.get('preview') || [];
|
||||
const hasPreviews = previews.some(item => {
|
||||
if (!item.image) {
|
||||
return false;
|
||||
}
|
||||
// We've already downloaded this!
|
||||
if (item.image.path) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (hasPreviews) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const contacts = this.get('contact') || [];
|
||||
const hasContacts = contacts.some(item => {
|
||||
if (!item.avatar || !item.avatar.avatar) {
|
||||
return false;
|
||||
}
|
||||
if (item.avatar.avatar.path) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (hasContacts) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const quote = this.get('quote');
|
||||
const quoteAttachments =
|
||||
quote && quote.attachments ? quote.attachments : [];
|
||||
const hasQuoteAttachments = quoteAttachments.some(item => {
|
||||
if (!item.thumbnail) {
|
||||
return false;
|
||||
}
|
||||
// We've already downloaded this!
|
||||
if (item.thumbnail.path) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (hasQuoteAttachments) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const sticker = this.get('sticker');
|
||||
if (sticker) {
|
||||
return !sticker.data || (sticker.data && !sticker.data.path);
|
||||
}
|
||||
|
||||
return false;
|
||||
return hasAttachmentDownloads(this.attributes);
|
||||
}
|
||||
|
||||
// Receive logic
|
||||
// NOTE: If you're changing any logic in this function that deals with the
|
||||
// count then you'll also have to modify the above function
|
||||
// hasAttachmentDownloads
|
||||
async queueAttachmentDownloads(): Promise<boolean> {
|
||||
const attachmentsToQueue = this.get('attachments') || [];
|
||||
const messageId = this.id;
|
||||
let count = 0;
|
||||
let bodyPending;
|
||||
|
||||
log.info(
|
||||
`Queueing ${
|
||||
attachmentsToQueue.length
|
||||
} attachment downloads for message ${this.idForLogging()}`
|
||||
);
|
||||
|
||||
const [longMessageAttachments, normalAttachments] = _.partition(
|
||||
attachmentsToQueue,
|
||||
attachment => MIME.isLongMessage(attachment.contentType)
|
||||
);
|
||||
|
||||
if (longMessageAttachments.length > 1) {
|
||||
log.error(
|
||||
`Received more than one long message attachment in message ${this.idForLogging()}`
|
||||
);
|
||||
const value = await queueAttachmentDownloads(this.attributes);
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Queueing ${
|
||||
longMessageAttachments.length
|
||||
} long message attachment downloads for message ${this.idForLogging()}`
|
||||
);
|
||||
|
||||
if (longMessageAttachments.length > 0) {
|
||||
count += 1;
|
||||
bodyPending = true;
|
||||
await AttachmentDownloads.addJob(longMessageAttachments[0], {
|
||||
messageId,
|
||||
type: 'long-message',
|
||||
index: 0,
|
||||
});
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Queueing ${
|
||||
normalAttachments.length
|
||||
} normal attachment downloads for message ${this.idForLogging()}`
|
||||
);
|
||||
const attachments = await Promise.all(
|
||||
normalAttachments.map((attachment, index) => {
|
||||
if (!attachment) {
|
||||
return attachment;
|
||||
}
|
||||
// We've already downloaded this!
|
||||
if (attachment.path) {
|
||||
log.info(
|
||||
`Normal attachment already downloaded for message ${this.idForLogging()}`
|
||||
);
|
||||
return attachment;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
|
||||
return AttachmentDownloads.addJob(attachment, {
|
||||
messageId,
|
||||
type: 'attachment',
|
||||
index,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const previewsToQueue = this.get('preview') || [];
|
||||
log.info(
|
||||
`Queueing ${
|
||||
previewsToQueue.length
|
||||
} preview attachment downloads for message ${this.idForLogging()}`
|
||||
);
|
||||
const preview = await Promise.all(
|
||||
previewsToQueue.map(async (item, index) => {
|
||||
if (!item.image) {
|
||||
return item;
|
||||
}
|
||||
// We've already downloaded this!
|
||||
if (item.image.path) {
|
||||
log.info(
|
||||
`Preview attachment already downloaded for message ${this.idForLogging()}`
|
||||
);
|
||||
return item;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
return {
|
||||
...item,
|
||||
image: await AttachmentDownloads.addJob(item.image, {
|
||||
messageId,
|
||||
type: 'preview',
|
||||
index,
|
||||
}),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const contactsToQueue = this.get('contact') || [];
|
||||
log.info(
|
||||
`Queueing ${
|
||||
contactsToQueue.length
|
||||
} contact attachment downloads for message ${this.idForLogging()}`
|
||||
);
|
||||
const contact = await Promise.all(
|
||||
contactsToQueue.map(async (item, index) => {
|
||||
if (!item.avatar || !item.avatar.avatar) {
|
||||
return item;
|
||||
}
|
||||
// We've already downloaded this!
|
||||
if (item.avatar.avatar.path) {
|
||||
log.info(
|
||||
`Contact attachment already downloaded for message ${this.idForLogging()}`
|
||||
);
|
||||
return item;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
return {
|
||||
...item,
|
||||
avatar: {
|
||||
...item.avatar,
|
||||
avatar: await AttachmentDownloads.addJob(item.avatar.avatar, {
|
||||
messageId,
|
||||
type: 'contact',
|
||||
index,
|
||||
}),
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
let quote = this.get('quote')!;
|
||||
const quoteAttachmentsToQueue =
|
||||
quote && quote.attachments ? quote.attachments : [];
|
||||
log.info(
|
||||
`Queueing ${
|
||||
quoteAttachmentsToQueue.length
|
||||
} quote attachment downloads for message ${this.idForLogging()}`
|
||||
);
|
||||
if (quoteAttachmentsToQueue.length > 0) {
|
||||
quote = {
|
||||
...quote,
|
||||
attachments: await Promise.all(
|
||||
(quote.attachments || []).map(async (item, index) => {
|
||||
if (!item.thumbnail) {
|
||||
return item;
|
||||
}
|
||||
// We've already downloaded this!
|
||||
if (item.thumbnail.path) {
|
||||
log.info(
|
||||
`Quote attachment already downloaded for message ${this.idForLogging()}`
|
||||
);
|
||||
return item;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
return {
|
||||
...item,
|
||||
thumbnail: await AttachmentDownloads.addJob(item.thumbnail, {
|
||||
messageId,
|
||||
type: 'quote',
|
||||
index,
|
||||
}),
|
||||
};
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
let sticker = this.get('sticker')!;
|
||||
if (sticker && sticker.data && sticker.data.path) {
|
||||
log.info(
|
||||
`Sticker attachment already downloaded for message ${this.idForLogging()}`
|
||||
);
|
||||
} else if (sticker) {
|
||||
log.info(`Queueing sticker download for message ${this.idForLogging()}`);
|
||||
count += 1;
|
||||
const { packId, stickerId, packKey } = sticker;
|
||||
|
||||
const status = getStickerPackStatus(packId);
|
||||
let data: AttachmentType | undefined;
|
||||
|
||||
if (status && (status === 'downloaded' || status === 'installed')) {
|
||||
try {
|
||||
data = await copyStickerToAttachments(packId, stickerId);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Problem copying sticker (${packId}, ${stickerId}) to attachments:`,
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!data && sticker.data) {
|
||||
data = await AttachmentDownloads.addJob(sticker.data, {
|
||||
messageId,
|
||||
type: 'sticker',
|
||||
index: 0,
|
||||
});
|
||||
}
|
||||
if (!status) {
|
||||
// Save the packId/packKey for future download/install
|
||||
savePackMetadata(packId, packKey, { messageId });
|
||||
} else {
|
||||
await addStickerPackReference(messageId, packId);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
throw new Error(
|
||||
'queueAttachmentDownloads: Failed to fetch sticker data'
|
||||
);
|
||||
}
|
||||
|
||||
sticker = {
|
||||
...sticker,
|
||||
packId,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Queued ${count} total attachment downloads for message ${this.idForLogging()}`
|
||||
);
|
||||
|
||||
if (count > 0) {
|
||||
this.set({
|
||||
bodyPending,
|
||||
attachments,
|
||||
preview,
|
||||
contact,
|
||||
quote,
|
||||
sticker,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
this.set(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
markAttachmentAsCorrupted(attachment: AttachmentType): void {
|
||||
|
@ -2207,6 +1915,18 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
`Starting handleDataMessage for message ${message.idForLogging()} in conversation ${conversation.idForLogging()}`
|
||||
);
|
||||
|
||||
if (
|
||||
type === 'story' &&
|
||||
!isConversationAccepted(conversation.attributes)
|
||||
) {
|
||||
log.info(
|
||||
'handleDataMessage: dropping story from !whitelisted',
|
||||
this.getSenderIdentifier()
|
||||
);
|
||||
confirm();
|
||||
return;
|
||||
}
|
||||
|
||||
// First, check for duplicates. If we find one, stop processing here.
|
||||
const inMemoryMessage = window.MessageController.findBySender(
|
||||
this.getSenderIdentifier()
|
||||
|
@ -2471,12 +2191,15 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
});
|
||||
}
|
||||
|
||||
const [quote, storyQuote] = await Promise.all([
|
||||
this.copyFromQuotedMessage(initialMessage.quote, conversation.id),
|
||||
findStoryMessage(conversation.id, initialMessage.storyContext),
|
||||
]);
|
||||
|
||||
const withQuoteReference = {
|
||||
...initialMessage,
|
||||
quote: await this.copyFromQuotedMessage(
|
||||
initialMessage.quote,
|
||||
conversation.id
|
||||
),
|
||||
quote,
|
||||
storyId: storyQuote?.id,
|
||||
};
|
||||
const dataMessage = await upgradeMessageSchema(withQuoteReference);
|
||||
|
||||
|
@ -2521,6 +2244,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
quote: dataMessage.quote,
|
||||
schemaVersion: dataMessage.schemaVersion,
|
||||
sticker: dataMessage.sticker,
|
||||
storyId: dataMessage.storyId,
|
||||
});
|
||||
|
||||
const isSupported = !isUnsupportedMessage(message.attributes);
|
||||
|
@ -2807,8 +2531,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
conversation.incrementMessageCount();
|
||||
window.Signal.Data.updateConversation(conversation.attributes);
|
||||
|
||||
// Only queue attachments for downloads if this is an outgoing message
|
||||
// or we've accepted the conversation
|
||||
// Only queue attachments for downloads if this is a story or
|
||||
// outgoing message or we've accepted the conversation
|
||||
const reduxState = window.reduxStore.getState();
|
||||
const attachments = this.get('attachments') || [];
|
||||
const shouldHoldOffDownload =
|
||||
|
@ -2818,6 +2542,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
this.hasAttachmentDownloads() &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
(this.getConversation()!.getAccepted() ||
|
||||
isStory(message.attributes) ||
|
||||
isOutgoing(message.attributes)) &&
|
||||
!shouldHoldOffDownload
|
||||
) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue