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.
		
		
		
		
		
			
		
			
				
	
	
		
			185 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			185 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
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,
 | 
						|
    };
 | 
						|
  }
 | 
						|
}
 |