'use strict' ; import { CreateBucketRequest , CreateWriteStreamOptions , GetFilesOptions , Storage } from '@google-cloud/storage' ; import { ListItemObject , ListItemPrefix , ListResults , PutObjectOptions , StorageProvider } from '@smcloudstore/core/dist/StorageProvider' ; import { IsStream } from '@smcloudstore/core/dist/StreamUtils' ; import { Duplex , Stream } from 'stream' ; /**
 * Connection options for a Google Cloud Storage provider.
 */ interface GoogleCloudConnectionOptions { /** ID of the Google Cloud project */ projectId : string ; /** Path of the JSON file containing the keys */ keyFilename : string ; } /**
 * Options passed when creating a container
 */ interface GoogleCloudCreateContainerOptions { /** Storage class to use. Defaults to 'multi_regional' */ class ? : 'multi_regional' | 'regional' | 'nearline' | 'coldline' ; /** Region in which to create the container (or multi-regional location if using multi_regional storage). Defaults to 'us' is class is 'multi_regional'; 'us-central1' otherwise. */ region ? : string ; } /**
 * Client to interact with Google Cloud Storage.
 */ class GoogleCloudStorageProvider extends StorageProvider { protected _client : Storage ; param connection constructor ( connection ) { super ( connection ) ; this . _provider = 'google-cloud-storage' ; if ( ! connection || ! Object . keys ( connection ) . length ) { if ( ! process . env . GCLOUD_PROJECT || ! process . env . GOOGLE_APPLICATION_CREDENTIALS ) { throw new Error ( 'Connection argument is empty' ) ; } } this . _client = new Storage ( connection ) ; } param container param options returns async createContainer ( container , options ? ) { if ( ! options ) { options = { } ; } const metadata = { } as CreateBucketRequest ; switch ( options . class ) { case 'multi_regional' : metadata . multiRegional = true ; metadata . location = 'us' ; break ; case 'regional' : metadata . regional = true ; metadata . location = 'us-central1' ; break ; case 'coldline' : metadata . coldline = true ; metadata . location = 'us-central1' ; break ; case 'nearline' : metadata . nearline = true ; metadata . location = 'us-central1' ; break ; } if ( options . region ) { metadata . location = options . region ; } const bucket = this . _client . bucket ( container ) ; return bucket . create ( metadata ) . then ( ( ) => { return ; } ) ; } param container returns async isContainer ( container ) { const bucket = this . _client . bucket ( container ) ; return bucket . exists ( ) . then ( ( response ) => { return ! ! response [ 0 ] ; } ) ; } 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 this . _client . getBuckets ( ) . then ( ( list ) => { if ( ! list || ! list [ 0 ] || ! list [ 0 ] . length ) { return [ ] ; } else { return list [ 0 ] . map ( ( el ) => ( el && el . name ) ) ; } } ) ; } param container returns async deleteContainer ( container ) { const bucket = this . _client . bucket ( container ) ; return bucket . delete ( ) . then ( ( ) => { return ; } ) ; } param container param path param data param options returns async putObject ( container , path , data , options ? ) { if ( ! options ) { options = { } ; } const bucket = this . _client . bucket ( container ) ; const file = bucket . file ( path ) ; let dataStream ; if ( IsStream ( data ) ) { dataStream = data as Stream ; } else { dataStream = new Duplex ( ) ; if ( typeof data == 'object' && Buffer . isBuffer ( data ) ) { ( dataStream as Duplex ) . push ( data ) ; } else if ( typeof data == 'string' ) { ( dataStream as Duplex ) . push ( data , 'utf8' ) ; } else { throw Error ( 'Invalid data argument: must be a stream, a Buffer or a string' ) ; } ( dataStream as Duplex ) . push ( null ) ; } return new Promise ( ( resolve , reject ) => { const metadataClone = Object . assign ( { } , options . metadata ) as { [ k ] : string ; } ; const streamOptions = { metadata : metadataClone , resumable : false , validation : 'md5' } as CreateWriteStreamOptions ; for ( const key in metadataClone ) { if ( ! metadataClone . hasOwnProperty ( key ) ) { continue ; } if ( key . toLowerCase ( ) == 'content-type' ) { streamOptions . contentType = metadataClone [ key ] ; delete metadataClone [ key ] ; break ; } } dataStream . pipe ( file . createWriteStream ( streamOptions ) ) . on ( 'error' , ( err ) => { reject ( err ) ; } ) . on ( 'finish' , ( ) => { resolve ( ) ; } ) ; } ) ; } param container param path returns async getObject ( container , path ) { const bucket = this . _client . bucket ( container ) ; const file = bucket . file ( path ) ; return Promise . resolve ( file . createReadStream ( { validation : 'md5' } ) ) ; } param container param prefix returns async listObjects ( container , prefix ? ) { let list = [ ] as ListResults ; const requestPromise = ( opts ) : Promise < ListResults > => { return new Promise ( ( resolve , reject ) => { if ( ! opts ) { opts = { autoPaginate : false , delimiter : '/' , prefix : prefix } ; } this . _client . bucket ( container ) . getFiles ( opts , ( err , files , nextQuery , apiResponse ) => { if ( err ) { return reject ( err ) ; } if ( files && files . length ) { list = list . concat ( files . map ( ( el ) => { const obj = { path : el . name } as ListItemObject ; if ( el . metadata ) { if ( el . metadata . size ) { obj . size = parseInt ( el . metadata . size , 10 ) ; } if ( el . metadata . updated ) { obj . lastModified = new Date ( el . metadata . updated ) ; } if ( el . metadata . timeCreated ) { obj . creationTime = new Date ( el . metadata . timeCreated ) ; } if ( el . metadata . md5Hash ) { obj . contentMD5 = Buffer . from ( el . metadata . md5Hash , 'base64' ) . toString ( 'hex' ) ; } if ( el . metadata . contentType ) { obj . contentType = el . metadata . contentType ; } } return obj ; } ) ) ; } if ( apiResponse && ( apiResponse as any ) . prefixes ) { list = list . concat ( ( apiResponse as any ) . prefixes . map ( ( el ) => { return { prefix : el } as ListItemPrefix ; } ) ) ; } if ( nextQuery ) { return resolve ( requestPromise ( nextQuery ) ) ; } else { return resolve ( list ) ; } } ) ; } ) ; } ; return requestPromise ( null ) ; } param container param path returns async deleteObject ( container , path ) { const bucket = this . _client . bucket ( container ) ; const file = bucket . file ( path ) ; return file . delete ( ) . then ( ( ) => { return ; } ) ; } param container param path param ttl returns async presignedGetUrl ( container , path , ttl ? ) { return this . presignedUrl ( 'read' , container , path , ttl ) ; } param container param path param options param ttl returns async presignedPutUrl ( container , path , options ? , ttl ? ) { const contentSettings = { } as any ; if ( options && options . metadata ) { for ( const key in options . metadata ) { if ( ! options . metadata . hasOwnProperty ( key ) ) { continue ; } if ( key . toLowerCase ( ) == 'content-type' ) { contentSettings . contentType = options . metadata [ key ] ; break ; } } } return this . presignedUrl ( 'write' , container , path , contentSettings , ttl ) ; } param operation param container param path param contentSettings param ttl returns async private presignedUrl ( action , container , path , contentSettings ? , ttl ? ) { if ( ! ttl || ttl < 1 ) { ttl = 86400 ; } const bucket = this . _client . bucket ( container ) ; const file = bucket . file ( path ) ; const config = Object . assign ( { } , { action : action , expires : new Date ( Date . now ( ) + ttl * 1000 ) } , contentSettings || { } ) ; return file . getSignedUrl ( config ) . then ( ( data ) => { if ( data && data [ 0 ] ) { return data [ 0 ] ; } else { throw Error ( 'No pre-signed URL was returned' ) ; } } ) ; } } export = GoogleCloudStorageProvider ;