We're Still Open!

We're still open for business during COVID-19. Contact us with any questions or concerns at contact@imageengine.io.

Documentation

Native Apps: Use meaningful User Agents

Optimizing images is equally important for apps. ImageEngine relies heavily on device detection. For this reason, it is important that applications (fully native or hybrids) provide meaningful User-Agent strings in their HTTP requests. For hybrid apps this is less of a challenge, but for fully native apps this issue must be addressed in order to avoid the default values.

The specification is clear:

The “User-Agent” header field contains information about the user agent originating the request, which is often used by servers to help identify the scope of reported interoperability problems, to work around or tailor responses to avoid particular user agent limitations, and for analytics regarding browser or operating system use.

The specification goes into more detail and concludes that User-Agents should consist of one or more product identifiers and optional versions. By convention, the product identifiers are listed in decreasing order of their significance for identifying the User-Agent software.

See these blog posts for more background:

Let’s dive right in and see how to compose meaningful User-Agent strings for native apps in the different platforms:

# Native Apps

We distinguish between fully native apps and apps based on a “web view” or browser component. Web view based apps, sometimes called “hybrid apps”, display web content inside the app.

# Native Android App

Based on the specification above, the template we want for the User-Agent of our app is:

<AppName>/<version> Dalvik/<version> (Linux; U; Android <android version>; <device ID> Build/<buildtag>)

For example:

Myapp/1 Dalvik/2.1.0 (Linux; U; Android 6.0.1; vivo 1610 Build/MMB29M)

The significant part here is Myapp/1 which identifies the native app and the version of the app. This is the most significant. The rest of the string identifies the underlying software. In Android, it is relatively easy to compose a custom User-Agent string. Here is some sample code:

URLConnection cn = new URL("http://....").openConnection();
cn.setRequestProperty("User-agent", "Myapp/1 " + System.getProperty("http.agent"));
cn.connect();

If you’re using the popular Picasso library, then the composition is similar. However, the actual request is made in a slightly different way. Have a look a this example app built with Picasso on Github.

# Native iOS App

User-Agents for iOS are slightly more complex due to the fact that we have to account for both Objective-C and Swift. A default User-Agent in iOS may look something like this:

MyApp/1 CFNetwork/808.3 Darwin/16.3.0

In iOS a component called “CFNetwork” handles the network communications. Also involved is the UNIX version iOS is built on: “Darwin”. Both of these are mentioned in the default User-Agent. In addition there may be a custom app identifier added. So not very meaningful.

In order to construct a more meaningful User-Agent we need more information added to it, like so:

<AppName/<version> <iDevice platform><Apple model identifier>  iOS/<OS version> CFNetwork/<version> Darwin/<version>

For example:

MyApp/1 iPhone5,2 iOS/10_1 CFNetwork/808.3 Darwin/16.3.0

The main challenge in constructing the User-Agent in iOS is to collect all the parameters needed. For example, it is not obvious how to find the CFNetwork- and Darwin values. We’ve written an example app in Objective-C and Swift with the functionality needed to gather all bits that go into the User-Agent string. Feel free to use it. Once the bits are collected, just call the function to compose the User-Agent before you make a request:

# OBJECTIVE-C

NSString* userAgent = getUAString();
NSURL* url = [NSURL URLWithString:@""];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setValue:userAgent forHTTPHeaderField:@"User-Agent"];

For full code of the getUAString() function, see Github.

# SWIFT

let userAgent = UAString()
if let url = NSURL(string: "") {
 let request = NSMutableURLRequest(url: url as URL)
 request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
 //...
}

For full code of the getUAString() function, see Github.

# Native Windows Phone

Even if not as popular as Android and iOS, Windows Phone is also worth mentioning. The semantic of the User-Agent is similar:

<app name>/<version> Windows Phone/<OS version> <device brand name>; <device model name>)

For example:

MyApp/1 Windows Phone/10.0 Microsoft; Lumia 550

You can get the device brand name and model name from the EasClientDeviceInformation class with the SystemManufacturer and SystemProductName properties. The OS version can be extracted from DeviceFamilyVersion property in the AnalyticsVersionInfo class.

# Webview based Apps

How should the User-Agent be formed? In the following we’ll demonstrate how to compose the User-Agent for webview based applications. The base of the User-Agent is the default User-Agent as provided by the platforms SDK, and then app and device information is appended. This is the defacto standard as it has developed in the wild:

<Stock User-Agent> WebViewApp <AppName>/<Version>

Note the WebViewApp identifier before the app name and version.

Let’s look at how to compose the correct User-Agent for different platforms and operating systems. For this purpose, we’ll construct User-Agents for a webview app called Foo, version 1.

# Hybrid Android apps

For our app running on a Google Pixel, this is how our User-Agent could look:

Mozilla/5.0 (Linux; Android 9; Pixel Build/PQ3A.190801.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.73 Mobile Safari/537.36 WebViewApp Foo/1

The code to construct the User-Agent is:

String ua = new WebView(this).getSettings().getUserAgentString();
webviewToUse.getSettings().setUserAgentString(ua + " WebViewApp Foo/1");

# Hybrid iOS Apps

For iOS it is slightly more complicated, but still not difficult. We still want to append app name and version, but in addition we want to append the specific iPhone model at the end:

Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 WebViewApp Foo/1 iPhone11,8

Above is the User-Agent of Foo version 1 running on an iPhone iPhone XR (iPhone11,8).

The examples below use the deviceName() function to get the model information. See github for full code examples.

# Objective C

In Objective C, we retrieve the user-agent by creating a webview:

UIWebView* webView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString* ua = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];

Then we modify the extracted User-Agent via the AppDelegate file:

NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys: ua + @" WebViewApp Foo/1 " + [self deviceName], @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];

# Swift

In Swift we do it like this:

let userAgent = UIWebView().stringByEvaluatingJavaScript(from: "navigator.userAgent")! + "WebViewApp Foo/1 " + getName();
UserDefaults.standard.register(defaults: ["UserAgent" : userAgent])

# Windows Phone

Like for Android, we only need to append the app name and version since the device info is already in the default User-Agent.

First we extract the User-Agent by creating a webview:

public static string UserAgent;

public void LoadUserAgent() {
	string Html = @"<html><html> <head> <script language=""javascript"" type=""text/javascript"">
                function notifyUserAgent() { window.external.notify(navigator.userAgent); } </script> </head>
                <body onload=""notifyUserAgent();""></body> </html>
		";

	var tempWebBrowser = new Microsoft.Phone.Controls.WebBrowser();
	tempWebBrowser.IsScriptEnabled = true;
	tempWebBrowser.Visibility = System.Windows.Visibility.Collapsed;
	tempWebBrowser.Loaded += (sender, args) => tempWebBrowser.NavigateToString(Html);
	tempWebBrowser.ScriptNotify += (sender, args) =>
	{
		UserAgent = args.Value;
		LayoutRoot.Children.Remove(tempWebBrowser);
	};
	LayoutRoot.Children.Add(tempWebBrowser);
}

Now, append " WebViewApp Foo/1".

UserAgent = UserAgent + " WebViewApp Foo/1";

Then have the webview use your custom User-Agent:

private void webView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
    webview.Stop();
    HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(args.Uri.Host));
    httpRequestMessage.Headers.Append("User-Agent", "UserAgent");
    webView.NavigateWithHttpRequestMessage(httpRequestMessage);
}

The Foo/1 app on a Lumia could look like this:

Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 535 Dual SIM) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.0.0 Mobile Safari/537.36 Edge/13.0 WebViewApp Foo/1