nylas 6.5.0 → 7.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,11 @@
1
+ <a href="https://www.nylas.com/">
2
+ <img src="https://brand.nylas.com/assets/downloads/logo_horizontal_png/Nylas-Logo-Horizontal-Blue_.png" alt="Aimeos logo" title="Aimeos" align="right" height="60" />
3
+ </a>
4
+
1
5
  # Nylas Node.js SDK
2
6
 
3
7
  [![Travis build status](https://travis-ci.org/nylas/nylas-nodejs.svg?branch=master)](https://travis-ci.org/nylas/nylas-nodejs)
8
+ [![codecov](https://codecov.io/gh/nylas/nylas-nodejs/branch/main/graph/badge.svg?token=94IMGU4F09)](https://codecov.io/gh/nylas/nylas-nodejs)
4
9
 
5
10
  This is the GitHub repository for the Nylas Node SDK and this repo is primarily for anyone who wants to make contributions to the SDK or install it from source. If you are looking to use Node to access the Nylas Email, Calendar, or Contacts API you should refer to our official [Node SDK Quickstart Guide](https://developer.nylas.com/docs/developer-tools/sdk/node-sdk/).
6
11
 
@@ -13,9 +18,9 @@ Here are some resources to help you get started:
13
18
  - [Nylas API Reference](https://developer.nylas.com/docs/api/)
14
19
 
15
20
 
16
- # Install
21
+ ## ⚙️ Install
17
22
 
18
- To run the Nylas Node SDK, you will first need to have [Node](https://nodejs.org/en/download/) and [npm](https://www.npmjs.com/get-npm) installed on your machine.
23
+ To install the Nylas Node SDK, you will first need to have [Node](https://nodejs.org/en/download/) and [npm](https://www.npmjs.com/get-npm) installed on your machine.
19
24
 
20
25
  Then, head to the nearest command line and run the following:
21
26
  `npm install nylas`
@@ -30,35 +35,39 @@ cd nylas-nodejs
30
35
  npm install
31
36
  ```
32
37
 
33
- # Usage
38
+ ## ⚡️ Usage
34
39
 
35
- Every resource (i.e., messages, events, contacts) is accessed via an instance of `Nylas`. Before making any requests, be sure to call `config` and initialize the `Nylas` instance with your `clientId` and `clientSecret`. Then, call `with` and pass it your `accessToken`. The `accessToken` allows `Nylas` to make requests for a given account's resources.
40
+ The SDK entrypoint and application resources (i.e., application accounts, authentication, webhooks) is accessed via an instance of `Nylas`. You can initialize a `Nylas` instance with your `clientId` and `clientSecret`.
36
41
 
37
42
  ```javascript
38
43
  const Nylas = require('nylas');
39
44
 
40
- Nylas.config({
45
+ const nylasClient = new Nylas({
41
46
  clientId: CLIENT_ID,
42
47
  clientSecret: CLIENT_SECRET,
43
48
  });
49
+ ```
44
50
 
45
- const nylas = Nylas.with(ACCESS_TOKEN);
51
+ Every account resource (i.e., messages, events, contacts) is accessed via an instance of `NylasConnection`. To initialize an instance of `NylasConnection`, call `with` on your `Nylas` instance and pass it your `accessToken`.
52
+
53
+ ```javascript
54
+ const nylasConnection = nylasClient.with(ACCESS_TOKEN);
46
55
  ```
47
56
 
48
57
  Then, you can use Nylas to access information about a user's account:
49
58
  ```javascript
50
- nylas.threads.list({}).then(threads => {
59
+ nylasConnection.threads.list().then(threads => {
51
60
  console.log(threads.length);
52
61
  });
53
62
  ```
54
63
 
55
64
  For more information about how to use the Nylas Node SDK, [take a look at our quickstart guide](https://developer.nylas.com/docs/developer-tools/sdk/node-sdk/).
56
65
 
57
- # Contributing
66
+ ## 💙 Contributing
58
67
 
59
68
  Please refer to [Contributing](Contributing.md) for information about how to make contributions to this project. We welcome questions, bug reports, and pull requests.
60
69
 
61
- # License
70
+ ## 📝 License
62
71
 
63
72
  This project is licensed under the terms of the MIT license. Please refer to [LICENSE](LICENSE.txt) for the full terms.
64
73
 
package/lib/config.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { WebhookTriggers } from './models/webhook';
2
+ import ExpressBinding from './server-bindings/express-binding';
3
+ import AccessToken from './models/access-token';
1
4
  export declare let apiServer: string | null;
2
5
  export declare function setApiServer(newApiServer: string | null): void;
3
6
  export declare let clientSecret: string;
@@ -15,3 +18,53 @@ export declare type AuthenticateUrlConfig = {
15
18
  provider?: string;
16
19
  scopes?: string[];
17
20
  };
21
+ export declare enum Region {
22
+ Us = "us",
23
+ Canada = "canada",
24
+ Ireland = "ireland",
25
+ Australia = "australia",
26
+ Staging = "staging"
27
+ }
28
+ export declare const DEFAULT_REGION = Region.Us;
29
+ export declare const regionConfig: {
30
+ us: {
31
+ nylasAPIUrl: string;
32
+ dashboardApiUrl: string;
33
+ callbackDomain: string;
34
+ websocketDomain: string;
35
+ telemetryApiUrl: string;
36
+ };
37
+ canada: {
38
+ nylasAPIUrl: string;
39
+ dashboardApiUrl: string;
40
+ callbackDomain: string;
41
+ websocketDomain: string;
42
+ telemetryApiUrl: string;
43
+ };
44
+ ireland: {
45
+ nylasAPIUrl: string;
46
+ dashboardApiUrl: string;
47
+ callbackDomain: string;
48
+ websocketDomain: string;
49
+ telemetryApiUrl: string;
50
+ };
51
+ australia: {
52
+ nylasAPIUrl: string;
53
+ dashboardApiUrl: string;
54
+ callbackDomain: string;
55
+ websocketDomain: string;
56
+ telemetryApiUrl: string;
57
+ };
58
+ staging: {
59
+ nylasAPIUrl: string;
60
+ dashboardApiUrl: string;
61
+ callbackDomain: string;
62
+ websocketDomain: string;
63
+ telemetryApiUrl: string;
64
+ };
65
+ };
66
+ export declare const DEFAULT_WEBHOOK_TRIGGERS: WebhookTriggers[];
67
+ export declare const ServerBindings: {
68
+ express: typeof ExpressBinding;
69
+ };
70
+ export declare type ExchangeCodeForTokenCallback = (error: Error | null, accessToken?: AccessToken) => void;
package/lib/config.js CHANGED
@@ -1,5 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ var _a;
2
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ var webhook_1 = require("./models/webhook");
8
+ var express_binding_1 = __importDefault(require("./server-bindings/express-binding"));
3
9
  exports.apiServer = null;
4
10
  function setApiServer(newApiServer) {
5
11
  exports.apiServer = newApiServer;
@@ -10,3 +16,53 @@ function setClientSecret(newClientSecret) {
10
16
  exports.clientSecret = newClientSecret;
11
17
  }
12
18
  exports.setClientSecret = setClientSecret;
19
+ var Region;
20
+ (function (Region) {
21
+ Region["Us"] = "us";
22
+ Region["Canada"] = "canada";
23
+ Region["Ireland"] = "ireland";
24
+ Region["Australia"] = "australia";
25
+ Region["Staging"] = "staging";
26
+ })(Region = exports.Region || (exports.Region = {}));
27
+ exports.DEFAULT_REGION = Region.Us;
28
+ exports.regionConfig = (_a = {},
29
+ _a[Region.Us] = {
30
+ nylasAPIUrl: 'https://api.nylas.com',
31
+ dashboardApiUrl: 'https://dashboard-api.nylas.com',
32
+ callbackDomain: 'cb.nylas.com',
33
+ websocketDomain: 'tunnel.nylas.com',
34
+ telemetryApiUrl: 'https://cli.nylas.com',
35
+ },
36
+ _a[Region.Canada] = {
37
+ nylasAPIUrl: 'https://canada.api.nylas.com',
38
+ dashboardApiUrl: 'https://canada.dashboard.nylas.com',
39
+ callbackDomain: 'cb.nylas.com',
40
+ websocketDomain: 'tunnel.nylas.com',
41
+ telemetryApiUrl: 'https://cli.nylas.com',
42
+ },
43
+ _a[Region.Ireland] = {
44
+ nylasAPIUrl: 'https://ireland.api.nylas.com',
45
+ dashboardApiUrl: 'https://ireland.dashboard.nylas.com',
46
+ callbackDomain: 'cb.nylas.com',
47
+ websocketDomain: 'tunnel.nylas.com',
48
+ telemetryApiUrl: 'https://cli.nylas.com',
49
+ },
50
+ _a[Region.Australia] = {
51
+ nylasAPIUrl: 'https://australia.api.nylas.com',
52
+ dashboardApiUrl: 'https://australia.dashboard.nylas.com',
53
+ callbackDomain: 'cb.nylas.com',
54
+ websocketDomain: 'tunnel.nylas.com',
55
+ telemetryApiUrl: 'https://cli.nylas.com',
56
+ },
57
+ _a[Region.Staging] = {
58
+ nylasAPIUrl: 'https://api-staging.nylas.com',
59
+ dashboardApiUrl: 'https://staging-dashboard.nylas.com',
60
+ callbackDomain: 'cb.nylas.com',
61
+ websocketDomain: 'tunnel.nylas.com',
62
+ telemetryApiUrl: 'https://cli.nylas.com',
63
+ },
64
+ _a);
65
+ exports.DEFAULT_WEBHOOK_TRIGGERS = Object.values(webhook_1.WebhookTriggers);
66
+ exports.ServerBindings = {
67
+ express: express_binding_1.default,
68
+ };
@@ -49,7 +49,6 @@ var EventParticipant = /** @class */ (function (_super) {
49
49
  }),
50
50
  status: attributes_1.default.String({
51
51
  modelKey: 'status',
52
- readOnly: true,
53
52
  }),
54
53
  };
55
54
  return EventParticipant;
@@ -44,6 +44,10 @@ export declare type EventProperties = {
44
44
  roundRobinOrder?: string[];
45
45
  metadata?: object;
46
46
  jobStatusId?: string;
47
+ organizerEmail?: string;
48
+ organizerName?: string;
49
+ visibility?: string;
50
+ customerEventId?: string;
47
51
  };
48
52
  export default class Event extends RestfulModel {
49
53
  calendarId: string;
@@ -72,6 +76,10 @@ export default class Event extends RestfulModel {
72
76
  roundRobinOrder?: string[];
73
77
  metadata?: object;
74
78
  jobStatusId?: string;
79
+ organizerEmail?: string;
80
+ organizerName?: string;
81
+ visibility?: string;
82
+ customerEventId?: string;
75
83
  static collectionName: string;
76
84
  static attributes: Record<string, Attribute>;
77
85
  constructor(connection: NylasConnection, props?: EventProperties);
@@ -143,6 +143,10 @@ var Event = /** @class */ (function (_super) {
143
143
  if (!this.notifications) {
144
144
  delete json.notifications;
145
145
  }
146
+ // Participant status cannot be updated
147
+ if (this.id && json.participants) {
148
+ json.participants.forEach(function (participant) { return delete participant.status; });
149
+ }
146
150
  return json;
147
151
  };
148
152
  Event.prototype.rsvp = function (status, comment, callback) {
@@ -273,6 +277,20 @@ var Event = /** @class */ (function (_super) {
273
277
  modelKey: 'jobStatusId',
274
278
  jsonKey: 'job_status_id',
275
279
  readOnly: true,
280
+ }), organizerEmail: attributes_1.default.String({
281
+ modelKey: 'organizerEmail',
282
+ jsonKey: 'organizer_email',
283
+ readOnly: true,
284
+ }), organizerName: attributes_1.default.String({
285
+ modelKey: 'organizerName',
286
+ jsonKey: 'organizer_name',
287
+ readOnly: true,
288
+ }), visibility: attributes_1.default.String({
289
+ modelKey: 'visibility',
290
+ readOnly: true,
291
+ }), customerEventId: attributes_1.default.String({
292
+ modelKey: 'customerEventId',
293
+ jsonKey: 'customer_event_id',
276
294
  }) });
277
295
  return Event;
278
296
  }(restful_model_1.default));
@@ -99,7 +99,7 @@ export declare type SchedulerBookingProperties = {
99
99
  calendarInviteToGuests?: boolean;
100
100
  cancellationPolicy?: string;
101
101
  confirmationEmailsToGuests?: boolean;
102
- confirmationEmailToHost?: boolean;
102
+ confirmationEmailsToHost?: boolean;
103
103
  confirmationMethod?: string;
104
104
  minBookingNotice?: number;
105
105
  minBuffer?: number;
@@ -116,7 +116,7 @@ export declare class SchedulerBooking extends Model implements SchedulerBookingP
116
116
  calendarInviteToGuests?: boolean;
117
117
  cancellationPolicy?: string;
118
118
  confirmationEmailsToGuests?: boolean;
119
- confirmationEmailToHost?: boolean;
119
+ confirmationEmailsToHost?: boolean;
120
120
  confirmationMethod?: string;
121
121
  minBookingNotice?: number;
122
122
  minBuffer?: number;
@@ -228,8 +228,8 @@ var SchedulerBooking = /** @class */ (function (_super) {
228
228
  modelKey: 'confirmationEmailsToGuests',
229
229
  jsonKey: 'confirmation_emails_to_guests',
230
230
  }),
231
- confirmationEmailToHost: attributes_1.default.Boolean({
232
- modelKey: 'confirmationEmailToHost',
231
+ confirmationEmailsToHost: attributes_1.default.Boolean({
232
+ modelKey: 'confirmationEmailsToHost',
233
233
  jsonKey: 'confirmation_emails_to_host',
234
234
  }),
235
235
  confirmationMethod: attributes_1.default.String({
@@ -8,6 +8,7 @@ export declare enum WebhookTriggers {
8
8
  AccountStopped = "account.stopped",
9
9
  AccountInvalid = "account.invalid",
10
10
  AccountSyncError = "account.sync_error",
11
+ MessageBounced = "message.bounced",
11
12
  MessageCreated = "message.created",
12
13
  MessageOpened = "message.opened",
13
14
  MessageUpdated = "message.updated",
@@ -25,6 +25,7 @@ var WebhookTriggers;
25
25
  WebhookTriggers["AccountStopped"] = "account.stopped";
26
26
  WebhookTriggers["AccountInvalid"] = "account.invalid";
27
27
  WebhookTriggers["AccountSyncError"] = "account.sync_error";
28
+ WebhookTriggers["MessageBounced"] = "message.bounced";
28
29
  WebhookTriggers["MessageCreated"] = "message.created";
29
30
  WebhookTriggers["MessageOpened"] = "message.opened";
30
31
  WebhookTriggers["MessageUpdated"] = "message.updated";
package/lib/nylas.d.ts CHANGED
@@ -5,28 +5,53 @@ import Connect from './models/connect';
5
5
  import RestfulModelCollection from './models/restful-model-collection';
6
6
  import ManagementModelCollection from './models/management-model-collection';
7
7
  import Webhook from './models/webhook';
8
- import { AuthenticateUrlConfig, NylasConfig } from './config';
8
+ import { AuthenticateUrlConfig, ExchangeCodeForTokenCallback, NylasConfig } from './config';
9
9
  import AccessToken from './models/access-token';
10
10
  import ApplicationDetails, { ApplicationDetailsProperties } from './models/application-details';
11
11
  declare class Nylas {
12
- static clientId: string;
13
- static get clientSecret(): string;
14
- static set clientSecret(newClientSecret: string);
15
- static get apiServer(): string | null;
16
- static set apiServer(newApiServer: string | null);
17
- static accounts: ManagementModelCollection<ManagementAccount> | RestfulModelCollection<Account>;
18
- static connect: Connect;
19
- static webhooks: ManagementModelCollection<Webhook>;
20
- static config(config: NylasConfig): Nylas;
21
- static clientCredentials(): boolean;
22
- static with(accessToken: string): NylasConnection;
23
- static application(options?: ApplicationDetailsProperties): Promise<ApplicationDetails>;
24
- static exchangeCodeForToken(code: string, callback?: (error: Error | null, accessToken?: string) => void): Promise<AccessToken>;
25
- static urlForAuthentication(options: AuthenticateUrlConfig): string;
12
+ clientId: string;
13
+ get clientSecret(): string;
14
+ set clientSecret(newClientSecret: string);
15
+ get apiServer(): string | null;
16
+ set apiServer(newApiServer: string | null);
17
+ accounts: ManagementModelCollection<ManagementAccount> | RestfulModelCollection<Account>;
18
+ connect: Connect;
19
+ webhooks: ManagementModelCollection<Webhook>;
20
+ constructor(config: NylasConfig);
21
+ /**
22
+ * Checks if the Nylas instance has been configured with credentials
23
+ * @return True if the Nylas instance has been configured with credentials
24
+ */
25
+ clientCredentials(): boolean;
26
+ /**
27
+ * Configure a NylasConnection instance to access a user's resources
28
+ * @param accessToken The access token to access the user's resources
29
+ * @return The configured NylasConnection instance
30
+ */
31
+ with(accessToken: string): NylasConnection;
32
+ /**
33
+ * Return information about a Nylas application
34
+ * @param options Application details to overwrite
35
+ * @return Information about the Nylas application
36
+ */
37
+ application(options?: ApplicationDetailsProperties): Promise<ApplicationDetails>;
38
+ /**
39
+ * Exchange an authorization code for an access token
40
+ * @param code One-time authorization code from Nylas
41
+ * @param callback Callback before returning the access token
42
+ * @return The {@link AccessToken} object containing the access token and other information
43
+ */
44
+ exchangeCodeForToken(code: string, callback?: ExchangeCodeForTokenCallback): Promise<AccessToken>;
45
+ /**
46
+ * Build the URL for authenticating users to your application via Hosted Authentication
47
+ * @param options Configuration for the authentication process
48
+ * @return The URL for hosted authentication
49
+ */
50
+ urlForAuthentication(options: AuthenticateUrlConfig): string;
26
51
  /**
27
52
  * Revoke a single access token
28
53
  * @param accessToken The access token to revoke
29
54
  */
30
- static revoke(accessToken: string): Promise<void>;
55
+ revoke(accessToken: string): Promise<void>;
31
56
  }
32
57
  export = Nylas;
package/lib/nylas.js CHANGED
@@ -23,29 +23,8 @@ var webhook_1 = __importDefault(require("./models/webhook"));
23
23
  var access_token_1 = __importDefault(require("./models/access-token"));
24
24
  var application_details_1 = __importDefault(require("./models/application-details"));
25
25
  var Nylas = /** @class */ (function () {
26
- function Nylas() {
27
- }
28
- Object.defineProperty(Nylas, "clientSecret", {
29
- get: function () {
30
- return config.clientSecret;
31
- },
32
- set: function (newClientSecret) {
33
- config.setClientSecret(newClientSecret);
34
- },
35
- enumerable: true,
36
- configurable: true
37
- });
38
- Object.defineProperty(Nylas, "apiServer", {
39
- get: function () {
40
- return config.apiServer;
41
- },
42
- set: function (newApiServer) {
43
- config.setApiServer(newApiServer);
44
- },
45
- enumerable: true,
46
- configurable: true
47
- });
48
- Nylas.config = function (config) {
26
+ function Nylas(config) {
27
+ this.clientId = '';
49
28
  if (config.apiServer && config.apiServer.indexOf('://') === -1) {
50
29
  throw new Error('Please specify a fully qualified URL for the API Server.');
51
30
  }
@@ -73,17 +52,51 @@ var Nylas = /** @class */ (function () {
73
52
  this.accounts = new restful_model_collection_1.default(account_1.default, conn);
74
53
  }
75
54
  return this;
76
- };
77
- Nylas.clientCredentials = function () {
55
+ }
56
+ Object.defineProperty(Nylas.prototype, "clientSecret", {
57
+ get: function () {
58
+ return config.clientSecret;
59
+ },
60
+ set: function (newClientSecret) {
61
+ config.setClientSecret(newClientSecret);
62
+ },
63
+ enumerable: true,
64
+ configurable: true
65
+ });
66
+ Object.defineProperty(Nylas.prototype, "apiServer", {
67
+ get: function () {
68
+ return config.apiServer;
69
+ },
70
+ set: function (newApiServer) {
71
+ config.setApiServer(newApiServer);
72
+ },
73
+ enumerable: true,
74
+ configurable: true
75
+ });
76
+ /**
77
+ * Checks if the Nylas instance has been configured with credentials
78
+ * @return True if the Nylas instance has been configured with credentials
79
+ */
80
+ Nylas.prototype.clientCredentials = function () {
78
81
  return this.clientId != null && this.clientSecret != null;
79
82
  };
80
- Nylas.with = function (accessToken) {
83
+ /**
84
+ * Configure a NylasConnection instance to access a user's resources
85
+ * @param accessToken The access token to access the user's resources
86
+ * @return The configured NylasConnection instance
87
+ */
88
+ Nylas.prototype.with = function (accessToken) {
81
89
  if (!accessToken) {
82
90
  throw new Error('This function requires an access token');
83
91
  }
84
92
  return new nylas_connection_1.default(accessToken, { clientId: this.clientId });
85
93
  };
86
- Nylas.application = function (options) {
94
+ /**
95
+ * Return information about a Nylas application
96
+ * @param options Application details to overwrite
97
+ * @return Information about the Nylas application
98
+ */
99
+ Nylas.prototype.application = function (options) {
87
100
  if (!this.clientId) {
88
101
  throw new Error('This function requires a clientId');
89
102
  }
@@ -106,7 +119,13 @@ var Nylas = /** @class */ (function () {
106
119
  return new application_details_1.default().fromJSON(res);
107
120
  });
108
121
  };
109
- Nylas.exchangeCodeForToken = function (code, callback) {
122
+ /**
123
+ * Exchange an authorization code for an access token
124
+ * @param code One-time authorization code from Nylas
125
+ * @param callback Callback before returning the access token
126
+ * @return The {@link AccessToken} object containing the access token and other information
127
+ */
128
+ Nylas.prototype.exchangeCodeForToken = function (code, callback) {
110
129
  if (!this.clientId || !this.clientSecret) {
111
130
  throw new Error('exchangeCodeForToken() cannot be called until you provide a clientId and secret via config()');
112
131
  }
@@ -131,10 +150,11 @@ var Nylas = /** @class */ (function () {
131
150
  errorMessage = body.message;
132
151
  throw new Error(errorMessage);
133
152
  }
153
+ var accessToken = new access_token_1.default().fromJSON(body);
134
154
  if (callback) {
135
- callback(null, body);
155
+ callback(null, accessToken);
136
156
  }
137
- return new access_token_1.default().fromJSON(body);
157
+ return accessToken;
138
158
  }, function (error) {
139
159
  var newError = new Error(error.message);
140
160
  if (callback) {
@@ -143,7 +163,12 @@ var Nylas = /** @class */ (function () {
143
163
  throw newError;
144
164
  });
145
165
  };
146
- Nylas.urlForAuthentication = function (options) {
166
+ /**
167
+ * Build the URL for authenticating users to your application via Hosted Authentication
168
+ * @param options Configuration for the authentication process
169
+ * @return The URL for hosted authentication
170
+ */
171
+ Nylas.prototype.urlForAuthentication = function (options) {
147
172
  if (!this.clientId) {
148
173
  throw new Error('urlForAuthentication() cannot be called until you provide a clientId via config()');
149
174
  }
@@ -172,15 +197,14 @@ var Nylas = /** @class */ (function () {
172
197
  * Revoke a single access token
173
198
  * @param accessToken The access token to revoke
174
199
  */
175
- Nylas.revoke = function (accessToken) {
176
- return Nylas.with(accessToken)
200
+ Nylas.prototype.revoke = function (accessToken) {
201
+ return this.with(accessToken)
177
202
  .request({
178
203
  method: 'POST',
179
204
  path: '/oauth/revoke',
180
205
  })
181
206
  .catch(function (err) { return Promise.reject(err); });
182
207
  };
183
- Nylas.clientId = '';
184
208
  return Nylas;
185
209
  }());
186
210
  module.exports = Nylas;
@@ -0,0 +1,18 @@
1
+ import Nylas from '../nylas';
2
+ import { RequestHandler, Router } from 'express';
3
+ import { ServerBindingOptions, ServerBinding } from './server-binding';
4
+ export default class ExpressBinding extends ServerBinding {
5
+ constructor(nylasClient: Nylas, options: ServerBindingOptions);
6
+ /**
7
+ * Middleware for the webhook endpoint so we can verify that Nylas is sending the events
8
+ */
9
+ webhookVerificationMiddleware(): RequestHandler;
10
+ /**
11
+ * Build middleware for an Express app with routes for:
12
+ * 1. '/nylas/webhook': Receiving webhook events, verifying its authenticity, and emitting webhook objects
13
+ * 2. '/nylas/generate-auth-url': Building the URL for authenticating users to your application via Hosted Authentication
14
+ * 3. '/nylas/exchange-mailbox-token': Exchange an authorization code for an access token
15
+ * @return The routes packaged as Express middleware
16
+ */
17
+ buildMiddleware(): Router;
18
+ }
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ extendStatics(d, b);
11
+ function __() { this.constructor = d; }
12
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
13
+ };
14
+ })();
15
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
16
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
17
+ return new (P || (P = Promise))(function (resolve, reject) {
18
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
19
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
20
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
21
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
22
+ });
23
+ };
24
+ var __generator = (this && this.__generator) || function (thisArg, body) {
25
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
26
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
27
+ function verb(n) { return function (v) { return step([n, v]); }; }
28
+ function step(op) {
29
+ if (f) throw new TypeError("Generator is already executing.");
30
+ while (_) try {
31
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
32
+ if (y = 0, t) op = [op[0] & 2, t.value];
33
+ switch (op[0]) {
34
+ case 0: case 1: t = op; break;
35
+ case 4: _.label++; return { value: op[1], done: false };
36
+ case 5: _.label++; y = op[1]; op = [0]; continue;
37
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
38
+ default:
39
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
40
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
41
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
42
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
43
+ if (t[2]) _.ops.pop();
44
+ _.trys.pop(); continue;
45
+ }
46
+ op = body.call(thisArg, _);
47
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
48
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
49
+ }
50
+ };
51
+ var __importDefault = (this && this.__importDefault) || function (mod) {
52
+ return (mod && mod.__esModule) ? mod : { "default": mod };
53
+ };
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ var express_1 = __importDefault(require("express"));
56
+ var server_binding_1 = require("./server-binding");
57
+ var body_parser_1 = __importDefault(require("body-parser"));
58
+ var routes_1 = require("../services/routes");
59
+ var ExpressBinding = /** @class */ (function (_super) {
60
+ __extends(ExpressBinding, _super);
61
+ function ExpressBinding(nylasClient, options) {
62
+ return _super.call(this, nylasClient, options) || this;
63
+ }
64
+ /**
65
+ * Middleware for the webhook endpoint so we can verify that Nylas is sending the events
66
+ */
67
+ ExpressBinding.prototype.webhookVerificationMiddleware = function () {
68
+ var _this = this;
69
+ return function (req, res, next) {
70
+ var isVerified = _this.verifyWebhookSignature(req.header(server_binding_1.ServerBinding.NYLAS_SIGNATURE_HEADER), req.body);
71
+ if (!isVerified) {
72
+ return res
73
+ .status(401)
74
+ .send('X-Nylas-Signature failed verification 🚷 ');
75
+ }
76
+ next();
77
+ };
78
+ };
79
+ /**
80
+ * Build middleware for an Express app with routes for:
81
+ * 1. '/nylas/webhook': Receiving webhook events, verifying its authenticity, and emitting webhook objects
82
+ * 2. '/nylas/generate-auth-url': Building the URL for authenticating users to your application via Hosted Authentication
83
+ * 3. '/nylas/exchange-mailbox-token': Exchange an authorization code for an access token
84
+ * @return The routes packaged as Express middleware
85
+ */
86
+ ExpressBinding.prototype.buildMiddleware = function () {
87
+ var _this = this;
88
+ var _a, _b, _c;
89
+ var router = express_1.default.Router();
90
+ // For the Nylas webhook endpoint, we should get the raw body to use for verification
91
+ router.use(routes_1.DefaultPaths.webhooks, body_parser_1.default.raw({ inflate: true, type: 'application/json' }));
92
+ router.use(express_1.default.json(), body_parser_1.default.urlencoded({ limit: '5mb', extended: true }) // support encoded bodies
93
+ );
94
+ router.post(((_a = this.overridePaths) === null || _a === void 0 ? void 0 : _a.webhooks) || routes_1.DefaultPaths.webhooks, this.webhookVerificationMiddleware(), function (req, res) {
95
+ var deltas = req.body.deltas || [];
96
+ _this.emitDeltaEvents(deltas);
97
+ res.status(200).send('ok');
98
+ });
99
+ router.post(((_b = this.overridePaths) === null || _b === void 0 ? void 0 : _b.buildAuthUrl) || routes_1.DefaultPaths.buildAuthUrl, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
100
+ var state, authUrl;
101
+ return __generator(this, function (_a) {
102
+ switch (_a.label) {
103
+ case 0:
104
+ state = '';
105
+ if (!this.csrfTokenExchangeOpts) return [3 /*break*/, 2];
106
+ return [4 /*yield*/, this.csrfTokenExchangeOpts.generateCsrfToken(req)];
107
+ case 1:
108
+ state = _a.sent();
109
+ _a.label = 2;
110
+ case 2: return [4 /*yield*/, this.buildAuthUrl({
111
+ scopes: this.defaultScopes,
112
+ clientUri: this.clientUri,
113
+ emailAddress: req.body.email_address,
114
+ successUrl: req.body.success_url,
115
+ state: state,
116
+ })];
117
+ case 3:
118
+ authUrl = _a.sent();
119
+ res.status(200).send(authUrl);
120
+ return [2 /*return*/];
121
+ }
122
+ });
123
+ }); });
124
+ router.post(((_c = this.overridePaths) === null || _c === void 0 ? void 0 : _c.exchangeCodeForToken) ||
125
+ routes_1.DefaultPaths.exchangeCodeForToken, function (req, res) { return __awaiter(_this, void 0, void 0, function () {
126
+ var csrfToken, isValidToken, accessTokenObj, e_1;
127
+ return __generator(this, function (_a) {
128
+ switch (_a.label) {
129
+ case 0:
130
+ _a.trys.push([0, 5, , 6]);
131
+ if (!this.csrfTokenExchangeOpts) return [3 /*break*/, 2];
132
+ csrfToken = req.body.csrfToken;
133
+ return [4 /*yield*/, this.csrfTokenExchangeOpts.validateCsrfToken(csrfToken, req)];
134
+ case 1:
135
+ isValidToken = _a.sent();
136
+ if (!isValidToken) {
137
+ return [2 /*return*/, res.status(401).send('Invalid CSRF State Token')];
138
+ }
139
+ _a.label = 2;
140
+ case 2: return [4 /*yield*/, this.exchangeCodeForToken(req.body.token)];
141
+ case 3:
142
+ accessTokenObj = _a.sent();
143
+ return [4 /*yield*/, this.exchangeMailboxTokenCallback(accessTokenObj, res)];
144
+ case 4:
145
+ _a.sent();
146
+ // If the callback event already sent a response then we don't need to do anything
147
+ if (!res.writableEnded) {
148
+ res.status(200).send('success');
149
+ }
150
+ return [3 /*break*/, 6];
151
+ case 5:
152
+ e_1 = _a.sent();
153
+ res.status(500).send(e_1.message);
154
+ return [3 /*break*/, 6];
155
+ case 6: return [2 /*return*/];
156
+ }
157
+ });
158
+ }); });
159
+ return router;
160
+ };
161
+ return ExpressBinding;
162
+ }(server_binding_1.ServerBinding));
163
+ exports.default = ExpressBinding;
@@ -0,0 +1,65 @@
1
+ /// <reference types="node" />
2
+ import { Request, Response, Router } from 'express';
3
+ import { Scope } from '../models/connect';
4
+ import { EventEmitter } from 'events';
5
+ import Webhook, { WebhookTriggers } from '../models/webhook';
6
+ import { WebhookDelta } from '../models/webhook-notification';
7
+ import AccessToken from '../models/access-token';
8
+ import Nylas from '../nylas';
9
+ import { OpenWebhookTunnelOptions } from '../services/tunnel';
10
+ import { BuildAuthUrl, ExchangeCodeForToken, VerifyWebhookSignature } from '../services/routes';
11
+ declare type Middleware = Router;
12
+ declare type ServerRequest = Request;
13
+ declare type ServerResponse = Response;
14
+ declare type ExchangeMailboxTokenCallback = (accessToken: AccessToken, res: ServerResponse) => void;
15
+ declare type CsrfTokenExchangeOptions = {
16
+ generateCsrfToken: (req: ServerRequest) => Promise<string>;
17
+ validateCsrfToken: (csrfToken: string, req: ServerRequest) => Promise<boolean>;
18
+ };
19
+ declare type OverridePaths = {
20
+ buildAuthUrl?: string;
21
+ exchangeCodeForToken?: string;
22
+ webhooks?: string;
23
+ };
24
+ export declare type ServerBindingOptions = {
25
+ defaultScopes: Scope[];
26
+ exchangeMailboxTokenCallback: ExchangeMailboxTokenCallback;
27
+ csrfTokenExchangeOpts?: CsrfTokenExchangeOptions;
28
+ clientUri?: string;
29
+ overridePaths?: OverridePaths;
30
+ };
31
+ export declare abstract class ServerBinding extends EventEmitter implements ServerBindingOptions {
32
+ nylasClient: Nylas;
33
+ defaultScopes: Scope[];
34
+ exchangeMailboxTokenCallback: ExchangeMailboxTokenCallback;
35
+ csrfTokenExchangeOpts?: CsrfTokenExchangeOptions;
36
+ clientUri?: string;
37
+ overridePaths?: OverridePaths;
38
+ static NYLAS_SIGNATURE_HEADER: string;
39
+ protected buildAuthUrl: BuildAuthUrl;
40
+ protected exchangeCodeForToken: ExchangeCodeForToken;
41
+ protected verifyWebhookSignature: VerifyWebhookSignature;
42
+ private _untypedOn;
43
+ private _untypedEmit;
44
+ protected constructor(nylasClient: Nylas, options: ServerBindingOptions);
45
+ abstract buildMiddleware(): Middleware;
46
+ on: <K extends WebhookTriggers>(event: K, listener: (payload: WebhookDelta) => void) => this;
47
+ emit: <K extends WebhookTriggers>(event: K, payload: WebhookDelta) => boolean;
48
+ /**
49
+ * Emit all incoming delta events
50
+ * @param deltas The list of delta JSON objects
51
+ */
52
+ emitDeltaEvents(deltas: Record<string, unknown>[]): void;
53
+ /**
54
+ * Start a local development websocket to get webhook events
55
+ * @param webhookTunnelConfig Optional configuration for setting region, triggers, and overriding callbacks
56
+ * @return The webhook details response from the API
57
+ */
58
+ startDevelopmentWebsocket(webhookTunnelConfig?: Partial<OpenWebhookTunnelOptions>): Promise<Webhook>;
59
+ /**
60
+ * Delta event processor to be used either by websocket or webhook
61
+ * @param d The delta event
62
+ */
63
+ protected handleDeltaEvent: (d: WebhookDelta) => void;
64
+ }
65
+ export {};
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ extendStatics(d, b);
11
+ function __() { this.constructor = d; }
12
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
13
+ };
14
+ })();
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ var events_1 = require("events");
17
+ var webhook_notification_1 = require("../models/webhook-notification");
18
+ var tunnel_1 = require("../services/tunnel");
19
+ var routes_1 = require("../services/routes");
20
+ var ServerBinding = /** @class */ (function (_super) {
21
+ __extends(ServerBinding, _super);
22
+ function ServerBinding(nylasClient, options) {
23
+ var _a;
24
+ var _this = _super.call(this) || this;
25
+ _this._untypedOn = _this.on;
26
+ _this._untypedEmit = _this.emit;
27
+ // Taken from the best StackOverflow answer of all time https://stackoverflow.com/a/56228127
28
+ _this.on = function (event, listener) { return _this._untypedOn(event, listener); };
29
+ _this.emit = function (event, payload) { return _this._untypedEmit(event, payload); };
30
+ /**
31
+ * Delta event processor to be used either by websocket or webhook
32
+ * @param d The delta event
33
+ */
34
+ _this.handleDeltaEvent = function (d) {
35
+ d.type && _this.emit(d.type, d);
36
+ };
37
+ _this.nylasClient = nylasClient;
38
+ _this.defaultScopes = options.defaultScopes;
39
+ _this.exchangeMailboxTokenCallback = options.exchangeMailboxTokenCallback;
40
+ _this.csrfTokenExchangeOpts = options.csrfTokenExchangeOpts;
41
+ _this.clientUri = options.clientUri;
42
+ (_a = routes_1.Routes(nylasClient), _this.buildAuthUrl = _a.buildAuthUrl, _this.exchangeCodeForToken = _a.exchangeCodeForToken, _this.verifyWebhookSignature = _a.verifyWebhookSignature);
43
+ _this.overridePaths = options.overridePaths;
44
+ return _this;
45
+ }
46
+ /**
47
+ * Emit all incoming delta events
48
+ * @param deltas The list of delta JSON objects
49
+ */
50
+ ServerBinding.prototype.emitDeltaEvents = function (deltas) {
51
+ var _this = this;
52
+ deltas.forEach(function (d) { return _this.handleDeltaEvent(new webhook_notification_1.WebhookDelta().fromJSON(d)); });
53
+ };
54
+ /**
55
+ * Start a local development websocket to get webhook events
56
+ * @param webhookTunnelConfig Optional configuration for setting region, triggers, and overriding callbacks
57
+ * @return The webhook details response from the API
58
+ */
59
+ ServerBinding.prototype.startDevelopmentWebsocket = function (webhookTunnelConfig) {
60
+ /* eslint-disable no-console */
61
+ var defaultOnClose = function () {
62
+ return console.log('Nylas websocket client connection closed');
63
+ };
64
+ var defaultOnConnectFail = function (e) {
65
+ return console.log('Failed to connect Nylas websocket client', e.message);
66
+ };
67
+ var defaultOnError = function (e) {
68
+ return console.log('Error in Nylas websocket client', e.message);
69
+ };
70
+ var defaultOnConnect = function () {
71
+ return console.log('Nylas websocket client connected');
72
+ };
73
+ /* eslint-enable no-console */
74
+ return tunnel_1.openWebhookTunnel(this.nylasClient, {
75
+ onMessage: (webhookTunnelConfig === null || webhookTunnelConfig === void 0 ? void 0 : webhookTunnelConfig.onMessage) || this.handleDeltaEvent,
76
+ onClose: (webhookTunnelConfig === null || webhookTunnelConfig === void 0 ? void 0 : webhookTunnelConfig.onClose) || defaultOnClose,
77
+ onConnectFail: (webhookTunnelConfig === null || webhookTunnelConfig === void 0 ? void 0 : webhookTunnelConfig.onConnectFail) || defaultOnConnectFail,
78
+ onError: (webhookTunnelConfig === null || webhookTunnelConfig === void 0 ? void 0 : webhookTunnelConfig.onError) || defaultOnError,
79
+ onConnect: (webhookTunnelConfig === null || webhookTunnelConfig === void 0 ? void 0 : webhookTunnelConfig.onConnect) || defaultOnConnect,
80
+ region: webhookTunnelConfig === null || webhookTunnelConfig === void 0 ? void 0 : webhookTunnelConfig.region,
81
+ triggers: webhookTunnelConfig === null || webhookTunnelConfig === void 0 ? void 0 : webhookTunnelConfig.triggers,
82
+ });
83
+ };
84
+ ServerBinding.NYLAS_SIGNATURE_HEADER = 'x-nylas-signature';
85
+ return ServerBinding;
86
+ }(events_1.EventEmitter));
87
+ exports.ServerBinding = ServerBinding;
@@ -0,0 +1,26 @@
1
+ /// <reference types="node" />
2
+ import { Scope } from '../models/connect';
3
+ import Nylas from '../nylas';
4
+ import AccessToken from '../models/access-token';
5
+ export declare enum DefaultPaths {
6
+ buildAuthUrl = "/nylas/generate-auth-url",
7
+ exchangeCodeForToken = "/nylas/exchange-mailbox-token",
8
+ webhooks = "/nylas/webhook"
9
+ }
10
+ export declare type BuildAuthUrl = (options: BuildAuthUrlOptions) => Promise<string>;
11
+ export declare type ExchangeCodeForToken = (code: string) => Promise<AccessToken>;
12
+ export declare type VerifyWebhookSignature = (nylasSignature: string, rawBody: Buffer) => boolean;
13
+ export declare type BuildAuthUrlOptions = {
14
+ scopes: Scope[];
15
+ emailAddress: string;
16
+ successUrl: string;
17
+ clientUri?: string;
18
+ state?: string;
19
+ };
20
+ declare type Routes = {
21
+ buildAuthUrl: BuildAuthUrl;
22
+ exchangeCodeForToken: ExchangeCodeForToken;
23
+ verifyWebhookSignature: VerifyWebhookSignature;
24
+ };
25
+ export declare const Routes: (nylasClient: Nylas) => Routes;
26
+ export default Routes;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (_) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ var crypto_1 = __importDefault(require("crypto"));
43
+ var DefaultPaths;
44
+ (function (DefaultPaths) {
45
+ DefaultPaths["buildAuthUrl"] = "/nylas/generate-auth-url";
46
+ DefaultPaths["exchangeCodeForToken"] = "/nylas/exchange-mailbox-token";
47
+ DefaultPaths["webhooks"] = "/nylas/webhook";
48
+ })(DefaultPaths = exports.DefaultPaths || (exports.DefaultPaths = {}));
49
+ exports.Routes = function (nylasClient) {
50
+ /**
51
+ * Build the URL for authenticating users to your application via Hosted Authentication
52
+ * @param options Configuration for the authentication process
53
+ * @return The URL for hosted authentication
54
+ */
55
+ var buildAuthUrl = function (options) { return __awaiter(void 0, void 0, void 0, function () {
56
+ var scopes, emailAddress, successUrl, clientUri, state;
57
+ return __generator(this, function (_a) {
58
+ scopes = options.scopes, emailAddress = options.emailAddress, successUrl = options.successUrl, clientUri = options.clientUri, state = options.state;
59
+ return [2 /*return*/, nylasClient.urlForAuthentication({
60
+ loginHint: emailAddress,
61
+ redirectURI: (clientUri || '') + successUrl,
62
+ scopes: scopes,
63
+ state: state,
64
+ })];
65
+ });
66
+ }); };
67
+ /**
68
+ * Exchange an authorization code for an access token
69
+ * @param code One-time authorization code from Nylas
70
+ * @param callback Callback before returning the access token
71
+ * @return The {@link AccessToken} object containing the access token and other information
72
+ */
73
+ var exchangeCodeForToken = function (code, callback) { return __awaiter(void 0, void 0, void 0, function () {
74
+ return __generator(this, function (_a) {
75
+ switch (_a.label) {
76
+ case 0: return [4 /*yield*/, nylasClient.exchangeCodeForToken(code, callback)];
77
+ case 1: return [2 /*return*/, _a.sent()];
78
+ }
79
+ });
80
+ }); };
81
+ /**
82
+ * Verify incoming webhook signature came from Nylas
83
+ * @param nylasSignature The signature to verify
84
+ * @param rawBody The raw body from the payload
85
+ * @return true if the webhook signature was verified from Nylas
86
+ */
87
+ var verifyWebhookSignature = function (nylasSignature, rawBody) {
88
+ var digest = crypto_1.default
89
+ .createHmac('sha256', nylasClient.clientSecret)
90
+ .update(rawBody)
91
+ .digest('hex');
92
+ return digest === nylasSignature;
93
+ };
94
+ return { buildAuthUrl: buildAuthUrl, exchangeCodeForToken: exchangeCodeForToken, verifyWebhookSignature: verifyWebhookSignature };
95
+ };
@@ -0,0 +1,29 @@
1
+ import { client as WebSocketClient } from 'websocket';
2
+ import { Region } from '../config';
3
+ import Nylas from '../nylas';
4
+ import Webhook, { WebhookTriggers } from '../models/webhook';
5
+ import { WebhookDelta } from '../models/webhook-notification';
6
+ export interface OpenWebhookTunnelOptions {
7
+ onMessage: (msg: WebhookDelta) => void;
8
+ onConnectFail?: (error: Error) => void;
9
+ onError?: (error: Error) => void;
10
+ onClose?: (wsClient: WebSocketClient) => void;
11
+ onConnect?: (wsClient: WebSocketClient) => void;
12
+ region?: Region;
13
+ triggers?: WebhookTriggers[];
14
+ }
15
+ /**
16
+ * Open a webhook tunnel and register it with the Nylas API
17
+ * 1. Creates a UUID
18
+ * 2. Opens a websocket connection to Nylas' webhook forwarding service,
19
+ * with the UUID as a header
20
+ * 3. Creates a new webhook pointed at the forwarding service with the UUID as the path
21
+ *
22
+ * When an event is received by the forwarding service, it will push directly to this websocket
23
+ * connection
24
+ *
25
+ * @param nylasClient The configured Nylas application
26
+ * @param config Configuration for the webhook tunnel, including callback functions, region, and events to subscribe to
27
+ * @return The webhook details response from the API
28
+ */
29
+ export declare const openWebhookTunnel: (nylasClient: Nylas, config: OpenWebhookTunnelOptions) => Promise<Webhook>;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var websocket_1 = require("websocket");
4
+ var uuid_1 = require("uuid");
5
+ var config_1 = require("../config");
6
+ var webhook_notification_1 = require("../models/webhook-notification");
7
+ /**
8
+ * Deletes the Nylas webhook
9
+ * @param nylasClient The Nylas application configured with the webhook
10
+ * @param nylasWebhook The Nylas webhook details
11
+ */
12
+ var deleteTunnelWebhook = function (nylasClient, nylasWebhook) {
13
+ if (nylasWebhook && nylasWebhook.id) {
14
+ /* eslint-disable no-console */
15
+ console.log("Shutting down the webhook tunnel and deleting id: " + nylasWebhook.id);
16
+ /* eslint-enable no-console */
17
+ nylasClient.webhooks
18
+ .delete(nylasWebhook)
19
+ .then(function () { return process.exit(); })
20
+ .catch(function (err) {
21
+ console.warn("Error while trying to deregister the webhook " + nylasWebhook.id + ": " + err.message);
22
+ process.exit();
23
+ });
24
+ }
25
+ };
26
+ /**
27
+ * Create a webhook to the Nylas forwarding service which will pass messages to our websocket
28
+ * @param nylasClient The configured Nylas application
29
+ * @param callbackDomain The domain name of the callback
30
+ * @param tunnelPath The path to the tunnel
31
+ * @param triggers The list of triggers to subscribe to
32
+ * @return The webhook details response from the API
33
+ */
34
+ var buildTunnelWebhook = function (nylasClient, callbackDomain, tunnelPath, triggers) {
35
+ var callbackUrl = "https://" + callbackDomain + "/" + tunnelPath;
36
+ return nylasClient.webhooks
37
+ .build({
38
+ callbackUrl: callbackUrl,
39
+ state: 'active',
40
+ test: true,
41
+ triggers: triggers,
42
+ })
43
+ .save()
44
+ .then(function (webhook) {
45
+ // Ensure that, upon all exits, delete the webhook on the Nylas application
46
+ process.on('SIGINT', function () { return deleteTunnelWebhook(nylasClient, webhook); });
47
+ process.on('SIGTERM', function () { return deleteTunnelWebhook(nylasClient, webhook); });
48
+ process.on('SIGBREAK', function () { return deleteTunnelWebhook(nylasClient, webhook); });
49
+ return webhook;
50
+ });
51
+ };
52
+ /**
53
+ * Open a webhook tunnel and register it with the Nylas API
54
+ * 1. Creates a UUID
55
+ * 2. Opens a websocket connection to Nylas' webhook forwarding service,
56
+ * with the UUID as a header
57
+ * 3. Creates a new webhook pointed at the forwarding service with the UUID as the path
58
+ *
59
+ * When an event is received by the forwarding service, it will push directly to this websocket
60
+ * connection
61
+ *
62
+ * @param nylasClient The configured Nylas application
63
+ * @param config Configuration for the webhook tunnel, including callback functions, region, and events to subscribe to
64
+ * @return The webhook details response from the API
65
+ */
66
+ exports.openWebhookTunnel = function (nylasClient, config) {
67
+ var triggers = config.triggers || config_1.DEFAULT_WEBHOOK_TRIGGERS;
68
+ var region = config.region || config_1.DEFAULT_REGION;
69
+ var _a = config_1.regionConfig[region], websocketDomain = _a.websocketDomain, callbackDomain = _a.callbackDomain;
70
+ // This UUID will map our websocket to a webhook in the forwarding server
71
+ var tunnelId = uuid_1.v4();
72
+ var client = new websocket_1.client({ closeTimeout: 60000 });
73
+ client.on('connectFailed', function (error) {
74
+ config.onConnectFail && config.onConnectFail(error);
75
+ });
76
+ client.on('connect', function (connection) {
77
+ config.onConnect && config.onConnect(client);
78
+ connection.on('error', function (error) {
79
+ config.onError && config.onError(error);
80
+ });
81
+ connection.on('close', function () {
82
+ config.onClose && config.onClose(client);
83
+ });
84
+ connection.on('message', function (message) {
85
+ // This shouldn't happen. If any of these are seen, open an issue
86
+ if (message.type === 'binary') {
87
+ config.onError &&
88
+ config.onError(new Error('Unknown binary message received'));
89
+ return;
90
+ }
91
+ try {
92
+ var req = JSON.parse(message.utf8Data);
93
+ var deltas = JSON.parse(req.body).deltas;
94
+ deltas.forEach(function (delta) {
95
+ return config.onMessage(new webhook_notification_1.WebhookDelta().fromJSON(delta));
96
+ });
97
+ }
98
+ catch (e) {
99
+ config.onError &&
100
+ config.onError(new Error("Error converting Nylas websocket event to JSON: " + (e &&
101
+ e.message)));
102
+ }
103
+ });
104
+ });
105
+ client.connect("wss://" + websocketDomain, undefined, undefined, {
106
+ 'Client-Id': nylasClient.clientId,
107
+ 'Client-Secret': nylasClient.clientSecret,
108
+ 'Tunnel-Id': tunnelId,
109
+ Region: region,
110
+ });
111
+ return buildTunnelWebhook(nylasClient, callbackDomain, tunnelId, triggers);
112
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nylas",
3
- "version": "6.5.0",
3
+ "version": "7.0.0-beta.0",
4
4
  "description": "A NodeJS wrapper for the Nylas REST API for email, contacts, and calendar.",
5
5
  "main": "lib/nylas.js",
6
6
  "types": "lib/nylas.d.ts",
@@ -9,6 +9,7 @@
9
9
  ],
10
10
  "scripts": {
11
11
  "test": "jest",
12
+ "test:coverage": "npm run test -- --coverage",
12
13
  "lint": "eslint --ext .js,.ts -f visualstudio .",
13
14
  "lint:fix": "npm run lint -- --fix",
14
15
  "lint:ci": "npm run lint:fix -- --quiet",
@@ -46,7 +47,11 @@
46
47
  "backoff": "^2.5.0",
47
48
  "form-data": "^4.0.0",
48
49
  "JSONStream": "^1.3.5",
49
- "node-fetch": "^2.6.1"
50
+ "node-fetch": "^2.6.1",
51
+ "express": "^4.17.13",
52
+ "body-parser": "^1.20.0",
53
+ "uuid": "^8.3.2",
54
+ "websocket": "^1.0.34"
50
55
  },
51
56
  "devDependencies": {
52
57
  "@babel/cli": "^7.13.16",
@@ -56,6 +61,9 @@
56
61
  "@types/backoff": "^2.5.1",
57
62
  "@types/jest": "^25.1.4",
58
63
  "@types/node-fetch": "^2.5.8",
64
+ "@types/uuid": "^8.3.4",
65
+ "@types/websocket": "^1.0.5",
66
+ "@types/express": "^4.17.13",
59
67
  "@typescript-eslint/eslint-plugin": "^2.25.0",
60
68
  "@typescript-eslint/parser": "^2.25.0",
61
69
  "babel-eslint": "^10.0.1",