Send and receive PniSignatureMessage
This commit is contained in:
parent
95be24e8f7
commit
00cfd92dd0
43 changed files with 1082 additions and 164 deletions
|
@ -107,7 +107,7 @@ export default class AccountManager extends EventTarget {
|
|||
async decryptDeviceName(base64: string): Promise<string> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const identityKey =
|
||||
await window.textsecure.storage.protocol.getIdentityKeyPair(ourUuid);
|
||||
window.textsecure.storage.protocol.getIdentityKeyPair(ourUuid);
|
||||
if (!identityKey) {
|
||||
throw new Error('decryptDeviceName: No identity key pair!');
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
const { storage } = window.textsecure;
|
||||
const deviceName = storage.user.getDeviceName();
|
||||
const identityKeyPair = await storage.protocol.getIdentityKeyPair(
|
||||
const identityKeyPair = storage.protocol.getIdentityKeyPair(
|
||||
storage.user.getCheckedUuid()
|
||||
);
|
||||
strictAssert(
|
||||
|
@ -362,7 +362,7 @@ export default class AccountManager extends EventTarget {
|
|||
|
||||
let identityKey: KeyPairType | undefined;
|
||||
try {
|
||||
identityKey = await store.getIdentityKeyPair(ourUuid);
|
||||
identityKey = store.getIdentityKeyPair(ourUuid);
|
||||
} catch (error) {
|
||||
// We swallow any error here, because we don't want to get into
|
||||
// a loop of repeated retries.
|
||||
|
@ -788,8 +788,7 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
const store = storage.protocol;
|
||||
const identityKey =
|
||||
maybeIdentityKey ?? (await store.getIdentityKeyPair(ourUuid));
|
||||
const identityKey = maybeIdentityKey ?? store.getIdentityKeyPair(ourUuid);
|
||||
strictAssert(identityKey, 'generateKeys: No identity key pair!');
|
||||
|
||||
const result: Omit<GeneratedKeysType, 'signedPreKey'> = {
|
||||
|
|
|
@ -45,7 +45,7 @@ import { normalizeUuid } from '../util/normalizeUuid';
|
|||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||
import { Zone } from '../util/Zone';
|
||||
import { deriveMasterKeyFromGroupV1 } from '../Crypto';
|
||||
import { deriveMasterKeyFromGroupV1, bytesToUuid } from '../Crypto';
|
||||
import type { DownloadedAttachmentType } from '../types/Attachment';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
|
@ -122,7 +122,8 @@ const GROUPV2_ID_LENGTH = 32;
|
|||
const RETRY_TIMEOUT = 2 * 60 * 1000;
|
||||
|
||||
type UnsealedEnvelope = Readonly<
|
||||
ProcessedEnvelope & {
|
||||
Omit<ProcessedEnvelope, 'sourceUuid'> & {
|
||||
sourceUuid: UUIDStringType;
|
||||
unidentifiedDeliveryReceived?: boolean;
|
||||
contentHint?: number;
|
||||
groupId?: string;
|
||||
|
@ -133,10 +134,16 @@ type UnsealedEnvelope = Readonly<
|
|||
}
|
||||
>;
|
||||
|
||||
type DecryptResult = Readonly<{
|
||||
envelope: UnsealedEnvelope;
|
||||
plaintext?: Uint8Array;
|
||||
}>;
|
||||
type DecryptResult = Readonly<
|
||||
| {
|
||||
envelope: UnsealedEnvelope;
|
||||
plaintext: Uint8Array;
|
||||
}
|
||||
| {
|
||||
envelope?: UnsealedEnvelope;
|
||||
plaintext?: undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
type DecryptSealedSenderResult = Readonly<{
|
||||
plaintext?: Uint8Array;
|
||||
|
@ -757,9 +764,9 @@ export default class MessageReceiver
|
|||
// Proto.Envelope fields
|
||||
type: decoded.type,
|
||||
source: item.source,
|
||||
sourceUuid: decoded.sourceUuid
|
||||
? UUID.cast(decoded.sourceUuid)
|
||||
: item.sourceUuid,
|
||||
sourceUuid:
|
||||
item.sourceUuid ||
|
||||
(decoded.sourceUuid ? UUID.cast(decoded.sourceUuid) : undefined),
|
||||
sourceDevice: decoded.sourceDevice || item.sourceDevice,
|
||||
destinationUuid: new UUID(
|
||||
decoded.destinationUuid || item.destinationUuid || ourUuid.toString()
|
||||
|
@ -787,10 +794,21 @@ export default class MessageReceiver
|
|||
throw new Error('Cached decrypted value was not a string!');
|
||||
}
|
||||
|
||||
strictAssert(
|
||||
envelope.sourceUuid,
|
||||
'Decrypted envelope must have source uuid'
|
||||
);
|
||||
|
||||
// Pacify typescript
|
||||
const decryptedEnvelope = {
|
||||
...envelope,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
};
|
||||
|
||||
// Maintain invariant: encrypted queue => decrypted queue
|
||||
this.addToQueue(
|
||||
async () => {
|
||||
this.queueDecryptedEnvelope(envelope, payloadPlaintext);
|
||||
this.queueDecryptedEnvelope(decryptedEnvelope, payloadPlaintext);
|
||||
},
|
||||
'queueDecryptedEnvelope',
|
||||
TaskType.Encrypted
|
||||
|
@ -1088,7 +1106,7 @@ export default class MessageReceiver
|
|||
`Rejecting envelope ${getEnvelopeId(envelope)}, ` +
|
||||
`unknown uuid: ${destinationUuid}`
|
||||
);
|
||||
return { plaintext: undefined, envelope };
|
||||
return { plaintext: undefined, envelope: undefined };
|
||||
}
|
||||
|
||||
const unsealedEnvelope = await this.unsealEnvelope(
|
||||
|
@ -1099,7 +1117,7 @@ export default class MessageReceiver
|
|||
|
||||
// Dropped early
|
||||
if (!unsealedEnvelope) {
|
||||
return { plaintext: undefined, envelope };
|
||||
return { plaintext: undefined, envelope: undefined };
|
||||
}
|
||||
|
||||
logId = getEnvelopeId(unsealedEnvelope);
|
||||
|
@ -1185,8 +1203,13 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
if (envelope.type !== Proto.Envelope.Type.UNIDENTIFIED_SENDER) {
|
||||
strictAssert(
|
||||
envelope.sourceUuid,
|
||||
'Unsealed envelope must have source uuid'
|
||||
);
|
||||
return {
|
||||
...envelope,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
cipherTextBytes: envelope.content,
|
||||
cipherTextType: envelopeTypeToCiphertextType(envelope.type),
|
||||
};
|
||||
|
@ -1259,6 +1282,10 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
if (envelope.type === Proto.Envelope.Type.RECEIPT) {
|
||||
strictAssert(
|
||||
envelope.sourceUuid,
|
||||
'Unsealed delivery receipt must have sourceUuid'
|
||||
);
|
||||
await this.onDeliveryReceipt(envelope);
|
||||
return { plaintext: undefined, envelope };
|
||||
}
|
||||
|
@ -1291,6 +1318,7 @@ export default class MessageReceiver
|
|||
// sender key to decrypt the next message in the queue!
|
||||
let isGroupV2 = false;
|
||||
|
||||
let inProgressMessageType = '';
|
||||
try {
|
||||
const content = Proto.Content.decode(plaintext);
|
||||
|
||||
|
@ -1300,6 +1328,7 @@ export default class MessageReceiver
|
|||
content.senderKeyDistributionMessage &&
|
||||
Bytes.isNotEmpty(content.senderKeyDistributionMessage)
|
||||
) {
|
||||
inProgressMessageType = 'sender key distribution';
|
||||
await this.handleSenderKeyDistributionMessage(
|
||||
stores,
|
||||
envelope,
|
||||
|
@ -1307,22 +1336,35 @@ export default class MessageReceiver
|
|||
);
|
||||
}
|
||||
|
||||
if (content.pniSignatureMessage) {
|
||||
inProgressMessageType = 'pni signature';
|
||||
await this.handlePniSignatureMessage(
|
||||
envelope,
|
||||
content.pniSignatureMessage
|
||||
);
|
||||
}
|
||||
|
||||
// Some sync messages have to be fully processed in the middle of
|
||||
// decryption queue since subsequent envelopes use their key material.
|
||||
const { syncMessage } = content;
|
||||
if (syncMessage?.pniIdentity) {
|
||||
inProgressMessageType = 'pni identity';
|
||||
await this.handlePNIIdentity(envelope, syncMessage.pniIdentity);
|
||||
return { plaintext: undefined, envelope };
|
||||
}
|
||||
|
||||
if (syncMessage?.pniChangeNumber) {
|
||||
inProgressMessageType = 'pni change number';
|
||||
await this.handlePNIChangeNumber(envelope, syncMessage.pniChangeNumber);
|
||||
return { plaintext: undefined, envelope };
|
||||
}
|
||||
|
||||
inProgressMessageType = '';
|
||||
} catch (error) {
|
||||
log.error(
|
||||
'MessageReceiver.decryptEnvelope: Failed to process sender ' +
|
||||
`key distribution message: ${Errors.toLogFormat(error)}`
|
||||
'MessageReceiver.decryptEnvelope: ' +
|
||||
`Failed to process ${inProgressMessageType} ` +
|
||||
`message: ${Errors.toLogFormat(error)}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1412,6 +1454,7 @@ export default class MessageReceiver
|
|||
source: envelope.source,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
sourceDevice: envelope.sourceDevice,
|
||||
wasSentEncrypted: false,
|
||||
},
|
||||
this.removeFromCache.bind(this, envelope)
|
||||
)
|
||||
|
@ -1549,7 +1592,7 @@ export default class MessageReceiver
|
|||
|
||||
private async innerDecrypt(
|
||||
stores: LockedStores,
|
||||
envelope: ProcessedEnvelope,
|
||||
envelope: UnsealedEnvelope,
|
||||
ciphertext: Uint8Array,
|
||||
uuidKind: UUIDKind
|
||||
): Promise<Uint8Array | undefined> {
|
||||
|
@ -2014,6 +2057,7 @@ export default class MessageReceiver
|
|||
source: envelope.source,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
sourceDevice: envelope.sourceDevice,
|
||||
destinationUuid: envelope.destinationUuid.toString(),
|
||||
timestamp: envelope.timestamp,
|
||||
serverGuid: envelope.serverGuid,
|
||||
serverTimestamp: envelope.serverTimestamp,
|
||||
|
@ -2138,6 +2182,7 @@ export default class MessageReceiver
|
|||
source: envelope.source,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
sourceDevice: envelope.sourceDevice,
|
||||
destinationUuid: envelope.destinationUuid.toString(),
|
||||
timestamp: envelope.timestamp,
|
||||
serverGuid: envelope.serverGuid,
|
||||
serverTimestamp: envelope.serverTimestamp,
|
||||
|
@ -2154,8 +2199,8 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
private async maybeUpdateTimestamp(
|
||||
envelope: ProcessedEnvelope
|
||||
): Promise<ProcessedEnvelope> {
|
||||
envelope: UnsealedEnvelope
|
||||
): Promise<UnsealedEnvelope> {
|
||||
const { retryPlaceholders } = window.Signal.Services;
|
||||
if (!retryPlaceholders) {
|
||||
log.warn('maybeUpdateTimestamp: retry placeholders not available!');
|
||||
|
@ -2209,7 +2254,7 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
private async innerHandleContentMessage(
|
||||
incomingEnvelope: ProcessedEnvelope,
|
||||
incomingEnvelope: UnsealedEnvelope,
|
||||
plaintext: Uint8Array
|
||||
): Promise<void> {
|
||||
const content = Proto.Content.decode(plaintext);
|
||||
|
@ -2311,7 +2356,7 @@ export default class MessageReceiver
|
|||
|
||||
private async handleSenderKeyDistributionMessage(
|
||||
stores: LockedStores,
|
||||
envelope: ProcessedEnvelope,
|
||||
envelope: UnsealedEnvelope,
|
||||
distributionMessage: Uint8Array
|
||||
): Promise<void> {
|
||||
const envelopeId = getEnvelopeId(envelope);
|
||||
|
@ -2324,11 +2369,6 @@ export default class MessageReceiver
|
|||
|
||||
const identifier = envelope.sourceUuid;
|
||||
const { sourceDevice } = envelope;
|
||||
if (!identifier) {
|
||||
throw new Error(
|
||||
`handleSenderKeyDistributionMessage: No identifier for envelope ${envelopeId}`
|
||||
);
|
||||
}
|
||||
if (!isNumber(sourceDevice)) {
|
||||
throw new Error(
|
||||
`handleSenderKeyDistributionMessage: Missing sourceDevice for envelope ${envelopeId}`
|
||||
|
@ -2358,8 +2398,44 @@ export default class MessageReceiver
|
|||
);
|
||||
}
|
||||
|
||||
private async handlePniSignatureMessage(
|
||||
envelope: UnsealedEnvelope,
|
||||
pniSignatureMessage: Proto.IPniSignatureMessage
|
||||
): Promise<void> {
|
||||
const envelopeId = getEnvelopeId(envelope);
|
||||
const logId = `handlePniSignatureMessage/${envelopeId}`;
|
||||
log.info(logId);
|
||||
|
||||
// Note: we don't call removeFromCache here because this message can be combined
|
||||
// with a dataMessage, for example. That processing will dictate cache removal.
|
||||
|
||||
const aci = envelope.sourceUuid;
|
||||
|
||||
const { pni: pniBytes, signature } = pniSignatureMessage;
|
||||
strictAssert(Bytes.isNotEmpty(pniBytes), `${logId}: missing PNI bytes`);
|
||||
const pni = bytesToUuid(pniBytes);
|
||||
strictAssert(pni, `${logId}: missing PNI`);
|
||||
strictAssert(Bytes.isNotEmpty(signature), `${logId}: empty signature`);
|
||||
|
||||
const isValid = await this.storage.protocol.verifyAlternateIdentity({
|
||||
aci: new UUID(aci),
|
||||
pni: new UUID(pni),
|
||||
signature,
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
log.info(`${logId}: merging pni=${pni} aci=${aci}`);
|
||||
window.ConversationController.maybeMergeContacts({
|
||||
pni,
|
||||
aci,
|
||||
e164: window.ConversationController.get(pni)?.get('e164'),
|
||||
reason: logId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async handleCallingMessage(
|
||||
envelope: ProcessedEnvelope,
|
||||
envelope: UnsealedEnvelope,
|
||||
callingMessage: Proto.ICallingMessage
|
||||
): Promise<void> {
|
||||
logUnexpectedUrgentValue(envelope, 'callingMessage');
|
||||
|
@ -2372,7 +2448,7 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
private async handleReceiptMessage(
|
||||
envelope: ProcessedEnvelope,
|
||||
envelope: UnsealedEnvelope,
|
||||
receiptMessage: Proto.IReceiptMessage
|
||||
): Promise<void> {
|
||||
strictAssert(receiptMessage.timestamp, 'Receipt message without timestamp');
|
||||
|
@ -2409,6 +2485,7 @@ export default class MessageReceiver
|
|||
source: envelope.source,
|
||||
sourceUuid: envelope.sourceUuid,
|
||||
sourceDevice: envelope.sourceDevice,
|
||||
wasSentEncrypted: true,
|
||||
},
|
||||
this.removeFromCache.bind(this, envelope)
|
||||
);
|
||||
|
@ -2418,7 +2495,7 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
private async handleTypingMessage(
|
||||
envelope: ProcessedEnvelope,
|
||||
envelope: UnsealedEnvelope,
|
||||
typingMessage: Proto.ITypingMessage
|
||||
): Promise<void> {
|
||||
this.removeFromCache(envelope);
|
||||
|
@ -2475,7 +2552,7 @@ export default class MessageReceiver
|
|||
);
|
||||
}
|
||||
|
||||
private handleNullMessage(envelope: ProcessedEnvelope): void {
|
||||
private handleNullMessage(envelope: UnsealedEnvelope): void {
|
||||
log.info('MessageReceiver.handleNullMessage', getEnvelopeId(envelope));
|
||||
|
||||
logUnexpectedUrgentValue(envelope, 'nullMessage');
|
||||
|
@ -2591,7 +2668,7 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
private async handleSyncMessage(
|
||||
envelope: ProcessedEnvelope,
|
||||
envelope: UnsealedEnvelope,
|
||||
syncMessage: ProcessedSyncMessage
|
||||
): Promise<void> {
|
||||
const ourNumber = this.storage.user.getNumber();
|
||||
|
|
|
@ -196,9 +196,13 @@ export default class OutgoingMessage {
|
|||
const contentProto = this.getContentProtoBytes();
|
||||
const { timestamp, contentHint, recipients, urgent } = this;
|
||||
let dataMessage: Uint8Array | undefined;
|
||||
let hasPniSignatureMessage = false;
|
||||
|
||||
if (proto instanceof Proto.Content && proto.dataMessage) {
|
||||
dataMessage = Proto.DataMessage.encode(proto.dataMessage).finish();
|
||||
if (proto instanceof Proto.Content) {
|
||||
if (proto.dataMessage) {
|
||||
dataMessage = Proto.DataMessage.encode(proto.dataMessage).finish();
|
||||
}
|
||||
hasPniSignatureMessage = Boolean(proto.pniSignatureMessage);
|
||||
} else if (proto instanceof Proto.DataMessage) {
|
||||
dataMessage = Proto.DataMessage.encode(proto).finish();
|
||||
}
|
||||
|
@ -215,6 +219,7 @@ export default class OutgoingMessage {
|
|||
contentProto,
|
||||
timestamp,
|
||||
urgent,
|
||||
hasPniSignatureMessage,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,9 @@ import {
|
|||
} from '@signalapp/libsignal-client';
|
||||
|
||||
import type { QuotedMessageType } from '../model-types.d';
|
||||
import type { ConversationModel } from '../models/conversations';
|
||||
import { GLOBAL_ZONE } from '../SignalProtocolStore';
|
||||
import { assert } from '../util/assert';
|
||||
import { assert, strictAssert } from '../util/assert';
|
||||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
|
@ -65,6 +66,7 @@ import type {
|
|||
import { concat, isEmpty, map } from '../util/iterables';
|
||||
import type { SendTypesType } from '../util/handleMessageSend';
|
||||
import { shouldSaveProto, sendTypesEnum } from '../util/handleMessageSend';
|
||||
import { uuidToBytes } from '../util/uuidToBytes';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import * as log from '../logging/log';
|
||||
import type { Avatar, EmbeddedContactType } from '../types/EmbeddedContact';
|
||||
|
@ -574,11 +576,43 @@ class Message {
|
|||
return proto;
|
||||
}
|
||||
|
||||
encode() {
|
||||
encode(): Uint8Array {
|
||||
return Proto.DataMessage.encode(this.toProto()).finish();
|
||||
}
|
||||
}
|
||||
|
||||
type AddPniSignatureMessageToProtoOptionsType = Readonly<{
|
||||
conversation?: ConversationModel;
|
||||
proto: Proto.Content;
|
||||
reason: string;
|
||||
}>;
|
||||
|
||||
function addPniSignatureMessageToProto({
|
||||
conversation,
|
||||
proto,
|
||||
reason,
|
||||
}: AddPniSignatureMessageToProtoOptionsType): void {
|
||||
if (!conversation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pniSignatureMessage = conversation?.getPniSignatureMessage();
|
||||
if (!pniSignatureMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(
|
||||
`addPniSignatureMessageToProto(${reason}): ` +
|
||||
`adding pni signature for ${conversation.idForLogging()}`
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
proto.pniSignatureMessage = {
|
||||
pni: uuidToBytes(pniSignatureMessage.pni),
|
||||
signature: pniSignatureMessage.signature,
|
||||
};
|
||||
}
|
||||
|
||||
export default class MessageSender {
|
||||
pendingMessages: {
|
||||
[id: string]: PQueue;
|
||||
|
@ -944,7 +978,10 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
async getContentMessage(
|
||||
options: Readonly<MessageOptionsType>
|
||||
options: Readonly<MessageOptionsType> &
|
||||
Readonly<{
|
||||
includePniSignatureMessage?: boolean;
|
||||
}>
|
||||
): Promise<Proto.Content> {
|
||||
const message = await this.getHydratedMessage(options);
|
||||
const dataMessage = message.toProto();
|
||||
|
@ -952,6 +989,24 @@ export default class MessageSender {
|
|||
const contentMessage = new Proto.Content();
|
||||
contentMessage.dataMessage = dataMessage;
|
||||
|
||||
const { includePniSignatureMessage } = options;
|
||||
if (includePniSignatureMessage) {
|
||||
strictAssert(
|
||||
message.recipients.length === 1,
|
||||
'getContentMessage: includePniSignatureMessage is single recipient only'
|
||||
);
|
||||
|
||||
const conversation = window.ConversationController.get(
|
||||
message.recipients[0]
|
||||
);
|
||||
|
||||
addPniSignatureMessageToProto({
|
||||
conversation,
|
||||
proto: contentMessage,
|
||||
reason: `getContentMessage(${message.timestamp})`,
|
||||
});
|
||||
}
|
||||
|
||||
return contentMessage;
|
||||
}
|
||||
|
||||
|
@ -1001,6 +1056,14 @@ export default class MessageSender {
|
|||
const contentMessage = new Proto.Content();
|
||||
contentMessage.typingMessage = typingMessage;
|
||||
|
||||
if (recipientId) {
|
||||
addPniSignatureMessageToProto({
|
||||
conversation: window.ConversationController.get(recipientId),
|
||||
proto: contentMessage,
|
||||
reason: `getTypingContentMessage(${finalTimestamp})`,
|
||||
});
|
||||
}
|
||||
|
||||
return contentMessage;
|
||||
}
|
||||
|
||||
|
@ -1100,14 +1163,19 @@ export default class MessageSender {
|
|||
groupId,
|
||||
options,
|
||||
urgent,
|
||||
includePniSignatureMessage,
|
||||
}: Readonly<{
|
||||
messageOptions: MessageOptionsType;
|
||||
contentHint: number;
|
||||
groupId: string | undefined;
|
||||
options?: SendOptionsType;
|
||||
urgent: boolean;
|
||||
includePniSignatureMessage?: boolean;
|
||||
}>): Promise<CallbackResultType> {
|
||||
const message = await this.getHydratedMessage(messageOptions);
|
||||
const proto = await this.getContentMessage({
|
||||
...messageOptions,
|
||||
includePniSignatureMessage,
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendMessageProto({
|
||||
|
@ -1121,9 +1189,9 @@ export default class MessageSender {
|
|||
contentHint,
|
||||
groupId,
|
||||
options,
|
||||
proto: message.toProto(),
|
||||
recipients: message.recipients || [],
|
||||
timestamp: message.timestamp,
|
||||
proto,
|
||||
recipients: messageOptions.recipients || [],
|
||||
timestamp: messageOptions.timestamp,
|
||||
urgent,
|
||||
});
|
||||
});
|
||||
|
@ -1276,6 +1344,7 @@ export default class MessageSender {
|
|||
storyContext,
|
||||
timestamp,
|
||||
urgent,
|
||||
includePniSignatureMessage,
|
||||
}: Readonly<{
|
||||
attachments: ReadonlyArray<AttachmentType> | undefined;
|
||||
contact?: Array<ContactWithHydratedAvatar>;
|
||||
|
@ -1294,6 +1363,7 @@ export default class MessageSender {
|
|||
storyContext?: StoryContextType;
|
||||
timestamp: number;
|
||||
urgent: boolean;
|
||||
includePniSignatureMessage?: boolean;
|
||||
}>): Promise<CallbackResultType> {
|
||||
return this.sendMessage({
|
||||
messageOptions: {
|
||||
|
@ -1315,6 +1385,7 @@ export default class MessageSender {
|
|||
groupId,
|
||||
options,
|
||||
urgent,
|
||||
includePniSignatureMessage,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1886,6 +1957,14 @@ export default class MessageSender {
|
|||
const contentMessage = new Proto.Content();
|
||||
contentMessage.callingMessage = callingMessage;
|
||||
|
||||
const conversation = window.ConversationController.get(recipientId);
|
||||
|
||||
addPniSignatureMessageToProto({
|
||||
conversation,
|
||||
proto: contentMessage,
|
||||
reason: `sendCallingMessage(${finalTimestamp})`,
|
||||
});
|
||||
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendMessageProtoAndWait({
|
||||
|
@ -1904,6 +1983,7 @@ export default class MessageSender {
|
|||
senderE164?: string;
|
||||
senderUuid?: string;
|
||||
timestamps: Array<number>;
|
||||
isDirectConversation: boolean;
|
||||
options?: Readonly<SendOptionsType>;
|
||||
}>
|
||||
): Promise<CallbackResultType> {
|
||||
|
@ -1918,6 +1998,7 @@ export default class MessageSender {
|
|||
senderE164?: string;
|
||||
senderUuid?: string;
|
||||
timestamps: Array<number>;
|
||||
isDirectConversation: boolean;
|
||||
options?: Readonly<SendOptionsType>;
|
||||
}>
|
||||
): Promise<CallbackResultType> {
|
||||
|
@ -1932,6 +2013,7 @@ export default class MessageSender {
|
|||
senderE164?: string;
|
||||
senderUuid?: string;
|
||||
timestamps: Array<number>;
|
||||
isDirectConversation: boolean;
|
||||
options?: Readonly<SendOptionsType>;
|
||||
}>
|
||||
): Promise<CallbackResultType> {
|
||||
|
@ -1946,12 +2028,14 @@ export default class MessageSender {
|
|||
senderUuid,
|
||||
timestamps,
|
||||
type,
|
||||
isDirectConversation,
|
||||
options,
|
||||
}: Readonly<{
|
||||
senderE164?: string;
|
||||
senderUuid?: string;
|
||||
timestamps: Array<number>;
|
||||
type: Proto.ReceiptMessage.Type;
|
||||
isDirectConversation: boolean;
|
||||
options?: Readonly<SendOptionsType>;
|
||||
}>): Promise<CallbackResultType> {
|
||||
if (!senderUuid && !senderE164) {
|
||||
|
@ -1960,21 +2044,35 @@ export default class MessageSender {
|
|||
);
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
|
||||
const receiptMessage = new Proto.ReceiptMessage();
|
||||
receiptMessage.type = type;
|
||||
receiptMessage.timestamp = timestamps.map(timestamp =>
|
||||
Long.fromNumber(timestamp)
|
||||
receiptMessage.timestamp = timestamps.map(receiptTimestamp =>
|
||||
Long.fromNumber(receiptTimestamp)
|
||||
);
|
||||
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.receiptMessage = receiptMessage;
|
||||
|
||||
if (isDirectConversation) {
|
||||
const conversation = window.ConversationController.get(
|
||||
senderUuid || senderE164
|
||||
);
|
||||
|
||||
addPniSignatureMessageToProto({
|
||||
conversation,
|
||||
proto: contentMessage,
|
||||
reason: `sendReceiptMessage(${type}, ${timestamp})`,
|
||||
});
|
||||
}
|
||||
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: senderUuid || senderE164,
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
timestamp,
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
options,
|
||||
urgent: false,
|
||||
|
@ -2052,6 +2150,7 @@ export default class MessageSender {
|
|||
sendType,
|
||||
timestamp,
|
||||
urgent,
|
||||
hasPniSignatureMessage,
|
||||
}: Readonly<{
|
||||
contentHint: number;
|
||||
messageId?: string;
|
||||
|
@ -2059,6 +2158,7 @@ export default class MessageSender {
|
|||
sendType: SendTypesType;
|
||||
timestamp: number;
|
||||
urgent: boolean;
|
||||
hasPniSignatureMessage: boolean;
|
||||
}>): SendLogCallbackType {
|
||||
let initialSavePromise: Promise<number>;
|
||||
|
||||
|
@ -2095,6 +2195,7 @@ export default class MessageSender {
|
|||
proto,
|
||||
timestamp,
|
||||
urgent,
|
||||
hasPniSignatureMessage,
|
||||
},
|
||||
{
|
||||
recipients: { [recipientUuid]: deviceIds },
|
||||
|
@ -2270,6 +2371,7 @@ export default class MessageSender {
|
|||
sendType: 'senderKeyDistributionMessage',
|
||||
timestamp,
|
||||
urgent,
|
||||
hasPniSignatureMessage: false,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
|
@ -2313,6 +2415,7 @@ export default class MessageSender {
|
|||
sendType: 'legacyGroupChange',
|
||||
timestamp,
|
||||
urgent: false,
|
||||
hasPniSignatureMessage: false,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
|
|
6
ts/textsecure/Types.d.ts
vendored
6
ts/textsecure/Types.d.ts
vendored
|
@ -267,6 +267,7 @@ export interface CallbackResultType {
|
|||
timestamp?: number;
|
||||
recipients?: Record<string, Array<number>>;
|
||||
urgent?: boolean;
|
||||
hasPniSignatureMessage?: boolean;
|
||||
}
|
||||
|
||||
export interface IRequestHandler {
|
||||
|
@ -278,3 +279,8 @@ export type PniKeyMaterialType = Readonly<{
|
|||
signedPreKey: Uint8Array;
|
||||
registrationId: number;
|
||||
}>;
|
||||
|
||||
export type PniSignatureMessageType = Readonly<{
|
||||
pni: UUIDStringType;
|
||||
signature: Uint8Array;
|
||||
}>;
|
||||
|
|
|
@ -132,6 +132,7 @@ export type DeliveryEventData = Readonly<{
|
|||
source?: string;
|
||||
sourceUuid?: UUIDStringType;
|
||||
sourceDevice?: number;
|
||||
wasSentEncrypted: boolean;
|
||||
}>;
|
||||
|
||||
export class DeliveryEvent extends ConfirmableEvent {
|
||||
|
@ -220,8 +221,9 @@ export class ProfileKeyUpdateEvent extends ConfirmableEvent {
|
|||
|
||||
export type MessageEventData = Readonly<{
|
||||
source?: string;
|
||||
sourceUuid?: UUIDStringType;
|
||||
sourceUuid: UUIDStringType;
|
||||
sourceDevice?: number;
|
||||
destinationUuid: UUIDStringType;
|
||||
timestamp: number;
|
||||
serverGuid?: string;
|
||||
serverTimestamp?: number;
|
||||
|
@ -246,6 +248,7 @@ export type ReadOrViewEventData = Readonly<{
|
|||
source?: string;
|
||||
sourceUuid?: UUIDStringType;
|
||||
sourceDevice?: number;
|
||||
wasSentEncrypted: true;
|
||||
}>;
|
||||
|
||||
export class ReadEvent extends ConfirmableEvent {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue