Skip to content

Commit 808c547

Browse files
committed
feat: implement deleteContactByName(name)
1 parent 9526dc9 commit 808c547

File tree

5 files changed

+88
-26
lines changed

5 files changed

+88
-26
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,23 @@ console.log(`New contact was ${success ? 'saved' : 'not saved'}.`)
125125
```
126126

127127
This method will return `false` if access to Contacts has not been granted.
128+
129+
### contacts.deleteContactByName(name)
130+
131+
* `name` String (required) - The first, last, or full name of a contact.
132+
133+
Returns `Boolean` - whether the contact was deleted successfully.
134+
135+
Deletes a contact to the user's contacts database.
136+
137+
If a contact's full name is 'Shelley Vohr', I could pass 'Shelley', 'Vohr', or 'Shelley Vohr' as `name`.
138+
However, you should take care to specify `name` to such a degree that you can be confident the first contact to be returned from a predicate search is the contact you intend to delete.
139+
140+
```js
141+
const name = 'Jonathan Appleseed'
142+
const deleted = contacts.deleteContactByName(name)
143+
144+
console.log(`Contact ${name} was ${deleted ? 'deleted' : 'not deleted'}.`)
145+
```
146+
147+
This method will return `false` if access to Contacts has not been granted.

contacts.mm

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,20 @@ CNAuthorizationStatus AuthStatus() {
132132
return [CNContactStore authorizationStatusForEntityType:entityType];
133133
}
134134

135+
NSArray* GetContactKeys() {
136+
NSArray *keys = @[
137+
CNContactGivenNameKey,
138+
CNContactFamilyNameKey,
139+
CNContactPhoneNumbersKey,
140+
CNContactEmailAddressesKey,
141+
CNContactNicknameKey,
142+
CNContactPostalAddressesKey,
143+
CNContactBirthdayKey
144+
];
145+
146+
return keys;
147+
}
148+
135149
/***** EXPORTED FUNCTIONS *****/
136150

137151
Napi::Value GetAuthStatus(const Napi::CallbackInfo &info) {
@@ -158,18 +172,10 @@ CNAuthorizationStatus AuthStatus() {
158172
if (AuthStatus() != CNAuthorizationStatusAuthorized)
159173
return contacts;
160174

161-
NSArray *keys = @[
162-
CNContactGivenNameKey,
163-
CNContactFamilyNameKey,
164-
CNContactPhoneNumbersKey,
165-
CNContactEmailAddressesKey,
166-
CNContactNicknameKey,
167-
CNContactPostalAddressesKey,
168-
CNContactBirthdayKey
169-
];
170-
171175
NSPredicate *predicate = [CNContact predicateForContactsInContainerWithIdentifier:addressBook.defaultContainerIdentifier];
172-
NSArray *cncontacts = [addressBook unifiedContactsMatchingPredicate:predicate keysToFetch:keys error:nil];
176+
NSArray *cncontacts = [addressBook unifiedContactsMatchingPredicate:predicate
177+
keysToFetch:GetContactKeys()
178+
error:nil];
173179

174180
int num_contacts = [cncontacts count];
175181
for (int i = 0; i < num_contacts; i++) {
@@ -188,22 +194,12 @@ CNAuthorizationStatus AuthStatus() {
188194
if (AuthStatus() != CNAuthorizationStatusAuthorized)
189195
return contacts;
190196

191-
NSArray *keys = @[
192-
CNContactGivenNameKey,
193-
CNContactFamilyNameKey,
194-
CNContactPhoneNumbersKey,
195-
CNContactEmailAddressesKey,
196-
CNContactNicknameKey,
197-
CNContactPostalAddressesKey,
198-
CNContactBirthdayKey
199-
];
200-
201197
std::string name_string = info[0].As<Napi::String>().Utf8Value();
202198
NSString *name = [NSString stringWithUTF8String:name_string.c_str()];
203199
NSPredicate *predicate = [CNContact predicateForContactsMatchingName:name];
204200

205201
NSArray *cncontacts = [addressBook unifiedContactsMatchingPredicate:predicate
206-
keysToFetch:keys
202+
keysToFetch:GetContactKeys()
207203
error:nil];
208204

209205
int num_contacts = [cncontacts count];
@@ -237,7 +233,7 @@ CNAuthorizationStatus AuthStatus() {
237233

238234
if (contact_data.Has("nickname")) {
239235
std::string nick_name = contact_data.Get("nickname").As<Napi::String>().Utf8Value();
240-
[contact setFamilyName:[NSString stringWithUTF8String:nick_name.c_str()]];
236+
[contact setNickname:[NSString stringWithUTF8String:nick_name.c_str()]];
241237
}
242238

243239
if (contact_data.Has("birthday")) {
@@ -265,6 +261,33 @@ CNAuthorizationStatus AuthStatus() {
265261
return Napi::Boolean::New(env, success);
266262
}
267263

264+
Napi::Value DeleteContactByName(const Napi::CallbackInfo &info) {
265+
Napi::Env env = info.Env();
266+
CNContactStore *addressBook = [[CNContactStore alloc] init];
267+
268+
if (AuthStatus() != CNAuthorizationStatusAuthorized)
269+
return Napi::Boolean::New(env, false);
270+
271+
std::string name_string = info[0].As<Napi::String>().Utf8Value();
272+
NSString *name = [NSString stringWithUTF8String:name_string.c_str()];
273+
NSPredicate *predicate = [CNContact predicateForContactsMatchingName:name];
274+
275+
NSArray *cncontacts = [addressBook unifiedContactsMatchingPredicate:predicate
276+
keysToFetch:GetContactKeys()
277+
error:nil];
278+
279+
// Place the burden on end-users to specify name enough that
280+
// the first contact to be returned from a predicate search would
281+
// be the person they want to delete
282+
CNContact *contact = (CNContact*)[cncontacts objectAtIndex:0];
283+
CNSaveRequest *request = [[CNSaveRequest alloc] init];
284+
[request deleteContact:[contact mutableCopy]];
285+
286+
bool success = [addressBook executeSaveRequest:request error:nil];
287+
288+
return Napi::Boolean::New(env, success);
289+
}
290+
268291
Napi::Object Init(Napi::Env env, Napi::Object exports) {
269292
exports.Set(
270293
Napi::String::New(env, "getAuthStatus"), Napi::Function::New(env, GetAuthStatus)
@@ -278,6 +301,9 @@ CNAuthorizationStatus AuthStatus() {
278301
exports.Set(
279302
Napi::String::New(env, "addNewContact"), Napi::Function::New(env, AddNewContact)
280303
);
304+
exports.Set(
305+
Napi::String::New(env, "deleteContactByName"), Napi::Function::New(env, DeleteContactByName)
306+
);
281307

282308
return exports;
283309
}

index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,16 @@ function addNewContact(contact) {
3636
return contacts.addNewContact.call(this, contact)
3737
}
3838

39+
function deleteContactByName(name) {
40+
if (typeof name !== 'string') throw new TypeError('name must be a string')
41+
42+
return contacts.deleteContactByName.call(this, name)
43+
}
44+
3945
module.exports = {
4046
getAuthStatus: contacts.getAuthStatus,
4147
getAllContacts: contacts.getAllContacts,
4248
getContactsByName,
43-
addNewContact
49+
addNewContact,
50+
deleteContactByName
4451
}

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/module.spec.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ const {
33
getAuthStatus,
44
getContactsByName,
55
getAllContacts,
6-
addNewContact
6+
addNewContact,
7+
deleteContactByName
78
} = require('../index')
89

910
describe('node-mac-contacts', () => {
@@ -80,4 +81,12 @@ describe('node-mac-contacts', () => {
8081
}).to.throw(/emailAddresses must be an array/)
8182
})
8283
})
84+
85+
describe('deleteContactByName(name)', () => {
86+
it('should throw if name is not a string', () => {
87+
expect(() => {
88+
deleteContactByName(12345)
89+
}).to.throw(/name must be a string/)
90+
})
91+
})
8392
})

0 commit comments

Comments
 (0)