~canonical-sysadmins/wordpress/4.6

« back to all changes in this revision

Viewing changes to wp-includes/js/heartbeat.js

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

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