Message Requests improvements

This commit is contained in:
Scott Nonnenberg 2020-08-06 17:50:54 -07:00 committed by GitHub
parent b63291507a
commit 81cb7730a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 302 additions and 263 deletions

View File

@ -1764,6 +1764,10 @@
}
},
"ConversationListItem--message-request": {
"message": "Message Request",
"description": "Preview shown for conversation if the user has not yet accepted an incoming message request"
},
"ConversationListItem--draft-prefix": {
"message": "Draft:",
"description": "Prefix shown in italic in conversation view when a draft is saved"

View File

@ -1885,6 +1885,11 @@
logger: window.log,
});
// Force a re-fetch here when we've processed our queue. Without this, we won't try
// again for two hours after our first attempt. Which might have been while we were
// offline or didn't have credentials.
window.Signal.RemoteConfig.refreshRemoteConfig();
let interval = setInterval(() => {
const view = window.owsDesktopApp.appView;
if (view) {

View File

@ -28,7 +28,7 @@
};
const { Util } = window.Signal;
const { Conversation, Contact, Message } = window.Signal.Types;
const { Contact, Message } = window.Signal.Types;
const {
deleteAttachmentData,
doesAttachmentExist,
@ -134,12 +134,6 @@
this.updateLastMessage.bind(this),
200
);
this.throttledUpdateSharedGroups =
this.throttledUpdateSharedGroups ||
_.throttle(
this.updateSharedGroups.bind(this),
1000 * 60 * 5 // five minutes
);
this.listenTo(
this.messageCollection,
@ -175,10 +169,11 @@
this.typingPauseTimer = null;
// Keep props ready
this.generateProps = () => {
const generateProps = () => {
this.cachedProps = this.getProps();
};
this.on('change', this.generateProps);
this.on('change', generateProps);
generateProps();
},
isMe() {
@ -451,8 +446,6 @@
getProps() {
const color = this.getColor();
this.throttledUpdateSharedGroups();
const typingValues = _.values(this.contactTypingTimers || {});
const typingMostRecent = _.first(_.sortBy(typingValues, 'timestamp'));
const typingContact = typingMostRecent
@ -575,15 +568,16 @@
async handleReadAndDownloadAttachments() {
let messages;
do {
const first = messages ? messages.first() : null;
// eslint-disable-next-line no-await-in-loop
messages = await window.Signal.Data.getOlderMessagesByConversation(
this.get('id'),
{
MessageCollection: Whisper.MessageCollection,
limit: 100,
receivedAt: messages
? messages.first().get('received_at')
: undefined,
receivedAt: first ? first.get('received_at') : null,
messageId: first ? first.id : null,
}
);
@ -959,13 +953,18 @@
(this.get('messageCountBeforeMessageRequests') || 0) > 0;
const hasNoMessages = (this.get('messageCount') || 0) === 0;
const isEmptyPrivateConvo = hasNoMessages && this.isPrivate();
const isEmptyWhitelistedGroup =
hasNoMessages && !this.isPrivate() && this.get('profileSharing');
return (
isFromOrAddedByTrustedContact ||
hasSentMessages ||
hasMessagesBeforeMessageRequests ||
// an empty conversation is the scenario where we need to rely on
// an empty group is the scenario where we need to rely on
// whether the profile has already been shared or not
(hasNoMessages && this.get('profileSharing'))
isEmptyPrivateConvo ||
isEmptyWhitelistedGroup
);
},
@ -1868,37 +1867,40 @@
return;
}
const messages = await window.Signal.Data.getOlderMessagesByConversation(
this.id,
{ limit: 1, MessageCollection: Whisper.MessageCollection }
);
const [previewMessage, activityMessage] = await Promise.all([
window.Signal.Data.getLastConversationPreview(this.id, {
Message: Whisper.Message,
}),
window.Signal.Data.getLastConversationActivity(this.id, {
Message: Whisper.Message,
}),
]);
// This is the less-restrictive of these two fetches; if it's falsey, both will be
if (!previewMessage) {
return;
}
const lastMessageModel = messages.at(0);
if (
this.hasDraft() &&
this.get('draftTimestamp') &&
(!lastMessageModel ||
lastMessageModel.get('sent_at') < this.get('draftTimestamp'))
previewMessage.get('sent_at') < this.get('draftTimestamp')
) {
return;
}
const lastMessageJSON = lastMessageModel
? lastMessageModel.toJSON()
: null;
const lastMessageStatusModel = lastMessageModel
? lastMessageModel.getMessagePropStatus()
: null;
const lastMessageUpdate = Conversation.createLastMessageUpdate({
currentTimestamp: this.get('timestamp') || null,
lastMessage: lastMessageJSON,
lastMessageStatus: lastMessageStatusModel,
lastMessageNotificationText: lastMessageModel
? lastMessageModel.getNotificationText()
: null,
const currentTimestamp = this.get('timestamp') || null;
const timestamp = activityMessage
? activityMessage.sent_at || currentTimestamp
: currentTimestamp;
this.set({
lastMessage: previewMessage.getNotificationText() || '',
lastMessageStatus: previewMessage.getMessagePropStatus() || null,
timestamp,
lastMessageDeletedForEveryone: previewMessage.deletedForEveryone,
});
this.set(lastMessageUpdate);
window.Signal.Data.updateConversation(this.attributes);
},

View File

@ -1,7 +1,6 @@
/* global crypto, window */
const { isFunction, isNumber } = require('lodash');
const { createLastMessageUpdate } = require('../../../ts/types/Conversation');
const {
arrayBufferToBase64,
base64ToArrayBuffer,
@ -161,7 +160,7 @@ module.exports = {
arrayBufferToBase64,
base64ToArrayBuffer,
computeHash,
createLastMessageUpdate,
deleteExternalFiles,
maybeUpdateAvatar,
maybeUpdateProfileAvatar,

View File

@ -16,6 +16,8 @@
(function() {
'use strict';
const FIVE_MINUTES = 1000 * 60 * 5;
window.Whisper = window.Whisper || {};
const { Message, MIME, VisualAttachment } = window.Signal.Types;
const {
@ -304,9 +306,12 @@
);
this.model.throttledGetProfiles =
this.model.throttledGetProfiles ||
_.throttle(this.model.getProfiles.bind(this.model), FIVE_MINUTES);
this.model.throttledUpdateSharedGroups =
this.model.throttledUpdateSharedGroups ||
_.throttle(
this.model.getProfiles.bind(this.model),
1000 * 60 * 5 // five minutes
this.model.updateSharedGroups.bind(this.model),
FIVE_MINUTES
);
this.debouncedMaybeGrabLinkPreview = _.debounce(
this.maybeGrabLinkPreview.bind(this),
@ -720,6 +725,7 @@
showVisualAttachment,
showExpiredIncomingTapToViewToast,
showExpiredOutgoingTapToViewToast,
updateSharedGroups: this.model.throttledUpdateSharedGroups,
}),
});

View File

@ -3750,6 +3750,17 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
align-items: center;
}
.module-conversation-list-item__message-request {
@include font-body-2-bold;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
}
.module-conversation-list-item__message__text {
flex-grow: 1;
flex-shrink: 1;

View File

@ -636,8 +636,6 @@ export class ConversationController {
await Promise.all(
this._conversations.map(async conversation => {
conversation.generateProps();
if (!conversation.get('lastMessage')) {
await conversation.updateLastMessage();
}

View File

@ -42,7 +42,7 @@ export function onChange(key: ConfigKeyType, fn: ConfigListenerType) {
};
}
const refreshRemoteConfig = async () => {
export const refreshRemoteConfig = async () => {
const now = Date.now();
const server = getServer();
const newConfig = await server.getConfig();

View File

@ -4,6 +4,8 @@
<util.LeftPaneContext theme={util.theme}>
<ConversationListItem
id="conversationId1"
isAccepted
title="Someone 🔥 Somewhere"
name="Someone 🔥 Somewhere"
type={'direct'}
phoneNumber="(202) 555-0011"
@ -25,8 +27,10 @@
<util.LeftPaneContext theme={util.theme}>
<ConversationListItem
id="conversationId1"
isAccepted
phoneNumber="(202) 555-0011"
type={'direct'}
title="Mr. Fire🔥"
name="Mr. Fire🔥"
color="green"
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -46,9 +50,11 @@
<util.LeftPaneContext theme={util.theme}>
<ConversationListItem
id="conversationId1"
isAccepted
isMe={true}
phoneNumber="(202) 555-0011"
type={'direct'}
title="Mr. Fire🔥"
name="Mr. Fire🔥"
color="green"
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -69,8 +75,10 @@
<div>
<ConversationListItem
id="conversationId1"
isAccepted
phoneNumber="(202) 555-0011"
type={'direct'}
title="Mr. Fire🔥"
name="Mr. Fire🔥"
color="green"
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -83,8 +91,10 @@
/>
<ConversationListItem
id="conversationId2"
isAccepted
phoneNumber="(202) 555-0011"
type={'direct'}
title="Mr. Fire🔥"
name="Mr. Fire🔥"
color="green"
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -97,8 +107,10 @@
/>
<ConversationListItem
id="conversationId3"
isAccepted
phoneNumber="(202) 555-0011"
type={'direct'}
title="Mr. Fire🔥"
name="Mr. Fire🔥"
color="green"
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -111,8 +123,10 @@
/>
<ConversationListItem
id="conversationId4"
isAccepted
phoneNumber="(202) 555-0011"
type={'direct'}
title="Mr. Fire🔥"
name="Mr. Fire🔥"
color="green"
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -125,8 +139,10 @@
/>
<ConversationListItem
id="conversationId5"
isAccepted
phoneNumber="(202) 555-0011"
type={'direct'}
title="Mr. Fire🔥"
name="Mr. Fire🔥"
color="green"
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -148,7 +164,9 @@
<div>
<ConversationListItem
id="conversationId1"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
unreadCount={4}
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -162,7 +180,51 @@
<div>
<ConversationListItem
id="conversationId2"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
unreadCount={4}
lastUpdated={Date.now() - 5 * 60 * 1000}
typingContact={{
name: 'Someone Here',
}}
lastMessage={{
status: 'read',
}}
onClick={result => console.log('onClick', result)}
i18n={util.i18n}
/>
</div>
</util.LeftPaneContext>
```
#### Message Request
```jsx
<util.LeftPaneContext theme={util.theme}>
<div>
<ConversationListItem
id="conversationId1"
isAccepted={false}
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
unreadCount={4}
lastUpdated={Date.now() - 5 * 60 * 1000}
typingContact={{
name: 'Someone Here',
}}
onClick={result => console.log('onClick', result)}
i18n={util.i18n}
/>
</div>
<div>
<ConversationListItem
id="conversationId2"
isAccepted={false}
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
unreadCount={4}
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -188,7 +250,9 @@
<div>
<ConversationListItem
id="conversationId1"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
unreadCount={4}
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -200,7 +264,9 @@
/>
<ConversationListItem
id="conversationId2"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
unreadCount={10}
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -212,7 +278,9 @@
/>
<ConversationListItem
id="conversationId3"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
unreadCount={250}
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -232,7 +300,9 @@
<util.LeftPaneContext theme={util.theme}>
<ConversationListItem
id="conversationId1"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
isSelected={true}
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -254,7 +324,9 @@ We don't want Jumbomoji or links.
<div>
<ConversationListItem
id="conversationId1"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
@ -265,7 +337,9 @@ We don't want Jumbomoji or links.
/>
<ConversationListItem
id="conversationId2"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
@ -287,7 +361,9 @@ We only show one line.
<div>
<ConversationListItem
id="conversationId1"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -299,7 +375,9 @@ We only show one line.
/>
<ConversationListItem
id="conversationId2"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
@ -311,7 +389,9 @@ We only show one line.
/>
<ConversationListItem
id="conversationId3"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
@ -325,7 +405,9 @@ We only show one line.
<ConversationListItem
id="conversationId4"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
unreadCount={8}
@ -338,7 +420,9 @@ We only show one line.
/>
<ConversationListItem
id="conversationId5"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
@ -350,7 +434,9 @@ We only show one line.
/>
<ConversationListItem
id="conversationId6"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
@ -374,7 +460,9 @@ On platforms that show scrollbars all the time, this is true all the time.
<div style={{ width: '280px' }}>
<ConversationListItem
id="conversationId1"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
lastUpdated={Date.now() - 5 * 60 * 1000}
@ -386,7 +474,9 @@ On platforms that show scrollbars all the time, this is true all the time.
/>
<ConversationListItem
id="conversationId2"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
@ -407,7 +497,9 @@ On platforms that show scrollbars all the time, this is true all the time.
<div>
<ConversationListItem
id="conversationId1"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 60 * 1000}
lastMessage={{
@ -418,7 +510,9 @@ On platforms that show scrollbars all the time, this is true all the time.
/>
<ConversationListItem
id="conversationId2"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 24 * 60 * 60 * 1000}
lastMessage={{
@ -429,7 +523,9 @@ On platforms that show scrollbars all the time, this is true all the time.
/>
<ConversationListItem
id="conversationId3"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 7 * 24 * 60 * 60 * 1000}
lastMessage={{
@ -440,7 +536,9 @@ On platforms that show scrollbars all the time, this is true all the time.
/>
<ConversationListItem
id="conversationId4"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 365 * 24 * 60 * 60 * 1000}
lastMessage={{
@ -460,7 +558,9 @@ On platforms that show scrollbars all the time, this is true all the time.
<div>
<ConversationListItem
id="conversationId1"
isAccepted
name="John"
title="John"
type={'direct'}
lastUpdated={null}
lastMessage={{
@ -471,7 +571,9 @@ On platforms that show scrollbars all the time, this is true all the time.
/>
<ConversationListItem
id="conversationId2"
isAccepted
name="Missing message"
title="Missing message"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
@ -482,7 +584,9 @@ On platforms that show scrollbars all the time, this is true all the time.
/>
<ConversationListItem
id="conversationId3"
isAccepted
phoneNumber="(202) 555-0011"
title="(202) 555-0011"
type={'direct'}
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{

View File

@ -26,6 +26,7 @@ export type PropsData = {
unreadCount?: number;
isSelected: boolean;
isAccepted?: boolean;
draftPreview?: string;
shouldShowDraft?: boolean;
@ -152,6 +153,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
const {
draftPreview,
i18n,
isAccepted,
lastMessage,
shouldShowDraft,
typingContact,
@ -187,7 +189,11 @@ export class ConversationListItem extends React.PureComponent<Props> {
: null
)}
>
{typingContact ? (
{!isAccepted ? (
<span className="module-conversation-list-item__message-request">
{i18n('ConversationListItem--message-request')}
</span>
) : typingContact ? (
<TypingAnimation i18n={i18n} />
) : (
<>

View File

@ -13,6 +13,7 @@ export type Props = {
membersCount?: number;
phoneNumber?: string;
onHeightChange?: () => unknown;
updateSharedGroups?: () => unknown;
} & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>;
const renderMembershipRow = ({
@ -113,6 +114,7 @@ export const ConversationHero = ({
profileName,
title,
onHeightChange,
updateSharedGroups,
}: Props) => {
const firstRenderRef = React.useRef(true);
@ -121,6 +123,11 @@ export const ConversationHero = ({
// component may have changed. The cleanup function notifies listeners of
// any potential height changes.
return () => {
// Kick off the expensive hydration of the current sharedGroupNames
if (updateSharedGroups) {
updateSharedGroups();
}
if (onHeightChange && !firstRenderRef.current) {
onHeightChange();
} else {
@ -135,7 +142,7 @@ export const ConversationHero = ({
`mc-${membersCount}`,
`n-${name}`,
`pn-${profileName}`,
...sharedGroupNames.map(g => `g-${g}`),
sharedGroupNames.map(g => `g-${g}`).join(' '),
]);
const phoneNumberOnly = Boolean(

View File

@ -50,7 +50,11 @@ type PropsHousekeepingType = {
actions: Object
) => JSX.Element;
renderLastSeenIndicator: (id: string) => JSX.Element;
renderHeroRow: (id: string, resizeHeroRow: () => unknown) => JSX.Element;
renderHeroRow: (
id: string,
resizeHeroRow: () => unknown,
updateSharedGroups: () => unknown
) => JSX.Element;
renderLoadingRow: (id: string) => JSX.Element;
renderTypingBubble: (id: string) => JSX.Element;
};
@ -70,6 +74,7 @@ type PropsActionsType = {
markMessageRead: (messageId: string) => unknown;
selectMessage: (messageId: string, conversationId: string) => unknown;
clearSelectedMessage: () => unknown;
updateSharedGroups: () => unknown;
} & MessageActionsType &
SafetyNumberActionsType;
@ -510,6 +515,7 @@ export class Timeline extends React.PureComponent<Props, State> {
renderLoadingRow,
renderLastSeenIndicator,
renderTypingBubble,
updateSharedGroups,
} = this.props;
const styleWithWidth = {
@ -524,7 +530,7 @@ export class Timeline extends React.PureComponent<Props, State> {
if (haveOldest && row === 0) {
rowContents = (
<div data-row={row} style={styleWithWidth} role="row">
{renderHeroRow(id, this.resizeHeroRow)}
{renderHeroRow(id, this.resizeHeroRow, updateSharedGroups)}
</div>
);
} else if (!haveOldest && row === 0) {

1
ts/model-types.d.ts vendored
View File

@ -85,7 +85,6 @@ declare class ConversationModelType extends Backbone.Model<
cleanup(): Promise<void>;
disableProfileSharing(): void;
dropProfileKey(): Promise<void>;
generateProps(): void;
getAccepted(): boolean;
getAvatarPath(): string | undefined;
getColor(): ColorType | undefined;

View File

@ -156,6 +156,8 @@ const dataInterface: ClientInterface = {
getTapToViewMessagesNeedingErase,
getOlderMessagesByConversation,
getNewerMessagesByConversation,
getLastConversationActivity,
getLastConversationPreview,
getMessageMetricsForConversation,
migrateConversationMessages,
@ -1022,6 +1024,32 @@ async function getNewerMessagesByConversation(
return new MessageCollection(handleMessageJSON(messages));
}
async function getLastConversationActivity(
conversationId: string,
options: {
Message: typeof MessageModelType;
}
): Promise<MessageModelType | undefined> {
const { Message } = options;
const result = await channels.getLastConversationActivity(conversationId);
if (result) {
return new Message(result);
}
return;
}
async function getLastConversationPreview(
conversationId: string,
options: {
Message: typeof MessageModelType;
}
): Promise<MessageModelType | undefined> {
const { Message } = options;
const result = await channels.getLastConversationPreview(conversationId);
if (result) {
return new Message(result);
}
return;
}
async function getMessageMetricsForConversation(conversationId: string) {
const result = await channels.getMessageMetricsForConversation(
conversationId

View File

@ -210,6 +210,12 @@ export type ServerInterface = DataInterface & {
conversationId: string,
options?: { limit?: number; receivedAt?: number }
) => Promise<Array<MessageTypeUnhydrated>>;
getLastConversationActivity: (
conversationId: string
) => Promise<MessageType | undefined>;
getLastConversationPreview: (
conversationId: string
) => Promise<MessageType | undefined>;
getNextExpiringMessage: () => Promise<MessageType>;
getNextTapToViewMessageToAgeOut: () => Promise<MessageType>;
getOutgoingWithoutExpiresAt: () => Promise<Array<MessageType>>;
@ -308,6 +314,18 @@ export type ClientInterface = DataInterface & {
MessageCollection: typeof MessageModelCollectionType;
}
) => Promise<MessageModelCollectionType>;
getLastConversationActivity: (
conversationId: string,
options: {
Message: typeof MessageModelType;
}
) => Promise<MessageModelType | undefined>;
getLastConversationPreview: (
conversationId: string,
options: {
Message: typeof MessageModelType;
}
) => Promise<MessageModelType | undefined>;
getNextExpiringMessage: ({
Message,
}: {

View File

@ -132,6 +132,8 @@ const dataInterface: ServerInterface = {
getOlderMessagesByConversation,
getNewerMessagesByConversation,
getMessageMetricsForConversation,
getLastConversationActivity,
getLastConversationPreview,
migrateConversationMessages,
getUnprocessedCount,
@ -2749,6 +2751,50 @@ async function getNewestMessageForConversation(conversationId: string) {
return row;
}
async function getLastConversationActivity(
conversationId: string
): Promise<MessageType | null> {
const db = getInstance();
const row = await db.get(
`SELECT * FROM messages WHERE
conversationId = $conversationId AND
type NOT IN ('profile-change', 'verified-change', 'message-history-unsynced') AND
json_extract(json, '$.expirationTimerUpdate.fromSync') != true
ORDER BY received_at DESC
LIMIT 1;`,
{
$conversationId: conversationId,
}
);
if (!row) {
return null;
}
return jsonToObject(row.json);
}
async function getLastConversationPreview(
conversationId: string
): Promise<MessageType | null> {
const db = getInstance();
const row = await db.get(
`SELECT * FROM messages WHERE
conversationId = $conversationId AND
type NOT IN ('profile-change', 'verified-change', 'message-history-unsynced')
ORDER BY received_at DESC
LIMIT 1;`,
{
$conversationId: conversationId,
}
);
if (!row) {
return null;
}
return jsonToObject(row.json);
}
async function getOldestUnreadMessageForConversation(conversationId: string) {
const db = getInstance();
const row = await db.get(

View File

@ -68,8 +68,18 @@ function renderEmojiPicker({
function renderLastSeenIndicator(id: string): JSX.Element {
return <FilteredSmartLastSeenIndicator id={id} />;
}
function renderHeroRow(id: string, onHeightChange: () => unknown): JSX.Element {
return <FilteredSmartHeroRow id={id} onHeightChange={onHeightChange} />;
function renderHeroRow(
id: string,
onHeightChange: () => unknown,
updateSharedGroups: () => unknown
): JSX.Element {
return (
<FilteredSmartHeroRow
id={id}
onHeightChange={onHeightChange}
updateSharedGroups={updateSharedGroups}
/>
);
}
function renderLoadingRow(id: string): JSX.Element {
return <FilteredSmartTimelineLoadingRow id={id} />;

View File

@ -1,152 +0,0 @@
import { assert } from 'chai';
import * as Conversation from '../../types/Conversation';
import {
IncomingMessage,
MessageHistoryUnsyncedMessage,
OutgoingMessage,
ProfileChangeNotificationMessage,
VerifiedChangeMessage,
} from '../../types/Message';
describe('Conversation', () => {
describe('createLastMessageUpdate', () => {
it('should reset last message if conversation has no messages', () => {
const input = {};
const expected = {
lastMessage: '',
lastMessageStatus: null,
timestamp: null,
};
const actual = Conversation.createLastMessageUpdate(input);
assert.deepEqual(actual, expected);
});
context('for regular message', () => {
it('should update last message text and timestamp', () => {
const input = {
currentTimestamp: 555,
lastMessageStatus: 'read',
lastMessage: {
type: 'outgoing',
conversationId: 'foo',
sent_at: 666,
timestamp: 666,
} as OutgoingMessage,
lastMessageNotificationText: 'New outgoing message',
};
const expected = {
lastMessage: 'New outgoing message',
lastMessageStatus: 'read',
lastMessageDeletedForEveryone: undefined,
timestamp: 666,
};
const actual = Conversation.createLastMessageUpdate(input);
assert.deepEqual(actual, expected);
});
});
context('for message history unsynced message', () => {
it('should skip update', () => {
const input = {
currentTimestamp: 555,
lastMessage: {
type: 'message-history-unsynced',
conversationId: 'foo',
sent_at: 666,
timestamp: 666,
} as MessageHistoryUnsyncedMessage,
lastMessageNotificationText: 'xoxoxoxo',
};
const expected = {
lastMessage: 'xoxoxoxo',
lastMessageStatus: null,
lastMessageDeletedForEveryone: undefined,
timestamp: 555,
};
const actual = Conversation.createLastMessageUpdate(input);
assert.deepEqual(actual, expected);
});
});
context('for verified change message', () => {
it('should skip update', () => {
const input = {
currentTimestamp: 555,
lastMessage: {
type: 'verified-change',
conversationId: 'foo',
sent_at: 666,
timestamp: 666,
} as VerifiedChangeMessage,
lastMessageNotificationText: 'Verified Changed',
};
const expected = {
lastMessage: '',
lastMessageStatus: null,
lastMessageDeletedForEveryone: undefined,
timestamp: 555,
};
const actual = Conversation.createLastMessageUpdate(input);
assert.deepEqual(actual, expected);
});
});
context('for expire timer update from sync', () => {
it('should update message but not timestamp (to prevent bump to top)', () => {
const input = {
currentTimestamp: 555,
lastMessage: {
type: 'incoming',
conversationId: 'foo',
sent_at: 666,
timestamp: 666,
expirationTimerUpdate: {
expireTimer: 111,
fromSync: true,
source: '+12223334455',
},
} as IncomingMessage,
lastMessageNotificationText: 'Last message before expired',
};
const expected = {
lastMessage: 'Last message before expired',
lastMessageStatus: null,
lastMessageDeletedForEveryone: undefined,
timestamp: 555,
};
const actual = Conversation.createLastMessageUpdate(input);
assert.deepEqual(actual, expected);
});
});
context('for profile change message', () => {
it('should update message but not timestamp (to prevent bump to top)', () => {
const input = {
currentTimestamp: 555,
lastMessage: {
type: 'profile-change',
conversationId: 'foo',
sent_at: 666,
timestamp: 666,
} as ProfileChangeNotificationMessage,
lastMessageNotificationText: 'John changed their profile name',
};
const expected = {
lastMessage: 'John changed their profile name',
lastMessageStatus: null,
lastMessageDeletedForEveryone: undefined,
timestamp: 555,
};
const actual = Conversation.createLastMessageUpdate(input);
assert.deepEqual(actual, expected);
});
});
});
});

View File

@ -1401,7 +1401,7 @@ class MessageReceiverInner extends EventTarget {
}
const to = sentMessage.message.group
? `group(${sentMessage.message.group.id.toBinary()})`
: sentMessage.destination;
: sentMessage.destination || sentMessage.destinationUuid;
window.log.info(
'sent message to',

View File

@ -1,58 +0,0 @@
import { Message } from './Message';
interface ConversationLastMessageUpdate {
lastMessage: string;
lastMessageStatus: string | null;
timestamp: number | null;
lastMessageDeletedForEveryone?: boolean;
}
export const createLastMessageUpdate = ({
currentTimestamp,
lastMessage,
lastMessageStatus,
lastMessageNotificationText,
}: {
currentTimestamp?: number;
lastMessage?: Message;
lastMessageStatus?: string;
lastMessageNotificationText?: string;
}): ConversationLastMessageUpdate => {
if (!lastMessage) {
return {
lastMessage: '',
lastMessageStatus: null,
timestamp: null,
};
}
const { type, expirationTimerUpdate, deletedForEveryone } = lastMessage;
const isMessageHistoryUnsynced = type === 'message-history-unsynced';
const isProfileChangedMessage = type === 'profile-change';
const isVerifiedChangeMessage = type === 'verified-change';
const isExpireTimerUpdateFromSync = Boolean(
expirationTimerUpdate && expirationTimerUpdate.fromSync
);
const shouldUpdateTimestamp = Boolean(
!isMessageHistoryUnsynced &&
!isProfileChangedMessage &&
!isVerifiedChangeMessage &&
!isExpireTimerUpdateFromSync
);
const newTimestamp = shouldUpdateTimestamp
? lastMessage.sent_at
: currentTimestamp;
const shouldUpdateLastMessageText = !isVerifiedChangeMessage;
const newLastMessageText = shouldUpdateLastMessageText
? lastMessageNotificationText
: '';
return {
lastMessage: deletedForEveryone ? '' : newLastMessageText || '',
lastMessageStatus: lastMessageStatus || null,
timestamp: newTimestamp || null,
lastMessageDeletedForEveryone: deletedForEveryone,
};
};

View File

@ -207,7 +207,7 @@
"rule": "jQuery-wrap(",
"path": "js/models/conversations.js",
"line": " await wrap(",
"lineNumber": 671,
"lineNumber": 665,
"reasonCategory": "falseMatch",
"updated": "2020-06-09T20:26:46.515Z"
},