├── .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
├── logo.png
├── package.json
├── patches
│ └── apollo-client-devtools+4.1.4.patch
└── yarn.lock
├── 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
├── logo.ico
├── logo.png
├── 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
│ ├── assets
│ ├── adaptive-icon.png
│ ├── favicon.png
│ ├── icon.png
│ └── splash.png
│ ├── babel.config.js
│ ├── examples
│ ├── apollo
│ │ ├── App.js
│ │ └── SimpleQuery.js
│ └── redux
│ │ ├── App.js
│ │ ├── app
│ │ └── store.js
│ │ └── features
│ │ └── counter
│ │ ├── Counter.js
│ │ ├── counterAPI.js
│ │ └── counterSlice.js
│ ├── package-lock.json
│ └── package.json
├── npm-package
├── .eslintrc
├── .gitignore
├── README.md
├── babel.config.js
├── bin
│ └── rndebugger-open.js
├── lib
│ └── injectDevToolsMiddleware.tmpl.js
├── package.json
├── src
│ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── injectDevToolsMiddleware.test.js.snap
│ │ └── injectDevToolsMiddleware.test.js
│ ├── injectDevToolsMiddleware.js
│ ├── main.js
│ └── open.js
└── yarn.lock
├── 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
│ ├── dmg-background.png
│ ├── dmg-background@2x.png
│ └── 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
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | npm-package/lib/
2 | *.tmpl.js
3 | *.bundle.js
4 | dist/
5 | release/
6 | node_modules/
7 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@babel/eslint-parser",
3 | "extends": ["airbnb", "prettier"],
4 | "env": {
5 | "browser": true,
6 | "node": true,
7 | "jest": true
8 | },
9 | "settings": {
10 | "import/core-modules": ["electron"]
11 | },
12 | "rules": {
13 | "linebreak-style": 0,
14 | "react/prefer-stateless-function": 0,
15 | "consistent-return": 0,
16 | "strict": 0,
17 | "no-console": 0,
18 | "no-param-reassign": ["error", { "props": false }],
19 | "no-underscore-dangle": [
20 | "error",
21 | {
22 | "allow": [
23 | "__IS_REDUX_NATIVE_MESSAGE__",
24 | "__AVAILABLE_METHODS_CAN_CALL_BY_RNDEBUGGER__",
25 | "__PLATFORM__",
26 | "__REPORT_REACT_DEVTOOLS_PORT__",
27 | "__FETCH_SUPPORT__",
28 | "__NETWORK_INSPECT__",
29 | "__FROM_DEBUGGER_WORKER__"
30 | ]
31 | }
32 | ],
33 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
34 | "import/prefer-default-export": 0,
35 | "import/no-extraneous-dependencies": ["error", { "optionalDependencies": true }],
36 | "semi": ["error", "never"]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: jhen0409
2 | open_collective: react-native-debugger
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
11 |
12 |
15 |
16 | React Native Debugger app version: [FILL THIS OUT]
17 | React Native version: [FILL THIS OUT]
18 | Platform: [FILL THIS OUT: iOS, Android, ...]
19 | Is real device of platform: [Yes or No]
20 | Operating System: [FILL THIS OUT: macOS, Linux, Windows]
21 |
22 |
24 |
--------------------------------------------------------------------------------
/.github/settings.yml:
--------------------------------------------------------------------------------
1 | repository:
2 | name: react-native-debugger
3 | has_issues: true
4 | has_wiki: true
5 | has_downloads: true
6 | default_branch: master
7 | allow_squash_merge: true
8 | allow_merge_commit: true
9 | allow_rebase_merge: true
10 |
11 | labels:
12 | - name: bug
13 | color: CC0000
14 | - name: help wanted
15 | color: 33aa3f
16 | - name: duplicate
17 | color: cfd3d7
18 | - name: enhancement
19 | color: a2eeef
20 | - name: question
21 | color: d876e3
22 | - name: invalid
23 | color: e4e669
24 | - name: wontfix
25 | color: ffffff
26 | - name: feature
27 | color: 336699
28 | - name: trivial
29 | oldname: good first issue
30 | color: afffb2
31 | - name: has PR
32 | color: 3100ad
33 | - name: documentation
34 | color: a1ed95
35 | - name: RFC
36 | color: 3100ad
37 | - name: WIP
38 | color: e4e669
39 | - name: don't merge
40 | color: CC0000
41 | - name: stale
42 | color: ffffff
43 | - name: cannot-reproduce
44 | color: bfdadc
45 | - name: more-information-needed
46 | color: c2e0c6
47 | - name: upstream
48 | color: 5319e7
49 | - name: waiting-for-response
50 | color: 3100ad
51 |
52 | # Integrations
53 | - name: integration/react-devtools
54 | color: 1b307c
55 | - name: integration/redux-devtools
56 | color: 7c2d9e
57 | - name: integration/apollo-client-devtools
58 | color: 7066e8
59 |
60 | # Features
61 | - name: feature/network-inspect
62 | color: fce885
63 |
64 | # Packages
65 | - name: package/react-native-debugger-open
66 | color: f7e78f
67 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | concurrency:
6 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
7 | cancel-in-progress: true
8 |
9 | jobs:
10 | build-test-linux:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: actions/setup-node@v3.7.0
15 | with:
16 | node-version: 18.x
17 | cache: 'yarn'
18 | - name: Setup
19 | run: sudo apt-get install -y libgbm-dev
20 | - name: Test
21 | id: test
22 | run: |
23 | yarn
24 | cd npm-package && yarn && cd ..
25 | yarn test
26 | yarn build
27 | xvfb-run --auto-servernum yarn test-e2e
28 | - name: Upload artifacts on failure
29 | if: ${{ failure() && steps.test.conclusion == 'failure' }}
30 | uses: actions/upload-artifact@v3
31 | with:
32 | name: artifacts
33 | path: artifacts
34 | retention-days: 1
35 | build-test-macos:
36 | runs-on: macOS-latest
37 | steps:
38 | - uses: actions/checkout@v3
39 | - uses: actions/setup-node@v3.7.0
40 | with:
41 | node-version: 18.x
42 | cache: 'yarn'
43 | - name: Test
44 | id: test
45 | run: |
46 | yarn
47 | cd npm-package && yarn && cd ..
48 | yarn test
49 | yarn build
50 | yarn test-e2e
51 | - name: Upload artifacts on failure
52 | if: ${{ failure() && steps.test.conclusion == 'failure' }}
53 | uses: actions/upload-artifact@v3
54 | with:
55 | name: artifacts
56 | path: artifacts
57 | retention-days: 1
58 | build-test-windows:
59 | runs-on: windows-2022
60 | steps:
61 | - uses: actions/checkout@v3
62 | - uses: actions/setup-node@v3.7.0
63 | with:
64 | node-version: 18.x
65 | cache: 'yarn'
66 | - name: Test
67 | id: test
68 | shell: bash
69 | run: |
70 | yarn config set network-timeout 500000 -g
71 | yarn
72 | cd npm-package && yarn && cd ..
73 | yarn build
74 | yarn test
75 | yarn test-e2e
76 | - name: Upload artifacts on failure
77 | if: ${{ failure() && steps.test.conclusion == 'failure' }}
78 | uses: actions/upload-artifact@v3
79 | with:
80 | name: artifacts
81 | path: artifacts
82 | retention-days: 1
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | release-linux:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v3
12 | - uses: actions/setup-node@v3.7.0
13 | with:
14 | node-version: 18.x
15 | cache: 'yarn'
16 | - name: Package
17 | run: |
18 | yarn
19 | yarn build
20 | yarn pack-linux
21 | - name: Upload assets to release
22 | uses: jhen0409/release-asset-action@master
23 | with:
24 | file: release/rn-debugger-linux-x64.zip
25 | pattern: release/react-native-debugger*
26 | github-token: ${{ secrets.GITHUB_TOKEN }}
27 | release-windows:
28 | runs-on: windows-2022
29 | steps:
30 | - uses: actions/checkout@v3
31 | - uses: actions/setup-node@v3.7.0
32 | with:
33 | node-version: 18.x
34 | cache: 'yarn'
35 | - name: Package
36 | shell: bash
37 | run: |
38 | yarn config set network-timeout 500000 -g
39 | yarn
40 | yarn build
41 | yarn pack-windows
42 | - name: Upload assets to release
43 | uses: jhen0409/release-asset-action@master
44 | with:
45 | file: release/rn-debugger-windows-x64.zip
46 | pattern: release/react_native_debugger*.exe
47 | github-token: ${{ secrets.GITHUB_TOKEN }}
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | yarn-error.log
4 | .DS_Store
5 | dist/js
6 | dist/main.js*
7 | release/
8 | *.bundle.js
9 | tmp/
10 | config_test
11 | .idea/
12 | artifacts/
13 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": true,
6 | "printWidth": 80
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2016 Jhen-Jie Hong
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Debugger
2 |
3 | [](#backers) [](#sponsors) [](https://github.com/jhen0409/react-native-debugger)
4 |
5 | ⚠️ 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).
6 |
7 | 
8 |
9 | > Run the redux example of [react-navigation](https://github.com/react-navigation/react-navigation/tree/master/example) with Redux DevTools setup
10 |
11 | This is a standalone app for debugging React Native apps:
12 |
13 | - Based on official [Remote Debugger](https://reactnative.dev/docs/debugging#chrome-developer-tools) and provide more functionality.
14 | - Includes [React Inspector](docs/react-devtools-integration.md) from [`react-devtools-core`](https://github.com/facebook/react/tree/master/packages/react-devtools-core).
15 | - 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).
16 | - Includes [Apollo Client DevTools](docs/apollo-client-devtools-integration.md) ([`apollographql/apollo-client-devtools`](https://github.com/apollographql/apollo-client-devtools)) as devtools extension.
17 |
18 | ## Installation
19 |
20 | To install the app, you can download a prebuilt binary from the [release page](https://github.com/jhen0409/react-native-debugger/releases).
21 |
22 | For **macOS**, you can use [Homebrew Cask](https://caskroom.github.io) to install:
23 |
24 | ### < Homebrew 2.6.0
25 |
26 | ```bash
27 | brew update && brew install --cask react-native-debugger
28 | ```
29 |
30 | ### >= Homebrew 2.6.0
31 |
32 | ```bash
33 | brew install --cask react-native-debugger
34 | ```
35 |
36 | This puts `React Native Debugger.app` in your `/applications/` folder.
37 |
38 | ### NOTICE: React Native Compatibility
39 |
40 | To use this app you need to ensure you are using the correct version of React Native Debugger and react-native:
41 |
42 | | React Native Debugger | react-native |
43 | | --------------------- | ------------ |
44 | | >= 0.11 | >= 0.62 |
45 | | <= 0.10 | <= 0.61 |
46 |
47 | 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`.
48 |
49 | Install last release of v0.10 (0.10.7)
50 |
51 | ### < Homebrew 2.6.0
52 |
53 | `brew update && brew cask install https://raw.githubusercontent.com/Homebrew/homebrew-cask/b6ac3795c1df9f97242481c0817b1165e3e6306a/Casks/react-native-debugger.rb`
54 |
55 | ### >= Homebrew 2.6.0
56 |
57 | `brew install --cask https://raw.githubusercontent.com/Homebrew/homebrew-cask/b6ac3795c1df9f97242481c0817b1165e3e6306a/Casks/react-native-debugger.rb`
58 |
59 | ### Arch-based distributions
60 |
61 | You can install [react-native-debugger-bin][1] from Arch User Repository:
62 |
63 | ```shell
64 | git clone https://aur.archlinux.org/react-native-debugger-bin.git
65 | cd react-native-debugger-bin
66 | makepkg -si
67 |
68 | # or using AUR helper
69 | paru -S react-native-debugger-bin
70 | ```
71 |
72 | ## Build from source
73 |
74 | Please read [Development section](docs/contributing.md#development) in docs/contributing.md for how to build the app from source.
75 |
76 | ## Documentation
77 |
78 | - [Getting Started](docs/getting-started.md)
79 | - [Debugger Integration](docs/debugger-integration.md)
80 | - [React DevTools Integration](docs/react-devtools-integration.md)
81 | - [Redux DevTools Integration](docs/redux-devtools-integration.md)
82 | - [Apollo Client DevTools Integration](docs/apollo-client-devtools-integration.md)
83 | - [Shortcut references](docs/shortcut-references.md)
84 | - [Network inspect of Chrome Developer Tools](docs/network-inspect-of-chrome-devtools.md)
85 | - [Enable open in editor in console](docs/enable-open-in-editor-in-console.md)
86 | - [Config file in home directory](docs/config-file-in-home-directory.md)
87 | - [Troubleshooting](docs/troubleshooting.md)
88 | - [Contributing](docs/contributing.md)
89 |
90 | ## Documentation (v0.10)
91 |
92 | Please visit [`v0.10 branch`](https://github.com/jhen0409/react-native-debugger/tree/v0.10).
93 |
94 | ## Credits
95 |
96 | - Great work of [React DevTools](https://github.com/facebook/react/tree/master/packages/react-devtools)
97 | - 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.
98 | - Great work of [Apollo Client DevTools](https://github.com/apollographql/apollo-client-devtools)). (Special thanks to [@Gongreg](https://github.com/Gongreg) for integrating this!)
99 |
100 | ## Backers
101 |
102 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/react-native-debugger#backer)]
103 |
104 |
105 |
106 | ## Sponsors
107 |
108 | 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)]
109 |
110 |
111 |
112 | ## LICENSE
113 |
114 | [MIT](LICENSE.md)
115 |
116 | [1]: https://aur.archlinux.org/packages/react-native-debugger-bin
117 |
--------------------------------------------------------------------------------
/__e2e__/buildTestBundle.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import webpack from 'webpack'
3 |
4 | const outputPath = '__e2e__/fixture'
5 | const filename = 'app.bundle.js'
6 |
7 | export const bundlePath = path.join(outputPath, filename)
8 |
9 | // Build a bundle for simulate RNDebugger worker run react-native bundle,
10 | // it included redux, mobx, remotedev tests
11 | export default function buildTestBundle() {
12 | return new Promise((resolve) => {
13 | webpack({
14 | mode: 'development',
15 | entry: './__e2e__/fixture/app',
16 | output: {
17 | path: path.resolve(__dirname, '..', outputPath),
18 | filename,
19 | },
20 | resolve: {
21 | mainFields: ['react-native', 'main', 'browser'],
22 | },
23 | }).run(resolve)
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/__e2e__/fixture/apollo.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | /*
3 | * Create an Apollo Client to test the bridge messages sent
4 | * wouldn't break the debugger proxy.
5 | */
6 |
7 | import { ApolloClient, InMemoryCache } from '@apollo/client'
8 | import gql from 'graphql-tag'
9 |
10 | const client = new ApolloClient({
11 | uri: 'https://spacex-production.up.railway.app/',
12 | cache: new InMemoryCache(),
13 | })
14 |
15 | export default async function run() {
16 | return client.query({
17 | query: gql`
18 | query ExampleQuery {
19 | company {
20 | name
21 | ceo
22 | employees
23 | }
24 | }
25 | `,
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/__e2e__/fixture/app.js:
--------------------------------------------------------------------------------
1 | import './setup'
2 |
3 | import runXHRTest from './xhr-test' // Install fetch polyfill before initial apollo-client
4 | import runApolloTest from './apollo'
5 | import runReduxTest from './redux'
6 | import runMobXTest from './mobx'
7 | import runRemoteDevTest from './remotedev'
8 |
9 | runReduxTest()
10 | runMobXTest()
11 | runRemoteDevTest()
12 | runApolloTest().catch((e) => console.error(e))
13 | runXHRTest().catch((e) => console.error(e))
14 |
--------------------------------------------------------------------------------
/__e2e__/fixture/mobx.js:
--------------------------------------------------------------------------------
1 | /* eslint prefer-arrow-callback: 0 */
2 | import { observable, action, useStrict } from 'mobx'
3 | import remotedev from 'mobx-remotedev/lib/dev'
4 |
5 | useStrict(true)
6 |
7 | export default function run() {
8 | const store = observable({ value: 0 })
9 | store.testPassForMobXStore1 = action(function testPassForMobXStore1() {})
10 |
11 | remotedev(store, { name: 'MobX store instance 1' }).testPassForMobXStore1()
12 |
13 | const store2 = observable({ value: 1 })
14 | store2.testPassForMobXStore2 = action(function testPassForMobXStore2() {})
15 |
16 | remotedev(store2, { name: 'MobX store instance 2' }).testPassForMobXStore2()
17 | }
18 |
--------------------------------------------------------------------------------
/__e2e__/fixture/redux.js:
--------------------------------------------------------------------------------
1 | /* eslint no-underscore-dangle: 0 */
2 |
3 | import { createStore } from 'redux'
4 |
5 | export default function run() {
6 | // Enhancer
7 | const store1 = createStore(
8 | (state) => state,
9 | { value: 0 },
10 | window.__REDUX_DEVTOOLS_EXTENSION__({
11 | name: 'Redux store instance 1',
12 | actionsWhitelist: ['@@INIT', 'TEST_PASS_FOR_REDUX_STORE_1', '^SHOW_FOR_REDUX_STORE_1$'],
13 | }),
14 | )
15 |
16 | // Compose enhancers
17 | const store2 = createStore(
18 | (state) => state,
19 | { value: 1 },
20 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
21 | name: 'Redux store instance 2',
22 | actionsBlacklist: ['NOT_SHOW_1_FOR_REDUX_STORE_2', 'NOT_SHOW_2_FOR_REDUX_STORE_2'],
23 | predicate: (state, action) => action.type !== 'NOT_SHOW_3_FOR_REDUX_STORE_2',
24 | })(/* No enhancers */),
25 | )
26 |
27 | store1.dispatch({ type: 'TEST_PASS_FOR_REDUX_STORE_1' })
28 | store1.dispatch({ type: 'SHOW_FOR_REDUX_STORE_1' })
29 | store1.dispatch({ type: 'NOT_SHOW_FOR_REDUX_STORE_1' })
30 |
31 | store2.dispatch({ type: 'TEST_PASS_FOR_REDUX_STORE_2' })
32 | store2.dispatch({ type: 'NOT_SHOW_1_FOR_REDUX_STORE_2' })
33 | store2.dispatch({ type: 'NOT_SHOW_2_FOR_REDUX_STORE_2' })
34 | store2.dispatch({ type: 'NOT_SHOW_3_FOR_REDUX_STORE_2' })
35 | }
36 |
--------------------------------------------------------------------------------
/__e2e__/fixture/remotedev.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux'
2 |
3 | const connectViaExtension = window.devToolsExtension.connect
4 |
5 | const logReducer = (reducer) => {
6 | const remotedev = connectViaExtension({
7 | name: 'RemoteDev store instance 1',
8 | actionCreators: {
9 | test: () => {},
10 | },
11 | })
12 | return (state, action) => {
13 | const reducedState = reducer(state, action)
14 | remotedev.send(action, reducedState)
15 | return reducedState
16 | }
17 | }
18 |
19 | const logRemotely = (next) => (reducer, initialState) => next(logReducer(reducer), initialState)
20 |
21 | export default function run() {
22 | const store = logRemotely(createStore)((state) => state, { value: 0 })
23 |
24 | store.dispatch({ type: 'TEST_PASS_FOR_REMOTEDEV_STORE_1' })
25 | }
26 |
--------------------------------------------------------------------------------
/__e2e__/fixture/setup.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-globals */
2 | /* eslint no-underscore-dangle: 0 */
3 |
4 | self.window = global
5 |
6 | // Remove native fetch as react-native use whatwg-fetch polyfill
7 | self.fetch = undefined
8 |
9 | const MessageQueue = function MessageQueue() {}
10 | MessageQueue.spy = () => {}
11 | MessageQueue.prototype.__spy = null
12 |
13 | const requiredModules = {
14 | NativeModules: {},
15 | Platform: {},
16 | setupDevtools: undefined,
17 | AsyncStorage: {},
18 | MessageQueue,
19 | }
20 | // Simulate React Native's window.require polyfill
21 | window.require = (moduleName) => {
22 | if (typeof moduleName !== 'number') {
23 | // From https://github.com/facebook/react-native/blob/5403946f098cc72c3d33ea5cee263fb3dd03891d/packager/src/Resolver/polyfills/require.js#L97
24 | console.warn(
25 | `Requiring module '${moduleName}' by name is only supported for `
26 | + 'debugging purposes and will BREAK IN PRODUCTION!',
27 | )
28 | }
29 | return requiredModules[moduleName]
30 | }
31 | window.__DEV__ = true
32 | window.__fbBatchedBridge = new MessageQueue()
33 |
--------------------------------------------------------------------------------
/__e2e__/fixture/xhr-test.js:
--------------------------------------------------------------------------------
1 | import 'whatwg-fetch'
2 |
3 | export default async function run() {
4 | // Fetch with forbidden header names
5 | await fetch('http://localhost:8099', {
6 | headers: {
7 | Origin: 'custom_origin_here',
8 | 'User-Agent': 'react-native',
9 | },
10 | })
11 |
12 | // It will log warning
13 | // because we can't use worker's FormData for upload file
14 | const data = { uri: 'uri' }
15 | const form = new FormData()
16 | form.append('file', data)
17 | }
18 |
--------------------------------------------------------------------------------
/__e2e__/mockRNServer.js:
--------------------------------------------------------------------------------
1 | import http from 'http'
2 | import WebSocket from 'ws'
3 |
4 | export default function createMockRNServer(port = 8081) {
5 | const server = http.createServer((req, res) => {
6 | if (req.method === 'GET' && req.url === '/debugger-ui') {
7 | res.writeHead(200, { 'Content-Type': 'text/html' })
8 | res.end('')
9 | }
10 | })
11 | const wss = new WebSocket.Server({ server })
12 | server.listen(port)
13 | return { server, wss }
14 | }
15 |
--------------------------------------------------------------------------------
/app/actions/debugger.js:
--------------------------------------------------------------------------------
1 | export const SET_DEBUGGER_LOCATION = 'SET_DEBUGGER_LOCATION'
2 | export const SET_DEBUGGER_STATUS = 'SET_DEBUGGER_STATUS'
3 | export const SET_DEBUGGER_WORKER = 'SET_DEBUGGER_WORKER'
4 | export const SYNC_STATE = 'SYNC_STATE'
5 | export const BEFORE_WINDOW_CLOSE = 'BEFORE_WINDOW_CLOSE'
6 |
7 | export const setDebuggerLocation = (loc) => ({
8 | type: SET_DEBUGGER_LOCATION,
9 | loc,
10 | })
11 |
12 | export const setDebuggerStatus = (status) => ({
13 | type: SET_DEBUGGER_STATUS,
14 | status,
15 | })
16 |
17 | export const setDebuggerWorker = (worker, status) => ({
18 | type: SET_DEBUGGER_WORKER,
19 | worker,
20 | status,
21 | })
22 |
23 | export const syncState = (payload) => ({
24 | type: SYNC_STATE,
25 | payload,
26 | })
27 |
28 | export const beforeWindowClose = () => ({
29 | type: BEFORE_WINDOW_CLOSE,
30 | })
31 |
--------------------------------------------------------------------------------
/app/actions/setting.js:
--------------------------------------------------------------------------------
1 | export const TOGGLE_DEVTOOLS = 'TOGGLE_DEVTOOLS'
2 | export const RESIZE_DEVTOOLS = 'RESIZE_DEVTOOLS'
3 | export const CHANGE_DEFAULT_THEME = 'CHANGE_DEFAULT_THEME'
4 |
5 | export const toggleDevTools = (name) => ({
6 | type: TOGGLE_DEVTOOLS,
7 | name,
8 | })
9 |
10 | export const resizeDevTools = (size) => ({
11 | type: RESIZE_DEVTOOLS,
12 | size,
13 | })
14 |
15 | export const changeDefaultTheme = (themeName) => ({
16 | type: CHANGE_DEFAULT_THEME,
17 | themeName,
18 | })
19 |
--------------------------------------------------------------------------------
/app/components/Draggable.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const styles = {
5 | draggable: {
6 | position: 'relative',
7 | zIndex: 1,
8 | cursor: 'ns-resize',
9 | padding: '3px 0',
10 | margin: '-3px 0',
11 | width: '100%',
12 | },
13 | }
14 |
15 | export default class Draggable extends PureComponent {
16 | onMove = (evt) => {
17 | evt.preventDefault()
18 | const { onMove } = this.props
19 | onMove?.(evt.pageX, evt.pageY)
20 | }
21 |
22 | onUp = (evt) => {
23 | evt.preventDefault()
24 | document.removeEventListener('mousemove', this.onMove)
25 | document.removeEventListener('mouseup', this.onUp)
26 | const { onStop } = this.props
27 | onStop?.()
28 | }
29 |
30 | startDragging = (evt) => {
31 | evt.preventDefault()
32 | document.addEventListener('mousemove', this.onMove)
33 | document.addEventListener('mouseup', this.onUp)
34 | const { onStart } = this.props
35 | onStart?.()
36 | }
37 |
38 | render() {
39 | return (
40 |
47 | )
48 | }
49 | }
50 |
51 | Draggable.propTypes = {
52 | onStart: PropTypes.func,
53 | onMove: PropTypes.func.isRequired,
54 | onStop: PropTypes.func,
55 | }
56 |
57 | Draggable.defaultProps = {
58 | onStart: undefined,
59 | onStop: undefined,
60 | }
61 |
--------------------------------------------------------------------------------
/app/components/FormInput.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | const styles = {
5 | title: { textAlign: 'center' },
6 | form: {
7 | display: 'flex',
8 | flexDirection: 'row',
9 | justifyContent: 'center',
10 | alignItems: 'center',
11 | margin: 8,
12 | },
13 | input: {
14 | appearance: 'none',
15 | fontSize: '16px',
16 | margin: 2,
17 | padding: '8px',
18 | border: 0,
19 | borderRadius: 2,
20 | },
21 | button: {
22 | fontSize: '16px',
23 | margin: 2,
24 | padding: '8px',
25 | border: 0,
26 | borderRadius: 2,
27 | },
28 | }
29 |
30 | export default class FormInput extends PureComponent {
31 | constructor(props) {
32 | super(props)
33 | this.state = { value: undefined }
34 | }
35 |
36 | handleOnChange = (evt) => {
37 | const { onInputChange } = this.props
38 | let { value } = evt.target
39 | if (onInputChange) {
40 | value = onInputChange(value)
41 | }
42 | this.setState({ value })
43 | }
44 |
45 | handleClick = (evt) => {
46 | const { inputProps, onSubmit } = this.props
47 | const { value } = this.state
48 | onSubmit(evt, value || inputProps.value)
49 | }
50 |
51 | render() {
52 | const { title, inputProps, button } = this.props
53 | const { value } = this.state
54 | const val = typeof value !== 'undefined' ? value : inputProps.value
55 | return (
56 |
57 |
{title}
58 |
59 | {
61 | // Enter/Return key pressed
62 | if (e.key === 'Enter') this.handleClick()
63 | }}
64 | type={inputProps.type}
65 | value={val}
66 | style={styles.input}
67 | onChange={this.handleOnChange}
68 | />
69 |
70 | {button}
71 |
72 |
73 |
74 | )
75 | }
76 | }
77 |
78 | FormInput.propTypes = {
79 | title: PropTypes.string.isRequired,
80 | inputProps: PropTypes.shape({
81 | type: PropTypes.string,
82 | value: PropTypes.string,
83 | }),
84 | button: PropTypes.string,
85 | onInputChange: PropTypes.func,
86 | onSubmit: PropTypes.func.isRequired,
87 | }
88 |
89 | FormInput.defaultProps = {
90 | inputProps: { type: 'input' },
91 | button: 'Confirm',
92 | onInputChange: null,
93 | }
94 |
--------------------------------------------------------------------------------
/app/containers/ReactInspector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import React, { Component } from 'react'
3 | import PropTypes from 'prop-types'
4 | import { tryADBReverse } from '../utils/adb'
5 |
6 | let ReactServer
7 | const getReactInspector = () => {
8 | if (ReactServer) return ReactServer
9 | // eslint-disable-next-line
10 | ReactServer = ReactServer || require('react-devtools-core/standalone').default;
11 |
12 | return ReactServer
13 | }
14 | const containerId = 'react-devtools-container'
15 |
16 | const styles = {
17 | container: {
18 | display: 'flex',
19 | height: '100%',
20 | justifyContent: 'center',
21 | position: 'relative',
22 | },
23 | waiting: {
24 | height: '100%',
25 | display: 'flex',
26 | WebkitUserSelect: 'none',
27 | textAlign: 'center',
28 | color: '#aaa',
29 | justifyContent: 'center',
30 | alignItems: 'center',
31 | position: 'absolute',
32 | top: 0,
33 | left: 0,
34 | right: 0,
35 | bottom: 0,
36 | },
37 | }
38 |
39 | const isReactPanelOpen = (props) => props.settingState.react
40 |
41 | class ReactInspector extends Component {
42 | static setProjectRoots(projectRoots) {
43 | getReactInspector().setProjectRoots(projectRoots)
44 | }
45 |
46 | listeningPort = window.reactDevToolsPort
47 |
48 | componentDidMount() {
49 | const { debuggerState } = this.props
50 | const { worker } = debuggerState
51 | if (worker) {
52 | this.server = this.startServer()
53 | worker.addEventListener('message', this.workerOnMessage)
54 | }
55 | }
56 |
57 | UNSAFE_componentWillReceiveProps(nextProps) {
58 | const { debuggerState } = this.props
59 | const { worker } = debuggerState
60 | const { worker: nextWorker } = nextProps.debuggerState
61 | if (nextWorker && nextWorker !== worker) {
62 | this.closeServerIfExists()
63 | if (isReactPanelOpen(this.props)) {
64 | this.server = this.startServer()
65 | }
66 | nextWorker.addEventListener('message', this.workerOnMessage)
67 | } else if (!nextWorker) {
68 | this.closeServerIfExists()
69 | }
70 | // Open / Close server when react panel opened / hidden
71 | if (!worker && !nextWorker) return
72 | if (isReactPanelOpen(this.props) && !isReactPanelOpen(nextProps)) {
73 | this.closeServerIfExists()
74 | } else if (!isReactPanelOpen(this.props) && isReactPanelOpen(nextProps)) {
75 | this.closeServerIfExists()
76 | this.server = this.startServer()
77 | }
78 | }
79 |
80 | shouldComponentUpdate() {
81 | return false
82 | }
83 |
84 | componentWillUnmount() {
85 | this.closeServerIfExists()
86 | }
87 |
88 | workerOnMessage = (message) => {
89 | const { data } = message
90 | if (!data || !data.__REPORT_REACT_DEVTOOLS_PORT__) return
91 |
92 | const port = Number(data.__REPORT_REACT_DEVTOOLS_PORT__)
93 | const { platform } = data
94 | if (port && port !== this.listeningPort) {
95 | this.listeningPort = port
96 | this.closeServerIfExists()
97 | if (isReactPanelOpen(this.props)) {
98 | this.server = this.startServer(port)
99 | }
100 | if (platform === 'android') tryADBReverse(port).catch(() => {})
101 | }
102 | }
103 |
104 | closeServerIfExists = () => {
105 | if (this.server) {
106 | this.server.close()
107 | this.server = null
108 | }
109 | }
110 |
111 | startServer(port = this.listeningPort) {
112 | let loggedWarn = false
113 |
114 | return getReactInspector()
115 | .setStatusListener((status) => {
116 | if (!loggedWarn && status === 'Failed to start the server.') {
117 | const message = port !== 8097
118 | ? 're-open the debugger window might be helpful.'
119 | : 'we recommended to upgrade React Native version to 0.39+ for random port support.'
120 | console.error(
121 | '[RNDebugger]',
122 | `Failed to start React DevTools server with port \`${port}\`,`,
123 | 'because another server is listening,',
124 | message,
125 | )
126 | loggedWarn = true
127 | }
128 | })
129 | .setContentDOMNode(document.getElementById(containerId))
130 | .startServer(port)
131 | }
132 |
133 | render() {
134 | return (
135 |
136 |
137 |
Waiting for React to connect…
138 |
139 |
140 | )
141 | }
142 | }
143 |
144 | ReactInspector.propTypes = {
145 | debuggerState: PropTypes.shape({
146 | worker: PropTypes.shape({
147 | addEventListener: PropTypes.func.isRequired,
148 | }),
149 | }).isRequired,
150 | }
151 |
152 | export default connect(
153 | (state) => ({
154 | settingState: state.setting,
155 | debuggerState: state.debugger,
156 | }),
157 | (dispatch) => ({ dispatch }),
158 | )(ReactInspector)
159 |
--------------------------------------------------------------------------------
/app/containers/redux/DevTools.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector, useDispatch } from 'react-redux'
3 | import styled from 'styled-components'
4 | import { Container, Notification } from '@redux-devtools/ui'
5 | import { clearNotification } from '@redux-devtools/app/lib/esm/actions'
6 | import Actions from '@redux-devtools/app/lib/esm/containers/Actions'
7 | import Settings from './Settings'
8 | import Header from './Header'
9 |
10 | const StyledContainer = styled(Container)`overflow: hidden;`
11 |
12 | function App() {
13 | const section = useSelector((state) => state.section)
14 | const theme = useSelector((state) => state.theme)
15 | const notification = useSelector((state) => state.notification)
16 |
17 | const dispatch = useDispatch()
18 |
19 | let body
20 | switch (section) {
21 | case 'Settings':
22 | body =
23 | break
24 | default:
25 | body =
26 | }
27 |
28 | return (
29 |
30 |
31 | {body}
32 | {notification && (
33 | dispatch(clearNotification())}
36 | >
37 | {notification.message}
38 |
39 | )}
40 |
41 | )
42 | }
43 |
44 | export default App
45 |
--------------------------------------------------------------------------------
/app/containers/redux/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { useDispatch } from 'react-redux'
4 | import {
5 | Tabs, Toolbar, Button, Divider,
6 | } from '@redux-devtools/ui'
7 | import { GoBook } from 'react-icons/go'
8 | import styled from 'styled-components'
9 | import { changeSection } from '@redux-devtools/app/lib/esm/actions'
10 | import { shell } from 'electron'
11 |
12 | const WindowDraggable = styled.div`
13 | display: flex;
14 | flex: 1;
15 | height: 100%;
16 | -webkit-app-region: drag;
17 | -webkit-user-select: none;
18 | `
19 |
20 | const tabs = [{ name: 'Actions' }, { name: 'Settings' }]
21 |
22 | function Header(props) {
23 | const { section } = props
24 |
25 | const dispatch = useDispatch()
26 |
27 | const handleChangeSection = useCallback(
28 | (sec) => dispatch(changeSection(sec)),
29 | [dispatch, changeSection],
30 | )
31 |
32 | const openHelp = useCallback(() => shell.openExternal('https://goo.gl/SHU4yL'), [])
33 |
34 | return (
35 |
36 |
44 |
45 |
46 |
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | Header.propTypes = {
58 | section: PropTypes.string.isRequired,
59 | }
60 |
61 | export default Header
62 |
--------------------------------------------------------------------------------
/app/containers/redux/Settings.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-named-as-default */
2 | import React, { Component } from 'react'
3 | import Tabs from '@redux-devtools/ui/lib/esm/Tabs/Tabs'
4 | import Themes from '@redux-devtools/app/lib/esm/components/Settings/Themes'
5 |
6 | export default class Settings extends Component {
7 | tabs = [
8 | { name: 'Themes', component: Themes },
9 | ]
10 |
11 | constructor(props) {
12 | super(props)
13 | this.state = { selected: 'Themes' }
14 | }
15 |
16 | handleSelect = (selected) => {
17 | this.setState({ selected })
18 | }
19 |
20 | render() {
21 | const { selected } = this.state
22 | return (
23 |
28 | )
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/globalStyles.js:
--------------------------------------------------------------------------------
1 | import { css, createGlobalStyle } from 'styled-components'
2 |
3 | const commonStyles = css``
4 |
5 | export const GlobalStyle =
6 | process.platform !== 'darwin'
7 | ? createGlobalStyle`
8 | ${commonStyles}
9 | ::-webkit-scrollbar {
10 | width: 8px;
11 | height: 8px;
12 | background-color: #95959588;
13 | }
14 | ::-webkit-scrollbar-thumb {
15 | background-color: #33333366;
16 | }
17 | `
18 | : createGlobalStyle`${commonStyles}`
19 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import { findAPortNotInUse } from 'portscanner'
2 | import { webFrame } from 'electron'
3 | import { getCurrentWindow } from '@electron/remote'
4 | import React from 'react'
5 | import { createRoot } from 'react-dom/client'
6 | import { Provider } from 'react-redux'
7 | import launchEditor from 'react-dev-utils/launchEditor'
8 | import { PersistGate } from 'redux-persist/integration/react'
9 | import './setup'
10 | import App from './containers/App'
11 | import configureStore from './store/configureStore'
12 | import { beforeWindowClose } from './actions/debugger'
13 | import { invokeDevMethod } from './utils/devMenu'
14 | import { client, tryADBReverse } from './utils/adb'
15 | import config from './utils/config'
16 | import { toggleOpenInEditor, isOpenInEditorEnabled } from './utils/devtools'
17 | import { GlobalStyle } from './globalStyles'
18 |
19 | const currentWindow = getCurrentWindow()
20 |
21 | webFrame.setZoomFactor(1)
22 | webFrame.setVisualZoomLevelLimits(1, 1)
23 |
24 | // Prevent dropped file
25 | document.addEventListener('drop', (e) => {
26 | e.preventDefault()
27 | e.stopPropagation()
28 | })
29 | document.addEventListener('dragover', (e) => {
30 | e.preventDefault()
31 | e.stopPropagation()
32 | })
33 |
34 | let store
35 | let persistor
36 | const handleReady = () => {
37 | const { defaultReactDevToolsPort = 19567 } = config
38 | findAPortNotInUse(Number(defaultReactDevToolsPort)).then((port) => {
39 | window.reactDevToolsPort = port
40 | const root = createRoot(document.getElementById('root'))
41 | root.render(
42 | <>
43 |
44 |
45 |
46 |
47 |
48 |
49 | >,
50 | )
51 | })
52 | }
53 |
54 | ;({ store, persistor } = configureStore(handleReady))
55 |
56 | // Provide for user
57 | window.adb = client
58 | window.adb.reverseAll = tryADBReverse
59 | window.adb.reversePackager = () =>
60 | tryADBReverse(store.getState().debugger.location.port)
61 |
62 | window.checkWindowInfo = () => {
63 | const debuggerState = store.getState().debugger
64 | return {
65 | isWorkerRunning: !!debuggerState.worker,
66 | location: debuggerState.location,
67 | isPortSettingRequired: debuggerState.isPortSettingRequired,
68 | }
69 | }
70 |
71 | window.beforeWindowClose = () =>
72 | new Promise((resolve) => {
73 | if (store.dispatch(beforeWindowClose())) {
74 | setTimeout(resolve, 200)
75 | } else {
76 | resolve()
77 | }
78 | })
79 |
80 | // For security, we should disable nodeIntegration when user use this open a website
81 | const originWindowOpen = window.open
82 | window.open = (url, frameName, features = '') => {
83 | const featureList = features.split(',')
84 | featureList.push('nodeIntegration=0')
85 | return originWindowOpen.call(window, url, frameName, featureList.join(','))
86 | }
87 |
88 | window.openInEditor = (file, lineNumber) => launchEditor(file, lineNumber)
89 | window.toggleOpenInEditor = () => {
90 | const { port } = store.getState().debugger.location
91 | return toggleOpenInEditor(currentWindow, port)
92 | }
93 | window.isOpenInEditorEnabled = () => isOpenInEditorEnabled(currentWindow)
94 |
95 | window.invokeDevMethod = (name) => invokeDevMethod(name)()
96 |
97 | // Package will missing /usr/local/bin,
98 | // we need fix it for ensure child process work
99 | // (like launchEditor of react-devtools)
100 | if (
101 | process.env.NODE_ENV === 'production' &&
102 | process.platform === 'darwin' &&
103 | process.env.PATH.indexOf('/usr/local/bin') === -1
104 | ) {
105 | process.env.PATH = `${process.env.PATH}:/usr/local/bin`
106 | }
107 |
--------------------------------------------------------------------------------
/app/middlewares/reduxAPI.js:
--------------------------------------------------------------------------------
1 | import { bindActionCreators } from 'redux'
2 | import { ipcRenderer } from 'electron'
3 | import { getGlobal } from '@electron/remote'
4 |
5 | import { UPDATE_STATE, LIFTED_ACTION } from '@redux-devtools/app/lib/esm/constants/actionTypes'
6 | import { DISCONNECTED } from '@redux-devtools/app/lib/esm/constants/socketActionTypes'
7 | import { nonReduxDispatch } from '@redux-devtools/app/lib/esm/utils/monitorActions'
8 | import { showNotification, liftedDispatch } from '@redux-devtools/app/lib/esm/actions'
9 | import { getActiveInstance } from '@redux-devtools/app/lib/esm/reducers/instances'
10 |
11 | import { SET_DEBUGGER_WORKER, SYNC_STATE } from '../actions/debugger'
12 | import { setReduxDevToolsMethods, updateSliderContent } from '../utils/devMenu'
13 |
14 | const unboundActions = {
15 | showNotification,
16 | updateState: (request) => ({
17 | type: UPDATE_STATE,
18 | request: request.data ? { ...request.data, id: request.id } : request,
19 | }),
20 | liftedDispatch,
21 | }
22 | let actions
23 | let worker
24 | let store
25 |
26 | const toWorker = ({
27 | message, action, state, toAll,
28 | }) => {
29 | if (!worker) return
30 |
31 | const { instances } = store.getState()
32 | const instanceId = getActiveInstance(instances)
33 | const id = instances.options[instanceId].connectionId
34 | worker.postMessage({
35 | method: 'emitReduxMessage',
36 | content: {
37 | type: message,
38 | action,
39 | state: nonReduxDispatch(store, message, instanceId, action, state, instances),
40 | instanceId,
41 | id,
42 | toAll,
43 | },
44 | })
45 | }
46 |
47 | const postImportMessage = (state) => {
48 | if (!worker) return
49 |
50 | const { instances } = store.getState()
51 | const instanceId = getActiveInstance(instances)
52 | const id = instances.options[instanceId].connectionId
53 | worker.postMessage({
54 | method: 'emitReduxMessage',
55 | content: {
56 | type: 'IMPORT',
57 | state,
58 | instanceId,
59 | id,
60 | },
61 | })
62 | }
63 |
64 | // Receive messages from worker
65 | const messaging = (message) => {
66 | const { data } = message
67 | if (!data || !data.__IS_REDUX_NATIVE_MESSAGE__) return
68 |
69 | const { content: request } = data
70 | if (request.type === 'ERROR') {
71 | actions.showNotification(request.payload)
72 | return
73 | }
74 | actions.updateState(request)
75 | }
76 |
77 | const initWorker = (wkr) => {
78 | wkr.addEventListener('message', messaging)
79 | worker = wkr
80 | }
81 |
82 | const removeWorker = () => {
83 | worker = null
84 | }
85 |
86 | const syncLiftedState = (liftedState) => {
87 | if (!getGlobal('isSyncState')()) return
88 |
89 | const { actionsById } = liftedState
90 | const payload = []
91 | liftedState.stagedActionIds.slice(1).forEach((id) => {
92 | payload.push(actionsById[id].action)
93 | })
94 | const serialized = JSON.stringify({ payload: JSON.stringify(payload) })
95 | ipcRenderer.send('sync-state', serialized)
96 | }
97 |
98 | export default (inStore) => {
99 | store = inStore
100 | actions = bindActionCreators(unboundActions, store.dispatch)
101 | return (next) => (action) => {
102 | if (action.type === SET_DEBUGGER_WORKER) {
103 | if (action.worker) {
104 | initWorker(action.worker)
105 | } else {
106 | removeWorker(action.worker)
107 | setReduxDevToolsMethods(false)
108 | next({ type: DISCONNECTED })
109 | }
110 | }
111 | if (action.type === LIFTED_ACTION) {
112 | toWorker(action)
113 | }
114 | if (
115 | action.type === UPDATE_STATE
116 | || action.type === LIFTED_ACTION
117 | || action.type === SYNC_STATE
118 | ) {
119 | next(action)
120 | const state = store.getState()
121 | const { instances } = state
122 | const id = getActiveInstance(instances)
123 | const liftedState = instances.states[id]
124 | if (liftedState && liftedState.computedStates.length > 1) {
125 | setReduxDevToolsMethods(true, actions.liftedDispatch)
126 | } else if (liftedState && liftedState.computedStates.length <= 1) {
127 | setReduxDevToolsMethods(false)
128 | }
129 | updateSliderContent(liftedState, action.action && action.action.dontUpdateTouchBarSlider)
130 | if (action.request && action.request.type === 'ACTION') {
131 | syncLiftedState(liftedState)
132 | }
133 | if (action.type === SYNC_STATE) {
134 | postImportMessage(action.payload)
135 | }
136 | return
137 | }
138 | return next(action)
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/app/reducers/debugger.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_DEBUGGER_STATUS,
3 | SET_DEBUGGER_WORKER,
4 | SET_DEBUGGER_LOCATION,
5 | } from '../actions/debugger'
6 | import config from '../utils/config'
7 |
8 | function getStatusMessage(status, port) {
9 | let message
10 | switch (status) {
11 | case 'new':
12 | message = 'New Window'
13 | break
14 | case 'waiting':
15 | message = 'Waiting for client connection'
16 | break
17 | case 'connected':
18 | message = 'Connected'
19 | break
20 | case 'disconnected':
21 | default:
22 | message = 'Attempting reconnection'
23 | }
24 | if (status !== 'new') {
25 | message += ` (port ${port})`
26 | }
27 | const title = `React Native Debugger - ${message}`
28 | if (title !== document.title) {
29 | document.title = title
30 | }
31 | return message
32 | }
33 |
34 | const initialState = {
35 | worker: null,
36 | status: 'disconnected',
37 | statusMessage: getStatusMessage(config.isPortSettingRequired ? 'new' : 'disconnected', 8081),
38 | location: {
39 | host: 'localhost',
40 | port: config.port || 8081,
41 | },
42 | isPortSettingRequired: config.isPortSettingRequired,
43 | }
44 |
45 | const actionsMap = {
46 | [SET_DEBUGGER_STATUS]: (state, action) => {
47 | const status = action.status || initialState.status
48 | const newState = {
49 | ...state,
50 | status,
51 | statusMessage: getStatusMessage(status, state.location.port),
52 | }
53 | return newState
54 | },
55 | [SET_DEBUGGER_WORKER]: (state, action) => {
56 | const status = action.status || initialState.status
57 | const newState = {
58 | ...state,
59 | worker: action.worker,
60 | status,
61 | statusMessage: getStatusMessage(status, state.location.port),
62 | }
63 | return newState
64 | },
65 | [SET_DEBUGGER_LOCATION]: (state, action) => {
66 | const location = { ...state.location, ...action.loc }
67 | const newState = {
68 | ...state,
69 | location,
70 | statusMessage: getStatusMessage(state.status, location.port),
71 | isPortSettingRequired: false,
72 | }
73 | return newState
74 | },
75 | }
76 |
77 | export default (state = initialState, action = {}) => {
78 | const reduceFn = actionsMap[action.type]
79 | if (!reduceFn) return state
80 | return reduceFn(state, action)
81 | }
82 |
--------------------------------------------------------------------------------
/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import { section } from '@redux-devtools/app/lib/esm/reducers/section'
3 | // import { connection } from '@redux-devtools/app/lib/esm/reducers/connection';
4 | // import { socket } from '@redux-devtools/app/lib/esm/reducers/socket';
5 | import { monitor } from '@redux-devtools/app/lib/esm/reducers/monitor'
6 | import { notification } from '@redux-devtools/app/lib/esm/reducers/notification'
7 | import { instances } from '@redux-devtools/app/lib/esm/reducers/instances'
8 | import { reports } from '@redux-devtools/app/lib/esm/reducers/reports'
9 | import { theme } from '@redux-devtools/app/lib/esm/reducers/theme'
10 |
11 | import setting from './setting'
12 | import debuggerReducer from './debugger'
13 |
14 | export default combineReducers({
15 | section,
16 | instances,
17 | reports,
18 | theme,
19 | monitor,
20 | notification,
21 |
22 | setting,
23 | debugger: debuggerReducer,
24 | })
25 |
--------------------------------------------------------------------------------
/app/reducers/setting.js:
--------------------------------------------------------------------------------
1 | import { TOGGLE_DEVTOOLS, RESIZE_DEVTOOLS, CHANGE_DEFAULT_THEME } from '../actions/setting'
2 |
3 | const initialState = {
4 | react: true,
5 | redux: true,
6 | size: 0.6,
7 | themeName: null,
8 | }
9 |
10 | const actionsMap = {
11 | [TOGGLE_DEVTOOLS]: (state, action) => ({
12 | ...state,
13 | [action.name]: !state[action.name],
14 | }),
15 | [RESIZE_DEVTOOLS]: (state, action) => {
16 | if (!state.redux || !state.react) {
17 | return state
18 | }
19 | const { size } = action
20 | if (size < 0.2) return { ...state, size: 0.2 }
21 | if (size > 0.8) return { ...state, size: 0.8 }
22 | return { ...state, size }
23 | },
24 | [CHANGE_DEFAULT_THEME]: (state, action) => ({
25 | ...state,
26 | themeName: action.themeName,
27 | }),
28 | }
29 |
30 | export default (state = initialState, action = {}) => {
31 | const reduceFn = actionsMap[action.type]
32 | if (!reduceFn) return state
33 | return reduceFn(state, action)
34 | }
35 |
--------------------------------------------------------------------------------
/app/setup.js:
--------------------------------------------------------------------------------
1 | import config from './utils/config'
2 |
3 | if (config.editor) {
4 | process.env.EDITOR = config.editor
5 | }
6 |
7 | if (config.fontFamily) {
8 | const styleEl = document.createElement('style')
9 | document.head.appendChild(styleEl)
10 | styleEl.sheet.insertRule(
11 | `div *, span * { font-family: ${config.fontFamily} !important; }`,
12 | 0,
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/app/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux'
2 | import { persistReducer, persistStore } from 'redux-persist'
3 | import localForage from 'localforage'
4 | import { exportStateMiddleware } from '@redux-devtools/app/lib/cjs/middlewares/exportState'
5 | import { instancesInitialState } from '@redux-devtools/app/lib/esm/reducers/instances'
6 | import debuggerAPI from '../middlewares/debuggerAPI'
7 | import reduxAPI from '../middlewares/reduxAPI'
8 | import rootReducer from '../reducers'
9 |
10 | const persistConfig = {
11 | key: 'redux-devtools',
12 | blacklist: ['instances', 'debugger'],
13 | storage: localForage,
14 | }
15 |
16 | const persistedReducer = persistReducer(persistConfig, rootReducer)
17 |
18 | const middlewares = applyMiddleware(
19 | debuggerAPI,
20 | exportStateMiddleware,
21 | reduxAPI,
22 | )
23 |
24 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose
25 | /* eslint-disable no-underscore-dangle */
26 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
27 | /* eslint-enable no-underscore-dangle */
28 | const enhancer = composeEnhancers(middlewares)
29 |
30 | const initialState = {
31 | instances: {
32 | ...instancesInitialState,
33 | selected: '',
34 | },
35 | }
36 |
37 | export default (callback) => {
38 | const store = createStore(persistedReducer, initialState, enhancer)
39 | const persistor = persistStore(store, null, () => callback?.(store))
40 | return { store, persistor }
41 | }
42 |
--------------------------------------------------------------------------------
/app/utils/adb.js:
--------------------------------------------------------------------------------
1 | import adb from 'adbkit'
2 |
3 | export const client = adb.createClient({ host: '127.0.0.1' })
4 |
5 | const reverse = (device, port) => client.reverse(device, `tcp:${port}`, `tcp:${port}`)
6 |
7 | export const tryADBReverse = async (port) => {
8 | const devices = await client.listDevices().filter((device) => device.type === 'device')
9 | return Promise.all(devices.map((device) => reverse(device.id, port)))
10 | }
11 |
--------------------------------------------------------------------------------
/app/utils/config.js:
--------------------------------------------------------------------------------
1 | import { getCurrentWindow } from '@electron/remote'
2 |
3 | export default getCurrentWindow().debuggerConfig || {}
4 |
--------------------------------------------------------------------------------
/app/utils/devMenu.js:
--------------------------------------------------------------------------------
1 | import { TouchBar, nativeImage, getCurrentWindow } from '@electron/remote'
2 |
3 | import { ipcRenderer } from 'electron'
4 | import config from './config'
5 |
6 | const { TouchBarButton, TouchBarSlider } = TouchBar || {}
7 | const currentWindow = getCurrentWindow()
8 |
9 | let worker
10 | let availableMethods = []
11 |
12 | /* reload, toggleElementInspector, networkInspect */
13 | let leftBar = {}
14 |
15 | let isSliderEnabled
16 | let storeLiftedState
17 | /* slider, prev, next */
18 | let rightBar = {}
19 |
20 | const getBarItems = (bar) => Object.keys(bar)
21 | .map((key) => bar[key])
22 | .filter((barItem) => !!barItem)
23 | const setTouchBar = () => currentWindow.setTouchBar(
24 | new TouchBar({
25 | items: [
26 | ...getBarItems(leftBar),
27 | ...(isSliderEnabled ? getBarItems(rightBar) : []),
28 | ],
29 | }),
30 | )
31 |
32 | const invokeDevMenuMethod = ({ name, args }) => worker && worker.postMessage({ method: 'invokeDevMenuMethod', name, args })
33 |
34 | let networkInspectEnabled = !!config.networkInspect
35 | const sendContextMenuUpdate = () => {
36 | ipcRenderer.send(`context-menu-available-methods-update-${currentWindow.id}`, {
37 | availableMethods,
38 | networkInspectEnabled,
39 | })
40 | }
41 |
42 | export const networkInspect = {
43 | isEnabled: () => !!networkInspectEnabled,
44 | getHighlightColor: () => (networkInspectEnabled ? '#7A7A7A' : '#363636'),
45 | toggle() {
46 | networkInspectEnabled = !networkInspectEnabled
47 | sendContextMenuUpdate()
48 | },
49 | }
50 |
51 | const devMenuMethods = {
52 | reload: () => invokeDevMenuMethod({ name: 'reload' }),
53 | toggleElementInspector: () => invokeDevMenuMethod({ name: 'toggleElementInspector' }),
54 | show: () => invokeDevMenuMethod({ name: 'show' }),
55 | networkInspect: () => {
56 | networkInspect.toggle()
57 | if (leftBar.networkInspect) {
58 | leftBar.networkInspect.backgroundColor = networkInspect.getHighlightColor()
59 | }
60 | invokeDevMenuMethod({
61 | name: 'networkInspect',
62 | args: [networkInspectEnabled],
63 | })
64 | },
65 | showAsyncStorage: () => {
66 | invokeDevMenuMethod({ name: 'showAsyncStorage' })
67 | },
68 | clearAsyncStorage: () => {
69 | if (
70 | window.confirm(
71 | 'Call `AsyncStorage.clear()` in current React Native debug session?',
72 | )
73 | ) {
74 | invokeDevMenuMethod({ name: 'clearAsyncStorage' })
75 | }
76 | },
77 | }
78 |
79 | export const invokeDevMethod = (name) => () => {
80 | if (availableMethods.includes(name)) {
81 | return devMenuMethods[name]()
82 | }
83 | }
84 |
85 | const hslShift = [0.5, 0.2, 0.8]
86 | const icon = (name, resizeOpts) => {
87 | const image = nativeImage.createFromNamedImage(name, hslShift)
88 | return image.resize(resizeOpts)
89 | }
90 |
91 | let namedImages
92 | const initNamedImages = () => {
93 | if (process.platform !== 'darwin' || namedImages) return
94 | namedImages = {
95 | reload: icon('NSTouchBarRefreshTemplate', { height: 20 }),
96 | toggleElementInspector: icon('NSTouchBarQuickLookTemplate', { height: 18 }),
97 | networkInspect: icon('NSTouchBarRecordStartTemplate', { height: 20 }),
98 | prev: icon('NSTouchBarGoBackTemplate', { height: 20 }),
99 | next: icon('NSTouchBarGoForwardTemplate', { height: 20 }),
100 | }
101 | }
102 |
103 | const setDevMenuMethodsForTouchBar = () => {
104 | if (process.platform !== 'darwin') return
105 | initNamedImages()
106 |
107 | leftBar = {
108 | // Default items
109 | networkInspect: new TouchBarButton({
110 | icon: namedImages.networkInspect,
111 | click: devMenuMethods.networkInspect,
112 | backgroundColor: networkInspect.getHighlightColor(),
113 | }),
114 | }
115 | if (availableMethods.includes('reload')) {
116 | leftBar.reload = new TouchBarButton({
117 | icon: namedImages.reload,
118 | click: devMenuMethods.reload,
119 | })
120 | }
121 | if (availableMethods.includes('toggleElementInspector')) {
122 | leftBar.toggleElementInspector = new TouchBarButton({
123 | icon: namedImages.toggleElementInspector,
124 | click: devMenuMethods.toggleElementInspector,
125 | })
126 | }
127 | setTouchBar()
128 | }
129 |
130 | // Reset TouchBar when reload the app
131 | setDevMenuMethodsForTouchBar([])
132 |
133 | export const setDevMenuMethods = (list, wkr) => {
134 | worker = wkr
135 | availableMethods = list
136 | sendContextMenuUpdate()
137 | setDevMenuMethodsForTouchBar()
138 | }
139 |
140 | export const setReduxDevToolsMethods = (enabled, dispatch) => {
141 | if (process.platform !== 'darwin') return
142 | initNamedImages()
143 |
144 | // Already setup
145 | if (enabled && isSliderEnabled) return
146 |
147 | const handleSliderChange = (nextIndex, dontUpdateTouchBarSlider = false) => dispatch({
148 | type: 'JUMP_TO_STATE',
149 | actionId: storeLiftedState.stagedActionIds[nextIndex],
150 | index: nextIndex,
151 | dontUpdateTouchBarSlider,
152 | })
153 |
154 | rightBar = {
155 | slider: new TouchBarSlider({
156 | value: 0,
157 | minValue: 0,
158 | maxValue: 0,
159 | change(nextIndex) {
160 | if (nextIndex !== storeLiftedState.currentStateIndex) {
161 | // Set `dontUpdateTouchBarSlider` true for keep slide experience
162 | handleSliderChange(nextIndex, true)
163 | }
164 | },
165 | }),
166 | prev: new TouchBarButton({
167 | icon: namedImages.prev,
168 | click() {
169 | const nextIndex = storeLiftedState.currentStateIndex - 1
170 | if (nextIndex >= 0) {
171 | handleSliderChange(nextIndex)
172 | }
173 | },
174 | }),
175 | next: new TouchBarButton({
176 | icon: namedImages.next,
177 | click() {
178 | const nextIndex = storeLiftedState.currentStateIndex + 1
179 | if (nextIndex < storeLiftedState.computedStates.length) {
180 | handleSliderChange(nextIndex)
181 | }
182 | },
183 | }),
184 | }
185 | isSliderEnabled = enabled
186 | setTouchBar()
187 | }
188 |
189 | export const updateSliderContent = (liftedState, dontUpdateTouchBarSlider) => {
190 | if (process.platform !== 'darwin') return
191 |
192 | storeLiftedState = liftedState
193 | if (isSliderEnabled && !dontUpdateTouchBarSlider) {
194 | const { currentStateIndex, computedStates } = liftedState
195 | rightBar.slider.maxValue = computedStates.length - 1
196 | rightBar.slider.value = currentStateIndex
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/app/utils/devtools.js:
--------------------------------------------------------------------------------
1 | import { getCatchConsoleLogScript } from '../../electron/devtools'
2 |
3 | let enabled = false
4 | export const toggleOpenInEditor = (win, port) => {
5 | if (win.devToolsWebContents) {
6 | enabled = !enabled
7 | return win.devToolsWebContents.executeJavaScript(`(() => {
8 | ${getCatchConsoleLogScript(port)}
9 | window.__IS_OPEN_IN_EDITOR_ENABLED__ = ${enabled};
10 | })()`)
11 | }
12 | }
13 |
14 | export const isOpenInEditorEnabled = () => enabled
15 |
16 | export const clearNetworkLogs = (win) => {
17 | if (win.devToolsWebContents) {
18 | return win.devToolsWebContents.executeJavaScript(`setTimeout(() => {
19 | const { network } = UI.panels;
20 | if (network && network.networkLogView && network.networkLogView.reset) {
21 | network.networkLogView.reset()
22 | }
23 | }, 100)`)
24 | }
25 | }
26 |
27 | export const selectRNDebuggerWorkerContext = (win) => {
28 | if (win.devToolsWebContents) {
29 | return win.devToolsWebContents.executeJavaScript(`setTimeout(() => {
30 | const { console } = UI.panels;
31 | if (console && console.view && console.view.consoleContextSelector) {
32 | const selector = console.view.consoleContextSelector;
33 | const item = selector.items.items.find(
34 | item => item.label() === 'RNDebuggerWorker.js'
35 | );
36 | if (item) {
37 | selector.itemSelected(item);
38 | }
39 | }
40 | }, 100)`)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/worker/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-restricted-globals": "off"
4 | }
5 | }
--------------------------------------------------------------------------------
/app/worker/apollo.js:
--------------------------------------------------------------------------------
1 |
2 | export function handleApolloClient() {
3 | // eslint-disable-next-line global-require
4 | require('apollo-client-devtools/build/hook')
5 | }
6 |
--------------------------------------------------------------------------------
/app/worker/asyncStorage.js:
--------------------------------------------------------------------------------
1 | export const getClearAsyncStorageFn = (AsyncStorage) => {
2 | if (!AsyncStorage.clear) return
3 | return () => AsyncStorage.clear().catch((f) => f)
4 | }
5 |
6 | function convertError(error) {
7 | if (!error) {
8 | return null
9 | }
10 | const out = new Error(error.message)
11 | out.key = error.key
12 | return out
13 | }
14 |
15 | function convertErrors(errs) {
16 | if (!errs) {
17 | return null
18 | }
19 | return (Array.isArray(errs) ? errs : [errs]).map((e) => convertError(e))
20 | }
21 |
22 | export const getSafeAsyncStorage = (NativeModules) => {
23 | const RCTAsyncStorage = NativeModules
24 | && (NativeModules.RNC_AsyncSQLiteDBStorage
25 | || NativeModules.RNCAsyncStorage
26 | || NativeModules.PlatformLocalStorage
27 | || NativeModules.AsyncRocksDBStorage
28 | || NativeModules.AsyncSQLiteDBStorage
29 | || NativeModules.AsyncLocalStorage)
30 |
31 | return {
32 | getItem(key) {
33 | if (!RCTAsyncStorage) return Promise.resolve(null)
34 | return new Promise((resolve, reject) => {
35 | RCTAsyncStorage.multiGet([key], (errors, result) => {
36 | // Unpack result to get value from [[key,value]]
37 | const value = result && result[0] && result[0][1] ? result[0][1] : null
38 | const errs = convertErrors(errors)
39 | if (errs) {
40 | reject(errs[0])
41 | } else {
42 | resolve(value)
43 | }
44 | })
45 | })
46 | },
47 | async setItem(key, value) {
48 | if (!RCTAsyncStorage) return Promise.resolve(null)
49 | return new Promise((resolve, reject) => {
50 | RCTAsyncStorage.multiSet([[key, value]], (errors) => {
51 | const errs = convertErrors(errors)
52 | if (errs) {
53 | reject(errs[0])
54 | } else {
55 | resolve(null)
56 | }
57 | })
58 | })
59 | },
60 | clear() {
61 | if (!RCTAsyncStorage) return Promise.resolve(null)
62 | return new Promise((resolve, reject) => {
63 | RCTAsyncStorage.clear((error) => {
64 | if (error && convertError(error)) {
65 | reject(convertError(error))
66 | } else {
67 | resolve(null)
68 | }
69 | })
70 | })
71 | },
72 | getAllKeys() {
73 | if (!RCTAsyncStorage) return Promise.resolve(null)
74 | return new Promise((resolve, reject) => {
75 | RCTAsyncStorage.getAllKeys((error, keys) => {
76 | if (error) {
77 | reject(convertError(error))
78 | } else {
79 | resolve(keys)
80 | }
81 | })
82 | })
83 | },
84 | }
85 | }
86 |
87 | export const getShowAsyncStorageFn = (AsyncStorage) => {
88 | if (!AsyncStorage.getAllKeys || !AsyncStorage.getItem) return
89 | return async () => {
90 | const keys = await AsyncStorage.getAllKeys()
91 | if (keys && keys.length) {
92 | const items = await Promise.all(
93 | keys.map((key) => AsyncStorage.getItem(key)),
94 | )
95 | const table = {}
96 | keys.forEach((key, index) => {
97 | table[key] = { content: items[index] }
98 | })
99 | console.table(table)
100 | } else {
101 | console.log('[RNDebugger] No AsyncStorage content.')
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/app/worker/devMenu.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-underscore-dangle */
2 |
3 | import { toggleNetworkInspect } from './networkInspect'
4 | import { getClearAsyncStorageFn, getShowAsyncStorageFn, getSafeAsyncStorage } from './asyncStorage'
5 |
6 | let availableDevMenuMethods = {}
7 |
8 | export const checkAvailableDevMenuMethods = ({ NativeModules }) => {
9 | // RN 0.43 use DevSettings, DevMenu will be deprecated
10 | const DevSettings = NativeModules.DevSettings || NativeModules.DevMenu
11 | // Currently `show dev menu` is only on DevMenu
12 | const showDevMenu = (DevSettings && DevSettings.show)
13 | || (NativeModules.DevMenu && NativeModules.DevMenu.show)
14 | || undefined
15 |
16 | const AsyncStorage = getSafeAsyncStorage(NativeModules)
17 | const methods = {
18 | ...DevSettings,
19 | show: showDevMenu,
20 | networkInspect: toggleNetworkInspect,
21 | showAsyncStorage: getShowAsyncStorageFn(AsyncStorage),
22 | clearAsyncStorage: getClearAsyncStorageFn(AsyncStorage),
23 | }
24 | if (methods.showAsyncStorage) {
25 | window.showAsyncStorageContentInDev = methods.showAsyncStorage
26 | }
27 | const result = Object.keys(methods).filter((key) => !!methods[key])
28 | availableDevMenuMethods = methods
29 |
30 | postMessage({ __AVAILABLE_METHODS_CAN_CALL_BY_RNDEBUGGER__: result })
31 | }
32 |
33 | export const invokeDevMenuMethodIfAvailable = (name, args = []) => {
34 | const method = availableDevMenuMethods[name]
35 | if (method) method(...args)
36 | }
37 |
--------------------------------------------------------------------------------
/app/worker/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 | /* global __fbBatchedBridge, importScripts: true */
10 |
11 | // Edit from https://github.com/facebook/react-native/blob/master/local-cli/server/util/debuggerWorker.js
12 |
13 | import './setup'
14 | import { checkAvailableDevMenuMethods, invokeDevMenuMethodIfAvailable } from './devMenu'
15 | import { reportDefaultReactDevToolsPort } from './reactDevTools'
16 | import devToolsEnhancer, { composeWithDevTools } from './reduxAPI'
17 | import * as RemoteDev from './remotedev'
18 | import { getRequiredModules } from './utils'
19 | import { toggleNetworkInspect } from './networkInspect'
20 | import { handleApolloClient } from './apollo'
21 |
22 | /* eslint-disable no-underscore-dangle */
23 | self.__REMOTEDEV__ = RemoteDev
24 |
25 | devToolsEnhancer.send = RemoteDev.send
26 | devToolsEnhancer.connect = RemoteDev.connect
27 | devToolsEnhancer.disconnect = RemoteDev.disconnect
28 |
29 | // Deprecated API, these may removed when redux-devtools-extension 3.0 release
30 | self.devToolsExtension = devToolsEnhancer
31 | self.reduxNativeDevTools = devToolsEnhancer
32 | self.reduxNativeDevToolsCompose = composeWithDevTools
33 |
34 | self.__REDUX_DEVTOOLS_EXTENSION__ = devToolsEnhancer
35 | self.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = composeWithDevTools
36 |
37 | const setupRNDebuggerBeforeImportScript = (message) => {
38 | self.__REACT_DEVTOOLS_PORT__ = message.reactDevToolsPort
39 | if (message.networkInspect) {
40 | self.__NETWORK_INSPECT__ = toggleNetworkInspect
41 | }
42 | }
43 |
44 | const noop = (f) => f
45 | const setupRNDebugger = async (message) => {
46 | // We need to regularly update JS runtime
47 | // because the changes of worker message (Redux DevTools, DevMenu)
48 | // doesn't notify to the remote JS runtime
49 | self.__RND_INTERVAL__ = setInterval(noop, 100); // eslint-disable-line
50 |
51 | handleApolloClient()
52 | toggleNetworkInspect(message.networkInspect)
53 | const modules = await getRequiredModules(message.moduleSize)
54 | if (modules) {
55 | checkAvailableDevMenuMethods(modules)
56 | reportDefaultReactDevToolsPort(modules)
57 | }
58 | }
59 |
60 | const messageHandlers = {
61 | executeApplicationScript(message, sendReply) {
62 | setupRNDebuggerBeforeImportScript(message)
63 |
64 | Object.keys(message.inject).forEach((key) => {
65 | self[key] = JSON.parse(message.inject[key])
66 | })
67 | let error
68 | try {
69 | importScripts(message.url)
70 | } catch (err) {
71 | error = err.message
72 | }
73 |
74 | if (!error) {
75 | setupRNDebugger(message)
76 | }
77 |
78 | sendReply(null /* result */, error)
79 |
80 | return false
81 | },
82 | emitReduxMessage() {
83 | // pass to other listeners
84 | return true
85 | },
86 | emitApolloMessage() {
87 | // pass to other listeners
88 | return true
89 | },
90 | invokeDevMenuMethod({ name, args }) {
91 | invokeDevMenuMethodIfAvailable(name, args)
92 | return false
93 | },
94 | beforeTerminate() {
95 | // Clean for notify native bridge
96 | if (window.__RND_INTERVAL__) {
97 | clearInterval(window.__RND_INTERVAL__)
98 | window.__RND_INTERVAL__ = null
99 | }
100 | return false
101 | },
102 | }
103 |
104 | addEventListener('message', (message) => {
105 | const object = message.data
106 |
107 | const sendReply = (result, error) => {
108 | postMessage({ replyID: object.id, result, error })
109 | }
110 |
111 | const handler = messageHandlers[object.method]
112 | if (handler) {
113 | // Special cased handlers
114 | return handler(object, sendReply)
115 | }
116 | // Other methods get called on the bridge
117 | let returnValue = [[], [], [], 0]
118 | let error
119 | try {
120 | if (typeof __fbBatchedBridge === 'object') {
121 | returnValue = __fbBatchedBridge[object.method].apply(null, object.arguments)
122 | } else {
123 | error = 'Failed to call function, __fbBatchedBridge is undefined'
124 | }
125 | } catch (err) {
126 | error = err.message
127 | } finally {
128 | sendReply(JSON.stringify(returnValue), error)
129 | }
130 | return false
131 | })
132 |
--------------------------------------------------------------------------------
/app/worker/networkInspect.js:
--------------------------------------------------------------------------------
1 | import getRNDebuggerFetchPolyfills from './polyfills/fetch'
2 |
3 | const isWorkerMethod = (fn) => String(fn).indexOf('[native code]') > -1
4 |
5 | /* eslint-disable no-underscore-dangle */
6 | let networkInspect
7 |
8 | export const toggleNetworkInspect = (enabled) => {
9 | if (!enabled && networkInspect) {
10 | self.fetch = networkInspect.fetch
11 | self.XMLHttpRequest = networkInspect.XMLHttpRequest
12 | self.FormData = networkInspect.FormData
13 | self.Headers = networkInspect.Headers
14 | self.Request = networkInspect.Request
15 | self.Response = networkInspect.Response
16 | networkInspect = null
17 | return
18 | }
19 | if (!enabled) return
20 | if (enabled && networkInspect) return
21 | if (isWorkerMethod(self.XMLHttpRequest) || isWorkerMethod(self.FormData)) {
22 | console.warn(
23 | '[RNDebugger] '
24 | + 'I tried to enable Network Inspect but XHR '
25 | + "have been replaced by worker's XHR. "
26 | + 'You can disable Network Inspect (documentation: https://goo.gl/BVvEkJ) '
27 | + 'or tracking your app code if you have called '
28 | + '`global.XMLHttpRequest = global.originalXMLHttpRequest`.',
29 | )
30 | return
31 | }
32 | networkInspect = {
33 | fetch: self.fetch,
34 | XMLHttpRequest: self.XMLHttpRequest,
35 | FormData: self.FormData,
36 | Headers: self.Headers,
37 | Request: self.Request,
38 | Response: self.Response,
39 | }
40 |
41 | self.XMLHttpRequest = self.originalXMLHttpRequest
42 | ? self.originalXMLHttpRequest
43 | : self.XMLHttpRequest
44 | self.FormData = self.originalFormData ? self.originalFormData : self.FormData
45 | const {
46 | fetch, Headers, Request, Response,
47 | } = getRNDebuggerFetchPolyfills()
48 | self.fetch = fetch
49 | self.Headers = Headers
50 | self.Request = Request
51 | self.Response = Response
52 |
53 | console.log(
54 | '[RNDebugger]',
55 | 'Network Inspect is enabled,',
56 | 'see the documentation (https://goo.gl/yEcRrU) for more information.',
57 | )
58 | }
59 |
60 | /*
61 | * `originalXMLHttpRequest` haven't permission to set forbidden header name
62 | * (https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name)
63 | * We have to use Electron session to solve this problem (See electron/main.js)
64 | */
65 | const forbiddenHeaderNames = [
66 | 'Accept-Charset',
67 | 'Accept-Encoding',
68 | 'Access-Control-Request-Headers',
69 | 'Access-Control-Request-Method',
70 | 'Connection',
71 | 'Content-Length',
72 | 'Cookie',
73 | 'Cookie2',
74 | 'Date',
75 | 'DNT',
76 | 'Expect',
77 | 'Host',
78 | 'Keep-Alive',
79 | 'Origin',
80 | 'Referer',
81 | 'TE',
82 | 'Trailer',
83 | 'Transfer-Encoding',
84 | 'Upgrade',
85 | 'Via',
86 | // Actually it still blocked on Chrome
87 | 'User-Agent',
88 | ]
89 | forbiddenHeaderNames.forEach((name) => forbiddenHeaderNames.push(name.toLowerCase()))
90 |
91 | const isForbiddenHeaderName = (header) => forbiddenHeaderNames.includes(header)
92 | || header.startsWith('Proxy-')
93 | || header.startsWith('proxy-')
94 | || header.startsWith('Sec-')
95 | || header.startsWith('sec-')
96 |
97 | export const replaceForbiddenHeadersForWorkerXHR = () => {
98 | if (!isWorkerMethod(self.XMLHttpRequest)) return
99 | const originalSetRequestHeader = self.XMLHttpRequest.prototype.setRequestHeader
100 | self.XMLHttpRequest.prototype.setRequestHeader = function setRequestHeader(header, value) {
101 | let replacedHeader = header
102 | if (isForbiddenHeaderName(header)) {
103 | replacedHeader = `__RN_DEBUGGER_SET_HEADER_REQUEST_${header}`
104 | }
105 | return originalSetRequestHeader.call(this, replacedHeader, value)
106 | }
107 | }
108 |
109 | export const addURIWarningForWorkerFormData = () => {
110 | if (!isWorkerMethod(self.FormData)) return
111 | const originAppend = FormData.prototype.append
112 | self.FormData.prototype.append = function append(key, value) {
113 | if (value && value.uri) {
114 | console.warn(
115 | '[RNDebugger] '
116 | + "Detected you're enabled Network Inspect and using `uri` in FormData, "
117 | + 'it will be a problem if you use it for upload, '
118 | + 'please see the documentation (https://goo.gl/yEcRrU) for more information.',
119 | )
120 | }
121 | return originAppend.call(this, key, value)
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/worker/reactDevTools.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-underscore-dangle */
2 |
3 | const methodGlobalName = '__REPORT_REACT_DEVTOOLS_PORT__'
4 |
5 | const reportReactDevToolsPort = (port, platform) => postMessage({
6 | [methodGlobalName]: port,
7 | platform,
8 | })
9 |
10 | export const reportDefaultReactDevToolsPort = async ({ setupDevtools, Platform }) => {
11 | if (Platform.__empty) return
12 | /*
13 | * [Fallback] React Native version under 0.39 can't specified the port
14 | */
15 | if (
16 | typeof setupDevtools === 'function'
17 | && setupDevtools.toString().indexOf('window.__REACT_DEVTOOLS_PORT__') === -1
18 | ) {
19 | reportReactDevToolsPort(8097, Platform.OS)
20 | } else {
21 | // React Inspector will keep the last reported port even if reload JS,
22 | // because we don't want to icrease the user waiting time for reload JS.
23 | // We need back to use the random port if we don't need fallback
24 | reportReactDevToolsPort(window.__REACT_DEVTOOLS_PORT__, Platform.OS)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/worker/remotedev.js:
--------------------------------------------------------------------------------
1 | // Edit from https://github.com/zalmoxisus/remotedev/blob/master/src/devTools.js
2 |
3 | import { stringify, parse } from 'jsan'
4 | import { generateId, getActionsArray } from '@redux-devtools/utils'
5 |
6 | let listenerAdded
7 | const listeners = {}
8 |
9 | export function extractState(message) {
10 | if (!message || !message.state) return undefined
11 | if (typeof message.state === 'string') return parse(message.state)
12 | return message.state
13 | }
14 |
15 | function handleMessages(message) {
16 | if (!message.payload) {
17 | message.payload = message.action
18 | }
19 | const fn = listeners[message.instanceId]
20 | if (!fn) return true
21 |
22 | if (typeof fn === 'function') {
23 | fn(message)
24 | } else {
25 | fn.forEach((func) => func(message))
26 | }
27 | return false
28 | }
29 |
30 | export function start() {
31 | if (!listenerAdded) {
32 | self.addEventListener('message', (message) => {
33 | const { method, content } = message.data
34 | if (method === 'emitReduxMessage') {
35 | return handleMessages(content)
36 | }
37 | })
38 | listenerAdded = true
39 | }
40 | }
41 |
42 | function transformAction(action, config) {
43 | if (action.action) return action
44 | const liftedAction = { timestamp: Date.now() }
45 | if (action) {
46 | if (config.getActionType) {
47 | liftedAction.action = config.getActionType(action)
48 | } else if (typeof action === 'string') {
49 | liftedAction.action = { type: action }
50 | } else if (!action.type) {
51 | liftedAction.action = { type: 'update' }
52 | } else {
53 | liftedAction.action = action
54 | }
55 | } else {
56 | liftedAction.action = { type: action }
57 | }
58 | return liftedAction
59 | }
60 |
61 | export function send(action, state, type, options) {
62 | start()
63 | setTimeout(() => {
64 | const message = {
65 | payload: state ? stringify(state) : '',
66 | action: type === 'ACTION' ? stringify(transformAction(action, options)) : action,
67 | type: type || 'ACTION',
68 | id: options.instanceId,
69 | instanceId: options.instanceId,
70 | name: options.name,
71 | }
72 | message.libConfig = {
73 | type: options.type,
74 | name: options.name,
75 | serialize: !!options.serialize,
76 | actionCreators: options.actionCreators,
77 | }
78 | postMessage({ __IS_REDUX_NATIVE_MESSAGE__: true, content: message })
79 | }, 0)
80 | }
81 |
82 | export function connect(options = {}) {
83 | const id = generateId(options.instanceId)
84 | const opts = {
85 | ...options,
86 | instanceId: id,
87 | name: options.name || id,
88 | actionCreators: JSON.stringify(getActionsArray(options.actionCreators || {})),
89 | }
90 | start()
91 | return {
92 | init(state, action) {
93 | send(action || {}, state, 'INIT', opts)
94 | },
95 | subscribe(listener) {
96 | if (!listener) return undefined
97 | if (!listeners[id]) listeners[id] = []
98 | listeners[id].push(listener)
99 |
100 | return function unsubscribe() {
101 | const index = listeners[id].indexOf(listener)
102 | listeners[id].splice(index, 1)
103 | }
104 | },
105 | unsubscribe() {
106 | delete listeners[id]
107 | },
108 | send(action, payload) {
109 | if (action) {
110 | send(action, payload, 'ACTION', opts)
111 | } else {
112 | send(undefined, payload, 'STATE', opts)
113 | }
114 | },
115 | error(payload) {
116 | send(undefined, payload, 'Error', opts)
117 | },
118 | }
119 | }
120 |
121 | // Not implemented
122 | export function disconnect() {}
123 |
--------------------------------------------------------------------------------
/app/worker/setup.js:
--------------------------------------------------------------------------------
1 | import {
2 | replaceForbiddenHeadersForWorkerXHR,
3 | addURIWarningForWorkerFormData,
4 | } from './networkInspect'
5 |
6 | // Add the missing `global` for WebWorker
7 | self.global = self
8 |
9 | /*
10 | * Blob is not supported for RN < 0.54,
11 | * we should remove it in WebWorker because
12 | * it will used for `whatwg-fetch` on older RN versions
13 | */
14 | if (self.Blob && self.Blob.toString() === 'function Blob() { [native code] }') {
15 | /*
16 | * RN > 0.54 will polyfill Blob.
17 | * If it is deleted before the RN setup, RN will not add a reference to the original.
18 | * We will need to be able to restore the original when running RN > 0.54 for networking tools,
19 | * so add the reference here as react-native will not do it if the original is deleted
20 | */
21 | self.originalBlob = self.Blob
22 | delete self.Blob
23 | }
24 |
25 | if (
26 | self.XMLHttpRequest
27 | && self.XMLHttpRequest.toString() === 'function XMLHttpRequest() { [native code] }'
28 | ) {
29 | self.originalXMLHttpRequest = self.XMLHttpRequest
30 | }
31 |
32 | if (self.FormData && self.FormData.toString() === 'function FormData() { [native code] }') {
33 | self.originalFormData = self.FormData
34 | }
35 |
36 | // Catch native fetch
37 | if (self.fetch && self.fetch.toString() === 'function fetch() { [native code] }') {
38 | /* eslint-disable-next-line no-underscore-dangle */
39 | self.__ORIGINAL_FETCH__ = self.fetch
40 | }
41 |
42 | replaceForbiddenHeadersForWorkerXHR()
43 | addURIWarningForWorkerFormData()
44 |
--------------------------------------------------------------------------------
/app/worker/utils.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-underscore-dangle */
2 |
3 | // Avoid warning of use metro require on dev mode
4 | // it actually unnecessary for RN >= 0.56, so it is backward compatibility
5 | const avoidWarnForRequire = (moduleNames) => {
6 | if (!moduleNames.length) moduleNames.push('NativeModules')
7 | return new Promise((resolve) => {
8 | setTimeout(() => {
9 | // It's replaced console.warn of react-native
10 | const originalWarn = console.warn
11 | console.warn = (...args) => {
12 | if (
13 | args[0]
14 | && moduleNames.some(
15 | (name) => args[0].indexOf(`Requiring module '${name}' by name`) > -1,
16 | )
17 | ) {
18 | return
19 | }
20 | return originalWarn(...args)
21 | }
22 | resolve(() => {
23 | console.warn = originalWarn
24 | })
25 | })
26 | })
27 | }
28 |
29 | let reactNative
30 |
31 | const getRequireMethod = () => {
32 | // RN >= 0.57
33 | if (typeof window.__r === 'function') return window.__r
34 | // RN < 0.57
35 | if (typeof window.require === 'function') return window.require
36 | }
37 |
38 | const lookupForRNModules = (size = 999) => {
39 | const metroRequire = getRequireMethod()
40 | let actualSize = size
41 | let getModule = metroRequire
42 | if (metroRequire.getModules) {
43 | const mods = metroRequire.getModules()
44 | actualSize = Object.keys(mods).length
45 | getModule = (moduleId) => {
46 | const mod = mods && mods[moduleId]
47 | return (mod && mod.publicModule && mod.publicModule.exports) || null
48 | }
49 | } else {
50 | getModule = (moduleId) => metroRequire(moduleId)
51 | }
52 | for (let moduleId = 0; moduleId <= actualSize - 1; moduleId += 1) {
53 | const rn = getModule(moduleId)
54 | if (rn && rn.requireNativeComponent && rn.NativeModules) {
55 | return rn
56 | }
57 | }
58 | return null
59 | }
60 |
61 | const getModule = (name, size) => {
62 | let result
63 | try {
64 | const metroRequire = getRequireMethod()
65 | // RN >= 0.56
66 | if (metroRequire.name === 'metroRequire') {
67 | const rn = reactNative || lookupForRNModules(size)
68 | reactNative = rn
69 | global.$reactNative = rn
70 | result = reactNative && reactNative[name]
71 | } else if (metroRequire.name === '_require') {
72 | result = metroRequire(name)
73 | }
74 | } catch (e) {} // eslint-disable-line
75 | return result || { __empty: true }
76 | }
77 |
78 | const requiredModules = {
79 | MessageQueue: (size) => (self.__fbBatchedBridge
80 | && Object.getPrototypeOf(self.__fbBatchedBridge).constructor)
81 | || getModule('MessageQueue', size),
82 | NativeModules: (size) => getModule('NativeModules', size),
83 | Platform: (size) => getModule('Platform', size),
84 | setupDevtools: (size) => getModule('setupDevtools', size),
85 | }
86 |
87 | export const getRequiredModules = async (size) => {
88 | if (!window.__DEV__ || !getRequireMethod()) return
89 | const done = await avoidWarnForRequire(Object.keys(requiredModules))
90 | const modules = {}
91 | Object.keys(requiredModules).forEach((name) => {
92 | modules[name] = requiredModules[name](size)
93 | })
94 | done()
95 | return modules
96 | }
97 |
98 | const RN_DEBUGGER_URL_PART = 'RNDebuggerWorker.js'
99 | const BUNDLE_URL_REGEXP = /(http[\S]*?index\.bundle\?[\S]*?)(:\d+:?\d?)/
100 |
101 | const addInlineSourceMap = (_, urlGroup1, urlGroup2) => `${urlGroup1}&inlineSourceMap=true${urlGroup2}`
102 | const mapStackLines = (line) => line.replace(BUNDLE_URL_REGEXP, addInlineSourceMap)
103 | const filterRnDebuggerLines = (line) => !line.includes(RN_DEBUGGER_URL_PART)
104 |
105 | export function updateStackWithSourceMap(stack) {
106 | const lines = stack.split('\n')
107 | const linesWithoutRNDebugger = lines.filter(filterRnDebuggerLines)
108 | const lineWithSourceMap = linesWithoutRNDebugger.map(mapStackLines)
109 | return lineWithSourceMap.join('\n')
110 | }
111 |
--------------------------------------------------------------------------------
/auto_update.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://github.com/jhen0409/react-native-debugger/releases/download/v0.14.0/rn-debugger-macos-universal.zip",
3 | "name": "v0.14.0",
4 | "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"
5 | }
6 |
--------------------------------------------------------------------------------
/auto_updater.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://github.com/jhen0409/react-native-debugger/releases/download/v0.10.13/rn-debugger-macos-x64.zip",
3 | "name": "v0.10.13",
4 | "notes": "Update apollo-client-devtools to v2.3.5"
5 | }
6 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (api) => {
2 | api.cache(true)
3 | return {
4 | presets: [['@babel/preset-env', { targets: { node: '18.5' } }], '@babel/preset-react'],
5 | plugins: [],
6 | env: {
7 | production: {
8 | plugins: [
9 | '@babel/plugin-transform-react-inline-elements',
10 | '@babel/plugin-transform-react-constant-elements',
11 | 'transform-react-remove-prop-types',
12 | ],
13 | },
14 | },
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/dist/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Native Debugger
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/dist/css/style.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | font-family: monaco, Consolas, Lucida Console, monospace;
4 | overflow: hidden;
5 | font-size: 100%;
6 | margin: 0;
7 | padding: 0;
8 | width: 100%;
9 | height: 100%;
10 | background-color: rgb(53, 59, 70);
11 | }
12 |
13 | #root {
14 | width: 100%;
15 | height: 100%;
16 | }
17 | #logs {
18 | position: fixed;
19 | top: 0;
20 | left: 0;
21 | white-space: pre;
22 | }
23 | #loading {
24 | color: #aaa;
25 | font-size: 30px;
26 | display: flex;
27 | height: 100%;
28 | justify-content: center;
29 | align-items: center;
30 | }
31 |
32 | ::-webkit-scrollbar {
33 | width: 8px;
34 | height: 8px;
35 | background-color: #555;
36 | }
37 | ::-webkit-scrollbar-thumb {
38 | background-color: #333;
39 | }
40 | ::-webkit-scrollbar-corner {
41 | background-color: #333;
42 | }
43 |
44 | @media print {
45 | @page {
46 | size: auto;
47 | margin: 0;
48 | }
49 | body {
50 | position: static;
51 | }
52 | }
53 | .CodeMirror {
54 | font-family: monaco, Consolas, Lucida Console, monospace !important;
55 | }
56 |
--------------------------------------------------------------------------------
/dist/devtools-helper/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/dist/devtools-helper/main.js:
--------------------------------------------------------------------------------
1 | const detectChromeDevToolsTheme = () => chrome.devtools.panels.themeName || 'default';
2 |
3 | const themeName = detectChromeDevToolsTheme();
4 |
5 | chrome.devtools.inspectedWindow.eval(`
6 | window.chromeDevToolsTheme = '${themeName}';
7 | if (window.notifyDevToolsThemeChange) {
8 | window.notifyDevToolsThemeChange(window.chromeDevToolsTheme);
9 | }
10 | `);
11 |
12 | window.addEventListener('message', ({ data }) => {
13 | if (data.type !== 'open-in-editor') {
14 | return;
15 | }
16 | const arr = data.source.split(':');
17 | const lineNumber = arr.pop(-1);
18 | const file = arr.join(':');
19 | chrome.devtools.inspectedWindow.eval(`
20 | if (window.openInEditor) {
21 | window.openInEditor('${file}', ${Number(lineNumber)});
22 | }
23 | `);
24 | });
25 |
--------------------------------------------------------------------------------
/dist/devtools-helper/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "RNDebugger devtools helper",
4 | "version": "0.0.1",
5 | "devtools_page": "main.html"
6 | }
7 |
--------------------------------------------------------------------------------
/dist/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/dist/logo.png
--------------------------------------------------------------------------------
/dist/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-debugger",
3 | "version": "0.14.0",
4 | "productName": "React Native Debugger",
5 | "description": "The standalone app for React Native Debugger, with React DevTools / Redux DevTools",
6 | "main": "main.js",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/jhen0409/react-native-debugger.git"
10 | },
11 | "author": "Jhen ",
12 | "license": "MIT",
13 | "scripts": {
14 | "postinstall": "patch-package"
15 | },
16 | "dependencies": {
17 | "adbkit": "^2.11.0",
18 | "electron-store": "^1.2.0",
19 | "react-devtools-core": "^4.28.0"
20 | },
21 | "devDependencies": {
22 | "apollo-client-devtools": "^4.1.4",
23 | "patch-package": "^6.2.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Documentation
2 |
3 | - [Getting Started](getting-started.md)
4 | - [Debugger Integration](debugger-integration.md)
5 | - [React DevTools Integration](react-devtools-integration.md)
6 | - [Redux DevTools Integration](redux-devtools-integration.md)
7 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
8 | - [Shortcuts references](shortcut-references.md)
9 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
10 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
11 | - [Config file in home directory](config-file-in-home-directory.md)
12 | - [Troubleshooting](troubleshooting.md)
13 | - [Contributing](contributing.md)
14 |
--------------------------------------------------------------------------------
/docs/apollo-client-devtools-integration.md:
--------------------------------------------------------------------------------
1 | # Apollo Client DevTools Integration
2 |
3 | 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:
4 |
5 |
6 |
7 | To ensure it works, you must use Apollo Client ^2.0.
8 |
9 | If the apollo tab doesn't appear, toggle developer tools off and on again.
10 |
11 | You can read Apollo DevTools [documentation](https://github.com/apollographql/apollo-client-devtools#apollo-client-devtools)).
12 |
13 |
14 | ## Other documentations
15 |
16 | - [Getting Started](getting-started.md)
17 | - [Debugger Integration](debugger-integration.md)
18 | - [React DevTools Integration](react-devtools-integration.md)
19 | - [Redux DevTools Integration](redux-devtools-integration.md)
20 | - [Shortcut references](shortcut-references.md)
21 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
22 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
23 | - [Config file in home directory](config-file-in-home-directory.md)
24 | - [Troubleshooting](troubleshooting.md)
25 | - [Contributing](contributing.md)
26 |
--------------------------------------------------------------------------------
/docs/config-file-in-home-directory.md:
--------------------------------------------------------------------------------
1 | # Config file in home directory
2 |
3 | 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:
4 |
5 |
6 | ```json5
7 | {
8 | // Font family of the debugger window
9 | fontFamily: 'monaco, Consolas, Lucida Console, monospace',
10 |
11 | // Zoom level of the debugger window, it will override persited zoomLevel
12 | zoomLevel: 0,
13 |
14 | // Settings of debugger window,
15 | windowBounds: {
16 | // Size of the debugger window, it will override persisted size
17 | width: 1024,
18 | height: 750,
19 |
20 | // Show frame for debugger window
21 | // but due to https://github.com/electron/electron/issues/3647
22 | // so we can't have custom title bar if no frame
23 | frame: true,
24 | },
25 |
26 | // Auto check update on RNDebugger startup
27 | autoUpdate: true,
28 |
29 | // RNDebugger will open debugger window with the ports when app launched
30 | defaultRNPackagerPorts: [8081],
31 |
32 | // Env for
33 | // open React DevTools source file link
34 | // and enable open in editor for console log for RNDebugger
35 | editor: '',
36 |
37 | // Set default react-devtools theme (default is match Chrome DevTools theme)
38 | // but the default theme doesn't change manually changed theme
39 | // see https://github.com/facebook/react-devtools/blob/master/frontend/Themes/Themes.js to get more
40 | defaultReactDevToolsTheme: 'RNDebugger',
41 |
42 | // Set default react-devtools port (default is \`19567+\` if it is not being used).
43 | // The devtools backend of React Native will use the port to connect to the devtools server.
44 | // You should use that if you have some rules for binding port.
45 | // (like https://github.com/jhen0409/react-native-debugger/issues/397)
46 | defaultReactDevToolsPort: 19567,
47 |
48 | // Enable Network Inspect by default
49 | // See https://github.com/jhen0409/react-native-debugger/blob/master/docs/network-inspect-of-chrome-devtools.md
50 | defaultNetworkInspect: false,
51 |
52 | // Refresh devtools when doing JS reload every N times. (-1 for disabled)
53 | // This can effectively avoid possible memory leaks (Like
54 | // https://github.com/jhen0409/react-native-debugger/issues/405) in devtools.
55 | timesJSLoadToRefreshDevTools: -1,
56 | }
57 | ```
58 |
59 | ## Other documentations
60 |
61 | - [Getting Started](getting-started.md)
62 | - [Debugger Integration](debugger-integration.md)
63 | - [React DevTools Integration](react-devtools-integration.md)
64 | - [Redux DevTools Integration](redux-devtools-integration.md)
65 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
66 | - [Shortcut references](shortcut-references.md)
67 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
68 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
69 | - [Troubleshooting](troubleshooting.md)
70 | - [Contributing](contributing.md)
71 |
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Development
4 |
5 | ### Fork this repo & install dependencies
6 |
7 | We're recommended use yarn because we keep the dependencies lock of yarn.
8 |
9 | ```bash
10 | # In react-native-debugger directory
11 | $ yarn
12 | $ cd npm-package && yarn && cd ..
13 | ```
14 |
15 | If you want to debug the [NPM package](../npm-package), just run `npm link ` on your React Native project.
16 |
17 | ### Run on development mode
18 | _Please ensure the `React Native Debugger` production / distribution app is closed._
19 |
20 | ```bash
21 | $ yarn dev:webpack # Then open the another terminal tab
22 | $ yarn dev:electron
23 | ```
24 | 1. From here, you can open a react-native project with remote debugging enabled.
25 | 1. To see the development build of the react-native-debugger, do x,y,z
26 |
27 | ### Run on production mode
28 |
29 | ```bash
30 | $ yarn build
31 | $ yarn start
32 | ```
33 |
34 | ### Run test
35 |
36 | Run lint and test, currently we just wrote E2E test for RNDebugger.
37 |
38 | ```bash
39 | $ yarn test
40 | $ yarn test-e2e
41 | ```
42 |
43 | You need to closes all React Native packager (make sure `8081` or `8088` port not listening) when running the test.
44 |
45 | ### Packaging app
46 |
47 | ```bash
48 | $ yarn run pack-macos # Use --notarize to notarize app
49 | # On macOS: brew install fakeroot dpkg rpm
50 | $ yarn run pack-linux
51 | # On macOS: brew install wine mono
52 | $ yarn run pack-windows
53 | $ yarn run pack # all
54 | ```
55 |
56 | If you want to build binaries yourself, please remove [../electron/update.js](electron/update.js) (and [electon/main.js usage](electon/main.js)).
57 |
58 | For macOS, note that if your app binary is not code signed, you will often get a firewall prompt from React DevTools server.
59 |
60 | ### [Optional] Prerequisites for packaging Linux / Windows app on macOS
61 |
62 | ```bash
63 | # Linux
64 | brew install fakeroot dpkg rpm
65 |
66 | # Windows
67 | brew tap homebrew/cask-versions
68 | brew install wine-stable mono
69 | ```
70 |
71 | ## Financial contributions
72 |
73 | We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/react-native-debugger).
74 | 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.
75 |
76 | ## Credits
77 |
78 | ### Contributors
79 |
80 | Thank you to all the people who have already contributed to react-native-debugger!
81 |
82 | ### Backers
83 |
84 | Thank you to all our backers! [[Become a backer](https://opencollective.com/react-native-debugger#backer)]
85 |
86 |
87 |
88 | ### Sponsors
89 |
90 | 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))
91 |
92 |
93 |
94 | ## Other documentations
95 |
96 | - [Getting Started](getting-started.md)
97 | - [Debugger Integration](debugger-integration.md)
98 | - [React DevTools Integration](react-devtools-integration.md)
99 | - [Redux DevTools Integration](redux-devtools-integration.md)
100 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
101 | - [Shortcut references](shortcut-references.md)
102 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
103 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
104 | - [Config file in home directory](config-file-in-home-directory.md)
105 | - [Troubleshooting](troubleshooting.md)
106 |
--------------------------------------------------------------------------------
/docs/debugger-integration.md:
--------------------------------------------------------------------------------
1 | # Debugger integration
2 |
3 | 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:
4 |
5 | - `Console`
6 | - `Sources`
7 | - `Network` (Inspect Network requests if you are enabled [Network Inspect](network-inspect-of-chrome-devtools.md))
8 | - `Memory`
9 |
10 | ## Multiple React Native packager (custom port) support
11 |
12 | 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.
13 |
14 | 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.
15 |
16 | 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.
17 |
18 | ## Debugging tips
19 |
20 | #### Global variables in console
21 |
22 | 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.
23 |
24 | - `$r`: You selected element on react-devtools.
25 | - `showAsyncStorageContentInDev()` - Log AsyncStorage content
26 | - `$reactNative.*` - Get react-native modules. For example, you can get `$reactNative.AsyncStorage`
27 |
28 | #### Enable `Debug Remotely` programmatically
29 |
30 | For enable `Debug Remotely` without using dev menu, you can use the built-in `DevSettings` native module:
31 |
32 | ```js
33 | import { NativeModules } from 'react-native'
34 |
35 | if (__DEV__) {
36 | NativeModules.DevSettings.setIsDebuggingRemotely(true)
37 | }
38 | ```
39 |
40 | 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.
41 |
42 | ## Other documentations
43 |
44 | - [Getting Started](getting-started.md)
45 | - [React DevTools Integration](react-devtools-integration.md)
46 | - [Redux DevTools Integration](redux-devtools-integration.md)
47 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
48 | - [Shortcut references](shortcut-references.md)
49 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
50 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
51 | - [Config file in home directory](config-file-in-home-directory.md)
52 | - [Troubleshooting](troubleshooting.md)
53 | - [Contributing](contributing.md)
54 |
--------------------------------------------------------------------------------
/docs/enable-open-in-editor-in-console.md:
--------------------------------------------------------------------------------
1 | # Enable open in editor in console
2 |
3 | You can toggle the application menu item:
4 |
5 |
6 |
7 | 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.
8 |
9 | ## Known issues
10 |
11 | - Currently this feature doesn't work with Haul bundler, please tracking [issue #141](https://github.com/jhen0409/react-native-debugger/issues/141).
12 |
13 | ## Other documentations
14 |
15 | - [Getting Started](getting-started.md)
16 | - [Debugger Integration](debugger-integration.md)
17 | - [React DevTools Integration](react-devtools-integration.md)
18 | - [Redux DevTools Integration](redux-devtools-integration.md)
19 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
20 | - [Shortcut references](shortcut-references.md)
21 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
22 | - [Config file in home directory](config-file-in-home-directory.md)
23 | - [Troubleshooting](troubleshooting.md)
24 | - [Contributing](contributing.md)
25 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | Just these steps will let you start RNDebugger out of box:
4 |
5 | - Install the latest version ([download page](https://github.com/jhen0409/react-native-debugger/releases)).
6 | - Make sure all debugger clients of React Native are closed, usually are `http://localhost:/debugger-ui`
7 | - Make sure RNDebugger is open and wait state.
8 | - 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.
9 | - Enable `Debug JS Remotely` of [developer menu](https://reactnative.dev/docs/debugging#accessing-the-in-app-developer-menu) on your app
10 |
11 | ## Launch by CLI or React Native packager
12 |
13 | Platform: macOS / Linux
14 |
15 | ### The `rndebugger:` URI scheme
16 |
17 | Launch RNDebugger by typing the following command:
18 |
19 | ```bash
20 | $ open "rndebugger://set-debugger-loc?host=localhost&port=8081"
21 | ```
22 |
23 | Or `xdg-open` for Linux:
24 |
25 | ```bash
26 | $ xdg-open "rndebugger://set-debugger-loc?host=localhost&port=8081"
27 | ```
28 |
29 | The `host` / `port` means React Native packager. You may need to set `port` if you customize the packager port. (`8081` by default)
30 |
31 | 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`:
32 |
33 | ```bash
34 | $ REACT_DEBUGGER="unset ELECTRON_RUN_AS_NODE && open -g 'rndebugger://set-debugger-loc?port=19000' ||" npm start
35 | ```
36 |
37 | You can use `open` on macOS or `xdg-open` on Linux, currently it is not supported for Windows.
38 |
39 | ### Use [`react-native-debugger-open`](../npm-package)
40 |
41 | If you don‘t need to add a dependency, you can use the package, it can help with:
42 |
43 | - 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 :)
44 | - 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
45 |
46 | ### What about Windows support?
47 |
48 | Currently the `rndebugger:` URI scheme doesn't support for Windows.
49 |
50 | 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.
51 |
52 | 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.
53 |
54 | ## Use Redux DevTools Extension API
55 |
56 | Using the same API as [`redux-devtools-extension`](https://github.com/zalmoxisus/redux-devtools-extension#1-with-redux) is very simple:
57 |
58 | ```js
59 | const store = createStore(
60 | reducer /* preloadedState, */,
61 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
62 | )
63 | ```
64 |
65 | See [`Redux DevTools Integration`](redux-devtools-integration.md) section for more information.
66 |
67 | ## Platform support
68 |
69 | - [React Native](https://github.com/facebook/react-native) >= 0.43
70 | - [React Native for macOS](https://github.com/ptmt/react-native-macos) (formerly react-native-desktop) >= 0.14.0
71 | - [React Native for Windows](https://github.com/Microsoft/react-native-windows)
72 |
73 | ## Auto-update RNDebugger app (Supported v0.5.0 after)
74 |
75 | Currently auto-update is only supported for macOS. Linux and Windows will show a dialog of new versions available for download.
76 |
77 | You can also click `React Native Debugger` (`RND` for Linux / Windows) -> `Check for Updates...` in the application menu.
78 |
79 | ## Other documentations
80 |
81 | - [Debugger Integration](debugger-integration.md)
82 | - [React DevTools Integration](react-devtools-integration.md)
83 | - [Redux DevTools Integration](redux-devtools-integration.md)
84 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
85 | - [Shortcut references](shortcut-references.md)
86 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
87 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
88 | - [Config file in home directory](config-file-in-home-directory.md)
89 | - [Troubleshooting](troubleshooting.md)
90 | - [Contributing](contributing.md)
91 |
--------------------------------------------------------------------------------
/docs/network-inspect-of-chrome-devtools.md:
--------------------------------------------------------------------------------
1 | # Network Inspect of Chrome Developer Tools
2 |
3 | **_WARNING_**: You should read [the limitations](#limitations) if you want to use this feature.
4 |
5 | When you have Network Inspect enabled you can inspect network requests that use `XMLHttpRequest` or `fetch` on the `Network` tab of Chrome Developer Tools.
6 |
7 | You can enable this feature by one of these ways:
8 |
9 | - [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);
10 | - by the `defaultNetworkInspect` option in the [config file](config-file-in-home-directory.md) (Network Inspect will be enabled permanently).
11 |
12 | ## How it works
13 |
14 | 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:
15 |
16 | ```js
17 | global.XMLHttpRequest = global.originalXMLHttpRequest
18 | ? global.originalXMLHttpRequest
19 | : global.XMLHttpRequest;
20 | global.FormData = global.originalFormData
21 | ? global.originalFormData
22 | : global.FormData;
23 |
24 | fetch; // Ensure to get the lazy property
25 |
26 | if (window.__FETCH_SUPPORT__) {
27 | // it's RNDebugger only to have
28 | window.__FETCH_SUPPORT__.blob = false;
29 | } else {
30 | /*
31 | * Set __FETCH_SUPPORT__ to false is just work for `fetch`.
32 | * If you're using another way you can just use the native Blob and remove the `else` statement
33 | */
34 | global.Blob = global.originalBlob ? global.originalBlob : global.Blob;
35 | global.FileReader = global.originalFileReader
36 | ? global.originalFileReader
37 | : global.FileReader;
38 | }
39 | ```
40 |
41 | > Note that replace `global.Blob` will cause issue like [#56](https://github.com/jhen0409/react-native-debugger/issues/56).
42 |
43 | This allows you can open the `Network` tab in devtools to inspect requests of `fetch` and `XMLHttpRequest`.
44 |
45 | You can also do this on the official remote debugger, but it has two differences:
46 |
47 | - RNDebugger is based on [Electron](https://github.com/electron/electron) so it doesn't have the CORS issue
48 | - 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`.
49 |
50 | ## Limitations
51 |
52 | There are some limitations of debugging network requests using Network Inspect:
53 |
54 | - [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.
55 | - [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.
56 | - React Native `FormData` supports the `uri` property. You can use files from `CameraRoll`, but `originalFormData` isn't supported.
57 | - 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`.
58 |
59 | 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).
60 |
61 | ## Other documentations
62 |
63 | - [Getting Started](getting-started.md)
64 | - [Debugger Integration](debugger-integration.md)
65 | - [React DevTools Integration](react-devtools-integration.md)
66 | - [Redux DevTools Integration](redux-devtools-integration.md)
67 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
68 | - [Shortcut references](shortcut-references.md)
69 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
70 | - [Config file in home directory](config-file-in-home-directory.md)
71 | - [Troubleshooting](troubleshooting.md)
72 | - [Contributing](contributing.md)
73 |
--------------------------------------------------------------------------------
/docs/react-devtools-integration.md:
--------------------------------------------------------------------------------
1 | # React DevTools Integration
2 |
3 | **_NOTE_** Supported React Native version is `>= 0.62`. Please downgrade RNDebugger version to `0.10` if you're using older versions of React Native.
4 |
5 | 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).
6 |
7 | 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.
8 |
9 | We made the server listen to a random port and inject `window.__REACT_DEVTOOLS_PORT__` global variable in debugger worker.
10 |
11 | For Android, we have the built-in `adb` util and it will reverse the port automatically.
12 |
13 | ## Get `$r` global variable of React Native runtime in the console
14 |
15 | Refer to [`Debugger Integration`](debugger-integration.md#debugging-tips).
16 |
17 | ## **_Question_**: I got `Unsupported` message from React DevTools
18 |
19 | If you're using React Native version >= 0.62 and keep React Native Debugger as the latest version, here is what you can do:
20 |
21 | 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:
22 |
23 | ```json
24 | {
25 | "resolutions": {
26 | "react-devtools-core": "~4.28.0"
27 | }
28 | }
29 | ```
30 |
31 | or NPM:
32 |
33 | ```json
34 | {
35 | "overrides": {
36 | "react-devtools-core": "~4.28.0"
37 | }
38 | }
39 | ```
40 |
41 | Reference: [Unsupported DevTools backend version - # React Native Debugger](https://gist.github.com/bvaughn/4bc90775530873fdf8e7ade4a039e579#react-native-debugger)
42 |
43 | 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.
44 |
45 | ## Other documentations
46 |
47 | - [Getting Started](getting-started.md)
48 | - [Debugger Integration](debugger-integration.md)
49 | - [Redux DevTools Integration](redux-devtools-integration.md)
50 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
51 | - [Shortcut references](shortcut-references.md)
52 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
53 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
54 | - [Config file in home directory](config-file-in-home-directory.md)
55 | - [Troubleshooting](troubleshooting.md)
56 | - [Contributing](contributing.md)
57 |
--------------------------------------------------------------------------------
/docs/redux-devtools-integration.md:
--------------------------------------------------------------------------------
1 | # Redux DevTools Integration
2 |
3 | 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).
4 |
5 | If you've enabled `Debug JS remotely` with React Native Debugger, the following API is already included in global:
6 |
7 | - `window.__REDUX_DEVTOOLS_EXTENSION__`
8 | - `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__`
9 | - `window.__REDUX_DEVTOOLS_EXTENSION__.connect`
10 | - You can just use [`redux-devtools-extension`](https://www.npmjs.com/package/redux-devtools-extension) npm package.
11 |
12 | See also:
13 |
14 | - [Redux DevTools main repository]](https://github.com/reduxjs/redux-devtools/blob/main/README.md)
15 | - [API Reference](https://github.com/reduxjs/redux-devtools/tree/main/extension/docs/API)
16 | - [Troubleshooting](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Troubleshooting.md)
17 | - Other Integrations
18 | - [`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.
19 |
20 | You can ignore the things specified by the browser extension.
21 |
22 | ## About `trace` feature
23 |
24 | - 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.
25 |
26 | ## Other documentations
27 |
28 | - [Getting Started](getting-started.md)
29 | - [Debugger Integration](debugger-integration.md)
30 | - [React DevTools Integration](react-devtools-integration.md)
31 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
32 | - [Shortcut references](shortcut-references.md)
33 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
34 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
35 | - [Config file in home directory](config-file-in-home-directory.md)
36 | - [Troubleshooting](troubleshooting.md)
37 | - [Contributing](contributing.md)
38 |
--------------------------------------------------------------------------------
/docs/shortcut-references.md:
--------------------------------------------------------------------------------
1 | # Shortcut references
2 |
3 | This section will explain about the following items in RNDebugger.
4 |
5 | - [Content menu](#context-menu)
6 | - [Touch Bar](#touch-bar-in-macos)
7 | - [Keyboard shortcuts](#keyboard-shortcuts)
8 |
9 | ## Context menu
10 |
11 | We have context menu (right-click) for provides useful features:
12 |
13 | 
14 |
15 | - Reload
16 | - Toggle Elements Inspector
17 | - Show Developer Menu [iOS only]
18 | - Enable / Disable [Network Inspect](debugger-integration.md#how-network-inspect-works)
19 | - Log AsyncStorage content
20 | - Clear AsyncStorage
21 |
22 | It includes the developer menu features, these would be useful for real device, instead of open developer menu in device manually.
23 |
24 | ## Keyboard shortcuts
25 |
26 | - Reload JS (macOS: `Command+R`, Windows / Linux: `Ctrl+R`)
27 | - Toggle Elements Inspector (macOS: `Command+I`, Windows / Linux: `Ctrl+I`)
28 | - New Debugger Window (macOS: `Command+T`, Windows / Linux: `Ctrl+T`)
29 | - Toggle Developer Tools (macOS: `Command+Option+I`, Windows / Linux: `Ctrl+Alt+I`)
30 | - Toggle Redux DevTools (macOS: `Command+Option+J`, Windows / Linux: `Ctrl+Alt+J`)
31 | - Toggle React DevTools (macOS: `Command+Option+K`, Windows / Linux: `Ctrl+Alt+K`)
32 | - Quickly into search field of React DevTools (Type `/`)
33 |
34 | You can also read [Keyboard Shortcuts Reference of Chrome Developer Tools](https://developers.google.com/web/tools/chrome-devtools/shortcuts).
35 |
36 | ## Touch Bar in macOS
37 |
38 |
39 |
40 | The `Redux Slider` will shown on right if you're using [`Redux API`](redux-devtools-integration.md),
41 |
42 | If your Mac haven't TouchBar support and you still want to use the feature, you can use [Touché](https://redsweater.com/touche/).
43 |
44 | ## Other documentations
45 |
46 | - [Getting Started](getting-started.md)
47 | - [Debugger Integration](debugger-integration.md)
48 | - [React DevTools Integration](react-devtools-integration.md)
49 | - [Redux DevTools Integration](redux-devtools-integration.md)
50 | - [Apollo Client DevTools Integration](apollo-client-devtools-integration.md)
51 | - [Network inspect of Chrome Developer Tools](network-inspect-of-chrome-devtools.md)
52 | - [Enable open in editor in console](enable-open-in-editor-in-console.md)
53 | - [Config file in home directory](config-file-in-home-directory.md)
54 | - [Troubleshooting](troubleshooting.md)
55 | - [Contributing](contributing.md)
56 |
--------------------------------------------------------------------------------
/electron/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Native Debugger
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/electron/config/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`openConfigFile 1`] = `
4 | {
5 | "config": {
6 | "autoUpdate": true,
7 | "defaultNetworkInspect": false,
8 | "defaultRNPackagerPorts": [
9 | 8081,
10 | ],
11 | "defaultReactDevToolsPort": 19567,
12 | "defaultReactDevToolsTheme": "RNDebugger",
13 | "editor": "",
14 | "timesJSLoadToRefreshDevTools": -1,
15 | "windowBounds": {
16 | "frame": true,
17 | },
18 | },
19 | }
20 | `;
21 |
22 | exports[`readConfig 1`] = `
23 | {
24 | "config": {
25 | "autoUpdate": true,
26 | "defaultNetworkInspect": false,
27 | "defaultRNPackagerPorts": [
28 | 8081,
29 | ],
30 | "defaultReactDevToolsPort": 19567,
31 | "defaultReactDevToolsTheme": "RNDebugger",
32 | "editor": "",
33 | "timesJSLoadToRefreshDevTools": -1,
34 | "windowBounds": {
35 | "frame": true,
36 | },
37 | },
38 | }
39 | `;
40 |
41 | exports[`readConfig 2`] = `
42 | {
43 | "config": {
44 | "autoUpdate": false,
45 | },
46 | }
47 | `;
48 |
49 | exports[`readConfig 3`] = `
50 | {
51 | "config": {
52 | "autoUpdate": true,
53 | "defaultNetworkInspect": false,
54 | "defaultRNPackagerPorts": [
55 | 8081,
56 | ],
57 | "defaultReactDevToolsPort": 19567,
58 | "defaultReactDevToolsTheme": "RNDebugger",
59 | "editor": "",
60 | "timesJSLoadToRefreshDevTools": -1,
61 | "windowBounds": {
62 | "frame": true,
63 | },
64 | },
65 | "error": [SyntaxError: Unexpected 'i' at line 1 column 16 of the JSON5 data. Still to read: "is_broken, }"],
66 | "isConfigBroken": true,
67 | }
68 | `;
69 |
--------------------------------------------------------------------------------
/electron/config/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 |
4 | jest.mock('electron', () => ({
5 | shell: {
6 | openPath: jest.fn(),
7 | },
8 | }))
9 |
10 | const testFile = path.join(__dirname, 'config_test')
11 |
12 | beforeAll(() => fs.existsSync(testFile) && fs.unlinkSync(testFile))
13 |
14 | /* eslint-disable global-require */
15 | test('readConfig', () => {
16 | const { readConfig } = require('..')
17 |
18 | expect(readConfig(testFile)).toMatchSnapshot()
19 |
20 | // User custom config
21 | fs.writeFileSync(testFile, '{ autoUpdate: false, }')
22 | expect(readConfig(testFile)).toMatchSnapshot()
23 |
24 | // Broken config
25 | fs.writeFileSync(testFile, '{ autoUpdate: is_broken, }')
26 | expect(readConfig(testFile)).toMatchSnapshot()
27 | })
28 |
29 | test('openConfigFile', () => {
30 | const { readConfig, openConfigFile } = require('..')
31 | const { shell } = require('electron')
32 |
33 | openConfigFile(testFile)
34 | expect(shell.openPath).toBeCalledWith(testFile)
35 | shell.openPath.mockClear()
36 |
37 | fs.unlinkSync(testFile)
38 | openConfigFile(testFile)
39 | expect(readConfig(testFile)).toMatchSnapshot()
40 | })
41 |
--------------------------------------------------------------------------------
/electron/config/index.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import json5 from 'json5'
4 | import { shell } from 'electron'
5 | import template from './template'
6 |
7 | export const filePath = path.join(
8 | process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'],
9 | '.rndebuggerrc',
10 | )
11 |
12 | export const readConfig = (configFile = filePath) => {
13 | if (!fs.existsSync(configFile)) {
14 | // Create a new one
15 | fs.writeFileSync(configFile, template)
16 | return { config: json5.parse(template) }
17 | }
18 | try {
19 | // eslint-disable-next-line
20 | return { config: json5.parse(fs.readFileSync(configFile, 'utf-8')) };
21 | } catch (error) {
22 | // Alert parse config not successful
23 | return { config: json5.parse(template), isConfigBroken: true, error }
24 | }
25 | }
26 |
27 | export const openConfigFile = (configFile = filePath) => {
28 | readConfig()
29 | shell.openPath(configFile)
30 | }
31 |
--------------------------------------------------------------------------------
/electron/config/template.js:
--------------------------------------------------------------------------------
1 | // json5
2 | module.exports = `{
3 | // Font family of the debugger window
4 | // fontFamily: 'monaco, Consolas, Lucida Console, monospace',
5 |
6 | // Zoom level of the debugger window, it will override persited zoomLevel
7 | // zoomLevel: 0,
8 |
9 | // Settings of debugger window,
10 | windowBounds: {
11 | // Size of the debugger window, it will override persisted size
12 | // width: 1024,
13 | // height: 750,
14 |
15 | // Show frame for debugger window
16 | // but due to https://github.com/electron/electron/issues/3647
17 | // so we can't have custom title bar if no frame
18 | // titleBarStyle: 'hidden',
19 | frame: true,
20 | },
21 |
22 | // Auto check update on RNDebugger startup
23 | autoUpdate: true,
24 |
25 | // RNDebugger will open debugger window with the ports when app launched
26 | defaultRNPackagerPorts: [8081],
27 |
28 | // Env for
29 | // open React DevTools source file link
30 | // and enable open in editor for console log for RNDebugger
31 | editor: '',
32 |
33 | // Set default react-devtools theme (default is match Chrome DevTools theme)
34 | // but the default theme doesn't change manually changed theme
35 | // see https://github.com/facebook/react-devtools/blob/master/frontend/Themes/Themes.js to get more
36 | defaultReactDevToolsTheme: 'RNDebugger',
37 |
38 | // Set default react-devtools port (default is \`19567+\` if it is not being used).
39 | // The devtools backend of React Native will use the port to connect to the devtools server.
40 | // You should use that if you have some rules for binding port.
41 | // (like https://github.com/jhen0409/react-native-debugger/issues/397)
42 | defaultReactDevToolsPort: 19567,
43 |
44 | // Enable Network Inspect by default
45 | // See https://github.com/jhen0409/react-native-debugger/blob/master/docs/network-inspect-of-chrome-devtools.md
46 | defaultNetworkInspect: false,
47 |
48 | // Refresh devtools when doing JS reload every N times. (-1 for disabled)
49 | // This can effectively avoid possible memory leaks (Like
50 | // https://github.com/jhen0409/react-native-debugger/issues/405) in devtools.
51 | timesJSLoadToRefreshDevTools: -1,
52 | }
53 | `
54 |
--------------------------------------------------------------------------------
/electron/context-menu.js:
--------------------------------------------------------------------------------
1 | import { ipcMain } from 'electron'
2 | import contextMenu from 'electron-context-menu'
3 | import { readConfig } from './config'
4 | import {
5 | toggleDevTools, n, item, separator,
6 | } from './menu/common'
7 |
8 | const invokeDevMethod = (win, name) => win.webContents.executeJavaScript(
9 | `window.invokeDevMethod && window.invokeDevMethod('${name}')`,
10 | )
11 |
12 | export const registerContextMenu = (win) => {
13 | const { config } = readConfig()
14 | const defaultContextMenuItems = [
15 | item('Toggle Developer Tools', n, () => toggleDevTools(win, 'chrome')),
16 | item('Toggle React DevTools', n, () => toggleDevTools(win, 'react')),
17 | item('Toggle Redux DevTools', n, () => toggleDevTools(win, 'redux')),
18 | ]
19 | let networkInspectEnabled = !!config.networkInspect
20 | let availableMethods = []
21 | contextMenu({
22 | window: win,
23 | showInspectElement: process.env.NODE_ENV === 'development',
24 | prepend: () => [
25 | availableMethods.includes('reload')
26 | && item('Reload JS', n, () => invokeDevMethod(win, 'reload')),
27 | availableMethods.includes('toggleElementInspector')
28 | && item('Toggle Element Inspector', n, () => invokeDevMethod(win, 'toggleElementInspector')),
29 | availableMethods.includes('show')
30 | && item('Show Developer Menu', n, () => invokeDevMethod(win, 'show')),
31 | item(
32 | networkInspectEnabled
33 | ? 'Disable Network Inspect'
34 | : 'Enable Network Inspect',
35 | n,
36 | () => invokeDevMethod(win, 'networkInspect'),
37 | ),
38 | availableMethods.includes('showAsyncStorage')
39 | && item('Log AsyncStorage content', n, () => invokeDevMethod(win, 'showAsyncStorage')),
40 | availableMethods.includes('clearAsyncStorage')
41 | && item('Clear AsyncStorage', n, () => invokeDevMethod(win, 'clearAsyncStorage')),
42 | separator,
43 | ]
44 | .filter((menuItem) => !!menuItem)
45 | .concat(defaultContextMenuItems),
46 | })
47 |
48 | const listener = (event, data) => {
49 | availableMethods = data.availableMethods || availableMethods
50 | networkInspectEnabled = typeof data.networkInspectEnabled === 'boolean'
51 | ? data.networkInspectEnabled
52 | : networkInspectEnabled
53 | }
54 |
55 | ipcMain.on(`context-menu-available-methods-update-${win.id}`, listener)
56 | return () => {
57 | ipcMain.off(`context-menu-available-methods-update-${win.id}`, listener)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/electron/debug.js:
--------------------------------------------------------------------------------
1 | require('electron-debug')(); // eslint-disable-line
2 |
--------------------------------------------------------------------------------
/electron/devtools.js:
--------------------------------------------------------------------------------
1 | export const getCatchConsoleLogScript = (port) => `
2 | window.__RN_PACKAGER_MATCHER__ = /^http:\\/\\/[^:]+:${port}/;
3 | if (!window.__INJECT_OPEN_IN_EDITOR_SCRIPT__) {
4 | const rndHelperQuery = 'iframe[data-devtools-extension="RNDebugger devtools helper"]';
5 | document.addEventListener('click', event => {
6 | if (!window.__IS_OPEN_IN_EDITOR_ENABLED__) {
7 | return;
8 | }
9 | const { target } = event;
10 | if (target.className === 'devtools-link') {
11 | const source = target.title;
12 | if (source && source.match(window.__RN_PACKAGER_MATCHER__)) {
13 | const rndHelper = document.querySelector(rndHelperQuery);
14 | if (rndHelper && rndHelper.contentWindow) {
15 | rndHelper.contentWindow.postMessage(
16 | {
17 | type: 'open-in-editor',
18 | source: source.replace(window.__RN_PACKAGER_MATCHER__, '')
19 | },
20 | '*'
21 | );
22 | return event.stopPropagation();
23 | }
24 | }
25 | }
26 | }, true);
27 | window.__INJECT_OPEN_IN_EDITOR_SCRIPT__ = true;
28 | }
29 | `
30 |
31 | export const catchConsoleLogLink = (win, host = 'localhost', port = 8081) => {
32 | if (win.devToolsWebContents) {
33 | return win.devToolsWebContents.executeJavaScript(`(() => {
34 | ${getCatchConsoleLogScript(host, port)}
35 | })()`)
36 | }
37 | }
38 |
39 | export const removeUnecessaryTabs = (win) => {
40 | if (
41 | process.env.NODE_ENV === 'production'
42 | && !process.env.DEBUG_RNDEBUGGER
43 | && win.devToolsWebContents
44 | ) {
45 | return win.devToolsWebContents.executeJavaScript(`(() => {
46 | const tabbedPane = UI.inspectorView.tabbedPane;
47 | if (tabbedPane) {
48 | tabbedPane.closeTab('elements');
49 | tabbedPane.closeTab('security');
50 | tabbedPane.closeTab('audits');
51 | tabbedPane.closeTab('audits2');
52 | tabbedPane.closeTab('lighthouse');
53 |
54 | tabbedPane.leftToolbar().element.remove();
55 | }
56 | })()`)
57 | }
58 | }
59 |
60 | export const activeTabs = (win) => {
61 | if (win.devToolsWebContents) {
62 | // Active network tab so we can do clearNetworkLogs
63 | return win.devToolsWebContents.executeJavaScript(`(() => {
64 | DevToolsAPI.showPanel('network');
65 | DevToolsAPI.showPanel('console');
66 | })()`)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/electron/extensions.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import { session } from 'electron'
3 |
4 | export default async () => {
5 | if (process.env.NODE_ENV === 'development') {
6 | await session.defaultSession.loadExtension(
7 | path.resolve('dist/devtools-helper/'),
8 | { allowFileAccess: true },
9 | )
10 | await session.defaultSession.loadExtension(
11 | path.join(
12 | __dirname,
13 | '../node_modules/apollo-client-devtools/build',
14 | ),
15 | { allowFileAccess: true },
16 | )
17 | } else if (process.env.PACKAGE === 'no') {
18 | await session.defaultSession.loadExtension(
19 | path.join(__dirname, 'devtools-helper/'),
20 | { allowFileAccess: true },
21 | )
22 | await session.defaultSession.loadExtension(
23 | path.join(
24 | __dirname,
25 | 'node_modules/apollo-client-devtools/build',
26 | ),
27 | { allowFileAccess: true },
28 | )
29 | } else {
30 | await session.defaultSession.loadExtension(
31 | path.join(__dirname, '../devtools-helper/'),
32 | { allowFileAccess: true },
33 | )
34 | await session.defaultSession.loadExtension(
35 | path.join(
36 | __dirname,
37 | '../ac-devtools-ext-build/', // See package script for why
38 | ),
39 | { allowFileAccess: true },
40 | )
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/electron/logo.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/electron/logo.icns
--------------------------------------------------------------------------------
/electron/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/electron/logo.ico
--------------------------------------------------------------------------------
/electron/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/electron/logo.png
--------------------------------------------------------------------------------
/electron/main.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import {
3 | app, ipcMain, session, BrowserWindow, Menu,
4 | } from 'electron'
5 | import { initialize } from '@electron/remote/main'
6 | import normalizeHeaderCase from 'header-case-normalizer'
7 | import installExtensions from './extensions'
8 | import { checkWindowInfo, createWindow } from './window'
9 | import { startListeningHandleURL, handleURL, parseUrl } from './url-handle'
10 | import { createMenuTemplate } from './menu'
11 | import { readConfig } from './config'
12 | import { sendSyncState } from './sync-state'
13 |
14 | initialize()
15 |
16 | // Uncomment if want to debug devtools backend
17 | // app.commandLine.appendSwitch('remote-debugging-port', '9222');
18 |
19 | app.commandLine.appendSwitch('disable-http-cache')
20 |
21 | process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 1
22 |
23 | const iconPath = path.resolve(__dirname, 'logo.png')
24 | const defaultOptions = { iconPath }
25 |
26 | const findWindow = async (_, port) => {
27 | const browserWindows = BrowserWindow.getAllWindows()
28 | const browserWindow = await browserWindows.reduce(async (promise, win) => {
29 | const acc = await promise
30 | if (acc) return acc
31 |
32 | const { isWorkerRunning, isPortSettingRequired, location } = await checkWindowInfo(win)
33 | return (!isWorkerRunning || location.port === port)
34 | && !isPortSettingRequired
35 | ? win
36 | : null
37 | }, Promise.resolve(null))
38 | if (!browserWindow) createWindow(defaultOptions)
39 | if (browserWindow) {
40 | if (browserWindow.isMinimized()) browserWindow.restore()
41 | browserWindow.focus()
42 | }
43 | return browserWindow
44 | }
45 |
46 | const handleCommandLine = async (commandLine) => {
47 | const url = commandLine.find((arg) => arg.startsWith('rndebugger://'))
48 | if (!url) {
49 | return
50 | }
51 | await handleURL(findWindow, url)
52 | }
53 |
54 | if (process.platform === 'linux') {
55 | const singleInstanceLock = app.requestSingleInstanceLock()
56 | if (!singleInstanceLock) {
57 | process.exit()
58 | } else {
59 | app.on('second-instance', async (event, commandLine) => {
60 | await handleCommandLine(commandLine)
61 | })
62 | }
63 | }
64 |
65 | startListeningHandleURL(findWindow)
66 |
67 | ipcMain.on('check-port-available', async (event, arg) => {
68 | const port = Number(arg)
69 | const windows = BrowserWindow.getAllWindows()
70 | const isPortAvailable = await windows.reduce(async (promise, win) => {
71 | const isAvailable = await promise
72 | if (!isAvailable) return false
73 |
74 | if (win.webContents !== event.sender) {
75 | const { isPortSettingRequired, location } = await checkWindowInfo(win)
76 | if (location.port === port && !isPortSettingRequired) {
77 | return false
78 | }
79 | }
80 | return true
81 | }, Promise.resolve(true))
82 | event.sender.send('check-port-available-reply', isPortAvailable)
83 | })
84 |
85 | ipcMain.on('sync-state', sendSyncState)
86 |
87 | app.on('activate', () => {
88 | if (BrowserWindow.getAllWindows().length !== 0) return
89 | createWindow(defaultOptions)
90 | })
91 |
92 | app.on('new-window-for-tab', () => {
93 | createWindow({ ...defaultOptions, isPortSettingRequired: true })
94 | })
95 |
96 | app.on('window-all-closed', () => {
97 | if (process.platform !== 'darwin') {
98 | app.quit()
99 | }
100 | })
101 |
102 | if (process.platform === 'darwin') {
103 | app.on('before-quit', async (event) => {
104 | event.preventDefault()
105 | BrowserWindow.getAllWindows().forEach((win) => {
106 | win.removeAllListeners('close')
107 | win.close()
108 | })
109 | process.exit()
110 | })
111 | }
112 |
113 | app.on('ready', async () => {
114 | await installExtensions()
115 |
116 | const { config } = readConfig()
117 |
118 | let { defaultRNPackagerPorts } = config
119 | if (!Array.isArray(defaultRNPackagerPorts)) {
120 | defaultRNPackagerPorts = [8081]
121 | }
122 |
123 | if (process.platform === 'linux') {
124 | const url = process.argv.find((arg) => arg.startsWith('rndebugger://'))
125 | const query = url ? parseUrl(url) : undefined
126 | if (query && query.port) {
127 | defaultRNPackagerPorts = [query.port]
128 | }
129 | }
130 |
131 | defaultRNPackagerPorts.forEach((port) => {
132 | createWindow({ port, ...defaultOptions })
133 | })
134 |
135 | const menuTemplate = createMenuTemplate(defaultOptions)
136 | const menu = Menu.buildFromTemplate(menuTemplate)
137 | Menu.setApplicationMenu(menu)
138 |
139 | const replaceHeaderPrefix = '__RN_DEBUGGER_SET_HEADER_REQUEST_'
140 | session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
141 | delete details.requestHeaders.Origin
142 | Object.entries(details.requestHeaders).forEach(([header, value]) => {
143 | if (header.startsWith(replaceHeaderPrefix)) {
144 | const originalHeader = normalizeHeaderCase(
145 | header.replace(replaceHeaderPrefix, ''),
146 | )
147 | details.requestHeaders[originalHeader] = value
148 | delete details.requestHeaders[header]
149 | }
150 | })
151 | callback({ cancel: false, requestHeaders: details.requestHeaders })
152 | })
153 | })
154 |
155 | // Pass all certificate errors in favor of Network Inspect feature
156 | app.on(
157 | 'certificate-error',
158 | (event, webContents, url, error, certificate, callback) => {
159 | event.preventDefault()
160 | callback(true)
161 | },
162 | )
163 |
--------------------------------------------------------------------------------
/electron/menu/common.js:
--------------------------------------------------------------------------------
1 | export const toggleDevTools = (win, type) => {
2 | if (!win || !type) return
3 | if (type === 'chrome') {
4 | win.toggleDevTools()
5 | return
6 | }
7 | win.webContents.send('toggle-devtools', type)
8 | }
9 |
10 | export const toggleFullscreen = (win) => win && win.setFullScreen(!win.isFullScreen())
11 | export const setAlwaysOnTop = (win, checked) => win && win.setAlwaysOnTop(checked)
12 | export const reload = (win) => win && win.webContents.reload()
13 | export const close = (win) => win && win.close()
14 | export const zoom = (win, val) => {
15 | if (!win) return
16 | const contents = win.webContents
17 | contents.zoomLevel += val
18 | }
19 | export const resetZoom = (win) => {
20 | if (win) {
21 | win.webContents.zoomLevel = 0
22 | }
23 | }
24 | export const toggleOpenInEditor = (win) => win && win.webContents.executeJavaScript('window.toggleOpenInEditor()')
25 |
26 | export const menu = (label, submenu, role) => ({ label, submenu, role })
27 | export const item = (label, accelerator, click, rest) => ({
28 | label,
29 | accelerator,
30 | click,
31 | ...rest,
32 | })
33 | export const separator = { type: 'separator' }
34 | export const n = undefined
35 |
--------------------------------------------------------------------------------
/electron/menu/darwin.js:
--------------------------------------------------------------------------------
1 | import { app, shell, BrowserWindow } from 'electron'
2 | import { createWindow } from '../window'
3 | import checkUpdate from '../update'
4 | import {
5 | menu,
6 | item,
7 | separator,
8 | n,
9 | toggleDevTools,
10 | toggleFullscreen,
11 | setAlwaysOnTop,
12 | reload,
13 | zoom,
14 | resetZoom,
15 | toggleOpenInEditor,
16 | } from './common'
17 | import { haveOpenedWindow, showAboutDialog } from './dialog'
18 | import { openConfigFile } from '../config'
19 | import { isSyncState, toggleSyncState } from '../sync-state'
20 |
21 | const getWin = () => BrowserWindow.getFocusedWindow()
22 |
23 | const viewItems = process.env.NODE_ENV === 'developemnt'
24 | ? [item('Reload Window', 'Alt+Command+R', () => reload(getWin()))]
25 | : []
26 |
27 | export default ({ iconPath }) => [
28 | menu('React Native Debugger', [
29 | item('About', n, () => showAboutDialog(iconPath)),
30 | item('Check for Updates...', n, () => checkUpdate(iconPath, true)),
31 | separator,
32 | item('Hide', 'Command+H', n, { selector: 'hide:' }),
33 | item('Hide Others', 'Command+Shift+H', n, { selector: 'hideOtherApplications:' }),
34 | item('Show All', n, n, { selector: 'unhideAllApplications:' }),
35 | separator,
36 | item('Quit', 'Command+Q', () => app.quit()),
37 | ]),
38 | menu(
39 | 'Debugger',
40 | [
41 | item('New Window', 'Command+T', () => createWindow({ iconPath, isPortSettingRequired: haveOpenedWindow() })),
42 | item('Enable Open in Editor for Console Log', n, () => toggleOpenInEditor(getWin()), {
43 | type: 'checkbox',
44 | checked: false,
45 | }),
46 | item('Toggle Device Sync', n, toggleSyncState, {
47 | type: 'checkbox',
48 | checked: isSyncState(),
49 | }),
50 | item('Open Config File', n, () => openConfigFile()),
51 | separator,
52 | item('Minimize', 'Command+M', n, { selector: 'performMiniaturize:' }),
53 | item('Close', 'Command+W', n, { selector: 'performClose:' }),
54 | separator,
55 | item('Bring All to Front', n, n, { selector: 'arrangeInFront:' }),
56 | item('Stay in Front', n, ({ checked }) => setAlwaysOnTop(getWin(), checked), {
57 | type: 'checkbox',
58 | checked: false,
59 | }),
60 | ],
61 | 'window',
62 | ),
63 | menu('Edit', [
64 | item('Undo', 'Command+Z', n, { selector: 'undo:' }),
65 | item('Redo', 'Shift+Command+Z', n, { selector: 'redo:' }),
66 | separator,
67 | item('Cut', 'Command+X', n, { selector: 'cut:' }),
68 | item('Copy', 'Command+C', n, { selector: 'copy:' }),
69 | item('Paste', 'Command+V', n, { selector: 'paste:' }),
70 | item('Select All', 'Command+A', n, { selector: 'selectAll:' }),
71 | ]),
72 | menu(
73 | 'View',
74 | viewItems.concat([
75 | item('Toggle Full Screen', 'F11', () => toggleFullscreen(getWin())),
76 | item('Toggle Developer Tools', 'Alt+Command+I', () => toggleDevTools(getWin(), 'chrome')),
77 | item('Toggle React DevTools', 'Alt+Command+J', () => toggleDevTools(getWin(), 'react')),
78 | item('Toggle Redux DevTools', 'Alt+Command+K', () => toggleDevTools(getWin(), 'redux')),
79 | separator,
80 | item('Zoom In', 'Command+=', () => zoom(getWin(), 1)),
81 | item('Zoom Out', 'Command+-', () => zoom(getWin(), -1)),
82 | item('Reset Zoom', 'Command+0', () => resetZoom(getWin())),
83 | ]),
84 | ),
85 | menu('Help', [
86 | item('Documentation', n, () => shell.openExternal('https://github.com/jhen0409/react-native-debugger/tree/master/docs')),
87 | item('Issues', n, () => shell.openExternal('https://github.com/jhen0409/react-native-debugger/issues')),
88 | item('Open Collective', n, () => shell.openExternal('https://opencollective.com/react-native-debugger')),
89 | ]),
90 | ]
91 |
--------------------------------------------------------------------------------
/electron/menu/dialog.js:
--------------------------------------------------------------------------------
1 | import { app, dialog, BrowserWindow } from 'electron'
2 | import multiline from 'multiline-template'
3 |
4 | const appName = app.name
5 | const detail = multiline`
6 | | Created by Jhen-Jie Hong
7 | | (https://github.com/jhen0409)
8 |
9 | | This software includes the following projects:
10 |
11 | | https://github.com/facebook/react-devtools
12 | | https://github.com/reduxjs/redux-devtools
13 | | https://github.com/apollographql/apollo-client-devtools
14 | `
15 |
16 | export const showAboutDialog = (iconPath) => dialog.showMessageBoxSync({
17 | title: 'About',
18 | message: `${appName} ${app.getVersion()}`,
19 | detail,
20 | icon: iconPath,
21 | buttons: [],
22 | })
23 |
24 | export const haveOpenedWindow = () => !!BrowserWindow.getAllWindows().length
25 |
--------------------------------------------------------------------------------
/electron/menu/index.js:
--------------------------------------------------------------------------------
1 | /* eslint global-require: 0 */
2 |
3 | import createMenuTemplateDarwin from './darwin'
4 | import createMenuTemplateLinuxWin from './linux+win'
5 |
6 | const createMenuTemplate = process.platform === 'darwin'
7 | ? createMenuTemplateDarwin
8 | : createMenuTemplateLinuxWin
9 |
10 | export { createMenuTemplate }
11 |
--------------------------------------------------------------------------------
/electron/menu/linux+win.js:
--------------------------------------------------------------------------------
1 | import { shell, BrowserWindow } from 'electron'
2 | import { createWindow } from '../window'
3 | import checkUpdate from '../update'
4 | import {
5 | menu,
6 | item,
7 | separator,
8 | n,
9 | toggleDevTools,
10 | toggleFullscreen,
11 | setAlwaysOnTop,
12 | reload,
13 | close,
14 | zoom,
15 | resetZoom,
16 | toggleOpenInEditor,
17 | } from './common'
18 | import {
19 | showAboutDialog,
20 | haveOpenedWindow,
21 | } from './dialog'
22 | import { openConfigFile } from '../config'
23 | import { toggleSyncState, isSyncState } from '../sync-state'
24 |
25 | const getWin = () => BrowserWindow.getFocusedWindow()
26 | const viewItems = process.env.NODE_ENV === 'developemnt'
27 | ? [item('Reload Window', 'Alt+CTRL+R', () => reload(getWin()))]
28 | : []
29 |
30 | export default ({ iconPath }) => [
31 | menu('RND', [
32 | item('About', n, () => showAboutDialog(iconPath)),
33 | item('Check for Updates...', n, () => checkUpdate(iconPath, true)),
34 | separator,
35 | item('Stay in Front', n, ({ checked }) => setAlwaysOnTop(getWin(), checked), {
36 | type: 'checkbox',
37 | checked: false,
38 | }),
39 | ]),
40 | menu(
41 | 'Debugger',
42 | [
43 | item('New Window', 'Ctrl+T', () => createWindow({ iconPath, isPortSettingRequired: haveOpenedWindow() })),
44 | item('Enable Open in Editor for Console Log', n, () => toggleOpenInEditor(getWin()), {
45 | type: 'checkbox',
46 | checked: false,
47 | }),
48 | item('Toggle Device Sync', n, toggleSyncState, {
49 | type: 'checkbox',
50 | checked: isSyncState(),
51 | }),
52 | item('Open Config File', n, () => openConfigFile()),
53 | separator,
54 | item('Close', 'Ctrl+W', () => close(getWin())),
55 | ],
56 | 'window',
57 | ),
58 | menu('Edit', [
59 | item('Undo', 'Ctrl+Z', n, { selector: 'undo:' }),
60 | item('Redo', 'Shift+Ctrl+Z', n, { selector: 'redo:' }),
61 | separator,
62 | item('Cut', 'Ctrl+X', n, { selector: 'cut:' }),
63 | item('Copy', 'Ctrl+C', n, { selector: 'copy:' }),
64 | item('Paste', 'Ctrl+V', n, { selector: 'paste:' }),
65 | item('Select All', 'Ctrl+A', n, { selector: 'selectAll:' }),
66 | ]),
67 | menu(
68 | 'View',
69 | viewItems.concat([
70 | item('Toggle Full Screen', 'F11', () => toggleFullscreen(getWin())),
71 | item('Toggle Developer Tools', 'Alt+Ctrl+I', () => toggleDevTools(getWin(), 'chrome')),
72 | item('Toggle React DevTools', 'Alt+Ctrl+J', () => toggleDevTools(getWin(), 'react')),
73 | item('Toggle Redux DevTools', 'Alt+Ctrl+K', () => toggleDevTools(getWin(), 'redux')),
74 | separator,
75 | item('Zoom In', 'Ctrl+=', () => zoom(getWin(), 1)),
76 | item('Zoom Out', 'Ctrl+-', () => zoom(getWin(), -1)),
77 | item('Reset Zoom', 'Ctrl+0', () => resetZoom(getWin())),
78 | ]),
79 | ),
80 | menu('Help', [
81 | item('Documentation', n, () => shell.openExternal('https://github.com/jhen0409/react-native-debugger/tree/master/docs')),
82 | item('Issues', n, () => shell.openExternal('https://github.com/jhen0409/react-native-debugger/issues')),
83 | item('Open Collective', n, () => shell.openExternal('https://opencollective.com/react-native-debugger')),
84 | ]),
85 | ]
86 |
--------------------------------------------------------------------------------
/electron/sync-state.js:
--------------------------------------------------------------------------------
1 | import { BrowserWindow } from 'electron'
2 |
3 | let syncState = false
4 |
5 | export const isSyncState = () => syncState
6 |
7 | // Take by renderer
8 | global.isSyncState = isSyncState
9 |
10 | export const toggleSyncState = () => {
11 | syncState = !syncState
12 | }
13 |
14 | export const sendSyncState = (event, payload) => {
15 | if (!isSyncState) return
16 |
17 | BrowserWindow.getAllWindows()
18 | .filter((win) => Number(win.webContents.id) !== event.sender.id)
19 | .forEach((win) => {
20 | win.webContents.send('sync-state', payload)
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/electron/update.js:
--------------------------------------------------------------------------------
1 | import { app, dialog, shell } from 'electron'
2 | import GhReleases from 'electron-gh-releases'
3 | import fetch from 'electron-fetch'
4 |
5 | const repo = 'jhen0409/react-native-debugger'
6 |
7 | const getFeed = () => fetch(`https://raw.githubusercontent.com/${repo}/master/auto_update.json`).then((res) => res.json())
8 |
9 | const showDialog = ({
10 | icon, buttons, message, detail,
11 | }) => dialog.showMessageBoxSync({
12 | type: 'info',
13 | buttons,
14 | title: 'React Native Debugger',
15 | icon,
16 | message,
17 | detail,
18 | })
19 |
20 | const notifyUpdateAvailable = ({ icon, detail }) => {
21 | const index = showDialog({
22 | message: 'A newer version is available.',
23 | buttons: ['Download', 'Later'],
24 | icon,
25 | detail,
26 | })
27 | return index === 0
28 | }
29 |
30 | const notifyUpdateDownloaded = ({ icon }) => {
31 | const index = showDialog({
32 | message:
33 | 'The newer version has been downloaded. '
34 | + 'Please restart the application to apply the update.',
35 | buttons: ['Restart', 'Later'],
36 | icon,
37 | })
38 | return index === 0
39 | }
40 |
41 | let checking = false
42 |
43 | export default (icon, notify) => {
44 | if (checking) return
45 |
46 | checking = true
47 | const updater = new GhReleases({
48 | repo,
49 | currentVersion: app.getVersion(),
50 | })
51 |
52 | updater.check(async (err, status) => {
53 | if (process.platform === 'linux' && err.message === 'This platform is not supported.') {
54 | err = null; // eslint-disable-line
55 | status = true; // eslint-disable-line
56 | }
57 | if (notify && err) {
58 | showDialog({ message: err.message, buttons: ['OK'] })
59 | checking = false
60 | return
61 | }
62 | if (err || !status) {
63 | checking = false
64 | return
65 | }
66 | const feed = await getFeed()
67 | const detail = `${feed.name}\n\n${feed.notes}`
68 | if (notify) {
69 | const open = notifyUpdateAvailable({ icon, detail })
70 | if (open) shell.openExternal('https://github.com/jhen0409/react-native-debugger/releases')
71 | } else if (
72 | process.env.NODE_ENV === 'production'
73 | && process.platform === 'darwin'
74 | && notifyUpdateAvailable({ icon, detail })
75 | ) {
76 | updater.download()
77 | console.log('[RNDebugger] Update downloading...')
78 | }
79 | checking = false
80 | })
81 |
82 | updater.on('update-downloaded', () => {
83 | console.log('[RNDebugger] Update downloaded')
84 | if (notifyUpdateDownloaded({ icon })) {
85 | updater.install()
86 | }
87 | })
88 | }
89 |
--------------------------------------------------------------------------------
/electron/url-handle/handleURL.js:
--------------------------------------------------------------------------------
1 | import { app } from 'electron'
2 | import net from 'net'
3 | import url from 'url'
4 | import qs from 'querystring'
5 | import fs from 'fs'
6 | import * as portfile from './port'
7 |
8 | const filterPaths = (list) => {
9 | const filteredList = list.filter((dir) => {
10 | try {
11 | return fs.lstatSync(dir).isDirectory()
12 | } catch (e) {
13 | return false
14 | }
15 | })
16 | if (!filteredList.length) {
17 | return
18 | }
19 | return filteredList
20 | }
21 |
22 | const resolveHost = (host) => (
23 | !host || host === 'undefined' || host === 'null' ? 'localhost' : host
24 | )
25 |
26 | export const parseUrl = (_url) => {
27 | const route = url.parse(_url)
28 | if (route.host !== 'set-debugger-loc') return
29 | const { host, port, projectRoots } = qs.parse(route.query)
30 | const query = {
31 | host: resolveHost(host),
32 | port: Number(port) || 8081,
33 | projectRoots: filterPaths(Array.isArray(projectRoots) ? projectRoots : [projectRoots]),
34 | }
35 | return query
36 | }
37 |
38 | export const handleURL = async (getWindow, path) => {
39 | const query = parseUrl(path)
40 | if (!query) {
41 | return
42 | }
43 | const payload = JSON.stringify(query)
44 |
45 | // This env will be get by new debugger window
46 | process.env.DEBUGGER_SETTING = payload
47 | const win = await getWindow(query.host, query.port)
48 | // if we can get the exists window, it will send the IPC event
49 | if (win) {
50 | win.webContents.send('set-debugger-loc', payload)
51 | }
52 | }
53 |
54 | const listenOpenURL = (getWindow) => app.on('open-url', (e, path) => {
55 | handleURL(getWindow, path)
56 | })
57 |
58 | const createHandleURLServer = (getWindow) => net
59 | .createServer((socket) => {
60 | socket.setEncoding('utf-8')
61 | socket.on('data', async (data) => {
62 | try {
63 | const obj = JSON.parse(data)
64 | if (typeof obj.path === 'string') {
65 | await handleURL(getWindow, obj.path)
66 | }
67 | socket.write('success')
68 | } catch (e) {
69 | socket.write('fail')
70 | } finally {
71 | socket.end()
72 | }
73 | })
74 | })
75 | .listen(0, '127.0.0.1')
76 | .on('listening', function server() {
77 | const { port } = this.address()
78 | portfile.write(port)
79 | portfile.watchExists(port)
80 | process.on('exit', () => portfile.unlink())
81 |
82 | console.log(`Starting listen set-debugger-loc request on port ${port}`)
83 | console.log('Will save port to `$HOME/.rndebugger_port` file')
84 | })
85 |
86 | export default (getWindow) => {
87 | // Handle set-debugger-loc for macOS
88 | // It's can be automatically open the app
89 | listenOpenURL(getWindow)
90 | // Handle set-debugger-loc for macOS/Linux/Windows
91 | createHandleURLServer(getWindow)
92 | }
93 |
--------------------------------------------------------------------------------
/electron/url-handle/index.js:
--------------------------------------------------------------------------------
1 | import startListeningHandleURL, { handleURL, parseUrl } from './handleURL'
2 | import * as port from './port'
3 |
4 | export {
5 | startListeningHandleURL, handleURL, parseUrl, port,
6 | }
7 |
--------------------------------------------------------------------------------
/electron/url-handle/port.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 |
4 | const homeEnv = process.platform === 'win32' ? 'USERPROFILE' : 'HOME'
5 | const portFile = path.join(process.env[homeEnv], '.rndebugger_port')
6 | let isWatching = false
7 |
8 | export const write = (port) => {
9 | fs.writeFileSync(portFile, String(port))
10 | }
11 |
12 | export function read() {
13 | if (!fs.existsSync(portFile)) return null
14 | return Number(fs.readFileSync(portFile, 'utf8'))
15 | }
16 |
17 | export const unlink = () => {
18 | if (fs.existsSync(portFile)) {
19 | fs.unlinkSync(portFile)
20 | }
21 | }
22 |
23 | export const watchExists = (port) => {
24 | if (isWatching) return
25 | isWatching = true
26 | fs.watchFile(portFile, (curr, prev) => {
27 | if (curr.mtime !== prev.mtime) write(port)
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/electron/window.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import {
3 | BrowserWindow, Menu, globalShortcut, dialog,
4 | } from 'electron'
5 | import Store from 'electron-store'
6 | import { enable } from '@electron/remote/main'
7 | import autoUpdate from './update'
8 | import { catchConsoleLogLink, removeUnecessaryTabs, activeTabs } from './devtools'
9 | import { selectRNDebuggerWorkerContext } from '../app/utils/devtools'
10 | import { readConfig, filePath as configFile } from './config'
11 | import { registerContextMenu } from './context-menu'
12 |
13 | const store = new Store()
14 |
15 | const executeJavaScript = (win, script) => win.webContents.executeJavaScript(script)
16 |
17 | export const checkWindowInfo = (win) => executeJavaScript(win, 'window.checkWindowInfo()')
18 |
19 | const checkIsOpenInEditorEnabled = (win) => executeJavaScript(win, 'window.isOpenInEditorEnabled()')
20 |
21 | const changeMenuItems = (menus) => {
22 | const rootMenuItems = Menu.getApplicationMenu().items
23 | Object.entries(menus).forEach(([key, subMenu]) => {
24 | const rootMenuItem = rootMenuItems.find(({ label }) => label === key)
25 | if (!rootMenuItem || !rootMenuItem.submenu) return
26 |
27 | Object.entries(subMenu).forEach(([subKey, menuSet]) => {
28 | const menuItem = rootMenuItem.submenu.items.find(
29 | ({ label }) => label === subKey,
30 | )
31 | if (!menuItem) return
32 |
33 | Object.assign(menuItem, menuSet)
34 | })
35 | })
36 | }
37 |
38 | const invokeDevMethod = (win, name) => executeJavaScript(
39 | win,
40 | `window.invokeDevMethod && window.invokeDevMethod('${name}')`,
41 | )
42 |
43 | const registerKeyboradShortcut = (win) => {
44 | const prefix = process.platform === 'darwin' ? 'Command' : 'Ctrl'
45 | // If another window focused, register a new shortcut
46 | if (
47 | globalShortcut.isRegistered(`${prefix}+R`)
48 | || globalShortcut.isRegistered(`${prefix}+I`)
49 | ) {
50 | globalShortcut.unregisterAll()
51 | }
52 | globalShortcut.register(`${prefix}+R`, () => invokeDevMethod(win, 'reload'))
53 | globalShortcut.register(`${prefix}+I`, () => invokeDevMethod(win, 'toggleElementInspector'))
54 | }
55 |
56 | const unregisterKeyboradShortcut = () => globalShortcut.unregisterAll()
57 |
58 | const registerShortcuts = async (win) => {
59 | registerKeyboradShortcut(win)
60 | changeMenuItems({
61 | Debugger: {
62 | 'Stay in Front': {
63 | checked: win.isAlwaysOnTop(),
64 | },
65 | 'Enable Open in Editor for Console Log': {
66 | checked: await checkIsOpenInEditorEnabled(win),
67 | },
68 | },
69 | })
70 | }
71 |
72 | const minSize = 100
73 | export const createWindow = ({ iconPath, isPortSettingRequired, port }) => {
74 | const { config, isConfigBroken, error } = readConfig()
75 |
76 | if (isConfigBroken) {
77 | dialog.showErrorBox(
78 | 'Root config error',
79 | `Parse root config failed, please checkout \`${configFile}\`, the error trace:\n\n`
80 | + `${error}\n\n`
81 | + 'RNDebugger will load default config instead. '
82 | + 'You can click `Debugger` -> `Open Config File` in application menu.',
83 | )
84 | }
85 |
86 | const winBounds = store.get('winBounds') || {}
87 | const increasePosition = BrowserWindow.getAllWindows().length * 10 || 0
88 | const {
89 | width, height, x = 0, y = 0,
90 | } = winBounds
91 | const win = new BrowserWindow({
92 | ...winBounds,
93 | width: width && width >= minSize ? width : 1024,
94 | height: height && height >= minSize ? height : 750,
95 | minWidth: minSize,
96 | minHeight: minSize,
97 | x: x + increasePosition,
98 | y: y + increasePosition,
99 | backgroundColor: '#272c37',
100 | tabbingIdentifier: 'rndebugger',
101 | webPreferences: {
102 | contextIsolation: false,
103 | nodeIntegration: true,
104 | // experimentalFeatures: true,
105 | // webSecurity: false,
106 | // webviewTag: true, // Use this for new inspector in the future
107 | },
108 | ...config.windowBounds,
109 | })
110 | enable(win.webContents)
111 |
112 | const isFirstWindow = BrowserWindow.getAllWindows().length === 1
113 |
114 | const { timesJSLoadToRefreshDevTools = -1 } = config
115 | win.debuggerConfig = {
116 | port,
117 | editor: config.editor,
118 | fontFamily: config.fontFamily,
119 | defaultReactDevToolsTheme: config.defaultReactDevToolsTheme,
120 | defaultReactDevToolsPort: config.defaultReactDevToolsPort,
121 | networkInspect: config.defaultNetworkInspect && 1,
122 | isPortSettingRequired: isPortSettingRequired && 1,
123 | timesJSLoadToRefreshDevTools,
124 | }
125 | win.loadURL(`file://${path.resolve(__dirname)}/app.html`)
126 | let unregisterContextMenu
127 | win.webContents.on('did-finish-load', () => {
128 | win.webContents.zoomLevel = config.zoomLevel || store.get('zoomLevel', 0)
129 | win.focus()
130 | unregisterContextMenu = registerContextMenu(win)
131 | registerShortcuts(win)
132 | if (!isPortSettingRequired) win.openDevTools()
133 | const checkUpdate = config.autoUpdate !== false
134 | if (checkUpdate && isFirstWindow) {
135 | autoUpdate(iconPath)
136 | }
137 | })
138 | win.webContents.on('devtools-opened', async () => {
139 | const { location } = await checkWindowInfo(win)
140 | activeTabs(win)
141 | catchConsoleLogLink(win, location.host, location.port)
142 | if (config.showAllDevToolsTab !== true) {
143 | removeUnecessaryTabs(win)
144 | }
145 | selectRNDebuggerWorkerContext(win)
146 | })
147 | win.on('show', () => {
148 | if (!win.isFocused()) return
149 | registerShortcuts(win)
150 | })
151 | win.on('focus', () => registerShortcuts(win))
152 | win.on('restore', () => registerShortcuts(win))
153 | win.on('hide', () => unregisterKeyboradShortcut())
154 | win.on('blur', () => unregisterKeyboradShortcut())
155 | win.on('minimize', () => unregisterKeyboradShortcut())
156 | win.close = async () => {
157 | unregisterKeyboradShortcut()
158 | store.set('winBounds', win.getBounds())
159 | store.set('zoomLevel', win.webContents.zoomLevel)
160 | await executeJavaScript(
161 | win,
162 | 'window.beforeWindowClose && window.beforeWindowClose()',
163 | )
164 | win.destroy()
165 | }
166 | win.on('close', (event) => {
167 | event.preventDefault()
168 | win.close()
169 | if (unregisterContextMenu) unregisterContextMenu()
170 | })
171 | return win
172 | }
173 |
--------------------------------------------------------------------------------
/examples/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "import/no-extraneous-dependencies": 0,
4 | "import/no-unresolved": 0
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
16 | # Temporary files created by Metro to check the health of the file watcher
17 | .metro-health-check*
18 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/App.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/style-prop-object */
2 | import { StatusBar } from 'expo-status-bar'
3 | import React from 'react'
4 | import { StyleSheet, View } from 'react-native'
5 | import ReduxApp from './examples/redux/App'
6 | import ApolloApp from './examples/apollo/App'
7 |
8 | const styles = StyleSheet.create({
9 | container: {
10 | flex: 1,
11 | backgroundColor: '#fff',
12 | },
13 | })
14 |
15 | export default function App() {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/README.md:
--------------------------------------------------------------------------------
1 | # test-old-bridge
2 |
3 | This is example created by `npx create-expo-app -t blank@48`, `"jsEngine": "jsc"` to `app.json`.
4 |
5 | The main purpose is for test functionality of React Native Debugger in old bridge.
6 |
7 | Currently the examples included
8 | - simple counter example for Redux
9 | - simple query example for Apollo Client
10 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "jsEngine": "jsc",
4 | "name": "test-old-bridge",
5 | "slug": "test-old-bridge",
6 | "version": "1.0.0",
7 | "orientation": "portrait",
8 | "icon": "./assets/icon.png",
9 | "userInterfaceStyle": "light",
10 | "splash": {
11 | "image": "./assets/splash.png",
12 | "resizeMode": "contain",
13 | "backgroundColor": "#ffffff"
14 | },
15 | "assetBundlePatterns": [
16 | "**/*"
17 | ],
18 | "ios": {
19 | "supportsTablet": true
20 | },
21 | "android": {
22 | "adaptiveIcon": {
23 | "foregroundImage": "./assets/adaptive-icon.png",
24 | "backgroundColor": "#ffffff"
25 | }
26 | },
27 | "web": {
28 | "favicon": "./assets/favicon.png"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/examples/test-old-bridge/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/examples/test-old-bridge/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/examples/test-old-bridge/assets/favicon.png
--------------------------------------------------------------------------------
/examples/test-old-bridge/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/examples/test-old-bridge/assets/icon.png
--------------------------------------------------------------------------------
/examples/test-old-bridge/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/examples/test-old-bridge/assets/splash.png
--------------------------------------------------------------------------------
/examples/test-old-bridge/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (api) => {
2 | api.cache(true)
3 | return {
4 | presets: ['babel-preset-expo'],
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/examples/apollo/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, Text, View } from 'react-native'
3 | import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'
4 | import SimpleQuery from './SimpleQuery'
5 |
6 | const styles = StyleSheet.create({
7 | container: {
8 | flex: 1,
9 | justifyContent: 'center',
10 | alignItems: 'center',
11 | backgroundColor: '#F5FCFF',
12 | },
13 | title: {
14 | marginBottom: 20,
15 | fontSize: 25,
16 | textAlign: 'center',
17 | fontWeight: 'bold',
18 | },
19 | })
20 |
21 | const client = new ApolloClient({
22 | uri: 'https://spacex-production.up.railway.app/',
23 | cache: new InMemoryCache(),
24 | })
25 |
26 | export default function App() {
27 | return (
28 |
29 |
30 | Apollo Client example
31 |
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/examples/apollo/SimpleQuery.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet, Text, Button } from 'react-native'
3 | import { useQuery } from '@apollo/client'
4 | import gql from 'graphql-tag'
5 |
6 | const styles = StyleSheet.create({
7 | text: {
8 | fontSize: 20,
9 | textAlign: 'center',
10 | margin: 10,
11 | },
12 | })
13 |
14 | const GET_DATA = gql`
15 | query ExampleQuery {
16 | company {
17 | name
18 | ceo
19 | employees
20 | }
21 | }
22 | `
23 |
24 | export default function SimpleQuery() {
25 | const { loading, error, data, refetch } = useQuery(GET_DATA)
26 |
27 | if (loading) return Loading...
28 | if (error) return Error :({error.message})
29 |
30 | return (
31 | <>
32 | Company: {data.company.name}
33 | CEO: {data.company.ceo}
34 | Employees: {data.company.employees}
35 | refetch()} title="Refetch" />
36 | >
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/examples/redux/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Provider } from 'react-redux'
3 | import { Counter } from './features/counter/Counter'
4 | import { store } from './app/store'
5 |
6 | export default function () {
7 | return (
8 |
9 |
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/examples/redux/app/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit'
2 | import counterReducer from '../features/counter/counterSlice'
3 |
4 | export const store = configureStore({
5 | reducer: {
6 | counter: counterReducer,
7 | },
8 | })
9 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/examples/redux/features/counter/Counter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | StyleSheet, View, Text, TouchableHighlight,
4 | } from 'react-native'
5 | import { useSelector, useDispatch } from 'react-redux'
6 | import {
7 | decrement,
8 | increment,
9 | incrementAsync,
10 | incrementIfOdd,
11 | selectCount,
12 | } from './counterSlice'
13 |
14 | const styles = StyleSheet.create({
15 | container: {
16 | flex: 1,
17 | justifyContent: 'center',
18 | alignItems: 'center',
19 | backgroundColor: '#F5FCFF',
20 | },
21 | title: {
22 | marginBottom: 20,
23 | fontSize: 25,
24 | textAlign: 'center',
25 | fontWeight: 'bold',
26 | },
27 | text: {
28 | fontSize: 20,
29 | textAlign: 'center',
30 | margin: 10,
31 | },
32 | })
33 |
34 | export function Counter() {
35 | const count = useSelector(selectCount)
36 | const dispatch = useDispatch()
37 | return (
38 |
39 | Redux example
40 |
41 | Clicked:
42 | {count}
43 | {' '}
44 | times
45 |
46 | dispatch(increment())}>
47 | +
48 |
49 | dispatch(decrement())}>
50 | -
51 |
52 | dispatch(incrementIfOdd(count))}>
53 | Increment if odd
54 |
55 | dispatch(incrementAsync())}>
56 | Increment async
57 |
58 |
59 | )
60 | }
61 |
62 | export default Counter
63 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/examples/redux/features/counter/counterAPI.js:
--------------------------------------------------------------------------------
1 | // A mock function to mimic making an async request for data
2 | export function fetchCount(amount = 1) {
3 | return new Promise((resolve) => {
4 | setTimeout(() => resolve({ data: amount }), 500)
5 | })
6 | }
7 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/examples/redux/features/counter/counterSlice.js:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
2 | import { fetchCount } from './counterAPI'
3 |
4 | const initialState = {
5 | value: 0,
6 | status: 'idle',
7 | }
8 |
9 | // The function below is called a thunk and allows us to perform async logic. It
10 | // can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
11 | // will call the thunk with the `dispatch` function as the first argument. Async
12 | // code can then be executed and other actions can be dispatched. Thunks are
13 | // typically used to make async requests.
14 | export const incrementAsync = createAsyncThunk(
15 | 'counter/fetchCount',
16 | async (amount) => {
17 | const response = await fetchCount(amount)
18 | // The value we return becomes the `fulfilled` action payload
19 | return response.data
20 | },
21 | )
22 |
23 | export const counterSlice = createSlice({
24 | name: 'counter',
25 | initialState,
26 | // The `reducers` field lets us define reducers and generate associated actions
27 | reducers: {
28 | increment: (state) => {
29 | // Redux Toolkit allows us to write "mutating" logic in reducers. It
30 | // doesn't actually mutate the state because it uses the Immer library,
31 | // which detects changes to a "draft state" and produces a brand new
32 | // immutable state based off those changes
33 | state.value += 1
34 | },
35 | decrement: (state) => {
36 | state.value -= 1
37 | },
38 | // Use the PayloadAction type to declare the contents of `action.payload`
39 | incrementByAmount: (state, action) => {
40 | state.value += action.payload
41 | },
42 | },
43 | // The `extraReducers` field lets the slice handle actions defined elsewhere,
44 | // including actions generated by createAsyncThunk or in other slices.
45 | extraReducers: (builder) => {
46 | builder
47 | .addCase(incrementAsync.pending, (state) => {
48 | state.status = 'loading'
49 | })
50 | .addCase(incrementAsync.fulfilled, (state, action) => {
51 | state.status = 'idle'
52 | state.value += action.payload
53 | })
54 | },
55 | })
56 |
57 | export const { increment, decrement, incrementByAmount } = counterSlice.actions
58 |
59 | // The function below is called a selector and allows us to select a value from
60 | // the state. Selectors can also be defined inline where they're used instead of
61 | // in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
62 | export const selectCount = (state) => state.counter.value
63 |
64 | // We can also write thunks by hand, which may contain both sync and async logic.
65 | // Here's an example of conditionally dispatching actions based on current state.
66 | export const incrementIfOdd = (amount) => (dispatch, getState) => {
67 | const currentValue = selectCount(getState())
68 | if (currentValue % 2 === 1) {
69 | dispatch(incrementByAmount(amount))
70 | }
71 | }
72 |
73 | export default counterSlice.reducer
74 |
--------------------------------------------------------------------------------
/examples/test-old-bridge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-old-bridge",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web",
10 | "postinstall": "rndebugger-open"
11 | },
12 | "dependencies": {
13 | "@apollo/client": "^3.7.17",
14 | "@reduxjs/toolkit": "^1.9.5",
15 | "expo": "~48.0.18",
16 | "expo-status-bar": "~1.4.4",
17 | "react": "18.2.0",
18 | "react-native": "0.71.8",
19 | "react-native-debugger-open": "^0.4.3",
20 | "react-redux": "^8.1.1",
21 | "redux": "^4.2.1"
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.20.0"
25 | },
26 | "private": true
27 | }
28 |
--------------------------------------------------------------------------------
/npm-package/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "global-require": "off"
4 | }
5 | }
--------------------------------------------------------------------------------
/npm-package/.gitignore:
--------------------------------------------------------------------------------
1 | lib/
2 |
--------------------------------------------------------------------------------
/npm-package/README.md:
--------------------------------------------------------------------------------
1 | # react-native-debugger-open [](https://www.npmjs.com/package/react-native-debugger-open)
2 |
3 | > Replace `open debugger-ui with Chrome` to `open React Native Debugger` from react-native packager
4 |
5 | __[macOS]__ If you opened the app before (registered URI scheme), you can use this patch open the app automatically.
6 | __[Linux / Windows]__ Currently it cannot open the app automatically, it just send `set-debugger-loc` request, so you need open the app yourself.
7 |
8 | ## Screenshot
9 |
10 | 
11 |
12 | > Demo with initial project of Create React Native App (Expo)
13 |
14 | ## Installation
15 |
16 | First, install [React Native Debugger](https://github.com/jhen0409/react-native-debugger#installation).
17 |
18 | In your React Native project:
19 |
20 | ```bash
21 | $ npm i --save-dev react-native-debugger-open # or -g
22 | ```
23 |
24 | ## Usage
25 |
26 | #### Inject to react-native packager
27 |
28 | Add command to your project's package.json:
29 |
30 | ```
31 | "scripts": {
32 | "postinstall": "rndebugger-open"
33 | }
34 | ```
35 |
36 | It will be run after `npm install`. (You can run `npm run postinstall` first)
37 | The `./node_modules/react-native/local-cli/server/middleware/getDevToolsMiddleware.js` code will be replaced.
38 |
39 | #### Use `REACT_DEBUGGER` env of react-native packager
40 |
41 | Instead of `Inject to react-native packager`, you can just do:
42 |
43 | ```bash
44 | $ REACT_DEBUGGER="rndebugger-open --open --port 8081" npm start
45 |
46 | # Windows
47 | $ set REACT_DEBUGGER="rndebugger-open --open --port 8081" && npm start
48 | ```
49 |
50 | If you're using Expo <= 48, use port 19000 instead of 8081.
51 |
52 | #### Options (--option)
53 |
54 | Name | Description
55 | ------------- | -------------
56 | `macos` | Use [react-native-macos](https://github.com/ptmt/react-native-macos) module name instead of react-native. Default is `false`
57 | `revert` | Revert rndebugger-open injection. Default is `false`
58 | `open` | Run open directly instead of inject patch
59 | `port` | Specified react-native packager port with `--open` option. Default is `8081`
60 |
61 | You can also [`Launch by CLI or React Native packager`](https://github.com/jhen0409/react-native-debugger/blob/master/docs/getting-started.md#launch-by-cli-or-react-native-packager-macos-only) instead of this package.
62 |
63 | ## LICENSE
64 |
65 | [MIT](https://github.com/jhen0409/react-native-debugger/blob/master/LICENSE.md)
66 |
--------------------------------------------------------------------------------
/npm-package/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (api) => {
2 | api.cache(true)
3 | return {
4 | presets: [['@babel/preset-env', { targets: { node: '12' } }]],
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/npm-package/bin/rndebugger-open.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | 'use strict'
4 |
5 | const defaultPort = 8081
6 |
7 | const argv = require('minimist')(process.argv.slice(2), {
8 | boolean: [
9 | // Inject / Revert code from react-native packager
10 | 'inject',
11 | 'revert',
12 | // Inject to react-native-desktop / react-native-macos package
13 | 'desktop',
14 | 'macos',
15 | // Open directly instead of Inject code
16 | 'open',
17 | ],
18 | string: ['port', 'host'],
19 | default: {
20 | inject: true,
21 | },
22 | })
23 |
24 | argv.port = Number(argv.port) || defaultPort
25 |
26 | let mod
27 | if (argv.open && (argv.port || argv.host)) {
28 | mod = require('../lib/open')
29 | } else {
30 | mod = require('../lib/main')
31 | }
32 |
33 | mod.default(argv, (pass, dontError) => {
34 | if (!pass && !dontError) process.exit(1)
35 | })
36 |
--------------------------------------------------------------------------------
/npm-package/lib/injectDevToolsMiddleware.tmpl.js:
--------------------------------------------------------------------------------
1 | ${startFlag}
2 | var __fs = require('fs');
3 | var __path = require('path');
4 | var __net = require('net');
5 | var __childProcess = require('child_process');
6 | var __home_env = process.platform === 'win32' ? 'USERPROFILE' : 'HOME';
7 | var __port_file = __path.join(process.env[__home_env], '.rndebugger_port');
8 |
9 | function __connectToRND(rndPath, log, cb) {
10 | var __port;
11 | try {
12 | __port = __fs.readFileSync(__port_file, 'utf-8');
13 | } catch (e) {
14 | log && console.log(
15 | '\n[RNDebugger] The port file `$HOME/.rndebugger_port` not found\n' +
16 | 'Maybe the React Native Debugger (^0.3) is not open?\n' +
17 | '(Please visit https://github.com/jhen0409/react-native-debugger#installation)\n'
18 | );
19 | return cb(false);
20 | }
21 | var __c = __net.createConnection({ host: '127.0.0.1', port: __port }, () => {
22 | let pass = false;
23 | __c.setEncoding('utf-8');
24 | __c.write(JSON.stringify({ path: rndPath }));
25 | __c.on('data', data => {
26 | pass = data === 'success';
27 | __c.end();
28 | });
29 | const __timeoutId = setTimeout(() => {
30 | log && console.log(
31 | '\n[RNDebugger] Cannot connect to port ' + __port + '.\n'
32 | );
33 | __c.end();
34 | }, 1000);
35 | __c.on('end', () => {
36 | clearTimeout(__timeoutId);
37 | !pass && log && console.log(
38 | '\n[RNDebugger] Try to set port of React Native server failed.\n'
39 | );
40 | cb(pass);
41 | });
42 |
43 | });
44 | }
45 |
46 | var __rndebuggerIsOpening = false;
47 | ${replaceFuncFlag}
48 | var __rnd_path = 'rndebugger://set-debugger-loc?host=' + ${args};
49 |
50 | if (__rndebuggerIsOpening) return;
51 | __rndebuggerIsOpening = true;
52 | if (process.platform === 'darwin' && !skipRNDebugger) {
53 | var __env = Object.assign({}, process.env);
54 | // This env is specified from Expo (and CRNA), we need avoid it included in rndebugger
55 | delete __env.ELECTRON_RUN_AS_NODE;
56 | __childProcess
57 | .spawn('open', ['-g', '-a', 'React Native Debugger', __rnd_path], { env: __env })
58 | .once('close', code => {
59 | if (code > 0) {
60 | __connectToRND(__rnd_path, false, pass => {
61 | if (!pass) {
62 | console.log(
63 | '\n[RNDebugger] Cannot open the app, maybe not install?\n' +
64 | '(Please visit https://github.com/jhen0409/react-native-debugger#installation)\n' +
65 | 'Or it\'s never started. (Not registered URI Scheme)\n'
66 | );
67 | }
68 | __rndebuggerIsOpening = false;
69 | !pass && ${keyFunc}${funcCall};
70 | });
71 | } else {
72 | __rndebuggerIsOpening = false;
73 | }
74 | })
75 | return;
76 | } else if (!skipRNDebugger) {
77 | __connectToRND(__rnd_path, true, pass => {
78 | __rndebuggerIsOpening = false;
79 | !pass && ${keyFunc}${funcCall};
80 | });
81 | return;
82 | }
83 | __rndebuggerIsOpening = false;
84 | ${endFlag}
85 |
--------------------------------------------------------------------------------
/npm-package/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-debugger-open",
3 | "version": "0.4.3",
4 | "description": "Replace `open debugger-ui with Chrome` to `open React Native Debugger` from react-native packager",
5 | "bin": {
6 | "rndebugger-open": "bin/rndebugger-open.js"
7 | },
8 | "main": "lib/open.js",
9 | "scripts": {
10 | "build": "babel src --out-dir lib --ignore **/__tests__",
11 | "prepublish": "npm run build"
12 | },
13 | "files": [
14 | "bin",
15 | "lib"
16 | ],
17 | "homepage": "https://github.com/jhen0409/react-native-debugger",
18 | "repository": "https://github.com/jhen0409/react-native-debugger/tree/master/npm-package",
19 | "keywords": [
20 | "react",
21 | "react-native",
22 | "debugger",
23 | "react-devtools",
24 | "redux-devtools",
25 | "electron"
26 | ],
27 | "author": "Jhen ",
28 | "license": "MIT",
29 | "dependencies": {
30 | "chalk": "^1.1.3",
31 | "es6-template": "^1.0.4",
32 | "minimist": "^1.2.0",
33 | "semver": "^5.4.1"
34 | },
35 | "devDependencies": {
36 | "@babel/cli": "^7.22.9",
37 | "fs-extra": "^4.0.2",
38 | "node-fetch": "^2.6.1"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/npm-package/src/main.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import cp from 'child_process'
3 | import path from 'path'
4 | import chalk from 'chalk'
5 | import { inject as injectMiddleware, revert as revertMiddleware } from './injectDevToolsMiddleware'
6 |
7 | const modulePath = path.join(process.cwd(), 'node_modules')
8 |
9 | const log = (pass, msg) => {
10 | const prefix = pass ? chalk.green.bgBlack('PASS') : chalk.red.bgBlack('FAIL')
11 | const color = pass ? chalk.blue : chalk.red
12 | console.log(prefix, color(msg))
13 | }
14 |
15 | export default (argv, cb) => {
16 | let moduleName
17 | if (argv.macos) {
18 | moduleName = 'react-native-macos'
19 | } else {
20 | moduleName = 'react-native'
21 | }
22 |
23 | // Revert injection
24 | if (argv.revert) {
25 | const passMiddleware = revertMiddleware(modulePath, moduleName)
26 | const msg = 'Revert injection of React Native Debugger from React Native packager'
27 | log(passMiddleware, msg + (!passMiddleware ? ', the file inject file not found.' : '.'))
28 | return cb(passMiddleware)
29 | }
30 |
31 | const inject = () => {
32 | const pass = injectMiddleware(modulePath, moduleName)
33 | const msg = 'Replace `open debugger-ui with Chrome` to `open React Native Debugger`'
34 | log(pass, msg + (pass ? '.' : ', the file inject file not found.'))
35 | cb(pass)
36 | }
37 |
38 | if (process.platform !== 'darwin') {
39 | inject()
40 | } else {
41 | const cwd = '/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/'; // eslint-disable-line
42 | const lsregisterPath = 'lsregister'
43 | if (!fs.existsSync(cwd + lsregisterPath)) return inject()
44 |
45 | cp.exec(`./${lsregisterPath} -dump | grep rndebugger:`, { cwd }, (err, stdout) => {
46 | if (stdout.length === 0) {
47 | log(
48 | false,
49 | '[RNDebugger] The `rndebugger://` URI scheme seems not registered, '
50 | + "maybe you haven't install the app? "
51 | + 'Run `brew update && brew install --cask react-native-debugger` '
52 | + 'or download from https://github.com/jhen0409/react-native-debugger/releases '
53 | + 'then open it to register the URI scheme.',
54 | )
55 | }
56 | inject()
57 | })
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/npm-package/src/open.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import net from 'net'
4 | import childProcess from 'child_process'
5 |
6 | const homeEnv = process.platform === 'win32' ? 'USERPROFILE' : 'HOME'
7 | const portFile = path.join(process.env[homeEnv], '.rndebugger_port')
8 |
9 | function connectToRND(rndPath, log, cb) {
10 | let port
11 | try {
12 | port = fs.readFileSync(portFile, 'utf-8')
13 | } catch (e) {
14 | if (log) {
15 | console.log(
16 | '\n[RNDebugger] The port file `$HOME/.rndebugger_port` not found\n'
17 | + 'Maybe the React Native Debugger (^0.3) is not open?\n'
18 | + '(Please visit https://github.com/jhen0409/react-native-debugger#installation)\n',
19 | )
20 | }
21 | return cb(false)
22 | }
23 | const connection = net.createConnection({ host: '127.0.0.1', port }, () => {
24 | let pass = false
25 | connection.setEncoding('utf-8')
26 | connection.write(JSON.stringify({ path: rndPath }))
27 | connection.on('data', (data) => {
28 | pass = data === 'success'
29 | connection.end()
30 | })
31 | const timeoutId = setTimeout(() => {
32 | if (log) {
33 | console.log(`\n[RNDebugger] Cannot connect to server with port ${port}.\n`)
34 | }
35 | connection.end()
36 | }, 1000)
37 | connection.on('end', () => {
38 | clearTimeout(timeoutId)
39 | if (!pass && log) {
40 | console.log('\n[RNDebugger] Try to set port of React Native packager failed.\n')
41 | }
42 | cb(pass)
43 | })
44 | })
45 | }
46 |
47 | export default ({ port, host = 'localhost' }, cb) => {
48 | const rndPath = `rndebugger://set-debugger-loc?host=${host}&port=${port}`
49 |
50 | if (process.platform === 'darwin') {
51 | const env = { ...process.env }
52 | // This env is specified from Expo (and CRNA), we need avoid it included in rndebugger
53 | delete env.ELECTRON_RUN_AS_NODE
54 | childProcess
55 | .spawn('open', ['-g', '-a', 'React Native Debugger', rndPath], { env })
56 | .once('close', (code) => {
57 | if (code > 0) {
58 | connectToRND(rndPath, false, (pass) => {
59 | if (!pass) {
60 | console.log(
61 | "\n[RNDebugger] Cannot open the app, maybe you haven't install the app?\n"
62 | + 'Run `brew update && brew cask install react-native-debugger` '
63 | + 'or download from https://github.com/jhen0409/react-native-debugger/releases\n',
64 | )
65 | }
66 | cb(pass, true)
67 | })
68 | } else {
69 | cb(true)
70 | }
71 | })
72 | } else {
73 | connectToRND(rndPath, true, (pass) => {
74 | cb(pass, true)
75 | })
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-debugger",
3 | "version": "0.14.0",
4 | "productName": "React Native Debugger In Dev",
5 | "description": "The standalone app for React Native Debugger, with React DevTools / Redux DevTools",
6 | "main": "electron/main.js",
7 | "scripts": {
8 | "pack-macos": "./scripts/package-macos.sh",
9 | "pack-linux": "./scripts/package-linux.sh",
10 | "pack-windows": "sh ./scripts/package-windows.sh",
11 | "pack": "yarn build && ./scripts/package-linux.sh && ./scripts/package-windows.sh && ./scripts/package-macos.sh",
12 | "start": "cd dist && cross-env NODE_ENV=production PACKAGE=no electron ./",
13 | "build:main": "cross-env NODE_ENV=production webpack --config webpack/main.prod.js --progress --profile",
14 | "build:renderer": "cross-env NODE_ENV=production webpack --config webpack/renderer.prod.js --progress --profile",
15 | "build": "yarn build:main && yarn build:renderer",
16 | "dev:webpack": "webpack-dev-server --config webpack/renderer.dev.js --port 3000 --allowed-hosts all",
17 | "dev:electron": "cross-env NODE_ENV=development electron -r @babel/register -r ./electron/debug .",
18 | "lint": "eslint .",
19 | "test": "yarn lint && jest __tests__",
20 | "test-e2e": "jest __e2e__ --runInBand --forceExit",
21 | "postinstall": "patch-package && node ./scripts/postinstall.js"
22 | },
23 | "repository": {
24 | "type": "git",
25 | "url": "git+https://github.com/jhen0409/react-native-debugger.git"
26 | },
27 | "author": "Jhen ",
28 | "license": "MIT",
29 | "devDependencies": {
30 | "@apollo/client": "^3.7.17",
31 | "@babel/core": "^7.22.9",
32 | "@babel/eslint-parser": "^7.22.9",
33 | "@babel/plugin-transform-react-constant-elements": "^7.22.5",
34 | "@babel/plugin-transform-react-inline-elements": "^7.22.5",
35 | "@babel/preset-env": "^7.22.9",
36 | "@babel/preset-react": "^7.22.5",
37 | "@babel/register": "^7.22.5",
38 | "@electron/notarize": "^2.1.0",
39 | "@electron/osx-sign": "^1.0.4",
40 | "@electron/universal": "^1.4.1",
41 | "babel-loader": "^9.1.3",
42 | "babel-plugin-transform-react-remove-prop-types": "^0.4.10",
43 | "cross-env": "^5.2.0",
44 | "css-loader": "^6.8.1",
45 | "electron": "^25.3.0",
46 | "electron-debug": "^3.2.0",
47 | "electron-installer-dmg": "^4.0.0",
48 | "electron-installer-windows": "^3.0.0",
49 | "electron-packager": "^17.1.1",
50 | "eslint": "^8.45.0",
51 | "eslint-config-airbnb": "^19.0.4",
52 | "eslint-config-prettier": "^8.8.0",
53 | "eslint-plugin-import": "^2.27.5",
54 | "eslint-plugin-jsx-a11y": "^6.7.1",
55 | "eslint-plugin-react": "^7.33.0",
56 | "jest": "^29.6.1",
57 | "mobx": "^3.6.2",
58 | "mobx-remotedev": "^0.2.8",
59 | "patch-package": "^6.2.2",
60 | "playwright-core": "^1.36.1",
61 | "shelljs": "^0.8.5",
62 | "style-loader": "^3.3.3",
63 | "terser-webpack-plugin": "^5.3.9",
64 | "wait-for-expect": "^3.0.2",
65 | "webpack": "^5.88.2",
66 | "webpack-bundle-analyzer": "^4.9.0",
67 | "webpack-cli": "^5.1.4",
68 | "webpack-dev-server": "^4.15.1",
69 | "whatwg-fetch": "^3.6.17",
70 | "ws": "^7.5.9"
71 | },
72 | "dependencies": {
73 | "@electron/remote": "^2.0.10",
74 | "@redux-devtools/app": "^2.2.1",
75 | "@redux-devtools/core": "^3.13.1",
76 | "@redux-devtools/instrument": "^2.1.0",
77 | "@redux-devtools/ui": "^1.3.0",
78 | "@redux-devtools/utils": "^2.0.1",
79 | "adbkit": "^2.11.0",
80 | "apollo-client-devtools": "^4.1.4",
81 | "electron-context-menu": "^3.6.1",
82 | "electron-fetch": "^1.2.1",
83 | "electron-gh-releases": "^2.0.4",
84 | "electron-store": "^1.2.0",
85 | "header-case-normalizer": "^1.0.3",
86 | "jsan": "^3.1.9",
87 | "json5": "^0.5.1",
88 | "localforage": "^1.10.0",
89 | "multiline-template": "^0.1.2",
90 | "portscanner": "^2.1.1",
91 | "prop-types": "^15.8.1",
92 | "react": "^18.2.0",
93 | "react-dev-utils": "^4.2.1",
94 | "react-devtools-core": "^4.28.0",
95 | "react-dom": "^18.2.0",
96 | "react-icons": "^4.10.1",
97 | "react-redux": "^8.1.1",
98 | "redux": "^4.2.1",
99 | "redux-persist": "^6.0.0",
100 | "styled-components": "^6.0.5"
101 | },
102 | "optionalDependencies": {
103 | "electron-installer-debian": "^2.0.1",
104 | "electron-installer-redhat": "^2.0.0"
105 | },
106 | "resolutions": {
107 | "ws": "^7.5.9",
108 | "react": "^18.2.0",
109 | "react-dom": "^18.2.0"
110 | },
111 | "jest": {
112 | "testEnvironment": "node"
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/patches/@redux-devtools+inspector-monitor-trace-tab+1.0.0.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@redux-devtools/inspector-monitor-trace-tab/src/openFile.ts b/node_modules/@redux-devtools/inspector-monitor-trace-tab/src/openFile.ts
2 | index d3cfdc9..9aeafeb 100644
3 | --- a/node_modules/@redux-devtools/inspector-monitor-trace-tab/src/openFile.ts
4 | +++ b/node_modules/@redux-devtools/inspector-monitor-trace-tab/src/openFile.ts
5 | @@ -101,41 +101,5 @@ export default function openFile(
6 | lineNumber: number,
7 | stackFrame: StackFrame
8 | ) {
9 | - if (!chrome || !chrome.storage) return; // TODO: Pass editor settings for using outside of browser extension
10 | - const storage = isFF
11 | - ? chrome.storage.local
12 | - : chrome.storage.sync || chrome.storage.local;
13 | - storage.get(
14 | - ['useEditor', 'editor', 'projectPath'],
15 | - function ({ useEditor, editor, projectPath }) {
16 | - if (
17 | - useEditor &&
18 | - projectPath &&
19 | - typeof editor === 'string' &&
20 | - /^\w{1,30}$/.test(editor)
21 | - ) {
22 | - openInEditor(editor.toLowerCase(), projectPath as string, stackFrame);
23 | - } else {
24 | - if (
25 | - chrome.devtools &&
26 | - chrome.devtools.panels &&
27 | - !!chrome.devtools.panels.openResource
28 | - ) {
29 | - openResource(fileName, lineNumber, stackFrame);
30 | - } else if (chrome.runtime && (chrome.runtime.openOptionsPage || isFF)) {
31 | - if (chrome.devtools && isFF) {
32 | - chrome.devtools.inspectedWindow.eval(
33 | - 'confirm("Set the editor to open the file in?")',
34 | - (result) => {
35 | - if (!result) return;
36 | - void chrome.runtime.sendMessage({ type: 'OPEN_OPTIONS' });
37 | - }
38 | - );
39 | - } else if (confirm('Set the editor to open the file in?')) {
40 | - chrome.runtime.openOptionsPage();
41 | - }
42 | - }
43 | - }
44 | - }
45 | - );
46 | + window.openInEditor && window.openInEditor(fileName, lineNumber)
47 | }
48 |
--------------------------------------------------------------------------------
/patches/@redux-devtools+ui+1.3.0.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@redux-devtools/ui/lib/esm/Tabs/Tabs.js b/node_modules/@redux-devtools/ui/lib/esm/Tabs/Tabs.js
2 | index 5454804..df65ca0 100644
3 | --- a/node_modules/@redux-devtools/ui/lib/esm/Tabs/Tabs.js
4 | +++ b/node_modules/@redux-devtools/ui/lib/esm/Tabs/Tabs.js
5 | @@ -88,12 +88,14 @@ var Tabs = /*#__PURE__*/function (_Component) {
6 |
7 | if (!this.SelectedComponent) {
8 | return /*#__PURE__*/React.createElement(TabsContainer, {
9 | - position: this.props.position
10 | + position: this.props.position,
11 | + style: this.props.style,
12 | }, tabsHeader);
13 | }
14 |
15 | return /*#__PURE__*/React.createElement(TabsContainer, {
16 | - position: this.props.position
17 | + position: this.props.position,
18 | + style: this.props.style,
19 | }, tabsHeader, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(this.SelectedComponent, this.selector && this.selector())));
20 | }
21 | }]);
22 |
--------------------------------------------------------------------------------
/patches/electron-gh-releases+2.0.4.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/electron-gh-releases/GhReleases.js b/node_modules/electron-gh-releases/GhReleases.js
2 | index cabe3b9..5ce1e2c 100644
3 | --- a/node_modules/electron-gh-releases/GhReleases.js
4 | +++ b/node_modules/electron-gh-releases/GhReleases.js
5 | @@ -103,7 +103,7 @@ var GhReleases = function (_events$EventEmitter) {
6 | }
7 |
8 | // On Mac we need to use the `auto_updater.json`
9 | - feedUrl = 'https://raw.githubusercontent.com/' + this.repo + '/master/auto_updater.json';
10 | + feedUrl = 'https://raw.githubusercontent.com/' + this.repo + '/master/auto_update.json';
11 |
12 | // Make sure feedUrl exists
13 | return got.get(feedUrl).then(function (res) {
14 |
--------------------------------------------------------------------------------
/patches/react-dev-utils+4.2.3.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/react-dev-utils/launchEditor.js b/node_modules/react-dev-utils/launchEditor.js
2 | index 3bb1d55..b78289e 100644
3 | --- a/node_modules/react-dev-utils/launchEditor.js
4 | +++ b/node_modules/react-dev-utils/launchEditor.js
5 | @@ -10,7 +10,7 @@ const fs = require('fs');
6 | const path = require('path');
7 | const child_process = require('child_process');
8 | const os = require('os');
9 | -const chalk = require('chalk');
10 | +const chalk = {red:f=>f,cyan:f=>f,green:f=>f};
11 | const shellQuote = require('shell-quote');
12 |
13 | function isTerminalEditor(editor) {
14 |
--------------------------------------------------------------------------------
/scripts/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "dest": "release/",
3 | "icon": "electron/logo.ico",
4 | "categories": ["Utility"],
5 | "lintianOverrides": ["changelog-file-missing-in-native-package"],
6 | "mimeType": ["x-scheme-handler/rndebugger"]
7 | }
8 |
--------------------------------------------------------------------------------
/scripts/mac/createDMG.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const createDMG = require('electron-installer-dmg')
3 | const pkg = require('../../package.json')
4 |
5 | const appPath = path.join(__dirname, '../../release/React Native Debugger.app')
6 |
7 | createDMG(
8 | {
9 | appPath,
10 | name: 'React Native Debugger',
11 | title: 'React Native Debugger',
12 | // https://github.com/sindresorhus/create-dmg/tree/master/assets
13 | background: path.join(__dirname, 'dmg-background.png'),
14 | icon: path.join(__dirname, '../../electron/logo.icns'),
15 | overwrite: true,
16 | contents: [
17 | {
18 | x: 180,
19 | y: 170,
20 | type: 'file',
21 | path: appPath,
22 | },
23 | {
24 | x: 480,
25 | y: 170,
26 | type: 'link',
27 | path: '/Applications',
28 | },
29 | ],
30 | dmgPath: path.join(
31 | __dirname,
32 | `../../release/react-native-debugger_${pkg.version}_universal.dmg`,
33 | ),
34 | },
35 | (err) => {
36 | if (err) console.log(err)
37 | },
38 | )
39 |
--------------------------------------------------------------------------------
/scripts/mac/createUniversalApp.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const { version: electronVersion } = require('electron/package.json')
3 | const { makeUniversalApp } = require('@electron/universal')
4 | const { signAsync } = require('@electron/osx-sign')
5 | const { notarize } = require('@electron/notarize')
6 |
7 | const isNotarizeNeeded = process.argv.includes('--notarize')
8 |
9 | const developerId = `${process.env.APPLE_DEVELOPER_NAME} (${process.env.APPLE_TEAM_ID})`
10 |
11 | async function run() {
12 | const appPath = path.join(
13 | __dirname,
14 | '../../release/React Native Debugger.app',
15 | )
16 | const x64AppPath = path.join(
17 | __dirname,
18 | '../../release/React Native Debugger-darwin-x64/React Native Debugger.app',
19 | )
20 | const arm64AppPath = path.join(
21 | __dirname,
22 | '../../release/React Native Debugger-darwin-arm64/React Native Debugger.app',
23 | )
24 | await makeUniversalApp({
25 | force: true,
26 | x64AppPath,
27 | arm64AppPath,
28 | outAppPath: appPath,
29 | })
30 |
31 | if (!isNotarizeNeeded) return
32 |
33 | const pathes = [appPath, x64AppPath, arm64AppPath]
34 | await pathes.reduce(async (promise, p) => {
35 | await promise
36 | try {
37 | await signAsync({
38 | app: p,
39 | identity: `Developer ID Application: ${developerId}`,
40 | optionsForFile: () => ({
41 | hardenedRuntime: true,
42 | entitlements: 'scripts/mac/entitlements.plist',
43 | }),
44 | platform: 'darwin',
45 | version: electronVersion,
46 | })
47 | await notarize({
48 | tool: 'notarytool',
49 | // xcrun notarytool store-credentials "AC_PASSWORD"
50 | // --apple-id "xxx" --team-id "xxx" --password ""
51 | keychainProfile: 'AC_PASSWORD',
52 | appPath: p,
53 | })
54 | } catch (e) {
55 | console.log(e)
56 | }
57 | }, Promise.resolve())
58 | }
59 |
60 | run()
61 |
--------------------------------------------------------------------------------
/scripts/mac/dmg-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/scripts/mac/dmg-background.png
--------------------------------------------------------------------------------
/scripts/mac/dmg-background@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jhen0409/react-native-debugger/bd3435a456a29e1eda017ad37b94451834b4ee3a/scripts/mac/dmg-background@2x.png
--------------------------------------------------------------------------------
/scripts/mac/entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-jit
6 | com.apple.security.cs.allow-unsigned-executable-memory
7 | com.apple.security.cs.disable-library-validation
8 | com.apple.security.cs.disable-executable-page-protection
9 |
10 |
--------------------------------------------------------------------------------
/scripts/package-linux.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PACKAGE_VERSION=$(node -e "console.log(require('./package.json').version)")
4 |
5 | echo "[v$PACKAGE_VERSION] Packaging linux x64..."
6 |
7 | # cp apollo-client-devtools/build to ac-devtools-ext
8 | cp -r dist/node_modules/apollo-client-devtools/build dist/node_modules/apollo-client-devtools/ac-devtools-ext-build
9 |
10 | electron-packager dist/ \
11 | --executable-name "react-native-debugger" \
12 | --overwrite \
13 | --platform linux \
14 | --arch x64 \
15 | --asar \
16 | --extra-resource=dist/devtools-helper \
17 | --extra-resource=dist/node_modules/apollo-client-devtools/ac-devtools-ext-build \
18 | --prune \
19 | --out release \
20 | --electron-version $(node -e "console.log(require('electron/package').version)") \
21 | --app-version $PACKAGE_VERSION
22 |
23 | electron-installer-debian --src release/React\ Native\ Debugger-linux-x64 --dest release/ --arch amd64 --config scripts/config.json
24 | electron-installer-redhat --src release/React\ Native\ Debugger-linux-x64 --dest release/ --arch amd64 --config scripts/config.json
25 |
26 | cd release/React\ Native\ Debugger-linux-x64
27 | zip -ryq9 ../rn-debugger-linux-x64.zip *
28 |
--------------------------------------------------------------------------------
/scripts/package-macos.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PACKAGE_VERSION=$(node -e "console.log(require('./package.json').version)")
4 |
5 | echo "[v$PACKAGE_VERSION] Packaging darwin x64..."
6 |
7 | # Check arg no-notarize
8 | if [ "$1" == "--notarize" ]; then
9 | NOTARIZE=1
10 | fi
11 |
12 | if [ "$NOTARIZE" == "1" ]; then
13 | if [ -z "$APPLE_DEVELOPER_NAME" ]; then
14 | echo -e "Apple Developer Name: \c"
15 | read APPLE_DEVELOPER_NAME
16 | fi
17 |
18 | if [ -z "$APPLE_TEAM_ID" ]; then
19 | echo -e "Apple Team ID: \c"
20 | read APPLE_TEAM_ID
21 | fi
22 | fi
23 |
24 | # cp apollo-client-devtools/build to ac-devtools-ext
25 | cp -r dist/node_modules/apollo-client-devtools/build dist/node_modules/apollo-client-devtools/ac-devtools-ext-build
26 |
27 | function build_with_arch() {
28 | electron-packager dist/ \
29 | --overwrite \
30 | --platform darwin \
31 | --arch $1 \
32 | --asar \
33 | --extra-resource=dist/devtools-helper \
34 | --extra-resource=dist/node_modules/apollo-client-devtools/ac-devtools-ext-build \
35 | --prune \
36 | --out release \
37 | --protocol-name "React Native Debugger" \
38 | --protocol "rndebugger" \
39 | --electron-version $(node -e "console.log(require('electron/package').version)") \
40 | --app-version $PACKAGE_VERSION \
41 | --icon electron/logo.icns \
42 | --darwin-dark-mode-support
43 | }
44 |
45 | build_with_arch x64
46 | build_with_arch arm64
47 |
48 | if [ "$NOTARIZE" == "1" ]; then
49 | node scripts/mac/createUniversalApp.js --notarize
50 | else
51 | node scripts/mac/createUniversalApp.js
52 | fi
53 |
54 | node scripts/mac/createDMG.js
55 |
56 | cd release
57 | ditto -c -k --keepParent React\ Native\ Debugger.app rn-debugger-macos-universal.zip
58 | cd React\ Native\ Debugger-darwin-arm64
59 | ditto -c -k --keepParent React\ Native\ Debugger.app ../rn-debugger-macos-arm64.zip
60 | cd ../React\ Native\ Debugger-darwin-x64
61 | ditto -c -k --keepParent React\ Native\ Debugger.app ../rn-debugger-macos-x64.zip
62 | cd ..
63 |
64 | # Print codesign information
65 | codesign -dv --verbose=4 React\ Native\ Debugger.app
66 |
67 | echo sha256: `shasum -a 256 rn-debugger-macos-universal.zip`
68 |
--------------------------------------------------------------------------------
/scripts/package-windows.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PACKAGE_VERSION=$(node -e "console.log(require('./package.json').version)")
4 |
5 | echo "[v$PACKAGE_VERSION] Packaging win32..."
6 |
7 | # cp apollo-client-devtools/build to ac-devtools-ext
8 | cp -r dist/node_modules/apollo-client-devtools/build dist/node_modules/apollo-client-devtools/ac-devtools-ext-build
9 |
10 | electron-packager dist/ \
11 | --executable-name "react-native-debugger" \
12 | --overwrite \
13 | --platform win32 \
14 | --arch x64 \
15 | --asar \
16 | --extra-resource=dist/devtools-helper \
17 | --extra-resource=dist/node_modules/apollo-client-devtools/ac-devtools-ext-build \
18 | --prune \
19 | --out release \
20 | --electron-version $(node -e "console.log(require('electron/package').version)") \
21 | --app-version $PACKAGE_VERSION \
22 | --icon electron/logo.ico
23 |
24 | electron-installer-windows --src release/React\ Native\ Debugger-win32-x64 --dest release/ --config scripts/config.json
25 |
26 | cd release/React\ Native\ Debugger-win32-x64
27 | npx bestzip ../rn-debugger-windows-x64.zip *
28 |
--------------------------------------------------------------------------------
/scripts/patch-modules.js:
--------------------------------------------------------------------------------
1 | const shell = require('shelljs')
2 | const path = require('path')
3 |
4 | console.log('Patch react-devtools-core')
5 |
6 | const rdStandalone = path.join(
7 | __dirname,
8 | '../dist/node_modules/react-devtools-core/dist/standalone.js',
9 | )
10 |
11 | // Avoid source map not found warning
12 | shell.sed(
13 | '-i',
14 | /sourceMappingURL=importFile\.worker\.worker\.js\.map'\]\)\),\{name:"\[name\]\.worker\.js/g,
15 | `sourceMappingURL_NotUsed=importFile.worker.worker.js.map'])),{name:"ReactDevToolsImportFile.worker.js`,
16 | rdStandalone,
17 | )
18 |
--------------------------------------------------------------------------------
/scripts/postinstall.js:
--------------------------------------------------------------------------------
1 | const shell = require('shelljs')
2 |
3 | async function run() {
4 | shell.cd('npm-package')
5 | shell.exec('yarn')
6 | shell.cd('-')
7 | shell.cd('dist')
8 | shell.exec('yarn')
9 | shell.rm(
10 | '-rf',
11 | 'node_modules/*/{example,examples,test,tests,*.md,*.markdown,CHANGELOG*,.*,Makefile}',
12 | )
13 | // Remove unnecessary files in apollo-client-devtools
14 | shell.rm(
15 | '-rf',
16 | 'node_modules/apollo-client-devtools/{assets,development/dev,src}',
17 | )
18 | // eslint-disable-next-line
19 | require('./patch-modules')
20 | }
21 |
22 | run()
23 |
--------------------------------------------------------------------------------
/webpack/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "import/no-extraneous-dependencies": 0
4 | }
5 | }
--------------------------------------------------------------------------------
/webpack/base.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const electronPkg = require('electron/package.json')
3 | const babelConfig = require('../babel.config')({ cache: () => {} })
4 |
5 | // Webpack 2 have native import / export support
6 | babelConfig.presets = [
7 | [
8 | '@babel/preset-env',
9 | {
10 | targets: { electron: electronPkg.version },
11 | modules: false,
12 | },
13 | ],
14 | '@babel/preset-react',
15 | ]
16 | babelConfig.babelrc = false
17 |
18 | module.exports = {
19 | output: {
20 | path: path.join(__dirname, '../dist/js'),
21 | filename: 'bundle.js',
22 | libraryTarget: 'commonjs2',
23 | },
24 | plugins: [],
25 | resolve: {
26 | extensions: ['.js'],
27 | alias: {},
28 | },
29 | module: {
30 | rules: [
31 | {
32 | test: /\.js$/,
33 | use: [
34 | {
35 | loader: 'babel-loader',
36 | options: babelConfig,
37 | },
38 | ],
39 | exclude: /node_modules/,
40 | },
41 | ],
42 | },
43 | externals: [
44 | 'react-devtools-core/standalone',
45 | // https://github.com/sindresorhus/conf/blob/master/index.js#L13
46 | 'electron-store',
47 | 'adbkit',
48 | 'electron-named-image',
49 | ],
50 | }
51 |
--------------------------------------------------------------------------------
/webpack/main.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const TerserPlugin = require('terser-webpack-plugin')
4 | const baseConfig = require('./base')
5 |
6 | module.exports = {
7 | ...baseConfig,
8 | mode: 'production',
9 | devtool: 'hidden-source-map',
10 | entry: './electron/main',
11 | output: {
12 | ...baseConfig.output,
13 | path: path.join(__dirname, '../dist'),
14 | filename: './main.js',
15 | },
16 | plugins: [
17 | new webpack.DefinePlugin({
18 | 'process.env.NODE_ENV': JSON.stringify('production'),
19 | }),
20 | new webpack.optimize.ModuleConcatenationPlugin(),
21 | ],
22 | optimization: {
23 | minimize: true,
24 | minimizer: [
25 | new TerserPlugin({
26 | terserOptions: { output: { comments: false } },
27 | }),
28 | ],
29 | },
30 | target: 'electron-main',
31 | node: {
32 | __dirname: false,
33 | __filename: false,
34 | },
35 | }
36 |
--------------------------------------------------------------------------------
/webpack/renderer.dev.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const baseConfig = require('./base')
3 |
4 | const port = 3000
5 |
6 | const baseDevConfig = {
7 | ...baseConfig,
8 | mode: 'development',
9 | devtool: 'inline-source-map',
10 | output: {
11 | ...baseConfig.output,
12 | publicPath: `http://localhost:${port}/js/`,
13 | },
14 | module: {
15 | rules: [
16 | ...baseConfig.module.rules,
17 | {
18 | test: /\.css?$/,
19 | use: ['style-loader', 'css-loader'],
20 | },
21 | ],
22 | },
23 | plugins: [
24 | ...baseConfig.plugins,
25 | new webpack.DefinePlugin({
26 | 'process.env.NODE_ENV': JSON.stringify('development'),
27 | }),
28 | ],
29 | }
30 |
31 | const buildDevConfig = (config) => ({
32 | ...baseDevConfig,
33 | ...config,
34 | })
35 |
36 | module.exports = [
37 | buildDevConfig({
38 | entry: './app/index',
39 | target: 'electron-renderer',
40 | }),
41 | buildDevConfig({
42 | entry: './app/worker/index.js',
43 | resolve: {
44 | ...baseDevConfig.resolve,
45 | aliasFields: ['browser'],
46 | },
47 | output: {
48 | ...baseDevConfig.output,
49 | filename: 'RNDebuggerWorker.js',
50 | libraryTarget: undefined,
51 | },
52 | target: 'webworker',
53 | }),
54 | ]
55 |
--------------------------------------------------------------------------------
/webpack/renderer.prod.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const TerserPlugin = require('terser-webpack-plugin')
3 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
4 | const baseConfig = require('./base')
5 |
6 | const baseProdConfig = {
7 | ...baseConfig,
8 | mode: 'production',
9 | devtool: 'hidden-source-map',
10 | output: {
11 | ...baseConfig.output,
12 | publicPath: 'js/',
13 | },
14 | module: {
15 | rules: [
16 | ...baseConfig.module.rules,
17 | {
18 | test: /\.css?$/,
19 | use: ['style-loader', 'css-loader'],
20 | },
21 | ],
22 | },
23 | plugins: [
24 | ...baseConfig.plugins,
25 | new webpack.DefinePlugin({
26 | 'process.env.NODE_ENV': JSON.stringify('production'),
27 | __REACT_DEVTOOLS_GLOBAL_HOOK__: 'false',
28 | }),
29 | new webpack.optimize.ModuleConcatenationPlugin(),
30 | ],
31 | optimization: {
32 | minimize: true,
33 | minimizer: [
34 | new TerserPlugin({
35 | terserOptions: { output: { comments: false } },
36 | }),
37 | ],
38 | },
39 | }
40 |
41 | const buildProdConfig = (config) => ({
42 | ...baseProdConfig,
43 | ...config,
44 | })
45 |
46 | module.exports = [
47 | buildProdConfig({
48 | entry: './app/index',
49 | target: 'electron-renderer',
50 | plugins: [
51 | ...baseProdConfig.plugins,
52 | process.env.ANALYZE_BUNDLE ? new BundleAnalyzerPlugin() : null,
53 | ].filter(Boolean),
54 | }),
55 | buildProdConfig({
56 | entry: './app/worker/index.js',
57 | resolve: {
58 | ...baseProdConfig.resolve,
59 | aliasFields: ['browser'],
60 | },
61 | output: {
62 | ...baseProdConfig.output,
63 | filename: 'RNDebuggerWorker.js',
64 | libraryTarget: undefined,
65 | },
66 | target: 'webworker',
67 | }),
68 | ]
69 |
--------------------------------------------------------------------------------