Skip to content

Commit

Permalink
Merge pull request #10041 from Automattic/try/one-click-site-verify
Browse files Browse the repository at this point in the history
One-click site verification and search console integration
  • Loading branch information
Tug authored Sep 13, 2018
2 parents ba3a757 + 8481989 commit f536cd1
Show file tree
Hide file tree
Showing 20 changed files with 685 additions and 3 deletions.
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

0 comments on commit f536cd1

Please sign in to comment.