Node-slack-sdk: fix files.upload from Buffer with formData options

Created on 3 Jan 2017  路  4Comments  路  Source: slackapi/node-slack-sdk

Getting Error: no_file_data while uploading Buffer object with files.upload.

// doesn't work (see trace log below)
const image = fs.readFileSync('image.png');
await bot.files.upload('image.png', {
  file: image
});

// text buffer doesn't work too
await bot.files.upload('text.txt', {
  file: new Buffer('hello world');
});

// uploads image as Plain Text (incorrect)
await bot.files.upload('image.png', {
  content: image
});

// works well with streams
const imageStream = fs.createReadStream('image.png')
await bot.files.upload('image.png', {
  file: imageStream
});

Stacktrace:

2016-12-27T13:58:29.415Z - error:  Error: no_file_data
     at handleHttpResponse (/bot/node_modules/@slack/client/lib/clients/transports/call-transport.js:105:17)
     at handleTransportResponse (/bot/node_modules/@slack/client/lib/clients/transports/call-transport.js:155:19)
     at apply (/bot/node_modules/lodash/lodash.js:499:17)
     at wrapper (/bot/node_modules/lodash/lodash.js:5356:16)
     at Request.handleRequestTranportRes (/bot/node_modules/@slack/client/lib/clients/transports/request.js:20:5)
     at apply (/bot/node_modules/lodash/lodash.js:499:17)
     at Request.wrapper [as _callback] (/bot/node_modules/lodash/lodash.js:5356:16)
     at Request.self.callback (/bot/node_modules/request/request.js:186:22)
...
bug good first issue

Most helpful comment

Took me quite a bit of searching to look for the issue, but it lies in the fact that the module request uses, form-data, only recognizes certain stream objects as files when evaluating what is a file and what is a multi-part data parameter:

Form-Data can recognize and fetch all the required information from common types of streams (fs.readStream, http.response and mikeal's request), for some other types of streams you'd need to provide "file"-related information manually:

So the fix would be to supply the extra information that form-data expects (just the file name at minimum) when submitting a buffer.

This should be implemented by the SDK in the future, but in the meantime, here's how you'd go about uploading a buffer as a file:

web.files.upload(
  fileName,
  {
    file: {
      value: fileBufferData,
      options: {
        filename: fileName,
        contentType: 'mime-type', // optional, will be guessed by `form-data` module
        knownLength: fileBufferData.length // optional, will be deduced by `form-data` module
      }
    }
  }
);

Note: I'm using an undocumented (but 2-year-old) method of forwarding formData options to the form-data module. I chose this method as it is the best choice when it comes to modifying this SDK as the way the request module suggests (using the requestObj.form() method) would require quite a bit of refactoring of how the SDK generates and sends requests.

All 4 comments

I too am interested in how to make this happen. I've tried wrapping the Buffer as a Readable stream... no dice. It works if I write my buffer to a temp file and then use fs.createReadStream but that is really messy, slow, and not fun. I just want to send my data that is already available in memory without having to jump through hoops.

Took me quite a bit of searching to look for the issue, but it lies in the fact that the module request uses, form-data, only recognizes certain stream objects as files when evaluating what is a file and what is a multi-part data parameter:

Form-Data can recognize and fetch all the required information from common types of streams (fs.readStream, http.response and mikeal's request), for some other types of streams you'd need to provide "file"-related information manually:

So the fix would be to supply the extra information that form-data expects (just the file name at minimum) when submitting a buffer.

This should be implemented by the SDK in the future, but in the meantime, here's how you'd go about uploading a buffer as a file:

web.files.upload(
  fileName,
  {
    file: {
      value: fileBufferData,
      options: {
        filename: fileName,
        contentType: 'mime-type', // optional, will be guessed by `form-data` module
        knownLength: fileBufferData.length // optional, will be deduced by `form-data` module
      }
    }
  }
);

Note: I'm using an undocumented (but 2-year-old) method of forwarding formData options to the form-data module. I chose this method as it is the best choice when it comes to modifying this SDK as the way the request module suggests (using the requestObj.form() method) would require quite a bit of refactoring of how the SDK generates and sends requests.

@clavin i want to say thank you so much for your help in pinpointing this problem! you rock!

im triaging old issues and since this is reproducible, im labeling it as a bug. i hope that someone in the community can pick it up (it seems fairly obvious now that you've spelled out the solution) and send a PR. if not, it may take a little while for it to get picked up given our current prioritization.

Took me quite a bit of searching to look for the issue, but it lies in the fact that the module request uses, form-data, only recognizes certain stream objects as files when evaluating what is a file and what is a multi-part data parameter:

Form-Data can recognize and fetch all the required information from common types of streams (fs.readStream, http.response and mikeal's request), for some other types of streams you'd need to provide "file"-related information manually:

So the fix would be to supply the extra information that form-data expects (just the file name at minimum) when submitting a buffer.

This _should_ be implemented by the SDK in the future, but in the meantime, here's how you'd go about uploading a buffer as a file:

web.files.upload(
  fileName,
  {
    file: {
      value: fileBufferData,
      options: {
        filename: fileName,
        contentType: 'mime-type', // optional, will be guessed by `form-data` module
        knownLength: fileBufferData.length // optional, will be deduced by `form-data` module
      }
    }
  }
);

Note: I'm using an undocumented (but 2-year-old) method of forwarding formData options to the form-data module. I chose this method as it is the best choice when it comes to modifying this SDK as the way the request module suggests (using the requestObj.form() method) would require quite a bit of refactoring of how the SDK generates and sends requests.

this really saved my day

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bmajz picture bmajz  路  14Comments

CoreyCole picture CoreyCole  路  12Comments

mpcowan picture mpcowan  路  15Comments

kurisubrooks picture kurisubrooks  路  36Comments

gyunaev picture gyunaev  路  12Comments