11import { axios } from "@pipedream/platform" ;
22import constants from "./common/constants.mjs" ;
3+ import { getFileStreamAndMetadata } from "@pipedream/platform" ;
4+ import path from "path" ;
35
46export default {
57 type : "app" ,
@@ -264,6 +266,8 @@ export default {
264266 type : "string[]" ,
265267 label : "Attachments" ,
266268 description : "File paths or URLs to attach to the ticket. Multiple files can be attached." ,
269+ optional : true ,
270+ } ,
267271 ticketTags : {
268272 type : "string[]" ,
269273 label : "Tags" ,
@@ -373,6 +377,14 @@ export default {
373377 ...args ,
374378 } ) ;
375379 } ,
380+ streamToBuffer ( stream ) {
381+ return new Promise ( ( resolve , reject ) => {
382+ const chunks = [ ] ;
383+ stream . on ( "data" , ( chunk ) => chunks . push ( chunk ) ) ;
384+ stream . on ( "end" , ( ) => resolve ( Buffer . concat ( chunks ) ) ) ;
385+ stream . on ( "error" , reject ) ;
386+ } ) ;
387+ } ,
376388 /**
377389 * Upload a single file (local path or http(s) URL) to Zendesk Uploads API.
378390 * @param {Object } params
@@ -383,67 +395,18 @@ export default {
383395 */
384396 async uploadFile ( {
385397 filePath, filename, customSubdomain, step,
386- } = { } ) {
398+ } ) {
387399 if ( ! filePath || typeof filePath !== "string" ) {
388400 throw new Error ( "uploadFile: 'filePath' (string) is required" ) ;
389401 }
390- const fs = await import ( "fs" ) ;
391- const path = await import ( "path" ) ;
392-
393- const contentTypeMap = {
394- ".pdf" : "application/pdf" ,
395- ".png" : "image/png" ,
396- ".jpg" : "image/jpeg" ,
397- ".jpeg" : "image/jpeg" ,
398- ".gif" : "image/gif" ,
399- ".txt" : "text/plain" ,
400- ".doc" : "application/msword" ,
401- ".docx" : "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ,
402- ".xls" : "application/vnd.ms-excel" ,
403- ".xlsx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ,
404- ".zip" : "application/zip" ,
405- } ;
406402
407- let fileContent ;
408- let contentType ;
409-
410- const isHttp = / ^ h t t p s ? : \/ \/ / i. test ( filePath ) ;
411- if ( isHttp ) {
412- // Fetch remote file as arraybuffer to preserve bytes
413- const res = await axios ( step , {
414- method : "get" ,
415- url : filePath ,
416- responseType : "arraybuffer" ,
417- returnFullResponse : true ,
418- timeout : 60_000 ,
419- } ) ;
420- fileContent = res . data ;
421-
422- const headerCT = res . headers ?. [ "content-type" ] ;
423- const cd = res . headers ?. [ "content-disposition" ] ;
403+ const {
404+ stream, metadata,
405+ } = await getFileStreamAndMetadata ( filePath ) ;
406+ const fileBinary = await this . streamToBuffer ( stream ) ;
424407
425- if ( ! filename ) {
426- const cdMatch = cd ?. match ( / f i l e n a m e \* ? = (?: U T F - 8 ' ' | " ) ? ( [ ^ \" ; ] + ) / i) ;
427- filename = cdMatch ?. [ 1 ]
428- ? decodeURIComponent ( cdMatch [ 1 ] . replace ( / ( ^ " | " $ ) / g, "" ) )
429- : ( ( ) => {
430- try {
431- return path . basename ( new URL ( filePath ) . pathname ) ;
432- } catch {
433- return "attachment" ;
434- }
435- } ) ( ) ;
436- }
437- const ext = path . extname ( filename || "" ) . toLowerCase ( ) ;
438- contentType = headerCT || contentTypeMap [ ext ] || "application/octet-stream" ;
439- } else {
440- // Local file: non-blocking read
441- if ( ! filename ) {
442- filename = path . basename ( filePath ) ;
443- }
444- fileContent = await fs . promises . readFile ( filePath ) ;
445- const ext = path . extname ( filename || "" ) . toLowerCase ( ) ;
446- contentType = contentTypeMap [ ext ] || "application/octet-stream" ;
408+ if ( ! filename ) {
409+ filename = path . basename ( filePath ) ;
447410 }
448411
449412 return this . makeRequest ( {
@@ -452,9 +415,11 @@ export default {
452415 path : `/uploads?filename=${ encodeURIComponent ( filename ) } ` ,
453416 customSubdomain,
454417 headers : {
455- "Content-Type" : contentType ,
418+ "Content-Type" : metadata . contentType ,
419+ "Content-Length" : metadata . size ,
420+ "Accept" : "application/json" ,
456421 } ,
457- data : fileContent ,
422+ data : Buffer . from ( fileBinary , "binary" ) ,
458423 } ) ;
459424 } ,
460425 async uploadFiles ( {
@@ -464,13 +429,18 @@ export default {
464429 return [ ] ;
465430 }
466431 const files = attachments
467- . map ( ( a ) => ( typeof a === "string" ? a . trim ( ) : a ) )
432+ . map ( ( a ) => ( typeof a === "string"
433+ ? a . trim ( )
434+ : a ) )
468435 . filter ( Boolean ) ;
469436
470437 const settled = await Promise . allSettled (
471438 files . map ( ( attachment ) =>
472- this . uploadFile ( { filePath : attachment , customSubdomain, step } ) ,
473- ) ,
439+ this . uploadFile ( {
440+ filePath : attachment ,
441+ customSubdomain,
442+ step,
443+ } ) ) ,
474444 ) ;
475445
476446 const tokens = [ ] ;
@@ -494,6 +464,7 @@ export default {
494464 throw new Error ( `Failed to upload ${ errors . length } /${ files . length } attachment(s): ${ errors . join ( "; " ) } ` ) ;
495465 }
496466 return tokens ;
467+ } ,
497468 listTicketComments ( {
498469 ticketId, ...args
499470 } = { } ) {
0 commit comments