diff --git a/src/views/ContactImportView.vue b/src/views/ContactImportView.vue index ebbc21da..004b7223 100644 --- a/src/views/ContactImportView.vue +++ b/src/views/ContactImportView.vue @@ -223,6 +223,75 @@ import { getContactJwtFromJwtUrl } from "../libs/crypto"; import { decodeEndorserJwt } from "../libs/crypto/vc"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; +/** + * Interface for contact data as stored in the database + * Differs from Contact interface in that contactMethods is stored as a JSON string + */ +interface ContactDbRecord { + did: string; + contactMethods: string; + name: string; + notes: string; + profileImageUrl: string; + publicKeyBase64: string; + nextPubKeyHashB64: string; + seesMe: boolean; + registered: boolean; +} + +/** + * Ensures a value is a string, never null or undefined + */ +function safeString(val: unknown): string { + return typeof val === 'string' ? val : (val == null ? '' : String(val)); +} + +/** + * Converts a Contact object to a ContactDbRecord for database storage + * @param contact The contact object to convert + * @returns A ContactDbRecord with contactMethods as a JSON string + * @throws Error if contact.did is missing or invalid + */ +function contactToDbRecord(contact: Contact): ContactDbRecord { + if (!contact.did) { + throw new Error("Contact must have a DID"); + } + + // Convert contactMethods array to JSON string, defaulting to empty array + const contactMethodsStr = (contact.contactMethods != null + ? JSON.stringify(contact.contactMethods) + : "[]"); + + return { + did: safeString(contact.did), // Required field, must be present + contactMethods: contactMethodsStr, + name: safeString(contact.name), + notes: safeString(contact.notes), + profileImageUrl: safeString(contact.profileImageUrl), + publicKeyBase64: safeString(contact.publicKeyBase64), + nextPubKeyHashB64: safeString(contact.nextPubKeyHashB64), + seesMe: contact.seesMe ?? false, + registered: contact.registered ?? false + }; +} + +/** + * Converts a ContactDbRecord back to a Contact object + * @param record The database record to convert + * @returns A Contact object with parsed contactMethods array + */ +function dbRecordToContact(record: ContactDbRecord): Contact { + return { + ...record, + name: safeString(record.name), + notes: safeString(record.notes), + profileImageUrl: safeString(record.profileImageUrl), + publicKeyBase64: safeString(record.publicKeyBase64), + nextPubKeyHashB64: safeString(record.nextPubKeyHashB64), + contactMethods: JSON.parse(record.contactMethods || "[]") + }; +} + /** * Contact Import View Component * @author Matthew Raymer @@ -533,25 +602,36 @@ export default class ContactImportView extends Vue { if (this.contactsSelected[i]) { const contact = this.contactsImporting[i]; const existingContact = this.contactsExisting[contact.did]; + const platformService = PlatformServiceFactory.getInstance(); + + // Convert contact to database record format + const contactToStore = contactToDbRecord(contact); + if (existingContact) { - const platformService = PlatformServiceFactory.getInstance(); - // @ts-expect-error because we're just using the value to store to the DB - contact.contactMethods = JSON.stringify(contact.contactMethods); + // Update existing contact const { sql, params } = databaseUtil.generateUpdateStatement( - contact as unknown as Record, + contactToStore as unknown as Record, "contacts", "did = ?", [contact.did], ); await platformService.dbExec(sql, params); if (USE_DEXIE_DB) { - await db.contacts.update(contact.did, contact); + // For Dexie, we need to parse the contactMethods back to an array + await db.contacts.update(contact.did, dbRecordToContact(contactToStore)); } updatedCount++; } else { - // without explicit clone on the Proxy, we get: DataCloneError: Failed to execute 'add' on 'IDBObjectStore': # could not be cloned. - // DataError: Failed to execute 'add' on 'IDBObjectStore': Evaluating the object store's key path yielded a value that is not a valid key. - await db.contacts.add(R.clone(contact)); + // Add new contact + const { sql, params } = databaseUtil.generateInsertStatement( + contactToStore as unknown as Record, + "contacts", + ); + await platformService.dbExec(sql, params); + if (USE_DEXIE_DB) { + // For Dexie, we need to parse the contactMethods back to an array + await db.contacts.add(dbRecordToContact(contactToStore)); + } importedCount++; } }