The URL
API needs to be polyfilled otherwise an error is thrown as many methods are not implemented in React Native and thus not conformant with the spec. The exact same goes for URLSearchParams
API as only a few methods are implemented as well.
To work around these issues, we used the implementations provided by whatwg-url
polyfill. whatwg-url
depends on two core Node.js modules, punycode
and util
, which are not available in React Native environment. We have shimed them with alternative implementations:
util
: https://github.com/browserify/node-util.whatwg-url
attempts to requireTextEncoder
andTextDecoder
APIs fromnode-util
and if such objects are not found, it fallbacks toglobal.TextEncoder
andglobal.TextDecoder
. Sincenode-util
does not provide encoding APIs as in Node.js, we have to polyfill them withtext-encoding
instead.punycode
: https://github.com/bestiejs/punycode.js
whatwg-url
also depends on standard built-in objects such as BigInt
and SharedArrayBuffer
via webidl-conversions which are not available in React Native as well. While we can polyfill BigInt
, SharedArrayBuffer
is little more tricker. If IPFS does not need SharedArrayBuffer
, we can probably come up with a way to cheat the type checks out by, e.g., using ArrayBuffer
in its place. Right now we're using react-native-url-polyfill
, which is an optimized URL standard-compliant implementation for React Native based on whatwg-url-without-unicode
. However, it comes without Unicode support thus it's more light in size.
Our experience reveals both whatwg-url
and react-native-url-polyfill
appear to not be so comformant with the spec as they claim to be. When the base
argument of the URL
constructor is an empty string and the url
argument is an absolute URL, the constructor is throwing an error indicating base
is invalid. As per spec, when url
is absolute the provided base
should be ignored. However, it is not implemented this way.
Another issue we detected concerns the normalization logic for the input options of the IPFS HTTP client factory. It seems there is a case where options
is a boxed string and an error is thrown by the URL
constructor because options.url
is undefined.
Runs just fine after setting up the client successfully.
Works with the hacks below.
Async generators have to be transformed as they are not available in JSC. The Babel plugin @babel/plugin-proposal-async-generator-functions takes care of that.
The browser implementation of IPFS for multipart request uses the FormData
API underneath. However, the implementation provided by React Native is broken as it only implements append
and getParts
methods. The latter is not even part of the FormData
spec but React Native uses it internally to construct a HTTP request and pass it down to iOS and Android implementations of the networking layer. IPFS uses the set
method which is causing the call to .add
to error out. To fix this issue, we have patched FormData
to implement the missing method.
Another issue we uncovered is that FormData
does not know how to handle Blob
objects. Instead, it accepts a blob-like object which contains a uri
property that indicates where a native file can be found in the device's disk. However, Blob
s do not have this property, so they have to be handled differently by FormData
. In React Native, Blob
s are created and managed in the native side. In JS land, it's only possible to hold an opaque reference to a given blob. However, each blob has a blobId
and an URI can be created based off it with the following scheme: blob:<Blob.data.blobId>?offset=<Blob.data.offset>&size=<Blob.data.size>
. URL.createObjectURL
static method does just that, which is part of the spec and can be found in React Native's implementation of the URL API. The same method can be found in the package which provides the URL polyfill we're using. Given a blobId
, all we have to do is patch FormData
to generate the URI for all Blob
parts and return it in the getParts
method, as expected by its interface. With this patch in place, HTTP requests that send FormData
with Blobs
can get through and reach their destination with complete and intact data.
ReadableStream
is not supported in React Native so request and response body streaming does not work. Furthermore, React Native's Fetch API implementation provided by whatwg-fetch
does not support ReadableStream
s either. As such, we aren't yet able to handle responses as response.body
is never defined and, consequenly, getReader()
fails. To workaround this issue, we have created a brute force implementation of ReadableStream
and added it to the whatwg-fetch
source for the time being. Note that, however, this implementation is too naive and does not offer true streaming. All it does it get the response body with Response.arrayBuffer()
and wrap it with a Uint8Array
as per ReadableStreamDefaultReader.read
spec. Thus, the whole response body is returned in a single chunk. See JakeChampion/fetch#746 (comment) for more info.
Judging by our experiments, response bodies in React Native appear to always be of blob type. As such, in order to whatwg-fetch
's Response.arrayBuffer()
to work, we had to implement FileReader.readAsArrayBuffer
which React Native does not at the moment. In order to get the raw binary data from the blob, we had to read it as a data URL and decode with atob
which had to be polyfilled with the base-64
package since it does not exist in the React Native environment.
You can find the changes we have made in the patch provided. Now provided by react-native-polyfill-globals
.
The Node.js implementation of IPFS for multipart request does not work as well. Although the HTTP request gets through successfully as the multipart request is created manually, the request body data is simply not correct and the file is not created in the IPFS node. node-fetch
, the Fetch API implementation in use for Node.js, accepts a Node.js' Readable
stream as the request body. When it-to-stream
is required in React Native, a Readable
stream is created from readable-stream
package which is not compatible with ReadableStream
WHATWG API.
It's also worth noting that nanoid
does not work out of the box in React Native because there is no built-in secure random generator. The recommended go to solution is to polyfill crypto.getRandomValues
global natively with react-native-get-random-values
as soon as the app starts. A solution such as react-native-crypto
might work as well, but it's probably overkill for this sole purpose as it offers much more cryptographic functions beyond what nanoid
requires.
Works with the fixes made for ipfs.add
.
Works with the fixes made for ipfs.add
.
For pubsub, subscriptions operate on the basis of a long-running HTTP response, i.e., an endless stream. As React Native does not support returning a ReadableStream
natively nor provide access to the underlying byte-stream (only base64 can be read through the bridge), so we have to fallback to XMLHttpRequest
. React Native's XHR provides progress events which buffers text allows us to concatenate a response by encoding it into its UTF-8 byte representation using the TextEncoder
API. Although very inefficient, it's some of sort of pseudo-streaming that works. The problem, however, is that we're reading text and not raw binary data so this may be a shortcoming for some use cases. Pubsub subcriptions are currently base64 encoded, so we should be fine for now in that regard.
To make pubsub subscriptions work, we have polyfilled ReadableStream
and integrated the stream's controller with XHR's progress events in React Native's fetch
implementation. It's important to note that progress events only work when XMLHttpRequest.responseType
is set to text
. If you wish to process raw binary data, either blob
or arraybuffer
has to be used. In this case, the response is read as a whole, when the load event is fired, and enqueued to the stream's controller as single chunk.
Other HTTP client methods continue to work as expected after these changes, on both iOS and Android.