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,
 | |
|   };
 | |
| }
 |