├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .npmrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.d.ts ├── index.mjs ├── jest.config.js ├── package.json ├── renovate.json └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "8" 8 | } 9 | } 10 | ] 11 | ], 12 | "plugins": [ 13 | "@babel/plugin-transform-modules-commonjs" 14 | ] 15 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{json,yml}] 15 | insert_final_newline = false 16 | 17 | [.babelrc] 18 | insert_final_newline = false 19 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true 4 | }, 5 | "extends": [ 6 | "standard", 7 | "prettier", 8 | "plugin:jest/recommended" 9 | ], 10 | "globals": { 11 | "Atomics": "readonly", 12 | "SharedArrayBuffer": "readonly" 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2018, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "indent": [ 20 | 2, 21 | 2 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | run.* 4 | index.js 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "9" 5 | - "10" 6 | before_script: npm run build 7 | script: npm test 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.0.1] - 2019-08-02 8 | ### Removed 9 | - Removed `verifySSL` option, since Axios doesn't provide any option to support this feature. 10 | 11 | ## [1.0.0] - 2019-07-29 12 | ### Added 13 | - First stable version. 14 | 15 | ## [0.0.1] - 2019-07-29 16 | ### Added 17 | - Initial version. 18 | 19 | [Unreleased]: https://github.com/woocommerce/woocommerce-rest-api-js-lib/compare/v1.0.1...HEAD 20 | [1.0.1]: https://github.com/woocommerce/woocommerce-rest-api-js-lib/compare/v1.0.0...v1.0.1 21 | [1.0.0]: https://github.com/woocommerce/woocommerce-rest-api-js-lib/compare/v0.0.1...v1.0.0 22 | [0.0.1]: https://github.com/woocommerce/woocommerce-rest-api-js-lib/releases/tag/v0.0.1 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 WooCommerce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WooCommerce REST API - JavaScript Library 2 | 3 | New JavaScript library for WooCommerce REST API, supports CommonJS (CJS) and Embedded System Module (ESM). 4 | 5 | Requests are made with [Axios library](https://github.com/axios/axios) with [support to promises](https://github.com/axios/axios#promises). 6 | 7 | [![build status](https://secure.travis-ci.org/woocommerce/woocommerce-rest-api-js-lib.svg)](http://travis-ci.org/woocommerce/woocommerce-rest-api-js-lib) 8 | [![dependency status](https://david-dm.org/woocommerce/woocommerce-rest-api-js-lib.svg)](https://david-dm.org/woocommerce/woocommerce-rest-api-js-lib) 9 | [![npm version](https://img.shields.io/npm/v/@woocommerce/woocommerce-rest-api.svg)](https://www.npmjs.com/package/@woocommerce/woocommerce-rest-api) 10 | 11 | ## Installation 12 | 13 | ``` 14 | npm install --save @woocommerce/woocommerce-rest-api 15 | ``` 16 | 17 | ## Getting started 18 | 19 | Generate API credentials (Consumer Key & Consumer Secret) following this instructions 20 | . 21 | 22 | Check out the WooCommerce API endpoints and data that can be manipulated in . 23 | 24 | ## Setup 25 | 26 | ### ESM example: 27 | 28 | ```js 29 | import WooCommerceRestApi from "@woocommerce/woocommerce-rest-api"; 30 | 31 | const api = new WooCommerceRestApi({ 32 | url: "http://example.com", 33 | consumerKey: "ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 34 | consumerSecret: "cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 35 | version: "wc/v3" 36 | }); 37 | ``` 38 | 39 | ### CJS example: 40 | 41 | ```js 42 | const WooCommerceRestApi = require("@woocommerce/woocommerce-rest-api").default; 43 | 44 | const api = new WooCommerceRestApi({ 45 | url: "http://example.com", 46 | consumerKey: "ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 47 | consumerSecret: "cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 48 | version: "wc/v3" 49 | }); 50 | ``` 51 | 52 | ### Options 53 | 54 | | Option | Type | Required | Description | 55 | |-------------------|-----------|----------|---------------------------------------------------------------------------------------------------------------------| 56 | | `url` | `String` | yes | Your Store URL, example: http://woo.dev/ | 57 | | `consumerKey` | `String` | yes | Your API consumer key | 58 | | `consumerSecret` | `String` | yes | Your API consumer secret | 59 | | `wpAPIPrefix` | `String` | no | Custom WP REST API URL prefix, used to support custom prefixes created with the `rest_url_prefix` filter | 60 | | `version` | `String` | no | API version, default is `v3` | 61 | | `encoding` | `String` | no | Encoding, default is 'utf-8' | 62 | | `queryStringAuth` | `Bool` | no | When `true` and using under HTTPS force Basic Authentication as query string, default is `false` | 63 | | `port` | `string` | no | Provide support for URLs with ports, eg: `8080` | 64 | | `timeout` | `Integer` | no | Define the request timeout | 65 | | `axiosConfig` | `Object` | no | Define the custom [Axios config](https://github.com/axios/axios#request-config), also override this library options | 66 | 67 | ## Methods 68 | 69 | ### GET 70 | 71 | - `.get(endpoint)` 72 | - `.get(endpoint, params)` 73 | 74 | | Params | Type | Description | 75 | |------------|----------|---------------------------------------------------------------| 76 | | `endpoint` | `String` | WooCommerce API endpoint, example: `customers` or `orders/12` | 77 | | `params` | `Object` | Query strings params, example: `{ per_page: 20 }` | 78 | 79 | ### POST 80 | 81 | - `.post(endpoint, data)` 82 | - `.post(endpoint, data, params)` 83 | 84 | | Params | Type | Description | 85 | |------------|----------|-------------------------------------------------------------| 86 | | `endpoint` | `String` | WooCommerce API endpoint, example: `customers` or `orders` | 87 | | `data` | `Object` | JS object to be converted into JSON and sent in the request | 88 | | `params` | `Object` | Query strings params | 89 | 90 | ### PUT 91 | 92 | - `.put(endpoint, data)` 93 | - `.put(endpoint, data, params)` 94 | 95 | | Params | Type | Description | 96 | |------------|----------|-------------------------------------------------------------------| 97 | | `endpoint` | `String` | WooCommerce API endpoint, example: `customers/1` or `orders/1234` | 98 | | `data` | `Object` | JS object to be converted into JSON and sent in the request | 99 | | `params` | `Object` | Query strings params | 100 | 101 | ### DELETE 102 | 103 | - `.delete(endpoint)` 104 | - `.delete(endpoint, params)` 105 | 106 | | Params | Type | Description | 107 | |------------|----------|-----------------------------------------------------------------| 108 | | `endpoint` | `String` | WooCommerce API endpoint, example: `customers/2` or `orders/12` | 109 | | `params` | `Object` | Query strings params, example: `{ force: true }` | 110 | 111 | ### OPTIONS 112 | 113 | - `.options(endpoint)` 114 | - `.options(endpoint, params)` 115 | 116 | | Params | Type | Description | 117 | |------------|----------|-----------------------------------------------------------------| 118 | | `endpoint` | `String` | WooCommerce API endpoint, example: `customers/2` or `orders/12` | 119 | | `params` | `Object` | Query strings params | 120 | 121 | ## Example of use 122 | 123 | ```js 124 | // import WooCommerceRestApi from "@woocommerce/woocommerce-rest-api"; 125 | const WooCommerceRestApi = require("@woocommerce/woocommerce-rest-api").default; 126 | 127 | const api = new WooCommerceRestApi({ 128 | url: "http://example.com", 129 | consumerKey: "ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 130 | consumerSecret: "cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 131 | version: "wc/v3" 132 | }); 133 | 134 | // List products 135 | api.get("products", { 136 | per_page: 20, // 20 products per page 137 | }) 138 | .then((response) => { 139 | // Successful request 140 | console.log("Response Status:", response.status); 141 | console.log("Response Headers:", response.headers); 142 | console.log("Response Data:", response.data); 143 | console.log("Total of pages:", response.headers['x-wp-totalpages']); 144 | console.log("Total of items:", response.headers['x-wp-total']); 145 | }) 146 | .catch((error) => { 147 | // Invalid request, for 4xx and 5xx statuses 148 | console.log("Response Status:", error.response.status); 149 | console.log("Response Headers:", error.response.headers); 150 | console.log("Response Data:", error.response.data); 151 | }) 152 | .finally(() => { 153 | // Always executed. 154 | }); 155 | 156 | // Create a product 157 | api.post("products", { 158 | name: "Premium Quality", // See more in https://woocommerce.github.io/woocommerce-rest-api-docs/#product-properties 159 | type: "simple", 160 | regular_price: "21.99", 161 | }) 162 | .then((response) => { 163 | // Successful request 164 | console.log("Response Status:", response.status); 165 | console.log("Response Headers:", response.headers); 166 | console.log("Response Data:", response.data); 167 | }) 168 | .catch((error) => { 169 | // Invalid request, for 4xx and 5xx statuses 170 | console.log("Response Status:", error.response.status); 171 | console.log("Response Headers:", error.response.headers); 172 | console.log("Response Data:", error.response.data); 173 | }) 174 | .finally(() => { 175 | // Always executed. 176 | }); 177 | 178 | // Edit a product 179 | api.put("products/1", { 180 | sale_price: "11.99", // See more in https://woocommerce.github.io/woocommerce-rest-api-docs/#product-properties 181 | }) 182 | .then((response) => { 183 | // Successful request 184 | console.log("Response Status:", response.status); 185 | console.log("Response Headers:", response.headers); 186 | console.log("Response Data:", response.data); 187 | }) 188 | .catch((error) => { 189 | // Invalid request, for 4xx and 5xx statuses 190 | console.log("Response Status:", error.response.status); 191 | console.log("Response Headers:", error.response.headers); 192 | console.log("Response Data:", error.response.data); 193 | }) 194 | .finally(() => { 195 | // Always executed. 196 | }); 197 | 198 | // Delete a product 199 | api.delete("products/1", { 200 | force: true, // Forces to delete instead of move to the Trash 201 | }) 202 | .then((response) => { 203 | // Successful request 204 | console.log("Response Status:", response.status); 205 | console.log("Response Headers:", response.headers); 206 | console.log("Response Data:", response.data); 207 | }) 208 | .catch((error) => { 209 | // Invalid request, for 4xx and 5xx statuses 210 | console.log("Response Status:", error.response.status); 211 | console.log("Response Headers:", error.response.headers); 212 | console.log("Response Data:", error.response.data); 213 | }) 214 | .finally(() => { 215 | // Always executed. 216 | }); 217 | ``` 218 | 219 | ## Changelog 220 | 221 | [See changelog for details](https://github.com/woocommerce/woocommerce-rest-api-js-lib/blob/master/CHANGELOG.md) 222 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as OAuth from 'oauth-1.0a' 2 | 3 | export declare type WooCommerceRestApiVersion = 4 | | 'wc/v3' 5 | | 'wc/v2' 6 | | 'wc/v1' 7 | | 'wc-api/v3' 8 | | 'wc-api/v2' 9 | | 'wc-api/v1' 10 | export declare type WooCommerceRestApiEncoding = 'utf-8' | 'ascii' 11 | export declare type WooCommerceRestApiMethod = 12 | | 'get' 13 | | 'post' 14 | | 'put' 15 | | 'delete' 16 | | 'options' 17 | 18 | export interface IWooCommerceRestApiOptions { 19 | /* Your Store URL, example: http://woo.dev/ */ 20 | url: string 21 | /* Your API consumer key */ 22 | consumerKey: string 23 | /* Your API consumer secret */ 24 | consumerSecret: string 25 | /* Custom WP REST API URL prefix, used to support custom prefixes created with the `rest_url_prefix filter` */ 26 | wpAPIPrefix?: string 27 | /* API version, default is `v3` */ 28 | version?: WooCommerceRestApiVersion 29 | /* Encoding, default is 'utf-8' */ 30 | encoding?: WooCommerceRestApiEncoding 31 | /* When `true` and using under HTTPS force Basic Authentication as query string, default is `false` */ 32 | queryStringAuth?: boolean 33 | /* Provide support for URLs with ports, eg: `8080` */ 34 | port?: number 35 | /* Define the request timeout */ 36 | timeout?: number 37 | /* Define the custom Axios config, also override this library options */ 38 | axiosConfig?: any 39 | } 40 | 41 | export interface IWooCommerceRestApiQuery { 42 | [key: string]: string 43 | } 44 | 45 | /** 46 | * WooCommerce REST API wrapper 47 | * 48 | * @param {Object} opt 49 | */ 50 | export default class WooCommerceRestApi { 51 | protected classVersion: string 52 | protected url: string 53 | protected consumerKey: string 54 | protected consumerSecret: string 55 | protected wpAPIPrefix: string 56 | protected version: WooCommerceRestApiVersion 57 | protected encoding: WooCommerceRestApiEncoding 58 | protected queryStringAuth: boolean 59 | protected port: number 60 | protected timeout: number 61 | protected axiosConfig: any 62 | 63 | /** 64 | * Class constructor. 65 | * 66 | * @param {Object} opt 67 | */ 68 | constructor(opt: IWooCommerceRestApiOptions | WooCommerceRestApi) 69 | 70 | /** 71 | * Set default options 72 | * 73 | * @param {Object} opt 74 | */ 75 | private _setDefaultsOptions(opt: IWooCommerceRestApiOptions): void 76 | 77 | /** 78 | * Parse params object. 79 | * 80 | * @param {Object} params 81 | * @param {Object} query 82 | */ 83 | private _parseParamsObject(params: any, query: any): IWooCommerceRestApiQuery 84 | 85 | /** 86 | * Normalize query string for oAuth 87 | * 88 | * @param {String} url 89 | * @param {Object} params 90 | * 91 | * @return {String} 92 | */ 93 | private _normalizeQueryString(url: string, params: any): string 94 | 95 | /** 96 | * Get URL 97 | * 98 | * @param {String} endpoint 99 | * @param {Object} params 100 | * 101 | * @return {String} 102 | */ 103 | private _getUrl(endpoint: string, params: any): string 104 | 105 | /** 106 | * Get OAuth 107 | * 108 | * @return {Object} 109 | */ 110 | private _getOAuth(): OAuth 111 | 112 | /** 113 | * Do requests 114 | * 115 | * @param {String} method 116 | * @param {String} endpoint 117 | * @param {Object} data 118 | * @param {Object} params 119 | * 120 | * @return {Object} 121 | */ 122 | private _request( 123 | method: WooCommerceRestApiMethod, 124 | endpoint: string, 125 | data: any, 126 | params: any 127 | ): Promise 128 | 129 | /** 130 | * GET requests 131 | * 132 | * @param {String} endpoint 133 | * @param {Object} params 134 | * 135 | * @return {Object} 136 | */ 137 | public get(endpoint: string): Promise 138 | public get(endpoint: string, params: any): Promise 139 | 140 | /** 141 | * POST requests 142 | * 143 | * @param {String} endpoint 144 | * @param {Object} data 145 | * @param {Object} params 146 | * 147 | * @return {Object} 148 | */ 149 | public post(endpoint: string, data: any): Promise 150 | public post(endpoint: string, data: any, params: any): Promise 151 | 152 | /** 153 | * PUT requests 154 | * 155 | * @param {String} endpoint 156 | * @param {Object} data 157 | * @param {Object} params 158 | * 159 | * @return {Object} 160 | */ 161 | public put(endpoint: string, data: any): Promise 162 | public put(endpoint: string, data: any, params: any): Promise 163 | 164 | /** 165 | * DELETE requests 166 | * 167 | * @param {String} endpoint 168 | * @param {Object} params 169 | * @param {Object} params 170 | * 171 | * @return {Object} 172 | */ 173 | public delete(endpoint: string): Promise 174 | public delete(endpoint: string, params: any): Promise 175 | 176 | /** 177 | * OPTIONS requests 178 | * 179 | * @param {String} endpoint 180 | * @param {Object} params 181 | * 182 | * @return {Object} 183 | */ 184 | public options(endpoint: string): Promise 185 | public options(endpoint: string, params: any): Promise 186 | } 187 | 188 | /** 189 | * Options Exception. 190 | */ 191 | export class OptionsException { 192 | public name: 'Options Error' 193 | public message: string 194 | 195 | /** 196 | * Constructor. 197 | * 198 | * @param {String} message 199 | */ 200 | constructor(message: string) 201 | } 202 | -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import axios from "axios"; 4 | import createHmac from "create-hmac"; 5 | import OAuth from "oauth-1.0a"; 6 | import Url from "url-parse"; 7 | 8 | /** 9 | * WooCommerce REST API wrapper 10 | * 11 | * @param {Object} opt 12 | */ 13 | export default class WooCommerceRestApi { 14 | /** 15 | * Class constructor. 16 | * 17 | * @param {Object} opt 18 | */ 19 | constructor(opt) { 20 | if (!(this instanceof WooCommerceRestApi)) { 21 | return new WooCommerceRestApi(opt); 22 | } 23 | 24 | opt = opt || {}; 25 | 26 | if (!opt.url) { 27 | throw new OptionsException("url is required"); 28 | } 29 | 30 | if (!opt.consumerKey) { 31 | throw new OptionsException("consumerKey is required"); 32 | } 33 | 34 | if (!opt.consumerSecret) { 35 | throw new OptionsException("consumerSecret is required"); 36 | } 37 | 38 | this.classVersion = "1.0.1"; 39 | this._setDefaultsOptions(opt); 40 | } 41 | 42 | /** 43 | * Set default options 44 | * 45 | * @param {Object} opt 46 | */ 47 | _setDefaultsOptions(opt) { 48 | this.url = opt.url; 49 | this.wpAPIPrefix = opt.wpAPIPrefix || "wp-json"; 50 | this.version = opt.version || "wc/v3"; 51 | this.isHttps = /^https/i.test(this.url); 52 | this.consumerKey = opt.consumerKey; 53 | this.consumerSecret = opt.consumerSecret; 54 | this.encoding = opt.encoding || "utf8"; 55 | this.queryStringAuth = opt.queryStringAuth || false; 56 | this.port = opt.port || ""; 57 | this.timeout = opt.timeout; 58 | this.axiosConfig = opt.axiosConfig || {}; 59 | } 60 | 61 | /** 62 | * Parse params object. 63 | * 64 | * @param {Object} params 65 | * @param {Object} query 66 | */ 67 | _parseParamsObject(params, query) { 68 | for (const key in params) { 69 | const value = params[key]; 70 | 71 | if (typeof value === "object") { 72 | for (const prop in value) { 73 | const itemKey = key.toString() + "[" + prop.toString() + "]"; 74 | query[itemKey] = value[prop]; 75 | } 76 | } else { 77 | query[key] = value; 78 | } 79 | } 80 | 81 | return query; 82 | } 83 | 84 | /** 85 | * Normalize query string for oAuth 86 | * 87 | * @param {String} url 88 | * @param {Object} params 89 | * 90 | * @return {String} 91 | */ 92 | _normalizeQueryString(url, params) { 93 | // Exit if don't find query string. 94 | if (url.indexOf("?") === -1 && Object.keys(params).length === 0) { 95 | return url; 96 | } 97 | 98 | const query = new Url(url, null, true).query; 99 | const values = []; 100 | 101 | let queryString = ""; 102 | 103 | // Include params object into URL.searchParams. 104 | this._parseParamsObject(params, query); 105 | 106 | for (const key in query) { 107 | values.push(key); 108 | } 109 | values.sort(); 110 | 111 | for (const i in values) { 112 | if (queryString.length) { 113 | queryString += "&"; 114 | } 115 | 116 | queryString += encodeURIComponent(values[i]) 117 | .replace(/%5B/g, "[") 118 | .replace(/%5D/g, "]"); 119 | queryString += "="; 120 | queryString += encodeURIComponent(query[values[i]]); 121 | } 122 | 123 | return url.split("?")[0] + "?" + queryString; 124 | } 125 | 126 | /** 127 | * Get URL 128 | * 129 | * @param {String} endpoint 130 | * @param {Object} params 131 | * 132 | * @return {String} 133 | */ 134 | _getUrl(endpoint, params) { 135 | const api = this.wpAPIPrefix + "/"; 136 | 137 | let url = this.url.slice(-1) === "/" ? this.url : this.url + "/"; 138 | 139 | url = url + api + this.version + "/" + endpoint; 140 | 141 | // Include port. 142 | if (this.port !== "") { 143 | const hostname = new Url(url).hostname; 144 | 145 | url = url.replace(hostname, hostname + ":" + this.port); 146 | } 147 | 148 | if (!this.isHttps) { 149 | return this._normalizeQueryString(url, params); 150 | } 151 | 152 | return url; 153 | } 154 | 155 | /** 156 | * Get OAuth 157 | * 158 | * @return {Object} 159 | */ 160 | _getOAuth() { 161 | const data = { 162 | consumer: { 163 | key: this.consumerKey, 164 | secret: this.consumerSecret 165 | }, 166 | signature_method: "HMAC-SHA256", 167 | hash_function: (base, key) => { 168 | return createHmac("sha256", key) 169 | .update(base) 170 | .digest("base64"); 171 | } 172 | }; 173 | 174 | return new OAuth(data); 175 | } 176 | 177 | /** 178 | * Do requests 179 | * 180 | * @param {String} method 181 | * @param {String} endpoint 182 | * @param {Object} data 183 | * @param {Object} params 184 | * 185 | * @return {Object} 186 | */ 187 | _request(method, endpoint, data, params = {}) { 188 | const url = this._getUrl(endpoint, params); 189 | 190 | const headers = { 191 | Accept: "application/json" 192 | }; 193 | // only set "User-Agent" in node environment 194 | // the checking method is identical to upstream axios 195 | if ( 196 | typeof process !== "undefined" && 197 | Object.prototype.toString.call(process) === "[object process]" 198 | ) { 199 | headers["User-Agent"] = 200 | "WooCommerce REST API - JS Client/" + this.classVersion; 201 | } 202 | 203 | let options = { 204 | url: url, 205 | method: method, 206 | responseEncoding: this.encoding, 207 | timeout: this.timeout, 208 | responseType: "json", 209 | headers 210 | }; 211 | 212 | if (this.isHttps) { 213 | if (this.queryStringAuth) { 214 | options.params = { 215 | consumer_key: this.consumerKey, 216 | consumer_secret: this.consumerSecret 217 | }; 218 | } else { 219 | options.auth = { 220 | username: this.consumerKey, 221 | password: this.consumerSecret 222 | }; 223 | } 224 | 225 | options.params = { ...options.params, ...params }; 226 | } else { 227 | options.params = this._getOAuth().authorize({ 228 | url: url, 229 | method: method 230 | }); 231 | } 232 | 233 | if (data) { 234 | options.headers["Content-Type"] = "application/json;charset=utf-8"; 235 | options.data = JSON.stringify(data); 236 | } 237 | 238 | // Allow set and override Axios options. 239 | options = { ...options, ...this.axiosConfig }; 240 | 241 | return axios(options); 242 | } 243 | 244 | /** 245 | * GET requests 246 | * 247 | * @param {String} endpoint 248 | * @param {Object} params 249 | * 250 | * @return {Object} 251 | */ 252 | get(endpoint, params = {}) { 253 | return this._request("get", endpoint, null, params); 254 | } 255 | 256 | /** 257 | * POST requests 258 | * 259 | * @param {String} endpoint 260 | * @param {Object} data 261 | * @param {Object} params 262 | * 263 | * @return {Object} 264 | */ 265 | post(endpoint, data, params = {}) { 266 | return this._request("post", endpoint, data, params); 267 | } 268 | 269 | /** 270 | * PUT requests 271 | * 272 | * @param {String} endpoint 273 | * @param {Object} data 274 | * @param {Object} params 275 | * 276 | * @return {Object} 277 | */ 278 | put(endpoint, data, params = {}) { 279 | return this._request("put", endpoint, data, params); 280 | } 281 | 282 | /** 283 | * DELETE requests 284 | * 285 | * @param {String} endpoint 286 | * @param {Object} params 287 | * @param {Object} params 288 | * 289 | * @return {Object} 290 | */ 291 | delete(endpoint, params = {}) { 292 | return this._request("delete", endpoint, null, params); 293 | } 294 | 295 | /** 296 | * OPTIONS requests 297 | * 298 | * @param {String} endpoint 299 | * @param {Object} params 300 | * 301 | * @return {Object} 302 | */ 303 | options(endpoint, params = {}) { 304 | return this._request("options", endpoint, null, params); 305 | } 306 | } 307 | 308 | /** 309 | * Options Exception. 310 | */ 311 | export class OptionsException { 312 | /** 313 | * Constructor. 314 | * 315 | * @param {String} message 316 | */ 317 | constructor(message) { 318 | this.name = "Options Error"; 319 | this.message = message; 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // All imported modules in your tests should be mocked automatically 6 | // automock: false, 7 | 8 | // Stop running tests after `n` failures 9 | // bail: 0, 10 | 11 | // Respect "browser" field in package.json when resolving modules 12 | // browser: false, 13 | 14 | // The directory where Jest should store its cached dependency information 15 | // cacheDirectory: "/tmp/jest_rs", 16 | 17 | // Automatically clear mock calls and instances between every test 18 | // clearMocks: false, 19 | 20 | // Indicates whether the coverage information should be collected while executing the test 21 | // collectCoverage: false, 22 | 23 | // An array of glob patterns indicating a set of files for which coverage information should be collected 24 | // collectCoverageFrom: null, 25 | 26 | // The directory where Jest should output its coverage files 27 | coverageDirectory: "coverage", 28 | 29 | // An array of regexp pattern strings used to skip coverage collection 30 | coveragePathIgnorePatterns: ["/node_modules/"], 31 | 32 | // A list of reporter names that Jest uses when writing coverage reports 33 | // coverageReporters: [ 34 | // "json", 35 | // "text", 36 | // "lcov", 37 | // "clover" 38 | // ], 39 | 40 | // An object that configures minimum threshold enforcement for coverage results 41 | // coverageThreshold: null, 42 | 43 | // A path to a custom dependency extractor 44 | // dependencyExtractor: null, 45 | 46 | // Make calling deprecated APIs throw helpful error messages 47 | // errorOnDeprecated: false, 48 | 49 | // Force coverage collection from ignored files using an array of glob patterns 50 | // forceCoverageMatch: [], 51 | 52 | // A path to a module which exports an async function that is triggered once before all test suites 53 | // globalSetup: null, 54 | 55 | // A path to a module which exports an async function that is triggered once after all test suites 56 | // globalTeardown: null, 57 | 58 | // A set of global variables that need to be available in all test environments 59 | // globals: {}, 60 | 61 | // An array of directory names to be searched recursively up from the requiring module's location 62 | // moduleDirectories: [ 63 | // "node_modules" 64 | // ], 65 | 66 | // An array of file extensions your modules use 67 | moduleFileExtensions: ["js", "mjs"], 68 | 69 | // A map from regular expressions to module names that allow to stub out resources with a single module 70 | moduleNameMapper: { 71 | axios: "axios/dist/node/axios.cjs" 72 | }, 73 | 74 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 75 | // modulePathIgnorePatterns: [], 76 | 77 | // Activates notifications for test results 78 | // notify: false, 79 | 80 | // An enum that specifies notification mode. Requires { notify: true } 81 | // notifyMode: "failure-change", 82 | 83 | // A preset that is used as a base for Jest's configuration 84 | // preset: null, 85 | 86 | // Run tests from one or more projects 87 | // projects: null, 88 | 89 | // Use this configuration option to add custom reporters to Jest 90 | // reporters: undefined, 91 | 92 | // Automatically reset mock state between every test 93 | // resetMocks: false, 94 | 95 | // Reset the module registry before running each individual test 96 | // resetModules: false, 97 | 98 | // A path to a custom resolver 99 | // resolver: null, 100 | 101 | // Automatically restore mock state between every test 102 | // restoreMocks: false, 103 | 104 | // The root directory that Jest should scan for tests and modules within 105 | // rootDir: null, 106 | 107 | // A list of paths to directories that Jest should use to search for files in 108 | // roots: [ 109 | // "" 110 | // ], 111 | 112 | // Allows you to use a custom runner instead of Jest's default test runner 113 | // runner: "jest-runner", 114 | 115 | // The paths to modules that run some code to configure or set up the testing environment before each test 116 | // setupFiles: [], 117 | 118 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 119 | // setupFilesAfterEnv: [], 120 | 121 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 122 | // snapshotSerializers: [], 123 | 124 | // The test environment that will be used for testing 125 | testEnvironment: "node" 126 | 127 | // Options that will be passed to the testEnvironment 128 | // testEnvironmentOptions: {}, 129 | 130 | // Adds a location field to test results 131 | // testLocationInResults: false, 132 | 133 | // The glob patterns Jest uses to detect test files 134 | // testMatch: [ 135 | // "**/__tests__/**/*.[jt]s?(x)", 136 | // "**/?(*.)+(spec|test).[tj]s?(x)" 137 | // ], 138 | 139 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 140 | // testPathIgnorePatterns: [ 141 | // "/node_modules/" 142 | // ], 143 | 144 | // The regexp pattern or array of patterns that Jest uses to detect test files 145 | // testRegex: [], 146 | 147 | // This option allows the use of a custom results processor 148 | // testResultsProcessor: null, 149 | 150 | // This option allows use of a custom test runner 151 | // testRunner: "jasmine2", 152 | 153 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 154 | // testURL: "http://localhost", 155 | 156 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 157 | // timers: "real", 158 | 159 | // A map from regular expressions to paths to transformers 160 | // transform: null, 161 | 162 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 163 | // transformIgnorePatterns: [ 164 | // "/node_modules/" 165 | // ], 166 | 167 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 168 | // unmockedModulePathPatterns: undefined, 169 | 170 | // Indicates whether each individual test should be reported during the run 171 | // verbose: null, 172 | 173 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 174 | // watchPathIgnorePatterns: [], 175 | 176 | // Whether to use watchman for file crawling 177 | // watchman: true, 178 | }; 179 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@woocommerce/woocommerce-rest-api", 3 | "version": "1.0.1", 4 | "description": "WooCommerce REST API - JavaScript Library", 5 | "author": "Automattic", 6 | "license": "MIT", 7 | "keywords": [ 8 | "wordpress", 9 | "woocommerce", 10 | "rest", 11 | "promise", 12 | "node" 13 | ], 14 | "homepage": "https://github.com/woocommerce/woocommerce-rest-api-js-lib", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/woocommerce/woocommerce-rest-api-js-lib.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/woocommerce/woocommerce-rest-api-js-lib/issues" 21 | }, 22 | "main": "index", 23 | "types": "index.d.ts", 24 | "files": [ 25 | "index.js", 26 | "index.mjs", 27 | "index.d.ts" 28 | ], 29 | "dependencies": { 30 | "axios": "^1.6.0", 31 | "create-hmac": "^1.1.7", 32 | "oauth-1.0a": "^2.2.6", 33 | "url-parse": "^1.4.7" 34 | }, 35 | "devDependencies": { 36 | "@babel/cli": "7.6.0", 37 | "@babel/core": "7.6.0", 38 | "@babel/plugin-transform-modules-commonjs": "7.6.0", 39 | "@babel/preset-env": "7.6.0", 40 | "babel-jest": "24.9.0", 41 | "del-cli": "3.0.0", 42 | "eslint": "6.4.0", 43 | "eslint-config-prettier": "6.3.0", 44 | "eslint-config-standard": "14.1.0", 45 | "eslint-plugin-import": "2.18.2", 46 | "eslint-plugin-jest": "22.17.0", 47 | "eslint-plugin-node": "10.0.0", 48 | "eslint-plugin-prettier": "3.1.1", 49 | "eslint-plugin-promise": "4.2.1", 50 | "eslint-plugin-standard": "4.0.1", 51 | "husky": "3.0.5", 52 | "jest": "24.9.0", 53 | "lint-staged": "9.3.0", 54 | "nock": "11.3.5", 55 | "prettier": "1.18.2" 56 | }, 57 | "scripts": { 58 | "build": "del index.js && babel index.mjs --out-dir .", 59 | "test": "jest", 60 | "format": "prettier --write \"*.mjs\" \"test.js\"", 61 | "lint": "eslint *.mjs", 62 | "prepare": "npm run build", 63 | "prepublishOnly": "npm test && npm run lint", 64 | "preversion": "npm run lint", 65 | "version": "npm run format && git add -A", 66 | "postversion": "git push && git push --tags" 67 | }, 68 | "husky": { 69 | "hooks": { 70 | "pre-commit": "lint-staged" 71 | } 72 | }, 73 | "lint-staged": { 74 | "*.{js,mjs}": [ 75 | "eslint --fix", 76 | "prettier --write", 77 | "git add" 78 | ] 79 | }, 80 | "engines": { 81 | "node": ">=8.0.0" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import WooCommerceRestApi from "./index"; 4 | import nock from "nock"; 5 | 6 | describe("#options", () => { 7 | test("wpAPIPrefix should set WP REST API custom path", () => { 8 | const api = new WooCommerceRestApi({ 9 | url: "https://test.dev", 10 | consumerKey: "ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 11 | consumerSecret: "cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 12 | wpAPIPrefix: "wp-rest", 13 | version: "wc/v3" 14 | }); 15 | 16 | const endpoint = "products"; 17 | const expected = "https://test.dev/wp-rest/wc/v3/" + endpoint; 18 | const url = api._getUrl(endpoint); 19 | 20 | expect(url).toBe(expected); 21 | }); 22 | }); 23 | 24 | describe("#methods", () => { 25 | const api = new WooCommerceRestApi({ 26 | url: "https://test.dev", 27 | consumerKey: "ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 28 | consumerSecret: "cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 29 | version: "wc/v3" 30 | }); 31 | 32 | test("_getUrl should return full endpoint URL", () => { 33 | const endpoint = "products"; 34 | const expected = "https://test.dev/wp-json/wc/v3/" + endpoint; 35 | const url = api._getUrl(endpoint); 36 | 37 | expect(url).toBe(expected); 38 | }); 39 | 40 | test("_normalizeQueryString should return query string sorted by name", () => { 41 | const url = 42 | "http://test.dev/wp-json/wc/v3/products?filter[q]=Woo+Album&fields=id&filter[limit]=1"; 43 | const expected = 44 | "http://test.dev/wp-json/wc/v3/products?fields=id&filter[limit]=1&filter[q]=Woo%20Album"; 45 | const normalized = api._normalizeQueryString(url); 46 | 47 | expect(normalized).toBe(expected); 48 | }); 49 | }); 50 | 51 | describe("#requests", () => { 52 | beforeEach(() => { 53 | nock.cleanAll(); 54 | }); 55 | 56 | const api = new WooCommerceRestApi({ 57 | url: "https://test.dev", 58 | consumerKey: "ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 59 | consumerSecret: "cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 60 | version: "wc/v3" 61 | }); 62 | 63 | test("should return content for basic auth", () => { 64 | expect.assertions(1); 65 | 66 | nock("https://test.dev/wp-json/wc/v3") 67 | .post("/orders", {}) 68 | .reply(201, { 69 | ok: true 70 | }); 71 | 72 | return api.post("orders", {}).then(response => { 73 | expect(response.status).toBe(201); 74 | }); 75 | }); 76 | 77 | test("should return content for get requests", () => { 78 | expect.assertions(1); 79 | nock("https://test.dev/wp-json/wc/v3") 80 | .get("/orders") 81 | .reply(200, { 82 | ok: true 83 | }); 84 | 85 | return api.get("orders", {}).then(response => { 86 | expect(response.status).toBe(200); 87 | }); 88 | }); 89 | 90 | test("should return content for put requests", () => { 91 | expect.assertions(1); 92 | nock("https://test.dev/wp-json/wc/v3") 93 | .put("/orders") 94 | .reply(200, { 95 | ok: true 96 | }); 97 | 98 | return api.put("orders", {}).then(response => { 99 | expect(response.status).toBe(200); 100 | }); 101 | }); 102 | 103 | test("should return content for delete requests", () => { 104 | expect.assertions(1); 105 | nock("https://test.dev/wp-json/wc/v3") 106 | .delete("/orders") 107 | .reply(200, { 108 | ok: true 109 | }); 110 | 111 | return api.delete("orders", {}).then(response => { 112 | expect(response.status).toBe(200); 113 | }); 114 | }); 115 | 116 | test("should return content for options requests", () => { 117 | expect.assertions(1); 118 | nock("https://test.dev/wp-json/wc/v3") 119 | .intercept("/orders", "OPTIONS") 120 | .reply(200, { 121 | ok: true 122 | }); 123 | 124 | return api.options("orders", {}).then(response => { 125 | expect(response.status).toBe(200); 126 | }); 127 | }); 128 | 129 | test("should return content for OAuth", () => { 130 | expect.assertions(1); 131 | const oAuth = new WooCommerceRestApi({ 132 | url: "http://test.dev", 133 | consumerKey: "ck_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 134 | consumerSecret: "cs_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 135 | version: "wc/v3" 136 | }); 137 | 138 | nock("http://test.dev/wp-json/wc/v3") 139 | .filteringPath(/\?.*/, "?params") 140 | .get("/orders?params") 141 | .reply(200, { 142 | ok: true 143 | }); 144 | 145 | return oAuth.get("orders", {}).then(response => { 146 | expect(response.status).toBe(200); 147 | }); 148 | }); 149 | }); 150 | --------------------------------------------------------------------------------