1// App Health browser logs. Public, origin-pinned key; nothing here is secret. 2// Logs form submits, clicks on [data-log] elements, and client errors to the 3// Logs tab at health.sassmaker.com. window.appHealthLog(event, options) is 4// available for custom events. Source: app-health/examples/dropin-log-client. 5(function () { 6 var KEY = 'ahk_pub_b7dddb4d70fd4f0faf43a6fc1adb3b2641d7dacd04cb9d9109636a7929505c94', 7 ENV = 'production', 8 URL = 'https://ingest.sassmaker.com/v1/logs'; 9 // No-op until a browser public key (ahk_pub_...) is provisioned above. 10 if (KEY.indexOf('ahk_pub_') !== 0) return; 11 function id() { 12 return crypto.randomUUID(); 13 } 14 function send(event, o) { 15 o = o || {}; 16 var props = {}, 17 src = o.props || {}; 18 for (var k in src) 19 if (src[k] !== undefined) 20 props[k] = typeof src[k] === 'string' ? src[k].slice(0, 500) : src[k]; 21 var body = JSON.stringify({ 22 public_key: KEY, 23 batch_id: id(), 24 schema_version: 'v1', 25 environment: ENV, 26 logs: [ 27 { 28 log_id: id(), 29 timestamp: Date.now(), 30 event: event, 31 level: o.level || 'info', 32 title: o.title, 33 description: o.description, 34 icon: o.icon, 35 props: props, 36 }, 37 ], 38 }); 39 if (document.visibilityState === 'hidden' && navigator.sendBeacon) { 40 navigator.sendBeacon(URL, new Blob([body], { type: 'text/plain' })); 41 return; 42 } 43 fetch(URL, { 44 method: 'POST', 45 headers: { 'content-type': 'text/plain' }, 46 body: body, 47 keepalive: true, 48 }).catch(function () {}); 49 } 50 window.appHealthLog = send; 51 document.addEventListener( 52 'submit', 53 function (e) { 54 var f = e.target; 55 if (!f || f.tagName !== 'FORM') return; 56 send('form.submitted', { 57 title: f.id || f.getAttribute('name') || f.getAttribute('action') || 'form', 58 props: { page: location.pathname }, 59 }); 60 }, 61 true 62 ); 63 document.addEventListener( 64 'click', 65 function (e) { 66 var t = e.target && e.target.closest ? e.target.closest('[data-log]') : null; 67 var name = t && t.getAttribute('data-log'); 68 if (name) 69 send(name, { 70 title: (t.textContent || '').trim().slice(0, 120) || name, 71 props: { page: location.pathname }, 72 }); 73 }, 74 true 75 ); 76 window.addEventListener('error', function (e) { 77 send('client.error', { 78 level: 'error', 79 title: String(e.message || 'error').slice(0, 200), 80 props: { page: location.pathname }, 81 }); 82 }); 83 window.addEventListener('unhandledrejection', function (e) {
84 var r = e.reason && e.reason.message ? e.reason.message : String(e.reason); 85 send('client.error', { 86 level: 'error', 87 title: r.slice(0, 200), 88 props: { page: location.pathname, kind: 'rejection' }, 89 }); 90 }); 91})();
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.