Asked  7 Months ago    Answers:  5   Viewed   50 times

I developed an app for iOS and Android which accesses an HTML file from my webserver using the in-app browser (Webview).

I don't want that a user can access this file without using the app. Is there a possibility to detect, if the user is accessing the file with the app or directly via a browser on this smartphone / tablet / computer? I think that a solution with PHP is much better, because Javascript can be switched off. At least Google Analytics can differentiate between Safari and Safari (in-app). It should work with every version of iOS and Android.

Thanks for your help.


After many attempts I finally found a working solution for me!

iOS: You can detect the difference between Safari and the in-app browser using the user agent. Probably there's a nicer solution, but it works.

// Safari (in-app)
if ((strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile/') !== false) && (strpos($_SERVER['HTTP_USER_AGENT'], 'Safari/') == false) {
    echo 'Safari (in-app)';

Android: The package name from the app is stored in the PHP variable $_SERVER['HTTP_X_REQUESTED_WITH'].

// Android (in-app)
    echo 'Android (in-app)';

As Tim van Elsloo already noted HTTP headers can be faked and this is not absolutely secure.



I'm not sure about Android, but when you're using the iOS SDK's UIWebView, it sends the name and version of your app as part of the user agent (YourApp/1.0).

You can then use PHP to check if your in-app webview is being used or not:

if (strpos($_SERVER['HTTP_USER_AGENT'], 'YourApp/') !== false)

I think Android does something similar as well.

Wednesday, March 31, 2021
answered 7 Months ago

Detection through class property $isEmptyElement does also not work because the tag has attributes.

That's simply wrong. An empty element with attributes is still empty and $isEmptyElement will reflect that. The problem with your code is that you test $isEmptyElement after moving to the attributes. This will change the current node to an attribute node which isn't an empty element. Something like the following should work:

        $isEmpty = $xmlReader->isEmptyElement;
        if ($xmlReader->hasAttributes) {
            while ($xmlReader->moveToNextAttribute()) {
        if ($isEmpty) {

Or, alternatively:

        if ($xmlReader->hasAttributes) {
            while ($xmlReader->moveToNextAttribute()) {
        if ($xmlReader->isEmptyElement) {
Wednesday, March 31, 2021
answered 7 Months ago

Your date string is not in a format specified to work with new Date. The only format in the spec is a simplified version of ISO-8601, but that was only added in ES5 and so support may be touch and go. Your string isn't in that format, but it's really close.

If you change the space to a T, you'll be in spec:

var dateString = "2015-12-31 00:00:00";
var d = new Date(dateString.replace(' ', 'T'));

(I'm assuming you're not actually using a string literal, hence the replace call.)

Note that there was an error in the ES5 specification which was corrected in ES2015 (ES6): What happens when there's no timezone indicator on the string. In ISO-8601, no indicator means "local time," but the ES5 specification said that it defaults to Z (UTC —loosely, GMT). They fixed it in the ES2015 specification, but unfortunately some JavaScript engines followed the ES5 specification and others followed ISO-8601 (and now ES2015). But wait, it gets worse: Using local time for strings that just contained dates proved problematic for existing code (and TC39 really, really tries not to break existing code), so in ES2016 they had to change it again to say: If it's a date-only string, interpret it as UTC, but if it's a date/time string, interpret it as local time.

So with all of that fun and games, for solid cross-browser support, you need to include a timezone indicator, because otherwise you don't know whether it will be interpreted as UTC or local time. You're allowed to use Z for GMT or +/- followed by HH:MM to give an offset. (Abbreviations like CST are not allowed, as there's no standard for them.)

If they don't support that yet, there's near universal support for YYYY/MM/DD HH:MM:SS (interpreted as local time), even though it's not specified. So:

var dateString = "2015-12-31 00:00:00";
var d = new Date(dateString.replace(/-/g, '/'));
Thursday, June 17, 2021
answered 4 Months ago

Current Xcode versions come only with the latest iOS SDK (which is a pity, but we cannot change it). Also Apple recommends to always use the latest SDK for building apps, and setting the "Deployment Target" to the minimum version that you plan to support.

You can copy an SDK (e.g. iPhoneOS5.1.sdk) from an older Xcode to


and it will appear in the build settings. But you might run into problems later with this configuration.

Sunday, July 18, 2021
answered 3 Months ago

You are passing the wrong $data value into openssl_verify(). This value should be the full JSON string you get from Google Play, not the purchase token inside it. It is important that the JSON string is untouched, as even if you were to add a space or newlines to it, the signature would no longer work.

All you need to do in your code above is to change this line:

$result = openssl_verify($data["purchaseToken"], base64_decode($signature), $key);


$result = openssl_verify($data, base64_decode($signature), $key);

And you should get a success, assuming you're using the correct public key and the JSON purchase string is valid. I'm pretty sure your JSON string is not the original string from Google however, as the ones from Google do not contain newlines. It will be one long line of JSON text. Make sure that's what you are passing to openssl_verify().

Monday, August 23, 2021
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :