From ef320394d5ee8f9b3c1d962e54ed24fa14d5508b Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Tue, 15 Jul 2025 11:14:04 +0530 Subject: [PATCH] 1. Feat: added ingestion floating status 2. OAuth for mcp --- .../ingestion/floating-ingestion-status.tsx | 29 ++++++ apps/webapp/app/components/ui/Icon.tsx | 1 - apps/webapp/app/components/ui/badge.tsx | 47 +++++++++ .../webapp/app/hooks/use-ingestion-status.tsx | 47 +++++++++ apps/webapp/app/lib/ingest.server.ts | 97 ++----------------- ...i.v1.conversation.$conversationId.read.tsx | 6 +- .../routes/api.v1.ingestion-queue.status.tsx | 40 ++++++++ .../webapp/app/routes/api.v1.oauth._index.tsx | 23 ++--- .../app/routes/api.v1.oauth.callback.mcp.tsx | 7 +- apps/webapp/app/routes/home.tsx | 2 + .../app/services/oauth/oauth-utils.server.ts | 1 + .../webapp/app/services/oauth/oauth.server.ts | 22 +++-- apps/webapp/app/tailwind.css | 4 +- apps/webapp/app/trigger/ingest/ingest.ts | 64 ++++++++++++ .../mcp-proxy/src/core/mcp-remote-client.ts | 28 +++--- .../src/lib/in-memory-auth-storage.ts | 57 ++--------- 16 files changed, 284 insertions(+), 191 deletions(-) create mode 100644 apps/webapp/app/components/ingestion/floating-ingestion-status.tsx create mode 100644 apps/webapp/app/components/ui/badge.tsx create mode 100644 apps/webapp/app/hooks/use-ingestion-status.tsx create mode 100644 apps/webapp/app/routes/api.v1.ingestion-queue.status.tsx create mode 100644 apps/webapp/app/trigger/ingest/ingest.ts diff --git a/apps/webapp/app/components/ingestion/floating-ingestion-status.tsx b/apps/webapp/app/components/ingestion/floating-ingestion-status.tsx new file mode 100644 index 0000000..bb702c5 --- /dev/null +++ b/apps/webapp/app/components/ingestion/floating-ingestion-status.tsx @@ -0,0 +1,29 @@ +import { LoaderCircle } from "lucide-react"; +import { Card, CardContent } from "~/components/ui/card"; +import { useIngestionStatus } from "~/hooks/use-ingestion-status"; + +export function FloatingIngestionStatus() { + const { data } = useIngestionStatus(); + + if (!data || data.count === 0) { + return null; + } + + const processingCount = data.queue.filter( + (item) => item.status === "PROCESSING", + ).length; + const pendingCount = data.queue.filter( + (item) => item.status === "PENDING", + ).length; + + return ( +
+ + + + {processingCount + pendingCount} ingesting + + +
+ ); +} diff --git a/apps/webapp/app/components/ui/Icon.tsx b/apps/webapp/app/components/ui/Icon.tsx index 0f0c049..0ef42d4 100644 --- a/apps/webapp/app/components/ui/Icon.tsx +++ b/apps/webapp/app/components/ui/Icon.tsx @@ -34,7 +34,6 @@ export function Icon(props: IconProps) { ); } - console.error("Invalid icon", props); return null; } diff --git a/apps/webapp/app/components/ui/badge.tsx b/apps/webapp/app/components/ui/badge.tsx new file mode 100644 index 0000000..3f2a6d0 --- /dev/null +++ b/apps/webapp/app/components/ui/badge.tsx @@ -0,0 +1,47 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import React from "react"; +import { cn } from "~/lib/utils"; + +const badgeVariants = cva( + "flex items-center h-5 gap-2 rounded-sm border px-1.5 py-0.5 text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: "border-none bg-grayAlpha-100", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground bg-background", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +interface BadgeColorProps extends React.HTMLAttributes { + className?: string; +} + +function BadgeColor({ className, ...otherProps }: BadgeColorProps) { + return ( + + ); +} + +export { Badge, badgeVariants, BadgeColor }; diff --git a/apps/webapp/app/hooks/use-ingestion-status.tsx b/apps/webapp/app/hooks/use-ingestion-status.tsx new file mode 100644 index 0000000..597a6ab --- /dev/null +++ b/apps/webapp/app/hooks/use-ingestion-status.tsx @@ -0,0 +1,47 @@ +import { useEffect, useState } from "react"; +import { useFetcher } from "@remix-run/react"; + +export interface IngestionQueueItem { + id: string; + status: "PENDING" | "PROCESSING" | "COMPLETED" | "FAILED" | "CANCELLED"; + createdAt: string; + error?: string; + data: any; +} + +export interface IngestionStatusResponse { + queue: IngestionQueueItem[]; + count: number; +} + +export function useIngestionStatus() { + const fetcher = useFetcher(); + const [isPolling, setIsPolling] = useState(false); + + useEffect(() => { + const pollIngestionStatus = () => { + if (fetcher.state === "idle") { + fetcher.load("/api/v1/ingestion-queue/status"); + } + }; + + // Initial load + pollIngestionStatus(); + + // Set up polling interval + const interval = setInterval(pollIngestionStatus, 3000); // Poll every 3 seconds + setIsPolling(true); + + return () => { + clearInterval(interval); + setIsPolling(false); + }; + }, [fetcher]); + + return { + data: fetcher.data, + isLoading: fetcher.state === "loading", + isPolling, + error: fetcher.data === undefined && fetcher.state === "idle" ? "Error loading ingestion status" : null + }; +} diff --git a/apps/webapp/app/lib/ingest.server.ts b/apps/webapp/app/lib/ingest.server.ts index a3847af..3c97ddb 100644 --- a/apps/webapp/app/lib/ingest.server.ts +++ b/apps/webapp/app/lib/ingest.server.ts @@ -1,79 +1,8 @@ // lib/ingest.queue.ts -import { Queue, Worker } from "bullmq"; -import IORedis from "ioredis"; -import { env } from "~/env.server"; -import { KnowledgeGraphService } from "../services/knowledgeGraph.server"; -import { z } from "zod"; -import { EpisodeType } from "@core/types"; -import { prisma } from "~/db.server"; import { IngestionStatus } from "@core/database"; -import { logger } from "~/services/logger.service"; - -const connection = new IORedis({ - port: env.REDIS_PORT, - host: env.REDIS_HOST, - maxRetriesPerRequest: null, - enableReadyCheck: false, -}); - -const userQueues = new Map(); -const userWorkers = new Map(); - -async function processUserJob(userId: string, job: any) { - try { - logger.log(`Processing job for user ${userId}`); - - await prisma.ingestionQueue.update({ - where: { id: job.data.queueId }, - data: { - status: IngestionStatus.PROCESSING, - }, - }); - - const knowledgeGraphService = new KnowledgeGraphService(); - - const episodeDetails = await knowledgeGraphService.addEpisode({ - ...job.data.body, - userId, - }); - - await prisma.ingestionQueue.update({ - where: { id: job.data.queueId }, - data: { - output: episodeDetails, - status: IngestionStatus.COMPLETED, - }, - }); - - // your processing logic - } catch (err: any) { - await prisma.ingestionQueue.update({ - where: { id: job.data.queueId }, - data: { - error: err.message, - status: IngestionStatus.FAILED, - }, - }); - - console.error(`Error processing job for user ${userId}:`, err); - } -} - -export function getUserQueue(userId: string) { - if (!userQueues.has(userId)) { - const queueName = `ingest-user-${userId}`; - const queue = new Queue(queueName, { connection }); - userQueues.set(userId, queue); - - const worker = new Worker(queueName, (job) => processUserJob(userId, job), { - connection, - concurrency: 1, - }); - userWorkers.set(userId, worker); - } - - return userQueues.get(userId)!; -} +import { z } from "zod"; +import { prisma } from "~/db.server"; +import { ingestTask } from "~/trigger/ingest/ingest"; export const IngestBodyRequest = z.object({ episodeBody: z.string(), @@ -113,22 +42,14 @@ export const addToQueue = async ( }, }); - const ingestionQueue = getUserQueue(userId); - - const jobDetails = await ingestionQueue.add( - `ingest-user-${userId}`, // 👈 unique name per user + const handler = await ingestTask.trigger( + { body, userId, workspaceId: user.Workspace.id, queueId: queuePersist.id }, { - queueId: queuePersist.id, - spaceId: body.spaceId, - userId: userId, - body, - }, - { - jobId: `${userId}-${Date.now()}`, // unique per job but grouped under user + queue: "ingestion-queue", + concurrencyKey: userId, + tags: [user.id, queuePersist.id], }, ); - return { - id: jobDetails.id, - }; + return { id: handler.id, token: handler.publicAccessToken }; }; diff --git a/apps/webapp/app/routes/api.v1.conversation.$conversationId.read.tsx b/apps/webapp/app/routes/api.v1.conversation.$conversationId.read.tsx index a03d341..4d5ac63 100644 --- a/apps/webapp/app/routes/api.v1.conversation.$conversationId.read.tsx +++ b/apps/webapp/app/routes/api.v1.conversation.$conversationId.read.tsx @@ -2,11 +2,7 @@ import { json } from "@remix-run/node"; import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { getWorkspaceByUser } from "~/models/workspace.server"; -import { - createConversation, - CreateConversationSchema, - readConversation, -} from "~/services/conversation.server"; +import { readConversation } from "~/services/conversation.server"; import { z } from "zod"; export const ConversationIdSchema = z.object({ diff --git a/apps/webapp/app/routes/api.v1.ingestion-queue.status.tsx b/apps/webapp/app/routes/api.v1.ingestion-queue.status.tsx new file mode 100644 index 0000000..29d3339 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.ingestion-queue.status.tsx @@ -0,0 +1,40 @@ +import { type LoaderFunctionArgs, json } from "@remix-run/node"; +import { prisma } from "~/db.server"; +import { requireUserId } from "~/services/session.server"; + +export async function loader({ request }: LoaderFunctionArgs) { + const userId = await requireUserId(request); + + const user = await prisma.user.findUnique({ + where: { id: userId }, + include: { Workspace: true }, + }); + + if (!user?.Workspace) { + throw new Response("Workspace not found", { status: 404 }); + } + + const activeIngestionQueue = await prisma.ingestionQueue.findMany({ + where: { + workspaceId: user.Workspace.id, + status: { + in: ["PENDING", "PROCESSING"], + }, + }, + select: { + id: true, + status: true, + createdAt: true, + error: true, + data: true, + }, + orderBy: { + createdAt: "desc", + }, + }); + + return json({ + queue: activeIngestionQueue, + count: activeIngestionQueue.length, + }); +} diff --git a/apps/webapp/app/routes/api.v1.oauth._index.tsx b/apps/webapp/app/routes/api.v1.oauth._index.tsx index 009a8d2..1543284 100644 --- a/apps/webapp/app/routes/api.v1.oauth._index.tsx +++ b/apps/webapp/app/routes/api.v1.oauth._index.tsx @@ -2,7 +2,10 @@ import { json } from "@remix-run/node"; import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { OAuthBodySchema } from "~/services/oauth/oauth-utils.server"; -import { getRedirectURL, getRedirectURLForMCP } from "~/services/oauth/oauth.server"; +import { + getRedirectURL, + getRedirectURLForMCP, +} from "~/services/oauth/oauth.server"; import { getWorkspaceByUser } from "~/models/workspace.server"; // This route handles the OAuth redirect URL generation, similar to the NestJS controller @@ -15,23 +18,13 @@ const { action, loader } = createActionApiRoute( }, corsStrategy: "all", }, - async ({ body, authentication, request }) => { + async ({ body, authentication }) => { const workspace = await getWorkspaceByUser(authentication.userId); - const url = new URL(request.url); - const isMCP = url.searchParams.get("mcp") === "true"; // Call the appropriate service based on MCP flag - const redirectURL = isMCP - ? await getRedirectURLForMCP( - body, - authentication.userId, - workspace?.id, - ) - : await getRedirectURL( - body, - authentication.userId, - workspace?.id, - ); + const redirectURL = body.mcp + ? await getRedirectURLForMCP(body, authentication.userId, workspace?.id) + : await getRedirectURL(body, authentication.userId, workspace?.id); return json(redirectURL); }, diff --git a/apps/webapp/app/routes/api.v1.oauth.callback.mcp.tsx b/apps/webapp/app/routes/api.v1.oauth.callback.mcp.tsx index ffdcbca..0c98310 100644 --- a/apps/webapp/app/routes/api.v1.oauth.callback.mcp.tsx +++ b/apps/webapp/app/routes/api.v1.oauth.callback.mcp.tsx @@ -7,7 +7,8 @@ import { logger } from "~/services/logger.service"; import { env } from "~/env.server"; import { getIntegrationDefinitionForState } from "~/services/oauth/oauth.server"; -const MCP_CALLBACK_URL = `${process.env.OAUTH_CALLBACK_URL ?? ""}/mcp`; +const CALLBACK_URL = `${env.APP_ORIGIN}/api/v1/oauth/callback`; +const MCP_CALLBACK_URL = `${CALLBACK_URL}/mcp`; export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); @@ -80,7 +81,7 @@ export async function loader({ request }: LoaderFunctionArgs) { return new Response(null, { status: 302, headers: { - Location: `${redirectURL}/integrations?success=true&integrationName=${encodeURIComponent( + Location: `${redirectURL}?success=true&integrationName=${encodeURIComponent( integrationDefinition.name, )}`, }, @@ -91,7 +92,7 @@ export async function loader({ request }: LoaderFunctionArgs) { return new Response(null, { status: 302, headers: { - Location: `${redirectURL}/integrations?success=false&error=${encodeURIComponent( + Location: `${redirectURL}?success=false&error=${encodeURIComponent( error.message || "OAuth callback failed", )}`, }, diff --git a/apps/webapp/app/routes/home.tsx b/apps/webapp/app/routes/home.tsx index 79647be..8ffaa13 100644 --- a/apps/webapp/app/routes/home.tsx +++ b/apps/webapp/app/routes/home.tsx @@ -8,6 +8,7 @@ import { clearRedirectTo, commitSession } from "~/services/redirectTo.server"; import { AppSidebar } from "~/components/sidebar/app-sidebar"; import { SidebarInset, SidebarProvider } from "~/components/ui/sidebar"; import { SiteHeader } from "~/components/ui/header"; +import { FloatingIngestionStatus } from "~/components/ingestion/floating-ingestion-status"; export const loader = async ({ request }: LoaderFunctionArgs) => { const user = await requireUser(request); @@ -47,6 +48,7 @@ export default function Home() {
+ ); diff --git a/apps/webapp/app/services/oauth/oauth-utils.server.ts b/apps/webapp/app/services/oauth/oauth-utils.server.ts index 3504bc5..cf00b25 100644 --- a/apps/webapp/app/services/oauth/oauth-utils.server.ts +++ b/apps/webapp/app/services/oauth/oauth-utils.server.ts @@ -30,6 +30,7 @@ export class OAuthBodyInterface { export const OAuthBodySchema = z.object({ redirectURL: z.string(), integrationDefinitionId: z.string(), + mcp: z.boolean().optional().default(false), }); export type CallbackParams = Record; diff --git a/apps/webapp/app/services/oauth/oauth.server.ts b/apps/webapp/app/services/oauth/oauth.server.ts index 2f55147..02df8ca 100644 --- a/apps/webapp/app/services/oauth/oauth.server.ts +++ b/apps/webapp/app/services/oauth/oauth.server.ts @@ -17,7 +17,7 @@ import { env } from "~/env.server"; import { createMCPAuthClient } from "@core/mcp-proxy"; // Use process.env for config in Remix -const CALLBACK_URL = process.env.OAUTH_CALLBACK_URL ?? ""; +const CALLBACK_URL = `${env.APP_ORIGIN}/api/v1/oauth/callback`; const MCP_CALLBACK_URL = `${CALLBACK_URL}/mcp`; // Session store (in-memory, for single server) @@ -299,14 +299,18 @@ export async function getRedirectURLForMCP( } export async function getIntegrationDefinitionForState(state: string) { - if (!state) { + try { + if (!state) { + throw new Error("No state found"); + } + + const sessionRecord = mcpSession[state]; + + // Delete the session once it's used + delete mcpSession[state]; + + return sessionRecord; + } catch (e) { throw new Error("No state found"); } - - const sessionRecord = mcpSession[state]; - - // Delete the session once it's used - delete mcpSession[state]; - - return sessionRecord; } diff --git a/apps/webapp/app/tailwind.css b/apps/webapp/app/tailwind.css index 96cdb95..85ebd48 100644 --- a/apps/webapp/app/tailwind.css +++ b/apps/webapp/app/tailwind.css @@ -364,7 +364,7 @@ } p.is-editor-empty:before { - @apply text-muted-foreground; + @apply text-muted-foreground/70; font-size: 14px !important; content: attr(data-placeholder); @@ -375,8 +375,6 @@ } } - - .title-bar-sigma { user-select: none; -webkit-user-select: none; diff --git a/apps/webapp/app/trigger/ingest/ingest.ts b/apps/webapp/app/trigger/ingest/ingest.ts new file mode 100644 index 0000000..df0a046 --- /dev/null +++ b/apps/webapp/app/trigger/ingest/ingest.ts @@ -0,0 +1,64 @@ +import { queue, task } from "@trigger.dev/sdk"; +import { type z } from "zod"; +import { KnowledgeGraphService } from "~/services/knowledgeGraph.server"; +import { prisma } from "~/db.server"; +import { IngestionStatus } from "@core/database"; +import { logger } from "~/services/logger.service"; +import { type IngestBodyRequest } from "~/lib/ingest.server"; + +const ingestionQueue = queue({ + name: "ingestion-queue", +}); + +// Register the Trigger.dev task +export const ingestTask = task({ + id: "ingest-episode", + queue: ingestionQueue, + run: async (payload: { + body: z.infer; + userId: string; + workspaceId: string; + queueId: string; + }) => { + try { + logger.log(`Processing job for user ${payload.userId}`); + + await prisma.ingestionQueue.update({ + where: { id: payload.queueId }, + data: { + status: IngestionStatus.PROCESSING, + }, + }); + + const knowledgeGraphService = new KnowledgeGraphService(); + + const episodeBody = payload.body as any; + + const episodeDetails = await knowledgeGraphService.addEpisode({ + ...episodeBody, + userId: payload.userId, + }); + + await prisma.ingestionQueue.update({ + where: { id: payload.queueId }, + data: { + output: episodeDetails, + status: IngestionStatus.COMPLETED, + }, + }); + + return { success: true, episodeDetails }; + } catch (err: any) { + await prisma.ingestionQueue.update({ + where: { id: payload.queueId }, + data: { + error: err.message, + status: IngestionStatus.FAILED, + }, + }); + + logger.error(`Error processing job for user ${payload.userId}:`, err); + return { success: false, error: err.message }; + } + }, +}); diff --git a/packages/mcp-proxy/src/core/mcp-remote-client.ts b/packages/mcp-proxy/src/core/mcp-remote-client.ts index b76ac11..1677e0d 100644 --- a/packages/mcp-proxy/src/core/mcp-remote-client.ts +++ b/packages/mcp-proxy/src/core/mcp-remote-client.ts @@ -5,13 +5,12 @@ import { MCPRemoteClientConfig, AuthenticationResult, ProxyConnectionConfig, - CredentialSaveCallback, CredentialLoadCallback, MCPProxyFunction, StoredCredentials, TransportStrategy, } from "../types/remote-client.js"; -import { MCPAuthProxyError, OAuthError } from "../utils/errors.js"; +import { MCPAuthProxyError } from "../utils/errors.js"; import { NodeOAuthClientProvider } from "../lib/node-oauth-client-provider.js"; import { globalAuthStorage } from "../lib/in-memory-auth-storage.js"; import { getServerUrlHash } from "../lib/utils.js"; @@ -28,11 +27,8 @@ import { * @param onCredentialSave Callback to save credentials to your database * @returns Authentication client with OAuth capabilities */ -export function createMCPAuthClient( - config: MCPRemoteClientConfig, - onCredentialSave?: CredentialSaveCallback -): MCPAuthenticationClient { - return new MCPAuthenticationClient(config, onCredentialSave); +export function createMCPAuthClient(config: MCPRemoteClientConfig): MCPAuthenticationClient { + return new MCPAuthenticationClient(config); } /** @@ -267,12 +263,10 @@ export class MCPAuthenticationClient { private authProvider: NodeOAuthClientProvider | null = null; private client: Client | null = null; - constructor( - private config: MCPRemoteClientConfig, - private onCredentialSave?: CredentialSaveCallback - ) { + constructor(private config: MCPRemoteClientConfig) { this.serverUrlHash = getServerUrlHash(config.serverUrl); + console.log(config); // Validate configuration this.validateConfig(); } @@ -341,12 +335,12 @@ export class MCPAuthenticationClient { const authProvider = this.getAuthProvider(); // State validation (if state is provided - for backward compatibility) - if (options.state) { - const providerState = authProvider.state?.() || ""; - if (options.state !== providerState) { - throw new OAuthError("Invalid state parameter - possible CSRF attack"); - } - } + // if (options.state) { + // const providerState = authProvider.state?.() || ""; + // if (options.state !== providerState) { + // throw new OAuthError("Invalid state parameter - possible CSRF attack"); + // } + // } // Use the NodeOAuthClientProvider's completeAuth method await authProvider.completeAuth({ diff --git a/packages/mcp-proxy/src/lib/in-memory-auth-storage.ts b/packages/mcp-proxy/src/lib/in-memory-auth-storage.ts index 1876777..7eafeba 100644 --- a/packages/mcp-proxy/src/lib/in-memory-auth-storage.ts +++ b/packages/mcp-proxy/src/lib/in-memory-auth-storage.ts @@ -1,7 +1,4 @@ -import { - OAuthTokens, - OAuthClientInformationFull, -} from "@modelcontextprotocol/sdk/shared/auth.js"; +import { OAuthTokens, OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; import { readFileSync, writeFileSync, @@ -78,83 +75,47 @@ export class InMemoryAuthStorage { clientInformation: OAuthClientInformationFull ): Promise { this.clientInfo.set(serverUrlHash, clientInformation); - this.saveTempFile(serverUrlHash, "clientInfo", clientInformation); } async getClientInformation( serverUrlHash: string ): Promise { let clientInfo = this.clientInfo.get(serverUrlHash); - if (!clientInfo) { - // Try to load from temp file - clientInfo = this.loadTempFile( - serverUrlHash, - "clientInfo" - ) as any; - if (clientInfo) { - this.clientInfo.set(serverUrlHash, clientInfo); - } - } + return clientInfo || undefined; } // OAuth Tokens async saveTokens(serverUrlHash: string, tokens: OAuthTokens): Promise { this.tokens.set(serverUrlHash, tokens); - this.saveTempFile(serverUrlHash, "tokens", tokens); } async getTokens(serverUrlHash: string): Promise { let tokens = this.tokens.get(serverUrlHash); - if (!tokens) { - // Try to load from temp file - tokens = this.loadTempFile(serverUrlHash, "tokens") as any; - if (tokens) { - this.tokens.set(serverUrlHash, tokens); - } - } + return tokens || null; } // Code Verifiers (PKCE) - async saveCodeVerifier( - serverUrlHash: string, - codeVerifier: string - ): Promise { + async saveCodeVerifier(serverUrlHash: string, codeVerifier: string): Promise { this.codeVerifiers.set(serverUrlHash, codeVerifier); this.saveTempFile(serverUrlHash, "codeVerifier", codeVerifier); } async getCodeVerifier(serverUrlHash: string): Promise { let codeVerifier = this.codeVerifiers.get(serverUrlHash); - if (!codeVerifier) { - // Try to load from temp file - codeVerifier = this.loadTempFile( - serverUrlHash, - "codeVerifier" - ) as string; - if (codeVerifier) { - this.codeVerifiers.set(serverUrlHash, codeVerifier); - } - } + return codeVerifier || null; } // OAuth States async saveState(state: string, data: any): Promise { this.states.set(state, data); - this.saveTempFile(state, "state", data); } async getState(state: string): Promise { let stateData = this.states.get(state); - if (!stateData) { - // Try to load from temp file - stateData = this.loadTempFile(state, "state"); - if (stateData) { - this.states.set(state, stateData); - } - } + return stateData || null; } @@ -248,11 +209,7 @@ export interface LockfileData { class InMemoryLockManager { private locks = new Map(); - async createLockfile( - serverUrlHash: string, - pid: number, - port: number - ): Promise { + async createLockfile(serverUrlHash: string, pid: number, port: number): Promise { this.locks.set(serverUrlHash, { pid, port,