Skip to content

Latest commit

 

History

History
66 lines (34 loc) · 12.5 KB

NOTES.md

File metadata and controls

66 lines (34 loc) · 12.5 KB

HTTP client

Factory

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:

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

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.

FormData

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, Blobs do not have this property, so they have to be handled differently by FormData. In React Native, Blobs 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

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 ReadableStreams 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.

FileReader

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.

Node.js implementation (not being used)

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.