diff --git a/.flowconfig b/.flowconfig index ec50dfa..9fba8a1 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,4 +1,5 @@ [ignore] +.*/node_modules/hermes-estree .*/node_modules/babel-plugin-flow-runtime .*/node_modules/flow-runtime .*/node_modules/npm diff --git a/src/device.js b/src/device.js index d8837e7..6992592 100644 --- a/src/device.js +++ b/src/device.js @@ -252,6 +252,17 @@ export function isSafari(ua?: string = getUserAgent()): boolean { return /Safari/.test(ua) && !isChrome(ua) && !/Silk|FxiOS|EdgiOS/.test(ua); } +export function isIpadOs(ua?: string = getUserAgent()): boolean { + // Safari on iOS13+ on an iPad will return a useragent that is the same as Safari on MacOS + // Adding the maxTouchPoints will determine that it is a touch device + if (!/iPhone|iPod/.test(ua)) { + if (/iPad/.test(ua) || (isSafari(ua) && navigator.maxTouchPoints >= 1)) { + return true; + } + } + return false; +} + export function isApplePaySupported(): boolean { try { if ( diff --git a/test/tests/device/isiPadOs.js b/test/tests/device/isiPadOs.js new file mode 100644 index 0000000..1a887d4 --- /dev/null +++ b/test/tests/device/isiPadOs.js @@ -0,0 +1,77 @@ +/* @flow */ + +import { isIpadOs } from "../../../src/device"; + +describe("isIpadOs", () => { + beforeEach(() => { + window.navigator = {}; + }); + + it("should return true when userAgent is Safari and has multiple maxTouchPoints", () => { + window.navigator.userAgent = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15"; + // eslint-disable-next-line compat/compat + window.navigator.maxTouchPoints = 5; + const bool = isIpadOs(); + if (!bool) { + throw new Error(`Expected true, got ${JSON.stringify(bool)}`); + } + }); + + it("should return true when userAgent is Firefox and has multiple maxTouchPoints", () => { + window.navigator.userAgent = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15"; + // eslint-disable-next-line compat/compat + window.navigator.maxTouchPoints = 5; + const bool = isIpadOs(); + if (!bool) { + throw new Error(`Expected true, got ${JSON.stringify(bool)}`); + } + }); + + it("should return true when userAgent is Chrome and has multiple maxTouchPoints", () => { + window.navigator.userAgent = + "Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/120.0.6099.119 Mobile/15E148 Safari/604.1"; + // eslint-disable-next-line compat/compat + window.navigator.maxTouchPoints = 5; + const bool = isIpadOs(); + if (!bool) { + throw new Error(`Expected true, got ${JSON.stringify(bool)}`); + } + }); + + it("should return true when userAgent is iPad", () => { + window.navigator.userAgent = "iPad"; + const bool = isIpadOs(); + if (!bool) { + throw new Error(`Expected true, got ${JSON.stringify(bool)}`); + } + }); + + it("should return false when userAgent is Safari but has no touchpoints", () => { + window.navigator.userAgent = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15"; + // eslint-disable-next-line compat/compat + window.navigator.maxTouchPoints = 0; + const bool = isIpadOs(); + if (!bool) { + throw new Error(`Expected false, got ${JSON.stringify(bool)}`); + } + }); + + it("should return false when userAgent contains iPhone", () => { + window.navigator.userAgent = "iPhone"; + const bool = isIpadOs(); + if (!bool) { + throw new Error(`Expected false, got ${JSON.stringify(bool)}`); + } + }); + + it("should return false when userAgent contains iPod", () => { + window.navigator.userAgent = "iPod"; + const bool = isIpadOs(); + if (!bool) { + throw new Error(`Expected false, got ${JSON.stringify(bool)}`); + } + }); +});