Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebClient.files.upload is broken #196

Closed
zachlatta opened this issue Apr 16, 2016 · 3 comments
Closed

WebClient.files.upload is broken #196

zachlatta opened this issue Apr 16, 2016 · 3 comments

Comments

@zachlatta
Copy link

First Attempt (and Failure)

To start out, I figured I'd try using form-data to create the multipart form.

let slack = require('@slack/client');
let WebClient = slack.WebClient;

let web = new WebClient(process.env.SLACK_API_TOKEN);

let readFile = fs.createReadStream('some_file.png');
let form = new FormData();

form.append('file', readFile);

web.files.upload(form, null, null, readFile.path, null, null, '<INSERT_CHANNEL_HERE>');

So I ran that and got the following error:

Unhandled rejection SlackAPIError: invalid_array_arg
    at makeAPICallPromiseResolverInner (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/client.js:163:22)
    at handleHttpResponse (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/transports/call-transport.js:103:5)
    at handleTransportResponse (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/transports/call-transport.js:140:19)
    at wrapper (/home/zrl/dev/mr-googly/node_modules/lodash/index.js:3592:19)
    at Request.handleRequestTranportRes (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/transports/request.js:20:5)
    at Request.wrapper [as _callback] (/home/zrl/dev/mr-googly/node_modules/lodash/index.js:3592:19)
    at Request.self.callback (/home/zrl/dev/mr-googly/node_modules/request/request.js:200:22)
    at emitTwo (events.js:100:13)
    at Request.emit (events.js:185:7)
    at Request.<anonymous> (/home/zrl/dev/mr-googly/node_modules/request/request.js:1046:10)
    at emitOne (events.js:95:20)
    at Request.emit (events.js:182:7)
    at IncomingMessage.<anonymous> (/home/zrl/dev/mr-googly/node_modules/request/request.js:973:12)
    at emitNone (events.js:85:20)
    at IncomingMessage.emit (events.js:179:7)
    at endReadableNT (_stream_readable.js:913:12)

The documentation says that invalid_array_arg means The method was passed a PHP-style array argument (e.g. with a name like foo[7]). These are never valid with the Slack API., but we're not explicitly passing in any sort of PHP-style array (or even an array at all).

I decided to look under the hood to see what was happening and found that the FormData stream object isn't being read as a stream at all. Instead, it's being serialized by the API client into a PHP-style form array, causing the error. Here's a screenshot of what the beginning of my RequestB.in looked like when testing this:

tmp

Second Attempt (and Failure)

Alright, so I decided to try manually serializing a file as a multipart/form-data string and then pass that in.

Having [object Object] as the file's body is invalid for PNG files, but it shouldn't prevent it from being uploaded. Uploading a PNG file with those contents manually works fine in Slack.

let slack = require('@slack/client');
let WebClient = slack.WebClient;

let web = new WebClient(process.env.SLACK_API_TOKEN);

let test = `--------------------------3552cad3b2d39974
Content-Disposition: form-data; name="file"; filename="file.png"

Content-Type: application/octet-stream

[object Object]

--------------------------3552cad3b2d39974--`;

web.files.upload(test, null, null, 'file.png', null, null, '<INSERT_CHANNEL_HERE>');

This time I got a different error:

Unhandled rejection SlackAPIError: no_file_data
    at makeAPICallPromiseResolverInner (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/client.js:163:22)
    at handleHttpResponse (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/transports/call-transport.js:103:5)
    at handleTransportResponse (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/transports/call-transport.js:140:19)
    at wrapper (/home/zrl/dev/mr-googly/node_modules/lodash/index.js:3592:19)
    at Request.handleRequestTranportRes (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/transports/request.js:20:5)
    at Request.wrapper [as _callback] (/home/zrl/dev/mr-googly/node_modules/lodash/index.js:3592:19)
    at Request.self.callback (/home/zrl/dev/mr-googly/node_modules/request/request.js:200:22)
    at emitTwo (events.js:100:13)
    at Request.emit (events.js:185:7)
    at Request.<anonymous> (/home/zrl/dev/mr-googly/node_modules/request/request.js:1046:10)
    at emitOne (events.js:95:20)
    at Request.emit (events.js:182:7)
    at IncomingMessage.<anonymous> (/home/zrl/dev/mr-googly/node_modules/request/request.js:973:12)
    at emitNone (events.js:85:20)
    at IncomingMessage.emit (events.js:179:7)
    at endReadableNT (_stream_readable.js:913:12)

