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
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This is a client for the [WordPress REST API](http://v2.wp-api.org/). It is **un
- [Custom Routes](#custom-routes)
- [Embedding Data](#embedding-data)
- [Paginated Collections](#working-with-paged-response-data)
- [Customizing HTTP Request Behavior](#customizing-http-request-behavior)
- [Authentication](#authentication)
- [API Documentation](#api-documentation)
- [Issues](#issues)
Expand Down Expand Up @@ -539,6 +540,58 @@ You can also use a `.page(pagenumber)` method on calls that support pagination t
wp.posts().perPage( 5 ).page( 3 ).then(/* ... */);
```

## Customizing HTTP Request Behavior

By default `node-wpapi` uses the [superagent](https://www.npmjs.com/package/superagent) library internally to make HTTP requests against the API endpoints. Superagent is a flexible tool that works on both the client and the browser, but you may want to use a different HTTP library, or to get data from a cache when available instead of making an HTTP request. To facilitate this, `node-wpapi` lets you supply a `transport` object when instantiating a site client to specify custom functions to use for one (or all) of GET, POST, PUT, DELETE & HEAD requests.

**This is advanced behavior; you will only need to utilize this functionality if your application has very specific HTTP handling or caching requirements.**

In order to maintain consistency with the rest of the API, custom transport methods should take in a WordPress API route handler query object (_e.g._ the result of calling `wp.posts()...` or any of the other chaining resource handlers), a `data` object (for POST, PUT and DELETE requests), and an optional callback function (as `node-wpapi` transport methods both return Promise objects _and_ support traditional `function( err, response )` callbacks).

The default HTTP transport methods are available as `WP.transport` (a property of the constructor object) and may be called within your transports if you wish to extend the existing behavior, as in the example below.

**Example:** Cache requests in a simple dictionary object, keyed by request URI. If a request's response is already available, serve from the cache; if not, use the default GET transport method to retrieve the data, save it in the cache, and return it to the consumer:

```js
var site = new WP({
endpoint: 'http://my-site.com/wp-json',
transport: {
// Only override the transport for the GET method, in this example
// Transport methods should take a wpquery object and a callback:
get: function( wpquery, cb ) {
var result = cache[ wpquery ];
// If a cache hit is found, return it via the same callback/promise
// signature as the default transport method:
if ( result ) {
if ( cb && typeof cb === 'function' ) {
// Invoke the callback function, if one was provided
cb( null, result );
}
// Return the data as a promise
return Promise.resolve( result );
}

// Delegate to default transport if no cached data was found
return WP.transport.get( wpquery, cb ).then(function( result ) {
cache[ wpquery ] = result;
return result;
});
}
}
});
```

You may set one or many custom HTTP transport methods on an existing WP site client instance (for example one returned through [Auto-discovery](#auto-discovery) by calling the `.transport()` method on the site client instance and passing an object of handler functions:

```js
site.transport({
get: function( wpquery, callbackFn ) { /* ... */},
put: function( wpquery, callbackFn ) { /* ... */}
});
```

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.

## Authentication

You must be authenticated with WordPress to create, edit or delete resources via the API. Some WP-API endpoints additionally require authentication for GET requests in cases where the data being requested could be considered private: examples include any of the `/users` endpoints, requests where the `context` query parameter is `true`, and `/revisions` for posts and pages, among others.
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/wp.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,50 @@ describe( 'wp', function() {

});

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

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

it( 'is a function', function() {
expect( site.transport ).to.be.a( 'function' );
});

it( 'is chainable', function() {
expect( site.transport() ).to.equal( site );
});

it( 'sets transport methods on the instance', function() {
sinon.stub( httpTransport, 'get' );
var customGet = sinon.stub();
site.transport({
get: customGet
});
function cb() {}
var query = site.root( '' );
query.get( cb );
expect( httpTransport.get ).not.to.have.been.called;
expect( customGet ).to.have.been.calledWith( query, cb );
httpTransport.get.restore();
});

it( 'does not impact or overwrite unspecified transport methods', function() {
var originalMethods = Object.assign( {}, site._options.transport );
site.transport({
get: function() {},
put: function() {}
});
var newMethods = Object.assign( {}, site._options.transport );
expect( newMethods.delete ).to.equal( originalMethods.delete );
expect( newMethods.post ).to.equal( originalMethods.post );

expect( newMethods.get ).not.to.equal( originalMethods.get );
expect( newMethods.put ).not.to.equal( originalMethods.put );
});

});

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

it( 'is defined', function() {
Expand Down
95 changes: 72 additions & 23 deletions wp.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,41 +84,90 @@ function WP( options ) {
// Ensure trailing slash on endpoint URI
this._options.endpoint = this._options.endpoint.replace( /\/?$/, '/' );

// Create the HTTP transport object
this._options.transport = Object.assign( {}, httpTransport, options.transport );

return this.bootstrap( options && options.routes );
return this
// Configure custom HTTP transport methods, if provided
.transport( options.transport )
// Bootstrap with a specific routes object, if provided
.bootstrap( options && options.routes );
}

/**
* Default HTTP transport methods object that can be extended to define custom
* HTTP transport behavior for a WP instance
* Set custom transport methods to use when making HTTP requests against the API
*
* Pass an object with a function for one or many of "get", "post", "put",
* "delete" and "head" and that function will be called when making that type
* of request. The provided transport functions should take a WP request handler
* instance (_e.g._ the result of a `wp.posts()...` chain or any other chaining
* request handler) as their first argument; a `data` object as their second
* argument (for POST, PUT and DELETE requests); and an optional callback as
* their final argument. Transport methods should invoke the callback with the
* response data (or error, as appropriate), and should also return a Promise.
*
* @example showing how a cache hit (keyed by URI) could short-circuit a get request
*
* var site = new WP({
* endpoint: 'http://my-site.com/wp-json',
* transport: {
* get: function( wpquery, cb ) {
* var result = cache[ wpquery ];
* // If a cache hit is found, return it via the same callback/promise
* // signature as the default transport method
* if ( result ) {
* if ( cb && typeof cb === 'function' ) {
* cb( null, result );
* }
* return Promise.resolve( result );
* endpoint: 'http://my-site.com/wp-json'
* });
*
* // Overwrite the GET behavior to inject a caching layer
* site.transport({
* get: function( wpquery, cb ) {
* var result = cache[ wpquery ];
* // If a cache hit is found, return it via the same callback/promise
* // signature as the default transport method
* if ( result ) {
* if ( cb && typeof cb === 'function' ) {
* cb( null, result );
* }

* // Delegate to default transport if no cached data was found
* return WP.transport.get( wpquery, cb ).then(function( result ) {
* cache[ wpquery ] = result;
* return result;
* });
* return Promise.resolve( result );
* }
*
* // Delegate to default transport if no cached data was found
* return WP.transport.get( wpquery, cb ).then(function( result ) {
* cache[ wpquery ] = result;
* return result;
* });
* }
* });
*
* This is advanced behavior; you will only need to utilize this functionality
* if your application has very specific HTTP handling or caching requirements.
* Refer to the "http-transport" module within this application for the code
* implementing the built-in transport methods.
*
* @chainable
* @param {Object} transport A dictionary of HTTP transport methods
* @param {Function} [transport.get] The function to use for GET requests
* @param {Function} [transport.post] The function to use for POST requests
* @param {Function} [transport.put] The function to use for PUT requests
* @param {Function} [transport.delete] The function to use for DELETE requests
* @param {Function} [transport.head] The function to use for HEAD requests
* @returns {WP} The WP instance, for chaining
*/
WP.prototype.transport = function( transport ) {
// Local reference to avoid need to reference via `this` inside forEach
var _options = this._options;

// Create the default transport if it does not exist
if ( ! _options.transport ) {
_options.transport = Object.create( WP.transport );
}

// Whitelist the methods that may be applied
[ 'get', 'head', 'post', 'put', 'delete' ].forEach(function( key ) {
if ( transport && transport[ key ] ) {
_options.transport[ key ] = transport[ key ];
}
});

return this;
};

/**
* Default HTTP transport methods object for all WP instances
*
* These methods may be extended or replaced on an instance-by-instance basis
*
* @static
* @property transport
* @type {Object}
Expand Down