Using Facebooks javascript api to post multipart/f

2019-08-29 07:10发布

问题:

According to Facebook's Graph documentation on the user photos edge, it's possible to post a photo from javascript using multipart/form-data encoded binary image. https://developers.facebook.com/docs/graph-api/reference/v2.2/user/photos

/* make the API call */
FB.api(
    "/me/photos",
    "POST",
    {
        "source": "{image-data}"
    },
    function (response) {
      if (response && !response.error) {
        /* handle the result */
      }
    }
);

The problem is there doesn't appear to be any sample documentation anywhere on the net on how exactly this is supposed to work. There is a method where you bypass FB.api and post directly to the edge via an xhr request. I've gotten this to work, but I'd really like to know how to do this using the FB.api command. Here is my code (FB app has been registered properly and all javascript sdk included elsewhere in the file):

function postImageToFacebook(authToken) {
    //Capture the image from a canvas
    var filename = "samplepic.png",
    mimeType = "image/png",
    message = "test comment",
    data = canvas.toDataURL("image/png"),
    encodedPng = data.substring(data.indexOf(',') + 1, data.length),
    imageData = Base64Binary.decode(encodedPng);

    // build the multipart/form-data
    var boundary = '----ThisIsTheBoundary1234567890';
    var formData = '--' + boundary + '\r\n'
    formData += 'Content-Disposition: form-data; name="source"; filename="' + filename + '"\r\n';
    formData += 'Content-Type: ' + mimeType + '\r\n\r\n';
    for (var i = 0; i < imageData.length; ++i) {
        formData += String.fromCharCode(imageData[i] & 0xff);
    }
    formData += '\r\n';
    formData += '--' + boundary + '\r\n';
    formData += 'Content-Disposition: form-data; name="message"\r\n\r\n';
    formData += message + '\r\n'
    formData += '--' + boundary + '--\r\n';



    //METHOD #1: send the data using xhr request
    var xhr = new XMLHttpRequest();
    xhr.open( 'POST', 'https://graph.facebook.com/me/photos?access_token=' + authToken, true );
    xhr.onload = xhr.onerror = function() {
        console.log( xhr.responseText );
    };
    //vvv This right here seems to be key, being able to add a header vvv
    xhr.setRequestHeader( "Content-Type", "multipart/form-data; boundary=" + boundary );
    xhr.sendAsBinary( formData );


    //METHOD #2: send the data using FB.api
    FB.api("/me/photos", "POST", {
        "source": formData
    }, function(response) {
        console.log(response)
    });

};

Method #1: works. I've used it numerous times to successfully share an image. When watching the network console in Chrome you can see content-type:multipart/form-data header being set to which it then puts all of the data into a "Request Payload" and the return response is a successful post ID.

Method #2: does not work. The error returned from Facebook is (#324) Requires upload file. Looking at network console, you can see that content-type:application/x-www-form-urlencoded header is set (which is what we don't want). The form gets put under the "source" post parameter under Form Data which is why I'm assuming it gets rejected by Facebook.

What is the magic required to make FB.api work properly here? The only clue they give in their docs is a link to W3's spec on multipart/form-data which I know my formData is conforming to since it works with xhr in method #1. Outside of the mention in facebook docs, I can't find any example of this javascript method actually working. Help?

回答1:

I did not get this to work with the official JS SDK (with FB.api), but this is how it works easily with a file input:

const fileReader = new FileReader();
const file = document.getElementById('imageInput').files[0];

fileReader.onloadend = async () => {
    const photoData = new Blob([fileReader.result], {type: 'image/jpg'});
    const formData = new FormData();

    formData.append('access_token', pageAccessToken);
    formData.append('source', photoData);
    formData.append('message', 'some status message');

    let response = await fetch(`https://graph.facebook.com/${pageId}/photos`, {
        body: formData,
        method: 'post'
    });
    response = await response.json();
    console.log(response);
};
fileReader.readAsArrayBuffer(file);

No additional plugin neccessary, FileReader and fetch API are native Javascript features.

If the input is a Base64 String of a canvas object, you can use this: Creating a Blob from a base64 string in JavaScript

More information: http://www.devils-heaven.com/facebook-javascript-sdk-photo-upload-with-formdata/