|  |  |  | import * as crypto from 'crypto'; | 
					
						
							|  |  |  | import { Attachment } from '../../types/Attachment'; | 
					
						
							|  |  |  | import { OpenGroup } from '../types'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   AttachmentPointer, | 
					
						
							|  |  |  |   Preview, | 
					
						
							|  |  |  |   Quote, | 
					
						
							|  |  |  |   QuotedAttachment, | 
					
						
							|  |  |  | } from '../messages/outgoing'; | 
					
						
							|  |  |  | import { LokiAppDotNetServerInterface } from '../../../js/modules/loki_app_dot_net_api'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface UploadParams { | 
					
						
							|  |  |  |   attachment: Attachment; | 
					
						
							|  |  |  |   openGroup?: OpenGroup; | 
					
						
							|  |  |  |   isAvatar?: boolean; | 
					
						
							|  |  |  |   isRaw?: boolean; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface RawPreview { | 
					
						
							|  |  |  |   url?: string; | 
					
						
							|  |  |  |   title?: string; | 
					
						
							|  |  |  |   image: Attachment; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface RawQuoteAttachment { | 
					
						
							|  |  |  |   contentType?: string; | 
					
						
							|  |  |  |   fileName?: string; | 
					
						
							|  |  |  |   thumbnail?: Attachment; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface RawQuote { | 
					
						
							|  |  |  |   id?: number; | 
					
						
							|  |  |  |   author?: string; | 
					
						
							|  |  |  |   text?: string; | 
					
						
							|  |  |  |   attachments?: Array<RawQuoteAttachment>; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // tslint:disable-next-line: no-unnecessary-class
 | 
					
						
							|  |  |  | export class AttachmentUtils { | 
					
						
							|  |  |  |   private constructor() {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static getDefaultServer(): LokiAppDotNetServerInterface { | 
					
						
							|  |  |  |     return window.tokenlessFileServerAdnAPI; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static async upload(params: UploadParams): Promise<AttachmentPointer> { | 
					
						
							|  |  |  |     const { attachment, openGroup, isAvatar = false, isRaw = false } = params; | 
					
						
							|  |  |  |     if (typeof attachment !== 'object' || attachment == null) { | 
					
						
							|  |  |  |       throw new Error('Invalid attachment passed.'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!(attachment.data instanceof ArrayBuffer)) { | 
					
						
							|  |  |  |       throw new TypeError( | 
					
						
							|  |  |  |         `\`attachment.data\` must be an \`ArrayBuffer\`; got: ${typeof attachment.data}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let server = this.getDefaultServer(); | 
					
						
							|  |  |  |     if (openGroup) { | 
					
						
							|  |  |  |       const openGroupServer = await window.lokiPublicChatAPI.findOrCreateServer( | 
					
						
							|  |  |  |         openGroup.server | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       if (!openGroupServer) { | 
					
						
							|  |  |  |         throw new Error( | 
					
						
							|  |  |  |           `Failed to get open group server: ${openGroup.server}.` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       server = openGroupServer; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const pointer: AttachmentPointer = { | 
					
						
							|  |  |  |       contentType: attachment.contentType ? attachment.contentType : undefined, | 
					
						
							|  |  |  |       size: attachment.size, | 
					
						
							|  |  |  |       fileName: attachment.fileName, | 
					
						
							|  |  |  |       flags: attachment.flags, | 
					
						
							|  |  |  |       caption: attachment.caption, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let attachmentData: ArrayBuffer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (isRaw || openGroup) { | 
					
						
							|  |  |  |       attachmentData = attachment.data; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       server = this.getDefaultServer(); | 
					
						
							|  |  |  |       pointer.key = new Uint8Array(crypto.randomBytes(64)); | 
					
						
							|  |  |  |       const iv = new Uint8Array(crypto.randomBytes(16)); | 
					
						
							|  |  |  |       const data = await window.textsecure.crypto.encryptAttachment( | 
					
						
							|  |  |  |         attachment.data, | 
					
						
							|  |  |  |         pointer.key.buffer, | 
					
						
							|  |  |  |         iv.buffer | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       pointer.digest = new Uint8Array(data.digest); | 
					
						
							|  |  |  |       attachmentData = data.ciphertext; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const result = isAvatar | 
					
						
							|  |  |  |       ? await server.putAvatar(attachmentData) | 
					
						
							|  |  |  |       : await server.putAttachment(attachmentData); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pointer.id = result.id; | 
					
						
							|  |  |  |     pointer.url = result.url; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return pointer; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static async uploadAvatar( | 
					
						
							|  |  |  |     attachment?: Attachment | 
					
						
							|  |  |  |   ): Promise<AttachmentPointer | undefined> { | 
					
						
							|  |  |  |     if (!attachment) { | 
					
						
							|  |  |  |       return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // isRaw is true since the data is already encrypted
 | 
					
						
							|  |  |  |     // and doesn't need to be encrypted again
 | 
					
						
							|  |  |  |     return this.upload({ | 
					
						
							|  |  |  |       attachment, | 
					
						
							|  |  |  |       isAvatar: true, | 
					
						
							|  |  |  |       isRaw: true, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static async uploadAttachments( | 
					
						
							|  |  |  |     attachments: Array<Attachment>, | 
					
						
							|  |  |  |     openGroup?: OpenGroup | 
					
						
							|  |  |  |   ): Promise<Array<AttachmentPointer>> { | 
					
						
							|  |  |  |     const promises = (attachments || []).map(async attachment => | 
					
						
							|  |  |  |       this.upload({ | 
					
						
							|  |  |  |         attachment, | 
					
						
							|  |  |  |         openGroup, | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Promise.all(promises); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static async uploadLinkPreviews( | 
					
						
							|  |  |  |     previews: Array<RawPreview>, | 
					
						
							|  |  |  |     openGroup?: OpenGroup | 
					
						
							|  |  |  |   ): Promise<Array<Preview>> { | 
					
						
							|  |  |  |     const promises = (previews || []).map(async item => { | 
					
						
							|  |  |  |       // some links does not have an image associated, and it makes the whole message fail to send
 | 
					
						
							|  |  |  |       if (!item.image) { | 
					
						
							|  |  |  |         return item; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         ...item, | 
					
						
							|  |  |  |         image: await this.upload({ | 
					
						
							|  |  |  |           attachment: item.image, | 
					
						
							|  |  |  |           openGroup, | 
					
						
							|  |  |  |         }), | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return Promise.all(promises); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public static async uploadQuoteThumbnails( | 
					
						
							|  |  |  |     quote?: RawQuote, | 
					
						
							|  |  |  |     openGroup?: OpenGroup | 
					
						
							|  |  |  |   ): Promise<Quote | undefined> { | 
					
						
							|  |  |  |     if (!quote) { | 
					
						
							|  |  |  |       return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const promises = (quote.attachments ?? []).map(async attachment => { | 
					
						
							|  |  |  |       let thumbnail: AttachmentPointer | undefined; | 
					
						
							|  |  |  |       if (attachment.thumbnail) { | 
					
						
							|  |  |  |         thumbnail = await this.upload({ | 
					
						
							|  |  |  |           attachment: attachment.thumbnail, | 
					
						
							|  |  |  |           openGroup, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         ...attachment, | 
					
						
							|  |  |  |         thumbnail, | 
					
						
							|  |  |  |       } as QuotedAttachment; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const attachments = await Promise.all(promises); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       ...quote, | 
					
						
							|  |  |  |       attachments, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |