mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
fix(slack): update slack config to support refresh token rotation (#1642)
This commit is contained in:
@@ -278,7 +278,22 @@ export async function refreshTokenIfNeeded(
|
||||
logger.info(`[${requestId}] Successfully refreshed access token`)
|
||||
return { accessToken: refreshedToken, refreshed: true }
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error refreshing token`, error)
|
||||
logger.warn(
|
||||
`[${requestId}] Refresh attempt failed, checking if another concurrent request succeeded`
|
||||
)
|
||||
|
||||
const freshCredential = await getCredential(requestId, credentialId, credential.userId)
|
||||
if (freshCredential?.accessToken) {
|
||||
const freshExpiresAt = freshCredential.accessTokenExpiresAt
|
||||
const stillValid = !freshExpiresAt || freshExpiresAt > new Date()
|
||||
|
||||
if (stillValid) {
|
||||
logger.info(`[${requestId}] Found valid token from concurrent refresh, using it`)
|
||||
return { accessToken: freshCredential.accessToken, refreshed: true }
|
||||
}
|
||||
}
|
||||
|
||||
logger.error(`[${requestId}] Refresh failed and no valid token found in DB`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function GET() {
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Sim Changelog</title>
|
||||
<link>https://sim.dev/changelog</link>
|
||||
<link>https://sim.ai/changelog</link>
|
||||
<description>Latest changes, fixes and updates in Sim.</description>
|
||||
<language>en-us</language>
|
||||
${items}
|
||||
|
||||
@@ -19,6 +19,7 @@ interface ChannelSelectorInputProps {
|
||||
onChannelSelect?: (channelId: string) => void
|
||||
isPreview?: boolean
|
||||
previewValue?: any | null
|
||||
previewContextValues?: Record<string, any>
|
||||
}
|
||||
|
||||
export function ChannelSelectorInput({
|
||||
@@ -28,15 +29,18 @@ export function ChannelSelectorInput({
|
||||
onChannelSelect,
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
previewContextValues,
|
||||
}: ChannelSelectorInputProps) {
|
||||
const params = useParams()
|
||||
const workflowIdFromUrl = (params?.workflowId as string) || ''
|
||||
// Use the proper hook to get the current value and setter (same as file-selector)
|
||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
|
||||
// Reactive upstream fields
|
||||
const [authMethod] = useSubBlockValue(blockId, 'authMethod')
|
||||
const [botToken] = useSubBlockValue(blockId, 'botToken')
|
||||
const [connectedCredential] = useSubBlockValue(blockId, 'credential')
|
||||
|
||||
const effectiveAuthMethod = previewContextValues?.authMethod ?? authMethod
|
||||
const effectiveBotToken = previewContextValues?.botToken ?? botToken
|
||||
const effectiveCredential = previewContextValues?.credential ?? connectedCredential
|
||||
const [selectedChannelId, setSelectedChannelId] = useState<string>('')
|
||||
const [_channelInfo, setChannelInfo] = useState<SlackChannelInfo | null>(null)
|
||||
|
||||
@@ -49,16 +53,16 @@ export function ChannelSelectorInput({
|
||||
isPreview,
|
||||
})
|
||||
|
||||
// Choose credential strictly based on auth method
|
||||
// Choose credential strictly based on auth method - use effective values
|
||||
const credential: string =
|
||||
(authMethod as string) === 'bot_token'
|
||||
? (botToken as string) || ''
|
||||
: (connectedCredential as string) || ''
|
||||
(effectiveAuthMethod as string) === 'bot_token'
|
||||
? (effectiveBotToken as string) || ''
|
||||
: (effectiveCredential as string) || ''
|
||||
|
||||
// Determine if connected OAuth credential is foreign (not applicable for bot tokens)
|
||||
const { isForeignCredential } = useForeignCredential(
|
||||
'slack',
|
||||
(authMethod as string) === 'bot_token' ? '' : (connectedCredential as string) || ''
|
||||
(effectiveAuthMethod as string) === 'bot_token' ? '' : (effectiveCredential as string) || ''
|
||||
)
|
||||
|
||||
// Get the current value from the store or prop value if in preview mode (same pattern as file-selector)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { AlertCircle, PlusIcon, Server, WrenchIcon, XIcon } from 'lucide-react'
|
||||
import type React from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { PlusIcon, Server, WrenchIcon, XIcon } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
@@ -374,42 +375,40 @@ function FileUploadSyncWrapper({
|
||||
)
|
||||
}
|
||||
|
||||
// Error boundary component for tool input
|
||||
class ToolInputErrorBoundary extends React.Component<
|
||||
{ children: React.ReactNode; blockName?: string },
|
||||
{ hasError: boolean; error?: Error }
|
||||
> {
|
||||
constructor(props: any) {
|
||||
super(props)
|
||||
this.state = { hasError: false }
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { hasError: true, error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
||||
console.error('ToolInput error:', error, info)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className='rounded-md bg-red-50 p-4 text-red-800 text-sm dark:bg-red-900/20 dark:text-red-200'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<AlertCircle className='h-4 w-4' />
|
||||
<span className='font-medium'>Tool Configuration Error</span>
|
||||
</div>
|
||||
<p className='mt-1 text-xs opacity-80'>
|
||||
{this.props.blockName ? `Block "${this.props.blockName}": ` : ''}
|
||||
Invalid tool reference. Please check the workflow configuration.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return this.props.children
|
||||
}
|
||||
function ChannelSelectorSyncWrapper({
|
||||
blockId,
|
||||
paramId,
|
||||
value,
|
||||
onChange,
|
||||
uiComponent,
|
||||
disabled,
|
||||
previewContextValues,
|
||||
}: {
|
||||
blockId: string
|
||||
paramId: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
uiComponent: any
|
||||
disabled: boolean
|
||||
previewContextValues?: Record<string, any>
|
||||
}) {
|
||||
return (
|
||||
<GenericSyncWrapper blockId={blockId} paramId={paramId} value={value} onChange={onChange}>
|
||||
<ChannelSelectorInput
|
||||
blockId={blockId}
|
||||
subBlock={{
|
||||
id: paramId,
|
||||
type: 'channel-selector' as const,
|
||||
title: paramId,
|
||||
provider: uiComponent.provider || 'slack',
|
||||
placeholder: uiComponent.placeholder,
|
||||
}}
|
||||
onChannelSelect={onChange}
|
||||
disabled={disabled}
|
||||
previewContextValues={previewContextValues}
|
||||
/>
|
||||
</GenericSyncWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToolInput({
|
||||
@@ -1060,19 +1059,14 @@ export function ToolInput({
|
||||
|
||||
case 'channel-selector':
|
||||
return (
|
||||
<ChannelSelectorInput
|
||||
<ChannelSelectorSyncWrapper
|
||||
blockId={blockId}
|
||||
subBlock={{
|
||||
id: `tool-${toolIndex || 0}-${param.id}`,
|
||||
type: 'channel-selector' as const,
|
||||
title: param.id,
|
||||
provider: uiComponent.provider || 'slack',
|
||||
placeholder: uiComponent.placeholder,
|
||||
}}
|
||||
onChannelSelect={onChange}
|
||||
paramId={param.id}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
uiComponent={uiComponent}
|
||||
disabled={disabled}
|
||||
isPreview={true}
|
||||
previewValue={value}
|
||||
previewContextValues={currentToolParams as any}
|
||||
/>
|
||||
)
|
||||
|
||||
|
||||
@@ -858,6 +858,7 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: false,
|
||||
supportsRefreshTokenRotation: true,
|
||||
}
|
||||
}
|
||||
case 'reddit': {
|
||||
|
||||
Reference in New Issue
Block a user