├── .gitignore ├── README.md ├── package.json ├── src └── index.tsx ├── test └── index.test.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | .rts2_cache_cjs 6 | .rts2_cache_esm 7 | .rts2_cache_umd 8 | dist 9 | yarn.lock 10 | package-lock.json 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @oieduardorabelo/use-user-agent 2 | 3 | React Hooks to detect browsers user-agent using [`ua-parser-js`](https://github.com/faisalman/ua-parser-js) as main dependency. 4 | 5 | To install it: 6 | 7 | ``` 8 | yarn add @oieduardorabelo/use-user-agent 9 | ``` 10 | 11 | ## Example 12 | 13 | An online demo is available at CodeSandbox: 14 | 15 | - **Live demo:** https://codesandbox.io/s/live-demo-use-user-agent-d7iyg 16 | 17 | If you've any issues, **open an issue with a CodeSandbox link** with your issue 18 | 19 | ## API Explained 20 | 21 | In your app, you can add: 22 | 23 | ```javascript 24 | import { useUserAgent } from '@oieduardorabelo/use-user-agent'; 25 | 26 | function App() { 27 | let details = useUserAgent(uastring) 28 | ... 29 | } 30 | ``` 31 | 32 | ### `details` object is composed of: 33 | 34 | - `details`: It is either `null` or an ua-parser-js object. 35 | - `details.os`: It is a `Object`, with keys `name` and `version` as `string|undefined` 36 | - `details.browser`: It is a `Object`, with keys `name`, `version` and `major` as `string|undefined` 37 | - `details.cpu`: It is a `Object`, with keys `architecture` as `string|undefined` 38 | - `details.device`: It is a `Object`, with keys `vendor`, `model` and `type` as `string|undefined` 39 | - `details.engine`: It is a `Object`, with keys `name` and `version` as `string|undefined` 40 | 41 | For full documentation, refer to [ua-parser-js repository](https://github.com/faisalman/ua-parser-js#example). 42 | 43 | ### `uastring` parameter is composed of: 44 | 45 | - `uastring`: It is a `String`, should be a user-agent string, if none is passed, we default to `window.navigator.userAgent` 46 | 47 | ## Examples 48 | 49 | Using default value from `useUserAgent()`: 50 | 51 | ```javascript 52 | import { useUserAgent } from '@oieduardorabelo/use-user-agent'; 53 | 54 | function App() { 55 | let details = useUserAgent(); // default is `window.navigator.userAgent` 56 | 57 | if (!details) { 58 | return null; 59 | } 60 | 61 | let { os, browser, cpu, device, engine } = details; 62 | 63 | return ( 64 |
65 |

My OS is {os.name}, on version {os.version}

66 |

My Browser is {browser.name}, on version {browser.version} with major {browser.major}

67 |

My CPU architecture is {cpu.architecture}

68 |

My Device is {device.vendor}, with model {device.model} of type {device.type}

69 |

My Engine is {engine.name} with version {engine.version}

70 |
71 | ); 72 | } 73 | ``` 74 | 75 | Passing a custom user-agent string: 76 | 77 | ```javascript 78 | import { useUserAgent } from '@oieduardorabelo/use-user-agent'; 79 | 80 | function App() { 81 | let uastring = "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 1.0.0; en-US) AppleWebKit/534.11 (KHTML, like Gecko) Version/7.1.0.7 Safari/534.11"; 82 | let details = useUserAgent(uastring); 83 | 84 | if (!details) { 85 | return null; 86 | } 87 | 88 | let { os, browser, cpu, device, engine } = details; 89 | 90 | return ( 91 |
92 |

My OS is {os.name}, on version {os.version}

93 |

My Browser is {browser.name}, on version {browser.version} with major {browser.major}

94 |

My CPU architecture is {cpu.architecture}

95 |

My Device is {device.vendor}, with model {device.model} of type {device.type}

96 |

My Engine is {engine.name} with version {engine.version}

97 |
98 | ); 99 | } 100 | ``` 101 | 102 | ### License 103 | 104 | [MIT License](https://oss.ninja/mit/oieduardorabelo/) © [Eduardo Rabelo](https://eduardorabelo.me) 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@oieduardorabelo/use-user-agent", 3 | "description": "React Hooks to detect browsers user-agent using ua-parser-js as main dependency.", 4 | "license": "MIT", 5 | "author": "Eduardo Rabelo (https://github.com/oieduardorabelo)", 6 | "repository": { 7 | "url": "https://github.com/oieduardorabelo/use-user-agent", 8 | "type": "git" 9 | }, 10 | "version": "5.0.1", 11 | "main": "dist/index.js", 12 | "module": "dist/use-user-agent.esm.js", 13 | "files": [ 14 | "dist" 15 | ], 16 | "scripts": { 17 | "build": "tsdx build", 18 | "fmt:p": "prettier-package-json --write", 19 | "start": "tsdx watch", 20 | "test": "tsdx test --env=jsdom" 21 | }, 22 | "typings": "dist/index.d.ts", 23 | "dependencies": { 24 | "ua-parser-js": "1.0.2" 25 | }, 26 | "peerDependencies": { 27 | "react": "16.8 - 18", 28 | "react-dom": "16.8 - 18" 29 | }, 30 | "devDependencies": { 31 | "@types/jest": "28.1.3", 32 | "@types/react": "18.0.14", 33 | "@types/react-dom": "18.0.5", 34 | "@types/ua-parser-js": "0.7.36", 35 | "husky": "8.0.1", 36 | "prettier": "2.7.1", 37 | "prettier-package-json": "2.6.4", 38 | "pretty-quick": "3.1.3", 39 | "react": "18.2.0", 40 | "react-dom": "18.2.0", 41 | "tsdx": "0.14.1", 42 | "tslib": "2.4.0", 43 | "typescript": "4.7.4" 44 | }, 45 | "keywords": [ 46 | "browser", 47 | "hooks", 48 | "parser", 49 | "react", 50 | "react-hooks", 51 | "ua", 52 | "ua-parser-js", 53 | "user-agent" 54 | ], 55 | "publishConfig": { 56 | "access": "public" 57 | }, 58 | "husky": { 59 | "hooks": { 60 | "pre-commit": "pretty-quick --staged" 61 | } 62 | }, 63 | "prettier": { 64 | "printWidth": 80, 65 | "semi": true, 66 | "singleQuote": true, 67 | "trailingComma": "es5" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as UAParser from 'ua-parser-js'; 3 | 4 | type IUseUserAgentReturn = Omit; 5 | 6 | function useUserAgent(uastring = window.navigator.userAgent) { 7 | let [state, setState] = React.useState(null); 8 | 9 | React.useEffect(() => { 10 | let didRun = true; 11 | 12 | try { 13 | const uaParser = new UAParser.UAParser(); 14 | uaParser.setUA(uastring); 15 | const payload = { 16 | os: uaParser.getOS(), 17 | browser: uaParser.getBrowser(), 18 | cpu: uaParser.getCPU(), 19 | device: uaParser.getDevice(), 20 | engine: uaParser.getEngine(), 21 | }; 22 | if (didRun) { 23 | setState(payload); 24 | } 25 | } catch (err) { 26 | if (didRun) { 27 | setState(null); 28 | } 29 | } 30 | 31 | return () => { 32 | didRun = false; 33 | }; 34 | }, [uastring]); 35 | 36 | return state; 37 | } 38 | 39 | export { useUserAgent }; 40 | -------------------------------------------------------------------------------- /test/index.test.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | globalThis.IS_REACT_ACT_ENVIRONMENT = true; 3 | 4 | import * as React from 'react'; 5 | import * as ReactDOMClient from 'react-dom/client'; 6 | import * as ReactDOMTestUtils from 'react-dom/test-utils'; 7 | 8 | import { useUserAgent } from '../src'; 9 | 10 | const TestHook = ({ callback }: { callback: any }) => { 11 | callback(); 12 | return null; 13 | }; 14 | 15 | let root: any = null; 16 | let container: any = null; 17 | beforeEach(() => { 18 | container = document.createElement('div'); 19 | document.body.appendChild(container); 20 | }); 21 | 22 | afterEach(() => { 23 | ReactDOMTestUtils.act(() => { 24 | root.unmount(); 25 | }); 26 | container = null; 27 | }); 28 | 29 | test('fulfill `details` object correctly', async () => { 30 | let actual: any = null; 31 | ReactDOMTestUtils.act(() => { 32 | root = ReactDOMClient.createRoot(container); 33 | }); 34 | ReactDOMTestUtils.act(() => { 35 | root.render( 36 | { 38 | actual = useUserAgent(window.navigator.userAgent); 39 | }} 40 | /> 41 | ); 42 | }); 43 | 44 | let keys = 'os,browser,cpu,device,engine'; 45 | expect(Object.keys(actual).join(',')).toBe(keys); 46 | }); 47 | 48 | test('uses default value as `window.navigator.userAgent`', async () => { 49 | let actual: any = null; 50 | 51 | ReactDOMTestUtils.act(() => { 52 | root = ReactDOMClient.createRoot(container); 53 | root.render( 54 | { 56 | actual = useUserAgent(); 57 | }} 58 | /> 59 | ); 60 | }); 61 | 62 | expect(actual.browser.name).toBe('WebKit'); 63 | }); 64 | 65 | test('custom user-agent string is parsed correctly', async () => { 66 | let actual: any = null; 67 | 68 | ReactDOMTestUtils.act(() => { 69 | root = ReactDOMClient.createRoot(container); 70 | root.render( 71 | { 73 | actual = useUserAgent( 74 | 'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 1.0.0; en-US) AppleWebKit/534.11 (KHTML, like Gecko) Version/7.1.0.7 Safari/534.11' 75 | ); 76 | }} 77 | /> 78 | ); 79 | }); 80 | 81 | expect(actual.browser.name).toBe('Safari'); 82 | }); 83 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "strictNullChecks": true, 13 | "strictFunctionTypes": true, 14 | "strictPropertyInitialization": true, 15 | "noImplicitThis": true, 16 | "alwaysStrict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noImplicitReturns": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "moduleResolution": "node", 22 | "baseUrl": "./", 23 | "paths": { 24 | "*": ["src/*", "node_modules/*"] 25 | }, 26 | "jsx": "react", 27 | "esModuleInterop": true 28 | } 29 | } 30 | --------------------------------------------------------------------------------