1+ import Crypto from "crypto" ;
12import * as fs from 'fs'
23import * as path from 'path' ;
34import datauri from 'datauri'
45import isUrl from 'is-url-superb'
56import { JsonObject } from 'type-fest' ;
6- import { ConfigObject , CustomError , DataURL , ERROR_NAME } from '../api/model' ;
7+ import { AdvancedFile , ConfigObject , CustomError , DataURL , ERROR_NAME } from '../api/model' ;
78import { default as axios , AxiosRequestConfig } from 'axios' ;
89import { SessionInfo } from '../api/model/sessionInfo' ;
910import { execSync } from 'child_process' ;
1011import {
1112 performance
1213} from 'perf_hooks' ;
14+ import mime from 'mime' ;
15+ import { tmpdir } from 'os' ;
16+ import { Readable } from "stream" ;
17+ import { log } from "../logging/logging" ;
1318
1419//@ts -ignore
1520process . send = process . send || function ( ) { } ;
@@ -248,9 +253,13 @@ export const generateGHIssueLink = (config : ConfigObject, sessionInfo: SessionI
248253 * download it and convert it to a DataURL. If Base64, returns it.
249254 * @param {string } file - The file to be converted to a DataURL.
250255 * @param {AxiosRequestConfig } requestConfig - AxiosRequestConfig = {}
256+ * @param {string } filename - Filename with an extension so a datauri mimetype can be inferred.
251257 * @returns A DataURL
252258 */
253- export const ensureDUrl = async ( file : string , requestConfig : AxiosRequestConfig = { } ) => {
259+ export const ensureDUrl = async ( file : string | Buffer , requestConfig : AxiosRequestConfig = { } , filename ?: string ) => {
260+ if ( Buffer . isBuffer ( file ) ) {
261+ return `data:${ mime . lookup ( filename ) } ;base64,${ file . toString ( 'base64' ) . split ( ',' ) [ 1 ] } `
262+ } else
254263 if ( ! isDataURL ( file ) && ! isBase64 ( file ) ) {
255264 //must be a file then
256265 const relativePath = path . join ( path . resolve ( process . cwd ( ) , file || '' ) ) ;
@@ -260,5 +269,104 @@ export const ensureDUrl = async (file : string, requestConfig: AxiosRequestConfi
260269 file = await getDUrl ( file , requestConfig ) ;
261270 } else throw new CustomError ( ERROR_NAME . FILE_NOT_FOUND , 'Cannot find file. Make sure the file reference is relative, a valid URL or a valid DataURL' )
262271 }
272+ if ( file . includes ( "data:" ) && file . includes ( "undefined" ) || file . includes ( "application/octet-stream" ) && filename && mime . lookup ( filename ) ) {
273+ file = `data:${ mime . lookup ( filename ) } ;base64,${ file . split ( ',' ) [ 1 ] } `
274+ }
263275 return file ;
264- }
276+ }
277+
278+ export const FileInputTypes = {
279+ "VALIDATED_FILE_PATH" : "VALIDATED_FILE_PATH" ,
280+ "URL" : "URL" ,
281+ "DATA_URL" : "DATA_URL" ,
282+ "BASE_64" : "BASE_64" ,
283+ "BUFFER" : "BUFFER" ,
284+ "READ_STREAM" : "READ_STREAM" ,
285+ }
286+
287+ export const FileOutputTypes = {
288+ ...FileInputTypes ,
289+ "TEMP_FILE_PATH" : "TEMP_FILE_PATH" ,
290+ }
291+
292+ /**
293+ * Remove file asynchronously
294+ * @param file Filepath
295+ * @returns
296+ */
297+ export function rmFileAsync ( file : string ) {
298+ return new Promise ( ( resolve , reject ) => {
299+ fs . unlink ( file , ( err ) => {
300+ if ( err ) {
301+ reject ( err ) ;
302+ } else {
303+ resolve ( true ) ;
304+ }
305+ } )
306+ } )
307+ }
308+
309+ /**
310+ * Takes a file parameter and consistently returns the desired type of file.
311+ * @param file The file path, URL, base64 or DataURL string of the file
312+ * @param outfileName The ouput filename of the file
313+ * @param desiredOutputType The type of file output required from this function
314+ * @param requestConfig optional axios config if file parameter is a url
315+ */
316+ export const assertFile : ( file : AdvancedFile | Buffer , outfileName : string , desiredOutputType : keyof typeof FileOutputTypes , requestConfig ?: any ) => Promise < string | Buffer | Readable > = async ( file , outfileName , desiredOutputType , requestConfig ) => {
317+ let inputType ;
318+ if ( typeof file == 'string' ) {
319+ if ( isDataURL ( file ) ) inputType = FileInputTypes . DATA_URL
320+ else if ( isBase64 ( file ) ) inputType = FileInputTypes . BASE_64
321+ /**
322+ * Check if it is a path
323+ */
324+ else {
325+ const relativePath = path . join ( path . resolve ( process . cwd ( ) , file || '' ) ) ;
326+ if ( fs . existsSync ( file ) || fs . existsSync ( relativePath ) ) {
327+ // file = await datauri(fs.existsSync(file) ? file : relativePath);
328+ inputType = FileInputTypes . VALIDATED_FILE_PATH ;
329+ } else if ( isUrl ( file ) ) inputType = FileInputTypes . URL ;
330+ /**
331+ * If not file type is determined by now then it is some sort of unidentifiable string. Throw an error.
332+ */
333+ if ( ! inputType ) throw new CustomError ( ERROR_NAME . FILE_NOT_FOUND , `Cannot find file. Make sure the file reference is relative, a valid URL or a valid DataURL: ${ file . slice ( 0 , 25 ) } ` )
334+ }
335+ } else {
336+ if ( Buffer . isBuffer ( file ) ) inputType = FileInputTypes . BUFFER
337+ /**
338+ * Leave space to determine if incoming file parameter is any other type of object (maybe one day people will submit a path object as a param?)
339+ */
340+ }
341+ if ( inputType === desiredOutputType ) return file ;
342+ switch ( desiredOutputType ) {
343+ case FileOutputTypes . DATA_URL :
344+ case FileOutputTypes . BASE_64 :
345+ return await ensureDUrl ( file as string , requestConfig , outfileName ) ;
346+ break ;
347+ case FileOutputTypes . TEMP_FILE_PATH : {
348+ /**
349+ * Create a temp file in tempdir, return the tempfile.
350+ */
351+ const tempFilePath = path . join ( tmpdir ( ) , `${ Crypto . randomBytes ( 6 ) . readUIntLE ( 0 , 6 ) . toString ( 36 ) } .${ outfileName } ` ) ;
352+ log . info ( `Saved temporary file to ${ tempFilePath } ` )
353+ if ( inputType != FileInputTypes . BUFFER ) {
354+ file = await ensureDUrl ( file as string , requestConfig , outfileName ) ;
355+ file = Buffer . from ( file . split ( ',' ) [ 1 ] , 'base64' )
356+ }
357+ await fs . writeFileSync ( tempFilePath , file ) ;
358+ return tempFilePath
359+ break ;
360+ }
361+ case FileOutputTypes . BUFFER :
362+ return Buffer . from ( ( await ensureDUrl ( file as string , requestConfig , outfileName ) ) . split ( ',' ) [ 1 ] , 'base64' ) ;
363+ break ;
364+ case FileOutputTypes . READ_STREAM : {
365+ if ( inputType === FileInputTypes . VALIDATED_FILE_PATH ) return fs . createReadStream ( file )
366+ else if ( inputType != FileInputTypes . BUFFER ) file = Buffer . from ( ( await ensureDUrl ( file as string , requestConfig , outfileName ) ) . split ( ',' ) [ 1 ] , 'base64' )
367+ return Readable . from ( file )
368+ break ;
369+ }
370+ }
371+ return file ;
372+ }
0 commit comments