callstack / callstack/react-native-brownfield

iOS: emitOnBrownfieldMessage: can crash with std::bad_function_call when native posts before JS requires the TurboModule (cold-start race)

Offen
#420 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
Vorherrschende Sprache
TypeScript
Sterne
547
Forks
52
Ø Merge
3 T. 16 Std.
Gemergte PRs (30 T.)
5

Beschreibung

`ReactNativeBrownfieldModule.mm`'s `+emitMessageFromNative:` only checks that the Obj-C singleton exists before calling `emitOnBrownfieldMessage:`:

```objc
+ (void)emitMessageFromNative:(NSString *)message {
if (_sharedInstance) {
[_sharedInstance emitOnBrownfieldMessage:@{ @"text": message }];
} else {
NSLog(@"ReactNativeBrownfieldModule is not initialized, dropping message");
}
}
```

`_sharedInstance` is set in `-init`, which runs as soon as the bridge/TurboModuleManager instantiates the Obj-C module. But the codegen `SpecBase`'s `_eventEmitterCallback` (the `std::function` that `emitOnBrownfieldMessage:` invokes) is only populated inside the C++ `SpecJSI` constructor, which only runs when `getTurboModule:` fires — i.e. when JS actually evaluates `TurboModuleRegistry.getEnforcing('ReactNativeBrownfield')`.

If a host app calls `ReactNativeBrownfield.shared.postMessage(...)` (→ posts `BrownfieldMessageToJSNotification` → `handleNativeToJSMessage:` → `emitMessageFromNative:`) after the Obj-C module exists but before JS has required the module — e.g. right as a hosting view controller's `viewDidAppear` fires on a cold app launch, racing bundle evaluation — `_eventEmitterCallback` is still empty, and calling it throws `std::bad_function_call`, crashing the whole app (uncaught C++ exception).

Confirmed still present on `main` (4.0.0) as of 2026-07-06 — `emitMessageFromNative:`'s guard is unchanged from what's below (also reproduced against 3.3.0 in our own app):

```objc
- (void)handleNativeToJSMessage:(NSNotification *)notification {
NSString *message = notification.userInfo[@"message"];
if (message) {
[ReactNativeBrownfieldModule emitMessageFromNative:message];
}
}
```

### Suggested fixes
- Wrap the `_eventEmitterCallback` invocation in a readiness check (or try/catch) inside the generated `emitOnBrownfieldMessage:`, or
- Expose a callback/promise so host apps can gate native→JS `postMessage` calls on "TurboModule ready" rather than just "bundle loaded" (the closest signal `startReactNative(onBundleLoaded:)` currently offers).

### Repro
Timing-dependent — we haven't produced a deterministic repro case, but the code path is unambiguous from source, and we hit it in production (Crashlytics, `std::__1::bad_function_call: std::exception`, 100% of occurrences within the first second of a session). Happy to share a sanitized stack trace if useful.

### Environment
- `@callstack/react-native-brownfield`: 3.3.0 (also read against `main`/4.0.0 source)
- iOS 26.x, React Native new architecture (TurboModules/Fabric)

Beitragsleitfaden

Beitragsleitfaden öffnen

Rechercherichtung

Beginne in ReactNativeBrownfieldModule.mm mit -handleNativeToJSMessage: und +emitMessageFromNative:, und verfolge dann emitOnBrownfieldMessage: bis zur Initialisierung des Callbacks in der generierten SpecBase. Reproduziere einen nativen Post oder instrumentiere ihn, bevor JS das TurboModule anfordert, und überprüfe, dass dieser Kaltstartpfad nicht mehr abstürzt, wenn der Callback nicht verfügbar ist.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
cpp, ios, objective-c, react-native
Bereich
api, mobile-dev
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Ruhig
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
48/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.