Receive support for DOE messages
This commit is contained in:
parent
b8a674bbb6
commit
ba5e2ff6e5
17 changed files with 291 additions and 42 deletions
|
@ -1691,6 +1691,10 @@
|
|||
"message": "View-once Video",
|
||||
"description": "Shown in notifications and in the left pane when a message is a view once video."
|
||||
},
|
||||
"message--deletedForEveryone": {
|
||||
"message": "This message was deleted.",
|
||||
"description": "Shown in a message's bubble when the message has been deleted for everyone."
|
||||
},
|
||||
"stickers--toast--InstallFailed": {
|
||||
"message": "Sticker pack could not be installed",
|
||||
"description": "Shown in a toast if the user attempts to install a sticker pack and it fails"
|
||||
|
|
|
@ -353,6 +353,7 @@
|
|||
<script type='text/javascript' src='js/read_syncs.js'></script>
|
||||
<script type='text/javascript' src='js/view_syncs.js'></script>
|
||||
<script type='text/javascript' src='js/reactions.js'></script>
|
||||
<script type='text/javascript' src='js/deletes.js'></script>
|
||||
<script type='text/javascript' src='js/libphonenumber-util.js'></script>
|
||||
<script type='text/javascript' src='js/models/messages.js'></script>
|
||||
<script type='text/javascript' src='js/models/conversations.js'></script>
|
||||
|
|
|
@ -2241,7 +2241,7 @@
|
|||
// Note: We do very little in this function, since everything in handleDataMessage is
|
||||
// inside a conversation-specific queue(). Any code here might run before an earlier
|
||||
// message is processed in handleDataMessage().
|
||||
async function onMessageReceived(event) {
|
||||
function onMessageReceived(event) {
|
||||
const { data, confirm } = event;
|
||||
|
||||
const messageDescriptor = getDescriptorForReceived(data);
|
||||
|
@ -2250,17 +2250,16 @@
|
|||
// eslint-disable-next-line no-bitwise
|
||||
const isProfileUpdate = Boolean(data.message.flags & PROFILE_KEY_UPDATE);
|
||||
if (isProfileUpdate) {
|
||||
await handleMessageReceivedProfileUpdate({
|
||||
return handleMessageReceivedProfileUpdate({
|
||||
data,
|
||||
confirm,
|
||||
messageDescriptor,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const message = await initIncomingMessage(data);
|
||||
const message = initIncomingMessage(data);
|
||||
|
||||
const result = await ConversationController.getOrCreateAndWait(
|
||||
const result = ConversationController.getOrCreate(
|
||||
messageDescriptor.id,
|
||||
messageDescriptor.type
|
||||
);
|
||||
|
@ -2286,11 +2285,26 @@
|
|||
// Note: We do not wait for completion here
|
||||
Whisper.Reactions.onReaction(reactionModel);
|
||||
confirm();
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (data.message.delete) {
|
||||
const { delete: del } = data.message;
|
||||
const deleteModel = Whisper.Deletes.add({
|
||||
targetSentTimestamp: del.targetSentTimestamp,
|
||||
serverTimestamp: data.serverTimestamp,
|
||||
fromId: data.source || data.sourceUuid,
|
||||
});
|
||||
// Note: We do not wait for completion here
|
||||
Whisper.Deletes.onDelete(deleteModel);
|
||||
confirm();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Don't wait for handleDataMessage, as it has its own per-conversation queueing
|
||||
message.handleDataMessage(data.message, event.confirm);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async function handleMessageSentProfileUpdate({
|
||||
|
@ -2341,6 +2355,7 @@
|
|||
sourceUuid: textsecure.storage.user.getUuid(),
|
||||
sourceDevice: data.device,
|
||||
sent_at: data.timestamp,
|
||||
serverTimestamp: data.serverTimestamp,
|
||||
sent_to: sentTo,
|
||||
received_at: now,
|
||||
conversationId: data.destination,
|
||||
|
@ -2357,7 +2372,7 @@
|
|||
// Note: We do very little in this function, since everything in handleDataMessage is
|
||||
// inside a conversation-specific queue(). Any code here might run before an earlier
|
||||
// message is processed in handleDataMessage().
|
||||
async function onSentMessage(event) {
|
||||
function onSentMessage(event) {
|
||||
const { data, confirm } = event;
|
||||
|
||||
const messageDescriptor = getDescriptorForSent(data);
|
||||
|
@ -2366,20 +2381,20 @@
|
|||
// eslint-disable-next-line no-bitwise
|
||||
const isProfileUpdate = Boolean(data.message.flags & PROFILE_KEY_UPDATE);
|
||||
if (isProfileUpdate) {
|
||||
await handleMessageSentProfileUpdate({
|
||||
return handleMessageSentProfileUpdate({
|
||||
data,
|
||||
confirm,
|
||||
messageDescriptor,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const message = await createSentMessage(data);
|
||||
const message = createSentMessage(data);
|
||||
|
||||
const ourNumber = textsecure.storage.user.getNumber();
|
||||
const ourUuid = textsecure.storage.user.getUuid();
|
||||
|
||||
if (data.message.reaction) {
|
||||
const { reaction } = data.message;
|
||||
const ourNumber = textsecure.storage.user.getNumber();
|
||||
const ourUuid = textsecure.storage.user.getUuid();
|
||||
const reactionModel = Whisper.Reactions.add({
|
||||
emoji: reaction.emoji,
|
||||
remove: reaction.remove,
|
||||
|
@ -2394,10 +2409,23 @@
|
|||
Whisper.Reactions.onReaction(reactionModel);
|
||||
|
||||
event.confirm();
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
await ConversationController.getOrCreateAndWait(
|
||||
if (data.message.delete) {
|
||||
const { delete: del } = data.message;
|
||||
const deleteModel = Whisper.Deletes.add({
|
||||
targetSentTimestamp: del.targetSentTimestamp,
|
||||
serverTimestamp: del.serverTimestamp,
|
||||
fromId: ourNumber || ourUuid,
|
||||
});
|
||||
// Note: We do not wait for completion here
|
||||
Whisper.Deletes.onDelete(deleteModel);
|
||||
confirm();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
ConversationController.getOrCreate(
|
||||
messageDescriptor.id,
|
||||
messageDescriptor.type
|
||||
);
|
||||
|
@ -2406,9 +2434,11 @@
|
|||
message.handleDataMessage(data.message, event.confirm, {
|
||||
data,
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async function initIncomingMessage(data) {
|
||||
function initIncomingMessage(data) {
|
||||
const targetId = data.source || data.sourceUuid;
|
||||
const conversation = ConversationController.get(targetId);
|
||||
const conversationId = conversation ? conversation.id : targetId;
|
||||
|
@ -2418,6 +2448,7 @@
|
|||
sourceUuid: data.sourceUuid,
|
||||
sourceDevice: data.sourceDevice,
|
||||
sent_at: data.timestamp,
|
||||
serverTimestamp: data.serverTimestamp,
|
||||
received_at: Date.now(),
|
||||
conversationId,
|
||||
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
|
||||
|
@ -2485,7 +2516,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function onError(ev) {
|
||||
function onError(ev) {
|
||||
const { error } = ev;
|
||||
window.log.error('background onError:', Errors.toLogFormat(error));
|
||||
|
||||
|
@ -2494,8 +2525,7 @@
|
|||
error.name === 'HTTPError' &&
|
||||
(error.code === 401 || error.code === 403)
|
||||
) {
|
||||
await unlinkAndDisconnect();
|
||||
return;
|
||||
return unlinkAndDisconnect();
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -2510,7 +2540,7 @@
|
|||
|
||||
Whisper.events.trigger('reconnectTimer');
|
||||
}
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (ev.proto) {
|
||||
|
@ -2520,13 +2550,13 @@
|
|||
}
|
||||
// Ignore this message. It is likely a duplicate delivery
|
||||
// because the server lost our ack the first time.
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
const envelope = ev.proto;
|
||||
const message = await initIncomingMessage(envelope);
|
||||
const message = initIncomingMessage(envelope);
|
||||
|
||||
const conversationId = message.get('conversationId');
|
||||
const conversation = await ConversationController.getOrCreateAndWait(
|
||||
const conversation = ConversationController.getOrCreate(
|
||||
conversationId,
|
||||
'private'
|
||||
);
|
||||
|
|
108
js/deletes.js
Normal file
108
js/deletes.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
/* global
|
||||
Backbone,
|
||||
Whisper,
|
||||
MessageController,
|
||||
ConversationController
|
||||
*/
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const ONE_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
window.Whisper = window.Whisper || {};
|
||||
Whisper.Deletes = new (Backbone.Collection.extend({
|
||||
forMessage(message) {
|
||||
const matchingDeletes = this.filter({
|
||||
targetSentTimestamp: message.get('sent_at'),
|
||||
fromId: message.getContact().get('id'),
|
||||
});
|
||||
|
||||
if (matchingDeletes.length > 0) {
|
||||
window.log.info('Found early DOE for message');
|
||||
this.remove(matchingDeletes);
|
||||
return matchingDeletes;
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
async onDelete(del) {
|
||||
try {
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(
|
||||
del.get('targetSentTimestamp'),
|
||||
{
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
}
|
||||
);
|
||||
|
||||
// The contact the delete message came from
|
||||
const fromContact = ConversationController.get(del.get('fromId'));
|
||||
|
||||
if (!fromContact) {
|
||||
window.log.info(
|
||||
'No contact for DOE',
|
||||
del.get('fromId'),
|
||||
del.get('targetSentTimestamp')
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const targetMessage = messages.find(m => {
|
||||
const messageContact = m.getContact();
|
||||
|
||||
if (!messageContact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find messages which are from the same contact who sent the DOE
|
||||
return messageContact.get('id') === fromContact.get('id');
|
||||
});
|
||||
|
||||
if (!targetMessage) {
|
||||
window.log.info(
|
||||
'No message for DOE',
|
||||
del.get('fromId'),
|
||||
del.get('targetSentTimestamp')
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the server timestamps for the DOE and the matching message
|
||||
// are less than one day apart
|
||||
const delta = Math.abs(
|
||||
del.get('serverTimestamp') - targetMessage.get('serverTimestamp')
|
||||
);
|
||||
if (delta > ONE_DAY) {
|
||||
window.log.info('Received late DOE. Dropping.', {
|
||||
fromId: del.get('fromId'),
|
||||
targetSentTimestamp: del.get('targetSentTimestamp'),
|
||||
messageServerTimestamp: message.get('serverTimestamp'),
|
||||
deleteServerTimestamp: del.get('serverTimestamp'),
|
||||
});
|
||||
this.remove(del);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const message = MessageController.register(
|
||||
targetMessage.id,
|
||||
targetMessage
|
||||
);
|
||||
|
||||
await message.handleDeleteForEveryone(del);
|
||||
|
||||
this.remove(del);
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
'Deletes.onDelete error:',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
}
|
||||
},
|
||||
}))();
|
||||
})();
|
|
@ -414,6 +414,7 @@
|
|||
lastMessage: {
|
||||
status: this.get('lastMessageStatus'),
|
||||
text: this.get('lastMessage'),
|
||||
deletedForEveryone: this.get('lastMessageDeletedForEveryone'),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -600,6 +600,8 @@
|
|||
isTapToViewExpired: isTapToView && this.get('isErased'),
|
||||
isTapToViewError:
|
||||
isTapToView && this.isIncoming() && this.get('isTapToViewInvalid'),
|
||||
|
||||
deletedForEveryone: this.get('deletedForEveryone') || false,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -1105,7 +1107,7 @@
|
|||
isErased() {
|
||||
return Boolean(this.get('isErased'));
|
||||
},
|
||||
async eraseContents() {
|
||||
async eraseContents(additionalProperties = {}) {
|
||||
if (this.get('isErased')) {
|
||||
return;
|
||||
}
|
||||
|
@ -1129,6 +1131,7 @@
|
|||
contact: [],
|
||||
sticker: null,
|
||||
preview: [],
|
||||
...additionalProperties,
|
||||
});
|
||||
this.trigger('content-changed');
|
||||
|
||||
|
@ -1442,12 +1445,17 @@
|
|||
const isOutgoing = this.get('type') === 'outgoing';
|
||||
const numDelivered = this.get('delivered');
|
||||
|
||||
// Case 1: We can reply if this is outgoing and delievered to at least one recipient
|
||||
// Case 1: We cannot reply if this message is deleted for everyone
|
||||
if (this.get('deletedForEveryone')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Case 2: We can reply if this is outgoing and delievered to at least one recipient
|
||||
if (isOutgoing && numDelivered > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Case 2: We can reply if there are no errors
|
||||
// Case 3: We can reply if there are no errors
|
||||
if (!errors || (errors && errors.length === 0)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -2457,6 +2465,11 @@
|
|||
message.handleReaction(reaction);
|
||||
});
|
||||
|
||||
// Does this message have any pending, previously-received associated
|
||||
// delete for everyone messages?
|
||||
const deletes = Whisper.Deletes.forMessage(message);
|
||||
deletes.forEach(del => Whisper.Deletes.onDelete(del));
|
||||
|
||||
Whisper.events.trigger('incrementProgress');
|
||||
confirm();
|
||||
} catch (error) {
|
||||
|
@ -2473,6 +2486,10 @@
|
|||
},
|
||||
|
||||
async handleReaction(reaction) {
|
||||
if (this.get('deletedForEveryone')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reactions = this.get('reactions') || [];
|
||||
const messageId = this.idForLogging();
|
||||
const count = reactions.length;
|
||||
|
@ -2512,6 +2529,27 @@
|
|||
Message: Whisper.Message,
|
||||
});
|
||||
},
|
||||
|
||||
async handleDeleteForEveryone(del) {
|
||||
window.log.info('Handling DOE.', {
|
||||
fromId: del.get('fromId'),
|
||||
targetSentTimestamp: del.get('targetSentTimestamp'),
|
||||
messageServerTimestamp: this.get('serverTimestamp'),
|
||||
deleteServerTimestamp: del.get('serverTimestamp'),
|
||||
});
|
||||
|
||||
// Remove any notifications for this message
|
||||
const notificationForMessage = Whisper.Notifications.findWhere({
|
||||
messageId: this.get('id'),
|
||||
});
|
||||
Whisper.Notifications.remove(notificationForMessage);
|
||||
|
||||
// Erase the contents of this message
|
||||
await this.eraseContents({ deletedForEveryone: true, reactions: [] });
|
||||
|
||||
// Update the conversation's last message in case this was the last message
|
||||
this.getConversation().updateLastMessage();
|
||||
},
|
||||
});
|
||||
|
||||
// Receive will be enabled before we enable send
|
||||
|
|
|
@ -179,6 +179,10 @@ message DataMessage {
|
|||
optional uint64 targetTimestamp = 5;
|
||||
}
|
||||
|
||||
message Delete {
|
||||
optional uint64 targetSentTimestamp = 1;
|
||||
}
|
||||
|
||||
enum ProtocolVersion {
|
||||
option allow_alias = true;
|
||||
|
||||
|
@ -206,6 +210,7 @@ message DataMessage {
|
|||
optional uint32 requiredProtocolVersion = 12;
|
||||
optional bool isViewOnce = 14;
|
||||
optional Reaction reaction = 16;
|
||||
optional Delete delete = 17;
|
||||
}
|
||||
|
||||
message NullMessage {
|
||||
|
|
|
@ -3396,9 +3396,12 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
|||
}
|
||||
}
|
||||
|
||||
.module-conversation-list-item__message__draft-prefix {
|
||||
.module-conversation-list-item__message {
|
||||
&__draft-prefix,
|
||||
&__deleted-for-everyone {
|
||||
font-style: italic;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.module-conversation-list-item__message__status-icon {
|
||||
|
@ -7869,6 +7872,10 @@ button.module-image__border-overlay:focus {
|
|||
&--with-reactions {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&--deleted-for-everyone {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
/* Spec: container > 438px and container < 593px */
|
||||
|
|
|
@ -31,6 +31,7 @@ export type PropsData = {
|
|||
lastMessage?: {
|
||||
status: 'sending' | 'sent' | 'delivered' | 'read' | 'error';
|
||||
text: string;
|
||||
deletedForEveryone?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -152,6 +153,9 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
const showingDraft = shouldShowDraft && draftPreview;
|
||||
const deletedForEveryone = Boolean(
|
||||
lastMessage && lastMessage.deletedForEveryone
|
||||
);
|
||||
|
||||
// Note: instead of re-using showingDraft here we explode it because
|
||||
// typescript can't tell that draftPreview is truthy otherwise
|
||||
|
@ -181,6 +185,10 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
|||
<span className="module-conversation-list-item__message__draft-prefix">
|
||||
{i18n('ConversationListItem--draft-prefix')}
|
||||
</span>
|
||||
) : deletedForEveryone ? (
|
||||
<span className="module-conversation-list-item__message__deleted-for-everyone">
|
||||
{i18n('message--deletedForEveryone')}
|
||||
</span>
|
||||
) : null}
|
||||
<MessageBody
|
||||
text={text.split('\n')[0]}
|
||||
|
|
|
@ -106,6 +106,8 @@ export type PropsData = {
|
|||
reactions?: ReactionViewerProps['reactions'];
|
||||
selectedReaction?: string;
|
||||
|
||||
deletedForEveryone?: boolean;
|
||||
|
||||
canReply: boolean;
|
||||
};
|
||||
|
||||
|
@ -934,10 +936,18 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public renderText() {
|
||||
const { text, textPending, i18n, direction, status } = this.props;
|
||||
const {
|
||||
deletedForEveryone,
|
||||
direction,
|
||||
i18n,
|
||||
status,
|
||||
text,
|
||||
textPending,
|
||||
} = this.props;
|
||||
|
||||
const contents =
|
||||
direction === 'incoming' && status === 'error'
|
||||
const contents = deletedForEveryone
|
||||
? i18n('message--deletedForEveryone')
|
||||
: direction === 'incoming' && status === 'error'
|
||||
? i18n('incomingError')
|
||||
: text;
|
||||
|
||||
|
@ -1677,7 +1687,11 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public renderContents() {
|
||||
const { isTapToView } = this.props;
|
||||
const { isTapToView, deletedForEveryone } = this.props;
|
||||
|
||||
if (deletedForEveryone) {
|
||||
return this.renderText();
|
||||
}
|
||||
|
||||
if (isTapToView) {
|
||||
return (
|
||||
|
@ -1863,9 +1877,11 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
this.handleOpen(event);
|
||||
};
|
||||
|
||||
// tslint:disable-next-line: cyclomatic-complexity
|
||||
public renderContainer() {
|
||||
const {
|
||||
authorColor,
|
||||
deletedForEveryone,
|
||||
direction,
|
||||
isSticker,
|
||||
isTapToView,
|
||||
|
@ -1903,6 +1919,9 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
: null,
|
||||
reactions && reactions.length > 0
|
||||
? 'module-message__container--with-reactions'
|
||||
: null,
|
||||
deletedForEveryone
|
||||
? 'module-message__container--deleted-for-everyone'
|
||||
: null
|
||||
);
|
||||
const containerStyles = {
|
||||
|
|
|
@ -88,6 +88,7 @@ export type MessageType = {
|
|||
phoneNumber?: string;
|
||||
};
|
||||
}>;
|
||||
deletedForEveryone?: boolean;
|
||||
|
||||
errors?: Array<Error>;
|
||||
group_update?: any;
|
||||
|
@ -627,6 +628,12 @@ function hasMessageHeightChanged(
|
|||
return true;
|
||||
}
|
||||
|
||||
const isDeletedForEveryone = message.deletedForEveryone;
|
||||
const wasDeletedForEveryone = previous.deletedForEveryone;
|
||||
if (isDeletedForEveryone !== wasDeletedForEveryone) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ describe('Conversation', () => {
|
|||
const expected = {
|
||||
lastMessage: 'New outgoing message',
|
||||
lastMessageStatus: 'read',
|
||||
lastMessageDeletedForEveryone: undefined,
|
||||
timestamp: 666,
|
||||
};
|
||||
|
||||
|
@ -61,6 +62,7 @@ describe('Conversation', () => {
|
|||
const expected = {
|
||||
lastMessage: 'xoxoxoxo',
|
||||
lastMessageStatus: null,
|
||||
lastMessageDeletedForEveryone: undefined,
|
||||
timestamp: 555,
|
||||
};
|
||||
|
||||
|
@ -84,6 +86,7 @@ describe('Conversation', () => {
|
|||
const expected = {
|
||||
lastMessage: '',
|
||||
lastMessageStatus: null,
|
||||
lastMessageDeletedForEveryone: undefined,
|
||||
timestamp: 555,
|
||||
};
|
||||
|
||||
|
@ -112,6 +115,7 @@ describe('Conversation', () => {
|
|||
const expected = {
|
||||
lastMessage: 'Last message before expired',
|
||||
lastMessageStatus: null,
|
||||
lastMessageDeletedForEveryone: undefined,
|
||||
timestamp: 555,
|
||||
};
|
||||
|
||||
|
|
5
ts/textsecure.d.ts
vendored
5
ts/textsecure.d.ts
vendored
|
@ -235,6 +235,7 @@ export declare class DataMessageClass {
|
|||
requiredProtocolVersion?: number;
|
||||
isViewOnce?: boolean;
|
||||
reaction?: DataMessageClass.Reaction;
|
||||
delete?: DataMessageClass.Delete;
|
||||
}
|
||||
|
||||
// Note: we need to use namespaces to express nested classes in Typescript
|
||||
|
@ -287,6 +288,10 @@ export declare namespace DataMessageClass {
|
|||
targetTimestamp?: ProtoBigNumberType;
|
||||
}
|
||||
|
||||
class Delete {
|
||||
targetSentTimestamp?: ProtoBigNumberType;
|
||||
}
|
||||
|
||||
class Sticker {
|
||||
packId?: ProtoBinaryType;
|
||||
packKey?: ProtoBinaryType;
|
||||
|
|
|
@ -1089,6 +1089,7 @@ class MessageReceiverInner extends EventTarget {
|
|||
ev.data = {
|
||||
destination,
|
||||
timestamp: timestamp.toNumber(),
|
||||
serverTimestamp: envelope.serverTimestamp,
|
||||
device: envelope.sourceDevice,
|
||||
unidentifiedStatus,
|
||||
message,
|
||||
|
@ -1159,6 +1160,7 @@ class MessageReceiverInner extends EventTarget {
|
|||
sourceUuid: envelope.sourceUuid,
|
||||
sourceDevice: envelope.sourceDevice,
|
||||
timestamp: envelope.timestamp.toNumber(),
|
||||
serverTimestamp: envelope.serverTimestamp,
|
||||
unidentifiedDeliveryReceived: envelope.unidentifiedDeliveryReceived,
|
||||
message,
|
||||
};
|
||||
|
@ -1795,6 +1797,13 @@ class MessageReceiverInner extends EventTarget {
|
|||
}
|
||||
}
|
||||
|
||||
const { delete: del } = decrypted;
|
||||
if (del) {
|
||||
if (del.targetSentTimestamp) {
|
||||
del.targetSentTimestamp = del.targetSentTimestamp.toNumber();
|
||||
}
|
||||
}
|
||||
|
||||
const groupMembers = decrypted.group ? decrypted.group.members || [] : [];
|
||||
|
||||
window.normalizeUuids(
|
||||
|
|
|
@ -4,6 +4,7 @@ interface ConversationLastMessageUpdate {
|
|||
lastMessage: string;
|
||||
lastMessageStatus: string | null;
|
||||
timestamp: number | null;
|
||||
lastMessageDeletedForEveryone?: boolean;
|
||||
}
|
||||
|
||||
export const createLastMessageUpdate = ({
|
||||
|
@ -25,7 +26,7 @@ export const createLastMessageUpdate = ({
|
|||
};
|
||||
}
|
||||
|
||||
const { type, expirationTimerUpdate } = lastMessage;
|
||||
const { type, expirationTimerUpdate, deletedForEveryone } = lastMessage;
|
||||
const isMessageHistoryUnsynced = type === 'message-history-unsynced';
|
||||
const isVerifiedChangeMessage = type === 'verified-change';
|
||||
const isExpireTimerUpdateFromSync = Boolean(
|
||||
|
@ -47,8 +48,9 @@ export const createLastMessageUpdate = ({
|
|||
: '';
|
||||
|
||||
return {
|
||||
lastMessage: newLastMessageText || '',
|
||||
lastMessage: deletedForEveryone ? '' : newLastMessageText || '',
|
||||
lastMessageStatus: lastMessageStatus || null,
|
||||
timestamp: newTimestamp || null,
|
||||
lastMessageDeletedForEveryone: deletedForEveryone,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,10 +2,11 @@ import { Attachment } from './Attachment';
|
|||
import { ContactType } from './Contact';
|
||||
import { IndexableBoolean, IndexablePresence } from './IndexedDB';
|
||||
|
||||
export type Message =
|
||||
export type Message = (
|
||||
| UserMessage
|
||||
| VerifiedChangeMessage
|
||||
| MessageHistoryUnsyncedMessage;
|
||||
| MessageHistoryUnsyncedMessage
|
||||
) & { deletedForEveryone?: boolean };
|
||||
export type UserMessage = IncomingMessage | OutgoingMessage;
|
||||
|
||||
export type IncomingMessage = Readonly<
|
||||
|
|
|
@ -11648,17 +11648,17 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.tsx",
|
||||
"line": " public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();",
|
||||
"lineNumber": 179,
|
||||
"lineNumber": 181,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-03-03T22:30:27.594Z"
|
||||
"updated": "2020-04-16T19:36:47.586Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.tsx",
|
||||
"line": " > = React.createRef();",
|
||||
"lineNumber": 183,
|
||||
"lineNumber": 185,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-03-03T22:30:27.594Z"
|
||||
"updated": "2020-04-16T19:36:47.586Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-createRef",
|
||||
|
|
Loading…
Reference in a new issue