[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
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 $document.ready( 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 ));
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Mon Mar 8 01:00:04 2021 | Cross-referenced by PHPXref 0.7.1 |