diff --git a/src/lib/seam/SeamProvider.tsx b/src/lib/seam/SeamProvider.tsx index 22a4ffee9..b0b9d5544 100644 --- a/src/lib/seam/SeamProvider.tsx +++ b/src/lib/seam/SeamProvider.tsx @@ -69,6 +69,7 @@ export function SeamProvider({ disableFontInjection = false, unminifiyCss = false, telemetryClient, + queryClient, ...props }: SeamProviderProps): JSX.Element { useSeamStyles({ disabled: disableCssInjection, unminified: unminifiyCss }) @@ -89,7 +90,10 @@ export function SeamProvider({ disabled={disableTelemetry} endpoint={endpoint} > - + {children} diff --git a/src/lib/seam/SeamQueryProvider.tsx b/src/lib/seam/SeamQueryProvider.tsx index 41759ba68..892859011 100644 --- a/src/lib/seam/SeamQueryProvider.tsx +++ b/src/lib/seam/SeamQueryProvider.tsx @@ -3,7 +3,11 @@ import type { SeamHttpEndpoints, SeamHttpOptionsWithClientSessionToken, } from '@seamapi/http/connect' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { + QueryClient, + QueryClientContext, + QueryClientProvider, +} from '@tanstack/react-query' import { createContext, type PropsWithChildren, @@ -21,6 +25,7 @@ export interface SeamQueryContext { publishableKey?: string | undefined userIdentifierKey?: string | undefined clientSessionToken?: string | undefined + queryKeyPrefix?: string | undefined } export type SeamQueryProviderProps = @@ -31,6 +36,7 @@ export type SeamQueryProviderProps = export interface SeamQueryProviderPropsWithClient extends SeamQueryProviderBaseProps { client: SeamHttp + queryKeyPrefix: string } export interface SeamQueryProviderPropsWithPublishableKey @@ -86,10 +92,21 @@ export function SeamQueryProvider({ } const { Provider } = seamContext + const queryClientFromContext = useContext(QueryClientContext) + + if ( + queryClientFromContext != null && + queryClient != null && + queryClientFromContext !== queryClient + ) { + throw new Error( + '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.' + ) + } return ( {children} @@ -128,6 +145,11 @@ const createSeamQueryContextValue = ( options: SeamQueryProviderProps ): SeamQueryContext => { if (isSeamQueryProviderPropsWithClient(options)) { + if (options.queryKeyPrefix == null) { + throw new InvalidSeamQueryProviderProps( + 'The client prop must be used with a queryKeyPrefix prop.' + ) + } return { ...options, endpointClient: null, diff --git a/src/lib/seam/use-seam-client.ts b/src/lib/seam/use-seam-client.ts index 49fe995ee..0cb95820e 100644 --- a/src/lib/seam/use-seam-client.ts +++ b/src/lib/seam/use-seam-client.ts @@ -8,6 +8,7 @@ import { useSeamQueryContext } from './SeamQueryProvider.js' export function useSeamClient(): { client: SeamHttp | null endpointClient: SeamHttpEndpoints | null + queryKeyPrefixes: string[] isPending: boolean isError: boolean error: unknown @@ -17,6 +18,7 @@ export function useSeamClient(): { clientOptions, publishableKey, clientSessionToken, + queryKeyPrefix, ...context } = useSeamQueryContext() const userIdentifierKey = useUserIdentifierKeyOrFingerprint( @@ -27,6 +29,7 @@ export function useSeamClient(): { [SeamHttp, SeamHttpEndpoints] >({ queryKey: [ + ...getQueryKeyPrefixes({ queryKeyPrefix }), 'client', { client, @@ -73,6 +76,12 @@ export function useSeamClient(): { return { client: data?.[0] ?? null, endpointClient: data?.[1] ?? null, + queryKeyPrefixes: getQueryKeyPrefixes({ + queryKeyPrefix, + userIdentifierKey, + publishableKey, + clientSessionToken, + }), isPending, isError, error, @@ -117,3 +126,29 @@ This is not recommended because the client session is now bound to this machine return fingerprint } + +const getQueryKeyPrefixes = ({ + queryKeyPrefix, + userIdentifierKey, + publishableKey, + clientSessionToken, +}: { + queryKeyPrefix: string | undefined + userIdentifierKey?: string + publishableKey?: string | undefined + clientSessionToken?: string | undefined +}): string[] => { + const seamPrefix = 'seam' + + if (queryKeyPrefix != null) return [seamPrefix, queryKeyPrefix] + + if (clientSessionToken != null) { + return [seamPrefix, clientSessionToken] + } + + if (publishableKey != null && userIdentifierKey != null) { + return [seamPrefix, publishableKey, userIdentifierKey] + } + + return [seamPrefix] +} diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index 1fa3a3d8d..1715a9b34 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -23,11 +23,15 @@ export function useSeamQuery( options: Parameters[1] & QueryOptions, SeamHttpApiError> = {} ): UseSeamQueryResult { - const { endpointClient: client } = useSeamClient() + const { endpointClient: client, queryKeyPrefixes } = useSeamClient() return useQuery({ enabled: client != null, ...options, - queryKey: [endpointPath, parameters], + queryKey: [ + ...queryKeyPrefixes, + ...endpointPath.split('/').filter((v) => v !== ''), + parameters, + ], queryFn: async () => { if (client == null) return null // Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory.