Heartbeat API: first run, see #23216

git-svn-id: http://core.svn.wordpress.org/trunk@23355 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Andrew Ozz 2013-01-29 06:15:25 +00:00
parent 28f6c5bcf4
commit 98bf511b56
6 changed files with 232 additions and 2 deletions

View File

@ -56,7 +56,7 @@ $core_actions_post = array(
'save-widget', 'set-post-thumbnail', 'date_format', 'time_format', 'wp-fullscreen-save-post',
'wp-remove-post-lock', 'dismiss-wp-pointer', 'upload-attachment', 'get-attachment',
'query-attachments', 'save-attachment', 'save-attachment-compat', 'send-link-to-editor',
'send-attachment-to-editor', 'save-attachment-order',
'send-attachment-to-editor', 'save-attachment-order', 'heartbeat',
);
// Register core Ajax calls.

View File

@ -2071,3 +2071,32 @@ function wp_ajax_send_link_to_editor() {
wp_send_json_success( $html );
}
function wp_ajax_heartbeat() {
check_ajax_referer( 'heartbeat-nonce', '_nonce' );
$response = array( 'pagenow' => '' );
if ( ! empty($_POST['pagenow']) )
$response['pagenow'] = sanitize_key($_POST['pagenow']);
if ( ! empty($_POST['data']) ) {
$data = (array) $_POST['data'];
// todo: how much to sanitize and preset and what to leave to be accessed from $data or $_POST..?
$user = wp_get_current_user();
$data['user_id'] = $user->exists() ? $user->ID : 0;
// todo: separate filters: 'heartbeat_[action]' so we call different callbacks only when there is data for them,
// or all callbacks listen to one filter and run when there is something for them in $data?
$response = apply_filters( 'heartbeat_received', $response, $data );
}
$response = apply_filters( 'heartbeat_send', $response );
// Allow the transport to be replaced with long-polling easily
do_action( 'heartbeat_tick', $response );
// always send the current time acording to the server
$response['time'] = time();
wp_send_json($response);
}

View File

@ -290,4 +290,7 @@ add_filter( 'default_option_link_manager_enabled', '__return_true' );
// This option no longer exists; tell plugins we always support auto-embedding.
add_filter( 'default_option_embed_autourls', '__return_true' );
// Default settings for heartbeat
add_filter( 'heartbeat_settings', 'wp_heartbeat_settings' );
unset($filter, $action);

View File

@ -2284,3 +2284,18 @@ function __checked_selected_helper( $helper, $current, $echo, $type ) {
return $result;
}
/**
* Default settings for heartbeat
*
* Outputs the nonce used in the heartbeat XHR
*
* @since 3.6.0
*
* @param array $settings
* @return array $settings
*/
function wp_heartbeat_settings( $settings ) {
$setting['nonce'] = wp_create_nonce( 'heartbeat-nonce' );
return $setting;
}

178
wp-includes/js/heartbeat.js Normal file
View File

@ -0,0 +1,178 @@
/**
* Heartbeat API
*/
// Ensure the global `wp` object exists.
window.wp = window.wp || {};
(function($){
var Heartbeat = function() {
var self = this,
running,
timeout,
nonce,
screen = typeof pagenow != 'undefined' ? pagenow : '',
settings,
tick = 0,
queue = {},
interval,
lastconnect = 0;
this.url = typeof ajaxurl != 'undefined' ? ajaxurl : 'wp-admin/admin-ajax.php';
this.autostart = true;
if ( typeof( window.heartbeatSettings != 'undefined' ) ) {
settings = $.extend( {}, window.heartbeatSettings );
delete window.heartbeatSettings;
// Add private vars
nonce = settings.nonce || '';
delete settings.nonce;
interval = settings.interval || 15000; // default interval
delete settings.interval;
// todo: needed?
// 'pagenow' can be added from settings if not already defined
screen = screen || settings.pagenow;
delete settings.pagenow;
// Add public vars
$.extend( this, settings );
}
function time(s) {
if ( s )
return parseInt( (new Date()).getTime() / 1000 );
return (new Date()).getTime();
}
// Set error state and fire an event if it persists for over 3 min
function errorstate() {
var since;
if ( lastconnect ) {
since = time() - lastconnect;
if ( since > 180000 ) {
self.connectionLost = true;
$(document).trigger( 'heartbeat-connection-lost', parseInt(since / 1000) );
} else if ( self.connectionLost ) {
self.connectionLost = false;
$(document).trigger( 'heartbeat-connection-restored' );
}
}
}
function connect() {
var data = {};
tick = time();
data.data = $.extend( {}, queue );
queue = {};
data.interval = interval / 1000;
data._nonce = nonce;
data.action = 'heartbeat';
data.pagenow = screen;
self.xhr = $.post( self.url, data, function(r){
lastconnect = time();
// Clear error state
if ( self.connectionLost )
errorstate();
self.tick(r);
}, 'json' ).always( function(){
next();
}).fail( function(r){
errorstate();
self.error(r);
});
};
function next() {
var delta = time() - tick;
if ( !running )
return;
if ( delta < interval ) {
timeout = window.setTimeout(
function(){
if ( running )
connect();
},
interval - delta
);
} else {
window.clearTimeout(timeout); // this has already expired?
connect();
}
};
this.interval = function(seconds) {
if ( seconds ) {
// Limit
if ( 5 > seconds || seconds > 60 )
return false;
interval = seconds * 1000;
} else if ( seconds === 0 ) {
// Allow long polling to be turned on
interval = 0;
}
return interval / 1000;
};
this.start = function() {
// start only once
if ( running )
return false;
running = true;
connect();
return true;
};
this.stop = function() {
if ( !running )
return false;
if ( self.xhr )
self.xhr.abort();
running = false;
return true;
}
this.send = function(action, data) {
if ( action )
queue[action] = data;
}
if ( this.autostart ) {
$(document).ready( function(){
// Start one tick (15 sec) after DOM ready
running = true;
tick = time();
next();
});
}
}
$.extend( Heartbeat.prototype, {
tick: function(r) {
$(document).trigger( 'heartbeat-tick', r );
},
error: function(r) {
$(document).trigger( 'heartbeat-error', r );
}
});
wp.heartbeat = new Heartbeat();
}(jQuery));

View File

@ -107,6 +107,11 @@ function wp_default_scripts( &$scripts ) {
) );
$scripts->add( 'autosave', "/wp-includes/js/autosave$suffix.js", array('schedule', 'wp-ajax-response'), false, 1 );
$scripts->add( 'heartbeat', "/wp-includes/js/heartbeat$suffix.js", array('jquery'), false, 1 );
did_action( 'init' ) && $scripts->localize( 'heartbeat', 'heartbeatSettings',
apply_filters( 'heartbeat_settings', array() )
);
$scripts->add( 'wp-lists', "/wp-includes/js/wp-lists$suffix.js", array( 'wp-ajax-response', 'jquery-color' ), false, 1 );
@ -371,7 +376,7 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'postbox', "/wp-admin/js/postbox$suffix.js", array('jquery-ui-sortable'), false, 1 );
$scripts->add( 'post', "/wp-admin/js/post$suffix.js", array('suggest', 'wp-lists', 'postbox'), false, 1 );
$scripts->add( 'post', "/wp-admin/js/post$suffix.js", array('suggest', 'wp-lists', 'postbox', 'heartbeat'), false, 1 );
did_action( 'init' ) && $scripts->localize( 'post', 'postL10n', array(
'ok' => __('OK'),
'cancel' => __('Cancel'),