├── .gitignore ├── README.md ├── __tests__ ├── UserAgent.test.js └── VersionRange.test.js ├── index.d.ts ├── index.js ├── jest ├── createCacheKeyFunction.js ├── environment.js └── preprocessor.js ├── lib ├── VersionRange.js ├── mapObject.js ├── memoizeStringOnly.js └── userAgentData.js ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | coverage 3 | node_modules 4 | npm-debug.log 5 | .nyc_output 6 | dist 7 | .idea 8 | .package-lock.json 9 | .cache 10 | yarn.lock 11 | public 12 | storybook-static 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UserAgent 2 | This package is same as useragent that Facebook's team uses in their products. 3 | 4 | ## Install 5 | ```bash 6 | yarn add @reacttips/useragent 7 | 8 | or 9 | 10 | npm install @reacttips/useragent 11 | ``` 12 | ## How to use 13 | Let's assume that we have user agent data as following: 14 | ```javascript 15 | const userAgentData = { 16 | browserArchitecture: '32', 17 | browserFullVersion: '7.0', 18 | browserName: 'Mobile Safari', 19 | deviceName: 'iPhone', 20 | engineName: 'WebKit', 21 | engineVersion: '537.51.2', 22 | platformArchitecture: '64', 23 | platformFullVersion: '7.1.2', 24 | platformName: 'iOS', 25 | } 26 | ``` 27 | 28 | There are some methods that you can use: 29 | 30 | ### isBrowser(query: string) 31 | 32 | ```javascript 33 | // Use to detect by browser name 34 | UserAgent.isBrowser('Mobile Safari') // true 35 | UserAgent.isBrowser('Chrome') // false 36 | UserAgent.isBrowser('Mobile Safari *') //true 37 | 38 | // detect scope to specific versions 39 | UserAgent.isBrowser('Mobile Safari *') // true 40 | UserAgent.isBrowser('Mobile Safari 7') // true 41 | UserAgent.isBrowser('Mobile Safari 7.0 - 7.1') // true 42 | UserAgent.isBrowser('Chrome *') // false 43 | UserAgent.isBrowser('Mobile Safari 6.0.1') // false 44 | ``` 45 | 46 | ### isBrowserArchitecture(query: string) 47 | ```javascript 48 | // Use to detect by browser architecture 49 | UserAgent.isBrowserArchitecture('32') // true 50 | UserAgent.isBrowserArchitecture('64') // false 51 | ``` 52 | 53 | ### isPlatformArchitecture(query: string) 54 | ```javascript 55 | // Use to detect by browser architecture 56 | UserAgent.isPlatformArchitecture('32') // false 57 | UserAgent.isPlatformArchitecture('64') // true 58 | ``` 59 | 60 | ### isDevice(query: string) 61 | ```javascript 62 | // Use to detect by device name 63 | UserAgent.isDevice('iPhone') // true 64 | UserAgent.isDevice('iPad') // false 65 | 66 | // User to check does not expose version information 67 | UserAgent.isDevice('iPhone *') // false 68 | UserAgent.isDevice('iPhone 5s') // false 69 | ``` 70 | 71 | ### isEngine(query: string) 72 | 73 | ```javascript 74 | // Use to detect by engine name 75 | UserAgent.isEngine('WebKit') // true 76 | UserAgent.isEngine('Gecko') // false 77 | 78 | // Use to check scope to specific versions 79 | UserAgent.isEngine('WebKit *') // true 80 | UserAgent.isEngine('WebKit 537.51.2') // true 81 | UserAgent.isEngine('WebKit ~> 537.51.0') // true 82 | UserAgent.isEngine('Gecko *') // false 83 | UserAgent.isEngine('WebKit 536.0.0') // false 84 | ``` 85 | 86 | ### isPlatform(query: string) 87 | 88 | ```javascript 89 | // Use to detect by platform name 90 | UserAgent.isPlatform('iOS') // true 91 | UserAgent.isPlatform('Windows') // false 92 | 93 | // Use to check scope to specific versions 94 | UserAgent.isPlatform('iOS *') // true 95 | UserAgent.isPlatform('iOS 7.1.2') // true 96 | UserAgent.isPlatform('iOS 7.1.x || 6.1.x') // true 97 | 98 | UserAgent.isPlatform('Windows *') // false 99 | UserAgent.isPlatform('iOS 6') // false 100 | ``` 101 | ### Other uses 102 | 103 | We can also use to normalize Windows version numbers. Let's assume 104 | user agent has: 105 | ```javascript 106 | const userAgent = { 107 | //... other properties 108 | platformName: 'Windows', 109 | platformFullVersion: '4.0', 110 | } 111 | ``` 112 | Then we can use: 113 | ```javascript 114 | UserAgent.isPlatform('Windows') // true 115 | UserAgent.isPlatform('Windows NT4.0') // true 116 | UserAgent.isPlatform('Windows Vista') // false 117 | ``` 118 | ## License 119 | All code of this repo has been written by Facebook. React Tips team 120 | does not keep copyright them. We only package them to the npm package 121 | to help people use it more easy. 122 | -------------------------------------------------------------------------------- /__tests__/UserAgent.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @emails oncall+jsinfra 8 | */ 9 | 10 | 'use strict'; 11 | 12 | var UserAgent = require('../index'); 13 | var UserAgentData = require('../lib/userAgentData'); 14 | 15 | describe('UserAgent', () => { 16 | function stubUserAgentData(object) { 17 | Object.assign(UserAgentData, object); 18 | } 19 | 20 | beforeEach(() => { 21 | jest.resetModuleRegistry(); 22 | 23 | stubUserAgentData({ 24 | browserArchitecture: '32', 25 | browserFullVersion: '7.0', 26 | browserName: 'Mobile Safari', 27 | deviceName: 'iPhone', 28 | engineName: 'WebKit', 29 | engineVersion: '537.51.2', 30 | platformArchitecture: '64', 31 | platformFullVersion: '7.1.2', 32 | platformName: 'iOS', 33 | }); 34 | }); 35 | 36 | describe('isBrowserArchitecture', () => { 37 | it('can detect by browser architecture', () => { 38 | expect(UserAgent.isBrowserArchitecture('32')).toBe(true); 39 | 40 | expect(UserAgent.isBrowserArchitecture('64')).toBe(false); 41 | }); 42 | }); 43 | 44 | describe('isPlatformArchitecture', () => { 45 | it('can detect by platform architecture', () => { 46 | expect(UserAgent.isPlatformArchitecture('32')).toBe(false); 47 | 48 | expect(UserAgent.isPlatformArchitecture('64')).toBe(true); 49 | }); 50 | }); 51 | 52 | describe('isBrowser', () => { 53 | it('can detect by browser name', () => { 54 | expect(UserAgent.isBrowser('Mobile Safari')).toBe(true); 55 | 56 | expect(UserAgent.isBrowser('Chrome')).toBe(false); 57 | }); 58 | 59 | it('can scope to specific versions', ()=> { 60 | expect(UserAgent.isBrowser('Mobile Safari *')).toBe(true); 61 | expect(UserAgent.isBrowser('Mobile Safari 7')).toBe(true); 62 | expect(UserAgent.isBrowser('Mobile Safari 7.0 - 7.1')).toBe(true); 63 | 64 | expect(UserAgent.isBrowser('Chrome *')).toBe(false); 65 | expect(UserAgent.isBrowser('Mobile Safari 6.0.1')).toBe(false); 66 | }); 67 | 68 | it('memoizes results', () => { 69 | expect(UserAgent.isBrowser('Mobile Safari')).toBe(true); 70 | 71 | stubUserAgentData({browserName: 'Chrome'}); 72 | expect(UserAgent.isBrowser('Chrome')).toBe(true); 73 | 74 | // returns previously memoized value even though UA has now "changed" 75 | expect(UserAgent.isBrowser('Mobile Safari')).toBe(true); 76 | }); 77 | }); 78 | 79 | describe('isDevice', () => { 80 | it('can detect by device name', () => { 81 | expect(UserAgent.isDevice('iPhone')).toBe(true); 82 | 83 | expect(UserAgent.isDevice('iPad')).toBe(false); 84 | }); 85 | 86 | it('does not expose version information', () => { 87 | expect(UserAgent.isDevice('iPhone *')).toBe(false); 88 | expect(UserAgent.isDevice('iPhone 5s')).toBe(false); 89 | }); 90 | 91 | it('memoizes results', () => { 92 | expect(UserAgent.isDevice('iPhone')).toBe(true); 93 | 94 | stubUserAgentData({deviceName: 'iPad'}); 95 | expect(UserAgent.isDevice('iPad')).toBe(true); 96 | 97 | // returns previously memoized value even though UA has now "changed" 98 | expect(UserAgent.isDevice('iPhone')).toBe(true); 99 | }); 100 | }); 101 | 102 | describe('isEngine', () => { 103 | it('can detect by engine name', () => { 104 | expect(UserAgent.isEngine('WebKit')).toBe(true); 105 | 106 | expect(UserAgent.isEngine('Gecko')).toBe(false); 107 | }); 108 | 109 | it('can scope to specific versions', () => { 110 | expect(UserAgent.isEngine('WebKit *')).toBe(true); 111 | expect(UserAgent.isEngine('WebKit 537.51.2')).toBe(true); 112 | expect(UserAgent.isEngine('WebKit ~> 537.51.0')).toBe(true); 113 | 114 | expect(UserAgent.isEngine('Gecko *')).toBe(false); 115 | expect(UserAgent.isEngine('WebKit 536.0.0')).toBe(false); 116 | }); 117 | 118 | it('memoizes results', () => { 119 | expect(UserAgent.isEngine('WebKit')).toBe(true); 120 | 121 | stubUserAgentData({engineName: 'Gecko'}); 122 | expect(UserAgent.isEngine('Gecko')).toBe(true); 123 | 124 | // returns previously memoized value even though UA has now "changed" 125 | expect(UserAgent.isEngine('WebKit')).toBe(true); 126 | }); 127 | }); 128 | 129 | describe('isPlatform', () => { 130 | it('can detect by platform name', () => { 131 | expect(UserAgent.isPlatform('iOS')).toBe(true); 132 | 133 | expect(UserAgent.isPlatform('Windows')).toBe(false); 134 | }); 135 | 136 | it('can scope to specific versions', () => { 137 | expect(UserAgent.isPlatform('iOS *')).toBe(true); 138 | expect(UserAgent.isPlatform('iOS 7.1.2')).toBe(true); 139 | expect(UserAgent.isPlatform('iOS 7.1.x || 6.1.x')).toBe(true); 140 | 141 | expect(UserAgent.isPlatform('Windows *')).toBe(false); 142 | expect(UserAgent.isPlatform('iOS 6')).toBe(false); 143 | }); 144 | 145 | it('normalizes Windows version numbers', () => { 146 | stubUserAgentData({ 147 | platformName: 'Windows', 148 | platformFullVersion: '4.0', 149 | }); 150 | 151 | expect(UserAgent.isPlatform('Windows')).toBe(true); 152 | expect(UserAgent.isPlatform('Windows NT4.0')).toBe(true); 153 | expect(UserAgent.isPlatform('Windows Vista')).toBe(false); 154 | }); 155 | 156 | it('memoizes results', () => { 157 | expect(UserAgent.isPlatform('iOS')).toBe(true); 158 | 159 | stubUserAgentData({platformName: 'Windows'}); 160 | expect(UserAgent.isPlatform('Windows')).toBe(true); 161 | 162 | // returns previously memoized value even though UA has now "changed" 163 | expect(UserAgent.isPlatform('iOS')).toBe(true); 164 | }); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /__tests__/VersionRange.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @oncall oncall+jsinfra 8 | */ 9 | 10 | 'use strict'; 11 | 12 | jest.unmock('../lib/VersionRange'); 13 | 14 | var VersionRange = require('../lib/VersionRange'); 15 | 16 | describe('VersionRange', () => { 17 | function assertVersion(a, b, contains) { 18 | expect(VersionRange.contains(a, b)).toBe(contains); 19 | } 20 | 21 | describe('contains', () => { 22 | it('considers * to match everything', () => { 23 | assertVersion('*', '1.0', true); 24 | }); 25 | 26 | it('treats the empty string as equivalent to "*"', () => { 27 | assertVersion('', '1.0', true); 28 | }); 29 | 30 | it('matches literal versions', () => { 31 | assertVersion('1.0', '1.0', true); 32 | 33 | assertVersion('1.0', '1.1', false); 34 | }); 35 | 36 | it('considers = to indicate an exact match', () => { 37 | assertVersion('=1.0', '1.0', true); 38 | assertVersion('= 1.0', '1.0', true); 39 | 40 | assertVersion('=1.0', '1.1', false); 41 | }); 42 | 43 | it('matches using the > operator', () => { 44 | assertVersion('> 0.1', '2', true); 45 | assertVersion('> 0.1', '0.2', true); 46 | assertVersion('> 0.1', '0.1.1', true); 47 | assertVersion('>0.1', '0.1.1', true); 48 | 49 | assertVersion('> 2.0', '1', false); 50 | assertVersion('> 2.0', '0.1', false); 51 | assertVersion('> 2.0', '1.9.9', false); 52 | assertVersion('>2.0', '1.9.9', false); 53 | }); 54 | 55 | it('matches using the >= operator', () => { 56 | assertVersion('>= 1.0', '1', true); 57 | assertVersion('>= 1.0', '1.0', true); 58 | assertVersion('>= 1.0', '1.1', true); 59 | assertVersion('>= 1.0', '2.0.0', true); 60 | 61 | assertVersion('>= 2.1', '1', false); 62 | assertVersion('>= 2.1', '2.0', false); 63 | assertVersion('>= 2.1', '2.0.9', false); 64 | }); 65 | 66 | it('matches using the < operator', () => { 67 | assertVersion('< 36.1', '36', true); 68 | assertVersion('< 36.1', '35.1', true); 69 | assertVersion('< 36.1', '32.1.0', true); 70 | 71 | assertVersion('< 36.1', '38', false); 72 | assertVersion('< 36.1', '36.1', false); 73 | assertVersion('< 36.1', '36.9', false); 74 | assertVersion('< 36.1', '39.1.1', false); 75 | }); 76 | 77 | it('matches using the <= operator', () => { 78 | assertVersion('<= 12.0', '11', true); 79 | assertVersion('<= 12.0', '12', true); 80 | assertVersion('<= 12.0', '10.9', true); 81 | assertVersion('<= 12.0', '7.6.1', true); 82 | 83 | assertVersion('<= 12.0', '13', false); 84 | assertVersion('<= 12.0', '12.1', false); 85 | assertVersion('<= 12.0', '13.1', false); 86 | assertVersion('<= 12.0', '13.2.1', false); 87 | }); 88 | 89 | it('matches using the ~ operator', () => { 90 | assertVersion('~1.0', '1.0.2', true); 91 | assertVersion('~1.3.1', '1.3.1', true); 92 | assertVersion('~1.3.1', '1.3.2', true); 93 | assertVersion('~ 1.3.1', '1.3.1', true); // whitespace variant 94 | 95 | assertVersion('~1', '2', false); 96 | assertVersion('~1', '0.1', false); 97 | assertVersion('~1.0', '2.0', false); 98 | assertVersion('~1.3.1', '1', false); 99 | assertVersion('~1.3.1', '1.3', false); 100 | assertVersion('~1.3.1', '1.4', false); 101 | assertVersion('~1.3.1', '2', false); 102 | }); 103 | 104 | it('matches using the ~> operator', () => { 105 | assertVersion('~>1', '1.1', true); 106 | assertVersion('~>1.0', '1.0.2', true); 107 | assertVersion('~>1.3.1', '1.3.1', true); 108 | assertVersion('~>1.3.1', '1.3.2', true); 109 | assertVersion('~> 1.3.1', '1.3.1', true); // whitespace variant 110 | 111 | assertVersion('~>1', '2', false); 112 | assertVersion('~>1', '0.1', false); 113 | assertVersion('~>1.0', '2.0', false); 114 | assertVersion('~>1.3.1', '1', false); 115 | assertVersion('~>1.3.1', '1.3', false); 116 | assertVersion('~>1.3.1', '1.4', false); 117 | assertVersion('~>1.3.1', '2', false); 118 | }); 119 | 120 | it('considers "x" to be a wildcard', () => { 121 | assertVersion('x', '7', true); 122 | assertVersion('X', '7', true); 123 | assertVersion('12.x', '12.1', true); 124 | assertVersion('12.X', '12.1', true); 125 | assertVersion('12.x.x', '12.0', true); // due to zero-padding 126 | assertVersion('12.x.x', '12.9.1', true); 127 | 128 | assertVersion('1.x', '2.0', false); 129 | assertVersion('1.x.x', '2.0.0', false); 130 | assertVersion('1.xylophone', '1.1', false); // not a wildcard 131 | }); 132 | 133 | it('treats "*" like "x" in when not used as the last component', () => { 134 | assertVersion('0.*.9', '0.1.9', true); 135 | assertVersion('*.*.9', '0.1.9', true); 136 | 137 | assertVersion('0.*.9', '0.1.0', false); 138 | assertVersion('*.*.9', '0.1.0', false); 139 | }); 140 | 141 | it('treats "*" like a "greedy x" when used as the last component', () => { 142 | assertVersion('0.*', '0.1', true); 143 | assertVersion('0.*', '0.1.2.3.4', true); 144 | assertVersion('0.*.*', '0.1', true); // due to zero-padding 145 | assertVersion('0.*.*', '0.1.2', true); 146 | assertVersion('0.*.*', '0.1.2.3.4', true); 147 | assertVersion('*.*.*', '0', true); 148 | assertVersion('*.*.*', '0.1', true); 149 | assertVersion('*.*.*', '0.1.2', true); 150 | assertVersion('*.*.*', '0.1.2.3.4', true); 151 | 152 | assertVersion('0.*', '1.0', false); 153 | assertVersion('0.*', '1.0.1', false); 154 | assertVersion('0.*.*', '1.0.1', false); 155 | }); 156 | 157 | it('matches using the || operator', () => { 158 | assertVersion('1.0 || 1.1', '1.0', true); 159 | assertVersion('1.0 || 1.1', '1.1', true); 160 | assertVersion('1.0||1.1', '1.1', true); // whitespace variant 161 | assertVersion('1 || 2 || 3', '3', true); 162 | assertVersion('> 2 || ~> 1.3.1', '1.3.2', true); 163 | assertVersion('> 2 || ~> 1.3.1', '3', true); 164 | 165 | assertVersion('1.0 || 1.1', '1.2', false); 166 | assertVersion('1 || 2 || 3', '4', false); 167 | assertVersion('> 2 || ~> 1.3.1', '1', false); 168 | assertVersion('> 2 || ~> 1.3.1', '1.4', false); 169 | }); 170 | 171 | it('matches using the - operator', () => { 172 | assertVersion('1.0 - 1.2', '1.0', true); 173 | assertVersion('1.0 - 1.2', '1.1', true); 174 | assertVersion('1.0 - 1.2', '1.2', true); 175 | 176 | assertVersion('1.0 - 1.2', '1.3', false); 177 | }); 178 | 179 | it('considers - to have higher precedence than ||', () => { 180 | assertVersion('1.0 - 1.2 || 2.0 - 2.1', '1.2', true); 181 | assertVersion('1.0 - 1.2 || 2.0 - 2.1', '2.0', true); 182 | 183 | assertVersion('1.0 - 1.2 || 2.0 - 2.1', '1.3', false); 184 | }); 185 | 186 | it('requires whitespace around the - operator', () => { 187 | assertVersion('1.0-1.2', '1.0-1.2', true); 188 | 189 | assertVersion('1.0-1.2', '1.1', false); 190 | }); 191 | 192 | it('complains about invalid range expressions', () => { 193 | // too many operands 194 | expect(() => VersionRange.contains('1 - 2 - 3', '2')).toThrow(); 195 | 196 | // non-simple operands 197 | expect(() => VersionRange.contains('>= 1.0 - > 2.1')).toThrow(); 198 | }); 199 | }); 200 | }); 201 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export = userAgent; 2 | export as namespace userAgent; 3 | 4 | declare const userAgent: UserAgent; 5 | 6 | declare interface UserAgent { 7 | isBrowser: (query: string) => boolean; 8 | isBrowserArchitecture: (query: string) => boolean; 9 | isDevice: (query: string) => boolean; 10 | isEngine: (query: string) => boolean; 11 | isPlatform: (query: string) => boolean; 12 | isPlatformArchitecture: (query: string) => boolean; 13 | } 14 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const mapObject = require('./lib/mapObject'); 9 | const memoizeStringOnly = require('./lib/memoizeStringOnly'); 10 | const UserAgentData = require('./lib/userAgentData'); 11 | const VersionRange = require('./lib/VersionRange'); 12 | 13 | /** 14 | * Checks to see whether `name` and `version` satisfy `query`. 15 | * 16 | * @param {string} name Name of the browser, device, engine or platform 17 | * @param {?string} version Version of the browser, engine or platform 18 | * @param {string} query Query of form "Name [range expression]" 19 | * @param {?function} normalizer Optional pre-processor for range expression 20 | * @return {boolean} 21 | */ 22 | function compare(name, version, query, normalizer) { 23 | // check for exact match with no version 24 | if (name === query) { 25 | return true; 26 | } 27 | 28 | // check for non-matching names 29 | if (!query.startsWith(name)) { 30 | return false; 31 | } 32 | 33 | // full comparison with version 34 | let range = query.slice(name.length); 35 | if (version) { 36 | range = normalizer ? normalizer(range) : range; 37 | return VersionRange.contains(range, version); 38 | } 39 | 40 | return false; 41 | } 42 | 43 | /** 44 | * Normalizes `version` by stripping any "NT" prefix, but only on the Windows 45 | * platform. 46 | * 47 | * Mimics the stripping performed by the `UserAgentWindowsPlatform` PHP class. 48 | * 49 | * @param {string} version 50 | * @return {string} 51 | */ 52 | function normalizePlatformVersion(version) { 53 | if (UserAgentData.platformName === 'Windows') { 54 | return version.replace(/^\s*NT/, ''); 55 | } 56 | 57 | return version; 58 | } 59 | 60 | /** 61 | * Provides client-side access to the authoritative PHP-generated User Agent 62 | * information supplied by the server. 63 | */ 64 | const UserAgent = { 65 | /** 66 | * Check if the User Agent browser matches `query`. 67 | * 68 | * `query` should be a string like "Chrome" or "Chrome > 33". 69 | * 70 | * Valid browser names include: 71 | * 72 | * - ACCESS NetFront 73 | * - AOL 74 | * - Amazon Silk 75 | * - Android 76 | * - BlackBerry 77 | * - BlackBerry PlayBook 78 | * - Chrome 79 | * - Chrome for iOS 80 | * - Chrome frame 81 | * - Facebook PHP SDK 82 | * - Facebook for iOS 83 | * - Firefox 84 | * - IE 85 | * - IE Mobile 86 | * - Mobile Safari 87 | * - Motorola Internet Browser 88 | * - Nokia 89 | * - Openwave Mobile Browser 90 | * - Opera 91 | * - Opera Mini 92 | * - Opera Mobile 93 | * - Safari 94 | * - UIWebView 95 | * - Unknown 96 | * - webOS 97 | * - etc... 98 | * 99 | * An authoritative list can be found in the PHP `BrowserDetector` class and 100 | * related classes in the same file (see calls to `new UserAgentBrowser` here: 101 | * https://fburl.com/50728104). 102 | * 103 | * @note Function results are memoized 104 | * 105 | * @param {string} query Query of the form "Name [range expression]" 106 | * @return {boolean} 107 | */ 108 | isBrowser(query) { 109 | return compare( 110 | UserAgentData.browserName, 111 | UserAgentData.browserFullVersion, 112 | query 113 | ); 114 | }, 115 | 116 | /** 117 | * Check if the User Agent browser uses a 32 or 64 bit architecture. 118 | * 119 | * @note Function results are memoized 120 | * 121 | * @param {string} query Query of the form "32" or "64". 122 | * @return {boolean} 123 | */ 124 | isBrowserArchitecture(query) { 125 | return compare( 126 | UserAgentData.browserArchitecture, 127 | null, 128 | query 129 | ); 130 | }, 131 | 132 | /** 133 | * Check if the User Agent device matches `query`. 134 | * 135 | * `query` should be a string like "iPhone" or "iPad". 136 | * 137 | * Valid device names include: 138 | * 139 | * - Kindle 140 | * - Kindle Fire 141 | * - Unknown 142 | * - iPad 143 | * - iPhone 144 | * - iPod 145 | * - etc... 146 | * 147 | * An authoritative list can be found in the PHP `DeviceDetector` class and 148 | * related classes in the same file (see calls to `new UserAgentDevice` here: 149 | * https://fburl.com/50728332). 150 | * 151 | * @note Function results are memoized 152 | * 153 | * @param {string} query Query of the form "Name" 154 | * @return {boolean} 155 | */ 156 | isDevice(query) { 157 | return compare(UserAgentData.deviceName, null, query); 158 | }, 159 | 160 | /** 161 | * Check if the User Agent rendering engine matches `query`. 162 | * 163 | * `query` should be a string like "WebKit" or "WebKit >= 537". 164 | * 165 | * Valid engine names include: 166 | * 167 | * - Gecko 168 | * - Presto 169 | * - Trident 170 | * - WebKit 171 | * - etc... 172 | * 173 | * An authoritative list can be found in the PHP `RenderingEngineDetector` 174 | * class related classes in the same file (see calls to `new 175 | * UserAgentRenderingEngine` here: https://fburl.com/50728617). 176 | * 177 | * @note Function results are memoized 178 | * 179 | * @param {string} query Query of the form "Name [range expression]" 180 | * @return {boolean} 181 | */ 182 | isEngine(query) { 183 | return compare( 184 | UserAgentData.engineName, 185 | UserAgentData.engineVersion, 186 | query 187 | ); 188 | }, 189 | 190 | /** 191 | * Check if the User Agent platform matches `query`. 192 | * 193 | * `query` should be a string like "Windows" or "iOS 5 - 6". 194 | * 195 | * Valid platform names include: 196 | * 197 | * - Android 198 | * - BlackBerry OS 199 | * - Java ME 200 | * - Linux 201 | * - Mac OS X 202 | * - Mac OS X Calendar 203 | * - Mac OS X Internet Account 204 | * - Symbian 205 | * - SymbianOS 206 | * - Windows 207 | * - Windows Mobile 208 | * - Windows Phone 209 | * - iOS 210 | * - iOS Facebook Integration Account 211 | * - iOS Facebook Social Sharing UI 212 | * - webOS 213 | * - Chrome OS 214 | * - etc... 215 | * 216 | * An authoritative list can be found in the PHP `PlatformDetector` class and 217 | * related classes in the same file (see calls to `new UserAgentPlatform` 218 | * here: https://fburl.com/50729226). 219 | * 220 | * @note Function results are memoized 221 | * 222 | * @param {string} query Query of the form "Name [range expression]" 223 | * @return {boolean} 224 | */ 225 | isPlatform(query) { 226 | return compare( 227 | UserAgentData.platformName, 228 | UserAgentData.platformFullVersion, 229 | query, 230 | normalizePlatformVersion 231 | ); 232 | }, 233 | 234 | /** 235 | * Check if the User Agent platform is a 32 or 64 bit architecture. 236 | * 237 | * @note Function results are memoized 238 | * 239 | * @param {string} query Query of the form "32" or "64". 240 | * @return {boolean} 241 | */ 242 | isPlatformArchitecture(query) { 243 | return compare( 244 | UserAgentData.platformArchitecture, 245 | null, 246 | query 247 | ); 248 | }, 249 | 250 | }; 251 | 252 | const _UserAgent = mapObject(UserAgent, memoizeStringOnly); 253 | module.exports = _UserAgent; 254 | -------------------------------------------------------------------------------- /jest/createCacheKeyFunction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const crypto = require('crypto'); 11 | const fs = require('fs'); 12 | const path = require('path'); 13 | 14 | function getGlobalCacheKey(files, values) { 15 | const presetVersion = require('../package').dependencies['babel-preset-fbjs']; 16 | 17 | const chunks = [ 18 | process.env.NODE_ENV, 19 | process.env.BABEL_ENV, 20 | presetVersion, 21 | ...values, 22 | ...files.map(file => fs.readFileSync(file)), 23 | ]; 24 | 25 | return chunks 26 | .reduce( 27 | (hash, chunk) => hash.update('\0', 'utf-8').update(chunk || ''), 28 | crypto.createHash('md5') 29 | ) 30 | .digest('hex'); 31 | } 32 | 33 | function getCacheKeyFunction(globalCacheKey) { 34 | return (src, file, configString, options) => { 35 | const {instrument, config} = options; 36 | const rootDir = config && config.rootDir; 37 | 38 | return crypto 39 | .createHash('md5') 40 | .update(globalCacheKey) 41 | .update('\0', 'utf8') 42 | .update(src) 43 | .update('\0', 'utf8') 44 | .update(rootDir ? path.relative(config.rootDir, file) : '') 45 | .update('\0', 'utf8') 46 | .update(instrument ? 'instrument' : '') 47 | .digest('hex'); 48 | }; 49 | } 50 | 51 | module.exports = (files = [], values = []) => { 52 | return getCacheKeyFunction(getGlobalCacheKey(files, values)); 53 | }; 54 | -------------------------------------------------------------------------------- /jest/environment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | global.__DEV__ = true; 9 | -------------------------------------------------------------------------------- /jest/preprocessor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const babel = require('@babel/core'); 11 | const createCacheKeyFunction = require('./createCacheKeyFunction'); 12 | const path = require('path'); 13 | 14 | module.exports = { 15 | process(src, filename) { 16 | const options = { 17 | presets: [ 18 | require('babel-preset-fbjs'), 19 | ], 20 | filename: filename, 21 | retainLines: true, 22 | }; 23 | return babel.transform(src, options).code; 24 | }, 25 | 26 | // Generate a cache key that is based on the contents of this file and the 27 | // fbjs preset package.json (used as a proxy for determining if the preset has 28 | // changed configuration at all). 29 | getCacheKey: createCacheKeyFunction([ 30 | __filename, 31 | path.join(path.dirname(require.resolve('babel-preset-fbjs')), 'package.json') 32 | ]), 33 | }; 34 | -------------------------------------------------------------------------------- /lib/VersionRange.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @providesModule VersionRange 8 | */ 9 | 10 | 'use strict'; 11 | 12 | const componentRegex = /\./; 13 | const orRegex = /\|\|/; 14 | const rangeRegex = /\s+\-\s+/; 15 | const modifierRegex = /^(<=|<|=|>=|~>|~|>|)?\s*(.+)/; 16 | const numericRegex = /^(\d*)(.*)/; 17 | 18 | /** 19 | * Splits input `range` on "||" and returns true if any subrange matches 20 | * `version`. 21 | * 22 | * @param {string} range 23 | * @param {string} version 24 | * @returns {boolean} 25 | */ 26 | function checkOrExpression(range, version) { 27 | const expressions = range.split(orRegex); 28 | 29 | if (expressions.length > 1) { 30 | return expressions.some(range => VersionRange.contains(range, version)); 31 | } else { 32 | range = expressions[0].trim(); 33 | return checkRangeExpression(range, version); 34 | } 35 | } 36 | 37 | /** 38 | * Splits input `range` on " - " (the surrounding whitespace is required) and 39 | * returns true if version falls between the two operands. 40 | * 41 | * @param {string} range 42 | * @param {string} version 43 | * @returns {boolean} 44 | */ 45 | function checkRangeExpression(range, version) { 46 | const expressions = range.split(rangeRegex); 47 | 48 | if (!(expressions.length > 0 && expressions.length <= 2)) { 49 | throw new Error('the "-" operator expects exactly 2 operands'); 50 | } 51 | 52 | if (expressions.length === 1) { 53 | return checkSimpleExpression(expressions[0], version); 54 | } else { 55 | const [startVersion, endVersion] = expressions; 56 | if (!(isSimpleVersion(startVersion) && isSimpleVersion(endVersion))) { 57 | throw new Error('operands to the "-" operator must be simple (no modifiers)'); 58 | } 59 | 60 | return ( 61 | checkSimpleExpression('>=' + startVersion, version) && 62 | checkSimpleExpression('<=' + endVersion, version) 63 | ); 64 | } 65 | } 66 | 67 | /** 68 | * Checks if `range` matches `version`. `range` should be a "simple" range (ie. 69 | * not a compound range using the " - " or "||" operators). 70 | * 71 | * @param {string} range 72 | * @param {string} version 73 | * @returns {boolean} 74 | */ 75 | function checkSimpleExpression(range, version) { 76 | range = range.trim(); 77 | if (range === '') { 78 | return true; 79 | } 80 | 81 | const versionComponents = version.split(componentRegex); 82 | const {modifier, rangeComponents} = getModifierAndComponents(range); 83 | switch (modifier) { 84 | case '<': 85 | return checkLessThan(versionComponents, rangeComponents); 86 | case '<=': 87 | return checkLessThanOrEqual(versionComponents, rangeComponents); 88 | case '>=': 89 | return checkGreaterThanOrEqual(versionComponents, rangeComponents); 90 | case '>': 91 | return checkGreaterThan(versionComponents, rangeComponents); 92 | case '~': 93 | case '~>': 94 | return checkApproximateVersion(versionComponents, rangeComponents); 95 | default: 96 | return checkEqual(versionComponents, rangeComponents); 97 | } 98 | } 99 | 100 | /** 101 | * Checks whether `a` is less than `b`. 102 | * 103 | * @param {array} a 104 | * @param {array} b 105 | * @returns {boolean} 106 | */ 107 | function checkLessThan(a, b) { 108 | return compareComponents(a, b) === -1; 109 | } 110 | 111 | /** 112 | * Checks whether `a` is less than or equal to `b`. 113 | * 114 | * @param {array} a 115 | * @param {array} b 116 | * @returns {boolean} 117 | */ 118 | function checkLessThanOrEqual(a, b) { 119 | const result = compareComponents(a, b); 120 | return result === -1 || result === 0; 121 | } 122 | 123 | /** 124 | * Checks whether `a` is equal to `b`. 125 | * 126 | * @param {array} a 127 | * @param {array} b 128 | * @returns {boolean} 129 | */ 130 | function checkEqual(a, b) { 131 | return compareComponents(a, b) === 0; 132 | } 133 | 134 | /** 135 | * Checks whether `a` is greater than or equal to `b`. 136 | * 137 | * @param {array} a 138 | * @param {array} b 139 | * @returns {boolean} 140 | */ 141 | function checkGreaterThanOrEqual(a, b) { 142 | const result = compareComponents(a, b); 143 | return result === 1 || result === 0; 144 | } 145 | 146 | /** 147 | * Checks whether `a` is greater than `b`. 148 | * 149 | * @param {array} a 150 | * @param {array} b 151 | * @returns {boolean} 152 | */ 153 | function checkGreaterThan(a, b) { 154 | return compareComponents(a, b) === 1; 155 | } 156 | 157 | /** 158 | * Checks whether `a` is "reasonably close" to `b` (as described in 159 | * https://www.npmjs.org/doc/misc/semver.html). For example, if `b` is "1.3.1" 160 | * then "reasonably close" is defined as ">= 1.3.1 and < 1.4". 161 | * 162 | * @param {array} a 163 | * @param {array} b 164 | * @returns {boolean} 165 | */ 166 | function checkApproximateVersion(a, b) { 167 | const lowerBound = b.slice(); 168 | const upperBound = b.slice(); 169 | 170 | if (upperBound.length > 1) { 171 | upperBound.pop(); 172 | } 173 | const lastIndex = upperBound.length - 1; 174 | const numeric = parseInt(upperBound[lastIndex], 10); 175 | if (isNumber(numeric)) { 176 | upperBound[lastIndex] = numeric + 1 + ''; 177 | } 178 | 179 | return ( 180 | checkGreaterThanOrEqual(a, lowerBound) && 181 | checkLessThan(a, upperBound) 182 | ); 183 | } 184 | 185 | /** 186 | * Extracts the optional modifier (<, <=, =, >=, >, ~, ~>) and version 187 | * components from `range`. 188 | * 189 | * For example, given `range` ">= 1.2.3" returns an object with a `modifier` of 190 | * `">="` and `components` of `[1, 2, 3]`. 191 | * 192 | * @param {string} range 193 | * @returns {object} 194 | */ 195 | function getModifierAndComponents(range) { 196 | const rangeComponents = range.split(componentRegex); 197 | const matches = rangeComponents[0].match(modifierRegex); 198 | if (!matches) { 199 | throw new Error('expected regex to match but it did not'); 200 | } 201 | 202 | return { 203 | modifier: matches[1], 204 | rangeComponents: [matches[2]].concat(rangeComponents.slice(1)), 205 | }; 206 | } 207 | 208 | /** 209 | * Determines if `number` is a number. 210 | * 211 | * @param {mixed} number 212 | * @returns {boolean} 213 | */ 214 | function isNumber(number) { 215 | return !isNaN(number) && isFinite(number); 216 | } 217 | 218 | /** 219 | * Tests whether `range` is a "simple" version number without any modifiers 220 | * (">", "~" etc). 221 | * 222 | * @param {string} range 223 | * @returns {boolean} 224 | */ 225 | function isSimpleVersion(range) { 226 | return !getModifierAndComponents(range).modifier; 227 | } 228 | 229 | /** 230 | * Zero-pads array `array` until it is at least `length` long. 231 | * 232 | * @param {array} array 233 | * @param {number} length 234 | */ 235 | function zeroPad(array, length) { 236 | for (let i = array.length; i < length; i++) { 237 | array[i] = '0'; 238 | } 239 | } 240 | 241 | /** 242 | * Normalizes `a` and `b` in preparation for comparison by doing the following: 243 | * 244 | * - zero-pads `a` and `b` 245 | * - marks any "x", "X" or "*" component in `b` as equivalent by zero-ing it out 246 | * in both `a` and `b` 247 | * - marks any final "*" component in `b` as a greedy wildcard by zero-ing it 248 | * and all of its successors in `a` 249 | * 250 | * @param {array} a 251 | * @param {array} b 252 | * @returns {array>} 253 | */ 254 | function normalizeVersions(a, b) { 255 | a = a.slice(); 256 | b = b.slice(); 257 | 258 | zeroPad(a, b.length); 259 | 260 | // mark "x" and "*" components as equal 261 | for (let i = 0; i < b.length; i++) { 262 | const matches = b[i].match(/^[x*]$/i); 263 | if (matches) { 264 | b[i] = a[i] = '0'; 265 | 266 | // final "*" greedily zeros all remaining components 267 | if (matches[0] === '*' && i === b.length - 1) { 268 | for (let j = i; j < a.length; j++) { 269 | a[j] = '0'; 270 | } 271 | } 272 | } 273 | } 274 | 275 | zeroPad(b, a.length); 276 | 277 | return [a, b]; 278 | } 279 | 280 | /** 281 | * Returns the numerical -- not the lexicographical -- ordering of `a` and `b`. 282 | * 283 | * For example, `10-alpha` is greater than `2-beta`. 284 | * 285 | * @param {string} a 286 | * @param {string} b 287 | * @returns {number} -1, 0 or 1 to indicate whether `a` is less than, equal to, 288 | * or greater than `b`, respectively 289 | */ 290 | function compareNumeric(a, b) { 291 | const aPrefix = a.match(numericRegex)[1]; 292 | const bPrefix = b.match(numericRegex)[1]; 293 | const aNumeric = parseInt(aPrefix, 10); 294 | const bNumeric = parseInt(bPrefix, 10); 295 | 296 | if (isNumber(aNumeric) && isNumber(bNumeric) && aNumeric !== bNumeric) { 297 | return compare(aNumeric, bNumeric); 298 | } else { 299 | return compare(a, b); 300 | } 301 | } 302 | 303 | /** 304 | * Returns the ordering of `a` and `b`. 305 | * 306 | * @param {string|number} a 307 | * @param {string|number} b 308 | * @returns {number} -1, 0 or 1 to indicate whether `a` is less than, equal to, 309 | * or greater than `b`, respectively 310 | */ 311 | function compare(a, b) { 312 | if (typeof a !== typeof b) { 313 | throw new Error('"a" and "b" must be of the same type'); 314 | } 315 | 316 | if (a > b) { 317 | return 1; 318 | } else if (a < b) { 319 | return -1; 320 | } else { 321 | return 0; 322 | } 323 | } 324 | 325 | /** 326 | * Compares arrays of version components. 327 | * 328 | * @param {array} a 329 | * @param {array} b 330 | * @returns {number} -1, 0 or 1 to indicate whether `a` is less than, equal to, 331 | * or greater than `b`, respectively 332 | */ 333 | function compareComponents(a, b) { 334 | const [aNormalized, bNormalized] = normalizeVersions(a, b); 335 | 336 | for (let i = 0; i < bNormalized.length; i++) { 337 | const result = compareNumeric(aNormalized[i], bNormalized[i]); 338 | if (result) { 339 | return result; 340 | } 341 | } 342 | 343 | return 0; 344 | } 345 | 346 | const VersionRange = { 347 | /** 348 | * Checks whether `version` satisfies the `range` specification. 349 | * 350 | * We support a subset of the expressions defined in 351 | * https://www.npmjs.org/doc/misc/semver.html: 352 | * 353 | * version Must match version exactly 354 | * =version Same as just version 355 | * >version Must be greater than version 356 | * >=version Must be greater than or equal to version 357 | * = 1.2.3 and < 1.3" 362 | * ~>version Equivalent to ~version 363 | * 1.2.x Must match "1.2.x", where "x" is a wildcard that matches 364 | * anything 365 | * 1.2.* Similar to "1.2.x", but "*" in the trailing position is a 366 | * "greedy" wildcard, so will match any number of additional 367 | * components: 368 | * "1.2.*" will match "1.2.1", "1.2.1.1", "1.2.1.1.1" etc 369 | * * Any version 370 | * "" (Empty string) Same as * 371 | * v1 - v2 Equivalent to ">= v1 and <= v2" 372 | * r1 || r2 Passes if either r1 or r2 are satisfied 373 | * 374 | * @param {string} range 375 | * @param {string} version 376 | * @returns {boolean} 377 | */ 378 | contains(range, version) { 379 | return checkOrExpression(range.trim(), version.trim()); 380 | }, 381 | }; 382 | 383 | module.exports = VersionRange; 384 | -------------------------------------------------------------------------------- /lib/mapObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @providesModule mapObject 8 | */ 9 | 10 | 'use strict'; 11 | 12 | var hasOwnProperty = Object.prototype.hasOwnProperty; 13 | 14 | /** 15 | * Executes the provided `callback` once for each enumerable own property in the 16 | * object and constructs a new object from the results. The `callback` is 17 | * invoked with three arguments: 18 | * 19 | * - the property value 20 | * - the property name 21 | * - the object being traversed 22 | * 23 | * Properties that are added after the call to `mapObject` will not be visited 24 | * by `callback`. If the values of existing properties are changed, the value 25 | * passed to `callback` will be the value at the time `mapObject` visits them. 26 | * Properties that are deleted before being visited are not visited. 27 | * 28 | * @grep function objectMap() 29 | * @grep function objMap() 30 | * 31 | * @param {?object} object 32 | * @param {function} callback 33 | * @param {*} context 34 | * @return {?object} 35 | */ 36 | function mapObject(object, callback, context) { 37 | if (!object) { 38 | return null; 39 | } 40 | var result = {}; 41 | for (var name in object) { 42 | if (hasOwnProperty.call(object, name)) { 43 | result[name] = callback.call(context, object[name], name, object); 44 | } 45 | } 46 | return result; 47 | } 48 | 49 | module.exports = mapObject; 50 | -------------------------------------------------------------------------------- /lib/memoizeStringOnly.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @providesModule memoizeStringOnly 8 | * @flow 9 | * @typechecks static-only 10 | */ 11 | 12 | 'use strict'; 13 | 14 | /** 15 | * Memoizes the return value of a function that accepts one string argument. 16 | */ 17 | function memoizeStringOnly(callback) { 18 | const cache = {}; 19 | return function(string) { 20 | if (!cache.hasOwnProperty(string)) { 21 | cache[string] = callback.call(this, string); 22 | } 23 | return cache[string]; 24 | }; 25 | } 26 | 27 | module.exports = memoizeStringOnly; 28 | -------------------------------------------------------------------------------- /lib/userAgentData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @providesModule UserAgentData 8 | */ 9 | 10 | /** 11 | * Usage note: 12 | * This module makes a best effort to export the same data we would internally. 13 | * At Facebook we use a server-generated module that does the parsing and 14 | * exports the data for the client to use. We can't rely on a server-side 15 | * implementation in open source so instead we make use of an open source 16 | * library to do the heavy lifting and then make some adjustments as necessary. 17 | * It's likely there will be some differences. Some we can smooth over. 18 | * Others are going to be harder. 19 | */ 20 | 21 | const UAParser = require('ua-parser-js'); 22 | 23 | const UNKNOWN = 'Unknown'; 24 | 25 | const PLATFORM_MAP = { 26 | 'Mac OS': 'Mac OS X', 27 | }; 28 | 29 | /** 30 | * Convert from UAParser platform name to what we expect. 31 | */ 32 | function convertPlatformName(name) { 33 | return PLATFORM_MAP[name] || name; 34 | } 35 | 36 | /** 37 | * Get the version number in parts. This is very naive. We actually get major 38 | * version as a part of UAParser already, which is generally good enough, but 39 | * let's get the minor just in case. 40 | */ 41 | function getBrowserVersion(version) { 42 | if (!version) { 43 | return { 44 | major: '', 45 | minor: '', 46 | }; 47 | } 48 | const parts = version.split('.'); 49 | return { 50 | major: parts[0], 51 | minor: parts[1], 52 | }; 53 | } 54 | 55 | /** 56 | * Get the UA data fom UAParser and then convert it to the format we're 57 | * expecting for our APIS. 58 | */ 59 | const parser = new UAParser(); 60 | const results = parser.getResult(); 61 | 62 | // Do some conversion first. 63 | const browserVersionData = getBrowserVersion(results.browser.version); 64 | const uaData = { 65 | browserArchitecture: results.cpu.architecture || UNKNOWN, 66 | browserFullVersion: results.browser.version || UNKNOWN, 67 | browserMinorVersion: browserVersionData.minor || UNKNOWN, 68 | browserName: results.browser.name || UNKNOWN, 69 | browserVersion: results.browser.major || UNKNOWN, 70 | deviceName: results.device.model || UNKNOWN, 71 | engineName: results.engine.name || UNKNOWN, 72 | engineVersion: results.engine.version || UNKNOWN, 73 | platformArchitecture: results.cpu.architecture || UNKNOWN, 74 | platformName: convertPlatformName(results.os.name) || UNKNOWN, 75 | platformVersion: results.os.version || UNKNOWN, 76 | platformFullVersion: results.os.version || UNKNOWN, 77 | }; 78 | 79 | 80 | module.exports = uaData; 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reacttips/useragent", 3 | "version": "1.0.1", 4 | "description": "User agent parser internal library used by Facebook team", 5 | "license": "Apache-2.0", 6 | "main": "index.js", 7 | "typings": "./index.d.ts", 8 | "files": [ 9 | "LICENSE", 10 | "README.md", 11 | "index.js", 12 | "index.d.ts", 13 | "lib/" 14 | ], 15 | "dependencies": { 16 | "ua-parser-js": "^0.7.18" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.0.0", 20 | "babel-preset-fbjs": "^3.4.0", 21 | "jest": "^25.4.0" 22 | }, 23 | "scripts": { 24 | "test": "yarn jest" 25 | }, 26 | "publishConfig": { 27 | "access": "public" 28 | } 29 | } 30 | --------------------------------------------------------------------------------