Skip to content

Commit

Permalink
Upgrade apollo-client-devtools to v4 (#783)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jhen0409 authored Jul 28, 2023
1 parent ea0f187 commit 054e6c8
Show file tree
Hide file tree
Showing 33 changed files with 3,519 additions and 1,160 deletions.
3 changes: 1 addition & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
"__REPORT_REACT_DEVTOOLS_PORT__",
"__FETCH_SUPPORT__",
"__NETWORK_INSPECT__",
"__APOLLO_CLIENT__",
"__APOLLO_DEVTOOLS_SHOULD_DISPLAY_PANEL__"
"__FROM_DEBUGGER_WORKER__"
]
}
],
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@ jobs:
- name: Setup
run: sudo apt-get install -y libgbm-dev
- name: Test
id: test
run: |
yarn
cd npm-package && yarn && cd ..
yarn test
yarn build
xvfb-run --auto-servernum yarn test-e2e
- name: Upload artifacts on failure
if: ${{ failure() && steps.test.conclusion == 'failure' }}
uses: actions/upload-artifact@v3
with:
name: artifacts
path: artifacts
retention-days: 1
build-test-macos:
runs-on: macOS-latest
steps:
Expand All @@ -33,12 +41,20 @@ jobs:
node-version: 18.x
cache: 'yarn'
- name: Test
id: test
run: |
yarn
cd npm-package && yarn && cd ..
yarn test
yarn build
yarn test-e2e
- name: Upload artifacts on failure
if: ${{ failure() && steps.test.conclusion == 'failure' }}
uses: actions/upload-artifact@v3
with:
name: artifacts
path: artifacts
retention-days: 1
build-test-windows:
runs-on: windows-2022
steps:
Expand All @@ -48,6 +64,7 @@ jobs:
node-version: 18.x
cache: 'yarn'
- name: Test
id: test
shell: bash
run: |
yarn config set network-timeout 500000 -g
Expand All @@ -56,3 +73,10 @@ jobs:
yarn build
yarn test
yarn test-e2e
- name: Upload artifacts on failure
if: ${{ failure() && steps.test.conclusion == 'failure' }}
uses: actions/upload-artifact@v3
with:
name: artifacts
path: artifacts
retention-days: 1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ release/
tmp/
config_test
.idea/
artifacts/
74 changes: 51 additions & 23 deletions __e2e__/app.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import waitForExpect from 'wait-for-expect'
import buildTestBundle, { bundlePath } from './buildTestBundle'
import createMockRNServer from './mockRNServer'

const delay = (time) => new Promise((resolve) => {
setTimeout(resolve, time)
})
const delay = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time)
})

jest.setTimeout(6e4)

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

