import AWS = require ( 'aws-sdk' ) ; import crypto = require ( 'crypto-js' ) ; import { Bucket } from './Bucket' ; import { Path } from './Path' ; import { Folder } from './Folder' ; import { IFolderContents } from './IFolderContents' ; import { IUploadConfig } from './IUploadConfig' ; import { File } from './File' ; import { IFileVersion } from './IFileVersion' ; import { IBackend } from './IBackend' ; export class AmazonS3Backend implements IBackend { /**
     * Amazon S3 API endpoint.
     */ private s3 : AWS . S3 ; /**
     * Whether to generate a download link.
     */ private allowDownload : boolean ; see https constructor ( region , accessKeyId , secretAccessKey , sessionToken = null , allowDownload = true ) { var creds = new AWS . Credentials ( accessKeyId , secretAccessKey , sessionToken ) ; this . s3 = new AWS . S3 ( { apiVersion : 'latest' , region : region , credentials : creds , sslEnabled : true , signatureVersion : 'v4' } ) ; this . allowDownload = allowDownload ; } /**
     * Get a bucket with the given name.
     */ public getBucket ( name ) { return this . s3 . getBucketVersioning ( { Bucket : name } ) . promise ( ) . then ( ( data ) => { return new Bucket ( name , data . Status === 'Enabled' ) ; } ) ; } /**
     * Get the contents of a folder.
     */ getContents ( bucket , folder ) { var params = { Bucket : bucket . name , Prefix : folder . getPath ( ) . toString ( ) , Delimiter : '/' } ; return this . s3 . listObjectsV2 ( params ) . promise ( ) . then ( ( data ) => { let folders = data . CommonPrefixes . map ( ( folderData ) => { return new Folder ( new Path ( folderData . Prefix ) ) ; } ) ; let files = data . Contents . filter ( ( fileData ) => { return folder . getPath ( ) . toString ( ) !== fileData . Key ; } ) . map ( ( fileData ) => { let downloadLink = this . allowDownload ? this . s3 . getSignedUrl ( 'getObject' , { Bucket : bucket . name , Key : fileData . Key , Expires : 900 } ) : undefined ; return new File ( new Path ( fileData . Key ) , downloadLink ) ; } ) ; return { folders : folders , files : files } ; } ) ; } /**
     * Get the deleted contents of a folder.
     *
     * Returns all folders even if they aren't currently deleted. This is because
     * there isn't a way to determine if a folder is deleted without comparing
     * the output of s3.listObjectVersions() and s3.listObjectsV2() and only
     * selecting the folders that are returned by s3.listObjectVersions, but not
     * by s3.listObjectsV2().
     */ getDeletedContents ( bucket , folder ) { var params = { Bucket : bucket . name , Prefix : folder . getPath ( ) . toString ( ) , Delimiter : '/' } ; return this . s3 . listObjectVersions ( params ) . promise ( ) . then ( ( data ) => { let folders = data . CommonPrefixes . map ( ( folderData ) => { return new Folder ( new Path ( folderData . Prefix ) , true ) ; } ) ; let files = data . DeleteMarkers . filter ( ( fileData ) => { return fileData . IsLatest && folder . getPath ( ) . toString ( ) !== fileData . Key ; } ) . map ( ( fileData ) => { let downloadLink = this . allowDownload ? this . s3 . getSignedUrl ( 'getObject' , { Bucket : bucket . name , Key : fileData . Key , Expires : 900 } ) : undefined ; return new File ( new Path ( fileData . Key ) , downloadLink , true ) ; } ) ; return { folders : folders , files : files } ; } ) ; } /**
     * Create a folder.
     */ createFolder ( bucket , folder ) { var params = { Bucket : bucket . name , Key : folder . getPath ( ) . toString ( ) , Body : '' } ; return this . s3 . putObject ( params ) . promise ( ) ; } /**
     * Delete a folder and its contents.
     */ deleteFolder ( bucket , folder ) { var getParams = { Bucket : bucket . name , Prefix : folder . getPath ( ) . toString ( ) } ; return this . s3 . listObjectsV2 ( getParams ) . promise ( ) . then ( ( data ) => { return data . Contents . map ( ( objectData ) => { return { Key : objectData . Key } ; } ) ; } ) . then ( ( objects ) => { var deleteParams = { Bucket : bucket . name , Delete : { Objects : objects } } ; return this . s3 . deleteObjects ( deleteParams ) . promise ( ) ; } ) ; } /**
     * Get settings necessary to upload files to the given folder.
     */ public getUploadConfig ( bucket , folder ) { return { url : `https://${ bucket . name }.s3.amazonaws.com/` , fields : { bucket : bucket . name } } ; } /**
     * Get versions of the given file.
     */ public getFileVersions ( bucket , file ) { var params = { Bucket : bucket . name , Prefix : file . getPath ( ) . toString ( ) } ; return this . s3 . listObjectVersions ( params ) . promise ( ) . then ( ( data ) => { let versions = data . Versions . map ( ( versionData ) => { let downloadLink = this . allowDownload ? this . s3 . getSignedUrl ( 'getObject' , { Bucket : bucket . name , Key : file . getPath ( ) . toString ( ) , VersionId : versionData . VersionId , Expires : 900 } ) : undefined ; return { latest : versionData . IsLatest , versionId : versionData . VersionId , lastModified : versionData . LastModified , deleteMarker : false , downloadLink : downloadLink } ; } ) ; let markers = data . DeleteMarkers . map ( ( markerData ) => { return { latest : markerData . IsLatest , versionId : markerData . VersionId , lastModified : markerData . LastModified , deleteMarker : true , downloadLink : null } ; } ) ; return versions . concat ( markers ) ; } ) ; } /**
     * Get full file path given a folder and file.
     */ public getFilePath ( folder , file ) { let filePath = file . name ; if ( file . hasOwnProperty ( 'fullPath' ) ) { filePath = file . fullPath ; } return folder . getPath ( ) . clone ( ) . push ( filePath ) . toString ( ) ; } /**
     * Delete a file.
     */ deleteFile ( bucket , file ) { var params = { Bucket : bucket . name , Key : file . getPath ( ) . toString ( ) } ; return this . s3 . deleteObject ( params ) . promise ( ) ; } /**
     * Upload file to S3 using ManagedUpload.
     * Expects the following parameters:
     *
     * params = {
     *   Bucket: name of the bucket
     *   Key: filepath
     *   Body: file object
     *   Dropzone: dropzone object
     * }
     */ public uploadFile ( params ) { let completedParts = [ ] ; let s3client = this . s3 ; let uploadParams = { params : params , service : s3client , partSize : 1024 * 1024 * 10 , queueSize : 1 } ; return new Promise ( ( resolve , reject ) => { let resumeUploadCallback = function ( err , data ) { if ( err ) { console . log ( err ) ; resolve ( { err : err , data : data } ) ; return ; } let byteCount = 0 ; for ( let i of Object . keys ( data . Parts ) ) { let fr = new FileReader ( ) ; let partNumber = data . Parts [ i ] . PartNumber ; fr . onload = function ( event ) { let partMD5 = '"' + crypto . MD5 ( event . target . result ) . toString ( crypto . enc . Hex ) + '"' ; if ( partMD5 === data . Parts [ i ] . ETag ) { if ( params . Body . s3upload ) { if ( params . Body . s3upload . completeInfo [ partNumber ] ) { return ; } params . Body . s3upload . completeInfo [ partNumber ] = { ETag : data . Parts [ i ] . ETag , PartNumber : partNumber } ; params . Body . s3upload . totalUploadedBytes = params . Body . s3upload . totalUploadedBytes + data . Parts [ i ] . Size ; params . Body . s3upload . doneParts = params . Body . s3upload . doneParts + 1 ; } } } ; let blob = params . Body . slice ( byteCount , byteCount + data . Parts [ i ] . Size ) ; fr . readAsBinaryString ( blob ) ; byteCount = byteCount + data . Parts [ i ] . Size ; } let upload = new AWS . S3 . ManagedUpload ( uploadParams ) ; params . Body . s3upload = upload ; params . Body . s3upload . computeChecksums = true ; params . Body . s3upload . on ( 'httpUploadProgress' , function ( progress ) { if ( progress . total ) { let percent = ( progress . loaded * 100 ) / progress . total ; params . Dropzone . emit ( 'uploadprogress' , params . Body , percent , progress . loaded ) ; } } ) ; params . Body . s3upload . send ( function ( err , data ) { resolve ( { err : err , data : data } ) ; } ) ; } ; let findMultipartUploadCallback = function ( err , data ) { if ( err ) { console . log ( err ) ; resolve ( { err : err , data : data } ) ; return ; } for ( let upload of data . Uploads ) { if ( upload . Key === params . Key ) { params . UploadId = upload . UploadId ; s3client . listParts ( { Bucket : params . Bucket , Key : upload . Key , UploadId : upload . UploadId } , resumeUploadCallback ) ; return ; } } let upload = new AWS . S3 . ManagedUpload ( uploadParams ) ; params . Body . s3upload = upload ; params . Body . s3upload . computeChecksums = true ; params . Body . s3upload . on ( 'httpUploadProgress' , function ( progress ) { if ( progress . total ) { let percent = ( progress . loaded * 100 ) / progress . total ; params . Dropzone . emit ( 'uploadprogress' , params . Body , percent , progress . loaded ) ; } } ) ; params . Body . s3upload . send ( function ( err , data ) { resolve ( { err : err , data : data } ) ; } ) ; } ; s3client . listMultipartUploads ( { Bucket : params . Bucket } , findMultipartUploadCallback ) ; } ) ; } /**
     * Cancel file upload.
     * Expects the following parameters:
     *
     * params = {
     *   file: file object
     * }
     */ public cancelUpload ( params ) { return new Promise ( ( resolve , reject ) => { params . file . s3upload . abort ( ) ; resolve ( true ) ; } ) ; } }