Skip to content

Commit 9067665

Browse files
authored
fix: Ensure query key is prefix by session scope (#695)
* Pass globalThis.seamQueryClient from SeamProvider * fix: Ensure query key is prefix by session scope * Return queryKeyPrefixes in context * feat: Enable using SeamQueryProvider inside existing QueryClientContext
1 parent 35ec479 commit 9067665

File tree

4 files changed

+70
-5
lines changed

4 files changed

+70
-5
lines changed

src/lib/seam/SeamProvider.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export function SeamProvider({
6969
disableFontInjection = false,
7070
unminifiyCss = false,
7171
telemetryClient,
72+
queryClient,
7273
...props
7374
}: SeamProviderProps): JSX.Element {
7475
useSeamStyles({ disabled: disableCssInjection, unminified: unminifiyCss })
@@ -89,7 +90,10 @@ export function SeamProvider({
8990
disabled={disableTelemetry}
9091
endpoint={endpoint}
9192
>
92-
<SeamQueryProvider {...props}>
93+
<SeamQueryProvider
94+
queryClient={queryClient ?? globalThis.seamQueryClient}
95+
{...props}
96+
>
9397
<Provider value={value}>
9498
<Telemetry>{children}</Telemetry>
9599
</Provider>

src/lib/seam/SeamQueryProvider.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import type {
33
SeamHttpEndpoints,
44
SeamHttpOptionsWithClientSessionToken,
55
} from '@seamapi/http/connect'
6-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
6+
import {
7+
QueryClient,
8+
QueryClientContext,
9+
QueryClientProvider,
10+
} from '@tanstack/react-query'
711
import {
812
createContext,
913
type PropsWithChildren,
@@ -21,6 +25,7 @@ export interface SeamQueryContext {
2125
publishableKey?: string | undefined
2226
userIdentifierKey?: string | undefined
2327
clientSessionToken?: string | undefined
28+
queryKeyPrefix?: string | undefined
2429
}
2530

2631
export type SeamQueryProviderProps =
@@ -31,6 +36,7 @@ export type SeamQueryProviderProps =
3136
export interface SeamQueryProviderPropsWithClient
3237
extends SeamQueryProviderBaseProps {
3338
client: SeamHttp
39+
queryKeyPrefix: string
3440
}
3541

3642
export interface SeamQueryProviderPropsWithPublishableKey
@@ -86,10 +92,21 @@ export function SeamQueryProvider({
8692
}
8793

8894
const { Provider } = seamContext
95+
const queryClientFromContext = useContext(QueryClientContext)
96+
97+
if (
98+
queryClientFromContext != null &&
99+
queryClient != null &&
100+
queryClientFromContext !== queryClient
101+
) {
102+
throw new Error(
103+
'The QueryClient passed into SeamQueryProvider is different from the one in the existing QueryClientContext. Omit the queryClient prop from SeamProvider or SeamQueryProvider to use the existing QueryClient provided by the QueryClientProvider.'
104+
)
105+
}
89106

90107
return (
91108
<QueryClientProvider
92-
client={queryClient ?? globalThis.seamQueryClient ?? defaultQueryClient}
109+
client={queryClientFromContext ?? queryClient ?? defaultQueryClient}
93110
>
94111
<Provider value={value}>
95112
<Session onSessionUpdate={onSessionUpdate}>{children}</Session>
@@ -128,6 +145,11 @@ const createSeamQueryContextValue = (
128145
options: SeamQueryProviderProps
129146
): SeamQueryContext => {
130147
if (isSeamQueryProviderPropsWithClient(options)) {
148+
if (options.queryKeyPrefix == null) {
149+
throw new InvalidSeamQueryProviderProps(
150+
'The client prop must be used with a queryKeyPrefix prop.'
151+
)
152+
}
131153
return {
132154
...options,
133155
endpointClient: null,

src/lib/seam/use-seam-client.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useSeamQueryContext } from './SeamQueryProvider.js'
88
export function useSeamClient(): {
99
client: SeamHttp | null
1010
endpointClient: SeamHttpEndpoints | null
11+
queryKeyPrefixes: string[]
1112
isPending: boolean
1213
isError: boolean
1314
error: unknown
@@ -17,6 +18,7 @@ export function useSeamClient(): {
1718
clientOptions,
1819
publishableKey,
1920
clientSessionToken,
21+
queryKeyPrefix,
2022
...context
2123
} = useSeamQueryContext()
2224
const userIdentifierKey = useUserIdentifierKeyOrFingerprint(
@@ -27,6 +29,7 @@ export function useSeamClient(): {
2729
[SeamHttp, SeamHttpEndpoints]
2830
>({
2931
queryKey: [
32+
...getQueryKeyPrefixes({ queryKeyPrefix }),
3033
'client',
3134
{
3235
client,
@@ -73,6 +76,12 @@ export function useSeamClient(): {
7376
return {
7477
client: data?.[0] ?? null,
7578
endpointClient: data?.[1] ?? null,
79+
queryKeyPrefixes: getQueryKeyPrefixes({
80+
queryKeyPrefix,
81+
userIdentifierKey,
82+
publishableKey,
83+
clientSessionToken,
84+
}),
7685
isPending,
7786
isError,
7887
error,
@@ -117,3 +126,29 @@ This is not recommended because the client session is now bound to this machine
117126

118127
return fingerprint
119128
}
129+
130+
const getQueryKeyPrefixes = ({
131+
queryKeyPrefix,
132+
userIdentifierKey,
133+
publishableKey,
134+
clientSessionToken,
135+
}: {
136+
queryKeyPrefix: string | undefined
137+
userIdentifierKey?: string
138+
publishableKey?: string | undefined
139+
clientSessionToken?: string | undefined
140+
}): string[] => {
141+
const seamPrefix = 'seam'
142+
143+
if (queryKeyPrefix != null) return [seamPrefix, queryKeyPrefix]
144+
145+
if (clientSessionToken != null) {
146+
return [seamPrefix, clientSessionToken]
147+
}
148+
149+
if (publishableKey != null && userIdentifierKey != null) {
150+
return [seamPrefix, publishableKey, userIdentifierKey]
151+
}
152+
153+
return [seamPrefix]
154+
}

src/lib/seam/use-seam-query.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ export function useSeamQuery<T extends SeamHttpEndpointQueryPaths>(
2323
options: Parameters<SeamHttpEndpoints[T]>[1] &
2424
QueryOptions<QueryData<T>, SeamHttpApiError> = {}
2525
): UseSeamQueryResult<T> {
26-
const { endpointClient: client } = useSeamClient()
26+
const { endpointClient: client, queryKeyPrefixes } = useSeamClient()
2727
return useQuery({
2828
enabled: client != null,
2929
...options,
30-
queryKey: [endpointPath, parameters],
30+
queryKey: [
31+
...queryKeyPrefixes,
32+
...endpointPath.split('/').filter((v) => v !== ''),
33+
parameters,
34+
],
3135
queryFn: async () => {
3236
if (client == null) return null
3337
// Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory.

0 commit comments

Comments
 (0)