UNPKG

reactotron-react-native

Version:

A development tool to explore, inspect, and diagnose your React Native apps.

205 lines (171 loc) 6.33 kB
import { Platform } from "react-native" import { createClient } from "reactotron-core-client" import type { ClientOptions, InferFeaturesFromPlugins, PluginCreator, Reactotron, 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" import overlay from "./plugins/overlay" import openInEditor, { OpenInEditorOptions } from "./plugins/openInEditor" import trackGlobalErrors, { TrackGlobalErrorsOptions } from "./plugins/trackGlobalErrors" import networking, { NetworkingOptions } from "./plugins/networking" import storybook from "./plugins/storybook" import devTools from "./plugins/devTools" import trackGlobalLogs from "./plugins/trackGlobalLogs" import { getHostFromUrl } from "./helpers/parseURL" import getReactNativePlatformConstants from "./helpers/getReactNativePlatformConstants" const REACTOTRON_ASYNC_CLIENT_ID = "@REACTOTRON/clientId" let tempClientId: string | null = null /** * Most of the time, host should be 'localhost'. * But sometimes, it's not. So we need to figure out what it is. * @see https://github.com/infinitered/reactotron/issues/1107 * * On an Android emulator, if you want to connect any servers of local, you will need run adb reverse on your terminal. This function gets the localhost IP of host machine directly to bypass this. */ 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 = NativeSourceCode.getConstants().scriptURL if (typeof scriptURL !== "string") throw new Error("Invalid non-string URL") return getHostFromUrl(scriptURL) } catch (error) { console.warn(`getHost: "${error.message}" for scriptURL - Falling back to ${defaultHost}`) return defaultHost } } 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"), port: 9090, name: "React Native App", environment: process.env.NODE_ENV || (__DEV__ ? "development" : "production"), client: { reactotronLibraryName: "reactotron-react-native", reactotronLibraryVersion: "REACTOTRON_REACT_NATIVE_VERSION", platform: Platform.OS, platformVersion: Platform.Version, osRelease, model, serverHost, forceTouch, interfaceIdiom, systemName, uiMode, serial, reactNativeVersion: getReactNativeVersion(), ...getReactNativeDimensions(), }, /* eslint-disable @typescript-eslint/no-use-before-define */ getClientId: async (name: string = "") => { if (reactotron.asyncStorageHandler) { return reactotron.asyncStorageHandler.getItem(REACTOTRON_ASYNC_CLIENT_ID) } // Generate clientId based on the device info const { screenWidth, screenHeight, screenScale } = getReactNativeDimensions() // Accounting for screen rotation const dimensions = [screenWidth, screenHeight].sort().join("-") const additionalInfo = Platform.select({ ios: systemName, android: model, default: "", }) tempClientId = [name, Platform.OS, Platform.Version, additionalInfo, dimensions, screenScale] .filter(Boolean) .join("-") return tempClientId }, setClientId: async (clientId: string) => { if (reactotron.asyncStorageHandler) { return reactotron.asyncStorageHandler.setItem(REACTOTRON_ASYNC_CLIENT_ID, clientId) } tempClientId = clientId }, proxyHack: true, } export interface UseReactNativeOptions { errors?: TrackGlobalErrorsOptions | boolean log?: boolean editor?: OpenInEditorOptions | boolean overlay?: boolean asyncStorage?: AsyncStorageOptions | boolean networking?: NetworkingOptions | boolean storybook?: boolean devTools?: boolean } export const reactNativeCorePlugins = [ asyncStorage(), trackGlobalErrors(), trackGlobalLogs(), openInEditor(), overlay(), networking(), storybook(), devTools(), ] satisfies PluginCreator<ReactotronCore>[] type ReactNativePluginFeatures = InferFeaturesFromPlugins< ReactotronCore, typeof reactNativeCorePlugins > export interface ReactotronReactNative extends Reactotron, ReactNativePluginFeatures { useReactNative: (options?: UseReactNativeOptions) => this asyncStorageHandler?: AsyncStorageStatic setAsyncStorageHandler: (asyncStorage: AsyncStorageStatic) => this } const reactotron = createClient<ReactotronReactNative>(DEFAULTS) function getPluginOptions<T>(options?: T | boolean): T { return typeof options === "object" ? options : null } reactotron.useReactNative = (options: UseReactNativeOptions = {}) => { if (options.errors !== false) { reactotron.use(trackGlobalErrors(getPluginOptions(options.errors))) } if (options.log !== false) { reactotron.use(trackGlobalLogs()) } if (options.editor !== false) { reactotron.use(openInEditor(getPluginOptions(options.editor))) } if (options.overlay !== false) { reactotron.use(overlay()) } if (options.asyncStorage !== false) { reactotron.use(asyncStorage(getPluginOptions(options.asyncStorage))) } if (options.networking !== false) { reactotron.use(networking(getPluginOptions(options.networking))) } if (options.storybook !== false) { reactotron.use(storybook()) } if (options.devTools !== false) { reactotron.use(devTools()) } return reactotron } reactotron.setAsyncStorageHandler = (asyncStorage: AsyncStorageStatic) => { reactotron.asyncStorageHandler = asyncStorage return reactotron } export { asyncStorage, trackGlobalErrors, trackGlobalLogs, openInEditor, overlay, networking, storybook, devTools, } export type { ClientOptions } export default reactotron