Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Add optional `trackingOptions.domainName` support for custom link and open tracking hostnames
- Add Contact metadata request/response types and `metadataPair` filtering, with contact webhook compatibility documentation

### Changed
- Clarify that event `default` visibility is Google-only
Expand Down
16 changes: 16 additions & 0 deletions src/models/contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export interface Contact {
givenName?: string;
jobTitle?: string;
managerName?: string;
/**
* Nylas-owned metadata associated with this contact. Metadata is not written
* to the provider and does not follow a contact if its public ID changes.
*/
metadata?: Record<string, string>;
middleName?: string;
nickname?: string;
notes?: string;
Expand Down Expand Up @@ -118,6 +123,12 @@ export interface ListContactQueryParams extends ListQueryParams {
* When set to true, returns the contacts also within the specified Contact Group subgroups, if the group parameter is set.
*/
recurse?: boolean;
/**
* Filters contacts by one indexed metadata entry in `key:value` form.
* Use one of `key1` through `key5`. This filter cannot be combined with
* provider-side contact filters; pagination parameters are supported.
*/
metadataPair?: Record<string, string>;
Comment thread
quzhi1 marked this conversation as resolved.
}

/**
Expand All @@ -139,6 +150,11 @@ export type CreateContactRequest = {
imAddresses?: InstantMessagingAddress[];
jobTitle?: string;
managerName?: string;
/**
* Nylas-owned metadata for the contact. On update, omission or `null`
* preserves existing metadata, an object replaces it, and `{}` clears it.
*/
metadata?: Record<string, string> | null;
middleName?: string;
nickname?: string;
notes?: string;
Expand Down
3 changes: 2 additions & 1 deletion src/models/webhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ export enum WebhookTriggers {
FolderUpdated = 'folder.updated',
FolderDeleted = 'folder.deleted',

// Contact triggers
// Contact triggers. Native iCloud supports both triggers. Yahoo supports
// neither trigger, including for contact changes made through the Nylas API.
ContactUpdated = 'contact.updated',
ContactDeleted = 'contact.deleted',

Expand Down
16 changes: 16 additions & 0 deletions tests/apiClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@ describe('APIClient', () => {
);
});

it('should serialize a contact metadata filter to the wire format', () => {
const options = client.requestOptions({
path: '/v3/grants/id123/contacts',
method: 'GET',
queryParams: {
metadataPair: { key1: 'sync_eligible' },
},
});

expect(options.url).toEqual(
new URL(
'https://api.us.nylas.com/v3/grants/id123/contacts?metadata_pair=key1%3Async_eligible'
)
);
});

it('should handle all the different types of query params', () => {
const options = client.requestOptions({
path: '/test',
Expand Down
21 changes: 21 additions & 0 deletions tests/resources/contacts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ describe('Contacts', () => {
});
});

it('should pass an indexed metadata filter to the API', async () => {
Comment thread
quzhi1 marked this conversation as resolved.
await contacts.list({
identifier: 'id123',
queryParams: {
metadataPair: { key1: 'sync_eligible' },
},
});

expect(apiClient.request).toHaveBeenCalledWith({
method: 'GET',
path: '/v3/grants/id123/contacts',
queryParams: {
metadataPair: { key1: 'sync_eligible' },
},
});
});

it('should paginate correctly if a nextCursor is present', async () => {
apiClient.request.mockResolvedValueOnce({
requestId: 'request123',
Expand Down Expand Up @@ -199,6 +216,7 @@ describe('Contacts', () => {
},
],
givenName: 'Test',
metadata: { key1: 'sync_eligible' },
},
overrides: {
apiUri: 'https://test.api.nylas.com',
Expand All @@ -220,6 +238,7 @@ describe('Contacts', () => {
},
],
givenName: 'Test',
metadata: { key1: 'sync_eligible' },
},
overrides: {
apiUri: 'https://test.api.nylas.com',
Expand All @@ -236,6 +255,7 @@ describe('Contacts', () => {
contactId: 'contact123',
requestBody: {
birthday: '1960-12-31',
metadata: {},
},
overrides: {
apiUri: 'https://test.api.nylas.com',
Expand All @@ -248,6 +268,7 @@ describe('Contacts', () => {
path: '/v3/grants/id123/contacts/contact123',
body: {
birthday: '1960-12-31',
metadata: {},
},
overrides: {
apiUri: 'https://test.api.nylas.com',
Expand Down
6 changes: 6 additions & 0 deletions tests/resources/webhooks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ describe('Webhooks', () => {
['MessageTransactionalBounced', 'message.transactional.bounced'],
['MessageTransactionalComplaint', 'message.transactional.complaint'],
['MessageTransactionalRejected', 'message.transactional.rejected'],
['ContactUpdated', 'contact.updated'],
['ContactDeleted', 'contact.deleted'],
['NotetakerCreated', 'notetaker.created'],
['NotetakerUpdated', 'notetaker.updated'],
['NotetakerDeleted', 'notetaker.deleted'],
Expand All @@ -334,6 +336,10 @@ describe('Webhooks', () => {
'message.updated.truncated'
);
});

it('should not expose an unsupported contact.created trigger', () => {
expect(Object.values(WebhookTriggers)).not.toContain('contact.created');
});
});

describe('extractChallengeParameter', () => {
Expand Down
Loading