|
1 | | -// GoatCounter: https://www.goatcounter.com |
2 | | -// This file is released under the ISC license: https://opensource.org/licenses/ISC |
3 | | -;(function() { |
| 1 | +; (function () { |
4 | 2 | 'use strict'; |
5 | | - |
6 | 3 | window.goatcounter = window.goatcounter || {} |
7 | | - |
8 | | - // Load settings from data-goatcounter-settings. |
9 | 4 | var s = document.querySelector('script[data-goatcounter]') |
10 | 5 | if (s && s.dataset.goatcounterSettings) { |
11 | | - try { var set = JSON.parse(s.dataset.goatcounterSettings) } |
| 6 | + try { var set = JSON.parse(s.dataset.goatcounterSettings) } |
12 | 7 | catch (err) { console.error('invalid JSON in data-goatcounter-settings: ' + err) } |
13 | 8 | for (var k in set) |
14 | 9 | if (['no_onload', 'no_events', 'allow_local', 'allow_frame', 'path', 'title', 'referrer', 'event'].indexOf(k) > -1) |
15 | 10 | window.goatcounter[k] = set[k] |
16 | 11 | } |
17 | | - |
18 | 12 | var enc = encodeURIComponent |
19 | | - |
20 | | - // Get all data we're going to send off to the counter endpoint. |
21 | | - window.goatcounter.get_data = function(vars) { |
| 13 | + window.goatcounter.get_data = function (vars) { |
22 | 14 | vars = vars || {} |
23 | 15 | var data = { |
24 | | - p: (vars.path === undefined ? goatcounter.path : vars.path), |
| 16 | + p: (vars.path === undefined ? goatcounter.path : vars.path), |
25 | 17 | r: (vars.referrer === undefined ? goatcounter.referrer : vars.referrer), |
26 | | - t: (vars.title === undefined ? goatcounter.title : vars.title), |
| 18 | + t: (vars.title === undefined ? goatcounter.title : vars.title), |
27 | 19 | e: !!(vars.event || goatcounter.event), |
28 | 20 | s: window.screen.width, |
29 | 21 | b: is_bot(), |
30 | 22 | q: location.search, |
31 | 23 | } |
32 | | - |
33 | | - var rcb, pcb, tcb // Save callbacks to apply later. |
34 | | - if (typeof(data.r) === 'function') rcb = data.r |
35 | | - if (typeof(data.t) === 'function') tcb = data.t |
36 | | - if (typeof(data.p) === 'function') pcb = data.p |
37 | | - |
| 24 | + var rcb, pcb, tcb |
| 25 | + if (typeof (data.r) === 'function') rcb = data.r |
| 26 | + if (typeof (data.t) === 'function') tcb = data.t |
| 27 | + if (typeof (data.p) === 'function') pcb = data.p |
38 | 28 | if (is_empty(data.r)) data.r = document.referrer |
39 | 29 | if (is_empty(data.t)) data.t = document.title |
40 | 30 | if (is_empty(data.p)) data.p = get_path() |
41 | | - |
42 | 31 | if (rcb) data.r = rcb(data.r) |
43 | 32 | if (tcb) data.t = tcb(data.t) |
44 | 33 | if (pcb) data.p = pcb(data.p) |
45 | 34 | return data |
46 | 35 | } |
47 | | - |
48 | | - // Check if a value is "empty" for the purpose of get_data(). |
49 | | - var is_empty = function(v) { return v === null || v === undefined || typeof(v) === 'function' } |
50 | | - |
51 | | - // See if this looks like a bot; there is some additional filtering on the |
52 | | - // backend, but these properties can't be fetched from there. |
53 | | - var is_bot = function() { |
54 | | - // Headless browsers are probably a bot. |
| 36 | + var is_empty = function (v) { return v === null || v === undefined || typeof (v) === 'function' } |
| 37 | + var is_bot = function () { |
55 | 38 | var w = window, d = document |
56 | 39 | if (w.callPhantom || w._phantom || w.phantom) |
57 | 40 | return 150 |
|
63 | 46 | return 153 |
64 | 47 | return 0 |
65 | 48 | } |
66 | | - |
67 | | - // Object to urlencoded string, starting with a ?. |
68 | | - var urlencode = function(obj) { |
| 49 | + var urlencode = function (obj) { |
69 | 50 | var p = [] |
70 | 51 | for (var k in obj) |
71 | 52 | if (obj[k] !== '' && obj[k] !== null && obj[k] !== undefined && obj[k] !== false) |
72 | 53 | p.push(enc(k) + '=' + enc(obj[k])) |
73 | 54 | return '?' + p.join('&') |
74 | 55 | } |
75 | | - |
76 | | - // Show a warning in the console. |
77 | | - var warn = function(msg) { |
| 56 | + var warn = function (msg) { |
78 | 57 | if (console && 'warn' in console) |
79 | 58 | console.warn('goatcounter: ' + msg) |
80 | 59 | } |
81 | | - |
82 | | - // Get the endpoint to send requests to. |
83 | | - var get_endpoint = function() { |
| 60 | + var get_endpoint = function () { |
84 | 61 | var s = document.querySelector('script[data-goatcounter]') |
85 | 62 | return (s && s.dataset.goatcounter) ? s.dataset.goatcounter : goatcounter.endpoint |
86 | 63 | } |
87 | | - |
88 | | - // Get current path. |
89 | | - var get_path = function() { |
| 64 | + var get_path = function () { |
90 | 65 | var loc = location, |
91 | 66 | c = document.querySelector('link[rel="canonical"][href]') |
92 | | - if (c) { // May be relative or point to different domain. |
| 67 | + if (c) { |
93 | 68 | var a = document.createElement('a') |
94 | 69 | a.href = c.href |
95 | 70 | if (a.hostname.replace(/^www\./, '') === location.hostname.replace(/^www\./, '')) |
96 | 71 | loc = a |
97 | 72 | } |
98 | 73 | return (loc.pathname + loc.search) || '/' |
99 | 74 | } |
100 | | - |
101 | | - // Run function after DOM is loaded. |
102 | | - var on_load = function(f) { |
| 75 | + var on_load = function (f) { |
103 | 76 | if (document.body === null) |
104 | | - document.addEventListener('DOMContentLoaded', function() { f() }, false) |
| 77 | + document.addEventListener('DOMContentLoaded', function () { f() }, false) |
105 | 78 | else |
106 | 79 | f() |
107 | 80 | } |
108 | | - |
109 | | - // Filter some requests that we (probably) don't want to count. |
110 | | - window.goatcounter.filter = function() { |
| 81 | + window.goatcounter.filter = function () { |
111 | 82 | if ('visibilityState' in document && document.visibilityState === 'prerender') |
112 | 83 | return 'visibilityState' |
113 | 84 | if (!goatcounter.allow_frame && location !== parent.location) |
|
120 | 91 | return 'disabled with #toggle-goatcounter' |
121 | 92 | return false |
122 | 93 | } |
123 | | - |
124 | | - // Get URL to send to GoatCounter. |
125 | | - window.goatcounter.url = function(vars) { |
| 94 | + window.goatcounter.url = function (vars) { |
126 | 95 | var data = window.goatcounter.get_data(vars || {}) |
127 | | - if (data.p === null) // null from user callback. |
| 96 | + if (data.p === null) |
128 | 97 | return |
129 | | - data.rnd = Math.random().toString(36).substr(2, 5) // Browsers don't always listen to Cache-Control. |
130 | | - |
| 98 | + data.rnd = Math.random().toString(36).substr(2, 5) |
131 | 99 | var endpoint = get_endpoint() |
132 | 100 | if (!endpoint) |
133 | 101 | return warn('no endpoint found') |
134 | | - |
135 | 102 | return endpoint + urlencode(data) |
136 | 103 | } |
137 | | - |
138 | | - // Count a hit. |
139 | | - window.goatcounter.count = function(vars) { |
| 104 | + window.goatcounter.count = function (vars) { |
140 | 105 | var f = goatcounter.filter() |
141 | 106 | if (f) |
142 | 107 | return warn('not counting because of: ' + f) |
143 | 108 | var url = goatcounter.url(vars) |
144 | 109 | if (!url) |
145 | 110 | return warn('not counting because path callback returned null') |
146 | | - |
147 | 111 | if (!navigator.sendBeacon(url)) { |
148 | | - // This mostly fails due to being blocked by CSP; try again with an |
149 | | - // image-based fallback. |
150 | 112 | var img = document.createElement('img') |
151 | 113 | img.src = url |
152 | | - img.style.position = 'absolute' // Affect layout less. |
| 114 | + img.style.position = 'absolute' |
153 | 115 | img.style.bottom = '0px' |
154 | 116 | img.style.width = '1px' |
155 | 117 | img.style.height = '1px' |
156 | 118 | img.loading = 'eager' |
157 | 119 | img.setAttribute('alt', '') |
158 | 120 | img.setAttribute('aria-hidden', 'true') |
159 | | - |
160 | | - var rm = function() { if (img && img.parentNode) img.parentNode.removeChild(img) } |
| 121 | + var rm = function () { if (img && img.parentNode) img.parentNode.removeChild(img) } |
161 | 122 | img.addEventListener('load', rm, false) |
162 | 123 | document.body.appendChild(img) |
163 | 124 | } |
164 | 125 | } |
165 | | - |
166 | | - // Get a query parameter. |
167 | | - window.goatcounter.get_query = function(name) { |
| 126 | + window.goatcounter.get_query = function (name) { |
168 | 127 | var s = location.search.substr(1).split('&') |
169 | 128 | for (var i = 0; i < s.length; i++) |
170 | 129 | if (s[i].toLowerCase().indexOf(name.toLowerCase() + '=') === 0) |
171 | 130 | return s[i].substr(name.length + 1) |
172 | 131 | } |
173 | | - |
174 | | - // Track click events. |
175 | | - window.goatcounter.bind_events = function() { |
176 | | - if (!document.querySelectorAll) // Just in case someone uses an ancient browser. |
| 132 | + window.goatcounter.bind_events = function () { |
| 133 | + if (!document.querySelectorAll) |
177 | 134 | return |
178 | | - |
179 | | - var send = function(elem) { |
180 | | - return function() { |
| 135 | + var send = function (elem) { |
| 136 | + return function () { |
181 | 137 | goatcounter.count({ |
182 | | - event: true, |
183 | | - path: (elem.dataset.goatcounterClick || elem.name || elem.id || ''), |
184 | | - title: (elem.dataset.goatcounterTitle || elem.title || (elem.innerHTML || '').substr(0, 200) || ''), |
| 138 | + event: true, |
| 139 | + path: (elem.dataset.goatcounterClick || elem.name || elem.id || ''), |
| 140 | + title: (elem.dataset.goatcounterTitle || elem.title || (elem.innerHTML || '').substr(0, 200) || ''), |
185 | 141 | referrer: (elem.dataset.goatcounterReferrer || elem.dataset.goatcounterReferral || ''), |
186 | 142 | }) |
187 | 143 | } |
188 | 144 | } |
189 | | - |
190 | | - Array.prototype.slice.call(document.querySelectorAll("*[data-goatcounter-click]")).forEach(function(elem) { |
| 145 | + Array.prototype.slice.call(document.querySelectorAll("*[data-goatcounter-click]")).forEach(function (elem) { |
191 | 146 | if (elem.dataset.goatcounterBound) |
192 | 147 | return |
193 | 148 | var f = send(elem) |
194 | 149 | elem.addEventListener('click', f, false) |
195 | | - elem.addEventListener('auxclick', f, false) // Middle click. |
| 150 | + elem.addEventListener('auxclick', f, false) |
196 | 151 | elem.dataset.goatcounterBound = 'true' |
197 | 152 | }) |
198 | 153 | } |
199 | | - |
200 | | - // Add a "visitor counter" frame or image. |
201 | | - window.goatcounter.visit_count = function(opt) { |
202 | | - on_load(function() { |
203 | | - opt = opt || {} |
204 | | - opt.type = opt.type || 'html' |
| 154 | + window.goatcounter.visit_count = function (opt) { |
| 155 | + on_load(function () { |
| 156 | + opt = opt || {} |
| 157 | + opt.type = opt.type || 'html' |
205 | 158 | opt.append = opt.append || 'body' |
206 | | - opt.path = opt.path || get_path() |
207 | | - opt.attr = opt.attr || {width: '200', height: (opt.no_branding ? '60' : '80')} |
208 | | - |
| 159 | + opt.path = opt.path || get_path() |
| 160 | + opt.attr = opt.attr || { width: '200', height: (opt.no_branding ? '60' : '80') } |
209 | 161 | opt.attr['src'] = get_endpoint() + 'er/' + enc(opt.path) + '.' + enc(opt.type) + '?' |
210 | 162 | if (opt.no_branding) opt.attr['src'] += '&no_branding=1' |
211 | | - if (opt.style) opt.attr['src'] += '&style=' + enc(opt.style) |
212 | | - if (opt.start) opt.attr['src'] += '&start=' + enc(opt.start) |
213 | | - if (opt.end) opt.attr['src'] += '&end=' + enc(opt.end) |
214 | | - |
215 | | - var tag = {png: 'img', svg: 'img', html: 'iframe'}[opt.type] |
| 163 | + if (opt.style) opt.attr['src'] += '&style=' + enc(opt.style) |
| 164 | + if (opt.start) opt.attr['src'] += '&start=' + enc(opt.start) |
| 165 | + if (opt.end) opt.attr['src'] += '&end=' + enc(opt.end) |
| 166 | + var tag = { png: 'img', svg: 'img', html: 'iframe' }[opt.type] |
216 | 167 | if (!tag) |
217 | 168 | return warn('visit_count: unknown type: ' + opt.type) |
218 | | - |
219 | 169 | if (opt.type === 'html') { |
220 | 170 | opt.attr['frameborder'] = '0' |
221 | | - opt.attr['scrolling'] = 'no' |
| 171 | + opt.attr['scrolling'] = 'no' |
222 | 172 | } |
223 | | - |
224 | 173 | var d = document.createElement(tag) |
225 | 174 | for (var k in opt.attr) |
226 | 175 | d.setAttribute(k, opt.attr[k]) |
227 | | - |
228 | 176 | var p = document.querySelector(opt.append) |
229 | 177 | if (!p) |
230 | 178 | return warn('visit_count: element to append to not found: ' + opt.append) |
231 | 179 | p.appendChild(d) |
232 | 180 | }) |
233 | 181 | } |
234 | | - |
235 | | - // Make it easy to skip your own views. |
236 | 182 | if (location.hash === '#toggle-goatcounter') { |
237 | 183 | if (localStorage.getItem('skipgc') === 't') { |
238 | 184 | localStorage.removeItem('skipgc', 't') |
|
243 | 189 | alert('GoatCounter tracking is now DISABLED in this browser until ' + location + ' is loaded again.') |
244 | 190 | } |
245 | 191 | } |
246 | | - |
247 | 192 | if (!goatcounter.no_onload) |
248 | | - on_load(function() { |
249 | | - // 1. Page is visible, count request. |
250 | | - // 2. Page is not yet visible; wait until it switches to 'visible' and count. |
251 | | - // See #487 |
| 193 | + on_load(function () { |
252 | 194 | if (!('visibilityState' in document) || document.visibilityState === 'visible') |
253 | 195 | goatcounter.count() |
254 | 196 | else { |
255 | | - var f = function(e) { |
| 197 | + var f = function (e) { |
256 | 198 | if (document.visibilityState !== 'visible') |
257 | 199 | return |
258 | 200 | document.removeEventListener('visibilitychange', f) |
259 | 201 | goatcounter.count() |
260 | 202 | } |
261 | 203 | document.addEventListener('visibilitychange', f) |
262 | 204 | } |
263 | | - |
264 | 205 | if (!goatcounter.no_events) |
265 | 206 | goatcounter.bind_events() |
266 | 207 | }) |
|
0 commit comments