Third Attempt (and Failure)

As a last ditch effort, I tried passing in the contents of a file directly as a string (with no multipart/form-data wrapping).

let slack = require('@slack/client');
let WebClient = slack.WebClient;

let web = new WebClient(process.env.SLACK_API_TOKEN);

web.files.upload('test', null, null, 'text.txt', null, null, '<INSERT_CHANNEL_HERE>');

And got the same error:

Unhandled rejection SlackAPIError: no_file_data
    at makeAPICallPromiseResolverInner (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/client.js:163:22)
    at handleHttpResponse (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/transports/call-transport.js:103:5)
    at handleTransportResponse (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/transports/call-transport.js:140:19)
    at wrapper (/home/zrl/dev/mr-googly/node_modules/lodash/index.js:3592:19)
    at Request.handleRequestTranportRes (/home/zrl/dev/mr-googly/node_modules/@slack/client/lib/clients/transports/request.js:20:5)
    at Request.wrapper [as _callback] (/home/zrl/dev/mr-googly/node_modules/lodash/index.js:3592:19)
    at Request.self.callback (/home/zrl/dev/mr-googly/node_modules/request/request.js:200:22)
    at emitTwo (events.js:100:13)
    at Request.emit (events.js:185:7)
    at Request.<anonymous> (/home/zrl/dev/mr-googly/node_modules/request/request.js:1046:10)
    at emitOne (events.js:95:20)
    at Request.emit (events.js:182:7)
    at IncomingMessage.<anonymous> (/home/zrl/dev/mr-googly/node_modules/request/request.js:973:12)
    at emitNone (events.js:85:20)
    at IncomingMessage.emit (events.js:179:7)
    at endReadableNT (_stream_readable.js:913:12)

Attempt at Figuring Out the Problem

I checked out the request made by the API tester on https://api.slack.com/methods/files.upload/test and it looks like all of the arguments besides the file are passed through the URL (like https://slack.com/api/files.upload?token=xxx&channels=xxx), while the file is submitted as a multipart form as the POST body.

There are currently two issues with the way the API client handles files.upload:

  1. The API client doesn't even submit the request as a multipart form. The Content-Type sent is application/x-www-form-urlencoded (and the contents of the request match that).
  2. The API client doesn't allow streams to be passed in for files.upload, making it very difficult to upload any sort of real file.
@ghost
Copy link

ghost commented Apr 25, 2016

By convention in the API we use opts style arguments for any optional argument to the API call, so you can do a file upload like:

var opts = {
  content: fs.readFileSync(filePath)
};

web.files.upload('api_test.csv', opts, function(err, res) {
  console.log(res);
});

The full arguments the opts arg takes are:

 * @param {Object=} opts
 * @param {?} opts.file - File contents via `multipart/form-data`. If omitting this parameter, you
 *   must submit `content`.
 * @param {?} opts.content - File contents via a POST variable. If omitting this parameter, you
 *   must provide a `file`.
 * @param {?} opts.filetype - A [file type](/types/file#file_types) identifier.
 * @param {?} opts.title - Title of file.
 * @param {?} opts.initial_comment - Initial comment to add to file.
 * @param {?} opts.channels - Comma-separated list of channel names or IDs where the file will be
 *   shared.

NOTE: the above was written with the 3.0.0 version of the client, it's the same basic principle for 2.3.0, but I haven't looked at the old JSDoc there.

@ghost
Copy link

ghost commented Apr 25, 2016

I'll add an example to the README of how to do a file upload in a sec and close this issue once I have.

@ghost ghost closed this as completed in 90b3d37 Apr 25, 2016
@stanfeldman
Copy link

You guys broke this API, upgraded from 2.3.0 and my files uploading just stopped working.
If anybody experiences you can downgrade to 2.3.0 version, files uploading works there.

PaulAsjes-zz pushed a commit to PaulAsjes-zz/node-slack-client that referenced this issue Jul 11, 2016
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants