unable to upload files using UploadCollection in SAPUI5 - odata
I am receiving 403 Forbidden error when I try to upload a file using UploadCollection.
The code in my view.js is:
var oOUpload = new sap.m.UploadCollection("oinspupload",{
multiple : true,
sameFilenameAllowed : true,
instantUpload : false,
uploadUrl : "/sap/opu/odata/sap/ZACCBILL_SRV/FileSet",
/* uploadComplete : function(oEvent){
//alert ("File Uploaded successfully");
// oController.fileUpload(oEvent);
}, */
fileDeleted : function(oEvent){
oController.fileDelete(oEvent);
},
fileRenamed : function(oEvent){
alert ("File renamed successfully");
//oController.fileRename(oEvent);
}
});
The code in my view.controller is:
OData.request({
requestUri : sServiceUrl,
method : "GET",
headers :
{
"X-Requested-With" : "XMLHttpRequest",
"Content-Type" : "application/atom+xml",
"DataServiceVersion" : "2.0",
"Authorization" : AuthToken,
"X-CSRF-Token" : "Fetch"
}
},
function(data, response) {
debugger;
if(sap.ui.Device.browser.chrome || sap.ui.Device.browser.msie || sap.ui.Device.browser.safari){
header_xcsrf_token = response.headers['x-csrf-token'];
}else if(sap.ui.Device.browser.firefox){
header_xcsrf_token = response.headers['X-CSRF-Token'];
}
xcsrf_token_ref.header_xcsrf_token = header_xcsrf_token;
csrftoken = xcsrf_token_ref.header_xcsrf_token;
debugger;
uploadattachments(xcsrf_token_ref);
},
function(err) {
debugger;
var request = err.request; // the request that was sent.
var response = err.response; // the response that was received.
alert("Error in Get -- Request "
+ request + " Response "
+ response);
});
function uploadattachments(token){
debugger;
var uploader;
uploader= sap.ui.getCore().byId("oinspupload");
var aItems = uploader.getItems();
var slug, sequence;
for (i = 0; i < aItems.length; i++) {
sequence = i + 1;
slug = "CONTAINERID1000040100;STATUSIB;SEQUENCE" + sequence+ ";FILENAMECamera.png" ;
uploader.addHeaderParameter(new sap.m.UploadCollectionParameter({name: "slug", value: slug }));
debugger;
uploader.addHeaderParameter(new sap.m.UploadCollectionParameter({name: "X-Csrf-Token", value: token.header_xcsrf_token }));
uploader.upload();
}
}
Please don't mind the missing parenthesis as the code above is not the complete code.
The above code works fine with fileuploader. I am sure the issue is that the uploadcollection is not passing the fetched CSRF Token properly but I am unable to figure out what's wrong.
Finally Found the solution myself with the help of the following blog
http://scn.sap.com/community/developer-center/front-end/blog/2016/03/29/using-the-uploadcollection-to-uploaddownload-archivelink-files-via-gateway
Upload Collection only works with instantUpload as true and does not work with instantUpload as false as of version 1.32.X. UploadCollection is Buggy and is yet to be rectified in the future versions. Also the CSRF token validation needs to be done in the change event. Below is the code:
View.js
var oOUpload = new sap.m.UploadCollection("oinspupload",{
multiple : true,
sameFilenameAllowed : false,
instantUpload : true,
uploadUrl : "/sap/opu/odata/sap/ZACCBILL_SRV/FileSet",
fileDeleted : function(oEvent){
oController.fileDelete(oEvent);
},
fileRenamed : function(oEvent){
alert ("File renamed successfully");
},
change: function(oEvent) {
debugger;
csrftoken = xcsrf_token_ref.header_xcsrf_token;
var oUploadCollection = oEvent.getSource();
var oCustomerHeaderToken = new sap.m.UploadCollectionParameter({
name : "x-csrf-token",
value : csrftoken
});
oUploadCollection.addHeaderParameter(oCustomerHeaderToken);
},
});
All header params must be added from "change" function. If you add it after, they won't be recieved on Backend.
Also, It is possible upload files with instantUpload=false. You only need bind uploadUrl parameter with a paremeter of view's model, and dynamically, it will change when you change the url.
For example:
View element:
<UploadCollection instantUpload="false" uploadUrl="{ResourceModel>/sServiceUrl}"/>
Controller onInitFunction:
var resourcemodel = this.getOwnerComponent().getModel("ZGW_PURCHREQ_01_SRV");
var oDataResource = {
sServiceUrl: resourcemodel.sServiceUrl + "/FileSet"
};
var jsonResource = new JSONModel(oDataResource);
this.getView().setModel(jsonResource, "ResourceModel");
When you fire upload, it will send a petition to uploadUrl defined on "sServiceUrl" of "ResourceModel".
Other option is set upload url and/or new header params before fire upload function with:
var oUploadCollection = this.getView().byId("UploadCollection");
var sServiceUrl = resourcemodel.sServiceUrl + "/FileSet";
var headerBanfn = null;
for (var i = 0; i < oUploadCollection._aFileUploadersForPendingUpload.length; i++) {
headerBanfn = new sap.ui.unified.FileUploaderParameter({
name: "banfn",
value: "123456"
});
oUploadCollection._aFileUploadersForPendingUpload[i].setUploadUrl(sServiceUrl);
oUploadCollection._aFileUploadersForPendingUpload[i].addHeaderParameter(headerBanfn);
}
oUploadCollection.upload();
I hope it was useful.
Related
Ajax calls working in Android but not iOS
I'm developing an app in Nativescript for the first time and running into an issue where AJAX calls work on Android but not iOS. I have a login.js file which requires a user-view-model (user-view-model.js), and when I test the code on Android it takes me to the "home" page but it hits the catch function on iOS. login.js: var dialogsModule = require("ui/dialogs"); var UserViewModel = require("../../shared/view-models/user-view-model"); var applicationSettings = require("application-settings"); var user = new UserViewModel({ email: "aaa#aaa.com", password: "aaa" }); var frameModule = require("ui/frame"); var page; exports.loaded = function(args) { page = args.object; page.bindingContext = user; }; exports.login = function () { user.login().catch(function(error) { dialogsModule.alert({ message: "Unfortunately we could not find your account.", okButtonText: "OK" }); return Promise.reject(); }).then(function(response) { console.dir(response) console.log("past response") applicationSettings.setString("user_id", response.user_id); applicationSettings.setString("first_name", response.first_name); applicationSettings.setString("last_name", response.last_name); applicationSettings.setString("user_type", response.user_type); var topmost = frameModule.topmost(); topmost.navigate("views/home/home"); }); }; user-view-model.js: var config = require("../../shared/config"); var fetchModule = require("fetch"); var observableModule = require("data/observable"); var http = require("http"); function User(info) { info = info || {}; var viewModel = new observableModule.fromObject({ email: info.email || "", password: info.password || "" }); viewModel.login = function() { let loginEmail = JSON.stringify(this.get("email")).replace(/['"]+/g, ''); let loginPassword = JSON.stringify(this.get("password")).replace(/['"]+/g, ''); console.log(loginEmail, loginPassword); let loginUrl = config.serverPHPServiceUrl + "Login.php?user_id=" + loginEmail + "&password=" + loginPassword; console.log(loginUrl); // I tried this way first and wasn't able to login on iOS, which made me try the second method below. // return fetchModule.fetch(loginUrl, { // method: "POST", // headers: { // "Content-Type": "application/json" // } // }).then(handleErrors).then(function(response) { // return response.json(); // }).then(function(data) { // console.dir(data); // console.log(data["results"][0]["user_id"]) // return data["results"][0]; // }); // This method works on Android but not iOS. return http.getJSON(loginUrl).then(function(response) { console.dir(response); return response.results[0]; }) }; return viewModel; }; function handleErrors(response) { console.log("in errors") if (!response.ok) { console.log(JSON.stringify(response)); throw Error(response.statusText); } return response; } module.exports = User; Is there anything fundamentally wrong with my code, or do asynchronous calls work differently on iOS vs Android in Nativescript? I did the Grocery tutorial and didn't run into this issue, so I didn't think this was the case. Does it matter that the backend is using PHP?
I fixed my issue: I started a new project with Angular 2 and ran into the same error, but then it gave me the error message "Error: The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." I solved it by adding "https" to my url call, but this post has another solution.
How to make ol.source.ImageWMS send POST request
In our project, we're using OpenLayers-3's ol.source.ImageWMS to show image provided by Mapserver WMS. Since we're using Mapserver runtime substitution, our request can become quite long, which could cause a problem for a GET request. Is there a way to make ol.source.ImageWMS send POST request?
I answer this just for the reference based on this Openlayers dev thread, hopefully it will help someone in the future!. I needed to pass a very long CQL request to a Geoserver wms, and GET was limited in size, so I used POST like the following: var POSTWMSLayer = new ol.layer.Image({ source: new ol.source.ImageWMS({ url: 'https://test.server.com/geoserver/wms', params: { 'LAYERS': 'firstworkspace:states', 'CQL_FILTER':'gid IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798,2799,2800,2801,2802,2803,2804,2805,2806,2807,2808,2809,2810,2811,2812,2813,2814,2815,2816,2817,2818,2819,2820,2821,2822,2823,2824,2825,2826,2827,2828,2829,2830,2831,2832,2833,2834,2835,2836,2837,2838,2839,2840,2841,2842,2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856,2857,2858,2859,2860,2861,2862,2863,2864,2865,2866,2867,2868,2869,2870,2871,2872,2873,2874,2875,2876,2877,2878,2879,2880,2881,2882,2883,2884,2885,2886,2887,2888,2889,2890,2891,2892,2893,2894,2895,2896,2897,2898,2899,2900,2901,2902,2903,2904,2905,2906,2907,2908,2909,2910,2911,2912,2913,2914,2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931,2932,2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,2943,2944,2945,2946,2947,2948,2949,2950,2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968,2969,2970,2971,2972,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2985,2986,2987,2988,2989,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,3113,3114,3115,3116,3117,3118,3119,3120,3121,3122,3123,3124,3125,3126,3127,3128,3129,3130,3131,3132,3133,3134,3135,3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156,3157,3158,3159) }, serverType: 'geoserver', imageLoadFunction: function (image, src) { var img = image.getImage(); if (typeof window.btoa === 'function') { var urlArray = src.split("?"); var url = urlArray[0]; var params = urlArray[1]; var xhr = new XMLHttpRequest(); xhr.onload = function (e) { if (this.status === 200) { var uInt8Array = new Uint8Array(this.response); var i = uInt8Array.length; var binaryString = new Array(i); while (i--) { binaryString[i] = String.fromCharCode(uInt8Array[i]); } var data = binaryString.join(''); var type = xhr.getResponseHeader('content-type'); if (type.indexOf('image') === 0) { img.src = 'data:' + type + ';base64,' + window.btoa(data); } } }; xhr.open('POST', url, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.responseType = 'arraybuffer'; xhr.send(params); } else { img.src = src; } } }) });
Actually the httprequest is the problem, use Httprequest Post method instead of get method in ol.source.ImageWMS? Get request can not pass long string parameters. For big parameters we need to pass request with post method. Now the bottleneck is that the post method is not supported in openlayers 3 whereas in old version it had support for post method. Note: This is old OpenLayers code var query = new OpenLayers.Layer.WMS.Post("My Layer", 'http://192.168.6.51:8090/geoserver/VP/wms', { LAYERS : 'Namespace:LayerName', sld_body : strSld_body, format : 'image/jpeg', transparent : 'true' }, { unsupportedBrowsers: [], isBaseLayer: false, yx : {'EPSG:4326' : true} } ); In openlayers 3 there may be a workaround.
bootstrap min. js is running the request 2 times and hang until not refresh the page
[my ajax] when i run the code the bootstrap.min.js will run but there two isssues comes in that 1. that is hit the request two time and 2. that after the 2 hit it will have to close the popup and hang the page until it refresh the page function add_edit_party(){ var place_id = myFunction(); var $j = jQuery.noConflict(); var estimated_wait = 0; var no_sms = ""; var is_hd = 0; $j('.alert-danger').removeClass('show').addClass('hide'); if($j('#'+place_id+'_wl_phone').val()){ phone_length = $j('#'+place_id+'_wl_phone').val().length; if (phone_length!=10){ $j('#'+place_id+'_wl_phone').next().removeClass('hide').addClass('show'); return false; } } if(!validateEmail($j('#'+place_id+'_wl_email').val())){ $j('#'+place_id+'_wl_email').next().removeClass('hide').addClass('show'); return false; } if($j('#'+place_id+'_wl_unknown_persons').val()==""){ $j('#'+place_id+'_wl_unknown_persons').next().removeClass('hide').addClass('show'); return false; } if($j('#'+place_id+'_wl_estimated_wait').val()=="" && $j('#serve_time').val() == "1"){ $j('#'+place_id+'_wl_estimated_wait').next().removeClass('hide').addClass('show'); return false; } if ($j('#serve_time').val() == "0"){ estimated_wait = 0; } if ($j('#serve_time').val() == "1") estimated_wait = $j('#'+place_id+'_wl_estimated_wait').val(); } place_party_req_id = ""; if($j('#place_party_req_id').val()){ place_party_req_id = $j('#place_party_req_id').val(); } request_param = { place_id : place_id, color_update_at: "3", name : $j('#'+place_id+'_wl_name').val(), notes : $j('#'+place_id+'_wl_notes').val(), phone : "0"+$j('#'+place_id+'_wl_phone').val(), email : $j('#'+place_id+'_wl_email').val(), unknown_persons : $j('#'+place_id+'_party_size').val(), size : $j('#'+place_id+'_party_size').val(), estimated_wait : estimated_wait, color_status: 3, party_request_id: place_party_req_id, authenticity_token : window._token }; $j(".loading").show(); var url='/add_party/'+place_id $j.ajax({ type: "POST", url: url, async: false, data: request_param, dataType : 'json', success: function(result){ if(result['party_id']){ if($j('#place_party_req_id').val()){ edit_party_request($j('#place_party_req_id').val(),'accepted'); $j('#place_party_req_id').val(""); } party_id = result['party_id']; if(estimated_wait=='0'){ seat_party(party_id); return; } refresh_wl(); setTimeout(function(){ jQuery(".loading").hide(); }, 600); $j('#myModal').modal('hide'); $j('.nav-tabs a:first').tab('show'); } }, error: function(msg){ //alert("Something went wrong..."); } }); return false; }
see whether you have added multiple js files in your layout file.It may be application.js or jquery.js.Many a times we add js file in the view as well as in the layout as well... Moreover there can be a chance where any custom js in application.js will also having a duplicate file already included in application.js file.Please check
Send a recorded file via Filetransfer with Cordova/Phonegap
I am trying to send a voice recording that I recorded via the Media plugin. When I try to send the file I get this FileError.NOT_FOUND_ERR error: Error opening file /myRecording100.wav: Error Domain=NSCocoaErrorDomain Code=260 "The operation couldn’t be completed. (Cocoa error 260.)" UserInfo=0xa358640 {NSFilePath=/myRecording100.wav, NSUnderlyingError=0xa34fb30 "The operation couldn’t be completed. No such file or directory"} 2014-08-06 17:02:26.919 Bring Me[40961:c07] FileTransferError { code = 1; source = "/myRecording100.wav"; target = "http://XXXX.xom"; } However, I can play the voice recording after recording it. Why would I be able to play the file (showing that the file was recorded and saved correctly) but FileTransfer be unable to send it? Here is my code (for ios): var my_recorder = null; var mediaFileFullName = null; // iOS var mediaRecFile = "myRecording100.wav"; var checkFileOnly = false; /****** Call when start recording ******/ function startRecording() { checkFileOnly = false; window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccessFileSystem, function() { console.log("***test: failed in creating media file in requestFileSystem"); }); } function onSuccessFileSystem(fileSystem) { if (checkFileOnly === true) { // Get File and send fileSystem.root.getFile(mediaRecFile, { create: false, exclusive: false }, onOK_GetFile, onFail_GetFile); } else { // Create File fileSystem.root.getFile(mediaRecFile, { create: true, exclusive: false }, onOK_SaveFile, onFail_GetFile); } } /* Save the file*/ function onOK_SaveFile(fileEntry) { mediaFileFullName = fileEntry.fullPath; my_recorder = new Media(mediaFileFullName, function() { document.location ="address_form.html"; // Redirect the user to an other page }, function(err) { console.log("playAudio():callback Record Error: "+err);} ); my_recorder.startRecord(); } /* Get the file and send it */ function onOK_GetFile(fileEntry) { mediaFileFullName = fileEntry.fullPath; /* // Read the recorded file is WORKING ! my_player = new Media(mediaFileFullName, onMediaCallSuccess, onMediaCallError); my_player.play(); */ var options = new FileUploadOptions(); options.fileKey = "want"; options.fileName = "file.wav"; options.mimeType = "audio/wav"; options.chunkedMode = false; options.params = parameters; var ft = new FileTransfer(); ft.upload(mediaFileFullName, "https://SERVER_ADDRESS", win, fail, options); } /****** Called when stop recording ******/ function stopRecording() { if (my_recorder) { my_recorder.stopRecord(); } }
Since the v1.0 of File plugin, to upload a file in the filesystem via the file-transfer plugin, you'll need to use the .toURL() method to access to it. If you are upgrading to a new (1.0.0 or newer) version of File, and you have previously been using entry.fullPath as arguments to download() or upload(), then you will need to change your code to use filesystem URLs instead. FileEntry.toURL() and DirectoryEntry.toURL() return a filesystem URL of the form So the correct code is : /* Get the file and send it */ function onOK_GetFile(fileEntry) { var options = new FileUploadOptions(); options.fileKey = "want"; options.fileName = "file.wav"; options.mimeType = "audio/wav"; options.chunkedMode = false; options.params = parameters; var ft = new FileTransfer(); ft.upload(fileEntry.toURL(), "https://SERVER_ADDRESS", win, fail, options); }
I got the exact same issue on iOS,and FileUploadOptions didn't work for me. In case someone is struggling as well, the solution for me has been to switch to LocalFileSystem.Temporary. Here there is a snippet which shows a full example (not tested on Android): var accessType = LocalFileSystem.TEMPORARY; // It was LocalFileSystem.PERSISTENT; /** Utility function to return a fileEntry together with the metadata. */ var getFile = function(name, create, successCallback, failCallback) { WL.Logger.debug("Request for file " + name + " received, create is " + create + "."); var onSuccessFileSystem = function(fileSystem) { fileSystem.root.getFile(name, { create: create, exclusive: false }, function(fileEntry){ WL.Logger.debug("Success, file entry for " + name + " is " + JSON.stringify(fileEntry)); fileEntry.getMetadata(function(metadata){ WL.Logger.debug("File entry " + name + " metadata is: " + JSON.stringify(metadata)); successCallback(fileEntry, metadata); }, function(err) { WL.Logger.debug("Fail to retrieve metadata, error: " + JSON.stringify(err)); if(failCallback) failCallback(err); }); }, function(err) { WL.Logger.error("Failed to retrieve the media file " + name + "."); if(failCallback) failCallback(err); }); } window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; window.requestFileSystem(accessType, 0, onSuccessFileSystem, function(err) { WL.Logger.error("Failed to access file system."); if(failCallback) failCallback(err); }); }; var Recorder = declare([ ], { mediaSrc : null, mediaObj : null, constructor : function(data, domNode){ this.mediaSrc = "new_recording.wav"; }, startRecord : function() { var self = this; var startRecording = function(source) { var onMediaCallSuccess = function() { WL.Logger.debug("Media object success."); }; var onMediaCallError = function(err) { WL.Logger.error("Error on the media object: " + JSON.stringify(err)); }; self.mediaObj = new Media(source, onMediaCallSuccess, onMediaCallError); self.mediaObj.startRecord(); }; // On iOS, first I need to create the file and then I can record. if (deviceCheck.phone.ios) { WL.Logger.debug("iOS detected, making sure the file exists."); getFile(this.mediaSrc, true, function(fileEntry){ startRecording(fileEntry.fullPath); }); } else { if (!deviceCheck.phone.android) WL.Logger.warn("Don't know the device, trying to record ..."); else WL.Logger.debug("Android detected."); startRecording(this.mediaSrc); } }, stopRecord : function() { this.mediaObj.stopRecord(); this.mediaObj.release(); }, play: function() { var p, playSuccess = function() { WL.Logger.debug("Play success."); p.release(); }, playFail = function() { WL.Logger.debug("Play fail."); }; p = new Media(this.mediaSrc, playSuccess, playFail); p.play(); }, getData : function(successCallback, failCallback) { var fileName = (deviceCheck.phone.android ? "/sdcard/" : "") + this.mediaSrc; WL.Logger.debug("Asking for the file entry ... "); getFile(this.mediaSrc, false, function(fileEntry, metadata) { WL.Logger.debug("Success: I found a file entry: " + fileEntry.nativeURL + ", size is " + metadata.size); fileEntry.file(function(file) { WL.Logger.debug("Success: file retrieved!"); var reader = new FileReader(); reader.onloadend = function(evt) { WL.Logger.debug("Sending content and event data to success callback."); successCallback(this.result, metadata, evt); }; reader.readAsDataURL(file); }, function(err){ WL.Logger.error("Error: Impossible to retrieve the file"); failCallback(err); }) }, function(err){ WL.Logger.error("Fail: no file entry found: " + JSON.stringify(err)); failCallback(err); }); } }); There is a bit of Worklight (debug output) and dojo (declare), but this code could be useful as reference.
Making a POST request to Tumblr's API inside a Chrome Extension
I'm trying to make a text post to Tumblr using their API and chrome_ex_oauth. API: http://www.tumblr.com/docs/en/api/v2#posting chrome_ex_oauth: http://code.google.com/chrome/extensions/tut_oauth.html The whole process of getting authorized works. What I can't get to work is doing a POST. I'm doing the following: Edit: I've updated the code to reflect Rob W's correct suggestion about the body field var stringify = function (parameters) { var params = []; for(var p in parameters) { params.push(encodeURIComponent(p) + '=' + encodeURIComponent(parameters[p])); } return params.join('&'); }; var onAuthorized = function() { var url = 'http://api.tumblr.com/v2/blog/jindie.tumblr.com/post'; var request = { 'method': 'POST', 'headers':{ 'Content-Type':'application/x-www-form-urlencoded' }, 'body': stringify({ 'type': 'text', 'state': 'draft', 'title': 'Test post...', 'body': 'Hello, World!' }) }; oauth.sendSignedRequest(url, function(responseText, xhr){alert(responseText);}, request); }; oauth.authorize(onAuthorized); I've been examining the code, and thinking what could be wrong, but I seriously have no idea. Do you? Do you know where I'm going wrong?
When the documentation doesn't help have a look at the source code, chrome_ex_oauth.js. You have to use 'body' instead of 'parameters': var request = { 'method': 'POST', 'body': { Debugging In order to find the cause, I followed these steps (annotated my thoughts): Apparently, the post body is empty. So, the implementation of the API must be wrong. Ctrl + F sendSignedRequest: ChromeExOAuth.prototype.sendSignedRequest = function(url, callback, opt_params) { var method = opt_params && opt_params['method'] || 'GET'; var body = opt_params && opt_params['body'] || null; var params = opt_params && opt_params['parameters'] || {}; var headers = opt_params && opt_params['headers'] || {}; var signedUrl = this.signURL(url, method, params); // Hmm...? Where is `params` being passed...? ChromeExOAuth.sendRequest(method, signedUrl, headers, body, function (xhr) { if (xhr.readyState == 4) { callback(xhr.responseText, xhr); } }); }; signURL doesn't modify params, so that's not a problem. Ctrl + F sendRequest: ChromeExOAuth.sendRequest = function(method, url, headers, body, callback) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(data) { callback(xhr, data); } xhr.open(method, url, true); if (headers) { . . . } xhr.send(body); // <-- !!! }; Got it! body has to be used instead of parameters. Backtracks the body variable to the request['body'] (see 2).