refactor: bunch of improvements

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-04-23 22:03:17 -04:00
parent ea527a1094
commit 2cd27b18e5
4 changed files with 186 additions and 151 deletions

View File

@@ -1,22 +1,22 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { IdentityBasic } from '@KTXM/MailManager/models/identity'
import { ServiceObject } from '@KTXM/MailManager/models/service'
import type { ServiceIdentity } from '@KTXM/MailManager/types/service'
import type { ProviderAuthPanelProps, ProviderAuthPanelEmits } from '@KTXM/MailManager/types/integration'
const props = defineProps<ProviderAuthPanelProps>()
const emit = defineEmits<ProviderAuthPanelEmits>()
const identity = ref(props.prefilledIdentity || props.emailAddress || '')
const secret = ref(props.prefilledSecret || '')
const identity = ref('')
const secret = ref('')
const rules = {
required: (value: unknown) => !!value || 'This field is required'
}
const isValid = computed(() => !!identity.value && !!secret.value)
const currentIdentity = computed((): ServiceIdentity | null => {
if (!isValid.value) {
if (!identity.value || !secret.value) {
return null
}
@@ -28,33 +28,13 @@ const currentIdentity = computed((): ServiceIdentity | null => {
})
watch(
currentIdentity,
value => {
if (value) {
emit('update:modelValue', value)
}
},
{ immediate: true, deep: true }
)
watch(
isValid,
value => {
emit('valid', value)
() => props.service,
service => {
syncFromService(service)
},
{ immediate: true }
)
watch(
() => props.modelValue,
value => {
if (value?.type === 'BA') {
identity.value = value.identity || ''
secret.value = value.secret || ''
}
}
)
watch(
() => props.emailAddress,
value => {
@@ -64,6 +44,55 @@ watch(
},
{ immediate: true }
)
watch(
currentIdentity,
value => {
const existingIdentity = props.service?.identity?.toJson() ?? null
if (sameIdentity(existingIdentity, value)) {
return
}
const nextService = createServiceObject(props.service)
nextService.identity = value ? new IdentityBasic(value.identity, value.secret) : null
emit('update:service', nextService)
},
{ immediate: true, deep: true }
)
function syncFromService(service?: ServiceObject) {
const serviceIdentity = service?.identity?.toJson() ?? null
if (serviceIdentity?.type === 'BA') {
identity.value = serviceIdentity.identity || props.prefilledIdentity || props.emailAddress || ''
secret.value = serviceIdentity.secret || props.prefilledSecret || ''
return
}
identity.value = props.prefilledIdentity || props.emailAddress || ''
secret.value = props.prefilledSecret || ''
}
function createServiceObject(service?: ServiceObject): ServiceObject {
const nextService = new ServiceObject()
if (service) {
nextService.fromJson(service.toJson())
}
return nextService
}
function sameIdentity(a: ServiceIdentity | null, b: ServiceIdentity | null): boolean {
if (a === null || b === null) {
return a === b
}
return a.type === 'BA'
&& b.type === 'BA'
&& a.identity === b.identity
&& a.secret === b.secret
}
</script>
<template>

View File

@@ -1,66 +1,26 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { LocationSocketSole } from '@KTXM/MailManager/models/location'
import { ServiceObject } from '@KTXM/MailManager/models/service'
import type {
ServiceLocation,
ServiceLocationSocketSole,
ServiceLocationUri,
} from '@KTXM/MailManager/types/service'
import type { ProviderConfigPanelProps, ProviderConfigPanelEmits } from '@KTXM/MailManager/types/integration'
import type {
ProviderProtocolPanelProps,
ProviderProtocolPanelEmits,
} from '@KTXM/MailManager/types/integration'
type ImapEncryption = 'none' | 'ssl' | 'tls' | 'starttls'
type ImapLocation = ServiceLocationSocketSole & {
type: 'SOCKET_SOLE' | 'URI'
verifyPeerName?: boolean
allowSelfSigned?: boolean
}
const props = defineProps<ProviderProtocolPanelProps>()
const emit = defineEmits<ProviderProtocolPanelEmits>()
const props = defineProps<ProviderConfigPanelProps>()
const emit = defineEmits<ProviderConfigPanelEmits>()
function asImapLocation(location?: ServiceLocation): ImapLocation | null {
if (!location) {
return null
}
if (location.type === 'SOCKET_SOLE') {
return location as ImapLocation
}
if (location.type === 'URI') {
const uriLocation = location as ServiceLocationUri & {
encryption?: ImapEncryption
verifyPeerName?: boolean
allowSelfSigned?: boolean
}
return {
type: 'URI',
host: uriLocation.host || '',
port: uriLocation.port || 993,
encryption: uriLocation.encryption || 'ssl',
verifyPeer: uriLocation.verifyPeer ?? true,
verifyHost: uriLocation.verifyPeerName ?? uriLocation.verifyHost ?? true,
verifyPeerName: uriLocation.verifyPeerName ?? uriLocation.verifyHost ?? true,
allowSelfSigned: uriLocation.allowSelfSigned ?? false,
}
}
return null
}
function defaultPortFor(encryption: ImapEncryption): number {
return encryption === 'ssl' || encryption === 'tls' ? 993 : 143
}
const sourceLocation = computed(() => asImapLocation(props.modelValue || props.discoveredLocation))
const host = ref(sourceLocation.value?.host || '')
const encryption = ref<ImapEncryption>(sourceLocation.value?.encryption || 'ssl')
const port = ref(String(sourceLocation.value?.port || defaultPortFor(encryption.value)))
const verifyPeer = ref(sourceLocation.value?.verifyPeer ?? true)
const verifyHost = ref(sourceLocation.value?.verifyPeerName ?? sourceLocation.value?.verifyHost ?? true)
const allowSelfSigned = ref(sourceLocation.value?.allowSelfSigned ?? false)
const host = ref('')
const encryption = ref<ImapEncryption>('ssl')
const port = ref('993')
const verifyPeer = ref(true)
const verifyHost = ref(true)
const encryptionOptions = [
{ title: 'Implicit TLS (SSL)', value: 'ssl' },
@@ -81,93 +41,98 @@ const rules = {
const isValid = computed(() => !!host.value && rules.port(port.value) === true)
const currentLocation = computed((): ServiceLocation | null => {
const currentLocation = computed((): ServiceLocationSocketSole | null => {
if (!isValid.value) {
return null
}
const numericPort = Number(port.value)
const location: ImapLocation = {
return {
type: 'SOCKET_SOLE',
host: host.value,
port: numericPort,
port: Number(port.value),
encryption: encryption.value,
verifyPeer: verifyPeer.value,
verifyHost: verifyHost.value,
verifyPeerName: verifyHost.value,
allowSelfSigned: allowSelfSigned.value,
}
return location as ServiceLocation
})
watch(
() => [props.service, props.discoveredLocation] as const,
([service, discoveredLocation]) => {
syncFromLocation(service?.location?.toJson() ?? discoveredLocation ?? null)
},
{ immediate: true }
)
watch(
currentLocation,
value => {
if (value) {
emit('update:modelValue', value)
location => {
const existingLocation = props.service?.location?.toJson() ?? null
if (sameLocation(existingLocation, location)) {
return
}
const nextService = createServiceObject(props.service)
nextService.location = location ? LocationSocketSole.fromJson(location) : null
emit('update:service', nextService)
},
{ immediate: true, deep: true }
)
watch(
isValid,
value => {
emit('valid', value)
},
{ immediate: true }
)
watch(
() => props.modelValue,
value => {
const next = asImapLocation(value)
if (!next) {
return
}
host.value = next.host || ''
encryption.value = next.encryption || 'ssl'
port.value = String(next.port || defaultPortFor(encryption.value))
verifyPeer.value = next.verifyPeer ?? true
verifyHost.value = next.verifyPeerName ?? next.verifyHost ?? true
allowSelfSigned.value = next.allowSelfSigned ?? false
}
)
watch(
() => props.discoveredLocation,
value => {
if (props.modelValue) {
return
}
const next = asImapLocation(value)
if (!next) {
return
}
host.value = next.host || ''
encryption.value = next.encryption || 'ssl'
port.value = String(next.port || defaultPortFor(encryption.value))
verifyPeer.value = next.verifyPeer ?? true
verifyHost.value = next.verifyPeerName ?? next.verifyHost ?? true
allowSelfSigned.value = next.allowSelfSigned ?? false
},
{ immediate: true }
)
watch(encryption, (next, previous) => {
const previousDefault = defaultPortFor(previous)
if (!port.value || Number(port.value) === previousDefault) {
port.value = String(defaultPortFor(next))
}
})
function syncFromLocation(location: ServiceLocation | null) {
const socketLocation = getSocketLocation(location)
host.value = socketLocation?.host ?? ''
encryption.value = socketLocation?.encryption ?? 'ssl'
port.value = String(socketLocation?.port ?? defaultPortFor(encryption.value))
verifyPeer.value = socketLocation?.verifyPeer ?? true
verifyHost.value = socketLocation?.verifyHost ?? true
}
function createServiceObject(service?: ServiceObject): ServiceObject {
const nextService = new ServiceObject()
if (service) {
nextService.fromJson(service.toJson())
}
return nextService
}
function getSocketLocation(location?: ServiceLocation | null): ServiceLocationSocketSole | null {
return location?.type === 'SOCKET_SOLE' ? location : null
}
function sameLocation(a: ServiceLocation | null, b: ServiceLocation | null): boolean {
if (a === null || b === null) {
return a === b
}
if (a.type !== 'SOCKET_SOLE' || b.type !== 'SOCKET_SOLE') {
return false
}
return a.host === b.host
&& a.port === b.port
&& a.encryption === b.encryption
&& (a.verifyPeer ?? true) === (b.verifyPeer ?? true)
&& (a.verifyHost ?? true) === (b.verifyHost ?? true)
}
function defaultPortFor(nextEncryption: ImapEncryption): number {
return nextEncryption === 'ssl' || nextEncryption === 'tls' ? 993 : 143
}
</script>
<template>
<div class="imap-config-panel">
<div class="imap-protocol-panel">
<h3 class="text-h6 mb-4">IMAP Connection Settings</h3>
<p class="text-body-2 mb-6">Configure the server address, transport security, and certificate verification for your IMAP mailbox.</p>
@@ -232,14 +197,6 @@ watch(encryption, (next, previous) => {
persistent-hint
class="mb-4"
/>
<v-switch
v-model="allowSelfSigned"
label="Allow self-signed certificates"
color="primary"
hint="Use only when your server is intentionally deployed with a self-signed certificate"
persistent-hint
/>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
@@ -256,7 +213,7 @@ watch(encryption, (next, previous) => {
</template>
<style scoped>
.imap-config-panel {
.imap-protocol-panel {
max-width: 800px;
}