1+ import { statSync } from 'node:fs' ;
12import { basename } from 'node:path' ;
23import { Readable } from 'node:stream' ;
34import { io , type Socket } from 'socket.io-client' ;
@@ -18,20 +19,78 @@ export type UploadFileSource =
1819 | AsyncIterable < Uint8Array >
1920 | NodeJS . ReadableStream ;
2021
21- function guessFilename ( source : UploadFileSource ) : string | undefined {
22- return 'path' in source && typeof source . path === 'string'
23- ? basename ( source . path )
24- : undefined ;
22+ async function * unifySources (
23+ data : UploadFileSource
24+ ) : AsyncIterable < Uint8Array > {
25+ if ( data instanceof Uint8Array ) {
26+ yield data ;
27+ return ;
28+ }
29+
30+ if ( data instanceof Blob ) {
31+ yield data . bytes ( ) ;
32+ return ;
33+ }
34+
35+ if ( Symbol . iterator in data ) {
36+ yield * data ;
37+ return ;
38+ }
39+
40+ if ( Symbol . asyncIterator in data ) {
41+ for await ( const chunk of data ) {
42+ if ( typeof chunk === 'string' )
43+ throw new Error (
44+ 'bad file data, received string but expected Uint8Array'
45+ ) ;
46+ yield chunk ;
47+ }
48+ return ;
49+ }
50+ }
51+
52+ function guessNameAndSize (
53+ source : UploadFileSource ,
54+ fileName ?: string ,
55+ fileSize ?: number
56+ ) : { name : string ; size : number } {
57+ const path =
58+ 'path' in source && typeof source . path === 'string'
59+ ? source . path
60+ : undefined ;
61+ const name =
62+ fileName ?? ( path !== undefined ? basename ( path ) : undefined ) ?? 'file' ;
63+ const size =
64+ fileSize ??
65+ ( source instanceof Uint8Array ? source . byteLength : undefined ) ??
66+ ( source instanceof Blob ? source . size : undefined ) ??
67+ ( path !== undefined
68+ ? statSync ( path , { throwIfNoEntry : false } ) ?. size
69+ : undefined ) ??
70+ ( fileName !== undefined
71+ ? statSync ( fileName , { throwIfNoEntry : false } ) ?. size
72+ : undefined ) ;
73+ if ( size === undefined ) {
74+ throw new Error (
75+ 'Could not determine the number of bytes, specify it explicitly when calling `upload`'
76+ ) ;
77+ }
78+ return { name, size } ;
2579}
2680
2781export class UploadFile {
2882 private readonly attributes : Array < [ key : string , value : unknown ] > = [ ] ;
2983 private readonly data : AsyncIterable < Uint8Array > ;
30- constructor (
31- data : UploadFileSource ,
32- private readonly filename = guessFilename ( data )
33- ) {
34- this . data = UploadFile . unifySources ( data ) ;
84+ private readonly filename ?: string ;
85+ private readonly fileSize : number ;
86+ constructor ( data : UploadFileSource , filename ?: string , fileSize ?: number ) {
87+ this . data = unifySources ( data ) ;
88+ const { name, size } = guessNameAndSize ( data , filename , fileSize ) ;
89+ this . filename = name ;
90+ this . fileSize = size ;
91+ }
92+ byteCount ( ) {
93+ return this . fileSize ;
3594 }
3695 add ( key : string , value : unknown ) {
3796 this . attributes . push ( [ key , value ] ) ;
@@ -60,36 +119,6 @@ export class UploadFile {
60119 // End multipart/form-data protocol
61120 yield enc . encode ( `\r\n--${ boundary } --\r\n` ) ;
62121 }
63-
64- static async * unifySources (
65- data : UploadFileSource
66- ) : AsyncIterable < Uint8Array > {
67- if ( data instanceof Uint8Array ) {
68- yield data ;
69- return ;
70- }
71-
72- if ( data instanceof Blob ) {
73- yield data . bytes ( ) ;
74- return ;
75- }
76-
77- if ( Symbol . iterator in data ) {
78- yield * data ;
79- return ;
80- }
81-
82- if ( Symbol . asyncIterator in data ) {
83- for await ( const chunk of data ) {
84- if ( typeof chunk === 'string' )
85- throw new Error (
86- 'bad file data, received string but expected Uint8Array'
87- ) ;
88- yield chunk ;
89- }
90- return ;
91- }
92- }
93122}
94123
95124export default class CloudConvert {
@@ -148,7 +177,7 @@ export default class CloudConvert {
148177 const presigned = options ?. presigned ?? false ;
149178 const flat = options ?. flat ?? false ;
150179 const url = new URL ( route , baseURL ) ;
151- const { contentType, search, body } = prepareParameters (
180+ const { contentLength , contentType, search, body } = prepareParameters (
152181 method ,
153182 parameters
154183 ) ;
@@ -158,6 +187,7 @@ export default class CloudConvert {
158187 const headers = {
159188 'User-Agent' : `cloudconvert-node/v${ version } (https://github.com/cloudconvert/cloudconvert-node)` ,
160189 ...( ! presigned ? { Authorization : `Bearer ${ this . apiKey } ` } : { } ) ,
190+ ...( contentLength ? { 'Content-Length' : contentLength } : { } ) ,
161191 ...( contentType ? { 'Content-Type' : contentType } : { } )
162192 } ;
163193 const res = await fetch ( url , {
@@ -230,6 +260,7 @@ function prepareParameters(
230260 method : 'GET' | 'POST' | 'DELETE' ,
231261 data ?: UploadFile | object
232262) : {
263+ contentLength ?: string ;
233264 contentType ?: string ;
234265 body ?: string | ReadableStream < Uint8Array > ;
235266 search ?: string ;
@@ -249,6 +280,7 @@ function prepareParameters(
249280 . map ( ( ) => Math . random ( ) . toString ( 36 ) [ 2 ] || 0 )
250281 . join ( '' ) } `;
251282 return {
283+ contentLength : data . byteCount ( ) . toString ( ) ,
252284 contentType : `multipart/form-data; boundary=${ boundary } ` ,
253285 body : asyncIterableToReadableStream ( data . stream ( boundary ) )
254286 } ;
0 commit comments