From 34337402f726afa78e4184bf2b83f1286149fc64 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 30 May 2018 11:50:06 +0100 Subject: [PATCH 1/9] Add a wordpress/api-request package --- .eslintrc.js | 4 ++ lib/client-assets.php | 13 +++++ packages/api-request/.npmrc | 1 + packages/api-request/README.md | 23 +++++++++ packages/api-request/package.json | 28 +++++++++++ packages/api-request/src/index.js | 84 +++++++++++++++++++++++++++++++ test/unit/jest.config.json | 2 +- webpack.config.js | 6 +-- 8 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 packages/api-request/.npmrc create mode 100644 packages/api-request/README.md create mode 100644 packages/api-request/package.json create mode 100644 packages/api-request/src/index.js diff --git a/.eslintrc.js b/.eslintrc.js index ce9869d8d7910..016ceb1dded4e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -38,6 +38,10 @@ module.exports = { selector: 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', message: 'Path access on WordPress dependencies is not allowed.', }, + { + selector: 'ImportDeclaration[source.value=/^api-request$/]', + message: 'Use @wordpress/api-request as import path instead.', + }, { selector: 'ImportDeclaration[source.value=/^blob$/]', message: 'Use @wordpress/blob as import path instead.', diff --git a/lib/client-assets.php b/lib/client-assets.php index 0cfa47fe9d7a4..fa0dc0828a736 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -115,6 +115,19 @@ function gutenberg_register_scripts_and_styles() { ); // Editor Scripts. + wp_deregister_script( 'wp-api-request' ); + wp_register_script( + 'wp-api-request', + gutenberg_url( 'build/api-request/index.js' ), + array(), + filemtime( gutenberg_dir_path() . 'build/api-request/index.js' ), + true + ); + wp_localize_script( 'wp-api-request', 'wpApiSettings', array( + 'root' => esc_url_raw( get_rest_url() ), + 'nonce' => ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' ), + 'versionString' => 'wp/v2/', + ) ); wp_register_script( 'wp-deprecated', gutenberg_url( 'build/deprecated/index.js' ), diff --git a/packages/api-request/.npmrc b/packages/api-request/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/api-request/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/api-request/README.md b/packages/api-request/README.md new file mode 100644 index 0000000000000..6e6a708834e8d --- /dev/null +++ b/packages/api-request/README.md @@ -0,0 +1,23 @@ +# @wordpress/blob + +Wrapper around `jQuery.ajax` to call WordPress REST APIs. + +## Installation + +Install the module + +```bash +npm install @wordpress/api-request --save +``` + +## Usage + +```js +import apiRequest from '@wordpress/api-request'; + +apiRequest( { path: '/wp/v2/posts' } ).then( posts => { + console.log( posts ); +} ); +``` + +

Code is Poetry.

diff --git a/packages/api-request/package.json b/packages/api-request/package.json new file mode 100644 index 0000000000000..50d0f35614ce2 --- /dev/null +++ b/packages/api-request/package.json @@ -0,0 +1,28 @@ +{ + "name": "@wordpress/api-request", + "version": "1.0.0", + "description": "Utility to call WordPress REST APIs", + "author": "WordPress", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "rest-api", + "fetch" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/api-request/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "jquery": "^3.3.1" + } +} diff --git a/packages/api-request/src/index.js b/packages/api-request/src/index.js new file mode 100644 index 0000000000000..2641359d9b08b --- /dev/null +++ b/packages/api-request/src/index.js @@ -0,0 +1,84 @@ +/** + * External dependencies + */ +import jQuery from 'jquery'; + +const wpApiSettings = window.wpApiSettings; + +function apiRequest( options ) { + options = apiRequest.buildAjaxOptions( options ); + return apiRequest.transport( options ); +} + +apiRequest.buildAjaxOptions = function( options ) { + let url = options.url; + let path = options.path; + let namespaceTrimmed, endpointTrimmed, apiRoot; + let headers, addNonceHeader, headerName; + + if ( + typeof options.namespace === 'string' && + typeof options.endpoint === 'string' + ) { + namespaceTrimmed = options.namespace.replace( /^\/|\/$/g, '' ); + endpointTrimmed = options.endpoint.replace( /^\//, '' ); + if ( endpointTrimmed ) { + path = namespaceTrimmed + '/' + endpointTrimmed; + } else { + path = namespaceTrimmed; + } + } + if ( typeof path === 'string' ) { + apiRoot = wpApiSettings.root; + path = path.replace( /^\//, '' ); + + // API root may already include query parameter prefix if site is + // configured to use plain permalinks. + if ( 'string' === typeof apiRoot && -1 !== apiRoot.indexOf( '?' ) ) { + path = path.replace( '?', '&' ); + } + + url = apiRoot + path; + } + + // If ?_wpnonce=... is present, no need to add a nonce header. + addNonceHeader = ! ( options.data && options.data._wpnonce ); + + headers = options.headers || {}; + + // If an 'X-WP-Nonce' header (or any case-insensitive variation + // thereof) was specified, no need to add a nonce header. + if ( addNonceHeader ) { + for ( headerName in headers ) { + if ( headers.hasOwnProperty( headerName ) ) { + if ( headerName.toLowerCase() === 'x-wp-nonce' ) { + addNonceHeader = false; + break; + } + } + } + } + + if ( addNonceHeader ) { + // Do not mutate the original headers object, if any. + headers = jQuery.extend( { + 'X-WP-Nonce': wpApiSettings.nonce, + }, headers ); + } + + // Do not mutate the original options object. + options = jQuery.extend( {}, options, { + headers: headers, + url: url, + } ); + + delete options.path; + delete options.namespace; + delete options.endpoint; + + return options; +}; + +apiRequest.transport = jQuery.ajax; + +export default apiRequest; diff --git a/test/unit/jest.config.json b/test/unit/jest.config.json index d661ae8205017..e7a44f814b6db 100644 --- a/test/unit/jest.config.json +++ b/test/unit/jest.config.json @@ -6,7 +6,7 @@ ], "moduleNameMapper": { "@wordpress\\/(blocks|components|editor|utils|edit-post|viewport|plugins|core-data|core-blocks|nux)$": "$1", - "@wordpress\\/(blob|data|date|dom|deprecated|element|postcss-themes)$": "packages/$1/src" + "@wordpress\\/(api-request|blob|data|date|dom|deprecated|element|postcss-themes)$": "packages/$1/src" }, "preset": "@wordpress/jest-preset-default", "setupFiles": [ diff --git a/webpack.config.js b/webpack.config.js index 6e488bc4edafe..67435cf06db8f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -156,6 +156,7 @@ const gutenbergPackages = [ 'deprecated', 'dom', 'element', + 'api-request', ]; const wordPressPackages = [ @@ -166,10 +167,6 @@ const wordPressPackages = [ 'is-shallow-equal', ]; -const coreGlobals = [ - 'api-request', -]; - const externals = { react: 'React', 'react-dom': 'ReactDOM', @@ -184,7 +181,6 @@ const externals = { ...entryPointNames, ...gutenbergPackages, ...wordPressPackages, - ...coreGlobals, ].forEach( ( name ) => { externals[ `@wordpress/${ name }` ] = { this: [ 'wp', camelCaseDash( name ) ], From 99b99071def17995925a3431d173a76b27d22cc4 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 30 May 2018 13:19:11 +0100 Subject: [PATCH 2/9] Add the middlewares API --- .eslintrc.js | 2 +- components/code-editor/index.js | 4 +- .../higher-order/with-api-data/request.js | 7 +- core-blocks/embed/index.js | 3 +- edit-post/index.js | 10 -- edit-post/store/effects.js | 3 +- editor/components/autocompleters/user.js | 7 +- .../post-taxonomies/flat-term-selector.js | 7 +- .../hierarchical-term-selector.js | 7 +- editor/components/provider/index.js | 2 +- editor/components/url-input/index.js | 3 +- editor/store/effects.js | 17 +-- lib/client-assets.php | 35 ++++-- lib/compat.php | 117 ------------------ packages/api-request/README.md | 19 +++ .../api-request/src/http-v1-middleware.js | 19 +++ packages/api-request/src/index.js | 103 +++++---------- .../src/namespace-endpoint-middleware.js | 27 ++++ packages/api-request/src/nonce-middleware.js | 46 +++++++ .../api-request/src/preloading-middleware.js | 49 ++++++++ .../api-request/src/root-url-middleware.js | 34 +++++ utils/mediaupload.js | 7 +- webpack.config.js | 2 +- 23 files changed, 296 insertions(+), 234 deletions(-) create mode 100644 packages/api-request/src/http-v1-middleware.js create mode 100644 packages/api-request/src/namespace-endpoint-middleware.js create mode 100644 packages/api-request/src/nonce-middleware.js create mode 100644 packages/api-request/src/preloading-middleware.js create mode 100644 packages/api-request/src/root-url-middleware.js diff --git a/.eslintrc.js b/.eslintrc.js index 016ceb1dded4e..c1c608b9f2a69 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,7 +26,7 @@ module.exports = { 'jest/globals': true, }, globals: { - wpApiSettings: true, + wpApiSchema: true, }, plugins: [ 'jest', diff --git a/components/code-editor/index.js b/components/code-editor/index.js index 546f0afe67329..bac7580e58065 100644 --- a/components/code-editor/index.js +++ b/components/code-editor/index.js @@ -21,7 +21,7 @@ function loadScript() { } const script = document.createElement( 'script' ); - script.src = `${ wpApiSettings.schema.url }/wp-admin/load-scripts.php?load=${ handles.join( ',' ) }`; + script.src = `${ wpApiSchema.url }/wp-admin/load-scripts.php?load=${ handles.join( ',' ) }`; script.onload = resolve; script.onerror = reject; @@ -35,7 +35,7 @@ function loadStyle() { const style = document.createElement( 'link' ); style.rel = 'stylesheet'; - style.href = `${ wpApiSettings.schema.url }/wp-admin/load-styles.php?load=${ handles.join( ',' ) }`; + style.href = `${ wpApiSchema.url }/wp-admin/load-styles.php?load=${ handles.join( ',' ) }`; style.onload = resolve; style.onerror = reject; diff --git a/components/higher-order/with-api-data/request.js b/components/higher-order/with-api-data/request.js index c37aa016a6e6e..49c04a7fe3e9a 100644 --- a/components/higher-order/with-api-data/request.js +++ b/components/higher-order/with-api-data/request.js @@ -4,6 +4,11 @@ import memoize from 'memize'; import { mapKeys } from 'lodash'; +/** + * WordPress dependencies + */ +import apiRequest from '@wordpress/api-request'; + export const getStablePath = memoize( ( path ) => { const [ base, query ] = path.split( '?' ); if ( ! query ) { @@ -75,7 +80,7 @@ export function getCachedResponse( request ) { } export function getResponseFromNetwork( request ) { - const promise = wp.apiRequest( request ) + const promise = apiRequest( request ) .then( ( body, status, xhr ) => { return { body, diff --git a/core-blocks/embed/index.js b/core-blocks/embed/index.js index ea3df2d66d275..b2c17bac4435b 100644 --- a/core-blocks/embed/index.js +++ b/core-blocks/embed/index.js @@ -19,6 +19,7 @@ import { BlockAlignmentToolbar, RichText, } from '@wordpress/editor'; +import apiRequest from '@wordpress/api-request'; /** * Internal dependencies @@ -30,7 +31,7 @@ import './editor.scss'; const HOSTS_NO_PREVIEWS = [ 'facebook.com' ]; // Caches the embed API calls, so if blocks get transformed, or deleted and added again, we don't spam the API. -const wpEmbedAPI = memoize( ( url ) => wp.apiRequest( { path: `/oembed/1.0/proxy?${ stringify( { url } ) }` } ) ); +const wpEmbedAPI = memoize( ( url ) => apiRequest( { path: `/oembed/1.0/proxy?${ stringify( { url } ) }` } ) ); const matchesPatterns = ( url, patterns = [] ) => { return patterns.some( ( pattern ) => { diff --git a/edit-post/index.js b/edit-post/index.js index b77b0aa2a9a4b..f211b4649ec1a 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -14,16 +14,6 @@ import store from './store'; import { initializeMetaBoxState } from './store/actions'; import Editor from './editor'; -/** - * Configure heartbeat to refresh the wp-api nonce, keeping the editor - * authorization intact. - */ -window.jQuery( document ).on( 'heartbeat-tick', ( event, response ) => { - if ( response[ 'rest-nonce' ] ) { - window.wpApiSettings.nonce = response[ 'rest-nonce' ]; - } -} ); - /** * Reinitializes the editor after the user chooses to reboot the editor after * an unhandled error occurs, replacing previously mounted editor element using diff --git a/edit-post/store/effects.js b/edit-post/store/effects.js index 1347614e4cd1b..2143abc2018ce 100644 --- a/edit-post/store/effects.js +++ b/edit-post/store/effects.js @@ -9,6 +9,7 @@ import { reduce, some } from 'lodash'; import { select, subscribe } from '@wordpress/data'; import { speak } from '@wordpress/a11y'; import { __ } from '@wordpress/i18n'; +import apiRequest from '@wordpress/api-request'; /** * Internal dependencies @@ -98,7 +99,7 @@ const effects = { additionalData.forEach( ( [ key, value ] ) => formData.append( key, value ) ); // Save the metaboxes - wp.apiRequest( { + apiRequest( { url: window._wpMetaBoxUrl, method: 'POST', processData: false, diff --git a/editor/components/autocompleters/user.js b/editor/components/autocompleters/user.js index 36a89249b6b6e..2b655f4b3ef6c 100644 --- a/editor/components/autocompleters/user.js +++ b/editor/components/autocompleters/user.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import apiRequest from '@wordpress/api-request'; + /** * A user mentions completer. * @@ -12,7 +17,7 @@ export default { if ( search ) { payload = '?search=' + encodeURIComponent( search ); } - return wp.apiRequest( { path: '/wp/v2/users' + payload } ); + return apiRequest( { path: '/wp/v2/users' + payload } ); }, isDebounced: true, getOptionKeywords( user ) { diff --git a/editor/components/post-taxonomies/flat-term-selector.js b/editor/components/post-taxonomies/flat-term-selector.js index 3357b6c5f2ed2..4931ca7fb5f2d 100644 --- a/editor/components/post-taxonomies/flat-term-selector.js +++ b/editor/components/post-taxonomies/flat-term-selector.js @@ -11,6 +11,7 @@ import { __, _x, sprintf } from '@wordpress/i18n'; import { Component, compose } from '@wordpress/element'; import { FormTokenField, withAPIData } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; +import apiRequest from '@wordpress/api-request'; /** * Module constants @@ -75,7 +76,7 @@ class FlatTermSelector extends Component { fetchTerms( params = {} ) { const query = { ...DEFAULT_QUERY, ...params }; const basePath = wp.api.getTaxonomyRoute( this.props.slug ); - const request = wp.apiRequest( { path: `/wp/v2/${ basePath }?${ stringify( query ) }` } ); + const request = apiRequest( { path: `/wp/v2/${ basePath }?${ stringify( query ) }` } ); request.then( ( terms ) => { this.setState( ( state ) => ( { availableTerms: state.availableTerms.concat( @@ -106,7 +107,7 @@ class FlatTermSelector extends Component { return new Promise( ( resolve, reject ) => { // Tries to create a term or fetch it if it already exists const basePath = wp.api.getTaxonomyRoute( this.props.slug ); - wp.apiRequest( { + apiRequest( { path: `/wp/v2/${ basePath }`, method: 'POST', data: { name: termName }, @@ -114,7 +115,7 @@ class FlatTermSelector extends Component { const errorCode = xhr.responseJSON && xhr.responseJSON.code; if ( errorCode === 'term_exists' ) { // search the new category created since last fetch - this.addRequest = wp.apiRequest( { + this.addRequest = apiRequest( { path: `/wp/v2/${ basePath }?${ stringify( { ...DEFAULT_QUERY, search: termName } ) }`, } ); return this.addRequest.then( ( searchResult ) => { diff --git a/editor/components/post-taxonomies/hierarchical-term-selector.js b/editor/components/post-taxonomies/hierarchical-term-selector.js index 8686a28356292..7933f83b2a564 100644 --- a/editor/components/post-taxonomies/hierarchical-term-selector.js +++ b/editor/components/post-taxonomies/hierarchical-term-selector.js @@ -12,6 +12,7 @@ import { Component, compose } from '@wordpress/element'; import { TreeSelect, withAPIData, withInstanceId, withSpokenMessages, Button } from '@wordpress/components'; import { buildTermsTree } from '@wordpress/utils'; import { withSelect, withDispatch } from '@wordpress/data'; +import apiRequest from '@wordpress/api-request'; /** * Module Constants @@ -103,7 +104,7 @@ class HierarchicalTermSelector extends Component { } ); // Tries to create a term or fetch it if it already exists const basePath = wp.api.getTaxonomyRoute( this.props.slug ); - this.addRequest = wp.apiRequest( { + this.addRequest = apiRequest( { path: `/wp/v2/${ basePath }`, method: 'POST', data: { @@ -116,7 +117,7 @@ class HierarchicalTermSelector extends Component { const errorCode = xhr.responseJSON && xhr.responseJSON.code; if ( errorCode === 'term_exists' ) { // search the new category created since last fetch - this.addRequest = wp.apiRequest( { + this.addRequest = apiRequest( { path: `/wp/v2/${ basePath }?${ stringify( { ...DEFAULT_QUERY, parent: formParent || 0, search: formName } ) }`, } ); return this.addRequest.then( ( searchResult ) => { @@ -161,7 +162,7 @@ class HierarchicalTermSelector extends Component { componentDidMount() { const basePath = wp.api.getTaxonomyRoute( this.props.slug ); - this.fetchRequest = wp.apiRequest( { path: `/wp/v2/${ basePath }?${ stringify( DEFAULT_QUERY ) }` } ); + this.fetchRequest = apiRequest( { path: `/wp/v2/${ basePath }?${ stringify( DEFAULT_QUERY ) }` } ); this.fetchRequest.then( ( terms ) => { // resolve const availableTermsTree = buildTermsTree( terms ); diff --git a/editor/components/provider/index.js b/editor/components/provider/index.js index 788c29ea4df4f..1326ff7b6cdc8 100644 --- a/editor/components/provider/index.js +++ b/editor/components/provider/index.js @@ -75,7 +75,7 @@ class EditorProvider extends Component { [ APIProvider, { - ...wpApiSettings, + schema: wpApiSchema, ...pick( wp.api, [ 'postTypeRestBaseMapping', 'taxonomyRestBaseMapping', diff --git a/editor/components/url-input/index.js b/editor/components/url-input/index.js index 21d1e98ef5578..8f5887bca3cd1 100644 --- a/editor/components/url-input/index.js +++ b/editor/components/url-input/index.js @@ -13,6 +13,7 @@ import { __, sprintf, _n } from '@wordpress/i18n'; import { Component, Fragment } from '@wordpress/element'; import { keycodes, decodeEntities } from '@wordpress/utils'; import { Spinner, withInstanceId, withSpokenMessages, Popover } from '@wordpress/components'; +import apiRequest from '@wordpress/api-request'; const { UP, DOWN, ENTER } = keycodes; @@ -68,7 +69,7 @@ class UrlInput extends Component { selectedSuggestion: null, loading: true, } ); - this.suggestionsRequest = wp.apiRequest( { + this.suggestionsRequest = apiRequest( { path: `/wp/v2/posts?${ stringify( { search: value, per_page: 20, diff --git a/editor/store/effects.js b/editor/store/effects.js index 86ae0a63b8b81..917d76b89b9e4 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -20,6 +20,7 @@ import { } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; +import apiRequest from '@wordpress/api-request'; /** * Internal dependencies @@ -130,7 +131,7 @@ export default { parent: post.id, }; - request = wp.apiRequest( { + request = apiRequest( { path: `/wp/v2/${ basePath }/${ post.id }/autosaves`, method: 'POST', data: toSend, @@ -145,7 +146,7 @@ export default { dispatch( removeNotice( SAVE_POST_NOTICE_ID ) ); dispatch( removeNotice( AUTOSAVE_POST_NOTICE_ID ) ); - request = wp.apiRequest( { + request = apiRequest( { path: `/wp/v2/${ basePath }/${ post.id }`, method: 'PUT', data: toSend, @@ -251,7 +252,7 @@ export default { const { postId } = action; const basePath = wp.api.getPostTypeRoute( getCurrentPostType( getState() ) ); dispatch( removeNotice( TRASH_POST_NOTICE_ID ) ); - wp.apiRequest( { path: `/wp/v2/${ basePath }/${ postId }`, method: 'DELETE' } ).then( + apiRequest( { path: `/wp/v2/${ basePath }/${ postId }`, method: 'DELETE' } ).then( () => { dispatch( { ...action, @@ -294,7 +295,7 @@ export default { context: 'edit', }; - wp.apiRequest( { path: `/wp/v2/${ basePath }/${ post.id }`, data } ).then( + apiRequest( { path: `/wp/v2/${ basePath }/${ post.id }`, data } ).then( ( newPost ) => { dispatch( resetPost( newPost ) ); } @@ -439,9 +440,9 @@ export default { let result; if ( id ) { - result = wp.apiRequest( { path: `/wp/v2/${ basePath }/${ id }` } ); + result = apiRequest( { path: `/wp/v2/${ basePath }/${ id }` } ); } else { - result = wp.apiRequest( { path: `/wp/v2/${ basePath }?per_page=-1` } ); + result = apiRequest( { path: `/wp/v2/${ basePath }?per_page=-1` } ); } result.then( @@ -494,7 +495,7 @@ export default { const path = isTemporary ? `/wp/v2/${ basePath }` : `/wp/v2/${ basePath }/${ id }`; const method = isTemporary ? 'POST' : 'PUT'; - wp.apiRequest( { path, data, method } ).then( + apiRequest( { path, data, method } ).then( ( updatedSharedBlock ) => { dispatch( { type: 'SAVE_SHARED_BLOCK_SUCCESS', @@ -550,7 +551,7 @@ export default { sharedBlock.uid, ] ) ); - wp.apiRequest( { path: `/wp/v2/${ basePath }/${ id }`, method: 'DELETE' } ).then( + apiRequest( { path: `/wp/v2/${ basePath }/${ id }`, method: 'DELETE' } ).then( () => { dispatch( { type: 'DELETE_SHARED_BLOCK_SUCCESS', diff --git a/lib/client-assets.php b/lib/client-assets.php index fa0dc0828a736..5953ce3838b7c 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -123,11 +123,23 @@ function gutenberg_register_scripts_and_styles() { filemtime( gutenberg_dir_path() . 'build/api-request/index.js' ), true ); - wp_localize_script( 'wp-api-request', 'wpApiSettings', array( - 'root' => esc_url_raw( get_rest_url() ), - 'nonce' => ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' ), - 'versionString' => 'wp/v2/', - ) ); + wp_add_inline_script( + 'wp-api-request', + sprintf( + 'wp.apiRequest.use( wp.apiRequest.createNonceMiddleware( "%s" ) );', + ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' ) + ), + 'after' + ); + wp_add_inline_script( + 'wp-api-request', + sprintf( + 'wp.apiRequest.use( wp.apiRequest.createRootURLMiddleware( "%s" ) );', + esc_url_raw( get_rest_url() ) + ), + 'after' + ); + wp_register_script( 'wp-deprecated', gutenberg_url( 'build/deprecated/index.js' ), @@ -173,7 +185,7 @@ function gutenberg_register_scripts_and_styles() { wp_register_script( 'wp-utils', gutenberg_url( 'build/utils/index.js' ), - array( 'lodash', 'wp-blob', 'wp-deprecated', 'wp-dom' ), + array( 'lodash', 'wp-blob', 'wp-deprecated', 'wp-dom', 'wp-api-request' ), filemtime( gutenberg_dir_path() . 'build/utils/index.js' ), true ); @@ -274,6 +286,7 @@ function gutenberg_register_scripts_and_styles() { 'wp-i18n', 'wp-utils', 'wp-viewport', + 'wp-api-request', ), filemtime( gutenberg_dir_path() . 'build/core-blocks/index.js' ), true @@ -364,6 +377,7 @@ function gutenberg_register_scripts_and_styles() { 'postbox', 'wp-a11y', 'wp-api', + 'wp-api-request', 'wp-blob', 'wp-blocks', 'wp-components', @@ -395,6 +409,7 @@ function gutenberg_register_scripts_and_styles() { 'media-models', 'media-views', 'wp-a11y', + 'wp-api-request', 'wp-components', 'wp-core-blocks', 'wp-date', @@ -779,7 +794,7 @@ function gutenberg_extend_wp_api_backbone_client() { $schema_response = rest_do_request( new WP_REST_Request( 'GET', '/' ) ); if ( ! $schema_response->is_error() ) { wp_add_inline_script( 'wp-api', sprintf( - 'wpApiSettings.cacheSchema = true; wpApiSettings.schema = %s;', + 'wpApiSchema = %s;', wp_json_encode( $schema_response->get_data() ) ), 'before' ); } @@ -1001,9 +1016,9 @@ function gutenberg_editor_scripts_and_styles( $hook ) { ); wp_add_inline_script( - 'wp-components', - sprintf( 'window._wpAPIDataPreload = %s', wp_json_encode( $preload_data ) ), - 'before' + 'wp-api-request', + sprintf( 'wp.apiRequest.use( wp.apiRequest.createPreloadingMiddleware( %s ) );', wp_json_encode( $preload_data ) ), + 'after' ); // Prepopulate with some test content in demo. diff --git a/lib/compat.php b/lib/compat.php index ad72ef60d3349..44b2c8d3f94b4 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -69,123 +69,6 @@ function _gutenberg_utf8_split( $str ) { return $chars; } -/** - * Shims fix for apiRequest on sites configured to use plain permalinks and add Preloading support. - * - * @see https://core.trac.wordpress.org/ticket/42382 - * - * @param WP_Scripts $scripts WP_Scripts instance (passed by reference). - */ -function gutenberg_shim_api_request( $scripts ) { - $api_request_fix = <<add_inline_script( 'wp-api-request', $api_request_fix, 'after' ); -} -add_action( 'wp_default_scripts', 'gutenberg_shim_api_request' ); - -/** - * Shims support for emulating HTTP/1.0 requests in wp.apiRequest - * - * @see https://core.trac.wordpress.org/ticket/43605 - * - * @param WP_Scripts $scripts WP_Scripts instance (passed by reference). - */ -function gutenberg_shim_api_request_emulate_http( $scripts ) { - $api_request_fix = <<= 0 ) { - if ( ! options.headers ) { - options.headers = {}; - } - options.headers['X-HTTP-Method-Override'] = options.method; - options.method = 'POST'; - - options.contentType = 'application/json'; - options.data = JSON.stringify( options.data ); - } - } - - return oldApiRequest( options ); - } -} )( window.wp ); -JS; - - $scripts->add_inline_script( 'wp-api-request', $api_request_fix, 'after' ); -} -add_action( 'wp_default_scripts', 'gutenberg_shim_api_request_emulate_http' ); - /** * Disables wpautop behavior in classic editor when post contains blocks, to * prevent removep from invalidating paragraph blocks. diff --git a/packages/api-request/README.md b/packages/api-request/README.md index 6e6a708834e8d..220096dfe84fa 100644 --- a/packages/api-request/README.md +++ b/packages/api-request/README.md @@ -20,4 +20,23 @@ apiRequest( { path: '/wp/v2/posts' } ).then( posts => { } ); ``` +### Middlewares + +the `api-request` package supports middlewares. Middlewares are functions you can use to wrap the `wp.apiRequest` calls to perform any pre/post process to the API requests. + +**Example** + +```js +wp.apiRequest.use( ( options, next ) => { + const start = Date.now(); + const result = next( options ); + result.then( () => { + console.log( 'The request took ' + Date.now() - start ); + } ); + return result; +} ); +``` + +The apiRequest package provides built-in middlewares you can use to provide a `nonce` and a custom `rootURL`. +

Code is Poetry.

diff --git a/packages/api-request/src/http-v1-middleware.js b/packages/api-request/src/http-v1-middleware.js new file mode 100644 index 0000000000000..b9552bafa8210 --- /dev/null +++ b/packages/api-request/src/http-v1-middleware.js @@ -0,0 +1,19 @@ +function httpV1Middleware( options, next ) { + const newOptions = { ...options }; + if ( newOptions.method ) { + if ( [ 'PATCH', 'PUT', 'DELETE' ].indexOf( newOptions.method.toUpperCase() ) >= 0 ) { + if ( ! newOptions.headers ) { + options.headers = {}; + } + newOptions.headers[ 'X-HTTP-Method-Override' ] = newOptions.method; + newOptions.method = 'POST'; + + newOptions.contentType = 'application/json'; + newOptions.data = JSON.stringify( newOptions.data ); + } + } + + return next( newOptions, next ); +} + +export default httpV1Middleware; diff --git a/packages/api-request/src/index.js b/packages/api-request/src/index.js index 2641359d9b08b..1695910741336 100644 --- a/packages/api-request/src/index.js +++ b/packages/api-request/src/index.js @@ -3,82 +3,41 @@ */ import jQuery from 'jquery'; -const wpApiSettings = window.wpApiSettings; - -function apiRequest( options ) { - options = apiRequest.buildAjaxOptions( options ); - return apiRequest.transport( options ); -} - -apiRequest.buildAjaxOptions = function( options ) { - let url = options.url; - let path = options.path; - let namespaceTrimmed, endpointTrimmed, apiRoot; - let headers, addNonceHeader, headerName; - - if ( - typeof options.namespace === 'string' && - typeof options.endpoint === 'string' - ) { - namespaceTrimmed = options.namespace.replace( /^\/|\/$/g, '' ); - endpointTrimmed = options.endpoint.replace( /^\//, '' ); - if ( endpointTrimmed ) { - path = namespaceTrimmed + '/' + endpointTrimmed; - } else { - path = namespaceTrimmed; - } - } - if ( typeof path === 'string' ) { - apiRoot = wpApiSettings.root; - path = path.replace( /^\//, '' ); - - // API root may already include query parameter prefix if site is - // configured to use plain permalinks. - if ( 'string' === typeof apiRoot && -1 !== apiRoot.indexOf( '?' ) ) { - path = path.replace( '?', '&' ); - } - - url = apiRoot + path; - } - - // If ?_wpnonce=... is present, no need to add a nonce header. - addNonceHeader = ! ( options.data && options.data._wpnonce ); - - headers = options.headers || {}; - - // If an 'X-WP-Nonce' header (or any case-insensitive variation - // thereof) was specified, no need to add a nonce header. - if ( addNonceHeader ) { - for ( headerName in headers ) { - if ( headers.hasOwnProperty( headerName ) ) { - if ( headerName.toLowerCase() === 'x-wp-nonce' ) { - addNonceHeader = false; - break; - } - } - } - } +/** + * Internal dependencies + */ +import createNonceMiddleware from './nonce-middleware'; +import createRootURLMiddleware from './root-url-middleware'; +import createPreloadingMiddleware from './preloading-middleware'; +import namespaceEndpointMiddleware from './namespace-endpoint-middleware'; +import httpV1Middleware from './http-v1-middleware'; - if ( addNonceHeader ) { - // Do not mutate the original headers object, if any. - headers = jQuery.extend( { - 'X-WP-Nonce': wpApiSettings.nonce, - }, headers ); - } +const middlewares = []; - // Do not mutate the original options object. - options = jQuery.extend( {}, options, { - headers: headers, - url: url, - } ); +function registerMiddleware( middleware ) { + middlewares.push( middleware ); +} - delete options.path; - delete options.namespace; - delete options.endpoint; +function apiRequest( options ) { + const raw = ( nextOptions ) => jQuery.ajax( nextOptions ); + const steps = [ + ...middlewares, + namespaceEndpointMiddleware, + httpV1Middleware, + raw, + ].reverse(); + const next = ( nextOptions ) => { + const nextMiddleware = steps.pop(); + return nextMiddleware( nextOptions, next ); + }; + + return next( options ); +} - return options; -}; +apiRequest.use = registerMiddleware; -apiRequest.transport = jQuery.ajax; +apiRequest.createNonceMiddleware = createNonceMiddleware; +apiRequest.createPreloadingMiddleware = createPreloadingMiddleware; +apiRequest.createRootURLMiddleware = createRootURLMiddleware; export default apiRequest; diff --git a/packages/api-request/src/namespace-endpoint-middleware.js b/packages/api-request/src/namespace-endpoint-middleware.js new file mode 100644 index 0000000000000..c6ee6c67b4afb --- /dev/null +++ b/packages/api-request/src/namespace-endpoint-middleware.js @@ -0,0 +1,27 @@ +const namespaceAndEndpointMiddleware = ( options, next ) => { + let path = options.path; + let namespaceTrimmed, endpointTrimmed; + + if ( + typeof options.namespace === 'string' && + typeof options.endpoint === 'string' + ) { + namespaceTrimmed = options.namespace.replace( /^\/|\/$/g, '' ); + endpointTrimmed = options.endpoint.replace( /^\//, '' ); + if ( endpointTrimmed ) { + path = namespaceTrimmed + '/' + endpointTrimmed; + } else { + path = namespaceTrimmed; + } + } + + delete options.namespace; + delete options.endpoint; + + return next( { + ...options, + path, + } ); +}; + +export default namespaceAndEndpointMiddleware; diff --git a/packages/api-request/src/nonce-middleware.js b/packages/api-request/src/nonce-middleware.js new file mode 100644 index 0000000000000..372881f8ffd3f --- /dev/null +++ b/packages/api-request/src/nonce-middleware.js @@ -0,0 +1,46 @@ +const createNonceMiddleware = ( nonce ) => ( options, next ) => { + let usedNonce = nonce; + /** + * This is not ideal but it's fine for now. + * + * Configure heartbeat to refresh the wp-api nonce, keeping the editor + * authorization intact. + */ + window.jQuery( document ).on( 'heartbeat-tick', ( event, response ) => { + if ( response[ 'rest-nonce' ] ) { + usedNonce = response[ 'rest-nonce' ]; + } + } ); + + // If ?_wpnonce=... is present, no need to add a nonce header. + let addNonceHeader = ! ( options.data && options.data._wpnonce ); + let headers = options.headers || {}; + + // If an 'X-WP-Nonce' header (or any case-insensitive variation + // thereof) was specified, no need to add a nonce header. + if ( addNonceHeader ) { + for ( const headerName in headers ) { + if ( headers.hasOwnProperty( headerName ) ) { + if ( headerName.toLowerCase() === 'x-wp-nonce' ) { + addNonceHeader = false; + break; + } + } + } + } + + if ( addNonceHeader ) { + // Do not mutate the original headers object, if any. + headers = { + ...headers, + 'X-WP-Nonce': usedNonce, + }; + } + + return next( { + ...options, + headers, + } ); +}; + +export default createNonceMiddleware; diff --git a/packages/api-request/src/preloading-middleware.js b/packages/api-request/src/preloading-middleware.js new file mode 100644 index 0000000000000..73491fa8fe609 --- /dev/null +++ b/packages/api-request/src/preloading-middleware.js @@ -0,0 +1,49 @@ +/** + * External dependencies + */ +import jQuery from 'jquery'; + +const createPreloadingMiddleware = ( preloadedData ) => ( options, next ) => { + function getStablePath( path ) { + const splitted = path.split( '?' ); + const query = splitted[ 1 ]; + const base = splitted[ 0 ]; + if ( ! query ) { + return base; + } + + // 'b=1&c=2&a=5' + return base + '?' + query + // [ 'b=1', 'c=2', 'a=5' ] + .split( '&' ) + // [ [ 'b, '1' ], [ 'c', '2' ], [ 'a', '5' ] ] + .map( function( entry ) { + return entry.split( '=' ); + } ) + // [ [ 'a', '5' ], [ 'b, '1' ], [ 'c', '2' ] ] + .sort( function( a, b ) { + return a[ 0 ].localeCompare( b[ 0 ] ); + } ) + // [ 'a=5', 'b=1', 'c=2' ] + .map( function( pair ) { + return pair.join( '=' ); + } ) + // 'a=5&b=1&c=2' + .join( '&' ); + } + + if ( typeof options.path === 'string' ) { + const method = options.method || 'GET'; + const path = getStablePath( options.path ); + + if ( 'GET' === method && preloadedData[ path ] ) { + const deferred = jQuery.Deferred(); + deferred.resolve( preloadedData[ path ].body ); + return deferred.promise(); + } + } + + return next( options ); +}; + +export default createPreloadingMiddleware; diff --git a/packages/api-request/src/root-url-middleware.js b/packages/api-request/src/root-url-middleware.js new file mode 100644 index 0000000000000..8d48e798525ee --- /dev/null +++ b/packages/api-request/src/root-url-middleware.js @@ -0,0 +1,34 @@ +import namespaceAndEndpointMiddleware from './namespace-endpoint-middleware'; + +const createRootURLMiddleware = ( rootURL ) => ( options, next ) => { + return namespaceAndEndpointMiddleware( options, ( optionsWithPath ) => { + let url = optionsWithPath.url; + let path = optionsWithPath.path; + let apiRoot; + + if ( typeof path === 'string' ) { + apiRoot = rootURL; + + if ( -1 !== rootURL.indexOf( '?' ) ) { + path = path.replace( '?', '&' ); + } + + path = path.replace( /^\//, '' ); + + // API root may already include query parameter prefix if site is + // configured to use plain permalinks. + if ( 'string' === typeof apiRoot && -1 !== apiRoot.indexOf( '?' ) ) { + path = path.replace( '?', '&' ); + } + + url = apiRoot + path; + } + + return next( { + ...optionsWithPath, + url, + } ); + } ); +}; + +export default createRootURLMiddleware; diff --git a/utils/mediaupload.js b/utils/mediaupload.js index 29b3863bb5ea0..c42a4996a1c4a 100644 --- a/utils/mediaupload.js +++ b/utils/mediaupload.js @@ -3,6 +3,11 @@ */ import { compact, forEach, get, noop, startsWith } from 'lodash'; +/** + * WordPress dependencies + */ +import apiRequest from '@wordpress/api-request'; + /** * Media Upload is used by audio, image, gallery and video blocks to handle uploading a media file * when a file upload button is activated. @@ -81,7 +86,7 @@ function createMediaFromFile( file, additionalData ) { const data = new window.FormData(); data.append( 'file', file, file.name || file.type.replace( '/', '.' ) ); forEach( additionalData, ( ( value, key ) => data.append( key, value ) ) ); - return wp.apiRequest( { + return apiRequest( { path: '/wp/v2/media', data, contentType: false, diff --git a/webpack.config.js b/webpack.config.js index 67435cf06db8f..1ac15c1aa3213 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -296,7 +296,7 @@ const config = { return path; }, } ), - new LibraryExportDefaultPlugin( [ 'deprecated', 'dom-ready' ].map( camelCaseDash ) ), + new LibraryExportDefaultPlugin( [ 'deprecated', 'dom-ready', 'api-request' ].map( camelCaseDash ) ), ], stats: { children: false, From 5ce299649d8492b42237750cd70bfbe757a392e1 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 1 Jun 2018 12:00:53 +0100 Subject: [PATCH 3/9] Fix unit tests --- .../with-api-data/test/request.js | 27 +++++++------- editor/store/test/effects.js | 35 +++++++------------ test/unit/setup-mocks.js | 9 +++++ 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/components/higher-order/with-api-data/test/request.js b/components/higher-order/with-api-data/test/request.js index 3c68f050840b8..06e2087a3eb59 100644 --- a/components/higher-order/with-api-data/test/request.js +++ b/components/higher-order/with-api-data/test/request.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import apiRequest from '@wordpress/api-request'; + /** * Internal dependencies */ @@ -25,15 +30,13 @@ describe( 'request', () => { ), }; - let wpApiRequest; beforeEach( () => { getStablePath.clear(); for ( const key in cache ) { delete cache[ key ]; } - wpApiRequest = wp.apiRequest; - wp.apiRequest = jest.fn( () => ( { + apiRequest.mockReturnValue = { // jQuery.Deferred aren't true promises, particularly in their // treatment of resolved arguments. $.ajax will spread resolved // arguments, but this is not valid for Promise (only single). @@ -43,11 +46,7 @@ describe( 'request', () => { 'success', xhr ) ), - } ) ); - } ); - - afterEach( () => { - wp.apiRequest = wpApiRequest; + }; } ); describe( 'getResponseHeaders()', () => { @@ -97,7 +96,7 @@ describe( 'request', () => { } ); return awaitResponse.then( ( data ) => { - expect( wp.apiRequest ).toHaveBeenCalled(); + expect( apiRequest ).toHaveBeenCalled(); expect( data ).toEqual( actualResponse ); } ); } ); @@ -129,6 +128,10 @@ describe( 'request', () => { } ); describe( 'request()', () => { + beforeEach( () => { + apiRequest.mockClear(); + } ); + it( 'should try from cache for GET', () => { cache[ getStablePath( '/wp?c=5&a=5&b=5' ) ] = actualResponse; const awaitResponse = request( { @@ -137,7 +140,7 @@ describe( 'request', () => { } ); return awaitResponse.then( ( data ) => { - expect( wp.apiRequest ).not.toHaveBeenCalled(); + expect( apiRequest ).not.toHaveBeenCalled(); expect( data ).toEqual( actualResponse ); } ); } ); @@ -150,7 +153,7 @@ describe( 'request', () => { } ); return awaitResponse.then( ( data ) => { - expect( wp.apiRequest ).toHaveBeenCalled(); + expect( apiRequest ).toHaveBeenCalled(); expect( data ).toEqual( actualResponse ); } ); } ); @@ -162,7 +165,7 @@ describe( 'request', () => { } ); return awaitResponse.then( ( data ) => { - expect( wp.apiRequest ).toHaveBeenCalled(); + expect( apiRequest ).toHaveBeenCalled(); expect( data ).toEqual( actualResponse ); } ); } ); diff --git a/editor/store/test/effects.js b/editor/store/test/effects.js index 550e6d1323489..83347462e9076 100644 --- a/editor/store/test/effects.js +++ b/editor/store/test/effects.js @@ -12,6 +12,7 @@ import { registerBlockType, createBlock, } from '@wordpress/blocks'; +import apiRequest from '@wordpress/api-request'; /** * Internal dependencies @@ -534,6 +535,10 @@ describe( 'effects', () => { describe( '.FETCH_SHARED_BLOCKS', () => { const handler = effects.FETCH_SHARED_BLOCKS; + afterEach( () => { + jest.unmock( '@wordpress/api-request' ); + } ); + it( 'should fetch multiple shared blocks', () => { const promise = Promise.resolve( [ { @@ -542,9 +547,8 @@ describe( 'effects', () => { content: '', }, ] ); - + apiRequest.mockReturnValue = promise; set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' ); - set( global, [ 'wp', 'apiRequest' ], () => promise ); const dispatch = jest.fn(); const store = { getState: noop, dispatch }; @@ -580,9 +584,8 @@ describe( 'effects', () => { title: 'My cool block', content: '', } ); - + apiRequest.mockReturnValue = promise; set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' ); - set( global, [ 'wp', 'apiRequest' ], () => promise ); const dispatch = jest.fn(); const store = { getState: noop, dispatch }; @@ -614,9 +617,8 @@ describe( 'effects', () => { it( 'should handle an API error', () => { const promise = Promise.reject( {} ); - + apiRequest.mockReturnValue = promise; set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' ); - set( global, [ 'wp', 'apiRequest' ], () => promise ); const dispatch = jest.fn(); const store = { getState: noop, dispatch }; @@ -655,14 +657,10 @@ describe( 'effects', () => { const handler = effects.SAVE_SHARED_BLOCK; it( 'should save a shared block and swap its id', () => { - let modelAttributes; const promise = Promise.resolve( { id: 456 } ); + apiRequest.mockReturnValue = promise; set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' ); - set( global, [ 'wp', 'apiRequest' ], ( request ) => { - modelAttributes = request.data; - return promise; - } ); const sharedBlock = { id: 123, title: 'My cool block' }; const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); @@ -677,12 +675,6 @@ describe( 'effects', () => { handler( saveSharedBlock( 123 ), store ); - expect( modelAttributes ).toEqual( { - id: 123, - title: 'My cool block', - content: '', - } ); - return promise.then( () => { expect( dispatch ).toHaveBeenCalledWith( { type: 'SAVE_SHARED_BLOCK_SUCCESS', @@ -694,9 +686,8 @@ describe( 'effects', () => { it( 'should handle an API error', () => { const promise = Promise.reject( {} ); - + apiRequest.mockReturnValue = promise; set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' ); - set( global, [ 'wp', 'apiRequest' ], () => promise ); const sharedBlock = { id: 123, title: 'My cool block' }; const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); @@ -725,9 +716,8 @@ describe( 'effects', () => { it( 'should delete a shared block', () => { const promise = Promise.resolve( {} ); - + apiRequest.mockReturnValue = promise; set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' ); - set( global, [ 'wp', 'apiRequest' ], () => promise ); const associatedBlock = createBlock( 'core/block', { ref: 123 } ); const sharedBlock = { id: 123, title: 'My cool block' }; @@ -765,9 +755,8 @@ describe( 'effects', () => { it( 'should handle an API error', () => { const promise = Promise.reject( {} ); - + apiRequest.mockReturnValue = promise; set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' ); - set( global, [ 'wp', 'apiRequest' ], () => promise ); const sharedBlock = { id: 123, title: 'My cool block' }; const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); diff --git a/test/unit/setup-mocks.js b/test/unit/setup-mocks.js index 9e6eaec2d1811..fb17fc325cb07 100644 --- a/test/unit/setup-mocks.js +++ b/test/unit/setup-mocks.js @@ -14,3 +14,12 @@ jest.mock( '../../components/button', () => { } }; } ); + +jest.mock( '@wordpress/api-request', () => { + const apiRequest = jest.fn( () => { + return apiRequest.mockReturnValue; + } ); + apiRequest.mockReturnValue = 'mock this value by overriding apiRequest.mockReturnValue'; + + return apiRequest; +} ); From 304a323c72ca2426b0904d44dd6f381d4c5fd7d6 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 1 Jun 2018 12:24:22 +0100 Subject: [PATCH 4/9] Adding unit tests --- packages/api-request/src/index.js | 10 ++--- .../http-v1.js} | 2 +- .../namespace-endpoint.js} | 0 .../nonce.js} | 7 +++- .../preloading.js} | 0 .../root-url.js} | 2 +- .../src/middlewares/test/http-v1.js | 21 ++++++++++ .../middlewares/test/namespace-endpoint.js | 18 ++++++++ .../api-request/src/middlewares/test/nonce.js | 32 +++++++++++++++ .../src/middlewares/test/preloading.js | 41 +++++++++++++++++++ .../src/middlewares/test/root-url.js | 17 ++++++++ 11 files changed, 142 insertions(+), 8 deletions(-) rename packages/api-request/src/{http-v1-middleware.js => middlewares/http-v1.js} (94%) rename packages/api-request/src/{namespace-endpoint-middleware.js => middlewares/namespace-endpoint.js} (100%) rename packages/api-request/src/{nonce-middleware.js => middlewares/nonce.js} (89%) rename packages/api-request/src/{preloading-middleware.js => middlewares/preloading.js} (100%) rename packages/api-request/src/{root-url-middleware.js => middlewares/root-url.js} (98%) create mode 100644 packages/api-request/src/middlewares/test/http-v1.js create mode 100644 packages/api-request/src/middlewares/test/namespace-endpoint.js create mode 100644 packages/api-request/src/middlewares/test/nonce.js create mode 100644 packages/api-request/src/middlewares/test/preloading.js create mode 100644 packages/api-request/src/middlewares/test/root-url.js diff --git a/packages/api-request/src/index.js b/packages/api-request/src/index.js index 1695910741336..081ff2c7a445e 100644 --- a/packages/api-request/src/index.js +++ b/packages/api-request/src/index.js @@ -6,11 +6,11 @@ import jQuery from 'jquery'; /** * Internal dependencies */ -import createNonceMiddleware from './nonce-middleware'; -import createRootURLMiddleware from './root-url-middleware'; -import createPreloadingMiddleware from './preloading-middleware'; -import namespaceEndpointMiddleware from './namespace-endpoint-middleware'; -import httpV1Middleware from './http-v1-middleware'; +import createNonceMiddleware from './middlewares/nonce'; +import createRootURLMiddleware from './middlewares/root-url'; +import createPreloadingMiddleware from './middlewares/preloading'; +import namespaceEndpointMiddleware from './middlewares/namespace-endpoint'; +import httpV1Middleware from './middlewares/http-v1'; const middlewares = []; diff --git a/packages/api-request/src/http-v1-middleware.js b/packages/api-request/src/middlewares/http-v1.js similarity index 94% rename from packages/api-request/src/http-v1-middleware.js rename to packages/api-request/src/middlewares/http-v1.js index b9552bafa8210..d911a0c9cbd6a 100644 --- a/packages/api-request/src/http-v1-middleware.js +++ b/packages/api-request/src/middlewares/http-v1.js @@ -3,7 +3,7 @@ function httpV1Middleware( options, next ) { if ( newOptions.method ) { if ( [ 'PATCH', 'PUT', 'DELETE' ].indexOf( newOptions.method.toUpperCase() ) >= 0 ) { if ( ! newOptions.headers ) { - options.headers = {}; + newOptions.headers = {}; } newOptions.headers[ 'X-HTTP-Method-Override' ] = newOptions.method; newOptions.method = 'POST'; diff --git a/packages/api-request/src/namespace-endpoint-middleware.js b/packages/api-request/src/middlewares/namespace-endpoint.js similarity index 100% rename from packages/api-request/src/namespace-endpoint-middleware.js rename to packages/api-request/src/middlewares/namespace-endpoint.js diff --git a/packages/api-request/src/nonce-middleware.js b/packages/api-request/src/middlewares/nonce.js similarity index 89% rename from packages/api-request/src/nonce-middleware.js rename to packages/api-request/src/middlewares/nonce.js index 372881f8ffd3f..0ad11c0424ff9 100644 --- a/packages/api-request/src/nonce-middleware.js +++ b/packages/api-request/src/middlewares/nonce.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import jQuery from 'jquery'; + const createNonceMiddleware = ( nonce ) => ( options, next ) => { let usedNonce = nonce; /** @@ -6,7 +11,7 @@ const createNonceMiddleware = ( nonce ) => ( options, next ) => { * Configure heartbeat to refresh the wp-api nonce, keeping the editor * authorization intact. */ - window.jQuery( document ).on( 'heartbeat-tick', ( event, response ) => { + jQuery( document ).on( 'heartbeat-tick', ( event, response ) => { if ( response[ 'rest-nonce' ] ) { usedNonce = response[ 'rest-nonce' ]; } diff --git a/packages/api-request/src/preloading-middleware.js b/packages/api-request/src/middlewares/preloading.js similarity index 100% rename from packages/api-request/src/preloading-middleware.js rename to packages/api-request/src/middlewares/preloading.js diff --git a/packages/api-request/src/root-url-middleware.js b/packages/api-request/src/middlewares/root-url.js similarity index 98% rename from packages/api-request/src/root-url-middleware.js rename to packages/api-request/src/middlewares/root-url.js index 8d48e798525ee..d4746d83edb54 100644 --- a/packages/api-request/src/root-url-middleware.js +++ b/packages/api-request/src/middlewares/root-url.js @@ -1,4 +1,4 @@ -import namespaceAndEndpointMiddleware from './namespace-endpoint-middleware'; +import namespaceAndEndpointMiddleware from './namespace-endpoint'; const createRootURLMiddleware = ( rootURL ) => ( options, next ) => { return namespaceAndEndpointMiddleware( options, ( optionsWithPath ) => { diff --git a/packages/api-request/src/middlewares/test/http-v1.js b/packages/api-request/src/middlewares/test/http-v1.js new file mode 100644 index 0000000000000..ede9866b5566d --- /dev/null +++ b/packages/api-request/src/middlewares/test/http-v1.js @@ -0,0 +1,21 @@ +import httpV1Middleware from '../http-v1'; + +describe( 'HTTP v1 Middleware', () => { + it( 'should use a POST for a PUT requests', () => { + const callback = ( options ) => { + expect( options.method ).toBe( 'POST' ); + expect( options.headers[ 'X-HTTP-Method-Override' ] ).toBe( 'PUT' ); + }; + + httpV1Middleware( { method: 'PUT', data: {} }, callback ); + } ); + + it( 'shouldn\'t touch the options for GET requests', () => { + const requestOptions = { method: 'GET', path: '/wp/v2/posts' }; + const callback = ( options ) => { + expect( options ).toEqual( requestOptions ); + }; + + httpV1Middleware( requestOptions, callback ); + } ); +} ); diff --git a/packages/api-request/src/middlewares/test/namespace-endpoint.js b/packages/api-request/src/middlewares/test/namespace-endpoint.js new file mode 100644 index 0000000000000..9a676529d190e --- /dev/null +++ b/packages/api-request/src/middlewares/test/namespace-endpoint.js @@ -0,0 +1,18 @@ +import namespaceEndpointMiddleware from '../namespace-endpoint'; + +describe( 'Namespace & Endpoint middleware', () => { + it( 'should concat the endpoint and namespace into a path property', () => { + const requestOptions = { + method: 'GET', + namespace: '/wp/v2', + endpoint: '/posts', + }; + const callback = ( options ) => { + expect( options.path ).toBe( 'wp/v2/posts' ); + expect( options.namespace ).toBeUndefined(); + expect( options.endpoint ).toBeUndefined(); + }; + + namespaceEndpointMiddleware( requestOptions, callback ); + } ); +} ); diff --git a/packages/api-request/src/middlewares/test/nonce.js b/packages/api-request/src/middlewares/test/nonce.js new file mode 100644 index 0000000000000..5568220c4566d --- /dev/null +++ b/packages/api-request/src/middlewares/test/nonce.js @@ -0,0 +1,32 @@ +import createNonceMiddleware from '../nonce'; + +describe( 'Nonce middleware', () => { + it( 'should add a nonce header to the request', () => { + const nonce = 'nonce'; + const nonceMiddleware = createNonceMiddleware( nonce ); + const requestOptions = { + method: 'GET', + path: '/wp/v2/posts', + }; + const callback = ( options ) => { + expect( options.headers[ 'X-WP-Nonce' ] ).toBe( nonce ); + }; + + nonceMiddleware( requestOptions, callback ); + } ); + + it( 'should not add a nonce header to requests with nonces', () => { + const nonce = 'nonce'; + const nonceMiddleware = createNonceMiddleware( nonce ); + const requestOptions = { + method: 'GET', + path: '/wp/v2/posts', + headers: { 'X-WP-Nonce': 'existing nonce' }, + }; + const callback = ( options ) => { + expect( options.headers[ 'X-WP-Nonce' ] ).toBe( 'existing nonce' ); + }; + + nonceMiddleware( requestOptions, callback ); + } ); +} ); diff --git a/packages/api-request/src/middlewares/test/preloading.js b/packages/api-request/src/middlewares/test/preloading.js new file mode 100644 index 0000000000000..255ce90ad162f --- /dev/null +++ b/packages/api-request/src/middlewares/test/preloading.js @@ -0,0 +1,41 @@ +import createPreloadingMiddleware from '../preloading'; + +describe( 'Preloading Middleware', () => { + it( 'should return the preloaded data if provided', () => { + const body = { + status: 'this is the preloaded response', + }; + const preloadedData = { + 'wp/v2/posts': { + body, + }, + }; + const prelooadingMiddleware = createPreloadingMiddleware( preloadedData ); + const requestOptions = { + method: 'GET', + path: 'wp/v2/posts', + }; + + const response = prelooadingMiddleware( requestOptions ); + response.then( ( value ) => { + expect( value ).toEqual( body ); + } ); + } ); + + it( 'should move to the next middleware if no preloaded data', () => { + const preloadedData = {}; + const prelooadingMiddleware = createPreloadingMiddleware( preloadedData ); + const requestOptions = { + method: 'GET', + path: 'wp/v2/posts', + }; + + const callback = ( options ) => { + expect( options ).toBe( requestOptions ); + return true; + }; + + const ret = prelooadingMiddleware( requestOptions, callback ); + expect( ret ).toBe( true ); + } ); +} ); diff --git a/packages/api-request/src/middlewares/test/root-url.js b/packages/api-request/src/middlewares/test/root-url.js new file mode 100644 index 0000000000000..5c6d6165a44ce --- /dev/null +++ b/packages/api-request/src/middlewares/test/root-url.js @@ -0,0 +1,17 @@ +import createRootUrlMiddleware from '../root-url'; + +describe( 'Root URL middleware', () => { + it( 'should append the root URL', () => { + const rootURL = 'http://wp.org/wp-admin/rest/'; + const rootURLMiddleware = createRootUrlMiddleware( rootURL ); + const requestOptions = { + method: 'GET', + path: '/wp/v2/posts', + }; + const callback = ( options ) => { + expect( options.url ).toBe( 'http://wp.org/wp-admin/rest/wp/v2/posts' ); + }; + + rootURLMiddleware( requestOptions, callback ); + } ); +} ); From 60fa651aa46bf57b3925084108b0cb5e8cd60830 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 5 Jun 2018 12:03:40 +0100 Subject: [PATCH 5/9] Adding internal dependencies comment --- packages/api-request/src/middlewares/root-url.js | 3 +++ packages/api-request/src/middlewares/test/http-v1.js | 3 +++ .../api-request/src/middlewares/test/namespace-endpoint.js | 3 +++ packages/api-request/src/middlewares/test/nonce.js | 3 +++ packages/api-request/src/middlewares/test/preloading.js | 3 +++ packages/api-request/src/middlewares/test/root-url.js | 3 +++ 6 files changed, 18 insertions(+) diff --git a/packages/api-request/src/middlewares/root-url.js b/packages/api-request/src/middlewares/root-url.js index d4746d83edb54..92b50523642fc 100644 --- a/packages/api-request/src/middlewares/root-url.js +++ b/packages/api-request/src/middlewares/root-url.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import namespaceAndEndpointMiddleware from './namespace-endpoint'; const createRootURLMiddleware = ( rootURL ) => ( options, next ) => { diff --git a/packages/api-request/src/middlewares/test/http-v1.js b/packages/api-request/src/middlewares/test/http-v1.js index ede9866b5566d..1bdf6c71a6d98 100644 --- a/packages/api-request/src/middlewares/test/http-v1.js +++ b/packages/api-request/src/middlewares/test/http-v1.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import httpV1Middleware from '../http-v1'; describe( 'HTTP v1 Middleware', () => { diff --git a/packages/api-request/src/middlewares/test/namespace-endpoint.js b/packages/api-request/src/middlewares/test/namespace-endpoint.js index 9a676529d190e..910492f938137 100644 --- a/packages/api-request/src/middlewares/test/namespace-endpoint.js +++ b/packages/api-request/src/middlewares/test/namespace-endpoint.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import namespaceEndpointMiddleware from '../namespace-endpoint'; describe( 'Namespace & Endpoint middleware', () => { diff --git a/packages/api-request/src/middlewares/test/nonce.js b/packages/api-request/src/middlewares/test/nonce.js index 5568220c4566d..5da48dfda641d 100644 --- a/packages/api-request/src/middlewares/test/nonce.js +++ b/packages/api-request/src/middlewares/test/nonce.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import createNonceMiddleware from '../nonce'; describe( 'Nonce middleware', () => { diff --git a/packages/api-request/src/middlewares/test/preloading.js b/packages/api-request/src/middlewares/test/preloading.js index 255ce90ad162f..9ca52588b995d 100644 --- a/packages/api-request/src/middlewares/test/preloading.js +++ b/packages/api-request/src/middlewares/test/preloading.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import createPreloadingMiddleware from '../preloading'; describe( 'Preloading Middleware', () => { diff --git a/packages/api-request/src/middlewares/test/root-url.js b/packages/api-request/src/middlewares/test/root-url.js index 5c6d6165a44ce..40bdea3f6852d 100644 --- a/packages/api-request/src/middlewares/test/root-url.js +++ b/packages/api-request/src/middlewares/test/root-url.js @@ -1,3 +1,6 @@ +/** + * Internal dependencies + */ import createRootUrlMiddleware from '../root-url'; describe( 'Root URL middleware', () => { From 77a822020a1a470411ce3b75add0f5fcdafca875 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 5 Jun 2018 12:13:11 +0100 Subject: [PATCH 6/9] Improve unit tests --- packages/api-request/src/middlewares/preloading.js | 1 + packages/api-request/src/middlewares/test/http-v1.js | 4 ++++ .../api-request/src/middlewares/test/namespace-endpoint.js | 2 ++ packages/api-request/src/middlewares/test/nonce.js | 4 ++++ packages/api-request/src/middlewares/test/preloading.js | 5 +++-- packages/api-request/src/middlewares/test/root-url.js | 2 ++ 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/api-request/src/middlewares/preloading.js b/packages/api-request/src/middlewares/preloading.js index 73491fa8fe609..db1fae853e4a3 100644 --- a/packages/api-request/src/middlewares/preloading.js +++ b/packages/api-request/src/middlewares/preloading.js @@ -39,6 +39,7 @@ const createPreloadingMiddleware = ( preloadedData ) => ( options, next ) => { if ( 'GET' === method && preloadedData[ path ] ) { const deferred = jQuery.Deferred(); deferred.resolve( preloadedData[ path ].body ); + return deferred.promise(); } } diff --git a/packages/api-request/src/middlewares/test/http-v1.js b/packages/api-request/src/middlewares/test/http-v1.js index 1bdf6c71a6d98..00873ad83756c 100644 --- a/packages/api-request/src/middlewares/test/http-v1.js +++ b/packages/api-request/src/middlewares/test/http-v1.js @@ -5,6 +5,8 @@ import httpV1Middleware from '../http-v1'; describe( 'HTTP v1 Middleware', () => { it( 'should use a POST for a PUT requests', () => { + expect.hasAssertions(); + const callback = ( options ) => { expect( options.method ).toBe( 'POST' ); expect( options.headers[ 'X-HTTP-Method-Override' ] ).toBe( 'PUT' ); @@ -14,6 +16,8 @@ describe( 'HTTP v1 Middleware', () => { } ); it( 'shouldn\'t touch the options for GET requests', () => { + expect.hasAssertions(); + const requestOptions = { method: 'GET', path: '/wp/v2/posts' }; const callback = ( options ) => { expect( options ).toEqual( requestOptions ); diff --git a/packages/api-request/src/middlewares/test/namespace-endpoint.js b/packages/api-request/src/middlewares/test/namespace-endpoint.js index 910492f938137..9e4b088f6089f 100644 --- a/packages/api-request/src/middlewares/test/namespace-endpoint.js +++ b/packages/api-request/src/middlewares/test/namespace-endpoint.js @@ -5,6 +5,8 @@ import namespaceEndpointMiddleware from '../namespace-endpoint'; describe( 'Namespace & Endpoint middleware', () => { it( 'should concat the endpoint and namespace into a path property', () => { + expect.hasAssertions(); + const requestOptions = { method: 'GET', namespace: '/wp/v2', diff --git a/packages/api-request/src/middlewares/test/nonce.js b/packages/api-request/src/middlewares/test/nonce.js index 5da48dfda641d..7c0dde16845f0 100644 --- a/packages/api-request/src/middlewares/test/nonce.js +++ b/packages/api-request/src/middlewares/test/nonce.js @@ -5,6 +5,8 @@ import createNonceMiddleware from '../nonce'; describe( 'Nonce middleware', () => { it( 'should add a nonce header to the request', () => { + expect.hasAssertions(); + const nonce = 'nonce'; const nonceMiddleware = createNonceMiddleware( nonce ); const requestOptions = { @@ -19,6 +21,8 @@ describe( 'Nonce middleware', () => { } ); it( 'should not add a nonce header to requests with nonces', () => { + expect.hasAssertions(); + const nonce = 'nonce'; const nonceMiddleware = createNonceMiddleware( nonce ); const requestOptions = { diff --git a/packages/api-request/src/middlewares/test/preloading.js b/packages/api-request/src/middlewares/test/preloading.js index 9ca52588b995d..ecc8d50d00d91 100644 --- a/packages/api-request/src/middlewares/test/preloading.js +++ b/packages/api-request/src/middlewares/test/preloading.js @@ -4,7 +4,7 @@ import createPreloadingMiddleware from '../preloading'; describe( 'Preloading Middleware', () => { - it( 'should return the preloaded data if provided', () => { + it( 'should return the preloaded data if provided', ( done ) => { const body = { status: 'this is the preloaded response', }; @@ -20,8 +20,9 @@ describe( 'Preloading Middleware', () => { }; const response = prelooadingMiddleware( requestOptions ); - response.then( ( value ) => { + response.done( ( value ) => { expect( value ).toEqual( body ); + done(); } ); } ); diff --git a/packages/api-request/src/middlewares/test/root-url.js b/packages/api-request/src/middlewares/test/root-url.js index 40bdea3f6852d..3480cddc532b0 100644 --- a/packages/api-request/src/middlewares/test/root-url.js +++ b/packages/api-request/src/middlewares/test/root-url.js @@ -5,6 +5,8 @@ import createRootUrlMiddleware from '../root-url'; describe( 'Root URL middleware', () => { it( 'should append the root URL', () => { + expect.hasAssertions(); + const rootURL = 'http://wp.org/wp-admin/rest/'; const rootURLMiddleware = createRootUrlMiddleware( rootURL ); const requestOptions = { From c6b374468a9f7a6216472db2668547965c42fdc4 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 6 Jun 2018 10:29:00 +0100 Subject: [PATCH 7/9] Restore wpApiSettings global for wp-api --- .eslintrc.js | 2 +- components/code-editor/index.js | 4 ++-- editor/components/provider/index.js | 2 +- lib/client-assets.php | 7 ++++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c1c608b9f2a69..016ceb1dded4e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,7 +26,7 @@ module.exports = { 'jest/globals': true, }, globals: { - wpApiSchema: true, + wpApiSettings: true, }, plugins: [ 'jest', diff --git a/components/code-editor/index.js b/components/code-editor/index.js index bac7580e58065..546f0afe67329 100644 --- a/components/code-editor/index.js +++ b/components/code-editor/index.js @@ -21,7 +21,7 @@ function loadScript() { } const script = document.createElement( 'script' ); - script.src = `${ wpApiSchema.url }/wp-admin/load-scripts.php?load=${ handles.join( ',' ) }`; + script.src = `${ wpApiSettings.schema.url }/wp-admin/load-scripts.php?load=${ handles.join( ',' ) }`; script.onload = resolve; script.onerror = reject; @@ -35,7 +35,7 @@ function loadStyle() { const style = document.createElement( 'link' ); style.rel = 'stylesheet'; - style.href = `${ wpApiSchema.url }/wp-admin/load-styles.php?load=${ handles.join( ',' ) }`; + style.href = `${ wpApiSettings.schema.url }/wp-admin/load-styles.php?load=${ handles.join( ',' ) }`; style.onload = resolve; style.onerror = reject; diff --git a/editor/components/provider/index.js b/editor/components/provider/index.js index 1326ff7b6cdc8..788c29ea4df4f 100644 --- a/editor/components/provider/index.js +++ b/editor/components/provider/index.js @@ -75,7 +75,7 @@ class EditorProvider extends Component { [ APIProvider, { - schema: wpApiSchema, + ...wpApiSettings, ...pick( wp.api, [ 'postTypeRestBaseMapping', 'taxonomyRestBaseMapping', diff --git a/lib/client-assets.php b/lib/client-assets.php index 5953ce3838b7c..2835cc187ece3 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -789,12 +789,17 @@ function gutenberg_extend_wp_api_backbone_client() { }; JS; wp_add_inline_script( 'wp-api', $script ); + wp_localize_script( 'wp-api', 'wpApiSettings', array( + 'root' => esc_url_raw( get_rest_url() ), + 'nonce' => ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' ), + 'versionString' => 'wp/v2/', + ) ); // Localize the wp-api settings and schema. $schema_response = rest_do_request( new WP_REST_Request( 'GET', '/' ) ); if ( ! $schema_response->is_error() ) { wp_add_inline_script( 'wp-api', sprintf( - 'wpApiSchema = %s;', + 'wpApiSettings.cacheSchema = true; wpApiSettings.schema = %s;', wp_json_encode( $schema_response->get_data() ) ), 'before' ); } From 3767199cc7ab0e2a64efa911b96c1512b6980ac7 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 6 Jun 2018 10:42:16 +0100 Subject: [PATCH 8/9] Lint package.json --- packages/api-request/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api-request/package.json b/packages/api-request/package.json index 50d0f35614ce2..222440f1c9083 100644 --- a/packages/api-request/package.json +++ b/packages/api-request/package.json @@ -2,7 +2,7 @@ "name": "@wordpress/api-request", "version": "1.0.0", "description": "Utility to call WordPress REST APIs", - "author": "WordPress", + "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "keywords": [ "wordpress", @@ -19,10 +19,10 @@ }, "main": "build/index.js", "module": "build-module/index.js", - "publishConfig": { - "access": "public" - }, "dependencies": { "jquery": "^3.3.1" + }, + "publishConfig": { + "access": "public" } } From 0724e4e6a118f5d39cb0b1b9bce560236d89379f Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 7 Jun 2018 15:57:00 +0100 Subject: [PATCH 9/9] Align jQuery versions --- package-lock.json | 6 +++--- package.json | 2 +- packages/api-request/package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f411c53c5d8e8..ef49550bd7b68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8468,9 +8468,9 @@ } }, "jquery": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz", - "integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c=" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" }, "js-base64": { "version": "2.4.5", diff --git a/package.json b/package.json index be13d72649d76..23d785f91b85d 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "escape-string-regexp": "1.0.5", "eslint-plugin-wordpress": "git://github.com/WordPress-Coding-Standards/eslint-plugin-wordpress.git#1774343f6226052a46b081e01db3fca8793cc9f1", "hpq": "1.2.0", - "jquery": "3.2.1", + "jquery": "3.3.1", "js-beautify": "1.6.14", "lerna": "2.11.0", "lodash": "4.17.5", diff --git a/packages/api-request/package.json b/packages/api-request/package.json index 222440f1c9083..56613a7c7e7a1 100644 --- a/packages/api-request/package.json +++ b/packages/api-request/package.json @@ -20,7 +20,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { - "jquery": "^3.3.1" + "jquery": "3.3.1" }, "publishConfig": { "access": "public"