Filter incoming bodyRanges, also filter before display
This commit is contained in:
parent
ec1246f60a
commit
4c9baaef80
10 changed files with 84 additions and 126 deletions
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -202,7 +202,7 @@ describe('processDataMessage', () => {
|
|||
thumbnail: PROCESSED_ATTACHMENT,
|
||||
},
|
||||
],
|
||||
bodyRanges: [],
|
||||
bodyRanges: undefined,
|
||||
type: 0,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
5
ts/textsecure/Types.d.ts
vendored
5
ts/textsecure/Types.d.ts
vendored
|
@ -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;
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue