Skip to content

Commit

Permalink
add compatibility guard
Browse files Browse the repository at this point in the history
  • Loading branch information
stanislav-atr committed Jan 11, 2023
1 parent 9531694 commit 51056fe
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 2 deletions.
18 changes: 16 additions & 2 deletions scripts/build-funcs.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ import path from 'path';
import { minify } from 'terser';
import { addCall, attachDependencies } from '../src/helpers/injector';
import { writeFile } from './helpers';
import { checkCompatibility } from '../src/helpers';

/**
* Creates compatibility guard string
* to be injected into scriptlets
*
* @returns {string}
*/
function prepareCompatibilityGuard() {
const compatibilityGuard = checkCompatibility;
const compatibilityGuardString = attachDependencies(compatibilityGuard);
return `${compatibilityGuardString}\n${compatibilityGuard.name}()`;
}

/**
* Method creates string for file with scriptlets functions,
Expand All @@ -27,10 +40,11 @@ import { writeFile } from './helpers';
* @returns {string}
*/
const getScriptletFunctionsString = () => {
const compatibilityGuardString = prepareCompatibilityGuard();

function wrapInFunc(name, code) {
return `function ${name}(source, args){\n${code}\n}`;
return `function ${name}(source, args){\n${compatibilityGuardString}\n${code}\n}`;
}

// we require scriptlets list dynamically, because scriptletsList file can be not built in the
// moment of this script execution
// eslint-disable-next-line import/no-unresolved,global-require
Expand Down
1 change: 1 addition & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export * from './parse-flags';
export * from './parse-keyword-value';
export * from './random-id';
export * from './throttle';
export * from './is-browser-supported';
172 changes: 172 additions & 0 deletions src/helpers/is-browser-supported.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
const BROWSER_NAMES = {
CHROME: 'Chrome',
FIREFOX: 'Firefox',
EDGE: 'Edg',
OPERA: 'Opera',
SAFARI: 'Safari',
};

const CHROMIUM_BRAND_NAME = 'Chromium';
const GOOGLE_CHROME_BRAND_NAME = 'Google Chrome';

const SUPPORTED_BROWSERS_DATA = {
[BROWSER_NAMES.CHROME]: {
// avoid Chromium-based Edge browser
MASK: /\s(Chrome)\/(\d+)\..+\s(?!.*Edg\/)/,
MIN_VERSION: 55,
},
[BROWSER_NAMES.FIREFOX]: {
MASK: /\s(Firefox)\/(\d+)\./,
MIN_VERSION: 52,
},
[BROWSER_NAMES.EDGE]: {
MASK: /\s(Edg)\/(\d+)\./,
MIN_VERSION: 15,
},
[BROWSER_NAMES.OPERA]: {
MASK: /\s(OPR)\/(\d+)\./,
MIN_VERSION: 42,
},
[BROWSER_NAMES.SAFARI]: {
MASK: /\sVersion\/(\d+)\..+\s(Safari)\//,
MIN_VERSION: 11,
},
};

/**
* @typedef {Object} BrandData
* @property {string} brand a string containing the brand, for example, "Google Chrome".
* @property {string} version a string containing the version, for example, "91".
*/

/**
* Returns chromium brand object from navigator.userAgentData.brands or null if not supported.
* Chromium because of all browsers based on it should be supported as well
* and it is universal wey to check it
* https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/brands
*
* @returns {BrandData|null} chromium brand object
*/
const getChromiumBrand = () => {
const brandsData = navigator.userAgentData?.brands;
if (!brandsData) {
return null;
}
// for chromium-based browsers
const chromiumBrand = brandsData.find((brandData) => {
return brandData.brand === CHROMIUM_BRAND_NAME
|| brandData.brand === GOOGLE_CHROME_BRAND_NAME;
});
return chromiumBrand || null;
};

/**
* Parses userAgent string
*
* @returns {Object|null} data object for supported browsers
*/
const parseUserAgent = () => {
let browserName;
let currentVersion;
const browserNames = Object.values(BROWSER_NAMES);

for (let i = 0; i < browserNames.length; i += 1) {
const match = SUPPORTED_BROWSERS_DATA[browserNames[i]].MASK.exec(navigator.userAgent);
if (match) {
// for safari order is different because of regexp
if (match[2] === browserNames[i]) {
// eslint-disable-next-line prefer-destructuring
browserName = match[2];
currentVersion = Number(match[1]);
} else {
// for others first is name and second is version
// eslint-disable-next-line prefer-destructuring
browserName = match[1];
currentVersion = Number(match[2]);
}
return { browserName, currentVersion };
}
}

return null;
};

/**
* @typedef {Object} BrowserInfo
* @property {string} browserName a string containing the brand, for example, "Google Chrome".
* @property {number} currentVersion current browser version
*/

/**
* Gets info about current browser
*
* @returns {BrowserInfo} current browser info
*/
const getCurrentBrowserInfoAsSupported = () => {
const brandData = getChromiumBrand();
if (!brandData) {
const uaInfo = parseUserAgent();
if (!uaInfo) {
return null;
}
const { browserName, currentVersion } = uaInfo;
return { browserName, currentVersion };
}

// if navigator.userAgentData is supported
const { brand, version } = brandData;
// handle chromium-based browsers
const browserName = brand === CHROMIUM_BRAND_NAME || brand === GOOGLE_CHROME_BRAND_NAME
? BROWSER_NAMES.CHROME
: brand;
return { browserName, currentVersion: Number(version) };
};

/**
* Checks whether the current browser is supported
*
* This function, it's dependencies and it's call are attached to each scriptlet
* by injector, while building scriptlets
*
* @returns {boolean} if current browser is supported
*/
export function isBrowserSupported() {
const ua = navigator.userAgent;
// do not support Internet Explorer
if (ua.includes('MSIE') || ua.includes('Trident/')) {
return false;
}

// for local testing purposes
if (ua.includes('jsdom')) {
return true;
}

const currentBrowserData = getCurrentBrowserInfoAsSupported();
if (!currentBrowserData) {
return false;
}

const { browserName, currentVersion } = currentBrowserData;

return currentVersion >= SUPPORTED_BROWSERS_DATA[browserName].MIN_VERSION;
}

/**
* Checks if current browser is supported
* to be injected to scriptlets at building
*
* @throws on unsupported browser
*/
export function checkCompatibility() {
if (!isBrowserSupported()) {
throw new Error('Browser is not supported by Scriptlets.');
}
}

checkCompatibility.injections = [
isBrowserSupported,
getCurrentBrowserInfoAsSupported,
getChromiumBrand,
parseUserAgent,
];

0 comments on commit 51056fe

Please sign in to comment.