Нет описания

heartbeat.js 23KB

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