feat: refactor Radicale contact sync logic to include retry mechanism and improve error handling

This commit is contained in:
2025-12-04 11:56:07 +01:00
parent 3292435e68
commit 1bc9dc2dd5

View File

@@ -138,6 +138,58 @@ export function isRadicaleEnabled() {
return getRadicaleConfig() !== null; return getRadicaleConfig() !== null;
} }
// Sync contact to Radicale (internal function with retry logic)
async function syncContactToRadicaleInternal(contact, forceUpdate = false) {
const config = getRadicaleConfig();
// Skip if not configured
if (!config) {
return { success: false, reason: 'not_configured' };
}
try {
const vcard = generateVCard(contact);
const auth = Buffer.from(`${config.username}:${config.password}`).toString('base64');
// Ensure URL ends with /
const baseUrl = config.url.endsWith('/') ? config.url : config.url + '/';
// Construct the URL for this specific contact
const vcardUrl = `${baseUrl}${config.username}/contacts/contact-${contact.contact_id}.vcf`;
const headers = {
'Authorization': `Basic ${auth}`,
'Content-Type': 'text/vcard; charset=utf-8'
};
// If not forcing update, only create if doesn't exist
if (!forceUpdate) {
headers['If-None-Match'] = '*';
}
const response = await fetch(vcardUrl, {
method: 'PUT',
headers: headers,
body: vcard
});
// Handle conflict - try again with force update
if (response.status === 412 || response.status === 409) {
// Conflict - contact already exists, update it instead
return await syncContactToRadicaleInternal(contact, true);
}
if (response.ok || response.status === 201 || response.status === 204) {
return { success: true, status: response.status, updated: forceUpdate };
} else {
const text = await response.text();
return { success: false, status: response.status, error: text };
}
} catch (error) {
return { success: false, error: error.message };
}
}
// Sync contact to Radicale // Sync contact to Radicale
export async function syncContactToRadicale(contact) { export async function syncContactToRadicale(contact) {
const config = getRadicaleConfig(); const config = getRadicaleConfig();
@@ -154,37 +206,16 @@ export async function syncContactToRadicale(contact) {
return { success: true, reason: 'inactive_skipped' }; return { success: true, reason: 'inactive_skipped' };
} }
try { const result = await syncContactToRadicaleInternal(contact);
const vcard = generateVCard(contact);
const auth = Buffer.from(`${config.username}:${config.password}`).toString('base64');
// Ensure URL ends with / if (result.success) {
const baseUrl = config.url.endsWith('/') ? config.url : config.url + '/'; const action = result.updated ? 'updated' : 'created';
console.log(`✅ Synced contact ${contact.contact_id} to Radicale (${action})`);
// Construct the URL for this specific contact } else if (result.reason !== 'not_configured') {
const vcardUrl = `${baseUrl}${config.username}/contacts/contact-${contact.contact_id}.vcf`; console.error(`❌ Failed to sync contact ${contact.contact_id} to Radicale: ${result.status} - ${result.error || result.reason}`);
const response = await fetch(vcardUrl, {
method: 'PUT',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'text/vcard; charset=utf-8'
},
body: vcard
});
if (response.ok || response.status === 201 || response.status === 204) {
console.log(`✅ Synced contact ${contact.contact_id} to Radicale`);
return { success: true, status: response.status };
} else {
const text = await response.text();
console.error(`❌ Failed to sync contact ${contact.contact_id} to Radicale: ${response.status} - ${text}`);
return { success: false, status: response.status, error: text };
}
} catch (error) {
console.error(`❌ Error syncing contact ${contact.contact_id} to Radicale:`, error);
return { success: false, error: error.message };
} }
return result;
} }
// Delete contact from Radicale // Delete contact from Radicale
@@ -229,6 +260,7 @@ export async function deleteContactFromRadicale(contactId) {
// Sync contact asynchronously (fire and forget) // Sync contact asynchronously (fire and forget)
export function syncContactAsync(contact) { export function syncContactAsync(contact) {
console.log(contact, isRadicaleEnabled());
if (!isRadicaleEnabled()) { if (!isRadicaleEnabled()) {
return; return;
} }