Seth
Seth

Reputation: 78

Better way to get AppleWebKit/Mobile/Safari version numbers for User-Agent string?

The iPhone app I'm working on uses WKWebView, but (my client requires that…) it must have a custom user-agent.

We'd like the user-agent string to mimic Safari's, which looks something like this:

Mozilla/5.0 (iPhone; CPU iPhone OS 9_0_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13A452 Safari/601.1

I know how to set the user-agent string. The easiest is just to set the UserAgent in standard user defaults.

[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": userAgent}];

The problem I'm having is actually with coming up with the version numbers Apple used in Safari's User-Agent. Specifically, the AppleWebKit version, that code immediately after Mobile (13A452), and the Safari version number.

The only solution I've come up with involves

  1. instantiating a WKWebView
  2. loading a dummy page from a string (else step 3 fails)
  3. In the didFinishNavigation delegate call, evaluate a javascript to return the current user-agent
  4. pull the UA apart to extract those version numbers
  5. build a new string similar to Safari's but with the app's own name/version at the end and put the Safari version in parens with a "like", to result in this:

Mozilla/5.0 (iPhone; CPU iPhone OS 9_0_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13A452 (like Safari/601.1) TheAppName/1.12

(The two iPhone versions are found via provided system calls, that's not a problem.)

Is there a better way? (Obviously, steps 3-4 could be replaced with extracting the version numbers via the javascript, but that just shifts things around.)

In short, is there any way to get those AppleWebKit/Mobile/Safari version numbers OTHER than via asking a webview instance for its own user-agent string?

I can provide some sample code or even a sample project if you can't follow the steps above, but my intent should be clear.

Upvotes: 4

Views: 4634

Answers (1)

danielpunkass
danielpunkass

Reputation: 17587

I think your best bet is to stick with something along the lines of what you're doing: it's the safest way to derive a user agent string that strongly resembles the system default one. Even though there is an overhead to creating a web view, loading a string, and querying for the user agent, it's got to be miniscule in the big scheme of things.

On the other hand, the data you're interested in should all be imminently "scrapable" from the system's own frameworks. Bear in mind that although iOS processes are sandboxed, this is primarily to prevent them accessing other apps' data. The design of sandboxing on both iOS and Mac leaves application processes able to read liberally from the system's files, which makes sense because these files support the expected loading of dynamic libraries and resource data into memory on behalf of applications.

So, for example add a line like this to your applicationDidFinishLaunching method:

NSLog(@"System frameworks: %@", [[NSFileManager defaultManager] directoryContentsAtPath:@"/System/Library/Frameworks/" excludingDirectories:NO]);

You'll see a long list of all the system frameworks available just where you'd expect them. To get right at the WebKit framework's CFBundleVersion, for example:

NSLog(@"%@", [[NSDictionary dictionaryWithContentsOfFile:@"/System/Library/Frameworks/WebKit.framework/Info.plist"] objectForKey:@"CFBundleVersion"]);

But of course NSBundle abstracts this file reading away and lets you get at the likely already in-memory dictionary directly:

NSLog(@"%@", [[[NSBundle bundleForClass:[[WKWebView class] infoDictionary] objectForKey:@"CFBundleVersion"]);

You could use similar tricks to either access attributes via NSBundle's infoDictionaruy, or else read data directly from the "disk" on your customers' phone.

Upvotes: 3

Related Questions