From 4c6d57bfe2bcc41dd7429fe7c157aafb77cea3e7 Mon Sep 17 00:00:00 2001 From: louisameline Date: Wed, 30 Apr 2014 16:43:57 +0200 Subject: [PATCH 1/4] Added a fourth 'options' parameter for request. The first option I created allows us to accept non-json (binary for example) resources sent by the API, like photos or videos --- README.md | 18 +++++++++++++ fb.js | 79 ++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index c0ac369..a8fc8f7 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,24 @@ FB.api('4', { fields: ['id', 'name'] }, function (res) { }); ``` +__Expecting a non-JSON response__ + +```js +var FB = require('fb'); + +// by default, the library expects a JSON response and will return an error otherwise. +// a contentType option can be provided in the fourth parameter to prevent this. +// for example, set content type to 'image/jpeg', ['image/jpeg', 'image/gif'] or just 'any' +FB.api('4/picture', { redirect: 1 }, { contentType: 'any'}, function (res) { + if(!res || res.error) { + console.log(!res ? 'error occurred' : res.error); + return; + } + console.log(res.id); + console.log(res.name); +}); +``` + ### Post ```js diff --git a/fb.js b/fb.js index 39d2e39..f955c79 100644 --- a/fb.js +++ b/fb.js @@ -99,7 +99,8 @@ * @access public * @param path {String} the url path * @param method {String} the http method (default: `"GET"`) - * @param params {Object} the parameters for the query + * @param params {Object} the query parameters sent to Facebook + * @param reqOptions {Object} options for the handling of the request * @param cb {Function} the callback function to handle the response */ api = function() { @@ -140,12 +141,14 @@ * * Make a api call to Graph server. * - * Except the path, all arguments to this function are optiona. So any of - * these are valid: + * Except the path, all arguments to this function are optional. + * Only if reqOptions is defined, then params must be provided (an object or null) + * So any of these are valid: * * FB.api('/me') // throw away the response * FB.api('/me', function(r) { console.log(r) }) * FB.api('/me', { fields: 'email' }); // throw away response + * FB.api('/123456789/picture', null, { content-type: 'image/jpg' }); * FB.api('/me', { fields: 'email' }, function(r) { console.log(r) }); * FB.api('/123456789', 'delete', function(r) { console.log(r) } ); * FB.api( @@ -162,6 +165,7 @@ , next = args.shift() , method , params + , reqOptions , cb; while(next) { @@ -170,8 +174,10 @@ method = next.toLowerCase(); } else if(type === 'function' && !cb) { cb = next; - } else if(type === 'object' && !params) { + } else if(type === 'object' && typeof params == 'undefined') { params = next; + } else if(type === 'object' && !reqOptions) { + reqOptions = next; } else { log('Invalid argument passed to FB.api(): ' + next); return; @@ -181,6 +187,16 @@ method = method || 'get'; params = params || {}; + + var defaultOptions = { + contentType: 'json' + }; + reqOptions = reqOptions || {}; + for(key in defaultOptions){ + if(typeof reqOptions[key] == 'undefined'){ + reqOptions[key] = defaultOptions[key]; + } + } // remove prefix slash if one is given, as it's already in the base url if(path[0] === '/') { @@ -192,7 +208,7 @@ return; } - oauthRequest('graph', path, method, params, cb); + oauthRequest('graph', path, method, params, reqOptions, cb); }; /** @@ -219,9 +235,10 @@ * @param path {String} the request path * @param method {String} the http method * @param params {Object} the parameters for the query + * @param reqOptions {Object} the options for the query * @param cb {Function} the callback function to handle the response */ - oauthRequest = function(domain, path, method, params, cb) { + oauthRequest = function(domain, path, method, params, reqOptions, cb) { var uri , body , key @@ -279,6 +296,7 @@ requestOptions = { method: method + , encoding: 'binary' , uri: uri , body: body }; @@ -301,19 +319,42 @@ response.headers && /.*text\/plain.*/.test(response.headers['content-type'])) { cb(parseOAuthApiResponse(body)); } else { - var json; - try { - json = JSON.parse(body); - } catch (ex) { - // sometimes FB is has API errors that return HTML and a message - // of "Sorry, something went wrong". These are infrequent and unpredictable but - // let's not let them blow up our application. - json = { error: { - code: 'JSONPARSE', - Error: ex - }}; - } - cb(json); + var ret; + // special treatment for (expected) json responses : decode them + if(reqOptions.contentType == 'json'){ + try { + ret = JSON.parse(body); + } catch (ex) { + // sometimes FB is has API errors that return HTML and a message + // of "Sorry, something went wrong". These are infrequent and unpredictable but + // let's not let them blow up our application. + ret = { error: { + code: 'JSONPARSE', + Error: ex + }}; + } + } + else { + // check that the content is of expected type(s) + if( reqOptions.contentType == 'any' + || ( response.headers + && ( + (typeof reqOptions.contentType == 'string' && reqOptions.contentType == response.headers['content-type']) + || (Array.isArray(reqOptions.contentType) && reqOptions.contentType.indexOf(response.headers['content-type']) !== -1) + ) + ) + ){ + ret = body; + } + else { + ret = { error: { + code: 'UNEXPECTED_CONTENT-TYPE', + expected: reqOptions.contentType, + received: response.headers['content-type'] + }}; + } + } + cb(ret); } }); }; From 3eb8cca9391066e3a66392d4f845a088c4629ae1 Mon Sep 17 00:00:00 2001 From: louisameline Date: Wed, 30 Apr 2014 16:48:23 +0200 Subject: [PATCH 2/4] Edited example --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a8fc8f7..be65ebe 100644 --- a/README.md +++ b/README.md @@ -67,14 +67,13 @@ var FB = require('fb'); // by default, the library expects a JSON response and will return an error otherwise. // a contentType option can be provided in the fourth parameter to prevent this. -// for example, set content type to 'image/jpeg', ['image/jpeg', 'image/gif'] or just 'any' -FB.api('4/picture', { redirect: 1 }, { contentType: 'any'}, function (res) { +// for example, set contentType to 'image/jpeg', ['image/jpeg', 'image/gif'] or just 'any' +FB.api('4/picture', { redirect: 1 }, { contentType: 'any' }, function (res) { if(!res || res.error) { console.log(!res ? 'error occurred' : res.error); return; } - console.log(res.id); - console.log(res.name); + console.log('Picture fetched. "Res" can be written in a file on disk.'); }); ``` From e839127513c1974cbf7b086fd1d669272c71b4c3 Mon Sep 17 00:00:00 2001 From: louisameline Date: Wed, 30 Apr 2014 16:54:04 +0200 Subject: [PATCH 3/4] indent --- fb.js | 94 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/fb.js b/fb.js index f955c79..a1337b3 100644 --- a/fb.js +++ b/fb.js @@ -142,8 +142,8 @@ * Make a api call to Graph server. * * Except the path, all arguments to this function are optional. - * Only if reqOptions is defined, then params must be provided (an object or null) - * So any of these are valid: + * Only if reqOptions is defined, then params must be provided (an object or null) + * So any of these are valid: * * FB.api('/me') // throw away the response * FB.api('/me', function(r) { console.log(r) }) @@ -187,16 +187,16 @@ method = method || 'get'; params = params || {}; - - var defaultOptions = { - contentType: 'json' - }; + + var defaultOptions = { + contentType: 'json' + }; reqOptions = reqOptions || {}; - for(key in defaultOptions){ - if(typeof reqOptions[key] == 'undefined'){ - reqOptions[key] = defaultOptions[key]; - } - } + for(key in defaultOptions){ + if(typeof reqOptions[key] == 'undefined'){ + reqOptions[key] = defaultOptions[key]; + } + } // remove prefix slash if one is given, as it's already in the base url if(path[0] === '/') { @@ -319,41 +319,41 @@ response.headers && /.*text\/plain.*/.test(response.headers['content-type'])) { cb(parseOAuthApiResponse(body)); } else { - var ret; - // special treatment for (expected) json responses : decode them - if(reqOptions.contentType == 'json'){ - try { - ret = JSON.parse(body); - } catch (ex) { - // sometimes FB is has API errors that return HTML and a message - // of "Sorry, something went wrong". These are infrequent and unpredictable but - // let's not let them blow up our application. - ret = { error: { - code: 'JSONPARSE', - Error: ex - }}; - } - } - else { - // check that the content is of expected type(s) - if( reqOptions.contentType == 'any' - || ( response.headers - && ( - (typeof reqOptions.contentType == 'string' && reqOptions.contentType == response.headers['content-type']) - || (Array.isArray(reqOptions.contentType) && reqOptions.contentType.indexOf(response.headers['content-type']) !== -1) - ) - ) - ){ - ret = body; - } - else { - ret = { error: { - code: 'UNEXPECTED_CONTENT-TYPE', - expected: reqOptions.contentType, - received: response.headers['content-type'] - }}; - } - } + var ret; + // special treatment for (expected) json responses : decode them + if(reqOptions.contentType == 'json'){ + try { + ret = JSON.parse(body); + } catch (ex) { + // sometimes FB is has API errors that return HTML and a message + // of "Sorry, something went wrong". These are infrequent and unpredictable but + // let's not let them blow up our application. + ret = { error: { + code: 'JSONPARSE', + Error: ex + }}; + } + } + else { + // check that the content is of expected type(s) + if( reqOptions.contentType == 'any' + || ( response.headers + && ( + (typeof reqOptions.contentType == 'string' && reqOptions.contentType == response.headers['content-type']) + || (Array.isArray(reqOptions.contentType) && reqOptions.contentType.indexOf(response.headers['content-type']) !== -1) + ) + ) + ){ + ret = body; + } + else { + ret = { error: { + code: 'UNEXPECTED_CONTENT-TYPE', + expected: reqOptions.contentType, + received: response.headers['content-type'] + }}; + } + } cb(ret); } }); @@ -646,7 +646,7 @@ if(options('proxy')) { requestOptions['proxy'] = options('proxy'); } - + request( requestOptions , function(error, response, body) { From 145b58db529d33a3599c2230fbf8df77dedaa26f Mon Sep 17 00:00:00 2001 From: louisameline Date: Wed, 30 Apr 2014 17:12:05 +0200 Subject: [PATCH 4/4] Updated the doc and the default content-type to a more proper 'application/json'. --- README.md | 9 ++++++--- fb.js | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be65ebe..26817c3 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,12 @@ __Expecting a non-JSON response__ ```js var FB = require('fb'); -// by default, the library expects a JSON response and will return an error otherwise. -// a contentType option can be provided in the fourth parameter to prevent this. -// for example, set contentType to 'image/jpeg', ['image/jpeg', 'image/gif'] or just 'any' +/* By default, the library expects a JSON response and will try to parse +* it before it is returned to you. An error is returned if this cannot be performed. +* A contentType option (which defaults to 'application/json') can be provided +* through the fourth parameter to prevent this. For example, set contentType +* to 'image/jpeg', ['image/jpeg', 'image/gif'] or just 'any'. +*/ FB.api('4/picture', { redirect: 1 }, { contentType: 'any' }, function (res) { if(!res || res.error) { console.log(!res ? 'error occurred' : res.error); diff --git a/fb.js b/fb.js index a1337b3..390fbc0 100644 --- a/fb.js +++ b/fb.js @@ -189,7 +189,7 @@ params = params || {}; var defaultOptions = { - contentType: 'json' + contentType: 'application/json' }; reqOptions = reqOptions || {}; for(key in defaultOptions){ @@ -321,7 +321,7 @@ } else { var ret; // special treatment for (expected) json responses : decode them - if(reqOptions.contentType == 'json'){ + if(reqOptions.contentType == 'application/json'){ try { ret = JSON.parse(body); } catch (ex) {