import { Tron } from './tron' import AsyncStorage from '@react-native-async-storage/async-storage' import { RootStore } from '../../models/root-store/root-store' import { onSnapshot } from 'mobx-state-tree' import { ReactotronConfig, DEFAULT_REACTOTRON_CONFIG } from './reactotron-config' import { mst } from 'reactotron-mst' import { clear } from '../../utils/storage' import { RootNavigation } from '../../navigators' import { Platform } from 'react-native' // Teach TypeScript about the bad things we want to do. declare global { interface Console { /** * Hey, it's Reactotron if we're in dev, and no-ops if we're in prod. */ tron: typeof Tron } } /** Do Nothing. */ const noop = () => undefined // in dev, we attach Reactotron, in prod we attach a interface-compatible mock. if (__DEV__) { console.tron = Tron // attach reactotron to `console.tron` } else { // attach a mock so if things sneaky by our __DEV__ guards, we won't crash. console.tron = { benchmark: noop, clear: noop, close: noop, configure: noop, connect: noop, display: noop, error: noop, image: noop, log: noop, logImportant: noop, onCustomCommand: noop, overlay: noop, reportError: noop, send: noop, startTimer: noop, storybookSwitcher: noop, use: noop, useReactNative: noop, warn: noop, } } /** * You'll probably never use the service like this since we hang the Reactotron * instance off of `console.tron`. This is only to be consistent with the other * services. */ export class Reactotron { config: ReactotronConfig rootStore: any /** * Create the Reactotron service. * * @param config the configuration */ constructor(config: ReactotronConfig = DEFAULT_REACTOTRON_CONFIG) { // merge the passed in config with some defaults this.config = { host: 'localhost', useAsyncStorage: true, ...config, state: { initial: false, snapshots: false, ...(config && config.state), }, } } /** * Hook into the root store for doing awesome state-related things. * * @param rootStore The root store */ setRootStore(rootStore: any, initialData: any) { if (__DEV__) { rootStore = rootStore as RootStore // typescript hack this.rootStore = rootStore const { initial, snapshots } = this.config.state const name = 'ROOT STORE' // logging features if (initial) { console.tron.display({ name, value: initialData, preview: 'Initial State', }) } // log state changes? if (snapshots) { onSnapshot(rootStore, (snapshot) => { console.tron.display({ name, value: snapshot, preview: 'New State' }) }) } console.tron.trackMstNode(rootStore) } } /** * Configure reactotron based on the the config settings passed in, then connect if we need to. */ async setup() { // only run this in dev... metro bundler will ignore this block: 🎉 if (__DEV__) { // configure reactotron Tron.configure({ name: this.config.name || require('../../../package.json').name, host: this.config.host, }) // hookup middleware if (Platform.OS !== 'web') { if (this.config.useAsyncStorage) { Tron.setAsyncStorageHandler(AsyncStorage) } Tron.useReactNative({ asyncStorage: this.config.useAsyncStorage ? undefined : false, }) } // ignore some chatty `mobx-state-tree` actions const RX = /postProcessSnapshot|@APPLY_SNAPSHOT/ // hookup mobx-state-tree middleware Tron.use( mst({ filter: (event) => RX.test(event.name) === false, }), ) // connect to the app Tron.connect() // Register Custom Commands Tron.onCustomCommand({ title: 'Reset Root Store', description: 'Resets the MST store', command: 'resetStore', handler: () => { console.tron.log('resetting store') clear() }, }) Tron.onCustomCommand({ title: 'Reset Navigation State', description: 'Resets the navigation state', command: 'resetNavigation', handler: () => { console.tron.log('resetting navigation state') RootNavigation.resetRoot({ routes: [] }) }, }) Tron.onCustomCommand({ title: 'Go Back', description: 'Goes back', command: 'goBack', handler: () => { console.tron.log('Going back') RootNavigation.goBack() }, }) // clear if we should if (this.config.clearOnLoad) { Tron.clear() } } } }