Show challenge when requested by server

This commit is contained in:
Fedor Indutny 2021-05-05 17:09:29 -07:00 committed by GitHub
parent 03c68da17d
commit 986d8a66bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 1986 additions and 128 deletions

View file

@ -3028,8 +3028,6 @@ export class ConversationModel extends window.Backbone
fromId: window.ConversationController.getOurConversationId(),
});
window.Whisper.Deletes.onDelete(deleteModel);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const destination = this.getSendTarget()!;
const recipients = this.getRecipients();
@ -3108,7 +3106,16 @@ export class ConversationModel extends window.Backbone
// anything to the database.
message.doNotSave = true;
return message.send(this.wrapSend(promise));
const result = await message.send(this.wrapSend(promise));
if (!message.hasSuccessfulDelivery()) {
// This is handled by `conversation_view` which displays a toast on
// send error.
throw new Error('No successful delivery for delete for everyone');
}
window.Whisper.Deletes.onDelete(deleteModel);
return result;
}).catch(error => {
window.log.error(
'Error sending deleteForEveryone',
@ -3138,7 +3145,6 @@ export class ConversationModel extends window.Backbone
timestamp,
fromSync: true,
});
window.Whisper.Reactions.onReaction(reactionModel);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const destination = this.getSendTarget()!;
@ -3239,15 +3245,17 @@ export class ConversationModel extends window.Backbone
);
})();
return message.send(this.wrapSend(promise));
}).catch(error => {
window.log.error('Error sending reaction', reaction, target, error);
const result = await message.send(this.wrapSend(promise));
const reverseReaction = reactionModel.clone();
reverseReaction.set('remove', !reverseReaction.get('remove'));
window.Whisper.Reactions.onReaction(reverseReaction);
if (!message.hasSuccessfulDelivery()) {
// This is handled by `conversation_view` which displays a toast on
// send error.
throw new Error('No successful delivery for reaction');
}
throw error;
window.Whisper.Reactions.onReaction(reactionModel);
return result;
});
}
@ -4167,25 +4175,17 @@ export class ConversationModel extends window.Backbone
const message = window.MessageController.register(model.id, model);
this.addSingleMessage(message);
const options = await this.getSendOptions();
message.send(
this.wrapSend(
// TODO: DESKTOP-724
// resetSession returns `Array<void>` which is incompatible with the
// expected promise return values. `[]` is truthy and wrapSend assumes
// it's a valid callback result type
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.textsecure.messaging.resetSession(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.get('uuid')!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.get('e164')!,
now,
options
)
)
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const uuid = this.get('uuid')!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const e164 = this.get('e164')!;
message.sendUtilityMessageWithRetry({
type: 'session-reset',
uuid,
e164,
now,
});
}
}

View file

