├── .babelrc ├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── package.json ├── public └── index.html ├── src ├── __test__ │ └── inapp.test.js ├── inapp.js ├── index.css ├── index.js └── qrcode.png └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "node": true 7 | }, 8 | "rules": { 9 | "no-console": 0, 10 | "no-useless-escape": 0, 11 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }] 12 | }, 13 | "globals": { 14 | "jest": true, 15 | "describe": true, 16 | "it": true, 17 | "expect": true, 18 | "beforeAll": true, 19 | "afterAll": true, 20 | "beforeEach": true, 21 | "afterEach": true, 22 | "ga": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | 5 | # Icon must end with two \r 6 | Icon 7 | 8 | 9 | # Thumbnails 10 | ._* 11 | 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | .com.apple.timemachine.donotpresent 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | 28 | # Logs 29 | logs 30 | *.log 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | 35 | # Runtime data 36 | pids 37 | *.pid 38 | *.seed 39 | *.pid.lock 40 | 41 | # Directory for instrumented libs generated by jscoverage/JSCover 42 | lib-cov 43 | 44 | # Coverage directory used by tools like istanbul 45 | coverage 46 | 47 | # nyc test coverage 48 | .nyc_output 49 | 50 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 51 | .grunt 52 | 53 | # Bower dependency directory (https://bower.io/) 54 | bower_components 55 | 56 | # node-waf configuration 57 | .lock-wscript 58 | 59 | # Compiled binary addons (http://nodejs.org/api/addons.html) 60 | build/Release 61 | 62 | # Dependency directories 63 | node_modules/ 64 | jspm_packages/ 65 | 66 | # Typescript v1 declaration files 67 | typings/ 68 | 69 | # Optional npm cache directory 70 | .npm 71 | 72 | # Optional eslint cache 73 | .eslintcache 74 | 75 | # Optional REPL history 76 | .node_repl_history 77 | 78 | # Output of 'npm pack' 79 | *.tgz 80 | 81 | # Yarn Integrity file 82 | .yarn-integrity 83 | 84 | # dotenv environment variables file 85 | .env 86 | 87 | /build 88 | /lib -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '6.9' 5 | 6 | sudo: required 7 | 8 | after_success: 9 | - npm install -g codecov 10 | - codecov 11 | 12 | after_success: 13 | - yarn semantic-release 14 | 15 | branches: 16 | except: 17 | - /^v\d+\.\d+\.\d+$/ 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Detect InApp 2 | 3 | detect browser or in-app information for mobile 4 | 5 | [![Build Status](https://travis-ci.org/f2etw/detect-inapp.svg?branch=master)](https://travis-ci.org/f2etw/detect-inapp) 6 | [![codecov](https://codecov.io/gh/f2etw/detect-inapp/branch/master/graph/badge.svg)](https://codecov.io/gh/f2etw/detect-inapp) 7 | [![npm](https://img.shields.io/npm/v/detect-inapp.svg)](https://npmjs.org/package/detect-inapp) 8 | [![downloads](https://img.shields.io/npm/dm/detect-inapp.svg)](https://npmjs.org/package/detect-inapp) 9 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com) 10 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 11 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 12 | 13 | # Code Example 14 | 15 | ``` 16 | import InApp from 'detect-inapp'; 17 | 18 | const inapp = new InApp(navigator.userAgent || navigator.vendor || window.opera); 19 | ``` 20 | 21 | # Installation 22 | 23 | `yarn add detect-inapp` 24 | 25 | # API Reference 26 | 27 | - `inapp.isMobile` 28 | 29 | - `inapp.isDesktop` 30 | 31 | - `inapp.isInApp` 32 | 33 | - `inapp.browser` 34 | 35 | `values: 'messenger', 'facebook', 'line', 'twitter', 'wechat', 'miui', 'instagram', 'chrome', 'safari', 'ie', 'firefox'` 36 | 37 | # License 38 | 39 | MIT License 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "detect-inapp", 3 | "version": "1.1.0", 4 | "description": "detect browser or in-app information for mobile", 5 | "main": "lib/inapp.js", 6 | "repository": "git@github.com:f2etw/detect-inapp.git", 7 | "author": "yuting1987@gmail.com", 8 | "license": "MIT", 9 | "scripts": { 10 | "prepublish": "npm run build", 11 | "develop": "jest --watch ./src", 12 | "test": "eslint ./src; jest --coverage ./src", 13 | "build": "rm -rf lib && mkdir lib && babel src/inapp.js --out-file lib/inapp.js", 14 | "demo": "react-scripts start", 15 | "deploy": "react-scripts build && gh-pages -d build", 16 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 17 | }, 18 | "homepage": "https://f2etw.github.io/detect-inapp", 19 | "files": [ 20 | "lib" 21 | ], 22 | "dependencies": { 23 | "lodash.findkey": "^4.6.0" 24 | }, 25 | "devDependencies": { 26 | "babel-cli": "^6.24.1", 27 | "babel-core": "^6.24.1", 28 | "babel-eslint": "^7.2.3", 29 | "babel-jest": "^20.0.3", 30 | "babel-polyfill": "^6.23.0", 31 | "babel-preset-react-native": "^1.9.2", 32 | "clipboard": "^1.6.1", 33 | "cross-env": "^5.0.0", 34 | "eslint": "^3.19.0", 35 | "eslint-config-airbnb": "^15.0.1", 36 | "eslint-plugin-import": "^2.2.0", 37 | "eslint-plugin-jsx-a11y": "^5.0.3", 38 | "eslint-plugin-react": "^7.0.1", 39 | "gh-pages": "^1.0.0", 40 | "jest": "^20.0.3", 41 | "lodash": "^4.17.5", 42 | "primer-css": "^6.0.0", 43 | "react": "15.5.4", 44 | "react-dom": "15.5.4", 45 | "react-icons": "^2.2.5", 46 | "react-scripts": "1.0.1", 47 | "semantic-release": "^6.3.6" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Detect InApp 9 | 18 | 19 | 20 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/__test__/inapp.test.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import InApp from '../inapp'; 3 | 4 | // check user agents from https://developers.whatismybrowser.com/ 5 | 6 | const DESKTOP = { 7 | MACOS: { 8 | CHROME: [ 9 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 10 | ], 11 | }, 12 | WINDOWS: { 13 | CHROME: [ 14 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 15 | ], 16 | FIREFOX: [ 17 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0', 18 | ], 19 | IE11: [ 20 | 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko', 21 | ], 22 | }, 23 | UBUNTU: { 24 | CHROME: [ 25 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36', 26 | ], 27 | FIREFOX: [ 28 | 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0', 29 | ], 30 | VIVALDI: [ 31 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.114 Safari/537.36 Vivaldi/1.9.818.50', 32 | ], 33 | }, 34 | }; 35 | 36 | const MOBILE = { 37 | IPHONE: { 38 | MESSENGER: [ 39 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27 [FBAN/MessengerForiOS;FBAV/117.0.0.36.70;FBBV/57539258;FBDV/iPhone7,2;FBMD/iPhone;FBSN/iOS;FBSV/10.2.1;FBSS/2;FBCR/中-華-電-信-;FBID/phone;FBLC/zh_TW;FBOP/5;FBRV/0]', 40 | ], 41 | FACEBOOK: [ 42 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27 [FBAN/FBIOS;FBAV/93.0.0.49.65;FBBV/58440824;FBDV/iPhone7,2;FBMD/iPhone;FBSN/iOS;FBSV/10.2.1;FBSS/2;FBCR/中-華-電-信-;FBID/phone;FBLC/zh_TW;FBOP/5;FBRV/0]', 43 | ], 44 | TWITTER: [ 45 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Mobile/14D27 Twitter for iPhone', 46 | ], 47 | LINE: [ 48 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27 Safari Line/7.3.2', 49 | ], 50 | INSTAGRAM: [ 51 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27 Instagram 10.21.0 (iPhone7,2; iOS 10_2_1; zh-Hant_JP; zh-Hant-JP; scale=2.00; gamut=normal; 750x1334)', 52 | ], 53 | WECHAT: [ 54 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27 MicroMessenger/6.5.8 NetType/WIFI Language/zh_CN', 55 | ], 56 | CHROME: [ 57 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/58.0.3029.113 Mobile/14D27 Safari/602.1', 58 | ], 59 | SAFARI: [ 60 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1', 61 | ], 62 | }, 63 | SONY: { 64 | MESSENGER: [ 65 | 'Mozilla/5.0 (Linux; Android 6.0.1; D6653 Build/23.5.A.0.575; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 [FB_IAB/MESSENGER;FBAV/118.0.0.19.82;]', 66 | ], 67 | FACEBOOK: [ 68 | 'Mozilla/5.0 (Linux; Android 6.0.1; D6653 Build/23.5.A.0.575; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/124.0.0.22.66;]', 69 | ], 70 | LINE: [ 71 | 'Mozilla/5.0 (Linux; Android 6.0.1; D6653 Build/23.5.A.0.575; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 Line/7.4.0/IAB', 72 | ], 73 | INSTAGRAM: [ 74 | 'Mozilla/5.0 (Linux; Android 6.0.1; D6653 Build/23.5.A.0.575; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 Instagram 10.21.0 Android (23/6.0.1; 480dpi; 1080x1776; Sony; D6653; D6653; qcom; zh_TW)', 75 | ], 76 | WECHAT: [ 77 | 'Mozilla/5.0 (Linux; Android 6.0.1; D6653 Build/23.5.A.0.575; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 MicroMessenger/6.5.7.1041 NetType/WIFI Language/zh_TW', 78 | ], 79 | CHROME: [ 80 | 'Mozilla/5.0 (Linux; Android 6.0.1; D6653 Build/23.5.A.0.575) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36', 81 | ], 82 | }, 83 | PIXEL: { 84 | PUFFIN: [ 85 | 'Mozilla/5.0 (Linux; Android 7.1.2; Pixel Build/N2G47E; zh-tw) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Mobile Safari/537.36 Puffin/6.0.9.15863AP', 86 | ], 87 | CHROME: [ 88 | "Mozilla/5.0 (Linux; Android 12; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Mobile Safari/537.36", 89 | ], 90 | }, 91 | HTC: { 92 | FACEBOOK: [], 93 | LINE: [], 94 | WECHAT: [], 95 | CHROME: [], 96 | }, 97 | SAMSUNG: { 98 | MESSENGER: [ 99 | 'Mozilla/5.0 (Linux; Android 6.0.1; SM-N9208 Build/MMB29K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 [FB_IAB/MESSENGER;FBAV/118.0.0.19.82;]', 100 | ], 101 | FACEBOOK: [ 102 | 'Mozilla/5.0 (Linux; Android 6.0.1; SM-N9208 Build/MMB29K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/125.0.0.16.80;]', 103 | ], 104 | LINE: [ 105 | 'Mozilla/5.0 (Linux; Android 6.0.1; SM-N9208 Build/MMB29K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 Line/7.4.0/IAB', 106 | ], 107 | WECHAT: [ 108 | 'Mozilla/5.0 (Linux; Android 6.0.1; SM-N9208 Build/MMB29K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 MicroMessenger/6.5.7.1041 NetType/WIFI Language/zh_TW', 109 | ], 110 | INSTAGRAM: [ 111 | 'Mozilla/5.0 (Linux; Android 6.0.1; SM-N9208 Build/MMB29K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 Instagram 10.22.0 Android (23/6.0.1; 560dpi; 1440x2560; samsung; SM-N9208; noblelte; samsungexynos7420; zh_TW)', 112 | ], 113 | CHROME: [ 114 | 'Mozilla/5.0 (Linux; Android 6.0.1; SM-N9208 Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36', 115 | ], 116 | }, 117 | ASUS: { 118 | MESSENGER: [ 119 | 'Mozilla/5.0 (Linux; Android 4.4.2; ASUS_T00F Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36 [FB_IAB/MESSENGER;FBAV/118.0.0.19.82;]', 120 | ], 121 | FACEBOOK: [ 122 | 'Mozilla/5.0 (Linux; Android 4.4.2; ASUS_T00F Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36[FBAN/EMA;FBLC/zh_TW;FBAV/42.0.0.3.64;]', 123 | ], 124 | CHROME: [ 125 | 'Mozilla/5.0 (Linux; Android 4.4.2; ASUS_T00F Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36', 126 | ], 127 | FIREFOX: [ 128 | 'Mozilla/5.0 (Android 4.4.2; Mobile; rv:53.0) Gecko/53.0 Firefox/53.0', 129 | ], 130 | }, 131 | REDMI: { 132 | MESSENGER: [ 133 | 'Mozilla/5.0 (Linux; Android 6.0.1; Redmi Note 3 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 [FB_IAB/MESSENGER;FBAV/118.0.0.19.82;]', 134 | ], 135 | FACEBOOK: [ 136 | 'Mozilla/5.0 (Linux; Android 6.0.1; Redmi Note 3 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/124.0.0.22.66;]', 137 | ], 138 | LINE: [ 139 | 'Mozilla/5.0 (Linux; Android 6.0.1; Redmi Note 3 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 Line/7.4.0/IAB', 140 | ], 141 | INSTAGRAM: [ 142 | 'Mozilla/5.0 (Linux; Android 6.0.1; Redmi Note 3 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 Instagram 10.21.0 Android (23/6.0.1; 480dpi; 1080x1920; Xiaomi; Redmi Note 3; kate; qcom; zh_TW)', 143 | ], 144 | MIUI: [ 145 | 'Mozilla/5.0 (Linux; U; Android 6.0.1; zh-tw; Redmi Note 3 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.146 Mobile Safari/537.36 XiaoMi/MiuiBrowser/8.5.8', 146 | ], 147 | CHROME: [ 148 | 'Mozilla/5.0 (Linux; Android 6.0.1; Redmi Note 3 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36', 149 | ], 150 | }, 151 | WINDOWS: { 152 | FACEBOOK: [], 153 | LINE: [], 154 | WECHAT: [], 155 | }, 156 | }; 157 | 158 | const TABLET = { 159 | IPAD: { 160 | MESSENGER: [ 161 | 'Mozilla/5.0 (iPad; CPU OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E304 [FBAN/MessengerForiOS;FBAV/117.0.0.36.70;FBBV/57539258;FBDV/iPad4,4;FBMD/iPad;FBSN/iOS;FBSV/10.3.1;FBSS/2;FBCR/;FBID/tablet;FBLC/zh_TW;FBOP/5;FBRV/0]', 162 | ], 163 | FACEBOOK: [ 164 | 'Mozilla/5.0 (iPad; CPU OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E304 [FBAN/FBIOS;FBAV/92.0.0.46.70;FBBV/57733420;FBDV/iPad4,4;FBMD/iPad;FBSN/iOS;FBSV/10.3.1;FBSS/2;FBCR/;FBID/tablet;FBLC/zh_TW;FBOP/5;FBRV/0]', 165 | ], 166 | TWITTER: [ 167 | 'Mozilla/5.0 (iPad; CPU OS 10_3_1 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Mobile/14E304 Twitter for iPhone', 168 | ], 169 | LINE: [ 170 | 'Mozilla/5.0 (iPad; CPU OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E304 Safari Line/7.3.0', 171 | ], 172 | INSTAGRAM: [ 173 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E304 Instagram 10.20.0 (iPad4,4; iOS 10_3_1; zh_TW; zh-TW; scale=2.00; gamut=normal; 640x960)', 174 | ], 175 | WECHAT: [ 176 | 'Mozilla/5.0 (iPad; CPU OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E304 MicroMessenger/6.5.8 NetType/WIFI Language/zh_TW', 177 | ], 178 | CHROME: [ 179 | 'Mozilla/5.0 (iPad; CPU OS 10_3_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/58.0.3029.83 Mobile/14E304 Safari/602.1', 180 | ], 181 | SAFARI: [ 182 | 'Mozilla/5.0 (iPad; CPU OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', 183 | ], 184 | }, 185 | }; 186 | 187 | describe('InApp', () => { 188 | describe('browser', () => { 189 | it('browser', () => { 190 | _.forEach(MOBILE, device => _.forEach(device, (useragents, name) => 191 | _.forEach(useragents, (useragent) => { 192 | const inapp = new InApp(useragent); 193 | expect(inapp.browser).toBe(name.toLocaleLowerCase()); 194 | }), 195 | )); 196 | }); 197 | }); 198 | 199 | describe('isMobile', () => { 200 | it('is mobile', () => { 201 | _.forEach(Object.assign({}, MOBILE, TABLET), device => _.forEach(device, useragents => 202 | _.forEach(useragents, (useragent) => { 203 | const inapp = new InApp(useragent); 204 | expect(inapp.isMobile).toBe(true); 205 | }), 206 | )); 207 | }); 208 | 209 | it('is not mobile', () => { 210 | _.forEach(DESKTOP, device => _.forEach(device, useragents => 211 | _.forEach(useragents, (useragent) => { 212 | const inapp = new InApp(useragent); 213 | expect(inapp.isMobile).toBe(false); 214 | }), 215 | )); 216 | }); 217 | }); 218 | 219 | describe('isDesktop', () => { 220 | it('is desktop', () => { 221 | _.forEach(DESKTOP, device => _.forEach(device, useragents => 222 | _.forEach(useragents, (useragent) => { 223 | const inapp = new InApp(useragent); 224 | expect(inapp.isDesktop).toBe(true); 225 | }), 226 | )); 227 | }); 228 | 229 | it('is not desktop', () => { 230 | _.forEach(Object.assign({}, MOBILE, TABLET), device => _.forEach(device, useragents => 231 | _.forEach(useragents, (useragent) => { 232 | const inapp = new InApp(useragent); 233 | expect(inapp.isDesktop).toBe(false); 234 | }), 235 | )); 236 | }); 237 | }); 238 | 239 | describe('inBot', () => { 240 | }); 241 | 242 | describe('isInApp', () => { 243 | it('is in app', () => { 244 | _.forEach(Object.assign({}, MOBILE, TABLET), device => _.forEach(device, (useragents, name) => 245 | _.forEach(useragents, (useragent) => { 246 | const inapp = new InApp(useragent); 247 | expect(inapp.isInApp).toBe(['CHROME', 'SAFARI', 'FIREFOX', 'MIUI', 'PUFFIN'].indexOf(name) < 0); 248 | }), 249 | )); 250 | }); 251 | 252 | it('is not in app', () => { 253 | _.forEach(DESKTOP, device => _.forEach(device, useragents => 254 | _.forEach(useragents, (useragent) => { 255 | const inapp = new InApp(useragent); 256 | expect(inapp.isInApp).toBe(false); 257 | }), 258 | )); 259 | }); 260 | }); 261 | }); 262 | -------------------------------------------------------------------------------- /src/inapp.js: -------------------------------------------------------------------------------- 1 | const findKey = require('lodash.findkey'); 2 | 3 | const BROWSER = { 4 | messenger: /\bFB[\w_]+\/(Messenger|MESSENGER)/, 5 | facebook: /\bFB[\w_]+\//, 6 | twitter: /\bTwitter/i, 7 | line: /\bLine\//i, 8 | wechat: /\bMicroMessenger\//i, 9 | puffin: /\bPuffin/i, 10 | miui: /\bMiuiBrowser\//i, 11 | instagram: /\bInstagram/i, 12 | chrome: /\bCrMo\b|CriOS|Android.*Chrome\/[.0-9]* (Mobile)?/, 13 | safari: /Version.*Mobile.*Safari|Safari.*Mobile|MobileSafari/, 14 | ie: /IEMobile|MSIEMobile/, 15 | firefox: /fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile|FxiOS/, 16 | }; 17 | 18 | class InApp { 19 | 20 | ua = ''; 21 | 22 | constructor(useragent) { 23 | this.ua = useragent; 24 | } 25 | 26 | get browser(): string { 27 | return findKey(BROWSER, regex => regex.test(this.ua)) || 'other'; 28 | } 29 | 30 | get isMobile(): boolean { 31 | return /(iPad|iPhone|Android|Mobile)/i.test(this.ua) || false; 32 | } 33 | 34 | get isDesktop(): boolean { 35 | return !this.isMobile; 36 | } 37 | 38 | get isInApp(): boolean { 39 | const rules = [ 40 | 'WebView', 41 | '(iPhone|iPod|iPad)(?!.*Safari\/)', 42 | 'Android.*(wv)', 43 | ]; 44 | const regex = new RegExp(`(${rules.join('|')})`, 'ig'); 45 | return Boolean(this.ua.match(regex)); 46 | } 47 | } 48 | 49 | module.exports = InApp; 50 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | .container { 6 | width: 100%; 7 | max-width: 320px; 8 | } 9 | 10 | .p-3 { 11 | margin-top: 3px; 12 | } 13 | 14 | .qrcode { 15 | text-align: center; 16 | } 17 | textarea { 18 | font-family: monospace, monospace; 19 | } 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ 2 | 3 | import 'primer-css/build/build.css'; 4 | import React, { Component } from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import Clippy from 'react-icons/lib/go/clippy'; 7 | import GitHub from 'react-icons/lib/go/mark-github'; 8 | import DiffRenamed from 'react-icons/lib/go/diff-renamed'; 9 | import Clipboard from 'clipboard'; 10 | import InApp from './inapp'; 11 | import qrcode from './qrcode.png'; 12 | import './index.css'; 13 | 14 | class App extends Component { 15 | 16 | state = { 17 | inapp: null, 18 | value: '', 19 | uri: 'twitter://', 20 | } 21 | 22 | componentWillMount() { 23 | const useragent = navigator.userAgent || navigator.vendor || window.opera; 24 | const inapp = new InApp(useragent); 25 | const value = [`${useragent}`]; 26 | if (navigator) for (let key in navigator) value.push(`${key}=${navigator[key]}`); // eslint-disable-line 27 | this.setState({ inapp, value: value.join('\n') }); 28 | window.ga('send', 'event', 'useragent', useragent, inapp.browser); 29 | } 30 | 31 | componentDidMount() { 32 | new Clipboard('.copy'); // eslint-disable-line 33 | } 34 | 35 | onUriChange = e => this.setState({ uri: e.target.value }); 36 | 37 | onOpenClick = async () => { 38 | const { inapp, uri } = this.state; 39 | const reply = await inapp.open({ ios: { uri } }); 40 | if (!reply) alert('Cannot Open'); // eslint-disable-line 41 | } 42 | 43 | render() { 44 | const { inapp, value, uri } = this.state; 45 | 46 | return ( 47 |
48 |
49 |
50 |