UNPKG

coach-core

Version:
222 lines (203 loc) 6.76 kB
'use strict'; const util = { /** * Make your URL absolute. * @memberof util * @param {String} url The URL to convert to absolute. * @returns {String} the absolute URL including host/protocol. */ getAbsoluteURL: function (url) { const a = globalThis.document.createElement('a'); a.href = url; return a.href; }, /** * Get the hostname from a URL * @memberof util * @param {String} url The URL. * @returns {String} the hostname from the URL. */ getHostname: function (url) { const a = globalThis.document.createElement('a'); a.href = url; return a.hostname; }, /** * Checks if an element exist in an array. * * @memberof util * @param {String} element the element. * @param {Array} array the array. * @returns {Boolean} true if the element exist. */ exists: function (element, array) { return array.includes(element); }, /** * Returns an array filter function for finding tags with an attribute specified value, matching case insensitive. * @param attributeName the name of the attribute to look for (html attribute values are always case insensitive). * @param attributeValue the value to match against, ignoring case. * @returns {Function} function that can be passed to Array#filter */ caseInsensitiveAttributeValueFilter: function ( attributeName, attributeValue ) { return function (item) { const attribute = item.getAttribute(attributeName) || ''; if (attribute.toLowerCase() === attributeValue.toLowerCase()) { return item; } return; }; }, /** * Is the connection used for the main document using HTTP/2? * Works in Chrome, Firefox, Edge and other browsers that supports Resource Timing * API v2 (not Safari yet). * @memberof util * @returns {Boolean} true if the connection is HTTP/2 */ isHTTP2: function () { const type = util.getConnectionType().toLowerCase(); return type === 'h2'; }, /** * Is the connection used for the main document using HTTP/3? * @memberof util * @returns {Boolean} true if the connection is HTTP/3 */ isHTTP3: function () { const type = util.getConnectionType().toLowerCase(); return type.startsWith('h3'); }, /** * Get the connection type used for the main document. Works in Chrome, Firefox, * Edge and + browsers that support Resource Timing * API v2. * @memberof util * @returns {String} http/1 or h2 for http 1 and 2 respectively. 'unknown' if browser lacks api to determine it. */ getConnectionType: function () { // it's easy in Chrome if ( globalThis.performance.getEntriesByType('navigation') && globalThis.performance.getEntriesByType('navigation')[0] && globalThis.performance.getEntriesByType('navigation')[0].nextHopProtocol ) { return globalThis.performance.getEntriesByType('navigation')[0] .nextHopProtocol; } else if ( globalThis.performance && globalThis.performance.getEntriesByType && globalThis.performance.getEntriesByType('resource') ) { // if you support resource timing v2 // it's kind of easy too const resources = globalThis.performance.getEntriesByType('resource'); // now we "only" need to know if it is v2 if (resources.length > 1 && resources[0].nextHopProtocol) { // if it's the same domain, say it's OK const host = document.domain; for (let i = 0, len = resources.length; i < len; i++) { if (host === util.getHostname(resources[i].name)) { return resources[i].nextHopProtocol; } } } } return 'unknown'; }, /** * Get JavaScript requests that are loaded synchronously. All URLs are absolute. * @memberof util * @param {Object} parentElement the parent element that has all the scripts. * @returns {Array} an array with the URL to each JavaScript file that is loaded synchronously. */ getSynchJSFiles: function (parentElement) { const scripts = Array.prototype.slice.call( parentElement.querySelectorAll('script') ); return scripts .filter(function (s) { return !s.async && s.src && !s.defer; }) .map(function (s) { return util.getAbsoluteURL(s.src); }); }, /** * Get JavaScript requests that are loaded asynchronously. All URLs are absolute. * @memberof util * @param {Object} parentElement the parent element that has all the scripts. * @returns {Array} an array with the URL to each JavaScript file that are loaded asynchronously. */ getAsynchJSFiles: function (parentElement) { const scripts = Array.prototype.slice.call( parentElement.querySelectorAll('script') ); return scripts .filter(function (s) { return s.async && s.src; }) .map(function (s) { return util.getAbsoluteURL(s.src); }); }, /** * Get Resource Hints hrefs by type * @memberof util * @param {String} type the name of the Resources hint: dns-prefetch, preconnect, prefetch, prerender * @returns {Array} an array of matching hrefs */ getResourceHintsHrefs: function (type) { const links = Array.prototype.slice.call( globalThis.document.head.querySelectorAll('link') ); return links .filter(function (link) { return link.rel === type; }) .map(function (link) { return link.href; }); }, /** * Get CSS requests. All URLs are absolute. * @memberof util * @param {Object} parentElement the parent element that has all the scripts. * @returns {Array} an array with the URL to each CSS file that is loaded synchronously. */ getCSSFiles: function (parentElement) { const links = Array.prototype.slice.call( parentElement.querySelectorAll('link') ); return links .filter(function (link) { // make sure we skip data: return link.rel === 'stylesheet' && !link.href.startsWith('data:'); }) .map(function (link) { return util.getAbsoluteURL(link.href); }); }, plural: function (number, text) { if (number > 1) { text += 's'; } return `${number} ${text}`; }, /** * Get the size of an asset. Will try to use the Resource Timing V2. If that's * not available or the asset size is unknown we report 0. **/ getTransferSize: function (url) { const entries = globalThis.performance.getEntriesByName(url, 'resource'); return entries.length === 1 && typeof entries[0].transferSize === 'number' ? entries[0].transferSize : 0; }, ms(ms) { return ms < 1000 ? ms + ' ms' : Number(ms / 1000).toFixed(3) + ' s'; } };