Making HTTP calls from mobile apps

OK I finally got the @dfinity/agent JS library working with React Native for both iOS and Android (at least for the emulators).

The process is quite involved, so it’s definitely not a straightforward integration like it is for web.

There are two core issues with using the agent JS library with RN:

  1. RN doesn’t provide direct access to the underlying byte stream for HTTP responses. Since canister responses are CBOR-encoded and therefore binary, reading those responses in JS land isn’t supported by RN out of the box.
  2. The agent JS library relies on a bunch of JS APIs that aren’t available in the default JS implementation shipped with RN apps. This is complicated by the fact that RN apps running on iOS actually use a different JS engine than RN apps running on Android.

Problem 1 can be solved by replacing the default fetch() implementation with the one offered by react-native-fetch-api. (rn-fetch-blob does not work.) You’ll then need to patch both that fetch library as well as the @dfinity/agent in your node_modules to configure it to work correctly, since it won’t work off the shelf. Basically, you need to make the fetch library pass base64-encoded bytes instead of blobs over the RN bridge.

Problem 2 is solved once these JS APIs are made available:

  • TextEncoder
  • ReadableStream
  • Buffer
  • BigInt
  • WebAssembly

The first two can be polyfilled using react-native-polyfill-globals, which incidentally is also the same library that polyfills the Fetch API implemented by the aforementioned react-native-fetch-api.

Buffer can be polyfilled using buffer and an explicit global.Buffer = buffer assignment.

The last two are the trickiest and the biggest pain in the ass. You could polyfill them using standard JS libraries like big-integer and webassemblyjs, but it won’t work because they’ll be too slow (or throw some cryptic errors). I’ve tried a bunch of polyfill libraries and none were performant enough for the BLS signature validation that is necessary for processing IC canister responses. (The one exception is JSBI as a polyfill for BigInt, which ran fast but required me to literally rewrite huge portions of the @dfinity/candid source. So not technically a drop-in polyfill.)

One thing I forgot to mention is that both BigInt and WebAssembly are supported on iOS but are NOT supported on Android. This is because RN uses the JavaScriptCore that ships with iOS devices, but RN bundles an older version of JavaScriptCore in the app itself for Android devices. And Apple pretty diligently updates their JavaScriptCore with new features on a regular basis. To get around this, I bundled a relatively new version of v8 instead of RN’s default JSC for Android, since v8 supports BigInt and WebAssembly if you use the JIT-enabled v8. Here is the catch: it executes wasm too slowly. The BLS signature validation wasm module hung for a few minutes before I killed it. I don’t know why v8 executed it slowly but iOS’s JSC was able to run it fast (under a second). To get around this, I manually converted the wasm bytes into JS code using wasm2js and patched the @dfinity/agent library again to run that JS code in lieu of wasm for validating BLS signatures. This reduced the execution time to under a second for Android, matching iOS. Now, update calls takes <5 seconds on both of my emulators, which I can work with.

Feel free to DM me if you’d like more details on how to get a RN mobile app set up to communicate with IC canisters. Hope this helps (if it hasn’t scared you away already).

15 Likes