Notice for App Developers

To use these methods, the contacts module must be installed in the native app:

Overview

Streams the device's entire contact list to the eitri-app over the EventBus, leaving the UI entirely to the developer. The list is delivered as a sequence of batches rather than a single response to avoid the size limit of one bridge message.

Permissions

  • Android: Requires READ_CONTACTS permission.
  • iOS: Requires contacts authorization; the host app must declare NSContactsUsageDescription in its Info.plist, otherwise the request fails.

Hierarchy

  • Contacts

Methods

  • Reports whether the host app is configured for the contacts capabilities that read the address book, so an eitri-app can degrade gracefully instead of hitting a failure.

    • iOS: verifies the host declares NSContactsUsageDescription in its Info.plist. Without that key CNContactStore access cannot be requested at all.
    • Android: verifies the host declares READ_CONTACTS or WRITE_CONTACTS in its manifest. An undeclared permission is silently denied without ever prompting the user. Either one counts, since a read-only host declares only READ_CONTACTS; use checkPermission with an access scope to tell read and write apart.

    pickContact is the exception on both platforms: the OS renders the picker and returns only the chosen contact, so it needs neither the iOS usage-description key nor a declared Android permission and stays usable when this resolves false.

    Resolves false when the contacts module is not installed at all, so a missing integration is reported as unsupported rather than throwing.

    Compatibility Control

    Returns Promise<boolean>

  • Opens a stream of the device's entire contact list and resolves with the channel name to subscribe to via EventBus for the resulting ContactStreamChunk batches.

    Native does not begin publishing until you call startContactsStream. The required sequence is: (1) call this method to get dataChannel, (2) subscribe to that channel, (3) call startContactsStream({ dataChannel }). Starting only after subscribing guarantees no chunk is lost to a publish-before-subscribe race.

    Each event is a ContactStreamChunk: data-bearing chunks carry final: false and a data array of Contact; a terminal chunk with final: true (and no data) closes the stream. On failure, a single chunk with success: false, final: true and an errorMessage is emitted instead — including "contacts.stream.not.started" when startContactsStream is not called within 30 seconds. Every path therefore ends in a final: true chunk, so a subscriber never waits indefinitely. Remember to clear the subscription once the stream ends.

    Example:

    const modules = await Eitri.modules();
    if (!modules?.contacts?.streamAllContacts) {
    return; // contacts module is unavailable; hide UI that depends on it
    }

    const permission = await Eitri.contacts.checkPermission({ access: "read" });
    if (permission.status !== "GRANTED") {
    const granted = await Eitri.contacts.requestPermission({ access: "read" });
    if (granted.status !== "GRANTED") {
    return; // permission refused
    }
    }

    const allContacts = [];
    // 1. Open the stream (native pauses before reading contacts)
    const { dataChannel } = await Eitri.contacts.streamAllContacts();

    // 2. Subscribe to the channel
    const onChunk = (chunk) => {
    if (!chunk.success) {
    console.log("contacts.stream.error:", chunk.errorMessage);
    Eitri.eventBus.clear({ channel: dataChannel, callback: onChunk });
    return;
    }
    if (chunk.data) {
    allContacts.push(...chunk.data);
    }
    if (chunk.final) {
    Eitri.eventBus.clear({ channel: dataChannel, callback: onChunk });
    console.log("received", allContacts.length, "contacts");
    }
    };
    Eitri.eventBus.subscribe({ channel: dataChannel, callback: onChunk });

    // 3. Start streaming (safe — the listener is already registered)
    await Eitri.contacts.startContactsStream({ dataChannel });

    Compatibility Control

    Returns Promise<ContactStreamResponse>

  • Signals the native side to begin streaming contacts for a channel opened by streamAllContacts. Must be called after subscribing to that channel via EventBus.

    This exists to avoid a race where native starts publishing chunks before the eitri-app has registered its listener — chunks published to a channel with no listener are dropped. Native holds the stream until this call arrives, for up to 30 seconds; if it never arrives, the stream closes with a terminal success: false chunk carrying errorMessage: "contacts.stream.not.started" rather than leaving a subscriber waiting forever. Calling this after that point is a no-op.

    See streamAllContacts for the full open → subscribe → start example.

    Compatibility Control

    Parameters

    • dataChannel: {
          dataChannel: string;
      }

      The dataChannel returned by streamAllContacts.

      • dataChannel: string

    Returns Promise<void>

  • Returns the current status of contacts access without prompting the user, scoped to the requested capability. Read and write are independent: checking "read" never reports blocked because write is unavailable, and checking "write" never reports blocked because read is unavailable.

    • Android: inspects READ_CONTACTS for "read", WRITE_CONTACTS for "write", or both for "readWrite". Returns BLOCKED before the first request, DENIED after a rejection that can still be retried, and GRANTED once the scoped permission(s) are allowed.
    • iOS: reflects CNContactStore authorization (a single read+write grant), so every scope maps to the same status: DENIED when not yet determined, BLOCKED when denied or restricted, and GRANTED once authorized.

    Compatibility Control

    Parameters

    • input: ContactsPermissionInput

      Which capability to check. access is required — pass the narrowest scope the eitri-app actually needs.

    Returns Promise<ContactsPermissionOutput>

  • Prompts the user for contacts access, if not already granted, scoped to the requested capability. Read and write are independent — request only what the eitri-app needs.

    • Android: shows the system prompt for READ_CONTACTS ("read"), WRITE_CONTACTS ("write"), or both ("readWrite"), and resolves with the resulting status.
    • iOS: requests CNContactStore access (one authorization covering read and write) for every scope, and resolves with the resulting status. A host that has not declared NSContactsUsageDescription resolves BLOCKED without ever reaching the native request, since making that request would terminate the process.

    Compatibility Control

    Parameters

    • input: ContactsPermissionInput

      Which capability to request. access is required — request only what the eitri-app actually needs.

    Returns Promise<ContactsPermissionOutput>

  • Stops a running contacts stream, so the native side abandons a read the eitri-app no longer needs — navigating away from the screen that opened it, or the user cancelling. Chunks already delivered stay delivered; no further chunk arrives, including a terminal one.

    Safe to call for an unknown or already-finished dataChannel: it resolves without effect.

    Compatibility Control

    Parameters

    • dataChannel: {
          dataChannel: string;
      }

      The dataChannel returned by streamAllContacts.

      • dataChannel: string

    Returns Promise<void>

  • Opens the native system contact card for the contact identified by id, so the user can view (and, through the OS UI, edit) it. Resolves once the card has been presented. Use the id you obtained from streamAllContacts or pickContact.

    • Android: launches ACTION_VIEW on the contact's lookup URI. The OS Contacts app renders the card, so no contacts permission is required.
    • iOS: presents a CNContactViewController, which must be handed a fully-fetched contact. That read goes through CNContactStore and therefore requires contacts authorization.

    This asymmetry matters when pairing with pickContact: on iOS the picker grants only a one-time snapshot of the chosen contact, never lasting access to it, so an id kept from the picker cannot be reopened later without authorization — the call rejects with a permission error. Android has no such restriction. To support both platforms, either check checkPermission with { access: "read" } before offering an "open contact" action, or render the picked contact's data in your own UI instead of opening the system card.

    Compatibility Control

    Parameters

    • id: string

    Returns Promise<void>

  • Opens the native system contact picker so the user can choose a single contact, and resolves with the chosen Contact (including its id), or null if the user dismisses the picker without choosing.

    This is the module picker. pickContactBasic is the built-in alternative that needs no module at all, at the cost of a much thinner contact on Android — compare the two before choosing.

    This is an alternative to streamAllContacts for getting a contact's data: the OS renders the list/selection UI and only the picked contact is returned, so it works without contacts read permission — with a platform difference in how much data comes back.

    • Android: launches ACTION_PICK against the contacts provider. The grant Android issues covers the returned URI exactly, not the sub-directories holding the data rows, so without READ_CONTACTS the result carries id and displayName only. Hold READ_CONTACTS to receive phones, emails, addresses and organization as well; check checkPermission with { access: "read" } if the eitri-app needs to know which shape to expect.
    • iOS: presents a CNContactPickerViewController, which runs out of process and returns the fully-populated contact regardless of authorization.

    The returned Contact carries the data the eitri-app needs, but on iOS the picker is a one-time snapshot rather than a grant: id cannot be used to read that contact again later, so passing it to openContact still requires authorization. Treat the returned object — not the id — as the result of picking.

    Compatibility Control

    Returns Promise<null | Contact>

  • Deletes the contact identified by id.

    Rejects when the contact no longer exists on either platform, so a stale id from an earlier stream is reported rather than resolving as a delete that never happened.

    This is a write operation:

    • Android: requires the WRITE_CONTACTS permission.
    • iOS: requires CNContactStore write access (the same authorization as reading).

    Compatibility Control

    Parameters

    • id: string

    Returns Promise<void>

  • Creates a new contact from the provided ContactInput. Resolves once the contact has been written, and rejects if the write fails.

    This is a write operation:

    • Android: requires the WRITE_CONTACTS permission.
    • iOS: requires CNContactStore write access (the same authorization as reading).

    Compatibility Control

    Parameters

    Returns Promise<void>

  • Opens the native system contact picker so the user chooses a single contact, and resolves with the chosen Contact — or null when the user dismisses the picker without choosing.

    Unlike the rest of this service, this method is built into eitri-machine: it needs no contacts module and no contacts permission, because the OS renders the picker and hands back only what the user explicitly selected. The trade-off is how much of the contact comes back, which differs sharply per platform — see What each platform returns below.

    pickContact is the module counterpart: it needs the contacts module installed, but returns the same fully-populated shape on both platforms. Prefer it when the eitri-app needs consistent data; prefer this one when it must work on a host app that ships no contacts module.

    What each platform returns

    Field iOS Android 17+ Android < 17
    id
    displayName
    phones ✅ all numbers ✅ all numbers ⚠️ only the single number picked
    emails ❌ always empty
    structuredName ❌ always null ❌ always null
    addresses ❌ always empty ❌ always empty
    organization ❌ always null ❌ always null
    • iOS presents a CNContactPickerViewController, which runs out of process and returns the whole contact record. Every field above is populated when the contact has it. normalizedNumber is the one exception: iOS exposes no normalized form, so it is always null.
    • Android 17+ uses the system contacts picker, which shares only the data fields the SDK declares — phones and e-mails. Names beyond displayName, postal addresses and organization are never shared, so those fields come back empty regardless of the contact.
    • Android below 17 has no contact-level picker: the OS offers a phone number picker, so the user picks one number rather than a contact. Only that number comes back, and reading the rest of the contact's data would require READ_CONTACTS.

    Treat the returned object — not the id — as the result of picking. On iOS the picker is a one-time snapshot rather than a grant, so id cannot be used to read that contact again later; passing it to openContact still requires authorization. Android has no such restriction.

    Rejects with contacts.pick.context.isnt.activity when the host app has no screen to present the picker from, and with contacts.pick.unavailable (Android) when no installed app can handle the pick. A dismissed picker resolves null — it is never reported as an error.

    Compatibility Control

    • API LEVEL 38 - Functionality added

    Returns Promise<null | Contact>

  • Opens the native system contact picker in multi-selection mode and resolves with the chosen contacts, or an empty array when the user dismisses the picker without choosing.

    Like pickContactBasic, this is built into eitri-machine and requires no contacts module and no contacts permission.

    Availability

    • iOS: always available.
    • Android: requires Android 17 or newer, which is where the system contacts picker first supports selecting several contacts. On older Android versions the call rejects with contacts.pick.multiple.unsupported. Fall back to pickContactBasic, which works on every Android version and routes to this same picker when it is available.

    Call isMultiplePickSupported before offering multi-selection in your UI, so an unsupported device never gets a button that can only fail.

    What each platform returns

    Each element carries the same per-platform shape described in pickContactBasic: iOS returns fully-populated contacts, while Android returns id, displayName, phones and emails only — structuredName, addresses and organization are never shared by the picker.

    The user may select as many contacts as they wish; neither platform imposes a cap.

    Rejects with contacts.pick.context.isnt.activity when the host app has no screen to present the picker from — a host integration fault, reported rather than resolving as an empty selection.

    Compatibility Control

    • API LEVEL 38 - Functionality added

    Returns Promise<Contact[]>

  • Reports whether this device can present the multi-selection contact picker, so an eitri-app can hide or disable a "pick several contacts" affordance instead of offering one that only fails when tapped.

    • iOS: always true.
    • Android: true only on Android 17 or newer, where the system contacts picker first supports selecting several contacts.

    Only pickContactsBasic depends on this. pickContactBasic works on every device regardless.

    if (await Eitri.contacts.isMultiplePickSupported()) {
    const contacts = await Eitri.contacts.pickContactsBasic();
    }

    Compatibility Control

    • API LEVEL 38 - Functionality added

    Returns Promise<boolean>

Generated using TypeDoc