Repository: jhen0409/react-native-debugger Branch: master Commit: bd3435a456a2 Files: 144 Total size: 364.4 KB Directory structure: gitextract_p_y_i4jj/ ├── .eslintignore ├── .eslintrc ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE.md │ ├── settings.yml │ └── workflows/ │ ├── main.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── LICENSE.md ├── README.md ├── __e2e__/ │ ├── app.spec.js │ ├── buildTestBundle.js │ ├── fixture/ │ │ ├── apollo.js │ │ ├── app.js │ │ ├── mobx.js │ │ ├── redux.js │ │ ├── remotedev.js │ │ ├── setup.js │ │ └── xhr-test.js │ └── mockRNServer.js ├── app/ │ ├── actions/ │ │ ├── debugger.js │ │ └── setting.js │ ├── components/ │ │ ├── Draggable.js │ │ └── FormInput.js │ ├── containers/ │ │ ├── App.js │ │ ├── ReactInspector.js │ │ └── redux/ │ │ ├── DevTools.js │ │ ├── Header.js │ │ └── Settings.js │ ├── globalStyles.js │ ├── index.js │ ├── middlewares/ │ │ ├── debuggerAPI.js │ │ └── reduxAPI.js │ ├── reducers/ │ │ ├── debugger.js │ │ ├── index.js │ │ └── setting.js │ ├── setup.js │ ├── store/ │ │ └── configureStore.js │ ├── utils/ │ │ ├── adb.js │ │ ├── config.js │ │ ├── devMenu.js │ │ └── devtools.js │ └── worker/ │ ├── .eslintrc │ ├── apollo.js │ ├── asyncStorage.js │ ├── devMenu.js │ ├── index.js │ ├── networkInspect.js │ ├── polyfills/ │ │ └── fetch.js │ ├── reactDevTools.js │ ├── reduxAPI.js │ ├── remotedev.js │ ├── setup.js │ └── utils.js ├── auto_update.json ├── auto_updater.json ├── babel.config.js ├── dist/ │ ├── app.html │ ├── css/ │ │ └── style.css │ ├── devtools-helper/ │ │ ├── main.html │ │ ├── main.js │ │ └── manifest.json │ ├── package.json │ └── patches/ │ └── apollo-client-devtools+4.1.4.patch ├── docs/ │ ├── README.md │ ├── apollo-client-devtools-integration.md │ ├── config-file-in-home-directory.md │ ├── contributing.md │ ├── debugger-integration.md │ ├── enable-open-in-editor-in-console.md │ ├── getting-started.md │ ├── network-inspect-of-chrome-devtools.md │ ├── react-devtools-integration.md │ ├── redux-devtools-integration.md │ ├── shortcut-references.md │ └── troubleshooting.md ├── electron/ │ ├── app.html │ ├── config/ │ │ ├── __tests__/ │ │ │ ├── __snapshots__/ │ │ │ │ └── index.test.js.snap │ │ │ └── index.test.js │ │ ├── index.js │ │ └── template.js │ ├── context-menu.js │ ├── debug.js │ ├── devtools.js │ ├── extensions.js │ ├── logo.icns │ ├── main.js │ ├── menu/ │ │ ├── common.js │ │ ├── darwin.js │ │ ├── dialog.js │ │ ├── index.js │ │ └── linux+win.js │ ├── sync-state.js │ ├── update.js │ ├── url-handle/ │ │ ├── handleURL.js │ │ ├── index.js │ │ └── port.js │ └── window.js ├── examples/ │ ├── .eslintrc │ └── test-old-bridge/ │ ├── .gitignore │ ├── App.js │ ├── README.md │ ├── app.json │ ├── babel.config.js │ ├── examples/ │ │ ├── apollo/ │ │ │ ├── App.js │ │ │ └── SimpleQuery.js │ │ └── redux/ │ │ ├── App.js │ │ ├── app/ │ │ │ └── store.js │ │ └── features/ │ │ └── counter/ │ │ ├── Counter.js │ │ ├── counterAPI.js │ │ └── counterSlice.js │ └── package.json ├── npm-package/ │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── bin/ │ │ └── rndebugger-open.js │ ├── package.json │ └── src/ │ ├── __tests__/ │ │ ├── __snapshots__/ │ │ │ └── injectDevToolsMiddleware.test.js.snap │ │ └── injectDevToolsMiddleware.test.js │ ├── injectDevToolsMiddleware.js │ ├── main.js │ └── open.js ├── package.json ├── patches/ │ ├── @redux-devtools+inspector-monitor-trace-tab+1.0.0.patch │ ├── @redux-devtools+ui+1.3.0.patch │ ├── apollo-client-devtools+4.1.4.patch │ ├── electron-gh-releases+2.0.4.patch │ └── react-dev-utils+4.2.3.patch ├── scripts/ │ ├── config.json │ ├── mac/ │ │ ├── createDMG.js │ │ ├── createUniversalApp.js │ │ └── entitlements.plist │ ├── package-linux.sh │ ├── package-macos.sh │ ├── package-windows.sh │ ├── patch-modules.js │ └── postinstall.js └── webpack/ ├── .eslintrc ├── base.js ├── main.prod.js ├── renderer.dev.js └── renderer.prod.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ npm-package/lib/ *.tmpl.js *.bundle.js dist/ release/ node_modules/ ================================================ FILE: .eslintrc ================================================ { "parser": "@babel/eslint-parser", "extends": ["airbnb", "prettier"], "env": { "browser": true, "node": true, "jest": true }, "settings": { "import/core-modules": ["electron"] }, "rules": { "linebreak-style": 0, "react/prefer-stateless-function": 0, "consistent-return": 0, "strict": 0, "no-console": 0, "no-param-reassign": ["error", { "props": false }], "no-underscore-dangle": [ "error", { "allow": [ "__IS_REDUX_NATIVE_MESSAGE__", "__AVAILABLE_METHODS_CAN_CALL_BY_RNDEBUGGER__", "__PLATFORM__", "__REPORT_REACT_DEVTOOLS_PORT__", "__FETCH_SUPPORT__", "__NETWORK_INSPECT__", "__FROM_DEBUGGER_WORKER__" ] } ], "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], "import/prefer-default-export": 0, "import/no-extraneous-dependencies": ["error", { "optionalDependencies": true }], "semi": ["error", "never"] } } ================================================ FILE: .github/FUNDING.yml ================================================ github: jhen0409 open_collective: react-native-debugger ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ React Native Debugger app version: [FILL THIS OUT] React Native version: [FILL THIS OUT] Platform: [FILL THIS OUT: iOS, Android, ...] Is real device of platform: [Yes or No] Operating System: [FILL THIS OUT: macOS, Linux, Windows] ================================================ FILE: .github/settings.yml ================================================ repository: name: react-native-debugger has_issues: true has_wiki: true has_downloads: true default_branch: master allow_squash_merge: true allow_merge_commit: true allow_rebase_merge: true labels: - name: bug color: CC0000 - name: help wanted color: 33aa3f - name: duplicate color: cfd3d7 - name: enhancement color: a2eeef - name: question color: d876e3 - name: invalid color: e4e669 - name: wontfix color: ffffff - name: feature color: 336699 - name: trivial oldname: good first issue color: afffb2 - name: has PR color: 3100ad - name: documentation color: a1ed95 - name: RFC color: 3100ad - name: WIP color: e4e669 - name: don't merge color: CC0000 - name: stale color: ffffff - name: cannot-reproduce color: bfdadc - name: more-information-needed color: c2e0c6 - name: upstream color: 5319e7 - name: waiting-for-response color: 3100ad # Integrations - name: integration/react-devtools color: 1b307c - name: integration/redux-devtools color: 7c2d9e - name: integration/apollo-client-devtools color: 7066e8 # Features - name: feature/network-inspect color: fce885 # Packages - name: package/react-native-debugger-open color: f7e78f ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: build-test-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3.7.0 with: node-version: 18.x cache: 'yarn' - name: Setup run: sudo apt-get install -y libgbm-dev - name: Test id: test run: | yarn cd npm-package && yarn && cd .. yarn test yarn build xvfb-run --auto-servernum yarn test-e2e - name: Upload artifacts on failure if: ${{ failure() && steps.test.conclusion == 'failure' }} uses: actions/upload-artifact@v3 with: name: artifacts path: artifacts retention-days: 1 build-test-macos: runs-on: macOS-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3.7.0 with: node-version: 18.x cache: 'yarn' - name: Test id: test run: | yarn cd npm-package && yarn && cd .. yarn test yarn build yarn test-e2e - name: Upload artifacts on failure if: ${{ failure() && steps.test.conclusion == 'failure' }} uses: actions/upload-artifact@v3 with: name: artifacts path: artifacts retention-days: 1 build-test-windows: runs-on: windows-2022 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3.7.0 with: node-version: 18.x cache: 'yarn' - name: Test id: test shell: bash run: | yarn config set network-timeout 500000 -g yarn cd npm-package && yarn && cd .. yarn build yarn test yarn test-e2e - name: Upload artifacts on failure if: ${{ failure() && steps.test.conclusion == 'failure' }} uses: actions/upload-artifact@v3 with: name: artifacts path: artifacts retention-days: 1 ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: release: types: [published] jobs: release-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3.7.0 with: node-version: 18.x cache: 'yarn' - name: Package run: | yarn yarn build yarn pack-linux - name: Upload assets to release uses: jhen0409/release-asset-action@master with: file: release/rn-debugger-linux-x64.zip pattern: release/react-native-debugger* github-token: ${{ secrets.GITHUB_TOKEN }} release-windows: runs-on: windows-2022 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3.7.0 with: node-version: 18.x cache: 'yarn' - name: Package shell: bash run: | yarn config set network-timeout 500000 -g yarn yarn build yarn pack-windows - name: Upload assets to release uses: jhen0409/release-asset-action@master with: file: release/rn-debugger-windows-x64.zip pattern: release/react_native_debugger*.exe github-token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ node_modules npm-debug.log yarn-error.log .DS_Store dist/js dist/main.js* release/ *.bundle.js tmp/ config_test .idea/ artifacts/ ================================================ FILE: .prettierrc ================================================ { "trailingComma": "all", "tabWidth": 2, "semi": false, "singleQuote": true, "printWidth": 80 } ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2016 Jhen-Jie Hong Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # React Native Debugger [![Backers on Open Collective](https://opencollective.com/react-native-debugger/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/react-native-debugger/sponsors/badge.svg)](#sponsors) [![CI Status](https://github.com/jhen0409/react-native-debugger/workflows/CI/badge.svg)](https://github.com/jhen0409/react-native-debugger) ⚠️ This app is currently only supported old [Remote Debugger](https://reactnative.dev/docs/debugging#chrome-developer-tools), if you're looking for new debugger support (e.g. Hermes / JSI / New Architecture) of React Native Debugger, please follow [discussion#774](https://github.com/jhen0409/react-native-debugger/discussions/774). ![React Native Debugger](https://user-images.githubusercontent.com/3001525/29451479-6621bf1a-83c8-11e7-8ebb-b4e98b1af91c.png) > Run the redux example of [react-navigation](https://github.com/react-navigation/react-navigation/tree/master/example) with Redux DevTools setup This is a standalone app for debugging React Native apps: - Based on official [Remote Debugger](https://reactnative.dev/docs/debugging#chrome-developer-tools) and provide more functionality. - Includes [React Inspector](docs/react-devtools-integration.md) from [`react-devtools-core`](https://github.com/facebook/react/tree/master/packages/react-devtools-core). - Includes Redux DevTools, made [the same API](docs/redux-devtools-integration.md) with [`redux-devtools-extension`](https://github.com/reduxjs/redux-devtools/tree/main/extension). - Includes [Apollo Client DevTools](docs/apollo-client-devtools-integration.md) ([`apollographql/apollo-client-devtools`](https://github.com/apollographql/apollo-client-devtools)) as devtools extension. ## Installation To install the app, you can download a prebuilt binary from the [release page](https://github.com/jhen0409/react-native-debugger/releases). For **macOS**, you can use [Homebrew Cask](https://caskroom.github.io) to install: ### < Homebrew 2.6.0 ```bash brew update && brew install --cask react-native-debugger ``` ### >= Homebrew 2.6.0 ```bash brew install --cask react-native-debugger ``` This puts `React Native Debugger.app` in your `/applications/` folder. ### NOTICE: React Native Compatibility To use this app you need to ensure you are using the correct version of React Native Debugger and react-native: | React Native Debugger | react-native | | --------------------- | ------------ | | >= 0.11 | >= 0.62 | | <= 0.10 | <= 0.61 | We used different auto-update feed for `v0.10` and `v0.11`, so you won't see update tips of `v0.11` from `v0.10`. Install last release of v0.10 (0.10.7) ### < Homebrew 2.6.0 `brew update && brew cask install https://raw.githubusercontent.com/Homebrew/homebrew-cask/b6ac3795c1df9f97242481c0817b1165e3e6306a/Casks/react-native-debugger.rb` ### >= Homebrew 2.6.0 `brew install --cask https://raw.githubusercontent.com/Homebrew/homebrew-cask/b6ac3795c1df9f97242481c0817b1165e3e6306a/Casks/react-native-debugger.rb` ### Arch-based distributions You can install [react-native-debugger-bin][1] from Arch User Repository: ```shell git clone https://aur.archlinux.org/react-native-debugger-bin.git cd react-native-debugger-bin makepkg -si # or using AUR helper paru -S react-native-debugger-bin ``` ## Build from source Please read [Development section](docs/contributing.md#development) in docs/contributing.md for how to build the app from source. ## Documentation - [Getting Started](docs/getting-started.md) - [Debugger Integration](docs/debugger-integration.md) - [React DevTools Integration](docs/react-devtools-integration.md) - [Redux DevTools Integration](docs/redux-devtools-integration.md) - [Apollo Client DevTools Integration](docs/apollo-client-devtools-integration.md) - [Shortcut references](docs/shortcut-references.md) - [Network inspect of Chrome Developer Tools](docs/network-inspect-of-chrome-devtools.md) - [Enable open in editor in console](docs/enable-open-in-editor-in-console.md) - [Config file in home directory](docs/config-file-in-home-directory.md) - [Troubleshooting](docs/troubleshooting.md) - [Contributing](docs/contributing.md) ## Documentation (v0.10) Please visit [`v0.10 branch`](https://github.com/jhen0409/react-native-debugger/tree/v0.10). ## Credits - Great work of [React DevTools](https://github.com/facebook/react/tree/master/packages/react-devtools) - Great work of [Redux DevTools](https://github.com/gaearon/redux-devtools) / [Remote Redux DevTools](https://github.com/zalmoxisus/remote-redux-devtools) and all third-party monitors. - Great work of [Apollo Client DevTools](https://github.com/apollographql/apollo-client-devtools)). (Special thanks to [@Gongreg](https://github.com/Gongreg) for integrating this!) ## Backers Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/react-native-debugger#backer)] ## Sponsors Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/react-native-debugger#sponsor)] ## LICENSE [MIT](LICENSE.md) [1]: https://aur.archlinux.org/packages/react-native-debugger-bin ================================================ FILE: __e2e__/app.spec.js ================================================ import fs from 'fs' import path from 'path' import net from 'net' import http from 'http' import executablePath from 'electron' import { _electron as electron } from 'playwright-core' import waitForExpect from 'wait-for-expect' import buildTestBundle, { bundlePath } from './buildTestBundle' import createMockRNServer from './mockRNServer' const delay = (time) => new Promise((resolve) => { setTimeout(resolve, time) }) jest.setTimeout(6e4) describe('Application launch', () => { let electronApp let mainWindow const logs = [] beforeAll(async () => { await buildTestBundle() process.env.PACKAGE = 'no' electronApp = await electron.launch({ executablePath, args: ['--user-dir=__e2e__/tmp', './main.js'], cwd: path.join(__dirname, '../dist'), }) mainWindow = await electronApp.firstWindow() mainWindow.on('console', (msg) => logs.push(msg)) await mainWindow.waitForLoadState() }) afterEach(async () => { const state = expect.getState() await mainWindow.screenshot({ path: `./artifacts/${state.currentTestName}.png`, }) const devtoolsWindow = electronApp.windows()[2] if (devtoolsWindow) { try { await delay(100) await devtoolsWindow.screenshot({ path: `./artifacts/devtools:${state.currentTestName}.png`, }) } catch (e) { console.error(e) } } }) afterAll(async () => { await electronApp.close() }) it('should show an initial window', async () => { expect(await mainWindow.title()).toBe( 'React Native Debugger - Attempting reconnection (port 8081)', ) }) it('should portfile (for debugger-open usage) always exists in home dir', async () => { const portFile = path.join( process.env.USERPROFILE || process.env.HOME, '.rndebugger_port', ) expect(fs.existsSync(portFile)).toBe(true) fs.unlinkSync(portFile) await waitForExpect(async () => { expect(fs.existsSync(portFile)).toBeTruthy() }) }) it("should contain Inspector monitor's component on Redux DevTools", async () => { const val = await mainWindow.textContent( '//div[contains(@class, "inspector-")]', ) expect(val).not.toBeNull() }) it('should contain an empty actions list on Redux DevTools', async () => { const val = await mainWindow.textContent( '//div[contains(@class, "actionListRows-")]', ) expect(val).toBe('') }) it('should show waiting message on React DevTools', async () => { const el = await mainWindow.locator( '//h2[text()="Waiting for React to connect…"]', ) expect(await el.isVisible()).toBe(true) }) const customRNServerPort = 8098 const getURLFromConnection = (server) => new Promise((resolve) => { server.on('connection', (socket, req) => { resolve(req.url) }) }) it('should connect to fake RN server', async () => { const { wss, server } = createMockRNServer() const url = await getURLFromConnection(wss) expect(url).toBe('/debugger-proxy?role=debugger&name=Chrome') await waitForExpect(async () => { expect(await mainWindow.title()).toBe( 'React Native Debugger - Waiting for client connection (port 8081)', ) }) server.close() wss.close() }) it('should connect to fake RN server (port 8088) with send set-debugger-loc after', async () => { const { wss, server } = createMockRNServer(customRNServerPort) const rndPath = `rndebugger://set-debugger-loc?host=localhost&port=${customRNServerPort}` const homeEnv = process.platform === 'win32' ? 'USERPROFILE' : 'HOME' const portFile = path.join(process.env[homeEnv], '.rndebugger_port') const rndPort = fs.readFileSync(portFile, 'utf-8') const sendSuccess = await new Promise((resolve) => { const socket = net.createConnection( { host: '127.0.0.1', port: rndPort }, () => { let pass socket.setEncoding('utf-8') socket.write(JSON.stringify({ path: rndPath })) socket.on('data', (data) => { pass = data === 'success' socket.end() }) socket.on('end', () => resolve(pass)) setTimeout(() => socket.end(), 1000) }, ) }) expect(sendSuccess).toBe(true) const url = await getURLFromConnection(wss) expect(url).toBe('/debugger-proxy?role=debugger&name=Chrome') await waitForExpect(async () => { expect(await mainWindow.title()).toBe( `React Native Debugger - Waiting for client connection (port ${customRNServerPort})`, ) }) server.close() wss.close() }) describe('Import fake script after', () => { const getOneRequestHeaders = (port) => new Promise((resolve) => { const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('') resolve(req.headers) server.close() }) server.listen(port) }) let headersPromise let server let wss beforeAll(async () => { const info = createMockRNServer(customRNServerPort) server = info.server wss = info.wss headersPromise = getOneRequestHeaders(8099) await new Promise((resolve) => { wss.on('connection', (socket) => { socket.on('message', (message) => { const data = JSON.parse(message) switch (data.replyID) { case 'createJSRuntime': socket.send( JSON.stringify({ id: 'sendFakeScript', method: 'executeApplicationScript', inject: [], url: `../../${bundlePath}`, }), ) break case 'sendFakeScript': return resolve() default: console.error(`Unexperted id ${data.replyID}, data:`, data) } }) socket.send( JSON.stringify({ id: 'createJSRuntime', method: 'prepareJSRuntime', }), ) }) }) expect(await mainWindow.title()).toBe( `React Native Debugger - Connected (port ${customRNServerPort})`, ) }) afterAll(() => { server.close() wss.close() }) it('should received forbidden header names from xhr-test', async () => { const headers = await headersPromise expect(headers.origin).toBe('custom_origin_here') expect(headers['user-agent']).toBe('react-native') }) it('should have @@INIT action on Redux DevTools', async () => { const el = await mainWindow .locator('div') .filter({ hasText: '@@redux/INIT' }) .first() expect(await el.isVisible()).toBeTruthy() // Last store is `RemoteDev store instance 1` }) let currentInstance = 'Autoselect instances' // Default instance const wait = () => delay(750) const selectInstance = async (instance) => { let el = mainWindow.locator(`//div[text()="${currentInstance}"]`) expect(await el.isVisible()).toBeTruthy() await el.click({ force: true }) await wait() currentInstance = instance el = mainWindow.locator(`//div[text()="${instance}"]`) expect(await el.isVisible()).toBeTruthy() await el.click({ force: true }) await wait() } const commit = async () => { await mainWindow.click('//button[text()="Commit"]', { force: true }) await wait() } const expectActions = { 'Redux store instance 1': { expt: [ '@@INIT', 'TEST_PASS_FOR_REDUX_STORE_1', 'SHOW_FOR_REDUX_STORE_1', ], notExpt: ['NOT_SHOW_FOR_REDUX_STORE_1', 'TEST_PASS_FOR_REDUX_STORE_2'], }, 'Redux store instance 2': { expt: ['@@INIT', 'TEST_PASS_FOR_REDUX_STORE_2'], notExpt: [ 'TEST_PASS_FOR_REDUX_STORE_1', 'NOT_SHOW_1_FOR_REDUX_STORE_2', 'NOT_SHOW_2_FOR_REDUX_STORE_2', 'NOT_SHOW_3_FOR_REDUX_STORE_2', ], }, 'MobX store instance 1': { expt: ['@@INIT', 'testPassForMobXStore1'], notExpt: ['TEST_PASS_FOR_REDUX_STORE_2'], }, 'MobX store instance 2': { expt: ['@@INIT', 'testPassForMobXStore2'], notExpt: ['testPassForMobXStore1'], }, 'RemoteDev store instance 1': { expt: ['@@redux/INIT', 'TEST_PASS_FOR_REMOTEDEV_STORE_1'], notExpt: ['testPassForMobXStore2'], }, } const eachAsync = (entries, fn) => entries.reduce( (promise, entry, index) => promise.then(() => fn(entry, index)), Promise.resolve(), ) const runExpectActions = async (name) => { const { expt, notExpt } = expectActions[name] await eachAsync(expt, async (action) => { const el = await mainWindow .locator('div') .filter({ hasText: action }) .first() expect(await el.isVisible()).toBeTruthy() }) await eachAsync(notExpt, async (action) => { const el = await mainWindow .locator('div') .filter({ hasText: action }) .first() expect(await el.isVisible()).toBeFalsy() }) } const checkInstance = async (name) => { await selectInstance(name) await runExpectActions(name) await commit() } it('should have two Redux store instances on Redux DevTools', async () => { await checkInstance('Redux store instance 1') await checkInstance('Redux store instance 2') }) it('should have two MobX store instances on Redux DevTools', async () => { await checkInstance('MobX store instance 1') await checkInstance('MobX store instance 2') }) it('should have one RemoteDev store instances on Redux DevTools', async () => { await checkInstance('RemoteDev store instance 1') }) it('should have only specific logs in console of main window', async () => { // Print renderer process logs logs.forEach((log) => console.log(`Message: ${log.text()}\nType: ${log.type()}`), ) expect(logs.length).toEqual(3) // clear + clear + warning const [, , formDataWarning] = logs expect(formDataWarning.type()).toBe('warning') expect( formDataWarning .text() .indexOf("Detected you're enabled Network Inspect") > 0, ).toBeTruthy() }) it('should show apollo devtools panel', async () => { const devtoolsWindow = electronApp.windows()[2] expect( await devtoolsWindow.evaluate(() => // eslint-disable-next-line no-undef Object.keys(UI.panels).some( (key) => key.startsWith('chrome-extension://') && key.endsWith('Apollo'), ), ), ).toBeTruthy() }) }) }) ================================================ FILE: __e2e__/buildTestBundle.js ================================================ import path from 'path' import webpack from 'webpack' const outputPath = '__e2e__/fixture' const filename = 'app.bundle.js' export const bundlePath = path.join(outputPath, filename) // Build a bundle for simulate RNDebugger worker run react-native bundle, // it included redux, mobx, remotedev tests export default function buildTestBundle() { return new Promise((resolve) => { webpack({ mode: 'development', entry: './__e2e__/fixture/app', output: { path: path.resolve(__dirname, '..', outputPath), filename, }, resolve: { mainFields: ['react-native', 'main', 'browser'], }, }).run(resolve) }) } ================================================ FILE: __e2e__/fixture/apollo.js ================================================ /* eslint-disable import/no-extraneous-dependencies */ /* * Create an Apollo Client to test the bridge messages sent * wouldn't break the debugger proxy. */ import { ApolloClient, InMemoryCache } from '@apollo/client' import gql from 'graphql-tag' const client = new ApolloClient({ uri: 'https://spacex-production.up.railway.app/', cache: new InMemoryCache(), }) export default async function run() { return client.query({ query: gql` query ExampleQuery { company { name ceo employees } } `, }) } ================================================ FILE: __e2e__/fixture/app.js ================================================ import './setup' import runXHRTest from './xhr-test' // Install fetch polyfill before initial apollo-client import runApolloTest from './apollo' import runReduxTest from './redux' import runMobXTest from './mobx' import runRemoteDevTest from './remotedev' runReduxTest() runMobXTest() runRemoteDevTest() runApolloTest().catch((e) => console.error(e)) runXHRTest().catch((e) => console.error(e)) ================================================ FILE: __e2e__/fixture/mobx.js ================================================ /* eslint prefer-arrow-callback: 0 */ import { observable, action, useStrict } from 'mobx' import remotedev from 'mobx-remotedev/lib/dev' useStrict(true) export default function run() { const store = observable({ value: 0 }) store.testPassForMobXStore1 = action(function testPassForMobXStore1() {}) remotedev(store, { name: 'MobX store instance 1' }).testPassForMobXStore1() const store2 = observable({ value: 1 }) store2.testPassForMobXStore2 = action(function testPassForMobXStore2() {}) remotedev(store2, { name: 'MobX store instance 2' }).testPassForMobXStore2() } ================================================ FILE: __e2e__/fixture/redux.js ================================================ /* eslint no-underscore-dangle: 0 */ import { createStore } from 'redux' export default function run() { // Enhancer const store1 = createStore( (state) => state, { value: 0 }, window.__REDUX_DEVTOOLS_EXTENSION__({ name: 'Redux store instance 1', actionsWhitelist: ['@@INIT', 'TEST_PASS_FOR_REDUX_STORE_1', '^SHOW_FOR_REDUX_STORE_1$'], }), ) // Compose enhancers const store2 = createStore( (state) => state, { value: 1 }, window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ name: 'Redux store instance 2', actionsBlacklist: ['NOT_SHOW_1_FOR_REDUX_STORE_2', 'NOT_SHOW_2_FOR_REDUX_STORE_2'], predicate: (state, action) => action.type !== 'NOT_SHOW_3_FOR_REDUX_STORE_2', })(/* No enhancers */), ) store1.dispatch({ type: 'TEST_PASS_FOR_REDUX_STORE_1' }) store1.dispatch({ type: 'SHOW_FOR_REDUX_STORE_1' }) store1.dispatch({ type: 'NOT_SHOW_FOR_REDUX_STORE_1' }) store2.dispatch({ type: 'TEST_PASS_FOR_REDUX_STORE_2' }) store2.dispatch({ type: 'NOT_SHOW_1_FOR_REDUX_STORE_2' }) store2.dispatch({ type: 'NOT_SHOW_2_FOR_REDUX_STORE_2' }) store2.dispatch({ type: 'NOT_SHOW_3_FOR_REDUX_STORE_2' }) } ================================================ FILE: __e2e__/fixture/remotedev.js ================================================ import { createStore } from 'redux' const connectViaExtension = window.devToolsExtension.connect const logReducer = (reducer) => { const remotedev = connectViaExtension({ name: 'RemoteDev store instance 1', actionCreators: { test: () => {}, }, }) return (state, action) => { const reducedState = reducer(state, action) remotedev.send(action, reducedState) return reducedState } } const logRemotely = (next) => (reducer, initialState) => next(logReducer(reducer), initialState) export default function run() { const store = logRemotely(createStore)((state) => state, { value: 0 }) store.dispatch({ type: 'TEST_PASS_FOR_REMOTEDEV_STORE_1' }) } ================================================ FILE: __e2e__/fixture/setup.js ================================================ /* eslint-disable no-restricted-globals */ /* eslint no-underscore-dangle: 0 */ self.window = global // Remove native fetch as react-native use whatwg-fetch polyfill self.fetch = undefined const MessageQueue = function MessageQueue() {} MessageQueue.spy = () => {} MessageQueue.prototype.__spy = null const requiredModules = { NativeModules: {}, Platform: {}, setupDevtools: undefined, AsyncStorage: {}, MessageQueue, } // Simulate React Native's window.require polyfill window.require = (moduleName) => { if (typeof moduleName !== 'number') { // From https://github.com/facebook/react-native/blob/5403946f098cc72c3d33ea5cee263fb3dd03891d/packager/src/Resolver/polyfills/require.js#L97 console.warn( `Requiring module '${moduleName}' by name is only supported for ` + 'debugging purposes and will BREAK IN PRODUCTION!', ) } return requiredModules[moduleName] } window.__DEV__ = true window.__fbBatchedBridge = new MessageQueue() ================================================ FILE: __e2e__/fixture/xhr-test.js ================================================ import 'whatwg-fetch' export default async function run() { // Fetch with forbidden header names await fetch('http://localhost:8099', { headers: { Origin: 'custom_origin_here', 'User-Agent': 'react-native', }, }) // It will log warning // because we can't use worker's FormData for upload file const data = { uri: 'uri' } const form = new FormData() form.append('file', data) } ================================================ FILE: __e2e__/mockRNServer.js ================================================ import http from 'http' import WebSocket from 'ws' export default function createMockRNServer(port = 8081) { const server = http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/debugger-ui') { res.writeHead(200, { 'Content-Type': 'text/html' }) res.end('') } }) const wss = new WebSocket.Server({ server }) server.listen(port) return { server, wss } } ================================================ FILE: app/actions/debugger.js ================================================ export const SET_DEBUGGER_LOCATION = 'SET_DEBUGGER_LOCATION' export const SET_DEBUGGER_STATUS = 'SET_DEBUGGER_STATUS' export const SET_DEBUGGER_WORKER = 'SET_DEBUGGER_WORKER' export const SYNC_STATE = 'SYNC_STATE' export const BEFORE_WINDOW_CLOSE = 'BEFORE_WINDOW_CLOSE' export const setDebuggerLocation = (loc) => ({ type: SET_DEBUGGER_LOCATION, loc, }) export const setDebuggerStatus = (status) => ({ type: SET_DEBUGGER_STATUS, status, }) export const setDebuggerWorker = (worker, status) => ({ type: SET_DEBUGGER_WORKER, worker, status, }) export const syncState = (payload) => ({ type: SYNC_STATE, payload, }) export const beforeWindowClose = () => ({ type: BEFORE_WINDOW_CLOSE, }) ================================================ FILE: app/actions/setting.js ================================================ export const TOGGLE_DEVTOOLS = 'TOGGLE_DEVTOOLS' export const RESIZE_DEVTOOLS = 'RESIZE_DEVTOOLS' export const CHANGE_DEFAULT_THEME = 'CHANGE_DEFAULT_THEME' export const toggleDevTools = (name) => ({ type: TOGGLE_DEVTOOLS, name, }) export const resizeDevTools = (size) => ({ type: RESIZE_DEVTOOLS, size, }) export const changeDefaultTheme = (themeName) => ({ type: CHANGE_DEFAULT_THEME, themeName, }) ================================================ FILE: app/components/Draggable.js ================================================ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' const styles = { draggable: { position: 'relative', zIndex: 1, cursor: 'ns-resize', padding: '3px 0', margin: '-3px 0', width: '100%', }, } export default class Draggable extends PureComponent { onMove = (evt) => { evt.preventDefault() const { onMove } = this.props onMove?.(evt.pageX, evt.pageY) } onUp = (evt) => { evt.preventDefault() document.removeEventListener('mousemove', this.onMove) document.removeEventListener('mouseup', this.onUp) const { onStop } = this.props onStop?.() } startDragging = (evt) => { evt.preventDefault() document.addEventListener('mousemove', this.onMove) document.addEventListener('mouseup', this.onUp) const { onStart } = this.props onStart?.() } render() { return (
) } } Draggable.propTypes = { onStart: PropTypes.func, onMove: PropTypes.func.isRequired, onStop: PropTypes.func, } Draggable.defaultProps = { onStart: undefined, onStop: undefined, } ================================================ FILE: app/components/FormInput.js ================================================ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' const styles = { title: { textAlign: 'center' }, form: { display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', margin: 8, }, input: { appearance: 'none', fontSize: '16px', margin: 2, padding: '8px', border: 0, borderRadius: 2, }, button: { fontSize: '16px', margin: 2, padding: '8px', border: 0, borderRadius: 2, }, } export default class FormInput extends PureComponent { constructor(props) { super(props) this.state = { value: undefined } } handleOnChange = (evt) => { const { onInputChange } = this.props let { value } = evt.target if (onInputChange) { value = onInputChange(value) } this.setState({ value }) } handleClick = (evt) => { const { inputProps, onSubmit } = this.props const { value } = this.state onSubmit(evt, value || inputProps.value) } render() { const { title, inputProps, button } = this.props const { value } = this.state const val = typeof value !== 'undefined' ? value : inputProps.value return (
{title}
{ // Enter/Return key pressed if (e.key === 'Enter') this.handleClick() }} type={inputProps.type} value={val} style={styles.input} onChange={this.handleOnChange} />
) } } FormInput.propTypes = { title: PropTypes.string.isRequired, inputProps: PropTypes.shape({ type: PropTypes.string, value: PropTypes.string, }), button: PropTypes.string, onInputChange: PropTypes.func, onSubmit: PropTypes.func.isRequired, } FormInput.defaultProps = { inputProps: { type: 'input' }, button: 'Confirm', onInputChange: null, } ================================================ FILE: app/containers/App.js ================================================ import { ipcRenderer } from 'electron' import { getCurrentWindow } from '@electron/remote' import React, { Component } from 'react' import PropTypes from 'prop-types' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import * as debuggerActionCreators from '../actions/debugger' import * as settingActionCreators from '../actions/setting' import ReduxDevTools from './redux/DevTools' import ReactInspector from './ReactInspector' import FormInput from '../components/FormInput' import Draggable from '../components/Draggable' import { catchConsoleLogLink } from '../../electron/devtools' const currentWindow = getCurrentWindow() const styles = { container: { height: '100%', }, wrapBackground: { display: 'flex', height: '100%', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', color: '#ccc', fontSize: '25px', WebkitUserSelect: 'none', }, text: { textAlign: 'center', margin: '7px', }, shortcut: { fontFamily: 'Monaco, monospace', color: '#ddd', backgroundColor: '#555', padding: '4px', borderRadius: '4px', letterSpacing: '3px', }, } const shortcutPrefix = process.platform === 'darwin' ? '⌥⌘' : 'Ctrl+Alt+' const background = (
{`${shortcutPrefix}K`} {' to toggle Redux DevTools'}
{`${shortcutPrefix}J`} {' to toggle React DevTools'}
) const removeAllIPCListeners = () => { ipcRenderer.removeAllListeners('toggle-devtools') ipcRenderer.removeAllListeners('set-debugger-loc') ipcRenderer.removeAllListeners('sync-state') } class App extends Component { componentDidMount() { const { settingActions, debuggerActions } = this.props ipcRenderer.on('toggle-devtools', (e, name) => settingActions.toggleDevTools(name)) ipcRenderer.on('sync-state', (event, arg) => debuggerActions.syncState(arg)) ipcRenderer.on('set-debugger-loc', (e, payload) => { const location = JSON.parse(payload) this.setDebuggerLocation(location) catchConsoleLogLink(currentWindow, location.host || 'localhost', location.port) }) const { debuggerState } = this.props if (!debuggerState.isPortSettingRequired) { this.setDebuggerLocation(JSON.parse(process.env.DEBUGGER_SETTING || '{}')) } window.onbeforeunload = removeAllIPCListeners window.notifyDevToolsThemeChange = settingActions.changeDefaultTheme } componentWillUnmount() { removeAllIPCListeners() window.notifyDevToolsThemeChange = null } onResize = (x, y) => { const { settingActions } = this.props settingActions.resizeDevTools(y / window.innerHeight) } setDebuggerLocation({ projectRoots, ...location }) { const { debuggerActions } = this.props debuggerActions.setDebuggerLocation(location) if (projectRoots) { ReactInspector.setProjectRoots(projectRoots) } } getReactBackgroundColor = () => { const { settingState } = this.props const { themeName } = settingState switch (themeName) { case 'dark': return '#242424' case 'default': return 'white' default: return 'transparent' } } getLayoutStyle = (size) => ({ width: '100%', height: `${size * 100}%`, display: size ? 'inline-block' : 'none', backgroundColor: this.getReactBackgroundColor(), }) getDevToolsSize() { const { settingState } = this.props const { redux, react, size } = settingState if (!redux || !react) { return { redux: redux ? 1 : 0, react: react ? 1 : 0, } } return { redux: size, react: 1 - size } } handlePortOnSubmit = (evt, port) => { ipcRenderer.once('check-port-available-reply', (event, available) => { if (!available) { window.alert(`The port ${port} is already used by another window.`) return } const { debuggerActions } = this.props debuggerActions.setDebuggerLocation({ ...JSON.parse(process.env.DEBUGGER_SETTING || '{}'), port, }) currentWindow.openDevTools() }) ipcRenderer.send('check-port-available', port) } renderPortSetting() { return (
Number(value.replace(/\D/g, '').substr(0, 5)) || ''} onSubmit={this.handlePortOnSubmit} />
) } renderReduxDevTools(size) { return (
) } renderReactInspector(size) { return (
) } render() { const { debuggerState } = this.props const { isPortSettingRequired } = debuggerState if (isPortSettingRequired) { return this.renderPortSetting() } const { redux, react } = this.getDevToolsSize() return (
{this.renderReduxDevTools(redux)} {this.renderReactInspector(react)} {!react && !redux && background}
) } } 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: screen shot 2019-02-01 at 1 51 27 pm 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: 2017-08-16 10 44 41 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: ![Context menu](https://cloud.githubusercontent.com/assets/3001525/25920996/5c488966-3606-11e7-8d0c-cb564671067b.gif) - 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 touch-bar 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: ![image](https://user-images.githubusercontent.com/848589/69504219-b0d46d00-0f85-11ea-99ed-de5e4e2e59c0.png) ## 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: 2017-09-19 5 32 05 pm After: 2017-09-19 5 31 33 pm Fortunately, the current versions of RNDebugger (Chromium 58) is better than Chrome (maybe >= 59?), but it still has a amount of growth: Before: 2017-09-19 5 40 18 pm After: 2017-09-19 5 41 27 pm 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: ![](https://user-images.githubusercontent.com/3001525/28763926-214df82c-75f4-11e7-98bc-1be54638f91b.png) 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: 2017-07-31 1 19 34 ## 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: 2017-07-18 10 09 01 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}