There's no method to detect whether your webpage is opened in Safari or in iOS internal browser (In-App aka WebView). Ofcourse, you can detect Instagram or Facebook WebViews because they clearly change userAgent string. But, for example, Telegram or Slack doesn't. This could affect some features that your app would want to use. For example, propose to install to homescreen. You just can't do this inside In-App.
Implement detection algorithm based on checking window.innerHeight property, that actually represents
the vertical height in pixels available to you webapp. This height is slightly greater for In-App compared to Safari (approx. 6 to 9 px).
It also depends on device height, i.e. iPhone model. Knowing the height of all models we can solve the task.
The combination of three components could give you the answer:
— iOS version (affects Safari height)
— screen.height (depend on iPhone model)
— window.innerHeight (depend on the app used - Safari or In-App)
Apparently, it's more easier and reliable to detect if we're inside In-App instead of detecting if we're in Safari, here's why:
— In case we can't be 100% sure whether we're in Safari or In-App, we need to consider that we're in Safari. That's because it's better to show the user incorrect information than not to show anything;
— Safari height depend on iOS version as well as Safari settings (address bar on top/bottom). In-App height is equal through all devices at least in iOS 17, 16, 15;
— There's much more probability that Apple will change something in Safari interface than in In-App interface.
So, let's stick to the following logic:
— check iOS version, screen.height, window.innerHeight;
— if the values matches In-App values from the table, we're inside In-App;
— else we're inside Safari;
— that's why we need only one function detect_inapp(): boolean;
— but! Let's add one more return value (NULL) for the case a) we're not on iOS at all or b) not on iPhone or c) not on Safari or d) iOS version < 15.
function detect_inapp() {
const inapp_data = {"932":[746],"852":[666],"926":[752],"844":[670],"812":[635,641],"667":[559],"896":[725,721],"736":[628],"568":[460]};
const is_ios_supported = !!navigator.userAgent.match(/iPhone OS 15_|iPhone OS 16_|iPhone OS 17_/i);
const is_ios17 = !!navigator.userAgent.match(/iOS 17/i);
const is_safari = !!navigator.userAgent.match(/Safari/i) && !!navigator.userAgent.match(/Mobile/i) && !!navigator.userAgent.match(/Version/i);
const screen_h = screen.height;
const window_h = window.innerHeight;
if(is_ios_supported && is_safari && inapp_data[screen_h].length) {
if(screen_h == 812 && window_h == 635) return is_ios17; // ambiguity case
return inapp_data[screen_h].indexOf(window_h) !== -1;
}
return null;
}
iPhone | iOS | Physical height | Logical height | Safari innerHeight | In-App innerHeight |
---|---|---|---|---|---|
iPhone 15 Pro Max | iOS 17.4 | 2796 px | 932 | 739 | 746 |
iPhone 15 Pro | iOS 17.1 | 2556 px | 852 | 659 | 666 |
iPhone 15 Plus | iOS 17.4 | 2796 px | 932 | 739 | 746 |
iPhone 15 | iOS 17.4 | 2556 px | 852 | 659 | 666 |
iPhone 14 Pro Max | iOS 17.4 | 2778 px | 932 | 739 | 746 |
iPhone 14 Pro | iOS 17.4 | 2556 px | 852 | 659 | 666 |
iPhone 14 Plus | iOS 17.4 | 2778 px | 926 | 745 | 752 |
iPhone 14 | iOS 17.4 | 2532 px | 844 | 663 | 670 |
iPhone 14 | iOS 16.4 | 2532 px | 844 | 664 | 670 |
iPhone 13 Pro Max | iOS 17.4 | 2778 px | 932 | 739 | 746 |
iPhone 13 Pro | iOS 17.4 | 2532 px | 844 | 663 | 670 |
iPhone 13 Pro | iOS 15.5 | 2532 px | 844 | 664 | 670 |
iPhone 13 | iOS 17.4 | 2532 px | 844 | 663 | 670 |
iPhone 13 mini | iOS 17.4 | 2340 px | 812 | 628 | 635 |
iPhone SE (3-gen) | iOS 17.4 | 1334px | 667 | 547 | 559 |
iPhone 12 Pro | iOS 17.4 | 2532 px | 844 | 663 | 670 |
iPhone 12 Pro Max | iOS 17.4 | 2778 px | 926 | 745 | 752 |
iPhone 12 Pro Max | iOS 16.0 | 2778 px | 926 | 746 | 752 |
iPhone 12 mini | iOS 17.4 | 2340 px | 812 | 628 | 635 |
iPhone 12 | iOS 17.4 | 2532 px | 844 | 663 | 670 |
iPhone SE (2-gen) | iOS 17.4 | 1334px | 667 | 547 | 559 |
iPhone 11 Pro Max | iOS 17.4 | 2688 px | 896 | 718 | 725 |
iPhone 11 Pro | iOS 17.4 | 2436 px | 812 | 634 | 641 |
iPhone 11 | iOS 17.4 | 1792 px | 896 | 714 | 721 |
iPhone XS | iOS 17.4 | 2436 px | 812 | 634 | 641 |
iPhone XS | iOS 16.6 | 2436 px | 812 | 635 | 641 |
iPhone XS | iOS 15.5 | 2436 px | 812 | 635 | 641 |
iPhone XS Max | iOS 17.4 | 2688 px | 896 | 718 | 725 |
iPhone XR | iOS 17.3 top | 1792 px | 896 | 714 | 721 |
iPhone X | iOS 17.4 | 2436 px | 812 | 634 | 641 |
iPhone 8 Plus | iOS 16.7 top | 1920 px | 736 | 620 | 628 |
iPhone 8 | iOS 16.4 | 1334 px | 667 | 548 | 559 |
iPhone 7 Plus | iOS 15.5 | 1920 px | 736 | 617 | 628 |
iPhone 7 | iOS 15.5 | 1334 px | 667 | 548 | 559 |
iPhone 6s Plus | iOS 15.5 | 1920 px | 736 | 617 | 628 |
iPhone 6s | iOS 15.8 | 1334 px | 667 | 548 | 559 |
iPhone 6 Plus | iOS 15.5 | 1920 px | 736 | 617 | 628 |
iPhone 6 | iOS 14 | 1334 px | 667 | ||
iPhone SE (1-gen) | iOS 15.5 | 1136 px | 568 | 449 | 460 |
{"932":[746],"852":[666],"926":[752],"844":[670],"812":[635,641],"667":[559],"896":[725,721],"736":[628],"568":[460]}
As you see from pair analytics, there's one amiguity case (intersection) between iPhone XS @ iOS 16/15 and iPhone 12/13 mini (812 screen height).
For these devices window.innerHeight = 635 both in Safari and in In-App. We'll follow this simple logic to increase the chance
of right detection:
— If a user has more latest model of iPhone (12 mini or 13 mini), there are more chances of latest iOS (17) installed;
— Safari height = 635 only on iPhone XS @ iOS 16/15. In case iOS is 17, the height will be 634, which is perfect (no ambiguity in this case);
— So, with height = 635, there are more chances that this is In-App, unless iOS version is strictly lower than 17.
There are acutally two types of "In-App":
1) Safari native In-App — the one we've explored here;
2) Non-native In-App — the one you can find in Instagram, Facebook, Viber and some other apps. It still uses Safari as an engine but the UI looks differently.
According to my research, you can detect second type by userAgent string in all cases (see below). So, there's no need to use the above script for second case, only for the first one.
Safari
Array
(
[932] => Array
(
[739] => iPhone 15 Pro Max @ iOS 17.4 iPhone 15 Plus @ iOS 17.4 iPhone 14 Pro Max @ iOS 17.4 iPhone 13 Pro Max @ iOS 17.4
)
[852] => Array
(
[659] => iPhone 15 Pro @ iOS 17.1 iPhone 15 @ iOS 17.4 iPhone 14 Pro @ iOS 17.4
)
[926] => Array
(
[745] => iPhone 14 Plus @ iOS 17.4 iPhone 12 Pro Max @ iOS 17.4
[746] => iPhone 12 Pro Max @ iOS 16.0
)
[844] => Array
(
[663] => iPhone 14 @ iOS 17.4 iPhone 13 Pro @ iOS 17.4 iPhone 13 @ iOS 17.4 iPhone 12 Pro @ iOS 17.4 iPhone 12 @ iOS 17.4
[664] => iPhone 14 @ iOS 16.4 iPhone 13 Pro @ iOS 15.5
)
[812] => Array
(
[628] => iPhone 13 mini @ iOS 17.4 iPhone 12 mini @ iOS 17.4
[634] => iPhone 11 Pro @ iOS 17.4 iPhone XS @ iOS 17.4 iPhone X @ iOS 17.4
[635] => iPhone XS @ iOS 16.6 iPhone XS @ iOS 15.5
)
[667] => Array
(
[547] => iPhone SE (3-gen) @ iOS 17.4 iPhone SE (2-gen) @ iOS 17.4
[548] => iPhone 8 @ iOS 16.4 iPhone 7 @ iOS 15.5 iPhone 6s @ iOS 15.8
)
[896] => Array
(
[718] => iPhone 11 Pro Max @ iOS 17.4 iPhone XS Max @ iOS 17.4
[714] => iPhone 11 @ iOS 17.4 iPhone XR @ iOS 17.3 top
)
[736] => Array
(
[620] => iPhone 8 Plus @ iOS 16.7 top
[617] => iPhone 7 Plus @ iOS 15.5 iPhone 6s Plus @ iOS 15.5 iPhone 6 Plus @ iOS 15.5
)
[568] => Array
(
[449] => iPhone SE (1-gen) @ iOS 15.5
)
)
In-App
Array
(
[932] => Array
(
[746] => iPhone 15 Pro Max @ iOS 17.4 iPhone 15 Plus @ iOS 17.4 iPhone 14 Pro Max @ iOS 17.4 iPhone 13 Pro Max @ iOS 17.4
)
[852] => Array
(
[666] => iPhone 15 Pro @ iOS 17.1 iPhone 15 @ iOS 17.4 iPhone 14 Pro @ iOS 17.4
)
[926] => Array
(
[752] => iPhone 14 Plus @ iOS 17.4 iPhone 12 Pro Max @ iOS 17.4 iPhone 12 Pro Max @ iOS 16.0
)
[844] => Array
(
[670] => iPhone 14 @ iOS 17.4 iPhone 14 @ iOS 16.4 iPhone 13 Pro @ iOS 17.4 iPhone 13 Pro @ iOS 15.5 iPhone 13 @ iOS 17.4 iPhone 12 Pro @ iOS 17.4 iPhone 12 @ iOS 17.4
)
[812] => Array
(
[635] => iPhone 13 mini @ iOS 17.4 iPhone 12 mini @ iOS 17.4
[641] => iPhone 11 Pro @ iOS 17.4 iPhone XS @ iOS 17.4 iPhone XS @ iOS 16.6 iPhone XS @ iOS 15.5 iPhone X @ iOS 17.4
)
[667] => Array
(
[559] => iPhone SE (3-gen) @ iOS 17.4 iPhone SE (2-gen) @ iOS 17.4 iPhone 8 @ iOS 16.4 iPhone 7 @ iOS 15.5 iPhone 6s @ iOS 15.8
)
[896] => Array
(
[725] => iPhone 11 Pro Max @ iOS 17.4 iPhone XS Max @ iOS 17.4
[721] => iPhone 11 @ iOS 17.4 iPhone XR @ iOS 17.3 top
)
[736] => Array
(
[628] => iPhone 8 Plus @ iOS 16.7 top iPhone 7 Plus @ iOS 15.5 iPhone 6s Plus @ iOS 15.5 iPhone 6 Plus @ iOS 15.5
)
[568] => Array
(
[460] => iPhone SE (1-gen) @ iOS 15.5
)
)
// Home Screen
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 && navigator.standalone = true
// Safari
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1
// Telegram
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1
// Slack
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1
// Brave
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1
// Opera
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 OPT/4.6.3
// Chrome
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/123.0.6312.52 Mobile/15E148 Safari/604.1
// Firefox
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/125.1 Mobile/15E148 Safari/605.1.15
// Firefox Focus
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/125 Mobile/15E148 Version/15.0
// Edge
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/123.0.2420.104 Version/16.0 Mobile/15E148 Safari/604.1
// Instagram
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/20G81 Instagram 327.1.6.30.88 (iPhone11,2; iOS 16_6_1; en_UA; en; scale=3.00; 1125x2436; 588348860)
// Facebook
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBAV/453.1.0.40.112;FBBV/586716179;FBDV/iPhone11,2;FBMD/iPhone;FBSN/iOS;FBSV/16.6.1;FBSS/3;FBCR/;FBID/phone;FBLC/en;FBOP/80]
// LinkedIn
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [LinkedInApp]/9.29.5227
// Viber
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)