Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One-click site verification and search console integration #10041

Merged
merged 4 commits into from
Sep 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions _inc/client/components/forms/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@
}
}

.jp-form-input-suffix {
text-align: center;
background: #f3f6f8;
border: 1px solid #c8d7e1;
color: #4f748e;
padding: rem( 8px ) rem( 14px );
white-space: nowrap;
flex: 1 0 auto;
display: flex;
}

button.jp-form-input-suffix {
background: initial;
border-radius: 0;
}

.jp-form-has-child {
margin-bottom: rem( 24px );

Expand Down
27 changes: 27 additions & 0 deletions _inc/client/lib/popup-monitor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Popup Monitor
=============

Popup Monitor is a small utility to facilitate the monitoring of a popup window close action, which is especially useful for temporary popup windows (e.g. an authorization step).

## Usage

A Popup Monitor instance offers an `open` function which accepts an identical set of arguments as the standard `window.open` browser offering. When the window is closed, a `close` event is emitted to the instance with the name of the closed window.

```js
import PopupMonitor 'lib/popup-monitor';

const popupMonitor = new PopupMonitor();

popupMonitor.open( 'https://wordpress.com/', 'my-popup' );

popupMonitor.on( 'close', function( name ) {
if ( 'my-popup' === name ) {
console.log( 'Window closed!' );
}
} );
```

## Methods

- `open( url, name, features )`: Proxies an identical call to `window.open` and begins to monitor the window open state.
- `getScreenCenterSpecs( width, height )`: A helper method for generating a feature (specification) string of a specific width and height at the center of the user's screen.
126 changes: 126 additions & 0 deletions _inc/client/lib/popup-monitor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/** @format */

/**
* External dependencies
*/

/**
* Internal dependencies
*/
import Emitter from 'mixins/emitter';

/**
* PopupMonitor component
*
* @public
*/
function PopupMonitor() {
this.intervals = {};
this.monitorInterval = null;
this.windowInstance = null;
this.onMessage = messageEvent => {
if ( messageEvent.source === this.windowInstance ) {
this.emit( 'message', messageEvent.data );
}
};
}

Emitter( PopupMonitor.prototype );

/**
* Opens a new popup and starts monitoring it for changes. This should only be
* invoked on a user action to avoid the popup being blocked. Returns the
* current instance of PopupMonitor to enable chainability
*
* @param {string} url The URL to be loaded in the newly opened window
* @param {string} name A string name for the new window
* @param {string} specs An optional parameter listing the features of the new window as a string
* @return {PopupMonitor} this instance
* @public
*/
PopupMonitor.prototype.open = function( url, name, specs ) {
name = name || Date.now();

this.windowInstance = window.open( url, name, specs );
this.startMonitoring( name, this.windowInstance );

window.addEventListener( 'message', this.onMessage, false );

return this;
};

/**
* Returns a popup window specification string fragment containing properties
* to visually center the popup on the user's current screen.
*
* @param {number} width The desired width of the popup
* @param {number} height The desired height of the popup
* @return {string} Popup window specificatino string fragment
* @public
*/
PopupMonitor.prototype.getScreenCenterSpecs = function( width, height ) {
const screenTop = typeof window.screenTop !== 'undefined' ? window.screenTop : window.screenY,
screenLeft = typeof window.screenLeft !== 'undefined' ? window.screenLeft : window.screenX;

return [
'width=' + width,
'height=' + height,
'top=' + ( screenTop + window.innerHeight / 2 - height / 2 ),
'left=' + ( screenLeft + window.innerWidth / 2 - width / 2 ),
].join();
};

/**
* Returns true if the popup with the specified name is closed, or false
* otherwise
*
* @param {string} name The name of the popup window to check
* @return {boolean} result
* @public
*/
PopupMonitor.prototype.isOpen = function( name ) {
let isClosed = false;

try {
isClosed = this.intervals[ name ] && this.intervals[ name ].closed;
} catch ( e ) {}

return ! isClosed;
};

/**
* Detects if any popup windows have closed since the last interval run and
* triggers a close event for any closed windows. If no popup windows remain
* open, then the interval is stopped.
*/
PopupMonitor.prototype.checkStatus = function() {
for ( const name in this.intervals ) {
if ( this.intervals.hasOwnProperty( name ) && ! this.isOpen( name ) ) {
this.emit( 'close', name );
delete this.intervals[ name ];
}
}

if ( 0 === Object.keys( this.intervals ).length ) {
clearInterval( this.monitorInterval );
delete this.monitorInterval;
window.removeEventListener( 'message', this.onMessage );
}
};

/**
* Starts monitoring a popup window instance for changes on a recurring
* interval.
*
* @param {string} name The name of hte popup window to monitor
* @param {window} windowInstance The popup window instance
*/
PopupMonitor.prototype.startMonitoring = function( name, windowInstance ) {
if ( ! this.monitorInterval ) {
this.monitorInterval = setInterval( this.checkStatus.bind( this ), 100 );
}

this.intervals[ name ] = windowInstance;
};

export default PopupMonitor;
14 changes: 14 additions & 0 deletions _inc/client/lib/sharing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Sharing
=========

Small utility library for requesting authorization of sharing services.

## Usage

```es6
import requestExternalAccess from 'lib/sharing';

requestExternalAccess( service.connect_URL, () => {
// Do something after the authorization window has closed
} );
```
27 changes: 27 additions & 0 deletions _inc/client/lib/sharing/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Internal dependencies
*/
import PopupMonitor from 'lib/popup-monitor';

const requestExternalAccess = ( url, cb ) => {
const popupMonitor = new PopupMonitor();
let lastMessage;

popupMonitor.open(
url,
null,
'toolbar=0,location=0,status=0,menubar=0,' + popupMonitor.getScreenCenterSpecs( 780, 700 )
);

popupMonitor.once( 'close', () => {
let keyringId = null;
if ( lastMessage && lastMessage.keyring_id ) {
keyringId = Number( lastMessage.keyring_id );
}
cb( keyringId );
} );

popupMonitor.on( 'message', message => ( lastMessage = message ) );
};

export default requestExternalAccess;
8 changes: 8 additions & 0 deletions _inc/client/rest-api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,14 @@ function JetpackRestApiClient( root, nonce ) {
.then( parseJsonResponse ),

fetchPluginsData: () => getRequest( `${ apiRoot }jetpack/v4/plugins`, getParams )
.then( checkStatus )
.then( parseJsonResponse ),

fetchVerifySiteGoogleStatus: () => getRequest( `${ apiRoot }jetpack/v4/verify-site/google`, getParams )
.then( checkStatus )
.then( parseJsonResponse ),

verifySiteGoogle: () => postRequest( `${ apiRoot }jetpack/v4/verify-site/google`, postParams )
.then( checkStatus )
.then( parseJsonResponse )
};
Expand Down
1 change: 1 addition & 0 deletions _inc/client/scss/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@
@import '../at-a-glance/style';
@import '../plans/style';
@import '../settings/style';
@import '../traffic/style';
11 changes: 11 additions & 0 deletions _inc/client/state/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,14 @@ export const REWIND_STATUS_FETCH_RECEIVE = 'REWIND_STATUS_FETCH_RECEIVE';
export const REWIND_STATUS_FETCH_FAIL = 'REWIND_STATUS_FETCH_FAIL';

export const MOCK_SWITCH_REWIND_STATE = 'MOCK_SWITCH_REWIND_STATE';

export const JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH = 'JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH';
export const JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH_FAIL = 'JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH_FAIL';
export const JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH_SUCCESS = 'JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH_SUCCESS';

export const JETPACK_SITE_VERIFY_GOOGLE_VERIFY_FETCH = 'JETPACK_SITE_VERIFY_GOOGLE_VERIFY_FETCH';
export const JETPACK_SITE_VERIFY_GOOGLE_VERIFY_FETCH_FAIL = 'JETPACK_SITE_VERIFY_GOOGLE_VERIFY_FETCH_FAIL';
export const JETPACK_SITE_VERIFY_GOOGLE_VERIFY_FETCH_SUCCESS = 'JETPACK_SITE_VERIFY_GOOGLE_VERIFY_FETCH_SUCCESS';
export const JETPACK_SITE_VERIFY_GOOGLE_REQUEST = 'JETPACK_SITE_VERIFY_GOOGLE_REQUEST';
export const JETPACK_SITE_VERIFY_GOOGLE_REQUEST_SUCCESS = 'JETPACK_SITE_VERIFY_GOOGLE_REQUEST_SUCCESS';
export const JETPACK_SITE_VERIFY_GOOGLE_REQUEST_FAIL = 'JETPACK_SITE_VERIFY_GOOGLE_REQUEST_FAIL';
6 changes: 6 additions & 0 deletions _inc/client/state/publicize/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Internal dependencies
*/
import * as reducer from './reducer';

export default reducer;
37 changes: 37 additions & 0 deletions _inc/client/state/publicize/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* External dependencies
*/
import { combineReducers } from 'redux';
import get from 'lodash/get';
import assign from 'lodash/assign';

/**
* Internal dependencies
*/
import {
JETPACK_SET_INITIAL_STATE,
} from 'state/action-types';

export const connectUrls = ( state = {}, action ) => {
switch ( action.type ) {
case JETPACK_SET_INITIAL_STATE:
return assign( {}, action.initialState.externalServicesConnectUrls );
default:
return state;
}
};

export const reducer = combineReducers( {
connectUrls,
} );

/**
* Return a connect url for a given service name.
*
* @param {Object} state Global state tree.
* @param {String} serviceName Name of the external service.
* @return {String} Url to connect to the service or null.
*/
export function getExternalServiceConnectUrl( state, serviceName ) {
return get( state.jetpack.publicize.connectUrls, serviceName, null );
}
6 changes: 5 additions & 1 deletion _inc/client/state/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { reducer as pluginsData } from 'state/site/plugins/reducer';
import { reducer as jetpackNotices } from 'state/jetpack-notices/reducer';
import { reducer as search } from 'state/search/reducer';
import { reducer as devCard } from 'state/dev-version/reducer';
import { reducer as publicize } from 'state/publicize/reducer';
import { reducer as siteVerify } from 'state/site-verify/reducer';

const jetpackReducer = combineReducers( {
initialState,
Expand All @@ -35,7 +37,9 @@ const jetpackReducer = combineReducers( {
jetpackNotices,
pluginsData,
search,
devCard
devCard,
publicize,
siteVerify
} );

export default combineReducers( {
Expand Down
67 changes: 67 additions & 0 deletions _inc/client/state/site-verify/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Internal dependencies
*/
import {
JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH,
JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH_FAIL,
JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH_SUCCESS,
JETPACK_SITE_VERIFY_GOOGLE_REQUEST,
JETPACK_SITE_VERIFY_GOOGLE_REQUEST_SUCCESS,
JETPACK_SITE_VERIFY_GOOGLE_REQUEST_FAIL,
} from 'state/action-types';

import restApi from 'rest-api';
import { translate as __ } from 'i18n-calypso';
import { createNotice } from 'components/global-notices/state/notices/actions';

export const checkVerifyStatusGoogle = () => {
return ( dispatch ) => {
dispatch( {
type: JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH
} );
return restApi.fetchVerifySiteGoogleStatus().then( data => {
dispatch( {
type: JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH_SUCCESS,
verified: data.verified,
token: data.token
} );

return data;
} ).catch( error => {
dispatch( {
type: JETPACK_SITE_VERIFY_GOOGLE_STATUS_FETCH_FAIL,
error: error.response,
} );

return Promise.reject( error.response );
} );
};
};

export const verifySiteGoogle = () => {
return ( dispatch ) => {
dispatch( {
type: JETPACK_SITE_VERIFY_GOOGLE_REQUEST
} );
return restApi.verifySiteGoogle().then( data => {
dispatch( {
verified: data.verified,
type: JETPACK_SITE_VERIFY_GOOGLE_REQUEST_SUCCESS,
} );

if ( data.verified ) {
dispatch( createNotice( 'is-success', __( 'Site is verified' ), { id: 'verify-site-google-verified', duration: 2000 } ) );
}

return data;
} ).catch( error => {
dispatch( {
type: JETPACK_SITE_VERIFY_GOOGLE_REQUEST_FAIL,
error: error.response,
} );

return Promise.reject( error.response );
} );
};
};

9 changes: 9 additions & 0 deletions _inc/client/state/site-verify/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Internal dependencies
*/
import * as reducer from './reducer';
import * as actions from './actions';

const all = { ...reducer, ...actions };

export default all;
Loading