├── .babelrc ├── .coveralls.yml ├── .editorconfig ├── .eslintrc.yml ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── release-drafter.yml └── workflows │ ├── draft-or-update-next-release.yml │ ├── merge-to-master.yml │ ├── publish.yml │ └── pull-request.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .nycrc ├── .testem.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── Bowser.html ├── BowserUAIsNotAStringError.html ├── Parser.html ├── bowser.js.html ├── global.html ├── index.html ├── parser.js.html ├── scripts │ ├── collapse.js │ ├── jquery-3.1.1.min.js │ ├── linenumber.js │ ├── nav.js │ ├── polyfill.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js │ └── search.js ├── styles │ ├── jsdoc.css │ └── prettify.css └── utils.js.html ├── index.d.ts ├── jsdoc.json ├── package-lock.json ├── package.json ├── src ├── bowser.js ├── constants.js ├── parser-browsers.js ├── parser-engines.js ├── parser-os.js ├── parser-platforms.js ├── parser.js └── utils.js ├── test ├── acceptance │ ├── test-list-of-ua.js │ └── useragentstrings.yml └── unit │ ├── bowser.js │ ├── constants.js │ ├── parser.js │ └── utils.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", { 3 | "useBuiltIns": "entry", 4 | "modules": "cjs", 5 | "loose": true, 6 | "targets": { 7 | "ie": "8", 8 | "browsers": ">2%" 9 | } 10 | }]], 11 | "plugins": [ 12 | "add-module-exports" 13 | ], 14 | "env": { 15 | "test": { 16 | "plugins": [ "istanbul" ], 17 | "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [{*.js,*.md}] 9 | charset = utf-8 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [Makefile] 14 | indent_style = tab 15 | 16 | [{package.json,.travis.yml}] 17 | indent_style = space 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parser: babel-eslint 2 | extends: airbnb-base 3 | rules: 4 | no-underscore-dangle: 0 5 | no-void: 0 6 | import/extensions: 7 | - 'error' 8 | - 'ignorePackages' 9 | - {js: 'always'} 10 | import/prefer-default-export: 1 11 | 12 | plugins: 13 | - ava 14 | - import 15 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We're always open to pull requests or code reviews. Everyone can become a permanent contributor. Just ping @lancedikson in the issues or on Twitter ❤️ 4 | 5 | ## Branches 6 | 7 | The project runs Git-flow, where the `master` branch is for development and `production` is for production. 8 | 9 | In a nutshell, if you are proposing a new feature that adds totally new functionality to `bowser`, it's better to branch from `master` and make a PR pointing back to `master` as well. 10 | 11 | If it's a small hot-fix, an improvement to the docs, or added support for a new browser/OS/platform/etc, then it's better to branch from `production` and make a PR pointing back to `production`. 12 | 13 | Following these simple rules will really help maintain the repo! Thanks ❤️ 14 | 15 | ## Adding Browser Support and Tests 16 | 17 | See the list in `test/acceptance/useragentstrings.yml` with example user agents and their expected `bowser` object. 18 | 19 | Whenever you add support for new browsers or notice a bug / mismatch, please update the list and check if all tests are still passing. Also, make sure to keep the list of browser aliases up-to-date in `src/constants.js`. 20 | 21 | For creating aliases, keep the following guidelines in mind: 22 | - use only lowercase letters for names 23 | - replace special characters such as space and dashes by underscore 24 | - whenever possible drop the word `browser` from the original browser name 25 | - always check for possible duplicates 26 | - aliases are supposed to also be a shorter version of the original name 27 | 28 | Examples: 29 | `Opera Coast` --> `opera_coast` 30 | `UC Browser` --> `uc` 31 | `SeaMonkey` --> `seamonkey` 32 | 33 | ## Testing 34 | 35 | If you'd like to contribute a change to `bowser`, modify the files in `src/`, and run the following (you'll need `node` + `npm` installed): 36 | 37 | ``` sh 38 | $ npm install 39 | $ npm run build #build 40 | $ npm test #run tests 41 | $ npm run lint #check lint rules 42 | ``` 43 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: lancedikson 2 | open_collective: lancedikson 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Template to report about browser detection issue 2 | 3 | `window.navigator.userAgent` of the browser is: ... 4 | And it's detected like ... 5 | However, the real name of the browser is ... 6 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION 🌈' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | version-resolver: 4 | major: 5 | labels: 6 | - major 7 | minor: 8 | labels: 9 | - minor 10 | patch: 11 | labels: 12 | - patch 13 | default: patch 14 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 15 | change-title-escapes: '\<*_&' 16 | template: | 17 | ## Changes 18 | 19 | $CHANGES 20 | -------------------------------------------------------------------------------- /.github/workflows/draft-or-update-next-release.yml: -------------------------------------------------------------------------------- 1 | name: 📝 Draft or update next release 2 | concurrency: draft_or_update_next_release 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: 9 | 10 | jobs: 11 | prepare-deployment: 12 | name: 📝 Draft or update next release 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 30 15 | steps: 16 | - uses: actions/checkout@v1 17 | 18 | - uses: release-drafter/release-drafter@v5 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/merge-to-master.yml: -------------------------------------------------------------------------------- 1 | name: 'Merge to master' 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [12.16.3] 14 | 15 | steps: 16 | - name: Get branch name (merge) 17 | if: github.event_name != 'pull_request' 18 | shell: bash 19 | run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV 20 | 21 | - name: Get branch name (pull request) 22 | if: github.event_name == 'pull_request' 23 | shell: bash 24 | run: echo "BRANCH_NAME=$(echo ${GITHUB_HEAD_REF} | tr / -)" >> $GITHUB_ENV 25 | 26 | - uses: actions/checkout@v2 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v1 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | - run: npm i -g nyc 32 | - run: npm ci 33 | - run: npm run build 34 | - run: nyc npm test && nyc report --reporter=text-lcov | ./node_modules/coveralls/bin/coveralls.js 35 | env: 36 | COVERALLS_SERVICE_NAME: GithubActions 37 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 38 | COVERALLS_GIT_BRANCH: ${{ env.BRANCH_NAME }} 39 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | # This job runs when a new release is published 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: 16 16 | registry-url: https://registry.npmjs.org 17 | - uses: actions/cache@v2 18 | with: 19 | path: ~/.npm 20 | key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} 21 | # Store the name of the release 22 | # See https://stackoverflow.com/questions/58177786/get-the-current-pushed-tag-in-github-actions 23 | - run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 24 | - run: npm ci 25 | - run: npm version $RELEASE_VERSION --no-git-tag-version 26 | - run: npm run build 27 | - run: npm publish --access public 28 | env: 29 | NODE_AUTH_TOKEN: ${{ secrets.BOWSER_NPM_PUBLISH_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: 'Pull Request' 2 | on: 3 | pull_request: 4 | types: [opened, reopened, synchronize] 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node: [12.16.3] 12 | name: Node ${{ matrix.node }} 13 | steps: 14 | - name: 'Checkout latest code' 15 | uses: actions/checkout@v3 16 | with: 17 | ref: ${{ github.event.pull_request.head.sha }} 18 | - name: Set up node 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: ${{ matrix.node }} 22 | - name: Install dependencies 23 | run: npm ci 24 | - name: Build 25 | run: npm run build 26 | - name: Run tests 27 | run: npm run test 28 | 29 | lint: 30 | name: 'ESLint' 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout latest code 34 | uses: actions/checkout@v3 35 | with: 36 | ref: ${{ github.event.pull_request.head.sha }} 37 | - name: Set up node 38 | uses: actions/setup-node@v3 39 | with: 40 | node-version: '16' 41 | - name: Install dependencies 42 | run: npm ci 43 | - name: Run ESLint 44 | run: npm run lint:check 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | .nyc_output 4 | coverage 5 | dist 6 | bundled.js* 7 | es5.js* 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | .nyc_output 3 | coverage 4 | **/.* 5 | node_modules 6 | .github 7 | docs 8 | *.gz 9 | jsdoc.json 10 | webpack.config.js 11 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMap": false, 3 | "instrument": false, 4 | "include": [ 5 | "src/**/*.js" 6 | ], 7 | "exclude": [ 8 | "*.js" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "custom", 3 | "src_files": [ 4 | "src/**/*.js", 5 | "test/**/*.js" 6 | ], 7 | "launchers": { 8 | "tap": { 9 | "command": "ava test/**/*.js --tap", 10 | "protocol": "tap" 11 | } 12 | }, 13 | "launch_in_dev": [ "tap" ] 14 | } 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Bowser Changelog 2 | 3 | ### 2.11.0 (Sep 12, 2020) 4 | - [ADD] Added support for aliases in `Parser#is` method (#437) 5 | - [ADD] Added more typings (#438, #427) 6 | - [ADD] Added support for MIUI Browser (#436) 7 | 8 | ### 2.10.0 (Jul 9, 2020) 9 | - [FIX] Fix for Firefox detection on iOS 13 [#415] 10 | - [FIX] Fixes for typings.d.ts [#409] 11 | - [FIX] Updated development dependencies 12 | 13 | ### 2.9.0 (Jan 28, 2020) 14 | - [ADD] Export more methods and constants via .d.ts [#388], [#390] 15 | 16 | ### 2.8.1 (Dec 26, 2019) 17 | - [FIX] Reverted [#382] as it broke build 18 | 19 | ### 2.8.0 (Dec 26, 2019) 20 | - [ADD] Add polyfills for Array.find & Object.assign [#383] 21 | - [ADD] Export constants with types.d.ts [#382] 22 | - [FIX] Add support for WeChat on Windows [#381] 23 | - [FIX] Fix detection of Firefox on iPad [#379] 24 | - [FIX] Add detection of Electron [#375] 25 | - [FIX] Updated dev-dependencies 26 | 27 | ### 2.7.0 (Oct 2, 2019) 28 | - [FIX] Add support for QQ Browser [#362] 29 | - [FIX] Add support for GSA [#364] 30 | - [FIX] Updated dependencies 31 | 32 | ### 2.6.0 (Sep 6, 2019) 33 | - [ADD] Define "module" export in package.json [#354] 34 | - [FIX] Fix Tablet PC detection [#334] 35 | 36 | ### 2.5.4 (Sep 2, 2019) 37 | - [FIX] Exclude docs from the npm package [#349] 38 | 39 | ### 2.5.3 (Aug 4, 2019) 40 | - [FIX] Add MacOS names support [#338] 41 | - [FIX] Point typings.d.ts from package.json [#341] 42 | - [FIX] Upgrade dependencies 43 | 44 | ### 2.5.2 (July 17, 2019) 45 | - [FIX] Fixes the bug undefined method because of failed build (#335) 46 | 47 | ### 2.5.1 (July 17, 2019) 48 | - [FIX] Fixes the bug with a custom Error class (#335) 49 | - [FIX] Fixes the settings for Babel to reduce the bundle size (#259) 50 | 51 | ### 2.5.0 (July 16, 2019) 52 | - [ADD] Add constant output so that users can quickly get all types (#325) 53 | - [FIX] Add support for Roku OS (#332) 54 | - [FIX] Update devDependencies 55 | - [FIX] Fix docs, README and added funding information 56 | 57 | ### 2.4.0 (May 3, 2019) 58 | - [FIX] Update regexp for generic browsers (#310) 59 | - [FIX] Fix issues with module.exports (#318) 60 | - [FIX] Update devDependencies (#316, #321, #322) 61 | - [FIX] Fix docs (#320) 62 | 63 | ### 2.3.0 (April 14, 2019) 64 | - [ADD] Add support for Blink-based MS Edge (#311) 65 | - [ADD] Add more types for TS (#289) 66 | - [FIX] Update dev-dependencies 67 | - [FIX] Update docs 68 | 69 | ### 2.2.1 (April 12, 2019) 70 | - [ADD] Add an alias for Samsung Internet 71 | - [FIX] Fix browser name detection for browsers without an alias (#313) 72 | 73 | ### 2.2.0 (April 7, 2019) 74 | - [ADD] Add short aliases for browser names (#295) 75 | - [FIX] Fix Yandex Browser version detection (#308) 76 | 77 | ### 2.1.2 (March 6, 2019) 78 | - [FIX] Fix buggy `getFirstMatch` reference 79 | 80 | ### 2.1.1 (March 6, 2019) 81 | - [ADD] Add detection of PlayStation 4 (#291) 82 | - [ADD] Deploy docs on GH Pages (#293) 83 | - [FIX] Fix files extensions for importing (#294) 84 | - [FIX] Fix docs (#295) 85 | 86 | ### 2.1.0 (January 24, 2019) 87 | - [ADD] Add new `Parser.getEngineName()` method (#288) 88 | - [ADD] Add detection of ChromeOS (#287) 89 | - [FIX] Fix README 90 | 91 | ### 2.0.0 (January 19, 2019) 92 | - [ADD] Support a non strict equality in `Parser.satisfies()` (#275) 93 | - [ADD] Add Android versions names (#276) 94 | - [ADD] Add a typings file (#277) 95 | - [ADD] Added support for Googlebot recognition (#278) 96 | - [FIX] Update building tools, avoid security issues 97 | 98 | ### 2.0.0-beta.3 (September 15, 2018) 99 | - [FIX] Fix Chrome Mobile detection (#253) 100 | - [FIX] Use built bowser for CI (#252) 101 | - [FIX] Update babel-plugin-add-module-exports (#251) 102 | 103 | ### 2.0.0-beta.2 (September 9, 2018) 104 | - [FIX] Fix failing comparing version through `Parser.satisfies` (#243) 105 | - [FIX] Fix travis testing, include eslint into CI testing 106 | - [FIX] Add support for Maxthon desktop browser (#246) 107 | - [FIX] Add support for Swing browser (#248) 108 | - [DOCS] Regenerate docs 109 | 110 | ### 2.0.0-beta.1 (August 18, 2018) 111 | - [ADD] Add loose version comparison to `Parser.compareVersion()` and `Parser.satisfies()` 112 | - [CHORE] Add CONTRIBUTING.md 113 | - [DOCS] Regenerate docs 114 | 115 | ### 2.0.0-alpha.4 (August 2, 2018) 116 | - [DOCS] Fix usage docs (#238) 117 | - [CHANGE] Make `./es5.js` the main file of the package (#239) 118 | 119 | ### 2.0.0-alpha.3 (July 22, 2018) 120 | - [CHANGE] Rename split and rename `compiled.js` to `es5.js` and `bundled.js` (#231, #236, #237) 121 | - [ADD] Add `Parser.some` (#235) 122 | 123 | ### 2.0.0-alpha.2 (July 17, 2018) 124 | - [CHANGE] Make `src/bowser` main file instead of the bundled one 125 | - [CHANGE] Move the bundled file to the root of the package to make it possible to `require('bowser/compiled')` (#231) 126 | - [REMOVE] Remove `typings.d.ts` before stable release (#232) 127 | - [FIX] Improve Nexus devices detection (#233) 128 | 129 | ### 2.0.0-alpha.1 (July 9, 2018) 130 | - [ADD] `Bowser.getParser()` 131 | - [ADD] `Bowser.parse` 132 | - [ADD] `Parser` class which describes parsing process 133 | - [CHANGE] Change bowser's returning object 134 | - [REMOVE] Remove bower support 135 | 136 | ### 1.9.4 (June 28, 2018) 137 | - [FIX] Fix NAVER Whale browser detection (#220) 138 | - [FIX] Fix MZ Browser browser detection (#219) 139 | - [FIX] Fix Firefox Focus browser detection (#191) 140 | - [FIX] Fix webOS browser detection (#186) 141 | 142 | ### 1.9.3 (March 12, 2018) 143 | - [FIX] Fix `typings.d.ts` — add `ipad`, `iphone`, `ipod` flags to the interface 144 | 145 | ### 1.9.2 (February 5, 2018) 146 | - [FIX] Fix `typings.d.ts` — add `osname` flag to the interface 147 | 148 | ### 1.9.1 (December 22, 2017) 149 | - [FIX] Fix `typings.d.ts` — add `chromium` flag to the interface 150 | 151 | ### 1.9.0 (December 20, 2017) 152 | - [ADD] Add a public method `.detect()` (#205) 153 | - [DOCS] Fix description of `chromium` flag in docs (#206) 154 | 155 | ### 1.8.1 (October 7, 2017) 156 | - [FIX] Fix detection of MS Edge on Android and iOS (#201) 157 | 158 | ### 1.8.0 (October 7, 2017) 159 | - [ADD] Add `osname` into result object (#200) 160 | 161 | ### 1.7.3 (August 30, 2017) 162 | - [FIX] Fix detection of Chrome on Android 8 OPR6 (#193) 163 | 164 | ### 1.7.2 (August 17, 2017) 165 | - [FIX] Fix typings.d.ts according to #185 166 | 167 | ### 1.7.1 (July 13, 2017) 168 | - [ADD] Fix detecting of Tablet PC as tablet (#183) 169 | 170 | ### 1.7.0 (May 18, 2017) 171 | - [ADD] Add OS version support for Windows and macOS (#178) 172 | 173 | ### 1.6.0 (December 5, 2016) 174 | - [ADD] Add some tests for Windows devices (#89) 175 | - [ADD] Add `root` to initialization process (#170) 176 | - [FIX] Upgrade .travis.yml config 177 | 178 | ### 1.5.0 (October 31, 2016) 179 | - [ADD] Throw an error when `minVersion` map has not a string as a version and fix readme (#165) 180 | - [FIX] Fix truly detection of Windows Phones (#167) 181 | 182 | ### 1.4.6 (September 19, 2016) 183 | - [FIX] Fix mobile Opera's version detection on Android 184 | - [FIX] Fix typescript typings — add `mobile` and `tablet` flags 185 | - [DOC] Fix description of `bowser.check` 186 | 187 | ### 1.4.5 (August 30, 2016) 188 | 189 | - [FIX] Add support of Samsung Internet for Android 190 | - [FIX] Fix case when `navigator.userAgent` is `undefined` 191 | - [DOC] Add information about `strictMode` in `check` function 192 | - [DOC] Consistent use of `bowser` variable in the README 193 | 194 | ### 1.4.4 (August 10, 2016) 195 | 196 | - [FIX] Fix AMD `define` call — pass name to the function 197 | 198 | ### 1.4.3 (July 27, 2016) 199 | 200 | - [FIX] Fix error `Object doesn't support this property or method` on IE8 201 | 202 | ### 1.4.2 (July 26, 2016) 203 | 204 | - [FIX] Fix missing `isUnsupportedBrowser` in typings description 205 | - [DOC] Fix `check`'s declaration in README 206 | 207 | ### 1.4.1 (July 7, 2016) 208 | 209 | - [FIX] Fix `strictMode` logic for `isUnsupportedBrowser` 210 | 211 | ### 1.4.0 (June 28, 2016) 212 | 213 | - [FEATURE] Add `bowser.compareVersions` method 214 | - [FEATURE] Add `bowser.isUnsupportedBrowser` method 215 | - [FEATURE] Add `bowser.check` method 216 | - [DOC] Changelog started 217 | - [DOC] Add API section to README 218 | - [FIX] Fix detection of browser type (A/C/X) for Chromium 219 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, Dustin Diaz (the "Original Author") 2 | All rights reserved. 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | Distributions of all or part of the Software intended to be used 19 | by the recipients as they would use the unmodified Software, 20 | containing modifications that substantially alter, remove, or 21 | disable functionality of the Software, outside of the documented 22 | configuration mechanisms provided by the Software, shall be 23 | modified such that the Original Author's bug reporting email 24 | addresses and urls are either replaced with the contact information 25 | of the parties responsible for the changes, or removed entirely. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | 36 | 37 | Except where noted, this license applies to any and all software 38 | programs and associated documentation files created by the 39 | Original Author, when distributed with the Software. 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Bowser 2 | A small, fast and rich-API browser/platform/engine detector for both browser and node. 3 | - **Small.** Use plain ES5-version which is ~4.8kB gzipped. 4 | - **Optimized.** Use only those parsers you need — it doesn't do useless work. 5 | - **Multi-platform.** It's browser- and node-ready, so you can use it in any environment. 6 | 7 | Don't hesitate to support the project on Github or [OpenCollective](https://opencollective.com/bowser) if you like it ❤️ Also, contributors are always welcome! 8 | 9 | [![Financial Contributors on Open Collective](https://opencollective.com/bowser/all/badge.svg?label=financial+contributors)](https://opencollective.com/bowser) [![Build Status](https://travis-ci.org/lancedikson/bowser.svg?branch=master)](https://travis-ci.org/lancedikson/bowser/) [![Greenkeeper badge](https://badges.greenkeeper.io/lancedikson/bowser.svg)](https://greenkeeper.io/) [![Coverage Status](https://coveralls.io/repos/github/lancedikson/bowser/badge.svg?branch=master)](https://coveralls.io/github/lancedikson/bowser?branch=master) ![Downloads](https://img.shields.io/npm/dm/bowser) 10 | 11 | # Contents 12 | - [Overview](#overview) 13 | - [Use cases](#use-cases) 14 | 15 | # Overview 16 | 17 | The library is made to help to detect what browser your user has and gives you a convenient API to filter the users somehow depending on their browsers. Check it out on this page: https://bowser-js.github.io/bowser-online/. 18 | 19 | ### ⚠️ Version 2.0 breaking changes ⚠️ 20 | 21 | Version 2.0 has drastically changed the API. All available methods are on the [docs page](https://bowser-js.github.io/bowser/docs/). 22 | 23 | _For legacy code, check out the [1.x](https://github.com/lancedikson/bowser/tree/v1.x) branch and install it through `npm install bowser@1.9.4`._ 24 | 25 | # Use cases 26 | 27 | First of all, require the library. This is a UMD Module, so it will work for AMD, TypeScript, ES6, and CommonJS module systems. 28 | 29 | ```javascript 30 | const Bowser = require("bowser"); // CommonJS 31 | 32 | import * as Bowser from "bowser"; // TypeScript 33 | 34 | import Bowser from "bowser"; // ES6 (and TypeScript with --esModuleInterop enabled) 35 | ``` 36 | 37 | By default, the exported version is the *ES5 transpiled version*, which **do not** include any polyfills. 38 | 39 | In case you don't use your own `babel-polyfill` you may need to have pre-built bundle with all needed polyfills. 40 | So, for you it's suitable to require bowser like this: `require('bowser/bundled')`. 41 | As the result, you get a ES5 version of bowser with `babel-polyfill` bundled together. 42 | 43 | You may need to use the source files, so they will be available in the package as well. 44 | 45 | ## Browser props detection 46 | 47 | Often we need to pick users' browser properties such as the name, the version, the rendering engine and so on. Here is an example how to do it with Bowser: 48 | 49 | ```javascript 50 | const browser = Bowser.getParser(window.navigator.userAgent); 51 | 52 | console.log(`The current browser name is "${browser.getBrowserName()}"`); 53 | // The current browser name is "Internet Explorer" 54 | ``` 55 | 56 | or 57 | 58 | ```javascript 59 | const browser = Bowser.getParser(window.navigator.userAgent); 60 | console.log(browser.getBrowser()); 61 | 62 | // outputs 63 | { 64 | name: "Internet Explorer" 65 | version: "11.0" 66 | } 67 | ``` 68 | 69 | or 70 | 71 | ```javascript 72 | console.log(Bowser.parse(window.navigator.userAgent)); 73 | 74 | // outputs 75 | { 76 | browser: { 77 | name: "Internet Explorer" 78 | version: "11.0" 79 | }, 80 | os: { 81 | name: "Windows" 82 | version: "NT 6.3" 83 | versionName: "8.1" 84 | }, 85 | platform: { 86 | type: "desktop" 87 | }, 88 | engine: { 89 | name: "Trident" 90 | version: "7.0" 91 | } 92 | } 93 | ``` 94 | 95 | 96 | ## Filtering browsers 97 | 98 | You could want to filter some particular browsers to provide any special support for them or make any workarounds. 99 | It could look like this: 100 | 101 | ```javascript 102 | const browser = Bowser.getParser(window.navigator.userAgent); 103 | const isValidBrowser = browser.satisfies({ 104 | // declare browsers per OS 105 | windows: { 106 | "internet explorer": ">10", 107 | }, 108 | macos: { 109 | safari: ">10.1" 110 | }, 111 | 112 | // per platform (mobile, desktop or tablet) 113 | mobile: { 114 | safari: '>=9', 115 | 'android browser': '>3.10' 116 | }, 117 | 118 | // or in general 119 | chrome: "~20.1.1432", 120 | firefox: ">31", 121 | opera: ">=22", 122 | 123 | // also supports equality operator 124 | chrome: "=20.1.1432", // will match particular build only 125 | 126 | // and loose-equality operator 127 | chrome: "~20", // will match any 20.* sub-version 128 | chrome: "~20.1" // will match any 20.1.* sub-version (20.1.19 as well as 20.1.12.42-alpha.1) 129 | }); 130 | ``` 131 | 132 | Settings for any particular OS or platform has more priority and redefines settings of standalone browsers. 133 | Thus, you can define OS or platform specific rules and they will have more priority in the end. 134 | 135 | More of API and possibilities you will find in the `docs` folder. 136 | 137 | ### Browser names for `.satisfies()` 138 | 139 | By default you are supposed to use the full browser name for `.satisfies`. 140 | But, there's a short way to define a browser using short aliases. The full 141 | list of aliases can be found in [the file](src/constants.js). 142 | 143 | ## Similar Projects 144 | * [Kong](https://github.com/BigBadBleuCheese/Kong) - A C# port of Bowser. 145 | 146 | ## Contributors 147 | 148 | ### Code Contributors 149 | 150 | This project exists thanks to all the people who contribute. [[Contribute](.github/CONTRIBUTING.md)]. 151 | 152 | 153 | ### Financial Contributors 154 | 155 | Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/bowser/contribute)] 156 | 157 | #### Individuals 158 | 159 | 160 | 161 | #### Organizations 162 | 163 | Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/bowser/contribute)] 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | ## License 177 | Licensed as MIT. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details. 178 | -------------------------------------------------------------------------------- /docs/Bowser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bowser - Documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 32 | 33 |
34 | 35 |

