Support for message retry requests
This commit is contained in:
parent
28f016ce48
commit
ee513a1965
37 changed files with 1996 additions and 359 deletions
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue