Skip to content

Commit 054e6c8

Browse files
authored
Upgrade apollo-client-devtools to v4 (#783)
* Initial work of upgrade apollo-client-devtools * Add prefix for all message types of apollo-client-devtools by patch * Middleware: Handle `ac-devtools:` prefix messages * Worker: Import devtools hook also add some patch to apollo-client-devtools: - Remove content script (it was attached to RNDebugger window) - No second arg on postMessage * Add apollo-client example * Fix tests * E2E: Take screenshot after each & upload artifacts on CI failed * E2E: Take devtools window screenshot * Fix typo in removeEventListener * Add test case for apollo-client-devtools
1 parent ea0f187 commit 054e6c8

33 files changed

Lines changed: 3519 additions & 1160 deletions

.eslintrc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
"__REPORT_REACT_DEVTOOLS_PORT__",
2727
"__FETCH_SUPPORT__",
2828
"__NETWORK_INSPECT__",
29-
"__APOLLO_CLIENT__",
30-
"__APOLLO_DEVTOOLS_SHOULD_DISPLAY_PANEL__"
29+
"__FROM_DEBUGGER_WORKER__"
3130
]
3231
}
3332
],

.github/workflows/main.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,20 @@ jobs:
1818
- name: Setup
1919
run: sudo apt-get install -y libgbm-dev
2020
- name: Test
21+
id: test
2122
run: |
2223
yarn
2324
cd npm-package && yarn && cd ..
2425
yarn test
2526
yarn build
2627
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
2735
build-test-macos:
2836
runs-on: macOS-latest
2937
steps:
@@ -33,12 +41,20 @@ jobs:
3341
node-version: 18.x
3442
cache: 'yarn'
3543
- name: Test
44+
id: test
3645
run: |
3746
yarn
3847
cd npm-package && yarn && cd ..
3948
yarn test
4049
yarn build
4150
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
4258
build-test-windows:
4359
runs-on: windows-2022
4460
steps:
@@ -48,6 +64,7 @@ jobs:
4864
node-version: 18.x
4965
cache: 'yarn'
5066
- name: Test
67+
id: test
5168
shell: bash
5269
run: |
5370
yarn config set network-timeout 500000 -g
@@ -56,3 +73,10 @@ jobs:
5673
yarn build
5774
yarn test
5875
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

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ release/
99
tmp/
1010
config_test
1111
.idea/
12+
artifacts/

__e2e__/app.spec.js

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import waitForExpect from 'wait-for-expect'
88
import buildTestBundle, { bundlePath } from './buildTestBundle'
99
import createMockRNServer from './mockRNServer'
1010

11-
const delay = (time) => new Promise((resolve) => {
12-
setTimeout(resolve, time)
13-
})
11+
const delay = (time) =>
12+
new Promise((resolve) => {
13+
setTimeout(resolve, time)
14+
})
1415

1516
jest.setTimeout(6e4)
1617

