mirror of
https://github.com/langgenius/dify.git
synced 2026-03-12 02:28:54 +08:00
use popup window for oauth
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -26,29 +25,35 @@ import {
|
||||
useInvalidateMCPTools,
|
||||
useMCPTools,
|
||||
useUpdateMCP,
|
||||
useUpdateMCPAuthorizationToken,
|
||||
useUpdateMCPTools,
|
||||
} from '@/service/use-tools'
|
||||
import { openOAuthPopup } from '@/hooks/use-oauth'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
detail: ToolWithProvider
|
||||
onUpdate: (isDelete?: boolean) => void
|
||||
onHide: () => void
|
||||
isCreation: boolean
|
||||
onFirstCreate: () => void
|
||||
}
|
||||
|
||||
const MCPDetailContent: FC<Props> = ({
|
||||
detail,
|
||||
onUpdate,
|
||||
onHide,
|
||||
isCreation,
|
||||
onFirstCreate,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
|
||||
const { data, isFetching: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '')
|
||||
const invalidateMCPTools = useInvalidateMCPTools()
|
||||
const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools()
|
||||
const { mutateAsync: authorizeMcp, isPending: isAuthorizing } = useAuthorizeMCP()
|
||||
const { mutateAsync: updateMCPAuthorizationToken } = useUpdateMCPAuthorizationToken()
|
||||
const toolList = data?.tools || []
|
||||
|
||||
const handleUpdateTools = useCallback(async () => {
|
||||
@ -81,7 +86,22 @@ const MCPDetailContent: FC<Props> = ({
|
||||
setFalse: hideDeleting,
|
||||
}] = 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 () => {
|
||||
onFirstCreate()
|
||||
if (!isCurrentWorkspaceManager)
|
||||
return
|
||||
if (!detail)
|
||||
return
|
||||
const res = await authorizeMcp({
|
||||
@ -91,7 +111,7 @@ const MCPDetailContent: FC<Props> = ({
|
||||
handleUpdateTools()
|
||||
|
||||
else if (res.authorization_url)
|
||||
router.push(res.authorization_url)
|
||||
openOAuthPopup(res.authorization_url, handleOAuthCallback)
|
||||
}, [detail, updateMCP, hideUpdateModal, onUpdate])
|
||||
|
||||
const handleUpdate = useCallback(async (data: any) => {
|
||||
@ -119,6 +139,11 @@ const MCPDetailContent: FC<Props> = ({
|
||||
}
|
||||
}, [detail, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate])
|
||||
|
||||
useEffect(() => {
|
||||
if (isCreation)
|
||||
handleAuthorize()
|
||||
}, [])
|
||||
|
||||
if (!detail)
|
||||
return null
|
||||
|
||||
|
||||
@ -10,12 +10,16 @@ type Props = {
|
||||
detail?: ToolWithProvider
|
||||
onUpdate: () => void
|
||||
onHide: () => void
|
||||
isCreation: boolean
|
||||
onFirstCreate: () => void
|
||||
}
|
||||
|
||||
const MCPDetailPanel: FC<Props> = ({
|
||||
detail,
|
||||
onUpdate,
|
||||
onHide,
|
||||
isCreation,
|
||||
onFirstCreate,
|
||||
}) => {
|
||||
const handleUpdate = (isDelete = false) => {
|
||||
if (isDelete)
|
||||
@ -41,6 +45,8 @@ const MCPDetailPanel: FC<Props> = ({
|
||||
detail={detail}
|
||||
onHide={onHide}
|
||||
onUpdate={handleUpdate}
|
||||
isCreation={isCreation}
|
||||
onFirstCreate={onFirstCreate}
|
||||
/>
|
||||
)}
|
||||
</Drawer>
|
||||
|
||||
@ -1,15 +1,10 @@
|
||||
'use client'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useMemo, useState } from 'react'
|
||||
import NewMCPCard from './create-card'
|
||||
import MCPCard from './provider-card'
|
||||
import MCPDetailPanel from './detail/provider-detail'
|
||||
import {
|
||||
useAllMCPTools,
|
||||
useAuthorizeMCP,
|
||||
useInvalidateMCPTools,
|
||||
useUpdateMCPAuthorizationToken,
|
||||
useUpdateMCPTools,
|
||||
} from '@/service/use-tools'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
@ -39,17 +34,8 @@ function renderDefaultCard() {
|
||||
const MCPList = ({
|
||||
searchText,
|
||||
}: 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 { mutateAsync: authorizeMcp } = useAuthorizeMCP()
|
||||
const { mutateAsync: updateTools } = useUpdateMCPTools()
|
||||
const invalidateMCPTools = useInvalidateMCPTools()
|
||||
const { mutateAsync: updateMCPAuthorizationToken } = useUpdateMCPAuthorizationToken()
|
||||
const [isCreation, setIsCreation] = useState<boolean>(false)
|
||||
|
||||
const filteredList = useMemo(() => {
|
||||
return list.filter((collection) => {
|
||||
@ -68,40 +54,9 @@ const MCPList = ({
|
||||
const handleCreate = async (provider: ToolWithProvider) => {
|
||||
await refetch() // update list
|
||||
setCurrentProviderID(provider.id)
|
||||
const res = await authorizeMcp({
|
||||
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)
|
||||
}
|
||||
setIsCreation(true)
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div
|
||||
@ -127,6 +82,8 @@ const MCPList = ({
|
||||
detail={currentProvider}
|
||||
onHide={() => setCurrentProviderID(undefined)}
|
||||
onUpdate={refetch}
|
||||
isCreation={isCreation}
|
||||
onFirstCreate={() => setIsCreation(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -20,11 +20,13 @@ import MCPList from './mcp'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { useAllToolProviders } from '@/service/use-tools'
|
||||
import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
|
||||
import { useOAuthCallback } from '@/hooks/use-oauth'
|
||||
|
||||
const ProviderList = () => {
|
||||
const { t } = useTranslation()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
|
||||
useOAuthCallback()
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const authCode = searchParams.get('code') || ''
|
||||
|
||||
47
web/hooks/use-oauth.ts
Normal file
47
web/hooks/use-oauth.ts
Normal file
@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user