More protobufjs migration
This commit is contained in:
parent
cf06e6638e
commit
ddbbe3a6b1
70 changed files with 3967 additions and 3369 deletions
|
@ -1,101 +1,160 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { ByteBufferClass } from '../window.d';
|
||||
import { AttachmentType } from './SendMessage';
|
||||
import { Reader } from 'protobufjs';
|
||||
|
||||
type ProtobufConstructorType = {
|
||||
decode: (data: ArrayBuffer) => ProtobufType;
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { normalizeUuid } from '../util/normalizeUuid';
|
||||
import { typedArrayToArrayBuffer } from '../Crypto';
|
||||
|
||||
import Avatar = Proto.ContactDetails.IAvatar;
|
||||
|
||||
type OptionalAvatar = { avatar?: Avatar | null };
|
||||
|
||||
type DecoderBase<Message extends OptionalAvatar> = {
|
||||
decodeDelimited(reader: Reader): Message | undefined;
|
||||
};
|
||||
|
||||
type ProtobufType = {
|
||||
avatar?: PackedAttachmentType;
|
||||
profileKey?: any;
|
||||
uuid?: string;
|
||||
members: Array<string>;
|
||||
export type MessageWithAvatar<Message extends OptionalAvatar> = Omit<
|
||||
Message,
|
||||
'avatar'
|
||||
> & {
|
||||
avatar?: (Avatar & { data: ArrayBuffer }) | null;
|
||||
};
|
||||
|
||||
export type PackedAttachmentType = AttachmentType & {
|
||||
length: number;
|
||||
};
|
||||
export type ModifiedGroupDetails = MessageWithAvatar<Proto.GroupDetails>;
|
||||
|
||||
export class ProtoParser {
|
||||
buffer: ByteBufferClass;
|
||||
export type ModifiedContactDetails = MessageWithAvatar<Proto.ContactDetails>;
|
||||
|
||||
protobuf: ProtobufConstructorType;
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
constructor(arrayBuffer: ArrayBuffer, protobuf: ProtobufConstructorType) {
|
||||
this.protobuf = protobuf;
|
||||
this.buffer = new window.dcodeIO.ByteBuffer();
|
||||
this.buffer.append(arrayBuffer);
|
||||
this.buffer.offset = 0;
|
||||
this.buffer.limit = arrayBuffer.byteLength;
|
||||
class ParserBase<
|
||||
Message extends OptionalAvatar,
|
||||
Decoder extends DecoderBase<Message>
|
||||
> {
|
||||
protected readonly reader: Reader;
|
||||
|
||||
constructor(arrayBuffer: ArrayBuffer, private readonly decoder: Decoder) {
|
||||
this.reader = new Reader(new FIXMEU8(arrayBuffer));
|
||||
}
|
||||
|
||||
next(): ProtobufType | undefined | null {
|
||||
protected decodeDelimited(): MessageWithAvatar<Message> | undefined {
|
||||
if (this.reader.pos === this.reader.len) {
|
||||
return undefined; // eof
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.buffer.limit === this.buffer.offset) {
|
||||
return undefined; // eof
|
||||
}
|
||||
const len = this.buffer.readVarint32();
|
||||
const nextBuffer = this.buffer
|
||||
.slice(this.buffer.offset, this.buffer.offset + len)
|
||||
.toArrayBuffer();
|
||||
const proto = this.decoder.decodeDelimited(this.reader);
|
||||
|
||||
const proto = this.protobuf.decode(nextBuffer);
|
||||
this.buffer.skip(len);
|
||||
|
||||
if (proto.avatar) {
|
||||
const attachmentLen = proto.avatar.length;
|
||||
proto.avatar.data = this.buffer
|
||||
.slice(this.buffer.offset, this.buffer.offset + attachmentLen)
|
||||
.toArrayBuffer();
|
||||
this.buffer.skip(attachmentLen);
|
||||
if (!proto) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (proto.profileKey) {
|
||||
proto.profileKey = proto.profileKey.toArrayBuffer();
|
||||
if (!proto.avatar) {
|
||||
return {
|
||||
...proto,
|
||||
avatar: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (proto.uuid) {
|
||||
window.normalizeUuids(
|
||||
proto,
|
||||
['uuid'],
|
||||
'ProtoParser::next (proto.uuid)'
|
||||
);
|
||||
}
|
||||
const attachmentLen = proto.avatar.length ?? 0;
|
||||
const avatarData = this.reader.buf.slice(
|
||||
this.reader.pos,
|
||||
this.reader.pos + attachmentLen
|
||||
);
|
||||
this.reader.skip(attachmentLen);
|
||||
|
||||
if (proto.members) {
|
||||
window.normalizeUuids(
|
||||
proto,
|
||||
proto.members.map((_member, i) => `members.${i}.uuid`),
|
||||
'ProtoParser::next (proto.members)'
|
||||
);
|
||||
}
|
||||
return {
|
||||
...proto,
|
||||
|
||||
return proto;
|
||||
avatar: {
|
||||
...proto.avatar,
|
||||
|
||||
data: typedArrayToArrayBuffer(avatarData),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
'ProtoParser.next error:',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupBuffer extends ParserBase<
|
||||
Proto.GroupDetails,
|
||||
typeof Proto.GroupDetails
|
||||
> {
|
||||
constructor(arrayBuffer: ArrayBuffer) {
|
||||
super(arrayBuffer, Proto.GroupDetails);
|
||||
}
|
||||
|
||||
public next(): ModifiedGroupDetails | undefined {
|
||||
const proto = this.decodeDelimited();
|
||||
if (!proto) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (!proto.members) {
|
||||
return proto;
|
||||
}
|
||||
return {
|
||||
...proto,
|
||||
members: proto.members.map((member, i) => {
|
||||
if (!member.uuid) {
|
||||
return member;
|
||||
}
|
||||
|
||||
return {
|
||||
...member,
|
||||
uuid: normalizeUuid(member.uuid, `GroupBuffer.member[${i}].uuid`),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupBuffer extends ProtoParser {
|
||||
export class ContactBuffer extends ParserBase<
|
||||
Proto.ContactDetails,
|
||||
typeof Proto.ContactDetails
|
||||
> {
|
||||
constructor(arrayBuffer: ArrayBuffer) {
|
||||
super(arrayBuffer, window.textsecure.protobuf.GroupDetails as any);
|
||||
super(arrayBuffer, Proto.ContactDetails);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContactBuffer extends ProtoParser {
|
||||
constructor(arrayBuffer: ArrayBuffer) {
|
||||
super(arrayBuffer, window.textsecure.protobuf.ContactDetails as any);
|
||||
public next(): ModifiedContactDetails | undefined {
|
||||
const proto = this.decodeDelimited();
|
||||
if (!proto) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!proto.uuid) {
|
||||
return proto;
|
||||
}
|
||||
|
||||
const { verified } = proto;
|
||||
|
||||
return {
|
||||
...proto,
|
||||
|
||||
verified:
|
||||
verified && verified.destinationUuid
|
||||
? {
|
||||
...verified,
|
||||
|
||||
destinationUuid: normalizeUuid(
|
||||
verified.destinationUuid,
|
||||
'ContactBuffer.verified.destinationUuid'
|
||||
),
|
||||
}
|
||||
: verified,
|
||||
|
||||
uuid: normalizeUuid(proto.uuid, 'ContactBuffer.uuid'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ const Crypto = {
|
|||
async decryptAttachment(
|
||||
encryptedBin: ArrayBuffer,
|
||||
keys: ArrayBuffer,
|
||||
theirDigest: ArrayBuffer
|
||||
theirDigest?: ArrayBuffer
|
||||
): Promise<ArrayBuffer> {
|
||||
if (keys.byteLength !== 64) {
|
||||
throw new Error('Got invalid length attachment keys');
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
|
||||
*/
|
||||
|
||||
export type EventHandler = (event: any) => unknown;
|
||||
|
||||
export default class EventTarget {
|
||||
listeners?: { [type: string]: Array<Function> };
|
||||
listeners?: { [type: string]: Array<EventHandler> };
|
||||
|
||||
dispatchEvent(ev: Event): Array<unknown> {
|
||||
if (!(ev instanceof Event)) {
|
||||
|
@ -36,7 +38,7 @@ export default class EventTarget {
|
|||
return results;
|
||||
}
|
||||
|
||||
addEventListener(eventName: string, callback: Function): void {
|
||||
addEventListener(eventName: string, callback: EventHandler): void {
|
||||
if (typeof eventName !== 'string') {
|
||||
throw new Error('First argument expects a string');
|
||||
}
|
||||
|
@ -54,7 +56,7 @@ export default class EventTarget {
|
|||
this.listeners[eventName] = listeners;
|
||||
}
|
||||
|
||||
removeEventListener(eventName: string, callback: Function): void {
|
||||
removeEventListener(eventName: string, callback: EventHandler): void {
|
||||
if (typeof eventName !== 'string') {
|
||||
throw new Error('First argument expects a string');
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,7 +23,6 @@ import {
|
|||
} from '@signalapp/signal-client';
|
||||
|
||||
import { WebAPIType } from './WebAPI';
|
||||
import { ContentClass, DataMessageClass } from '../textsecure.d';
|
||||
import {
|
||||
CallbackResultType,
|
||||
SendMetadataType,
|
||||
|
@ -42,6 +41,7 @@ import { Sessions, IdentityKeys } from '../LibSignalStores';
|
|||
import { typedArrayToArrayBuffer as toArrayBuffer } from '../Crypto';
|
||||
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
||||
import { getKeysForIdentifier } from './getKeysForIdentifier';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
|
||||
export const enum SenderCertificateMode {
|
||||
WithE164,
|
||||
|
@ -72,13 +72,13 @@ type OutgoingMessageOptionsType = SendOptionsType & {
|
|||
|
||||
function ciphertextMessageTypeToEnvelopeType(type: number) {
|
||||
if (type === CiphertextMessageType.PreKey) {
|
||||
return window.textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE;
|
||||
return Proto.Envelope.Type.PREKEY_BUNDLE;
|
||||
}
|
||||
if (type === CiphertextMessageType.Whisper) {
|
||||
return window.textsecure.protobuf.Envelope.Type.CIPHERTEXT;
|
||||
return Proto.Envelope.Type.CIPHERTEXT;
|
||||
}
|
||||
if (type === CiphertextMessageType.Plaintext) {
|
||||
return window.textsecure.protobuf.Envelope.Type.PLAINTEXT_CONTENT;
|
||||
return Proto.Envelope.Type.PLAINTEXT_CONTENT;
|
||||
}
|
||||
throw new Error(
|
||||
`ciphertextMessageTypeToEnvelopeType: Unrecognized type ${type}`
|
||||
|
@ -96,11 +96,11 @@ function getPaddedMessageLength(messageLength: number): number {
|
|||
return messagePartCount * 160;
|
||||
}
|
||||
|
||||
export function padMessage(messageBuffer: ArrayBuffer): Uint8Array {
|
||||
export function padMessage(messageBuffer: Uint8Array): Uint8Array {
|
||||
const plaintext = new Uint8Array(
|
||||
getPaddedMessageLength(messageBuffer.byteLength + 1) - 1
|
||||
);
|
||||
plaintext.set(new Uint8Array(messageBuffer));
|
||||
plaintext.set(messageBuffer);
|
||||
plaintext[messageBuffer.byteLength] = 0x80;
|
||||
|
||||
return plaintext;
|
||||
|
@ -113,7 +113,7 @@ export default class OutgoingMessage {
|
|||
|
||||
identifiers: Array<string>;
|
||||
|
||||
message: ContentClass | PlaintextContent;
|
||||
message: Proto.Content | PlaintextContent;
|
||||
|
||||
callback: (result: CallbackResultType) => void;
|
||||
|
||||
|
@ -141,14 +141,14 @@ export default class OutgoingMessage {
|
|||
server: WebAPIType,
|
||||
timestamp: number,
|
||||
identifiers: Array<string>,
|
||||
message: ContentClass | DataMessageClass | PlaintextContent,
|
||||
message: Proto.Content | Proto.DataMessage | PlaintextContent,
|
||||
contentHint: number,
|
||||
groupId: string | undefined,
|
||||
callback: (result: CallbackResultType) => void,
|
||||
options: OutgoingMessageOptionsType = {}
|
||||
) {
|
||||
if (message instanceof window.textsecure.protobuf.DataMessage) {
|
||||
const content = new window.textsecure.protobuf.Content();
|
||||
if (message instanceof Proto.DataMessage) {
|
||||
const content = new Proto.Content();
|
||||
content.dataMessage = message;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
this.message = content;
|
||||
|
@ -304,8 +304,8 @@ export default class OutgoingMessage {
|
|||
if (!this.plaintext) {
|
||||
const { message } = this;
|
||||
|
||||
if (message instanceof window.textsecure.protobuf.Content) {
|
||||
this.plaintext = padMessage(message.toArrayBuffer());
|
||||
if (message instanceof Proto.Content) {
|
||||
this.plaintext = padMessage(Proto.Content.encode(message).finish());
|
||||
} else {
|
||||
this.plaintext = message.serialize();
|
||||
}
|
||||
|
@ -324,7 +324,7 @@ export default class OutgoingMessage {
|
|||
}): Promise<CiphertextMessage> {
|
||||
const { message } = this;
|
||||
|
||||
if (message instanceof window.textsecure.protobuf.Content) {
|
||||
if (message instanceof Proto.Content) {
|
||||
return signalEncrypt(
|
||||
Buffer.from(this.getPlaintext()),
|
||||
protocolAddress,
|
||||
|
@ -421,8 +421,7 @@ export default class OutgoingMessage {
|
|||
);
|
||||
|
||||
return {
|
||||
type:
|
||||
window.textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER,
|
||||
type: Proto.Envelope.Type.UNIDENTIFIED_SENDER,
|
||||
destinationDeviceId,
|
||||
destinationRegistrationId,
|
||||
content: buffer.toString('base64'),
|
||||
|
|
|
@ -14,7 +14,8 @@ import {
|
|||
} from '../Crypto';
|
||||
import { calculateAgreement, createKeyPair, generateKeyPair } from '../Curve';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { assert } from '../util/assert';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { normalizeUuid } from '../util/normalizeUuid';
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
@ -35,7 +36,7 @@ class ProvisioningCipherInner {
|
|||
async decrypt(
|
||||
provisionEnvelope: Proto.ProvisionEnvelope
|
||||
): Promise<ProvisionDecryptResult> {
|
||||
assert(
|
||||
strictAssert(
|
||||
provisionEnvelope.publicKey && provisionEnvelope.body,
|
||||
'Missing required fields in ProvisionEnvelope'
|
||||
);
|
||||
|
@ -79,19 +80,17 @@ class ProvisioningCipherInner {
|
|||
new FIXMEU8(plaintext)
|
||||
);
|
||||
const privKey = provisionMessage.identityKeyPrivate;
|
||||
assert(privKey, 'Missing identityKeyPrivate in ProvisionMessage');
|
||||
strictAssert(privKey, 'Missing identityKeyPrivate in ProvisionMessage');
|
||||
|
||||
const keyPair = createKeyPair(typedArrayToArrayBuffer(privKey));
|
||||
window.normalizeUuids(
|
||||
provisionMessage,
|
||||
['uuid'],
|
||||
'ProvisioningCipher.decrypt'
|
||||
);
|
||||
|
||||
const { uuid } = provisionMessage;
|
||||
strictAssert(uuid, 'Missing uuid in provisioning message');
|
||||
|
||||
const ret: ProvisionDecryptResult = {
|
||||
identityKeyPair: keyPair,
|
||||
number: provisionMessage.number,
|
||||
uuid: provisionMessage.uuid,
|
||||
uuid: normalizeUuid(uuid, 'ProvisionMessage.uuid'),
|
||||
provisioningCode: provisionMessage.provisioningCode,
|
||||
userAgent: provisionMessage.userAgent,
|
||||
readReceipts: provisionMessage.readReceipts,
|
||||
|
|
|
@ -30,22 +30,16 @@ import {
|
|||
import createTaskWithTimeout from './TaskWithTimeout';
|
||||
import OutgoingMessage, { SerializedCertificateType } from './OutgoingMessage';
|
||||
import Crypto from './Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
concatenateBytes,
|
||||
getRandomBytes,
|
||||
getZeroes,
|
||||
hexToArrayBuffer,
|
||||
typedArrayToArrayBuffer,
|
||||
} from '../Crypto';
|
||||
import {
|
||||
AttachmentPointerClass,
|
||||
CallingMessageClass,
|
||||
ContentClass,
|
||||
DataMessageClass,
|
||||
StorageServiceCallOptionsType,
|
||||
StorageServiceCredentials,
|
||||
SyncMessageClass,
|
||||
} from '../textsecure.d';
|
||||
import { MessageError, SignedPreKeyRotationError } from './Errors';
|
||||
import { BodyRangesType } from '../types/Util';
|
||||
|
@ -56,18 +50,6 @@ import {
|
|||
import { concat } from '../util/iterables';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
|
||||
function stringToArrayBuffer(str: string): ArrayBuffer {
|
||||
if (typeof str !== 'string') {
|
||||
throw new Error('Passed non-string to stringToArrayBuffer');
|
||||
}
|
||||
const res = new ArrayBuffer(str.length);
|
||||
const uint = new Uint8Array(res);
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
uint[i] = str.charCodeAt(i);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export type SendMetadataType = {
|
||||
[identifier: string]: {
|
||||
accessKey: string;
|
||||
|
@ -101,7 +83,7 @@ type PreviewType = {
|
|||
|
||||
type QuoteAttachmentType = {
|
||||
thumbnail?: AttachmentType;
|
||||
attachmentPointer?: AttachmentPointerClass;
|
||||
attachmentPointer?: Proto.IAttachmentPointer;
|
||||
};
|
||||
|
||||
export type GroupV2InfoType = {
|
||||
|
@ -130,7 +112,7 @@ export type AttachmentType = {
|
|||
height: number;
|
||||
caption: string;
|
||||
|
||||
attachmentPointer?: AttachmentPointerClass;
|
||||
attachmentPointer?: Proto.IAttachmentPointer;
|
||||
|
||||
blurHash?: string;
|
||||
};
|
||||
|
@ -174,6 +156,9 @@ export type GroupSendOptionsType = {
|
|||
groupCallUpdate?: GroupCallUpdateType;
|
||||
};
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
class Message {
|
||||
attachments: Array<any>;
|
||||
|
||||
|
@ -219,7 +204,7 @@ class Message {
|
|||
|
||||
dataMessage: any;
|
||||
|
||||
attachmentPointers?: Array<any>;
|
||||
attachmentPointers: Array<Proto.IAttachmentPointer> = [];
|
||||
|
||||
deletedForEveryoneTimestamp?: number;
|
||||
|
||||
|
@ -301,17 +286,14 @@ class Message {
|
|||
}
|
||||
|
||||
isEndSession() {
|
||||
return (
|
||||
(this.flags || 0) &
|
||||
window.textsecure.protobuf.DataMessage.Flags.END_SESSION
|
||||
);
|
||||
return (this.flags || 0) & Proto.DataMessage.Flags.END_SESSION;
|
||||
}
|
||||
|
||||
toProto(): DataMessageClass {
|
||||
if (this.dataMessage instanceof window.textsecure.protobuf.DataMessage) {
|
||||
toProto(): Proto.DataMessage {
|
||||
if (this.dataMessage instanceof Proto.DataMessage) {
|
||||
return this.dataMessage;
|
||||
}
|
||||
const proto = new window.textsecure.protobuf.DataMessage();
|
||||
const proto = new Proto.DataMessage();
|
||||
|
||||
proto.timestamp = this.timestamp;
|
||||
proto.attachments = this.attachmentPointers;
|
||||
|
@ -330,19 +312,19 @@ class Message {
|
|||
proto.flags = this.flags;
|
||||
}
|
||||
if (this.groupV2) {
|
||||
proto.groupV2 = new window.textsecure.protobuf.GroupContextV2();
|
||||
proto.groupV2 = new Proto.GroupContextV2();
|
||||
proto.groupV2.masterKey = this.groupV2.masterKey;
|
||||
proto.groupV2.revision = this.groupV2.revision;
|
||||
proto.groupV2.groupChange = this.groupV2.groupChange || null;
|
||||
} else if (this.group) {
|
||||
proto.group = new window.textsecure.protobuf.GroupContext();
|
||||
proto.group.id = stringToArrayBuffer(this.group.id);
|
||||
proto.group = new Proto.GroupContext();
|
||||
proto.group.id = Bytes.fromString(this.group.id);
|
||||
proto.group.type = this.group.type;
|
||||
}
|
||||
if (this.sticker) {
|
||||
proto.sticker = new window.textsecure.protobuf.DataMessage.Sticker();
|
||||
proto.sticker.packId = hexToArrayBuffer(this.sticker.packId);
|
||||
proto.sticker.packKey = base64ToArrayBuffer(this.sticker.packKey);
|
||||
proto.sticker = new Proto.DataMessage.Sticker();
|
||||
proto.sticker.packId = Bytes.fromHex(this.sticker.packId);
|
||||
proto.sticker.packKey = Bytes.fromBase64(this.sticker.packKey);
|
||||
proto.sticker.stickerId = this.sticker.stickerId;
|
||||
|
||||
if (this.sticker.attachmentPointer) {
|
||||
|
@ -350,7 +332,7 @@ class Message {
|
|||
}
|
||||
}
|
||||
if (this.reaction) {
|
||||
proto.reaction = new window.textsecure.protobuf.DataMessage.Reaction();
|
||||
proto.reaction = new Proto.DataMessage.Reaction();
|
||||
proto.reaction.emoji = this.reaction.emoji || null;
|
||||
proto.reaction.remove = this.reaction.remove || false;
|
||||
proto.reaction.targetAuthorUuid = this.reaction.targetAuthorUuid || null;
|
||||
|
@ -359,7 +341,7 @@ class Message {
|
|||
|
||||
if (Array.isArray(this.preview)) {
|
||||
proto.preview = this.preview.map(preview => {
|
||||
const item = new window.textsecure.protobuf.DataMessage.Preview();
|
||||
const item = new Proto.DataMessage.Preview();
|
||||
item.title = preview.title;
|
||||
item.url = preview.url;
|
||||
item.description = preview.description || null;
|
||||
|
@ -369,8 +351,8 @@ class Message {
|
|||
});
|
||||
}
|
||||
if (this.quote) {
|
||||
const { QuotedAttachment } = window.textsecure.protobuf.DataMessage.Quote;
|
||||
const { BodyRange, Quote } = window.textsecure.protobuf.DataMessage;
|
||||
const { QuotedAttachment } = Proto.DataMessage.Quote;
|
||||
const { BodyRange, Quote } = Proto.DataMessage;
|
||||
|
||||
proto.quote = new Quote();
|
||||
const { quote } = proto;
|
||||
|
@ -396,24 +378,26 @@ class Message {
|
|||
const bodyRange = new BodyRange();
|
||||
bodyRange.start = range.start;
|
||||
bodyRange.length = range.length;
|
||||
bodyRange.mentionUuid = range.mentionUuid;
|
||||
if (range.mentionUuid !== undefined) {
|
||||
bodyRange.mentionUuid = range.mentionUuid;
|
||||
}
|
||||
return bodyRange;
|
||||
});
|
||||
if (
|
||||
quote.bodyRanges.length &&
|
||||
(!proto.requiredProtocolVersion ||
|
||||
proto.requiredProtocolVersion <
|
||||
window.textsecure.protobuf.DataMessage.ProtocolVersion.MENTIONS)
|
||||
Proto.DataMessage.ProtocolVersion.MENTIONS)
|
||||
) {
|
||||
proto.requiredProtocolVersion =
|
||||
window.textsecure.protobuf.DataMessage.ProtocolVersion.MENTIONS;
|
||||
Proto.DataMessage.ProtocolVersion.MENTIONS;
|
||||
}
|
||||
}
|
||||
if (this.expireTimer) {
|
||||
proto.expireTimer = this.expireTimer;
|
||||
}
|
||||
if (this.profileKey) {
|
||||
proto.profileKey = this.profileKey;
|
||||
proto.profileKey = new FIXMEU8(this.profileKey);
|
||||
}
|
||||
if (this.deletedForEveryoneTimestamp) {
|
||||
proto.delete = {
|
||||
|
@ -422,7 +406,7 @@ class Message {
|
|||
}
|
||||
if (this.mentions) {
|
||||
proto.requiredProtocolVersion =
|
||||
window.textsecure.protobuf.DataMessage.ProtocolVersion.MENTIONS;
|
||||
Proto.DataMessage.ProtocolVersion.MENTIONS;
|
||||
proto.bodyRanges = this.mentions.map(
|
||||
({ start, length, mentionUuid }) => ({
|
||||
start,
|
||||
|
@ -433,7 +417,7 @@ class Message {
|
|||
}
|
||||
|
||||
if (this.groupCallUpdate) {
|
||||
const { GroupCallUpdate } = window.textsecure.protobuf.DataMessage;
|
||||
const { GroupCallUpdate } = Proto.DataMessage;
|
||||
|
||||
const groupCallUpdate = new GroupCallUpdate();
|
||||
groupCallUpdate.eraId = this.groupCallUpdate.eraId;
|
||||
|
@ -446,7 +430,9 @@ class Message {
|
|||
}
|
||||
|
||||
toArrayBuffer() {
|
||||
return this.toProto().toArrayBuffer();
|
||||
return typedArrayToArrayBuffer(
|
||||
Proto.DataMessage.encode(this.toProto()).finish()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,13 +478,13 @@ export default class MessageSender {
|
|||
);
|
||||
}
|
||||
|
||||
getRandomPadding(): ArrayBuffer {
|
||||
getRandomPadding(): Uint8Array {
|
||||
// Generate a random int from 1 and 512
|
||||
const buffer = getRandomBytes(2);
|
||||
const paddingLength = (new Uint16Array(buffer)[0] & 0x1ff) + 1;
|
||||
|
||||
// Generate a random padding buffer of the chosen size
|
||||
return getRandomBytes(paddingLength);
|
||||
return new FIXMEU8(getRandomBytes(paddingLength));
|
||||
}
|
||||
|
||||
getPaddedAttachment(data: ArrayBuffer): ArrayBuffer {
|
||||
|
@ -511,10 +497,11 @@ export default class MessageSender {
|
|||
|
||||
async makeAttachmentPointer(
|
||||
attachment: AttachmentType
|
||||
): Promise<AttachmentPointerClass | undefined> {
|
||||
if (typeof attachment !== 'object' || attachment == null) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
): Promise<Proto.IAttachmentPointer> {
|
||||
assert(
|
||||
typeof attachment === 'object' && attachment !== null,
|
||||
'Got null attachment in `makeAttachmentPointer`'
|
||||
);
|
||||
|
||||
const { data, size } = attachment;
|
||||
if (!(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
|
||||
|
@ -535,12 +522,12 @@ export default class MessageSender {
|
|||
const result = await Crypto.encryptAttachment(padded, key, iv);
|
||||
const id = await this.server.putAttachment(result.ciphertext);
|
||||
|
||||
const proto = new window.textsecure.protobuf.AttachmentPointer();
|
||||
const proto = new Proto.AttachmentPointer();
|
||||
proto.cdnId = id;
|
||||
proto.contentType = attachment.contentType;
|
||||
proto.key = key;
|
||||
proto.key = new FIXMEU8(key);
|
||||
proto.size = attachment.size;
|
||||
proto.digest = result.digest;
|
||||
proto.digest = new FIXMEU8(result.digest);
|
||||
|
||||
if (attachment.fileName) {
|
||||
proto.fileName = attachment.fileName;
|
||||
|
@ -657,11 +644,11 @@ export default class MessageSender {
|
|||
return message.toArrayBuffer();
|
||||
}
|
||||
|
||||
async getContentMessage(options: MessageOptionsType): Promise<ContentClass> {
|
||||
async getContentMessage(options: MessageOptionsType): Promise<Proto.Content> {
|
||||
const message = await this.getHydratedMessage(options);
|
||||
const dataMessage = message.toProto();
|
||||
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.dataMessage = dataMessage;
|
||||
|
||||
return contentMessage;
|
||||
|
@ -685,8 +672,8 @@ export default class MessageSender {
|
|||
groupMembers: Array<string>;
|
||||
isTyping: boolean;
|
||||
timestamp?: number;
|
||||
}): ContentClass {
|
||||
const ACTION_ENUM = window.textsecure.protobuf.TypingMessage.Action;
|
||||
}): Proto.Content {
|
||||
const ACTION_ENUM = Proto.TypingMessage.Action;
|
||||
const { recipientId, groupId, isTyping, timestamp } = options;
|
||||
|
||||
if (!recipientId && !groupId) {
|
||||
|
@ -698,12 +685,14 @@ export default class MessageSender {
|
|||
const finalTimestamp = timestamp || Date.now();
|
||||
const action = isTyping ? ACTION_ENUM.STARTED : ACTION_ENUM.STOPPED;
|
||||
|
||||
const typingMessage = new window.textsecure.protobuf.TypingMessage();
|
||||
typingMessage.groupId = groupId || null;
|
||||
const typingMessage = new Proto.TypingMessage();
|
||||
if (groupId) {
|
||||
typingMessage.groupId = new FIXMEU8(groupId);
|
||||
}
|
||||
typingMessage.action = action;
|
||||
typingMessage.timestamp = finalTimestamp;
|
||||
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.typingMessage = typingMessage;
|
||||
|
||||
return contentMessage;
|
||||
|
@ -767,7 +756,7 @@ export default class MessageSender {
|
|||
group: groupV1
|
||||
? {
|
||||
id: groupV1.id,
|
||||
type: window.textsecure.protobuf.GroupContext.Type.DELIVER,
|
||||
type: Proto.GroupContext.Type.DELIVER,
|
||||
}
|
||||
: undefined,
|
||||
mentions,
|
||||
|
@ -781,8 +770,8 @@ export default class MessageSender {
|
|||
};
|
||||
}
|
||||
|
||||
createSyncMessage(): SyncMessageClass {
|
||||
const syncMessage = new window.textsecure.protobuf.SyncMessage();
|
||||
createSyncMessage(): Proto.SyncMessage {
|
||||
const syncMessage = new Proto.SyncMessage();
|
||||
|
||||
syncMessage.padding = this.getRandomPadding();
|
||||
|
||||
|
@ -843,7 +832,7 @@ export default class MessageSender {
|
|||
}: {
|
||||
timestamp: number;
|
||||
recipients: Array<string>;
|
||||
proto: ContentClass | DataMessageClass | PlaintextContent;
|
||||
proto: Proto.Content | Proto.DataMessage | PlaintextContent;
|
||||
contentHint: number;
|
||||
groupId: string | undefined;
|
||||
callback: (result: CallbackResultType) => void;
|
||||
|
@ -885,7 +874,7 @@ export default class MessageSender {
|
|||
}: {
|
||||
timestamp: number;
|
||||
recipients: Array<string>;
|
||||
proto: ContentClass | DataMessageClass | PlaintextContent;
|
||||
proto: Proto.Content | Proto.DataMessage | PlaintextContent;
|
||||
contentHint: number;
|
||||
groupId: string | undefined;
|
||||
options?: SendOptionsType;
|
||||
|
@ -920,7 +909,7 @@ export default class MessageSender {
|
|||
options,
|
||||
}: {
|
||||
identifier: string | undefined;
|
||||
proto: DataMessageClass | ContentClass | PlaintextContent;
|
||||
proto: Proto.DataMessage | Proto.Content | PlaintextContent;
|
||||
timestamp: number;
|
||||
contentHint: number;
|
||||
options?: SendOptionsType;
|
||||
|
@ -1030,10 +1019,10 @@ export default class MessageSender {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const dataMessage = window.textsecure.protobuf.DataMessage.decode(
|
||||
encodedDataMessage
|
||||
const dataMessage = Proto.DataMessage.decode(
|
||||
new FIXMEU8(encodedDataMessage)
|
||||
);
|
||||
const sentMessage = new window.textsecure.protobuf.SyncMessage.Sent();
|
||||
const sentMessage = new Proto.SyncMessage.Sent();
|
||||
sentMessage.timestamp = timestamp;
|
||||
sentMessage.message = dataMessage;
|
||||
if (destination) {
|
||||
|
@ -1063,13 +1052,17 @@ export default class MessageSender {
|
|||
// number we sent to.
|
||||
if (sentTo && sentTo.length) {
|
||||
sentMessage.unidentifiedStatus = sentTo.map(identifier => {
|
||||
const status = new window.textsecure.protobuf.SyncMessage.Sent.UnidentifiedDeliveryStatus();
|
||||
const status = new Proto.SyncMessage.Sent.UnidentifiedDeliveryStatus();
|
||||
const conv = window.ConversationController.get(identifier);
|
||||
if (conv && conv.get('e164')) {
|
||||
status.destination = conv.get('e164');
|
||||
}
|
||||
if (conv && conv.get('uuid')) {
|
||||
status.destinationUuid = conv.get('uuid');
|
||||
if (conv) {
|
||||
const e164 = conv.get('e164');
|
||||
if (e164) {
|
||||
status.destination = e164;
|
||||
}
|
||||
const uuid = conv.get('uuid');
|
||||
if (uuid) {
|
||||
status.destinationUuid = uuid;
|
||||
}
|
||||
}
|
||||
status.unidentified = Boolean(unidentifiedLookup[identifier]);
|
||||
return status;
|
||||
|
@ -1078,12 +1071,10 @@ export default class MessageSender {
|
|||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.sent = sentMessage;
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1101,17 +1092,14 @@ export default class MessageSender {
|
|||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||
if (myDevice !== 1) {
|
||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
||||
request.type =
|
||||
window.textsecure.protobuf.SyncMessage.Request.Type.BLOCKED;
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.request = request;
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1132,17 +1120,14 @@ export default class MessageSender {
|
|||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||
if (myDevice !== 1) {
|
||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
||||
request.type =
|
||||
window.textsecure.protobuf.SyncMessage.Request.Type.CONFIGURATION;
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.request = request;
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1163,16 +1148,14 @@ export default class MessageSender {
|
|||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||
if (myDevice !== 1) {
|
||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
||||
request.type = window.textsecure.protobuf.SyncMessage.Request.Type.GROUPS;
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.GROUPS;
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.request = request;
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1194,17 +1177,14 @@ export default class MessageSender {
|
|||
|
||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||
if (myDevice !== 1) {
|
||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
||||
request.type =
|
||||
window.textsecure.protobuf.SyncMessage.Request.Type.CONTACTS;
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.CONTACTS;
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.request = request;
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1229,18 +1209,15 @@ export default class MessageSender {
|
|||
return;
|
||||
}
|
||||
|
||||
const fetchLatest = new window.textsecure.protobuf.SyncMessage.FetchLatest();
|
||||
fetchLatest.type =
|
||||
window.textsecure.protobuf.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
|
||||
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
||||
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
|
||||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.fetchLatest = fetchLatest;
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
await this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1262,17 +1239,15 @@ export default class MessageSender {
|
|||
return;
|
||||
}
|
||||
|
||||
const request = new window.textsecure.protobuf.SyncMessage.Request();
|
||||
request.type = window.textsecure.protobuf.SyncMessage.Request.Type.KEYS;
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.KEYS;
|
||||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.request = request;
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
await this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1295,25 +1270,19 @@ export default class MessageSender {
|
|||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myDevice = window.textsecure.storage.user.getDeviceId();
|
||||
if (myDevice === 1) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.read = [];
|
||||
for (let i = 0; i < reads.length; i += 1) {
|
||||
const read = new window.textsecure.protobuf.SyncMessage.Read();
|
||||
read.timestamp = reads[i].timestamp;
|
||||
read.sender = reads[i].senderE164 || null;
|
||||
read.senderUuid = reads[i].senderUuid || null;
|
||||
const proto = new Proto.SyncMessage.Read(reads[i]);
|
||||
|
||||
syncMessage.read.push(read);
|
||||
syncMessage.read.push(proto);
|
||||
}
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1339,18 +1308,18 @@ export default class MessageSender {
|
|||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
|
||||
const viewOnceOpen = new window.textsecure.protobuf.SyncMessage.ViewOnceOpen();
|
||||
viewOnceOpen.sender = sender || null;
|
||||
viewOnceOpen.senderUuid = senderUuid || null;
|
||||
viewOnceOpen.timestamp = timestamp || null;
|
||||
const viewOnceOpen = new Proto.SyncMessage.ViewOnceOpen();
|
||||
if (sender !== undefined) {
|
||||
viewOnceOpen.sender = sender;
|
||||
}
|
||||
viewOnceOpen.senderUuid = senderUuid;
|
||||
viewOnceOpen.timestamp = timestamp;
|
||||
syncMessage.viewOnceOpen = viewOnceOpen;
|
||||
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1379,19 +1348,23 @@ export default class MessageSender {
|
|||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
|
||||
const response = new window.textsecure.protobuf.SyncMessage.MessageRequestResponse();
|
||||
response.threadE164 = responseArgs.threadE164 || null;
|
||||
response.threadUuid = responseArgs.threadUuid || null;
|
||||
response.groupId = responseArgs.groupId || null;
|
||||
const response = new Proto.SyncMessage.MessageRequestResponse();
|
||||
if (responseArgs.threadE164 !== undefined) {
|
||||
response.threadE164 = responseArgs.threadE164;
|
||||
}
|
||||
if (responseArgs.threadUuid !== undefined) {
|
||||
response.threadUuid = responseArgs.threadUuid;
|
||||
}
|
||||
if (responseArgs.groupId) {
|
||||
response.groupId = new FIXMEU8(responseArgs.groupId);
|
||||
}
|
||||
response.type = responseArgs.type;
|
||||
syncMessage.messageRequestResponse = response;
|
||||
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1417,15 +1390,14 @@ export default class MessageSender {
|
|||
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const ENUM =
|
||||
window.textsecure.protobuf.SyncMessage.StickerPackOperation.Type;
|
||||
const ENUM = Proto.SyncMessage.StickerPackOperation.Type;
|
||||
|
||||
const packOperations = operations.map(item => {
|
||||
const { packId, packKey, installed } = item;
|
||||
|
||||
const operation = new window.textsecure.protobuf.SyncMessage.StickerPackOperation();
|
||||
operation.packId = hexToArrayBuffer(packId);
|
||||
operation.packKey = base64ToArrayBuffer(packKey);
|
||||
const operation = new Proto.SyncMessage.StickerPackOperation();
|
||||
operation.packId = Bytes.fromHex(packId);
|
||||
operation.packKey = Bytes.fromBase64(packKey);
|
||||
operation.type = installed ? ENUM.INSTALL : ENUM.REMOVE;
|
||||
|
||||
return operation;
|
||||
|
@ -1434,12 +1406,10 @@ export default class MessageSender {
|
|||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.stickerPackOperation = packOperations;
|
||||
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1476,7 +1446,7 @@ export default class MessageSender {
|
|||
);
|
||||
|
||||
return promise.then(async () => {
|
||||
const verified = new window.textsecure.protobuf.Verified();
|
||||
const verified = new Proto.Verified();
|
||||
verified.state = state;
|
||||
if (destinationE164) {
|
||||
verified.destination = destinationE164;
|
||||
|
@ -1484,18 +1454,16 @@ export default class MessageSender {
|
|||
if (destinationUuid) {
|
||||
verified.destinationUuid = destinationUuid;
|
||||
}
|
||||
verified.identityKey = identityKey;
|
||||
verified.identityKey = new FIXMEU8(identityKey);
|
||||
verified.nullMessage = padding;
|
||||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.verified = verified;
|
||||
|
||||
const secondMessage = new window.textsecure.protobuf.Content();
|
||||
const secondMessage = new Proto.Content();
|
||||
secondMessage.syncMessage = syncMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
await this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
|
@ -1515,21 +1483,19 @@ export default class MessageSender {
|
|||
options: SendOptionsType,
|
||||
groupId?: string
|
||||
): Promise<CallbackResultType> {
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendMessage({
|
||||
messageOptions: {
|
||||
recipients,
|
||||
timestamp: Date.now(),
|
||||
profileKey,
|
||||
flags: window.textsecure.protobuf.DataMessage.Flags.PROFILE_KEY_UPDATE,
|
||||
flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE,
|
||||
...(groupId
|
||||
? {
|
||||
group: {
|
||||
id: groupId,
|
||||
type: window.textsecure.protobuf.GroupContext.Type.DELIVER,
|
||||
type: Proto.GroupContext.Type.DELIVER,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
@ -1542,18 +1508,16 @@ export default class MessageSender {
|
|||
|
||||
async sendCallingMessage(
|
||||
recipientId: string,
|
||||
callingMessage: CallingMessageClass,
|
||||
callingMessage: Proto.ICallingMessage,
|
||||
options?: SendOptionsType
|
||||
): Promise<void> {
|
||||
const recipients = [recipientId];
|
||||
const finalTimestamp = Date.now();
|
||||
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.callingMessage = callingMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
await this.sendMessageProtoAndWait({
|
||||
timestamp: finalTimestamp,
|
||||
|
@ -1583,17 +1547,14 @@ export default class MessageSender {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const receiptMessage = new window.textsecure.protobuf.ReceiptMessage();
|
||||
receiptMessage.type =
|
||||
window.textsecure.protobuf.ReceiptMessage.Type.DELIVERY;
|
||||
const receiptMessage = new Proto.ReceiptMessage();
|
||||
receiptMessage.type = Proto.ReceiptMessage.Type.DELIVERY;
|
||||
receiptMessage.timestamp = timestamps;
|
||||
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.receiptMessage = receiptMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: uuid || e164,
|
||||
|
@ -1615,16 +1576,14 @@ export default class MessageSender {
|
|||
timestamps: Array<number>;
|
||||
options?: SendOptionsType;
|
||||
}): Promise<CallbackResultType> {
|
||||
const receiptMessage = new window.textsecure.protobuf.ReceiptMessage();
|
||||
receiptMessage.type = window.textsecure.protobuf.ReceiptMessage.Type.READ;
|
||||
const receiptMessage = new Proto.ReceiptMessage();
|
||||
receiptMessage.type = Proto.ReceiptMessage.Type.READ;
|
||||
receiptMessage.timestamp = timestamps;
|
||||
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.receiptMessage = receiptMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: senderUuid || senderE164,
|
||||
|
@ -1640,10 +1599,10 @@ export default class MessageSender {
|
|||
uuid,
|
||||
e164,
|
||||
padding,
|
||||
}: { uuid?: string; e164?: string; padding?: ArrayBuffer },
|
||||
}: { uuid?: string; e164?: string; padding?: Uint8Array },
|
||||
options?: SendOptionsType
|
||||
): Promise<CallbackResultType> {
|
||||
const nullMessage = new window.textsecure.protobuf.NullMessage();
|
||||
const nullMessage = new Proto.NullMessage();
|
||||
|
||||
const identifier = uuid || e164;
|
||||
if (!identifier) {
|
||||
|
@ -1652,12 +1611,10 @@ export default class MessageSender {
|
|||
|
||||
nullMessage.padding = padding || this.getRandomPadding();
|
||||
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.nullMessage = nullMessage;
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
// We want the NullMessage to look like a normal outgoing message
|
||||
const timestamp = Date.now();
|
||||
|
@ -1679,9 +1636,9 @@ export default class MessageSender {
|
|||
CallbackResultType | void | Array<CallbackResultType | void | Array<void>>
|
||||
> {
|
||||
window.log.info('resetSession: start');
|
||||
const proto = new window.textsecure.protobuf.DataMessage();
|
||||
const proto = new Proto.DataMessage();
|
||||
proto.body = 'TERMINATE';
|
||||
proto.flags = window.textsecure.protobuf.DataMessage.Flags.END_SESSION;
|
||||
proto.flags = Proto.DataMessage.Flags.END_SESSION;
|
||||
proto.timestamp = timestamp;
|
||||
|
||||
const identifier = uuid || e164;
|
||||
|
@ -1691,9 +1648,7 @@ export default class MessageSender {
|
|||
throw error;
|
||||
};
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
const sendToContactPromise = window.textsecure.storage.protocol
|
||||
.archiveAllSessions(identifier)
|
||||
|
@ -1723,7 +1678,9 @@ export default class MessageSender {
|
|||
return sendToContactPromise;
|
||||
}
|
||||
|
||||
const buffer = proto.toArrayBuffer();
|
||||
const buffer = typedArrayToArrayBuffer(
|
||||
Proto.DataMessage.encode(proto).finish()
|
||||
);
|
||||
const sendSyncPromise = this.sendSyncMessage({
|
||||
encodedDataMessage: buffer,
|
||||
timestamp,
|
||||
|
@ -1745,9 +1702,7 @@ export default class MessageSender {
|
|||
profileKey?: ArrayBuffer,
|
||||
options?: SendOptionsType
|
||||
): Promise<CallbackResultType> {
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendMessage({
|
||||
messageOptions: {
|
||||
|
@ -1755,8 +1710,7 @@ export default class MessageSender {
|
|||
timestamp,
|
||||
expireTimer,
|
||||
profileKey,
|
||||
flags:
|
||||
window.textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||
flags: Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||
},
|
||||
contentHint: ContentHint.DEFAULT,
|
||||
groupId: undefined,
|
||||
|
@ -1773,9 +1727,7 @@ export default class MessageSender {
|
|||
plaintext: PlaintextContent;
|
||||
uuid: string;
|
||||
}): Promise<CallbackResultType> {
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendMessageProtoAndWait({
|
||||
timestamp: Date.now(),
|
||||
|
@ -1799,13 +1751,17 @@ export default class MessageSender {
|
|||
options,
|
||||
}: {
|
||||
recipients: Array<string>;
|
||||
proto: ContentClass;
|
||||
proto: Proto.Content;
|
||||
timestamp: number;
|
||||
contentHint: number;
|
||||
groupId: string | undefined;
|
||||
options?: SendOptionsType;
|
||||
}): Promise<CallbackResultType> {
|
||||
const dataMessage = proto.dataMessage?.toArrayBuffer();
|
||||
const dataMessage = proto.dataMessage
|
||||
? typedArrayToArrayBuffer(
|
||||
Proto.DataMessage.encode(proto.dataMessage).finish()
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const myE164 = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
|
@ -1887,14 +1843,12 @@ export default class MessageSender {
|
|||
},
|
||||
options?: SendOptionsType
|
||||
): Promise<CallbackResultType> {
|
||||
const contentMessage = new window.textsecure.protobuf.Content();
|
||||
const contentMessage = new Proto.Content();
|
||||
|
||||
const senderKeyDistributionMessage = await this.getSenderKeyDistributionMessage(
|
||||
distributionId
|
||||
);
|
||||
contentMessage.senderKeyDistributionMessage = window.dcodeIO.ByteBuffer.wrap(
|
||||
typedArrayToArrayBuffer(senderKeyDistributionMessage.serialize())
|
||||
);
|
||||
contentMessage.senderKeyDistributionMessage = senderKeyDistributionMessage.serialize();
|
||||
|
||||
return this.sendGroupProto({
|
||||
recipients: identifiers,
|
||||
|
@ -1913,14 +1867,16 @@ export default class MessageSender {
|
|||
groupIdentifiers: Array<string>,
|
||||
options?: SendOptionsType
|
||||
): Promise<CallbackResultType> {
|
||||
const proto = new window.textsecure.protobuf.DataMessage();
|
||||
proto.group = new window.textsecure.protobuf.GroupContext();
|
||||
proto.group.id = stringToArrayBuffer(groupId);
|
||||
proto.group.type = window.textsecure.protobuf.GroupContext.Type.QUIT;
|
||||
const proto = new Proto.Content({
|
||||
dataMessage: {
|
||||
group: {
|
||||
id: Bytes.fromString(groupId),
|
||||
type: Proto.GroupContext.Type.QUIT,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
return this.sendGroupProto({
|
||||
recipients: groupIdentifiers,
|
||||
proto,
|
||||
|
@ -1949,11 +1905,10 @@ export default class MessageSender {
|
|||
timestamp,
|
||||
expireTimer,
|
||||
profileKey,
|
||||
flags:
|
||||
window.textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||
flags: Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||
group: {
|
||||
id: groupId,
|
||||
type: window.textsecure.protobuf.GroupContext.Type.DELIVER,
|
||||
type: Proto.GroupContext.Type.DELIVER,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1967,9 +1922,7 @@ export default class MessageSender {
|
|||
});
|
||||
}
|
||||
|
||||
const {
|
||||
ContentHint,
|
||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
return this.sendMessage({
|
||||
messageOptions,
|
||||
contentHint: ContentHint.DEFAULT,
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import EventTarget from './EventTarget';
|
||||
import EventTarget, { EventHandler } from './EventTarget';
|
||||
import MessageReceiver from './MessageReceiver';
|
||||
import { ContactSyncEvent, GroupSyncEvent } from './messageReceiverEvents';
|
||||
import MessageSender from './SendMessage';
|
||||
import { assert } from '../util/assert';
|
||||
|
||||
|
@ -20,9 +21,9 @@ class SyncRequestInner extends EventTarget {
|
|||
|
||||
timeout: any;
|
||||
|
||||
oncontact: Function;
|
||||
oncontact: (event: ContactSyncEvent) => void;
|
||||
|
||||
ongroup: Function;
|
||||
ongroup: (event: GroupSyncEvent) => void;
|
||||
|
||||
timeoutMillis: number;
|
||||
|
||||
|
@ -43,10 +44,10 @@ class SyncRequestInner extends EventTarget {
|
|||
}
|
||||
|
||||
this.oncontact = this.onContactSyncComplete.bind(this);
|
||||
receiver.addEventListener('contactsync', this.oncontact);
|
||||
receiver.addEventListener('contactSync', this.oncontact);
|
||||
|
||||
this.ongroup = this.onGroupSyncComplete.bind(this);
|
||||
receiver.addEventListener('groupsync', this.ongroup);
|
||||
receiver.addEventListener('groupSync', this.ongroup);
|
||||
|
||||
this.timeoutMillis = timeoutMillis || 60000;
|
||||
}
|
||||
|
@ -126,9 +127,15 @@ class SyncRequestInner extends EventTarget {
|
|||
export default class SyncRequest {
|
||||
private inner: SyncRequestInner;
|
||||
|
||||
addEventListener: (name: string, handler: Function) => void;
|
||||
addEventListener: (
|
||||
name: 'success' | 'timeout',
|
||||
handler: EventHandler
|
||||
) => void;
|
||||
|
||||
removeEventListener: (name: string, handler: Function) => void;
|
||||
removeEventListener: (
|
||||
name: 'success' | 'timeout',
|
||||
handler: EventHandler
|
||||
) => void;
|
||||
|
||||
constructor(
|
||||
sender: MessageSender,
|
||||
|
|
151
ts/textsecure/Types.d.ts
vendored
151
ts/textsecure/Types.d.ts
vendored
|
@ -1,6 +1,8 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { SignalService as Proto } from '../protobuf';
|
||||
|
||||
export {
|
||||
IdentityKeyType,
|
||||
PreKeyType,
|
||||
|
@ -56,3 +58,152 @@ export type OuterSignedPrekeyType = {
|
|||
};
|
||||
|
||||
export type SessionResetsType = Record<string, number>;
|
||||
|
||||
export type ProcessedEnvelope = Readonly<{
|
||||
id: string;
|
||||
receivedAtCounter: number;
|
||||
receivedAtDate: number;
|
||||
messageAgeSec: number;
|
||||
|
||||
// Mostly from Proto.Envelope except for null/undefined
|
||||
type: Proto.Envelope.Type;
|
||||
source?: string;
|
||||
sourceUuid?: string;
|
||||
sourceDevice?: number;
|
||||
timestamp: number;
|
||||
legacyMessage?: Uint8Array;
|
||||
content?: Uint8Array;
|
||||
serverGuid: string;
|
||||
serverTimestamp: number;
|
||||
}>;
|
||||
|
||||
export type ProcessedAttachment = {
|
||||
cdnId?: string;
|
||||
cdnKey?: string;
|
||||
digest?: string;
|
||||
contentType?: string;
|
||||
key?: string;
|
||||
size?: number;
|
||||
fileName?: string;
|
||||
flags?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
caption?: string;
|
||||
blurHash?: string;
|
||||
cdnNumber?: number;
|
||||
};
|
||||
|
||||
export type ProcessedGroupContext = {
|
||||
id: string;
|
||||
type: Proto.GroupContext.Type;
|
||||
name?: string;
|
||||
membersE164: ReadonlyArray<string>;
|
||||
avatar?: ProcessedAttachment;
|
||||
|
||||
// Computed fields
|
||||
derivedGroupV2Id: string;
|
||||
};
|
||||
|
||||
export type ProcessedGroupV2Context = {
|
||||
masterKey: string;
|
||||
revision?: number;
|
||||
groupChange?: string;
|
||||
|
||||
// Computed fields
|
||||
id: string;
|
||||
secretParams: string;
|
||||
publicParams: string;
|
||||
};
|
||||
|
||||
export type ProcessedQuoteAttachment = {
|
||||
contentType?: string;
|
||||
fileName?: string;
|
||||
thumbnail?: ProcessedAttachment;
|
||||
};
|
||||
|
||||
export type ProcessedQuote = {
|
||||
id?: number;
|
||||
authorUuid?: string;
|
||||
text?: string;
|
||||
attachments: ReadonlyArray<ProcessedQuoteAttachment>;
|
||||
bodyRanges: ReadonlyArray<Proto.DataMessage.IBodyRange>;
|
||||
};
|
||||
|
||||
export type ProcessedAvatar = {
|
||||
avatar?: ProcessedAttachment;
|
||||
isProfile: boolean;
|
||||
};
|
||||
|
||||
export type ProcessedContact = Omit<Proto.DataMessage.IContact, 'avatar'> & {
|
||||
avatar?: ProcessedAvatar;
|
||||
};
|
||||
|
||||
export type ProcessedPreview = {
|
||||
url?: string;
|
||||
title?: string;
|
||||
image?: ProcessedAttachment;
|
||||
description?: string;
|
||||
date?: number;
|
||||
};
|
||||
|
||||
export type ProcessedSticker = {
|
||||
packId?: string;
|
||||
packKey?: string;
|
||||
stickerId?: number;
|
||||
data?: ProcessedAttachment;
|
||||
};
|
||||
|
||||
export type ProcessedReaction = {
|
||||
emoji?: string;
|
||||
remove: boolean;
|
||||
targetAuthorUuid?: string;
|
||||
targetTimestamp?: number;
|
||||
};
|
||||
|
||||
export type ProcessedDelete = {
|
||||
targetSentTimestamp?: number;
|
||||
};
|
||||
|
||||
export type ProcessedBodyRange = Proto.DataMessage.IBodyRange;
|
||||
|
||||
export type ProcessedGroupCallUpdate = Proto.DataMessage.IGroupCallUpdate;
|
||||
|
||||
export type ProcessedDataMessage = {
|
||||
body?: string;
|
||||
attachments: ReadonlyArray<ProcessedAttachment>;
|
||||
group?: ProcessedGroupContext;
|
||||
groupV2?: ProcessedGroupV2Context;
|
||||
flags: number;
|
||||
expireTimer: number;
|
||||
profileKey?: string;
|
||||
timestamp: number;
|
||||
quote?: ProcessedQuote;
|
||||
contact?: ReadonlyArray<ProcessedContact>;
|
||||
preview?: ReadonlyArray<ProcessedPreview>;
|
||||
sticker?: ProcessedSticker;
|
||||
requiredProtocolVersion?: number;
|
||||
isViewOnce: boolean;
|
||||
reaction?: ProcessedReaction;
|
||||
delete?: ProcessedDelete;
|
||||
bodyRanges?: ReadonlyArray<ProcessedBodyRange>;
|
||||
groupCallUpdate?: ProcessedGroupCallUpdate;
|
||||
};
|
||||
|
||||
export type ProcessedUnidentifiedDeliveryStatus = Omit<
|
||||
Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus,
|
||||
'destinationUuid'
|
||||
> & {
|
||||
destinationUuid?: string;
|
||||
};
|
||||
|
||||
export type ProcessedSent = Omit<
|
||||
Proto.SyncMessage.ISent,
|
||||
'destinationId' | 'unidentifiedStatus'
|
||||
> & {
|
||||
destinationId?: string;
|
||||
unidentifiedStatus?: Array<ProcessedUnidentifiedDeliveryStatus>;
|
||||
};
|
||||
|
||||
export type ProcessedSyncMessage = Omit<Proto.ISyncMessage, 'sent'> & {
|
||||
sent?: ProcessedSent;
|
||||
};
|
||||
|
|
|
@ -33,7 +33,7 @@ import { Long } from '../window.d';
|
|||
import { assert } from '../util/assert';
|
||||
import { getUserAgent } from '../util/getUserAgent';
|
||||
import { toWebSafeBase64 } from '../util/webSafeBase64';
|
||||
import { isPackIdValid, redactPackId } from '../../js/modules/stickers';
|
||||
import { isPackIdValid, redactPackId } from '../types/Stickers';
|
||||
import {
|
||||
arrayBufferToBase64,
|
||||
base64ToArrayBuffer,
|
||||
|
@ -53,7 +53,6 @@ import { calculateAgreement, generateKeyPair } from '../Curve';
|
|||
import * as linkPreviewFetch from '../linkPreviews/linkPreviewFetch';
|
||||
|
||||
import {
|
||||
AvatarUploadAttributesClass,
|
||||
StorageServiceCallOptionsType,
|
||||
StorageServiceCredentials,
|
||||
} from '../textsecure.d';
|
||||
|
@ -2161,7 +2160,7 @@ export function initialize({
|
|||
return Proto.GroupExternalCredential.decode(new FIXMEU8(response));
|
||||
}
|
||||
|
||||
function verifyAttributes(attributes: AvatarUploadAttributesClass) {
|
||||
function verifyAttributes(attributes: Proto.IAvatarUploadAttributes) {
|
||||
const {
|
||||
key,
|
||||
credential,
|
||||
|
@ -2213,8 +2212,8 @@ export function initialize({
|
|||
responseType: 'arraybuffer',
|
||||
host: storageUrl,
|
||||
});
|
||||
const attributes = window.textsecure.protobuf.AvatarUploadAttributes.decode(
|
||||
response
|
||||
const attributes = Proto.AvatarUploadAttributes.decode(
|
||||
new FIXMEU8(response)
|
||||
);
|
||||
|
||||
const verified = verifyAttributes(attributes);
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
import { connection as WebSocket, IMessage } from 'websocket';
|
||||
|
||||
import EventTarget from './EventTarget';
|
||||
import EventTarget, { EventHandler } from './EventTarget';
|
||||
|
||||
import { dropNull } from '../util/dropNull';
|
||||
import { isOlderThan } from '../util/timestamp';
|
||||
|
@ -120,6 +120,12 @@ export type WebSocketResourceOptions = {
|
|||
keepalive?: KeepAliveOptionsType | true;
|
||||
};
|
||||
|
||||
export class CloseEvent extends Event {
|
||||
constructor(public readonly code: number, public readonly reason: string) {
|
||||
super('close');
|
||||
}
|
||||
}
|
||||
|
||||
export default class WebSocketResource extends EventTarget {
|
||||
private outgoingId = 1;
|
||||
|
||||
|
@ -159,6 +165,15 @@ export default class WebSocketResource extends EventTarget {
|
|||
});
|
||||
}
|
||||
|
||||
public addEventListener(
|
||||
name: 'close',
|
||||
handler: (ev: CloseEvent) => void
|
||||
): void;
|
||||
|
||||
public addEventListener(name: string, handler: EventHandler): void {
|
||||
return super.addEventListener(name, handler);
|
||||
}
|
||||
|
||||
public sendRequest(
|
||||
options: OutgoingWebSocketRequestOptions
|
||||
): OutgoingWebSocketRequest {
|
||||
|
@ -204,10 +219,7 @@ export default class WebSocketResource extends EventTarget {
|
|||
}
|
||||
|
||||
window.log.warn('Dispatching our own socket close event');
|
||||
const ev = new Event('close');
|
||||
ev.code = code;
|
||||
ev.reason = reason;
|
||||
this.dispatchEvent(ev);
|
||||
this.dispatchEvent(new CloseEvent(code, reason || 'normal'));
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
|
|
370
ts/textsecure/messageReceiverEvents.ts
Normal file
370
ts/textsecure/messageReceiverEvents.ts
Normal file
|
@ -0,0 +1,370 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { ProcessedDataMessage, ProcessedSent } from './Types.d';
|
||||
import type {
|
||||
ModifiedContactDetails,
|
||||
ModifiedGroupDetails,
|
||||
} from './ContactsParser';
|
||||
|
||||
export class ReconnectEvent extends Event {
|
||||
constructor() {
|
||||
super('reconnect');
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyEvent extends Event {
|
||||
constructor() {
|
||||
super('empty');
|
||||
}
|
||||
}
|
||||
|
||||
export class ProgressEvent extends Event {
|
||||
public readonly count: number;
|
||||
|
||||
constructor({ count }: { count: number }) {
|
||||
super('progress');
|
||||
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
export type TypingEventData = Readonly<{
|
||||
typingMessage: Proto.ITypingMessage;
|
||||
timestamp: number;
|
||||
started: boolean;
|
||||
stopped: boolean;
|
||||
groupId?: string;
|
||||
groupV2Id?: string;
|
||||
}>;
|
||||
|
||||
export type TypingEventConfig = {
|
||||
sender?: string;
|
||||
senderUuid?: string;
|
||||
senderDevice: number;
|
||||
typing: TypingEventData;
|
||||
};
|
||||
|
||||
export class TypingEvent extends Event {
|
||||
public readonly sender?: string;
|
||||
|
||||
public readonly senderUuid?: string;
|
||||
|
||||
public readonly senderDevice: number;
|
||||
|
||||
public readonly typing: TypingEventData;
|
||||
|
||||
constructor({ sender, senderUuid, senderDevice, typing }: TypingEventConfig) {
|
||||
super('typing');
|
||||
|
||||
this.sender = sender;
|
||||
this.senderUuid = senderUuid;
|
||||
this.senderDevice = senderDevice;
|
||||
this.typing = typing;
|
||||
}
|
||||
}
|
||||
|
||||
export class ErrorEvent extends Event {
|
||||
constructor(public readonly error: Error) {
|
||||
super('error');
|
||||
}
|
||||
}
|
||||
|
||||
export type DecryptionErrorEventData = Readonly<{
|
||||
cipherTextBytes?: ArrayBuffer;
|
||||
cipherTextType?: number;
|
||||
contentHint?: number;
|
||||
groupId?: string;
|
||||
receivedAtCounter: number;
|
||||
receivedAtDate: number;
|
||||
senderDevice: number;
|
||||
senderUuid: string;
|
||||
timestamp: number;
|
||||
}>;
|
||||
|
||||
export class DecryptionErrorEvent extends Event {
|
||||
constructor(public readonly decryptionError: DecryptionErrorEventData) {
|
||||
super('decryption-error');
|
||||
}
|
||||
}
|
||||
|
||||
export type RetryRequestEventData = Readonly<{
|
||||
groupId?: string;
|
||||
requesterUuid: string;
|
||||
requesterDevice: number;
|
||||
senderDevice: number;
|
||||
sentAt: number;
|
||||
}>;
|
||||
|
||||
export class RetryRequestEvent extends Event {
|
||||
constructor(public readonly retryRequest: RetryRequestEventData) {
|
||||
super('retry-request');
|
||||
}
|
||||
}
|
||||
|
||||
export class ContactEvent extends Event {
|
||||
constructor(public readonly contactDetails: ModifiedContactDetails) {
|
||||
super('contact');
|
||||
}
|
||||
}
|
||||
|
||||
export class ContactSyncEvent extends Event {
|
||||
constructor() {
|
||||
super('contactSync');
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupEvent extends Event {
|
||||
constructor(public readonly groupDetails: ModifiedGroupDetails) {
|
||||
super('group');
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupSyncEvent extends Event {
|
||||
constructor() {
|
||||
super('groupSync');
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Confirmable events below
|
||||
//
|
||||
|
||||
export type ConfirmCallback = () => void;
|
||||
|
||||
export class ConfirmableEvent extends Event {
|
||||
constructor(type: string, public readonly confirm: ConfirmCallback) {
|
||||
super(type);
|
||||
}
|
||||
}
|
||||
|
||||
export type DeliveryEventData = Readonly<{
|
||||
timestamp: number;
|
||||
envelopeTimestamp?: number;
|
||||
source?: string;
|
||||
sourceUuid?: string;
|
||||
sourceDevice?: number;
|
||||
}>;
|
||||
|
||||
export class DeliveryEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly deliveryReceipt: DeliveryEventData,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('delivery', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export type SentEventData = Readonly<{
|
||||
destination?: string;
|
||||
destinationUuid?: string;
|
||||
timestamp?: number;
|
||||
serverTimestamp?: number;
|
||||
device?: number;
|
||||
unidentifiedStatus: ProcessedSent['unidentifiedStatus'];
|
||||
message: ProcessedDataMessage;
|
||||
isRecipientUpdate: boolean;
|
||||
receivedAtCounter: number;
|
||||
receivedAtDate: number;
|
||||
expirationStartTimestamp?: number;
|
||||
}>;
|
||||
|
||||
export class SentEvent extends ConfirmableEvent {
|
||||
constructor(public readonly data: SentEventData, confirm: ConfirmCallback) {
|
||||
super('sent', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export type ProfileKeyUpdateData = Readonly<{
|
||||
source?: string;
|
||||
sourceUuid?: string;
|
||||
profileKey: string;
|
||||
}>;
|
||||
|
||||
export class ProfileKeyUpdateEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly data: ProfileKeyUpdateData,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('profileKeyUpdate', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageEventData = Readonly<{
|
||||
source?: string;
|
||||
sourceUuid?: string;
|
||||
sourceDevice?: number;
|
||||
timestamp: number;
|
||||
serverGuid?: string;
|
||||
serverTimestamp?: number;
|
||||
unidentifiedDeliveryReceived: boolean;
|
||||
message: ProcessedDataMessage;
|
||||
receivedAtCounter: number;
|
||||
receivedAtDate: number;
|
||||
}>;
|
||||
|
||||
export class MessageEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly data: MessageEventData,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('message', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export type ReadEventData = Readonly<{
|
||||
timestamp: number;
|
||||
envelopeTimestamp: number;
|
||||
source?: string;
|
||||
sourceUuid?: string;
|
||||
}>;
|
||||
|
||||
export class ReadEvent extends ConfirmableEvent {
|
||||
constructor(public readonly read: ReadEventData, confirm: ConfirmCallback) {
|
||||
super('read', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigurationEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly configuration: Proto.SyncMessage.IConfiguration,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('configuration', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export type ViewSyncOptions = {
|
||||
source?: string;
|
||||
sourceUuid?: string;
|
||||
timestamp?: number;
|
||||
};
|
||||
|
||||
export class ViewSyncEvent extends ConfirmableEvent {
|
||||
public readonly source?: string;
|
||||
|
||||
public readonly sourceUuid?: string;
|
||||
|
||||
public readonly timestamp?: number;
|
||||
|
||||
constructor(
|
||||
{ source, sourceUuid, timestamp }: ViewSyncOptions,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('viewSync', confirm);
|
||||
|
||||
this.source = source;
|
||||
this.sourceUuid = sourceUuid;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageRequestResponseOptions = {
|
||||
threadE164?: string;
|
||||
threadUuid?: string;
|
||||
messageRequestResponseType: Proto.SyncMessage.IMessageRequestResponse['type'];
|
||||
groupId?: string;
|
||||
groupV2Id?: string;
|
||||
};
|
||||
|
||||
export class MessageRequestResponseEvent extends ConfirmableEvent {
|
||||
public readonly threadE164?: string;
|
||||
|
||||
public readonly threadUuid?: string;
|
||||
|
||||
public readonly messageRequestResponseType?: MessageRequestResponseOptions['messageRequestResponseType'];
|
||||
|
||||
public readonly groupId?: string;
|
||||
|
||||
public readonly groupV2Id?: string;
|
||||
|
||||
constructor(
|
||||
{
|
||||
threadE164,
|
||||
threadUuid,
|
||||
messageRequestResponseType,
|
||||
groupId,
|
||||
groupV2Id,
|
||||
}: MessageRequestResponseOptions,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('messageRequestResponse', confirm);
|
||||
|
||||
this.threadE164 = threadE164;
|
||||
this.threadUuid = threadUuid;
|
||||
this.messageRequestResponseType = messageRequestResponseType;
|
||||
this.groupId = groupId;
|
||||
this.groupV2Id = groupV2Id;
|
||||
}
|
||||
}
|
||||
|
||||
export class FetchLatestEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly eventType: Proto.SyncMessage.IFetchLatest['type'],
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('fetchLatest', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export class KeysEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly storageServiceKey: ArrayBuffer,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('keys', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export type StickerPackEventData = Readonly<{
|
||||
id?: string;
|
||||
key?: string;
|
||||
isInstall: boolean;
|
||||
isRemove: boolean;
|
||||
}>;
|
||||
|
||||
export class StickerPackEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly stickerPacks: ReadonlyArray<StickerPackEventData>,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('sticker-pack', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export type VerifiedEventData = Readonly<{
|
||||
state: Proto.IVerified['state'];
|
||||
destination?: string;
|
||||
destinationUuid?: string;
|
||||
identityKey?: ArrayBuffer;
|
||||
|
||||
// Used in `ts/background.ts`
|
||||
viaContactSync?: boolean;
|
||||
}>;
|
||||
|
||||
export class VerifiedEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly verified: VerifiedEventData,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('verified', confirm);
|
||||
}
|
||||
}
|
||||
|
||||
export type ReadSyncEventData = Readonly<{
|
||||
timestamp?: number;
|
||||
envelopeTimestamp: number;
|
||||
sender?: string;
|
||||
senderUuid?: string;
|
||||
}>;
|
||||
|
||||
export class ReadSyncEvent extends ConfirmableEvent {
|
||||
constructor(
|
||||
public readonly read: ReadSyncEventData,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('readSync', confirm);
|
||||
}
|
||||
}
|
352
ts/textsecure/processDataMessage.ts
Normal file
352
ts/textsecure/processDataMessage.ts
Normal file
|
@ -0,0 +1,352 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import Long from 'long';
|
||||
|
||||
import { assert, strictAssert } from '../util/assert';
|
||||
import { dropNull, shallowDropNull } from '../util/dropNull';
|
||||
import { normalizeNumber } from '../util/normalizeNumber';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { deriveGroupFields } from '../groups';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
|
||||
|
||||
import {
|
||||
ProcessedAttachment,
|
||||
ProcessedDataMessage,
|
||||
ProcessedGroupContext,
|
||||
ProcessedGroupV2Context,
|
||||
ProcessedQuote,
|
||||
ProcessedContact,
|
||||
ProcessedPreview,
|
||||
ProcessedSticker,
|
||||
ProcessedReaction,
|
||||
ProcessedDelete,
|
||||
} from './Types.d';
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
const FLAGS = Proto.DataMessage.Flags;
|
||||
export const ATTACHMENT_MAX = 32;
|
||||
|
||||
export function processAttachment(
|
||||
attachment: Proto.IAttachmentPointer
|
||||
): ProcessedAttachment;
|
||||
export function processAttachment(
|
||||
attachment?: Proto.IAttachmentPointer | null
|
||||
): ProcessedAttachment | undefined;
|
||||
|
||||
export function processAttachment(
|
||||
attachment?: Proto.IAttachmentPointer | null
|
||||
): ProcessedAttachment | undefined {
|
||||
if (!attachment) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...shallowDropNull(attachment),
|
||||
|
||||
cdnId: attachment.cdnId ? attachment.cdnId.toString() : undefined,
|
||||
key: attachment.key ? Bytes.toBase64(attachment.key) : undefined,
|
||||
digest: attachment.digest ? Bytes.toBase64(attachment.digest) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async function processGroupContext(
|
||||
group?: Proto.IGroupContext | null
|
||||
): Promise<ProcessedGroupContext | undefined> {
|
||||
if (!group) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
strictAssert(group.id, 'group context without id');
|
||||
strictAssert(
|
||||
group.type !== undefined && group.type !== null,
|
||||
'group context without type'
|
||||
);
|
||||
|
||||
const masterKey = await deriveMasterKeyFromGroupV1(
|
||||
typedArrayToArrayBuffer(group.id)
|
||||
);
|
||||
const data = deriveGroupFields(new FIXMEU8(masterKey));
|
||||
|
||||
const derivedGroupV2Id = Bytes.toBase64(data.id);
|
||||
|
||||
const result: ProcessedGroupContext = {
|
||||
id: Bytes.toBinary(group.id),
|
||||
type: group.type,
|
||||
name: dropNull(group.name),
|
||||
membersE164: group.membersE164 ?? [],
|
||||
avatar: processAttachment(group.avatar),
|
||||
derivedGroupV2Id,
|
||||
};
|
||||
|
||||
if (result.type === Proto.GroupContext.Type.DELIVER) {
|
||||
result.name = undefined;
|
||||
result.membersE164 = [];
|
||||
result.avatar = undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function processGroupV2Context(
|
||||
groupV2?: Proto.IGroupContextV2 | null
|
||||
): ProcessedGroupV2Context | undefined {
|
||||
if (!groupV2) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
strictAssert(groupV2.masterKey, 'groupV2 context without masterKey');
|
||||
const data = deriveGroupFields(groupV2.masterKey);
|
||||
|
||||
return {
|
||||
masterKey: Bytes.toBase64(groupV2.masterKey),
|
||||
revision: dropNull(groupV2.revision),
|
||||
groupChange: groupV2.groupChange
|
||||
? Bytes.toBase64(groupV2.groupChange)
|
||||
: undefined,
|
||||
id: Bytes.toBase64(data.id),
|
||||
secretParams: Bytes.toBase64(data.secretParams),
|
||||
publicParams: Bytes.toBase64(data.publicParams),
|
||||
};
|
||||
}
|
||||
|
||||
export function processQuote(
|
||||
quote?: Proto.DataMessage.IQuote | null
|
||||
): ProcessedQuote | undefined {
|
||||
if (!quote) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: normalizeNumber(dropNull(quote.id)),
|
||||
authorUuid: dropNull(quote.authorUuid),
|
||||
text: dropNull(quote.text),
|
||||
attachments: (quote.attachments ?? []).map(attachment => {
|
||||
return {
|
||||
contentType: dropNull(attachment.contentType),
|
||||
fileName: dropNull(attachment.fileName),
|
||||
thumbnail: processAttachment(attachment.thumbnail),
|
||||
};
|
||||
}),
|
||||
bodyRanges: quote.bodyRanges ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
export function processContact(
|
||||
contact?: ReadonlyArray<Proto.DataMessage.IContact> | null
|
||||
): ReadonlyArray<ProcessedContact> | undefined {
|
||||
if (!contact) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return contact.map(item => {
|
||||
return {
|
||||
...item,
|
||||
avatar: item.avatar
|
||||
? {
|
||||
avatar: processAttachment(item.avatar.avatar),
|
||||
isProfile: Boolean(item.avatar.isProfile),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function isLinkPreviewDateValid(value: unknown): value is number {
|
||||
return (
|
||||
typeof value === 'number' &&
|
||||
!Number.isNaN(value) &&
|
||||
Number.isFinite(value) &&
|
||||
value > 0
|
||||
);
|
||||
}
|
||||
|
||||
function cleanLinkPreviewDate(
|
||||
value?: Long | number | null
|
||||
): number | undefined {
|
||||
const result = normalizeNumber(value ?? undefined);
|
||||
return isLinkPreviewDateValid(result) ? result : undefined;
|
||||
}
|
||||
|
||||
export function processPreview(
|
||||
preview?: ReadonlyArray<Proto.DataMessage.IPreview> | null
|
||||
): ReadonlyArray<ProcessedPreview> | undefined {
|
||||
if (!preview) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return preview.map(item => {
|
||||
return {
|
||||
url: dropNull(item.url),
|
||||
title: dropNull(item.title),
|
||||
image: item.image ? processAttachment(item.image) : undefined,
|
||||
description: dropNull(item.description),
|
||||
date: cleanLinkPreviewDate(item.date),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function processSticker(
|
||||
sticker?: Proto.DataMessage.ISticker | null
|
||||
): ProcessedSticker | undefined {
|
||||
if (!sticker) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
packId: sticker.packId ? Bytes.toHex(sticker.packId) : undefined,
|
||||
packKey: sticker.packKey ? Bytes.toBase64(sticker.packKey) : undefined,
|
||||
stickerId: normalizeNumber(dropNull(sticker.stickerId)),
|
||||
data: processAttachment(sticker.data),
|
||||
};
|
||||
}
|
||||
|
||||
export function processReaction(
|
||||
reaction?: Proto.DataMessage.IReaction | null
|
||||
): ProcessedReaction | undefined {
|
||||
if (!reaction) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
emoji: dropNull(reaction.emoji),
|
||||
remove: Boolean(reaction.remove),
|
||||
targetAuthorUuid: dropNull(reaction.targetAuthorUuid),
|
||||
targetTimestamp: normalizeNumber(dropNull(reaction.targetTimestamp)),
|
||||
};
|
||||
}
|
||||
|
||||
export function processDelete(
|
||||
del?: Proto.DataMessage.IDelete | null
|
||||
): ProcessedDelete | undefined {
|
||||
if (!del) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
targetSentTimestamp: normalizeNumber(dropNull(del.targetSentTimestamp)),
|
||||
};
|
||||
}
|
||||
|
||||
export async function processDataMessage(
|
||||
message: Proto.IDataMessage,
|
||||
envelopeTimestamp: number
|
||||
): Promise<ProcessedDataMessage> {
|
||||
/* eslint-disable no-bitwise */
|
||||
|
||||
// Now that its decrypted, validate the message and clean it up for consumer
|
||||
// processing
|
||||
// Note that messages may (generally) only perform one action and we ignore remaining
|
||||
// fields after the first action.
|
||||
|
||||
if (!message.timestamp) {
|
||||
throw new Error('Missing timestamp on dataMessage');
|
||||
}
|
||||
|
||||
const timestamp = normalizeNumber(message.timestamp);
|
||||
|
||||
if (envelopeTimestamp !== timestamp) {
|
||||
throw new Error(
|
||||
`Timestamp ${timestamp} in DataMessage did not ` +
|
||||
`match envelope timestamp ${envelopeTimestamp}`
|
||||
);
|
||||
}
|
||||
|
||||
const result: ProcessedDataMessage = {
|
||||
body: dropNull(message.body),
|
||||
attachments: (
|
||||
message.attachments ?? []
|
||||
).map((attachment: Proto.IAttachmentPointer) =>
|
||||
processAttachment(attachment)
|
||||
),
|
||||
group: await processGroupContext(message.group),
|
||||
groupV2: processGroupV2Context(message.groupV2),
|
||||
flags: message.flags ?? 0,
|
||||
expireTimer: message.expireTimer ?? 0,
|
||||
profileKey: message.profileKey
|
||||
? Bytes.toBase64(message.profileKey)
|
||||
: undefined,
|
||||
timestamp,
|
||||
quote: processQuote(message.quote),
|
||||
contact: processContact(message.contact),
|
||||
preview: processPreview(message.preview),
|
||||
sticker: processSticker(message.sticker),
|
||||
requiredProtocolVersion: normalizeNumber(
|
||||
dropNull(message.requiredProtocolVersion)
|
||||
),
|
||||
isViewOnce: Boolean(message.isViewOnce),
|
||||
reaction: processReaction(message.reaction),
|
||||
delete: processDelete(message.delete),
|
||||
bodyRanges: message.bodyRanges ?? [],
|
||||
groupCallUpdate: dropNull(message.groupCallUpdate),
|
||||
};
|
||||
|
||||
const isEndSession = Boolean(result.flags & FLAGS.END_SESSION);
|
||||
const isExpirationTimerUpdate = Boolean(
|
||||
result.flags & FLAGS.EXPIRATION_TIMER_UPDATE
|
||||
);
|
||||
const isProfileKeyUpdate = Boolean(result.flags & FLAGS.PROFILE_KEY_UPDATE);
|
||||
// The following assertion codifies an assumption: 0 or 1 flags are set, but never
|
||||
// more. This assumption is fine as of this writing, but may not always be.
|
||||
const flagCount = [
|
||||
isEndSession,
|
||||
isExpirationTimerUpdate,
|
||||
isProfileKeyUpdate,
|
||||
].filter(Boolean).length;
|
||||
assert(
|
||||
flagCount <= 1,
|
||||
`Expected exactly <=1 flags to be set, but got ${flagCount}`
|
||||
);
|
||||
|
||||
if (isEndSession) {
|
||||
result.body = undefined;
|
||||
result.attachments = [];
|
||||
result.group = undefined;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (isExpirationTimerUpdate) {
|
||||
result.body = undefined;
|
||||
result.attachments = [];
|
||||
} else if (isProfileKeyUpdate) {
|
||||
result.body = undefined;
|
||||
result.attachments = [];
|
||||
} else if (result.flags !== 0) {
|
||||
throw new Error(`Unknown flags in message: ${result.flags}`);
|
||||
}
|
||||
|
||||
if (result.group) {
|
||||
switch (result.group.type) {
|
||||
case Proto.GroupContext.Type.UPDATE:
|
||||
result.body = undefined;
|
||||
result.attachments = [];
|
||||
break;
|
||||
case Proto.GroupContext.Type.QUIT:
|
||||
result.body = undefined;
|
||||
result.attachments = [];
|
||||
break;
|
||||
case Proto.GroupContext.Type.DELIVER:
|
||||
// Cleaned up in `processGroupContext`
|
||||
break;
|
||||
default: {
|
||||
const err = new Error(
|
||||
`Unknown group message type: ${result.group.type}`
|
||||
);
|
||||
err.warn = true;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const attachmentCount = result.attachments.length;
|
||||
if (attachmentCount > ATTACHMENT_MAX) {
|
||||
throw new Error(
|
||||
`Too many attachments: ${attachmentCount} included in one message, ` +
|
||||
`max is ${ATTACHMENT_MAX}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
60
ts/textsecure/processSyncMessage.ts
Normal file
60
ts/textsecure/processSyncMessage.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { normalizeUuid } from '../util/normalizeUuid';
|
||||
import {
|
||||
ProcessedUnidentifiedDeliveryStatus,
|
||||
ProcessedSent,
|
||||
ProcessedSyncMessage,
|
||||
} from './Types.d';
|
||||
|
||||
import UnidentifiedDeliveryStatus = Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus;
|
||||
|
||||
function processUnidentifiedDeliveryStatus(
|
||||
status: UnidentifiedDeliveryStatus
|
||||
): ProcessedUnidentifiedDeliveryStatus {
|
||||
const { destinationUuid } = status;
|
||||
|
||||
return {
|
||||
...status,
|
||||
|
||||
destinationUuid: destinationUuid
|
||||
? normalizeUuid(
|
||||
destinationUuid,
|
||||
'syncMessage.sent.unidentifiedStatus.destinationUuid'
|
||||
)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function processSent(
|
||||
sent?: Proto.SyncMessage.ISent | null
|
||||
): ProcessedSent | undefined {
|
||||
if (!sent) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { destinationUuid, unidentifiedStatus } = sent;
|
||||
|
||||
return {
|
||||
...sent,
|
||||
|
||||
destinationUuid: destinationUuid
|
||||
? normalizeUuid(destinationUuid, 'syncMessage.sent.destinationUuid')
|
||||
: undefined,
|
||||
|
||||
unidentifiedStatus: unidentifiedStatus
|
||||
? unidentifiedStatus.map(processUnidentifiedDeliveryStatus)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function processSyncMessage(
|
||||
syncMessage: Proto.ISyncMessage
|
||||
): ProcessedSyncMessage {
|
||||
return {
|
||||
...syncMessage,
|
||||
sent: processSent(syncMessage.sent),
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue