Skip to content

Commit

Permalink
fix(reactotron-react-native): new arch support (#1508 by @frankcalise)
Browse files Browse the repository at this point in the history
## 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
```
  • Loading branch information
frankcalise authored Oct 4, 2024
1 parent 1f05c7c commit c114e09
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 56 deletions.
35 changes: 24 additions & 11 deletions apps/example-app/app/devtools/ReactotronConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* free desktop app for inspecting and debugging your React Native app.
* @see https://github.com/infinitered/reactotron
*/
import { Platform, NativeModules } from "react-native"
import { Platform } from "react-native"

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

import { Reactotron } from "./ReactotronClient"

let DevMenu = null
/**
* This Platform.OS iOS restriction can be lifted in React Native 0.77
* The `DevMenu` module was missing in Android for the New Architecture
* See this PR for more details: https://github.com/facebook/react-native/pull/46723
*/
if (Platform.OS === "ios") {
DevMenu = require("react-native/Libraries/NativeModules/specs/NativeDevMenu")
}

const reactotron = Reactotron.configure({
name: require("../../package.json").name,
onConnect: () => {
Expand All @@ -29,7 +39,7 @@ const reactotron = Reactotron.configure({
mst({
/** ignore some chatty `mobx-state-tree` actions */
filter: (event) => /postProcessSnapshot|@APPLY_SNAPSHOT/.test(event.name) === false,
}),
})
)

if (Platform.OS !== "web") {
Expand All @@ -53,15 +63,18 @@ if (Platform.OS !== "web") {
* NOTE: If you edit this file while running the app, you will need to do a full refresh
* or else your custom commands won't be registered correctly.
*/
reactotron.onCustomCommand({
title: "Show Dev Menu",
description: "Opens the React Native dev menu",
command: "showDevMenu",
handler: () => {
Reactotron.log("Showing React Native dev menu")
NativeModules.DevMenu.show()
},
})

if (Platform.OS === "ios") {
reactotron.onCustomCommand({
title: "Show Dev Menu",
description: "Opens the React Native dev menu",
command: "showDevMenu",
handler: () => {
Reactotron.log("Showing React Native dev menu")
DevMenu.show()
},
})
}

reactotron.onCustomCommand({
title: "Reset Root Store",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Platform, PlatformIOSStatic, PlatformAndroidStatic } from "react-native"

interface PlatformConstants {
osRelease: string
model: string
serverHost: string
uiMode: string
serial: string
forceTouch: boolean
interfaceIdiom: string
systemName: string
}

export default function getReactNativePlatformConstants(): PlatformConstants {
const defaults: PlatformConstants = {
osRelease: "",
model: "",
serverHost: "",
uiMode: "",
serial: "",
forceTouch: false,
interfaceIdiom: "",
systemName: "",
}

if (Platform.OS === "android") {
const constants = Platform.constants as PlatformAndroidStatic["constants"]

return {
...defaults,
osRelease: constants.Release,
model: constants.Model,
serverHost: constants.ServerHost,
uiMode: constants.uiMode,
serial: constants.Serial,
}
} else if (Platform.OS === "ios") {
const constants = Platform.constants as PlatformIOSStatic["constants"]
return {
...defaults,
forceTouch: constants.forceTouchAvailable || false,
interfaceIdiom: constants.interfaceIdiom,
systemName: constants.systemName,
}
}

return defaults
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,15 @@ describe("getReactNativeVersion", () => {
expect(result).toBe(null)
})

it("should return null if there is no reactNativeVersion on platform constants", () => {
const result = getReactNativeVersionWithModules({ PlatformConstants: {} })

expect(result).toBe(null)
})

it("should return null if the major version is not a number", () => {
const result = getReactNativeVersionWithModules({
PlatformConstants: { reactNativeVersion: { major: "Hello" } },
})
const result = getReactNativeVersionWithModules({ reactNativeVersion: { major: "Hello" } })

expect(result).toBe(null)
})

it("should return a version", () => {
const result = getReactNativeVersionWithModules({
PlatformConstants: { reactNativeVersion: { major: 0, minor: 59, patch: 8, prerelease: 5 } },
reactNativeVersion: { major: 0, minor: 59, patch: 8, prerelease: 5 },
})

expect(result).toEqual("0.59.8-5")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NativeModules } from "react-native"
import { getReactNativeVersionWithModules } from "./getReactNativeVersionWithModules"
import { Platform } from "react-native"

export default function getReactNativeVersion(): string | null {
return getReactNativeVersionWithModules(NativeModules)
return getReactNativeVersionWithModules(Platform.constants)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
export function getReactNativeVersionWithModules(nativeModules: any): string | null {
export function getReactNativeVersionWithModules(constants: any): string | null {
try {
// dodge some bullets
if (!nativeModules.PlatformConstants) return null
if (!nativeModules.PlatformConstants.reactNativeVersion) return null
if (!constants) return null
if (!constants.reactNativeVersion) return null

// grab the raw numbers
const major = nativeModules.PlatformConstants.reactNativeVersion.major
const minor = nativeModules.PlatformConstants.reactNativeVersion.minor
const patch = nativeModules.PlatformConstants.reactNativeVersion.patch
const prerelease = nativeModules.PlatformConstants.reactNativeVersion.prerelease
const major = constants.reactNativeVersion.major
const minor = constants.reactNativeVersion.minor
const patch = constants.reactNativeVersion.patch
const prerelease = constants.reactNativeVersion.prerelease

// check the major or jet
if (typeof major !== "number") return null
Expand Down
11 changes: 8 additions & 3 deletions lib/reactotron-react-native/src/plugins/devTools.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { Platform } from "react-native"
import type { ReactotronCore, Plugin } from "reactotron-core-client"
import { NativeModules } from "react-native"

let DevMenu = { show: () => {}, reload: () => {} }
if (Platform.OS === "ios") {
DevMenu = require("react-native/Libraries/NativeModules/specs/NativeDevMenu")
}

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

if (command.type === "devtools.open") {
NativeModules.DevMenu.show()
DevMenu.show()
}

if (command.type === "devtools.reload") {
NativeModules.DevMenu.reload()
DevMenu.reload()
}
},
} satisfies Plugin<ReactotronCore>
Expand Down
47 changes: 24 additions & 23 deletions lib/reactotron-react-native/src/reactotron-react-native.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Platform, NativeModules } from "react-native"
import { Platform } from "react-native"
import { createClient } from "reactotron-core-client"
import type {
ClientOptions,
Expand All @@ -8,7 +8,8 @@ import type {
ReactotronCore,
} from "reactotron-core-client"
import type { AsyncStorageStatic } from "@react-native-async-storage/async-storage"

// eslint-disable-next-line import/namespace, import/default
import NativeSourceCode from "react-native/Libraries/NativeModules/specs/NativeSourceCode"
import getReactNativeVersion from "./helpers/getReactNativeVersion"
import getReactNativeDimensions from "./helpers/getReactNativeDimensions"
import asyncStorage, { AsyncStorageOptions } from "./plugins/asyncStorage"
Expand All @@ -20,8 +21,7 @@ import storybook from "./plugins/storybook"
import devTools from "./plugins/devTools"
import trackGlobalLogs from "./plugins/trackGlobalLogs"
import { getHostFromUrl } from "./helpers/parseURL"

const constants = NativeModules.PlatformConstants || {}
import getReactNativePlatformConstants from "./helpers/getReactNativePlatformConstants"

const REACTOTRON_ASYNC_CLIENT_ID = "@REACTOTRON/clientId"

Expand All @@ -37,7 +37,8 @@ let tempClientId: string | null = null
const getHost = (defaultHost = "localhost") => {
try {
// RN Reference: https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/specs/modules/NativeSourceCode.js
const scriptURL = NativeModules?.SourceCode?.getConstants().scriptURL
const scriptURL = NativeSourceCode.getConstants().scriptURL

if (typeof scriptURL !== "string") throw new Error("Invalid non-string URL")

return getHostFromUrl(scriptURL)
Expand All @@ -47,6 +48,9 @@ const getHost = (defaultHost = "localhost") => {
}
}

const { osRelease, model, serverHost, forceTouch, interfaceIdiom, systemName, uiMode, serial } =
getReactNativePlatformConstants()

const DEFAULTS: ClientOptions<ReactotronReactNative> = {
createSocket: (path: string) => new WebSocket(path), // eslint-disable-line
host: getHost("localhost"),
Expand All @@ -58,15 +62,14 @@ const DEFAULTS: ClientOptions<ReactotronReactNative> = {
reactotronLibraryVersion: "REACTOTRON_REACT_NATIVE_VERSION",
platform: Platform.OS,
platformVersion: Platform.Version,
osRelease: constants.Release,
model: constants.Model,
serverHost: constants.ServerHost,
forceTouch: constants.forceTouchAvailable || false,
interfaceIdiom: constants.interfaceIdiom,
systemName: constants.systemName,
uiMode: constants.uiMode,
serial: constants.Serial,
androidId: constants.androidID,
osRelease,
model,
serverHost,
forceTouch,
interfaceIdiom,
systemName,
uiMode,
serial,
reactNativeVersion: getReactNativeVersion(),
...getReactNativeDimensions(),
},
Expand All @@ -82,15 +85,13 @@ const DEFAULTS: ClientOptions<ReactotronReactNative> = {
// Accounting for screen rotation
const dimensions = [screenWidth, screenHeight].sort().join("-")

tempClientId = [
name,
Platform.OS,
Platform.Version,
constants.systemName,
constants.Model,
dimensions,
screenScale,
]
const additionalInfo = Platform.select({
ios: systemName,
android: model,
default: "",
})

tempClientId = [name, Platform.OS, Platform.Version, additionalInfo, dimensions, screenScale]
.filter(Boolean)
.join("-")

Expand Down

0 comments on commit c114e09

Please sign in to comment.