Skip to content

Commit b6bc0b6

Browse files
committed
feat: add optional keys to contacts (#6)
1 parent f5e8968 commit b6bc0b6

File tree

3 files changed

+149
-45
lines changed

3 files changed

+149
-45
lines changed

README.md

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ console.log(`Authorization access to contacts is: ${authStatus}`)
4646
*/
4747
```
4848

49-
### contacts.getAllContacts()
49+
### contacts.getAllContacts([extraProperties])
50+
51+
* `extraProperties` string[] (optional) - an array of extra contact properties to fetch that can be any of: `jobTitle`, `departmentName`, `organizationName`, `middleName`, `note`, `contactImage`, or `contactThumbnailImage`.
5052

5153
Returns `Array<Object>` - Returns an array of contact objects.
5254

@@ -59,7 +61,13 @@ The returned objects will take the following format:
5961
* `phoneNumbers` String[] - An array of phone numbers as strings in [E.164 format](https://en.wikipedia.org/wiki/E.164).
6062
* `emailAddresses` String[] - An array of email addresses as strings.
6163
* `postalAddresses` String[] - An array of postal as strings.
62-
* `contactImage` Buffer (optional) - a Buffer representation of the contact's image, if one has been set.
64+
* `jobTitle` String (optional) - The contact's job title.
65+
* `departmentName` String (optional) - The name of the department associated with the contact.
66+
* `organizationName` String (optional) - The name of the organization associated with the contact.
67+
* `middleName` String (optional) - The contact's middle name.
68+
* `note` String (optional) - The note associated with the contact.
69+
* `contactImage` Buffer (optional) - a Buffer representation of the contact's profile picture.
70+
* `contactThumbnailImage` Buffer (optional) - a Buffer representation of The thumbnail version of the contact’s profile picture.
6371

6472
This method will return an empty array (`[]`) if access to Contacts has not been granted.
6573

@@ -77,17 +85,16 @@ console.log(allContacts[0])
7785
nickname: 'Johnny',
7886
birthday: '1970-01-01',
7987
phoneNumbers: [ +11234566789' ],
80-
emailAddresses: [ 'johnny@appleseed.com' ]
81-
postalAddresses: [ '123 Pine Tree Way\nBlack Oak, Arkansas 72414\nUnited States' ],
82-
contactImage: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 48 00 48 00 00 ff e1 00 4c 45 78 69 66 00 00 4d 4d 00 2a 00 00 00 08 00 01 87 69 00 04 00 00 00 01 00 00 ... 139493 more bytes>
88+
emailAddresses: [ 'johnny@appleseed.com' ],
89+
postalAddresses: [ '123 Pine Tree Way\nBlack Oak, Arkansas 72414\nUnited States' ]
8390
}
8491
]
8592
*/
8693
```
8794

88-
### contacts.getContactsByName(name)
95+
### contacts.getContactsByName(name[, extraProperties])
8996

90-
* `name` String (required) - The first, last, or full name of a contact.
97+
* `extraProperties` string[] (optional) - an array of extra contact properties to fetch that can be any of: `jobTitle`, `departmentName`, `organizationName`, `middleName`, `note`, `contactImage`, or `contactThumbnailImage`.
9198

9299
Returns `Array<Object>` - Returns an array of contact objects where either the first or last name of the contact matches `name`.
93100

@@ -102,7 +109,13 @@ The returned object will take the following format:
102109
* `phoneNumbers` String[] - An array of phone numbers as strings in [E.164 format](https://en.wikipedia.org/wiki/E.164).
103110
* `emailAddresses` String[] - An array of email addresses as strings.
104111
* `postalAddresses` String[] - An array of postal as strings.
105-
* `contactImage` Buffer (optional) - a Buffer representation of the contact's image, if one has been set.
112+
* `jobTitle` String (optional) - The contact's job title.
113+
* `departmentName` String (optional) - The name of the department associated with the contact.
114+
* `organizationName` String (optional) - The name of the organization associated with the contact.
115+
* `middleName` String (optional) - The contact's middle name.
116+
* `note` String (optional) - The note associated with the contact.
117+
* `contactImage` Buffer (optional) - a Buffer representation of the contact's profile picture.
118+
* `contactThumbnailImage` Buffer (optional) - a Buffer representation of The thumbnail version of the contact’s profile picture.
106119

107120
This method will return an empty array (`[]`) if access to Contacts has not been granted.
108121

@@ -120,9 +133,8 @@ console.log(contacts)
120133
nickname: 'Johnny',
121134
birthday: '1970-01-01',
122135
phoneNumbers: [ +11234566789' ],
123-
emailAddresses: [ 'johnny@appleseed.com' ]
136+
emailAddresses: [ 'johnny@appleseed.com' ],
124137
postalAddresses: [ '123 Pine Tree Way\nBlack Oak, Arkansas 72414\nUnited States' ]
125-
contactImage: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 48 00 48 00 00 ff e1 00 4c 45 78 69 66 00 00 4d 4d 00 2a 00 00 00 08 00 01 87 69 00 04 00 00 00 01 00 00 ... 139493 more bytes>
126138
}
127139
]
128140
*/

contacts.mm

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,12 @@
6767
return result;
6868
}
6969

