// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ import type { SetNonNullable, SetOptional } from 'type-fest' import type { Ref } from 'vue' import { computed, ref, watch } from 'vue' import MutationHandler from '#shared/server/apollo/handler/MutationHandler.ts' import { useDebouncedLoading } from '#shared/composables/useDebouncedLoading.ts' import type { FormSubmitData } from '#shared/components/Form/types.ts' import type { MutationSendError } from '#shared/types/error.ts' import type { ChannelEmailInboundConfiguration, ChannelEmailOutboundConfiguration, } from '#shared/graphql/types.ts' import UserError from '#shared/errors/UserError.ts' import { i18n } from '#shared/i18n.ts' import { useChannelEmailValidateConfigurationRoundtripMutation } from '#desktop/entities/channel-email/graphql/mutations/channelEmailValidateConfigurationRoundtrip.api.ts' import { useChannelEmailAddMutation } from '#desktop/entities/channel-email/graphql/mutations/channelEmailAdd.api.ts' import type { EmailChannelSteps, EmailChannelForms, } from '../types/email-channel.ts' import type { EmailAccountData } from '../types/email-account.ts' import type { UpdateMetaInformationInboundFunction, EmailInboundMetaInformation, EmailOutboundData, EmailInboundData, EmailInboundMessagesData, } from '../types/email-inbound-outbound.ts' import { useChannelEmailGuessConfigurationMutation } from '../graphql/mutations/channelEmailGuessConfiguration.api.ts' import { useChannelEmailValidateConfigurationInboundMutation } from '../graphql/mutations/channelEmailValidateConfigurationInbound.api.ts' import { useChannelEmailValidateConfigurationOutboundMutation } from '../graphql/mutations/channelEmailValidateConfigurationOutbound.api.ts' export const useEmailChannelConfiguration = ( emailChannelForms: EmailChannelForms, metaInformationInbound: Ref>, updateMetaInformationInbound: UpdateMetaInformationInboundFunction, onSuccessCallback: () => void, ) => { const { loading, debouncedLoading } = useDebouncedLoading() const activeStep = ref('account') const pendingActiveStep = ref>(null) const setActiveStep = (nextStep: EmailChannelSteps) => { if (!debouncedLoading.value) { activeStep.value = nextStep return } pendingActiveStep.value = nextStep } watch(debouncedLoading, (newValue: boolean) => { if (!newValue && pendingActiveStep.value) { activeStep.value = pendingActiveStep.value pendingActiveStep.value = null } }) const stepTitle = computed(() => { switch (activeStep.value) { case 'inbound': case 'inbound-messages': return __('Email Inbound') case 'outbound': return __('Email Outbound') default: return __('Email Account') } }) const activeForm = computed(() => { switch (activeStep.value) { case 'inbound': return emailChannelForms.emailInbound.form.value case 'inbound-messages': return emailChannelForms.emailInboundMessages.form.value case 'outbound': return emailChannelForms.emailOutbound.form.value default: return emailChannelForms.emailAccount.form.value } }) const validateConfigurationRoundtripAndChannelAdd = async ( account: EmailAccountData, inboundConfiguration: EmailInboundData, outboundConfiguration: EmailOutboundData, ) => { const validateConfigurationRoundtripMutation = new MutationHandler( useChannelEmailValidateConfigurationRoundtripMutation(), ) const addEmailChannelMutation = new MutationHandler( useChannelEmailAddMutation(), ) // Transform port field to real number for usage in the mutation. inboundConfiguration.port = Number(inboundConfiguration.port) outboundConfiguration.port = Number(outboundConfiguration.port) // Extend inbound configuration with archive information when needed. if (metaInformationInbound.value?.archive) { inboundConfiguration = { ...inboundConfiguration, archive: true, archiveBefore: metaInformationInbound.value.archiveBefore, } } try { const roundTripResult = await validateConfigurationRoundtripMutation.send( { inboundConfiguration, outboundConfiguration, emailAddress: account.email, }, ) if ( roundTripResult?.channelEmailValidateConfigurationRoundtrip?.success ) { try { const addChannelResult = await addEmailChannelMutation.send({ input: { inboundConfiguration, outboundConfiguration, emailAddress: account.email, emailRealname: account.realname, }, }) if (addChannelResult?.channelEmailAdd?.channel) { onSuccessCallback() } } catch (errors) { emailChannelForms.emailAccount.setErrors(errors as MutationSendError) setActiveStep('account') } } } catch (errors) { if ( errors instanceof UserError && Object.keys(errors.getFieldErrorList()).length > 0 ) { if ( Object.keys(errors.getFieldErrorList()).some((key) => key.startsWith('outbound'), ) ) { setActiveStep('outbound') emailChannelForms.emailOutbound.setErrors(errors as MutationSendError) } else { setActiveStep('inbound') emailChannelForms.emailInbound.setErrors(errors as MutationSendError) } return } emailChannelForms.emailAccount.setErrors( new UserError([ { message: i18n.t( 'Email sending and receiving could not be verified. Please check your settings.', ), }, ]), ) setActiveStep('account') } } const guessEmailAccount = (data: FormSubmitData) => { loading.value = true const guessConfigurationMutation = new MutationHandler( useChannelEmailGuessConfigurationMutation(), ) return guessConfigurationMutation .send({ emailAddress: data.email, password: data.password, }) .then(async (result) => { if ( result?.channelEmailGuessConfiguration?.result.inboundConfiguration && result?.channelEmailGuessConfiguration?.result.outboundConfiguration ) { const inboundConfiguration = result.channelEmailGuessConfiguration .result.inboundConfiguration as SetOptional< SetNonNullable>, '__typename' > delete inboundConfiguration.__typename const outboundConfiguration = result.channelEmailGuessConfiguration .result.outboundConfiguration as SetOptional< SetNonNullable>, '__typename' > delete outboundConfiguration.__typename emailChannelForms.emailInbound.updateFieldValues(inboundConfiguration) emailChannelForms.emailOutbound.updateFieldValues( outboundConfiguration, ) const mailboxStats = result?.channelEmailGuessConfiguration?.result.mailboxStats if ( mailboxStats?.contentMessages && mailboxStats?.contentMessages > 0 ) { updateMetaInformationInbound(mailboxStats, 'roundtrip') setActiveStep('inbound-messages') return } await validateConfigurationRoundtripAndChannelAdd( data, inboundConfiguration, outboundConfiguration, ) } else { emailChannelForms.emailInbound.updateFieldValues({ user: data.email, password: data.password, }) emailChannelForms.emailOutbound.updateFieldValues({ user: data.email, password: data.password, }) emailChannelForms.emailInbound.setErrors( new UserError([ { message: i18n.t( 'The server settings could not be automatically detected. Please configure them manually.', ), }, ]), ) setActiveStep('inbound') } }) .finally(() => { loading.value = false }) } const validateEmailInbound = (data: FormSubmitData) => { loading.value = true const validationConfigurationInbound = new MutationHandler( useChannelEmailValidateConfigurationInboundMutation(), ) return validationConfigurationInbound .send({ inboundConfiguration: { ...data, port: Number(data.port), }, }) .then((result) => { if (result?.channelEmailValidateConfigurationInbound?.success) { emailChannelForms.emailOutbound.updateFieldValues({ host: data.host, user: data.user, password: data.password, }) const mailboxStats = result?.channelEmailValidateConfigurationInbound?.mailboxStats if ( mailboxStats?.contentMessages && mailboxStats?.contentMessages > 0 && !data.keepOnServer ) { updateMetaInformationInbound(mailboxStats, 'outbound') setActiveStep('inbound-messages') return } setActiveStep('outbound') } }) .finally(() => { loading.value = false }) } const importEmailInboundMessages = async ( data: FormSubmitData, ) => { if (metaInformationInbound.value && data.archive) { metaInformationInbound.value.archive = true metaInformationInbound.value.archiveBefore = new Date().toISOString() } if (metaInformationInbound.value?.nextAction === 'outbound') { setActiveStep('outbound') } if (metaInformationInbound.value?.nextAction === 'roundtrip') { loading.value = true await validateConfigurationRoundtripAndChannelAdd( emailChannelForms.emailAccount.values.value, emailChannelForms.emailInbound.values.value, emailChannelForms.emailOutbound.values.value, ) loading.value = false } } const validateEmailOutbound = (data: FormSubmitData) => { loading.value = true const validationConfigurationOutbound = new MutationHandler( useChannelEmailValidateConfigurationOutboundMutation(), ) return validationConfigurationOutbound .send({ outboundConfiguration: { ...data, port: Number(data.port), }, emailAddress: emailChannelForms.emailAccount.values.value ?.email as string, }) .then(async (result) => { if (result?.channelEmailValidateConfigurationOutbound?.success) { await validateConfigurationRoundtripAndChannelAdd( emailChannelForms.emailAccount.values.value, emailChannelForms.emailInbound.values.value, emailChannelForms.emailOutbound.values.value, ) } }) .finally(() => { loading.value = false }) } return { debouncedLoading, stepTitle, activeStep, activeForm, guessEmailAccount, validateEmailInbound, importEmailInboundMessages, validateEmailOutbound, } }