feat: refactor Radicale contact sync logic to include retry mechanism and improve error handling
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user