Bowser

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 |
46 | 47 |

48 | Bowser 49 |

50 | 51 |

Bowser is a static object, that provides an API to the Parsers

52 | 53 | 54 |
55 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |

Methods

75 | 76 | 77 | 78 | 79 | 80 | 81 |

(static) getParser(UA, skipParsingopt) → {Parser}

82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 | 90 | 91 |
Source:
92 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
127 | 128 | 129 | 130 | 131 | 132 |
133 |

Creates a Parser instance

134 |
135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
Example
145 | 146 |
const parser = Bowser.getParser(window.navigator.userAgent);
147 | const result = parser.getResult();
148 | 149 | 150 | 151 | 152 |
Parameters:
153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 192 | 193 | 194 | 201 | 202 | 203 | 204 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 227 | 228 | 229 | 238 | 239 | 240 | 241 | 246 | 247 | 248 | 250 | 251 | 252 | 253 | 254 |
NameTypeAttributesDefaultDescription
UA 185 | 186 | 187 | String 188 | 189 | 190 | 191 | 195 | 196 | 197 | 198 | 199 | 200 | 205 | 206 |

UserAgent string

skipParsing 220 | 221 | 222 | Boolean 223 | 224 | 225 | 226 | 230 | 231 | <optional>
232 | 233 | 234 | 235 | 236 | 237 |
242 | 243 | false 244 | 245 |

Will make the Parser postpone parsing until you ask it 249 | explicitly. Same as skipParsing for Parser.

255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 |
Throws:
270 | 271 | 272 | 273 |
274 |
275 |
276 |

when UA is not a String

277 |
278 |
279 |
280 |
281 |
282 |
283 | Type 284 |
285 |
286 | 287 | Error 288 | 289 | 290 |
291 |
292 |
293 |
294 |
295 | 296 | 297 | 298 | 299 | 300 |
Returns:
301 | 302 | 303 | 304 | 305 |
306 |
307 | Type 308 |
309 |
310 | 311 | Parser 312 | 313 | 314 |
315 |
316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 |

(static) parse(UA) → {ParsedResult}

327 | 328 | 329 | 330 | 331 | 332 | 333 |
334 | 335 | 336 |
Source:
337 |
340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 |
372 | 373 | 374 | 375 | 376 | 377 |
378 |

Creates a Parser instance and runs Parser.getResult immediately

379 |
380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 |
Example
390 | 391 |
const result = Bowser.parse(window.navigator.userAgent);
392 | 393 | 394 | 395 | 396 |
Parameters:
397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 |
NameTypeDescription
UA 425 | 426 |
438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 |
Returns:
455 | 456 | 457 | 458 | 459 |
460 |
461 | Type 462 |
463 |
464 | 465 | ParsedResult 466 | 467 | 468 |
469 |
470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 |
482 | 483 |
484 | 485 | 486 | 487 | 488 | 489 | 490 |
491 | 492 |
493 | 494 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | -------------------------------------------------------------------------------- /docs/BowserUAIsNotAStringError.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BowserUAIsNotAStringError - Documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 32 | 33 |
34 | 35 |

BowserUAIsNotAStringError

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 |
46 | 47 |

48 | BowserUAIsNotAStringError 49 |

50 | 51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 | 59 | 60 | 61 | 62 |

new BowserUAIsNotAStringError()

63 | 64 | 65 | 66 | 67 | 68 | 69 |
70 | 71 | 72 |
Source:
73 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
108 | 109 | 110 | 111 |
Properties:
112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 |
NameTypeDescription
name 141 | 142 |
154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 |
189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 |
211 | 212 |
213 | 214 | 215 | 216 | 217 | 218 | 219 |
220 | 221 |
222 | 223 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /docs/bowser.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | bowser.js - Documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 32 | 33 |
34 | 35 |

bowser.js

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 |
/*!
 46 |  * Bowser - a browser detector
 47 |  * https://github.com/lancedikson/bowser
 48 |  * MIT License | (c) Dustin Diaz 2012-2015
 49 |  * MIT License | (c) Denis Demchenko 2015-2019
 50 |  */
 51 | import Parser from './parser.js';
 52 | import {
 53 |   BROWSER_MAP,
 54 |   ENGINE_MAP,
 55 |   OS_MAP,
 56 |   PLATFORMS_MAP,
 57 | } from './constants.js';
 58 | 
 59 | /**
 60 |  * Bowser class.
 61 |  * Keep it simple as much as it can be.
 62 |  * It's supposed to work with collections of {@link Parser} instances
 63 |  * rather then solve one-instance problems.
 64 |  * All the one-instance stuff is located in Parser class.
 65 |  *
 66 |  * @class
 67 |  * @classdesc Bowser is a static object, that provides an API to the Parsers
 68 |  * @hideconstructor
 69 |  */
 70 | class Bowser {
 71 |   /**
 72 |    * Creates a {@link Parser} instance
 73 |    *
 74 |    * @param {String} UA UserAgent string
 75 |    * @param {Boolean} [skipParsing=false] Will make the Parser postpone parsing until you ask it
 76 |    * explicitly. Same as `skipParsing` for {@link Parser}.
 77 |    * @returns {Parser}
 78 |    * @throws {Error} when UA is not a String
 79 |    *
 80 |    * @example
 81 |    * const parser = Bowser.getParser(window.navigator.userAgent);
 82 |    * const result = parser.getResult();
 83 |    */
 84 |   static getParser(UA, skipParsing = false) {
 85 |     if (typeof UA !== 'string') {
 86 |       throw new Error('UserAgent should be a string');
 87 |     }
 88 |     return new Parser(UA, skipParsing);
 89 |   }
 90 | 
 91 |   /**
 92 |    * Creates a {@link Parser} instance and runs {@link Parser.getResult} immediately
 93 |    *
 94 |    * @param UA
 95 |    * @return {ParsedResult}
 96 |    *
 97 |    * @example
 98 |    * const result = Bowser.parse(window.navigator.userAgent);
 99 |    */
100 |   static parse(UA) {
101 |     return (new Parser(UA)).getResult();
102 |   }
103 | 
104 |   static get BROWSER_MAP() {
105 |     return BROWSER_MAP;
106 |   }
107 | 
108 |   static get ENGINE_MAP() {
109 |     return ENGINE_MAP;
110 |   }
111 | 
112 |   static get OS_MAP() {
113 |     return OS_MAP;
114 |   }
115 | 
116 |   static get PLATFORMS_MAP() {
117 |     return PLATFORMS_MAP;
118 |   }
119 | }
120 | 
121 | export default Bowser;
122 | 
123 |
124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 |
132 | 133 |
134 | 135 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Home - Documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |

45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 |

Bowser

61 |

A small, fast and rich-API browser/platform/engine detector for both browser and node.

62 |
    63 |
  • Small. Use plain ES5-version which is ~4.8kB gzipped.
  • 64 |
  • Optimized. Use only those parsers you need — it doesn't do useless work.
  • 65 |
  • Multi-platform. It's browser- and node-ready, so you can use it in any environment.
  • 66 |
67 |

Don't hesitate to support the project on Github or OpenCollective if you like it ❤️ Also, contributors are always welcome!

68 |

Financial Contributors on Open Collective Build Status Greenkeeper badge Coverage Status Downloads

69 |

Contents

70 | 76 |

Overview

77 |

The library is made to help to detect what browser your user has and gives you a convenient API to filter the users somehow depending on their browsers. Check it out on this page: https://bowser-js.github.io/bowser-online/.

78 |

⚠️ Version 2.0 breaking changes ⚠️

79 |

Version 2.0 has drastically changed the API. All available methods are on the docs page.

80 |

For legacy code, check out the 1.x branch and install it through npm install bowser@1.9.4.

81 |

Use cases

82 |

First of all, require the library. This is a UMD Module, so it will work for AMD, TypeScript, ES6, and CommonJS module systems.

83 |
const Bowser = require("bowser"); // CommonJS
 84 | 
 85 | import * as Bowser from "bowser"; // TypeScript
 86 | 
 87 | import Bowser from "bowser"; // ES6 (and TypeScript with --esModuleInterop enabled)
 88 | 
89 |

By default, the exported version is the ES5 transpiled version, which do not include any polyfills.

90 |

In case you don't use your own babel-polyfill you may need to have pre-built bundle with all needed polyfills. 91 | So, for you it's suitable to require bowser like this: require('bowser/bundled'). 92 | As the result, you get a ES5 version of bowser with babel-polyfill bundled together.

93 |

You may need to use the source files, so they will be available in the package as well.

94 |

Browser props detection

95 |

Often we need to pick users' browser properties such as the name, the version, the rendering engine and so on. Here is an example how to do it with Bowser:

96 |
const browser = Bowser.getParser(window.navigator.userAgent);
 97 | 
 98 | console.log(`The current browser name is "${browser.getBrowserName()}"`);
 99 | // The current browser name is "Internet Explorer"
100 | 
101 |

or

102 |
const browser = Bowser.getParser(window.navigator.userAgent);
103 | console.log(browser.getBrowser());
104 | 
105 | // outputs
106 | {
107 |   name: "Internet Explorer"
108 |   version: "11.0"
109 | }
110 | 
111 |

or

112 |
console.log(Bowser.parse(window.navigator.userAgent));
113 | 
114 | // outputs
115 | {
116 |   browser: {
117 |     name: "Internet Explorer"
118 |     version: "11.0"
119 |   },
120 |   os: {
121 |     name: "Windows"
122 |     version: "NT 6.3"
123 |     versionName: "8.1"
124 |   },
125 |   platform: {
126 |     type: "desktop"
127 |   },
128 |   engine: {
129 |     name: "Trident"
130 |     version: "7.0"
131 |   }
132 | }
133 | 
134 |

Filtering browsers

135 |

You could want to filter some particular browsers to provide any special support for them or make any workarounds. 136 | It could look like this:

137 |
const browser = Bowser.getParser(window.navigator.userAgent);
138 | const isValidBrowser = browser.satisfies({
139 |   // declare browsers per OS
140 |   windows: {
141 |     "internet explorer": ">10",
142 |   },
143 |   macos: {
144 |     safari: ">10.1"
145 |   },
146 | 
147 |   // per platform (mobile, desktop or tablet)
148 |   mobile: {
149 |     safari: '>=9',
150 |     'android browser': '>3.10'
151 |   },
152 | 
153 |   // or in general
154 |   chrome: "~20.1.1432",
155 |   firefox: ">31",
156 |   opera: ">=22",
157 | 
158 |   // also supports equality operator
159 |   chrome: "=20.1.1432", // will match particular build only
160 | 
161 |   // and loose-equality operator
162 |   chrome: "~20",        // will match any 20.* sub-version
163 |   chrome: "~20.1"       // will match any 20.1.* sub-version (20.1.19 as well as 20.1.12.42-alpha.1)
164 | });
165 | 
166 |

Settings for any particular OS or platform has more priority and redefines settings of standalone browsers. 167 | Thus, you can define OS or platform specific rules and they will have more priority in the end.

168 |

More of API and possibilities you will find in the docs folder.

169 |

Browser names for .satisfies()

170 |

By default you are supposed to use the full browser name for .satisfies. 171 | But, there's a short way to define a browser using short aliases. The full 172 | list of aliases can be found in the file.

173 |

Similar Projects

174 |
    175 |
  • Kong - A C# port of Bowser.
  • 176 |
177 |

Contributors

178 |

Code Contributors

179 |

This project exists thanks to all the people who contribute. [Contribute]. 180 |

181 |

Financial Contributors

182 |

Become a financial contributor and help us sustain our community. [Contribute]

183 |

Individuals

184 |

185 |

Organizations

186 |

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

187 |

188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |

197 |

License

198 |

Licensed as MIT. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details.

199 |
200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 |
209 | 210 |
211 | 212 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /docs/parser.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | parser.js - Documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 32 | 33 |
34 | 35 |

