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';
|
} from '../Crypto';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import type { DraftBodyRangeMention } from '../types/BodyRange';
|
import type { DraftBodyRangeMention } from '../types/BodyRange';
|
||||||
import { BodyRange } from '../types/BodyRange';
|
import { BodyRange, hydrateRanges } from '../types/BodyRange';
|
||||||
import { migrateColor } from '../util/migrateColor';
|
import { migrateColor } from '../util/migrateColor';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import { dropNull } from '../util/dropNull';
|
import { dropNull } from '../util/dropNull';
|
||||||
|
@ -1094,24 +1094,7 @@ export class ConversationModel extends window.Backbone
|
||||||
const draft = this.get('draft');
|
const draft = this.get('draft');
|
||||||
|
|
||||||
const rawBodyRanges = this.get('draftBodyRanges') || [];
|
const rawBodyRanges = this.get('draftBodyRanges') || [];
|
||||||
const bodyRanges = rawBodyRanges.map(range => {
|
const bodyRanges = hydrateRanges(rawBodyRanges, findAndFormatContact);
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (draft) {
|
if (draft) {
|
||||||
return {
|
return {
|
||||||
|
@ -3902,24 +3885,7 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawBodyRanges = this.get('lastMessageBodyRanges') || [];
|
const rawBodyRanges = this.get('lastMessageBodyRanges') || [];
|
||||||
const bodyRanges = rawBodyRanges.map(range => {
|
const bodyRanges = hydrateRanges(rawBodyRanges, findAndFormatContact);
|
||||||
// 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 text = stripNewlinesForLeftPane(lastMessageText);
|
const text = stripNewlinesForLeftPane(lastMessageText);
|
||||||
const prefix = this.get('lastMessagePrefix');
|
const prefix = this.get('lastMessagePrefix');
|
||||||
|
|
|
@ -53,7 +53,7 @@ import * as expirationTimer from '../util/expirationTimer';
|
||||||
import { getUserLanguages } from '../util/userLanguages';
|
import { getUserLanguages } from '../util/userLanguages';
|
||||||
|
|
||||||
import type { ReactionType } from '../types/Reactions';
|
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 reactionUtil from '../reactions/util';
|
||||||
import * as Stickers from '../types/Stickers';
|
import * as Stickers from '../types/Stickers';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
|
@ -1957,30 +1957,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
id,
|
id,
|
||||||
|
|
||||||
attachments: quote.attachments.slice(),
|
attachments: quote.attachments.slice(),
|
||||||
bodyRanges: quote.bodyRanges
|
bodyRanges: quote.bodyRanges?.slice(),
|
||||||
.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),
|
|
||||||
|
|
||||||
// Just placeholder values for the fields
|
// Just placeholder values for the fields
|
||||||
referencedMessageNotFound: false,
|
referencedMessageNotFound: false,
|
||||||
|
|
|
@ -48,7 +48,7 @@ import type {
|
||||||
HydratedBodyRangeMention,
|
HydratedBodyRangeMention,
|
||||||
HydratedBodyRangesType,
|
HydratedBodyRangesType,
|
||||||
} from '../../types/BodyRange';
|
} from '../../types/BodyRange';
|
||||||
import { BodyRange } from '../../types/BodyRange';
|
import { BodyRange, hydrateRanges } from '../../types/BodyRange';
|
||||||
import type { AssertProps } from '../../types/Util';
|
import type { AssertProps } from '../../types/Util';
|
||||||
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
||||||
import { getMentionsRegex } from '../../types/Message';
|
import { getMentionsRegex } from '../../types/Message';
|
||||||
|
@ -317,22 +317,9 @@ export const processBodyRanges = (
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return bodyRanges
|
return hydrateRanges(bodyRanges, options.conversationSelector)?.sort(
|
||||||
.map(range => {
|
(a, b) => b.start - a.start
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const extractHydratedMentions = (
|
export const extractHydratedMentions = (
|
||||||
|
|
|
@ -28,11 +28,9 @@ import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
} from './conversations';
|
} from './conversations';
|
||||||
|
|
||||||
import type { HydratedBodyRangeType } from '../../types/BodyRange';
|
import { hydrateRanges } from '../../types/BodyRange';
|
||||||
import { BodyRange } from '../../types/BodyRange';
|
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import { getOwn } from '../../util/getOwn';
|
import { getOwn } from '../../util/getOwn';
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
|
||||||
|
|
||||||
export const getSearch = (state: StateType): SearchStateType => state.search;
|
export const getSearch = (state: StateType): SearchStateType => state.search;
|
||||||
|
|
||||||
|
@ -178,7 +176,6 @@ export const getCachedSelectorForMessageSearchResult = createSelector(
|
||||||
searchConversationId?: string,
|
searchConversationId?: string,
|
||||||
targetedMessageId?: string
|
targetedMessageId?: string
|
||||||
) => {
|
) => {
|
||||||
const bodyRanges = message.bodyRanges || [];
|
|
||||||
return {
|
return {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
|
@ -187,24 +184,8 @@ export const getCachedSelectorForMessageSearchResult = createSelector(
|
||||||
conversationId: message.conversationId,
|
conversationId: message.conversationId,
|
||||||
sentAt: message.sent_at,
|
sentAt: message.sent_at,
|
||||||
snippet: message.snippet || '',
|
snippet: message.snippet || '',
|
||||||
bodyRanges: bodyRanges.map((range): HydratedBodyRangeType => {
|
bodyRanges:
|
||||||
// Hydrate user information on mention
|
hydrateRanges(message.bodyRanges, conversationSelector) || [],
|
||||||
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);
|
|
||||||
}),
|
|
||||||
body: message.body || '',
|
body: message.body || '',
|
||||||
|
|
||||||
isSelected: Boolean(
|
isSelected: Boolean(
|
||||||
|
|
|
@ -42,7 +42,7 @@ import {
|
||||||
reduceStorySendStatus,
|
reduceStorySendStatus,
|
||||||
resolveStorySendStatus,
|
resolveStorySendStatus,
|
||||||
} from '../../util/resolveStorySendStatus';
|
} from '../../util/resolveStorySendStatus';
|
||||||
import { BodyRange } from '../../types/BodyRange';
|
import { BodyRange, hydrateRanges } from '../../types/BodyRange';
|
||||||
|
|
||||||
export const getStoriesState = (state: StateType): StoriesStateType =>
|
export const getStoriesState = (state: StateType): StoriesStateType =>
|
||||||
state.stories;
|
state.stories;
|
||||||
|
@ -302,24 +302,10 @@ export const getStoryReplies = createSelector(
|
||||||
? me
|
? me
|
||||||
: conversationSelector(reply.sourceUuid || reply.source);
|
: conversationSelector(reply.sourceUuid || reply.source);
|
||||||
|
|
||||||
const { bodyRanges } = reply;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
author: getAvatarData(conversation),
|
author: getAvatarData(conversation),
|
||||||
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
|
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
|
||||||
bodyRanges: bodyRanges?.map(bodyRange => {
|
bodyRanges: hydrateRanges(reply.bodyRanges, conversationSelector),
|
||||||
if (BodyRange.isMention(bodyRange)) {
|
|
||||||
const mentionConvo = conversationSelector(bodyRange.mentionUuid);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...bodyRange,
|
|
||||||
conversationID: mentionConvo.id,
|
|
||||||
replacementText: mentionConvo.title,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return bodyRange;
|
|
||||||
}),
|
|
||||||
reactionEmoji: reply.storyReaction?.emoji,
|
reactionEmoji: reply.storyReaction?.emoji,
|
||||||
contactNameColor: contactNameColorSelector(
|
contactNameColor: contactNameColorSelector(
|
||||||
reply.conversationId,
|
reply.conversationId,
|
||||||
|
|
|
@ -202,7 +202,7 @@ describe('processDataMessage', () => {
|
||||||
thumbnail: PROCESSED_ATTACHMENT,
|
thumbnail: PROCESSED_ATTACHMENT,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
bodyRanges: [],
|
bodyRanges: undefined,
|
||||||
type: 0,
|
type: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -125,6 +125,7 @@ import { chunk } from '../util/iterables';
|
||||||
import { isOlderThan } from '../util/timestamp';
|
import { isOlderThan } from '../util/timestamp';
|
||||||
import { inspectUnknownFieldTags } from '../util/inspectProtobufs';
|
import { inspectUnknownFieldTags } from '../util/inspectProtobufs';
|
||||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||||
|
import { filterAndClean } from '../types/BodyRange';
|
||||||
|
|
||||||
const GROUPV1_ID_LENGTH = 16;
|
const GROUPV1_ID_LENGTH = 16;
|
||||||
const GROUPV2_ID_LENGTH = 32;
|
const GROUPV2_ID_LENGTH = 32;
|
||||||
|
@ -2121,8 +2122,8 @@ export default class MessageReceiver
|
||||||
|
|
||||||
const message: ProcessedDataMessage = {
|
const message: ProcessedDataMessage = {
|
||||||
attachments,
|
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,
|
preview,
|
||||||
canReplyToStory: Boolean(msg.allowsReplies),
|
canReplyToStory: Boolean(msg.allowsReplies),
|
||||||
expireTimer: DurationInSeconds.DAY,
|
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 { MIMEType } from '../types/MIME';
|
||||||
import type { DurationInSeconds } from '../util/durations';
|
import type { DurationInSeconds } from '../util/durations';
|
||||||
import type { AnyPaymentEvent } from '../types/Payment';
|
import type { AnyPaymentEvent } from '../types/Payment';
|
||||||
|
import type { RawBodyRange } from '../types/BodyRange';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
IdentityKeyType,
|
IdentityKeyType,
|
||||||
|
@ -150,7 +151,7 @@ export type ProcessedQuote = {
|
||||||
authorUuid?: string;
|
authorUuid?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
attachments: ReadonlyArray<ProcessedQuoteAttachment>;
|
attachments: ReadonlyArray<ProcessedQuoteAttachment>;
|
||||||
bodyRanges: ReadonlyArray<ProcessedBodyRange>;
|
bodyRanges?: ReadonlyArray<ProcessedBodyRange>;
|
||||||
type: Proto.DataMessage.Quote.Type;
|
type: Proto.DataMessage.Quote.Type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -190,7 +191,7 @@ export type ProcessedDelete = {
|
||||||
targetSentTimestamp?: number;
|
targetSentTimestamp?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProcessedBodyRange = Proto.DataMessage.IBodyRange;
|
export type ProcessedBodyRange = RawBodyRange;
|
||||||
|
|
||||||
export type ProcessedGroupCallUpdate = Proto.DataMessage.IGroupCallUpdate;
|
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 { SECOND, DurationInSeconds } from '../util/durations';
|
||||||
import type { AnyPaymentEvent } from '../types/Payment';
|
import type { AnyPaymentEvent } from '../types/Payment';
|
||||||
import { PaymentEventKind } from '../types/Payment';
|
import { PaymentEventKind } from '../types/Payment';
|
||||||
|
import { filterAndClean } from '../types/BodyRange';
|
||||||
|
|
||||||
const FLAGS = Proto.DataMessage.Flags;
|
const FLAGS = Proto.DataMessage.Flags;
|
||||||
export const ATTACHMENT_MAX = 32;
|
export const ATTACHMENT_MAX = 32;
|
||||||
|
@ -175,8 +176,7 @@ export function processQuote(
|
||||||
thumbnail: processAttachment(attachment.thumbnail),
|
thumbnail: processAttachment(attachment.thumbnail),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
// We need to remove all of the extra stuff on these objects so serialize properly
|
bodyRanges: filterAndClean(quote.bodyRanges),
|
||||||
bodyRanges: quote.bodyRanges?.map(item => ({ ...item })) ?? [],
|
|
||||||
type: quote.type || Proto.DataMessage.Quote.Type.NORMAL,
|
type: quote.type || Proto.DataMessage.Quote.Type.NORMAL,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -349,8 +349,7 @@ export function processDataMessage(
|
||||||
isViewOnce: Boolean(message.isViewOnce),
|
isViewOnce: Boolean(message.isViewOnce),
|
||||||
reaction: processReaction(message.reaction),
|
reaction: processReaction(message.reaction),
|
||||||
delete: processDelete(message.delete),
|
delete: processDelete(message.delete),
|
||||||
// We need to remove all of the extra stuff on these objects so serialize properly
|
bodyRanges: filterAndClean(message.bodyRanges),
|
||||||
bodyRanges: message.bodyRanges?.map(item => ({ ...item })) ?? [],
|
|
||||||
groupCallUpdate: dropNull(message.groupCallUpdate),
|
groupCallUpdate: dropNull(message.groupCallUpdate),
|
||||||
storyContext: dropNull(message.storyContext),
|
storyContext: dropNull(message.storyContext),
|
||||||
giftBadge: processGiftBadge(message.giftBadge),
|
giftBadge: processGiftBadge(message.giftBadge),
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { SignalService as Proto } from '../protobuf';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { assertDev } from '../util/assert';
|
import { assertDev } from '../util/assert';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
|
|
||||||
// Cold storage of body ranges
|
// Cold storage of body ranges
|
||||||
|
|
||||||
|
@ -43,6 +44,10 @@ export namespace BodyRange {
|
||||||
displayStyle: DisplayStyle;
|
displayStyle: DisplayStyle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isRawRange(range: BodyRange<object>): range is RawBodyRange {
|
||||||
|
return isMention(range) || isFormatting(range);
|
||||||
|
}
|
||||||
|
|
||||||
// these overloads help inference along
|
// these overloads help inference along
|
||||||
export function isMention(
|
export function isMention(
|
||||||
bodyRange: HydratedBodyRangeType
|
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
|
* Insert a range into an existing range tree, splitting up the range if it intersects
|
||||||
* with an existing range
|
* with an existing range
|
||||||
|
|
Loading…
Add table
Reference in a new issue