Support for message retry requests

This commit is contained in:
Scott Nonnenberg 2021-05-28 12:11:19 -07:00 committed by GitHub
parent 28f016ce48
commit ee513a1965
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1996 additions and 359 deletions

View file

@ -70,8 +70,8 @@ export class OutgoingMessageError extends ReplayableError {
// Note: Data to resend message is no longer captured
constructor(
incomingIdentifier: string,
_m: ArrayBuffer,
_t: number,
_m: unknown,
_t: unknown,
httpError?: Error
) {
const identifier = incomingIdentifier.split('.')[0];

View file

@ -13,9 +13,12 @@
import { isNumber, map, omit, noop } from 'lodash';
import PQueue from 'p-queue';
import { v4 as getGuid } from 'uuid';
import { z } from 'zod';
import {
DecryptionErrorMessage,
groupDecrypt,
PlaintextContent,
PreKeySignalMessage,
processSenderKeyDistributionMessage,
ProtocolAddress,
@ -73,7 +76,30 @@ const GROUPV1_ID_LENGTH = 16;
const GROUPV2_ID_LENGTH = 32;
const RETRY_TIMEOUT = 2 * 60 * 1000;
type SessionResetsType = Record<string, number>;
const decryptionErrorTypeSchema = z
.object({
cipherTextBytes: z.instanceof(ArrayBuffer).optional(),
cipherTextType: z.number().optional(),
contentHint: z.number().optional(),
groupId: z.string().optional(),
receivedAtCounter: z.number(),
receivedAtDate: z.number(),
senderDevice: z.number(),
senderUuid: z.string(),
timestamp: z.number(),
})
.passthrough();
export type DecryptionErrorType = z.infer<typeof decryptionErrorTypeSchema>;
const retryRequestTypeSchema = z
.object({
requesterUuid: z.string(),
requesterDevice: z.number(),
senderDevice: z.number(),
sentAt: z.number(),
})
.passthrough();
export type RetryRequestType = z.infer<typeof retryRequestTypeSchema>;
declare global {
// We want to extend `Event`, so we need an interface.
@ -107,6 +133,8 @@ declare global {
timestamp?: any;
typing?: any;
verified?: any;
retryRequest?: RetryRequestType;
decryptionError?: DecryptionErrorType;
}
// We want to extend `Error`, so we need an interface.
// eslint-disable-next-line no-restricted-syntax
@ -261,8 +289,6 @@ class MessageReceiverInner extends EventTarget {
maxSize: 30,
processBatch: this.cacheRemoveBatch.bind(this),
});
this.cleanupSessionResets();
}
static stringToArrayBuffer = (string: string): ArrayBuffer =>
@ -1122,7 +1148,14 @@ class MessageReceiverInner extends EventTarget {
ArrayBuffer | { isMe: boolean } | { isBlocked: boolean } | undefined
>;
if (envelope.type === envelopeTypeEnum.CIPHERTEXT) {
if (envelope.type === envelopeTypeEnum.PLAINTEXT_CONTENT) {
const buffer = Buffer.from(ciphertext.toArrayBuffer());
const plaintextContent = PlaintextContent.deserialize(buffer);
promise = Promise.resolve(
this.unpad(typedArrayToArrayBuffer(plaintextContent.body()))
);
} else if (envelope.type === envelopeTypeEnum.CIPHERTEXT) {
window.log.info('message from', this.getEnvelopeId(envelope));
if (!identifier) {
throw new Error(
@ -1215,6 +1248,13 @@ class MessageReceiverInner extends EventTarget {
originalSource || originalSourceUuid
);
// eslint-disable-next-line no-param-reassign
envelope.contentHint = messageContent.contentHint();
// eslint-disable-next-line no-param-reassign
envelope.groupId = messageContent.groupId()?.toString('base64');
// eslint-disable-next-line no-param-reassign
envelope.usmc = messageContent;
if (
(envelope.source && this.isBlocked(envelope.source)) ||
(envelope.sourceUuid && this.isUuidBlocked(envelope.sourceUuid))
@ -1231,6 +1271,17 @@ class MessageReceiverInner extends EventTarget {
);
}
if (
messageContent.msgType() ===
unidentifiedSenderTypeEnum.PLAINTEXT_CONTENT
) {
const plaintextContent = PlaintextContent.deserialize(
messageContent.contents()
);
return plaintextContent.body();
}
if (
messageContent.msgType() ===
unidentifiedSenderTypeEnum.SENDERKEY_MESSAGE
@ -1345,10 +1396,26 @@ class MessageReceiverInner extends EventTarget {
}
if (uuid && deviceId) {
// It is safe (from deadlocks) to await this call because the session
// reset is going to be scheduled on a separate p-queue in
// ts/background.ts
await this.lightSessionReset(uuid, deviceId);
const event = new Event('decryption-error');
event.decryptionError = {
cipherTextBytes: envelope.usmc
? typedArrayToArrayBuffer(envelope.usmc.contents())
: undefined,
cipherTextType: envelope.usmc ? envelope.usmc.msgType() : undefined,
contentHint: envelope.contentHint,
groupId: envelope.groupId,
receivedAtCounter: envelope.receivedAtCounter,
receivedAtDate: envelope.receivedAtDate,
senderDevice: deviceId,
senderUuid: uuid,
timestamp: envelope.timestamp.toNumber(),
};
// Avoid deadlocks by scheduling processing on decrypted queue
this.addToQueue(
() => this.dispatchAndWait(event),
TaskType.Decrypted
);
} else {
const envelopeId = this.getEnvelopeId(envelope);
window.log.error(
@ -1360,40 +1427,6 @@ class MessageReceiverInner extends EventTarget {
});
}
isOverHourIntoPast(timestamp: number): boolean {
const HOUR = 1000 * 60 * 60;
const now = Date.now();
const oneHourIntoPast = now - HOUR;
return isNumber(timestamp) && timestamp <= oneHourIntoPast;
}
// We don't lose anything if we delete keys over an hour into the past, because we only
// change our behavior if the timestamps stored are less than an hour ago.
cleanupSessionResets(): void {
const sessionResets = window.storage.get(
'sessionResets',
{}
) as SessionResetsType;
const keys = Object.keys(sessionResets);
keys.forEach(key => {
const timestamp = sessionResets[key];
if (!timestamp || this.isOverHourIntoPast(timestamp)) {
delete sessionResets[key];
}
});
window.storage.put('sessionResets', sessionResets);
}
async lightSessionReset(uuid: string, deviceId: number): Promise<void> {
const event = new Event('light-session-reset');
event.senderUuid = uuid;
event.senderDevice = deviceId;
await this.dispatchAndWait(event);
}
async handleSentMessage(
envelope: EnvelopeClass,
sentContainer: SyncMessageClass.Sent
@ -1630,7 +1663,10 @@ class MessageReceiverInner extends EventTarget {
// make sure to process it first. If that fails, we still try to process
// the rest of the message.
try {
if (content.senderKeyDistributionMessage) {
if (
content.senderKeyDistributionMessage &&
!isByteBufferEmpty(content.senderKeyDistributionMessage)
) {
await this.handleSenderKeyDistributionMessage(
envelope,
content.senderKeyDistributionMessage
@ -1643,6 +1679,16 @@ class MessageReceiverInner extends EventTarget {
);
}
if (
content.decryptionErrorMessage &&
!isByteBufferEmpty(content.decryptionErrorMessage)
) {
await this.handleDecryptionError(
envelope,
content.decryptionErrorMessage
);
return;
}
if (content.syncMessage) {
await this.handleSyncMessage(envelope, content.syncMessage);
return;
@ -1675,6 +1721,34 @@ class MessageReceiverInner extends EventTarget {
}
}
async handleDecryptionError(
envelope: EnvelopeClass,
decryptionError: ByteBufferClass
) {
const envelopeId = this.getEnvelopeId(envelope);
window.log.info(`handleDecryptionError: ${envelopeId}`);
const buffer = Buffer.from(decryptionError.toArrayBuffer());
const request = DecryptionErrorMessage.deserialize(buffer);
this.removeFromCache(envelope);
const { sourceUuid, sourceDevice } = envelope;
if (!sourceUuid || !sourceDevice) {
window.log.error('handleDecryptionError: Missing uuid or device!');
return;
}
const event = new Event('retry-request');
event.retryRequest = {
sentAt: request.timestamp(),
requesterUuid: sourceUuid,
requesterDevice: sourceDevice,
senderDevice: request.deviceId(),
};
await this.dispatchAndWait(event);
}
async handleSenderKeyDistributionMessage(
envelope: EnvelopeClass,
distributionMessage: ByteBufferClass
@ -2603,10 +2677,6 @@ export default class MessageReceiver {
this.stopProcessing = inner.stopProcessing.bind(inner);
this.unregisterBatchers = inner.unregisterBatchers.bind(inner);
// For tests
this.isOverHourIntoPast = inner.isOverHourIntoPast.bind(inner);
this.cleanupSessionResets = inner.cleanupSessionResets.bind(inner);
inner.connect();
this.getProcessedCount = () => inner.processedCount;
}
@ -2629,10 +2699,6 @@ export default class MessageReceiver {
unregisterBatchers: () => void;
isOverHourIntoPast: (timestamp: number) => boolean;
cleanupSessionResets: () => void;
getProcessedCount: () => number;
static stringToArrayBuffer = MessageReceiverInner.stringToArrayBuffer;

View file

@ -13,10 +13,13 @@ import { reject } from 'lodash';
import { z } from 'zod';
import {
CiphertextMessageType,
CiphertextMessage,
PlaintextContent,
ProtocolAddress,
sealedSenderEncryptMessage,
sealedSenderEncrypt,
SenderCertificate,
signalEncrypt,
UnidentifiedSenderMessageContent,
} from '@signalapp/signal-client';
import { WebAPIType } from './WebAPI';
@ -73,6 +76,9 @@ function ciphertextMessageTypeToEnvelopeType(type: number) {
if (type === CiphertextMessageType.Whisper) {
return window.textsecure.protobuf.Envelope.Type.CIPHERTEXT;
}
if (type === CiphertextMessageType.Plaintext) {
return window.textsecure.protobuf.Envelope.Type.PLAINTEXT_CONTENT;
}
throw new Error(
`ciphertextMessageTypeToEnvelopeType: Unrecognized type ${type}`
);
@ -106,12 +112,10 @@ export default class OutgoingMessage {
identifiers: Array<string>;
message: ContentClass;
message: ContentClass | PlaintextContent;
callback: (result: CallbackResultType) => void;
silent?: boolean;
plaintext?: Uint8Array;
identifiersCompleted: number;
@ -128,12 +132,17 @@ export default class OutgoingMessage {
online?: boolean;
groupId?: string;
contentHint: number;
constructor(
server: WebAPIType,
timestamp: number,
identifiers: Array<string>,
message: ContentClass | DataMessageClass,
silent: boolean | undefined,
message: ContentClass | DataMessageClass | PlaintextContent,
contentHint: number,
groupId: string | undefined,
callback: (result: CallbackResultType) => void,
options: OutgoingMessageOptionsType = {}
) {
@ -149,8 +158,9 @@ export default class OutgoingMessage {
this.server = server;
this.timestamp = timestamp;
this.identifiers = identifiers;
this.contentHint = contentHint;
this.groupId = groupId;
this.callback = callback;
this.silent = silent;
this.identifiersCompleted = 0;
this.errors = [];
@ -186,12 +196,7 @@ export default class OutgoingMessage {
if (error && error.code === 428) {
error = new SendMessageChallengeError(identifier, error);
} else {
error = new OutgoingMessageError(
identifier,
this.message.toArrayBuffer(),
this.timestamp,
error
);
error = new OutgoingMessageError(identifier, null, null, error);
}
}
@ -246,7 +251,6 @@ export default class OutgoingMessage {
} catch (error) {
if (error?.message?.includes('untrusted identity for address')) {
error.timestamp = this.timestamp;
error.originalMessage = this.message.toArrayBuffer();
}
throw error;
}
@ -265,7 +269,6 @@ export default class OutgoingMessage {
identifier,
jsonData,
timestamp,
this.silent,
this.online,
{ accessKey }
);
@ -274,7 +277,6 @@ export default class OutgoingMessage {
identifier,
jsonData,
timestamp,
this.silent,
this.online
);
}
@ -299,18 +301,45 @@ export default class OutgoingMessage {
getPlaintext(): ArrayBuffer {
if (!this.plaintext) {
this.plaintext = padMessage(this.message.toArrayBuffer());
const { message } = this;
if (message instanceof window.textsecure.protobuf.Content) {
this.plaintext = padMessage(message.toArrayBuffer());
} else {
this.plaintext = message.serialize();
}
}
return this.plaintext;
}
async getCiphertextMessage({
identityKeyStore,
protocolAddress,
sessionStore,
}: {
identityKeyStore: IdentityKeys;
protocolAddress: ProtocolAddress;
sessionStore: Sessions;
}): Promise<CiphertextMessage> {
const { message } = this;
if (message instanceof window.textsecure.protobuf.Content) {
return signalEncrypt(
Buffer.from(this.getPlaintext()),
protocolAddress,
sessionStore,
identityKeyStore
);
}
return message.asCiphertextMessage();
}
async doSendMessage(
identifier: string,
deviceIds: Array<number>,
recurse?: boolean
): Promise<void> {
const plaintext = this.getPlaintext();
const { sendMetadata } = this;
const { accessKey, senderCertificate } = sendMetadata?.[identifier] || {};
@ -364,15 +393,29 @@ export default class OutgoingMessage {
const destinationRegistrationId = activeSession.remoteRegistrationId();
if (sealedSender && senderCertificate) {
const ciphertextMessage = await this.getCiphertextMessage({
identityKeyStore,
protocolAddress,
sessionStore,
});
const certificate = SenderCertificate.deserialize(
Buffer.from(senderCertificate.serialized)
);
const groupIdBuffer = this.groupId
? Buffer.from(this.groupId, 'base64')
: null;
const buffer = await sealedSenderEncryptMessage(
Buffer.from(plaintext),
protocolAddress,
const content = UnidentifiedSenderMessageContent.new(
ciphertextMessage,
certificate,
sessionStore,
this.contentHint,
groupIdBuffer
);
const buffer = await sealedSenderEncrypt(
content,
protocolAddress,
identityKeyStore
);
@ -385,12 +428,11 @@ export default class OutgoingMessage {
};
}
const ciphertextMessage = await signalEncrypt(
Buffer.from(plaintext),
const ciphertextMessage = await this.getCiphertextMessage({
identityKeyStore,
protocolAddress,
sessionStore,
identityKeyStore
);
});
const type = ciphertextMessageTypeToEnvelopeType(
ciphertextMessage.type()
);
@ -487,8 +529,6 @@ export default class OutgoingMessage {
if (error?.message?.includes('untrusted identity for address')) {
// eslint-disable-next-line no-param-reassign
error.timestamp = this.timestamp;
// eslint-disable-next-line no-param-reassign
error.originalMessage = this.message.toArrayBuffer();
window.log.error(
'Got "key changed" error from encrypt - no identityKey for application layer',
identifier,

View file

@ -12,6 +12,7 @@ import { Dictionary } from 'lodash';
import PQueue from 'p-queue';
import { AbortSignal } from 'abort-controller';
import {
PlaintextContent,
ProtocolAddress,
SenderKeyDistributionMessage,
} from '@signalapp/signal-client';
@ -795,10 +796,11 @@ export default class MessageSender {
async sendMessage(
attrs: MessageOptionsType,
contentHint: number,
groupId: string | undefined,
options?: SendOptionsType
): Promise<CallbackResultType> {
const message = new Message(attrs);
const silent = false;
return Promise.all([
this.uploadAttachments(message),
@ -812,6 +814,8 @@ export default class MessageSender {
message.timestamp,
message.recipients || [],
message.toProto(),
contentHint,
groupId,
(res: CallbackResultType) => {
res.dataMessage = message.toArrayBuffer();
if (res.errors && res.errors.length > 0) {
@ -820,7 +824,6 @@ export default class MessageSender {
resolve(res);
}
},
silent,
options
);
})
@ -830,9 +833,10 @@ export default class MessageSender {
sendMessageProto(
timestamp: number,
recipients: Array<string>,
messageProto: ContentClass | DataMessageClass,
messageProto: ContentClass | DataMessageClass | PlaintextContent,
contentHint: number,
groupId: string | undefined,
callback: (result: CallbackResultType) => void,
silent?: boolean,
options?: SendOptionsType
): void {
const rejections = window.textsecure.storage.get(
@ -848,7 +852,8 @@ export default class MessageSender {
timestamp,
recipients,
messageProto,
silent,
contentHint,
groupId,
callback,
options
);
@ -863,8 +868,9 @@ export default class MessageSender {
async sendMessageProtoAndWait(
timestamp: number,
identifiers: Array<string>,
messageProto: DataMessageClass,
silent?: boolean,
messageProto: ContentClass | DataMessageClass | PlaintextContent,
contentHint: number,
groupId: string | undefined,
options?: SendOptionsType
): Promise<CallbackResultType> {
return new Promise((resolve, reject) => {
@ -881,8 +887,9 @@ export default class MessageSender {
timestamp,
identifiers,
messageProto,
contentHint,
groupId,
callback,
silent,
options
);
});
@ -890,9 +897,9 @@ export default class MessageSender {
async sendIndividualProto(
identifier: string,
proto: DataMessageClass | ContentClass,
proto: DataMessageClass | ContentClass | PlaintextContent,
timestamp: number,
silent?: boolean,
contentHint: number,
options?: SendOptionsType
): Promise<CallbackResultType> {
return new Promise((resolve, reject) => {
@ -907,13 +914,16 @@ export default class MessageSender {
timestamp,
[identifier],
proto,
contentHint,
undefined, // groupId
callback,
silent,
options
);
});
}
// You might wonder why this takes a groupId. models/messages.resend() can send a group
// message to just one person.
async sendMessageToIdentifier(
identifier: string,
messageText: string | undefined,
@ -925,6 +935,8 @@ export default class MessageSender {
deletedForEveryoneTimestamp: number | undefined,
timestamp: number,
expireTimer: number | undefined,
contentHint: number,
groupId: string | undefined,
profileKey?: ArrayBuffer,
options?: SendOptionsType
): Promise<CallbackResultType> {
@ -942,6 +954,8 @@ export default class MessageSender {
expireTimer,
profileKey,
},
contentHint,
groupId,
options
);
}
@ -1018,12 +1032,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
timestamp,
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1043,12 +1060,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1071,12 +1091,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1098,12 +1121,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1127,12 +1153,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1160,12 +1189,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
await this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1189,12 +1221,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
await this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1224,12 +1259,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1261,12 +1299,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1299,12 +1340,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
sendOptions
);
}
@ -1344,12 +1388,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.syncMessage = syncMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1397,12 +1444,15 @@ export default class MessageSender {
const secondMessage = new window.textsecure.protobuf.Content();
secondMessage.syncMessage = syncMessage;
const innerSilent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
myUuid || myNumber,
secondMessage,
now,
innerSilent,
ContentHint.SUPPLEMENTARY,
options
);
});
@ -1416,6 +1466,10 @@ export default class MessageSender {
sendOptions: SendOptionsType,
groupId?: string
): Promise<CallbackResultType> {
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendMessage(
{
recipients,
@ -1431,6 +1485,8 @@ export default class MessageSender {
}
: {}),
},
ContentHint.SUPPLEMENTARY,
undefined, // groupId
sendOptions
);
}
@ -1446,13 +1502,16 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.callingMessage = callingMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
await this.sendMessageProtoAndWait(
finalTimestamp,
recipients,
contentMessage,
silent,
ContentHint.SUPPLEMENTARY,
undefined, // groupId
sendOptions
);
}
@ -1481,12 +1540,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.receiptMessage = receiptMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
recipientUuid || recipientE164,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1504,12 +1566,15 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.receiptMessage = receiptMessage;
const silent = true;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto(
senderUuid || senderE164,
contentMessage,
Date.now(),
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1534,14 +1599,17 @@ export default class MessageSender {
const contentMessage = new window.textsecure.protobuf.Content();
contentMessage.nullMessage = nullMessage;
// We want the NullMessage to look like a normal outgoing message; not silent
const silent = false;
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
// We want the NullMessage to look like a normal outgoing message
const timestamp = Date.now();
return this.sendIndividualProto(
identifier,
contentMessage,
timestamp,
silent,
ContentHint.SUPPLEMENTARY,
options
);
}
@ -1555,7 +1623,6 @@ export default class MessageSender {
CallbackResultType | void | Array<CallbackResultType | void | Array<void>>
> {
window.log.info('resetSession: start');
const silent = false;
const proto = new window.textsecure.protobuf.DataMessage();
proto.body = 'TERMINATE';
proto.flags = window.textsecure.protobuf.DataMessage.Flags.END_SESSION;
@ -1568,6 +1635,10 @@ export default class MessageSender {
throw error;
};
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
const sendToContactPromise = window.textsecure.storage.protocol
.archiveAllSessions(identifier)
.catch(logError('resetSession/archiveAllSessions1 error:'))
@ -1579,7 +1650,7 @@ export default class MessageSender {
identifier,
proto,
timestamp,
silent,
ContentHint.SUPPLEMENTARY,
options
).catch(logError('resetSession/sendToContact error:'));
})
@ -1619,6 +1690,10 @@ export default class MessageSender {
profileKey?: ArrayBuffer,
options?: SendOptionsType
): Promise<CallbackResultType> {
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendMessage(
{
recipients: [identifier],
@ -1628,6 +1703,31 @@ export default class MessageSender {
flags:
window.textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
},
ContentHint.SUPPLEMENTARY,
undefined, // groupId
options
);
}
async sendRetryRequest({
options,
plaintext,
uuid,
}: {
options?: SendOptionsType;
plaintext: PlaintextContent;
uuid: string;
}): Promise<CallbackResultType> {
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendMessageProtoAndWait(
Date.now(),
[uuid],
plaintext,
ContentHint.SUPPLEMENTARY,
undefined, // groupId
options
);
}
@ -1639,6 +1739,8 @@ export default class MessageSender {
providedIdentifiers: Array<string>,
proto: ContentClass,
timestamp = Date.now(),
contentHint: number,
groupId: string | undefined,
options?: SendOptionsType
): Promise<CallbackResultType> {
const myE164 = window.textsecure.storage.user.getNumber();
@ -1658,7 +1760,6 @@ export default class MessageSender {
}
return new Promise((resolve, reject) => {
const silent = true;
const callback = (res: CallbackResultType) => {
res.dataMessage = proto.dataMessage?.toArrayBuffer();
if (res.errors && res.errors.length > 0) {
@ -1672,21 +1773,17 @@ export default class MessageSender {
timestamp,
providedIdentifiers,
proto,
contentHint,
groupId,
callback,
silent,
options
);
});
}
// The one group send exception - a message that should never be sent via sender key
async sendSenderKeyDistributionMessage(
{
distributionId,
identifiers,
}: { distributionId: string; identifiers: Array<string> },
options?: SendOptionsType
): Promise<CallbackResultType> {
async getSenderKeyDistributionMessage(
distributionId: string
): Promise<SenderKeyDistributionMessage> {
const ourUuid = window.textsecure.storage.user.getUuid();
if (!ourUuid) {
throw new Error(
@ -1702,7 +1799,7 @@ export default class MessageSender {
const address = `${ourUuid}.${ourDeviceId}`;
const senderKeyStore = new SenderKeys();
const message = await window.textsecure.storage.protocol.enqueueSenderKeyJob(
return window.textsecure.storage.protocol.enqueueSenderKeyJob(
address,
async () =>
SenderKeyDistributionMessage.create(
@ -1711,13 +1808,40 @@ export default class MessageSender {
senderKeyStore
)
);
}
const proto = new window.textsecure.protobuf.Content();
proto.senderKeyDistributionMessage = window.dcodeIO.ByteBuffer.wrap(
typedArrayToArrayBuffer(message.serialize())
// The one group send exception - a message that should never be sent via sender key
async sendSenderKeyDistributionMessage(
{
contentHint,
distributionId,
groupId,
identifiers,
}: {
contentHint: number;
distributionId: string;
groupId: string | undefined;
identifiers: Array<string>;
},
options?: SendOptionsType
): Promise<CallbackResultType> {
const contentMessage = new window.textsecure.protobuf.Content();
const senderKeyDistributionMessage = await this.getSenderKeyDistributionMessage(
distributionId
);
contentMessage.senderKeyDistributionMessage = window.dcodeIO.ByteBuffer.wrap(
typedArrayToArrayBuffer(senderKeyDistributionMessage.serialize())
);
return this.sendGroupProto(identifiers, proto, Date.now(), options);
return this.sendGroupProto(
identifiers,
contentMessage,
Date.now(),
contentHint,
groupId,
options
);
}
// GroupV1-only functions; not to be used in the future
@ -1731,7 +1855,18 @@ export default class MessageSender {
proto.group = new window.textsecure.protobuf.GroupContext();
proto.group.id = stringToArrayBuffer(groupId);
proto.group.type = window.textsecure.protobuf.GroupContext.Type.QUIT;
return this.sendGroupProto(groupIdentifiers, proto, Date.now(), options);
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendGroupProto(
groupIdentifiers,
proto,
Date.now(),
ContentHint.SUPPLEMENTARY,
undefined, // only for GV2 ids
options
);
}
async sendExpirationTimerUpdateToGroup(
@ -1770,7 +1905,15 @@ export default class MessageSender {
});
}
return this.sendMessage(attrs, options);
const {
ContentHint,
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
return this.sendMessage(
attrs,
ContentHint.SUPPLEMENTARY,
undefined, // only for GV2 ids
options
);
}
// Simple pass-throughs

View file

@ -934,14 +934,12 @@ export type WebAPIType = {
destination: string,
messageArray: Array<MessageType>,
timestamp: number,
silent?: boolean,
online?: boolean
) => Promise<void>;
sendMessagesUnauth: (
destination: string,
messageArray: Array<MessageType>,
timestamp: number,
silent?: boolean,
online?: boolean,
options?: { accessKey?: string }
) => Promise<void>;
@ -1446,7 +1444,7 @@ export function initialize({
const capabilities: CapabilitiesUploadType = {
'gv2-3': true,
'gv1-migration': true,
senderKey: false,
senderKey: true,
};
const { accessKey } = options;
@ -1684,15 +1682,11 @@ export function initialize({
destination: string,
messageArray: Array<MessageType>,
timestamp: number,
silent?: boolean,
online?: boolean,
{ accessKey }: { accessKey?: string } = {}
) {
const jsonData: any = { messages: messageArray, timestamp };
if (silent) {
jsonData.silent = true;
}
if (online) {
jsonData.online = true;
}
@ -1712,14 +1706,10 @@ export function initialize({
destination: string,
messageArray: Array<MessageType>,
timestamp: number,
silent?: boolean,
online?: boolean
) {
const jsonData: any = { messages: messageArray, timestamp };
if (silent) {
jsonData.silent = true;
}
if (online) {
jsonData.online = true;
}