parser.js

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 |
import browserParsersList from './parser-browsers.js';
 46 | import osParsersList from './parser-os.js';
 47 | import platformParsersList from './parser-platforms.js';
 48 | import enginesParsersList from './parser-engines.js';
 49 | import Utils from './utils.js';
 50 | 
 51 | /**
 52 |  * The main class that arranges the whole parsing process.
 53 |  */
 54 | class Parser {
 55 |   /**
 56 |    * Create instance of Parser
 57 |    *
 58 |    * @param {String} UA User-Agent string
 59 |    * @param {Boolean} [skipParsing=false] parser can skip parsing in purpose of performance
 60 |    * improvements if you need to make a more particular parsing
 61 |    * like {@link Parser#parseBrowser} or {@link Parser#parsePlatform}
 62 |    *
 63 |    * @throw {Error} in case of empty UA String
 64 |    *
 65 |    * @constructor
 66 |    */
 67 |   constructor(UA, skipParsing = false) {
 68 |     if (UA === void (0) || UA === null || UA === '') {
 69 |       throw new Error("UserAgent parameter can't be empty");
 70 |     }
 71 | 
 72 |     this._ua = UA;
 73 | 
 74 |     /**
 75 |      * @typedef ParsedResult
 76 |      * @property {Object} browser
 77 |      * @property {String|undefined} [browser.name]
 78 |      * Browser name, like `"Chrome"` or `"Internet Explorer"`
 79 |      * @property {String|undefined} [browser.version] Browser version as a String `"12.01.45334.10"`
 80 |      * @property {Object} os
 81 |      * @property {String|undefined} [os.name] OS name, like `"Windows"` or `"macOS"`
 82 |      * @property {String|undefined} [os.version] OS version, like `"NT 5.1"` or `"10.11.1"`
 83 |      * @property {String|undefined} [os.versionName] OS name, like `"XP"` or `"High Sierra"`
 84 |      * @property {Object} platform
 85 |      * @property {String|undefined} [platform.type]
 86 |      * platform type, can be either `"desktop"`, `"tablet"` or `"mobile"`
 87 |      * @property {String|undefined} [platform.vendor] Vendor of the device,
 88 |      * like `"Apple"` or `"Samsung"`
 89 |      * @property {String|undefined} [platform.model] Device model,
 90 |      * like `"iPhone"` or `"Kindle Fire HD 7"`
 91 |      * @property {Object} engine
 92 |      * @property {String|undefined} [engine.name]
 93 |      * Can be any of this: `WebKit`, `Blink`, `Gecko`, `Trident`, `Presto`, `EdgeHTML`
 94 |      * @property {String|undefined} [engine.version] String version of the engine
 95 |      */
 96 |     this.parsedResult = {};
 97 | 
 98 |     if (skipParsing !== true) {
 99 |       this.parse();
100 |     }
101 |   }
102 | 
103 |   /**
104 |    * Get UserAgent string of current Parser instance
105 |    * @return {String} User-Agent String of the current <Parser> object
106 |    *
107 |    * @public
108 |    */
109 |   getUA() {
110 |     return this._ua;
111 |   }
112 | 
113 |   /**
114 |    * Test a UA string for a regexp
115 |    * @param {RegExp} regex
116 |    * @return {Boolean}
117 |    */
118 |   test(regex) {
119 |     return regex.test(this._ua);
120 |   }
121 | 
122 |   /**
123 |    * Get parsed browser object
124 |    * @return {Object}
125 |    */
126 |   parseBrowser() {
127 |     this.parsedResult.browser = {};
128 | 
129 |     const browserDescriptor = Utils.find(browserParsersList, (_browser) => {
130 |       if (typeof _browser.test === 'function') {
131 |         return _browser.test(this);
132 |       }
133 | 
134 |       if (_browser.test instanceof Array) {
135 |         return _browser.test.some(condition => this.test(condition));
136 |       }
137 | 
138 |       throw new Error("Browser's test function is not valid");
139 |     });
140 | 
141 |     if (browserDescriptor) {
142 |       this.parsedResult.browser = browserDescriptor.describe(this.getUA());
143 |     }
144 | 
145 |     return this.parsedResult.browser;
146 |   }
147 | 
148 |   /**
149 |    * Get parsed browser object
150 |    * @return {Object}
151 |    *
152 |    * @public
153 |    */
154 |   getBrowser() {
155 |     if (this.parsedResult.browser) {
156 |       return this.parsedResult.browser;
157 |     }
158 | 
159 |     return this.parseBrowser();
160 |   }
161 | 
162 |   /**
163 |    * Get browser's name
164 |    * @return {String} Browser's name or an empty string
165 |    *
166 |    * @public
167 |    */
168 |   getBrowserName(toLowerCase) {
169 |     if (toLowerCase) {
170 |       return String(this.getBrowser().name).toLowerCase() || '';
171 |     }
172 |     return this.getBrowser().name || '';
173 |   }
174 | 
175 | 
176 |   /**
177 |    * Get browser's version
178 |    * @return {String} version of browser
179 |    *
180 |    * @public
181 |    */
182 |   getBrowserVersion() {
183 |     return this.getBrowser().version;
184 |   }
185 | 
186 |   /**
187 |    * Get OS
188 |    * @return {Object}
189 |    *
190 |    * @example
191 |    * this.getOS();
192 |    * {
193 |    *   name: 'macOS',
194 |    *   version: '10.11.12'
195 |    * }
196 |    */
197 |   getOS() {
198 |     if (this.parsedResult.os) {
199 |       return this.parsedResult.os;
200 |     }
201 | 
202 |     return this.parseOS();
203 |   }
204 | 
205 |   /**
206 |    * Parse OS and save it to this.parsedResult.os
207 |    * @return {*|{}}
208 |    */
209 |   parseOS() {
210 |     this.parsedResult.os = {};
211 | 
212 |     const os = Utils.find(osParsersList, (_os) => {
213 |       if (typeof _os.test === 'function') {
214 |         return _os.test(this);
215 |       }
216 | 
217 |       if (_os.test instanceof Array) {
218 |         return _os.test.some(condition => this.test(condition));
219 |       }
220 | 
221 |       throw new Error("Browser's test function is not valid");
222 |     });
223 | 
224 |     if (os) {
225 |       this.parsedResult.os = os.describe(this.getUA());
226 |     }
227 | 
228 |     return this.parsedResult.os;
229 |   }
230 | 
231 |   /**
232 |    * Get OS name
233 |    * @param {Boolean} [toLowerCase] return lower-cased value
234 |    * @return {String} name of the OS — macOS, Windows, Linux, etc.
235 |    */
236 |   getOSName(toLowerCase) {
237 |     const { name } = this.getOS();
238 | 
239 |     if (toLowerCase) {
240 |       return String(name).toLowerCase() || '';
241 |     }
242 | 
243 |     return name || '';
244 |   }
245 | 
246 |   /**
247 |    * Get OS version
248 |    * @return {String} full version with dots ('10.11.12', '5.6', etc)
249 |    */
250 |   getOSVersion() {
251 |     return this.getOS().version;
252 |   }
253 | 
254 |   /**
255 |    * Get parsed platform
256 |    * @return {{}}
257 |    */
258 |   getPlatform() {
259 |     if (this.parsedResult.platform) {
260 |       return this.parsedResult.platform;
261 |     }
262 | 
263 |     return this.parsePlatform();
264 |   }
265 | 
266 |   /**
267 |    * Get platform name
268 |    * @param {Boolean} [toLowerCase=false]
269 |    * @return {*}
270 |    */
271 |   getPlatformType(toLowerCase = false) {
272 |     const { type } = this.getPlatform();
273 | 
274 |     if (toLowerCase) {
275 |       return String(type).toLowerCase() || '';
276 |     }
277 | 
278 |     return type || '';
279 |   }
280 | 
281 |   /**
282 |    * Get parsed platform
283 |    * @return {{}}
284 |    */
285 |   parsePlatform() {
286 |     this.parsedResult.platform = {};
287 | 
288 |     const platform = Utils.find(platformParsersList, (_platform) => {
289 |       if (typeof _platform.test === 'function') {
290 |         return _platform.test(this);
291 |       }
292 | 
293 |       if (_platform.test instanceof Array) {
294 |         return _platform.test.some(condition => this.test(condition));
295 |       }
296 | 
297 |       throw new Error("Browser's test function is not valid");
298 |     });
299 | 
300 |     if (platform) {
301 |       this.parsedResult.platform = platform.describe(this.getUA());
302 |     }
303 | 
304 |     return this.parsedResult.platform;
305 |   }
306 | 
307 |   /**
308 |    * Get parsed engine
309 |    * @return {{}}
310 |    */
311 |   getEngine() {
312 |     if (this.parsedResult.engine) {
313 |       return this.parsedResult.engine;
314 |     }
315 | 
316 |     return this.parseEngine();
317 |   }
318 | 
319 |   /**
320 |    * Get engines's name
321 |    * @return {String} Engines's name or an empty string
322 |    *
323 |    * @public
324 |    */
325 |   getEngineName(toLowerCase) {
326 |     if (toLowerCase) {
327 |       return String(this.getEngine().name).toLowerCase() || '';
328 |     }
329 |     return this.getEngine().name || '';
330 |   }
331 | 
332 |   /**
333 |    * Get parsed platform
334 |    * @return {{}}
335 |    */
336 |   parseEngine() {
337 |     this.parsedResult.engine = {};
338 | 
339 |     const engine = Utils.find(enginesParsersList, (_engine) => {
340 |       if (typeof _engine.test === 'function') {
341 |         return _engine.test(this);
342 |       }
343 | 
344 |       if (_engine.test instanceof Array) {
345 |         return _engine.test.some(condition => this.test(condition));
346 |       }
347 | 
348 |       throw new Error("Browser's test function is not valid");
349 |     });
350 | 
351 |     if (engine) {
352 |       this.parsedResult.engine = engine.describe(this.getUA());
353 |     }
354 | 
355 |     return this.parsedResult.engine;
356 |   }
357 | 
358 |   /**
359 |    * Parse full information about the browser
360 |    * @returns {Parser}
361 |    */
362 |   parse() {
363 |     this.parseBrowser();
364 |     this.parseOS();
365 |     this.parsePlatform();
366 |     this.parseEngine();
367 | 
368 |     return this;
369 |   }
370 | 
371 |   /**
372 |    * Get parsed result
373 |    * @return {ParsedResult}
374 |    */
375 |   getResult() {
376 |     return Utils.assign({}, this.parsedResult);
377 |   }
378 | 
379 |   /**
380 |    * Check if parsed browser matches certain conditions
381 |    *
382 |    * @param {Object} checkTree It's one or two layered object,
383 |    * which can include a platform or an OS on the first layer
384 |    * and should have browsers specs on the bottom-laying layer
385 |    *
386 |    * @returns {Boolean|undefined} Whether the browser satisfies the set conditions or not.
387 |    * Returns `undefined` when the browser is no described in the checkTree object.
388 |    *
389 |    * @example
390 |    * const browser = Bowser.getParser(window.navigator.userAgent);
391 |    * if (browser.satisfies({chrome: '>118.01.1322' }))
392 |    * // or with os
393 |    * if (browser.satisfies({windows: { chrome: '>118.01.1322' } }))
394 |    * // or with platforms
395 |    * if (browser.satisfies({desktop: { chrome: '>118.01.1322' } }))
396 |    */
397 |   satisfies(checkTree) {
398 |     const platformsAndOSes = {};
399 |     let platformsAndOSCounter = 0;
400 |     const browsers = {};
401 |     let browsersCounter = 0;
402 | 
403 |     const allDefinitions = Object.keys(checkTree);
404 | 
405 |     allDefinitions.forEach((key) => {
406 |       const currentDefinition = checkTree[key];
407 |       if (typeof currentDefinition === 'string') {
408 |         browsers[key] = currentDefinition;
409 |         browsersCounter += 1;
410 |       } else if (typeof currentDefinition === 'object') {
411 |         platformsAndOSes[key] = currentDefinition;
412 |         platformsAndOSCounter += 1;
413 |       }
414 |     });
415 | 
416 |     if (platformsAndOSCounter > 0) {
417 |       const platformsAndOSNames = Object.keys(platformsAndOSes);
418 |       const OSMatchingDefinition = Utils.find(platformsAndOSNames, name => (this.isOS(name)));
419 | 
420 |       if (OSMatchingDefinition) {
421 |         const osResult = this.satisfies(platformsAndOSes[OSMatchingDefinition]);
422 | 
423 |         if (osResult !== void 0) {
424 |           return osResult;
425 |         }
426 |       }
427 | 
428 |       const platformMatchingDefinition = Utils.find(
429 |         platformsAndOSNames,
430 |         name => (this.isPlatform(name)),
431 |       );
432 |       if (platformMatchingDefinition) {
433 |         const platformResult = this.satisfies(platformsAndOSes[platformMatchingDefinition]);
434 | 
435 |         if (platformResult !== void 0) {
436 |           return platformResult;
437 |         }
438 |       }
439 |     }
440 | 
441 |     if (browsersCounter > 0) {
442 |       const browserNames = Object.keys(browsers);
443 |       const matchingDefinition = Utils.find(browserNames, name => (this.isBrowser(name, true)));
444 | 
445 |       if (matchingDefinition !== void 0) {
446 |         return this.compareVersion(browsers[matchingDefinition]);
447 |       }
448 |     }
449 | 
450 |     return undefined;
451 |   }
452 | 
453 |   /**
454 |    * Check if the browser name equals the passed string
455 |    * @param browserName The string to compare with the browser name
456 |    * @param [includingAlias=false] The flag showing whether alias will be included into comparison
457 |    * @returns {boolean}
458 |    */
459 |   isBrowser(browserName, includingAlias = false) {
460 |     const defaultBrowserName = this.getBrowserName().toLowerCase();
461 |     let browserNameLower = browserName.toLowerCase();
462 |     const alias = Utils.getBrowserTypeByAlias(browserNameLower);
463 | 
464 |     if (includingAlias && alias) {
465 |       browserNameLower = alias.toLowerCase();
466 |     }
467 |     return browserNameLower === defaultBrowserName;
468 |   }
469 | 
470 |   compareVersion(version) {
471 |     let expectedResults = [0];
472 |     let comparableVersion = version;
473 |     let isLoose = false;
474 | 
475 |     const currentBrowserVersion = this.getBrowserVersion();
476 | 
477 |     if (typeof currentBrowserVersion !== 'string') {
478 |       return void 0;
479 |     }
480 | 
481 |     if (version[0] === '>' || version[0] === '<') {
482 |       comparableVersion = version.substr(1);
483 |       if (version[1] === '=') {
484 |         isLoose = true;
485 |         comparableVersion = version.substr(2);
486 |       } else {
487 |         expectedResults = [];
488 |       }
489 |       if (version[0] === '>') {
490 |         expectedResults.push(1);
491 |       } else {
492 |         expectedResults.push(-1);
493 |       }
494 |     } else if (version[0] === '=') {
495 |       comparableVersion = version.substr(1);
496 |     } else if (version[0] === '~') {
497 |       isLoose = true;
498 |       comparableVersion = version.substr(1);
499 |     }
500 | 
501 |     return expectedResults.indexOf(
502 |       Utils.compareVersions(currentBrowserVersion, comparableVersion, isLoose),
503 |     ) > -1;
504 |   }
505 | 
506 |   isOS(osName) {
507 |     return this.getOSName(true) === String(osName).toLowerCase();
508 |   }
509 | 
510 |   isPlatform(platformType) {
511 |     return this.getPlatformType(true) === String(platformType).toLowerCase();
512 |   }
513 | 
514 |   isEngine(engineName) {
515 |     return this.getEngineName(true) === String(engineName).toLowerCase();
516 |   }
517 | 
518 |   /**
519 |    * Is anything? Check if the browser is called "anything",
520 |    * the OS called "anything" or the platform called "anything"
521 |    * @param {String} anything
522 |    * @param [includingAlias=false] The flag showing whether alias will be included into comparison
523 |    * @returns {Boolean}
524 |    */
525 |   is(anything, includingAlias = false) {
526 |     return this.isBrowser(anything, includingAlias) || this.isOS(anything)
527 |       || this.isPlatform(anything);
528 |   }
529 | 
530 |   /**
531 |    * Check if any of the given values satisfies this.is(anything)
532 |    * @param {String[]} anythings
533 |    * @returns {Boolean}
534 |    */
535 |   some(anythings = []) {
536 |     return anythings.some(anything => this.is(anything));
537 |   }
538 | }
539 | 
540 | export default Parser;
541 | 
542 |
543 |
544 | 545 | 546 | 547 | 548 | 549 | 550 |
551 | 552 |
553 | 554 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | -------------------------------------------------------------------------------- /docs/scripts/collapse.js: -------------------------------------------------------------------------------- 1 | function hideAllButCurrent(){ 2 | //by default all submenut items are hidden 3 | //but we need to rehide them for search 4 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(parent) { 5 | parent.style.display = "none"; 6 | }); 7 | 8 | //only current page (if it exists) should be opened 9 | var file = window.location.pathname.split("/").pop().replace(/\.html/, ''); 10 | document.querySelectorAll("nav > ul > li > a").forEach(function(parent) { 11 | var href = parent.attributes.href.value.replace(/\.html/, ''); 12 | if (file === href) { 13 | parent.parentNode.querySelectorAll("ul li").forEach(function(elem) { 14 | elem.style.display = "block"; 15 | }); 16 | } 17 | }); 18 | } 19 | 20 | hideAllButCurrent(); -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/scripts/nav.js: -------------------------------------------------------------------------------- 1 | function scrollToNavItem() { 2 | var path = window.location.href.split('/').pop().replace(/\.html/, ''); 3 | document.querySelectorAll('nav a').forEach(function(link) { 4 | var href = link.attributes.href.value.replace(/\.html/, ''); 5 | if (path === href) { 6 | link.scrollIntoView({block: 'center'}); 7 | return; 8 | } 9 | }) 10 | } 11 | 12 | scrollToNavItem(); 13 | -------------------------------------------------------------------------------- /docs/scripts/polyfill.js: -------------------------------------------------------------------------------- 1 | //IE Fix, src: https://www.reddit.com/r/programminghorror/comments/6abmcr/nodelist_lacks_foreach_in_internet_explorer/ 2 | if (typeof(NodeList.prototype.forEach)!==typeof(alert)){ 3 | NodeList.prototype.forEach=Array.prototype.forEach; 4 | } -------------------------------------------------------------------------------- /docs/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p ul > li:not(.level-hide)").forEach(function(elem) { 16 | elem.style.display = "block"; 17 | }); 18 | 19 | if (typeof hideAllButCurrent === "function"){ 20 | //let's do what ever collapse wants to do 21 | hideAllButCurrent(); 22 | } else { 23 | //menu by default should be opened 24 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) { 25 | elem.style.display = "block"; 26 | }); 27 | } 28 | } else { 29 | //we are searching 30 | document.documentElement.setAttribute(searchAttr, ''); 31 | 32 | //show all parents 33 | document.querySelectorAll("nav > ul > li").forEach(function(elem) { 34 | elem.style.display = "block"; 35 | }); 36 | //hide all results 37 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) { 38 | elem.style.display = "none"; 39 | }); 40 | //show results matching filter 41 | document.querySelectorAll("nav > ul > li > ul a").forEach(function(elem) { 42 | if (!contains(elem.parentNode, search)) { 43 | return; 44 | } 45 | elem.parentNode.style.display = "block"; 46 | }); 47 | //hide parents without children 48 | document.querySelectorAll("nav > ul > li").forEach(function(parent) { 49 | var countSearchA = 0; 50 | parent.querySelectorAll("a").forEach(function(elem) { 51 | if (contains(elem, search)) { 52 | countSearchA++; 53 | } 54 | }); 55 | 56 | var countUl = 0; 57 | var countUlVisible = 0; 58 | parent.querySelectorAll("ul").forEach(function(ulP) { 59 | // count all elements that match the search 60 | if (contains(ulP, search)) { 61 | countUl++; 62 | } 63 | 64 | // count all visible elements 65 | var children = ulP.children 66 | for (i=0; i ul { 227 | padding: 0 10px; 228 | } 229 | 230 | nav > ul > li > a { 231 | color: #606; 232 | margin-top: 10px; 233 | } 234 | 235 | nav ul ul a { 236 | color: hsl(207, 1%, 60%); 237 | border-left: 1px solid hsl(207, 10%, 86%); 238 | } 239 | 240 | nav ul ul a, 241 | nav ul ul a:active { 242 | padding-left: 20px 243 | } 244 | 245 | nav h2 { 246 | font-size: 13px; 247 | margin: 10px 0 0 0; 248 | padding: 0; 249 | } 250 | 251 | nav > h2 > a { 252 | margin: 10px 0 -10px; 253 | color: #606 !important; 254 | } 255 | 256 | footer { 257 | color: hsl(0, 0%, 28%); 258 | margin-left: 250px; 259 | display: block; 260 | padding: 15px; 261 | font-style: italic; 262 | font-size: 90%; 263 | } 264 | 265 | .ancestors { 266 | color: #999 267 | } 268 | 269 | .ancestors a { 270 | color: #999 !important; 271 | } 272 | 273 | .clear { 274 | clear: both 275 | } 276 | 277 | .important { 278 | font-weight: bold; 279 | color: #950B02; 280 | } 281 | 282 | .yes-def { 283 | text-indent: -1000px 284 | } 285 | 286 | .type-signature { 287 | color: #CA79CA 288 | } 289 | 290 | .type-signature:last-child { 291 | color: #eee; 292 | } 293 | 294 | .name, .signature { 295 | font-family: Consolas, Monaco, 'Andale Mono', monospace 296 | } 297 | 298 | .signature { 299 | color: #fc83ff; 300 | } 301 | 302 | .details { 303 | margin-top: 6px; 304 | border-left: 2px solid #DDD; 305 | line-height: 20px; 306 | font-size: 14px; 307 | } 308 | 309 | .details dt { 310 | width: auto; 311 | float: left; 312 | padding-left: 10px; 313 | } 314 | 315 | .details dd { 316 | margin-left: 70px; 317 | margin-top: 6px; 318 | margin-bottom: 6px; 319 | } 320 | 321 | .details ul { 322 | margin: 0 323 | } 324 | 325 | .details ul { 326 | list-style-type: none 327 | } 328 | 329 | .details pre.prettyprint { 330 | margin: 0 331 | } 332 | 333 | .details .object-value { 334 | padding-top: 0 335 | } 336 | 337 | .description { 338 | margin-bottom: 1em; 339 | margin-top: 1em; 340 | } 341 | 342 | .code-caption { 343 | font-style: italic; 344 | font-size: 107%; 345 | margin: 0; 346 | } 347 | 348 | .prettyprint { 349 | font-size: 14px; 350 | overflow: auto; 351 | } 352 | 353 | .prettyprint.source { 354 | width: inherit; 355 | line-height: 18px; 356 | display: block; 357 | background-color: #0d152a; 358 | color: #aeaeae; 359 | } 360 | 361 | .prettyprint code { 362 | line-height: 18px; 363 | display: block; 364 | background-color: #0d152a; 365 | color: #4D4E53; 366 | } 367 | 368 | .prettyprint > code { 369 | padding: 15px; 370 | } 371 | 372 | .prettyprint .linenums code { 373 | padding: 0 15px 374 | } 375 | 376 | .prettyprint .linenums li:first-of-type code { 377 | padding-top: 15px 378 | } 379 | 380 | .prettyprint code span.line { 381 | display: inline-block 382 | } 383 | 384 | .prettyprint.linenums { 385 | padding-left: 70px; 386 | -webkit-user-select: none; 387 | -moz-user-select: none; 388 | -ms-user-select: none; 389 | user-select: none; 390 | } 391 | 392 | .prettyprint.linenums ol { 393 | padding-left: 0 394 | } 395 | 396 | .prettyprint.linenums li { 397 | border-left: 3px #34446B solid; 398 | } 399 | 400 | .prettyprint.linenums li.selected, .prettyprint.linenums li.selected * { 401 | background-color: #34446B; 402 | } 403 | 404 | .prettyprint.linenums li * { 405 | -webkit-user-select: text; 406 | -moz-user-select: text; 407 | -ms-user-select: text; 408 | user-select: text; 409 | } 410 | 411 | table { 412 | border-spacing: 0; 413 | border: 1px solid #ddd; 414 | border-collapse: collapse; 415 | border-radius: 3px; 416 | box-shadow: 0 1px 3px rgba(0,0,0,0.1); 417 | width: 100%; 418 | font-size: 14px; 419 | margin: 1em 0; 420 | } 421 | 422 | td, th { 423 | margin: 0px; 424 | text-align: left; 425 | vertical-align: top; 426 | padding: 10px; 427 | display: table-cell; 428 | } 429 | 430 | thead tr, thead tr { 431 | background-color: #fff; 432 | font-weight: bold; 433 | border-bottom: 1px solid #ddd; 434 | } 435 | 436 | .params .type { 437 | white-space: nowrap; 438 | } 439 | 440 | .params code { 441 | white-space: pre; 442 | } 443 | 444 | .params td, .params .name, .props .name, .name code { 445 | color: #4D4E53; 446 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 447 | font-size: 100%; 448 | } 449 | 450 | .params td { 451 | border-top: 1px solid #eee 452 | } 453 | 454 | .params td.description > p:first-child, .props td.description > p:first-child { 455 | margin-top: 0; 456 | padding-top: 0; 457 | } 458 | 459 | .params td.description > p:last-child, .props td.description > p:last-child { 460 | margin-bottom: 0; 461 | padding-bottom: 0; 462 | } 463 | 464 | span.param-type, .params td .param-type, .param-type dd { 465 | color: #606; 466 | font-family: Consolas, Monaco, 'Andale Mono', monospace 467 | } 468 | 469 | .param-type dt, .param-type dd { 470 | display: inline-block 471 | } 472 | 473 | .param-type { 474 | margin: 14px 0; 475 | } 476 | 477 | .disabled { 478 | color: #454545 479 | } 480 | 481 | /* navicon button */ 482 | .navicon-button { 483 | display: none; 484 | position: relative; 485 | padding: 2.0625rem 1.5rem; 486 | transition: 0.25s; 487 | cursor: pointer; 488 | -webkit-user-select: none; 489 | -moz-user-select: none; 490 | -ms-user-select: none; 491 | user-select: none; 492 | opacity: .8; 493 | } 494 | .navicon-button .navicon:before, .navicon-button .navicon:after { 495 | transition: 0.25s; 496 | } 497 | .navicon-button:hover { 498 | transition: 0.5s; 499 | opacity: 1; 500 | } 501 | .navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after { 502 | transition: 0.25s; 503 | } 504 | .navicon-button:hover .navicon:before { 505 | top: .825rem; 506 | } 507 | .navicon-button:hover .navicon:after { 508 | top: -.825rem; 509 | } 510 | 511 | /* navicon */ 512 | .navicon { 513 | position: relative; 514 | width: 2.5em; 515 | height: .3125rem; 516 | background: #000; 517 | transition: 0.3s; 518 | border-radius: 2.5rem; 519 | } 520 | .navicon:before, .navicon:after { 521 | display: block; 522 | content: ""; 523 | height: .3125rem; 524 | width: 2.5rem; 525 | background: #000; 526 | position: absolute; 527 | z-index: -1; 528 | transition: 0.3s 0.25s; 529 | border-radius: 1rem; 530 | } 531 | .navicon:before { 532 | top: .625rem; 533 | } 534 | .navicon:after { 535 | top: -.625rem; 536 | } 537 | 538 | /* open */ 539 | .nav-trigger:checked + label:not(.steps) .navicon:before, 540 | .nav-trigger:checked + label:not(.steps) .navicon:after { 541 | top: 0 !important; 542 | } 543 | 544 | .nav-trigger:checked + label .navicon:before, 545 | .nav-trigger:checked + label .navicon:after { 546 | transition: 0.5s; 547 | } 548 | 549 | /* Minus */ 550 | .nav-trigger:checked + label { 551 | -webkit-transform: scale(0.75); 552 | transform: scale(0.75); 553 | } 554 | 555 | /* × and + */ 556 | .nav-trigger:checked + label.plus .navicon, 557 | .nav-trigger:checked + label.x .navicon { 558 | background: transparent; 559 | } 560 | 561 | .nav-trigger:checked + label.plus .navicon:before, 562 | .nav-trigger:checked + label.x .navicon:before { 563 | -webkit-transform: rotate(-45deg); 564 | transform: rotate(-45deg); 565 | background: #FFF; 566 | } 567 | 568 | .nav-trigger:checked + label.plus .navicon:after, 569 | .nav-trigger:checked + label.x .navicon:after { 570 | -webkit-transform: rotate(45deg); 571 | transform: rotate(45deg); 572 | background: #FFF; 573 | } 574 | 575 | .nav-trigger:checked + label.plus { 576 | -webkit-transform: scale(0.75) rotate(45deg); 577 | transform: scale(0.75) rotate(45deg); 578 | } 579 | 580 | .nav-trigger:checked ~ nav { 581 | left: 0 !important; 582 | } 583 | 584 | .nav-trigger:checked ~ .overlay { 585 | display: block; 586 | } 587 | 588 | .nav-trigger { 589 | position: fixed; 590 | top: 0; 591 | clip: rect(0, 0, 0, 0); 592 | } 593 | 594 | .overlay { 595 | display: none; 596 | position: fixed; 597 | top: 0; 598 | bottom: 0; 599 | left: 0; 600 | right: 0; 601 | width: 100%; 602 | height: 100%; 603 | background: hsla(0, 0%, 0%, 0.5); 604 | z-index: 1; 605 | } 606 | 607 | /* nav level */ 608 | .level-hide { 609 | display: none; 610 | } 611 | html[data-search-mode] .level-hide { 612 | display: block; 613 | } 614 | 615 | 616 | @media only screen and (min-width: 320px) and (max-width: 680px) { 617 | body { 618 | overflow-x: hidden; 619 | } 620 | 621 | nav { 622 | background: #FFF; 623 | width: 250px; 624 | height: 100%; 625 | position: fixed; 626 | top: 0; 627 | right: 0; 628 | bottom: 0; 629 | left: -250px; 630 | z-index: 3; 631 | padding: 0 10px; 632 | transition: left 0.2s; 633 | } 634 | 635 | .navicon-button { 636 | display: inline-block; 637 | position: fixed; 638 | top: 1.5em; 639 | right: 0; 640 | z-index: 2; 641 | } 642 | 643 | #main { 644 | width: 100%; 645 | min-width: 360px; 646 | } 647 | 648 | #main h1.page-title { 649 | margin: 1em 0; 650 | } 651 | 652 | #main section { 653 | padding: 0; 654 | } 655 | 656 | footer { 657 | margin-left: 0; 658 | } 659 | } 660 | 661 | /** Add a '#' to static members */ 662 | [data-type="member"] a::before { 663 | content: '#'; 664 | display: inline-block; 665 | margin-left: -14px; 666 | margin-right: 5px; 667 | } 668 | 669 | #disqus_thread{ 670 | margin-left: 30px; 671 | } 672 | -------------------------------------------------------------------------------- /docs/styles/prettify.css: -------------------------------------------------------------------------------- 1 | .pln { 2 | color: #ddd; 3 | } 4 | 5 | /* string content */ 6 | .str { 7 | color: #61ce3c; 8 | } 9 | 10 | /* a keyword */ 11 | .kwd { 12 | color: #fbde2d; 13 | } 14 | 15 | /* a comment */ 16 | .com { 17 | color: #aeaeae; 18 | } 19 | 20 | /* a type name */ 21 | .typ { 22 | color: #8da6ce; 23 | } 24 | 25 | /* a literal value */ 26 | .lit { 27 | color: #fbde2d; 28 | } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #ddd; 33 | } 34 | 35 | /* lisp open bracket */ 36 | .opn { 37 | color: #000000; 38 | } 39 | 40 | /* lisp close bracket */ 41 | .clo { 42 | color: #000000; 43 | } 44 | 45 | /* a markup tag name */ 46 | .tag { 47 | color: #8da6ce; 48 | } 49 | 50 | /* a markup attribute name */ 51 | .atn { 52 | color: #fbde2d; 53 | } 54 | 55 | /* a markup attribute value */ 56 | .atv { 57 | color: #ddd; 58 | } 59 | 60 | /* a declaration */ 61 | .dec { 62 | color: #EF5050; 63 | } 64 | 65 | /* a variable name */ 66 | .var { 67 | color: #c82829; 68 | } 69 | 70 | /* a function name */ 71 | .fun { 72 | color: #4271ae; 73 | } 74 | 75 | /* Specify class=linenums on a pre to get line numbering */ 76 | ol.linenums { 77 | margin-top: 0; 78 | margin-bottom: 0; 79 | } 80 | -------------------------------------------------------------------------------- /docs/utils.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | utils.js - Documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 32 | 33 |
34 | 35 |

utils.js

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 |
import { BROWSER_MAP, BROWSER_ALIASES_MAP } from './constants.js';
 46 | 
 47 | export default class Utils {
 48 |   /**
 49 |    * Get first matched item for a string
 50 |    * @param {RegExp} regexp
 51 |    * @param {String} ua
 52 |    * @return {Array|{index: number, input: string}|*|boolean|string}
 53 |    */
 54 |   static getFirstMatch(regexp, ua) {
 55 |     const match = ua.match(regexp);
 56 |     return (match && match.length > 0 && match[1]) || '';
 57 |   }
 58 | 
 59 |   /**
 60 |    * Get second matched item for a string
 61 |    * @param regexp
 62 |    * @param {String} ua
 63 |    * @return {Array|{index: number, input: string}|*|boolean|string}
 64 |    */
 65 |   static getSecondMatch(regexp, ua) {
 66 |     const match = ua.match(regexp);
 67 |     return (match && match.length > 1 && match[2]) || '';
 68 |   }
 69 | 
 70 |   /**
 71 |    * Match a regexp and return a constant or undefined
 72 |    * @param {RegExp} regexp
 73 |    * @param {String} ua
 74 |    * @param {*} _const Any const that will be returned if regexp matches the string
 75 |    * @return {*}
 76 |    */
 77 |   static matchAndReturnConst(regexp, ua, _const) {
 78 |     if (regexp.test(ua)) {
 79 |       return _const;
 80 |     }
 81 |     return void (0);
 82 |   }
 83 | 
 84 |   static getWindowsVersionName(version) {
 85 |     switch (version) {
 86 |       case 'NT': return 'NT';
 87 |       case 'XP': return 'XP';
 88 |       case 'NT 5.0': return '2000';
 89 |       case 'NT 5.1': return 'XP';
 90 |       case 'NT 5.2': return '2003';
 91 |       case 'NT 6.0': return 'Vista';
 92 |       case 'NT 6.1': return '7';
 93 |       case 'NT 6.2': return '8';
 94 |       case 'NT 6.3': return '8.1';
 95 |       case 'NT 10.0': return '10';
 96 |       default: return undefined;
 97 |     }
 98 |   }
 99 | 
100 |   /**
101 |    * Get macOS version name
102 |    *    10.5 - Leopard
103 |    *    10.6 - Snow Leopard
104 |    *    10.7 - Lion
105 |    *    10.8 - Mountain Lion
106 |    *    10.9 - Mavericks
107 |    *    10.10 - Yosemite
108 |    *    10.11 - El Capitan
109 |    *    10.12 - Sierra
110 |    *    10.13 - High Sierra
111 |    *    10.14 - Mojave
112 |    *    10.15 - Catalina
113 |    *
114 |    * @example
115 |    *   getMacOSVersionName("10.14") // 'Mojave'
116 |    *
117 |    * @param  {string} version
118 |    * @return {string} versionName
119 |    */
120 |   static getMacOSVersionName(version) {
121 |     const v = version.split('.').splice(0, 2).map(s => parseInt(s, 10) || 0);
122 |     v.push(0);
123 |     if (v[0] !== 10) return undefined;
124 |     switch (v[1]) {
125 |       case 5: return 'Leopard';
126 |       case 6: return 'Snow Leopard';
127 |       case 7: return 'Lion';
128 |       case 8: return 'Mountain Lion';
129 |       case 9: return 'Mavericks';
130 |       case 10: return 'Yosemite';
131 |       case 11: return 'El Capitan';
132 |       case 12: return 'Sierra';
133 |       case 13: return 'High Sierra';
134 |       case 14: return 'Mojave';
135 |       case 15: return 'Catalina';
136 |       default: return undefined;
137 |     }
138 |   }
139 | 
140 |   /**
141 |    * Get Android version name
142 |    *    1.5 - Cupcake
143 |    *    1.6 - Donut
144 |    *    2.0 - Eclair
145 |    *    2.1 - Eclair
146 |    *    2.2 - Froyo
147 |    *    2.x - Gingerbread
148 |    *    3.x - Honeycomb
149 |    *    4.0 - Ice Cream Sandwich
150 |    *    4.1 - Jelly Bean
151 |    *    4.4 - KitKat
152 |    *    5.x - Lollipop
153 |    *    6.x - Marshmallow
154 |    *    7.x - Nougat
155 |    *    8.x - Oreo
156 |    *    9.x - Pie
157 |    *
158 |    * @example
159 |    *   getAndroidVersionName("7.0") // 'Nougat'
160 |    *
161 |    * @param  {string} version
162 |    * @return {string} versionName
163 |    */
164 |   static getAndroidVersionName(version) {
165 |     const v = version.split('.').splice(0, 2).map(s => parseInt(s, 10) || 0);
166 |     v.push(0);
167 |     if (v[0] === 1 && v[1] < 5) return undefined;
168 |     if (v[0] === 1 && v[1] < 6) return 'Cupcake';
169 |     if (v[0] === 1 && v[1] >= 6) return 'Donut';
170 |     if (v[0] === 2 && v[1] < 2) return 'Eclair';
171 |     if (v[0] === 2 && v[1] === 2) return 'Froyo';
172 |     if (v[0] === 2 && v[1] > 2) return 'Gingerbread';
173 |     if (v[0] === 3) return 'Honeycomb';
174 |     if (v[0] === 4 && v[1] < 1) return 'Ice Cream Sandwich';
175 |     if (v[0] === 4 && v[1] < 4) return 'Jelly Bean';
176 |     if (v[0] === 4 && v[1] >= 4) return 'KitKat';
177 |     if (v[0] === 5) return 'Lollipop';
178 |     if (v[0] === 6) return 'Marshmallow';
179 |     if (v[0] === 7) return 'Nougat';
180 |     if (v[0] === 8) return 'Oreo';
181 |     if (v[0] === 9) return 'Pie';
182 |     return undefined;
183 |   }
184 | 
185 |   /**
186 |    * Get version precisions count
187 |    *
188 |    * @example
189 |    *   getVersionPrecision("1.10.3") // 3
190 |    *
191 |    * @param  {string} version
192 |    * @return {number}
193 |    */
194 |   static getVersionPrecision(version) {
195 |     return version.split('.').length;
196 |   }
197 | 
198 |   /**
199 |    * Calculate browser version weight
200 |    *
201 |    * @example
202 |    *   compareVersions('1.10.2.1',  '1.8.2.1.90')    // 1
203 |    *   compareVersions('1.010.2.1', '1.09.2.1.90');  // 1
204 |    *   compareVersions('1.10.2.1',  '1.10.2.1');     // 0
205 |    *   compareVersions('1.10.2.1',  '1.0800.2');     // -1
206 |    *   compareVersions('1.10.2.1',  '1.10',  true);  // 0
207 |    *
208 |    * @param {String} versionA versions versions to compare
209 |    * @param {String} versionB versions versions to compare
210 |    * @param {boolean} [isLoose] enable loose comparison
211 |    * @return {Number} comparison result: -1 when versionA is lower,
212 |    * 1 when versionA is bigger, 0 when both equal
213 |    */
214 |   /* eslint consistent-return: 1 */
215 |   static compareVersions(versionA, versionB, isLoose = false) {
216 |     // 1) get common precision for both versions, for example for "10.0" and "9" it should be 2
217 |     const versionAPrecision = Utils.getVersionPrecision(versionA);
218 |     const versionBPrecision = Utils.getVersionPrecision(versionB);
219 | 
220 |     let precision = Math.max(versionAPrecision, versionBPrecision);
221 |     let lastPrecision = 0;
222 | 
223 |     const chunks = Utils.map([versionA, versionB], (version) => {
224 |       const delta = precision - Utils.getVersionPrecision(version);
225 | 
226 |       // 2) "9" -> "9.0" (for precision = 2)
227 |       const _version = version + new Array(delta + 1).join('.0');
228 | 
229 |       // 3) "9.0" -> ["000000000"", "000000009"]
230 |       return Utils.map(_version.split('.'), chunk => new Array(20 - chunk.length).join('0') + chunk).reverse();
231 |     });
232 | 
233 |     // adjust precision for loose comparison
234 |     if (isLoose) {
235 |       lastPrecision = precision - Math.min(versionAPrecision, versionBPrecision);
236 |     }
237 | 
238 |     // iterate in reverse order by reversed chunks array
239 |     precision -= 1;
240 |     while (precision >= lastPrecision) {
241 |       // 4) compare: "000000009" > "000000010" = false (but "9" > "10" = true)
242 |       if (chunks[0][precision] > chunks[1][precision]) {
243 |         return 1;
244 |       }
245 | 
246 |       if (chunks[0][precision] === chunks[1][precision]) {
247 |         if (precision === lastPrecision) {
248 |           // all version chunks are same
249 |           return 0;
250 |         }
251 | 
252 |         precision -= 1;
253 |       } else if (chunks[0][precision] < chunks[1][precision]) {
254 |         return -1;
255 |       }
256 |     }
257 | 
258 |     return undefined;
259 |   }
260 | 
261 |   /**
262 |    * Array::map polyfill
263 |    *
264 |    * @param  {Array} arr
265 |    * @param  {Function} iterator
266 |    * @return {Array}
267 |    */
268 |   static map(arr, iterator) {
269 |     const result = [];
270 |     let i;
271 |     if (Array.prototype.map) {
272 |       return Array.prototype.map.call(arr, iterator);
273 |     }
274 |     for (i = 0; i < arr.length; i += 1) {
275 |       result.push(iterator(arr[i]));
276 |     }
277 |     return result;
278 |   }
279 | 
280 |   /**
281 |    * Array::find polyfill
282 |    *
283 |    * @param  {Array} arr
284 |    * @param  {Function} predicate
285 |    * @return {Array}
286 |    */
287 |   static find(arr, predicate) {
288 |     let i;
289 |     let l;
290 |     if (Array.prototype.find) {
291 |       return Array.prototype.find.call(arr, predicate);
292 |     }
293 |     for (i = 0, l = arr.length; i < l; i += 1) {
294 |       const value = arr[i];
295 |       if (predicate(value, i)) {
296 |         return value;
297 |       }
298 |     }
299 |     return undefined;
300 |   }
301 | 
302 |   /**
303 |    * Object::assign polyfill
304 |    *
305 |    * @param  {Object} obj
306 |    * @param  {Object} ...objs
307 |    * @return {Object}
308 |    */
309 |   static assign(obj, ...assigners) {
310 |     const result = obj;
311 |     let i;
312 |     let l;
313 |     if (Object.assign) {
314 |       return Object.assign(obj, ...assigners);
315 |     }
316 |     for (i = 0, l = assigners.length; i < l; i += 1) {
317 |       const assigner = assigners[i];
318 |       if (typeof assigner === 'object' && assigner !== null) {
319 |         const keys = Object.keys(assigner);
320 |         keys.forEach((key) => {
321 |           result[key] = assigner[key];
322 |         });
323 |       }
324 |     }
325 |     return obj;
326 |   }
327 | 
328 |   /**
329 |    * Get short version/alias for a browser name
330 |    *
331 |    * @example
332 |    *   getBrowserAlias('Microsoft Edge') // edge
333 |    *
334 |    * @param  {string} browserName
335 |    * @return {string}
336 |    */
337 |   static getBrowserAlias(browserName) {
338 |     return BROWSER_ALIASES_MAP[browserName];
339 |   }
340 | 
341 |   /**
342 |    * Get short version/alias for a browser name
343 |    *
344 |    * @example
345 |    *   getBrowserAlias('edge') // Microsoft Edge
346 |    *
347 |    * @param  {string} browserAlias
348 |    * @return {string}
349 |    */
350 |   static getBrowserTypeByAlias(browserAlias) {
351 |     return BROWSER_MAP[browserAlias] || '';
352 |   }
353 | }
354 | 
355 |
356 |
357 | 358 | 359 | 360 | 361 | 362 | 363 |
364 | 365 |
366 | 367 |
368 | Documentation generated by JSDoc 3.6.3 on Sat Sep 12 2020 11:21:13 GMT+0300 (Eastern European Summer Time) using the docdash theme. 369 |
370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for Bowser v2 2 | // Project: https://github.com/lancedikson/bowser 3 | // Definitions by: Alexander P. Cerutti , 4 | 5 | export default Bowser; 6 | export as namespace Bowser; 7 | 8 | declare namespace Bowser { 9 | /** 10 | * Creates a Parser instance 11 | * @param {string} UA - User agent string 12 | * @param {boolean} skipParsing 13 | */ 14 | 15 | function getParser(UA: string, skipParsing?: boolean): Parser.Parser; 16 | 17 | /** 18 | * Creates a Parser instance and runs Parser.getResult immediately 19 | * @param UA - User agent string 20 | * @returns {Parser.ParsedResult} 21 | */ 22 | 23 | function parse(UA: string): Parser.ParsedResult; 24 | 25 | /** 26 | * Constants exposed via bowser getters 27 | */ 28 | const BROWSER_MAP: Record; 29 | const ENGINE_MAP: Record; 30 | const OS_MAP: Record; 31 | const PLATFORMS_MAP: Record; 32 | 33 | namespace Parser { 34 | interface Parser { 35 | constructor(UA: string, skipParsing?: boolean): Parser.Parser; 36 | 37 | /** 38 | * Check if the version is equals the browser version 39 | * @param version The string to compare with the browser version 40 | * @returns {boolean} 41 | */ 42 | 43 | compareVersion(version: string): boolean; 44 | 45 | /** 46 | * Get parsed browser object 47 | * @return {BrowserDetails} Browser's details 48 | */ 49 | 50 | getBrowser(): BrowserDetails; 51 | 52 | /** 53 | * Get browser's name 54 | * @param {Boolean} [toLowerCase] return lower-cased value 55 | * @return {String} Browser's name or an empty string 56 | */ 57 | 58 | getBrowserName(toLowerCase?: boolean): string; 59 | 60 | /** 61 | * Get browser's version 62 | * @return {String} version of browser 63 | */ 64 | 65 | getBrowserVersion(): string | undefined; 66 | 67 | /** 68 | * Get OS 69 | * @return {OSDetails} - OS Details 70 | * 71 | * @example 72 | * this.getOS(); // { 73 | * // name: 'macOS', 74 | * // version: '10.11.12', 75 | * // } 76 | */ 77 | 78 | getOS(): OSDetails; 79 | 80 | /** 81 | * Get OS name 82 | * @param {Boolean} [toLowerCase] return lower-cased value 83 | * @return {String} name of the OS — macOS, Windows, Linux, etc. 84 | */ 85 | 86 | getOSName(toLowerCase?: boolean): string; 87 | 88 | /** 89 | * Get OS version 90 | * @return {String} full version with dots ('10.11.12', '5.6', etc) 91 | */ 92 | 93 | getOSVersion(): string; 94 | 95 | /** 96 | * Get parsed platform 97 | * @returns {PlatformDetails} 98 | */ 99 | 100 | getPlatform(): PlatformDetails; 101 | 102 | /** 103 | * Get platform name 104 | * @param {boolean} toLowerCase 105 | */ 106 | 107 | getPlatformType(toLowerCase?: boolean): string; 108 | 109 | /** 110 | * Get parsed engine 111 | * @returns {EngineDetails} 112 | */ 113 | 114 | getEngine(): EngineDetails; 115 | 116 | /** 117 | * Get parsed engine's name 118 | * @returns {String} Engine's name or an empty string 119 | */ 120 | 121 | getEngineName(): string; 122 | 123 | /** 124 | * Get parsed result 125 | * @return {ParsedResult} 126 | */ 127 | 128 | getResult(): ParsedResult; 129 | 130 | /** 131 | * Get UserAgent string of current Parser instance 132 | * @return {String} User-Agent String of the current object 133 | */ 134 | 135 | getUA(): string; 136 | 137 | /** 138 | * Is anything? Check if the browser is called "anything", 139 | * the OS called "anything" or the platform called "anything" 140 | * @param {String} anything 141 | * @param [includingAlias=false] The flag showing whether alias will be included into comparison 142 | * @returns {Boolean} 143 | */ 144 | 145 | is(anything: any, includingAlias?: boolean): boolean; 146 | 147 | /** 148 | * Check if the browser name equals the passed string 149 | * @param browserName The string to compare with the browser name 150 | * @param [includingAlias=false] The flag showing whether alias will be included into comparison 151 | * @returns {boolean} 152 | */ 153 | 154 | isBrowser(browserName: string, includingAlias?: boolean): boolean; 155 | 156 | /** 157 | * Check if the engine name equals the passed string 158 | * @param engineName The string to compare with the engine name 159 | * @returns {boolean} 160 | */ 161 | 162 | isEngine(engineName: string): boolean; 163 | 164 | /** 165 | * Check if the OS name equals the passed string 166 | * @param OSName The string to compare with the OS name 167 | * @returns {boolean} 168 | */ 169 | 170 | isOS(OSName: string): boolean; 171 | 172 | /** 173 | * Check if the platform name equals the passed string 174 | * @param platformName The string to compare with the platform name 175 | * @returns {boolean} 176 | */ 177 | 178 | isPlatform(platformName: string): boolean; 179 | 180 | /** 181 | * Parse full information about the browser 182 | * @returns {Parser.Parser} 183 | */ 184 | 185 | parse(): Parser.Parser; 186 | 187 | /** 188 | * Get parsed browser object 189 | * @returns {BrowserDetails} 190 | */ 191 | 192 | parseBrowser(): BrowserDetails; 193 | 194 | /** 195 | * Get parsed engine 196 | * @returns {EngineDetails} 197 | */ 198 | 199 | parseEngine(): EngineDetails; 200 | 201 | /** 202 | * Parse OS and save it to this.parsedResult.os 203 | * @returns {OSDetails} 204 | */ 205 | 206 | parseOS(): OSDetails; 207 | 208 | /** 209 | * Get parsed platform 210 | * @returns {PlatformDetails} 211 | */ 212 | 213 | parsePlatform(): PlatformDetails; 214 | 215 | /** 216 | * Check if parsed browser matches certain conditions 217 | * 218 | * @param {checkTree} checkTree It's one or two layered object, 219 | * which can include a platform or an OS on the first layer 220 | * and should have browsers specs on the bottom-laying layer 221 | * 222 | * @returns {Boolean|undefined} Whether the browser satisfies the set conditions or not. 223 | * Returns `undefined` when the browser is no described in the checkTree object. 224 | * 225 | * @example 226 | * const browser = new Bowser(UA); 227 | * if (browser.check({chrome: '>118.01.1322' })) 228 | * // or with os 229 | * if (browser.check({windows: { chrome: '>118.01.1322' } })) 230 | * // or with platforms 231 | * if (browser.check({desktop: { chrome: '>118.01.1322' } })) 232 | */ 233 | 234 | satisfies(checkTree: checkTree): boolean | undefined; 235 | 236 | /** 237 | * Check if the browser name equals the passed string 238 | * @param {string} browserName The string to compare with the browser name 239 | * @param [includingAlias=false] The flag showing whether alias will be included into comparison 240 | * @returns {boolean} 241 | */ 242 | 243 | isBrowser(browserName: string, includingAlias?: boolean): boolean; 244 | 245 | /** 246 | * Check if the engine name equals the passed string 247 | * @param {string} engineName The string to compare with the engine name 248 | * @returns {boolean} 249 | */ 250 | 251 | isEngine(engineName: string): boolean; 252 | 253 | /** 254 | * Check if the platform type equals the passed string 255 | * @param {string} platformType The string to compare with the platform type 256 | * @returns {boolean} 257 | */ 258 | 259 | isPlatform(platformType: string): boolean; 260 | 261 | /** 262 | * Check if the OS name equals the passed string 263 | * @param {string} osName The string to compare with the OS name 264 | * @returns {boolean} 265 | */ 266 | 267 | isOS(osName: string): boolean; 268 | 269 | /** 270 | * Check if any of the given values satisfies `.is(anything)` 271 | * @param {string[]} anythings 272 | * @returns {boolean} true if at least one condition is satisfied, false otherwise. 273 | */ 274 | 275 | some(anythings: string[]): boolean | undefined; 276 | 277 | /** 278 | * Test a UA string for a regexp 279 | * @param regex 280 | * @returns {boolean} true if the regex matches the UA, false otherwise. 281 | */ 282 | 283 | test(regex: RegExp): boolean; 284 | } 285 | 286 | interface ParsedResult { 287 | browser: BrowserDetails; 288 | os: OSDetails; 289 | platform: PlatformDetails; 290 | engine: EngineDetails; 291 | } 292 | 293 | interface Details { 294 | name?: string; 295 | version?: string; 296 | } 297 | 298 | interface OSDetails extends Details { 299 | versionName?: string; 300 | } 301 | 302 | interface PlatformDetails { 303 | type?: string; 304 | vendor?: string; 305 | model?: string; 306 | } 307 | 308 | type BrowserDetails = Details; 309 | type EngineDetails = Details; 310 | 311 | interface checkTree { 312 | [key: string]: any; 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "include": ["src", "README.md"], 7 | "includePattern": ".js$", 8 | "excludePattern": "(node_modules/|docs)" 9 | }, 10 | "plugins": [ 11 | "plugins/markdown" 12 | ], 13 | "opts": { 14 | "template": "node_modules/docdash", 15 | "encoding": "utf8", 16 | "destination": "docs/", 17 | "recurse": true, 18 | "verbose": true 19 | }, 20 | "templates": { 21 | "cleverLinks": false, 22 | "monospaceLinks": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bowser", 3 | "description": "Lightweight browser detector", 4 | "keywords": [ 5 | "browser", 6 | "useragent", 7 | "user-agent", 8 | "parser", 9 | "ua", 10 | "detection", 11 | "ender", 12 | "sniff" 13 | ], 14 | "homepage": "https://github.com/lancedikson/bowser", 15 | "author": "Dustin Diaz (http://dustindiaz.com)", 16 | "contributors": [ 17 | { 18 | "name": "Denis Demchenko", 19 | "url": "http://twitter.com/lancedikson" 20 | } 21 | ], 22 | "main": "es5.js", 23 | "browser": "es5.js", 24 | "module": "src/bowser.js", 25 | "types": "index.d.ts", 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/lancedikson/bowser.git" 29 | }, 30 | "devDependencies": { 31 | "@babel/cli": "^7.11.6", 32 | "@babel/core": "^7.8.0", 33 | "@babel/polyfill": "^7.8.3", 34 | "@babel/preset-env": "^7.8.2", 35 | "@babel/register": "^7.8.3", 36 | "ava": "^3.0.0", 37 | "babel-eslint": "^10.0.3", 38 | "babel-loader": "^8.0.6", 39 | "babel-plugin-add-module-exports": "^1.0.2", 40 | "babel-plugin-istanbul": "^6.0.0", 41 | "compression-webpack-plugin": "^4.0.0", 42 | "coveralls": "^3.0.6", 43 | "docdash": "^1.1.1", 44 | "eslint": "^6.5.1", 45 | "eslint-config-airbnb-base": "^13.2.0", 46 | "eslint-plugin-ava": "^10.0.0", 47 | "eslint-plugin-import": "^2.18.2", 48 | "gh-pages": "^3.0.0", 49 | "jsdoc": "^3.6.3", 50 | "nyc": "^15.0.0", 51 | "sinon": "^9.0.0", 52 | "testem": "^3.0.0", 53 | "webpack": "^4.41.0", 54 | "webpack-bundle-analyzer": "^3.5.2", 55 | "webpack-cli": "^3.3.9", 56 | "yamljs": "^0.3.0" 57 | }, 58 | "ava": { 59 | "require": [ 60 | "@babel/register" 61 | ] 62 | }, 63 | "bugs": { 64 | "url": "https://github.com/lancedikson/bowser/issues" 65 | }, 66 | "directories": { 67 | "test": "test" 68 | }, 69 | "scripts": { 70 | "build": "webpack --config webpack.config.js", 71 | "generate-and-deploy-docs": "npm run generate-docs && gh-pages --dist docs --dest docs", 72 | "watch": "webpack --watch --config webpack.config.js", 73 | "prepublishOnly": "npm run build", 74 | "lint:check": "eslint ./src", 75 | "lint:fix": "eslint --fix ./src", 76 | "testem": "testem", 77 | "test": "nyc --reporter=html --reporter=text ava", 78 | "test:watch": "ava --watch", 79 | "coverage": "nyc report --reporter=text-lcov | coveralls", 80 | "generate-docs": "jsdoc -c jsdoc.json" 81 | }, 82 | "license": "MIT" 83 | } 84 | -------------------------------------------------------------------------------- /src/bowser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bowser - a browser detector 3 | * https://github.com/lancedikson/bowser 4 | * MIT License | (c) Dustin Diaz 2012-2015 5 | * MIT License | (c) Denis Demchenko 2015-2019 6 | */ 7 | import Parser from './parser.js'; 8 | import { 9 | BROWSER_MAP, 10 | ENGINE_MAP, 11 | OS_MAP, 12 | PLATFORMS_MAP, 13 | } from './constants.js'; 14 | 15 | /** 16 | * Bowser class. 17 | * Keep it simple as much as it can be. 18 | * It's supposed to work with collections of {@link Parser} instances 19 | * rather then solve one-instance problems. 20 | * All the one-instance stuff is located in Parser class. 21 | * 22 | * @class 23 | * @classdesc Bowser is a static object, that provides an API to the Parsers 24 | * @hideconstructor 25 | */ 26 | class Bowser { 27 | /** 28 | * Creates a {@link Parser} instance 29 | * 30 | * @param {String} UA UserAgent string 31 | * @param {Boolean} [skipParsing=false] Will make the Parser postpone parsing until you ask it 32 | * explicitly. Same as `skipParsing` for {@link Parser}. 33 | * @returns {Parser} 34 | * @throws {Error} when UA is not a String 35 | * 36 | * @example 37 | * const parser = Bowser.getParser(window.navigator.userAgent); 38 | * const result = parser.getResult(); 39 | */ 40 | static getParser(UA, skipParsing = false) { 41 | if (typeof UA !== 'string') { 42 | throw new Error('UserAgent should be a string'); 43 | } 44 | return new Parser(UA, skipParsing); 45 | } 46 | 47 | /** 48 | * Creates a {@link Parser} instance and runs {@link Parser.getResult} immediately 49 | * 50 | * @param UA 51 | * @return {ParsedResult} 52 | * 53 | * @example 54 | * const result = Bowser.parse(window.navigator.userAgent); 55 | */ 56 | static parse(UA) { 57 | return (new Parser(UA)).getResult(); 58 | } 59 | 60 | static get BROWSER_MAP() { 61 | return BROWSER_MAP; 62 | } 63 | 64 | static get ENGINE_MAP() { 65 | return ENGINE_MAP; 66 | } 67 | 68 | static get OS_MAP() { 69 | return OS_MAP; 70 | } 71 | 72 | static get PLATFORMS_MAP() { 73 | return PLATFORMS_MAP; 74 | } 75 | } 76 | 77 | export default Bowser; 78 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | // NOTE: this list must be up-to-date with browsers listed in 2 | // test/acceptance/useragentstrings.yml 3 | export const BROWSER_ALIASES_MAP = { 4 | 'Amazon Silk': 'amazon_silk', 5 | 'Android Browser': 'android', 6 | Bada: 'bada', 7 | BlackBerry: 'blackberry', 8 | Chrome: 'chrome', 9 | Chromium: 'chromium', 10 | Electron: 'electron', 11 | Epiphany: 'epiphany', 12 | Firefox: 'firefox', 13 | Focus: 'focus', 14 | Generic: 'generic', 15 | 'Google Search': 'google_search', 16 | Googlebot: 'googlebot', 17 | 'Internet Explorer': 'ie', 18 | 'K-Meleon': 'k_meleon', 19 | Maxthon: 'maxthon', 20 | 'Microsoft Edge': 'edge', 21 | 'MZ Browser': 'mz', 22 | 'NAVER Whale Browser': 'naver', 23 | Opera: 'opera', 24 | 'Opera Coast': 'opera_coast', 25 | 'Pale Moon': 'pale_moon', 26 | PhantomJS: 'phantomjs', 27 | Puffin: 'puffin', 28 | QupZilla: 'qupzilla', 29 | QQ: 'qq', 30 | QQLite: 'qqlite', 31 | Safari: 'safari', 32 | Sailfish: 'sailfish', 33 | 'Samsung Internet for Android': 'samsung_internet', 34 | SeaMonkey: 'seamonkey', 35 | Sleipnir: 'sleipnir', 36 | Swing: 'swing', 37 | Tizen: 'tizen', 38 | 'UC Browser': 'uc', 39 | Vivaldi: 'vivaldi', 40 | 'WebOS Browser': 'webos', 41 | WeChat: 'wechat', 42 | 'Yandex Browser': 'yandex', 43 | Roku: 'roku', 44 | }; 45 | 46 | export const BROWSER_MAP = { 47 | amazon_silk: 'Amazon Silk', 48 | android: 'Android Browser', 49 | bada: 'Bada', 50 | blackberry: 'BlackBerry', 51 | chrome: 'Chrome', 52 | chromium: 'Chromium', 53 | electron: 'Electron', 54 | epiphany: 'Epiphany', 55 | firefox: 'Firefox', 56 | focus: 'Focus', 57 | generic: 'Generic', 58 | googlebot: 'Googlebot', 59 | google_search: 'Google Search', 60 | ie: 'Internet Explorer', 61 | k_meleon: 'K-Meleon', 62 | maxthon: 'Maxthon', 63 | edge: 'Microsoft Edge', 64 | mz: 'MZ Browser', 65 | naver: 'NAVER Whale Browser', 66 | opera: 'Opera', 67 | opera_coast: 'Opera Coast', 68 | pale_moon: 'Pale Moon', 69 | phantomjs: 'PhantomJS', 70 | puffin: 'Puffin', 71 | qupzilla: 'QupZilla', 72 | qq: 'QQ Browser', 73 | qqlite: 'QQ Browser Lite', 74 | safari: 'Safari', 75 | sailfish: 'Sailfish', 76 | samsung_internet: 'Samsung Internet for Android', 77 | seamonkey: 'SeaMonkey', 78 | sleipnir: 'Sleipnir', 79 | swing: 'Swing', 80 | tizen: 'Tizen', 81 | uc: 'UC Browser', 82 | vivaldi: 'Vivaldi', 83 | webos: 'WebOS Browser', 84 | wechat: 'WeChat', 85 | yandex: 'Yandex Browser', 86 | }; 87 | 88 | export const PLATFORMS_MAP = { 89 | tablet: 'tablet', 90 | mobile: 'mobile', 91 | desktop: 'desktop', 92 | tv: 'tv', 93 | bot: 'bot', 94 | }; 95 | 96 | export const OS_MAP = { 97 | WindowsPhone: 'Windows Phone', 98 | Windows: 'Windows', 99 | MacOS: 'macOS', 100 | iOS: 'iOS', 101 | Android: 'Android', 102 | WebOS: 'WebOS', 103 | BlackBerry: 'BlackBerry', 104 | Bada: 'Bada', 105 | Tizen: 'Tizen', 106 | Linux: 'Linux', 107 | ChromeOS: 'Chrome OS', 108 | PlayStation4: 'PlayStation 4', 109 | Roku: 'Roku', 110 | }; 111 | 112 | export const ENGINE_MAP = { 113 | EdgeHTML: 'EdgeHTML', 114 | Blink: 'Blink', 115 | Trident: 'Trident', 116 | Presto: 'Presto', 117 | Gecko: 'Gecko', 118 | WebKit: 'WebKit', 119 | }; 120 | -------------------------------------------------------------------------------- /src/parser-engines.js: -------------------------------------------------------------------------------- 1 | import Utils from './utils.js'; 2 | import { ENGINE_MAP } from './constants.js'; 3 | 4 | /* 5 | * More specific goes first 6 | */ 7 | export default [ 8 | /* EdgeHTML */ 9 | { 10 | test(parser) { 11 | return parser.getBrowserName(true) === 'microsoft edge'; 12 | }, 13 | describe(ua) { 14 | const isBlinkBased = /\sedg\//i.test(ua); 15 | 16 | // return blink if it's blink-based one 17 | if (isBlinkBased) { 18 | return { 19 | name: ENGINE_MAP.Blink, 20 | }; 21 | } 22 | 23 | // otherwise match the version and return EdgeHTML 24 | const version = Utils.getFirstMatch(/edge\/(\d+(\.?_?\d+)+)/i, ua); 25 | 26 | return { 27 | name: ENGINE_MAP.EdgeHTML, 28 | version, 29 | }; 30 | }, 31 | }, 32 | 33 | /* Trident */ 34 | { 35 | test: [/trident/i], 36 | describe(ua) { 37 | const engine = { 38 | name: ENGINE_MAP.Trident, 39 | }; 40 | 41 | const version = Utils.getFirstMatch(/trident\/(\d+(\.?_?\d+)+)/i, ua); 42 | 43 | if (version) { 44 | engine.version = version; 45 | } 46 | 47 | return engine; 48 | }, 49 | }, 50 | 51 | /* Presto */ 52 | { 53 | test(parser) { 54 | return parser.test(/presto/i); 55 | }, 56 | describe(ua) { 57 | const engine = { 58 | name: ENGINE_MAP.Presto, 59 | }; 60 | 61 | const version = Utils.getFirstMatch(/presto\/(\d+(\.?_?\d+)+)/i, ua); 62 | 63 | if (version) { 64 | engine.version = version; 65 | } 66 | 67 | return engine; 68 | }, 69 | }, 70 | 71 | /* Gecko */ 72 | { 73 | test(parser) { 74 | const isGecko = parser.test(/gecko/i); 75 | const likeGecko = parser.test(/like gecko/i); 76 | return isGecko && !likeGecko; 77 | }, 78 | describe(ua) { 79 | const engine = { 80 | name: ENGINE_MAP.Gecko, 81 | }; 82 | 83 | const version = Utils.getFirstMatch(/gecko\/(\d+(\.?_?\d+)+)/i, ua); 84 | 85 | if (version) { 86 | engine.version = version; 87 | } 88 | 89 | return engine; 90 | }, 91 | }, 92 | 93 | /* Blink */ 94 | { 95 | test: [/(apple)?webkit\/537\.36/i], 96 | describe() { 97 | return { 98 | name: ENGINE_MAP.Blink, 99 | }; 100 | }, 101 | }, 102 | 103 | /* WebKit */ 104 | { 105 | test: [/(apple)?webkit/i], 106 | describe(ua) { 107 | const engine = { 108 | name: ENGINE_MAP.WebKit, 109 | }; 110 | 111 | const version = Utils.getFirstMatch(/webkit\/(\d+(\.?_?\d+)+)/i, ua); 112 | 113 | if (version) { 114 | engine.version = version; 115 | } 116 | 117 | return engine; 118 | }, 119 | }, 120 | ]; 121 | -------------------------------------------------------------------------------- /src/parser-os.js: -------------------------------------------------------------------------------- 1 | import Utils from './utils.js'; 2 | import { OS_MAP } from './constants.js'; 3 | 4 | export default [ 5 | /* Roku */ 6 | { 7 | test: [/Roku\/DVP/], 8 | describe(ua) { 9 | const version = Utils.getFirstMatch(/Roku\/DVP-(\d+\.\d+)/i, ua); 10 | return { 11 | name: OS_MAP.Roku, 12 | version, 13 | }; 14 | }, 15 | }, 16 | 17 | /* Windows Phone */ 18 | { 19 | test: [/windows phone/i], 20 | describe(ua) { 21 | const version = Utils.getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i, ua); 22 | return { 23 | name: OS_MAP.WindowsPhone, 24 | version, 25 | }; 26 | }, 27 | }, 28 | 29 | /* Windows */ 30 | { 31 | test: [/windows /i], 32 | describe(ua) { 33 | const version = Utils.getFirstMatch(/Windows ((NT|XP)( \d\d?.\d)?)/i, ua); 34 | const versionName = Utils.getWindowsVersionName(version); 35 | 36 | return { 37 | name: OS_MAP.Windows, 38 | version, 39 | versionName, 40 | }; 41 | }, 42 | }, 43 | 44 | /* Firefox on iPad */ 45 | { 46 | test: [/Macintosh(.*?) FxiOS(.*?)\//], 47 | describe(ua) { 48 | const result = { 49 | name: OS_MAP.iOS, 50 | }; 51 | const version = Utils.getSecondMatch(/(Version\/)(\d[\d.]+)/, ua); 52 | if (version) { 53 | result.version = version; 54 | } 55 | return result; 56 | }, 57 | }, 58 | 59 | /* macOS */ 60 | { 61 | test: [/macintosh/i], 62 | describe(ua) { 63 | const version = Utils.getFirstMatch(/mac os x (\d+(\.?_?\d+)+)/i, ua).replace(/[_\s]/g, '.'); 64 | const versionName = Utils.getMacOSVersionName(version); 65 | 66 | const os = { 67 | name: OS_MAP.MacOS, 68 | version, 69 | }; 70 | if (versionName) { 71 | os.versionName = versionName; 72 | } 73 | return os; 74 | }, 75 | }, 76 | 77 | /* iOS */ 78 | { 79 | test: [/(ipod|iphone|ipad)/i], 80 | describe(ua) { 81 | const version = Utils.getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i, ua).replace(/[_\s]/g, '.'); 82 | 83 | return { 84 | name: OS_MAP.iOS, 85 | version, 86 | }; 87 | }, 88 | }, 89 | 90 | /* Android */ 91 | { 92 | test(parser) { 93 | const notLikeAndroid = !parser.test(/like android/i); 94 | const butAndroid = parser.test(/android/i); 95 | return notLikeAndroid && butAndroid; 96 | }, 97 | describe(ua) { 98 | const version = Utils.getFirstMatch(/android[\s/-](\d+(\.\d+)*)/i, ua); 99 | const versionName = Utils.getAndroidVersionName(version); 100 | const os = { 101 | name: OS_MAP.Android, 102 | version, 103 | }; 104 | if (versionName) { 105 | os.versionName = versionName; 106 | } 107 | return os; 108 | }, 109 | }, 110 | 111 | /* WebOS */ 112 | { 113 | test: [/(web|hpw)[o0]s/i], 114 | describe(ua) { 115 | const version = Utils.getFirstMatch(/(?:web|hpw)[o0]s\/(\d+(\.\d+)*)/i, ua); 116 | const os = { 117 | name: OS_MAP.WebOS, 118 | }; 119 | 120 | if (version && version.length) { 121 | os.version = version; 122 | } 123 | return os; 124 | }, 125 | }, 126 | 127 | /* BlackBerry */ 128 | { 129 | test: [/blackberry|\bbb\d+/i, /rim\stablet/i], 130 | describe(ua) { 131 | const version = Utils.getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i, ua) 132 | || Utils.getFirstMatch(/blackberry\d+\/(\d+([_\s]\d+)*)/i, ua) 133 | || Utils.getFirstMatch(/\bbb(\d+)/i, ua); 134 | 135 | return { 136 | name: OS_MAP.BlackBerry, 137 | version, 138 | }; 139 | }, 140 | }, 141 | 142 | /* Bada */ 143 | { 144 | test: [/bada/i], 145 | describe(ua) { 146 | const version = Utils.getFirstMatch(/bada\/(\d+(\.\d+)*)/i, ua); 147 | 148 | return { 149 | name: OS_MAP.Bada, 150 | version, 151 | }; 152 | }, 153 | }, 154 | 155 | /* Tizen */ 156 | { 157 | test: [/tizen/i], 158 | describe(ua) { 159 | const version = Utils.getFirstMatch(/tizen[/\s](\d+(\.\d+)*)/i, ua); 160 | 161 | return { 162 | name: OS_MAP.Tizen, 163 | version, 164 | }; 165 | }, 166 | }, 167 | 168 | /* Linux */ 169 | { 170 | test: [/linux/i], 171 | describe() { 172 | return { 173 | name: OS_MAP.Linux, 174 | }; 175 | }, 176 | }, 177 | 178 | /* Chrome OS */ 179 | { 180 | test: [/CrOS/], 181 | describe() { 182 | return { 183 | name: OS_MAP.ChromeOS, 184 | }; 185 | }, 186 | }, 187 | 188 | /* Playstation 4 */ 189 | { 190 | test: [/PlayStation 4/], 191 | describe(ua) { 192 | const version = Utils.getFirstMatch(/PlayStation 4[/\s](\d+(\.\d+)*)/i, ua); 193 | return { 194 | name: OS_MAP.PlayStation4, 195 | version, 196 | }; 197 | }, 198 | }, 199 | ]; 200 | -------------------------------------------------------------------------------- /src/parser-platforms.js: -------------------------------------------------------------------------------- 1 | import Utils from './utils.js'; 2 | import { PLATFORMS_MAP } from './constants.js'; 3 | 4 | /* 5 | * Tablets go first since usually they have more specific 6 | * signs to detect. 7 | */ 8 | 9 | export default [ 10 | /* Googlebot */ 11 | { 12 | test: [/googlebot/i], 13 | describe() { 14 | return { 15 | type: PLATFORMS_MAP.bot, 16 | vendor: 'Google', 17 | }; 18 | }, 19 | }, 20 | 21 | /* Huawei */ 22 | { 23 | test: [/huawei/i], 24 | describe(ua) { 25 | const model = Utils.getFirstMatch(/(can-l01)/i, ua) && 'Nova'; 26 | const platform = { 27 | type: PLATFORMS_MAP.mobile, 28 | vendor: 'Huawei', 29 | }; 30 | if (model) { 31 | platform.model = model; 32 | } 33 | return platform; 34 | }, 35 | }, 36 | 37 | /* Nexus Tablet */ 38 | { 39 | test: [/nexus\s*(?:7|8|9|10).*/i], 40 | describe() { 41 | return { 42 | type: PLATFORMS_MAP.tablet, 43 | vendor: 'Nexus', 44 | }; 45 | }, 46 | }, 47 | 48 | /* iPad */ 49 | { 50 | test: [/ipad/i], 51 | describe() { 52 | return { 53 | type: PLATFORMS_MAP.tablet, 54 | vendor: 'Apple', 55 | model: 'iPad', 56 | }; 57 | }, 58 | }, 59 | 60 | /* Firefox on iPad */ 61 | { 62 | test: [/Macintosh(.*?) FxiOS(.*?)\//], 63 | describe() { 64 | return { 65 | type: PLATFORMS_MAP.tablet, 66 | vendor: 'Apple', 67 | model: 'iPad', 68 | }; 69 | }, 70 | }, 71 | 72 | /* Amazon Kindle Fire */ 73 | { 74 | test: [/kftt build/i], 75 | describe() { 76 | return { 77 | type: PLATFORMS_MAP.tablet, 78 | vendor: 'Amazon', 79 | model: 'Kindle Fire HD 7', 80 | }; 81 | }, 82 | }, 83 | 84 | /* Another Amazon Tablet with Silk */ 85 | { 86 | test: [/silk/i], 87 | describe() { 88 | return { 89 | type: PLATFORMS_MAP.tablet, 90 | vendor: 'Amazon', 91 | }; 92 | }, 93 | }, 94 | 95 | /* Tablet */ 96 | { 97 | test: [/tablet(?! pc)/i], 98 | describe() { 99 | return { 100 | type: PLATFORMS_MAP.tablet, 101 | }; 102 | }, 103 | }, 104 | 105 | /* iPod/iPhone */ 106 | { 107 | test(parser) { 108 | const iDevice = parser.test(/ipod|iphone/i); 109 | const likeIDevice = parser.test(/like (ipod|iphone)/i); 110 | return iDevice && !likeIDevice; 111 | }, 112 | describe(ua) { 113 | const model = Utils.getFirstMatch(/(ipod|iphone)/i, ua); 114 | return { 115 | type: PLATFORMS_MAP.mobile, 116 | vendor: 'Apple', 117 | model, 118 | }; 119 | }, 120 | }, 121 | 122 | /* Nexus Mobile */ 123 | { 124 | test: [/nexus\s*[0-6].*/i, /galaxy nexus/i], 125 | describe() { 126 | return { 127 | type: PLATFORMS_MAP.mobile, 128 | vendor: 'Nexus', 129 | }; 130 | }, 131 | }, 132 | 133 | /* Nokia */ 134 | { 135 | test: [/Nokia/i], 136 | describe(ua) { 137 | const model = Utils.getFirstMatch(/Nokia\s+([0-9]+(\.[0-9]+)?)/i, ua); 138 | const platform = { 139 | type: PLATFORMS_MAP.mobile, 140 | vendor: 'Nokia', 141 | }; 142 | if (model) { 143 | platform.model = model; 144 | } 145 | return platform; 146 | }, 147 | }, 148 | 149 | /* Mobile */ 150 | { 151 | test: [/[^-]mobi/i], 152 | describe() { 153 | return { 154 | type: PLATFORMS_MAP.mobile, 155 | }; 156 | }, 157 | }, 158 | 159 | /* BlackBerry */ 160 | { 161 | test(parser) { 162 | return parser.getBrowserName(true) === 'blackberry'; 163 | }, 164 | describe() { 165 | return { 166 | type: PLATFORMS_MAP.mobile, 167 | vendor: 'BlackBerry', 168 | }; 169 | }, 170 | }, 171 | 172 | /* Bada */ 173 | { 174 | test(parser) { 175 | return parser.getBrowserName(true) === 'bada'; 176 | }, 177 | describe() { 178 | return { 179 | type: PLATFORMS_MAP.mobile, 180 | }; 181 | }, 182 | }, 183 | 184 | /* Windows Phone */ 185 | { 186 | test(parser) { 187 | return parser.getBrowserName() === 'windows phone'; 188 | }, 189 | describe() { 190 | return { 191 | type: PLATFORMS_MAP.mobile, 192 | vendor: 'Microsoft', 193 | }; 194 | }, 195 | }, 196 | 197 | /* Android Tablet */ 198 | { 199 | test(parser) { 200 | const osMajorVersion = Number(String(parser.getOSVersion()).split('.')[0]); 201 | return parser.getOSName(true) === 'android' && (osMajorVersion >= 3); 202 | }, 203 | describe() { 204 | return { 205 | type: PLATFORMS_MAP.tablet, 206 | }; 207 | }, 208 | }, 209 | 210 | /* Android Mobile */ 211 | { 212 | test(parser) { 213 | return parser.getOSName(true) === 'android'; 214 | }, 215 | describe() { 216 | return { 217 | type: PLATFORMS_MAP.mobile, 218 | }; 219 | }, 220 | }, 221 | 222 | /* desktop */ 223 | { 224 | test(parser) { 225 | return parser.getOSName(true) === 'macos'; 226 | }, 227 | describe() { 228 | return { 229 | type: PLATFORMS_MAP.desktop, 230 | vendor: 'Apple', 231 | }; 232 | }, 233 | }, 234 | 235 | /* Windows */ 236 | { 237 | test(parser) { 238 | return parser.getOSName(true) === 'windows'; 239 | }, 240 | describe() { 241 | return { 242 | type: PLATFORMS_MAP.desktop, 243 | }; 244 | }, 245 | }, 246 | 247 | /* Linux */ 248 | { 249 | test(parser) { 250 | return parser.getOSName(true) === 'linux'; 251 | }, 252 | describe() { 253 | return { 254 | type: PLATFORMS_MAP.desktop, 255 | }; 256 | }, 257 | }, 258 | 259 | /* PlayStation 4 */ 260 | { 261 | test(parser) { 262 | return parser.getOSName(true) === 'playstation 4'; 263 | }, 264 | describe() { 265 | return { 266 | type: PLATFORMS_MAP.tv, 267 | }; 268 | }, 269 | }, 270 | 271 | /* Roku */ 272 | { 273 | test(parser) { 274 | return parser.getOSName(true) === 'roku'; 275 | }, 276 | describe() { 277 | return { 278 | type: PLATFORMS_MAP.tv, 279 | }; 280 | }, 281 | }, 282 | ]; 283 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | import browserParsersList from './parser-browsers.js'; 2 | import osParsersList from './parser-os.js'; 3 | import platformParsersList from './parser-platforms.js'; 4 | import enginesParsersList from './parser-engines.js'; 5 | import Utils from './utils.js'; 6 | 7 | /** 8 | * The main class that arranges the whole parsing process. 9 | */ 10 | class Parser { 11 | /** 12 | * Create instance of Parser 13 | * 14 | * @param {String} UA User-Agent string 15 | * @param {Boolean} [skipParsing=false] parser can skip parsing in purpose of performance 16 | * improvements if you need to make a more particular parsing 17 | * like {@link Parser#parseBrowser} or {@link Parser#parsePlatform} 18 | * 19 | * @throw {Error} in case of empty UA String 20 | * 21 | * @constructor 22 | */ 23 | constructor(UA, skipParsing = false) { 24 | if (UA === void (0) || UA === null || UA === '') { 25 | throw new Error("UserAgent parameter can't be empty"); 26 | } 27 | 28 | this._ua = UA; 29 | 30 | /** 31 | * @typedef ParsedResult 32 | * @property {Object} browser 33 | * @property {String|undefined} [browser.name] 34 | * Browser name, like `"Chrome"` or `"Internet Explorer"` 35 | * @property {String|undefined} [browser.version] Browser version as a String `"12.01.45334.10"` 36 | * @property {Object} os 37 | * @property {String|undefined} [os.name] OS name, like `"Windows"` or `"macOS"` 38 | * @property {String|undefined} [os.version] OS version, like `"NT 5.1"` or `"10.11.1"` 39 | * @property {String|undefined} [os.versionName] OS name, like `"XP"` or `"High Sierra"` 40 | * @property {Object} platform 41 | * @property {String|undefined} [platform.type] 42 | * platform type, can be either `"desktop"`, `"tablet"` or `"mobile"` 43 | * @property {String|undefined} [platform.vendor] Vendor of the device, 44 | * like `"Apple"` or `"Samsung"` 45 | * @property {String|undefined} [platform.model] Device model, 46 | * like `"iPhone"` or `"Kindle Fire HD 7"` 47 | * @property {Object} engine 48 | * @property {String|undefined} [engine.name] 49 | * Can be any of this: `WebKit`, `Blink`, `Gecko`, `Trident`, `Presto`, `EdgeHTML` 50 | * @property {String|undefined} [engine.version] String version of the engine 51 | */ 52 | this.parsedResult = {}; 53 | 54 | if (skipParsing !== true) { 55 | this.parse(); 56 | } 57 | } 58 | 59 | /** 60 | * Get UserAgent string of current Parser instance 61 | * @return {String} User-Agent String of the current object 62 | * 63 | * @public 64 | */ 65 | getUA() { 66 | return this._ua; 67 | } 68 | 69 | /** 70 | * Test a UA string for a regexp 71 | * @param {RegExp} regex 72 | * @return {Boolean} 73 | */ 74 | test(regex) { 75 | return regex.test(this._ua); 76 | } 77 | 78 | /** 79 | * Get parsed browser object 80 | * @return {Object} 81 | */ 82 | parseBrowser() { 83 | this.parsedResult.browser = {}; 84 | 85 | const browserDescriptor = Utils.find(browserParsersList, (_browser) => { 86 | if (typeof _browser.test === 'function') { 87 | return _browser.test(this); 88 | } 89 | 90 | if (Array.isArray(_browser.test)) { 91 | return _browser.test.some(condition => this.test(condition)); 92 | } 93 | 94 | throw new Error("Browser's test function is not valid"); 95 | }); 96 | 97 | if (browserDescriptor) { 98 | this.parsedResult.browser = browserDescriptor.describe(this.getUA()); 99 | } 100 | 101 | return this.parsedResult.browser; 102 | } 103 | 104 | /** 105 | * Get parsed browser object 106 | * @return {Object} 107 | * 108 | * @public 109 | */ 110 | getBrowser() { 111 | if (this.parsedResult.browser) { 112 | return this.parsedResult.browser; 113 | } 114 | 115 | return this.parseBrowser(); 116 | } 117 | 118 | /** 119 | * Get browser's name 120 | * @return {String} Browser's name or an empty string 121 | * 122 | * @public 123 | */ 124 | getBrowserName(toLowerCase) { 125 | if (toLowerCase) { 126 | return String(this.getBrowser().name).toLowerCase() || ''; 127 | } 128 | return this.getBrowser().name || ''; 129 | } 130 | 131 | 132 | /** 133 | * Get browser's version 134 | * @return {String} version of browser 135 | * 136 | * @public 137 | */ 138 | getBrowserVersion() { 139 | return this.getBrowser().version; 140 | } 141 | 142 | /** 143 | * Get OS 144 | * @return {Object} 145 | * 146 | * @example 147 | * this.getOS(); 148 | * { 149 | * name: 'macOS', 150 | * version: '10.11.12' 151 | * } 152 | */ 153 | getOS() { 154 | if (this.parsedResult.os) { 155 | return this.parsedResult.os; 156 | } 157 | 158 | return this.parseOS(); 159 | } 160 | 161 | /** 162 | * Parse OS and save it to this.parsedResult.os 163 | * @return {*|{}} 164 | */ 165 | parseOS() { 166 | this.parsedResult.os = {}; 167 | 168 | const os = Utils.find(osParsersList, (_os) => { 169 | if (typeof _os.test === 'function') { 170 | return _os.test(this); 171 | } 172 | 173 | if (Array.isArray(_os.test)) { 174 | return _os.test.some(condition => this.test(condition)); 175 | } 176 | 177 | throw new Error("Browser's test function is not valid"); 178 | }); 179 | 180 | if (os) { 181 | this.parsedResult.os = os.describe(this.getUA()); 182 | } 183 | 184 | return this.parsedResult.os; 185 | } 186 | 187 | /** 188 | * Get OS name 189 | * @param {Boolean} [toLowerCase] return lower-cased value 190 | * @return {String} name of the OS — macOS, Windows, Linux, etc. 191 | */ 192 | getOSName(toLowerCase) { 193 | const { name } = this.getOS(); 194 | 195 | if (toLowerCase) { 196 | return String(name).toLowerCase() || ''; 197 | } 198 | 199 | return name || ''; 200 | } 201 | 202 | /** 203 | * Get OS version 204 | * @return {String} full version with dots ('10.11.12', '5.6', etc) 205 | */ 206 | getOSVersion() { 207 | return this.getOS().version; 208 | } 209 | 210 | /** 211 | * Get parsed platform 212 | * @return {{}} 213 | */ 214 | getPlatform() { 215 | if (this.parsedResult.platform) { 216 | return this.parsedResult.platform; 217 | } 218 | 219 | return this.parsePlatform(); 220 | } 221 | 222 | /** 223 | * Get platform name 224 | * @param {Boolean} [toLowerCase=false] 225 | * @return {*} 226 | */ 227 | getPlatformType(toLowerCase = false) { 228 | const { type } = this.getPlatform(); 229 | 230 | if (toLowerCase) { 231 | return String(type).toLowerCase() || ''; 232 | } 233 | 234 | return type || ''; 235 | } 236 | 237 | /** 238 | * Get parsed platform 239 | * @return {{}} 240 | */ 241 | parsePlatform() { 242 | this.parsedResult.platform = {}; 243 | 244 | const platform = Utils.find(platformParsersList, (_platform) => { 245 | if (typeof _platform.test === 'function') { 246 | return _platform.test(this); 247 | } 248 | 249 | if (Array.isArray(_platform.test)) { 250 | return _platform.test.some(condition => this.test(condition)); 251 | } 252 | 253 | throw new Error("Browser's test function is not valid"); 254 | }); 255 | 256 | if (platform) { 257 | this.parsedResult.platform = platform.describe(this.getUA()); 258 | } 259 | 260 | return this.parsedResult.platform; 261 | } 262 | 263 | /** 264 | * Get parsed engine 265 | * @return {{}} 266 | */ 267 | getEngine() { 268 | if (this.parsedResult.engine) { 269 | return this.parsedResult.engine; 270 | } 271 | 272 | return this.parseEngine(); 273 | } 274 | 275 | /** 276 | * Get engines's name 277 | * @return {String} Engines's name or an empty string 278 | * 279 | * @public 280 | */ 281 | getEngineName(toLowerCase) { 282 | if (toLowerCase) { 283 | return String(this.getEngine().name).toLowerCase() || ''; 284 | } 285 | return this.getEngine().name || ''; 286 | } 287 | 288 | /** 289 | * Get parsed platform 290 | * @return {{}} 291 | */ 292 | parseEngine() { 293 | this.parsedResult.engine = {}; 294 | 295 | const engine = Utils.find(enginesParsersList, (_engine) => { 296 | if (typeof _engine.test === 'function') { 297 | return _engine.test(this); 298 | } 299 | 300 | if (Array.isArray(_engine.test)) { 301 | return _engine.test.some(condition => this.test(condition)); 302 | } 303 | 304 | throw new Error("Browser's test function is not valid"); 305 | }); 306 | 307 | if (engine) { 308 | this.parsedResult.engine = engine.describe(this.getUA()); 309 | } 310 | 311 | return this.parsedResult.engine; 312 | } 313 | 314 | /** 315 | * Parse full information about the browser 316 | * @returns {Parser} 317 | */ 318 | parse() { 319 | this.parseBrowser(); 320 | this.parseOS(); 321 | this.parsePlatform(); 322 | this.parseEngine(); 323 | 324 | return this; 325 | } 326 | 327 | /** 328 | * Get parsed result 329 | * @return {ParsedResult} 330 | */ 331 | getResult() { 332 | return Utils.assign({}, this.parsedResult); 333 | } 334 | 335 | /** 336 | * Check if parsed browser matches certain conditions 337 | * 338 | * @param {Object} checkTree It's one or two layered object, 339 | * which can include a platform or an OS on the first layer 340 | * and should have browsers specs on the bottom-laying layer 341 | * 342 | * @returns {Boolean|undefined} Whether the browser satisfies the set conditions or not. 343 | * Returns `undefined` when the browser is no described in the checkTree object. 344 | * 345 | * @example 346 | * const browser = Bowser.getParser(window.navigator.userAgent); 347 | * if (browser.satisfies({chrome: '>118.01.1322' })) 348 | * // or with os 349 | * if (browser.satisfies({windows: { chrome: '>118.01.1322' } })) 350 | * // or with platforms 351 | * if (browser.satisfies({desktop: { chrome: '>118.01.1322' } })) 352 | */ 353 | satisfies(checkTree) { 354 | const platformsAndOSes = {}; 355 | let platformsAndOSCounter = 0; 356 | const browsers = {}; 357 | let browsersCounter = 0; 358 | 359 | const allDefinitions = Object.keys(checkTree); 360 | 361 | allDefinitions.forEach((key) => { 362 | const currentDefinition = checkTree[key]; 363 | if (typeof currentDefinition === 'string') { 364 | browsers[key] = currentDefinition; 365 | browsersCounter += 1; 366 | } else if (typeof currentDefinition === 'object') { 367 | platformsAndOSes[key] = currentDefinition; 368 | platformsAndOSCounter += 1; 369 | } 370 | }); 371 | 372 | if (platformsAndOSCounter > 0) { 373 | const platformsAndOSNames = Object.keys(platformsAndOSes); 374 | const OSMatchingDefinition = Utils.find(platformsAndOSNames, name => (this.isOS(name))); 375 | 376 | if (OSMatchingDefinition) { 377 | const osResult = this.satisfies(platformsAndOSes[OSMatchingDefinition]); 378 | 379 | if (osResult !== void 0) { 380 | return osResult; 381 | } 382 | } 383 | 384 | const platformMatchingDefinition = Utils.find( 385 | platformsAndOSNames, 386 | name => (this.isPlatform(name)), 387 | ); 388 | if (platformMatchingDefinition) { 389 | const platformResult = this.satisfies(platformsAndOSes[platformMatchingDefinition]); 390 | 391 | if (platformResult !== void 0) { 392 | return platformResult; 393 | } 394 | } 395 | } 396 | 397 | if (browsersCounter > 0) { 398 | const browserNames = Object.keys(browsers); 399 | const matchingDefinition = Utils.find(browserNames, name => (this.isBrowser(name, true))); 400 | 401 | if (matchingDefinition !== void 0) { 402 | return this.compareVersion(browsers[matchingDefinition]); 403 | } 404 | } 405 | 406 | return undefined; 407 | } 408 | 409 | /** 410 | * Check if the browser name equals the passed string 411 | * @param {string} browserName The string to compare with the browser name 412 | * @param [includingAlias=false] The flag showing whether alias will be included into comparison 413 | * @returns {boolean} 414 | */ 415 | isBrowser(browserName, includingAlias = false) { 416 | const defaultBrowserName = this.getBrowserName().toLowerCase(); 417 | let browserNameLower = browserName.toLowerCase(); 418 | const alias = Utils.getBrowserTypeByAlias(browserNameLower); 419 | 420 | if (includingAlias && alias) { 421 | browserNameLower = alias.toLowerCase(); 422 | } 423 | return browserNameLower === defaultBrowserName; 424 | } 425 | 426 | compareVersion(version) { 427 | let expectedResults = [0]; 428 | let comparableVersion = version; 429 | let isLoose = false; 430 | 431 | const currentBrowserVersion = this.getBrowserVersion(); 432 | 433 | if (typeof currentBrowserVersion !== 'string') { 434 | return void 0; 435 | } 436 | 437 | if (version[0] === '>' || version[0] === '<') { 438 | comparableVersion = version.substr(1); 439 | if (version[1] === '=') { 440 | isLoose = true; 441 | comparableVersion = version.substr(2); 442 | } else { 443 | expectedResults = []; 444 | } 445 | if (version[0] === '>') { 446 | expectedResults.push(1); 447 | } else { 448 | expectedResults.push(-1); 449 | } 450 | } else if (version[0] === '=') { 451 | comparableVersion = version.substr(1); 452 | } else if (version[0] === '~') { 453 | isLoose = true; 454 | comparableVersion = version.substr(1); 455 | } 456 | 457 | return expectedResults.indexOf( 458 | Utils.compareVersions(currentBrowserVersion, comparableVersion, isLoose), 459 | ) > -1; 460 | } 461 | 462 | /** 463 | * Check if the OS name equals the passed string 464 | * @param {string} osName The string to compare with the OS name 465 | * @returns {boolean} 466 | */ 467 | isOS(osName) { 468 | return this.getOSName(true) === String(osName).toLowerCase(); 469 | } 470 | 471 | /** 472 | * Check if the platform type equals the passed string 473 | * @param {string} platformType The string to compare with the platform type 474 | * @returns {boolean} 475 | */ 476 | isPlatform(platformType) { 477 | return this.getPlatformType(true) === String(platformType).toLowerCase(); 478 | } 479 | 480 | /** 481 | * Check if the engine name equals the passed string 482 | * @param {string} engineName The string to compare with the engine name 483 | * @returns {boolean} 484 | */ 485 | isEngine(engineName) { 486 | return this.getEngineName(true) === String(engineName).toLowerCase(); 487 | } 488 | 489 | /** 490 | * Is anything? Check if the browser is called "anything", 491 | * the OS called "anything" or the platform called "anything" 492 | * @param {String} anything 493 | * @param [includingAlias=false] The flag showing whether alias will be included into comparison 494 | * @returns {Boolean} 495 | */ 496 | is(anything, includingAlias = false) { 497 | return this.isBrowser(anything, includingAlias) || this.isOS(anything) 498 | || this.isPlatform(anything); 499 | } 500 | 501 | /** 502 | * Check if any of the given values satisfies this.is(anything) 503 | * @param {String[]} anythings 504 | * @returns {Boolean} 505 | */ 506 | some(anythings = []) { 507 | return anythings.some(anything => this.is(anything)); 508 | } 509 | } 510 | 511 | export default Parser; 512 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { BROWSER_MAP, BROWSER_ALIASES_MAP } from './constants.js'; 2 | 3 | export default class Utils { 4 | /** 5 | * Get first matched item for a string 6 | * @param {RegExp} regexp 7 | * @param {String} ua 8 | * @return {Array|{index: number, input: string}|*|boolean|string} 9 | */ 10 | static getFirstMatch(regexp, ua) { 11 | const match = ua.match(regexp); 12 | return (match && match.length > 0 && match[1]) || ''; 13 | } 14 | 15 | /** 16 | * Get second matched item for a string 17 | * @param regexp 18 | * @param {String} ua 19 | * @return {Array|{index: number, input: string}|*|boolean|string} 20 | */ 21 | static getSecondMatch(regexp, ua) { 22 | const match = ua.match(regexp); 23 | return (match && match.length > 1 && match[2]) || ''; 24 | } 25 | 26 | /** 27 | * Match a regexp and return a constant or undefined 28 | * @param {RegExp} regexp 29 | * @param {String} ua 30 | * @param {*} _const Any const that will be returned if regexp matches the string 31 | * @return {*} 32 | */ 33 | static matchAndReturnConst(regexp, ua, _const) { 34 | if (regexp.test(ua)) { 35 | return _const; 36 | } 37 | return void (0); 38 | } 39 | 40 | static getWindowsVersionName(version) { 41 | switch (version) { 42 | case 'NT': return 'NT'; 43 | case 'XP': return 'XP'; 44 | case 'NT 5.0': return '2000'; 45 | case 'NT 5.1': return 'XP'; 46 | case 'NT 5.2': return '2003'; 47 | case 'NT 6.0': return 'Vista'; 48 | case 'NT 6.1': return '7'; 49 | case 'NT 6.2': return '8'; 50 | case 'NT 6.3': return '8.1'; 51 | case 'NT 10.0': return '10'; 52 | default: return undefined; 53 | } 54 | } 55 | 56 | /** 57 | * Get macOS version name 58 | * 10.5 - Leopard 59 | * 10.6 - Snow Leopard 60 | * 10.7 - Lion 61 | * 10.8 - Mountain Lion 62 | * 10.9 - Mavericks 63 | * 10.10 - Yosemite 64 | * 10.11 - El Capitan 65 | * 10.12 - Sierra 66 | * 10.13 - High Sierra 67 | * 10.14 - Mojave 68 | * 10.15 - Catalina 69 | * 70 | * @example 71 | * getMacOSVersionName("10.14") // 'Mojave' 72 | * 73 | * @param {string} version 74 | * @return {string} versionName 75 | */ 76 | static getMacOSVersionName(version) { 77 | const v = version.split('.').splice(0, 2).map(s => parseInt(s, 10) || 0); 78 | v.push(0); 79 | if (v[0] !== 10) return undefined; 80 | switch (v[1]) { 81 | case 5: return 'Leopard'; 82 | case 6: return 'Snow Leopard'; 83 | case 7: return 'Lion'; 84 | case 8: return 'Mountain Lion'; 85 | case 9: return 'Mavericks'; 86 | case 10: return 'Yosemite'; 87 | case 11: return 'El Capitan'; 88 | case 12: return 'Sierra'; 89 | case 13: return 'High Sierra'; 90 | case 14: return 'Mojave'; 91 | case 15: return 'Catalina'; 92 | default: return undefined; 93 | } 94 | } 95 | 96 | /** 97 | * Get Android version name 98 | * 1.5 - Cupcake 99 | * 1.6 - Donut 100 | * 2.0 - Eclair 101 | * 2.1 - Eclair 102 | * 2.2 - Froyo 103 | * 2.x - Gingerbread 104 | * 3.x - Honeycomb 105 | * 4.0 - Ice Cream Sandwich 106 | * 4.1 - Jelly Bean 107 | * 4.4 - KitKat 108 | * 5.x - Lollipop 109 | * 6.x - Marshmallow 110 | * 7.x - Nougat 111 | * 8.x - Oreo 112 | * 9.x - Pie 113 | * 114 | * @example 115 | * getAndroidVersionName("7.0") // 'Nougat' 116 | * 117 | * @param {string} version 118 | * @return {string} versionName 119 | */ 120 | static getAndroidVersionName(version) { 121 | const v = version.split('.').splice(0, 2).map(s => parseInt(s, 10) || 0); 122 | v.push(0); 123 | if (v[0] === 1 && v[1] < 5) return undefined; 124 | if (v[0] === 1 && v[1] < 6) return 'Cupcake'; 125 | if (v[0] === 1 && v[1] >= 6) return 'Donut'; 126 | if (v[0] === 2 && v[1] < 2) return 'Eclair'; 127 | if (v[0] === 2 && v[1] === 2) return 'Froyo'; 128 | if (v[0] === 2 && v[1] > 2) return 'Gingerbread'; 129 | if (v[0] === 3) return 'Honeycomb'; 130 | if (v[0] === 4 && v[1] < 1) return 'Ice Cream Sandwich'; 131 | if (v[0] === 4 && v[1] < 4) return 'Jelly Bean'; 132 | if (v[0] === 4 && v[1] >= 4) return 'KitKat'; 133 | if (v[0] === 5) return 'Lollipop'; 134 | if (v[0] === 6) return 'Marshmallow'; 135 | if (v[0] === 7) return 'Nougat'; 136 | if (v[0] === 8) return 'Oreo'; 137 | if (v[0] === 9) return 'Pie'; 138 | return undefined; 139 | } 140 | 141 | /** 142 | * Get version precisions count 143 | * 144 | * @example 145 | * getVersionPrecision("1.10.3") // 3 146 | * 147 | * @param {string} version 148 | * @return {number} 149 | */ 150 | static getVersionPrecision(version) { 151 | return version.split('.').length; 152 | } 153 | 154 | /** 155 | * Calculate browser version weight 156 | * 157 | * @example 158 | * compareVersions('1.10.2.1', '1.8.2.1.90') // 1 159 | * compareVersions('1.010.2.1', '1.09.2.1.90'); // 1 160 | * compareVersions('1.10.2.1', '1.10.2.1'); // 0 161 | * compareVersions('1.10.2.1', '1.0800.2'); // -1 162 | * compareVersions('1.10.2.1', '1.10', true); // 0 163 | * 164 | * @param {String} versionA versions versions to compare 165 | * @param {String} versionB versions versions to compare 166 | * @param {boolean} [isLoose] enable loose comparison 167 | * @return {Number} comparison result: -1 when versionA is lower, 168 | * 1 when versionA is bigger, 0 when both equal 169 | */ 170 | /* eslint consistent-return: 1 */ 171 | static compareVersions(versionA, versionB, isLoose = false) { 172 | // 1) get common precision for both versions, for example for "10.0" and "9" it should be 2 173 | const versionAPrecision = Utils.getVersionPrecision(versionA); 174 | const versionBPrecision = Utils.getVersionPrecision(versionB); 175 | 176 | let precision = Math.max(versionAPrecision, versionBPrecision); 177 | let lastPrecision = 0; 178 | 179 | const chunks = Utils.map([versionA, versionB], (version) => { 180 | const delta = precision - Utils.getVersionPrecision(version); 181 | 182 | // 2) "9" -> "9.0" (for precision = 2) 183 | const _version = version + new Array(delta + 1).join('.0'); 184 | 185 | // 3) "9.0" -> ["000000000"", "000000009"] 186 | return Utils.map(_version.split('.'), chunk => new Array(20 - chunk.length).join('0') + chunk).reverse(); 187 | }); 188 | 189 | // adjust precision for loose comparison 190 | if (isLoose) { 191 | lastPrecision = precision - Math.min(versionAPrecision, versionBPrecision); 192 | } 193 | 194 | // iterate in reverse order by reversed chunks array 195 | precision -= 1; 196 | while (precision >= lastPrecision) { 197 | // 4) compare: "000000009" > "000000010" = false (but "9" > "10" = true) 198 | if (chunks[0][precision] > chunks[1][precision]) { 199 | return 1; 200 | } 201 | 202 | if (chunks[0][precision] === chunks[1][precision]) { 203 | if (precision === lastPrecision) { 204 | // all version chunks are same 205 | return 0; 206 | } 207 | 208 | precision -= 1; 209 | } else if (chunks[0][precision] < chunks[1][precision]) { 210 | return -1; 211 | } 212 | } 213 | 214 | return undefined; 215 | } 216 | 217 | /** 218 | * Array::map polyfill 219 | * 220 | * @param {Array} arr 221 | * @param {Function} iterator 222 | * @return {Array} 223 | */ 224 | static map(arr, iterator) { 225 | const result = []; 226 | let i; 227 | if (Array.prototype.map) { 228 | return Array.prototype.map.call(arr, iterator); 229 | } 230 | for (i = 0; i < arr.length; i += 1) { 231 | result.push(iterator(arr[i])); 232 | } 233 | return result; 234 | } 235 | 236 | /** 237 | * Array::find polyfill 238 | * 239 | * @param {Array} arr 240 | * @param {Function} predicate 241 | * @return {Array} 242 | */ 243 | static find(arr, predicate) { 244 | let i; 245 | let l; 246 | if (Array.prototype.find) { 247 | return Array.prototype.find.call(arr, predicate); 248 | } 249 | for (i = 0, l = arr.length; i < l; i += 1) { 250 | const value = arr[i]; 251 | if (predicate(value, i)) { 252 | return value; 253 | } 254 | } 255 | return undefined; 256 | } 257 | 258 | /** 259 | * Object::assign polyfill 260 | * 261 | * @param {Object} obj 262 | * @param {Object} ...objs 263 | * @return {Object} 264 | */ 265 | static assign(obj, ...assigners) { 266 | const result = obj; 267 | let i; 268 | let l; 269 | if (Object.assign) { 270 | return Object.assign(obj, ...assigners); 271 | } 272 | for (i = 0, l = assigners.length; i < l; i += 1) { 273 | const assigner = assigners[i]; 274 | if (typeof assigner === 'object' && assigner !== null) { 275 | const keys = Object.keys(assigner); 276 | keys.forEach((key) => { 277 | result[key] = assigner[key]; 278 | }); 279 | } 280 | } 281 | return obj; 282 | } 283 | 284 | /** 285 | * Get short version/alias for a browser name 286 | * 287 | * @example 288 | * getBrowserAlias('Microsoft Edge') // edge 289 | * 290 | * @param {string} browserName 291 | * @return {string} 292 | */ 293 | static getBrowserAlias(browserName) { 294 | return BROWSER_ALIASES_MAP[browserName]; 295 | } 296 | 297 | /** 298 | * Get browser name for a short version/alias 299 | * 300 | * @example 301 | * getBrowserTypeByAlias('edge') // Microsoft Edge 302 | * 303 | * @param {string} browserAlias 304 | * @return {string} 305 | */ 306 | static getBrowserTypeByAlias(browserAlias) { 307 | return BROWSER_MAP[browserAlias] || ''; 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /test/acceptance/test-list-of-ua.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import yaml from 'yamljs'; 3 | import path from 'path'; 4 | import Bowser from '../../src/bowser'; 5 | import BowserBuild from '../../es5'; 6 | 7 | const listOfUA = yaml.load(path.join(__dirname, 'useragentstrings.yml')); 8 | 9 | const browserNames = Object.keys(listOfUA); 10 | 11 | browserNames.forEach((browserName) => { 12 | listOfUA[browserName].forEach((browser, index) => { 13 | test(`Test ${browserName} ${index}`, (t) => { 14 | const parsed = Bowser.parse(browser.ua); 15 | const parsedBuild = BowserBuild.parse(browser.ua); 16 | t.deepEqual(parsed, browser.spec, `${browser.ua}`); 17 | t.deepEqual(parsedBuild, browser.spec, `${browser.ua}`); 18 | t.is(parsed.browser.name, browserName, `${browser.ua}`); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/unit/bowser.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import Bowser from '../../src/bowser'; 3 | import Parser from '../../src/parser'; 4 | 5 | const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 OPR/43.0.2442.1165'; 6 | const browser = Bowser.getParser(UA); 7 | 8 | test('Bowser`s constructor returns a Parser instance', (t) => { 9 | t.truthy(browser instanceof Parser); 10 | }); 11 | 12 | test('Bowser`s constructor fails if UA is empty', (t) => { 13 | t.throws(() => (Bowser.getParser())); 14 | }); 15 | 16 | test('Bowser.parse parses UA and returns result', (t) => { 17 | t.deepEqual(Bowser.parse(UA), browser.getResult()); 18 | }); 19 | -------------------------------------------------------------------------------- /test/unit/constants.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { BROWSER_ALIASES_MAP } from '../../src/constants'; 3 | 4 | test('check duplicate aliases', (t) => { 5 | const aliasesList = Object.keys(BROWSER_ALIASES_MAP).map(value => (BROWSER_ALIASES_MAP[value])); 6 | let foundOnce, foundTwice; 7 | 8 | const duplicates = aliasesList.filter(item => { 9 | foundOnce = aliasesList.indexOf(item); 10 | foundTwice = aliasesList.indexOf(item, foundOnce + 1); 11 | return +foundTwice !== -1; 12 | }); 13 | 14 | t.deepEqual(duplicates, []); 15 | }); 16 | -------------------------------------------------------------------------------- /test/unit/parser.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import sinon from 'sinon'; 3 | import Parser from '../../src/parser'; 4 | import Bowser from '../../src/bowser'; 5 | 6 | const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 OPR/43.0.2442.1165'; 7 | const parser = new Parser(UA, true); 8 | 9 | const EDGE_UA = 'Mozilla/5.0 (Linux; Android 8.0; Pixel XL Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.0 Mobile Safari/537.36 EdgA/41.1.35.1'; 10 | const edgeParser = new Parser(EDGE_UA, true); 11 | 12 | const FOCUS_UA = 'Mozilla/5.0 (Linux; Android 7.1.1) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/1.2.1 Chrome/59.0.3071.125'; 13 | const focusParser = new Parser(FOCUS_UA, true); 14 | 15 | test('constructor', (t) => { 16 | t.truthy(parser instanceof Parser); 17 | }); 18 | 19 | test('Parser.getUA returns a correct UA', (t) => { 20 | t.is(parser.getUA(), UA); 21 | }); 22 | 23 | test('Parser.test', (t) => { 24 | t.truthy(parser.test(/Chrome/i)); 25 | }); 26 | 27 | test('Parser.parseBrowser is being called when the Parser.getBrowser() is called', (t) => { 28 | const spy = sinon.spy(parser, 'parseBrowser'); 29 | const b = parser.getBrowser(); 30 | t.truthy(spy.called); 31 | t.is(b.name, 'Opera'); 32 | t.is(b.version, '43.0.2442.1165'); 33 | parser.parseBrowser.restore(); 34 | }); 35 | 36 | test('Parser.getBrowserName returns a correct result', (t) => { 37 | t.is(parser.getBrowserName(), 'Opera'); 38 | }); 39 | 40 | test('Parser.getBrowserVersion returns a correct result', (t) => { 41 | t.is(parser.getBrowserVersion(), '43.0.2442.1165'); 42 | }); 43 | 44 | test('Parser.parseOS is being called when getOS() called', (t) => { 45 | const spy = sinon.spy(parser, 'parseOS'); 46 | parser.getOS(); 47 | t.truthy(spy.called); 48 | parser.parseOS.restore(); 49 | }); 50 | 51 | test('Parser.getOSName gives a name of the browser', (t) => { 52 | t.is(parser.getOSName(), 'macOS'); 53 | }); 54 | 55 | test('Parser.getOSName gives a lower-cased name of the browser', (t) => { 56 | t.is(parser.getOSName(true), 'macos'); 57 | }); 58 | 59 | test('Parser.getOSVersion returns a correct result', (t) => { 60 | t.is(parser.getOSVersion(), '10.12.4'); 61 | }); 62 | 63 | test('Parser.parseEngine is being called when getEngine() called', (t) => { 64 | const spy = sinon.spy(parser, 'parseEngine'); 65 | parser.getEngine(); 66 | t.truthy(spy.called); 67 | parser.parseEngine.restore(); 68 | }); 69 | 70 | test('Parser.getEngineName gives a name of the engine', (t) => { 71 | t.is(parser.getEngineName(), 'Blink'); 72 | }); 73 | 74 | test('Parser.getEngineName gives a lower-cased name of the engine', (t) => { 75 | t.is(parser.getEngineName(true), 'blink'); 76 | }); 77 | 78 | test('Skip parsing shouldn\'t parse', (t) => { 79 | t.deepEqual((new Parser(UA, true)).getResult(), {}); 80 | }); 81 | 82 | test('Parser.satisfies should make simple comparisons', (t) => { 83 | // also covers Parser.compareVersion() method 84 | t.is(parser.satisfies({ opera: '>42' }), true); 85 | t.is(parser.satisfies({ opera: '<44' }), true); 86 | t.is(parser.satisfies({ opera: '=43.0.2442.1165' }), true); 87 | t.is(parser.satisfies({ opera: '~43.0' }), true); 88 | t.is(parser.satisfies({ opera: '>=43' }), true); 89 | t.is(parser.satisfies({ opera: '<=43' }), true); 90 | t.is(parser.satisfies({ opera: '>=43.0' }), true); 91 | t.is(parser.satisfies({ opera: '>=43.0.2442.1165' }), true); 92 | t.is(parser.satisfies({ opera: '<=43.0.2442.1165' }), true); 93 | t.is(parser.satisfies({ opera: '>=43.0.2443' }), false); 94 | t.is(parser.satisfies({ opera: '<=43.0.2443' }), true); 95 | t.is(parser.satisfies({ opera: '>=43.0.2441' }), true); 96 | t.is(parser.satisfies({ opera: '~43' }), true); 97 | }); 98 | 99 | test('Parser.satisfies should make complex comparison', (t) => { 100 | t.is(parser.satisfies({ 101 | macos: { 102 | safari: '>11', 103 | }, 104 | ios: { 105 | safari: '>10', 106 | }, 107 | opera: '>42', 108 | }), true); 109 | }); 110 | 111 | test('Parser.satisfies should respect platform and OS specific declarations', (t) => { 112 | t.is(parser.satisfies({ 113 | macos: { 114 | opera: '>45', 115 | }, 116 | opera: '>42', 117 | }), false); 118 | 119 | t.is(parser.satisfies({ 120 | desktop: { 121 | opera: '>45', 122 | }, 123 | opera: '>42', 124 | }), false); 125 | 126 | t.is(parser.satisfies({ 127 | macos: { 128 | opera: '>45', 129 | }, 130 | desktop: { 131 | opera: '>42', 132 | }, 133 | opera: '>42', 134 | }), false); 135 | 136 | t.is(parser.satisfies({ 137 | macos: { 138 | chrome: '>45', 139 | }, 140 | desktop: { 141 | chrome: '>42', 142 | }, 143 | firefox: '>42', 144 | }), void 0); 145 | }); 146 | 147 | test('Parser.satisfies for versionless UA strings', (t) => { 148 | const _parser = new Parser('Mozilla/5.0 (iPhone; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15G77 [FBAN/FBIOS;FBDV/iPhone7,2;FBMD/iPhone;FBSN/iOS;FBSV/11.4.1;FBSS/2;FBCR/vfnl;FBID/phone;FBLC/nl_NL;FBOP/5;FBRV/0]'); 149 | 150 | t.is(_parser.satisfies({ 151 | safari: '>9', 152 | }), void 0); 153 | }); 154 | 155 | test('Parser.satisfies should consider aliases while handling browsers', (t) => { 156 | t.is(edgeParser.satisfies({ 'Microsoft Edge': '=41.1.35.1' }), true); 157 | t.is(edgeParser.satisfies({ 'microsoft edge': '=41.1.35.1' }), true); 158 | t.is(edgeParser.satisfies({ 'edge': '=41.1.35.1' }), true); 159 | t.is(edgeParser.satisfies({ 'Edge': '=41.1.35.1' }), true); 160 | }); 161 | 162 | test('Parser.is should pass', (t) => { 163 | t.is(parser.is('opera'), true); 164 | t.is(parser.is('desktop'), true); 165 | t.is(parser.is('macos'), true); 166 | }); 167 | 168 | test('Parser.is should pass when not including aliases', (t) => { 169 | t.is(edgeParser.is('Microsoft Edge', false), true); 170 | t.is(edgeParser.is('microsoft edge', false), true); 171 | t.is(edgeParser.is('mIcrosoft eDge', false), true); 172 | t.is(edgeParser.is('edge', false), false); 173 | t.is(edgeParser.is('Edge', false), false); 174 | t.is(edgeParser.is('desktop', false), false); 175 | t.is(edgeParser.is('macos', false), false); 176 | t.is(edgeParser.is('mobile', false), true); 177 | t.is(edgeParser.is('android', false), true); 178 | }); 179 | 180 | test('Parser.is should pass when including aliases', (t) => { 181 | t.is(edgeParser.is('Microsoft Edge', true), true); 182 | t.is(edgeParser.is('microsoft edge', true), true); 183 | t.is(edgeParser.is('mIcrosoft eDge', true), true); 184 | t.is(edgeParser.is('edge', true), true); 185 | t.is(edgeParser.is('Edge', true), true); 186 | t.is(edgeParser.is('desktop', true), false); 187 | t.is(edgeParser.is('macos', true), false); 188 | t.is(edgeParser.is('mobile', true), true); 189 | t.is(edgeParser.is('android', true), true); 190 | }); 191 | 192 | test('Parser.is using constants should pass', (t) => { 193 | t.is(parser.is(Bowser.BROWSER_MAP.opera), true); 194 | t.is(parser.is(Bowser.PLATFORMS_MAP.desktop), true); 195 | t.is(parser.is(Bowser.OS_MAP.MacOS), true); 196 | }); 197 | 198 | test('Parser.some should pass', (t) => { 199 | t.is(parser.some(['opera', 'chrome', 'firefox']), true); 200 | t.is(parser.some(['macos', 'windows']), true); 201 | t.is(parser.some(['chrome', 'firefox']), false); 202 | t.is(parser.some([]), false); 203 | t.is(parser.some(), false); 204 | }); 205 | 206 | test('Parser.isBrowser should pass when not loosely checking', (t) => { 207 | t.is(edgeParser.isBrowser('Microsoft Edge', false), true); 208 | t.is(edgeParser.isBrowser('microsoft edge', false), true); 209 | t.is(edgeParser.isBrowser('mIcrosoft eDge', false), true); 210 | t.is(edgeParser.isBrowser('edge', false), false); 211 | t.is(edgeParser.isBrowser('Edge', false), false); 212 | }); 213 | 214 | test('Parser.isBrowser should pass when loosely checking', (t) => { 215 | t.is(edgeParser.isBrowser('Microsoft Edge', true), true); 216 | t.is(edgeParser.isBrowser('microsoft edge', true), true); 217 | t.is(edgeParser.isBrowser('mIcrosoft eDge', true), true); 218 | t.is(edgeParser.isBrowser('edge', true), true); 219 | t.is(edgeParser.isBrowser('Edge', true), true); 220 | }); 221 | 222 | test('Parser.isBrowser should pass for non-aliased browsers', (t) => { 223 | t.is(focusParser.isBrowser('Focus', true), true); 224 | t.is(focusParser.isBrowser('Focus', false), true); 225 | }); 226 | 227 | test('Parser.isEngine should pass', (t) => { 228 | t.is(parser.isEngine('blink'), true); 229 | t.is(parser.isEngine('webkit'), false); 230 | }); 231 | -------------------------------------------------------------------------------- /test/unit/utils.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { 3 | getFirstMatch, 4 | getSecondMatch, 5 | matchAndReturnConst, 6 | getWindowsVersionName, 7 | getMacOSVersionName, 8 | getAndroidVersionName, 9 | getVersionPrecision, 10 | compareVersions, 11 | map, 12 | find, 13 | assign, 14 | getBrowserAlias, 15 | getBrowserTypeByAlias 16 | } from '../../src/utils'; 17 | 18 | test('getFirstMatch', (t) => { 19 | const matchedVersion = getFirstMatch(/version\/(\S+)/i, 'Chrome Version/11.11.11'); 20 | t.is(matchedVersion, '11.11.11'); 21 | }); 22 | 23 | test('getSecondMatch', (t) => { 24 | const matchedVersion = getSecondMatch(/version\/(\S+).*version\/(\S+)/i, 'Chrome Version/11.11.11 Chrome Version/22.22.22'); 25 | t.is(matchedVersion, '22.22.22'); 26 | }); 27 | 28 | test('matchAndReturnConst', (t) => { 29 | const _const = matchAndReturnConst(/version/i, 'version', "_const"); 30 | t.is(_const, '_const'); 31 | }); 32 | 33 | test('getWindowsVersionName', (t) => { 34 | t.is(getWindowsVersionName('NT 5.0'), '2000'); 35 | t.is(getWindowsVersionName('XXX'), void 0); 36 | }); 37 | 38 | test('getMacOSVersionName', (t) => { 39 | t.is(getMacOSVersionName('10.14.5'), 'Mojave'); 40 | t.is(getMacOSVersionName('10.15'), 'Catalina'); 41 | t.is(getMacOSVersionName('10.999999'), void 0); 42 | t.is(getMacOSVersionName('XXX'), void 0); 43 | }); 44 | 45 | test('getAndroidVersionName', (t) => { 46 | t.is(getAndroidVersionName('1.0'), void 0); 47 | t.is(getAndroidVersionName('8.0'), 'Oreo'); 48 | t.is(getAndroidVersionName('9'), 'Pie'); 49 | t.is(getAndroidVersionName('XXX'), void 0); 50 | }); 51 | 52 | test('getVersionPrecision', (t) => { 53 | const precision = getVersionPrecision("10.14.5"); 54 | t.is(precision, 3); 55 | }); 56 | 57 | test('compareVersions', (t) => { 58 | const comparisionsTasks = [ 59 | ['9.0', '10', -1], 60 | ['11', '10', 1], 61 | ['1.10.2.1', '1.8.2.1.90', 1], 62 | ['1.010.2.1', '1.08.2.1.90', 1], 63 | ['1.10.2.1', '1.10.2.1', 0], 64 | ['1.10.2.1', '1.10.2', 0, true], 65 | ['1.10.2.1', '1.10', 0, true], 66 | ['1.10.2.1', '1', 0, true], 67 | ['1.10.2.1', '1.0800.2', -1], 68 | ['1.0.0-alpha', '1.0.0-alpha.1', -1], 69 | ['1.0.0-alpha.1', '1.0.0-alpha.beta', -1], 70 | ['1.0.0-alpha.beta', '1.0.0-beta', -1], 71 | ['1.0.0-beta', '1.0.0-beta.2', -1], 72 | ['1.0.0-beta.11', '1.0.0-rc.1', -1], 73 | ['1.0.0-rc.1', '1.0.0', -1], 74 | ]; 75 | 76 | comparisionsTasks.forEach((testingParams) => { 77 | const versionA = testingParams[0]; 78 | const versionB = testingParams[1]; 79 | const result = testingParams[2]; 80 | const isLoose = testingParams.length > 3 ? testingParams[3] : false; 81 | let matching = isLoose ? '~' : ' == '; 82 | 83 | if (result > 0) { 84 | matching = ' > '; 85 | } else if (result < 0) { 86 | matching = ' < '; 87 | } 88 | 89 | t.is(compareVersions(versionA, versionB, isLoose), result, `version ${versionA} should be ${matching} version ${versionB}`); 90 | }); 91 | }); 92 | 93 | test('map', (t) => { 94 | const result = map([1,2], (value) => value+2); 95 | t.is(result[0], 3); 96 | t.is(result[1], 4); 97 | const original = Array.prototype.map; 98 | delete Array.prototype.map; 99 | const polyfillResult = map([1,2], (value) => value+2); 100 | Array.prototype.map = original; 101 | t.is(polyfillResult[0], 3); 102 | t.is(polyfillResult[1], 4); 103 | }); 104 | 105 | test('find', (t) => { 106 | const result = find([1,2], (value) => value==2); 107 | t.is(result, 2); 108 | const original = Array.prototype.find; 109 | delete Array.prototype.find; 110 | const polyfillResultFound = find([1,2], (value) => value==2); 111 | const polyfillResultNotFound = find([1,2], (value) => value==3); 112 | Array.prototype.find = original; 113 | t.is(polyfillResultFound, 2); 114 | t.is(polyfillResultNotFound, undefined); 115 | }); 116 | 117 | test('assign', (t) => { 118 | const result = assign({}, { a: 1 }, { b: 1 }, { b: 2, c: 3 }); 119 | t.is(result['a'], 1); 120 | t.is(result['b'], 2); 121 | t.is(result['c'], 3); 122 | const original = Object.assign; 123 | delete Object.assign; 124 | const polyfillResult = assign({}, { a: 1 }, { b: 1 }, null, { b: 2, c: 3 }); 125 | Object.assign = original; 126 | t.is(polyfillResult['a'], 1); 127 | t.is(polyfillResult['b'], 2); 128 | t.is(polyfillResult['c'], 3); 129 | }); 130 | 131 | test('getBrowserAlias', (t) => { 132 | t.is(getBrowserAlias('Microsoft Edge'), 'edge'); 133 | t.is(getBrowserAlias('Unexisting Browser'), void 0); 134 | }); 135 | 136 | test('getBrowserTypeByAlias', (t) => { 137 | t.is(getBrowserTypeByAlias('edge'), 'Microsoft Edge'); 138 | t.is(getBrowserTypeByAlias(void 0), ''); 139 | }); 140 | 141 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | // const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 3 | const CompressionPlugin = require('compression-webpack-plugin'); 4 | 5 | module.exports = { 6 | plugins: [ 7 | new CompressionPlugin(), 8 | ], 9 | mode: 'production', // "production" | "development" | "none" 10 | // Chosen mode tells webpack to use its built-in optimizations accordingly. 11 | entry: { 12 | bundled: ['@babel/polyfill', './src/bowser.js'], 13 | es5: './src/bowser.js', 14 | }, // string | object | array 15 | // defaults to ./src 16 | // Here the application starts executing 17 | // and webpack starts bundling 18 | output: { 19 | // options related to how webpack emits results 20 | path: path.resolve(__dirname, './'), // string 21 | // the target directory for all output files 22 | // must be an absolute path (use the Node.js path module) 23 | filename: '[name].js', // string 24 | // the filename template for entry chunks 25 | library: 'bowser', 26 | libraryTarget: 'umd', // universal module definition 27 | // the type of the exported library 28 | globalObject: 'this', 29 | }, 30 | module: { 31 | // configuration regarding modules 32 | rules: [ 33 | // rules for modules (configure loaders, parser options, etc.) 34 | { 35 | test: /\.js$/, 36 | exclude: /(node_modules|bower_components)/, 37 | use: { 38 | loader: 'babel-loader', 39 | }, 40 | }, 41 | ], 42 | }, 43 | }; 44 | --------------------------------------------------------------------------------