diff --git a/apps/desktop/src/lib/trpc/routers/auth/index.ts b/apps/desktop/src/lib/trpc/routers/auth/index.ts index 774b382095d..2b980cfb143 100644 --- a/apps/desktop/src/lib/trpc/routers/auth/index.ts +++ b/apps/desktop/src/lib/trpc/routers/auth/index.ts @@ -1,16 +1,16 @@ import { AUTH_PROVIDERS } from "@superset/shared/constants"; import { observable } from "@trpc/server/observable"; -import { authService } from "main/lib/auth"; +import { type AuthSession, authService } from "main/lib/auth"; import { z } from "zod"; import { publicProcedure, router } from "../.."; +/** Auth state emitted by onAuthState subscription */ +export type AuthState = (AuthSession & { token: string | null }) | null; + export const createAuthRouter = () => { return router({ onAuthState: publicProcedure.subscription(() => { - return observable< - | (ReturnType & { token: string | null }) - | null - >((emit) => { + return observable((emit) => { const emitCurrent = () => { const sessionData = authService.getSession(); const token = authService.getAccessToken(); diff --git a/apps/desktop/src/main/lib/auth/auth.ts b/apps/desktop/src/main/lib/auth/auth.ts index 1e92d792582..ae7aabac8a7 100644 --- a/apps/desktop/src/main/lib/auth/auth.ts +++ b/apps/desktop/src/main/lib/auth/auth.ts @@ -45,7 +45,10 @@ interface StoredAuth { expiresAt: string; } -type Session = Awaited>; +type SessionResponse = Awaited>; +/** Session data from the auth API */ +export type AuthSession = NonNullable; +type Session = AuthSession; class AuthService extends EventEmitter { private token: string | null = null; diff --git a/apps/desktop/src/main/lib/auth/index.ts b/apps/desktop/src/main/lib/auth/index.ts index 91e69aedfb1..7741ad34177 100644 --- a/apps/desktop/src/main/lib/auth/index.ts +++ b/apps/desktop/src/main/lib/auth/index.ts @@ -1,2 +1,2 @@ -export type { SignInResult } from "./auth"; +export type { AuthSession, SignInResult } from "./auth"; export { authService, parseAuthDeepLink } from "./auth"; diff --git a/apps/desktop/src/renderer/contexts/AuthProvider/AuthProvider.tsx b/apps/desktop/src/renderer/contexts/AuthProvider/AuthProvider.tsx index 6e74155a248..721f833d4d5 100644 --- a/apps/desktop/src/renderer/contexts/AuthProvider/AuthProvider.tsx +++ b/apps/desktop/src/renderer/contexts/AuthProvider/AuthProvider.tsx @@ -2,9 +2,11 @@ import { createContext, type ReactNode, useContext } from "react"; import type { RouterOutputs } from "../../lib/trpc"; import { trpc } from "../../lib/trpc"; +type AuthState = RouterOutputs["auth"]["onAuthState"]; + interface AuthContextValue { token: string | null; - session: RouterOutputs["auth"]["onAuthState"] | null; + session: AuthState; } const AuthContext = createContext(null); @@ -12,12 +14,9 @@ const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const { data: authState } = trpc.auth.onAuthState.useSubscription(); - const token = authState?.token ?? null; - const session = authState ?? null; - const value: AuthContextValue = { - token, - session, + token: authState?.token ?? null, + session: authState ?? null, }; return {children}; diff --git a/apps/desktop/src/renderer/contexts/TRPCProvider/TRPCProvider.tsx b/apps/desktop/src/renderer/contexts/TRPCProvider/TRPCProvider.tsx index d210d61f1c5..13c2896860b 100644 --- a/apps/desktop/src/renderer/contexts/TRPCProvider/TRPCProvider.tsx +++ b/apps/desktop/src/renderer/contexts/TRPCProvider/TRPCProvider.tsx @@ -1,8 +1,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { trpc } from "lib/trpc"; import { useState } from "react"; -import superjson from "superjson"; -import { ipcLink } from "trpc-electron/renderer"; +import { reactClient } from "../../lib/trpc-client"; export function TRPCProvider({ children }: { children: React.ReactNode }) { const [queryClient] = useState( @@ -20,13 +19,9 @@ export function TRPCProvider({ children }: { children: React.ReactNode }) { }, }), ); - const [trpcClient] = useState(() => - trpc.createClient({ - links: [ipcLink({ transformer: superjson })], - }), - ); + return ( - + {children} ); diff --git a/apps/desktop/src/renderer/lib/session-id-link.ts b/apps/desktop/src/renderer/lib/session-id-link.ts new file mode 100644 index 00000000000..c35dcf0a8e8 --- /dev/null +++ b/apps/desktop/src/renderer/lib/session-id-link.ts @@ -0,0 +1,33 @@ +import type { TRPCLink } from "@trpc/client"; +import { observable } from "@trpc/server/observable"; +import type { AppRouter } from "lib/trpc/routers"; + +/** + * Global counter for unique operation IDs across all tRPC clients. + * Starts from Date.now() to ensure uniqueness across page refreshes. + */ +let globalOperationId = Date.now(); + +/** + * Assigns globally unique operation IDs to prevent collisions between + * the React client and proxy client (each creates separate IPCClients + * that both receive all IPC responses and match by ID). + */ +export function sessionIdLink(): TRPCLink { + return () => { + return ({ op, next }) => { + const uniqueId = ++globalOperationId; + + return observable((observer) => { + return next({ + ...op, + id: uniqueId, + }).subscribe({ + next: (result) => observer.next(result), + error: (err) => observer.error(err), + complete: () => observer.complete(), + }); + }); + }; + }; +} diff --git a/apps/desktop/src/renderer/lib/trpc-client.ts b/apps/desktop/src/renderer/lib/trpc-client.ts index f98d6f92535..8dc7cc2d07c 100644 --- a/apps/desktop/src/renderer/lib/trpc-client.ts +++ b/apps/desktop/src/renderer/lib/trpc-client.ts @@ -2,10 +2,15 @@ import { createTRPCProxyClient } from "@trpc/client"; import type { AppRouter } from "lib/trpc/routers"; import superjson from "superjson"; import { ipcLink } from "trpc-electron/renderer"; +import { sessionIdLink } from "./session-id-link"; +import { trpc } from "./trpc"; -/** - * Use this when you need to call tRPC procedures from stores, utilities, etc. - */ +/** tRPC client for React hooks (used by TRPCProvider). */ +export const reactClient = trpc.createClient({ + links: [sessionIdLink(), ipcLink({ transformer: superjson })], +}); + +/** tRPC proxy client for imperative calls from stores/utilities. */ export const trpcClient = createTRPCProxyClient({ - links: [ipcLink({ transformer: superjson })], + links: [sessionIdLink(), ipcLink({ transformer: superjson })], });