WKWebView CORS Solution
Send/Proxy Network Requests With Native Code In An Asynchronous Manner Using Promise
Background
I was facing the CORS problem while building a web-based iOS app using WKWebView. The app needs to send API requests using JavaScript inside the WebView, but there is no workaround to disable the CORS protection of WKWebView but listen to its complaint — Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at...
The easiest way to solve the problem is to modify the backend code to include “Access-Control-Allow-Origin” header in the response. However, I have no control of the backend, so that the only possible solution is to proxy the API request using native code instead of doing the request directly in the WebView (I don’t want to fallback to UIWebView).
The challenge for this solution is to bridge the JavaScript code and the native code (ObjC/Swift) in an asynchronous manner, and there are two steps to conquer it.
First Step: Allow JavaScript to call native code
In the native side, I provide an interface for JavaScript to invoke by writing the code below:
After that, I am able to use JavaScript to invoke native code to send network requests like this (I will explain the use of uuid
later):
const uuidv4 = require('uuid/v4');
const uuid = uuidv4();window.webkit.messageHandlers.native.postMessage({
type: 'SEND_HTTP_REQUEST',
url,
body,
method: 'POST',
uuid,
accept: 'application/json',
content_type: 'application/x-www-form-urlencoded; charset=utf-8'
});
Second Step: Get the result back from the native code
After the first step, I am already able to proxy the API request with native code. However, I have to find a way to get the result back as the postMessage
method of the messageHandler
is a one-way communication, which means the result will not be returned after that method call.
The workaround is to expose a global callback function in JavaScript side for native code to invoke after it gets the result. Also, to allow multiple requests happen in parallel and match a result to a request call, I use uuid
.
Additionally, I would like to use Promise
to handle the API requests, so that the code to expose the callback function looks like this:
By doing so, I have a pool of promises
(actually, they are resolve-reject
pairs) indexed by uuid
stored in window.jsCallbackBridge.promises
, and the native code can invoke window.jsCallbackBridge.resolvePromise
after it gets the result.
The code for dealing with results in the native side looks like this:
The lines above fill the blanks in the first step (marked as //TODO:
).
Note: I use
base64-encoded
data when passing the result from native side to JavaScript. The reason is that I expect the result data to bejson
and I need to parse it later, but the data in thejson
may contain special characters in plain text which may cause problems while parsing.
In the end, I can create the asynchronous function returning Promise
to send the API request:
I might be able to provide a working example later when I am free, thanks for reading.