Search code examples
javafacebookrestfacebook-graph-apiput

No Response from REST API, & Parse.com Cloud Error: Can't Form Encode an Object


I am trying to publish a Facebook post to a user's own wall using the Facebook Graph API, but I am experiencing multiple problems.

I have a Wordpress site and I am using the OneAll.com service to maintain and manage my users' social logins. I had added the required permission for posting for a user to my Facebook app, after I had already linked an account to a Facebook account. Problematically, OneAll does not seem to update the access token stored in that social identity until it expires. But it seems that access token was prematurely expired due to the newly added app permission, so I need to manually re-synchronize the user's social identity on OneAll.

They do have a function to call to synchronize an identity through their API (Here), but I cannot seem to successfully send a request to it.

I tried to set up an HTTP PUT request as specified in my Java servlet, but it fails every time when called with error 411 returned. I have done my research and that seems to be a need to specify the content-length in the request, yet it does not seem to work even when I do try adding that.

So instead I tried setting up a cloud function in my app's Parse.com cloud code to activate this resync, but this one fails each time too, with an error code I can't seem to figure out:

Input: {"oaIDToken":"924e6f**********"}
Result: Uncaught Error: Can't form encode an Object

Any idea what this error means and how to fix it? I can't tell what I am doing wrong. This is my cloud function:

Parse.Cloud.define("forceOAIDUpdate", function(request, response) {
var IDToken = request.params.oaIDToken;
var IDURL = "https://w*******d.api.oneall.com/identities/" + IDToken + "/synchronize.json";

Parse.Cloud.httpRequest({
        method: 'PUT',
        url: IDURL,
        body: {
            request: {
                synchronize: {
                    update_user_data: true,
                    force_token_update: true
                }
            }
        },
        success: function(httpResponse) {
            console.log("OA ID token successfully refreshed.");
            console.log(httpResponse.text);
            response.success("OA ID token refreshed");
        },
        error: function(httpResponse) {
            console.error('Requested OA ID refresh failed with response code ' + 
                httpResponse.status);
            response.error("Failed to refresh OA ID. Error: " + 
                httpResponse.data + httpResponse.text + httpResponse.error);
        }
    });
});

I know for a fact the OneAll identity token the function receives is valid/correct because it works in my other functions which perform other actions for the user. Also, I had the Basic Authentication login attached to the request when I tried doing it in my Java servlet, but it didn't seem to make a difference. Either way, is it possible that missing authentication is what is causing this error? And if so, how do I insert that authentication header in that Parse cloud HTTP request? I've checked and cannot find any resources online which clearly describe it.

There is another really odd thing I can't figure out. I have tried to manually make this call to OneAll's REST API using a REST Console/Client, but no matter the URL to any port of their REST API, the connection always fails - the console returns immediately that there was no response. This is true with or without the Basic Authentication header attached to the request. What in the world is going on??

I've reached a "writer's block", so-to-say, and have run out of ideas to debug this. I'll greatly appreciate any assistance. This problem has had me stumped for too many hours already.


