Skip to content

Commit 2c362c0

Browse files
committed
swap to evenets for server info
1 parent 6b7c803 commit 2c362c0

File tree

3 files changed

+64
-61
lines changed

3 files changed

+64
-61
lines changed

packages/react-router-devtools/src/client/hooks/useDevServerConnection.ts

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect } from "react"
2-
import { useNavigation } from "react-router"
32
import type { ActionEvent, LoaderEvent } from "../../server/event-queue.js"
3+
import { eventClient } from "../../shared/event-client.js"
44
import type { ServerInfo } from "../context/rdtReducer.js"
55
import { useServerInfo } from "../context/useRDTContext.js"
66
import { cutArrayToLastN } from "../utils/common.js"
@@ -49,46 +49,27 @@ const updateRouteInfo = (
4949
}
5050

5151
const useDevServerConnection = () => {
52-
const navigation = useNavigation()
5352
const { server, setServerInfo } = useServerInfo()
5453

55-
// Pull the event queue from the server when the page is idle
54+
// Listen to loader and action events from the event client
5655
useEffect(() => {
57-
if (typeof import.meta.hot === "undefined") return
58-
if (navigation.state !== "idle") return
59-
// We send a pull & clear event to pull the event queue and clear it
60-
import.meta.hot.send("all-route-info")
61-
}, [navigation.state])
62-
63-
useEffect(() => {
64-
// biome-ignore lint/suspicious/noExplicitAny: we don't care about the type
65-
const cb2 = (data: any) => {
66-
const events = JSON.parse(data).data
67-
const routes: ServerInfo["routes"] = {}
68-
for (const routeInfo of Object.values(events)) {
69-
// biome-ignore lint/suspicious/noExplicitAny: we don't care about the type
70-
const { loader, action } = routeInfo as any
71-
const events = [
72-
// biome-ignore lint/suspicious/noExplicitAny: we don't care about the type
73-
loader.map((e: any) => ({ type: "loader", data: e })),
74-
// biome-ignore lint/suspicious/noExplicitAny: we don't care about the type
75-
action.map((e: any) => ({ type: "action", data: e })),
76-
].flat()
77-
for (const event of events) {
78-
updateRouteInfo(server, routes, event, false)
79-
}
80-
}
56+
// Listen to loader events
57+
const unsubscribeLoader = eventClient.on("loader-event", (event) => {
58+
const routes: ServerInfo["routes"] = { ...server?.routes }
59+
updateRouteInfo(server, routes, { type: "loader", data: event.payload as LoaderEvent["data"] }, false)
60+
setServerInfo({ routes })
61+
})
8162

63+
// Listen to action events
64+
const unsubscribeAction = eventClient.on("action-event", (event) => {
65+
const routes: ServerInfo["routes"] = { ...server?.routes }
66+
updateRouteInfo(server, routes, { type: "action", data: event.payload as ActionEvent["data"] }, false)
8267
setServerInfo({ routes })
83-
}
84-
if (typeof import.meta.hot !== "undefined") {
85-
import.meta.hot.on("all-route-info", cb2)
86-
}
68+
})
8769

8870
return () => {
89-
if (typeof import.meta.hot !== "undefined") {
90-
import.meta.hot.dispose(cb2)
91-
}
71+
unsubscribeLoader()
72+
unsubscribeAction()
9273
}
9374
}, [server, setServerInfo])
9475

packages/react-router-devtools/src/server/utils.ts

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import chalk from "chalk"
22
import type { ActionFunctionArgs, LoaderFunctionArgs, UNSAFE_DataWithResponseInit } from "react-router"
3-
import { bigIntReplacer } from "../shared/bigint-util.js"
3+
import { eventClient } from "../shared/event-client.js"
44
import { sendEvent } from "../shared/send-event.js"
55
import { type DevToolsServerConfig, getConfig } from "./config.js"
66
import { actionLog, errorLog, infoLog, loaderLog, redirectLog } from "./logger.js"
@@ -223,6 +223,35 @@ export const extractHeadersFromResponseOrRequest = (
223223
return Object.fromEntries(headers.entries())
224224
}
225225

226+
/**
227+
* Converts BigInt values to strings in an object/array structure
228+
* This allows the data to be serialized to JSON without errors
229+
*/
230+
// biome-ignore lint/suspicious/noExplicitAny: we don't care about the type
231+
const serializeBigInt = (obj: any): any => {
232+
if (obj === null || obj === undefined) {
233+
return obj
234+
}
235+
236+
if (typeof obj === "bigint") {
237+
return obj.toString()
238+
}
239+
240+
if (Array.isArray(obj)) {
241+
return obj.map(serializeBigInt)
242+
}
243+
244+
if (typeof obj === "object") {
245+
const result: Record<string, unknown> = {}
246+
for (const [key, value] of Object.entries(obj)) {
247+
result[key] = serializeBigInt(value)
248+
}
249+
return result
250+
}
251+
252+
return obj
253+
}
254+
226255
const storeAndEmitActionOrLoaderInfo = async (
227256
type: "action" | "loader",
228257
routeId: string,
@@ -233,34 +262,24 @@ const storeAndEmitActionOrLoaderInfo = async (
233262
const responseHeaders = extractHeadersFromResponseOrRequest(response)
234263
const requestHeaders = extractHeadersFromResponseOrRequest(args.request)
235264

236-
// create the event
237-
const event = {
238-
type,
239-
data: {
240-
id: routeId,
241-
executionTime: end,
242-
timestamp: new Date().getTime(),
243-
responseData: isDataFunctionResponse(response) ? response.data : response,
244-
requestHeaders,
245-
responseHeaders,
246-
},
247-
}
248-
if (typeof process === "undefined") {
249-
return
265+
const responseData = isDataFunctionResponse(response) ? response.data : response
266+
267+
// create the event data matching LoaderEvent["data"] / ActionEvent["data"] type
268+
const eventData = {
269+
id: routeId,
270+
executionTime: end,
271+
timestamp: new Date().getTime(),
272+
responseData: serializeBigInt(responseData),
273+
requestHeaders: requestHeaders || {},
274+
responseHeaders: responseHeaders || {},
275+
requestData: undefined, // TODO: Extract request data if needed (e.g., formData for actions)
250276
}
251-
const port = process.rdt_port
252277

253-
if (port) {
254-
fetch(`http://localhost:${port}/__rrdt`, {
255-
method: "POST",
256-
body: JSON.stringify(event, bigIntReplacer),
257-
})
258-
.then(async (res) => {
259-
if (res.ok) {
260-
await res.text()
261-
}
262-
})
263-
.catch(() => {})
278+
// Emit the event via event client
279+
if (type === "loader") {
280+
eventClient.emit("loader-event", eventData)
281+
} else {
282+
eventClient.emit("action-event", eventData)
264283
}
265284
}
266285

packages/react-router-devtools/src/shared/event-client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { EventClient } from "@tanstack/devtools-event-client"
2+
import type { ActionEvent, LoaderEvent } from "../server/event-queue.js"
23
import type { RequestEvent } from "./request-event"
34

45
interface EventMap {
56
"rdt:request-event": RequestEvent
67
"rdt:get-events": RequestEvent[] // Both request (empty array) and response (array of events)
78
"rdt:remove-event": { id: string; startTime: number; fromClient?: boolean }
9+
"rdt:loader-event": LoaderEvent["data"]
10+
"rdt:action-event": ActionEvent["data"]
811
}
912

1013
export const eventClient = new EventClient<EventMap>({

0 commit comments

Comments
 (0)