From ca24ae83634a81ca835de2d7377103b3eb296ec0 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Mon, 30 Jun 2025 19:31:36 -0700 Subject: [PATCH 1/4] Pass globalThis.seamQueryClient from SeamProvider --- src/lib/seam/SeamProvider.tsx | 6 +++++- src/lib/seam/SeamQueryProvider.tsx | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) 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..cda2022c9 100644 --- a/src/lib/seam/SeamQueryProvider.tsx +++ b/src/lib/seam/SeamQueryProvider.tsx @@ -88,9 +88,7 @@ export function SeamQueryProvider({ const { Provider } = seamContext return ( - + {children} From 8c125e66543a1c2af6d6e69686103bad6c0b45b5 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Mon, 30 Jun 2025 20:02:38 -0700 Subject: [PATCH 2/4] fix: Ensure query key is prefix by session scope --- src/lib/seam/SeamQueryProvider.tsx | 7 +++++++ src/lib/seam/use-seam-client.ts | 32 ++++++++++++++++++++++++++++++ src/lib/seam/use-seam-query.ts | 8 ++++++-- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/lib/seam/SeamQueryProvider.tsx b/src/lib/seam/SeamQueryProvider.tsx index cda2022c9..4a160b864 100644 --- a/src/lib/seam/SeamQueryProvider.tsx +++ b/src/lib/seam/SeamQueryProvider.tsx @@ -21,6 +21,7 @@ export interface SeamQueryContext { publishableKey?: string | undefined userIdentifierKey?: string | undefined clientSessionToken?: string | undefined + queryKeyPrefix?: string | undefined } export type SeamQueryProviderProps = @@ -31,6 +32,7 @@ export type SeamQueryProviderProps = export interface SeamQueryProviderPropsWithClient extends SeamQueryProviderBaseProps { client: SeamHttp + queryKeyPrefix: string } export interface SeamQueryProviderPropsWithPublishableKey @@ -126,6 +128,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..b8a89e4e9 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 + queryKeyPrefix: 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: [ + 'seam', 'client', { client, @@ -73,6 +76,15 @@ export function useSeamClient(): { return { client: data?.[0] ?? null, endpointClient: data?.[1] ?? null, + queryKeyPrefix: [ + 'seam', + queryKeyPrefix ?? + getQueryKeyPrefix({ + userIdentifierKey, + publishableKey, + clientSessionToken, + }), + ], isPending, isError, error, @@ -117,3 +129,23 @@ This is not recommended because the client session is now bound to this machine return fingerprint } + +const getQueryKeyPrefix = ({ + userIdentifierKey, + publishableKey, + clientSessionToken, +}: { + userIdentifierKey: string + publishableKey: string | undefined + clientSessionToken: string | undefined +}): string => { + if (clientSessionToken != null) { + return clientSessionToken + } + + if (publishableKey != null) { + return [publishableKey, userIdentifierKey].join(':') + } + + throw new Error('Could not determine a queryKeyPrefix') +} diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index 1fa3a3d8d..abe82af7b 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, queryKeyPrefix } = useSeamClient() return useQuery({ enabled: client != null, ...options, - queryKey: [endpointPath, parameters], + queryKey: [ + ...queryKeyPrefix, + ...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. From 0a02e7201644cd61673f8e7987214476c9a67e39 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Mon, 30 Jun 2025 20:10:17 -0700 Subject: [PATCH 3/4] Return queryKeyPrefixes in context --- src/lib/seam/use-seam-client.ts | 43 ++++++++++++++++++--------------- src/lib/seam/use-seam-query.ts | 4 +-- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/lib/seam/use-seam-client.ts b/src/lib/seam/use-seam-client.ts index b8a89e4e9..0cb95820e 100644 --- a/src/lib/seam/use-seam-client.ts +++ b/src/lib/seam/use-seam-client.ts @@ -8,7 +8,7 @@ import { useSeamQueryContext } from './SeamQueryProvider.js' export function useSeamClient(): { client: SeamHttp | null endpointClient: SeamHttpEndpoints | null - queryKeyPrefix: string[] + queryKeyPrefixes: string[] isPending: boolean isError: boolean error: unknown @@ -29,7 +29,7 @@ export function useSeamClient(): { [SeamHttp, SeamHttpEndpoints] >({ queryKey: [ - 'seam', + ...getQueryKeyPrefixes({ queryKeyPrefix }), 'client', { client, @@ -76,15 +76,12 @@ export function useSeamClient(): { return { client: data?.[0] ?? null, endpointClient: data?.[1] ?? null, - queryKeyPrefix: [ - 'seam', - queryKeyPrefix ?? - getQueryKeyPrefix({ - userIdentifierKey, - publishableKey, - clientSessionToken, - }), - ], + queryKeyPrefixes: getQueryKeyPrefixes({ + queryKeyPrefix, + userIdentifierKey, + publishableKey, + clientSessionToken, + }), isPending, isError, error, @@ -130,22 +127,28 @@ This is not recommended because the client session is now bound to this machine return fingerprint } -const getQueryKeyPrefix = ({ +const getQueryKeyPrefixes = ({ + queryKeyPrefix, userIdentifierKey, publishableKey, clientSessionToken, }: { - userIdentifierKey: string - publishableKey: string | undefined - clientSessionToken: string | undefined -}): string => { + 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 clientSessionToken + return [seamPrefix, clientSessionToken] } - if (publishableKey != null) { - return [publishableKey, userIdentifierKey].join(':') + if (publishableKey != null && userIdentifierKey != null) { + return [seamPrefix, publishableKey, userIdentifierKey] } - throw new Error('Could not determine a queryKeyPrefix') + return [seamPrefix] } diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index abe82af7b..1715a9b34 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -23,12 +23,12 @@ export function useSeamQuery( options: Parameters[1] & QueryOptions, SeamHttpApiError> = {} ): UseSeamQueryResult { - const { endpointClient: client, queryKeyPrefix } = useSeamClient() + const { endpointClient: client, queryKeyPrefixes } = useSeamClient() return useQuery({ enabled: client != null, ...options, queryKey: [ - ...queryKeyPrefix, + ...queryKeyPrefixes, ...endpointPath.split('/').filter((v) => v !== ''), parameters, ], From 0a537ea1934db8a66db26cce9f3e81c50af1d3bd Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Tue, 1 Jul 2025 10:44:33 -0700 Subject: [PATCH 4/4] feat: Enable using SeamQueryProvider inside existing QueryClientContext --- src/lib/seam/SeamQueryProvider.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/lib/seam/SeamQueryProvider.tsx b/src/lib/seam/SeamQueryProvider.tsx index 4a160b864..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, @@ -88,9 +92,22 @@ 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}