Skip to content

Commit 7b57e0f

Browse files
committed
feat: add contact-changed event listener
1 parent 16788bc commit 7b57e0f

File tree

5 files changed

+86
-11
lines changed

5 files changed

+86
-11
lines changed

.prettierrc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@
33
"tabWidth": 2,
44
"semi": false,
55
"singleQuote": true,
6-
"printWidth": 100,
76
"endOfLine": "lf"
87
}

README.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ In your app, you should put the reason you're requesting to manipulate user's co
2222

2323
## API
2424

25-
### contacts.getAuthStatus()
25+
### `contacts.getAuthStatus()`
2626

2727
Returns `String` - Can be one of 'Not Determined', 'Denied', 'Authorized', or 'Restricted'.
2828

@@ -46,7 +46,7 @@ console.log(`Authorization access to contacts is: ${authStatus}`)
4646
*/
4747
```
4848

49-
### contacts.getAllContacts([extraProperties])
49+
### `contacts.getAllContacts([extraProperties])`
5050

5151
* `extraProperties` string[] (optional) - an array of extra contact properties to fetch that can be any of: `jobTitle`, `departmentName`, `organizationName`, `middleName`, `note`, `contactImage`, `contactThumbnailImage`, `instantMessageAddresses`, or `socialProfiles`.
5252

@@ -94,7 +94,7 @@ console.log(allContacts[0])
9494
*/
9595
```
9696

97-
### contacts.getContactsByName(name[, extraProperties])
97+
### `contacts.getContactsByName(name[, extraProperties])`
9898

9999
* `extraProperties` string[] (optional) - an array of extra contact properties to fetch that can be any of: `jobTitle`, `departmentName`, `organizationName`, `middleName`, `note`, `contactImage`, `contactThumbnailImage`, `instantMessageAddresses`, or `socialProfiles`.
100100

@@ -144,7 +144,7 @@ console.log(contacts)
144144
*/
145145
```
146146

147-
### contacts.addNewContact(contact)
147+
### `contacts.addNewContact(contact)`
148148

149149
* `contact` Object
150150
* `firstName` String (required) - The first name of the contact.
@@ -175,7 +175,7 @@ const success = contacts.addNewContact({
175175
console.log(`New contact was ${success ? 'saved' : 'not saved'}.`)
176176
```
177177

178-
### contacts.deleteContact(name)
178+
### `contacts.deleteContact(name)`
179179

180180
* `name` String (required) - The first, last, or full name of a contact.
181181

@@ -197,7 +197,7 @@ const deleted = contacts.deleteContact(name)
197197
console.log(`Contact ${name} was ${deleted ? 'deleted' : 'not deleted'}.`)
198198
```
199199

200-
### contacts.updateContact(contact)
200+
### `contacts.updateContact(contact)`
201201

202202
* `contact` Object
203203
* `firstName` String (required) - The first name of the contact.
@@ -227,3 +227,26 @@ const updated = contacts.updateContact({
227227

228228
console.log(`Contact was ${updated ? 'updated' : 'not updated'}.`)
229229
```
230+
231+
### `contacts.listener`
232+
233+
This module exposes an `EventEmitter`, which can be used to listen to potential changes to the `CNContactStore`. When a contact is changed either with methods contained in this module, or manually by a user, the `contact-changed` event will be emitted.
234+
235+
Example:
236+
237+
```js
238+
const { listener, addNewContact } = require('node-mac-contacts')
239+
240+
addNewContact({
241+
firstName: 'William',
242+
lastName: 'Grapeseed',
243+
nickname: 'Billy',
244+
birthday: '1990-09-09',
245+
phoneNumbers: ['+1234567890'],
246+
emailAddresses: ['billy@grapeseed.com'],
247+
})
248+
249+
listener.on('contact-changed', () => {
250+
console.log('A contact was changed!')
251+
})
252+
```

contacts.mm

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#import <Contacts/Contacts.h>
22
#include <napi.h>
33

4+
Napi::ThreadSafeFunction ts_fn;
5+
46
/***** HELPERS *****/
57

68
// Parses and returns an array of email addresses as strings.
@@ -468,7 +470,7 @@ CNAuthorizationStatus AuthStatus() {
468470
}
469471

