Skip to content

Commit a94fe68

Browse files
Kim Biesbjergcursoragent
andcommitted
fix: safely serialize query/mutation data to prevent DataCloneError with framework proxies
Frameworks like Vue 3, MobX, and Solid wrap state in Proxy objects. When mapQueryToData / mapMutationToData pass these values directly into the payload sent via window.postMessage, the structured clone algorithm fails with a DataCloneError. Add a safeClone helper that JSON-round-trips values before they are included in the message payload. This reads through Proxy getters and produces plain objects that are safe for structured cloning. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 41c5f1b commit a94fe68

1 file changed

Lines changed: 22 additions & 8 deletions

File tree

src/injected/injected.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ import {
1919
import { TanStackQueryActionExecutor } from "./modules/action-executor";
2020
import { sendToContentScript } from "./modules/message-sender";
2121

22+
/**
23+
* Safely deep-clone a value into a plain, structured-cloneable object.
24+
* Frameworks like Vue 3, MobX, and Solid wrap state in Proxy objects that
25+
* cannot be passed through `window.postMessage` (structured clone algorithm).
26+
* JSON round-tripping reads through Proxy getters, producing plain objects.
27+
*/
28+
function safeClone<T>(value: T): T {
29+
try {
30+
return JSON.parse(JSON.stringify(value));
31+
} catch {
32+
return "[Unserializable]" as T;
33+
}
34+
}
35+
2236
// Main injected script class
2337
class InjectedScript {
2438
private detectionCleanup: (() => void) | null = null;
@@ -282,11 +296,11 @@ class InjectedScript {
282296
// Map Query object to QueryData format
283297
private mapQueryToData(query: Query): QueryData {
284298
return {
285-
queryKey: query.queryKey,
299+
queryKey: safeClone(query.queryKey),
286300
queryHash: query.queryHash,
287301
state: {
288-
data: query.state.data,
289-
error: query.state.error,
302+
data: safeClone(query.state.data),
303+
error: safeClone(query.state.error),
290304
status: query.state.status,
291305
isFetching: query.state.fetchStatus === "fetching",
292306
isPending: query.state.status === "pending",
@@ -298,7 +312,7 @@ class InjectedScript {
298312
errorUpdatedAt: query.state.errorUpdatedAt,
299313
fetchStatus: query.state.fetchStatus,
300314
},
301-
meta: query.meta || {},
315+
meta: safeClone(query.meta || {}),
302316
isActive: query.getObserversCount() > 0,
303317
observersCount: query.getObserversCount(),
304318
};
@@ -309,10 +323,10 @@ class InjectedScript {
309323
return {
310324
mutationId: mutation.mutationId,
311325
state: mutation.state.status,
312-
variables: mutation.state.variables,
313-
context: mutation.state.context,
314-
data: mutation.state.data,
315-
error: mutation.state.error,
326+
variables: safeClone(mutation.state.variables),
327+
context: safeClone(mutation.state.context),
328+
data: safeClone(mutation.state.data),
329+
error: safeClone(mutation.state.error),
316330
submittedAt: mutation.state.submittedAt,
317331
isPending: mutation.state.status === "pending",
318332
};

0 commit comments

Comments
 (0)