use popup window for oauth

pull/22091/head
jZonG 11 months ago
parent 8c95cf359e
commit 81ea5f1b77

@ -1,6 +1,5 @@
'use client' 'use client'
import React, { useCallback } from 'react' import React, { useCallback, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import type { FC } from 'react' import type { FC } from 'react'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -26,29 +25,35 @@ import {
useInvalidateMCPTools, useInvalidateMCPTools,
useMCPTools, useMCPTools,
useUpdateMCP, useUpdateMCP,
useUpdateMCPAuthorizationToken,
useUpdateMCPTools, useUpdateMCPTools,
} from '@/service/use-tools' } from '@/service/use-tools'
import { openOAuthPopup } from '@/hooks/use-oauth'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
type Props = { type Props = {
detail: ToolWithProvider detail: ToolWithProvider
onUpdate: (isDelete?: boolean) => void onUpdate: (isDelete?: boolean) => void
onHide: () => void onHide: () => void
isCreation: boolean
onFirstCreate: () => void
} }
const MCPDetailContent: FC<Props> = ({ const MCPDetailContent: FC<Props> = ({
detail, detail,
onUpdate, onUpdate,
onHide, onHide,
isCreation,
onFirstCreate,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter()
const { isCurrentWorkspaceManager } = useAppContext() const { isCurrentWorkspaceManager } = useAppContext()
const { data, isFetching: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '') const { data, isFetching: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '')
const invalidateMCPTools = useInvalidateMCPTools() const invalidateMCPTools = useInvalidateMCPTools()
const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools() const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools()
const { mutateAsync: authorizeMcp, isPending: isAuthorizing } = useAuthorizeMCP() const { mutateAsync: authorizeMcp, isPending: isAuthorizing } = useAuthorizeMCP()
const { mutateAsync: updateMCPAuthorizationToken } = useUpdateMCPAuthorizationToken()
const toolList = data?.tools || [] const toolList = data?.tools || []
const handleUpdateTools = useCallback(async () => { const handleUpdateTools = useCallback(async () => {
@ -81,7 +86,22 @@ const MCPDetailContent: FC<Props> = ({
setFalse: hideDeleting, setFalse: hideDeleting,
}] = useBoolean(false) }] = useBoolean(false)
const handleOAuthCallback = async (state: string, code: string) => {
if (!isCurrentWorkspaceManager)
return
if (detail.id !== state)
return
await updateMCPAuthorizationToken({
provider_id: state,
authorization_code: code,
})
handleUpdateTools()
}
const handleAuthorize = useCallback(async () => { const handleAuthorize = useCallback(async () => {
onFirstCreate()
if (!isCurrentWorkspaceManager)
return
if (!detail) if (!detail)
return return
const res = await authorizeMcp({ const res = await authorizeMcp({
@ -91,7 +111,7 @@ const MCPDetailContent: FC<Props> = ({
handleUpdateTools() handleUpdateTools()
else if (res.authorization_url) else if (res.authorization_url)
router.push(res.authorization_url) openOAuthPopup(res.authorization_url, handleOAuthCallback)
}, [detail, updateMCP, hideUpdateModal, onUpdate]) }, [detail, updateMCP, hideUpdateModal, onUpdate])
const handleUpdate = useCallback(async (data: any) => { const handleUpdate = useCallback(async (data: any) => {
@ -119,6 +139,11 @@ const MCPDetailContent: FC<Props> = ({
} }
}, [detail, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate]) }, [detail, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate])
useEffect(() => {
if (isCreation)
handleAuthorize()
}, [])
if (!detail) if (!detail)
return null return null

@ -10,12 +10,16 @@ type Props = {
detail?: ToolWithProvider detail?: ToolWithProvider
onUpdate: () => void onUpdate: () => void
onHide: () => void onHide: () => void
isCreation: boolean
onFirstCreate: () => void
} }
const MCPDetailPanel: FC<Props> = ({ const MCPDetailPanel: FC<Props> = ({
detail, detail,
onUpdate, onUpdate,
onHide, onHide,
isCreation,
onFirstCreate,
}) => { }) => {
const handleUpdate = (isDelete = false) => { const handleUpdate = (isDelete = false) => {
if (isDelete) if (isDelete)
@ -41,6 +45,8 @@ const MCPDetailPanel: FC<Props> = ({
detail={detail} detail={detail}
onHide={onHide} onHide={onHide}
onUpdate={handleUpdate} onUpdate={handleUpdate}
isCreation={isCreation}
onFirstCreate={onFirstCreate}
/> />
)} )}
</Drawer> </Drawer>

@ -1,15 +1,10 @@
'use client' 'use client'
import { useEffect, useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import NewMCPCard from './create-card' import NewMCPCard from './create-card'
import MCPCard from './provider-card' import MCPCard from './provider-card'
import MCPDetailPanel from './detail/provider-detail' import MCPDetailPanel from './detail/provider-detail'
import { import {
useAllMCPTools, useAllMCPTools,
useAuthorizeMCP,
useInvalidateMCPTools,
useUpdateMCPAuthorizationToken,
useUpdateMCPTools,
} from '@/service/use-tools' } from '@/service/use-tools'
import type { ToolWithProvider } from '@/app/components/workflow/types' import type { ToolWithProvider } from '@/app/components/workflow/types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
@ -39,17 +34,8 @@ function renderDefaultCard() {
const MCPList = ({ const MCPList = ({
searchText, searchText,
}: Props) => { }: Props) => {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const authCode = searchParams.get('code') || ''
const providerID = searchParams.get('state') || ''
const { data: list = [], refetch } = useAllMCPTools() const { data: list = [], refetch } = useAllMCPTools()
const { mutateAsync: authorizeMcp } = useAuthorizeMCP() const [isCreation, setIsCreation] = useState<boolean>(false)
const { mutateAsync: updateTools } = useUpdateMCPTools()
const invalidateMCPTools = useInvalidateMCPTools()
const { mutateAsync: updateMCPAuthorizationToken } = useUpdateMCPAuthorizationToken()
const filteredList = useMemo(() => { const filteredList = useMemo(() => {
return list.filter((collection) => { return list.filter((collection) => {
@ -68,40 +54,9 @@ const MCPList = ({
const handleCreate = async (provider: ToolWithProvider) => { const handleCreate = async (provider: ToolWithProvider) => {
await refetch() // update list await refetch() // update list
setCurrentProviderID(provider.id) setCurrentProviderID(provider.id)
const res = await authorizeMcp({ setIsCreation(true)
provider_id: provider.id,
})
if (res.result === 'success') {
await refetch() // update authorization in list
await updateTools(provider.id)
invalidateMCPTools(provider.id)
await refetch() // update tool list in provider list
}
else if (res.authorization_url) {
router.push(res.authorization_url)
}
} }
const handleUpdateAuthorization = async (providerID: string, code: string) => {
const targetProvider = list.find(provider => provider.id === providerID)
router.replace(pathname)
if (!targetProvider) return
await updateMCPAuthorizationToken({
provider_id: providerID,
authorization_code: code,
})
await refetch()
setCurrentProviderID(providerID)
await updateTools(providerID)
invalidateMCPTools(providerID)
await refetch()
}
useEffect(() => {
if (authCode && providerID && list.length > 0)
handleUpdateAuthorization(providerID, authCode)
}, [authCode, providerID, list])
return ( return (
<> <>
<div <div
@ -127,6 +82,8 @@ const MCPList = ({
detail={currentProvider} detail={currentProvider}
onHide={() => setCurrentProviderID(undefined)} onHide={() => setCurrentProviderID(undefined)}
onUpdate={refetch} onUpdate={refetch}
isCreation={isCreation}
onFirstCreate={() => setIsCreation(false)}
/> />
)} )}
</> </>

@ -20,11 +20,13 @@ import MCPList from './mcp'
import { useSelector as useAppContextSelector } from '@/context/app-context' import { useSelector as useAppContextSelector } from '@/context/app-context'
import { useAllToolProviders } from '@/service/use-tools' import { useAllToolProviders } from '@/service/use-tools'
import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins' import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
import { useOAuthCallback } from '@/hooks/use-oauth'
const ProviderList = () => { const ProviderList = () => {
const { t } = useTranslation() const { t } = useTranslation()
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
useOAuthCallback()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const authCode = searchParams.get('code') || '' const authCode = searchParams.get('code') || ''

@ -0,0 +1,47 @@
'use client'
import { useEffect } from 'react'
import { useSearchParams } from 'next/navigation'
export const useOAuthCallback = () => {
const searchParams = useSearchParams()
useEffect(() => {
const code = searchParams.get('code')
const state = searchParams.get('state')
if (code && state && window.opener) {
window.opener.postMessage({
type: 'oauth_callback',
payload: {
code,
state,
},
}, '*')
window.close()
}
}, [searchParams])
}
export const openOAuthPopup = (url: string, callback: (state: string, code: string) => void) => {
const width = 600
const height = 600
const left = window.screenX + (window.outerWidth - width) / 2
const top = window.screenY + (window.outerHeight - height) / 2
const popup = window.open(
url,
'OAuth',
`width=${width},height=${height},left=${left},top=${top},scrollbars=yes`,
)
const handleMessage = (event: MessageEvent) => {
if (event.data?.type === 'oauth_callback') {
window.removeEventListener('message', handleMessage)
const { code, state } = event.data.payload
callback(state, code)
}
}
window.addEventListener('message', handleMessage)
return popup
}
Loading…
Cancel
Save