-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(devtools): custom bridge for react-devtools (#5037)
- Loading branch information
Showing
12 changed files
with
244 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './server'; | ||
export * from './mount'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { BirpcReturn, createBirpc } from 'birpc'; | ||
import createDeferPromise from 'p-defer'; | ||
import { ReactDevtoolsWallAgent } from '@/utils/react-devtools'; | ||
import { | ||
ClientFunctions, | ||
MountPointFunctions, | ||
CLIENT_CONNECT_EVENT, | ||
} from '@/types/rpc'; | ||
|
||
export interface MountPointConnection { | ||
mountPoint: BirpcReturn<MountPointFunctions, ClientFunctions>; | ||
wall: ReactDevtoolsWallAgent; | ||
} | ||
|
||
export const setupMountPointConnection = async () => { | ||
const wallAgent = new ReactDevtoolsWallAgent(); | ||
const mountPointWindow = window.parent; | ||
if (!mountPointWindow || mountPointWindow === window) { | ||
throw new Error("Can't resolve the parent window."); | ||
} | ||
const channel = new MessageChannel(); | ||
const port = channel.port1; | ||
const mountPointPort = channel.port2; | ||
|
||
const connectTask = createDeferPromise<MountPointConnection>(); | ||
|
||
const mountPoint = createBirpc<MountPointFunctions, ClientFunctions>( | ||
{ | ||
async sendReactDevtoolsData(e) { | ||
wallAgent.emit(e); | ||
}, | ||
async onMountPointConnect() { | ||
connectTask.resolve({ mountPoint, wall: wallAgent }); | ||
}, | ||
}, | ||
{ | ||
post: data => port.postMessage(data), | ||
on: cb => (port.onmessage = cb), | ||
deserialize: e => e.data, | ||
timeout: 500, | ||
}, | ||
); | ||
|
||
wallAgent.sender = e => mountPoint.sendReactDevtoolsData(e); | ||
|
||
mountPointWindow.postMessage({ type: CLIENT_CONNECT_EVENT }, '*', [ | ||
mountPointPort, | ||
]); | ||
|
||
return connectTask.promise; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import createDeferPromise from 'p-defer'; | ||
import { createBirpc } from 'birpc'; | ||
import { activate, createBridge } from 'react-devtools-inline/backend'; | ||
import { | ||
CLIENT_CONNECT_EVENT, | ||
ClientFunctions, | ||
MountPointFunctions, | ||
} from '@/types/rpc'; | ||
import { ReactDevtoolsWallAgent } from '@/utils/react-devtools'; | ||
|
||
export interface SetupOptions { | ||
port: MessagePort; | ||
} | ||
|
||
export const setupClientConnection = async (options: SetupOptions) => { | ||
const { port } = options; | ||
const wallAgent = new ReactDevtoolsWallAgent(); | ||
const client = createBirpc<ClientFunctions, MountPointFunctions>( | ||
{ | ||
async sendReactDevtoolsData(e) { | ||
wallAgent.emit(e); | ||
}, | ||
async activateReactDevtools() { | ||
const bridge = createBridge(window, wallAgent); | ||
activate(window, { bridge }); | ||
}, | ||
}, | ||
{ | ||
post: data => port.postMessage(data), | ||
on: cb => (port.onmessage = cb), | ||
deserialize: e => e.data, | ||
timeout: 500, | ||
}, | ||
); | ||
|
||
wallAgent.sender = e => client.sendReactDevtoolsData(e); | ||
|
||
return { client, wall: wallAgent }; | ||
}; | ||
|
||
export const waitClientConnection = async () => { | ||
const connectTask = | ||
createDeferPromise<Awaited<ReturnType<typeof setupClientConnection>>>(); | ||
const handleMessage = async (e: MessageEvent) => { | ||
if (!e.data) return; | ||
if (typeof e.data !== 'object') return; | ||
if (e.data.type !== CLIENT_CONNECT_EVENT) return; | ||
const port = e.ports[0]; | ||
if (!port) throw new Error('Missing message channel port from devtools.'); | ||
window.removeEventListener('message', handleMessage); | ||
const conn = await setupClientConnection({ port }); | ||
await conn.client.onMountPointConnect(); | ||
connectTask.resolve(conn); | ||
}; | ||
window.addEventListener('message', handleMessage); | ||
|
||
return connectTask.promise; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './client'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ReactDevtoolsWallEvent } from '@/utils/react-devtools'; | ||
|
||
export interface ClientFunctions { | ||
sendReactDevtoolsData: (e: ReactDevtoolsWallEvent) => Promise<void>; | ||
onMountPointConnect: () => Promise<void>; | ||
} | ||
|
||
export interface MountPointFunctions { | ||
sendReactDevtoolsData: (e: ReactDevtoolsWallEvent) => Promise<void>; | ||
activateReactDevtools: () => Promise<void>; | ||
} | ||
|
||
export const CLIENT_CONNECT_EVENT = 'modern_js_devtools::client::connect'; | ||
|
||
export type ClientConnectEventType = typeof CLIENT_CONNECT_EVENT; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import type { Wall } from 'react-devtools-inline'; | ||
|
||
export interface ReactDevtoolsWallEvent { | ||
event: string; | ||
payload: any; | ||
transferable?: any[] | undefined; | ||
} | ||
|
||
export type ReactDevtoolsWallListener = (event: ReactDevtoolsWallEvent) => void; | ||
|
||
export class ReactDevtoolsWallAgent implements Wall { | ||
listeners: ReactDevtoolsWallListener[] = []; | ||
|
||
sender?: (event: ReactDevtoolsWallEvent) => void; | ||
|
||
send(event: string, payload: any, transferable?: any[] | undefined): void { | ||
this.sender?.({ | ||
event, | ||
payload, | ||
transferable, | ||
}); | ||
} | ||
|
||
listen(fn: ReactDevtoolsWallListener): ReactDevtoolsWallListener { | ||
this.listeners.includes(fn) || this.listeners.push(fn); | ||
return fn; | ||
} | ||
|
||
emit(event: ReactDevtoolsWallEvent) { | ||
for (const listener of this.listeners) { | ||
listener(event); | ||
} | ||
} | ||
} |
Oops, something went wrong.