import assert from 'assert' ; import fetch from 'node-fetch' ; import multiStream from 'multistream' ; import retry from 'async-retry' ; import Sema from 'async-sema' ; import { File } from './types' ; interface FileRefOptions { mode ? : number ; digest : string ; contentType ? : string ; mutable ? : boolean ; } const semaToDownloadFromS3 = new Sema ( 5 ) ; class BailableError extends Error { public bail : boolean ; constructor ( ... args ) { super ( ... args ) ; this . bail = false ; } } export default class FileRef implements File { public type : 'FileRef' ; public mode : number ; public digest : string ; public contentType : string | undefined ; private mutable : boolean ; constructor ( { mode = 0o100644 , digest , contentType , mutable = false , } ) { assert ( typeof mode === 'number' ) ; assert ( typeof digest === 'string' ) ; this . type = 'FileRef' ; this . mode = mode ; this . digest = digest ; this . contentType = contentType ; this . mutable = mutable ; } async toStreamAsync ( ) { let url = '' ; const [ digestType , digestHash ] = this . digest . split ( ':' ) ; if ( digestType === 'sha' ) { url = this . mutable ? `https://now-files.s3.amazonaws.com/${ digestHash }` : `https://dmmcy0pwk6bqi.cloudfront.net/${ digestHash }` ; } else if ( digestType === 'sha+ephemeral' ) { url = `https://now-ephemeral-files.s3.amazonaws.com/${ digestHash }` ; } else { throw new Error ( 'Expected digest to be sha' ) ; } await semaToDownloadFromS3 . acquire ( ) ; try { return await retry ( async ( ) => { const resp = await fetch ( url ) ; if ( ! resp . ok ) { const error = new BailableError ( `download: ${ resp . status } ${ resp . statusText } for ${ url }` ) ; if ( resp . status === 403 ) error . bail = true ; throw error ; } return resp . body ; } , { factor : 1 , retries : 3 } ) ; } finally { semaToDownloadFromS3 . release ( ) ; } } toStream ( ) { let flag = false ; return multiStream ( cb => { if ( flag ) return cb ( null , null ) ; flag = true ; this . toStreamAsync ( ) . then ( stream => { cb ( null , stream ) ; } ) . catch ( error => { cb ( error , null ) ; } ) ; } ) ; } }