Filter incoming bodyRanges, also filter before display

This commit is contained in:
Scott Nonnenberg 2023-04-11 17:16:46 -07:00 committed by GitHub
parent ec1246f60a
commit 4c9baaef80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 84 additions and 126 deletions

View file

@ -85,7 +85,7 @@ import {
} from '../Crypto';
import * as Bytes from '../Bytes';
import type { DraftBodyRangeMention } from '../types/BodyRange';
import { BodyRange } from '../types/BodyRange';
import { BodyRange, hydrateRanges } from '../types/BodyRange';
import { migrateColor } from '../util/migrateColor';
import { isNotNil } from '../util/isNotNil';
import { dropNull } from '../util/dropNull';
@ -1094,24 +1094,7 @@ export class ConversationModel extends window.Backbone
const draft = this.get('draft');
const rawBodyRanges = this.get('draftBodyRanges') || [];
const bodyRanges = rawBodyRanges.map(range => {
// Hydrate user information on mention
if (BodyRange.isMention(range)) {
const conversation = findAndFormatContact(range.mentionUuid);
return {
...range,
conversationID: conversation.id,
replacementText: conversation.title,
};
}
if (BodyRange.isFormatting(range)) {
return range;
}
throw missingCaseError(range);
});
const bodyRanges = hydrateRanges(rawBodyRanges, findAndFormatContact);
if (draft) {
return {
@ -3902,24 +3885,7 @@ export class ConversationModel extends window.Backbone
}
const rawBodyRanges = this.get('lastMessageBodyRanges') || [];
const bodyRanges = rawBodyRanges.map(range => {
// Hydrate user information on mention
if (BodyRange.isMention(range)) {
const conversation = findAndFormatContact(range.mentionUuid);
return {
...range,
conversationID: conversation.id,
replacementText: conversation.title,
};
}
if (BodyRange.isFormatting(range)) {
return range;
}
throw missingCaseError(range);
});
const bodyRanges = hydrateRanges(rawBodyRanges, findAndFormatContact);
const text = stripNewlinesForLeftPane(lastMessageText);
const prefix = this.get('lastMessagePrefix');

View file

@ -53,7 +53,7 @@ import * as expirationTimer from '../util/expirationTimer';
import { getUserLanguages } from '../util/userLanguages';
import type { ReactionType } from '../types/Reactions';
import { isValidUuid, UUID, UUIDKind } from '../types/UUID';
import { UUID, UUIDKind } from '../types/UUID';
import * as reactionUtil from '../reactions/util';
import * as Stickers from '../types/Stickers';
import * as Errors from '../types/errors';
@ -1957,30 +1957,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
id,
attachments: quote.attachments.slice(),
bodyRanges: quote.bodyRanges
.map(({ start, length, mentionUuid }) => {
strictAssert(
start != null,
'Received quote with a bodyRange.start == null'
);
strictAssert(
length != null,
'Received quote with a bodyRange.length == null'
);
if (!isValidUuid(mentionUuid)) {
log.warn(
`copyFromQuotedMessage: invalid mentionUuid ${mentionUuid}`
);
return undefined;
}
return {
start,
length,
mentionUuid,
};
})
.filter(isNotNil),
bodyRanges: quote.bodyRanges?.slice(),
// Just placeholder values for the fields
referencedMessageNotFound: false,

View file

@ -48,7 +48,7 @@ import type {
HydratedBodyRangeMention,
HydratedBodyRangesType,
} from '../../types/BodyRange';
import { BodyRange } from '../../types/BodyRange';
import { BodyRange, hydrateRanges } from '../../types/BodyRange';
import type { AssertProps } from '../../types/Util';
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
import { getMentionsRegex } from '../../types/Message';
@ -317,22 +317,9 @@ export const processBodyRanges = (
return undefined;
}
return bodyRanges
.map(range => {
const { conversationSelector } = options;
if (BodyRange.isMention(range)) {
const conversation = conversationSelector(range.mentionUuid);
return {
...range,
conversationID: conversation.id,
replacementText: conversation.title,
};
}
return range;
})
.sort((a, b) => b.start - a.start);
return hydrateRanges(bodyRanges, options.conversationSelector)?.sort(
(a, b) => b.start - a.start
);
};
export const extractHydratedMentions = (

View file

@ -28,11 +28,9 @@ import {
getConversationSelector,
} from './conversations';
import type { HydratedBodyRangeType } from '../../types/BodyRange';
import { BodyRange } from '../../types/BodyRange';
import { hydrateRanges } from '../../types/BodyRange';
import * as log from '../../logging/log';
import { getOwn } from '../../util/getOwn';
import { missingCaseError } from '../../util/missingCaseError';
export const getSearch = (state: StateType): SearchStateType => state.search;
@ -178,7 +176,6 @@ export const getCachedSelectorForMessageSearchResult = createSelector(
searchConversationId?: string,
targetedMessageId?: string
) => {
const bodyRanges = message.bodyRanges || [];
return {
from,
to,
@ -187,24 +184,8 @@ export const getCachedSelectorForMessageSearchResult = createSelector(
conversationId: message.conversationId,
sentAt: message.sent_at,
snippet: message.snippet || '',
bodyRanges: bodyRanges.map((range): HydratedBodyRangeType => {
// Hydrate user information on mention
if (BodyRange.isMention(range)) {
const conversation = conversationSelector(range.mentionUuid);
return {
...range,
conversationID: conversation.id,
replacementText: conversation.title,
};
}
if (BodyRange.isFormatting(range)) {
return range;
}
throw missingCaseError(range);
}),
bodyRanges:
hydrateRanges(message.bodyRanges, conversationSelector) || [],
body: message.body || '',
isSelected: Boolean(

View file

@ -42,7 +42,7 @@ import {
reduceStorySendStatus,
resolveStorySendStatus,
} from '../../util/resolveStorySendStatus';
import { BodyRange } from '../../types/BodyRange';
import { BodyRange, hydrateRanges } from '../../types/BodyRange';
export const getStoriesState = (state: StateType): StoriesStateType =>
state.stories;
@ -302,24 +302,10 @@ export const getStoryReplies = createSelector(
? me
: conversationSelector(reply.sourceUuid || reply.source);
const { bodyRanges } = reply;
return {
author: getAvatarData(conversation),
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
bodyRanges: bodyRanges?.map(bodyRange => {
if (BodyRange.isMention(bodyRange)) {
const mentionConvo = conversationSelector(bodyRange.mentionUuid);
return {
...bodyRange,
conversationID: mentionConvo.id,
replacementText: mentionConvo.title,
};
}
return bodyRange;
}),
bodyRanges: hydrateRanges(reply.bodyRanges, conversationSelector),
reactionEmoji: reply.storyReaction?.emoji,
contactNameColor: contactNameColorSelector(
reply.conversationId,

View file

@ -202,7 +202,7 @@ describe('processDataMessage', () => {
thumbnail: PROCESSED_ATTACHMENT,
},
],
bodyRanges: [],
bodyRanges: undefined,
type: 0,
});
});

View file

@ -125,6 +125,7 @@ import { chunk } from '../util/iterables';
import { isOlderThan } from '../util/timestamp';
import { inspectUnknownFieldTags } from '../util/inspectProtobufs';
import { incrementMessageCounter } from '../util/incrementMessageCounter';
import { filterAndClean } from '../types/BodyRange';
const GROUPV1_ID_LENGTH = 16;
const GROUPV2_ID_LENGTH = 32;
@ -2121,8 +2122,8 @@ export default class MessageReceiver
const message: ProcessedDataMessage = {
attachments,
// We need to remove all of the extra stuff on these objects so serialize properly
bodyRanges: msg.bodyRanges?.map(item => ({ ...item })),
bodyRanges: filterAndClean(msg.bodyRanges),
preview,
canReplyToStory: Boolean(msg.allowsReplies),
expireTimer: DurationInSeconds.DAY,

View file

@ -9,6 +9,7 @@ import type { GiftBadgeStates } from '../components/conversation/Message';
import type { MIMEType } from '../types/MIME';
import type { DurationInSeconds } from '../util/durations';
import type { AnyPaymentEvent } from '../types/Payment';
import type { RawBodyRange } from '../types/BodyRange';
export {
IdentityKeyType,
@ -150,7 +151,7 @@ export type ProcessedQuote = {
authorUuid?: string;
text?: string;
attachments: ReadonlyArray<ProcessedQuoteAttachment>;
bodyRanges: ReadonlyArray<ProcessedBodyRange>;
bodyRanges?: ReadonlyArray<ProcessedBodyRange>;
type: Proto.DataMessage.Quote.Type;
};
@ -190,7 +191,7 @@ export type ProcessedDelete = {
targetSentTimestamp?: number;
};
export type ProcessedBodyRange = Proto.DataMessage.IBodyRange;
export type ProcessedBodyRange = RawBodyRange;
export type ProcessedGroupCallUpdate = Proto.DataMessage.IGroupCallUpdate;

View file

@ -31,6 +31,7 @@ import { APPLICATION_OCTET_STREAM, stringToMIMEType } from '../types/MIME';
import { SECOND, DurationInSeconds } from '../util/durations';
import type { AnyPaymentEvent } from '../types/Payment';
import { PaymentEventKind } from '../types/Payment';
import { filterAndClean } from '../types/BodyRange';
const FLAGS = Proto.DataMessage.Flags;
export const ATTACHMENT_MAX = 32;
@ -175,8 +176,7 @@ export function processQuote(
thumbnail: processAttachment(attachment.thumbnail),
};
}),
// We need to remove all of the extra stuff on these objects so serialize properly
bodyRanges: quote.bodyRanges?.map(item => ({ ...item })) ?? [],
bodyRanges: filterAndClean(quote.bodyRanges),
type: quote.type || Proto.DataMessage.Quote.Type.NORMAL,
};
}
@ -349,8 +349,7 @@ export function processDataMessage(
isViewOnce: Boolean(message.isViewOnce),
reaction: processReaction(message.reaction),
delete: processDelete(message.delete),
// We need to remove all of the extra stuff on these objects so serialize properly
bodyRanges: message.bodyRanges?.map(item => ({ ...item })) ?? [],
bodyRanges: filterAndClean(message.bodyRanges),
groupCallUpdate: dropNull(message.groupCallUpdate),
storyContext: dropNull(message.storyContext),
giftBadge: processGiftBadge(message.giftBadge),

View file

@ -9,6 +9,7 @@ import { SignalService as Proto } from '../protobuf';
import * as log from '../logging/log';
import { assertDev } from '../util/assert';
import { missingCaseError } from '../util/missingCaseError';
import type { ConversationType } from '../state/ducks/conversations';
// Cold storage of body ranges
@ -43,6 +44,10 @@ export namespace BodyRange {
displayStyle: DisplayStyle;
};
export function isRawRange(range: BodyRange<object>): range is RawBodyRange {
return isMention(range) || isFormatting(range);
}
// these overloads help inference along
export function isMention(
bodyRange: HydratedBodyRangeType
@ -122,6 +127,61 @@ export type RangeNode = BodyRange<
}
>;
// We drop unknown bodyRanges and remove extra stuff so they serialize properly
export function filterAndClean(
ranges: ReadonlyArray<Proto.DataMessage.IBodyRange> | undefined | null
): ReadonlyArray<RawBodyRange> | undefined {
if (!ranges) {
return undefined;
}
return ranges
.filter((range: Proto.DataMessage.IBodyRange): range is RawBodyRange => {
if (!isNumber(range.start)) {
log.warn('filterAndClean: Dropping bodyRange with non-number start');
return false;
}
if (!isNumber(range.length)) {
log.warn('filterAndClean: Dropping bodyRange with non-number length');
return false;
}
if (range.mentionUuid) {
return true;
}
if (range.style) {
return true;
}
log.warn('filterAndClean: Dropping unknown bodyRange');
return false;
})
.map(range => ({ ...range }));
}
export function hydrateRanges(
ranges: ReadonlyArray<BodyRange<object>> | undefined,
conversationSelector: (id: string) => ConversationType
): Array<HydratedBodyRangeType> | undefined {
if (!ranges) {
return undefined;
}
return ranges.filter(BodyRange.isRawRange).map(range => {
if (BodyRange.isMention(range)) {
const conversation = conversationSelector(range.mentionUuid);
return {
...range,
conversationID: conversation.id,
replacementText: conversation.title,
};
}
return range;
});
}
/**
* Insert a range into an existing range tree, splitting up the range if it intersects
* with an existing range