beforeAll(async () => {
await buildTestBundle()
process.env.E2E_TEST = '1'
process.env.PACKAGE = 'no'
electronApp = await electron.launch({
executablePath,
Expand All @@ -33,6 +33,24 @@ describe('Application launch', () => {
await mainWindow.waitForLoadState()
})

afterEach(async () => {
const state = expect.getState()
await mainWindow.screenshot({
path: `./artifacts/${state.currentTestName}.png`,
})
const devtoolsWindow = electronApp.windows()[2]
if (devtoolsWindow) {
try {
await delay(100)
await devtoolsWindow.screenshot({
path: `./artifacts/devtools:${state.currentTestName}.png`,
})
} catch (e) {
console.error(e)
}
}
})

afterAll(async () => {
await electronApp.close()
})
Expand Down Expand Up @@ -79,11 +97,12 @@ describe('Application launch', () => {
})

const customRNServerPort = 8098
const getURLFromConnection = (server) => new Promise((resolve) => {
server.on('connection', (socket, req) => {
resolve(req.url)
const getURLFromConnection = (server) =>
new Promise((resolve) => {
server.on('connection', (socket, req) => {
resolve(req.url)
})
})
})

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

describe('Import fake script after', () => {
const getOneRequestHeaders = (port) => new Promise((resolve) => {
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('')
resolve(req.headers)
server.close()
const getOneRequestHeaders = (port) =>
new Promise((resolve) => {
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('')
resolve(req.headers)
server.close()
})
server.listen(port)
})
server.listen(port)
})

let headersPromise
let server
Expand Down Expand Up @@ -263,10 +283,11 @@ describe('Application launch', () => {
},
}

const eachAsync = (entries, fn) => entries.reduce(
(promise, entry, index) => promise.then(() => fn(entry, index)),
Promise.resolve(),
)
const eachAsync = (entries, fn) =>
entries.reduce(
(promise, entry, index) => promise.then(() => fn(entry, index)),
Promise.resolve(),
)

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

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

it('should show apollo devtools panel', async () => {
const devtoolsWindow = electronApp.windows()[2]
expect(
await mainWindow.evaluate(
() => window.__APOLLO_DEVTOOLS_SHOULD_DISPLAY_PANEL__,
await devtoolsWindow.evaluate(() =>
// eslint-disable-next-line no-undef
Object.keys(UI.panels).some(
(key) =>
key.startsWith('chrome-extension://') && key.endsWith('Apollo'),
),
),
).toBeTruthy()
})
Expand Down
2 changes: 1 addition & 1 deletion __e2e__/buildTestBundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function buildTestBundle() {
filename,
},
resolve: {
mainFields: ['main', 'browser'],
mainFields: ['react-native', 'main', 'browser'],
},
}).run(resolve)
})
Expand Down
26 changes: 21 additions & 5 deletions __e2e__/fixture/apollo.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
/* eslint-disable import/no-extraneous-dependencies */
/*
* Create an Apollo Client to test the bridge messages sent
* wouldn't break the debugger proxy.
*/

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

new ApolloClient({
uri: 'https://fakerql.com/graphql',
});
const client = new ApolloClient({
uri: 'https://spacex-production.up.railway.app/',
cache: new InMemoryCache(),
})

export default async function run() {
return client.query({
query: gql`
query ExampleQuery {
company {
name
ceo
employees
}
}
`,
})
}
8 changes: 5 additions & 3 deletions __e2e__/fixture/app.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import './setup'

import runXHRTest from './xhr-test' // Install fetch polyfill before initial apollo-client
import runApolloTest from './apollo'
import runReduxTest from './redux'
import runMobXTest from './mobx'
import runRemoteDevTest from './remotedev'
import runXHRTest from './xhr-test'
import './apollo'

runReduxTest()
runMobXTest()
runRemoteDevTest()
runXHRTest()
runApolloTest().catch((e) => console.error(e))
runXHRTest().catch((e) => console.error(e))
4 changes: 2 additions & 2 deletions __e2e__/fixture/xhr-test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'whatwg-fetch'

export default function run() {
export default async function run() {
// Fetch with forbidden header names
fetch('http://localhost:8099', {
await fetch('http://localhost:8099', {
headers: {
Origin: 'custom_origin_here',
'User-Agent': 'react-native',
Expand Down
30 changes: 11 additions & 19 deletions app/middlewares/debuggerAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,14 @@ let host
let port
let socket

const APOLLO_BACKEND = 'apollo-devtools-backend'
const APOLLO_PROXY = 'apollo-devtools-proxy'
const APOLLO_MESSAGE_PREFIX = 'ac-devtools:'

const workerOnMessage = (message) => {
const { data } = message

if (data && data.source === APOLLO_BACKEND) {
if (!window.__APOLLO_DEVTOOLS_SHOULD_DISPLAY_PANEL__) {
window.__APOLLO_DEVTOOLS_SHOULD_DISPLAY_PANEL__ = true
}

postMessage(
{
source: APOLLO_BACKEND,
payload: data,
},
'*',
)
if (data && data.message?.startsWith(APOLLO_MESSAGE_PREFIX)) {
data.__FROM_DEBUGGER_WORKER__ = true
postMessage(data, '*')
return false
}

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

const onWindowMessage = (e) => {
const { data } = e
if (data && data.source === APOLLO_PROXY) {
const message = typeof data.payload === 'string' ? { event: data.payload } : data.payload
if (
!data?.__FROM_DEBUGGER_WORKER__ &&
data?.message?.startsWith(APOLLO_MESSAGE_PREFIX)
) {
worker.postMessage({
method: 'emitApolloMessage',
source: APOLLO_PROXY,
...message,
...data,
})
return false
}
}

Expand All @@ -89,7 +81,7 @@ const shutdownJSRuntime = () => {
scriptExecuted = false
if (worker) {
worker.terminate()
window.removeEventListener('messsage', onWindowMessage)
window.removeEventListener('message', onWindowMessage)
setDevMenuMethods([])
}
worker = null
Expand Down
49 changes: 3 additions & 46 deletions app/worker/apollo.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,5 @@
import Bridge from 'apollo-client-devtools/src/bridge'
import { initBackend, sendBridgeReady } from 'apollo-client-devtools/src/backend'
import { version as devToolsVersion } from 'apollo-client-devtools/package.json'
import { getSafeAsyncStorage } from './asyncStorage'

export function handleApolloClient({ NativeModules } = {}) {
const interval = setInterval(() => {
if (!self.__APOLLO_CLIENT__) {
return
}

clearInterval(interval)

const hook = {
ApolloClient: self.__APOLLO_CLIENT__,
devToolsVersion,
}

let listener

const bridge = new Bridge({
listen(fn) {
listener = self.addEventListener('message', (evt) => {
if (evt.data.source === 'apollo-devtools-proxy') {
return fn(evt.data)
}
})
},
send(data) {
postMessage({
...data,
source: 'apollo-devtools-backend',
})
},
})

bridge.on('init', () => {
sendBridgeReady()
})

bridge.on('shutdown', () => {
self.removeEventListener('message', listener)
})

initBackend(bridge, hook, getSafeAsyncStorage(NativeModules))
}, 1000)
return interval
export function handleApolloClient() {
// eslint-disable-next-line global-require
require('apollo-client-devtools/build/hook')
}
Loading

0 comments on commit 054e6c8

Please sign in to comment.