)
}
}
App.propTypes = {
settingState: PropTypes.shape({
redux: PropTypes.bool.isRequired,
react: PropTypes.bool.isRequired,
size: PropTypes.number.isRequired,
themeName: PropTypes.string.isRequired,
}).isRequired,
settingActions: PropTypes.shape({
toggleDevTools: PropTypes.func.isRequired,
resizeDevTools: PropTypes.func.isRequired,
changeDefaultTheme: PropTypes.func.isRequired,
}).isRequired,
debuggerState: PropTypes.shape({
isPortSettingRequired: PropTypes.bool.isRequired,
}).isRequired,
debuggerActions: PropTypes.shape({
setDebuggerLocation: PropTypes.func.isRequired,
syncState: PropTypes.func.isRequired,
}).isRequired,
}
export default connect(
(state) => ({
debuggerState: state.debugger,
settingState: state.setting,
}),
(dispatch) => ({
debuggerActions: bindActionCreators(debuggerActionCreators, dispatch),
settingActions: bindActionCreators(settingActionCreators, dispatch),
}),
)(App)
================================================
FILE: app/containers/ReactInspector.js
================================================
import { connect } from 'react-redux'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { tryADBReverse } from '../utils/adb'
let ReactServer
const getReactInspector = () => {
if (ReactServer) return ReactServer
// eslint-disable-next-line
ReactServer = ReactServer || require('react-devtools-core/standalone').default;
return ReactServer
}
const containerId = 'react-devtools-container'
const styles = {
container: {
display: 'flex',
height: '100%',
justifyContent: 'center',
position: 'relative',
},
waiting: {
height: '100%',
display: 'flex',
WebkitUserSelect: 'none',
textAlign: 'center',
color: '#aaa',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
}
const isReactPanelOpen = (props) => props.settingState.react
class ReactInspector extends Component {
static setProjectRoots(projectRoots) {
getReactInspector().setProjectRoots(projectRoots)
}
listeningPort = window.reactDevToolsPort
componentDidMount() {
const { debuggerState } = this.props
const { worker } = debuggerState
if (worker) {
this.server = this.startServer()
worker.addEventListener('message', this.workerOnMessage)
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
const { debuggerState } = this.props
const { worker } = debuggerState
const { worker: nextWorker } = nextProps.debuggerState
if (nextWorker && nextWorker !== worker) {
this.closeServerIfExists()
if (isReactPanelOpen(this.props)) {
this.server = this.startServer()
}
nextWorker.addEventListener('message', this.workerOnMessage)
} else if (!nextWorker) {
this.closeServerIfExists()
}
// Open / Close server when react panel opened / hidden
if (!worker && !nextWorker) return
if (isReactPanelOpen(this.props) && !isReactPanelOpen(nextProps)) {
this.closeServerIfExists()
} else if (!isReactPanelOpen(this.props) && isReactPanelOpen(nextProps)) {
this.closeServerIfExists()
this.server = this.startServer()
}
}
shouldComponentUpdate() {
return false
}
componentWillUnmount() {
this.closeServerIfExists()
}
workerOnMessage = (message) => {
const { data } = message
if (!data || !data.__REPORT_REACT_DEVTOOLS_PORT__) return
const port = Number(data.__REPORT_REACT_DEVTOOLS_PORT__)
const { platform } = data
if (port && port !== this.listeningPort) {
this.listeningPort = port
this.closeServerIfExists()
if (isReactPanelOpen(this.props)) {
this.server = this.startServer(port)
}
if (platform === 'android') tryADBReverse(port).catch(() => {})
}
}
closeServerIfExists = () => {
if (this.server) {
this.server.close()
this.server = null
}
}
startServer(port = this.listeningPort) {
let loggedWarn = false
return getReactInspector()
.setStatusListener((status) => {
if (!loggedWarn && status === 'Failed to start the server.') {
const message = port !== 8097
? 're-open the debugger window might be helpful.'
: 'we recommended to upgrade React Native version to 0.39+ for random port support.'
console.error(
'[RNDebugger]',
`Failed to start React DevTools server with port \`${port}\`,`,
'because another server is listening,',
message,
)
loggedWarn = true
}
})
.setContentDOMNode(document.getElementById(containerId))
.startServer(port)
}
render() {
return (
Waiting for React to connect…
)
}
}
ReactInspector.propTypes = {
debuggerState: PropTypes.shape({
worker: PropTypes.shape({
addEventListener: PropTypes.func.isRequired,
}),
}).isRequired,
}
export default connect(
(state) => ({
settingState: state.setting,
debuggerState: state.debugger,
}),
(dispatch) => ({ dispatch }),
)(ReactInspector)
================================================
FILE: app/containers/redux/DevTools.js
================================================
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import styled from 'styled-components'
import { Container, Notification } from '@redux-devtools/ui'
import { clearNotification } from '@redux-devtools/app/lib/esm/actions'
import Actions from '@redux-devtools/app/lib/esm/containers/Actions'
import Settings from './Settings'
import Header from './Header'
const StyledContainer = styled(Container)`overflow: hidden;`
function App() {
const section = useSelector((state) => state.section)
const theme = useSelector((state) => state.theme)
const notification = useSelector((state) => state.notification)
const dispatch = useDispatch()
let body
switch (section) {
case 'Settings':
body =
break
default:
body =
}
return (
{body}
{notification && (
dispatch(clearNotification())}
>
{notification.message}
)}
)
}
export default App
================================================
FILE: app/containers/redux/Header.js
================================================
import React, { useCallback } from 'react'
import PropTypes from 'prop-types'
import { useDispatch } from 'react-redux'
import {
Tabs, Toolbar, Button, Divider,
} from '@redux-devtools/ui'
import { GoBook } from 'react-icons/go'
import styled from 'styled-components'
import { changeSection } from '@redux-devtools/app/lib/esm/actions'
import { shell } from 'electron'
const WindowDraggable = styled.div`
display: flex;
flex: 1;
height: 100%;
-webkit-app-region: drag;
-webkit-user-select: none;
`
const tabs = [{ name: 'Actions' }, { name: 'Settings' }]
function Header(props) {
const { section } = props
const dispatch = useDispatch()
const handleChangeSection = useCallback(
(sec) => dispatch(changeSection(sec)),
[dispatch, changeSection],
)
const openHelp = useCallback(() => shell.openExternal('https://goo.gl/SHU4yL'), [])
return (
)
}
Header.propTypes = {
section: PropTypes.string.isRequired,
}
export default Header
================================================
FILE: app/containers/redux/Settings.js
================================================
/* eslint-disable import/no-named-as-default */
import React, { Component } from 'react'
import Tabs from '@redux-devtools/ui/lib/esm/Tabs/Tabs'
import Themes from '@redux-devtools/app/lib/esm/components/Settings/Themes'
export default class Settings extends Component {
tabs = [
{ name: 'Themes', component: Themes },
]
constructor(props) {
super(props)
this.state = { selected: 'Themes' }
}
handleSelect = (selected) => {
this.setState({ selected })
}
render() {
const { selected } = this.state
return (
)
}
}
================================================
FILE: app/globalStyles.js
================================================
import { css, createGlobalStyle } from 'styled-components'
const commonStyles = css``
export const GlobalStyle =
process.platform !== 'darwin'
? createGlobalStyle`
${commonStyles}
::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: #95959588;
}
::-webkit-scrollbar-thumb {
background-color: #33333366;
}
`
: createGlobalStyle`${commonStyles}`
================================================
FILE: app/index.js
================================================
import { findAPortNotInUse } from 'portscanner'
import { webFrame } from 'electron'
import { getCurrentWindow } from '@electron/remote'
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import launchEditor from 'react-dev-utils/launchEditor'
import { PersistGate } from 'redux-persist/integration/react'
import './setup'
import App from './containers/App'
import configureStore from './store/configureStore'
import { beforeWindowClose } from './actions/debugger'
import { invokeDevMethod } from './utils/devMenu'
import { client, tryADBReverse } from './utils/adb'
import config from './utils/config'
import { toggleOpenInEditor, isOpenInEditorEnabled } from './utils/devtools'
import { GlobalStyle } from './globalStyles'
const currentWindow = getCurrentWindow()
webFrame.setZoomFactor(1)
webFrame.setVisualZoomLevelLimits(1, 1)
// Prevent dropped file
document.addEventListener('drop', (e) => {
e.preventDefault()
e.stopPropagation()
})
document.addEventListener('dragover', (e) => {
e.preventDefault()
e.stopPropagation()
})
let store
let persistor
const handleReady = () => {
const { defaultReactDevToolsPort = 19567 } = config
findAPortNotInUse(Number(defaultReactDevToolsPort)).then((port) => {
window.reactDevToolsPort = port
const root = createRoot(document.getElementById('root'))
root.render(
<>
>,
)
})
}
;({ store, persistor } = configureStore(handleReady))
// Provide for user
window.adb = client
window.adb.reverseAll = tryADBReverse
window.adb.reversePackager = () =>
tryADBReverse(store.getState().debugger.location.port)
window.checkWindowInfo = () => {
const debuggerState = store.getState().debugger
return {
isWorkerRunning: !!debuggerState.worker,
location: debuggerState.location,
isPortSettingRequired: debuggerState.isPortSettingRequired,
}
}
window.beforeWindowClose = () =>
new Promise((resolve) => {
if (store.dispatch(beforeWindowClose())) {
setTimeout(resolve, 200)
} else {
resolve()
}
})
// For security, we should disable nodeIntegration when user use this open a website
const originWindowOpen = window.open
window.open = (url, frameName, features = '') => {
const featureList = features.split(',')
featureList.push('nodeIntegration=0')
return originWindowOpen.call(window, url, frameName, featureList.join(','))
}
window.openInEditor = (file, lineNumber) => launchEditor(file, lineNumber)
window.toggleOpenInEditor = () => {
const { port } = store.getState().debugger.location
return toggleOpenInEditor(currentWindow, port)
}
window.isOpenInEditorEnabled = () => isOpenInEditorEnabled(currentWindow)
window.invokeDevMethod = (name) => invokeDevMethod(name)()
// Package will missing /usr/local/bin,
// we need fix it for ensure child process work
// (like launchEditor of react-devtools)
if (
process.env.NODE_ENV === 'production' &&
process.platform === 'darwin' &&
process.env.PATH.indexOf('/usr/local/bin') === -1
) {
process.env.PATH = `${process.env.PATH}:/usr/local/bin`
}
================================================
FILE: app/middlewares/debuggerAPI.js
================================================
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// Take from https://github.com/facebook/react-native/blob/master/local-cli/server/util/debugger.html
import { getCurrentWindow } from '@electron/remote'
import { bindActionCreators } from 'redux'
import { checkPortStatus } from 'portscanner'
import * as debuggerActions from '../actions/debugger'
import { setDevMenuMethods, networkInspect } from '../utils/devMenu'
import { tryADBReverse } from '../utils/adb'
import { clearNetworkLogs, selectRNDebuggerWorkerContext } from '../utils/devtools'
import config from '../utils/config'
const currentWindow = getCurrentWindow()
const { SET_DEBUGGER_LOCATION, BEFORE_WINDOW_CLOSE } = debuggerActions
let worker
let queuedMessages = []
let scriptExecuted = false
let actions
let host
let port
let socket
const APOLLO_MESSAGE_PREFIX = 'ac-devtools:'
const workerOnMessage = (message) => {
const { data } = message
if (data && data.message?.startsWith(APOLLO_MESSAGE_PREFIX)) {
data.__FROM_DEBUGGER_WORKER__ = true
postMessage(data, '*')
return false
}
if (data && (data.__IS_REDUX_NATIVE_MESSAGE__ || data.__REPORT_REACT_DEVTOOLS_PORT__)) {
return true
}
const list = data && data.__AVAILABLE_METHODS_CAN_CALL_BY_RNDEBUGGER__
if (list) {
setDevMenuMethods(list, worker)
return false
}
socket.send(JSON.stringify(data))
}
const onWindowMessage = (e) => {
const { data } = e
if (
!data?.__FROM_DEBUGGER_WORKER__ &&
data?.message?.startsWith(APOLLO_MESSAGE_PREFIX)
) {
worker.postMessage({
method: 'emitApolloMessage',
...data,
})
return false
}
}
const createJSRuntime = () => {
// This worker will run the application javascript code,
// making sure that it's run in an environment without a global
// document, to make it consistent with the JSC executor environment.
// eslint-disable-next-line
worker = new Worker(`${__webpack_public_path__}RNDebuggerWorker.js`);
worker.addEventListener('message', workerOnMessage)
window.addEventListener('message', onWindowMessage)
actions.setDebuggerWorker(worker, 'connected')
}
const shutdownJSRuntime = () => {
const { setDebuggerWorker } = actions
scriptExecuted = false
if (worker) {
worker.terminate()
window.removeEventListener('message', onWindowMessage)
setDevMenuMethods([])
}
worker = null
setDebuggerWorker(null, 'disconnected')
}
const isScriptBuildForAndroid = (url) => url && (url.indexOf('.android.bundle') > -1 || url.indexOf('platform=android') > -1)
let preconnectTimeout
const preconnect = async (fn, firstTimeout) => {
if (firstTimeout || (await checkPortStatus(port, host)) !== 'open') {
preconnectTimeout = setTimeout(() => preconnect(fn), 500)
return
}
socket = await fn()
}
const clearLogs = () => {
if (process.env.NODE_ENV !== 'development') {
console.clear()
clearNetworkLogs(currentWindow)
}
}
const flushQueuedMessages = () => {
if (!worker) return
// Flush any messages queued up and clear them
queuedMessages.forEach((message) => worker.postMessage(message))
queuedMessages = []
}
let loadCount = 0
const checkJSLoadCount = () => {
loadCount += 1
if (
currentWindow.webContents.isDevToolsOpened()
&& config.timesJSLoadToRefreshDevTools >= 0
&& loadCount > 0
&& loadCount % config.timesJSLoadToRefreshDevTools === 0
) {
currentWindow.webContents.closeDevTools()
currentWindow.webContents.openDevTools()
console.warn(
'[RNDebugger]',
`Refreshed the devtools panel as React Native app was reloaded ${loadCount} times.`,
'If you want to update or disable this,',
'Open `Debugger` -> `Open Config File` to change `timesJSLoadToRefreshDevTools` field.',
)
loadCount = 0
}
}
const connectToDebuggerProxy = async () => {
const ws = new WebSocket(`ws://${host}:${port}/debugger-proxy?role=debugger&name=Chrome`)
const { setDebuggerStatus } = actions
ws.onopen = () => setDebuggerStatus('waiting')
ws.onmessage = async (message) => {
if (!message.data) return
const object = JSON.parse(message.data)
if (object.$event === 'client-disconnected') {
shutdownJSRuntime()
return
}
if (!object.method) return
// Special message that asks for a new JS runtime
if (object.method === 'prepareJSRuntime') {
shutdownJSRuntime()
createJSRuntime()
clearLogs()
selectRNDebuggerWorkerContext(currentWindow)
ws.send(JSON.stringify({ replyID: object.id }))
} else if (object.method === '$disconnected') {
shutdownJSRuntime()
} else {
if (!worker) return
if (object.method === 'executeApplicationScript') {
object.networkInspect = networkInspect.isEnabled()
object.reactDevToolsPort = window.reactDevToolsPort
if (isScriptBuildForAndroid(object.url)) {
// Reserve React Inspector port for debug via USB on Android real device
tryADBReverse(window.reactDevToolsPort).catch(() => {})
}
// Clear logs even if no error catched
clearLogs()
scriptExecuted = true
checkJSLoadCount()
}
if (scriptExecuted) {
// Otherwise, pass through to the worker provided the
// application script has been executed. If not add
// it to a queue until it has been executed.
worker.postMessage(object)
flushQueuedMessages()
} else {
queuedMessages.push(object)
}
}
}
ws.onerror = () => {}
ws.onclose = (e) => {
shutdownJSRuntime()
if (e.reason) {
console.warn(e.reason)
}
preconnect(connectToDebuggerProxy, true)
}
return ws
}
const setDebuggerLoc = ({ host: packagerHost, port: packagerPort }) => {
if (host === packagerHost && port === Number(packagerPort)) return
host = packagerHost || 'localhost'
port = packagerPort || config.port || 8081
if (socket) {
shutdownJSRuntime()
socket.close()
} else {
// Should ensure cleared timeout if called preconnect twice
clearTimeout(preconnectTimeout)
preconnect(connectToDebuggerProxy)
}
}
export default ({ dispatch }) => {
actions = bindActionCreators(debuggerActions, dispatch)
return (next) => (action) => {
if (action.type === SET_DEBUGGER_LOCATION) {
setDebuggerLoc(action.loc)
}
if (action.type === BEFORE_WINDOW_CLOSE) {
// Return boolean instead of handle reducer
if (!worker) return false
worker.postMessage({ method: 'beforeTerminate' })
return true
}
return next(action)
}
}
================================================
FILE: app/middlewares/reduxAPI.js
================================================
import { bindActionCreators } from 'redux'
import { ipcRenderer } from 'electron'
import { getGlobal } from '@electron/remote'
import { UPDATE_STATE, LIFTED_ACTION } from '@redux-devtools/app/lib/esm/constants/actionTypes'
import { DISCONNECTED } from '@redux-devtools/app/lib/esm/constants/socketActionTypes'
import { nonReduxDispatch } from '@redux-devtools/app/lib/esm/utils/monitorActions'
import { showNotification, liftedDispatch } from '@redux-devtools/app/lib/esm/actions'
import { getActiveInstance } from '@redux-devtools/app/lib/esm/reducers/instances'
import { SET_DEBUGGER_WORKER, SYNC_STATE } from '../actions/debugger'
import { setReduxDevToolsMethods, updateSliderContent } from '../utils/devMenu'
const unboundActions = {
showNotification,
updateState: (request) => ({
type: UPDATE_STATE,
request: request.data ? { ...request.data, id: request.id } : request,
}),
liftedDispatch,
}
let actions
let worker
let store
const toWorker = ({
message, action, state, toAll,
}) => {
if (!worker) return
const { instances } = store.getState()
const instanceId = getActiveInstance(instances)
const id = instances.options[instanceId].connectionId
worker.postMessage({
method: 'emitReduxMessage',
content: {
type: message,
action,
state: nonReduxDispatch(store, message, instanceId, action, state, instances),
instanceId,
id,
toAll,
},
})
}
const postImportMessage = (state) => {
if (!worker) return
const { instances } = store.getState()
const instanceId = getActiveInstance(instances)
const id = instances.options[instanceId].connectionId
worker.postMessage({
method: 'emitReduxMessage',
content: {
type: 'IMPORT',
state,
instanceId,
id,
},
})
}
// Receive messages from worker
const messaging = (message) => {
const { data } = message
if (!data || !data.__IS_REDUX_NATIVE_MESSAGE__) return
const { content: request } = data
if (request.type === 'ERROR') {
actions.showNotification(request.payload)
return
}
actions.updateState(request)
}
const initWorker = (wkr) => {
wkr.addEventListener('message', messaging)
worker = wkr
}
const removeWorker = () => {
worker = null
}
const syncLiftedState = (liftedState) => {
if (!getGlobal('isSyncState')()) return
const { actionsById } = liftedState
const payload = []
liftedState.stagedActionIds.slice(1).forEach((id) => {
payload.push(actionsById[id].action)
})
const serialized = JSON.stringify({ payload: JSON.stringify(payload) })
ipcRenderer.send('sync-state', serialized)
}
export default (inStore) => {
store = inStore
actions = bindActionCreators(unboundActions, store.dispatch)
return (next) => (action) => {
if (action.type === SET_DEBUGGER_WORKER) {
if (action.worker) {
initWorker(action.worker)
} else {
removeWorker(action.worker)
setReduxDevToolsMethods(false)
next({ type: DISCONNECTED })
}
}
if (action.type === LIFTED_ACTION) {
toWorker(action)
}
if (
action.type === UPDATE_STATE
|| action.type === LIFTED_ACTION
|| action.type === SYNC_STATE
) {
next(action)
const state = store.getState()
const { instances } = state
const id = getActiveInstance(instances)
const liftedState = instances.states[id]
if (liftedState && liftedState.computedStates.length > 1) {
setReduxDevToolsMethods(true, actions.liftedDispatch)
} else if (liftedState && liftedState.computedStates.length <= 1) {
setReduxDevToolsMethods(false)
}
updateSliderContent(liftedState, action.action && action.action.dontUpdateTouchBarSlider)
if (action.request && action.request.type === 'ACTION') {
syncLiftedState(liftedState)
}
if (action.type === SYNC_STATE) {
postImportMessage(action.payload)
}
return
}
return next(action)
}
}
================================================
FILE: app/reducers/debugger.js
================================================
import {
SET_DEBUGGER_STATUS,
SET_DEBUGGER_WORKER,
SET_DEBUGGER_LOCATION,
} from '../actions/debugger'
import config from '../utils/config'
function getStatusMessage(status, port) {
let message
switch (status) {
case 'new':
message = 'New Window'
break
case 'waiting':
message = 'Waiting for client connection'
break
case 'connected':
message = 'Connected'
break
case 'disconnected':
default:
message = 'Attempting reconnection'
}
if (status !== 'new') {
message += ` (port ${port})`
}
const title = `React Native Debugger - ${message}`
if (title !== document.title) {
document.title = title
}
return message
}
const initialState = {
worker: null,
status: 'disconnected',
statusMessage: getStatusMessage(config.isPortSettingRequired ? 'new' : 'disconnected', 8081),
location: {
host: 'localhost',
port: config.port || 8081,
},
isPortSettingRequired: config.isPortSettingRequired,
}
const actionsMap = {
[SET_DEBUGGER_STATUS]: (state, action) => {
const status = action.status || initialState.status
const newState = {
...state,
status,
statusMessage: getStatusMessage(status, state.location.port),
}
return newState
},
[SET_DEBUGGER_WORKER]: (state, action) => {
const status = action.status || initialState.status
const newState = {
...state,
worker: action.worker,
status,
statusMessage: getStatusMessage(status, state.location.port),
}
return newState
},
[SET_DEBUGGER_LOCATION]: (state, action) => {
const location = { ...state.location, ...action.loc }
const newState = {
...state,
location,
statusMessage: getStatusMessage(state.status, location.port),
isPortSettingRequired: false,
}
return newState
},
}
export default (state = initialState, action = {}) => {
const reduceFn = actionsMap[action.type]
if (!reduceFn) return state
return reduceFn(state, action)
}
================================================
FILE: app/reducers/index.js
================================================
import { combineReducers } from 'redux'
import { section } from '@redux-devtools/app/lib/esm/reducers/section'
// import { connection } from '@redux-devtools/app/lib/esm/reducers/connection';
// import { socket } from '@redux-devtools/app/lib/esm/reducers/socket';
import { monitor } from '@redux-devtools/app/lib/esm/reducers/monitor'
import { notification } from '@redux-devtools/app/lib/esm/reducers/notification'
import { instances } from '@redux-devtools/app/lib/esm/reducers/instances'
import { reports } from '@redux-devtools/app/lib/esm/reducers/reports'
import { theme } from '@redux-devtools/app/lib/esm/reducers/theme'
import setting from './setting'
import debuggerReducer from './debugger'
export default combineReducers({
section,
instances,
reports,
theme,
monitor,
notification,
setting,
debugger: debuggerReducer,
})
================================================
FILE: app/reducers/setting.js
================================================
import { TOGGLE_DEVTOOLS, RESIZE_DEVTOOLS, CHANGE_DEFAULT_THEME } from '../actions/setting'
const initialState = {
react: true,
redux: true,
size: 0.6,
themeName: null,
}
const actionsMap = {
[TOGGLE_DEVTOOLS]: (state, action) => ({
...state,
[action.name]: !state[action.name],
}),
[RESIZE_DEVTOOLS]: (state, action) => {
if (!state.redux || !state.react) {
return state
}
const { size } = action
if (size < 0.2) return { ...state, size: 0.2 }
if (size > 0.8) return { ...state, size: 0.8 }
return { ...state, size }
},
[CHANGE_DEFAULT_THEME]: (state, action) => ({
...state,
themeName: action.themeName,
}),
}
export default (state = initialState, action = {}) => {
const reduceFn = actionsMap[action.type]
if (!reduceFn) return state
return reduceFn(state, action)
}
================================================
FILE: app/setup.js
================================================
import config from './utils/config'
if (config.editor) {
process.env.EDITOR = config.editor
}
if (config.fontFamily) {
const styleEl = document.createElement('style')
document.head.appendChild(styleEl)
styleEl.sheet.insertRule(
`div *, span * { font-family: ${config.fontFamily} !important; }`,
0,
)
}
================================================
FILE: app/store/configureStore.js
================================================
import { createStore, applyMiddleware, compose } from 'redux'
import { persistReducer, persistStore } from 'redux-persist'
import localForage from 'localforage'
import { exportStateMiddleware } from '@redux-devtools/app/lib/cjs/middlewares/exportState'
import { instancesInitialState } from '@redux-devtools/app/lib/esm/reducers/instances'
import debuggerAPI from '../middlewares/debuggerAPI'
import reduxAPI from '../middlewares/reduxAPI'
import rootReducer from '../reducers'
const persistConfig = {
key: 'redux-devtools',
blacklist: ['instances', 'debugger'],
storage: localForage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const middlewares = applyMiddleware(
debuggerAPI,
exportStateMiddleware,
reduxAPI,
)
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
/* eslint-enable no-underscore-dangle */
const enhancer = composeEnhancers(middlewares)
const initialState = {
instances: {
...instancesInitialState,
selected: '',
},
}
export default (callback) => {
const store = createStore(persistedReducer, initialState, enhancer)
const persistor = persistStore(store, null, () => callback?.(store))
return { store, persistor }
}
================================================
FILE: app/utils/adb.js
================================================
import adb from 'adbkit'
export const client = adb.createClient({ host: '127.0.0.1' })
const reverse = (device, port) => client.reverse(device, `tcp:${port}`, `tcp:${port}`)
export const tryADBReverse = async (port) => {
const devices = await client.listDevices().filter((device) => device.type === 'device')
return Promise.all(devices.map((device) => reverse(device.id, port)))
}
================================================
FILE: app/utils/config.js
================================================
import { getCurrentWindow } from '@electron/remote'
export default getCurrentWindow().debuggerConfig || {}
================================================
FILE: app/utils/devMenu.js
================================================
import { TouchBar, nativeImage, getCurrentWindow } from '@electron/remote'
import { ipcRenderer } from 'electron'
import config from './config'
const { TouchBarButton, TouchBarSlider } = TouchBar || {}
const currentWindow = getCurrentWindow()
let worker
let availableMethods = []
/* reload, toggleElementInspector, networkInspect */
let leftBar = {}
let isSliderEnabled
let storeLiftedState
/* slider, prev, next */
let rightBar = {}
const getBarItems = (bar) => Object.keys(bar)
.map((key) => bar[key])
.filter((barItem) => !!barItem)
const setTouchBar = () => currentWindow.setTouchBar(
new TouchBar({
items: [
...getBarItems(leftBar),
...(isSliderEnabled ? getBarItems(rightBar) : []),
],
}),
)
const invokeDevMenuMethod = ({ name, args }) => worker && worker.postMessage({ method: 'invokeDevMenuMethod', name, args })
let networkInspectEnabled = !!config.networkInspect
const sendContextMenuUpdate = () => {
ipcRenderer.send(`context-menu-available-methods-update-${currentWindow.id}`, {
availableMethods,
networkInspectEnabled,
})
}
export const networkInspect = {
isEnabled: () => !!networkInspectEnabled,
getHighlightColor: () => (networkInspectEnabled ? '#7A7A7A' : '#363636'),
toggle() {
networkInspectEnabled = !networkInspectEnabled
sendContextMenuUpdate()
},
}
const devMenuMethods = {
reload: () => invokeDevMenuMethod({ name: 'reload' }),
toggleElementInspector: () => invokeDevMenuMethod({ name: 'toggleElementInspector' }),
show: () => invokeDevMenuMethod({ name: 'show' }),
networkInspect: () => {
networkInspect.toggle()
if (leftBar.networkInspect) {
leftBar.networkInspect.backgroundColor = networkInspect.getHighlightColor()
}
invokeDevMenuMethod({
name: 'networkInspect',
args: [networkInspectEnabled],
})
},
showAsyncStorage: () => {
invokeDevMenuMethod({ name: 'showAsyncStorage' })
},
clearAsyncStorage: () => {
if (
window.confirm(
'Call `AsyncStorage.clear()` in current React Native debug session?',
)
) {
invokeDevMenuMethod({ name: 'clearAsyncStorage' })
}
},
}
export const invokeDevMethod = (name) => () => {
if (availableMethods.includes(name)) {
return devMenuMethods[name]()
}
}
const hslShift = [0.5, 0.2, 0.8]
const icon = (name, resizeOpts) => {
const image = nativeImage.createFromNamedImage(name, hslShift)
return image.resize(resizeOpts)
}
let namedImages
const initNamedImages = () => {
if (process.platform !== 'darwin' || namedImages) return
namedImages = {
reload: icon('NSTouchBarRefreshTemplate', { height: 20 }),
toggleElementInspector: icon('NSTouchBarQuickLookTemplate', { height: 18 }),
networkInspect: icon('NSTouchBarRecordStartTemplate', { height: 20 }),
prev: icon('NSTouchBarGoBackTemplate', { height: 20 }),
next: icon('NSTouchBarGoForwardTemplate', { height: 20 }),
}
}
const setDevMenuMethodsForTouchBar = () => {
if (process.platform !== 'darwin') return
initNamedImages()
leftBar = {
// Default items
networkInspect: new TouchBarButton({
icon: namedImages.networkInspect,
click: devMenuMethods.networkInspect,
backgroundColor: networkInspect.getHighlightColor(),
}),
}
if (availableMethods.includes('reload')) {
leftBar.reload = new TouchBarButton({
icon: namedImages.reload,
click: devMenuMethods.reload,
})
}
if (availableMethods.includes('toggleElementInspector')) {
leftBar.toggleElementInspector = new TouchBarButton({
icon: namedImages.toggleElementInspector,
click: devMenuMethods.toggleElementInspector,
})
}
setTouchBar()
}
// Reset TouchBar when reload the app
setDevMenuMethodsForTouchBar([])
export const setDevMenuMethods = (list, wkr) => {
worker = wkr
availableMethods = list
sendContextMenuUpdate()
setDevMenuMethodsForTouchBar()
}
export const setReduxDevToolsMethods = (enabled, dispatch) => {
if (process.platform !== 'darwin') return
initNamedImages()
// Already setup
if (enabled && isSliderEnabled) return
const handleSliderChange = (nextIndex, dontUpdateTouchBarSlider = false) => dispatch({
type: 'JUMP_TO_STATE',
actionId: storeLiftedState.stagedActionIds[nextIndex],
index: nextIndex,
dontUpdateTouchBarSlider,
})
rightBar = {
slider: new TouchBarSlider({
value: 0,
minValue: 0,
maxValue: 0,
change(nextIndex) {
if (nextIndex !== storeLiftedState.currentStateIndex) {
// Set `dontUpdateTouchBarSlider` true for keep slide experience
handleSliderChange(nextIndex, true)
}
},
}),
prev: new TouchBarButton({
icon: namedImages.prev,
click() {
const nextIndex = storeLiftedState.currentStateIndex - 1
if (nextIndex >= 0) {
handleSliderChange(nextIndex)
}
},
}),
next: new TouchBarButton({
icon: namedImages.next,
click() {
const nextIndex = storeLiftedState.currentStateIndex + 1
if (nextIndex < storeLiftedState.computedStates.length) {
handleSliderChange(nextIndex)
}
},
}),
}
isSliderEnabled = enabled
setTouchBar()
}
export const updateSliderContent = (liftedState, dontUpdateTouchBarSlider) => {
if (process.platform !== 'darwin') return
storeLiftedState = liftedState
if (isSliderEnabled && !dontUpdateTouchBarSlider) {
const { currentStateIndex, computedStates } = liftedState
rightBar.slider.maxValue = computedStates.length - 1
rightBar.slider.value = currentStateIndex
}
}
================================================
FILE: app/utils/devtools.js
================================================
import { getCatchConsoleLogScript } from '../../electron/devtools'
let enabled = false
export const toggleOpenInEditor = (win, port) => {
if (win.devToolsWebContents) {
enabled = !enabled
return win.devToolsWebContents.executeJavaScript(`(() => {
${getCatchConsoleLogScript(port)}
window.__IS_OPEN_IN_EDITOR_ENABLED__ = ${enabled};
})()`)
}
}
export const isOpenInEditorEnabled = () => enabled
export const clearNetworkLogs = (win) => {
if (win.devToolsWebContents) {
return win.devToolsWebContents.executeJavaScript(`setTimeout(() => {
const { network } = UI.panels;
if (network && network.networkLogView && network.networkLogView.reset) {
network.networkLogView.reset()
}
}, 100)`)
}
}
export const selectRNDebuggerWorkerContext = (win) => {
if (win.devToolsWebContents) {
return win.devToolsWebContents.executeJavaScript(`setTimeout(() => {
const { console } = UI.panels;
if (console && console.view && console.view.consoleContextSelector) {
const selector = console.view.consoleContextSelector;
const item = selector.items.items.find(
item => item.label() === 'RNDebuggerWorker.js'
);
if (item) {
selector.itemSelected(item);
}
}
}, 100)`)
}
}
================================================
FILE: app/worker/.eslintrc
================================================
{
"rules": {
"no-restricted-globals": "off"
}
}
================================================
FILE: app/worker/apollo.js
================================================
export function handleApolloClient() {
// eslint-disable-next-line global-require
require('apollo-client-devtools/build/hook')
}
================================================
FILE: app/worker/asyncStorage.js
================================================
export const getClearAsyncStorageFn = (AsyncStorage) => {
if (!AsyncStorage.clear) return
return () => AsyncStorage.clear().catch((f) => f)
}
function convertError(error) {
if (!error) {
return null
}
const out = new Error(error.message)
out.key = error.key
return out
}
function convertErrors(errs) {
if (!errs) {
return null
}
return (Array.isArray(errs) ? errs : [errs]).map((e) => convertError(e))
}
export const getSafeAsyncStorage = (NativeModules) => {
const RCTAsyncStorage = NativeModules
&& (NativeModules.RNC_AsyncSQLiteDBStorage
|| NativeModules.RNCAsyncStorage
|| NativeModules.PlatformLocalStorage
|| NativeModules.AsyncRocksDBStorage
|| NativeModules.AsyncSQLiteDBStorage
|| NativeModules.AsyncLocalStorage)
return {
getItem(key) {
if (!RCTAsyncStorage) return Promise.resolve(null)
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiGet([key], (errors, result) => {
// Unpack result to get value from [[key,value]]
const value = result && result[0] && result[0][1] ? result[0][1] : null
const errs = convertErrors(errors)
if (errs) {
reject(errs[0])
} else {
resolve(value)
}
})
})
},
async setItem(key, value) {
if (!RCTAsyncStorage) return Promise.resolve(null)
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiSet([[key, value]], (errors) => {
const errs = convertErrors(errors)
if (errs) {
reject(errs[0])
} else {
resolve(null)
}
})
})
},
clear() {
if (!RCTAsyncStorage) return Promise.resolve(null)
return new Promise((resolve, reject) => {
RCTAsyncStorage.clear((error) => {
if (error && convertError(error)) {
reject(convertError(error))
} else {
resolve(null)
}
})
})
},
getAllKeys() {
if (!RCTAsyncStorage) return Promise.resolve(null)
return new Promise((resolve, reject) => {
RCTAsyncStorage.getAllKeys((error, keys) => {
if (error) {
reject(convertError(error))
} else {
resolve(keys)
}
})
})
},
}
}
export const getShowAsyncStorageFn = (AsyncStorage) => {
if (!AsyncStorage.getAllKeys || !AsyncStorage.getItem) return
return async () => {
const keys = await AsyncStorage.getAllKeys()
if (keys && keys.length) {
const items = await Promise.all(
keys.map((key) => AsyncStorage.getItem(key)),
)
const table = {}
keys.forEach((key, index) => {
table[key] = { content: items[index] }
})
console.table(table)
} else {
console.log('[RNDebugger] No AsyncStorage content.')
}
}
}
================================================
FILE: app/worker/devMenu.js
================================================
/* eslint-disable no-underscore-dangle */
import { toggleNetworkInspect } from './networkInspect'
import { getClearAsyncStorageFn, getShowAsyncStorageFn, getSafeAsyncStorage } from './asyncStorage'
let availableDevMenuMethods = {}
export const checkAvailableDevMenuMethods = ({ NativeModules }) => {
// RN 0.43 use DevSettings, DevMenu will be deprecated
const DevSettings = NativeModules.DevSettings || NativeModules.DevMenu
// Currently `show dev menu` is only on DevMenu
const showDevMenu = (DevSettings && DevSettings.show)
|| (NativeModules.DevMenu && NativeModules.DevMenu.show)
|| undefined
const AsyncStorage = getSafeAsyncStorage(NativeModules)
const methods = {
...DevSettings,
show: showDevMenu,
networkInspect: toggleNetworkInspect,
showAsyncStorage: getShowAsyncStorageFn(AsyncStorage),
clearAsyncStorage: getClearAsyncStorageFn(AsyncStorage),
}
if (methods.showAsyncStorage) {
window.showAsyncStorageContentInDev = methods.showAsyncStorage
}
const result = Object.keys(methods).filter((key) => !!methods[key])
availableDevMenuMethods = methods
postMessage({ __AVAILABLE_METHODS_CAN_CALL_BY_RNDEBUGGER__: result })
}
export const invokeDevMenuMethodIfAvailable = (name, args = []) => {
const method = availableDevMenuMethods[name]
if (method) method(...args)
}
================================================
FILE: app/worker/index.js
================================================
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/* global __fbBatchedBridge, importScripts: true */
// Edit from https://github.com/facebook/react-native/blob/master/local-cli/server/util/debuggerWorker.js
import './setup'
import { checkAvailableDevMenuMethods, invokeDevMenuMethodIfAvailable } from './devMenu'
import { reportDefaultReactDevToolsPort } from './reactDevTools'
import devToolsEnhancer, { composeWithDevTools } from './reduxAPI'
import * as RemoteDev from './remotedev'
import { getRequiredModules } from './utils'
import { toggleNetworkInspect } from './networkInspect'
import { handleApolloClient } from './apollo'
/* eslint-disable no-underscore-dangle */
self.__REMOTEDEV__ = RemoteDev
devToolsEnhancer.send = RemoteDev.send
devToolsEnhancer.connect = RemoteDev.connect
devToolsEnhancer.disconnect = RemoteDev.disconnect
// Deprecated API, these may removed when redux-devtools-extension 3.0 release
self.devToolsExtension = devToolsEnhancer
self.reduxNativeDevTools = devToolsEnhancer
self.reduxNativeDevToolsCompose = composeWithDevTools
self.__REDUX_DEVTOOLS_EXTENSION__ = devToolsEnhancer
self.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = composeWithDevTools
const setupRNDebuggerBeforeImportScript = (message) => {
self.__REACT_DEVTOOLS_PORT__ = message.reactDevToolsPort
if (message.networkInspect) {
self.__NETWORK_INSPECT__ = toggleNetworkInspect
}
}
const noop = (f) => f
const setupRNDebugger = async (message) => {
// We need to regularly update JS runtime
// because the changes of worker message (Redux DevTools, DevMenu)
// doesn't notify to the remote JS runtime
self.__RND_INTERVAL__ = setInterval(noop, 100); // eslint-disable-line
handleApolloClient()
toggleNetworkInspect(message.networkInspect)
const modules = await getRequiredModules(message.moduleSize)
if (modules) {
checkAvailableDevMenuMethods(modules)
reportDefaultReactDevToolsPort(modules)
}
}
const messageHandlers = {
executeApplicationScript(message, sendReply) {
setupRNDebuggerBeforeImportScript(message)
Object.keys(message.inject).forEach((key) => {
self[key] = JSON.parse(message.inject[key])
})
let error
try {
importScripts(message.url)
} catch (err) {
error = err.message
}
if (!error) {
setupRNDebugger(message)
}
sendReply(null /* result */, error)
return false
},
emitReduxMessage() {
// pass to other listeners
return true
},
emitApolloMessage() {
// pass to other listeners
return true
},
invokeDevMenuMethod({ name, args }) {
invokeDevMenuMethodIfAvailable(name, args)
return false
},
beforeTerminate() {
// Clean for notify native bridge
if (window.__RND_INTERVAL__) {
clearInterval(window.__RND_INTERVAL__)
window.__RND_INTERVAL__ = null
}
return false
},
}
addEventListener('message', (message) => {
const object = message.data
const sendReply = (result, error) => {
postMessage({ replyID: object.id, result, error })
}
const handler = messageHandlers[object.method]
if (handler) {
// Special cased handlers
return handler(object, sendReply)
}
// Other methods get called on the bridge
let returnValue = [[], [], [], 0]
let error
try {
if (typeof __fbBatchedBridge === 'object') {
returnValue = __fbBatchedBridge[object.method].apply(null, object.arguments)
} else {
error = 'Failed to call function, __fbBatchedBridge is undefined'
}
} catch (err) {
error = err.message
} finally {
sendReply(JSON.stringify(returnValue), error)
}
return false
})
================================================
FILE: app/worker/networkInspect.js
================================================
import getRNDebuggerFetchPolyfills from './polyfills/fetch'
const isWorkerMethod = (fn) => String(fn).indexOf('[native code]') > -1
/* eslint-disable no-underscore-dangle */
let networkInspect
export const toggleNetworkInspect = (enabled) => {
if (!enabled && networkInspect) {
self.fetch = networkInspect.fetch
self.XMLHttpRequest = networkInspect.XMLHttpRequest
self.FormData = networkInspect.FormData
self.Headers = networkInspect.Headers
self.Request = networkInspect.Request
self.Response = networkInspect.Response
networkInspect = null
return
}
if (!enabled) return
if (enabled && networkInspect) return
if (isWorkerMethod(self.XMLHttpRequest) || isWorkerMethod(self.FormData)) {
console.warn(
'[RNDebugger] '
+ 'I tried to enable Network Inspect but XHR '
+ "have been replaced by worker's XHR. "
+ 'You can disable Network Inspect (documentation: https://goo.gl/BVvEkJ) '
+ 'or tracking your app code if you have called '
+ '`global.XMLHttpRequest = global.originalXMLHttpRequest`.',
)
return
}
networkInspect = {
fetch: self.fetch,
XMLHttpRequest: self.XMLHttpRequest,
FormData: self.FormData,
Headers: self.Headers,
Request: self.Request,
Response: self.Response,
}
self.XMLHttpRequest = self.originalXMLHttpRequest
? self.originalXMLHttpRequest
: self.XMLHttpRequest
self.FormData = self.originalFormData ? self.originalFormData : self.FormData
const {
fetch, Headers, Request, Response,
} = getRNDebuggerFetchPolyfills()
self.fetch = fetch
self.Headers = Headers
self.Request = Request
self.Response = Response
console.log(
'[RNDebugger]',
'Network Inspect is enabled,',
'see the documentation (https://goo.gl/yEcRrU) for more information.',
)
}
/*
* `originalXMLHttpRequest` haven't permission to set forbidden header name
* (https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name)
* We have to use Electron session to solve this problem (See electron/main.js)
*/
const forbiddenHeaderNames = [
'Accept-Charset',
'Accept-Encoding',
'Access-Control-Request-Headers',
'Access-Control-Request-Method',
'Connection',
'Content-Length',
'Cookie',
'Cookie2',
'Date',
'DNT',
'Expect',
'Host',
'Keep-Alive',
'Origin',
'Referer',
'TE',
'Trailer',
'Transfer-Encoding',
'Upgrade',
'Via',
// Actually it still blocked on Chrome
'User-Agent',
]
forbiddenHeaderNames.forEach((name) => forbiddenHeaderNames.push(name.toLowerCase()))
const isForbiddenHeaderName = (header) => forbiddenHeaderNames.includes(header)
|| header.startsWith('Proxy-')
|| header.startsWith('proxy-')
|| header.startsWith('Sec-')
|| header.startsWith('sec-')
export const replaceForbiddenHeadersForWorkerXHR = () => {
if (!isWorkerMethod(self.XMLHttpRequest)) return
const originalSetRequestHeader = self.XMLHttpRequest.prototype.setRequestHeader
self.XMLHttpRequest.prototype.setRequestHeader = function setRequestHeader(header, value) {
let replacedHeader = header
if (isForbiddenHeaderName(header)) {
replacedHeader = `__RN_DEBUGGER_SET_HEADER_REQUEST_${header}`
}
return originalSetRequestHeader.call(this, replacedHeader, value)
}
}
export const addURIWarningForWorkerFormData = () => {
if (!isWorkerMethod(self.FormData)) return
const originAppend = FormData.prototype.append
self.FormData.prototype.append = function append(key, value) {
if (value && value.uri) {
console.warn(
'[RNDebugger] '
+ "Detected you're enabled Network Inspect and using `uri` in FormData, "
+ 'it will be a problem if you use it for upload, '
+ 'please see the documentation (https://goo.gl/yEcRrU) for more information.',
)
}
return originAppend.call(this, key, value)
}
}
================================================
FILE: app/worker/polyfills/fetch.js
================================================
/* eslint-disable no-underscore-dangle */
/* eslint-disable no-param-reassign */
/* eslint-disable no-prototype-builtins */
/* eslint-disable no-new */
/* eslint-disable no-restricted-syntax */
/* eslint-disable func-names */
export default function getRNDebuggerFetchPolyfills() {
const support = {
searchParams: 'URLSearchParams' in self,
iterable: 'Symbol' in self && 'iterator' in Symbol,
blob: false, // NOTE: Default for RNDebugger
formData: 'FormData' in self,
arrayBuffer: 'ArrayBuffer' in self,
}
function isDataView(obj) {
return obj && DataView.prototype.isPrototypeOf(obj)
}
let isArrayBufferView
if (support.arrayBuffer) {
const viewClasses = [
'[object Int8Array]',
'[object Uint8Array]',
'[object Uint8ClampedArray]',
'[object Int16Array]',
'[object Uint16Array]',
'[object Int32Array]',
'[object Uint32Array]',
'[object Float32Array]',
'[object Float64Array]',
]
isArrayBufferView = ArrayBuffer.isView
|| function (obj) {
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
}
}
function normalizeName(name) {
if (typeof name !== 'string') {
name = String(name)
}
if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name) || name === '') {
throw new TypeError('Invalid character in header field name')
}
return name.toLowerCase()
}
function normalizeValue(value) {
if (typeof value !== 'string') {
value = String(value)
}
return value
}
// Build a destructive iterator for the value list
function iteratorFor(items) {
const iterator = {
next() {
const value = items.shift()
return { done: value === undefined, value }
},
}
if (support.iterable) {
iterator[Symbol.iterator] = function () {
return iterator
}
}
return iterator
}
function Headers(headers) {
this.map = {}
if (headers instanceof Headers) {
headers.forEach(function (value, name) {
this.append(name, value)
}, this)
} else if (Array.isArray(headers)) {
headers.forEach(function (header) {
this.append(header[0], header[1])
}, this)
} else if (headers) {
Object.getOwnPropertyNames(headers).forEach(function (name) {
this.append(name, headers[name])
}, this)
}
}
Headers.prototype.append = function (name, value) {
name = normalizeName(name)
value = normalizeValue(value)
const oldValue = this.map[name]
this.map[name] = oldValue ? `${oldValue}, ${value}` : value
}
Headers.prototype.delete = function (name) {
delete this.map[normalizeName(name)]
}
Headers.prototype.get = function (name) {
name = normalizeName(name)
return this.has(name) ? this.map[name] : null
}
Headers.prototype.has = function (name) {
return this.map.hasOwnProperty(normalizeName(name))
}
Headers.prototype.set = function (name, value) {
this.map[normalizeName(name)] = normalizeValue(value)
}
Headers.prototype.forEach = function (callback, thisArg) {
for (const name in this.map) {
if (this.map.hasOwnProperty(name)) {
callback.call(thisArg, this.map[name], name, this)
}
}
}
Headers.prototype.keys = function () {
const items = []
this.forEach((value, name) => {
items.push(name)
})
return iteratorFor(items)
}
Headers.prototype.values = function () {
const items = []
this.forEach((value) => {
items.push(value)
})
return iteratorFor(items)
}
Headers.prototype.entries = function () {
const items = []
this.forEach((value, name) => {
items.push([name, value])
})
return iteratorFor(items)
}
if (support.iterable) {
Headers.prototype[Symbol.iterator] = Headers.prototype.entries
}
function consumed(body) {
if (body.bodyUsed) {
return Promise.reject(new TypeError('Already read'))
}
body.bodyUsed = true
}
function fileReaderReady(reader) {
return new Promise((resolve, reject) => {
reader.onload = function () {
resolve(reader.result)
}
reader.onerror = function () {
reject(reader.error)
}
})
}
function readBlobAsArrayBuffer(blob) {
const reader = new FileReader()
const promise = fileReaderReady(reader)
reader.readAsArrayBuffer(blob)
return promise
}
function readBlobAsText(blob) {
const reader = new FileReader()
const promise = fileReaderReady(reader)
reader.readAsText(blob)
return promise
}
function readArrayBufferAsText(buf) {
const view = new Uint8Array(buf)
const chars = new Array(view.length)
for (let i = 0; i < view.length; i += 1) {
chars[i] = String.fromCharCode(view[i])
}
return chars.join('')
}
function bufferClone(buf) {
if (buf.slice) {
return buf.slice(0)
}
const view = new Uint8Array(buf.byteLength)
view.set(new Uint8Array(buf))
return view.buffer
}
function decode(body) {
const form = new FormData()
body
.trim()
.split('&')
.forEach((bytes) => {
if (bytes) {
const split = bytes.split('=')
const name = split.shift().replace(/\+/g, ' ')
const value = split.join('=').replace(/\+/g, ' ')
form.append(decodeURIComponent(name), decodeURIComponent(value))
}
})
return form
}
function Body() {
this.bodyUsed = false
this._initBody = function (body) {
this._bodyInit = body
if (!body) {
this._bodyText = ''
} else if (typeof body === 'string') {
this._bodyText = body
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
this._bodyBlob = body
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
this._bodyFormData = body
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this._bodyText = body.toString()
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
this._bodyArrayBuffer = bufferClone(body.buffer)
// IE 10-11 can't handle a DataView body.
this._bodyInit = new Blob([this._bodyArrayBuffer])
} else if (
support.arrayBuffer
&& (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))
) {
this._bodyArrayBuffer = bufferClone(body)
} else {
const bodyText = Object.prototype.toString.call(body)
body = bodyText
this._bodyText = bodyText
}
if (!this.headers.get('content-type')) {
if (typeof body === 'string') {
this.headers.set('content-type', 'text/plain;charset=UTF-8')
} else if (this._bodyBlob && this._bodyBlob.type) {
this.headers.set('content-type', this._bodyBlob.type)
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8')
}
}
}
if (support.blob) {
this.blob = function () {
const rejected = consumed(this)
if (rejected) {
return rejected
}
if (this._bodyBlob) {
return Promise.resolve(this._bodyBlob)
} if (this._bodyArrayBuffer) {
return Promise.resolve(new Blob([this._bodyArrayBuffer]))
} if (this._bodyFormData) {
throw new Error('could not read FormData body as blob')
} else {
return Promise.resolve(new Blob([this._bodyText]))
}
}
this.arrayBuffer = function () {
if (this._bodyArrayBuffer) {
return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
}
return this.blob().then(readBlobAsArrayBuffer)
}
}
this.text = function () {
const rejected = consumed(this)
if (rejected) {
return rejected
}
if (this._bodyBlob) {
return readBlobAsText(this._bodyBlob)
} if (this._bodyArrayBuffer) {
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
} if (this._bodyFormData) {
throw new Error('could not read FormData body as text')
} else {
return Promise.resolve(this._bodyText)
}
}
if (support.formData) {
this.formData = function () {
return this.text().then(decode)
}
}
this.json = function () {
return this.text().then(JSON.parse)
}
return this
}
// HTTP methods whose capitalization should be normalized
const methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
function normalizeMethod(method) {
const upcased = method.toUpperCase()
return methods.indexOf(upcased) > -1 ? upcased : method
}
function Request(input, options) {
options = options || {}
let { body } = options
if (input instanceof Request) {
if (input.bodyUsed) {
throw new TypeError('Already read')
}
this.url = input.url
this.credentials = input.credentials
if (!options.headers) {
this.headers = new Headers(input.headers)
}
this.method = input.method
this.mode = input.mode
this.signal = input.signal
if (!body && input._bodyInit != null) {
body = input._bodyInit
input.bodyUsed = true
}
} else {
this.url = String(input)
}
this.credentials = options.credentials || this.credentials || 'same-origin'
if (options.headers || !this.headers) {
this.headers = new Headers(options.headers)
}
this.method = normalizeMethod(options.method || this.method || 'GET')
this.mode = options.mode || this.mode || null
this.signal = options.signal || this.signal
this.referrer = null
if ((this.method === 'GET' || this.method === 'HEAD') && body) {
throw new TypeError('Body not allowed for GET or HEAD requests')
}
this._initBody(body)
}
Request.prototype.clone = function () {
return new Request(this, { body: this._bodyInit })
}
function parseHeaders(rawHeaders) {
const headers = new Headers()
// Replace instances of \r\n and \n followed by
// at least one space or horizontal tab with a space
// https://tools.ietf.org/html/rfc7230#section-3.2
const preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ')
preProcessedHeaders.split(/\r?\n/).forEach((line) => {
const parts = line.split(':')
const key = parts.shift().trim()
if (key) {
const value = parts.join(':').trim()
headers.append(key, value)
}
})
return headers
}
Body.call(Request.prototype)
function Response(bodyInit, options) {
if (!options) {
options = {}
}
this.type = 'default'
this.status = options.status === undefined ? 200 : options.status
this.ok = this.status >= 200 && this.status < 300
this.statusText = 'statusText' in options ? options.statusText : 'OK'
this.headers = new Headers(options.headers)
this.url = options.url || ''
this._initBody(bodyInit)
}
Body.call(Response.prototype)
Response.prototype.clone = function () {
return new Response(this._bodyInit, {
status: this.status,
statusText: this.statusText,
headers: new Headers(this.headers),
url: this.url,
})
}
Response.error = function () {
const response = new Response(null, { status: 0, statusText: '' })
response.type = 'error'
return response
}
const redirectStatuses = [301, 302, 303, 307, 308]
Response.redirect = function (url, status) {
if (redirectStatuses.indexOf(status) === -1) {
throw new RangeError('Invalid status code')
}
return new Response(null, { status, headers: { location: url } })
}
let { DOMException } = self
try {
new DOMException()
} catch (err) {
DOMException = function (message, name) {
this.message = message
this.name = name
const error = Error(message)
this.stack = error.stack
}
DOMException.prototype = Object.create(Error.prototype)
DOMException.prototype.constructor = DOMException
}
function fetch(input, init) {
return new Promise((resolve, reject) => {
const request = new Request(input, init)
if (request.signal && request.signal.aborted) {
reject(new DOMException('Aborted', 'AbortError'))
return
}
const xhr = new XMLHttpRequest()
function abortXhr() {
xhr.abort()
}
xhr.onload = function () {
const options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || ''),
}
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
const body = 'response' in xhr ? xhr.response : xhr.responseText
resolve(new Response(body, options))
}
xhr.onerror = function () {
reject(new TypeError('Network request failed'))
}
xhr.ontimeout = function () {
reject(new TypeError('Network request failed'))
}
xhr.onabort = function () {
reject(new DOMException('Aborted', 'AbortError'))
}
xhr.open(request.method, request.url, true)
if (request.credentials === 'include') {
xhr.withCredentials = true
} else if (request.credentials === 'omit') {
xhr.withCredentials = false
}
if ('responseType' in xhr && support.blob) {
xhr.responseType = 'blob'
}
request.headers.forEach((value, name) => {
xhr.setRequestHeader(name, value)
})
if (request.signal) {
request.signal.addEventListener('abort', abortXhr)
xhr.onreadystatechange = function () {
// DONE (success or failure)
if (xhr.readyState === 4) {
request.signal.removeEventListener('abort', abortXhr)
}
}
}
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
})
}
fetch.polyfill = true
return {
fetch,
Headers,
Request,
Response,
}
}
================================================
FILE: app/worker/reactDevTools.js
================================================
/* eslint-disable no-underscore-dangle */
const methodGlobalName = '__REPORT_REACT_DEVTOOLS_PORT__'
const reportReactDevToolsPort = (port, platform) => postMessage({
[methodGlobalName]: port,
platform,
})
export const reportDefaultReactDevToolsPort = async ({ setupDevtools, Platform }) => {
if (Platform.__empty) return
/*
* [Fallback] React Native version under 0.39 can't specified the port
*/
if (
typeof setupDevtools === 'function'
&& setupDevtools.toString().indexOf('window.__REACT_DEVTOOLS_PORT__') === -1
) {
reportReactDevToolsPort(8097, Platform.OS)
} else {
// React Inspector will keep the last reported port even if reload JS,
// because we don't want to icrease the user waiting time for reload JS.
// We need back to use the random port if we don't need fallback
reportReactDevToolsPort(window.__REACT_DEVTOOLS_PORT__, Platform.OS)
}
}
================================================
FILE: app/worker/reduxAPI.js
================================================
import { instrument } from '@redux-devtools/instrument'
import {
evalAction,
getActionsArray,
generateId,
stringify,
getSeralizeParameter,
importState,
getLocalFilter,
isFiltered,
filterStagedActions,
filterState,
} from '@redux-devtools/utils'
import { updateStackWithSourceMap } from './utils'
function configureStore(next, subscriber, options) {
return instrument(subscriber, options)(next)
}
const instances = {
/* [id]: { name, store, ... } */
}
let lastAction
let isExcess
let listenerAdded
let locked
let paused
function getStackTrace(config, toExcludeFromTrace) {
if (!config.trace) return undefined
if (typeof config.trace === 'function') return config.trace()
let stack
let extraFrames = 0
let prevStackTraceLimit
const { traceLimit } = config
const error = Error()
if (Error.captureStackTrace) {
if (Error.stackTraceLimit < traceLimit) {
prevStackTraceLimit = Error.stackTraceLimit
Error.stackTraceLimit = traceLimit
}
Error.captureStackTrace(error, toExcludeFromTrace)
} else {
extraFrames = 3
}
stack = error.stack
if (prevStackTraceLimit) Error.stackTraceLimit = prevStackTraceLimit
if (
extraFrames
|| typeof Error.stackTraceLimit !== 'number'
|| Error.stackTraceLimit > traceLimit
) {
const frames = stack.split('\n')
if (frames.length > traceLimit) {
stack = frames
.slice(0, traceLimit + extraFrames + (frames[0] === 'Error' ? 1 : 0))
.join('\n')
}
}
return updateStackWithSourceMap(stack)
}
function getLiftedState(store, filters) {
return filterStagedActions(store.liftedStore.getState(), filters)
}
function relay(type, state, instance, action, nextActionId) {
const {
filters,
predicate,
stateSanitizer,
actionSanitizer,
serializeState,
serializeAction,
} = instance
const message = {
type,
id: instance.id,
name: instance.name,
}
if (state) {
message.payload = type === 'ERROR'
? state
: stringify(
filterState(
state,
type,
filters,
stateSanitizer,
actionSanitizer,
nextActionId,
predicate,
),
serializeState,
)
}
if (type === 'ACTION') {
action.stack = getStackTrace(instance, true)
message.action = stringify(
!actionSanitizer ? action : actionSanitizer(action.action, nextActionId - 1),
serializeAction,
)
message.isExcess = isExcess
message.nextActionId = nextActionId
} else if (instance) {
message.libConfig = {
type: 'redux',
actionCreators: stringify(instance.actionCreators),
serialize: !!instance.serialize,
}
}
postMessage({ __IS_REDUX_NATIVE_MESSAGE__: true, content: message })
}
function dispatchRemotely(action, instance) {
try {
const { store, actionCreators } = instance
const result = evalAction(action, actionCreators)
store.dispatch(result)
} catch (e) {
relay('ERROR', e.message, instance)
}
}
function importPayloadFrom(store, state, instance) {
try {
const nextLiftedState = importState(state, instance)
if (!nextLiftedState) return
store.liftedStore.dispatch({ type: 'IMPORT_STATE', ...nextLiftedState })
relay('STATE', getLiftedState(store, instance.filters), instance)
} catch (e) {
relay('ERROR', e.message, instance)
}
}
function exportState({ id: instanceId, store, serializeState }) {
const liftedState = store.liftedStore.getState()
const { actionsById } = liftedState
const payload = []
liftedState.stagedActionIds.slice(1).forEach((id) => {
payload.push(actionsById[id].action)
})
postMessage({
__IS_REDUX_NATIVE_MESSAGE__: true,
content: {
type: 'EXPORT',
payload: stringify(payload, serializeState),
committedState:
typeof liftedState.committedState !== 'undefined'
? stringify(liftedState.committedState, serializeState)
: undefined,
instanceId,
},
})
}
function handleMessages(message) {
const {
id, instanceId, type, action, state, toAll,
} = message
if (toAll) {
Object.keys(instances).forEach((key) => {
handleMessages({ ...message, id: key, toAll: false })
})
return false
}
const instance = instances[id || instanceId]
if (!instance) return true
const { store, filters } = instance
if (!store) return false
switch (type) {
case 'DISPATCH':
store.liftedStore.dispatch(action)
break
case 'ACTION':
dispatchRemotely(action, instance)
break
case 'IMPORT':
importPayloadFrom(store, state, instance)
break
case 'EXPORT':
exportState(instance)
break
case 'UPDATE':
relay('STATE', getLiftedState(store, filters), instance)
break
default:
break
}
return false
}
function start(instance) {
if (!listenerAdded) {
self.addEventListener('message', (message) => {
const { method, content } = message.data
if (method === 'emitReduxMessage') {
handleMessages(content)
}
})
listenerAdded = true
}
const { store, actionCreators, filters } = instance
if (typeof actionCreators === 'function') {
instance.actionCreators = actionCreators()
}
relay('STATE', getLiftedState(store, filters), instance)
}
function checkForReducerErrors(liftedState, instance) {
if (liftedState.computedStates[liftedState.currentStateIndex].error) {
relay('STATE', filterStagedActions(liftedState, instance.filters), instance)
return true
}
return false
}
function monitorReducer(state = {}, action = {}) {
lastAction = action.type
return state
}
function handleChange(state, liftedState, maxAge, instance) {
if (checkForReducerErrors(liftedState, instance)) return
const { filters, predicate } = instance
if (lastAction === 'PERFORM_ACTION') {
const { nextActionId } = liftedState
const liftedAction = liftedState.actionsById[nextActionId - 1]
if (isFiltered(liftedAction.action, filters)) return
if (predicate && !predicate(state, liftedAction.action)) return
relay('ACTION', state, instance, liftedAction, nextActionId)
if (!isExcess && maxAge) isExcess = liftedState.stagedActionIds.length >= maxAge
} else {
if (lastAction === 'JUMP_TO_STATE') return
if (lastAction === 'PAUSE_RECORDING') {
paused = liftedState.isPaused
} else if (lastAction === 'LOCK_CHANGES') {
locked = liftedState.isLocked
}
if (paused || locked) {
if (lastAction) lastAction = undefined
else return
}
relay('STATE', filterStagedActions(liftedState, filters), instance)
}
}
export default function devToolsEnhancer(options = {}) {
const {
name,
maxAge = 30,
shouldCatchErrors = !!global.shouldCatchErrors,
shouldHotReload,
shouldRecordChanges,
shouldStartLocked,
pauseActionType = '@@PAUSED',
actionCreators,
filters,
actionsBlacklist,
actionsWhitelist,
actionSanitizer,
stateSanitizer,
deserializeState,
deserializeAction,
serialize,
predicate,
trace,
traceLimit,
} = options
const id = generateId(options.instanceId)
const serializeState = getSeralizeParameter(options, 'serializeState')
const serializeAction = getSeralizeParameter(options, 'serializeAction')
return (next) => (reducer, initialState) => {
const store = configureStore(next, monitorReducer, {
maxAge,
shouldCatchErrors,
shouldHotReload,
shouldRecordChanges,
shouldStartLocked,
pauseActionType,
})(reducer, initialState)
instances[id] = {
name: name || id,
id,
store,
filters: getLocalFilter({
actionsWhitelist: (filters && filters.whitelist) || actionsWhitelist,
actionsBlacklist: (filters && filters.blacklist) || actionsBlacklist,
}),
actionCreators: actionCreators && (() => getActionsArray(actionCreators)),
stateSanitizer,
actionSanitizer,
deserializeState,
deserializeAction,
serializeState,
serializeAction,
serialize,
predicate,
trace,
traceLimit,
}
start(instances[id])
store.subscribe(() => {
handleChange(store.getState(), store.liftedStore.getState(), maxAge, instances[id])
})
return store
}
}
const preEnhancer = (instanceId) => (next) => (reducer, initialState, enhancer) => {
const store = next(reducer, initialState, enhancer)
if (instances[instanceId]) {
instances[instanceId].store = store
}
return {
...store,
dispatch: (action) => (locked ? action : store.dispatch(action)),
}
}
devToolsEnhancer.updateStore = (newStore, instanceId) => {
console.warn(
'[RNDebugger]',
'`updateStore` is deprecated use `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__` instead:',
'https://github.com/jhen0409/react-native-debugger/blob/master/docs/redux-devtools-integration.md',
)
const keys = Object.keys(instances)
if (!keys.length) return
if (keys.length > 1 && !instanceId) {
console.warn(
'You have multiple stores,',
'please provide `instanceId` argument (`updateStore(store, instanceId)`)',
)
}
if (instanceId) {
const instance = instances[instanceId]
if (!instance) return
instance.store = newStore
} else {
instances[keys[0]].store = newStore
}
}
const compose = (options) => (...funcs) => (...args) => {
const instanceId = generateId(options.instanceId)
return [preEnhancer(instanceId), ...funcs].reduceRight(
(composed, f) => f(composed),
devToolsEnhancer({ ...options, instanceId })(...args),
)
}
export function composeWithDevTools(...funcs) {
if (funcs.length === 0) {
return devToolsEnhancer()
}
if (funcs.length === 1 && typeof funcs[0] === 'object') {
return compose(funcs[0])
}
return compose({})(...funcs)
}
================================================
FILE: app/worker/remotedev.js
================================================
// Edit from https://github.com/zalmoxisus/remotedev/blob/master/src/devTools.js
import { stringify, parse } from 'jsan'
import { generateId, getActionsArray } from '@redux-devtools/utils'
let listenerAdded
const listeners = {}
export function extractState(message) {
if (!message || !message.state) return undefined
if (typeof message.state === 'string') return parse(message.state)
return message.state
}
function handleMessages(message) {
if (!message.payload) {
message.payload = message.action
}
const fn = listeners[message.instanceId]
if (!fn) return true
if (typeof fn === 'function') {
fn(message)
} else {
fn.forEach((func) => func(message))
}
return false
}
export function start() {
if (!listenerAdded) {
self.addEventListener('message', (message) => {
const { method, content } = message.data
if (method === 'emitReduxMessage') {
return handleMessages(content)
}
})
listenerAdded = true
}
}
function transformAction(action, config) {
if (action.action) return action
const liftedAction = { timestamp: Date.now() }
if (action) {
if (config.getActionType) {
liftedAction.action = config.getActionType(action)
} else if (typeof action === 'string') {
liftedAction.action = { type: action }
} else if (!action.type) {
liftedAction.action = { type: 'update' }
} else {
liftedAction.action = action
}
} else {
liftedAction.action = { type: action }
}
return liftedAction
}
export function send(action, state, type, options) {
start()
setTimeout(() => {
const message = {
payload: state ? stringify(state) : '',
action: type === 'ACTION' ? stringify(transformAction(action, options)) : action,
type: type || 'ACTION',
id: options.instanceId,
instanceId: options.instanceId,
name: options.name,
}
message.libConfig = {
type: options.type,
name: options.name,
serialize: !!options.serialize,
actionCreators: options.actionCreators,
}
postMessage({ __IS_REDUX_NATIVE_MESSAGE__: true, content: message })
}, 0)
}
export function connect(options = {}) {
const id = generateId(options.instanceId)
const opts = {
...options,
instanceId: id,
name: options.name || id,
actionCreators: JSON.stringify(getActionsArray(options.actionCreators || {})),
}
start()
return {
init(state, action) {
send(action || {}, state, 'INIT', opts)
},
subscribe(listener) {
if (!listener) return undefined
if (!listeners[id]) listeners[id] = []
listeners[id].push(listener)
return function unsubscribe() {
const index = listeners[id].indexOf(listener)
listeners[id].splice(index, 1)
}
},
unsubscribe() {
delete listeners[id]
},
send(action, payload) {
if (action) {
send(action, payload, 'ACTION', opts)
} else {
send(undefined, payload, 'STATE', opts)
}
},
error(payload) {
send(undefined, payload, 'Error', opts)
},
}
}
// Not implemented
export function disconnect() {}
================================================
FILE: app/worker/setup.js
================================================
import {
replaceForbiddenHeadersForWorkerXHR,
addURIWarningForWorkerFormData,
} from './networkInspect'
// Add the missing `global` for WebWorker
self.global = self
/*
* Blob is not supported for RN < 0.54,
* we should remove it in WebWorker because
* it will used for `whatwg-fetch` on older RN versions
*/
if (self.Blob && self.Blob.toString() === 'function Blob() { [native code] }') {
/*
* RN > 0.54 will polyfill Blob.
* If it is deleted before the RN setup, RN will not add a reference to the original.
* We will need to be able to restore the original when running RN > 0.54 for networking tools,
* so add the reference here as react-native will not do it if the original is deleted
*/
self.originalBlob = self.Blob
delete self.Blob
}
if (
self.XMLHttpRequest
&& self.XMLHttpRequest.toString() === 'function XMLHttpRequest() { [native code] }'
) {
self.originalXMLHttpRequest = self.XMLHttpRequest
}
if (self.FormData && self.FormData.toString() === 'function FormData() { [native code] }') {
self.originalFormData = self.FormData
}
// Catch native fetch
if (self.fetch && self.fetch.toString() === 'function fetch() { [native code] }') {
/* eslint-disable-next-line no-underscore-dangle */
self.__ORIGINAL_FETCH__ = self.fetch
}
replaceForbiddenHeadersForWorkerXHR()
addURIWarningForWorkerFormData()
================================================
FILE: app/worker/utils.js
================================================
/* eslint-disable no-underscore-dangle */
// Avoid warning of use metro require on dev mode
// it actually unnecessary for RN >= 0.56, so it is backward compatibility
const avoidWarnForRequire = (moduleNames) => {
if (!moduleNames.length) moduleNames.push('NativeModules')
return new Promise((resolve) => {
setTimeout(() => {
// It's replaced console.warn of react-native
const originalWarn = console.warn
console.warn = (...args) => {
if (
args[0]
&& moduleNames.some(
(name) => args[0].indexOf(`Requiring module '${name}' by name`) > -1,
)
) {
return
}
return originalWarn(...args)
}
resolve(() => {
console.warn = originalWarn
})
})
})
}
let reactNative
const getRequireMethod = () => {
// RN >= 0.57
if (typeof window.__r === 'function') return window.__r
// RN < 0.57
if (typeof window.require === 'function') return window.require
}
const lookupForRNModules = (size = 999) => {
const metroRequire = getRequireMethod()
let actualSize = size
let getModule = metroRequire
if (metroRequire.getModules) {
const mods = metroRequire.getModules()
actualSize = Object.keys(mods).length
getModule = (moduleId) => {
const mod = mods && mods[moduleId]
return (mod && mod.publicModule && mod.publicModule.exports) || null
}
} else {
getModule = (moduleId) => metroRequire(moduleId)
}
for (let moduleId = 0; moduleId <= actualSize - 1; moduleId += 1) {
const rn = getModule(moduleId)
if (rn && rn.requireNativeComponent && rn.NativeModules) {
return rn
}
}
return null
}
const getModule = (name, size) => {
let result
try {
const metroRequire = getRequireMethod()
// RN >= 0.56
if (metroRequire.name === 'metroRequire') {
const rn = reactNative || lookupForRNModules(size)
reactNative = rn
global.$reactNative = rn
result = reactNative && reactNative[name]
} else if (metroRequire.name === '_require') {
result = metroRequire(name)
}
} catch (e) {} // eslint-disable-line
return result || { __empty: true }
}
const requiredModules = {
MessageQueue: (size) => (self.__fbBatchedBridge
&& Object.getPrototypeOf(self.__fbBatchedBridge).constructor)
|| getModule('MessageQueue', size),
NativeModules: (size) => getModule('NativeModules', size),
Platform: (size) => getModule('Platform', size),
setupDevtools: (size) => getModule('setupDevtools', size),
}
export const getRequiredModules = async (size) => {
if (!window.__DEV__ || !getRequireMethod()) return
const done = await avoidWarnForRequire(Object.keys(requiredModules))
const modules = {}
Object.keys(requiredModules).forEach((name) => {
modules[name] = requiredModules[name](size)
})
done()
return modules
}
const RN_DEBUGGER_URL_PART = 'RNDebuggerWorker.js'
const BUNDLE_URL_REGEXP = /(http[\S]*?index\.bundle\?[\S]*?)(:\d+:?\d?)/
const addInlineSourceMap = (_, urlGroup1, urlGroup2) => `${urlGroup1}&inlineSourceMap=true${urlGroup2}`
const mapStackLines = (line) => line.replace(BUNDLE_URL_REGEXP, addInlineSourceMap)
const filterRnDebuggerLines = (line) => !line.includes(RN_DEBUGGER_URL_PART)
export function updateStackWithSourceMap(stack) {
const lines = stack.split('\n')
const linesWithoutRNDebugger = lines.filter(filterRnDebuggerLines)
const lineWithSourceMap = linesWithoutRNDebugger.map(mapStackLines)
return lineWithSourceMap.join('\n')
}
================================================
FILE: auto_update.json
================================================
{
"url": "https://github.com/jhen0409/react-native-debugger/releases/download/v0.14.0/rn-debugger-macos-universal.zip",
"name": "v0.14.0",
"notes": "- Upgrade react-devtools-core to v4.28.0\n- Upgrade redux-devtools to latest version\n- Upgrade apollo-client-devtools to v4- More: https://github.com/jhen0409/react-native-debugger/releases/tag/v0.14.0"
}
================================================
FILE: auto_updater.json
================================================
{
"url": "https://github.com/jhen0409/react-native-debugger/releases/download/v0.10.13/rn-debugger-macos-x64.zip",
"name": "v0.10.13",
"notes": "Update apollo-client-devtools to v2.3.5"
}
================================================
FILE: babel.config.js
================================================
module.exports = (api) => {
api.cache(true)
return {
presets: [['@babel/preset-env', { targets: { node: '18.5' } }], '@babel/preset-react'],
plugins: [],
env: {
production: {
plugins: [
'@babel/plugin-transform-react-inline-elements',
'@babel/plugin-transform-react-constant-elements',
'transform-react-remove-prop-types',
],
},
},
}
}
================================================
FILE: dist/app.html
================================================
React Native Debugger
Loading...
================================================
FILE: dist/css/style.css
================================================
html,
body {
font-family: monaco, Consolas, Lucida Console, monospace;
overflow: hidden;
font-size: 100%;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: rgb(53, 59, 70);
}
#root {
width: 100%;
height: 100%;
}
#logs {
position: fixed;
top: 0;
left: 0;
white-space: pre;
}
#loading {
color: #aaa;
font-size: 30px;
display: flex;
height: 100%;
justify-content: center;
align-items: center;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: #555;
}
::-webkit-scrollbar-thumb {
background-color: #333;
}
::-webkit-scrollbar-corner {
background-color: #333;
}
@media print {
@page {
size: auto;
margin: 0;
}
body {
position: static;
}
}
.CodeMirror {
font-family: monaco, Consolas, Lucida Console, monospace !important;
}
================================================
FILE: dist/devtools-helper/main.html
================================================
================================================
FILE: dist/devtools-helper/main.js
================================================
const detectChromeDevToolsTheme = () => chrome.devtools.panels.themeName || 'default';
const themeName = detectChromeDevToolsTheme();
chrome.devtools.inspectedWindow.eval(`
window.chromeDevToolsTheme = '${themeName}';
if (window.notifyDevToolsThemeChange) {
window.notifyDevToolsThemeChange(window.chromeDevToolsTheme);
}
`);
window.addEventListener('message', ({ data }) => {
if (data.type !== 'open-in-editor') {
return;
}
const arr = data.source.split(':');
const lineNumber = arr.pop(-1);
const file = arr.join(':');
chrome.devtools.inspectedWindow.eval(`
if (window.openInEditor) {
window.openInEditor('${file}', ${Number(lineNumber)});
}
`);
});
================================================
FILE: dist/devtools-helper/manifest.json
================================================
{
"manifest_version": 2,
"name": "RNDebugger devtools helper",
"version": "0.0.1",
"devtools_page": "main.html"
}
================================================
FILE: dist/package.json
================================================
{
"name": "react-native-debugger",
"version": "0.14.0",
"productName": "React Native Debugger",
"description": "The standalone app for React Native Debugger, with React DevTools / Redux DevTools",
"main": "main.js",
"repository": {
"type": "git",
"url": "git+https://github.com/jhen0409/react-native-debugger.git"
},
"author": "Jhen ",
"license": "MIT",
"scripts": {
"postinstall": "patch-package"
},
"dependencies": {
"adbkit": "^2.11.0",
"electron-store": "^1.2.0",
"react-devtools-core": "^4.28.0"
},
"devDependencies": {
"apollo-client-devtools": "^4.1.4",
"patch-package": "^6.2.2"
}
}
================================================
FILE: dist/patches/apollo-client-devtools+4.1.4.patch
================================================
diff --git a/node_modules/apollo-client-devtools/build/background.js b/node_modules/apollo-client-devtools/build/background.js
index 1b46d15..5767881 100644
--- a/node_modules/apollo-client-devtools/build/background.js
+++ b/node_modules/apollo-client-devtools/build/background.js
@@ -171,21 +171,21 @@ chrome.runtime.onConnect.addListener(port => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.RELOAD_TAB_COMPLETE = exports.RELOADING_TAB = exports.EXPLORER_RESPONSE = exports.EXPLORER_REQUEST = exports.PANEL_CLOSED = exports.PANEL_OPEN = exports.UPDATE = exports.REQUEST_DATA = exports.ACTION_HOOK_FIRED = exports.CREATE_DEVTOOLS_PANEL = exports.APOLLO_CLIENT_FOUND = exports.FIND_APOLLO_CLIENT = exports.DEVTOOLS_INITIALIZED = exports.REQUEST_TAB_ID = exports.CLIENT_FOUND = void 0;
-exports.CLIENT_FOUND = "client-found";
-exports.REQUEST_TAB_ID = "request-tab-id";
-exports.DEVTOOLS_INITIALIZED = "devtools-initialized";
-exports.FIND_APOLLO_CLIENT = "find-apollo-client";
-exports.APOLLO_CLIENT_FOUND = "apollo-client-found";
-exports.CREATE_DEVTOOLS_PANEL = "create-devtools-panel";
-exports.ACTION_HOOK_FIRED = "action-hook-fired";
-exports.REQUEST_DATA = "request-data";
-exports.UPDATE = "update";
-exports.PANEL_OPEN = "panel-open";
-exports.PANEL_CLOSED = "panel-closed";
-exports.EXPLORER_REQUEST = "explorer-request";
-exports.EXPLORER_RESPONSE = "explorer-response";
-exports.RELOADING_TAB = "reloading-tab";
-exports.RELOAD_TAB_COMPLETE = "reload-tab-complete";
+exports.CLIENT_FOUND = "ac-devtools:client-found";
+exports.REQUEST_TAB_ID = "ac-devtools:request-tab-id";
+exports.DEVTOOLS_INITIALIZED = "ac-devtools:devtools-initialized";
+exports.FIND_APOLLO_CLIENT = "ac-devtools:find-apollo-client";
+exports.APOLLO_CLIENT_FOUND = "ac-devtools:apollo-client-found";
+exports.CREATE_DEVTOOLS_PANEL = "ac-devtools:create-devtools-panel";
+exports.ACTION_HOOK_FIRED = "ac-devtools:action-hook-fired";
+exports.REQUEST_DATA = "ac-devtools:request-data";
+exports.UPDATE = "ac-devtools:update";
+exports.PANEL_OPEN = "ac-devtools:panel-open";
+exports.PANEL_CLOSED = "ac-devtools:panel-closed";
+exports.EXPLORER_REQUEST = "ac-devtools:explorer-request";
+exports.EXPLORER_RESPONSE = "ac-devtools:explorer-response";
+exports.RELOADING_TAB = "ac-devtools:reloading-tab";
+exports.RELOAD_TAB_COMPLETE = "ac-devtools:reload-tab-complete";
/***/ })
diff --git a/node_modules/apollo-client-devtools/build/devtools.js b/node_modules/apollo-client-devtools/build/devtools.js
index 165495f..8290715 100644
--- a/node_modules/apollo-client-devtools/build/devtools.js
+++ b/node_modules/apollo-client-devtools/build/devtools.js
@@ -165,22 +165,21 @@ exports["default"] = EventTarget;
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.RELOAD_TAB_COMPLETE = exports.RELOADING_TAB = exports.EXPLORER_RESPONSE = exports.EXPLORER_REQUEST = exports.PANEL_CLOSED = exports.PANEL_OPEN = exports.UPDATE = exports.REQUEST_DATA = exports.ACTION_HOOK_FIRED = exports.CREATE_DEVTOOLS_PANEL = exports.APOLLO_CLIENT_FOUND = exports.FIND_APOLLO_CLIENT = exports.DEVTOOLS_INITIALIZED = exports.REQUEST_TAB_ID = exports.CLIENT_FOUND = void 0;
-exports.CLIENT_FOUND = "client-found";
-exports.REQUEST_TAB_ID = "request-tab-id";
-exports.DEVTOOLS_INITIALIZED = "devtools-initialized";
-exports.FIND_APOLLO_CLIENT = "find-apollo-client";
-exports.APOLLO_CLIENT_FOUND = "apollo-client-found";
-exports.CREATE_DEVTOOLS_PANEL = "create-devtools-panel";
-exports.ACTION_HOOK_FIRED = "action-hook-fired";
-exports.REQUEST_DATA = "request-data";
-exports.UPDATE = "update";
-exports.PANEL_OPEN = "panel-open";
-exports.PANEL_CLOSED = "panel-closed";
-exports.EXPLORER_REQUEST = "explorer-request";
-exports.EXPLORER_RESPONSE = "explorer-response";
-exports.RELOADING_TAB = "reloading-tab";
-exports.RELOAD_TAB_COMPLETE = "reload-tab-complete";
-
+exports.CLIENT_FOUND = "ac-devtools:client-found";
+exports.REQUEST_TAB_ID = "ac-devtools:request-tab-id";
+exports.DEVTOOLS_INITIALIZED = "ac-devtools:devtools-initialized";
+exports.FIND_APOLLO_CLIENT = "ac-devtools:find-apollo-client";
+exports.APOLLO_CLIENT_FOUND = "ac-devtools:apollo-client-found";
+exports.CREATE_DEVTOOLS_PANEL = "ac-devtools:create-devtools-panel";
+exports.ACTION_HOOK_FIRED = "ac-devtools:action-hook-fired";
+exports.REQUEST_DATA = "ac-devtools:request-data";
+exports.UPDATE = "ac-devtools:update";
+exports.PANEL_OPEN = "ac-devtools:panel-open";
+exports.PANEL_CLOSED = "ac-devtools:panel-closed";
+exports.EXPLORER_REQUEST = "ac-devtools:explorer-request";
+exports.EXPLORER_RESPONSE = "ac-devtools:explorer-response";
+exports.RELOADING_TAB = "ac-devtools:reloading-tab";
+exports.RELOAD_TAB_COMPLETE = "ac-devtools:reload-tab-complete";
/***/ }),
diff --git a/node_modules/apollo-client-devtools/build/hook.js b/node_modules/apollo-client-devtools/build/hook.js
index 63aa181..d6539a7 100644
--- a/node_modules/apollo-client-devtools/build/hook.js
+++ b/node_modules/apollo-client-devtools/build/hook.js
@@ -4712,21 +4712,21 @@ exports["default"] = EventTarget;
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.RELOAD_TAB_COMPLETE = exports.RELOADING_TAB = exports.EXPLORER_RESPONSE = exports.EXPLORER_REQUEST = exports.PANEL_CLOSED = exports.PANEL_OPEN = exports.UPDATE = exports.REQUEST_DATA = exports.ACTION_HOOK_FIRED = exports.CREATE_DEVTOOLS_PANEL = exports.APOLLO_CLIENT_FOUND = exports.FIND_APOLLO_CLIENT = exports.DEVTOOLS_INITIALIZED = exports.REQUEST_TAB_ID = exports.CLIENT_FOUND = void 0;
-exports.CLIENT_FOUND = "client-found";
-exports.REQUEST_TAB_ID = "request-tab-id";
-exports.DEVTOOLS_INITIALIZED = "devtools-initialized";
-exports.FIND_APOLLO_CLIENT = "find-apollo-client";
-exports.APOLLO_CLIENT_FOUND = "apollo-client-found";
-exports.CREATE_DEVTOOLS_PANEL = "create-devtools-panel";
-exports.ACTION_HOOK_FIRED = "action-hook-fired";
-exports.REQUEST_DATA = "request-data";
-exports.UPDATE = "update";
-exports.PANEL_OPEN = "panel-open";
-exports.PANEL_CLOSED = "panel-closed";
-exports.EXPLORER_REQUEST = "explorer-request";
-exports.EXPLORER_RESPONSE = "explorer-response";
-exports.RELOADING_TAB = "reloading-tab";
-exports.RELOAD_TAB_COMPLETE = "reload-tab-complete";
+exports.CLIENT_FOUND = "ac-devtools:client-found";
+exports.REQUEST_TAB_ID = "ac-devtools:request-tab-id";
+exports.DEVTOOLS_INITIALIZED = "ac-devtools:devtools-initialized";
+exports.FIND_APOLLO_CLIENT = "ac-devtools:find-apollo-client";
+exports.APOLLO_CLIENT_FOUND = "ac-devtools:apollo-client-found";
+exports.CREATE_DEVTOOLS_PANEL = "ac-devtools:create-devtools-panel";
+exports.ACTION_HOOK_FIRED = "ac-devtools:action-hook-fired";
+exports.REQUEST_DATA = "ac-devtools:request-data";
+exports.UPDATE = "ac-devtools:update";
+exports.PANEL_OPEN = "ac-devtools:panel-open";
+exports.PANEL_CLOSED = "ac-devtools:panel-closed";
+exports.EXPLORER_REQUEST = "ac-devtools:explorer-request";
+exports.EXPLORER_RESPONSE = "ac-devtools:explorer-response";
+exports.RELOADING_TAB = "ac-devtools:reloading-tab";
+exports.RELOAD_TAB_COMPLETE = "ac-devtools:reload-tab-complete";
/***/ }),
@@ -4859,7 +4859,7 @@ function initializeHook() {
});
const clientRelay = new Relay_1.default();
clientRelay.addConnection("tab", (message) => {
- window.postMessage(message, "*");
+ window.postMessage(message);
});
window.addEventListener("message", ({ data }) => {
clientRelay.broadcast(data);
diff --git a/node_modules/apollo-client-devtools/build/panel.js b/node_modules/apollo-client-devtools/build/panel.js
index 4b3ad6e..f7e021e 100644
--- a/node_modules/apollo-client-devtools/build/panel.js
+++ b/node_modules/apollo-client-devtools/build/panel.js
@@ -54713,21 +54713,21 @@ exports["default"] = EventTarget;
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.RELOAD_TAB_COMPLETE = exports.RELOADING_TAB = exports.EXPLORER_RESPONSE = exports.EXPLORER_REQUEST = exports.PANEL_CLOSED = exports.PANEL_OPEN = exports.UPDATE = exports.REQUEST_DATA = exports.ACTION_HOOK_FIRED = exports.CREATE_DEVTOOLS_PANEL = exports.APOLLO_CLIENT_FOUND = exports.FIND_APOLLO_CLIENT = exports.DEVTOOLS_INITIALIZED = exports.REQUEST_TAB_ID = exports.CLIENT_FOUND = void 0;
-exports.CLIENT_FOUND = "client-found";
-exports.REQUEST_TAB_ID = "request-tab-id";
-exports.DEVTOOLS_INITIALIZED = "devtools-initialized";
-exports.FIND_APOLLO_CLIENT = "find-apollo-client";
-exports.APOLLO_CLIENT_FOUND = "apollo-client-found";
-exports.CREATE_DEVTOOLS_PANEL = "create-devtools-panel";
-exports.ACTION_HOOK_FIRED = "action-hook-fired";
-exports.REQUEST_DATA = "request-data";
-exports.UPDATE = "update";
-exports.PANEL_OPEN = "panel-open";
-exports.PANEL_CLOSED = "panel-closed";
-exports.EXPLORER_REQUEST = "explorer-request";
-exports.EXPLORER_RESPONSE = "explorer-response";
-exports.RELOADING_TAB = "reloading-tab";
-exports.RELOAD_TAB_COMPLETE = "reload-tab-complete";
+exports.CLIENT_FOUND = "ac-devtools:client-found";
+exports.REQUEST_TAB_ID = "ac-devtools:request-tab-id";
+exports.DEVTOOLS_INITIALIZED = "ac-devtools:devtools-initialized";
+exports.FIND_APOLLO_CLIENT = "ac-devtools:find-apollo-client";
+exports.APOLLO_CLIENT_FOUND = "ac-devtools:apollo-client-found";
+exports.CREATE_DEVTOOLS_PANEL = "ac-devtools:create-devtools-panel";
+exports.ACTION_HOOK_FIRED = "ac-devtools:action-hook-fired";
+exports.REQUEST_DATA = "ac-devtools:request-data";
+exports.UPDATE = "ac-devtools:update";
+exports.PANEL_OPEN = "ac-devtools:panel-open";
+exports.PANEL_CLOSED = "ac-devtools:panel-closed";
+exports.EXPLORER_REQUEST = "ac-devtools:explorer-request";
+exports.EXPLORER_RESPONSE = "ac-devtools:explorer-response";
+exports.RELOADING_TAB = "ac-devtools:reloading-tab";
+exports.RELOAD_TAB_COMPLETE = "ac-devtools:reload-tab-complete";
/***/ }),
diff --git a/node_modules/apollo-client-devtools/build/tab.js b/node_modules/apollo-client-devtools/build/tab.js
index 9ed84be..702f76c 100644
--- a/node_modules/apollo-client-devtools/build/tab.js
+++ b/node_modules/apollo-client-devtools/build/tab.js
@@ -135,21 +135,21 @@ exports["default"] = EventTarget;
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.RELOAD_TAB_COMPLETE = exports.RELOADING_TAB = exports.EXPLORER_RESPONSE = exports.EXPLORER_REQUEST = exports.PANEL_CLOSED = exports.PANEL_OPEN = exports.UPDATE = exports.REQUEST_DATA = exports.ACTION_HOOK_FIRED = exports.CREATE_DEVTOOLS_PANEL = exports.APOLLO_CLIENT_FOUND = exports.FIND_APOLLO_CLIENT = exports.DEVTOOLS_INITIALIZED = exports.REQUEST_TAB_ID = exports.CLIENT_FOUND = void 0;
-exports.CLIENT_FOUND = "client-found";
-exports.REQUEST_TAB_ID = "request-tab-id";
-exports.DEVTOOLS_INITIALIZED = "devtools-initialized";
-exports.FIND_APOLLO_CLIENT = "find-apollo-client";
-exports.APOLLO_CLIENT_FOUND = "apollo-client-found";
-exports.CREATE_DEVTOOLS_PANEL = "create-devtools-panel";
-exports.ACTION_HOOK_FIRED = "action-hook-fired";
-exports.REQUEST_DATA = "request-data";
-exports.UPDATE = "update";
-exports.PANEL_OPEN = "panel-open";
-exports.PANEL_CLOSED = "panel-closed";
-exports.EXPLORER_REQUEST = "explorer-request";
-exports.EXPLORER_RESPONSE = "explorer-response";
-exports.RELOADING_TAB = "reloading-tab";
-exports.RELOAD_TAB_COMPLETE = "reload-tab-complete";
+exports.CLIENT_FOUND = "ac-devtools:client-found";
+exports.REQUEST_TAB_ID = "ac-devtools:request-tab-id";
+exports.DEVTOOLS_INITIALIZED = "ac-devtools:devtools-initialized";
+exports.FIND_APOLLO_CLIENT = "ac-devtools:find-apollo-client";
+exports.APOLLO_CLIENT_FOUND = "ac-devtools:apollo-client-found";
+exports.CREATE_DEVTOOLS_PANEL = "ac-devtools:create-devtools-panel";
+exports.ACTION_HOOK_FIRED = "ac-devtools:action-hook-fired";
+exports.REQUEST_DATA = "ac-devtools:request-data";
+exports.UPDATE = "ac-devtools:update";
+exports.PANEL_OPEN = "ac-devtools:panel-open";
+exports.PANEL_CLOSED = "ac-devtools:panel-closed";
+exports.EXPLORER_REQUEST = "ac-devtools:explorer-request";
+exports.EXPLORER_RESPONSE = "ac-devtools:explorer-response";
+exports.RELOADING_TAB = "ac-devtools:reloading-tab";
+exports.RELOAD_TAB_COMPLETE = "ac-devtools:reload-tab-complete";
/***/ }),
@@ -260,22 +260,22 @@ __webpack_require__(/*! ./tabRelay */ "./src/extension/tab/tabRelay.ts");
A common workaround for this issue is to inject an inlined function
into the inspected tab.
*/
-if (typeof document === "object" && document instanceof HTMLDocument) {
- const script = document.createElement("script");
- script.setAttribute("type", "module");
- script.setAttribute("src", chrome.extension.getURL("hook.js"));
- document.addEventListener("DOMContentLoaded", () => {
- var _a;
- const importMap = document.querySelector("script[type=\"importmap\"]");
- if (importMap != null) {
- (_a = importMap.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(script, importMap.nextSibling);
- }
- else {
- const head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
- head.insertBefore(script, head.lastChild);
- }
- });
-}
+// if (typeof document === "object" && document instanceof HTMLDocument) {
+// const script = document.createElement("script");
+// script.setAttribute("type", "module");
+// script.setAttribute("src", chrome.extension.getURL("hook.js"));
+// document.addEventListener("DOMContentLoaded", () => {
+// var _a;
+// const importMap = document.querySelector("script[type=\"importmap\"]");
+// if (importMap != null) {
+// (_a = importMap.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(script, importMap.nextSibling);
+// }
+// else {
+// const head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
+// head.insertBefore(script, head.lastChild);
+// }
+// });
+// }
})();
================================================
FILE: docs/README.md
================================================
# Documentation
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcuts references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/apollo-client-devtools-integration.md
================================================
# Apollo Client DevTools Integration
React Native debugger has integration for the [Apollo Client DevTools](https://github.com/apollographql/apollo-client-devtools), you can see the `Apollo` tab in Developer Tools:
To ensure it works, you must use Apollo Client ^2.0.
If the apollo tab doesn't appear, toggle developer tools off and on again.
You can read Apollo DevTools [documentation](https://github.com/apollographql/apollo-client-devtools#apollo-client-devtools)).
## Other documentations
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/config-file-in-home-directory.md
================================================
# Config file in home directory
We could configure RNDebugger app in `~/.rndebuggerrc` (the config can be opened from the main menu: Debugger → Open Config File), the file used [json5](https://github.com/json5/json5) as format, see the following default template:
```json5
{
// Font family of the debugger window
fontFamily: 'monaco, Consolas, Lucida Console, monospace',
// Zoom level of the debugger window, it will override persited zoomLevel
zoomLevel: 0,
// Settings of debugger window,
windowBounds: {
// Size of the debugger window, it will override persisted size
width: 1024,
height: 750,
// Show frame for debugger window
// but due to https://github.com/electron/electron/issues/3647
// so we can't have custom title bar if no frame
frame: true,
},
// Auto check update on RNDebugger startup
autoUpdate: true,
// RNDebugger will open debugger window with the ports when app launched
defaultRNPackagerPorts: [8081],
// Env for
// open React DevTools source file link
// and enable open in editor for console log for RNDebugger
editor: '',
// Set default react-devtools theme (default is match Chrome DevTools theme)
// but the default theme doesn't change manually changed theme
// see https://github.com/facebook/react-devtools/blob/master/frontend/Themes/Themes.js to get more
defaultReactDevToolsTheme: 'RNDebugger',
// Set default react-devtools port (default is \`19567+\` if it is not being used).
// The devtools backend of React Native will use the port to connect to the devtools server.
// You should use that if you have some rules for binding port.
// (like https://github.com/jhen0409/react-native-debugger/issues/397)
defaultReactDevToolsPort: 19567,
// Enable Network Inspect by default
// See https://github.com/jhen0409/react-native-debugger/blob/master/docs/network-inspect-of-chrome-devtools.md
defaultNetworkInspect: false,
// Refresh devtools when doing JS reload every N times. (-1 for disabled)
// This can effectively avoid possible memory leaks (Like
// https://github.com/jhen0409/react-native-debugger/issues/405) in devtools.
timesJSLoadToRefreshDevTools: -1,
}
```
## Other documentations
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/contributing.md
================================================
# Contributing
## Development
### Fork this repo & install dependencies
We're recommended use yarn because we keep the dependencies lock of yarn.
```bash
# In react-native-debugger directory
$ yarn
$ cd npm-package && yarn && cd ..
```
If you want to debug the [NPM package](../npm-package), just run `npm link ` on your React Native project.
### Run on development mode
_Please ensure the `React Native Debugger` production / distribution app is closed._
```bash
$ yarn dev:webpack # Then open the another terminal tab
$ yarn dev:electron
```
1. From here, you can open a react-native project with remote debugging enabled.
1. To see the development build of the react-native-debugger, do x,y,z
### Run on production mode
```bash
$ yarn build
$ yarn start
```
### Run test
Run lint and test, currently we just wrote E2E test for RNDebugger.
```bash
$ yarn test
$ yarn test-e2e
```
You need to closes all React Native packager (make sure `8081` or `8088` port not listening) when running the test.
### Packaging app
```bash
$ yarn run pack-macos # Use --notarize to notarize app
# On macOS: brew install fakeroot dpkg rpm
$ yarn run pack-linux
# On macOS: brew install wine mono
$ yarn run pack-windows
$ yarn run pack # all
```
If you want to build binaries yourself, please remove [../electron/update.js](electron/update.js) (and [electon/main.js usage](electon/main.js)).
For macOS, note that if your app binary is not code signed, you will often get a firewall prompt from React DevTools server.
### [Optional] Prerequisites for packaging Linux / Windows app on macOS
```bash
# Linux
brew install fakeroot dpkg rpm
# Windows
brew tap homebrew/cask-versions
brew install wine-stable mono
```
## Financial contributions
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/react-native-debugger).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
## Credits
### Contributors
Thank you to all the people who have already contributed to react-native-debugger!
### Backers
Thank you to all our backers! [[Become a backer](https://opencollective.com/react-native-debugger#backer)]
### Sponsors
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/react-native-debugger#sponsor))
## Other documentations
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
================================================
FILE: docs/debugger-integration.md
================================================
# Debugger integration
The Debugger worker is referenced from [react-native](https://github.com/facebook/react-native/blob/master/local-cli/server/util/) debugger-ui, so it's only working if you're enabled `Debug JS Remotely`, you can debug your app in Chrome Developer Tools, we keep the following tabs:
- `Console`
- `Sources`
- `Network` (Inspect Network requests if you are enabled [Network Inspect](network-inspect-of-chrome-devtools.md))
- `Memory`
## Multiple React Native packager (custom port) support
We can use [`react-native-debugger-open`](../npm-package) package to detect RN packager port, it will open an another window automatically if another debugger workers are running.
If you don't use [the npm package](../npm-package) and want to change port, click `Debugger` -> `New Window` (`Command⌘ + T` for macOS, `Ctrl + T` for Linux / Windows) in application menu, you need to type an another RN packager port. The default port is use [`Expo`](https://github.com/expo/expo) (and [`create-react-native-app`](https://github.com/react-community/create-react-native-app)) default port.
For macOS (10.12+), it used native tabs feature, see [the support page](https://support.apple.com/en-us/HT206998) for known how to use and setting.
## Debugging tips
#### Global variables in console
When you enabled remote debugging, RNDebugger should switched context to `RNDebuggerWorker.js` automatically, so you can get global variables of React Native runtime in the console.
- `$r`: You selected element on react-devtools.
- `showAsyncStorageContentInDev()` - Log AsyncStorage content
- `$reactNative.*` - Get react-native modules. For example, you can get `$reactNative.AsyncStorage`
#### Enable `Debug Remotely` programmatically
For enable `Debug Remotely` without using dev menu, you can use the built-in `DevSettings` native module:
```js
import { NativeModules } from 'react-native'
if (__DEV__) {
NativeModules.DevSettings.setIsDebuggingRemotely(true)
}
```
If you're using Expo, you can still use the method, but it probably only works with `jsEngine: jsc` in `app.json`, `jsEngine: hermes` may not works.
## Other documentations
- [Getting Started](getting-started.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/enable-open-in-editor-in-console.md
================================================
# Enable open in editor in console
You can toggle the application menu item:
Instead of open file in `Sources` tab, you can open file in editor by click source link in console. This feature is disabled by default.
## Known issues
- Currently this feature doesn't work with Haul bundler, please tracking [issue #141](https://github.com/jhen0409/react-native-debugger/issues/141).
## Other documentations
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/getting-started.md
================================================
# Getting Started
Just these steps will let you start RNDebugger out of box:
- Install the latest version ([download page](https://github.com/jhen0409/react-native-debugger/releases)).
- Make sure all debugger clients of React Native are closed, usually are `http://localhost:/debugger-ui`
- Make sure RNDebugger is open and wait state.
- RNDebugger will try connect to debugger proxy, use port `8081` by default, you can create a new debugger window (macOS: `Command+T`, Linux/Windows: `Ctrl+T`) to specify the port if you want.
- Enable `Debug JS Remotely` of [developer menu](https://reactnative.dev/docs/debugging#accessing-the-in-app-developer-menu) on your app
## Launch by CLI or React Native packager
Platform: macOS / Linux
### The `rndebugger:` URI scheme
Launch RNDebugger by typing the following command:
```bash
$ open "rndebugger://set-debugger-loc?host=localhost&port=8081"
```
Or `xdg-open` for Linux:
```bash
$ xdg-open "rndebugger://set-debugger-loc?host=localhost&port=8081"
```
The `host` / `port` means React Native packager. You may need to set `port` if you customize the packager port. (`8081` by default)
From [`Debugging using a custom JavaScript debugger`](https://reactnative.dev/docs/0.71/debugging#debugging-using-a-custom-javascript-debugger) of React Native docs, you can use `REACT_DEBUGGER` env on react-native packager, it will try to launch RNDebugger when you turn on `Debug JS Remotely`:
```bash
$ REACT_DEBUGGER="unset ELECTRON_RUN_AS_NODE && open -g 'rndebugger://set-debugger-loc?port=19000' ||" npm start
```
You can use `open` on macOS or `xdg-open` on Linux, currently it is not supported for Windows.
### Use [`react-native-debugger-open`](../npm-package)
If you don‘t need to add a dependency, you can use the package, it can help with:
- Replace `open debugger-ui with Chrome` to `open React Native Debugger` in react-native packager, saving you from closing the debugger-ui page everytime it automatically opens :)
- Detect react-native packager port then send to the app, if you launch packager with custom `--port` or use Expo, this will be very useful
### What about Windows support?
Currently the `rndebugger:` URI scheme doesn't support for Windows.
In [`react-native-debugger-open`](../npm-package), it can be sent the `host` / `port` setting if RNDebugger opened, but can't automatically open if closed.
If you want to have the feature (`rndebugger:` or another way), you are welcome to contribute. Please read [contributing](https://github.com/jhen0409/react-native-debugger/blob/master/docs/contributing.md) to become a maintainer.
## Use Redux DevTools Extension API
Using the same API as [`redux-devtools-extension`](https://github.com/zalmoxisus/redux-devtools-extension#1-with-redux) is very simple:
```js
const store = createStore(
reducer /* preloadedState, */,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
)
```
See [`Redux DevTools Integration`](redux-devtools-integration.md) section for more information.
## Platform support
- [React Native](https://github.com/facebook/react-native) >= 0.43
- [React Native for macOS](https://github.com/ptmt/react-native-macos) (formerly react-native-desktop) >= 0.14.0
- [React Native for Windows](https://github.com/Microsoft/react-native-windows)
## Auto-update RNDebugger app (Supported v0.5.0 after)
Currently auto-update is only supported for macOS. Linux and Windows will show a dialog of new versions available for download.
You can also click `React Native Debugger` (`RND` for Linux / Windows) -> `Check for Updates...` in the application menu.
## Other documentations
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/network-inspect-of-chrome-devtools.md
================================================
# Network Inspect of Chrome Developer Tools
**_WARNING_**: You should read [the limitations](#limitations) if you want to use this feature.
When you have Network Inspect enabled you can inspect network requests that use `XMLHttpRequest` or `fetch` on the `Network` tab of Chrome Developer Tools.
You can enable this feature by one of these ways:
- [context menu or Touch Bar](shortcut-references.md) (Network Inspect will be enabled while the RNDebugger is running, after closing it will reset to the default value);
- by the `defaultNetworkInspect` option in the [config file](config-file-in-home-directory.md) (Network Inspect will be enabled permanently).
## How it works
See [the comments of `react-native/Libraries/Utilities/PolyfillFunctions#L15-L27`](https://github.com/facebook/react-native/blob/ab97b9f6021d2b31b7155970c2be0c83f7e43f04/Libraries/Utilities/PolyfillFunctions.js#L15-L27). It uses `XMLHttpRequest` from WebWorker in Chrome, basically it can manually setup by:
```js
global.XMLHttpRequest = global.originalXMLHttpRequest
? global.originalXMLHttpRequest
: global.XMLHttpRequest;
global.FormData = global.originalFormData
? global.originalFormData
: global.FormData;
fetch; // Ensure to get the lazy property
if (window.__FETCH_SUPPORT__) {
// it's RNDebugger only to have
window.__FETCH_SUPPORT__.blob = false;
} else {
/*
* Set __FETCH_SUPPORT__ to false is just work for `fetch`.
* If you're using another way you can just use the native Blob and remove the `else` statement
*/
global.Blob = global.originalBlob ? global.originalBlob : global.Blob;
global.FileReader = global.originalFileReader
? global.originalFileReader
: global.FileReader;
}
```
> Note that replace `global.Blob` will cause issue like [#56](https://github.com/jhen0409/react-native-debugger/issues/56).
This allows you can open the `Network` tab in devtools to inspect requests of `fetch` and `XMLHttpRequest`.
You can also do this on the official remote debugger, but it has two differences:
- RNDebugger is based on [Electron](https://github.com/electron/electron) so it doesn't have the CORS issue
- We support setting [`Forbidden header names`](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name), so you can use headers like `Origin` and `User-Agent`.
## Limitations
There are some limitations of debugging network requests using Network Inspect:
- [iOS] Requests pass `NSExceptionDomains` checks. If you forget to set a domain name, the requests will break in production. You should be clear about the difference.
- [Android] If your network request would have caused `java.security.cert.CertPathValidatorException`, the Network Inpsect will skip that because it uses Debugger's network client.
- React Native `FormData` supports the `uri` property. You can use files from `CameraRoll`, but `originalFormData` isn't supported.
- It can't inspect request like `Image`s loaded from urls for `src`, so if your `Image` source has a set session, the session can't apply to `fetch` and `XMLHttpRequest`.
If you want to inspect deeper network requests (like requests made with `Image`), use tools like [Charles](https://www.charlesproxy.com) or [Flipper](https://github.com/facebook/flipper).
## Other documentations
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/react-devtools-integration.md
================================================
# React DevTools Integration
**_NOTE_** Supported React Native version is `>= 0.62`. Please downgrade RNDebugger version to `0.10` if you're using older versions of React Native.
The [React DevTools](https://reactnative.dev/docs/debugging#react-developer-tools) is built by [`facebook/react/packages/react-devtools-core`](https://github.com/facebook/react/tree/master/packages/react-devtools-core).
It will open a WebSocket server to waiting React Native connection. The connection is already included in React Native (see [`setUpReactDevTools.js`](https://github.com/facebook/react-native/blob/0.62-stable/Libraries/Core/setUpReactDevTools.js)), it will keep trying to connect the React DevTools server in development mode, it should work well without any specification.
We made the server listen to a random port and inject `window.__REACT_DEVTOOLS_PORT__` global variable in debugger worker.
For Android, we have the built-in `adb` util and it will reverse the port automatically.
## Get `$r` global variable of React Native runtime in the console
Refer to [`Debugger Integration`](debugger-integration.md#debugging-tips).
## **_Question_**: I got `Unsupported` message from React DevTools
If you're using React Native version >= 0.62 and keep React Native Debugger as the latest version, here is what you can do:
In your app project, make sure the `react-devtools-core` dependency to match the React DevTools version. Add resolutions in your `package.json` for Yarn:
```json
{
"resolutions": {
"react-devtools-core": "~4.28.0"
}
}
```
or NPM:
```json
{
"overrides": {
"react-devtools-core": "~4.28.0"
}
}
```
Reference: [Unsupported DevTools backend version - # React Native Debugger](https://gist.github.com/bvaughn/4bc90775530873fdf8e7ade4a039e579#react-native-debugger)
If the React Native version of your project doesn't support `react-devtools-core@4.25`, please consider downgrade React Native Debugger version to v0.12.
## Other documentations
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/redux-devtools-integration.md
================================================
# Redux DevTools Integration
We used [@redux-devtools/app](https://github.com/reduxjs/redux-devtools/tree/main/packages/redux-devtools-app) and made the API same with [Redux DevTools Extension](https://github.com/reduxjs/redux-devtools/tree/main/extension).
If you've enabled `Debug JS remotely` with React Native Debugger, the following API is already included in global:
- `window.__REDUX_DEVTOOLS_EXTENSION__`
- `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__`
- `window.__REDUX_DEVTOOLS_EXTENSION__.connect`
- You can just use [`redux-devtools-extension`](https://www.npmjs.com/package/redux-devtools-extension) npm package.
See also:
- [Redux DevTools main repository]](https://github.com/reduxjs/redux-devtools/blob/main/README.md)
- [API Reference](https://github.com/reduxjs/redux-devtools/tree/main/extension/docs/API)
- [Troubleshooting](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Troubleshooting.md)
- Other Integrations
- [`mobx-state-tree`](https://github.com/mobxjs/mobx-state-tree) - Use [`connectReduxDevtools`](https://github.com/mobxjs/mobx-state-tree/tree/3fc79b0b3ce7ad3e26d6bd5745fd9412d35c431c/packages/mst-middlewares#connectreduxdevtools) middleware.
You can ignore the things specified by the browser extension.
## About `trace` feature
- The debugger app might be slowed down if you enabled the `trace` feature and visited the `Trace` tab, because it will load and parse the source map for every selected action.
## Other documentations
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/shortcut-references.md
================================================
# Shortcut references
This section will explain about the following items in RNDebugger.
- [Content menu](#context-menu)
- [Touch Bar](#touch-bar-in-macos)
- [Keyboard shortcuts](#keyboard-shortcuts)
## Context menu
We have context menu (right-click) for provides useful features:

- Reload
- Toggle Elements Inspector
- Show Developer Menu [iOS only]
- Enable / Disable [Network Inspect](debugger-integration.md#how-network-inspect-works)
- Log AsyncStorage content
- Clear AsyncStorage
It includes the developer menu features, these would be useful for real device, instead of open developer menu in device manually.
## Keyboard shortcuts
- Reload JS (macOS: `Command+R`, Windows / Linux: `Ctrl+R`)
- Toggle Elements Inspector (macOS: `Command+I`, Windows / Linux: `Ctrl+I`)
- New Debugger Window (macOS: `Command+T`, Windows / Linux: `Ctrl+T`)
- Toggle Developer Tools (macOS: `Command+Option+I`, Windows / Linux: `Ctrl+Alt+I`)
- Toggle Redux DevTools (macOS: `Command+Option+J`, Windows / Linux: `Ctrl+Alt+J`)
- Toggle React DevTools (macOS: `Command+Option+K`, Windows / Linux: `Ctrl+Alt+K`)
- Quickly into search field of React DevTools (Type `/`)
You can also read [Keyboard Shortcuts Reference of Chrome Developer Tools](https://developers.google.com/web/tools/chrome-devtools/shortcuts).
## Touch Bar in macOS
The `Redux Slider` will shown on right if you're using [`Redux API`](redux-devtools-integration.md),
If your Mac haven't TouchBar support and you still want to use the feature, you can use [Touché](https://redsweater.com/touche/).
## Other documentations
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Troubleshooting](troubleshooting.md)
- [Contributing](contributing.md)
================================================
FILE: docs/troubleshooting.md
================================================
# Troubleshooting
## I got `Unsupported` meesage from React DevTools
If you're using React Native version >= 0.62 and keep React Native Debugger as the latest version, here is what you can do:
In your app project, make sure the `react-devtools-core` dependency to match the React DevTools version. Add resolutions in your `package.json` for Yarn:
```json
{
"resolutions": {
"react-devtools-core": "~4.28.0"
}
}
```
or NPM:
```json
{
"overrides": {
"react-devtools-core": "~4.28.0"
}
}
```
Reference: [Unsupported DevTools backend version - # React Native Debugger](https://gist.github.com/bvaughn/4bc90775530873fdf8e7ade4a039e579#react-native-debugger)
If the React Native version of your project doesn't support `react-devtools-core@4.25`, please consider downgrade React Native Debugger version to v0.12.
## Network fetch got issue like [`SyntaxError: Unexpected token o in JSON at position 1`](https://github.com/jhen0409/react-native-debugger/issues/382#issuecomment-544226529) if Network Inspect enabled
This may be caused by some library used / made fetch polyfills, it may used `Blob` but RNDebugger does not support it. If you got this issue, try to use global `fetch` / `XMLHttpRequest` instead, or try [#382#issuecomment-544226529](https://github.com/jhen0409/react-native-debugger/issues/382#issuecomment-544226529).
## Debugger causes app to load stale JS bundle ([#423](https://github.com/jhen0409/react-native-debugger/issues/423))
This issue was fixed by [v0.10.9](https://github.com/jhen0409/react-native-debugger/releases/tag/v0.10.9) and [v0.11.1](https://github.com/jhen0409/react-native-debugger/releases/tag/v0.11.1). If you are still using the old version for some reason, you can turn off Network cache manually on devtools:

## Some shortcuts (e.g. `Reload` / `Clear AsyncStorage`) are missing on the Debugger
- For Android and React Native version less than v0.60, you need to add and link [`react-native-devsettings-android`](https://github.com/jhen0409/react-native-devsettings-android) package
- If you're not using dev bundle (dev=true) from React Native packager, it will not working as expected.
- For some reasons, some dependencies may affected [Promise](https://github.com/jhen0409/react-native-debugger/blob/master/app/worker/utils.js#L7) behavior. It is recommended to use the initial project to find out the reason.
- If you are sure it is caused by a new version of React Native, please file an new issue.
## How to resolve problem of high memory usage on devtools?
You may have got a problem when you often reload JS, devtools process takes your RAM even more than 1G, it does not seem to clean.
In case of using [official remote debugger](https://reactnative.dev/docs/debugging#chrome-developer-tools), tested a initial project with remote debugging mode on Chrome 62 (beta), continuous reload JS 30 times:
Before:
After:
Fortunately, the current versions of RNDebugger (Chromium 58) is better than Chrome (maybe >= 59?), but it still has a amount of growth:
Before:
After:
To avoid similar problems in the future, there is a way to restart the Chrome devtools (macOS: `CMD+OPTION+I`, Linux/Windows: `CTRL+ALT+I`), the same applies to official remote debugger on Chrome. You can also consider to use [`timesJSLoadToRefreshDevTools` option in Config file in home directory](config-file-in-home-directory.md).
## [iOS] Debugger can't load bundle when I use real device
If you're getting the following error:

It may caused by [`xip.io`](http://xip.io) service (RN use it for debug on real device), it lead your machine IP doesn't resolved sometimes. It's [enabled by default](https://github.com/facebook/react-native/blob/ca9202f2385354b7a6b4d818ceb46bd96a037a7b/scripts/react-native-xcode.sh#L94), you can disable it in RN custom script on Xcode if you sure you don't need the service:
## React Inspector get stuck at "Connecting to React…" for RN ^0.43 ([#45](https://github.com/jhen0409/react-native-debugger/issues/45))
It usually on Android, the problem is related to `requestIdleCallback` API (try to check if it not work on debug mode).
This issue have been fixed in `react-devtools-core@^2.3.0`, please ensure the version is correct in your React Native project (Note that it's dependency of `react-native`).
Also, sometimes it have timer problem between host machine and device (emulator), you need make sure `date & time` setting is correct:
Or try to restart your device (emulator).
## [Windows 10] React native debugger process starts but no visible window ([#459](https://github.com/jhen0409/react-native-debugger/issues/459))
This issue is caused by Windows 10 dark mode, for a workaround please disable dark mode and enable it again after launching react-native-debugger
## Other documentations
- [Getting Started](getting-started.md)
- [Debugger Integration](debugger-integration.md)
- [React DevTools Integration](react-devtools-integration.md)
- [Redux DevTools Integration](redux-devtools-integration.md)
- [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
- [Shortcut references](shortcut-references.md)
- [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
- [Enable open in editor in console](enable-open-in-editor-in-console.md)
- [Config file in home directory](config-file-in-home-directory.md)
- [Contributing](contributing.md)
================================================
FILE: electron/app.html
================================================
React Native Debugger
Loading...
================================================
FILE: electron/config/__tests__/__snapshots__/index.test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`openConfigFile 1`] = `
{
"config": {
"autoUpdate": true,
"defaultNetworkInspect": false,
"defaultRNPackagerPorts": [
8081,
],
"defaultReactDevToolsPort": 19567,
"defaultReactDevToolsTheme": "RNDebugger",
"editor": "",
"timesJSLoadToRefreshDevTools": -1,
"windowBounds": {
"frame": true,
},
},
}
`;
exports[`readConfig 1`] = `
{
"config": {
"autoUpdate": true,
"defaultNetworkInspect": false,
"defaultRNPackagerPorts": [
8081,
],
"defaultReactDevToolsPort": 19567,
"defaultReactDevToolsTheme": "RNDebugger",
"editor": "",
"timesJSLoadToRefreshDevTools": -1,
"windowBounds": {
"frame": true,
},
},
}
`;
exports[`readConfig 2`] = `
{
"config": {
"autoUpdate": false,
},
}
`;
exports[`readConfig 3`] = `
{
"config": {
"autoUpdate": true,
"defaultNetworkInspect": false,
"defaultRNPackagerPorts": [
8081,
],
"defaultReactDevToolsPort": 19567,
"defaultReactDevToolsTheme": "RNDebugger",
"editor": "",
"timesJSLoadToRefreshDevTools": -1,
"windowBounds": {
"frame": true,
},
},
"error": [SyntaxError: Unexpected 'i' at line 1 column 16 of the JSON5 data. Still to read: "is_broken, }"],
"isConfigBroken": true,
}
`;
================================================
FILE: electron/config/__tests__/index.test.js
================================================
import fs from 'fs'
import path from 'path'
jest.mock('electron', () => ({
shell: {
openPath: jest.fn(),
},
}))
const testFile = path.join(__dirname, 'config_test')
beforeAll(() => fs.existsSync(testFile) && fs.unlinkSync(testFile))
/* eslint-disable global-require */
test('readConfig', () => {
const { readConfig } = require('..')
expect(readConfig(testFile)).toMatchSnapshot()
// User custom config
fs.writeFileSync(testFile, '{ autoUpdate: false, }')
expect(readConfig(testFile)).toMatchSnapshot()
// Broken config
fs.writeFileSync(testFile, '{ autoUpdate: is_broken, }')
expect(readConfig(testFile)).toMatchSnapshot()
})
test('openConfigFile', () => {
const { readConfig, openConfigFile } = require('..')
const { shell } = require('electron')
openConfigFile(testFile)
expect(shell.openPath).toBeCalledWith(testFile)
shell.openPath.mockClear()
fs.unlinkSync(testFile)
openConfigFile(testFile)
expect(readConfig(testFile)).toMatchSnapshot()
})
================================================
FILE: electron/config/index.js
================================================
import fs from 'fs'
import path from 'path'
import json5 from 'json5'
import { shell } from 'electron'
import template from './template'
export const filePath = path.join(
process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'],
'.rndebuggerrc',
)
export const readConfig = (configFile = filePath) => {
if (!fs.existsSync(configFile)) {
// Create a new one
fs.writeFileSync(configFile, template)
return { config: json5.parse(template) }
}
try {
// eslint-disable-next-line
return { config: json5.parse(fs.readFileSync(configFile, 'utf-8')) };
} catch (error) {
// Alert parse config not successful
return { config: json5.parse(template), isConfigBroken: true, error }
}
}
export const openConfigFile = (configFile = filePath) => {
readConfig()
shell.openPath(configFile)
}
================================================
FILE: electron/config/template.js
================================================
// json5
module.exports = `{
// Font family of the debugger window
// fontFamily: 'monaco, Consolas, Lucida Console, monospace',
// Zoom level of the debugger window, it will override persited zoomLevel
// zoomLevel: 0,
// Settings of debugger window,
windowBounds: {
// Size of the debugger window, it will override persisted size
// width: 1024,
// height: 750,
// Show frame for debugger window
// but due to https://github.com/electron/electron/issues/3647
// so we can't have custom title bar if no frame
// titleBarStyle: 'hidden',
frame: true,
},
// Auto check update on RNDebugger startup
autoUpdate: true,
// RNDebugger will open debugger window with the ports when app launched
defaultRNPackagerPorts: [8081],
// Env for
// open React DevTools source file link
// and enable open in editor for console log for RNDebugger
editor: '',
// Set default react-devtools theme (default is match Chrome DevTools theme)
// but the default theme doesn't change manually changed theme
// see https://github.com/facebook/react-devtools/blob/master/frontend/Themes/Themes.js to get more
defaultReactDevToolsTheme: 'RNDebugger',
// Set default react-devtools port (default is \`19567+\` if it is not being used).
// The devtools backend of React Native will use the port to connect to the devtools server.
// You should use that if you have some rules for binding port.
// (like https://github.com/jhen0409/react-native-debugger/issues/397)
defaultReactDevToolsPort: 19567,
// Enable Network Inspect by default
// See https://github.com/jhen0409/react-native-debugger/blob/master/docs/network-inspect-of-chrome-devtools.md
defaultNetworkInspect: false,
// Refresh devtools when doing JS reload every N times. (-1 for disabled)
// This can effectively avoid possible memory leaks (Like
// https://github.com/jhen0409/react-native-debugger/issues/405) in devtools.
timesJSLoadToRefreshDevTools: -1,
}
`
================================================
FILE: electron/context-menu.js
================================================
import { ipcMain } from 'electron'
import contextMenu from 'electron-context-menu'
import { readConfig } from './config'
import {
toggleDevTools, n, item, separator,
} from './menu/common'
const invokeDevMethod = (win, name) => win.webContents.executeJavaScript(
`window.invokeDevMethod && window.invokeDevMethod('${name}')`,
)
export const registerContextMenu = (win) => {
const { config } = readConfig()
const defaultContextMenuItems = [
item('Toggle Developer Tools', n, () => toggleDevTools(win, 'chrome')),
item('Toggle React DevTools', n, () => toggleDevTools(win, 'react')),
item('Toggle Redux DevTools', n, () => toggleDevTools(win, 'redux')),
]
let networkInspectEnabled = !!config.networkInspect
let availableMethods = []
contextMenu({
window: win,
showInspectElement: process.env.NODE_ENV === 'development',
prepend: () => [
availableMethods.includes('reload')
&& item('Reload JS', n, () => invokeDevMethod(win, 'reload')),
availableMethods.includes('toggleElementInspector')
&& item('Toggle Element Inspector', n, () => invokeDevMethod(win, 'toggleElementInspector')),
availableMethods.includes('show')
&& item('Show Developer Menu', n, () => invokeDevMethod(win, 'show')),
item(
networkInspectEnabled
? 'Disable Network Inspect'
: 'Enable Network Inspect',
n,
() => invokeDevMethod(win, 'networkInspect'),
),
availableMethods.includes('showAsyncStorage')
&& item('Log AsyncStorage content', n, () => invokeDevMethod(win, 'showAsyncStorage')),
availableMethods.includes('clearAsyncStorage')
&& item('Clear AsyncStorage', n, () => invokeDevMethod(win, 'clearAsyncStorage')),
separator,
]
.filter((menuItem) => !!menuItem)
.concat(defaultContextMenuItems),
})
const listener = (event, data) => {
availableMethods = data.availableMethods || availableMethods
networkInspectEnabled = typeof data.networkInspectEnabled === 'boolean'
? data.networkInspectEnabled
: networkInspectEnabled
}
ipcMain.on(`context-menu-available-methods-update-${win.id}`, listener)
return () => {
ipcMain.off(`context-menu-available-methods-update-${win.id}`, listener)
}
}
================================================
FILE: electron/debug.js
================================================
require('electron-debug')(); // eslint-disable-line
================================================
FILE: electron/devtools.js
================================================
export const getCatchConsoleLogScript = (port) => `
window.__RN_PACKAGER_MATCHER__ = /^http:\\/\\/[^:]+:${port}/;
if (!window.__INJECT_OPEN_IN_EDITOR_SCRIPT__) {
const rndHelperQuery = 'iframe[data-devtools-extension="RNDebugger devtools helper"]';
document.addEventListener('click', event => {
if (!window.__IS_OPEN_IN_EDITOR_ENABLED__) {
return;
}
const { target } = event;
if (target.className === 'devtools-link') {
const source = target.title;
if (source && source.match(window.__RN_PACKAGER_MATCHER__)) {
const rndHelper = document.querySelector(rndHelperQuery);
if (rndHelper && rndHelper.contentWindow) {
rndHelper.contentWindow.postMessage(
{
type: 'open-in-editor',
source: source.replace(window.__RN_PACKAGER_MATCHER__, '')
},
'*'
);
return event.stopPropagation();
}
}
}
}, true);
window.__INJECT_OPEN_IN_EDITOR_SCRIPT__ = true;
}
`
export const catchConsoleLogLink = (win, host = 'localhost', port = 8081) => {
if (win.devToolsWebContents) {
return win.devToolsWebContents.executeJavaScript(`(() => {
${getCatchConsoleLogScript(host, port)}
})()`)
}
}
export const removeUnecessaryTabs = (win) => {
if (
process.env.NODE_ENV === 'production'
&& !process.env.DEBUG_RNDEBUGGER
&& win.devToolsWebContents
) {
return win.devToolsWebContents.executeJavaScript(`(() => {
const tabbedPane = UI.inspectorView.tabbedPane;
if (tabbedPane) {
tabbedPane.closeTab('elements');
tabbedPane.closeTab('security');
tabbedPane.closeTab('audits');
tabbedPane.closeTab('audits2');
tabbedPane.closeTab('lighthouse');
tabbedPane.leftToolbar().element.remove();
}
})()`)
}
}
export const activeTabs = (win) => {
if (win.devToolsWebContents) {
// Active network tab so we can do clearNetworkLogs
return win.devToolsWebContents.executeJavaScript(`(() => {
DevToolsAPI.showPanel('network');
DevToolsAPI.showPanel('console');
})()`)
}
}
================================================
FILE: electron/extensions.js
================================================
import path from 'path'
import { session } from 'electron'
export default async () => {
if (process.env.NODE_ENV === 'development') {
await session.defaultSession.loadExtension(
path.resolve('dist/devtools-helper/'),
{ allowFileAccess: true },
)
await session.defaultSession.loadExtension(
path.join(
__dirname,
'../node_modules/apollo-client-devtools/build',
),
{ allowFileAccess: true },
)
} else if (process.env.PACKAGE === 'no') {
await session.defaultSession.loadExtension(
path.join(__dirname, 'devtools-helper/'),
{ allowFileAccess: true },
)
await session.defaultSession.loadExtension(
path.join(
__dirname,
'node_modules/apollo-client-devtools/build',
),
{ allowFileAccess: true },
)
} else {
await session.defaultSession.loadExtension(
path.join(__dirname, '../devtools-helper/'),
{ allowFileAccess: true },
)
await session.defaultSession.loadExtension(
path.join(
__dirname,
'../ac-devtools-ext-build/', // See package script for why
),
{ allowFileAccess: true },
)
}
}
================================================
FILE: electron/main.js
================================================
import path from 'path'
import {
app, ipcMain, session, BrowserWindow, Menu,
} from 'electron'
import { initialize } from '@electron/remote/main'
import normalizeHeaderCase from 'header-case-normalizer'
import installExtensions from './extensions'
import { checkWindowInfo, createWindow } from './window'
import { startListeningHandleURL, handleURL, parseUrl } from './url-handle'
import { createMenuTemplate } from './menu'
import { readConfig } from './config'
import { sendSyncState } from './sync-state'
initialize()
// Uncomment if want to debug devtools backend
// app.commandLine.appendSwitch('remote-debugging-port', '9222');
app.commandLine.appendSwitch('disable-http-cache')
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 1
const iconPath = path.resolve(__dirname, 'logo.png')
const defaultOptions = { iconPath }
const findWindow = async (_, port) => {
const browserWindows = BrowserWindow.getAllWindows()
const browserWindow = await browserWindows.reduce(async (promise, win) => {
const acc = await promise
if (acc) return acc
const { isWorkerRunning, isPortSettingRequired, location } = await checkWindowInfo(win)
return (!isWorkerRunning || location.port === port)
&& !isPortSettingRequired
? win
: null
}, Promise.resolve(null))
if (!browserWindow) createWindow(defaultOptions)
if (browserWindow) {
if (browserWindow.isMinimized()) browserWindow.restore()
browserWindow.focus()
}
return browserWindow
}
const handleCommandLine = async (commandLine) => {
const url = commandLine.find((arg) => arg.startsWith('rndebugger://'))
if (!url) {
return
}
await handleURL(findWindow, url)
}
if (process.platform === 'linux') {
const singleInstanceLock = app.requestSingleInstanceLock()
if (!singleInstanceLock) {
process.exit()
} else {
app.on('second-instance', async (event, commandLine) => {
await handleCommandLine(commandLine)
})
}
}
startListeningHandleURL(findWindow)
ipcMain.on('check-port-available', async (event, arg) => {
const port = Number(arg)
const windows = BrowserWindow.getAllWindows()
const isPortAvailable = await windows.reduce(async (promise, win) => {
const isAvailable = await promise
if (!isAvailable) return false
if (win.webContents !== event.sender) {
const { isPortSettingRequired, location } = await checkWindowInfo(win)
if (location.port === port && !isPortSettingRequired) {
return false
}
}
return true
}, Promise.resolve(true))
event.sender.send('check-port-available-reply', isPortAvailable)
})
ipcMain.on('sync-state', sendSyncState)
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length !== 0) return
createWindow(defaultOptions)
})
app.on('new-window-for-tab', () => {
createWindow({ ...defaultOptions, isPortSettingRequired: true })
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
if (process.platform === 'darwin') {
app.on('before-quit', async (event) => {
event.preventDefault()
BrowserWindow.getAllWindows().forEach((win) => {
win.removeAllListeners('close')
win.close()
})
process.exit()
})
}
app.on('ready', async () => {
await installExtensions()
const { config } = readConfig()
let { defaultRNPackagerPorts } = config
if (!Array.isArray(defaultRNPackagerPorts)) {
defaultRNPackagerPorts = [8081]
}
if (process.platform === 'linux') {
const url = process.argv.find((arg) => arg.startsWith('rndebugger://'))
const query = url ? parseUrl(url) : undefined
if (query && query.port) {
defaultRNPackagerPorts = [query.port]
}
}
defaultRNPackagerPorts.forEach((port) => {
createWindow({ port, ...defaultOptions })
})
const menuTemplate = createMenuTemplate(defaultOptions)
const menu = Menu.buildFromTemplate(menuTemplate)
Menu.setApplicationMenu(menu)
const replaceHeaderPrefix = '__RN_DEBUGGER_SET_HEADER_REQUEST_'
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
delete details.requestHeaders.Origin
Object.entries(details.requestHeaders).forEach(([header, value]) => {
if (header.startsWith(replaceHeaderPrefix)) {
const originalHeader = normalizeHeaderCase(
header.replace(replaceHeaderPrefix, ''),
)
details.requestHeaders[originalHeader] = value
delete details.requestHeaders[header]
}
})
callback({ cancel: false, requestHeaders: details.requestHeaders })
})
})
// Pass all certificate errors in favor of Network Inspect feature
app.on(
'certificate-error',
(event, webContents, url, error, certificate, callback) => {
event.preventDefault()
callback(true)
},
)
================================================
FILE: electron/menu/common.js
================================================
export const toggleDevTools = (win, type) => {
if (!win || !type) return
if (type === 'chrome') {
win.toggleDevTools()
return
}
win.webContents.send('toggle-devtools', type)
}
export const toggleFullscreen = (win) => win && win.setFullScreen(!win.isFullScreen())
export const setAlwaysOnTop = (win, checked) => win && win.setAlwaysOnTop(checked)
export const reload = (win) => win && win.webContents.reload()
export const close = (win) => win && win.close()
export const zoom = (win, val) => {
if (!win) return
const contents = win.webContents
contents.zoomLevel += val
}
export const resetZoom = (win) => {
if (win) {
win.webContents.zoomLevel = 0
}
}
export const toggleOpenInEditor = (win) => win && win.webContents.executeJavaScript('window.toggleOpenInEditor()')
export const menu = (label, submenu, role) => ({ label, submenu, role })
export const item = (label, accelerator, click, rest) => ({
label,
accelerator,
click,
...rest,
})
export const separator = { type: 'separator' }
export const n = undefined
================================================
FILE: electron/menu/darwin.js
================================================
import { app, shell, BrowserWindow } from 'electron'
import { createWindow } from '../window'
import checkUpdate from '../update'
import {
menu,
item,
separator,
n,
toggleDevTools,
toggleFullscreen,
setAlwaysOnTop,
reload,
zoom,
resetZoom,
toggleOpenInEditor,
} from './common'
import { haveOpenedWindow, showAboutDialog } from './dialog'
import { openConfigFile } from '../config'
import { isSyncState, toggleSyncState } from '../sync-state'
const getWin = () => BrowserWindow.getFocusedWindow()
const viewItems = process.env.NODE_ENV === 'developemnt'
? [item('Reload Window', 'Alt+Command+R', () => reload(getWin()))]
: []
export default ({ iconPath }) => [
menu('React Native Debugger', [
item('About', n, () => showAboutDialog(iconPath)),
item('Check for Updates...', n, () => checkUpdate(iconPath, true)),
separator,
item('Hide', 'Command+H', n, { selector: 'hide:' }),
item('Hide Others', 'Command+Shift+H', n, { selector: 'hideOtherApplications:' }),
item('Show All', n, n, { selector: 'unhideAllApplications:' }),
separator,
item('Quit', 'Command+Q', () => app.quit()),
]),
menu(
'Debugger',
[
item('New Window', 'Command+T', () => createWindow({ iconPath, isPortSettingRequired: haveOpenedWindow() })),
item('Enable Open in Editor for Console Log', n, () => toggleOpenInEditor(getWin()), {
type: 'checkbox',
checked: false,
}),
item('Toggle Device Sync', n, toggleSyncState, {
type: 'checkbox',
checked: isSyncState(),
}),
item('Open Config File', n, () => openConfigFile()),
separator,
item('Minimize', 'Command+M', n, { selector: 'performMiniaturize:' }),
item('Close', 'Command+W', n, { selector: 'performClose:' }),
separator,
item('Bring All to Front', n, n, { selector: 'arrangeInFront:' }),
item('Stay in Front', n, ({ checked }) => setAlwaysOnTop(getWin(), checked), {
type: 'checkbox',
checked: false,
}),
],
'window',
),
menu('Edit', [
item('Undo', 'Command+Z', n, { selector: 'undo:' }),
item('Redo', 'Shift+Command+Z', n, { selector: 'redo:' }),
separator,
item('Cut', 'Command+X', n, { selector: 'cut:' }),
item('Copy', 'Command+C', n, { selector: 'copy:' }),
item('Paste', 'Command+V', n, { selector: 'paste:' }),
item('Select All', 'Command+A', n, { selector: 'selectAll:' }),
]),
menu(
'View',
viewItems.concat([
item('Toggle Full Screen', 'F11', () => toggleFullscreen(getWin())),
item('Toggle Developer Tools', 'Alt+Command+I', () => toggleDevTools(getWin(), 'chrome')),
item('Toggle React DevTools', 'Alt+Command+J', () => toggleDevTools(getWin(), 'react')),
item('Toggle Redux DevTools', 'Alt+Command+K', () => toggleDevTools(getWin(), 'redux')),
separator,
item('Zoom In', 'Command+=', () => zoom(getWin(), 1)),
item('Zoom Out', 'Command+-', () => zoom(getWin(), -1)),
item('Reset Zoom', 'Command+0', () => resetZoom(getWin())),
]),
),
menu('Help', [
item('Documentation', n, () => shell.openExternal('https://github.com/jhen0409/react-native-debugger/tree/master/docs')),
item('Issues', n, () => shell.openExternal('https://github.com/jhen0409/react-native-debugger/issues')),
item('Open Collective', n, () => shell.openExternal('https://opencollective.com/react-native-debugger')),
]),
]
================================================
FILE: electron/menu/dialog.js
================================================
import { app, dialog, BrowserWindow } from 'electron'
import multiline from 'multiline-template'
const appName = app.name
const detail = multiline`
| Created by Jhen-Jie Hong
| (https://github.com/jhen0409)
| This software includes the following projects:
| https://github.com/facebook/react-devtools
| https://github.com/reduxjs/redux-devtools
| https://github.com/apollographql/apollo-client-devtools
`
export const showAboutDialog = (iconPath) => dialog.showMessageBoxSync({
title: 'About',
message: `${appName} ${app.getVersion()}`,
detail,
icon: iconPath,
buttons: [],
})
export const haveOpenedWindow = () => !!BrowserWindow.getAllWindows().length
================================================
FILE: electron/menu/index.js
================================================
/* eslint global-require: 0 */
import createMenuTemplateDarwin from './darwin'
import createMenuTemplateLinuxWin from './linux+win'
const createMenuTemplate = process.platform === 'darwin'
? createMenuTemplateDarwin
: createMenuTemplateLinuxWin
export { createMenuTemplate }
================================================
FILE: electron/menu/linux+win.js
================================================
import { shell, BrowserWindow } from 'electron'
import { createWindow } from '../window'
import checkUpdate from '../update'
import {
menu,
item,
separator,
n,
toggleDevTools,
toggleFullscreen,
setAlwaysOnTop,
reload,
close,
zoom,
resetZoom,
toggleOpenInEditor,
} from './common'
import {
showAboutDialog,
haveOpenedWindow,
} from './dialog'
import { openConfigFile } from '../config'
import { toggleSyncState, isSyncState } from '../sync-state'
const getWin = () => BrowserWindow.getFocusedWindow()
const viewItems = process.env.NODE_ENV === 'developemnt'
? [item('Reload Window', 'Alt+CTRL+R', () => reload(getWin()))]
: []
export default ({ iconPath }) => [
menu('RND', [
item('About', n, () => showAboutDialog(iconPath)),
item('Check for Updates...', n, () => checkUpdate(iconPath, true)),
separator,
item('Stay in Front', n, ({ checked }) => setAlwaysOnTop(getWin(), checked), {
type: 'checkbox',
checked: false,
}),
]),
menu(
'Debugger',
[
item('New Window', 'Ctrl+T', () => createWindow({ iconPath, isPortSettingRequired: haveOpenedWindow() })),
item('Enable Open in Editor for Console Log', n, () => toggleOpenInEditor(getWin()), {
type: 'checkbox',
checked: false,
}),
item('Toggle Device Sync', n, toggleSyncState, {
type: 'checkbox',
checked: isSyncState(),
}),
item('Open Config File', n, () => openConfigFile()),
separator,
item('Close', 'Ctrl+W', () => close(getWin())),
],
'window',
),
menu('Edit', [
item('Undo', 'Ctrl+Z', n, { selector: 'undo:' }),
item('Redo', 'Shift+Ctrl+Z', n, { selector: 'redo:' }),
separator,
item('Cut', 'Ctrl+X', n, { selector: 'cut:' }),
item('Copy', 'Ctrl+C', n, { selector: 'copy:' }),
item('Paste', 'Ctrl+V', n, { selector: 'paste:' }),
item('Select All', 'Ctrl+A', n, { selector: 'selectAll:' }),
]),
menu(
'View',
viewItems.concat([
item('Toggle Full Screen', 'F11', () => toggleFullscreen(getWin())),
item('Toggle Developer Tools', 'Alt+Ctrl+I', () => toggleDevTools(getWin(), 'chrome')),
item('Toggle React DevTools', 'Alt+Ctrl+J', () => toggleDevTools(getWin(), 'react')),
item('Toggle Redux DevTools', 'Alt+Ctrl+K', () => toggleDevTools(getWin(), 'redux')),
separator,
item('Zoom In', 'Ctrl+=', () => zoom(getWin(), 1)),
item('Zoom Out', 'Ctrl+-', () => zoom(getWin(), -1)),
item('Reset Zoom', 'Ctrl+0', () => resetZoom(getWin())),
]),
),
menu('Help', [
item('Documentation', n, () => shell.openExternal('https://github.com/jhen0409/react-native-debugger/tree/master/docs')),
item('Issues', n, () => shell.openExternal('https://github.com/jhen0409/react-native-debugger/issues')),
item('Open Collective', n, () => shell.openExternal('https://opencollective.com/react-native-debugger')),
]),
]
================================================
FILE: electron/sync-state.js
================================================
import { BrowserWindow } from 'electron'
let syncState = false
export const isSyncState = () => syncState
// Take by renderer
global.isSyncState = isSyncState
export const toggleSyncState = () => {
syncState = !syncState
}
export const sendSyncState = (event, payload) => {
if (!isSyncState) return
BrowserWindow.getAllWindows()
.filter((win) => Number(win.webContents.id) !== event.sender.id)
.forEach((win) => {
win.webContents.send('sync-state', payload)
})
}
================================================
FILE: electron/update.js
================================================
import { app, dialog, shell } from 'electron'
import GhReleases from 'electron-gh-releases'
import fetch from 'electron-fetch'
const repo = 'jhen0409/react-native-debugger'
const getFeed = () => fetch(`https://raw.githubusercontent.com/${repo}/master/auto_update.json`).then((res) => res.json())
const showDialog = ({
icon, buttons, message, detail,
}) => dialog.showMessageBoxSync({
type: 'info',
buttons,
title: 'React Native Debugger',
icon,
message,
detail,
})
const notifyUpdateAvailable = ({ icon, detail }) => {
const index = showDialog({
message: 'A newer version is available.',
buttons: ['Download', 'Later'],
icon,
detail,
})
return index === 0
}
const notifyUpdateDownloaded = ({ icon }) => {
const index = showDialog({
message:
'The newer version has been downloaded. '
+ 'Please restart the application to apply the update.',
buttons: ['Restart', 'Later'],
icon,
})
return index === 0
}
let checking = false
export default (icon, notify) => {
if (checking) return
checking = true
const updater = new GhReleases({
repo,
currentVersion: app.getVersion(),
})
updater.check(async (err, status) => {
if (process.platform === 'linux' && err.message === 'This platform is not supported.') {
err = null; // eslint-disable-line
status = true; // eslint-disable-line
}
if (notify && err) {
showDialog({ message: err.message, buttons: ['OK'] })
checking = false
return
}
if (err || !status) {
checking = false
return
}
const feed = await getFeed()
const detail = `${feed.name}\n\n${feed.notes}`
if (notify) {
const open = notifyUpdateAvailable({ icon, detail })
if (open) shell.openExternal('https://github.com/jhen0409/react-native-debugger/releases')
} else if (
process.env.NODE_ENV === 'production'
&& process.platform === 'darwin'
&& notifyUpdateAvailable({ icon, detail })
) {
updater.download()
console.log('[RNDebugger] Update downloading...')
}
checking = false
})
updater.on('update-downloaded', () => {
console.log('[RNDebugger] Update downloaded')
if (notifyUpdateDownloaded({ icon })) {
updater.install()
}
})
}
================================================
FILE: electron/url-handle/handleURL.js
================================================
import { app } from 'electron'
import net from 'net'
import url from 'url'
import qs from 'querystring'
import fs from 'fs'
import * as portfile from './port'
const filterPaths = (list) => {
const filteredList = list.filter((dir) => {
try {
return fs.lstatSync(dir).isDirectory()
} catch (e) {
return false
}
})
if (!filteredList.length) {
return
}
return filteredList
}
const resolveHost = (host) => (
!host || host === 'undefined' || host === 'null' ? 'localhost' : host
)
export const parseUrl = (_url) => {
const route = url.parse(_url)
if (route.host !== 'set-debugger-loc') return
const { host, port, projectRoots } = qs.parse(route.query)
const query = {
host: resolveHost(host),
port: Number(port) || 8081,
projectRoots: filterPaths(Array.isArray(projectRoots) ? projectRoots : [projectRoots]),
}
return query
}
export const handleURL = async (getWindow, path) => {
const query = parseUrl(path)
if (!query) {
return
}
const payload = JSON.stringify(query)
// This env will be get by new debugger window
process.env.DEBUGGER_SETTING = payload
const win = await getWindow(query.host, query.port)
// if we can get the exists window, it will send the IPC event
if (win) {
win.webContents.send('set-debugger-loc', payload)
}
}
const listenOpenURL = (getWindow) => app.on('open-url', (e, path) => {
handleURL(getWindow, path)
})
const createHandleURLServer = (getWindow) => net
.createServer((socket) => {
socket.setEncoding('utf-8')
socket.on('data', async (data) => {
try {
const obj = JSON.parse(data)
if (typeof obj.path === 'string') {
await handleURL(getWindow, obj.path)
}
socket.write('success')
} catch (e) {
socket.write('fail')
} finally {
socket.end()
}
})
})
.listen(0, '127.0.0.1')
.on('listening', function server() {
const { port } = this.address()
portfile.write(port)
portfile.watchExists(port)
process.on('exit', () => portfile.unlink())
console.log(`Starting listen set-debugger-loc request on port ${port}`)
console.log('Will save port to `$HOME/.rndebugger_port` file')
})
export default (getWindow) => {
// Handle set-debugger-loc for macOS
// It's can be automatically open the app
listenOpenURL(getWindow)
// Handle set-debugger-loc for macOS/Linux/Windows
createHandleURLServer(getWindow)
}
================================================
FILE: electron/url-handle/index.js
================================================
import startListeningHandleURL, { handleURL, parseUrl } from './handleURL'
import * as port from './port'
export {
startListeningHandleURL, handleURL, parseUrl, port,
}
================================================
FILE: electron/url-handle/port.js
================================================
import fs from 'fs'
import path from 'path'
const homeEnv = process.platform === 'win32' ? 'USERPROFILE' : 'HOME'
const portFile = path.join(process.env[homeEnv], '.rndebugger_port')
let isWatching = false
export const write = (port) => {
fs.writeFileSync(portFile, String(port))
}
export function read() {
if (!fs.existsSync(portFile)) return null
return Number(fs.readFileSync(portFile, 'utf8'))
}
export const unlink = () => {
if (fs.existsSync(portFile)) {
fs.unlinkSync(portFile)
}
}
export const watchExists = (port) => {
if (isWatching) return
isWatching = true
fs.watchFile(portFile, (curr, prev) => {
if (curr.mtime !== prev.mtime) write(port)
})
}
================================================
FILE: electron/window.js
================================================
import path from 'path'
import {
BrowserWindow, Menu, globalShortcut, dialog,
} from 'electron'
import Store from 'electron-store'
import { enable } from '@electron/remote/main'
import autoUpdate from './update'
import { catchConsoleLogLink, removeUnecessaryTabs, activeTabs } from './devtools'
import { selectRNDebuggerWorkerContext } from '../app/utils/devtools'
import { readConfig, filePath as configFile } from './config'
import { registerContextMenu } from './context-menu'
const store = new Store()
const executeJavaScript = (win, script) => win.webContents.executeJavaScript(script)
export const checkWindowInfo = (win) => executeJavaScript(win, 'window.checkWindowInfo()')
const checkIsOpenInEditorEnabled = (win) => executeJavaScript(win, 'window.isOpenInEditorEnabled()')
const changeMenuItems = (menus) => {
const rootMenuItems = Menu.getApplicationMenu().items
Object.entries(menus).forEach(([key, subMenu]) => {
const rootMenuItem = rootMenuItems.find(({ label }) => label === key)
if (!rootMenuItem || !rootMenuItem.submenu) return
Object.entries(subMenu).forEach(([subKey, menuSet]) => {
const menuItem = rootMenuItem.submenu.items.find(
({ label }) => label === subKey,
)
if (!menuItem) return
Object.assign(menuItem, menuSet)
})
})
}
const invokeDevMethod = (win, name) => executeJavaScript(
win,
`window.invokeDevMethod && window.invokeDevMethod('${name}')`,
)
const registerKeyboradShortcut = (win) => {
const prefix = process.platform === 'darwin' ? 'Command' : 'Ctrl'
// If another window focused, register a new shortcut
if (
globalShortcut.isRegistered(`${prefix}+R`)
|| globalShortcut.isRegistered(`${prefix}+I`)
) {
globalShortcut.unregisterAll()
}
globalShortcut.register(`${prefix}+R`, () => invokeDevMethod(win, 'reload'))
globalShortcut.register(`${prefix}+I`, () => invokeDevMethod(win, 'toggleElementInspector'))
}
const unregisterKeyboradShortcut = () => globalShortcut.unregisterAll()
const registerShortcuts = async (win) => {
registerKeyboradShortcut(win)
changeMenuItems({
Debugger: {
'Stay in Front': {
checked: win.isAlwaysOnTop(),
},
'Enable Open in Editor for Console Log': {
checked: await checkIsOpenInEditorEnabled(win),
},
},
})
}
const minSize = 100
export const createWindow = ({ iconPath, isPortSettingRequired, port }) => {
const { config, isConfigBroken, error } = readConfig()
if (isConfigBroken) {
dialog.showErrorBox(
'Root config error',
`Parse root config failed, please checkout \`${configFile}\`, the error trace:\n\n`
+ `${error}\n\n`
+ 'RNDebugger will load default config instead. '
+ 'You can click `Debugger` -> `Open Config File` in application menu.',
)
}
const winBounds = store.get('winBounds') || {}
const increasePosition = BrowserWindow.getAllWindows().length * 10 || 0
const {
width, height, x = 0, y = 0,
} = winBounds
const win = new BrowserWindow({
...winBounds,
width: width && width >= minSize ? width : 1024,
height: height && height >= minSize ? height : 750,
minWidth: minSize,
minHeight: minSize,
x: x + increasePosition,
y: y + increasePosition,
backgroundColor: '#272c37',
tabbingIdentifier: 'rndebugger',
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
// experimentalFeatures: true,
// webSecurity: false,
// webviewTag: true, // Use this for new inspector in the future
},
...config.windowBounds,
})
enable(win.webContents)
const isFirstWindow = BrowserWindow.getAllWindows().length === 1
const { timesJSLoadToRefreshDevTools = -1 } = config
win.debuggerConfig = {
port,
editor: config.editor,
fontFamily: config.fontFamily,
defaultReactDevToolsTheme: config.defaultReactDevToolsTheme,
defaultReactDevToolsPort: config.defaultReactDevToolsPort,
networkInspect: config.defaultNetworkInspect && 1,
isPortSettingRequired: isPortSettingRequired && 1,
timesJSLoadToRefreshDevTools,
}
win.loadURL(`file://${path.resolve(__dirname)}/app.html`)
let unregisterContextMenu
win.webContents.on('did-finish-load', () => {
win.webContents.zoomLevel = config.zoomLevel || store.get('zoomLevel', 0)
win.focus()
unregisterContextMenu = registerContextMenu(win)
registerShortcuts(win)
if (!isPortSettingRequired) win.openDevTools()
const checkUpdate = config.autoUpdate !== false
if (checkUpdate && isFirstWindow) {
autoUpdate(iconPath)
}
})
win.webContents.on('devtools-opened', async () => {
const { location } = await checkWindowInfo(win)
activeTabs(win)
catchConsoleLogLink(win, location.host, location.port)
if (config.showAllDevToolsTab !== true) {
removeUnecessaryTabs(win)
}
selectRNDebuggerWorkerContext(win)
})
win.on('show', () => {
if (!win.isFocused()) return
registerShortcuts(win)
})
win.on('focus', () => registerShortcuts(win))
win.on('restore', () => registerShortcuts(win))
win.on('hide', () => unregisterKeyboradShortcut())
win.on('blur', () => unregisterKeyboradShortcut())
win.on('minimize', () => unregisterKeyboradShortcut())
win.close = async () => {
unregisterKeyboradShortcut()
store.set('winBounds', win.getBounds())
store.set('zoomLevel', win.webContents.zoomLevel)
await executeJavaScript(
win,
'window.beforeWindowClose && window.beforeWindowClose()',
)
win.destroy()
}
win.on('close', (event) => {
event.preventDefault()
win.close()
if (unregisterContextMenu) unregisterContextMenu()
})
return win
}
================================================
FILE: examples/.eslintrc
================================================
{
"rules": {
"import/no-extraneous-dependencies": 0,
"import/no-unresolved": 0
}
}
================================================
FILE: examples/test-old-bridge/.gitignore
================================================
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
================================================
FILE: examples/test-old-bridge/App.js
================================================
/* eslint-disable react/style-prop-object */
import { StatusBar } from 'expo-status-bar'
import React from 'react'
import { StyleSheet, View } from 'react-native'
import ReduxApp from './examples/redux/App'
import ApolloApp from './examples/apollo/App'
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
})
export default function App() {
return (
)
}
================================================
FILE: examples/test-old-bridge/README.md
================================================
# test-old-bridge
This is example created by `npx create-expo-app -t blank@48`, `"jsEngine": "jsc"` to `app.json`.
The main purpose is for test functionality of React Native Debugger in old bridge.
Currently the examples included
- simple counter example for Redux
- simple query example for Apollo Client
================================================
FILE: examples/test-old-bridge/app.json
================================================
{
"expo": {
"jsEngine": "jsc",
"name": "test-old-bridge",
"slug": "test-old-bridge",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
================================================
FILE: examples/test-old-bridge/babel.config.js
================================================
module.exports = (api) => {
api.cache(true)
return {
presets: ['babel-preset-expo'],
}
}
================================================
FILE: examples/test-old-bridge/examples/apollo/App.js
================================================
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'
import SimpleQuery from './SimpleQuery'
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
title: {
marginBottom: 20,
fontSize: 25,
textAlign: 'center',
fontWeight: 'bold',
},
})
const client = new ApolloClient({
uri: 'https://spacex-production.up.railway.app/',
cache: new InMemoryCache(),
})
export default function App() {
return (
Apollo Client example
)
}
================================================
FILE: examples/test-old-bridge/examples/apollo/SimpleQuery.js
================================================
import React from 'react'
import { StyleSheet, Text, Button } from 'react-native'
import { useQuery } from '@apollo/client'
import gql from 'graphql-tag'
const styles = StyleSheet.create({
text: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
})
const GET_DATA = gql`
query ExampleQuery {
company {
name
ceo
employees
}
}
`
export default function SimpleQuery() {
const { loading, error, data, refetch } = useQuery(GET_DATA)
if (loading) return Loading...
if (error) return Error :({error.message})
return (
<>
Company: {data.company.name}CEO: {data.company.ceo}Employees: {data.company.employees}