1/** 2 * Albacross first-party loader. 3 */ 4(function (window, document) { 5 'use strict'; 6 7 var TRACKER_SRC = 'https://serve.albacross.com/track.js'; 8 var SOURCE_NAME = 'WordPress-Plugin'; 9 var LOADER_NAME = 'albacross-loader'; 10 11 /* Attributes a delay-JavaScript engine may move the real src into. */ 12 var SRC_ATTRIBUTES = ['src', 'data-rocket-src', 'data-src']; 13 14 /* Opt-out markers understood by the common optimizers, copied onto the 15 * injected tracker so it is not delayed, deferred or combined either. */ 16 var OPT_OUT_ATTRIBUTES = [ 17 ['data-cfasync', 'false'], 18 ['data-nowprocket', '1'], 19 ['data-no-optimize', '1'], 20 ['data-noptimize', '1'], 21 ['data-no-defer', '1'], 22 ['data-jetpack-boost', 'ignore'], 23 ['data-pagespeed-no-defer', '1'] 24 ]; 25 26 /** 27 * Log a message without ever throwing. 28 * 29 * @param {string} message Message to log. 30 */ 31 function warn(message) { 32 if (window.console && typeof window.console.warn === 'function') { 33 window.console.warn('Albacross: ' + message); 34 } 35 } 36 37 /** 38 * Apply the same rules as albacross_sanitize_client_id() in PHP. 39 * 40 * @param {*} value Raw value. 41 * @return {string} Sanitized Client ID, possibly empty. 42 */ 43 function sanitizeClientId(value) { 44 if (typeof value !== 'string' && typeof value !== 'number') { 45 return ''; 46 } 47 48 return String(value).replace(/[^A-Za-z0-9_-]/g, '').substring(0, 64); 49 } 50 51 /** 52 * Read an attribute without throwing on non-elements. 53 * 54 * @param {Element} tag Element to read from. 55 * @param {string} name Attribute name. 56 * @return {string} Attribute value, or an empty string. 57 */ 58 function attr(tag, name) { 59 if (!tag || typeof tag.getAttribute !== 'function') { 60 return ''; 61 } 62 63 return tag.getAttribute(name) || ''; 64 } 65 66 /** 67 * Read a query argument out of a URL. 68 * 69 * Hand-parsed rather than using URL and URLSearchParams, which are not 70 * available in every browser this plugin still supports. 71 * 72 * @param {string} url URL to read. 73 * @param {string} name Argument name. 74 * @return {string} Decoded value, or an empty string. 75 */ 76 function queryArg(url, name) { 77 if (!url) { 78 return ''; 79 } 80 81 var match = new RegExp('[?&]' + name + '=([^&#]*)').exec(url); 82 83 if (!match) { 84 return ''; 85 } 86 87 try { 88 return decodeURIComponent(match[1].replace(/\+/g, ' ')); 89 } catch (e) { 90 return match[1]; 91 } 92 } 93 94 /** 95 * Collect the elements that may be this loader's own script tag, in 96 * descending order of confidence. 97 * 98 * @return {Element[]} Candidate elements, without duplicates. 99 */ 100 function candidateTags() { 101 var selector = []; 102 var candidates = []; 103 var found; 104 var i; 105 106 for (i = 0; i < SRC_ATTRIBUTES.length; i++) { 107 selector.push('script[' + SRC_ATTRIBUTES[i] + '*="' + LOADER_NAME + '"]'); 108 } 109 110 found = [ 111 document.currentScript, 112 document.getElementById('albacross-tracker-js'), 113 document.querySelector('[data-albacross-client-id]'), 114 document.querySelector(selector.join(',')) 115 ]; 116 117 for (i = 0; i < found.length; i++) { 118 if (found[i] && -1 === indexOf(candidates, found[i])) { 119 candidates.push(found[i]); 120 } 121 } 122 123 return candidates; 124 } 125 126 /** 127 * Array.prototype.indexOf for the elements collected above. 128 * 129 * @param {Array} list List to search. 130 * @param {*} value Value to look for. 131 * @return {number} Index, or -1. 132 */ 133 function indexOf(list, value) { 134 for (var i = 0; i < list.length; i++) { 135 if (list[i] === value) { 136 return i; 137 } 138 } 139 140 return -1; 141 } 142 143 /** 144 * Read a query argument from whichever src attribute survived. 145 * 146 * @param {Element} tag Candidate script tag. 147 * @param {string} name Argument name. 148 * @return {string} Value, or an empty string. 149 */ 150 function queryArgFromTag(tag, name) { 151 for (var i = 0; i < SRC_ATTRIBUTES.length; i++) { 152 var value = queryArg(attr(tag, SRC_ATTRIBUTES[i]), name); 153 154 if (value) { 155 return value; 156 } 157 } 158 159 return ''; 160 } 161 162 /** 163 * Resolve the Client ID from the first channel that survived. 164 * 165 * @param {Element[]} tags Candidate script tags. 166 * @return {string} Sanitized Client ID, or an empty string. 167 */
168 function resolveClientId(tags) { 169 var clientId = sanitizeClientId(window._nQc); 170 var i; 171 172 if (clientId) { 173 return clientId; 174 } 175 176 for (i = 0; i < tags.length; i++) { 177 clientId = sanitizeClientId(attr(tags[i], 'data-albacross-client-id')); 178 179 if (clientId) { 180 return clientId; 181 } 182 } 183 184 for (i = 0; i < tags.length; i++) { 185 clientId = sanitizeClientId(queryArgFromTag(tags[i], 'client_id')); 186 187 if (clientId) { 188 return clientId; 189 } 190 } 191 192 return ''; 193 } 194 195 /** 196 * Resolve the plugin version. Informational only, so failure is fine. 197 * 198 * @param {Element[]} tags Candidate script tags. 199 * @return {string} Version string, or an empty string. 200 */ 201 function resolveVersion(tags) { 202 var version = typeof window._nQsv === 'string' ? window._nQsv : ''; 203 var i; 204 205 for (i = 0; !version && i < tags.length; i++) { 206 version = attr(tags[i], 'data-albacross-version') || 207 queryArgFromTag(tags[i], 'plugin_version'); 208 } 209 210 return version; 211 } 212 213 /** 214 * Whether the tracker is already present in the document. 215 * 216 * Covers the inline snippet used before 1.6.0, a tag added through a tag 217 * manager, and a second copy of this loader. 218 * 219 * @return {boolean} True when track.js is already on the page. 220 */ 221 function trackerPresent() { 222 /* track.js sets this itself once it has initialized. */ 223 if (window._nQ_scriptLoaded) { 224 return true; 225 } 226 227 var scripts = document.getElementsByTagName('script'); 228 229 for (var i = 0; i < scripts.length; i++) { 230 for (var j = 0; j < SRC_ATTRIBUTES.length; j++) { 231 if (-1 !== attr(scripts[i], SRC_ATTRIBUTES[j]).indexOf('serve.albacross.com/track.js')) { 232 return true; 233 } 234 } 235 } 236 237 return false; 238 } 239 240 /* Guard against this file executing more than once. 241 * 242 * The flag has a name of its own on purpose. window._nQ_scriptLoaded 243 * belongs to track.js, which uses it as its re-entry guard: track.js only 244 * sets its cookies and starts sending events when _nQc is set and 245 * _nQ_scriptLoaded is not. Setting it here would leave track.js loaded but 246 * inert, which is what 1.6.2 did. This file must only ever read it. */ 247 if (window._nQ_wpLoaderRan) { 248 return; 249 } 250 251 window._nQ_wpLoaderRan = true; 252 253 var tags = candidateTags(); 254 var clientId = resolveClientId(tags); 255 256 if (!clientId) { 257 warn( 258 'no Client ID found. The tracking script was not loaded. Check that ' + 259 'the Client ID is saved in the plugin settings, and that your ' + 260 'optimization plugin or CDN is not stripping inline scripts, data ' + 261 'attributes and query strings from ' + LOADER_NAME + '.js.' 262 ); 263 264 return; 265 } 266 267 window._nQc = clientId; 268 window._nQs = SOURCE_NAME; 269 window._nQsv = resolveVersion(tags); 270 271 if (trackerPresent()) { 272 return; 273 } 274 275 var tracker = document.createElement('script'); 276 277 tracker.async = true; 278 tracker.src = TRACKER_SRC; 279 280 for (var i = 0; i < OPT_OUT_ATTRIBUTES.length; i++) { 281 tracker.setAttribute(OPT_OUT_ATTRIBUTES[i][0], OPT_OUT_ATTRIBUTES[i][1]); 282 } 283 284 /* Carry the page's Content Security Policy nonce over, when there is one. */ 285 for (var n = 0; n < tags.length; n++) { 286 var nonce = tags[n].nonce || attr(tags[n], 'nonce'); 287 288 if (nonce) { 289 tracker.setAttribute('nonce', nonce); 290 tracker.nonce = nonce; 291 break; 292 } 293 } 294 295 tracker.onerror = function () { 296 warn( 297 'could not load ' + TRACKER_SRC + '. This is usually a consent tool ' + 298 'that has not granted permission yet, a Content Security Policy, or ' + 299 'a tracking blocker in the visitor\'s browser.' 300 ); 301 }; 302 303 (document.head || document.documentElement).appendChild(tracker); 304}(window, document));
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.