fix: de-namespace addressBookIds for shared contacts in create and update operations #133

This commit is contained in:
Linus Rath
2026-04-02 11:34:26 +02:00
parent cdea876992
commit 1b2c70a90b
2 changed files with 44 additions and 8 deletions
+2 -5
View File
@@ -304,11 +304,8 @@ export function ContactForm({ contact, addressBooks, allKeywords, onSave, onCanc
if (contact?.addressBookIds) {
const ids = Object.keys(contact.addressBookIds).filter(k => contact.addressBookIds[k]);
if (ids.length > 0) {
// For shared contacts, the addressBookIds uses the original (non-namespaced) id
// but we need the namespaced id to match addressBooks entries
if (contact.isShared && contact.accountId) {
return `${contact.accountId}:${ids[0]}`;
}
// addressBookIds are already namespaced for shared contacts (e.g. "accountId:bookId")
// so we can use them directly to match addressBook entries
return ids[0];
}
}
+42 -3
View File
@@ -155,8 +155,33 @@ export const useContactStore = create<ContactStore>()(
createContact: async (client, contact) => {
set({ isLoading: true, error: null });
try {
const accountId = contact.isShared ? contact.accountId : undefined;
const created = await client.createContact(contact, accountId);
// Determine target account from the selected address book
let accountId = contact.isShared ? contact.accountId : undefined;
let cleanedContact = contact;
// De-namespace addressBookIds if they reference a shared address book
if (contact.addressBookIds) {
const books = get().addressBooks;
const deNamespaced: Record<string, boolean> = {};
let sharedAccountId: string | undefined;
for (const [bookId, value] of Object.entries(contact.addressBookIds)) {
const book = books.find(b => b.id === bookId);
if (book?.isShared && book.originalId) {
deNamespaced[book.originalId] = value;
sharedAccountId = book.accountId;
} else {
deNamespaced[bookId] = value;
}
}
if (sharedAccountId) {
accountId = sharedAccountId;
cleanedContact = { ...contact, addressBookIds: deNamespaced, isShared: true, accountId: sharedAccountId };
} else {
cleanedContact = { ...contact, addressBookIds: deNamespaced };
}
}
const created = await client.createContact(cleanedContact, accountId);
// Preserve shared account metadata
if (contact.isShared && contact.accountId) {
created.accountId = contact.accountId;
@@ -182,7 +207,21 @@ export const useContactStore = create<ContactStore>()(
const contact = get().contacts.find(c => c.id === id);
const originalId = contact?.originalId || id;
const accountId = contact?.isShared ? contact.accountId : undefined;
await client.updateContact(originalId, updates, accountId);
// De-namespace addressBookIds for shared contacts before sending to JMAP server
let cleanedUpdates = updates;
if (contact?.isShared && contact?.accountId && updates.addressBookIds) {
const prefix = `${contact.accountId}:`;
const deNamespaced = Object.fromEntries(
Object.entries(updates.addressBookIds).map(([k, v]) => [
k.startsWith(prefix) ? k.slice(prefix.length) : k,
v
])
);
cleanedUpdates = { ...updates, addressBookIds: deNamespaced };
}
await client.updateContact(originalId, cleanedUpdates, accountId);
set((state) => ({
contacts: state.contacts.map(c =>
c.id === id ? { ...c, ...updates } : c