@@ -21,7 +22,6 @@ describe('Application launch', () => {
2122

2223
beforeAll(async () => {
2324
await buildTestBundle()
24-
process.env.E2E_TEST = '1'
2525
process.env.PACKAGE = 'no'
2626
electronApp = await electron.launch({
2727
executablePath,
@@ -33,6 +33,24 @@ describe('Application launch', () => {
3333
await mainWindow.waitForLoadState()
3434
})
3535

36+
afterEach(async () => {
37+
const state = expect.getState()
38+
await mainWindow.screenshot({
39+
path: `./artifacts/${state.currentTestName}.png`,
40+
})
41+
const devtoolsWindow = electronApp.windows()[2]
42+
if (devtoolsWindow) {
43+
try {
44+
await delay(100)
45+
await devtoolsWindow.screenshot({
46+
path: `./artifacts/devtools:${state.currentTestName}.png`,
47+
})
48+
} catch (e) {
49+
console.error(e)
50+
}
51+
}
52+
})
53+
3654
afterAll(async () => {
3755
await electronApp.close()
3856
})
@@ -79,11 +97,12 @@ describe('Application launch', () => {
7997
})
8098

8199
const customRNServerPort = 8098
82-
const getURLFromConnection = (server) => new Promise((resolve) => {
83-
server.on('connection', (socket, req) => {
84-
resolve(req.url)
100+
const getURLFromConnection = (server) =>
101+
new Promise((resolve) => {
102+
server.on('connection', (socket, req) => {
103+
resolve(req.url)
104+
})
85105
})
86-
})
87106

88107
it('should connect to fake RN server', async () => {
89108
const { wss, server } = createMockRNServer()
@@ -139,15 +158,16 @@ describe('Application launch', () => {
139158
})
140159

141160
describe('Import fake script after', () => {
142-
const getOneRequestHeaders = (port) => new Promise((resolve) => {
143-
const server = http.createServer((req, res) => {
144-
res.writeHead(200, { 'Content-Type': 'text/plain' })
145-
res.end('')
146-
resolve(req.headers)
147-
server.close()
161+
const getOneRequestHeaders = (port) =>
162+
new Promise((resolve) => {
163+
const server = http.createServer((req, res) => {
164+
res.writeHead(200, { 'Content-Type': 'text/plain' })
165+
res.end('')
166+
resolve(req.headers)
167+
server.close()
168+
})
169+
server.listen(port)
148170
})
149-
server.listen(port)
150-
})
151171

152172
let headersPromise
153173
let server
@@ -263,10 +283,11 @@ describe('Application launch', () => {
263283
},
264284
}
265285

266-
const eachAsync = (entries, fn) => entries.reduce(
267-
(promise, entry, index) => promise.then(() => fn(entry, index)),
268-
Promise.resolve(),
269-
)
286+
const eachAsync = (entries, fn) =>
287+
entries.reduce(
288+
(promise, entry, index) => promise.then(() => fn(entry, index)),
289+
Promise.resolve(),
290+
)
270291

271292
const runExpectActions = async (name) => {
272293
const { expt, notExpt } = expectActions[name]
@@ -310,7 +331,9 @@ describe('Application launch', () => {
310331

311332
it('should have only specific logs in console of main window', async () => {
312333
// Print renderer process logs
313-
logs.forEach((log) => console.log(`Message: ${log.text()}\nType: ${log.type()}`))
334+
logs.forEach((log) =>
335+
console.log(`Message: ${log.text()}\nType: ${log.type()}`),
336+
)
314337
expect(logs.length).toEqual(3) // clear + clear + warning
315338
const [, , formDataWarning] = logs
316339
expect(formDataWarning.type()).toBe('warning')
@@ -322,9 +345,14 @@ describe('Application launch', () => {
322345
})
323346

324347
it('should show apollo devtools panel', async () => {
348+
const devtoolsWindow = electronApp.windows()[2]
325349
expect(
326-
await mainWindow.evaluate(
327-
() => window.__APOLLO_DEVTOOLS_SHOULD_DISPLAY_PANEL__,
350+
await devtoolsWindow.evaluate(() =>
351+
// eslint-disable-next-line no-undef
352+
Object.keys(UI.panels).some(
353+
(key) =>
354+
key.startsWith('chrome-extension://') && key.endsWith('Apollo'),
355+
),
328356
),
329357
).toBeTruthy()
330358
})

__e2e__/buildTestBundle.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default function buildTestBundle() {
1818
filename,
1919
},
2020
resolve: {
21-
mainFields: ['main', 'browser'],
21+
mainFields: ['react-native', 'main', 'browser'],
2222
},
2323
}).run(resolve)
2424
})

__e2e__/fixture/apollo.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
12
/*
23
* Create an Apollo Client to test the bridge messages sent
34
* wouldn't break the debugger proxy.
45
*/
56

6-
/* eslint-disable */
7-
import ApolloClient from 'apollo-boost';
7+
import { ApolloClient, InMemoryCache } from '@apollo/client'
8+
import gql from 'graphql-tag'
89

9-
new ApolloClient({
10-
uri: 'https://fakerql.com/graphql',
11-
});
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+
}

__e2e__/fixture/app.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import './setup'
2+
3+
import runXHRTest from './xhr-test' // Install fetch polyfill before initial apollo-client
4+
import runApolloTest from './apollo'
25
import runReduxTest from './redux'
36
import runMobXTest from './mobx'
47
import runRemoteDevTest from './remotedev'
5-
import runXHRTest from './xhr-test'
6-
import './apollo'
78

89
runReduxTest()
910
runMobXTest()
1011
runRemoteDevTest()
11-
runXHRTest()
12+
runApolloTest().catch((e) => console.error(e))
13+
runXHRTest().catch((e) => console.error(e))

__e2e__/fixture/xhr-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import 'whatwg-fetch'
22

3-
export default function run() {
3+
export default async function run() {
44
// Fetch with forbidden header names
5-
fetch('http://localhost:8099', {
5+
await fetch('http://localhost:8099', {
66
headers: {
77
Origin: 'custom_origin_here',
88
'User-Agent': 'react-native',

app/middlewares/debuggerAPI.js

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,14 @@ let host
2929
let port
3030
let socket
3131

32-
const APOLLO_BACKEND = 'apollo-devtools-backend'
33-
const APOLLO_PROXY = 'apollo-devtools-proxy'
32+
const APOLLO_MESSAGE_PREFIX = 'ac-devtools:'
3433

3534
const workerOnMessage = (message) => {
3635
const { data } = message
3736

38-
if (data && data.source === APOLLO_BACKEND) {
39-
if (!window.__APOLLO_DEVTOOLS_SHOULD_DISPLAY_PANEL__) {
40-
window.__APOLLO_DEVTOOLS_SHOULD_DISPLAY_PANEL__ = true
41-
}
42-
43-
postMessage(
44-
{
45-
source: APOLLO_BACKEND,
46-
payload: data,
47-
},
48-
'*',
49-
)
37+
if (data && data.message?.startsWith(APOLLO_MESSAGE_PREFIX)) {
38+
data.__FROM_DEBUGGER_WORKER__ = true
39+
postMessage(data, '*')
5040
return false
5141
}
5242

@@ -63,13 +53,15 @@ const workerOnMessage = (message) => {
6353

6454
const onWindowMessage = (e) => {
6555
const { data } = e
66-
if (data && data.source === APOLLO_PROXY) {
67-
const message = typeof data.payload === 'string' ? { event: data.payload } : data.payload
56+
if (
57+
!data?.__FROM_DEBUGGER_WORKER__ &&
58+
data?.message?.startsWith(APOLLO_MESSAGE_PREFIX)
59+
) {
6860
worker.postMessage({
6961
method: 'emitApolloMessage',
70-
source: APOLLO_PROXY,
71-
...message,
62+
...data,
7263
})
64+
return false
7365
}
7466
}
7567

@@ -89,7 +81,7 @@ const shutdownJSRuntime = () => {
8981
scriptExecuted = false
9082
if (worker) {
9183
worker.terminate()
92-
window.removeEventListener('messsage', onWindowMessage)
84+
window.removeEventListener('message', onWindowMessage)
9385
setDevMenuMethods([])
9486
}
9587
worker = null

app/worker/apollo.js

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,5 @@
1-
import Bridge from 'apollo-client-devtools/src/bridge'
2-
import { initBackend, sendBridgeReady } from 'apollo-client-devtools/src/backend'
3-
import { version as devToolsVersion } from 'apollo-client-devtools/package.json'
4-
import { getSafeAsyncStorage } from './asyncStorage'
51

6-
export function handleApolloClient({ NativeModules } = {}) {
7-
const interval = setInterval(() => {
8-
if (!self.__APOLLO_CLIENT__) {
9-
return
10-
}
11-
12-
clearInterval(interval)
13-
14-
const hook = {
15-
ApolloClient: self.__APOLLO_CLIENT__,
16-
devToolsVersion,
17-
}
18-
19-
let listener
20-
21-
const bridge = new Bridge({
22-
listen(fn) {
23-
listener = self.addEventListener('message', (evt) => {
24-
if (evt.data.source === 'apollo-devtools-proxy') {
25-
return fn(evt.data)
26-
}
27-
})
28-
},
29-
send(data) {
30-
postMessage({
31-
...data,
32-
source: 'apollo-devtools-backend',
33-
})
34-
},
35-
})
36-
37-
bridge.on('init', () => {
38-
sendBridgeReady()
39-
})
40-
41-
bridge.on('shutdown', () => {
42-
self.removeEventListener('message', listener)
43-
})
44-
45-
initBackend(bridge, hook, getSafeAsyncStorage(NativeModules))
46-
}, 1000)
47-
return interval
2+
export function handleApolloClient() {
3+
// eslint-disable-next-line global-require
4+
require('apollo-client-devtools/build/hook')
485
}

0 commit comments

Comments
 (0)