1(() => { 2 const selectors = [ 3 "[data-extension-user-count]", 4 "[data-extension-chrome-users]", 5 "[data-extension-edge-users]", 6 "[data-extension-safari-users]", 7 "[data-extension-count-checked]", 8 "[data-extension-chrome-version]", 9 "[data-extension-edge-version]", 10 "[data-extension-safari-version]" 11 ]; 12 13 if (!document.querySelector(selectors.join(","))) { 14 return; 15 } 16 17 loadExtensionStoreStatus(); 18 19 async function loadExtensionStoreStatus() { 20 try { 21 const response = await fetch("/api/extension-status.ashx", { 22 cache: "no-store", 23 headers: { Accept: "application/json" } 24 }); 25 const data = await response.json(); 26 if (!response.ok || !data.ok || !data.stores) { 27 return; 28 } 29 30 const chrome = data.stores.chrome || {}; 31 const edge = data.stores.edge || {}; 32 const safari = data.stores.safari || {}; 33 34 updateText("[data-extension-user-count]", formatCount(data.totalUsers)); 35 updateText("[data-extension-chrome-users]", formatCount(chrome.users)); 36 updateText("[data-extension-edge-users]", formatCount(edge.users)); 37 updateText("[data-extension-safari-users]", formatOptionalCount(safari.users)); 38 updateText("[data-extension-count-checked]", formatCheckedDate(data.checkedUtc)); 39 updateText("[data-extension-chrome-version]", chrome.version); 40 updateText("[data-extension-edge-version]", edge.version); 41 updateText("[data-extension-safari-version]", formatVersion(safari.version)); 42 43 const countContainer = document.querySelector("[data-extension-count-container]"); 44 if (countContainer) { 45 countContainer.title = `Combined store-reported reach: Chrome and Edge users plus Safari App Store Connect downloads, last checked ${formatCheckedDate(data.checkedUtc)}.`; 46 } 47 } catch (_) { 48 // Static last-known-good values remain visible when the cache API is unavailable. 49 } 50 } 51 52 function updateText(selector, value) { 53 if (value === "" || value === null || value === undefined) { 54 return; 55 } 56 document.querySelectorAll(selector).forEach((element) => { 57 element.textContent = value; 58 }); 59 } 60 61 function formatCount(value) { 62 const count = Number(value); 63 return Number.isFinite(count) && count >= 0 64 ? new Intl.NumberFormat("en-US").format(count) 65 : ""; 66 } 67 68 function formatOptionalCount(value) { 69 const count = Number(value); 70 if (!Number.isFinite(count) || count < 0) { 71 return "pending"; 72 } 73 return new Intl.NumberFormat("en-US").format(count); 74 } 75 76 function formatVersion(value) { 77 if (!value || String(value).trim() === "") { 78 return "pending"; 79 } 80 return value; 81 } 82 83 function formatCheckedDate(value) { 84 const date = new Date(value || ""); 85 if (Number.isNaN(date.getTime())) { 86 return ""; 87 } 88 return new Intl.DateTimeFormat("en-US", { 89 month: "long", 90 day: "numeric", 91 year: "numeric", 92 timeZone: "America/Los_Angeles" 93 }).format(date); 94 } 95})();
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.