/** * @license * Copyright The Closure Library Authors. * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Utilities used by goog.labs.userAgent tools. These functions * should not be used outside of goog.labs.userAgent.*. * */ goog.module('goog.labs.userAgent.util'); goog.module.declareLegacyNamespace(); const {USE_CLIENT_HINTS} = goog.require('goog.labs.userAgent'); const {caseInsensitiveContains, contains} = goog.require('goog.string.internal'); /** * @const {boolean} If true, use navigator.userAgentData without check. * TODO(user) FEATURESET_YEAR >= 2022 if it supports mobile and all the * brands we need. */ const ASSUME_CLIENT_HINTS_SUPPORT = false; /** * Gets the native userAgent string from navigator if it exists. * If navigator or navigator.userAgent string is missing, returns an empty * string. * @return {string} */ function getNativeUserAgentString() { const navigator = getNavigator(); if (navigator) { const userAgent = navigator.userAgent; if (userAgent) { return userAgent; } } return ''; } /** * Gets the native userAgentData object from navigator if it exists. * If navigator.userAgentData object is missing or USE_CLIENT_HINTS is set to * false, returns null. * @return {?NavigatorUAData} */ function getNativeUserAgentData() { if (!USE_CLIENT_HINTS) { return null; } const navigator = getNavigator(); // TODO(user): Use navigator?.userAgent ?? null once it's supported. if (navigator) { return navigator.userAgentData || null; } return null; } /** * Getter for the native navigator. * @return {!Navigator} */ function getNavigator() { return goog.global.navigator; } /** * A possible override for applications which wish to not check * navigator.userAgent but use a specified value for detection instead. * @type {string} */ let userAgentInternal = getNativeUserAgentString(); /** * A possible override for applications which wish to not check * navigator.userAgentData but use a specified value for detection instead. * @type {?NavigatorUAData} */ let userAgentDataInternal = getNativeUserAgentData(); /** * Override the user agent string with the given value. * This should only be used for testing within the goog.labs.userAgent * namespace. * Pass `null` to use the native browser object instead. * @param {?string=} userAgent The userAgent override. * @return {void} */ function setUserAgent(userAgent = undefined) { userAgentInternal = typeof userAgent === 'string' ? userAgent : getNativeUserAgentString(); } /** @return {string} The user agent string. */ function getUserAgent() { return userAgentInternal; } /** * Override the user agent data object with the given value. * This should only be used for testing within the goog.labs.userAgent * namespace. * Pass `null` to specify the absence of userAgentData. Note that this behavior * is different from setUserAgent. * @param {?NavigatorUAData} userAgentData The userAgentData override. */ function setUserAgentData(userAgentData) { userAgentDataInternal = userAgentData; } /** * If the user agent data object was overridden using setUserAgentData, * reset it so that it uses the native browser object instead, if it exists. */ function resetUserAgentData() { userAgentDataInternal = getNativeUserAgentData(); } /** @return {?NavigatorUAData} Navigator.userAgentData if exist */ function getUserAgentData() { return userAgentDataInternal; } /** * Checks if any string in userAgentData.brands matches str. * Returns false if userAgentData is not supported. * @param {string} str * @return {boolean} Whether any brand string from userAgentData contains the * given string. */ function matchUserAgentDataBrand(str) { const data = getUserAgentData(); if (!data) return false; return data.brands.some(({brand}) => brand && contains(brand, str)); } /** * @param {string} str * @return {boolean} Whether the user agent contains the given string. */ function matchUserAgent(str) { const userAgent = getUserAgent(); return contains(userAgent, str); } /** * @param {string} str * @return {boolean} Whether the user agent contains the given string, ignoring * case. */ function matchUserAgentIgnoreCase(str) { const userAgent = getUserAgent(); return caseInsensitiveContains(userAgent, str); } /** * Parses the user agent into tuples for each section. * @param {string} userAgent * @return {!Array>} Tuples of key, version, and the contents of * the parenthetical. */ function extractVersionTuples(userAgent) { // Matches each section of a user agent string. // Example UA: // Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) // AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405 // This has three version tuples: Mozilla, AppleWebKit, and Mobile. const versionRegExp = new RegExp( // Key. Note that a key may have a space. // (i.e. 'Mobile Safari' in 'Mobile Safari/5.0') '(\\w[\\w ]+)' + '/' + // slash '([^\\s]+)' + // version (i.e. '5.0b') '\\s*' + // whitespace '(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched. 'g'); const data = []; let match; // Iterate and collect the version tuples. Each iteration will be the // next regex match. while (match = versionRegExp.exec(userAgent)) { data.push([ match[1], // key match[2], // value // || undefined as this is not undefined in IE7 and IE8 match[3] || undefined // info ]); } return data; } exports = { ASSUME_CLIENT_HINTS_SUPPORT, extractVersionTuples, getNativeUserAgentString, getUserAgent, getUserAgentData, matchUserAgent, matchUserAgentDataBrand, matchUserAgentIgnoreCase, resetUserAgentData, setUserAgent, setUserAgentData, };