PageSourceSearch

https://wan-na.jp/column/wp-content/plugins/aurora-heatmap/js/reporter.js?ver=1.5.4

js wan-na.jp collected 2026-10-07 00:58:17 UTC 9,638 bytes, 431 lines download raw bytes

1/**
2 * Aurora Heatmap Reporter
3 *
4 * @package aurora-heatmap
5 * @copyright 2019-2022 R3098 <[email protected]>
6 * @version 1.5.4
7 */
8
9/**
10 * Anonymous function for scope
11 */
12(function() {
13	"use strict";
14
15	var html, body;
16
17	/**
18	 * Main object
19	 */
20	var self = {
21		readY: 0,
22		readPosition: 1,
23		readTimerCount: 0,
24		maxReadY: 0,
25		disabled: false,
26		ajax_delay: 0,
27
28		/**
29		 * Initializer
30		 */
31		init: function() {
32			html = document.documentElement;
33			body = document.body;
34
35			this.args          = aurora_heatmap_reporter,
36			this.args.interval = ( parseInt( this.args.interval ) || 10 ) * 1000;
37			this.args.stacks   = parseInt( this.args.stacks ) || 10;
38			this.args.reports  = this.args.reports.split( ',' );
39			this.args.debug    = ! ! parseInt( this.args.debug );
40
41			this.args.ajax_delay_time = parseInt( this.args.ajax_delay_time );
42			if ( ! Number.isInteger( this.args.ajax_delay_time ) ) {
43				this.args.ajax_delay_time = 3000;
44			}
45			this.ajax_delay = new Date().getTime() + this.args.ajax_delay_time;
46
47			const callback_click     = self.push_click.bind( self );
48			const callback_breakaway = self.push_breakaway.bind( self );
49
50			const md = new MobileDetect( window.navigator.userAgent );
51
52			this.args.access = md.mobile() ? 'mobile' : md.tablet() ? 'tablet' : 'pc';
53
54			switch ( this.args.access ) {
55				case 'mobile':
56					this.readPosition = 0.1; // 10% of the window.
57					window.addEventListener( 'pagehide', callback_breakaway );
58					Array.prototype.forEach.call(
59						document.body.children,
60						function( e ) {
61							e.addEventListener( 'click', callback_click );
62						}
63					);
64					break;
65				case 'pc':
66					this.readPosition = 0.5; // Center of the screen.
67					window.addEventListener( 'beforeunload', callback_breakaway );
68					document.addEventListener( 'click', callback_click );
69					break;
70				case 'tablet':
71				default:
72					return;
73			}
74
75			/**
76			 * Hook Calculate reading area
77			 */
78			window.setInterval(
79				function() {
80					self.calc_attention();
81				},
82				1000
83			);
84		},
85
86		/**
87		 * Get current readY
88		 */
89		getReadY: function() {
90			return Math.floor( this.getScrollTop() + this.getWindowHeight() * this.readPosition );
91		},
92
93		/**
94		 * Calcurate attention area
95		 */
96		calc_attention: function() {
97			var readY     = this.getReadY();
98			this.maxReadY = Math.max( this.maxReadY, readY );
99			if ( readY === this.readY ) {
100				this.readTimerCount++;
101			} else {
102				this.readTimerCount = 0;
103			}
104			this.readY = readY;
105			if ( 3 === this.readTimerCount ) {
106				this.push_attention();
107			}
108			var now = new Date().getTime();
109			if ( now - this.lastTime > this.args.interval ) {
110				this.push_data( null, true );
111			}
112		},
113
114		/**
115		 * Get Mouse Cursor Position
116		 *
117		 * @param event event
118		 * @return Object
119		 */
120		getCursorPos: function( event ) {
121			var x, y;
122			if ( ( event.clientX || event.clientY ) && body.scrollLeft ) {
123				return {
124					x: event.clientX + body.scrollLeft,
125					y: event.clientY + body.scrollTop,
126				};
127			} else if ( ( event.clientX || event.clientY ) && document.compatMode == 'CSS1Compat' && html.scrollLeft ) {
128				return {
129					x: event.clientX + html.scrollLeft,
130					y: event.clientY + html.scrollTop,
131				};
132			} else if ( event.pageX || event.pageY ) {
133				return {
134					x: event.pageX,
135					y: event.pageY,
136				};
137			}
138		},
139
140		/**
141		 * Push click data
142		 *
143		 * @param event event
144		 */
145		push_click: function( event ) {
146			var pos = this.getCursorPos( event );
147			if ( ! pos ) {
148				return;
149			}
150			pos.event = 'click_' + this.args.access;
151			this.push_data( pos, true );
152		},
153
154		/**
155		 * Get content end
156		 */
157		getContentEnd: function() {
158			var e = document.getElementsByClassName( 'ahm-content-end-marker' ), y = 0;
159			if ( e && e.length ) {
160				e = e[ e.length - 1 ];
161				y = this.getPageHeight() - window.pageYOffset - e.getBoundingClientRect().bottom;
162				y = Math.max( 0, y );
163			}
164			return y;
165		},
166
167		/**
168		 * Push breakaway data
169		 *
170		 * @param event event
171		 */
172		push_breakaway: function( event ) {
173			var maxReadY = Math.max( this.maxReadY, this.getReadY() );
174
175			this.push_data(
176				{
177					event: 'breakaway_' + this.args.access,
178					x: this.getContentEnd(),
179					y: maxReadY,
180				},
181				false
182			);
183		},
184
185		/**
186		 * Push attention data
187		 */
188		push_attention: function() {
189			this.push_data(
190				{
191					event: 'attention_' + this.args.access,
192					x: this.getContentEnd(),
193					y: this.readY,
194				},
195				true
196			);
197		},
198
199		/**
200		 * Get Scroll Top
201		 *
202		 * @return Number
203		 */
204		getScrollTop: function() {
205			return html.scrollTop || body.scrollTop;
206		},
207
208		/**
209		 * Get Page Width
210		 *
211		 * @return Number
212		 */
213		getPageWidth: function() {
214			return html.clientWidth || body.clientWidth || 0;
215		},
216
217		/**
218		 * Get Page Height
219		 *
220		 * @return Number
221		 */
222		getPageHeight: function() {
223			return Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight );
224		},
225
226		/**
227		 * Get Window Height
228		 *
229		 * @return Number
230		 */
231		getWindowHeight: function() {
232			return window.innerHeight || html.clientHeight || null;
233		},
234
235		/**
236		 * Temporary stacking data
237		 */
238		stack: [],
239
240		/**
241		 * Last sending time
242		 */
243		lastTime: new Date().getTime(),
244
245		/**
246		 * Build preview HTML
247		 *
248		 * For debug.
249		 *
250		 * @param Object e
251		 * @return String
252		 */
253		build_preview: function( e ) {
254			return '<div><b>event=</b> ' + e.event + ' <b>x=</b> ' + ('x' in e ? e.x : 'null') + ' <b>y=</b> ' + e.y + ' <b>height=</b> ' + e.height + ' <b>width=</b> ' + e.width + '</div>';
255		},
256
257		/**
258		 * Show preview HTML
259		 *
260		 * @param String title
261		 * @param Array  content
262		 * @param Object style
263		 * @param Number timeout
264		 */
265		show_preview: function( title, content, style, timeout ) {
266			var div = document.createElement( 'div' );
267			div.setAttribute( 'style', 'color:#000;padding:0.2em;position:fixed;right:0;border:1px solid #000;font-family:monospace;z-index:999999;' );
268			div.style.background = style.background;
269			div.style.top        = style.top;
270			div.innerHTML        = '<div style="color:' + style.color + '"><b>' + title + '</b></div>' + content.map( this.build_preview ).join( '' );
271			body.appendChild( div );
272			window.setTimeout(
273				function() {
274					body.removeChild( div );
275				},
276				timeout
277			);
278		},
279
280		/**
281		 * Push data
282		 *
283		 * @param Object  data
284		 * @param Boolean is_async
285		 */
286		push_data: function( data, is_async ) {
287			if ( this.disabled ) {
288				return;
289			}
290
291			var now = new Date().getTime(), post;
292
293			if ( data && ~this.args.reports.indexOf( data.event ) ) {
294				data.time   = now;
295				data.width  = this.getPageWidth();
296				data.height = this.getPageHeight();
297				this.stack.push( data );
298
299				// Debug: display stored data for 1 second.
300				if ( this.args.debug ) {
301					this.show_preview( 'Store', [ data ], { color: '#963', background: '#ffc', top: '0' }, 1000 );
302				}
303			}
304
305			// Wait jQuery.
306			if ( ! 'jQuery' in window ) {
307				return;
308			}
309
310			// Avoid recording such as JavaScript redirects.
311			if ( now <= this.ajax_delay ) {
312				return;
313			}
314
315			// For async, check interval and stacks.
316			if ( is_async && ( now - this.lastTime ) < this.args.interval && this.stack.length < this.args.stacks ) {
317				return;
318			}
319
320			// Stacked no data, do nothing.
321			if ( ! this.stack.length ) {
322				return;
323			}
324
325			[ post, this.stack ] = [ this.stack, [] ];
326			post.forEach(
327				function( e ) {
328					e.time   = Math.floor( ( e.time - now ) / 1000 );
329					e.x      = Math.floor( e.x );
330					e.y      = Math.floor( e.y );
331					e.width  = Math.floor( e.width );
332					e.height = Math.floor( e.height );
333				}
334			);
335			this.lastTime = now;
336
337			// Debug: display sending data for 5 seconds.
338			if ( this.args.debug ) {
339				this.show_preview( 'Send', post, { color: '#369', background: '#cff', top: '4em' }, 5000 );
340			}
341
342			var res = jQuery.ajax(
343				{
344					type: 'POST',
345					datatype: 'json',
346					url: this.args.ajax_url,
347					cache: false,
348					timeout: 3000,
349					async: is_async,
350					data: {
351						url: document.location.href,
352						title: document.title,
353						data: post,
354						action: this.args.action,
355					},
356				}
357			);
358
359			if ( res.readyState ) {
360				return;
361			}
362
363			if ( navigator.sendBeacon ) {
364				var form = new FormData();
365				form.append( 'action', this.args.action );
366				form.append( 'url', document.location.href );
367				form.append( 'title', document.title );
368				post.forEach(
369					function( e, i ) {
370						for ( var key in e ) {
371							if ( ! e.hasOwnProperty( key ) ) {
372								continue;
373							}
374							form.append( 'data[' + i + '][' + key + ']', e[key] );
375						}
376					}
377				);
378
379				if ( navigator.sendBeacon( this.args.ajax_url, form ) ) {
380					return;
381				}
382			}
383
384			self.disabled = true;
385		},
386	};
387
388	function init() {
389		var ms           = Date.now();
390		var requirements = [ 'jQuery', 'MobileDetect', 'aurora_heatmap_reporter' ];
391
392		// Avoid ReferenceError.
393		function check() {
394			var now = Date.now();
395
396			// Check reqruirements.
397			requirements = requirements.filter(
398				function( e ) {
399					return ! window[ e ];
400				}
401			);
402
403			if ( ! requirements.length ) {
404				// Start main initialization.
405				self.init();
406			} else if ( 15000 < now - ms ) {
407				// Timed out in 15 seconds.
408				requirements.forEach(
409					function( e ) {
410						console.error( e + ' is not defined. Timed out.' );
411					}
412				);
413			} else {
414				// Retry.
415				setTimeout( check, 250 );
416			}
417		}
418
419		check();
420	}
421
422	if ( ~location.search.indexOf( "aurora-heatmap=" ) ) {
423		return;
424	} else if ( 'loading' !== document.readyState ) {
425		init();
426	} else {
427		document.addEventListener( 'DOMContentLoaded', init );
428	}
429})();
430
431/* vim: set ts=4 sw=4 sts=4 noet: */

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.