Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Safari 17 to Karma and remove some old browsers #150

Merged
merged 14 commits into from
Dec 11, 2023
104 changes: 7 additions & 97 deletions docs/browser_support.md
Original file line number Diff line number Diff line change
@@ -1,105 +1,15 @@
# Browser support

The library supports all popular browsers.
We use the following command to determine which browsers to support:

```bash
npx browserslist "cover 96% in us, not IE < 11"
```
We aim to cover at least 99% of all users according to the Fingerprint Pro statistics.

At the moment, these browsers are:

- **Internet Explorer** 11 ([see the section below](#old-browsers-requirements))
- **Edge** 93+
- **Chrome** 49+
- **Firefox** 52+
- **Desktop Safari** 12.1+
- **Mobile Safari** 10.3+
- **Samsung Internet** 14.0+
- **Android Browser** 4.4+ ([see the section below](#old-browsers-requirements))
- **Edge** 105+
- **Chrome** 65+
- **Firefox** 75+
- **Desktop Safari** 12.1+
- **Mobile Safari** 12.0+
- **Samsung Internet** 14.0+

Other browsers will probably also work, but we don't guarantee it.

## Old browsers requirements

### `import()` support

If you use the "Browser ECMAScript module" installation methods, you may have an error.
Old browsers don't support [import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import).
Replace it with a `<script>` tag:

```diff
+ // Note that we use iife.min.js with older browsers
+ <script src="https://openfpcdn.io/botd/v1/iife.min.js"></script>
<script>
- const botdPromise = import('https://openfpcdn.io/botd/v1')
- .then(BotD => BotD.load())
+ var botdPromise = BotD.load()

// ...
</script>
```

### Polyfills

Very old browsers like Internet Explorer 11 and Android Browser 4.4
require a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) polyfill to work.
Add a Promise polyfill before loading the BotD agent.
Examples for various installation methods:

#### Webpack/Rollup/NPM/Yarn

```bash
# Install the polyfill package first:
npm i promise-polyfill
# or
yarn add promise-polyfill
```

```diff
+ import 'promise-polyfill/src/polyfill'
import BotD from '@fingerprintjs/botd'

// ...
```

#### UMD

```diff
require(
[
'https://openfpcdn.io/botd/v1/umd.min.js',
+ 'https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js',
],
function (BotD) {
// ...
}
)
```

#### CommonJS syntax:

```diff
+ require('promise-polyfill/src/polyfill')
const BotD = require('@fingerprintjs/botd')

// ...
```

### Code syntax

Old browsers like IE11 don't support `const`, `let` and arrow functions (`=>`).
Use `var` and the classic function syntax instead:

```diff
- const botdPromise = BotD.load()
+ var botdPromise = BotD.load()

botdPromise
- .then(fp => fp.detect())
+ .then(function (fp) { return fp.detect() })
- .then(result => {
+ .then(function (result) {
// Handle the result
})
```
190 changes: 10 additions & 180 deletions karma.conf.ts
Original file line number Diff line number Diff line change
@@ -1,181 +1,11 @@
import { Config, CustomLauncher } from 'karma'
import { KarmaTypescriptConfig } from 'karma-typescript/dist/api/configuration'
import { karmaPlugin, setHttpsAndServerForKarma, BrowserFlags } from '@fpjs-incubator/broyster/node'

declare module 'karma' {
interface ConfigOptions {
karmaTypescriptConfig?: KarmaTypescriptConfig | undefined
}

interface Config extends ConfigOptions {
preset?: string
reporters: ConfigOptions['reporters']
}
}

// The shapes of these objects are taken from:
// https://github.com/SeleniumHQ/selenium/tree/d8ddb4d83972df0f565ef65264bcb733e7a94584/javascript/node/selenium-webdriver
// It doesn't work, trying to work it out with BrowserStack support. Todo: solve it with the support.
/*
const chromeIncognitoCapabilities = {
'goog:chromeOptions': {
args: ['--incognito'],
import { makeKarmaConfigurator } from '@fpjs-incubator/broyster/node'

module.exports = makeKarmaConfigurator({
projectName: 'BotD',
includeFiles: ['src/**/*.ts', 'tests/**/*.ts', 'test-dist/botd.min.js'],
configureCustom(karmaConfig) {
karmaConfig.set({
failOnEmptyTestSuite: false,
})
},
}
const firefoxIncognitoCapabilities = {
'moz:firefoxOptions': {
prefs: {
'browser.privatebrowsing.autostart': true,
},
},
}
*/

/*
* You can find values for any supported browsers in the interactive form at
* https://www.browserstack.com/docs/automate/javascript-testing/configure-test-run-options
* The keys are arbitrary values.
*
* Only Chrome is supported on Android, only Safari is supported on iOS: https://www.browserstack.com/question/659
*/
/* eslint-disable max-len */
// prettier-ignore
const browserstackBrowsers = {
Windows10_Chrome57: { platform: 'Windows', osVersion: '10', browserName: 'Chrome', browserVersion: '57', useHttps: true },
// Windows10_Chrome57_Incognito: { platform: 'Windows', osVersion: '10', browserName: 'Chrome', browserVersion: '57', ...chromeIncognitoCapabilities },
Windows11_ChromeLatest: { platform: 'Windows', osVersion: '11', browserName: 'Chrome', browserVersion: 'latest-beta', useHttps: true },
// Windows11_ChromeLatest_Incognito: { platform: 'Windows', osVersion: '11', browserName: 'Chrome', browserVersion: 'latest-beta, ...chromeIncognitoCapabilities },
Windows10_Firefox67: { platform: 'Windows', osVersion: '10', browserName: 'Firefox', browserVersion: '67', useHttps: true },
// Windows10_Firefox67_Incognito: { platform: 'Windows', osVersion: '10', browserName: 'Firefox', browserVersion: '67', ...firefoxIncognitoCapabilities },
Windows11_FirefoxLatest: { platform: 'Windows', osVersion: '11', browserName: 'Firefox', browserVersion: 'latest-beta', useHttps: true },
// Windows11_FirefoxLatest_Incognito: { platform: 'Windows', osVersion: '11', browserName: 'Firefox', browserVersion: 'latest-beta, ...firefoxIncognitoCapabilities },
Windows11_EdgeLatest: { platform: 'Windows', osVersion: '11', browserName: 'Edge', browserVersion: 'latest-beta', useHttps: true },
'OSX10.14_Safari12': { platform: 'OS X', osVersion: 'Mojave', browserName: 'Safari', browserVersion: '12', useHttps: true },
OSX12_Safari15: { platform: 'OS X', osVersion: 'Monterey', browserName: 'Safari', browserVersion: '15', useHttps: false },
OSX13_Safari16: { platform: 'OS X', osVersion: 'Ventura', browserName: 'Safari', browserVersion: '16', useHttps: false },
OSX13_ChromeLatest: { platform: 'OS X', osVersion: 'Ventura', browserName: 'Chrome', browserVersion: 'latest-beta', useHttps: true },
// OSX13_ChromeLatest_Incognito: { platform: 'OS X', osVersion: 'Ventura', browserName: 'Chrome', browserVersion: 'latest-beta, ...chromeIncognitoCapabilities },
OSX13_FirefoxLatest: { platform: 'OS X', osVersion: 'Ventura', browserName: 'Firefox', browserVersion: 'latest-beta', useHttps: true },
// OSX13_FirefoxLatest_Incognito: { platform: 'OS X', osVersion: 'Ventura', browserName: 'Firefox', browserVersion: 'latest-beta, ...firefoxIncognitoCapabilities },
OSX13_EdgeLatest: { platform: 'OS X', osVersion: 'Ventura', browserName: 'Edge', browserVersion: 'latest-beta', useHttps: true },
Android13_ChromeLatest: { deviceName: ['Google Pixel 7', 'Google Pixel 7 Pro', 'Google Pixel 6 Pro'], platform: 'Android', osVersion: '13.0', browserName: 'Chrome', browserVersion: 'latest-beta', useHttps: true, flags: [BrowserFlags.MobileUserAgent], },
iOS11_Safari: { deviceName: ['iPhone 8 Plus', 'iPhone 6S', 'iPhone 8', 'iPhone 6'], platform: 'iOS', osVersion: '11', browserName: 'Safari', useHttps: true, flags: [BrowserFlags.MobileUserAgent], },
iOS12_Safari: { deviceName: ['iPhone XS', 'iPhone 6S', 'iPhone 8 Plus', 'iPhone XR'], platform: 'iOS', osVersion: '12', browserName: 'Safari', useHttps: true, flags: [BrowserFlags.MobileUserAgent], },
iOS13_Safari: { deviceName: ['iPhone 11 Pro', 'iPhone 8', 'iPhone XS', 'iPhone 11 Pro Max'], platform: 'iOS', osVersion: '13', browserName: 'Safari', useHttps: true, flags: [BrowserFlags.MobileUserAgent], },
iOS14_Safari: { deviceName: ['iPhone 11', 'iPhone XS', 'iPhone 12 Pro', 'iPhone 12 mini'], platform: 'iOS', osVersion: '14', browserName: 'Safari', useHttps: true, flags: [BrowserFlags.MobileUserAgent], },
iOS15_Safari: { deviceName: ['iPhone 13', 'iPhone 13 Mini', 'iPhone 11 Pro', 'iPhone 11'], platform: 'iOS', osVersion: '15', browserName: 'Safari', useHttps: true, flags: [BrowserFlags.MobileUserAgent], },
iOS16_Safari: { deviceName: ['iPhone 14', 'iPhone 14 Pro Max', 'iPhone 14 Pro', 'iPhone 14 Plus'], platform: 'iOS', osVersion: '16', browserName: 'Safari', useHttps: true, flags: [BrowserFlags.MobileUserAgent],},
}

/* eslint-enable max-len */

function makeBuildNumber() {
return `No CI ${Math.floor(Math.random() * 1e10)}`
}

function setupLocal(config: Config) {
const ciSpecificFiles = ['resources/karma/karma_global_setup_retries.ts']
const ciEnabled = process.env.CI
const files = [...(ciEnabled ? ciSpecificFiles : []), 'src/**/*.ts', 'tests/**/*.ts', 'test-dist/botd.min.js']

config.set({
frameworks: ['jasmine', 'karma-typescript'],
files,
preprocessors: {
'**/*.ts': 'karma-typescript',
},
reporters: ['spec', 'summary'],
browsers: ['ChromeHeadless', 'FirefoxHeadless'],
concurrency: 3,

karmaTypescriptConfig: {
tsconfig: 'tsconfig.json',
compilerOptions: {
module: 'commonjs',
sourceMap: true,
},
include: files,
},

specReporter: {
suppressSummary: true,
suppressErrorSummary: true,
suppressPassed: true,
suppressSkipped: true,
},
})
}

function setupBrowserstack(config: Config) {
setupLocal(config)

const customLaunchers: Record<string, CustomLauncher> = {}
for (const [key, data] of Object.entries(browserstackBrowsers)) {
customLaunchers[key] = {
base: 'BrowserStack',
name: key.replace(/_/g, ' '),
...data,
}
}

config.set({
reporters: [...(config.reporters || []), 'BrowserStack'],
plugins: [karmaPlugin, 'karma-*'],
browsers: Object.keys(customLaunchers),
customLaunchers,
concurrency: 5,
failOnEmptyTestSuite: false,
retryLimit: 3,
captureTimeout: 15_000,
browserStack: {
project: 'BotD',
// A build number is required to group testing sessions in the BrowserStack UI.
// GitHub Actions will add a value for GITHUB_RUN_ID. More on the environment variables:
// https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables#default-environment-variables
build: process.env.GITHUB_RUN_ID || makeBuildNumber(),
// The timeout is reduced for testing sessions to not hold the BrowserStack queue long in case of problems.
idleTimeout: 20_000,
queueTimeout: 300_000,
},
})
setHttpsAndServerForKarma(config)
}

function setupBrowserStackBetaBuilds(config: Config) {
setupBrowserstack(config)

const customLaunchers: Record<string, CustomLauncher> = {}
for (const [key, data] of Object.entries(browserstackBrowsers)) {
if ('browserVersion' in data && data['browserVersion'].includes('beta')) {
customLaunchers[key] = {
base: 'BrowserStack',
name: key.replace(/_/g, ' '),
...data,
}
}
}

config.set({
browsers: Object.keys(customLaunchers),
customLaunchers,

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
browserStack: { ...config.browserStack!, project: 'Monitoring' },
})
}

/**
* Add `--preset local` or `--preset browserstack` to the Karma command to choose where to run the tests.
*/
module.exports = (config: Config) => {
switch (config.preset) {
case 'local':
return setupLocal(config)
case 'browserstack':
return setupBrowserstack(config)
case 'browserstack-beta':
return setupBrowserStackBetaBuilds(config)
default:
throw new Error('No --preset option is set or an unknown value is set')
}
}
})
11 changes: 1 addition & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,11 @@
"check:ssr": "node --require ./dist/botd.cjs.js --eval '' || (echo \"The distributive files can't be used with server side rendering. Make sure the code doesn't use browser API until an exported function is called.\" && exit 1)"
},
"devDependencies": {
"@fpjs-incubator/broyster": "^0.1.5",
"@fpjs-incubator/broyster": "^0.1.7",
"@rollup/plugin-json": "^5.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
"@types/jasmine": "^3.5.14",
"@types/karma": "^6.3.3",
"@types/karma-spec-reporter": "^0.0.3",
"@types/karma-summary-reporter": "^3.1.0",
"@types/ua-parser-js": "^0.7.36",
"@types/webpack": "^5.28.0",
"@typescript-eslint/eslint-plugin": "^5.40.1",
Expand All @@ -67,12 +64,6 @@
"eslint-plugin-prettier": "^3.3.1",
"html-webpack-plugin": "^5.5.0",
"karma": "^6.4.1",
"karma-chrome-launcher": "^3.1.1",
"karma-firefox-launcher": "^2.1.2",
"karma-jasmine": "^4.0.2",
"karma-spec-reporter": "^0.0.34",
"karma-summary-reporter": "^3.1.1",
"karma-typescript": "^5.5.3",
"license-webpack-plugin": "^4.0.2",
"prettier": "^2.2.1",
"promise-polyfill": "^8.2.0",
Expand Down
4 changes: 4 additions & 0 deletions playground/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
getMozAppearanceSupport,
isAndroid,
isDesktopWebKit,
isIPad,
} from '../src/utils/browser'
import { getBrowserVersion } from '../tests/utils'
import './style.css'