470472
// Removes a CNContact from the CNContactStore.
471-
Napi::Value DeleteContact(const Napi::CallbackInfo &info) {
473+
Napi::Boolean DeleteContact(const Napi::CallbackInfo &info) {
472474
Napi::Env env = info.Env();
473475

474476
if (AuthStatus() != CNAuthorizationStatusAuthorized)
@@ -488,7 +490,7 @@ CNAuthorizationStatus AuthStatus() {
488490
}
489491

490492
// Updates an existing CNContact in the CNContactStore.
491-
Napi::Value UpdateContact(const Napi::CallbackInfo &info) {
493+
Napi::Boolean UpdateContact(const Napi::CallbackInfo &info) {
492494
Napi::Env env = info.Env();
493495

494496
if (AuthStatus() != CNAuthorizationStatusAuthorized)
@@ -506,8 +508,31 @@ CNAuthorizationStatus AuthStatus() {
506508
return Napi::Boolean::New(env, success);
507509
}
508510

511+
Napi::Boolean HandleEmit(const Napi::CallbackInfo &info) {
512+
Napi::Env env = info.Env();
513+
ts_fn = Napi::ThreadSafeFunction::New(env, info[0].As<Napi::Function>(),
514+
"emitCallback", 0, 1);
515+
516+
[[NSNotificationCenter defaultCenter]
517+
addObserverForName:CNContactStoreDidChangeNotification
518+
object:nil
519+
queue:[NSOperationQueue mainQueue]
520+
usingBlock:^(NSNotification *note) {
521+
auto callback = [](Napi::Env env, Napi::Function js_cb,
522+
const char *value) {
523+
js_cb.Call({Napi::String::New(env, value)});
524+
};
525+
ts_fn.BlockingCall("contact-changed", callback);
526+
ts_fn.Release();
527+
}];
528+
529+
return Napi::Boolean::New(env, true);
530+
}
531+
509532
// Initializes all functions exposed to JS.
510533
Napi::Object Init(Napi::Env env, Napi::Object exports) {
534+
exports.Set(Napi::String::New(env, "handleEmit"),
535+
Napi::Function::New(env, HandleEmit));
511536
exports.Set(Napi::String::New(env, "getAuthStatus"),
512537
Napi::Function::New(env, GetAuthStatus));
513538
exports.Set(Napi::String::New(env, "getAllContacts"),

index.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
const contacts = require('bindings')('contacts.node')
22

3+
const { EventEmitter } = require('events')
4+
5+
const listener = new EventEmitter()
6+
contacts.handleEmit(listener.emit.bind(listener))
7+
38
const validProperties = [
49
'jobTitle',
510
'departmentName',
@@ -19,7 +24,9 @@ function getAllContacts(extraProperties = []) {
1924

2025
if (!extraProperties.every((p) => validProperties.includes(p))) {
2126
throw new TypeError(
22-
`properties in extraProperties must be one of ${validProperties.join(', ')}`,
27+
`properties in extraProperties must be one of ${validProperties.join(
28+
', ',
29+
)}`,
2330
)
2431
}
2532

@@ -37,7 +44,9 @@ function getContactsByName(name, extraProperties = []) {
3744

3845
if (!extraProperties.every((p) => validProperties.includes(p))) {
3946
throw new TypeError(
40-
`properties in extraProperties must be one of ${validProperties.join(', ')}`,
47+
`properties in extraProperties must be one of ${validProperties.join(
48+
', ',
49+
)}`,
4150
)
4251
}
4352

@@ -141,6 +150,7 @@ function deleteContact(name) {
141150
}
142151

143152
module.exports = {
153+
listener,
144154
getAuthStatus: contacts.getAuthStatus,
145155
getAllContacts,
146156
getContactsByName,

test/module.spec.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
addNewContact,
77
deleteContact,
88
updateContact,
9+
listener,
910
} = require('../index')
1011

1112
describe('node-mac-contacts', () => {
@@ -170,4 +171,21 @@ describe('node-mac-contacts', () => {
170171
}).to.throw(/emailAddresses must be an array/)
171172
})
172173
})
174+
175+
describe('listener', () => {
176+
it('emits an event when the contact is changed', (done) => {
177+
addNewContact({
178+
firstName: 'William',
179+
lastName: 'Grapeseed',
180+
nickname: 'Billy',
181+
birthday: '1990-09-09',
182+
phoneNumbers: ['+1234567890'],
183+
emailAddresses: ['billy@grapeseed.com'],
184+
})
185+
186+
listener.once('contact-changed', () => {
187+
done()
188+
})
189+
})
190+
})
173191
})

0 commit comments

Comments
 (0)