├── .babelrc ├── .eslintrc ├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── package.json ├── src ├── hoc.tsx ├── react-ua.spec.tsx └── react-ua.tsx └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | "@babel/preset-typescript" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:react/recommended"], 3 | "plugins": ["react", "jest", "prettier"], 4 | "parser": "babel-eslint", 5 | "env": { 6 | "browser": true, 7 | "jest": true 8 | }, 9 | "rules": { 10 | "prettier/prettier": "error", 11 | "no-useless-catch": "off", 12 | "react/prop-types": "off" 13 | }, 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "jsx": true, 18 | "modules": true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 14 18 | registry-url: https://registry.npmjs.org/ 19 | # - run: npm ci 20 | - run: yarn 21 | - run: npm t 22 | - run: npm run build 23 | - run: npm whoami && npm publish 24 | env: 25 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | yarn-error.log 4 | coverage 5 | .idea -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Antony Budianto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-ua 2 | 3 | [![npm version](https://badge.fury.io/js/react-ua.svg)](https://badge.fury.io/js/react-ua) 4 | [![Build Status](https://travis-ci.org/antonybudianto/react-ua.svg?branch=master)](https://travis-ci.org/antonybudianto/react-ua) 5 | 6 |

7 | 8 |

9 | 10 | React User Agent Component and Provider, SSR-ready, using new React Context API 11 | 12 | ## Requirement 13 | 14 | - React 16.8.0 15 | 16 | ## Features 17 | 18 | - Wrapper for [ua-parser-js](https://github.com/faisalman/ua-parser-js) 19 | - Using new React [Hooks API](https://reactjs.org/docs/hooks-faq.html) 20 | - Using new React [Context API](https://reactjs.org/docs/context.html) 21 | - SSR-ready 22 | - Unit-tested 23 | 24 | > Try it [live at StackBlitz](https://stackblitz.com/edit/demo-react-ua) 25 | 26 | ```js 27 | import React from 'react'; 28 | import { UserAgentProvider, useUserAgent } from 'react-ua'; 29 | 30 | const Comp = () => { 31 | const ua = useUserAgent(); 32 | return
OS: {ua.os.name}
; 33 | }; 34 | 35 | const App = () => ( 36 | 37 | 38 | 39 | ); 40 | 41 | ReactDOM.render(, document.getElementById('#root')); 42 | 43 | // SSR 44 | const el = ( 45 | 46 | 47 | 48 | ); 49 | 50 | ReactDOMServer.renderToString(el); 51 | ``` 52 | 53 | ## HOC (deprecated) 54 | 55 | ```tsx 56 | import { withUserAgent } from 'react-ua/hoc'; 57 | 58 | const CompWithHoc = withUserAgent(({ ua }) =>
OS: {ua.os.name}
); 59 | 60 | const App = () => ( 61 | 62 | 63 | 64 | ); 65 | ``` 66 | 67 | ## License 68 | 69 | MIT 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ua", 3 | "version": "4.0.0", 4 | "description": "React User Agent component and provider with new React Context API", 5 | "main": "dist/react-ua.js", 6 | "source": "src/react-ua.js", 7 | "files": [ 8 | "dist" 9 | ], 10 | "scripts": { 11 | "build": "babel --extensions=.ts,.tsx --config-file=./.babelrc src/. -d dist/. --ignore=**/*.spec.tsx", 12 | "dev": "babel --watch --extensions=.ts,.tsx --config-file=./.babelrc src/. -d dist/. --ignore=**/*.spec.tsx", 13 | "old-prerelease": "npm run build", 14 | "old-release": "npm publish --access public", 15 | "lint": "eslint src", 16 | "test": "jest", 17 | "test:ci": "jest --coverage", 18 | "prt": "prettier src/**/*.js --write" 19 | }, 20 | "jest": { 21 | "testEnvironment": "jsdom" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/antonybudianto/react-ua.git" 26 | }, 27 | "keywords": [ 28 | "react", 29 | "user agent", 30 | "ua-parser-js" 31 | ], 32 | "author": "Antony Budianto ", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/antonybudianto/react-ua/issues" 36 | }, 37 | "homepage": "https://github.com/antonybudianto/react-ua#readme", 38 | "peerDependencies": { 39 | "react": "^16.9.0 || ^17.0.0 || ^18.0.0" 40 | }, 41 | "devDependencies": { 42 | "@babel/cli": "^7.20.7", 43 | "@babel/core": "^7.20.7", 44 | "@babel/preset-env": "^7.20.2", 45 | "@babel/preset-react": "^7.18.6", 46 | "@babel/preset-typescript": "^7.18.6", 47 | "@types/jest": "^29.2.5", 48 | "@testing-library/react": "13.0.0", 49 | "babel-eslint": "10.0.3", 50 | "babel-jest": "24.9.0", 51 | "eslint": "6.3.0", 52 | "eslint-plugin-jest": "22.17.0", 53 | "eslint-plugin-prettier": "^3.1.0", 54 | "eslint-plugin-react": "7.14.3", 55 | "jest": "24.9.0", 56 | "prettier": "2.6.2", 57 | "react": "18.2.0", 58 | "react-dom": "18.2.0" 59 | }, 60 | "dependencies": { 61 | "ua-parser-js": "^0.7.24" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/hoc.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UserAgent } from './react-ua'; 3 | 4 | /** 5 | * @legacy Please migrate to use hooks 6 | * Legacy HOC 7 | */ 8 | export const withUserAgent = (Comp) => 9 | class UserAgentHoc extends React.PureComponent { 10 | // This method works with Next.js 11 | static async getInitialProps(ctx) { 12 | let initialProps = {}; 13 | 14 | if (Comp.getInitialProps) { 15 | initialProps = await Comp.getInitialProps(ctx); 16 | } 17 | 18 | return initialProps; 19 | } 20 | 21 | render() { 22 | return {(ua) => }; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/react-ua.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, cleanup } from '@testing-library/react'; 3 | import { UserAgentProvider, UserAgent, useUserAgent } from './react-ua'; 4 | import { withUserAgent } from './hoc'; 5 | import PropTypes from 'prop-types'; 6 | 7 | const mockUa = 8 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'; 9 | 10 | Object.defineProperty( 11 | window.navigator, 12 | 'userAgent', 13 | (function (_value) { 14 | return { 15 | get: function _get() { 16 | return _value; 17 | }, 18 | set: function _set(v) { 19 | _value = v; 20 | }, 21 | }; 22 | })(mockUa) 23 | ); 24 | 25 | afterEach(cleanup); 26 | 27 | describe('react-ua', () => { 28 | it('should render with default provider', () => { 29 | const el = ( 30 | 31 | {(v) =>
Browser: {v.browser.name}
}
32 |
33 | ); 34 | const { getByText } = render(el); 35 | getByText('Browser: Chrome'); 36 | }); 37 | 38 | it('should render with custom provider value', () => { 39 | const customUa = 40 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/13.2b11866 Mobile/16A366 Safari/605.1.15'; 41 | const el = ( 42 | 43 | {(v) =>
OS: {v.os.name}
}
44 |
45 | ); 46 | const { getByText } = render(el); 47 | getByText('OS: iOS'); 48 | }); 49 | 50 | it('should render HOC component with default provider', () => { 51 | const Comp = ({ ua }) =>
Browser: {ua.browser.name}
; 52 | Comp.propTypes = { 53 | ua: PropTypes.object, 54 | }; 55 | 56 | const CompWithHoc = withUserAgent(Comp); 57 | const el = ( 58 | 59 | 60 | 61 | ); 62 | const { getByText } = render(el); 63 | getByText('Browser: Chrome'); 64 | }); 65 | 66 | it('should render HOC component with custom provider value', () => { 67 | const customUa = 68 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/13.2b11866 Mobile/16A366 Safari/605.1.15'; 69 | 70 | const Comp = ({ ua }) =>
OS: {ua.os.name}
; 71 | Comp.propTypes = { 72 | ua: PropTypes.object, 73 | }; 74 | 75 | const CompWithHoc = withUserAgent(Comp); 76 | const el = ( 77 | 78 | 79 | 80 | ); 81 | const { getByText } = render(el); 82 | getByText('OS: iOS'); 83 | }); 84 | 85 | it('should render hook useUserAgent', () => { 86 | const customUa = 87 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/13.2b11866 Mobile/16A366 Safari/605.1.15'; 88 | 89 | const CompWithHooks = () => { 90 | const ua = useUserAgent(); 91 | return
OS: {ua.os.name}
; 92 | }; 93 | const el = ( 94 | 95 | 96 | 97 | ); 98 | const { getByText } = render(el); 99 | getByText('OS: iOS'); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /src/react-ua.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import type { FC, ReactNode } from 'react'; 3 | import UAParser from 'ua-parser-js'; 4 | 5 | interface UserAgentProviderProps { 6 | value?: string; 7 | children: ReactNode; 8 | } 9 | 10 | const UserAgentContext = React.createContext({}); 11 | 12 | export const UserAgent = UserAgentContext.Consumer; 13 | 14 | export const UserAgentProvider: FC = ({ 15 | value, 16 | children, 17 | }) => { 18 | const initUA = useMemo(() => { 19 | return new UAParser(value).getResult(); 20 | }, [value]); 21 | 22 | return ( 23 | 24 | {children} 25 | 26 | ); 27 | }; 28 | 29 | export const useUserAgent = () => React.useContext(UserAgentContext); 30 | --------------------------------------------------------------------------------