Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This library is an isomorphic client for the [WordPress REST API](http://develop
- [Embedding Data](#embedding-data)
- [Collection Pagination](#collection-pagination)
- [Customizing HTTP Request Behavior](#customizing-http-request-behavior)
- [Specifying HTTP Headers](#specifying-http-headers)
- [Authentication](#authentication)
- [API Documentation](#api-documentation)
- [Issues](#issues)
Expand Down Expand Up @@ -809,6 +810,35 @@ site.transport({
```

Note that these transport methods are the internal methods used by `create` and `.update`, so the names of these methods therefore map to the HTTP verbs "get", "post", "put", "head" and "delete"; name your transport methods accordingly or they will not be used.
### Specifying HTTP Headers

If you need to send additional HTTP headers along with your request (for example to provide a specific `Authorization` header for use with alternative authentication schemes), you can use the `.setHeaders()` method to specify one or more headers to send with the dispatched request:

#### Set headers for a single request

```js
// Specify a single header to send with the outgoing request
wp.posts().setHeaders( 'Authorization', 'Bearer xxxxx.yyyyy.zzzzz' )...

// Specify multiple headers to send with the outgoing request
wp.posts().setHeaders({
Authorization: 'Bearer xxxxx.yyyyy.zzzzz',
'Accept-Language': 'pt-BR'
})...
```

#### Set headers globally

You can also set headers globally on the WPAPI instance itself, which will then be used for all subsequent requests created from that site instance:

```js
// Specify a header to be used by all subsequent requests
wp.setHeaders( 'Authorization', 'Bearer xxxxx.yyyyy.zzzzz' );

// These will now be sent with an Authorization header
wp.users().me()...
wp.posts().id( unpublishedPostId )...
```

## Authentication

Expand Down
34 changes: 34 additions & 0 deletions lib/constructors/wp-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function WPRequest( options ) {
// Whitelisted options keys
'auth',
'endpoint',
'headers',
'username',
'password',
'nonce'
Expand Down Expand Up @@ -652,6 +653,39 @@ WPRequest.prototype.file = function( file, name ) {
// HTTP Methods: Public Interface
// ==============================

/**
* Specify one or more headers to send with the dispatched HTTP request.
*
* @example Set a single header to be used on this request
*
* request.setHeaders( 'Authorization', 'Bearer trustme' )...
*
* @example Set multiple headers to be used by this request
*
* request.setHeaders({
* Authorization: 'Bearer comeonwereoldfriendsright',
* 'Accept-Language': 'en-CA'
* })...
*
* @method setHeaders
* @chainable
* @param {String|Object} headers The name of the header to set, or an object of
* header names and their associated string values
* @param {String} [value] The value of the header being set
* @return {WPRequest} The WPRequest instance (for chaining)
*/
WPRequest.prototype.setHeaders = function( headers, value ) {
// We can use the same iterator function below to handle explicit key-value
// pairs if we convert them into to an object we can iterate over:
if ( typeof headers === 'string' ) {
headers = keyValToObj( headers, value );
}

this._options.headers = Object.assign( {}, this._options.headers || {}, headers );

return this;
};

/**
* Get (download the data for) the specified resource
*
Expand Down
27 changes: 26 additions & 1 deletion lib/http-transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,27 @@ var objectReduce = require( './util/object-reduce' );
var isEmptyObject = require( './util/is-empty-object' );

/**
* Conditionally set basic authentication on a server request object
* Set any provided headers on the outgoing request object. Runs after _auth.
*
* @method _setHeaders
* @private
* @param {Object} request A superagent request object
* @param {Object} options A WPRequest _options object
* @param {Object} A superagent request object, with any available headers set
*/
function _setHeaders( request, options ) {
// If there's no headers, do nothing
if ( ! options.headers ) {
return request;
}

return objectReduce( options.headers, function( request, value, key ) {
return request.set( key, value );
}, request );
}

/**
* Conditionally set basic authentication on a server request object.
*
* @method _auth
* @private
Expand Down Expand Up @@ -265,6 +285,7 @@ function _httpGet( wpreq, callback ) {
var url = wpreq.toString();

var request = _auth( agent.get( url ), wpreq._options );
request = _setHeaders( request, wpreq._options );

return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
}
Expand All @@ -284,6 +305,7 @@ function _httpPost( wpreq, data, callback ) {
var url = wpreq.toString();
data = data || {};
var request = _auth( agent.post( url ), wpreq._options, true );
request = _setHeaders( request, wpreq._options );

if ( wpreq._attachment ) {
// Data must be form-encoded alongside image attachment
Expand Down Expand Up @@ -312,6 +334,7 @@ function _httpPut( wpreq, data, callback ) {
data = data || {};

var request = _auth( agent.put( url ), wpreq._options, true ).send( data );
request = _setHeaders( request, wpreq._options );

return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
}
Expand All @@ -333,6 +356,7 @@ function _httpDelete( wpreq, data, callback ) {
checkMethodSupport( 'delete', wpreq );
var url = wpreq.toString();
var request = _auth( agent.del( url ), wpreq._options, true ).send( data );
request = _setHeaders( request, wpreq._options );

return invokeAndPromisify( request, callback, returnBody.bind( null, wpreq ) );
}
Expand All @@ -349,6 +373,7 @@ function _httpHead( wpreq, callback ) {
checkMethodSupport( 'head', wpreq );
var url = wpreq.toString();
var request = _auth( agent.head( url ), wpreq._options );
request = _setHeaders( request, wpreq._options );

return invokeAndPromisify( request, callback, returnHeaders );
}
Expand Down
65 changes: 65 additions & 0 deletions tests/integration/custom-http-headers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use strict';
var chai = require( 'chai' );
// Variable to use as our "success token" in promise assertions
var SUCCESS = 'success';
// Chai-as-promised and the `expect( prom ).to.eventually.equal( SUCCESS ) is
// used to ensure that the assertions running within the promise chains are
// actually run.
chai.use( require( 'chai-as-promised' ) );
var expect = chai.expect;

var WPAPI = require( '../../' );

// Inspecting the titles of the returned posts arrays is an easy way to
// validate that the right page of results was returned
var getTitles = require( './helpers/get-rendered-prop' ).bind( null, 'title' );
var base64credentials = new Buffer( 'apiuser:password' ).toString( 'base64' );

describe( 'integration: custom HTTP Headers', function() {
var wp;

beforeEach(function() {
wp = new WPAPI({
endpoint: 'http://wpapi.loc/wp-json'
});
});

// Testing basic authentication is an acceptable proxy for whether a header
// value (Authentication:, in this case) is being set
it( 'can be provided using WPRequest#setHeaders()', function() {
var prom = wp.posts()
.setHeaders( 'Authorization', 'Basic ' + base64credentials )
.status([ 'future', 'draft' ])
.get()
.then(function( posts ) {
expect( getTitles( posts ) ).to.deep.equal([
'Scheduled',
'Draft'
]);
return SUCCESS;
});
return expect( prom ).to.eventually.equal( SUCCESS );
});

it( 'can be provided at the WPAPI instance level using WPAPI#setHeaders()', function() {
var authenticated = WPAPI
.site( 'http://wpapi.loc/wp-json' )
.setHeaders( 'Authorization', 'Basic ' + base64credentials );
var prom = authenticated.posts()
.status([ 'future', 'draft' ])
.get()
.then(function( posts ) {
expect( getTitles( posts ) ).to.deep.equal([
'Scheduled',
'Draft'
]);
return authenticated.users().me();
})
.then(function( me ) {
expect( me.slug ).to.equal( 'apiuser' );
return SUCCESS;
});
return expect( prom ).to.eventually.equal( SUCCESS );
});

});
79 changes: 79 additions & 0 deletions tests/unit/lib/constructors/wp-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,85 @@ describe( 'WPRequest', function() {

});

describe( '.setHeaders()', function() {

it( 'method exists', function() {
expect( request ).to.have.property( 'setHeaders' );
expect( request.setHeaders ).to.be.a( 'function' );
});

it( 'will have no effect if called without any arguments', function() {
request.setHeaders();
expect( request._options.headers ).to.deep.equal({});
});

it( 'will set a header key/value pair', function() {
request.setHeaders( 'Authorization', 'Bearer sometoken' );
expect( request._options.headers ).to.deep.equal({
Authorization: 'Bearer sometoken'
});
});

it( 'will replace an existing header key/value pair', function() {
request
.setHeaders( 'Authorization', 'Bearer sometoken' )
.setHeaders( 'Authorization', 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' );
expect( request._options.headers ).to.deep.equal({
Authorization: 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
});
});

it( 'will set multiple header key/value pairs with chained calls', function() {
request
.setHeaders( 'Accept-Language', 'en-US' )
.setHeaders( 'Authorization', 'Bearer sometoken' );
expect( request._options.headers ).to.deep.equal({
'Accept-Language': 'en-US',
Authorization: 'Bearer sometoken'
});
});

it( 'will set multiple header key/value pairs when passed an object', function() {
request.setHeaders({
'Accept-Language': 'en-US',
Authorization: 'Bearer sometoken'
});
expect( request._options.headers ).to.deep.equal({
'Accept-Language': 'en-US',
Authorization: 'Bearer sometoken'
});
});

it( 'will replace multiple existing header key/value pairs when passed an object', function() {
request
.setHeaders({
'Accept-Language': 'en-US',
Authorization: 'Bearer sometoken'
})
.setHeaders({
'Accept-Language': 'pt-BR',
Authorization: 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
});
expect( request._options.headers ).to.deep.equal({
'Accept-Language': 'pt-BR',
Authorization: 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
});
});

it( 'inherits headers from the constructor options object', function() {
request = new WPRequest({
endpoint: '/',
headers: {
'Accept-Language': 'pt-BR'
}
});
expect( request._options.headers ).to.deep.equal({
'Accept-Language': 'pt-BR'
});
});

});

describe( '.toString()', function() {

beforeEach(function() {
Expand Down
55 changes: 55 additions & 0 deletions tests/unit/wpapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,61 @@ describe( 'WPAPI', function() {

});

describe( '.setHeaders()', function() {

beforeEach(function() {
site = new WPAPI({ endpoint: 'http://my.site.com/wp-json' });
});

it( 'is defined', function() {
expect( site ).to.have.property( 'setHeaders' );
expect( site.setHeaders ).to.be.a( 'function' );
});

it( 'initializes site-wide headers object if called with no arguments', function() {
expect( site._options ).not.to.have.property( 'headers' );
site.setHeaders();
expect( site._options ).to.have.property( 'headers' );
expect( site._options.headers ).to.deep.equal({});
});

it( 'sets site-wide headers when provided a name-value pair', function() {
site.setHeaders( 'Accept-Language', 'en-US' );
expect( site._options ).to.have.property( 'headers' );
expect( site._options.headers ).to.deep.equal({
'Accept-Language': 'en-US'
});
});

it( 'sets site-wide headers when provided an object of header name-value pairs', function() {
site.setHeaders({
'Accept-Language': 'en-CA',
Authorization: 'Bearer sometoken'
});
expect( site._options ).to.have.property( 'headers' );
expect( site._options.headers ).to.deep.equal({
'Accept-Language': 'en-CA',
Authorization: 'Bearer sometoken'
});
});

it( 'passes headers to all subsequently-instantiated handlers', function() {
site.setHeaders({
'Accept-Language': 'en-IL',
Authorization: 'Bearer chicagostylepizza'
});
var req = site.root( '' );
expect( req ).to.have.property( '_options' );
expect( req._options ).to.be.an( 'object' );
expect( req._options ).to.have.property( 'headers' );
expect( req._options.headers ).to.deep.equal({
'Accept-Language': 'en-IL',
Authorization: 'Bearer chicagostylepizza'
});
});

});

describe( '.registerRoute()', function() {

it( 'is a function', function() {
Expand Down
Loading