Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/mighty-banks-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/query-devtools': patch
---

`setupStyleSheet` now sets `window.__nonce__` when a `styleNonce` is provided.

The devtools use [goober](https://goober.js.org/) for CSS-in-JS, which reads `window.__nonce__` every time it creates or accesses its style element. Without this, goober overwrote the nonce with `undefined`, causing CSP violations even when `styleNonce` was correctly passed to `<ReactQueryDevtools>`.
13 changes: 13 additions & 0 deletions packages/query-devtools/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,7 @@ describe('Utils tests', () => {
describe('setupStyleSheet', () => {
afterEach(() => {
document.head.querySelector('#_goober')?.remove()
delete (window as any).__nonce__
})

it('should not insert any style tag when "nonce" is missing', () => {
Expand Down Expand Up @@ -1042,6 +1043,18 @@ describe('Utils tests', () => {
expect(styleTags).toHaveLength(1)
expect(styleTags[0]?.getAttribute('nonce')).toBe('first-nonce')
})

it('should set window.__nonce__ so goober preserves the nonce on its style element', () => {
setupStyleSheet('test-nonce')

expect((window as any).__nonce__).toBe('test-nonce')
})

it('should not set window.__nonce__ when nonce is missing', () => {
setupStyleSheet()

expect((window as any).__nonce__).toBeUndefined()
})
})

describe('sortFns', () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/query-devtools/src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,12 @@ export const deleteNestedDataByPath = (
// Adds a nonce to the style tag if needed
export const setupStyleSheet = (nonce?: string, target?: ShadowRoot) => {
if (!nonce) return

// Goober reads window.__nonce__ every time it creates or accesses its style
// element (e.nonce = window.__nonce__). Without this, goober overwrites the
// nonce we set on the pre-created element with undefined, clearing it.
;(window as any).__nonce__ = nonce

const styleExists =
document.querySelector('#_goober') || target?.querySelector('#_goober')

Expand Down