@ -4,6 +4,8 @@
import {
CustomError,
MessageAttributesType,
RetryOptions,
ShallowChallengeError,
QuotedMessageType,
WhatIsThis,
} from '../model-types.d';
@ -1041,6 +1043,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
const sentTo = this.get('sent_to') || [];
if (this.hasErrors()) {
if (this.getLastChallengeError()) {
return 'paused';
}
if (sent || sentTo.length > 0) {
return 'partial-sent';
}
@ -2000,6 +2005,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
'code',
'number',
'identifier',
'retryAfter',
'data',
'reason'
) as Required<Error>;
}
@ -2009,6 +2016,13 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
this.set({ errors });
if (
!this.doNotSave &&
errors.some(error => error.name === 'SendMessageChallengeError')
) {
await window.Signal.challengeHandler.register(this);
}
if (!skipSave && !this.doNotSave) {
await window.Signal.Data.saveMessage(this.attributes, {
Message: window.Whisper.Message,
@ -2109,7 +2123,13 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
return null;
}
this.set({ errors: undefined });
const retryOptions = this.get('retryOptions');
this.set({ errors: undefined, retryOptions: undefined });
if (retryOptions) {
return this.sendUtilityMessageWithRetry(retryOptions);
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const conversation = this.getConversation()!;
@ -2252,11 +2272,35 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
e.name === 'MessageError' ||
e.name === 'OutgoingMessageError' ||
e.name === 'SendMessageNetworkError' ||
e.name === 'SendMessageChallengeError' ||
e.name === 'SignedPreKeyRotationError' ||
e.name === 'OutgoingIdentityKeyError'
);
}
public getLastChallengeError(): ShallowChallengeError | undefined {
const errors: ReadonlyArray<CustomError> | undefined = this.get('errors');
if (!errors) {
return undefined;
}
const challengeErrors = errors
.filter((error): error is ShallowChallengeError => {
return (
error.name === 'SendMessageChallengeError' &&
_.isNumber(error.retryAfter) &&
_.isObject(error.data)
);
})
.sort((a, b) => a.retryAfter - b.retryAfter);
return challengeErrors.pop();
}
public hasSuccessfulDelivery(): boolean {
return (this.get('sent_to') || []).length !== 0;
}
canDeleteForEveryone(): boolean {
// is someone else's message
if (this.isIncoming()) {
@ -2423,6 +2467,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
(e.name === 'MessageError' ||
e.name === 'OutgoingMessageError' ||
e.name === 'SendMessageNetworkError' ||
e.name === 'SendMessageChallengeError' ||
e.name === 'SignedPreKeyRotationError' ||
e.name === 'OutgoingIdentityKeyError')
);
@ -2564,6 +2609,59 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
});
}
// Currently used only for messages that have to be retried when the server
// responds with 428 and we have to retry sending the message on challenge
// solution.
//
// Supported types of messages:
// * `session-reset` see `endSession` in `ts/models/conversations.ts`
async sendUtilityMessageWithRetry(options: RetryOptions): Promise<void> {
if (options.type === 'session-reset') {
const conv = this.getConversation();
if (!conv) {
throw new Error(
`Failed to find conversation for message: ${this.idForLogging()}`
);
}
if (!window.textsecure.messaging) {
throw new Error('Offline');
}
this.set({
retryOptions: options,
});
const sendOptions = await conv.getSendOptions();
// We don't have to check `sent_to` here, because:
//
// 1. This happens only in private conversations
// 2. Messages to different device ids for the same identifier are sent
// in a single request to the server. So partial success is not
// possible.
await this.send(
conv.wrapSend(
// TODO: DESKTOP-724
// resetSession returns `Array<void>` which is incompatible with the
// expected promise return values. `[]` is truthy and wrapSend assumes
// it's a valid callback result type
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.textsecure.messaging.resetSession(
options.uuid,
options.e164,
options.now,
sendOptions
)
)
);
return;
}
throw new Error(`Unsupported retriable type: ${options.type}`);
}
async sendSyncMessageOnly(dataMessage: ArrayBuffer): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const conv = this.getConversation()!;
@ -2590,7 +2688,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
});
} catch (result) {
const errors = (result && result.errors) || [new Error('Unknown error')];
this.set({ errors });
this.saveErrors(errors);
} finally {
await window.Signal.Data.saveMessage(this.attributes, {
Message: window.Whisper.Message,
@ -4094,6 +4192,33 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}
}
export async function getMessageById(
messageId: string
): Promise<MessageModel | undefined> {
let message = window.MessageController.getById(messageId);
if (message) {
return message;
}
try {
message = await window.Signal.Data.getMessageById(messageId, {
Message: window.Whisper.Message,
});
} catch (error) {
window.log.error(
`failed to load message with id ${messageId} ` +
`due to error ${error && error.stack}`
);
}
if (!message) {
return undefined;
}
message = window.MessageController.register(message.id, message);
return message;
}
window.Whisper.Message = MessageModel;
window.Whisper.Message.getLongMessageAttachment = ({