Skip to content

Commit c114e09

Browse files
authored
fix(reactotron-react-native): new arch support (#1508 by @frankcalise)
## Describe your PR - Closes #1486 - `NativeModules` aren't available in bridgeless, so we utilize the `TurboModuleRegistry` to dig up the same calls we were using (this is backwards compatible) - [ ] Maybe want to do better TSing over `eslint-disable` but I'll leave that up to @morganick's review 😅 > [!WARNING] > The `DevMenu` change is not Expo Go compatible. This will never work in Expo Go as documented in infinitered/ignite#2678. This would only impact the client devtools code, which they could remove the custom command from the Reactotron configuration (in this repo, it's just in the example app, hence the CNG change) ```bash ERROR Invariant Violation: TurboModuleRegistry.getEnforcing(...): 'DevMenu' could not be found. Verify that a module by this name is registered in the native binary.Bridgeless mode: false. TurboModule interop: false. Modules loaded: {"NativeModules":["PlatformConstants","LogBox","BlobModule","PlatformConstants","SourceCode","PlatformConstants","DeviceInfo"],"TurboModules":[],"NotFound":["DevMenu"]}, js engine: hermes ```
1 parent 1f05c7c commit c114e09

7 files changed

+115
-56
lines changed

apps/example-app/app/devtools/ReactotronConfig.ts

+24-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* free desktop app for inspecting and debugging your React Native app.
44
* @see https://github.com/infinitered/reactotron
55
*/
6-
import { Platform, NativeModules } from "react-native"
6+
import { Platform } from "react-native"
77

88
import AsyncStorage from "@react-native-async-storage/async-storage"
99
import { ArgType } from "reactotron-core-client"
@@ -16,6 +16,16 @@ import { goBack, resetRoot, navigate } from "app/navigators/navigationUtilities"
1616

1717
import { Reactotron } from "./ReactotronClient"
1818

19+
let DevMenu = null
20+
/**
21+
* This Platform.OS iOS restriction can be lifted in React Native 0.77
22+
* The `DevMenu` module was missing in Android for the New Architecture
23+
* See this PR for more details: https://github.com/facebook/react-native/pull/46723
24+
*/
25+
if (Platform.OS === "ios") {
26+
DevMenu = require("react-native/Libraries/NativeModules/specs/NativeDevMenu")
27+
}
28+
1929
const reactotron = Reactotron.configure({
2030
name: require("../../package.json").name,
2131
onConnect: () => {
@@ -29,7 +39,7 @@ const reactotron = Reactotron.configure({
2939
mst({
3040
/** ignore some chatty `mobx-state-tree` actions */
3141
filter: (event) => /postProcessSnapshot|@APPLY_SNAPSHOT/.test(event.name) === false,
32-
}),
42+
})
3343
)
3444

3545
if (Platform.OS !== "web") {
@@ -53,15 +63,18 @@ if (Platform.OS !== "web") {
5363
* NOTE: If you edit this file while running the app, you will need to do a full refresh
5464
* or else your custom commands won't be registered correctly.
5565
*/
56-
reactotron.onCustomCommand({
57-
title: "Show Dev Menu",
58-
description: "Opens the React Native dev menu",
59-
command: "showDevMenu",
60-
handler: () => {
61-
Reactotron.log("Showing React Native dev menu")
62-
NativeModules.DevMenu.show()
63-
},
64-
})
66+
67+
if (Platform.OS === "ios") {
68+
reactotron.onCustomCommand({
69+
title: "Show Dev Menu",
70+
description: "Opens the React Native dev menu",
71+
command: "showDevMenu",
72+
handler: () => {
73+
Reactotron.log("Showing React Native dev menu")
74+
DevMenu.show()
75+
},
76+
})
77+
}
6578

6679
reactotron.onCustomCommand({
6780
title: "Reset Root Store",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Platform, PlatformIOSStatic, PlatformAndroidStatic } from "react-native"
2+
3+
interface PlatformConstants {
4+
osRelease: string
5+
model: string
6+
serverHost: string
7+
uiMode: string
8+
serial: string
9+
forceTouch: boolean
10+
interfaceIdiom: string
11+
systemName: string
12+
}
13+
14+
export default function getReactNativePlatformConstants(): PlatformConstants {
15+
const defaults: PlatformConstants = {
16+
osRelease: "",
17+
model: "",
18+
serverHost: "",
19+
uiMode: "",
20+
serial: "",
21+
forceTouch: false,
22+
interfaceIdiom: "",
23+
systemName: "",
24+
}
25+
26+
if (Platform.OS === "android") {
27+
const constants = Platform.constants as PlatformAndroidStatic["constants"]
28+
29+
return {
30+
...defaults,
31+
osRelease: constants.Release,
32+
model: constants.Model,
33+
serverHost: constants.ServerHost,
34+
uiMode: constants.uiMode,
35+
serial: constants.Serial,
36+
}
37+
} else if (Platform.OS === "ios") {
38+
const constants = Platform.constants as PlatformIOSStatic["constants"]
39+
return {
40+
...defaults,
41+
forceTouch: constants.forceTouchAvailable || false,
42+
interfaceIdiom: constants.interfaceIdiom,
43+
systemName: constants.systemName,
44+
}
45+
}
46+
47+
return defaults
48+
}

lib/reactotron-react-native/src/helpers/getReactNativeVersion.test.ts

+2-10
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,15 @@ describe("getReactNativeVersion", () => {
77
expect(result).toBe(null)
88
})
99

10-
it("should return null if there is no reactNativeVersion on platform constants", () => {
11-
const result = getReactNativeVersionWithModules({ PlatformConstants: {} })
12-
13-
expect(result).toBe(null)
14-
})
15-
1610
it("should return null if the major version is not a number", () => {
17-
const result = getReactNativeVersionWithModules({
18-
PlatformConstants: { reactNativeVersion: { major: "Hello" } },
19-
})
11+
const result = getReactNativeVersionWithModules({ reactNativeVersion: { major: "Hello" } })
2012

2113
expect(result).toBe(null)
2214
})
2315

2416
it("should return a version", () => {
2517
const result = getReactNativeVersionWithModules({
26-
PlatformConstants: { reactNativeVersion: { major: 0, minor: 59, patch: 8, prerelease: 5 } },
18+
reactNativeVersion: { major: 0, minor: 59, patch: 8, prerelease: 5 },
2719
})
2820

2921
expect(result).toEqual("0.59.8-5")
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { NativeModules } from "react-native"
21
import { getReactNativeVersionWithModules } from "./getReactNativeVersionWithModules"
2+
import { Platform } from "react-native"
33

44
export default function getReactNativeVersion(): string | null {
5-
return getReactNativeVersionWithModules(NativeModules)
5+
return getReactNativeVersionWithModules(Platform.constants)
66
}

lib/reactotron-react-native/src/helpers/getReactNativeVersionWithModules.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
export function getReactNativeVersionWithModules(nativeModules: any): string | null {
1+
export function getReactNativeVersionWithModules(constants: any): string | null {
22
try {
33
// dodge some bullets
4-
if (!nativeModules.PlatformConstants) return null
5-
if (!nativeModules.PlatformConstants.reactNativeVersion) return null
4+
if (!constants) return null
5+
if (!constants.reactNativeVersion) return null
66

77
// grab the raw numbers
8-
const major = nativeModules.PlatformConstants.reactNativeVersion.major
9-
const minor = nativeModules.PlatformConstants.reactNativeVersion.minor
10-
const patch = nativeModules.PlatformConstants.reactNativeVersion.patch
11-
const prerelease = nativeModules.PlatformConstants.reactNativeVersion.prerelease
8+
const major = constants.reactNativeVersion.major
9+
const minor = constants.reactNativeVersion.minor
10+
const patch = constants.reactNativeVersion.patch
11+
const prerelease = constants.reactNativeVersion.prerelease
1212

1313
// check the major or jet
1414
if (typeof major !== "number") return null

lib/reactotron-react-native/src/plugins/devTools.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1+
import { Platform } from "react-native"
12
import type { ReactotronCore, Plugin } from "reactotron-core-client"
2-
import { NativeModules } from "react-native"
3+
4+
let DevMenu = { show: () => {}, reload: () => {} }
5+
if (Platform.OS === "ios") {
6+
DevMenu = require("react-native/Libraries/NativeModules/specs/NativeDevMenu")
7+
}
38

49
const devTools = () => () => {
510
return {
611
onCommand: (command) => {
712
if (command.type !== "devtools.open" && command.type !== "devtools.reload") return
813

914
if (command.type === "devtools.open") {
10-
NativeModules.DevMenu.show()
15+
DevMenu.show()
1116
}
1217

1318
if (command.type === "devtools.reload") {
14-
NativeModules.DevMenu.reload()
19+
DevMenu.reload()
1520
}
1621
},
1722
} satisfies Plugin<ReactotronCore>

lib/reactotron-react-native/src/reactotron-react-native.ts

+24-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Platform, NativeModules } from "react-native"
1+
import { Platform } from "react-native"
22
import { createClient } from "reactotron-core-client"
33
import type {
44
ClientOptions,
@@ -8,7 +8,8 @@ import type {
88
ReactotronCore,
99
} from "reactotron-core-client"
1010
import type { AsyncStorageStatic } from "@react-native-async-storage/async-storage"
11-
11+
// eslint-disable-next-line import/namespace, import/default
12+
import NativeSourceCode from "react-native/Libraries/NativeModules/specs/NativeSourceCode"
1213
import getReactNativeVersion from "./helpers/getReactNativeVersion"
1314
import getReactNativeDimensions from "./helpers/getReactNativeDimensions"
1415
import asyncStorage, { AsyncStorageOptions } from "./plugins/asyncStorage"
@@ -20,8 +21,7 @@ import storybook from "./plugins/storybook"
2021
import devTools from "./plugins/devTools"
2122
import trackGlobalLogs from "./plugins/trackGlobalLogs"
2223
import { getHostFromUrl } from "./helpers/parseURL"
23-
24-
const constants = NativeModules.PlatformConstants || {}
24+
import getReactNativePlatformConstants from "./helpers/getReactNativePlatformConstants"
2525

2626
const REACTOTRON_ASYNC_CLIENT_ID = "@REACTOTRON/clientId"
2727

@@ -37,7 +37,8 @@ let tempClientId: string | null = null
3737
const getHost = (defaultHost = "localhost") => {
3838
try {
3939
// RN Reference: https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/specs/modules/NativeSourceCode.js
40-
const scriptURL = NativeModules?.SourceCode?.getConstants().scriptURL
40+
const scriptURL = NativeSourceCode.getConstants().scriptURL
41+
4142
if (typeof scriptURL !== "string") throw new Error("Invalid non-string URL")
4243

4344
return getHostFromUrl(scriptURL)
@@ -47,6 +48,9 @@ const getHost = (defaultHost = "localhost") => {
4748
}
4849
}
4950

51+
const { osRelease, model, serverHost, forceTouch, interfaceIdiom, systemName, uiMode, serial } =
52+
getReactNativePlatformConstants()
53+
5054
const DEFAULTS: ClientOptions<ReactotronReactNative> = {
5155
createSocket: (path: string) => new WebSocket(path), // eslint-disable-line
5256
host: getHost("localhost"),
@@ -58,15 +62,14 @@ const DEFAULTS: ClientOptions<ReactotronReactNative> = {
5862
reactotronLibraryVersion: "REACTOTRON_REACT_NATIVE_VERSION",
5963
platform: Platform.OS,
6064
platformVersion: Platform.Version,
61-
osRelease: constants.Release,
62-
model: constants.Model,
63-
serverHost: constants.ServerHost,
64-
forceTouch: constants.forceTouchAvailable || false,
65-
interfaceIdiom: constants.interfaceIdiom,
66-
systemName: constants.systemName,
67-
uiMode: constants.uiMode,
68-
serial: constants.Serial,
69-
androidId: constants.androidID,
65+
osRelease,
66+
model,
67+
serverHost,
68+
forceTouch,
69+
interfaceIdiom,
70+
systemName,
71+
uiMode,
72+
serial,
7073
reactNativeVersion: getReactNativeVersion(),
7174
...getReactNativeDimensions(),
7275
},
@@ -82,15 +85,13 @@ const DEFAULTS: ClientOptions<ReactotronReactNative> = {
8285
// Accounting for screen rotation
8386
const dimensions = [screenWidth, screenHeight].sort().join("-")
8487

85-
tempClientId = [
86-
name,
87-
Platform.OS,
88-
Platform.Version,
89-
constants.systemName,
90-
constants.Model,
91-
dimensions,
92-
screenScale,
93-
]
88+
const additionalInfo = Platform.select({
89+
ios: systemName,
90+
android: model,
91+
default: "",
92+
})
93+
94+
tempClientId = [name, Platform.OS, Platform.Version, additionalInfo, dimensions, screenScale]
9495
.filter(Boolean)
9596
.join("-")
9697

0 commit comments

Comments
 (0)