type DetectionResult =
Expand Down Expand Up @@ -40,10 +42,12 @@ const runDetection = async (): Promise<DetectionResult> => {
const debugData = {
browserEngineKind: getBrowserEngineKind(),
browserKind: getBrowserKind(),
browserVersion: getBrowserVersion(),
documentFocus: getDocumentFocus(),
mozAppearanceSupport: getMozAppearanceSupport(),
isAndroid: isAndroid(),
isDesktopWebKit: isDesktopWebKit(),
isIPad: isIPad(),
}

return {
Expand Down
2 changes: 2 additions & 0 deletions playground/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const config: Configuration = {

performance: {
hints: isDev ? false : 'error',
maxAssetSize: Infinity,
maxEntrypointSize: Infinity,
},
}

Expand Down
3 changes: 0 additions & 3 deletions resources/karma/karma_global_setup_retries.ts

This file was deleted.

11 changes: 10 additions & 1 deletion src/sources/notification_permissions.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { getBrowserMajorVersion, getBrowserVersion, isHeadlessChrome, isMobile, isWebKit } from '../../tests/utils'
import { BotdError } from '../types'
import { BotdError, BrowserEngineKind, BrowserKind } from '../types'
import { getBrowserEngineKind, getBrowserKind, isDesktopWebKit } from '../utils/browser'
import getNotificationPermissions from './notification_permissions'

describe('Sources', () => {
describe('notificaionPermissions', () => {
it('returns an expected value or throws', async () => {
if (
getBrowserKind() === BrowserKind.Safari &&
getBrowserEngineKind() === BrowserEngineKind.Webkit &&
!isDesktopWebKit()
) {
return
bayotop marked this conversation as resolved.
Show resolved Hide resolved
}

const { major, minor } = getBrowserVersion() ?? { major: 0, minor: 0 }
if (isWebKit() && (major < 16 || (major === 16 && minor < 5))) {
await expectAsync(getNotificationPermissions()).toBeRejectedWithError(
Expand Down
Loading
Loading