53 lines
1.8 KiB
TypeScript
53 lines
1.8 KiB
TypeScript
/**
|
|
* Device contacts import (mobile). Reads the OS address book via the
|
|
* `ulti-core` contacts plugin (Android ContactsContract / iOS Contacts) and
|
|
* maps to the shared `ContactImportInput` shape used by the import dialog.
|
|
*/
|
|
import { invoke } from "@/lib/native/bridge"
|
|
import { isTauriRuntime } from "@/lib/platform"
|
|
import type { ContactImportInput } from "@/lib/contacts/import-parsers"
|
|
|
|
type DeviceContact = {
|
|
display_name?: string | null
|
|
first_name?: string | null
|
|
last_name?: string | null
|
|
emails: string[]
|
|
phones: string[]
|
|
organization?: string | null
|
|
}
|
|
|
|
function splitDisplayName(name: string): { firstName: string; lastName: string } {
|
|
const parts = name.trim().split(/\s+/)
|
|
if (parts.length <= 1) return { firstName: parts[0] ?? "", lastName: "" }
|
|
return { firstName: parts[0], lastName: parts.slice(1).join(" ") }
|
|
}
|
|
|
|
function toImportInput(c: DeviceContact): ContactImportInput {
|
|
let firstName = c.first_name ?? ""
|
|
let lastName = c.last_name ?? ""
|
|
if (!firstName && !lastName && c.display_name) {
|
|
const parts = splitDisplayName(c.display_name)
|
|
firstName = parts.firstName
|
|
lastName = parts.lastName
|
|
}
|
|
return {
|
|
firstName,
|
|
lastName,
|
|
company: c.organization ?? undefined,
|
|
emails: c.emails.map((value) => ({ value, label: "personal" })),
|
|
phones: c.phones.map((value) => ({ value, label: "mobile" })),
|
|
}
|
|
}
|
|
|
|
/** True when device contacts import is available (native runtime). */
|
|
export function deviceContactsAvailable(): boolean {
|
|
return isTauriRuntime()
|
|
}
|
|
|
|
/** Fetch device contacts. Throws a coded error if unsupported/denied. */
|
|
export async function fetchDeviceContacts(): Promise<ContactImportInput[]> {
|
|
const list = await invoke<DeviceContact[]>("plugin:ulti-core|contacts_fetch")
|
|
if (!list) throw new Error("contacts_unavailable")
|
|
return list.map(toImportInput)
|
|
}
|