From d464383dfb3969cd6f4bf0c667a5101611e87946 Mon Sep 17 00:00:00 2001 From: "Daybrush (Younkue Choi)" Date: Mon, 31 Aug 2020 16:05:48 +0900 Subject: [PATCH] feat: support client hints #33 (#34) * feat: support client hints #33 * chore: fix editorconfig * fix: travis node version * chore: add demo type in commit.template * demo: add majorVersion * skip: apply review --- .babelrc | 16 - .editorconfig | 10 +- .eslintignore | 4 +- .eslintrc | 37 +- .gitignore | 4 +- .travis.yml | 2 +- README.md | 188 +- config/banner.js | 17 - config/commit.template | 1 + config/release.js | 80 - config/uglify.js | 17 - config/validate-commit-msg.js | 7 +- config/webpack.config.development.js | 14 - config/webpack.config.production.js | 41 - demo/_data/egjs.yml | 4 +- demo/assets/html/demo.html | 23 +- demo/assets/js/demo.js | 69 +- demo/common/css/page.css | 13 +- demo/demo.md | 13 +- demo/started.md | 75 +- global.d.ts | 7 + index.d.ts | 27 - jest.config.js | 18 + jsdoc.json | 2 +- karma.conf.js | 61 - mocha.opts | 1 - package-lock.json | 24648 +++++++------------------ package.json | 145 +- rollup.config.js | 76 +- src/Agent.js | 60 - src/Parser.js | 181 - src/agent.ts | 67 + src/browser.js | 8 - src/index.js | 8 - src/index.umd.ts | 7 + src/parseRules.js | 106 - src/presets.ts | 148 + src/types.ts | 80 + src/userAgent.ts | 54 + src/userAgentData.ts | 65 + src/utils.ts | 91 + test/manual/index.html | 29 +- test/manual/js/test.js | 5 - test/unit/agent.spec.js | 107 - test/unit/agent_list.js | 1000 - test/unit/userAgent.spec.ts | 81 + test/unit/userAgentConsts.ts | 1198 ++ test/unit/userAgentData.spec.ts | 69 + test/unit/userAgentDataConsts.ts | 207 + tsconfig.declaration.json | 13 + tsconfig.json | 22 + webpack.config.js | 45 - 52 files changed, 9021 insertions(+), 20250 deletions(-) delete mode 100644 .babelrc delete mode 100644 config/banner.js delete mode 100644 config/release.js delete mode 100644 config/uglify.js delete mode 100644 config/webpack.config.development.js delete mode 100644 config/webpack.config.production.js create mode 100644 global.d.ts delete mode 100644 index.d.ts create mode 100644 jest.config.js delete mode 100644 karma.conf.js delete mode 100644 mocha.opts delete mode 100644 src/Agent.js delete mode 100644 src/Parser.js create mode 100644 src/agent.ts delete mode 100644 src/browser.js delete mode 100755 src/index.js create mode 100644 src/index.umd.ts delete mode 100644 src/parseRules.js create mode 100644 src/presets.ts create mode 100644 src/types.ts create mode 100644 src/userAgent.ts create mode 100644 src/userAgentData.ts create mode 100644 src/utils.ts delete mode 100644 test/manual/js/test.js delete mode 100755 test/unit/agent.spec.js delete mode 100644 test/unit/agent_list.js create mode 100644 test/unit/userAgent.spec.ts create mode 100644 test/unit/userAgentConsts.ts create mode 100644 test/unit/userAgentData.spec.ts create mode 100644 test/unit/userAgentDataConsts.ts create mode 100644 tsconfig.declaration.json create mode 100644 tsconfig.json delete mode 100644 webpack.config.js diff --git a/.babelrc b/.babelrc deleted file mode 100644 index bda728b..0000000 --- a/.babelrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "presets": [ - [ - "es2015", - { - "loose": true - } - ] - ], - "plugins": [ - "add-module-exports", - "transform-object-assign", - "transform-es3-property-literals", - "transform-es3-member-expression-literals" - ] -} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index 76ef3d6..5a17c61 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,14 +1,18 @@ # http://editorconfig.org root = true -[*.js] +[{*.js,*.ts}] charset = utf-8 end_of_line = lf -indent_style = tab +indent_style = space +indent_size = 4 insert_final_newline = true max_line_length = 80 trim_trailing_whitespace = true [{package.json,.travis.yml}] indent_style = space -indent_size = 2 +indent_size = 4 + +[*] +insert_final_newline = true diff --git a/.eslintignore b/.eslintignore index 88277f6..5b0ec4f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,3 @@ -src/Array.js \ No newline at end of file +node_modules +dist +*.js diff --git a/.eslintrc b/.eslintrc index 4c2be56..7bacf2d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,13 +1,26 @@ { - // Naver https://github.com/naver/eslint-config-naver/blob/master/STYLE_GUIDE.md - "extends": "naver", - "rules": { - "comma-dangle": ["error", { - "arrays": "always-multiline", - "objects": "always-multiline", - "imports": "always-multiline", - "exports": "always-multiline", - "functions": "never" - }] - } -} \ No newline at end of file + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-empty-interface": "off", + "no-empty-interface": "off", + "comma-dangle": [ + "error", + "always-multiline" + ], + "semi": [ + "error", + "always" + ] + } +} diff --git a/.gitignore b/.gitignore index 4cd00ab..47c1366 100644 --- a/.gitignore +++ b/.gitignore @@ -163,7 +163,7 @@ demo/dist demo/doc .sass-cache/ .jekyll-metadata - +.jekyll-cache ### SublimeText ### # cache files for sublime text @@ -209,3 +209,5 @@ doc/ demo/_data/version.yml demo/release dist/ +declaration/ + diff --git a/.travis.yml b/.travis.yml index 127e927..b2e88b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "6" + - "12" dist: trusty sudo: required install: diff --git a/README.md b/README.md index 1b6944e..f214890 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -# egjs-agent [![npm version](https://badge.fury.io/js/%40egjs%2Fagent.svg)](https://badge.fury.io/js/%40egjs%2Fagent) [![Build Status](https://travis-ci.org/naver/egjs-agent.svg?branch=master)](https://travis-ci.org/naver/egjs-agent) [![Coverage Status](https://coveralls.io/repos/github/naver/egjs-agent/badge.svg?branch=master)](https://coveralls.io/github/naver/egjs-agent?branch=master) -Extracts browser and operating system information from the user agent string. + +# egjs-agent +[![npm version](https://img.shields.io/npm/v/@egjs/agent.svg?style=flat-square&color=007acc&label=version)](https://www.npmjs.com/package/@egjs/agent) [![Build Status](https://img.shields.io/travis/naver/egjs-agent.svg?style=flat-square&label=build)](https://travis-ci.org/naver/egjs-agent) [![Coverage Status](https://img.shields.io/coveralls/github/naver/egjs-agent.svg?style=flat-square&label=coverage)](https://coveralls.io/github/naver/egjs-agent?branch=master) +![Support TypeScript](https://img.shields.io/static/v1.svg?label=&message=TypeScript&color=294E80&style=flat-square&logo=typescript) + +Extracts browser and operating system information from the user agent string or user agent object(userAgentData). ## Documents - [Get Started and Demos](https://naver.github.io/egjs-agent/) @@ -10,27 +14,171 @@ Extracts browser and operating system information from the user agent string. Download dist files from repo directly or install it via npm. -### For development (Uncompressed) +### Installation with npm -You can download the uncompressed files for development +The following command shows how to install egjs-agent using npm. -- Latest : https://naver.github.io/egjs-agent/release/latest/dist/agent.js -- Specific version : https://naver.github.io/egjs-agent/release/[VERSION]/dist/agent.js +```bash +$ npm install @egjs/agent +``` -### For production (Compressed) +### For development -You can download the compressed files for production +You can download the files for development -- Latest : https://naver.github.io/egjs-agent/release/latest/dist/agent.min.js -- Specific version : https://naver.github.io/egjs-agent/release/[VERSION]/dist/agent.min.js +* Latest : https://naver.github.io/egjs-agent/release/latest/dist/agent.js +* Latest(min): https://naver.github.io/egjs-agent/release/latest/dist/agent.min.js +* Specific version : https://naver.github.io/egjs-agent/release/[VERSION]/dist/agent.js +* Specific version(min): https://naver.github.io/egjs-agent/release/[VERSION]/dist/agent.min.js -### Installation with npm +## Prepare for Client Hints! -The following command shows how to install egjs-agent using npm. +Chrome was planned to freeze userAgent to improve user privacy, and it is being applied as an experimental feature in 84+. + +Not only Chrome, but other browsers will come someday. + +> It is still an experimental feature until Chrome(Chromium) 86. +> +> If you want to test, enable the flag below. +> +> **chrome://flags/#enable-experimental-web-platform-features** +> +> **chrome://flags/#freeze-user-agent** + + +### In Chrome (Chrome 87, Mac, Freeze User-Agent) +* Before +``` +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4243.0 Safari/537.36 +``` +* After +``` +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.0.0 Safari/537.36 +``` +### In the future +1. the following attribute values will not appear correctly. + * navigator.userAgent + * navigator.appVersion + * navigator.platform + * navigator.productSub + * navigator.vendor +2. You should use `navigator.userAgentData` instead of `navigator.userAgent`. + * Browser version only shows major. + * OS name, OS version, Browser full version should be fetched asynchronously. + * The only OS name that you can check synchronously are iOS and Android. + +### Possible (You can know exactly) +* Check browser name, major version +* Check mobile +* Check iOS, Android +* Check Webkit, Chromium +* Check old OS, OS Version (Chrome 84 support range or less) + * **< Android 5.0** + * **< Mac OS X 10.10** + * **< Windows 7** + * **< iOS Unknown** + +```js +import getAgent from "@egjs/agent"; + +const agent = getAgent(); + +// Check iOS +agent.os.name === "ios" +agent.os.majorVersion === 9 +// Check Android +agent.os.name === "android" +agent.os.majorVersion === 4 +parseFloat(agent.os.version) <= 4.4 +// Check browser name +agent.browser.name === "safari" +// Check webkit +agent.browser.webkit +// Check chromium +agent.browser.chromium +``` +### Not Possible (If synchronous) +* Check OS (Mac with browsers except Safari, Windows, Linux), OS version +* Check Browser full version. + +```js +import getAgent from "@egjs/agent"; +const agent = getAgent(); + +// If the full version is 10.5, it is displayed as 10. +agent.browser.version +// "unknown" +agent.os.name +// -1 +agent.os.majorVersion +``` + +### Possible (If asynchronous) +* You can get accurate agent information. +* Check OS(Mac, Windows, Linux), OS version +* Check Browser full version. +```js +import { getAccurateAgent } from "@egjs/agent"; + +// Use Promise +getAccurateAgent().then(agent => { + // Check OS, OS version + agent.os.name + agent.os.version + + // Check Browser full verion + agent.browser.version +}); + +// Use Callback +getAccurateAgent(agent => {}); +``` + +### If you dare to use synchrous, you have to choose. +* You cannot get the OS name and version other than iOS, Android. +* You can only get the major version of the browser. However, unless there is a serious bug, you will mainly check the major version. +* Instead, infer to browser, Webkit, or Chromium. + +#### Examples +* Check Mac Safari, iOS all browsers + +Mac Safari, iOS all browsers are webkit. +```js +import getAgent from "@egjs/agent"; + +const agent = getAgent(); + +if ((agent.os.name === "mac" || agent.os.name === "ios") && agent.browser.webkit) { + console.log("is mac, ios webkit"); +} +``` +* Check Android version >= 5 +```js +import getAgent from "@egjs/agent"; + +const agent = getAgent(); + +if ( + agent.os.name === "android" + && (agent.os.majorVersion >= 5 || agent.os.majorVersion === -1)) { + console.log("is Android >= 5"); +} +``` +* Check iOS version >= 10 +```js +import getAgent from "@egjs/agent"; + +const agent = getAgent(); + + +if ( + agent.os.name === "ios" + && (agent.os.majorVersion >= 10 || agent.os.majorVersion === -1) +) { + console.log("is iOS"); +} -```bash -$ npm install @egjs/agent ``` @@ -41,6 +189,12 @@ The following are the supported browsers. |---|---|---|---|---|---| |7+|latest|latest|latest|3+|2.1+| +### Client Hints Support + +Chromium (Chrome, Edge, Whale)|Gecko (Firefox)|Webkit (Safari)| +|---|---|---| +|84+ (Experimental)|Unknown|Unknown| + @@ -72,7 +226,7 @@ $ npm install Use npm script to build eg.agent ```bash -# Run webpack-dev-server for development +# Run rollup $ npm start # Build @@ -89,7 +243,7 @@ Two folders will be created after complete build is completed. ### Linting -To keep the same code style, we adopted [ESLint](http://eslint.org/) to maintain our code quality. The [rules](https://github.com/naver/eslint-config-naver/tree/master/rules) are modified version based on [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript). +To keep the same code style, we adopted [ESLint](http://eslint.org/) to maintain our code quality. Setup your editor for check or run below command for linting. ```bash @@ -103,7 +257,7 @@ Once you created a branch and done with development, you must perform a test run ```bash $ npm run test ``` -Running a `npm run test` command will start [Mocha](https://mochajs.org/) tests via [Karma-runner](https://karma-runner.github.io/). +Running a `npm run test` command will start [Jest](https://jestjs.io/). ## Bug Report diff --git a/config/banner.js b/config/banner.js deleted file mode 100644 index 49010c4..0000000 --- a/config/banner.js +++ /dev/null @@ -1,17 +0,0 @@ -var pkg = require("../package.json"); - -module.exports = { - common: [ - "Copyright (c) 2017 " + pkg.author.name, - pkg.name + " project is licensed under the " + pkg.license + " license", - "", - pkg.name + " JavaScript library", - pkg.homepage, - "", - "@version " + pkg.version - ].join("\r\n"), - pkgd: [ - "All-in-one packaged file for ease use of '" + pkg.name + "' with below dependencies.", - "NOTE: This is not an official distribution file and is only for user convenience.", - ""].join("\r\n") -}; diff --git a/config/commit.template b/config/commit.template index 14e8f08..f7c6868 100644 --- a/config/commit.template +++ b/config/commit.template @@ -19,6 +19,7 @@ Ref #30 - style: Changes that do not affect the meaning of the code. Such as white-space, formatting, missing semi-colons, etc... It also possible to change JSHint, JSCS rules of the code. - refactor: A code change that neither fixes a bug nor adds a feature - test: Adding missing tests. Changing tests. + - demo: Adding demo. Changing demos. - chore: Changes to the build process or tools and libraries such as documentation generation ============ See More ============= diff --git a/config/release.js b/config/release.js deleted file mode 100644 index e20c532..0000000 --- a/config/release.js +++ /dev/null @@ -1,80 +0,0 @@ -const fs = require("fs-extra"); -const exec = require("sync-exec"); -const pkg = require("../package.json"); -const chalk = require("chalk"); -function shell(cmd, ignore = false) { - let result = exec(cmd); - if (!result.stderr) { - !ignore && console.log(result.stdout); - console.log(chalk.gray(`# ${cmd}`)); - } else { - if (!ignore) { - console.error(result.stderr); - throw new Error(result.stderr); - } - } - return result.stdout; -} -function restore(newVersion, version) { - pkg.version = version; - console.log(chalk.redBright("** Restore Tasks ** ")); - console.log(chalk.red("- Restore package.json version")); - fs.writeJsonSync(__dirname + "/../package.json", pkg, {spaces: "\t"}); - if (shell("git rev-parse HEAD") !== gitid) { - console.log(chalk.red("- Restore git log")); - shell("git reset HEAD^1"); - } - console.log(chalk.red("- Remove remote version")); - shell(`git push upstream : ${newVersion}`, true); -} - -const version = pkg.version; -const isRcBranch = /\d+\.\d+\.\d+-rc/.test(version); -const hasUpstream = (function() { - const result = shell("git remote show", true); - return !!~result.split("\n").indexOf("upstream"); -})(); -const hasDistfolder = (function() { - const result = fs.readFileSync(__dirname + "/../.gitignore",{encoding: "utf8"}); - return result.split("\n").indexOf("dist/") === -1 || result.split("\n").indexOf("dist") === -1; -})(); - -if (isRcBranch && hasUpstream && hasDistfolder) { - const gitid = shell("git rev-parse HEAD"); - const newVersion = version.substring(0, version.indexOf("-rc")); - try { - pkg.version = newVersion; - - console.log(chalk.green(`1. Change package.json version to '${newVersion}'`)); - fs.writeJsonSync(__dirname + "/../package.json", pkg, {spaces: "\t"}); - - console.log(chalk.green("2. Build")); - shell("npm run build"); - - console.log(chalk.green("3. Commit")); - shell(`git add --all`); - shell(`git commit -m "chore(release): Release ${newVersion}"`); - - console.log(chalk.green(`4. Create local tag '${newVersion}'`)); - shell(`git tag -d ${newVersion}`, true); - shell(`git tag ${newVersion}`); - - console.log(chalk.green(`5. Push tag '${newVersion}'`)); - shell(`git push upstream :${newVersion}`, true); - shell(`git push upstream ${newVersion}`); - - console.log(chalk.green(`6. Deploy demo: '${newVersion}'`)); - shell(`npm run demo:deploy`); - - console.log(chalk.green(`7. Register npm with ${newVersion}. The registration procedure is as follows.`)); - console.log(chalk.greenBright(` # npm login`)); - console.log(chalk.greenBright(` # npm publish --access public`) + "\n"); - console.log(chalk.redBright(`* Use 'npm unpublish ${pkg.name}#${newVersion}' if you want to erase deployed version.`)); - } catch(e) { - restore(newVersion, version); - } -} else { - !isRcBranch && console.log("The version of 'package.json' must be named by X.X.X-rc to deploy. " + chalk.redBright("Change package.version")); - !hasUpstream && console.log("You can deploy when you have an 'upstream' remote repository. " + chalk.redBright("Add 'upstream' remote repository")); - !hasDistfolder && console.log("You can deploy when you include 'dist' folder. " + chalk.redBright("Remove 'dist' folder in .gitignore")); -} diff --git a/config/uglify.js b/config/uglify.js deleted file mode 100644 index 78d65e5..0000000 --- a/config/uglify.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - include: /\.min\.js$/, - beautify: false, - mangle: { - screw_ie8: false, - keep_fnames: true - }, - compress: { - screw_ie8: false, - warnings: false - }, - output: { - screw_ie8: false - }, - comments: false, - sourceMap: true -}; diff --git a/config/validate-commit-msg.js b/config/validate-commit-msg.js index 3a6d5b4..11fa110 100644 --- a/config/validate-commit-msg.js +++ b/config/validate-commit-msg.js @@ -25,7 +25,8 @@ var TYPES = { style: true, refactor: true, test: true, - chore: true + chore: true, + demo: true, }; var error = function() { @@ -38,7 +39,7 @@ var error = function() { var validateMessage = function(message, fullMessage) { var isValid = true; - + if (IGNORED.test(message)) { console.log('Commit message validation ignored.'); return true; @@ -114,4 +115,4 @@ if (process.argv.join('').indexOf('jasmine-node') === -1) { process.exit(0); } }); -} \ No newline at end of file +} diff --git a/config/webpack.config.development.js b/config/webpack.config.development.js deleted file mode 100644 index df30742..0000000 --- a/config/webpack.config.development.js +++ /dev/null @@ -1,14 +0,0 @@ -var merge = require("webpack-merge"); -var WriteFilePlugin = require("write-file-webpack-plugin"); - -var config = { - devtool: "inline-source-map", - devServer: { - publicPath: "/dist/" - }, - plugins: [new WriteFilePlugin()] -}; - -module.exports = function (common) { - return merge(common, config); -}; diff --git a/config/webpack.config.production.js b/config/webpack.config.production.js deleted file mode 100644 index 5683706..0000000 --- a/config/webpack.config.production.js +++ /dev/null @@ -1,41 +0,0 @@ -var merge = require("webpack-merge"); -var webpack = require("webpack"); -var path = require("path"); -var CleanWebpackPlugin = require("clean-webpack-plugin"); -var UglifyJSPlugin = require("uglifyjs-webpack-plugin"); -var uglifyConfig = require("./uglify"); -var banner = require("./banner"); - - -var config = { - entry: { - "agent": "./src/index.js", - "agent.min": "./src/index.js" - }, - module: { - rules: [{ - test: /(\.js)$/, - loader: "eslint-loader", - include: path.resolve(process.cwd(), "src"), - exclude: /(node_modules)/, - enforce: "pre" - }] - }, - plugins: [ - new CleanWebpackPlugin([path.resolve(__dirname, "../dist")], { - root: path.resolve(__dirname, "../"), - verbose: true, - dry: false - }), - new UglifyJSPlugin(uglifyConfig), - new webpack.BannerPlugin(banner.common) - ] -}; - -module.exports = function (common) { - return merge.strategy({ - entry: "replace", - module: "append", - plugins: "append" - })(common, config); -}; diff --git a/demo/_data/egjs.yml b/demo/_data/egjs.yml index a37421b..aa1ea59 100644 --- a/demo/_data/egjs.yml +++ b/demo/_data/egjs.yml @@ -1,9 +1,9 @@ component: Agent home: //naver.github.io/egjs -desc: Extracts browser and operating system information from the user agent string. +desc: Extracts browser and operating system information from the user agent string or user agent object(userAgentData). dist: - release/latest/dist/agent.min.js -hashtag: "#user-agent-parser #browser-detection" +hashtag: "#user-agent-parser #browser-detection #client-hints" GA: UA-70842526-15 github: user: naver diff --git a/demo/assets/html/demo.html b/demo/assets/html/demo.html index 162ee73..af08735 100644 --- a/demo/assets/html/demo.html +++ b/demo/assets/html/demo.html @@ -4,7 +4,8 @@

Target User Agent

(Copy and paste the user agent string to parse)