|
8 | 8 | top, |
9 | 9 | doNotTrack, |
10 | 10 | } = window; |
11 | | - const { hostname, href, origin } = location; |
12 | 11 | const { currentScript, referrer } = document; |
13 | | - const localStorage = href.startsWith('data:') ? undefined : window.localStorage; |
14 | | - |
15 | 12 | if (!currentScript) return; |
16 | 13 |
|
| 14 | + const { hostname, href, origin } = location; |
| 15 | + const localStorage = href.startsWith('data:') ? undefined : window.localStorage; |
| 16 | + |
17 | 17 | const _data = 'data-'; |
18 | 18 | const _false = 'false'; |
19 | 19 | const _true = 'true'; |
20 | 20 | const attr = currentScript.getAttribute.bind(currentScript); |
21 | 21 | const website = attr(_data + 'website-id'); |
22 | 22 | const hostUrl = attr(_data + 'host-url'); |
23 | | - const tag = attr(_data + 'tag'); |
| 23 | + const tag = attr(_data + 'tag') || undefined; |
24 | 24 | const autoTrack = attr(_data + 'auto-track') !== _false; |
25 | 25 | const dnt = attr(_data + 'do-not-track') === _true; |
26 | 26 | const excludeSearch = attr(_data + 'exclude-search') === _true; |
|
41 | 41 | website, |
42 | 42 | screen, |
43 | 43 | language, |
44 | | - title, |
| 44 | + title: document.title, |
45 | 45 | hostname, |
46 | 46 | url: currentUrl, |
47 | 47 | referrer: currentRef, |
48 | | - tag: tag ? tag : undefined, |
| 48 | + tag, |
49 | 49 | id: identity ? identity : undefined, |
50 | 50 | }); |
51 | 51 |
|
|
56 | 56 |
|
57 | 57 | /* Event handlers */ |
58 | 58 |
|
59 | | - const handlePush = (state, title, url) => { |
| 59 | + const handlePush = (_state, _title, url) => { |
60 | 60 | if (!url) return; |
61 | 61 |
|
62 | 62 | currentRef = currentUrl; |
63 | 63 | currentUrl = new URL(url, location.href); |
64 | 64 |
|
65 | | - if (excludeSearch) { |
66 | | - currentUrl.search = ''; |
67 | | - } |
68 | | - |
69 | | - if (excludeHash) { |
70 | | - currentUrl.hash = ''; |
71 | | - } |
72 | | - |
| 65 | + if (excludeSearch) currentUrl.search = ''; |
| 66 | + if (excludeHash) currentUrl.hash = ''; |
73 | 67 | currentUrl = currentUrl.toString(); |
74 | 68 |
|
75 | 69 | if (currentUrl !== currentRef) { |
|
80 | 74 | const handlePathChanges = () => { |
81 | 75 | const hook = (_this, method, callback) => { |
82 | 76 | const orig = _this[method]; |
83 | | - |
84 | 77 | return (...args) => { |
85 | 78 | callback.apply(null, args); |
86 | | - |
87 | 79 | return orig.apply(_this, args); |
88 | 80 | }; |
89 | 81 | }; |
|
92 | 84 | history.replaceState = hook(history, 'replaceState', handlePush); |
93 | 85 | }; |
94 | 86 |
|
95 | | - const handleTitleChanges = () => { |
96 | | - const observer = new MutationObserver(([entry]) => { |
97 | | - title = entry && entry.target ? entry.target.text : undefined; |
98 | | - }); |
99 | | - |
100 | | - const node = document.querySelector('head > title'); |
101 | | - |
102 | | - if (node) { |
103 | | - observer.observe(node, { |
104 | | - subtree: true, |
105 | | - characterData: true, |
106 | | - childList: true, |
107 | | - }); |
108 | | - } |
109 | | - }; |
110 | | - |
111 | 87 | const handleClicks = () => { |
112 | | - document.addEventListener( |
113 | | - 'click', |
114 | | - async e => { |
115 | | - const isSpecialTag = tagName => ['BUTTON', 'A'].includes(tagName); |
116 | | - |
117 | | - const trackElement = async el => { |
118 | | - const attr = el.getAttribute.bind(el); |
119 | | - const eventName = attr(eventNameAttribute); |
120 | | - |
121 | | - if (eventName) { |
122 | | - const eventData = {}; |
123 | | - |
124 | | - el.getAttributeNames().forEach(name => { |
125 | | - const match = name.match(eventRegex); |
126 | | - |
127 | | - if (match) { |
128 | | - eventData[match[1]] = attr(name); |
129 | | - } |
130 | | - }); |
131 | | - |
132 | | - return track(eventName, eventData); |
133 | | - } |
134 | | - }; |
135 | | - |
136 | | - const findParentTag = (rootElem, maxSearchDepth) => { |
137 | | - let currentElement = rootElem; |
138 | | - for (let i = 0; i < maxSearchDepth; i++) { |
139 | | - if (isSpecialTag(currentElement.tagName)) { |
140 | | - return currentElement; |
141 | | - } |
142 | | - currentElement = currentElement.parentElement; |
143 | | - if (!currentElement) { |
144 | | - return null; |
145 | | - } |
146 | | - } |
147 | | - }; |
| 88 | + const trackElement = async el => { |
| 89 | + const eventName = el.getAttribute(eventNameAttribute); |
| 90 | + if (eventName) { |
| 91 | + const eventData = {}; |
148 | 92 |
|
149 | | - const el = e.target; |
150 | | - const parentElement = isSpecialTag(el.tagName) ? el : findParentTag(el, 10); |
| 93 | + el.getAttributeNames().forEach(name => { |
| 94 | + const match = name.match(eventRegex); |
| 95 | + if (match) eventData[match[1]] = el.getAttribute(name); |
| 96 | + }); |
151 | 97 |
|
152 | | - if (parentElement) { |
153 | | - const { href, target } = parentElement; |
154 | | - const eventName = parentElement.getAttribute(eventNameAttribute); |
| 98 | + return track(eventName, eventData); |
| 99 | + } |
| 100 | + }; |
| 101 | + const onClick = async e => { |
| 102 | + const el = e.target; |
| 103 | + const parentElement = el.closest('a,button'); |
| 104 | + if (!parentElement) return trackElement(el); |
155 | 105 |
|
156 | | - if (eventName) { |
157 | | - if (parentElement.tagName === 'A') { |
158 | | - const external = |
159 | | - target === '_blank' || |
160 | | - e.ctrlKey || |
161 | | - e.shiftKey || |
162 | | - e.metaKey || |
163 | | - (e.button && e.button === 1); |
| 106 | + const { href, target } = parentElement; |
| 107 | + if (!parentElement.getAttribute(eventNameAttribute)) return; |
164 | 108 |
|
165 | | - if (eventName && href) { |
166 | | - if (!external) { |
167 | | - e.preventDefault(); |
168 | | - } |
169 | | - return trackElement(parentElement).then(() => { |
170 | | - if (!external) { |
171 | | - (target === '_top' ? top.location : location).href = href; |
172 | | - } |
173 | | - }); |
174 | | - } |
175 | | - } else if (parentElement.tagName === 'BUTTON') { |
176 | | - return trackElement(parentElement); |
177 | | - } |
| 109 | + if (parentElement.tagName === 'BUTTON') { |
| 110 | + return trackElement(parentElement); |
| 111 | + } |
| 112 | + if (parentElement.tagName === 'A' && href) { |
| 113 | + const external = |
| 114 | + target === '_blank' || |
| 115 | + e.ctrlKey || |
| 116 | + e.shiftKey || |
| 117 | + e.metaKey || |
| 118 | + (e.button && e.button === 1); |
| 119 | + if (!external) e.preventDefault(); |
| 120 | + return trackElement(parentElement).then(() => { |
| 121 | + if (!external) { |
| 122 | + (target === '_top' ? top.location : location).href = href; |
178 | 123 | } |
179 | | - } else { |
180 | | - return trackElement(el); |
181 | | - } |
182 | | - }, |
183 | | - true, |
184 | | - ); |
| 124 | + }); |
| 125 | + } |
| 126 | + }; |
| 127 | + document.addEventListener('click', onClick, true); |
185 | 128 | }; |
186 | 129 |
|
187 | 130 | /* Tracking functions */ |
|
195 | 138 |
|
196 | 139 | const send = async (payload, type = 'event') => { |
197 | 140 | if (trackingDisabled()) return; |
198 | | - |
199 | | - const headers = { |
200 | | - 'Content-Type': 'application/json', |
201 | | - }; |
202 | | - |
203 | | - if (typeof cache !== 'undefined') { |
204 | | - headers['x-umami-cache'] = cache; |
205 | | - } |
206 | | - |
207 | 141 | try { |
208 | 142 | const res = await fetch(endpoint, { |
209 | 143 | method: 'POST', |
210 | 144 | body: JSON.stringify({ type, payload }), |
211 | | - headers, |
| 145 | + headers: { |
| 146 | + 'Content-Type': 'application/json', |
| 147 | + ...(typeof cache !== 'undefined' && { 'x-umami-cache': cache }), |
| 148 | + }, |
212 | 149 | credentials: 'omit', |
213 | 150 | }); |
214 | 151 |
|
215 | 152 | const data = await res.json(); |
216 | | - |
217 | 153 | if (data) { |
218 | 154 | disabled = !!data.disabled; |
219 | 155 | cache = data.cache; |
220 | 156 | } |
221 | 157 | } catch (e) { |
222 | | - /* empty */ |
| 158 | + /* no-op */ |
223 | 159 | } |
224 | 160 | }; |
225 | 161 |
|
226 | 162 | const init = () => { |
227 | 163 | if (!initialized) { |
| 164 | + initialized = true; |
228 | 165 | track(); |
229 | 166 | handlePathChanges(); |
230 | | - handleTitleChanges(); |
231 | 167 | handleClicks(); |
232 | | - initialized = true; |
233 | 168 | } |
234 | 169 | }; |
235 | 170 |
|
236 | | - const track = (name, data) => { |
237 | | - if (typeof name === 'string') { |
238 | | - return send({ |
239 | | - ...getPayload(), |
240 | | - name, |
241 | | - data, |
242 | | - }); |
243 | | - } else if (typeof name === 'object') { |
244 | | - return send({ ...name }); |
245 | | - } else if (typeof name === 'function') { |
246 | | - return send(name(getPayload())); |
247 | | - } |
| 171 | + const track = (obj, data) => { |
| 172 | + if (typeof obj === 'string') return send({ ...getPayload(), name: obj, data }); |
| 173 | + if (typeof obj === 'object') return send(obj); |
| 174 | + if (typeof obj === 'function') return send(obj(getPayload())); |
248 | 175 | return send(getPayload()); |
249 | 176 | }; |
250 | 177 |
|
|
274 | 201 |
|
275 | 202 | let currentUrl = href; |
276 | 203 | let currentRef = referrer.startsWith(origin) ? '' : referrer; |
277 | | - let title = document.title; |
278 | | - let cache; |
279 | | - let initialized; |
| 204 | + let initialized = false; |
280 | 205 | let disabled = false; |
| 206 | + let cache; |
281 | 207 | let identity; |
282 | 208 |
|
283 | 209 | if (autoTrack && !trackingDisabled()) { |
|
0 commit comments