Skip to content

Commit e206dd8

Browse files
authored
Improved webhook notification support (#102)
# Description This PR improves the support for webhook notifications. There are now two new classes representing notification attributes for event and job status notifications. There are also new fields for event metadata visibility, and a new enum representing Job Status actions (`JobStatus.Action`). There is another improvement planned for webhook notification in the next major release: 1. `Notification.Attributes` will be turned into an interface, and classes like `EventNotificationAttributes` and `JobStatusNotificationAttributes` will implement `Attributes`. The current contents of `Attributes` will be moved into another class that will also implement `Attributes`. 2. `Notification.MessageTrackingData` will be abstracted and split into smaller classes with fields specific to corresponding webhook notifications. because these changes are breaking changes, it will not be implemented until the next major version. # License <!-- Your PR comment must contain the following line for us to merge the PR. --> I confirm that this contribution is made under the terms of the MIT license and that I have the authority necessary to make this contribution on behalf of its copyright owner.
1 parent 6caa3da commit e206dd8

4 files changed

Lines changed: 255 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This section contains changes that have been committed but not yet released.
66

77
### Added
88

9+
* Improved webhook notification support with the addition of more notification attributes and event metadata fields
10+
911
### Changed
1012

1113
* NylasAccount.revokeAccessToken() returns a boolean value now

src/main/java/com/nylas/JobStatus.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.nylas;
22

3+
import com.squareup.moshi.Json;
4+
import com.squareup.moshi.adapters.EnumJsonAdapter;
5+
36
import java.util.Map;
47

58
public class JobStatus extends AccountOwnedModel {
@@ -9,6 +12,51 @@ public class JobStatus extends AccountOwnedModel {
912
private String object;
1013
private String status;
1114
private Map<String, Object> metadata;
15+
16+
/** Known actions for job status */
17+
public enum Action {
18+
@Json(name="create_calendar")
19+
CREATE_CALENDAR,
20+
@Json(name="update_calendar")
21+
UPDATE_CALENDAR,
22+
@Json(name="delete_calendar")
23+
DELETE_CALENDAR,
24+
@Json(name="create_contact")
25+
CREATE_CONTACT,
26+
@Json(name="update_contact")
27+
UPDATE_CONTACT,
28+
@Json(name="delete_contact")
29+
DELETE_CONTACT,
30+
@Json(name="create_folder")
31+
CREATE_FOLDER,
32+
@Json(name="update_folder")
33+
UPDATE_FOLDER,
34+
@Json(name="delete_folder")
35+
DELETE_FOLDER,
36+
@Json(name="create_label")
37+
CREATE_LABEL,
38+
@Json(name="update_label")
39+
UPDATE_LABEL,
40+
@Json(name="create_event")
41+
CREATE_EVENT,
42+
@Json(name="update_event")
43+
UPDATE_EVENT,
44+
@Json(name="delete_event")
45+
DELETE_EVENT,
46+
@Json(name="update_message")
47+
UPDATE_MESSAGE,
48+
@Json(name="save_draft")
49+
SAVE_DRAFT,
50+
@Json(name="new_outbox")
51+
NEW_OUTBOX,
52+
53+
UNKNOWN;
54+
55+
@Override
56+
public String toString() {
57+
return super.toString().toLowerCase();
58+
}
59+
}
1260

1361
public String getAction() {
1462
return action;
@@ -76,5 +124,11 @@ public String toString() {
76124
static class JobStatusJson {
77125

78126
}
79-
127+
128+
/**
129+
* Adapter for deserializing job actions as enums.
130+
* Actions from the API that are not matched returns {@link Action#UNKNOWN}
131+
*/
132+
public static final EnumJsonAdapter<Action> JOB_STATUS_ACTIONS_ADAPTER =
133+
EnumJsonAdapter.create(Action.class).withUnknownFallback(Action.UNKNOWN);
80134
}

src/main/java/com/nylas/JsonHelper.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,22 @@ public class JsonHelper {
2323

2424
static {
2525
moshi = new Moshi.Builder()
26+
// Polymorphic adapters
2627
.add(Event.WHEN_JSON_FACTORY)
2728
.add(Event.EVENT_NOTIFICATION_JSON_FACTORY)
2829
.add(Delta.ACCOUNT_OWNED_MODEL_JSON_FACTORY)
30+
// Custom adapters
2931
.add(new NeuralCategorizer.CategorizeCustomAdapter())
3032
.add(new Integration.IntegrationCustomAdapter())
3133
.add(new Integration.IntegrationListCustomAdapter())
3234
.add(new Grant.GrantCustomAdapter())
3335
.add(new Grant.GrantListCustomAdapter())
3436
.add(new LoginInfo.LoginInfoCustomAdapter())
37+
.add(new Notification.WebhookDeltaAdapter())
38+
// Date adapters
3539
.add(Date.class, new Rfc3339DateJsonAdapter().nullSafe())
40+
// Enum adapters
41+
.add(JobStatus.Action.class, JobStatus.JOB_STATUS_ACTIONS_ADAPTER)
3642
.build();
3743
}
3844

src/main/java/com/nylas/Notification.java

Lines changed: 192 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package com.nylas;
22

3+
import java.io.IOException;
34
import java.nio.charset.StandardCharsets;
45
import java.security.GeneralSecurityException;
56
import java.time.Instant;
67
import java.util.Arrays;
78
import java.util.List;
9+
import java.util.Map;
810

911
import javax.crypto.Mac;
1012
import javax.crypto.spec.SecretKeySpec;
1113

14+
import com.squareup.moshi.FromJson;
15+
import com.squareup.moshi.Json;
1216
import com.squareup.moshi.JsonAdapter;
17+
import com.nylas.JobStatus.Action;
18+
import com.squareup.moshi.JsonReader;
1319

1420
public class Notification {
1521

@@ -98,8 +104,8 @@ public static class ObjectData {
98104
private String object;
99105
private String account_id;
100106
private String id;
101-
private Attributes attributes;
102107
private MessageTrackingData metadata;
108+
private transient Attributes attributes;
103109

104110
/**
105111
* The type of the object that triggered this notification
@@ -146,7 +152,14 @@ public String toString() {
146152
}
147153

148154
}
149-
155+
156+
/**
157+
* Attributes for a message notification
158+
* <br>
159+
* Note: In the next major release Attributes will become an interface,
160+
* and this class will become "MessageNotificationAttributes" extending
161+
* from Attributes
162+
*/
150163
public static class Attributes {
151164
private String received_date;
152165
private String thread_id;
@@ -170,10 +183,160 @@ public String toString() {
170183
return "Attributes [receivedDate=" + received_date + ", threadId=" + thread_id + "]";
171184
}
172185
}
186+
187+
/**
188+
* Attributes for event notifications
189+
*/
190+
public static class EventNotificationAttributes extends Attributes {
191+
private String calendar_id;
192+
private Boolean created_before_account_connection;
193+
194+
/**
195+
* Calendar ID of the event
196+
*/
197+
public String getCalendarId() {
198+
return calendar_id;
199+
}
200+
201+
/**
202+
* Indicates if the event was created before the account was connected to Nylas
203+
*/
204+
public Boolean getCreatedBeforeAccountConnection() {
205+
return created_before_account_connection;
206+
}
207+
208+
@Override
209+
public String toString() {
210+
return "EventNotificationAttributes [calendarId=" + calendar_id + ", createdBeforeAccountConnection=" + created_before_account_connection + "]";
211+
}
212+
}
213+
214+
/**
215+
* Attributes for job status notifications
216+
*/
217+
public static class JobStatusNotificationAttributes extends Attributes {
218+
private Action action;
219+
private String message_id;
220+
private String job_status_id;
221+
private Extras extras;
222+
223+
/**
224+
* Event that triggered the job status webhook
225+
*/
226+
public Action getAction() {
227+
return action;
228+
}
229+
230+
/**
231+
* ID of the message associated with the Job
232+
*/
233+
public String getMessageId() {
234+
return message_id;
235+
}
236+
237+
/**
238+
* ID of the job
239+
*/
240+
public String getJobStatusId() {
241+
return job_status_id;
242+
}
243+
244+
/**
245+
* If the job has a status of cancelled, delayed, or failed then extras will contain more information
246+
*/
247+
public Extras getExtras() {
248+
return extras;
249+
}
250+
251+
@Override
252+
public String toString() {
253+
return "JobStatusNotificationAttributes [action=" + action + ", threadId=" + getThreadId() + ", messageId="
254+
+ message_id + ", jobStatusId=" + job_status_id + ", extras=" + extras + "]";
255+
}
256+
257+
/**
258+
* Extra information in the event that a job was cancelled, delayed or failed
259+
*/
260+
public static class Extras {
261+
private String reason;
262+
private Long send_at;
263+
private Long original_send_at;
264+
265+
/**
266+
* Reason for status
267+
*/
268+
public String getReason() {
269+
return reason;
270+
}
271+
272+
/**
273+
* Unix timestamp for when the message was sent
274+
*/
275+
public Instant getSendAt() {
276+
return Instants.toNullableInstant(send_at);
277+
}
278+
279+
/**
280+
* Unix timestamp assigned from sending a message
281+
*/
282+
public Instant getOriginalSendAt() {
283+
return Instants.toNullableInstant(original_send_at);
284+
}
285+
286+
@Override
287+
public String toString() {
288+
return "Extras [reason=" + reason + ", sendAt=" + getSendAt() + ", originalSendAt=" + getOriginalSendAt() + "]";
289+
}
290+
}
291+
}
292+
293+
/**
294+
* This adapter identifies the type of object this notification is for,
295+
* and deserializes it to the appropriate Attributes class.
296+
*/
297+
@SuppressWarnings("unchecked")
298+
static class WebhookDeltaAdapter {
299+
@FromJson
300+
Delta fromJson(JsonReader reader, JsonAdapter<Delta> delegate) throws IOException {
301+
Map<String, Object> json = JsonHelper.jsonToMap(reader);
302+
Delta delta = delegate.fromJson(JsonHelper.mapToJson(json));
303+
if(delta != null && delta.object_data != null) {
304+
Map<String, Object> objectDataJson = (Map<String, Object>) json.get("object_data");
305+
if(objectDataJson.get("attributes") != null) {
306+
Map<String, Object> attributesJson = (Map<String, Object>) objectDataJson.get("attributes");
307+
if(attributesJson != null) {
308+
if(delta.object != null) {
309+
Class<? extends Attributes> attributeClass;
310+
switch(delta.object) {
311+
case "event":
312+
attributeClass = EventNotificationAttributes.class;
313+
break;
314+
case "job_status":
315+
attributeClass = JobStatusNotificationAttributes.class;
316+
break;
317+
case "message":
318+
default:
319+
attributeClass = Attributes.class;
320+
}
321+
delta.object_data.attributes = JsonHelper
322+
.moshi()
323+
.adapter(attributeClass)
324+
.fromJson(JsonHelper.mapToJson(attributesJson));
325+
}
326+
}
327+
}
328+
}
329+
330+
return delta;
331+
}
332+
}
173333

174-
/*
175-
* Perhaps this class could be better served by splitting into subclasses for specific
176-
* notification types. Not sure it's worth the time investment yet to get the Moshi JSON parsing done.
334+
/**
335+
* Metadata for webhook notifications
336+
* <br>
337+
* Note: In the next major release this class will be split into
338+
* smaller classes more specific to the different types of metadata
339+
* depending on the type of webhook notification
177340
*/
178341
public static class MessageTrackingData {
179342
private String message_id;
@@ -192,6 +355,11 @@ public static class MessageTrackingData {
192355
// message.link_clicked specific fields
193356
private List<LinkClickCount> link_data;
194357
private List<LinkClick> recents; // also in message.opened
358+
359+
// event specific fields
360+
@Json(name = "event-type")
361+
private String eventType;
362+
private String message;
195363

196364
/**
197365
* Nylas message ID for the tracked message
@@ -283,12 +451,30 @@ public List<LinkClick> getRecentClicks() {
283451
return recents;
284452
}
285453

454+
/**
455+
* The custom event type set for the Event
456+
*
457+
* Available for event notifications only
458+
*/
459+
public String getEventType() {
460+
return eventType;
461+
}
462+
463+
/**
464+
* The custom message set for the Event
465+
*
466+
* Available for event notifications only
467+
*/
468+
public String getMessage() {
469+
return message;
470+
}
471+
286472
@Override
287473
public String toString() {
288474
return "MessageTrackingData [messageId=" + message_id + ", senderAppId=" + sender_app_id + ", payload="
289475
+ payload + ", timestamp=" + getTimestamp() + ", threadId=" + thread_id + ", fromSelf=" + from_self
290476
+ ", replyToMessageId=" + reply_to_message_id + ", openedCount=" + count + ", linkClickCounts="
291-
+ link_data + ", recentClicks=" + recents + "]";
477+
+ link_data + ", recentClicks=" + recents + ", eventType=" + eventType + ", message=" + message + "]";
292478
}
293479
}
294480

0 commit comments

Comments
 (0)