I'm working on an Apple TV app using TVMLKit. My app's JavaScript code tries to send an HTTP request to a server using XMLHttpRequest
. The server is expecting a specific user agent, so I tried this:
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.setRequestHeader("User-Agent", "MyApp");
request.send();
The server receives a different User-Agent header:
User-Agent: <Projectname>/1 CFNetwork/758.1.6 Darwin/15.0.0
If I change the header name to something different, it shows up in the request headers. I guess Apple is replacing the User-Agent field right before sending the request. Is there a way to prevent this?
After spending two days on investigating this question I've came to solution with creating native GET
and POST
methods in swift end exposing them to javascript. This isn't best solution but still I want to share it. Maybe it could help someone.
Here how it works
First we need to install Alamofire library. We will use it for creating requests.
Readme on github has all instructions you need to install it
After installing Alamofire we need to import it in AppDelegate.swift
import Alamofire
Then we need to create function in app controller (AppDelegate.swift
) that will expose methods to javascript
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext)
{
let requests = [String : AnyObject]()
let get: @convention(block) (String, String, [String : String]?) -> Void = { (cId:String, url:String, headers:[String : String]?) in
Alamofire.request(.GET, url, headers: headers)
.responseString { response in
jsContext.evaluateScript("requests." + cId + "(" + response.result.value! + ")")
}
}
let post: @convention(block) (String, String, [String : AnyObject]?, [String : String]?) -> Void = { (cId:String, url:String, parameters:[String : AnyObject]?, headers:[String : String]?) in
Alamofire.request(.POST, url, parameters: parameters, headers: headers)
.responseString { response in
jsContext.evaluateScript("requests." + cId + "(" + response.result.value! + ")")
}
}
jsContext.setObject(requests, forKeyedSubscript: "requests");
jsContext.setObject(unsafeBitCast(get, AnyObject.self), forKeyedSubscript: "nativeGET");
jsContext.setObject(unsafeBitCast(post, AnyObject.self), forKeyedSubscript: "nativePOST");
}
Full code of AppDelegate.swift
you can find here
All set! Now we have access to nativeGET
and nativePOST
functions from javascript.
The last thing is to make requests and retrieve responses. I haven't understand how to make callback executions in swift so I've used jsonp
approach using runtime generated functions and passing their names to native functions.
Here how it looks in javascript
export function get(url, headers = {}) {
return new Promise((resolve) => {
const cId = `get${Date.now()}`;
requests[cId] = response => {
delete requests[cId];
resolve(response);
}
nativeGET(cId, url, headers);
});
}
export function post(url, parameters = {}, headers = {}) {
return new Promise((resolve) => {
const cId = `post${Date.now()}`;
requests[cId] = response => {
delete requests[cId];
resolve(response);
}
nativePOST(cId, url, parameters, headers);
});
}
The code above is written in ES6 and you'll need to include Promise
polifill in your TVJS app.
Now we can make GET
and POST
requests applying any header we need
post('http://example.com/', {
login: 'xxx',
password: 'yyy'
}, {
'User-Agent': 'My custom User-Agent'
})