1/** 2 * wb_glitchtip - storefront errors, relayed through the shop. 3 * 4 * Reports go to the shop's own endpoint, never straight to GlitchTip: the DSN 5 * stays on the server, and the same scrubbing and limits apply as for PHP 6 * errors. Nothing here may change how the page behaves - every path is 7 * wrapped, the wrapped fetch/XHR return exactly what the originals return, 8 * and reports leave by sendBeacon, which neither blocks nor delays navigation. 9 * 10 * Never read: form values, element text, cookies, storage. Clicks are 11 * recorded as tag#id.class only. 12 * 13 * @author WBShop <[email protected]> 14 * @copyright WBShop 15 * @license Commercial license - all rights reserved 16 */ 17(function () { 18 'use strict'; 19 20 var cfg = window.wbGlitchtip; 21 if (!cfg || !cfg.endpoint || window.__wbGlitchtipLoaded) { 22 return; 23 } 24 window.__wbGlitchtipLoaded = true; 25 26 var MAX_EVENTS = 5; // per page view 27 var MAX_CRUMBS = 20; 28 var IGNORED_STATUS = { 401: 1, 403: 1, 422: 1, 429: 1 }; 29 var sent = 0; 30 var seen = {}; 31 var crumbs = []; 32 var origFetch = window.fetch; 33 34 function now() { 35 return Date.now() / 1000; 36 } 37 38 function crumb(c) { 39 try { 40 c.timestamp = now(); 41 crumbs.push(c); 42 if (crumbs.length > MAX_CRUMBS) { 43 crumbs.shift(); 44 } 45 } catch (e) { /* never */ } 46 } 47 48 function send(kind, message, stack, extra) { 49 try { 50 if (sent >= MAX_EVENTS) { 51 return; 52 } 53 var key = kind + '|' + String(message).slice(0, 200); 54 if (seen[key]) { 55 return; 56 } 57 seen[key] = 1; 58 sent++; 59 var body = JSON.stringify({ 60 kind: kind, 61 message: String(message || '').slice(0, 1000), 62 stack: String(stack || '').slice(0, 6000), 63 url: String(location.href).slice(0, 2000), 64 extra: extra || {}, 65 breadcrumbs: crumbs.slice(-MAX_CRUMBS) 66 }); 67 if (navigator.sendBeacon) { 68 navigator.sendBeacon(cfg.endpoint, new Blob([body], { type: 'text/plain' })); 69 } else if (origFetch) { 70 origFetch.call(window, cfg.endpoint, { method: 'POST', body: body, keepalive: true, credentials: 'omit' }); 71 } 72 } catch (e) { /* never */ } 73 } 74 75 function parse(url) { 76 var a = document.createElement('a'); 77 a.href = url; 78 return a; 79 } 80 81 /* Exactly this module's endpoints - a looser match ("module=wb_glitchtip") 82 also swallowed every module whose name merely starts the same way. */ 83 var ownPath = parse(cfg.endpoint).pathname; 84 function isOwnReport(url) { 85 var u = String(url); 86 return parse(u).pathname === ownPath 87 || /[?&]module=wb_glitchtip(&|$)/.test(u) 88 || u.indexOf('/module/wb_glitchtip/') !== -1; 89 } 90 91 function http(method, url, status, ms) { 92 try { 93 if (isOwnReport(url)) { 94 return; 95 } 96 var a = parse(url); 97 var same = a.host === location.host; 98 var path = a.pathname || '/'; 99 var level = status === 0 || status >= 500 ? 'error' : (status >= 400 ? 'warning' : 'info'); 100 var data = { method: String(method || 'GET').toUpperCase(), status_code: status, duration_ms: ms }; 101 if (same) { 102 data.path = path; 103 } else { 104 data.host = a.host; 105 } 106 crumb({ category: 'xhr', level: level, data: data }); 107 if (same && status >= 400 && !IGNORED_STATUS[status]) { 108 send('http', String(method || 'GET').toUpperCase() + ' ' + path + ' -> ' + status, '', { 109 method: String(method || 'GET').toUpperCase(), path: path, status: status, ms: ms 110 }); 111 } 112 } catch (e) { /* never */ } 113 } 114 115 window.addEventListener('error', function (ev) { 116 try { 117 /* Resource load errors (img, script tags) have no message: not JS errors. */ 118 if (!ev || !ev.message) { 119 return; 120 } 121 send('error', ev.message, ev.error && ev.error.stack, { 122 filename: ev.filename, lineno: ev.lineno, colno: ev.colno 123 }); 124 } catch (e) { /* never */ } 125 }); 126 127 window.addEventListener('unhandledrejection', function (ev) { 128 try { 129 var r = ev && ev.reason; 130 var message = r && r.message ? (r.name ? r.name + ': ' : '') + r.message :
130String(r); 131 send('rejection', message, r && r.stack); 132 } catch (e) { /* never */ } 133 }); 134 135 if (origFetch) { 136 window.fetch = function (input, init) { 137 var t0 = Date.now(); 138 var p = origFetch.apply(this, arguments); 139 try { 140 var method = (init && init.method) || (input && input.method) || 'GET'; 141 var url = typeof input === 'string' ? input : (input && input.url) || String(input); 142 p.then(function (res) { 143 http(method, url, res.status, Date.now() - t0); 144 }, function () { 145 http(method, url, 0, Date.now() - t0); 146 }); 147 } catch (e) { /* never */ } 148 return p; 149 }; 150 } 151 152 if (window.XMLHttpRequest) { 153 var proto = XMLHttpRequest.prototype; 154 var open = proto.open; 155 var sendXhr = proto.send; 156 proto.open = function (method, url) { 157 try { 158 this.__wbgt = { m: method, u: url }; 159 } catch (e) { /* never */ } 160 return open.apply(this, arguments); 161 }; 162 proto.send = function () { 163 try { 164 var xhr = this; 165 var s = xhr.__wbgt; 166 if (s) { 167 s.t = Date.now(); 168 xhr.addEventListener('loadend', function () { 169 http(s.m, s.u, xhr.status, Date.now() - s.t); 170 }); 171 } 172 } catch (e) { /* never */ } 173 return sendXhr.apply(this, arguments); 174 }; 175 } 176 177 document.addEventListener('click', function (ev) { 178 try { 179 var t = ev.target; 180 if (!t || !t.tagName) { 181 return; 182 } 183 var cls = typeof t.className === 'string' ? t.className.trim().split(/\s+/).slice(0, 2).join('.') : ''; 184 crumb({ 185 category: 'ui.click', 186 message: t.tagName.toLowerCase() + (t.id ? '#' + t.id : '') + (cls ? '.' + cls : '') 187 }); 188 } catch (e) { /* never */ } 189 }, true); 190 191 crumb({ category: 'navigation', data: { path: location.pathname } }); 192 193 /* For a quick check in the console: wbGlitchtip.status() */ 194 cfg.status = function () { 195 return { sent: sent, breadcrumbs: crumbs.length, endpoint: cfg.endpoint }; 196 }; 197})();
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.