70-
Napi::Buffer<uint8_t> GetContactImage(Napi::Env env, CNContact *cncontact) {
70+
Napi::Buffer<uint8_t> GetContactImage(Napi::Env env, CNContact *cncontact,
71+
bool thumbnail) {
7172
std::vector<uint8_t> data;
7273

73-
NSData *image_data = [cncontact imageData];
74+
NSData *image_data =
75+
thumbnail ? [cncontact thumbnailImageData] : [cncontact imageData];
7476
const uint8 *bytes = (uint8 *)[image_data bytes];
7577
data.assign(bytes, bytes + [image_data length]);
7678

@@ -83,31 +85,58 @@
8385
Napi::Object CreateContact(Napi::Env env, CNContact *cncontact) {
8486
Napi::Object contact = Napi::Object::New(env);
8587

88+
// Default contact properties.
89+
8690
contact.Set("firstName", std::string([[cncontact givenName] UTF8String]));
8791
contact.Set("lastName", std::string([[cncontact familyName] UTF8String]));
8892
contact.Set("nickname", std::string([[cncontact nickname] UTF8String]));
8993

9094
std::string birthday = GetBirthday(cncontact);
9195
contact.Set("birthday", birthday.empty() ? "" : birthday);
9296

93-
// Populate phone number array
9497
Napi::Array phone_numbers = GetPhoneNumbers(env, cncontact);
9598
contact.Set("phoneNumbers", phone_numbers);
9699

97-
// Populate email address array
98100
Napi::Array email_addresses = GetEmailAddresses(env, cncontact);
99101
contact.Set("emailAddresses", email_addresses);
100102

101-
// Populate postal address array
102103
Napi::Array postal_addresses = GetPostalAddresses(env, cncontact);
103104
contact.Set("postalAddresses", postal_addresses);
104105

105-
// Populate contact image if one exists.
106-
Napi::Buffer<uint8_t> image_buffer = GetContactImage(env, cncontact);
107-
if (image_buffer.Length() > 0) {
106+
// Optional contact properties.
107+
108+
if ([cncontact isKeyAvailable:CNContactImageDataKey]) {
109+
Napi::Buffer<uint8_t> image_buffer = GetContactImage(env, cncontact, false);
108110
contact.Set("contactImage", image_buffer);
109111
}
110112

113+
if ([cncontact isKeyAvailable:CNContactThumbnailImageDataKey]) {
114+
Napi::Buffer<uint8_t> image_buffer = GetContactImage(env, cncontact, true);
115+
contact.Set("contactThumbnailImage", image_buffer);
116+
}
117+
118+
if ([cncontact isKeyAvailable:CNContactJobTitleKey]) {
119+
contact.Set("jobTitle", std::string([[cncontact jobTitle] UTF8String]));
120+
}
121+
122+
if ([cncontact isKeyAvailable:CNContactDepartmentNameKey]) {
123+
contact.Set("departmentName",
124+
std::string([[cncontact departmentName] UTF8String]));
125+
}
126+
127+
if ([cncontact isKeyAvailable:CNContactOrganizationNameKey]) {
128+
contact.Set("organizationName",
129+
std::string([[cncontact organizationName] UTF8String]));
130+
}
131+
132+
if ([cncontact isKeyAvailable:CNContactNoteKey]) {
133+
contact.Set("note", std::string([[cncontact note] UTF8String]));
134+
}
135+
136+
if ([cncontact isKeyAvailable:CNContactMiddleNameKey]) {
137+
contact.Set("middleName", std::string([[cncontact middleName] UTF8String]));
138+
}
139+
111140
return contact;
112141
}
113142

@@ -174,28 +203,54 @@ CNAuthorizationStatus AuthStatus() {
174203
return [CNContactStore authorizationStatusForEntityType:entityType];
175204
}
176205

177-
// Returns the set of Contacts properties to retrieve from the CNContactStore
178-
NSArray *GetContactKeys() {
179-
NSArray *keys = @[
206+
// Returns the set of Contacts properties to retrieve from the CNContactStore.
207+
NSArray *GetContactKeys(Napi::Array requested_keys) {
208+
NSMutableArray *keys = [NSMutableArray arrayWithArray:@[
180209
CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey,
181210
CNContactEmailAddressesKey, CNContactNicknameKey,
182-
CNContactPostalAddressesKey, CNContactBirthdayKey, CNContactImageDataKey
183-
];
211+
CNContactPostalAddressesKey, CNContactBirthdayKey
212+
]];
213+
214+
// Iterate through requested keys and add each to the default set.
215+
int num_keys = requested_keys.Length();
216+
if (num_keys > 0) {
217+
for (int i = 0; i < num_keys; i++) {
218+
Napi::Value val = requested_keys[i];
219+
std::string key = val.As<Napi::String>().Utf8Value();
220+
221+
if (key == "contactImage") {
222+
[keys addObject:CNContactImageDataKey];
223+
} else if (key == "contactThumbnailImage") {
224+
[keys addObject:CNContactThumbnailImageDataKey];
225+
} else if (key == "jobTitle") {
226+
[keys addObject:CNContactJobTitleKey];
227+
} else if (key == "departmentName") {
228+
[keys addObject:CNContactDepartmentNameKey];
229+
} else if (key == "organizationName") {
230+
[keys addObject:CNContactOrganizationNameKey];
231+
} else if (key == "note") {
232+
[keys addObject:CNContactNoteKey];
233+
} else if (key == "middleName") {
234+
[keys addObject:CNContactMiddleNameKey];
235+
}
236+
}
237+
}
184238

185239
return keys;
186240
}
187241

188242
// Returns all contacts in the CNContactStore matching a specified name string
189243
// predicate.
190-
NSArray *FindContacts(const std::string &name_string) {
244+
NSArray *FindContacts(const std::string &name_string, Napi::Array extra_keys) {
191245
CNContactStore *addressBook = [[CNContactStore alloc] init];
192246

193247
NSString *name = [NSString stringWithUTF8String:name_string.c_str()];
194248
NSPredicate *predicate = [CNContact predicateForContactsMatchingName:name];
195249

196-
return [addressBook unifiedContactsMatchingPredicate:predicate
197-
keysToFetch:GetContactKeys()
198-
error:nil];
250+
return
251+
[addressBook unifiedContactsMatchingPredicate:predicate
252+
keysToFetch:GetContactKeys(extra_keys)
253+
error:nil];
199254
}
200255

201256
// Creates a new CNContact in order to update, delete, or add it to the
@@ -267,9 +322,11 @@ CNAuthorizationStatus AuthStatus() {
267322
// Returns an array of all a user's Contacts as objects.
268323
Napi::Array GetAllContacts(const Napi::CallbackInfo &info) {
269324
Napi::Env env = info.Env();
325+
270326
Napi::Array contacts = Napi::Array::New(env);
271327
CNContactStore *addressBook = [[CNContactStore alloc] init];
272328
NSMutableArray *cncontacts = [[NSMutableArray alloc] init];
329+
Napi::Array extra_keys = info[0].As<Napi::Array>();
273330

274331
if (AuthStatus() != CNAuthorizationStatusAuthorized)
275332
return contacts;
@@ -283,7 +340,7 @@ CNAuthorizationStatus AuthStatus() {
283340
predicateForContactsInContainerWithIdentifier:[container identifier]];
284341
NSArray *container_contacts =
285342
[addressBook unifiedContactsMatchingPredicate:predicate
286-
keysToFetch:GetContactKeys()
343+
keysToFetch:GetContactKeys(extra_keys)
287344
error:nil];
288345

289346
[cncontacts addObjectsFromArray:container_contacts];
@@ -307,7 +364,8 @@ CNAuthorizationStatus AuthStatus() {
307364
return contacts;
308365

309366
const std::string name_string = info[0].As<Napi::String>().Utf8Value();
310-
NSArray *cncontacts = FindContacts(name_string);
367+
Napi::Array extra_keys = info[1].As<Napi::Array>();
368+
NSArray *cncontacts = FindContacts(name_string, extra_keys);
311369

312370
int num_contacts = [cncontacts count];
313371
for (int i = 0; i < num_contacts; i++) {
@@ -344,7 +402,7 @@ CNAuthorizationStatus AuthStatus() {
344402
return Napi::Boolean::New(env, false);
345403

346404
const std::string name_string = info[0].As<Napi::String>().Utf8Value();
347-
NSArray *cncontacts = FindContacts(name_string);
405+
NSArray *cncontacts = FindContacts(name_string, Napi::Array::New(env));
348406

349407
CNContact *contact = (CNContact *)[cncontacts objectAtIndex:0];
350408
CNSaveRequest *request = [[CNSaveRequest alloc] init];

index.js

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
const contacts = require('bindings')('contacts.node')
22

3-
function getContactsByName(name) {
4-
if (typeof name !== 'string') throw new TypeError('name must be a string')
3+
function getAllContacts(extraProperties = []) {
4+
if (Array.isArray(extraProperties)) {
5+
throw new TypeError('extraProperties must be an array')
6+
}
7+
8+
return contacts.getAllContacts.call(this, extraProperties)
9+
}
10+
11+
function getContactsByName(name, extraProperties = []) {
12+
if (typeof name !== 'string') {
13+
throw new TypeError('name must be a string')
14+
}
15+
16+
if (Array.isArray(extraProperties)) {
17+
throw new TypeError('extraProperties must be an array')
18+
}
519

6-
return contacts.getContactsByName.call(this, name)
20+
return contacts.getContactsByName.call(this, name, extraProperties)
721
}
822

923
function addNewContact(contact) {
@@ -17,16 +31,25 @@ function addNewContact(contact) {
1731
const hasPhoneNumbers = contact.hasOwnProperty('phoneNumbers')
1832
const hasEmailAddresses = contact.hasOwnProperty('emailAddresses')
1933

20-
if (hasFirstName && typeof contact.firstName !== 'string')
34+
if (hasFirstName && typeof contact.firstName !== 'string') {
2135
throw new TypeError('firstName must be a string')
22-
if (hasLastName && typeof contact.lastName !== 'string')
36+
}
37+
38+
if (hasLastName && typeof contact.lastName !== 'string') {
2339
throw new TypeError('lastName must be a string')
24-
if (hasNickname && typeof contact.nickname !== 'string')
40+
}
41+
42+
if (hasNickname && typeof contact.nickname !== 'string') {
2543
throw new TypeError('nickname must be a string')
26-
if (hasPhoneNumbers && !Array.isArray(contact.phoneNumbers))
44+
}
45+
46+
if (hasPhoneNumbers && !Array.isArray(contact.phoneNumbers)) {
2747
throw new TypeError('phoneNumbers must be an array')
28-
if (hasEmailAddresses && !Array.isArray(contact.emailAddresses))
48+
}
49+
50+
if (hasEmailAddresses && !Array.isArray(contact.emailAddresses)) {
2951
throw new TypeError('emailAddresses must be an array')
52+
}
3053

3154
if (hasBirthday) {
3255
const datePattern = /^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$/
@@ -52,16 +75,25 @@ function updateContact(contact) {
5275
const hasPhoneNumbers = contact.hasOwnProperty('phoneNumbers')
5376
const hasEmailAddresses = contact.hasOwnProperty('emailAddresses')
5477

55-
if (hasFirstName && typeof contact.firstName !== 'string')
78+
if (hasFirstName && typeof contact.firstName !== 'string') {
5679
throw new TypeError('firstName must be a string')
57-
if (hasLastName && typeof contact.lastName !== 'string')
80+
}
81+
82+
if (hasLastName && typeof contact.lastName !== 'string') {
5883
throw new TypeError('lastName must be a string')
59-
if (hasNickname && typeof contact.nickname !== 'string')
84+
}
85+
86+
if (hasNickname && typeof contact.nickname !== 'string') {
6087
throw new TypeError('nickname must be a string')
61-
if (hasPhoneNumbers && !Array.isArray(contact.phoneNumbers))
88+
}
89+
90+
if (hasPhoneNumbers && !Array.isArray(contact.phoneNumbers)) {
6291
throw new TypeError('phoneNumbers must be an array')
63-
if (hasEmailAddresses && !Array.isArray(contact.emailAddresses))
92+
}
93+
94+
if (hasEmailAddresses && !Array.isArray(contact.emailAddresses)) {
6495
throw new TypeError('emailAddresses must be an array')
96+
}
6597

6698
if (hasBirthday) {
6799
const datePattern = /^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$/
@@ -77,14 +109,16 @@ function updateContact(contact) {
77109
}
78110

79111
function deleteContact(name) {
80-
if (typeof name !== 'string') throw new TypeError('name must be a string')
112+
if (typeof name !== 'string') {
113+
throw new TypeError('name must be a string')
114+
}
81115

82116
return contacts.deleteContact.call(this, name)
83117
}
84118

85119
module.exports = {
86120
getAuthStatus: contacts.getAuthStatus,
87-
getAllContacts: contacts.getAllContacts,
121+
getAllContacts,
88122
getContactsByName,
89123
addNewContact,
90124
deleteContact,

0 commit comments

Comments
 (0)