import useResizeObserver from '@react-hook/resize-observer'; import classNames from 'classnames'; import type { CSSProperties } from 'react'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { createGlobalState } from 'react-use'; import type { ReflectResponseService } from '../hooks/useGrpc'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; import type { GrpcMetadataEntry, GrpcRequest } from '@yaakapp/api'; import { AUTH_TYPE_BASIC, AUTH_TYPE_BEARER, AUTH_TYPE_NONE } from '../lib/models'; import { BasicAuth } from './BasicAuth'; import { BearerAuth } from './BearerAuth'; import { Button } from './core/Button'; import { Icon } from './core/Icon'; import { IconButton } from './core/IconButton'; import { PairOrBulkEditor } from './core/PairOrBulkEditor'; import { RadioDropdown } from './core/RadioDropdown'; import { HStack, VStack } from './core/Stacks'; import type { TabItem } from './core/Tabs/Tabs'; import { TabContent, Tabs } from './core/Tabs/Tabs'; import { EmptyStateText } from './EmptyStateText'; import { GrpcEditor } from './GrpcEditor'; import { UrlBar } from './UrlBar'; interface Props { style?: CSSProperties; className?: string; activeRequest: GrpcRequest; protoFiles: string[]; reflectionError?: string; reflectionLoading?: boolean; methodType: | 'unary' | 'client_streaming' | 'server_streaming' | 'streaming' | 'no-schema' | 'no-method'; isStreaming: boolean; onCommit: () => void; onCancel: () => void; onSend: (v: { message: string }) => void; onGo: () => void; services: ReflectResponseService[] | null; } const useActiveTab = createGlobalState('message'); export function GrpcConnectionSetupPane({ style, services, methodType, activeRequest, protoFiles, reflectionError, reflectionLoading, isStreaming, onGo, onCommit, onCancel, onSend, }: Props) { const updateRequest = useUpdateAnyGrpcRequest(); const [activeTab, setActiveTab] = useActiveTab(); const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null); const [paneSize, setPaneSize] = useState(99999); const urlContainerEl = useRef(null); useResizeObserver(urlContainerEl.current, (entry) => { setPaneSize(entry.contentRect.width); }); const handleChangeUrl = useCallback( (url: string) => updateRequest.mutateAsync({ id: activeRequest.id, update: { url } }), [activeRequest.id, updateRequest], ); const handleChangeMessage = useCallback( (message: string) => { return updateRequest.mutateAsync({ id: activeRequest.id, update: { message } }); }, [activeRequest.id, updateRequest], ); const select = useMemo(() => { const options = services?.flatMap((s) => s.methods.map((m) => ({ label: `${s.name.split('.', 2).pop() ?? s.name}/${m.name}`, value: `${s.name}/${m.name}`, })), ) ?? []; const value = `${activeRequest?.service ?? ''}/${activeRequest?.method ?? ''}`; return { value, options }; }, [activeRequest?.method, activeRequest?.service, services]); const handleChangeService = useCallback( async (v: string) => { const [serviceName, methodName] = v.split('/', 2); if (serviceName == null || methodName == null) throw new Error('Should never happen'); await updateRequest.mutateAsync({ id: activeRequest.id, update: { service: serviceName, method: methodName, }, }); }, [activeRequest.id, updateRequest], ); const handleConnect = useCallback(async () => { if (activeRequest == null) return; if (activeRequest.service == null || activeRequest.method == null) { alert({ id: 'grpc-invalid-service-method', title: 'Error', body: 'Service or method not selected', }); } onGo(); }, [activeRequest, onGo]); const handleSend = useCallback(async () => { if (activeRequest == null) return; onSend({ message: activeRequest.message }); }, [activeRequest, onSend]); const tabs: TabItem[] = useMemo( () => [ { value: 'message', label: 'Message' }, { value: 'auth', label: 'Auth', options: { value: activeRequest.authenticationType, items: [ { label: 'Basic Auth', shortLabel: 'Basic', value: AUTH_TYPE_BASIC }, { label: 'Bearer Token', shortLabel: 'Bearer', value: AUTH_TYPE_BEARER }, { type: 'separator' }, { label: 'No Authentication', shortLabel: 'Auth', value: AUTH_TYPE_NONE }, ], onChange: async (authenticationType) => { let authentication: GrpcRequest['authentication'] = activeRequest.authentication; if (authenticationType === AUTH_TYPE_BASIC) { authentication = { username: authentication.username ?? '', password: authentication.password ?? '', }; } else if (authenticationType === AUTH_TYPE_BEARER) { authentication = { token: authentication.token ?? '', }; } await updateRequest.mutateAsync({ id: activeRequest.id, update: { authenticationType, authentication }, }); }, }, }, { value: 'metadata', label: 'Metadata' }, ], [ activeRequest.authentication, activeRequest.authenticationType, activeRequest.id, updateRequest, ], ); const handleMetadataChange = useCallback( (metadata: GrpcMetadataEntry[]) => updateRequest.mutate({ id: activeRequest.id, update: { metadata } }), [activeRequest.id, updateRequest], ); return (
({ label: o.label, value: o.value, type: 'default', shortLabel: o.label, }))} extraItems={[ { label: 'Refresh', type: 'default', key: 'custom', leftSlot: , }, ]} > {methodType === 'client_streaming' || methodType === 'streaming' ? ( <> {isStreaming && ( <> )} ) : ( )}
{activeRequest.authenticationType === AUTH_TYPE_BASIC ? ( ) : activeRequest.authenticationType === AUTH_TYPE_BEARER ? ( ) : ( No Authentication {activeRequest.authenticationType} )}
); }