You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
6.6 KiB
TypeScript
234 lines
6.6 KiB
TypeScript
import ByteBuffer from 'bytebuffer';
|
|
import { isEmpty } from 'lodash';
|
|
import { SignalService } from '../../../../protobuf';
|
|
import { Reaction } from '../../../../types/Reaction';
|
|
import { LokiProfile } from '../../../../types/message';
|
|
import { ExpirableMessage, ExpirableMessageParams } from '../ExpirableMessage';
|
|
|
|
interface AttachmentPointerCommon {
|
|
contentType?: string;
|
|
key?: Uint8Array;
|
|
size?: number;
|
|
thumbnail?: Uint8Array;
|
|
digest?: Uint8Array;
|
|
fileName?: string;
|
|
flags?: number;
|
|
width?: number;
|
|
height?: number;
|
|
caption?: string;
|
|
}
|
|
|
|
export interface AttachmentPointer extends AttachmentPointerCommon {
|
|
url?: string;
|
|
id?: number;
|
|
}
|
|
|
|
export interface AttachmentPointerWithUrl extends AttachmentPointerCommon {
|
|
url: string;
|
|
id: number;
|
|
}
|
|
|
|
export interface Preview {
|
|
url: string;
|
|
title?: string;
|
|
image?: AttachmentPointer;
|
|
}
|
|
|
|
export interface PreviewWithAttachmentUrl {
|
|
url: string;
|
|
id: number;
|
|
title?: string;
|
|
image?: AttachmentPointerWithUrl;
|
|
}
|
|
|
|
interface QuotedAttachmentCommon {
|
|
contentType?: string;
|
|
fileName?: string;
|
|
}
|
|
|
|
export interface QuotedAttachment extends QuotedAttachmentCommon {
|
|
thumbnail?: AttachmentPointer;
|
|
}
|
|
|
|
export interface QuotedAttachmentWithUrl extends QuotedAttachmentCommon {
|
|
thumbnail?: AttachmentPointerWithUrl | QuotedAttachment;
|
|
}
|
|
|
|
export interface Quote {
|
|
id: number;
|
|
author: string;
|
|
text?: string;
|
|
attachments?: Array<QuotedAttachmentWithUrl>;
|
|
}
|
|
|
|
export interface VisibleMessageParams extends ExpirableMessageParams {
|
|
attachments?: Array<AttachmentPointerWithUrl>;
|
|
body?: string;
|
|
quote?: Quote;
|
|
lokiProfile?: LokiProfile;
|
|
preview?: Array<PreviewWithAttachmentUrl>;
|
|
reaction?: Reaction;
|
|
syncTarget?: string; // undefined means it is not a synced message
|
|
}
|
|
|
|
export class VisibleMessage extends ExpirableMessage {
|
|
public readonly reaction?: Reaction;
|
|
|
|
private readonly attachments?: Array<AttachmentPointerWithUrl>;
|
|
private readonly body?: string;
|
|
private readonly quote?: Quote;
|
|
private readonly profileKey?: Uint8Array;
|
|
private readonly profile?: SignalService.DataMessage.ILokiProfile;
|
|
private readonly preview?: Array<PreviewWithAttachmentUrl>;
|
|
|
|
/// In the case of a sync message, the public key of the person the message was targeted at.
|
|
/// - Note: `null or undefined` if this isn't a sync message.
|
|
private readonly syncTarget?: string;
|
|
|
|
constructor(params: VisibleMessageParams) {
|
|
super({
|
|
timestamp: params.timestamp,
|
|
identifier: params.identifier,
|
|
expirationType: params.expirationType,
|
|
expireTimer: params.expireTimer,
|
|
});
|
|
this.attachments = params.attachments;
|
|
this.body = params.body;
|
|
this.quote = params.quote;
|
|
|
|
const profile = buildProfileForOutgoingMessage(params);
|
|
|
|
this.profile = profile.lokiProfile;
|
|
this.profileKey = profile.profileKey;
|
|
|
|
this.preview = params.preview;
|
|
this.reaction = params.reaction;
|
|
this.syncTarget = params.syncTarget;
|
|
}
|
|
|
|
public contentProto(): SignalService.Content {
|
|
const content = super.contentProto();
|
|
content.dataMessage = this.dataProto();
|
|
return content;
|
|
}
|
|
|
|
public dataProto(): SignalService.DataMessage {
|
|
const dataMessage = super.dataProto();
|
|
|
|
if (this.body) {
|
|
dataMessage.body = this.body;
|
|
}
|
|
|
|
dataMessage.attachments = this.attachments || [];
|
|
|
|
if (this.preview) {
|
|
dataMessage.preview = this.preview;
|
|
}
|
|
if (this.reaction) {
|
|
dataMessage.reaction = this.reaction;
|
|
}
|
|
if (this.syncTarget) {
|
|
dataMessage.syncTarget = this.syncTarget;
|
|
}
|
|
|
|
if (this.profile) {
|
|
dataMessage.profile = this.profile;
|
|
}
|
|
|
|
if (this.profileKey && this.profileKey.length) {
|
|
dataMessage.profileKey = this.profileKey;
|
|
}
|
|
|
|
if (this.quote) {
|
|
dataMessage.quote = new SignalService.DataMessage.Quote();
|
|
|
|
dataMessage.quote.id = this.quote.id;
|
|
dataMessage.quote.author = this.quote.author;
|
|
dataMessage.quote.text = this.quote.text;
|
|
if (this.quote.attachments) {
|
|
dataMessage.quote.attachments = this.quote.attachments.map(attachment => {
|
|
const quotedAttachment = new SignalService.DataMessage.Quote.QuotedAttachment();
|
|
if (attachment.contentType) {
|
|
quotedAttachment.contentType = attachment.contentType;
|
|
}
|
|
if (attachment.fileName) {
|
|
quotedAttachment.fileName = attachment.fileName;
|
|
}
|
|
if (attachment.thumbnail && (attachment.thumbnail as any).id) {
|
|
quotedAttachment.thumbnail = attachment.thumbnail as any; // be sure to keep the typescript guard on id above
|
|
}
|
|
|
|
return quotedAttachment;
|
|
});
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(this.preview)) {
|
|
dataMessage.preview = this.preview.map(preview => {
|
|
const item = new SignalService.DataMessage.Preview();
|
|
if (preview.title) {
|
|
item.title = preview.title;
|
|
}
|
|
if (preview.url) {
|
|
item.url = preview.url;
|
|
}
|
|
item.image = preview.image || null;
|
|
|
|
return item;
|
|
});
|
|
}
|
|
|
|
dataMessage.timestamp = this.timestamp;
|
|
|
|
return dataMessage;
|
|
}
|
|
|
|
public isEqual(comparator: VisibleMessage): boolean {
|
|
return this.identifier === comparator.identifier && this.timestamp === comparator.timestamp;
|
|
}
|
|
}
|
|
|
|
export function buildProfileForOutgoingMessage(params: { lokiProfile?: LokiProfile }) {
|
|
let profileKey: Uint8Array | undefined;
|
|
if (params.lokiProfile && params.lokiProfile.profileKey) {
|
|
if (
|
|
params.lokiProfile.profileKey instanceof Uint8Array ||
|
|
(params.lokiProfile.profileKey as any) instanceof ByteBuffer
|
|
) {
|
|
profileKey = new Uint8Array(params.lokiProfile.profileKey);
|
|
} else {
|
|
profileKey = new Uint8Array(ByteBuffer.wrap(params.lokiProfile.profileKey).toArrayBuffer());
|
|
}
|
|
}
|
|
|
|
const displayName = params.lokiProfile?.displayName;
|
|
|
|
// no need to include the avatarPointer if there is no profileKey associated with it.
|
|
const avatarPointer =
|
|
params.lokiProfile?.avatarPointer &&
|
|
!isEmpty(profileKey) &&
|
|
params.lokiProfile.avatarPointer &&
|
|
!isEmpty(params.lokiProfile.avatarPointer)
|
|
? params.lokiProfile.avatarPointer
|
|
: undefined;
|
|
|
|
let lokiProfile: SignalService.DataMessage.ILokiProfile | undefined;
|
|
if (avatarPointer || displayName) {
|
|
lokiProfile = new SignalService.DataMessage.LokiProfile();
|
|
|
|
// we always need a profileKey tom decode an avatar pointer
|
|
if (avatarPointer && avatarPointer.length && profileKey) {
|
|
lokiProfile.profilePicture = avatarPointer;
|
|
}
|
|
|
|
if (displayName) {
|
|
lokiProfile.displayName = displayName;
|
|
}
|
|
}
|
|
|
|
return {
|
|
lokiProfile,
|
|
profileKey: lokiProfile?.profilePicture ? profileKey : undefined,
|
|
};
|
|
}
|