5

In a 2018 post, Facebook engineers claim that:

React Native was designed to return a single JSON message that lists mutations to perform, like [["createView", attrs], ["manageChildren", ...]]. We designed the entire system to never rely on getting a synchronous response back and to ensure everything in that list could be fully serialized to JSON and back.

Where in the React Native codebase* is the exact code responsible for this very feature (building the JSON)? Or is the quoted claim maybe a lie "simplification"?

I'm desperately trying to find it, but seem to be running in circles; whenever I find a track which initially seems to look promising, it never seems to be able to lead me to a place where such JSON would be generated :(

* Please note, I'm specifically interested in the Android codebase; and ideally, RN v0.55, the most recent version as of writing.

If possible, I'm also interested in the complementary "reverse" code. I mean, I assume that the "native" (Java in case of Android) code must also send some "event" signals back to JS at some point, and I assume they're also JSON. So, again, where does this code live? (I'm interested in the JS code which receives and deserializes that JSON.)

(Also, I'd be extra grateful to learn if those "low level" features (sending and receiving the JSON) are accessible in raw form from JS in an Expo-based app; but this is 100% optional in an answer, as I'm reasonably confident that I'll be able to find that out by myself, once I know the answer to the main question above.)

Note: for full disclosure, I've also cross-posted this question on HN, but no answers there yet as of writing. I plan to remove this note from here when the discussion thread on HN gets stale.

Edit No.1

Based a "Related" question suggested by SO, I'm starting to get a fuzzy feeling that a concept/keyword named "BatchedBridge" may be something very close to what I'm looking for. My intuition seems to tell me this might be the first serious step in the right direction. I've found a blog article describing how to observe messages flying both ways through BatchedBridge, which reportedly results in logs like below:

{type: 1, module: "WebSocketModule", method: "addListener", args: Array(1)}
{type: 1, module: "WebSocketModule", method: "connect", args: Array(4)}
{type: 0, module: "RCTDeviceEventEmitter", method: "emit", args: Array(2)}
{type: 1, module: "Timing", method: "createTimer", args: Array(4)}

And another article on the same blog shows how to send events from Java to JS over this "bridge", and listen to them in JS. This could answer half of the final, optional part of my question, about how to subscribe to Java events from JS in Expo. (In Java: this.reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onSessionConnect", params);, in JS: DeviceEventEmitter.addListener('onSessionConnect', (event) => { console.log(event); });)

Another interesting article on RN internals, resulting from googling with 'react-native BatchedBridge' as keywords.

Edit 2, tentative

The place where JSON is built might be line 556 below, though I'm not very sure:

550 void JSCExecutor::callNativeModules(Value&& value) {
...
556   auto calls = value.toJSONString();
557   m_delegate->callNativeModules(*this, folly::parseJson(calls), true);

called e.g. from l.642 below:

624 void JSCExecutor::invokeCallback(
625     const double callbackId,
626     const folly::dynamic& arguments) {
...
628   auto result = [&] {
...
634       return m_invokeCallbackAndReturnFlushedQueueJS->callAsFunction(
635           {Value::makeNumber(m_context, callbackId),
636            Value::fromDynamic(m_context, std::move(arguments))});
...
642   callNativeModules(std::move(result));
akavel
  • 4,789
  • 1
  • 35
  • 66

1 Answers1

2

Based on the articles linked in the question, as well as some videos from 2015, after a day of digging in, I believe that it's indeed the BatchedBridge, a.k.a. MessageQueue, which is the bus for calling between Java/Swift and JS.

If I understand correctly, a JS->Java call is done with MessageQueue.enqueueNativeCall, defined as:

  enqueueNativeCall(
    moduleID: number,
    methodID: number,
    params: any[],
    onFail: ?Function,
    onSucc: ?Function,
  )

while a Java->JS call is done via any one of the following functions (in the same file):

callFunctionReturnFlushedQueue(module: string, method: string, args: any[])
callFunctionReturnResultAndFlushedQueue(module: string, method: string, args: any[])
invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[])

This still doesn't actually answer the question about "where's the JSON?", but I was in fact more interested in finding the bus and verifying whether it's JSON that's flowing through it. Though I'm not even sure now whether it ever materializes as actual JSON string in the end, I believe that it's at least "ideologially" JSON. Non-JSON-compatible objects seem to be not allowed on the MessageQueue IIUC, and "function pointers/callback" are, I believe, converted to names of functions, so that they can be called asynchronously from Java. I believe that function results are ignored (i.e. all JS functions are assumed to have void return type).

edit: the callFunctionReturnResultAndFlushedQueue seems to return JS function's result, so it appears it would have to be non-async (blocking). But after searching the react-native repo for m_callFunctionReturnResultAndFlushedQueueJS and callFunctionReturnResultAndFlushedQueue, none of them appear to be called from anywhere. You can never be 100% sure in C++, but for now I'm going to cross my fingers and assume it can be safely ignored.

akavel
  • 4,789
  • 1
  • 35
  • 66
  • 1
    Hi, I'm the post author. The line you quoted above in JSCExecutor is the one that serializes to JSON, I believe. It is possible that I'm misremembering and the result returned from JS never becomes JSON (as opposed to the command from native, which definitely does) but it did at one point and we haven't relaxed any restrictions, as you noticed. We will. – Sophie Alpert Jun 16 '18 at 03:29
  • @SophieAlpert Woohoo, cool for chiming in, and thanks for the confirmation! :D <3 By the way, I'm experimenting with trying to take advantage of those JSON-like restrictions for Expo support in Elm language. I understand from the article that this is intended to change, and this may force me to stay with RN 0.55 forever; that's a pity for me, but I still believe it may bring me enough value. Also there's always the possibility I could fork Expo if I'd really need. – akavel Jun 16 '18 at 13:26