Solution

  • I finally figured it out!!!

    The Can't form encode an Object error from Parse simply meant that my body parameters needed to be encoded as a JSON string.

    The authentication problem was resolved by including the Basic Authorization in the HTTP Request header. But it had to be encoded in Base64 and since Parse's Cloud code does not seem to support the btoa() function (as I've tried), I had to manually include/create a Base64 encoder.

    So I changed the Parse Cloud function to the following and it finally worked:

    Parse.Cloud.define("forceOAIDUpdate", function(request, response) {
                    var Base64 = {
                // private property
                _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
                // public method for encoding
                encode : function (input) {
                    var output = "";
                    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
                    var i = 0;
                    input = Base64._utf8_encode(input);
                    while (i < input.length) {
                        chr1 = input.charCodeAt(i++);
                        chr2 = input.charCodeAt(i++);
                        chr3 = input.charCodeAt(i++);
                        enc1 = chr1 >> 2;
                        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                        enc4 = chr3 & 63;
                        if (isNaN(chr2)) {
                            enc3 = enc4 = 64;
                        } else if (isNaN(chr3)) {
                            enc4 = 64;
                        }
                        output = output +
                        Base64._keyStr.charAt(enc1) + Base64._keyStr.charAt(enc2) +
                        Base64._keyStr.charAt(enc3) + Base64._keyStr.charAt(enc4);
                    }
                    return output;
                },
                // public method for decoding
                decode : function (input) {
                    var output = "";
                    var chr1, chr2, chr3;
                    var enc1, enc2, enc3, enc4;
                    var i = 0;
                    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
                    while (i < input.length) {
                        enc1 = Base64._keyStr.indexOf(input.charAt(i++));
                        enc2 = Base64._keyStr.indexOf(input.charAt(i++));
                        enc3 = Base64._keyStr.indexOf(input.charAt(i++));
                        enc4 = Base64._keyStr.indexOf(input.charAt(i++));
                        chr1 = (enc1 << 2) | (enc2 >> 4);
                        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                        chr3 = ((enc3 & 3) << 6) | enc4;
                        output = output + String.fromCharCode(chr1);
                        if (enc3 != 64) {
                            output = output + String.fromCharCode(chr2);
                        }
                        if (enc4 != 64) {
                            output = output + String.fromCharCode(chr3);
                        }
                    }
                    output = Base64._utf8_decode(output);
                    return output;
                },
                // private method for UTF-8 encoding
                _utf8_encode : function (string) {
                    string = string.replace(/\r\n/g,"\n");
                    var utftext = "";
                    for (var n = 0; n < string.length; n++) {
                        var c = string.charCodeAt(n);
                        if (c < 128) {
                            utftext += String.fromCharCode(c);
                        }
                        else if((c > 127) && (c < 2048)) {
                            utftext += String.fromCharCode((c >> 6) | 192);
                            utftext += String.fromCharCode((c & 63) | 128);
                        }
                        else {
                            utftext += String.fromCharCode((c >> 12) | 224);
                            utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                            utftext += String.fromCharCode((c & 63) | 128);
                        }
                    }
                    return utftext;
                },
                // private method for UTF-8 decoding
                _utf8_decode : function (utftext) {
                    var string = "";
                    var i = 0;
                    var c = c1 = c2 = 0;
                    while ( i < utftext.length ) {
                        c = utftext.charCodeAt(i);
                        if (c < 128) {
                            string += String.fromCharCode(c);
                            i++;
                        }
                        else if((c > 191) && (c < 224)) {
                            c2 = utftext.charCodeAt(i+1);
                            string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                            i += 2;
                        }
                        else {
                            c2 = utftext.charCodeAt(i+1);
                            c3 = utftext.charCodeAt(i+2);
                            string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                            i += 3;
                        }
                    }
                    return string;
                }
                }
    
    var IDToken = request.params.oaIDToken;
    var key = "f1a914*******************************";
    var secret = "e804************************************";
    var basicAuthOA = key + ":" + secret;
    var basicAuthOAEncoded = Base64.encode(basicAuthOA);
    var IDURL = "https://whentosend.api.oneall.com/identities/" + IDToken + "/synchronize.json";
    var headerAuth = {
        'Authorization': "Basic " + basicAuthOAEncoded
    }
    
    var params = { 
        request: {
            synchronize: {
                update_user_data: true,
                force_token_update: true
            }
        }
    }
    
    Parse.Cloud.httpRequest({
        method: 'PUT',
        url: IDURL,
        headers: headerAuth,
        body: JSON.stringify(params),
        success: function(httpResponse) {
            console.log("OA ID token successfully refreshed.");
            console.log(httpResponse.text);
            response.success("OA ID token refreshed");
        },
        error: function(httpResponse) {
            console.error('Requested OA ID refresh failed with response code ' + 
                httpResponse.status);
            response.error("Failed to refresh OA ID. Error: " + 
                httpResponse.data + httpResponse.text + httpResponse.error);
        }
    });
    });
    

    I hope this helps someone else save a ton of debugging time. It frustratingly took me days to figure this out, and I wouldn't wish that on any programmer.