balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

heartbeat.js (23140B)


      1 /**
      2  * Heartbeat API
      3  *
      4  * Heartbeat is a simple server polling API that sends XHR requests to
      5  * the server every 15 - 60 seconds and triggers events (or callbacks) upon
      6  * receiving data. Currently these 'ticks' handle transports for post locking,
      7  * login-expiration warnings, autosave, and related tasks while a user is logged in.
      8  *
      9  * Available PHP filters (in ajax-actions.php):
     10  * - heartbeat_received
     11  * - heartbeat_send
     12  * - heartbeat_tick
     13  * - heartbeat_nopriv_received
     14  * - heartbeat_nopriv_send
     15  * - heartbeat_nopriv_tick
     16  * @see wp_ajax_nopriv_heartbeat(), wp_ajax_heartbeat()
     17  *
     18  * Custom jQuery events:
     19  * - heartbeat-send
     20  * - heartbeat-tick
     21  * - heartbeat-error
     22  * - heartbeat-connection-lost
     23  * - heartbeat-connection-restored
     24  * - heartbeat-nonces-expired
     25  *
     26  * @since 3.6.0
     27  * @output wp-includes/js/heartbeat.js
     28  */
     29 
     30 ( function( $, window, undefined ) {
     31 
     32 	/**
     33 	 * Constructs the Heartbeat API.
     34 	 *
     35 	 * @since 3.6.0
     36 	 *
     37 	 * @return {Object} An instance of the Heartbeat class.
     38 	 * @constructor
     39 	 */
     40 	var Heartbeat = function() {
     41 		var $document = $(document),
     42 			settings = {
     43 				// Suspend/resume.
     44 				suspend: false,
     45 
     46 				// Whether suspending is enabled.
     47 				suspendEnabled: true,
     48 
     49 				// Current screen id, defaults to the JS global 'pagenow' when present
     50 				// (in the admin) or 'front'.
     51 				screenId: '',
     52 
     53 				// XHR request URL, defaults to the JS global 'ajaxurl' when present.
     54 				url: '',
     55 
     56 				// Timestamp, start of the last connection request.
     57 				lastTick: 0,
     58 
     59 				// Container for the enqueued items.
     60 				queue: {},
     61 
     62 				// Connect interval (in seconds).
     63 				mainInterval: 60,
     64 
     65 				// Used when the interval is set to 5 seconds temporarily.
     66 				tempInterval: 0,
     67 
     68 				// Used when the interval is reset.
     69 				originalInterval: 0,
     70 
     71 				// Used to limit the number of Ajax requests.
     72 				minimalInterval: 0,
     73 
     74 				// Used together with tempInterval.
     75 				countdown: 0,
     76 
     77 				// Whether a connection is currently in progress.
     78 				connecting: false,
     79 
     80 				// Whether a connection error occurred.
     81 				connectionError: false,
     82 
     83 				// Used to track non-critical errors.
     84 				errorcount: 0,
     85 
     86 				// Whether at least one connection has been completed successfully.
     87 				hasConnected: false,
     88 
     89 				// Whether the current browser window is in focus and the user is active.
     90 				hasFocus: true,
     91 
     92 				// Timestamp, last time the user was active. Checked every 30 seconds.
     93 				userActivity: 0,
     94 
     95 				// Flag whether events tracking user activity were set.
     96 				userActivityEvents: false,
     97 
     98 				// Timer that keeps track of how long a user has focus.
     99 				checkFocusTimer: 0,
    100 
    101 				// Timer that keeps track of how long needs to be waited before connecting to
    102 				// the server again.
    103 				beatTimer: 0
    104 			};
    105 
    106 		/**
    107 		 * Sets local variables and events, then starts the heartbeat.
    108 		 *
    109 		 * @since 3.8.0
    110 		 * @access private
    111 		 *
    112 		 * @return {void}
    113 		 */
    114 		function initialize() {
    115 			var options, hidden, visibilityState, visibilitychange;
    116 
    117 			if ( typeof window.pagenow === 'string' ) {
    118 				settings.screenId = window.pagenow;
    119 			}
    120 
    121 			if ( typeof window.ajaxurl === 'string' ) {
    122 				settings.url = window.ajaxurl;
    123 			}
    124 
    125 			// Pull in options passed from PHP.
    126 			if ( typeof window.heartbeatSettings === 'object' ) {
    127 				options = window.heartbeatSettings;
    128 
    129 				// The XHR URL can be passed as option when window.ajaxurl is not set.
    130 				if ( ! settings.url && options.ajaxurl ) {
    131 					settings.url = options.ajaxurl;
    132 				}
    133 
    134 				/*
    135 				 * The interval can be from 15 to 120 seconds and can be set temporarily to 5 seconds.
    136 				 * It can be set in the initial options or changed later through JS and/or through PHP.
    137 				 */
    138 				if ( options.interval ) {
    139 					settings.mainInterval = options.interval;
    140 
    141 					if ( settings.mainInterval < 15 ) {
    142 						settings.mainInterval = 15;
    143 					} else if ( settings.mainInterval > 120 ) {
    144 						settings.mainInterval = 120;
    145 					}
    146 				}
    147 
    148 				/*
    149 				 * Used to limit the number of Ajax requests. Overrides all other intervals
    150 				 * if they are shorter. Needed for some hosts that cannot handle frequent requests
    151 				 * and the user may exceed the allocated server CPU time, etc. The minimal interval
    152 				 * can be up to 600 seconds, however setting it to longer than 120 seconds
    153 				 * will limit or disable some of the functionality (like post locks).
    154 				 * Once set at initialization, minimalInterval cannot be changed/overridden.
    155 				 */
    156 				if ( options.minimalInterval ) {
    157 					options.minimalInterval = parseInt( options.minimalInterval, 10 );
    158 					settings.minimalInterval = options.minimalInterval > 0 && options.minimalInterval <= 600 ? options.minimalInterval * 1000 : 0;
    159 				}
    160 
    161 				if ( settings.minimalInterval && settings.mainInterval < settings.minimalInterval ) {
    162 					settings.mainInterval = settings.minimalInterval;
    163 				}
    164 
    165 				// 'screenId' can be added from settings on the front end where the JS global
    166 				// 'pagenow' is not set.
    167 				if ( ! settings.screenId ) {
    168 					settings.screenId = options.screenId || 'front';
    169 				}
    170 
    171 				if ( options.suspension === 'disable' ) {
    172 					settings.suspendEnabled = false;
    173 				}
    174 			}
    175 
    176 			// Convert to milliseconds.
    177 			settings.mainInterval = settings.mainInterval * 1000;
    178 			settings.originalInterval = settings.mainInterval;
    179 
    180 			/*
    181 			 * Switch the interval to 120 seconds by using the Page Visibility API.
    182 			 * If the browser doesn't support it (Safari < 7, Android < 4.4, IE < 10), the
    183 			 * interval will be increased to 120 seconds after 5 minutes of mouse and keyboard
    184 			 * inactivity.
    185 			 */
    186 			if ( typeof document.hidden !== 'undefined' ) {
    187 				hidden = 'hidden';
    188 				visibilitychange = 'visibilitychange';
    189 				visibilityState = 'visibilityState';
    190 			} else if ( typeof document.msHidden !== 'undefined' ) { // IE10.
    191 				hidden = 'msHidden';
    192 				visibilitychange = 'msvisibilitychange';
    193 				visibilityState = 'msVisibilityState';
    194 			} else if ( typeof document.webkitHidden !== 'undefined' ) { // Android.
    195 				hidden = 'webkitHidden';
    196 				visibilitychange = 'webkitvisibilitychange';
    197 				visibilityState = 'webkitVisibilityState';
    198 			}
    199 
    200 			if ( hidden ) {
    201 				if ( document[hidden] ) {
    202 					settings.hasFocus = false;
    203 				}
    204 
    205 				$document.on( visibilitychange + '.wp-heartbeat', function() {
    206 					if ( document[visibilityState] === 'hidden' ) {
    207 						blurred();
    208 						window.clearInterval( settings.checkFocusTimer );
    209 					} else {
    210 						focused();
    211 						if ( document.hasFocus ) {
    212 							settings.checkFocusTimer = window.setInterval( checkFocus, 10000 );
    213 						}
    214 					}
    215 				});
    216 			}
    217 
    218 			// Use document.hasFocus() if available.
    219 			if ( document.hasFocus ) {
    220 				settings.checkFocusTimer = window.setInterval( checkFocus, 10000 );
    221 			}
    222 
    223 			$(window).on( 'unload.wp-heartbeat', function() {
    224 				// Don't connect anymore.
    225 				settings.suspend = true;
    226 
    227 				// Abort the last request if not completed.
    228 				if ( settings.xhr && settings.xhr.readyState !== 4 ) {
    229 					settings.xhr.abort();
    230 				}
    231 			});
    232 
    233 			// Check for user activity every 30 seconds.
    234 			window.setInterval( checkUserActivity, 30000 );
    235 
    236 			// Start one tick after DOM ready.
    237 			$( function() {
    238 				settings.lastTick = time();
    239 				scheduleNextTick();
    240 			});
    241 		}
    242 
    243 		/**
    244 		 * Returns the current time according to the browser.
    245 		 *
    246 		 * @since 3.6.0
    247 		 * @access private
    248 		 *
    249 		 * @return {number} Returns the current time.
    250 		 */
    251 		function time() {
    252 			return (new Date()).getTime();
    253 		}
    254 
    255 		/**
    256 		 * Checks if the iframe is from the same origin.
    257 		 *
    258 		 * @since 3.6.0
    259 		 * @access private
    260 		 *
    261 		 * @return {boolean} Returns whether or not the iframe is from the same origin.
    262 		 */
    263 		function isLocalFrame( frame ) {
    264 			var origin, src = frame.src;
    265 
    266 			/*
    267 			 * Need to compare strings as WebKit doesn't throw JS errors when iframes have
    268 			 * different origin. It throws uncatchable exceptions.
    269 			 */
    270 			if ( src && /^https?:\/\//.test( src ) ) {
    271 				origin = window.location.origin ? window.location.origin : window.location.protocol + '//' + window.location.host;
    272 
    273 				if ( src.indexOf( origin ) !== 0 ) {
    274 					return false;
    275 				}
    276 			}
    277 
    278 			try {
    279 				if ( frame.contentWindow.document ) {
    280 					return true;
    281 				}
    282 			} catch(e) {}
    283 
    284 			return false;
    285 		}
    286 
    287 		/**
    288 		 * Checks if the document's focus has changed.
    289 		 *
    290 		 * @since 4.1.0
    291 		 * @access private
    292 		 *
    293 		 * @return {void}
    294 		 */
    295 		function checkFocus() {
    296 			if ( settings.hasFocus && ! document.hasFocus() ) {
    297 				blurred();
    298 			} else if ( ! settings.hasFocus && document.hasFocus() ) {
    299 				focused();
    300 			}
    301 		}
    302 
    303 		/**
    304 		 * Sets error state and fires an event on XHR errors or timeout.
    305 		 *
    306 		 * @since 3.8.0
    307 		 * @access private
    308 		 *
    309 		 * @param {string} error  The error type passed from the XHR.
    310 		 * @param {number} status The HTTP status code passed from jqXHR
    311 		 *                        (200, 404, 500, etc.).
    312 		 *
    313 		 * @return {void}
    314 		 */
    315 		function setErrorState( error, status ) {
    316 			var trigger;
    317 
    318 			if ( error ) {
    319 				switch ( error ) {
    320 					case 'abort':
    321 						// Do nothing.
    322 						break;
    323 					case 'timeout':
    324 						// No response for 30 seconds.
    325 						trigger = true;
    326 						break;
    327 					case 'error':
    328 						if ( 503 === status && settings.hasConnected ) {
    329 							trigger = true;
    330 							break;
    331 						}
    332 						/* falls through */
    333 					case 'parsererror':
    334 					case 'empty':
    335 					case 'unknown':
    336 						settings.errorcount++;
    337 
    338 						if ( settings.errorcount > 2 && settings.hasConnected ) {
    339 							trigger = true;
    340 						}
    341 
    342 						break;
    343 				}
    344 
    345 				if ( trigger && ! hasConnectionError() ) {
    346 					settings.connectionError = true;
    347 					$document.trigger( 'heartbeat-connection-lost', [error, status] );
    348 					wp.hooks.doAction( 'heartbeat.connection-lost', error, status );
    349 				}
    350 			}
    351 		}
    352 
    353 		/**
    354 		 * Clears the error state and fires an event if there is a connection error.
    355 		 *
    356 		 * @since 3.8.0
    357 		 * @access private
    358 		 *
    359 		 * @return {void}
    360 		 */
    361 		function clearErrorState() {
    362 			// Has connected successfully.
    363 			settings.hasConnected = true;
    364 
    365 			if ( hasConnectionError() ) {
    366 				settings.errorcount = 0;
    367 				settings.connectionError = false;
    368 				$document.trigger( 'heartbeat-connection-restored' );
    369 				wp.hooks.doAction( 'heartbeat.connection-restored' );
    370 			}
    371 		}
    372 
    373 		/**
    374 		 * Gathers the data and connects to the server.
    375 		 *
    376 		 * @since 3.6.0
    377 		 * @access private
    378 		 *
    379 		 * @return {void}
    380 		 */
    381 		function connect() {
    382 			var ajaxData, heartbeatData;
    383 
    384 			// If the connection to the server is slower than the interval,
    385 			// heartbeat connects as soon as the previous connection's response is received.
    386 			if ( settings.connecting || settings.suspend ) {
    387 				return;
    388 			}
    389 
    390 			settings.lastTick = time();
    391 
    392 			heartbeatData = $.extend( {}, settings.queue );
    393 			// Clear the data queue. Anything added after this point will be sent on the next tick.
    394 			settings.queue = {};
    395 
    396 			$document.trigger( 'heartbeat-send', [ heartbeatData ] );
    397 			wp.hooks.doAction( 'heartbeat.send', heartbeatData );
    398 
    399 			ajaxData = {
    400 				data: heartbeatData,
    401 				interval: settings.tempInterval ? settings.tempInterval / 1000 : settings.mainInterval / 1000,
    402 				_nonce: typeof window.heartbeatSettings === 'object' ? window.heartbeatSettings.nonce : '',
    403 				action: 'heartbeat',
    404 				screen_id: settings.screenId,
    405 				has_focus: settings.hasFocus
    406 			};
    407 
    408 			if ( 'customize' === settings.screenId  ) {
    409 				ajaxData.wp_customize = 'on';
    410 			}
    411 
    412 			settings.connecting = true;
    413 			settings.xhr = $.ajax({
    414 				url: settings.url,
    415 				type: 'post',
    416 				timeout: 30000, // Throw an error if not completed after 30 seconds.
    417 				data: ajaxData,
    418 				dataType: 'json'
    419 			}).always( function() {
    420 				settings.connecting = false;
    421 				scheduleNextTick();
    422 			}).done( function( response, textStatus, jqXHR ) {
    423 				var newInterval;
    424 
    425 				if ( ! response ) {
    426 					setErrorState( 'empty' );
    427 					return;
    428 				}
    429 
    430 				clearErrorState();
    431 
    432 				if ( response.nonces_expired ) {
    433 					$document.trigger( 'heartbeat-nonces-expired' );
    434 					wp.hooks.doAction( 'heartbeat.nonces-expired' );
    435 				}
    436 
    437 				// Change the interval from PHP.
    438 				if ( response.heartbeat_interval ) {
    439 					newInterval = response.heartbeat_interval;
    440 					delete response.heartbeat_interval;
    441 				}
    442 
    443 				// Update the heartbeat nonce if set.
    444 				if ( response.heartbeat_nonce && typeof window.heartbeatSettings === 'object' ) {
    445 					window.heartbeatSettings.nonce = response.heartbeat_nonce;
    446 					delete response.heartbeat_nonce;
    447 				}
    448 
    449 				// Update the Rest API nonce if set and wp-api loaded.
    450 				if ( response.rest_nonce && typeof window.wpApiSettings === 'object' ) {
    451 					window.wpApiSettings.nonce = response.rest_nonce;
    452 					// This nonce is required for api-fetch through heartbeat.tick.
    453 					// delete response.rest_nonce;
    454 				}
    455 
    456 				$document.trigger( 'heartbeat-tick', [response, textStatus, jqXHR] );
    457 				wp.hooks.doAction( 'heartbeat.tick', response, textStatus, jqXHR );
    458 
    459 				// Do this last. Can trigger the next XHR if connection time > 5 seconds and newInterval == 'fast'.
    460 				if ( newInterval ) {
    461 					interval( newInterval );
    462 				}
    463 			}).fail( function( jqXHR, textStatus, error ) {
    464 				setErrorState( textStatus || 'unknown', jqXHR.status );
    465 				$document.trigger( 'heartbeat-error', [jqXHR, textStatus, error] );
    466 				wp.hooks.doAction( 'heartbeat.error', jqXHR, textStatus, error );
    467 			});
    468 		}
    469 
    470 		/**
    471 		 * Schedules the next connection.
    472 		 *
    473 		 * Fires immediately if the connection time is longer than the interval.
    474 		 *
    475 		 * @since 3.8.0
    476 		 * @access private
    477 		 *
    478 		 * @return {void}
    479 		 */
    480 		function scheduleNextTick() {
    481 			var delta = time() - settings.lastTick,
    482 				interval = settings.mainInterval;
    483 
    484 			if ( settings.suspend ) {
    485 				return;
    486 			}
    487 
    488 			if ( ! settings.hasFocus ) {
    489 				interval = 120000; // 120 seconds. Post locks expire after 150 seconds.
    490 			} else if ( settings.countdown > 0 && settings.tempInterval ) {
    491 				interval = settings.tempInterval;
    492 				settings.countdown--;
    493 
    494 				if ( settings.countdown < 1 ) {
    495 					settings.tempInterval = 0;
    496 				}
    497 			}
    498 
    499 			if ( settings.minimalInterval && interval < settings.minimalInterval ) {
    500 				interval = settings.minimalInterval;
    501 			}
    502 
    503 			window.clearTimeout( settings.beatTimer );
    504 
    505 			if ( delta < interval ) {
    506 				settings.beatTimer = window.setTimeout(
    507 					function() {
    508 						connect();
    509 					},
    510 					interval - delta
    511 				);
    512 			} else {
    513 				connect();
    514 			}
    515 		}
    516 
    517 		/**
    518 		 * Sets the internal state when the browser window becomes hidden or loses focus.
    519 		 *
    520 		 * @since 3.6.0
    521 		 * @access private
    522 		 *
    523 		 * @return {void}
    524 		 */
    525 		function blurred() {
    526 			settings.hasFocus = false;
    527 		}
    528 
    529 		/**
    530 		 * Sets the internal state when the browser window becomes visible or is in focus.
    531 		 *
    532 		 * @since 3.6.0
    533 		 * @access private
    534 		 *
    535 		 * @return {void}
    536 		 */
    537 		function focused() {
    538 			settings.userActivity = time();
    539 
    540 			// Resume if suspended.
    541 			settings.suspend = false;
    542 
    543 			if ( ! settings.hasFocus ) {
    544 				settings.hasFocus = true;
    545 				scheduleNextTick();
    546 			}
    547 		}
    548 
    549 		/**
    550 		 * Runs when the user becomes active after a period of inactivity.
    551 		 *
    552 		 * @since 3.6.0
    553 		 * @access private
    554 		 *
    555 		 * @return {void}
    556 		 */
    557 		function userIsActive() {
    558 			settings.userActivityEvents = false;
    559 			$document.off( '.wp-heartbeat-active' );
    560 
    561 			$('iframe').each( function( i, frame ) {
    562 				if ( isLocalFrame( frame ) ) {
    563 					$( frame.contentWindow ).off( '.wp-heartbeat-active' );
    564 				}
    565 			});
    566 
    567 			focused();
    568 		}
    569 
    570 		/**
    571 		 * Checks for user activity.
    572 		 *
    573 		 * Runs every 30 seconds. Sets 'hasFocus = true' if user is active and the window
    574 		 * is in the background. Sets 'hasFocus = false' if the user has been inactive
    575 		 * (no mouse or keyboard activity) for 5 minutes even when the window has focus.
    576 		 *
    577 		 * @since 3.8.0
    578 		 * @access private
    579 		 *
    580 		 * @return {void}
    581 		 */
    582 		function checkUserActivity() {
    583 			var lastActive = settings.userActivity ? time() - settings.userActivity : 0;
    584 
    585 			// Throttle down when no mouse or keyboard activity for 5 minutes.
    586 			if ( lastActive > 300000 && settings.hasFocus ) {
    587 				blurred();
    588 			}
    589 
    590 			// Suspend after 10 minutes of inactivity when suspending is enabled.
    591 			// Always suspend after 60 minutes of inactivity. This will release the post lock, etc.
    592 			if ( ( settings.suspendEnabled && lastActive > 600000 ) || lastActive > 3600000 ) {
    593 				settings.suspend = true;
    594 			}
    595 
    596 			if ( ! settings.userActivityEvents ) {
    597 				$document.on( 'mouseover.wp-heartbeat-active keyup.wp-heartbeat-active touchend.wp-heartbeat-active', function() {
    598 					userIsActive();
    599 				});
    600 
    601 				$('iframe').each( function( i, frame ) {
    602 					if ( isLocalFrame( frame ) ) {
    603 						$( frame.contentWindow ).on( 'mouseover.wp-heartbeat-active keyup.wp-heartbeat-active touchend.wp-heartbeat-active', function() {
    604 							userIsActive();
    605 						});
    606 					}
    607 				});
    608 
    609 				settings.userActivityEvents = true;
    610 			}
    611 		}
    612 
    613 		// Public methods.
    614 
    615 		/**
    616 		 * Checks whether the window (or any local iframe in it) has focus, or the user
    617 		 * is active.
    618 		 *
    619 		 * @since 3.6.0
    620 		 * @memberOf wp.heartbeat.prototype
    621 		 *
    622 		 * @return {boolean} True if the window or the user is active.
    623 		 */
    624 		function hasFocus() {
    625 			return settings.hasFocus;
    626 		}
    627 
    628 		/**
    629 		 * Checks whether there is a connection error.
    630 		 *
    631 		 * @since 3.6.0
    632 		 *
    633 		 * @memberOf wp.heartbeat.prototype
    634 		 *
    635 		 * @return {boolean} True if a connection error was found.
    636 		 */
    637 		function hasConnectionError() {
    638 			return settings.connectionError;
    639 		}
    640 
    641 		/**
    642 		 * Connects as soon as possible regardless of 'hasFocus' state.
    643 		 *
    644 		 * Will not open two concurrent connections. If a connection is in progress,
    645 		 * will connect again immediately after the current connection completes.
    646 		 *
    647 		 * @since 3.8.0
    648 		 *
    649 		 * @memberOf wp.heartbeat.prototype
    650 		 *
    651 		 * @return {void}
    652 		 */
    653 		function connectNow() {
    654 			settings.lastTick = 0;
    655 			scheduleNextTick();
    656 		}
    657 
    658 		/**
    659 		 * Disables suspending.
    660 		 *
    661 		 * Should be used only when Heartbeat is performing critical tasks like
    662 		 * autosave, post-locking, etc. Using this on many screens may overload
    663 		 * the user's hosting account if several browser windows/tabs are left open
    664 		 * for a long time.
    665 		 *
    666 		 * @since 3.8.0
    667 		 *
    668 		 * @memberOf wp.heartbeat.prototype
    669 		 *
    670 		 * @return {void}
    671 		 */
    672 		function disableSuspend() {
    673 			settings.suspendEnabled = false;
    674 		}
    675 
    676 		/**
    677 		 * Gets/Sets the interval.
    678 		 *
    679 		 * When setting to 'fast' or 5, the interval is 5 seconds for the next 30 ticks
    680 		 * (for 2 minutes and 30 seconds) by default. In this case the number of 'ticks'
    681 		 * can be passed as second argument. If the window doesn't have focus,
    682 		 * the interval slows down to 2 minutes.
    683 		 *
    684 		 * @since 3.6.0
    685 		 *
    686 		 * @memberOf wp.heartbeat.prototype
    687 		 *
    688 		 * @param {string|number} speed Interval: 'fast' or 5, 15, 30, 60, 120.
    689 		 *                              Fast equals 5.
    690 		 * @param {string}        ticks Tells how many ticks before the interval reverts
    691 		 *                              back. Used with speed = 'fast' or 5.
    692 		 *
    693 		 * @return {number} Current interval in seconds.
    694 		 */
    695 		function interval( speed, ticks ) {
    696 			var newInterval,
    697 				oldInterval = settings.tempInterval ? settings.tempInterval : settings.mainInterval;
    698 
    699 			if ( speed ) {
    700 				switch ( speed ) {
    701 					case 'fast':
    702 					case 5:
    703 						newInterval = 5000;
    704 						break;
    705 					case 15:
    706 						newInterval = 15000;
    707 						break;
    708 					case 30:
    709 						newInterval = 30000;
    710 						break;
    711 					case 60:
    712 						newInterval = 60000;
    713 						break;
    714 					case 120:
    715 						newInterval = 120000;
    716 						break;
    717 					case 'long-polling':
    718 						// Allow long polling (experimental).
    719 						settings.mainInterval = 0;
    720 						return 0;
    721 					default:
    722 						newInterval = settings.originalInterval;
    723 				}
    724 
    725 				if ( settings.minimalInterval && newInterval < settings.minimalInterval ) {
    726 					newInterval = settings.minimalInterval;
    727 				}
    728 
    729 				if ( 5000 === newInterval ) {
    730 					ticks = parseInt( ticks, 10 ) || 30;
    731 					ticks = ticks < 1 || ticks > 30 ? 30 : ticks;
    732 
    733 					settings.countdown = ticks;
    734 					settings.tempInterval = newInterval;
    735 				} else {
    736 					settings.countdown = 0;
    737 					settings.tempInterval = 0;
    738 					settings.mainInterval = newInterval;
    739 				}
    740 
    741 				/*
    742 				 * Change the next connection time if new interval has been set.
    743 				 * Will connect immediately if the time since the last connection
    744 				 * is greater than the new interval.
    745 				 */
    746 				if ( newInterval !== oldInterval ) {
    747 					scheduleNextTick();
    748 				}
    749 			}
    750 
    751 			return settings.tempInterval ? settings.tempInterval / 1000 : settings.mainInterval / 1000;
    752 		}
    753 
    754 		/**
    755 		 * Enqueues data to send with the next XHR.
    756 		 *
    757 		 * As the data is send asynchronously, this function doesn't return the XHR
    758 		 * response. To see the response, use the custom jQuery event 'heartbeat-tick'
    759 		 * on the document, example:
    760 		 *		$(document).on( 'heartbeat-tick.myname', function( event, data, textStatus, jqXHR ) {
    761 		 *			// code
    762 		 *		});
    763 		 * If the same 'handle' is used more than once, the data is not overwritten when
    764 		 * the third argument is 'true'. Use `wp.heartbeat.isQueued('handle')` to see if
    765 		 * any data is already queued for that handle.
    766 		 *
    767 		 * @since 3.6.0
    768 		 *
    769 		 * @memberOf wp.heartbeat.prototype
    770 		 *
    771 		 * @param {string}  handle      Unique handle for the data, used in PHP to
    772 		 *                              receive the data.
    773 		 * @param {*}       data        The data to send.
    774 		 * @param {boolean} noOverwrite Whether to overwrite existing data in the queue.
    775 		 *
    776 		 * @return {boolean} True if the data was queued.
    777 		 */
    778 		function enqueue( handle, data, noOverwrite ) {
    779 			if ( handle ) {
    780 				if ( noOverwrite && this.isQueued( handle ) ) {
    781 					return false;
    782 				}
    783 
    784 				settings.queue[handle] = data;
    785 				return true;
    786 			}
    787 			return false;
    788 		}
    789 
    790 		/**
    791 		 * Checks if data with a particular handle is queued.
    792 		 *
    793 		 * @since 3.6.0
    794 		 *
    795 		 * @param {string} handle The handle for the data.
    796 		 *
    797 		 * @return {boolean} True if the data is queued with this handle.
    798 		 */
    799 		function isQueued( handle ) {
    800 			if ( handle ) {
    801 				return settings.queue.hasOwnProperty( handle );
    802 			}
    803 		}
    804 
    805 		/**
    806 		 * Removes data with a particular handle from the queue.
    807 		 *
    808 		 * @since 3.7.0
    809 		 *
    810 		 * @memberOf wp.heartbeat.prototype
    811 		 *
    812 		 * @param {string} handle The handle for the data.
    813 		 *
    814 		 * @return {void}
    815 		 */
    816 		function dequeue( handle ) {
    817 			if ( handle ) {
    818 				delete settings.queue[handle];
    819 			}
    820 		}
    821 
    822 		/**
    823 		 * Gets data that was enqueued with a particular handle.
    824 		 *
    825 		 * @since 3.7.0
    826 		 *
    827 		 * @memberOf wp.heartbeat.prototype
    828 		 *
    829 		 * @param {string} handle The handle for the data.
    830 		 *
    831 		 * @return {*} The data or undefined.
    832 		 */
    833 		function getQueuedItem( handle ) {
    834 			if ( handle ) {
    835 				return this.isQueued( handle ) ? settings.queue[handle] : undefined;
    836 			}
    837 		}
    838 
    839 		initialize();
    840 
    841 		// Expose public methods.
    842 		return {
    843 			hasFocus: hasFocus,
    844 			connectNow: connectNow,
    845 			disableSuspend: disableSuspend,
    846 			interval: interval,
    847 			hasConnectionError: hasConnectionError,
    848 			enqueue: enqueue,
    849 			dequeue: dequeue,
    850 			isQueued: isQueued,
    851 			getQueuedItem: getQueuedItem
    852 		};
    853 	};
    854 
    855 	/**
    856 	 * Ensure the global `wp` object exists.
    857 	 *
    858 	 * @namespace wp
    859 	 */
    860 	window.wp = window.wp || {};
    861 
    862 	/**
    863 	 * Contains the Heartbeat API.
    864 	 *
    865 	 * @namespace wp.heartbeat
    866 	 * @type {Heartbeat}
    867 	 */
    868 	window.wp.heartbeat = new Heartbeat();
    869 
    870 }( jQuery, window ));