'use strict' ; import S3 = require ( 'aws-sdk/clients/s3' ) ; import { Stream } from 'stream' ; import { ListResults , PutObjectOptions , StorageProvider , } from '../../core/src/StorageProvider' ; import { SMCloudStoreFactories } from '../../smcloudstore/src/SMCloudStore' ; import { PromiseResult } from 'aws-sdk/lib/request' ; import { AWSError } from 'aws-sdk' ; /**
 * Connection options for an AWS S3 provider.
 */ interface AwsS3ConnectionOptions { /** Access Key ID */ accessKeyId : string ; /** Secret Access Key */ secretAccessKey : string ; /** Default region to use; if not set, defaults to "US Standard" (Virginia) */ region ? : string ; } /**
 * ACL for containers and objects. Refer to the [documentation](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) for more details. Default value is `private` for containers and null (inherited) for objects.
 *
 * For consistency with other providers, a few aliases are added:
 * - `none` is an alias for `private`
 * - `public` is an alias for `public-read`
 */ type AwsS3ACL = 'private' | 'public-read' | 'public-read-write' | 'authenticated-read' | 'none' | 'public' ; /**
 * Options passed when creating a container
 */ interface AwsS3CreateContainerOptions { /** Determine access level for all objects in the container */ access ? : AwsS3ACL ; } /**
 * Options passed when putting an object.
 */ interface AwsS3PutObjectOptions extends PutObjectOptions { /** Determine access level for the object. Default: inherit from the container */ access ? : AwsS3ACL ; /** Enable AES256 server-side encryption for the object at rest, with AWS-managed keys. Default: disabled */ serverSideEncryption ? : boolean ; /** Storage class to use. Refer to the [documentation](https://docs.aws.amazon.com/AmazonS3/latest/dev/storage-class-intro.html) for more details. Default is 'STANDARD' */ class ? : 'STANDARD' | 'REDUCED_REDUNDANCY' | 'STANDARD_IA' | 'ONEZONE_IA' ; } param access returns function ACLString ( access ) { switch ( access ) { case 'public-read' : case 'public' : return 'public-read' ; case 'public-read-write' : case 'authenticated-read' : return access ; case 'none' : case 'private' : default : return 'private' ; } } param options returns function PutObjectMethodOptions ( options ) { const methodOptions = { } as S3 . PutObjectRequest ; if ( ! options ) { return methodOptions ; } if ( options . access ) { methodOptions . ACL = ACLString ( options . access ) ; } if ( options . class ) { methodOptions . StorageClass = options . class ; } if ( options . serverSideEncryption ) { methodOptions . ServerSideEncryption = 'AES256' ; } if ( options . metadata ) { methodOptions . Metadata = { } ; for ( const key in options . metadata ) { if ( ! options . metadata . hasOwnProperty ( key ) ) { continue ; } const keyLowerCase = key . toLowerCase ( ) ; switch ( keyLowerCase ) { case 'cache-control' : methodOptions . CacheControl = options . metadata [ key ] ; break ; case 'content-disposition' : methodOptions . ContentDisposition = options . metadata [ key ] ; break ; case 'content-encoding' : methodOptions . ContentEncoding = options . metadata [ key ] ; break ; case 'content-language' : methodOptions . ContentLanguage = options . metadata [ key ] ; break ; case 'content-md5' : methodOptions . ContentMD5 = options . metadata [ key ] ; break ; case 'content-type' : methodOptions . ContentType = options . metadata [ key ] ; break ; default : methodOptions . Metadata [ key ] = options . metadata [ key ] ; break ; } } } return methodOptions ; } /**
 * Client to interact with a generic S3 object storage server, using the Minio library.
 */ export class AwsS3Provider extends StorageProvider { protected _client : S3 ; protected _region : string ; param connection param factories constructor ( connection , protected readonly factories ) { super ( connection ) ; this . _provider = 'aws-s3' ; this . _region = connection . region || '' ; const options = Object . assign ( connection , { apiVersion : '2006-03-01' } ) as S3 . ClientConfiguration ; this . _client = new S3 ( options ) ; } param container param options returns async createContainer ( container , options ? ) { return new Promise ( ( resolve , reject ) => { if ( ! options ) { options = { } ; } const methodOptions = { ACL : ACLString ( options . access ) , Bucket : container , CreateBucketConfiguration : { LocationConstraint : this . _region } } as S3 . CreateBucketRequest ; this . _client . createBucket ( methodOptions , function ( err , data ) { if ( err || ! data || ! data . Location ) { return reject ( err || Error ( 'Invalid response while creating container' ) ) ; } resolve ( ) ; } ) ; } ) ; } param container returns async isContainer ( container ) { return new Promise ( ( resolve , reject ) => { const methodOptions = { Bucket : container } as S3 . HeadBucketRequest ; this . _client . headBucket ( methodOptions , function ( err , data ) { if ( err ) { if ( err . statusCode == 404 ) { resolve ( false ) ; } else if ( err . statusCode === 403 ) { resolve ( false ) ; } else { return reject ( err ) ; } } else { resolve ( true ) ; } } ) ; } ) ; } param container param options returns async ensureContainer ( container , options ? ) { return this . isContainer ( container ) . then ( ( exists ) => { if ( ! exists ) { return this . createContainer ( container , options ) ; } } ) ; } returns async listContainers ( ) { return new Promise ( ( resolve , reject ) => { this . _client . listBuckets ( function ( err , data ) { if ( err ) { return reject ( err || Error ( 'Invalid response while listing containers' ) ) ; } const list = [ ] ; for ( const bucket of data . Buckets ) { if ( bucket && bucket . Name ) { list . push ( bucket . Name ) ; } } resolve ( list ) ; } ) ; } ) ; } param container returns async deleteContainer ( container ) { return new Promise ( ( resolve , reject ) => { const methodOptions = { Bucket : container } as S3 . DeleteBucketRequest ; this . _client . deleteBucket ( methodOptions , function ( err , data ) { if ( err || ! data ) { return reject ( err || Error ( 'Invalid response while deleting container' ) ) ; } resolve ( ) ; } ) ; } ) ; } param container param path param data param options returns async putObject ( container , path , data , options ? ) { if ( ! options ) { options = { } ; } return new Promise ( ( resolve , reject ) => { const methodOptions = Object . assign ( { } , { Body : data , Bucket : container , Key : path } , PutObjectMethodOptions ( options ) ) as S3 . PutObjectRequest ; this . _client . putObject ( methodOptions , function ( err , response ) { if ( err || ! response || ! response . ETag ) { return reject ( err || Error ( 'Invalid response while putting object' ) ) ; } resolve ( ) ; } ) ; } ) ; } param container param path returns async getObject ( container , path ) { const methodOptions = { Bucket : container , Key : path } as S3 . GetObjectRequest ; const stream = this . _client . getObject ( methodOptions ) . createReadStream ( ) ; return Promise . resolve ( stream ) ; } param container param path returns async async getObjectBase64 ( container , path ) { const methodOptions = { Bucket : container , Key : path } as S3 . GetObjectRequest ; const data = await this . _client . getObject ( methodOptions ) . promise ( ) ; return data . Body . toString ( 'base64' ) ; } param container param prefix returns async listObjects = ( container , prefix ? ) : Promise < ListResults > => { const files = [ ] ; const folders = [ ] ; const { createListItemPrefix , createListItemObject } = this . factories ; const makeRequest = ( continuationToken ? ) : Promise < ListResults > => { return new Promise ( ( resolve , reject ) => { const methodOptions = { Bucket : container , ContinuationToken : continuationToken || undefined , Delimiter : '/' , MaxKeys : 500 , Prefix : prefix } as S3 . ListObjectsV2Request ; this . _client . listObjectsV2 ( methodOptions , function ( err , data ) { if ( err ) { return reject ( err ) ; } for ( const el of data . Contents ) { const add = createListItemObject ( ) ; add . lastModified = el . LastModified ; add . path = el . Key ; add . size = el . Size ; if ( el . ETag . indexOf ( '-' ) >= 0 ) { add . contentMD5 = el . ETag ; } files . push ( add ) ; } for ( const el of data . CommonPrefixes ) { const dir = createListItemPrefix ( ) ; dir . prefix = el . Prefix ; folders . push ( dir ) ; } if ( data . ContinuationToken ) { resolve ( makeRequest ( data . ContinuationToken ) ) ; } else { resolve ( { files , folders } ) ; } } ) ; } ) ; } ; return makeRequest ( ) ; } ; param container param path returns async deleteObject ( container , path ) { return new Promise ( ( resolve , reject ) => { const methodOptions = { Bucket : container , Key : path } as S3 . DeleteObjectRequest ; this . _client . deleteObject ( methodOptions , function ( err , data ) { if ( err || ! data ) { return reject ( err || Error ( 'Invalid response while deleting object' ) ) ; } resolve ( ) ; } ) ; } ) ; } param container param path param ttl returns async presignedGetUrl ( container , path , ttl ? ) { return this . presignedUrl ( 'getObject' , container , path , ttl ) ; } param container param path param options param ttl returns async presignedPutUrl ( container , path , options ? , ttl ? ) { const additionalMethodOptions = PutObjectMethodOptions ( options ) ; return this . presignedUrl ( 'putObject' , container , path , additionalMethodOptions , ttl ) ; } param operation param container param path param additionalMethodOptions param ttl returns async private presignedUrl ( operation , container , path , additionalMethodOptions , ttl ? ) { if ( ! ttl || ttl < 1 ) { ttl = 86400 ; } const methodOptions = Object . assign ( { } , { Bucket : container , Expires : ttl , Key : path } , additionalMethodOptions ) ; return new Promise ( ( resolve , reject ) => { this . _client . getSignedUrl ( operation , methodOptions , function ( err , url ) { if ( err || ! url ) { return reject ( err || Error ( 'Invalid result when generating the presigned url' ) ) ; } resolve ( url ) ; } ) ; } ) ; } }