1import { PermissionResponse, PermissionStatus, UnavailabilityError, uuid } from 'expo-modules-core'; 2import { Platform, Share } from 'react-native'; 3 4import ExpoContacts from './ExpoContacts'; 5 6export type CalendarFormatType = CalendarFormats | `${CalendarFormats}`; 7 8export type ContainerType = ContainerTypes | `${ContainerTypes}`; 9 10export type ContactType = ContactTypes | `${ContactTypes}`; 11 12export type FieldType = Fields | `${Fields}`; 13 14export type Date = { 15 /** 16 * Day. 17 */ 18 day?: number; 19 /** 20 * Month - adjusted for JavaScript `Date` which starts at `0`. 21 */ 22 month?: number; 23 /** 24 * Year. 25 */ 26 year?: number; 27 /** 28 * Unique ID. This value will be generated by the OS. 29 */ 30 id?: string; 31 /** 32 * Localized display name. 33 */ 34 label: string; 35 /** 36 * Format for the input date. 37 */ 38 format?: CalendarFormatType; 39}; 40 41export type Relationship = { 42 /** 43 * Localized display name. 44 */ 45 label: string; 46 /** 47 * Name of related contact. 48 */ 49 name?: string; 50 /** 51 * Unique ID. This value will be generated by the OS. 52 */ 53 id?: string; 54}; 55 56export type Email = { 57 /** 58 * Email address. 59 */ 60 email?: string; 61 /** 62 * Flag signifying if it is a primary email address. 63 */ 64 isPrimary?: boolean; 65 /** 66 * Localized display name. 67 */ 68 label: string; 69 /** 70 * Unique ID. This value will be generated by the OS. 71 */ 72 id?: string; 73}; 74 75export type PhoneNumber = { 76 /** 77 * Phone number. 78 */ 79 number?: string; 80 /** 81 * Flag signifying if it is a primary phone number. 82 */ 83 isPrimary?: boolean; 84 /** 85 * Phone number without format. 86 * @example `8674305` 87 */ 88 digits?: string; 89 /** 90 * Country code. 91 * @example `+1` 92 */ 93 countryCode?: string; 94 /** 95 * Localized display name. 96 */ 97 label: string; 98 /** 99 * Unique ID. This value will be generated by the OS. 100 */ 101 id?: string; 102}; 103 104export type Address = { 105 /** 106 * Street name. 107 */ 108 street?: string; 109 /** 110 * City name. 111 */ 112 city?: string; 113 /** 114 * Country name 115 */ 116 country?: string; 117 /** 118 * Region or state name. 119 */ 120 region?: string; 121 /** 122 * Neighborhood name. 123 */ 124 neighborhood?: string; 125 /** 126 * Local post code. 127 */ 128 postalCode?: string; 129 /** 130 * P.O. Box. 131 */ 132 poBox?: string; 133 /** 134 * [Standard country code](https://www.iso.org/iso-3166-country-codes.html). 135 */ 136 isoCountryCode?: string; 137 /** 138 * Localized display name. 139 */ 140 label: string; 141 /** 142 * Unique ID. This value will be generated by the OS. 143 */ 144 id?: string; 145}; 146 147/** 148 * @platform ios 149 */ 150export type SocialProfile = { 151 /** 152 * Name of social app. 153 */ 154 service?: string; 155 /** 156 * Localized profile name. 157 */ 158 localizedProfile?: string; 159 /** 160 * Web URL. 161 */ 162 url?: string; 163 /** 164 * Username in social app. 165 */ 166 username?: string; 167 /** 168 * Username ID in social app. 169 */ 170 userId?: string; 171 /** 172 * Localized display name. 173 */ 174 label: string; 175 /** 176 * Unique ID. This value will be generated by the OS. 177 */ 178 id?: string; 179}; 180 181export type InstantMessageAddress = { 182 /** 183 * Name of instant messaging app. 184 */ 185 service?: string; 186 /** 187 * Username in IM app. 188 */ 189 username?: string; 190 /** 191 * Localized name of app. 192 */ 193 localizedService?: string; 194 /** 195 * Localized display name. 196 */ 197 label: string; 198 /** 199 * Unique ID. This value will be generated by the OS. 200 */ 201 id?: string; 202}; 203 204export type UrlAddress = { 205 /** 206 * Localized display name. 207 */ 208 label: string; 209 /** 210 * Web URL. 211 */ 212 url?: string; 213 /** 214 * Unique ID. This value will be generated by the OS. 215 */ 216 id?: string; 217}; 218 219// @needs-audit 220/** 221 * Information regarding thumbnail images. 222 * > On Android you can get dimensions using [`Image.getSize`](https://reactnative.dev/docs/image#getsize) method. 223 */ 224export type Image = { 225 uri?: string; 226 /** 227 * Image width. 228 * @platform ios 229 */ 230 width?: number; 231 /** 232 * Image height 233 * @platform ios 234 */ 235 height?: number; 236 /** 237 * Image as Base64 string. 238 */ 239 base64?: string; 240}; 241 242/** 243 * A set of fields that define information about a single contact entity. 244 */ 245export type Contact = { 246 /** 247 * Immutable identifier used for querying and indexing. This value will be generated by the OS when the contact is created. 248 */ 249 id?: string; 250 /** 251 * Denoting a person or company. 252 */ 253 contactType: ContactType; 254 /** 255 * Full name with proper format. 256 */ 257 name: string; 258 /** 259 * Given name. 260 */ 261 firstName?: string; 262 /** 263 * Middle name 264 */ 265 middleName?: string; 266 /** 267 * Last name. 268 */ 269 lastName?: string; 270 /** 271 * Maiden name. 272 */ 273 maidenName?: string; 274 /** 275 * Dr. Mr. Mrs. ect… 276 */ 277 namePrefix?: string; 278 /** 279 * Jr. Sr. ect… 280 */ 281 nameSuffix?: string; 282 /** 283 * An alias to the proper name. 284 */ 285 nickname?: string; 286 /** 287 * Pronunciation of the first name. 288 */ 289 phoneticFirstName?: string; 290 /** 291 * Pronunciation of the middle name. 292 */ 293 phoneticMiddleName?: string; 294 /** 295 * Pronunciation of the last name. 296 */ 297 phoneticLastName?: string; 298 /** 299 * Organization the entity belongs to. 300 */ 301 company?: string; 302 /** 303 * Job description. 304 */ 305 jobTitle?: string; 306 /** 307 * Job department. 308 */ 309 department?: string; 310 /** 311 * Additional information. 312 * > On iOS 13+, the `note` field [requires your app to request additional entitlements](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_contacts_notes). 313 * > The Expo Go app does not contain those entitlements, so in order to test this feature you will need to [request the entitlement from Apple](https://developer.apple.com/contact/request/contact-note-field), 314 * > set the [`ios.accessesContactNotes`](./../config/app/#accessescontactnotes) field in **app config** to `true`, and [create your development build](/develop/development-builds/create-a-build/). 315 */ 316 note?: string; 317 /** 318 * Used for efficient retrieval of images. 319 */ 320 imageAvailable?: boolean; 321 /** 322 * Thumbnail image. On iOS it size is set to 320×320px, on Android it may vary. 323 */ 324 image?: Image; 325 /** 326 * Raw image without cropping, usually large. 327 */ 328 rawImage?: Image; 329 /** 330 * Birthday information in Gregorian format. 331 */ 332 birthday?: Date; 333 /** 334 * A labeled list of other relevant user dates in Gregorian format. 335 */ 336 dates?: Date[]; 337 /** 338 * Names of other relevant user connections. 339 */ 340 relationships?: Relationship[]; 341 /** 342 * Email addresses. 343 */ 344 emails?: Email[]; 345 /** 346 * Phone numbers. 347 */ 348 phoneNumbers?: PhoneNumber[]; 349 /** 350 * Locations. 351 */ 352 addresses?: Address[]; 353 /** 354 * Instant messaging connections. 355 */ 356 instantMessageAddresses?: InstantMessageAddress[]; 357 /** 358 * Associated web URLs. 359 */ 360 urlAddresses?: UrlAddress[]; 361 /** 362 * Birthday that doesn't conform to the Gregorian calendar format, interpreted based on the [calendar `format`](#date) setting. 363 * @platform ios 364 */ 365 nonGregorianBirthday?: Date; 366 /** 367 * Social networks. 368 * @platform ios 369 */ 370 socialProfiles?: SocialProfile[]; 371}; 372 373/** 374 * The return value for queried contact operations like `getContactsAsync`. 375 */ 376export type ContactResponse = { 377 /** 378 * An array of contacts that match a particular query. 379 */ 380 data: Contact[]; 381 /** 382 * This will be `true` if there are more contacts to retrieve beyond what is returned. 383 */ 384 hasNextPage: boolean; 385 /** 386 * This will be `true if there are previous contacts that weren't retrieved due to `pageOffset` limit. 387 */ 388 hasPreviousPage: boolean; 389}; 390 391export type ContactSort = `${SortTypes}`; 392 393/** 394 * Used to query contacts from the user's device. 395 */ 396export type ContactQuery = { 397 /** 398 * The max number of contacts to return. If skipped or set to `0` all contacts will be returned. 399 */ 400 pageSize?: number; 401 /** 402 * The number of contacts to skip before gathering contacts. 403 */ 404 pageOffset?: number; 405 /** 406 * If specified, the defined fields will be returned. If skipped, all fields will be returned. 407 */ 408 fields?: FieldType[]; 409 /** 410 * Sort method used when gathering contacts. 411 */ 412 sort?: ContactSort; 413 /** 414 * Get all contacts whose name contains the provided string (not case-sensitive). 415 */ 416 name?: string; 417 /** 418 * Get contacts with a matching ID or array of IDs. 419 */ 420 id?: string | string[]; 421 /** 422 * Get all contacts that belong to the group matching this ID. 423 * @platform ios 424 */ 425 groupId?: string; 426 /** 427 * Get all contacts that belong to the container matching this ID. 428 * @platform ios 429 */ 430 containerId?: string; 431 /** 432 * Prevent unification of contacts when gathering. 433 * @default false 434 * @platform ios 435 */ 436 rawContacts?: boolean; 437}; 438 439/** 440 * Denotes the functionality of a native contact form. 441 */ 442export type FormOptions = { 443 /** 444 * The properties that will be displayed. On iOS those properties does nothing while in editing mode. 445 */ 446 displayedPropertyKeys?: FieldType[]; 447 /** 448 * Controller title. 449 */ 450 message?: string; 451 /** 452 * Used if contact doesn't have a name defined. 453 */ 454 alternateName?: string; 455 /** 456 * Allows for contact mutation. 457 */ 458 allowsEditing?: boolean; 459 /** 460 * Actions like share, add, create. 461 */ 462 allowsActions?: boolean; 463 /** 464 * Show or hide the similar contacts. 465 */ 466 shouldShowLinkedContacts?: boolean; 467 /** 468 * Present the new contact controller. If set to `false` the unknown controller will be shown. 469 */ 470 isNew?: boolean; 471 /** 472 * The name of the left bar button. 473 */ 474 cancelButtonTitle?: string; 475 /** 476 * Prevents the controller from animating in. 477 */ 478 preventAnimation?: boolean; 479 /** 480 * The parent group for a new contact. 481 */ 482 groupId?: string; 483}; 484 485/** 486 * Used to query native contact groups. 487 * @platform ios 488 */ 489export type GroupQuery = { 490 /** 491 * Query the group with a matching ID. 492 */ 493 groupId?: string; 494 /** 495 * Query all groups matching a name. 496 */ 497 groupName?: string; 498 /** 499 * Query all groups that belong to a certain container. 500 */ 501 containerId?: string; 502}; 503 504/** 505 * A parent to contacts. A contact can belong to multiple groups. Here are some query operations you can perform: 506 * - Child Contacts: `getContactsAsync({ groupId })` 507 * - Groups From Container: `getGroupsAsync({ containerId })` 508 * - Groups Named: `getContainersAsync({ groupName })` 509 * @platform ios 510 */ 511export type Group = { 512 /** 513 * Immutable id representing the group. 514 */ 515 name?: string; 516 /** 517 * The editable name of a group. 518 */ 519 id?: string; 520}; 521 522/** 523 * Used to query native contact containers. 524 * @platform ios 525 */ 526export type ContainerQuery = { 527 /** 528 * Query all the containers that parent a contact. 529 */ 530 contactId?: string; 531 /** 532 * Query all the containers that parent a group. 533 */ 534 groupId?: string; 535 /** 536 * Query all the containers that matches ID or an array od IDs. 537 */ 538 containerId?: string | string[]; 539}; 540 541export type Container = { 542 name: string; 543 id: string; 544 type: ContainerType; 545}; 546 547export { PermissionStatus, PermissionResponse }; 548 549/** 550 * Returns whether the Contacts API is enabled on the current device. This method does not check the app permissions. 551 * @returns A promise that fulfills with a `boolean`, indicating whether the Contacts API is available on the current device. It always resolves to `false` on web. 552 */ 553export async function isAvailableAsync(): Promise<boolean> { 554 return !!ExpoContacts.getContactsAsync; 555} 556 557export async function shareContactAsync( 558 contactId: string, 559 message: string, 560 shareOptions: object = {} 561): Promise<any> { 562 if (Platform.OS === 'ios') { 563 const url = await writeContactToFileAsync({ 564 id: contactId, 565 }); 566 return await Share.share( 567 { 568 url, 569 message, 570 }, 571 shareOptions 572 ); 573 } else if (!ExpoContacts.shareContactAsync) { 574 throw new UnavailabilityError('Contacts', 'shareContactAsync'); 575 } 576 return await ExpoContacts.shareContactAsync(contactId, message); 577} 578 579/** 580 * Return a list of contacts that fit a given criteria. You can get all of the contacts by passing no criteria. 581 * @param contactQuery Object used to query contacts. 582 * @return A promise that fulfills with `ContactResponse` object returned from the query. 583 * @example 584 * ```js 585 * const { data } = await Contacts.getContactsAsync({ 586 * fields: [Contacts.Fields.Emails], 587 * }); 588 * 589 * if (data.length > 0) { 590 * const contact = data[0]; 591 * console.log(contact); 592 * } 593 * ``` 594 */ 595export async function getContactsAsync(contactQuery: ContactQuery = {}): Promise<ContactResponse> { 596 if (!ExpoContacts.getContactsAsync) { 597 throw new UnavailabilityError('Contacts', 'getContactsAsync'); 598 } 599 return await ExpoContacts.getContactsAsync(contactQuery); 600} 601 602export async function getPagedContactsAsync( 603 contactQuery: ContactQuery = {} 604): Promise<ContactResponse> { 605 const { pageSize, ...nOptions } = contactQuery; 606 607 if (pageSize && pageSize <= 0) { 608 throw new Error('Error: Contacts.getPagedContactsAsync: `pageSize` must be greater than 0'); 609 } 610 611 return await getContactsAsync({ 612 ...nOptions, 613 pageSize, 614 }); 615} 616 617/** 618 * Used for gathering precise data about a contact. Returns a contact matching the given `id`. 619 * @param id The ID of a system contact. 620 * @param fields If specified, the fields defined will be returned. When skipped, all fields will be returned. 621 * @return A promise that fulfills with `Contact` object with ID matching the input ID, or `undefined` if there is no match. 622 * @example 623 * ```js 624 * const contact = await Contacts.getContactByIdAsync('161A368D-D614-4A15-8DC6-665FDBCFAE55'); 625 * if (contact) { 626 * console.log(contact); 627 * } 628 * ``` 629 */ 630export async function getContactByIdAsync( 631 id: string, 632 fields?: FieldType[] 633): Promise<Contact | undefined> { 634 if (!ExpoContacts.getContactsAsync) { 635 throw new UnavailabilityError('Contacts', 'getContactsAsync'); 636 } 637 638 if (id == null) { 639 throw new Error('Error: Contacts.getContactByIdAsync: Please pass an ID as a parameter'); 640 } else { 641 const results = await ExpoContacts.getContactsAsync({ 642 pageSize: 1, 643 pageOffset: 0, 644 fields, 645 id, 646 }); 647 if (results && results.data && results.data.length > 0) { 648 return results.data[0]; 649 } 650 } 651 return undefined; 652} 653 654/** 655 * Creates a new contact and adds it to the system. 656 * > **Note**: For Android users, the Expo Go app does not have the required `WRITE_CONTACTS` permission to write to Contacts. 657 * > You will need to create a [development build](/develop/development-builds/create-a-build/) and add permission in there manually to use this method. 658 * @param contact A contact with the changes you wish to persist. The `id` parameter will not be used. 659 * @param containerId @tag-ios The container that will parent the contact. 660 * @return A promise that fulfills with ID of the new system contact. 661 * @example 662 * ```js 663 * const contact = { 664 * [Contacts.Fields.FirstName]: 'Bird', 665 * [Contacts.Fields.LastName]: 'Man', 666 * [Contacts.Fields.Company]: 'Young Money', 667 * }; 668 * const contactId = await Contacts.addContactAsync(contact); 669 * ``` 670 */ 671export async function addContactAsync(contact: Contact, containerId?: string): Promise<string> { 672 if (!ExpoContacts.addContactAsync) { 673 throw new UnavailabilityError('Contacts', 'addContactAsync'); 674 } 675 676 const noIdContact = removeIds(contact); 677 678 return await ExpoContacts.addContactAsync(noIdContact, containerId); 679} 680 681/** 682 * Mutate the information of an existing contact. Due to an iOS bug, `nonGregorianBirthday` field cannot be modified. 683 * > **info** On Android, you can use [`presentFormAsync`](#contactspresentformasynccontactid-contact-formoptions) to make edits to contacts. 684 * @param contact A contact object including the wanted changes. 685 * @return A promise that fulfills with ID of the updated system contact if mutation was successful. 686 * @example 687 * ```js 688 * const contact = { 689 * id: '161A368D-D614-4A15-8DC6-665FDBCFAE55', 690 * [Contacts.Fields.FirstName]: 'Drake', 691 * [Contacts.Fields.Company]: 'Young Money', 692 * }; 693 * await Contacts.updateContactAsync(contact); 694 * ``` 695 * @platform ios 696 */ 697export async function updateContactAsync(contact: Contact): Promise<string> { 698 if (!ExpoContacts.updateContactAsync) { 699 throw new UnavailabilityError('Contacts', 'updateContactAsync'); 700 } 701 return await ExpoContacts.updateContactAsync(contact); 702} 703 704// @needs-audit 705/** 706 * Delete a contact from the system. 707 * @param contactId ID of the contact you want to delete. 708 * @example 709 * ```js 710 * await Contacts.removeContactAsync('161A368D-D614-4A15-8DC6-665FDBCFAE55'); 711 * ``` 712 * @platform ios 713 */ 714export async function removeContactAsync(contactId: string): Promise<any> { 715 if (!ExpoContacts.removeContactAsync) { 716 throw new UnavailabilityError('Contacts', 'removeContactAsync'); 717 } 718 return await ExpoContacts.removeContactAsync(contactId); 719} 720 721/** 722 * Query a set of contacts and write them to a local URI that can be used for sharing. 723 * @param contactQuery Used to query contact you want to write. 724 * @return A promise that fulfills with shareable local URI, or `undefined` if there was no match. 725 * @example 726 * ```js 727 * const localUri = await Contacts.writeContactToFileAsync({ 728 * id: '161A368D-D614-4A15-8DC6-665FDBCFAE55', 729 * }); 730 * Share.share({ url: localUri, message: 'Call me!' }); 731 * ``` 732 */ 733export async function writeContactToFileAsync( 734 contactQuery: ContactQuery = {} 735): Promise<string | undefined> { 736 if (!ExpoContacts.writeContactToFileAsync) { 737 throw new UnavailabilityError('Contacts', 'writeContactToFileAsync'); 738 } 739 return await ExpoContacts.writeContactToFileAsync(contactQuery); 740} 741 742// @needs-audit 743/** 744 * Present a native form for manipulating contacts. 745 * @param contactId The ID of a system contact. 746 * @param contact A contact with the changes you want to persist. 747 * @param formOptions Options for the native editor. 748 * @example 749 * ```js 750 * await Contacts.presentFormAsync('161A368D-D614-4A15-8DC6-665FDBCFAE55'); 751 * ``` 752 */ 753export async function presentFormAsync( 754 contactId?: string | null, 755 contact?: Contact | null, 756 formOptions: FormOptions = {} 757): Promise<any> { 758 if (!ExpoContacts.presentFormAsync) { 759 throw new UnavailabilityError('Contacts', 'presentFormAsync'); 760 } 761 if (Platform.OS === 'ios') { 762 const adjustedOptions = formOptions; 763 764 if (contactId) { 765 if (contact) { 766 contact = undefined; 767 console.log( 768 'Expo.Contacts.presentFormAsync: You should define either a `contact` or a `contactId` but not both.' 769 ); 770 } 771 if (adjustedOptions.isNew !== undefined) { 772 console.log( 773 'Expo.Contacts.presentFormAsync: `formOptions.isNew` is not supported with `contactId`' 774 ); 775 } 776 } 777 return await ExpoContacts.presentFormAsync(contactId, contact, adjustedOptions); 778 } else { 779 return await ExpoContacts.presentFormAsync(contactId, contact, formOptions); 780 } 781} 782 783// iOS Only 784 785/** 786 * Add a group to a container. 787 * @param groupId The group you want to target. 788 * @param containerId The container you want to add membership to. 789 * @example 790 * ```js 791 * await Contacts.addExistingGroupToContainerAsync( 792 * '161A368D-D614-4A15-8DC6-665FDBCFAE55', 793 * '665FDBCFAE55-D614-4A15-8DC6-161A368D' 794 * ); 795 * ``` 796 * @platform ios 797 */ 798export async function addExistingGroupToContainerAsync( 799 groupId: string, 800 containerId: string 801): Promise<any> { 802 if (!ExpoContacts.addExistingGroupToContainerAsync) { 803 throw new UnavailabilityError('Contacts', 'addExistingGroupToContainerAsync'); 804 } 805 806 return await ExpoContacts.addExistingGroupToContainerAsync(groupId, containerId); 807} 808 809/** 810 * Create a group with a name, and add it to a container. If the container is undefined, the default container will be targeted. 811 * @param name Name of the new group. 812 * @param containerId The container you to add membership to. 813 * @return A promise that fulfills with ID of the new group. 814 * @example 815 * ```js 816 * const groupId = await Contacts.createGroupAsync('Sailor Moon'); 817 * ``` 818 * @platform ios 819 */ 820export async function createGroupAsync(name?: string, containerId?: string): Promise<string> { 821 if (!ExpoContacts.createGroupAsync) { 822 throw new UnavailabilityError('Contacts', 'createGroupAsync'); 823 } 824 825 name = name || uuid.v4(); 826 if (!containerId) { 827 containerId = await getDefaultContainerIdAsync(); 828 } 829 830 return await ExpoContacts.createGroupAsync(name, containerId); 831} 832 833/** 834 * Change the name of an existing group. 835 * @param groupName New name for an existing group. 836 * @param groupId ID of the group you want to edit. 837 * @example 838 * ```js 839 * await Contacts.updateGroupName('Expo Friends', '161A368D-D614-4A15-8DC6-665FDBCFAE55'); 840 * ``` 841 * @platform ios 842 */ 843export async function updateGroupNameAsync(groupName: string, groupId: string): Promise<any> { 844 if (!ExpoContacts.updateGroupNameAsync) { 845 throw new UnavailabilityError('Contacts', 'updateGroupNameAsync'); 846 } 847 848 return await ExpoContacts.updateGroupNameAsync(groupName, groupId); 849} 850 851// @needs-audit 852/** 853 * Delete a group from the device. 854 * @param groupId ID of the group you want to remove. 855 * @example 856 * ```js 857 * await Contacts.removeGroupAsync('161A368D-D614-4A15-8DC6-665FDBCFAE55'); 858 * ``` 859 * @platform ios 860 */ 861export async function removeGroupAsync(groupId: string): Promise<any> { 862 if (!ExpoContacts.removeGroupAsync) { 863 throw new UnavailabilityError('Contacts', 'removeGroupAsync'); 864 } 865 866 return await ExpoContacts.removeGroupAsync(groupId); 867} 868 869// @needs-audit 870/** 871 * Add a contact as a member to a group. A contact can be a member of multiple groups. 872 * @param contactId ID of the contact you want to edit. 873 * @param groupId ID for the group you want to add membership to. 874 * @example 875 * ```js 876 * await Contacts.addExistingContactToGroupAsync( 877 * '665FDBCFAE55-D614-4A15-8DC6-161A368D', 878 * '161A368D-D614-4A15-8DC6-665FDBCFAE55' 879 * ); 880 * ``` 881 * @platform ios 882 */ 883export async function addExistingContactToGroupAsync( 884 contactId: string, 885 groupId: string 886): Promise<any> { 887 if (!ExpoContacts.addExistingContactToGroupAsync) { 888 throw new UnavailabilityError('Contacts', 'addExistingContactToGroupAsync'); 889 } 890 891 return await ExpoContacts.addExistingContactToGroupAsync(contactId, groupId); 892} 893 894// @needs-audit 895/** 896 * Remove a contact's membership from a given group. This will not delete the contact. 897 * @param contactId ID of the contact you want to remove. 898 * @param groupId ID for the group you want to remove membership of. 899 * @example 900 * ```js 901 * await Contacts.removeContactFromGroupAsync( 902 * '665FDBCFAE55-D614-4A15-8DC6-161A368D', 903 * '161A368D-D614-4A15-8DC6-665FDBCFAE55' 904 * ); 905 * ``` 906 * @platform ios 907 */ 908export async function removeContactFromGroupAsync( 909 contactId: string, 910 groupId: string 911): Promise<any> { 912 if (!ExpoContacts.removeContactFromGroupAsync) { 913 throw new UnavailabilityError('Contacts', 'removeContactFromGroupAsync'); 914 } 915 916 return await ExpoContacts.removeContactFromGroupAsync(contactId, groupId); 917} 918 919// @needs-audit 920/** 921 * Query and return a list of system groups. 922 * @param groupQuery Information regarding which groups you want to get. 923 * @example 924 * ```js 925 * const groups = await Contacts.getGroupsAsync({ groupName: 'sailor moon' }); 926 * const allGroups = await Contacts.getGroupsAsync({}); 927 * ``` 928 * @return A promise that fulfills with array of groups that fit the query. 929 * @platform ios 930 */ 931export async function getGroupsAsync(groupQuery: GroupQuery): Promise<Group[]> { 932 if (!ExpoContacts.getGroupsAsync) { 933 throw new UnavailabilityError('Contacts', 'getGroupsAsync'); 934 } 935 936 return await ExpoContacts.getGroupsAsync(groupQuery); 937} 938 939/** 940 * Get the default container's ID. 941 * @return A promise that fulfills with default container ID. 942 * @example 943 * ```js 944 * const containerId = await Contacts.getDefaultContainerIdAsync(); 945 * ``` 946 * @platform ios 947 */ 948export async function getDefaultContainerIdAsync(): Promise<string> { 949 if (!ExpoContacts.getDefaultContainerIdentifierAsync) { 950 throw new UnavailabilityError('Contacts', 'getDefaultContainerIdentifierAsync'); 951 } 952 953 return await ExpoContacts.getDefaultContainerIdentifierAsync(); 954} 955 956/** 957 * Query a list of system containers. 958 * @param containerQuery Information used to gather containers. 959 * @return A promise that fulfills with array of containers that fit the query. 960 * @example 961 * ```js 962 * const allContainers = await Contacts.getContainersAsync({ 963 * contactId: '665FDBCFAE55-D614-4A15-8DC6-161A368D', 964 * }); 965 * ``` 966 * @platform ios 967 */ 968export async function getContainersAsync(containerQuery: ContainerQuery): Promise<Container[]> { 969 if (!ExpoContacts.getContainersAsync) { 970 throw new UnavailabilityError('Contacts', 'getContainersAsync'); 971 } 972 973 return await ExpoContacts.getContainersAsync(containerQuery); 974} 975 976/** 977 * Checks user's permissions for accessing contacts data. 978 * @return A promise that resolves to a [PermissionResponse](#permissionresponse) object. 979 */ 980export async function getPermissionsAsync(): Promise<PermissionResponse> { 981 if (!ExpoContacts.getPermissionsAsync) { 982 throw new UnavailabilityError('Contacts', 'getPermissionsAsync'); 983 } 984 985 return await ExpoContacts.getPermissionsAsync(); 986} 987 988/** 989 * Asks the user to grant permissions for accessing contacts data. 990 * @return A promise that resolves to a [PermissionResponse](#permissionresponse) object. 991 */ 992export async function requestPermissionsAsync(): Promise<PermissionResponse> { 993 if (!ExpoContacts.requestPermissionsAsync) { 994 throw new UnavailabilityError('Contacts', 'requestPermissionsAsync'); 995 } 996 997 return await ExpoContacts.requestPermissionsAsync(); 998} 999 1000/** @private */ 1001function removeIds(contact: Contact): Contact { 1002 const updatedContact = { ...contact }; 1003 if (contact.id && __DEV__) { 1004 console.warn( 1005 `You have set an id = ${contact.id} for the contact. This value will be ignored, because the id will be generated by the OS` 1006 ); 1007 delete updatedContact.id; 1008 } 1009 1010 for (const key of Object.keys(contact)) { 1011 if (Array.isArray(contact[key])) { 1012 updatedContact[key] = contact[key].map((item, index) => { 1013 if (item.id) { 1014 __DEV__ && 1015 console.warn( 1016 `You have set an id "${item.id}" at index "${index}" for the key "${key}" of the contact. This value will be ignored, because the id will be generated by the OS` 1017 ); 1018 return { ...item, id: null }; 1019 } 1020 return item; 1021 }); 1022 } 1023 } 1024 return updatedContact; 1025} 1026 1027/** 1028 * Possible fields to retrieve for a contact. 1029 */ 1030export enum Fields { 1031 ID = 'id', 1032 ContactType = 'contactType', 1033 Name = 'name', 1034 FirstName = 'firstName', 1035 MiddleName = 'middleName', 1036 LastName = 'lastName', 1037 MaidenName = 'maidenName', 1038 NamePrefix = 'namePrefix', 1039 NameSuffix = 'nameSuffix', 1040 Nickname = 'nickname', 1041 PhoneticFirstName = 'phoneticFirstName', 1042 PhoneticMiddleName = 'phoneticMiddleName', 1043 PhoneticLastName = 'phoneticLastName', 1044 Birthday = 'birthday', 1045 /** 1046 * @platform ios 1047 */ 1048 NonGregorianBirthday = 'nonGregorianBirthday', 1049 Emails = 'emails', 1050 PhoneNumbers = 'phoneNumbers', 1051 Addresses = 'addresses', 1052 /** 1053 * @platform ios 1054 */ 1055 SocialProfiles = 'socialProfiles', 1056 InstantMessageAddresses = 'instantMessageAddresses', 1057 UrlAddresses = 'urlAddresses', 1058 Company = 'company', 1059 JobTitle = 'jobTitle', 1060 Department = 'department', 1061 ImageAvailable = 'imageAvailable', 1062 Image = 'image', 1063 RawImage = 'rawImage', 1064 ExtraNames = 'extraNames', 1065 Note = 'note', 1066 Dates = 'dates', 1067 Relationships = 'relationships', 1068} 1069 1070/** 1071 * This format denotes the common calendar format used to specify how a date is calculated in `nonGregorianBirthday` fields. 1072 */ 1073export enum CalendarFormats { 1074 Gregorian = 'gregorian', 1075 /** 1076 * @platform ios 1077 */ 1078 Buddhist = 'buddhist', 1079 /** 1080 * @platform ios 1081 */ 1082 Chinese = 'chinese', 1083 /** 1084 * @platform ios 1085 */ 1086 Coptic = 'coptic', 1087 /** 1088 * @platform ios 1089 */ 1090 EthiopicAmeteMihret = 'ethiopicAmeteMihret', 1091 /** 1092 * @platform ios 1093 */ 1094 EthiopicAmeteAlem = 'ethiopicAmeteAlem', 1095 /** 1096 * @platform ios 1097 */ 1098 Hebrew = 'hebrew', 1099 /** 1100 * @platform ios 1101 */ 1102 ISO8601 = 'iso8601', 1103 /** 1104 * @platform ios 1105 */ 1106 Indian = 'indian', 1107 /** 1108 * @platform ios 1109 */ 1110 Islamic = 'islamic', 1111 /** 1112 * @platform ios 1113 */ 1114 IslamicCivil = 'islamicCivil', 1115 /** 1116 * @platform ios 1117 */ 1118 Japanese = 'japanese', 1119 /** 1120 * @platform ios 1121 */ 1122 Persian = 'persian', 1123 /** 1124 * @platform ios 1125 */ 1126 RepublicOfChina = 'republicOfChina', 1127 /** 1128 * @platform ios 1129 */ 1130 IslamicTabular = 'islamicTabular', 1131 /** 1132 * @platform ios 1133 */ 1134 IslamicUmmAlQura = 'islamicUmmAlQura', 1135} 1136 1137/** 1138 * @platform ios 1139 */ 1140export enum ContainerTypes { 1141 /** 1142 * A local non-iCloud container. 1143 */ 1144 Local = 'local', 1145 /** 1146 * In association with email server. 1147 */ 1148 Exchange = 'exchange', 1149 /** 1150 * With cardDAV protocol used for sharing. 1151 */ 1152 CardDAV = 'cardDAV', 1153 /** 1154 * Unknown container. 1155 */ 1156 Unassigned = 'unassigned', 1157} 1158 1159export enum SortTypes { 1160 /** 1161 * The user default method of sorting. 1162 * @platform android 1163 */ 1164 UserDefault = 'userDefault', 1165 /** 1166 * Sort by first name in ascending order. 1167 */ 1168 FirstName = 'firstName', 1169 /** 1170 * Sort by last name in ascending order. 1171 */ 1172 LastName = 'lastName', 1173 /** 1174 * No sorting should be applied. 1175 */ 1176 None = 'none', 1177} 1178 1179export enum ContactTypes { 1180 /** 1181 * Contact is a human. 1182 */ 1183 Person = 'person', 1184 /** 1185 * Contact is group or company. 1186 */ 1187 Company = 'company', 1188} 1189