1. 1 : /**
  2. 2 : * @file browser.js
  3. 3 : * @module browser
  4. 4 : */
  5. 5 : import * as Dom from './dom';
  6. 6 : import window from 'global/window';
  7. 7 :
  8. 8 : const USER_AGENT = window.navigator && window.navigator.userAgent || '';
  9. 9 : const webkitVersionMap = (/AppleWebKit\/([\d.]+)/i).exec(USER_AGENT);
  10. 10 : const appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
  11. 11 :
  12. 12 : /**
  13. 13 : * Whether or not this device is an iPod.
  14. 14 : *
  15. 15 : * @static
  16. 16 : * @const
  17. 17 : * @type {Boolean}
  18. 18 : */
  19. 19 : export const IS_IPOD = (/iPod/i).test(USER_AGENT);
  20. 20 :
  21. 21 : /**
  22. 22 : * The detected iOS version - or `null`.
  23. 23 : *
  24. 24 : * @static
  25. 25 : * @const
  26. 26 : * @type {string|null}
  27. 27 : */
  28. 28 : export const IOS_VERSION = (function() {
  29. 29 : const match = USER_AGENT.match(/OS (\d+)_/i);
  30. 30 :
  31. 31 : if (match && match[1]) {
  32. 32 : return match[1];
  33. 33 : }
  34. 34 : return null;
  35. 35 : }());
  36. 36 :
  37. 37 : /**
  38. 38 : * Whether or not this is an Android device.
  39. 39 : *
  40. 40 : * @static
  41. 41 : * @const
  42. 42 : * @type {Boolean}
  43. 43 : */
  44. 44 : export const IS_ANDROID = (/Android/i).test(USER_AGENT);
  45. 45 :
  46. 46 : /**
  47. 47 : * The detected Android version - or `null`.
  48. 48 : *
  49. 49 : * @static
  50. 50 : * @const
  51. 51 : * @type {number|string|null}
  52. 52 : */
  53. 53 : export const ANDROID_VERSION = (function() {
  54. 54 : // This matches Android Major.Minor.Patch versions
  55. 55 : // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  56. 56 : const match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
  57. 57 :
  58. 58 : if (!match) {
  59. 59 : return null;
  60. 60 : }
  61. 61 :
  62. 62 : const major = match[1] && parseFloat(match[1]);
  63. 63 : const minor = match[2] && parseFloat(match[2]);
  64. 64 :
  65. 65 : if (major && minor) {
  66. 66 : return parseFloat(match[1] + '.' + match[2]);
  67. 67 : } else if (major) {
  68. 68 : return major;
  69. 69 : }
  70. 70 : return null;
  71. 71 : }());
  72. 72 :
  73. 73 : /**
  74. 74 : * Whether or not this is a native Android browser.
  75. 75 : *
  76. 76 : * @static
  77. 77 : * @const
  78. 78 : * @type {Boolean}
  79. 79 : */
  80. 80 : export const IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
  81. 81 :
  82. 82 : /**
  83. 83 : * Whether or not this is Mozilla Firefox.
  84. 84 : *
  85. 85 : * @static
  86. 86 : * @const
  87. 87 : * @type {Boolean}
  88. 88 : */
  89. 89 : export const IS_FIREFOX = (/Firefox/i).test(USER_AGENT);
  90. 90 :
  91. 91 : /**
  92. 92 : * Whether or not this is Microsoft Edge.
  93. 93 : *
  94. 94 : * @static
  95. 95 : * @const
  96. 96 : * @type {Boolean}
  97. 97 : */
  98. 98 : export const IS_EDGE = (/Edg/i).test(USER_AGENT);
  99. 99 :
  100. 100 : /**
  101. 101 : * Whether or not this is Google Chrome.
  102. 102 : *
  103. 103 : * This will also be `true` for Chrome on iOS, which will have different support
  104. 104 : * as it is actually Safari under the hood.
  105. 105 : *
  106. 106 : * @static
  107. 107 : * @const
  108. 108 : * @type {Boolean}
  109. 109 : */
  110. 110 : export const IS_CHROME = !IS_EDGE && ((/Chrome/i).test(USER_AGENT) || (/CriOS/i).test(USER_AGENT));
  111. 111 :
  112. 112 : /**
  113. 113 : * The detected Google Chrome version - or `null`.
  114. 114 : *
  115. 115 : * @static
  116. 116 : * @const
  117. 117 : * @type {number|null}
  118. 118 : */
  119. 119 : export const CHROME_VERSION = (function() {
  120. 120 : const match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
  121. 121 :
  122. 122 : if (match && match[2]) {
  123. 123 : return parseFloat(match[2]);
  124. 124 : }
  125. 125 : return null;
  126. 126 : }());
  127. 127 :
  128. 128 : /**
  129. 129 : * The detected Internet Explorer version - or `null`.
  130. 130 : *
  131. 131 : * @static
  132. 132 : * @const
  133. 133 : * @type {number|null}
  134. 134 : */
  135. 135 : export const IE_VERSION = (function() {
  136. 136 : const result = (/MSIE\s(\d+)\.\d/).exec(USER_AGENT);
  137. 137 : let version = result && parseFloat(result[1]);
  138. 138 :
  139. 139 : if (!version && (/Trident\/7.0/i).test(USER_AGENT) && (/rv:11.0/).test(USER_AGENT)) {
  140. 140 : // IE 11 has a different user agent string than other IE versions
  141. 141 : version = 11.0;
  142. 142 : }
  143. 143 :
  144. 144 : return version;
  145. 145 : }());
  146. 146 :
  147. 147 : /**
  148. 148 : * Whether or not this is desktop Safari.
  149. 149 : *
  150. 150 : * @static
  151. 151 : * @const
  152. 152 : * @type {Boolean}
  153. 153 : */
  154. 154 : export const IS_SAFARI = (/Safari/i).test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
  155. 155 :
  156. 156 : /**
  157. 157 : * Whether or not this is a Windows machine.
  158. 158 : *
  159. 159 : * @static
  160. 160 : * @const
  161. 161 : * @type {Boolean}
  162. 162 : */
  163. 163 : export const IS_WINDOWS = (/Windows/i).test(USER_AGENT);
  164. 164 :
  165. 165 : /**
  166. 166 : * Whether or not this device is touch-enabled.
  167. 167 : *
  168. 168 : * @static
  169. 169 : * @const
  170. 170 : * @type {Boolean}
  171. 171 : */
  172. 172 : export const TOUCH_ENABLED = Boolean(Dom.isReal() && (
  173. 173 : 'ontouchstart' in window ||
  174. 174 : window.navigator.maxTouchPoints ||
  175. 175 : window.DocumentTouch && window.document instanceof window.DocumentTouch));
  176. 176 :
  177. 177 : /**
  178. 178 : * Whether or not this device is an iPad.
  179. 179 : *
  180. 180 : * @static
  181. 181 : * @const
  182. 182 : * @type {Boolean}
  183. 183 : */
  184. 184 : export const IS_IPAD = (/iPad/i).test(USER_AGENT) ||
  185. 185 : (IS_SAFARI && TOUCH_ENABLED && !(/iPhone/i).test(USER_AGENT));
  186. 186 :
  187. 187 : /**
  188. 188 : * Whether or not this device is an iPhone.
  189. 189 : *
  190. 190 : * @static
  191. 191 : * @const
  192. 192 : * @type {Boolean}
  193. 193 : */
  194. 194 : // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
  195. 195 : // to identify iPhones, we need to exclude iPads.
  196. 196 : // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
  197. 197 : export const IS_IPHONE = (/iPhone/i).test(USER_AGENT) && !IS_IPAD;
  198. 198 :
  199. 199 : /**
  200. 200 : * Whether or not this is an iOS device.
  201. 201 : *
  202. 202 : * @static
  203. 203 : * @const
  204. 204 : * @type {Boolean}
  205. 205 : */
  206. 206 : export const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
  207. 207 :
  208. 208 : /**
  209. 209 : * Whether or not this is any flavor of Safari - including iOS.
  210. 210 : *
  211. 211 : * @static
  212. 212 : * @const
  213. 213 : * @type {Boolean}
  214. 214 : */
  215. 215 : export const IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;