crisp-api 6.4.0 → 7.0.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/CHANGELOG.md CHANGED
@@ -1,6 +1,22 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ ## v7.0.0
5
+
6
+ ### Breaking Changes
7
+
8
+ * ⚠️ Renamed the `CrispClient.rebind` method into `CrispClient.rebindSocket` (update your code if you use it).
9
+
10
+ ### New Features
11
+
12
+ * Added support for receiving Web Hooks through the RTM events pipeline (via `CrispClient.setRtmMode(Crisp.RTM_MODES.WebHooks)`).
13
+
14
+ ## v6.4.1
15
+
16
+ ### Bug Fixes
17
+
18
+ * Return more informative error reasons for non `2xx / 3xx` response codes on `HEAD` requests.
19
+
4
20
  ## v6.4.0
5
21
 
6
22
  ### New Features
@@ -18,7 +34,7 @@ Changelog
18
34
 
19
35
  ### Breaking Changes
20
36
 
21
- * Support for NodeJS 8 has been removed. The minimum version is now NodeJS 10.
37
+ * ⚠️ Support for NodeJS 8 has been removed. The minimum version is now NodeJS 10.
22
38
 
23
39
  ### Changes
24
40
 
@@ -48,8 +64,8 @@ Changelog
48
64
 
49
65
  ### Breaking Changes
50
66
 
51
- * Support for NodeJS 6 has been removed. The minimum version is now NodeJS 8.
52
- * The `CrispClient.on` method now returns a `Promise`. Please update your code accordingly. We do recommend that you add error catchers.
67
+ * ⚠️ Support for NodeJS 6 has been removed. The minimum version is now NodeJS 8.
68
+ * ⚠️ The `CrispClient.on` method now returns a `Promise`. Please update your code accordingly. We do recommend that you add error catchers.
53
69
 
54
70
  ### New Features
55
71
 
@@ -105,7 +121,7 @@ Changelog
105
121
 
106
122
  ### Breaking Changes
107
123
 
108
- * The package has been renamed from `node-crisp-api` to `crisp-api`. Since it is typical of NPM packages to skip the `node-` prefix in their name, we chose to normalize the package name to this community standard. The programmatic API did not change, so you can simply update the package name to the new name in your `package.json` and all imports.
124
+ * ⚠️ The package has been renamed from `node-crisp-api` to `crisp-api`. Since it is typical of NPM packages to skip the `node-` prefix in their name, we chose to normalize the package name to this community standard. The programmatic API did not change, so you can simply update the package name to the new name in your `package.json` and all imports.
109
125
 
110
126
  ## v4.2.0
111
127
 
@@ -141,14 +157,14 @@ Changelog
141
157
 
142
158
  ### Breaking Changes
143
159
 
144
- **⚠️ Major changes follow, that will likely require that you update your integration code. If you want to wait to apply those changes, we recommend that you pin `node-crisp-api` to `3.0.0` or lower in your `package.json`.**
160
+ **🆘 Major changes follow, that will likely require that you update your integration code. If you want to wait to apply those changes, we recommend that you pin `node-crisp-api` to `3.0.0` or lower in your `package.json`.**
145
161
 
146
- * All resource methods have been nested into their parent category, eg. `website`. So all calls to eg. `CrispClient.websiteConversation` or `CrispClient.websitePeople` become `CrispClient.website`. This makes API calls more readable throughout your code.
162
+ * ⚠️ All resource methods have been nested into their parent category, eg. `website`. So all calls to eg. `CrispClient.websiteConversation` or `CrispClient.websitePeople` become `CrispClient.website`. This makes API calls more readable throughout your code.
147
163
 
148
164
  ## v3.0.0
149
165
 
150
166
  ### Breaking Changes
151
167
 
152
- **⚠️ Major changes follow, that will likely require that you update your integration code. If you want to wait to apply those changes, we recommend that you pin `node-crisp-api` to `2.0.0` or lower in your `package.json`.**
168
+ **🆘 Major changes follow, that will likely require that you update your integration code. If you want to wait to apply those changes, we recommend that you pin `node-crisp-api` to `2.0.0` or lower in your `package.json`.**
153
169
 
154
- * The programmatic interface to `node-crisp-api` has been completely revamped, so that all REST API methods specified in [REST API Reference (V1)](https://docs.crisp.chat/references/rest-api/v1/) are also available in this wrapper. Most method names have been changed as to match their name in the reference. Please check the [README](./README.md) for a full list of available methods.
170
+ * ⚠️ The programmatic interface to `node-crisp-api` has been completely revamped, so that all REST API methods specified in [REST API Reference (V1)](https://docs.crisp.chat/references/rest-api/v1/) are also available in this wrapper. Most method names have been changed as to match their name in the reference. Please check the [README](./README.md) for a full list of available methods.
package/README.md CHANGED
@@ -142,7 +142,7 @@ All methods that you will most likely need when building a Crisp integration are
142
142
  </details>
143
143
  <details>
144
144
  <summary>
145
- <a href="#plugin">RTM Events</a>
145
+ <a href="#realtime-events">RTM Events</a>
146
146
  </summary>
147
147
  <ul>
148
148
  <li><a href="#session--reference">Session Events</a></li>
@@ -2399,10 +2399,42 @@ _👉 Notice: The `peopleID` argument can be an email or the `peopleID`._
2399
2399
 
2400
2400
  You can bind to realtime events from Crisp, in order to get notified of incoming messages and updates in websites.
2401
2401
 
2402
- Before you start with RTM events, please consider the following:
2402
+ You won't receive any event if you don't explicitly subscribe to realtime events, as the library doesn't connect to the realtime backend automatically.
2403
+
2404
+ **There are two ways to receive realtime events:**
2405
+
2406
+ 1. Using Web Hooks (**⭐ recommended**)
2407
+ 2. Using WebSockets with the RTM API
2408
+
2409
+ **Before you start with RTM events, please consider the following:**
2403
2410
 
2404
2411
  * You won't receive any event if you don't explicitly subscribe to realtime events using `CrispClient.on()`, as the library doesn't connect to the realtime backend automatically. This method returns a `Promise` object.
2405
- * Whenever the list of websites that your authentication token is entitled to receive events for changes, you will need to call `CrispClient.rebind()`. This method also returns a `Promise` object.
2412
+ * Whenever the list of websites that your authentication token is entitled to receive events for changes, you will need to call `CrispClient.rebindSocket()`. This method also returns a `Promise` object. _This only applies to WebSockets with the RTM API_.
2413
+
2414
+ ### Receive realtime events
2415
+
2416
+ #### Receive events over Web Hooks
2417
+
2418
+ To start listening for events and bind a handler, check out the [events over Web Hooks example](https://github.com/crisp-im/node-crisp-api/blob/master/examples/events_webhooks.js).
2419
+
2420
+ You will need to adjust your code so that:
2421
+
2422
+ 1. The RTM events mode is set to Web Hooks: `CrispClient.setRtmMode(Crisp.RTM_MODES.WebHooks)`
2423
+ 2. Your HTTP endpoint mounts a route listening for POST requests, and upon receiving requests:
2424
+ 1. It verifies the requests with: `CrispClient.verifyHook(secret, body, timestamp, signature)`
2425
+ 2. It receives the Web Hook with: `CrispClient.receiveHook(body)`
2426
+
2427
+ Plugin Web Hooks will need to be configured first for this to work. Check out our [Web Hooks Quickstart guide](https://docs.crisp.chat/guides/web-hooks/quickstart/) and our [Web Hooks Reference](https://docs.crisp.chat/references/web-hooks/v1/) to get started.
2428
+
2429
+ #### Receive events over WebSockets (RTM API)
2430
+
2431
+ To start listening for events and bind a handler, check out the [events over WebSockets example](https://github.com/crisp-im/node-crisp-api/blob/master/examples/events_websockets.js).
2432
+
2433
+ You will need to adjust your code so that:
2434
+
2435
+ 1. The RTM events mode is set to WebSockets: `CrispClient.setRtmMode(Crisp.RTM_MODES.WebSockets)`
2436
+
2437
+ ### Available realtime events
2406
2438
 
2407
2439
  Available events are listed below:
2408
2440
 
package/lib/crisp.js CHANGED
@@ -14,26 +14,41 @@ var pkg = require("../package.json");
14
14
  var got = require("got");
15
15
 
16
16
  var URL = require("url").URL;
17
+ var Crypto = require("crypto");
17
18
  var EventEmitter = require("fbemitter").EventEmitter;
18
19
 
19
20
 
21
+ // RTM modes available
22
+ Crisp.RTM_MODES = {
23
+ WebSockets : "websockets",
24
+ WebHooks : "webhooks"
25
+ };
26
+
27
+ Crisp.AVAILABLE_RTM_MODES = [
28
+ Crisp.RTM_MODES.WebSockets,
29
+ Crisp.RTM_MODES.WebHooks
30
+ ];
31
+
32
+
20
33
  // Base configuration
21
34
  Crisp.DEFAULT_REQUEST_TIMEOUT = 10000;
22
35
  Crisp.DEFAULT_SOCKET_TIMEOUT = 10000;
23
36
  Crisp.DEFAULT_SOCKET_RECONNECT_DELAY = 5000;
24
37
  Crisp.DEFAULT_SOCKET_RECONNECT_DELAY_MAX = 10000;
25
38
  Crisp.DEFAULT_SOCKET_RECONNECT_FACTOR = 0.75;
26
- Crisp.DEFAULT_SOCKET_SCHEDULE = 500;
39
+ Crisp.DEFAULT_BROKER_SCHEDULE = 500;
27
40
  Crisp.DEFAULT_EVENT_REBIND_INTERVAL_MIN = 2500;
28
41
  Crisp.DEFAULT_USERAGENT_PREFIX = "node-crisp-api/";
29
42
 
30
43
 
31
44
  // REST API defaults
32
- Crisp.DEFAULT_REST_HOST = "https://api.crisp.chat";
33
- Crisp.DEFAULT_REST_BASE_PATH = "/v1/";
45
+ Crisp.DEFAULT_REST_HOST = "https://api.crisp.chat";
46
+ Crisp.DEFAULT_REST_BASE_PATH = "/v1/";
34
47
 
35
48
 
36
49
  // RTM API defaults
50
+ Crisp.DEFAULT_RTM_MODE = Crisp.RTM_MODES.WebSockets;
51
+
37
52
  Crisp.DEFAULT_RTM_EVENTS = [
38
53
  // Session Events
39
54
  "session:update_availability",
@@ -162,7 +177,8 @@ function Crisp() {
162
177
 
163
178
  /** @private */
164
179
  this._rtm = {
165
- host : null
180
+ host : null,
181
+ mode : Crisp.DEFAULT_RTM_MODE
166
182
  };
167
183
 
168
184
  /** @private */
@@ -175,16 +191,19 @@ function Crisp() {
175
191
  this._socket = null;
176
192
 
177
193
  /** @private */
178
- this._socketScheduler = null;
194
+ this._loopback = null;
179
195
 
180
196
  /** @private */
181
197
  this._lastEventRebind = null;
182
198
 
183
199
  /** @private */
184
- this._socketBindHooks = [];
200
+ this._brokerScheduler = null;
201
+
202
+ /** @private */
203
+ this._brokerBindHooks = [];
185
204
 
186
205
  /** @private */
187
- this._boundEvents = [];
206
+ this._boundEvents = {};
188
207
 
189
208
  // Prepare
190
209
  this._prepareServices();
@@ -193,7 +212,7 @@ function Crisp() {
193
212
 
194
213
  Crisp.prototype = {
195
214
  /**
196
- * Set the REST API host
215
+ * Sets the REST API host
197
216
  * @memberof Crisp
198
217
  * @method setRestHost
199
218
  * @param {string} host - Hostname
@@ -207,7 +226,7 @@ Crisp.prototype = {
207
226
  },
208
227
 
209
228
  /**
210
- * Set the RTM API host
229
+ * Sets the RTM API host
211
230
  * @memberof Crisp
212
231
  * @method setRtmHost
213
232
  * @param {string} host - Hostname
@@ -220,6 +239,23 @@ Crisp.prototype = {
220
239
  }
221
240
  },
222
241
 
242
+ /**
243
+ * Sets the RTM channel mode (ie. WebSockets or Web Hooks)
244
+ * @memberof Crisp
245
+ * @method setRtmMode
246
+ * @param {string} mode - RTM mode ('websockets' or 'webhooks')
247
+ */
248
+ setRtmMode : function(mode) {
249
+ if (Crisp.AVAILABLE_RTM_MODES.indexOf(mode) !== -1) {
250
+ this._rtm.mode = mode;
251
+ } else {
252
+ throw new Error(
253
+ "[Crisp] setRtmMode: parameter mode value should be one of: " +
254
+ Crisp.AVAILABLE_RTM_MODES.join(", ")
255
+ );
256
+ }
257
+ },
258
+
223
259
  /**
224
260
  * Sets the tier
225
261
  * @memberof Crisp
@@ -231,7 +267,7 @@ Crisp.prototype = {
231
267
  },
232
268
 
233
269
  /**
234
- * Authenticate
270
+ * Authenticates
235
271
  * @memberof Crisp
236
272
  * @method authenticate
237
273
  * @param {string} identifier
@@ -247,7 +283,7 @@ Crisp.prototype = {
247
283
  },
248
284
 
249
285
  /**
250
- * Authenticate (with tier)
286
+ * Authenticates (with tier)
251
287
  * @memberof Crisp
252
288
  * @method authenticateTier
253
289
  * @param {string} tier
@@ -368,7 +404,7 @@ Crisp.prototype = {
368
404
  },
369
405
 
370
406
  /**
371
- * Bind socket event
407
+ * Binds RTM event
372
408
  * @memberof Crisp
373
409
  * @method on
374
410
  * @param {string} event
@@ -391,30 +427,41 @@ Crisp.prototype = {
391
427
  }
392
428
 
393
429
  // Important: we do not allow .on() to be called once socket is connected, \
394
- // as we consider event listeners must be bound once all together. This \
395
- // prevents bogous integrations from sending flood of 'socket:bind'` to \
396
- // the RTM API.
397
- if (this._socket) {
430
+ // or loopback is bound as we consider event listeners must be bound \
431
+ // once all together. This prevents bogous integrations from sending \
432
+ // flood of 'socket:bind'` to the RTM API, if using WebSockets. Web \
433
+ // Hooks follows the same scheme for consistency's sake.
434
+ if (this._socket || this._loopback) {
398
435
  throw new Error(
399
- "[Crisp] on: socket is already bound, please bind event earlier on: " +
400
- "'" + event + "'"
436
+ "[Crisp] on: connector is already bound, please listen to event " +
437
+ "earlier on: '" + event + "'"
401
438
  );
402
439
  }
403
440
 
404
441
  // Add listener to emitter
405
442
  this._emitter.addListener(event, callback);
406
443
 
407
- // Subscribe event on the socket
408
- if (this._boundEvents.indexOf(event) === -1) {
409
- this._boundEvents.push(event);
410
-
411
- // Socket not connected? Connect now.
412
- return this._prepareSocket(
413
- function(socket, emitter) {
414
- // Listen for event (once socket is bound)
415
- socket.on(event, function(data) {
416
- emitter.emit(event, data);
417
- });
444
+ // Subscribe event on the broker
445
+ if (this._boundEvents[event] !== true) {
446
+ var rtmMode = this._rtm.mode;
447
+
448
+ // Mark event as bound
449
+ this._boundEvents[event] = true;
450
+
451
+ // Broker not connected? Connect now.
452
+ return this._prepareBroker(
453
+ function(instance, emitter) {
454
+ // Listen for event? (once instance is bound)
455
+ switch (rtmMode) {
456
+ case Crisp.RTM_MODES.WebSockets: {
457
+ // Listen on socket event
458
+ instance.on(event, function(data) {
459
+ emitter.emit(event, data);
460
+ });
461
+
462
+ break;
463
+ }
464
+ }
418
465
  }
419
466
  );
420
467
  }
@@ -423,14 +470,96 @@ Crisp.prototype = {
423
470
  },
424
471
 
425
472
  /**
426
- * Rebind socket events
473
+ * Receives a raw event and dispatches it to the listener (used for Web Hooks)
474
+ * @memberof Crisp
475
+ * @method receiveHook
476
+ * @param {object} body
477
+ */
478
+ receiveHook : function(body) {
479
+ var self = this;
480
+
481
+ if (self._loopback) {
482
+ // Ensure payload is readable
483
+ if (!body || typeof body !== "object") {
484
+ return new Error("[Crisp] receiveHook: empty hook payload");
485
+ }
486
+
487
+ // Ensure payload is properly formatted
488
+ if (!body.event || !body.data ||
489
+ typeof body.event !== "string" || typeof body.data !== "object") {
490
+ return new Error("[Crisp] receiveHook: malformatted hook payload");
491
+ }
492
+
493
+ // Check if event is subscribed to? (in routing table)
494
+ // Notice: if not in routing table, then silently discard the event w/o \
495
+ // any error, as we do not want an HTTP failure status to be sent in \
496
+ // response by the implementor.
497
+ if (self._boundEvents[body.event] !== true) {
498
+ return null;
499
+ }
500
+
501
+ // Dispatch event to event bus
502
+ // Notice: go asynchronous, so that the event is processed ASAP and \
503
+ // dispatched on the event bus later, as the hook might be received \
504
+ // synchronously over HTTP.
505
+ process.nextTick(function() {
506
+ self._loopback.emit(body.event, body.data);
507
+ });
508
+
509
+ return null;
510
+ }
511
+
512
+ return new Error("[Crisp] receiveHook: hook loopback not bound");
513
+ },
514
+
515
+ /**
516
+ * Verifies an event string and checks that signatures match (used for Web \
517
+ * Hooks)
518
+ * @memberof Crisp
519
+ * @method verifyHook
520
+ * @param {string} secret
521
+ * @param {object} body
522
+ * @param {string} timestamp
523
+ * @param {string} signature
524
+ */
525
+ verifyHook : function(secret, body, timestamp, signature) {
526
+ if (this._loopback) {
527
+ // Ensure all provided data is valid
528
+ if (!secret || !signature || !body || typeof body !== "object" ||
529
+ !timestamp || isNaN(timestamp) === true) {
530
+ return false;
531
+ }
532
+
533
+ // Compute local trace
534
+ var localTrace = ("[" + timestamp + ";" + JSON.stringify(body) + "]");
535
+
536
+ // Create local HMAC
537
+ var localMac = Crypto.createHmac("sha256", secret);
538
+
539
+ localMac.update(localTrace);
540
+
541
+ // Compute local signature, and compare
542
+ var localSignature = localMac.digest("hex");
543
+
544
+ return (
545
+ (signature === localSignature) ? true : false
546
+ );
547
+ }
548
+
549
+ // Default: not verified (signatures do not match or loopback not /yet?/ \
550
+ // bound)
551
+ return false;
552
+ },
553
+
554
+ /**
555
+ * Rebinds socket events (used for WebSockets)
427
556
  * @memberof Crisp
428
557
  * @method rebind
429
558
  */
430
- rebind : function() {
559
+ rebindSocket : function() {
431
560
  if (!this._socket) {
432
561
  throw new Error(
433
- "[Crisp] rebind: cannot rebind a socket that is not yet bound"
562
+ "[Crisp] rebindSocket: cannot rebind a socket that is not yet bound"
434
563
  );
435
564
  }
436
565
 
@@ -442,7 +571,8 @@ Crisp.prototype = {
442
571
  ((nowTime - this._lastEventRebind) <
443
572
  Crisp.DEFAULT_EVENT_REBIND_INTERVAL_MIN)) {
444
573
  throw new Error(
445
- "[Crisp] rebind: cannot rebind, last rebind was requested too recently"
574
+ "[Crisp] rebindSocket: cannot rebind, last rebind was requested too " +
575
+ "recently"
446
576
  );
447
577
  }
448
578
 
@@ -455,14 +585,14 @@ Crisp.prototype = {
455
585
  },
456
586
 
457
587
  /**
458
- * Prepare a URI based from path segments
588
+ * Prepares a URI based from path segments
459
589
  * @memberof Crisp
460
590
  * @private
461
591
  * @method _prepareRestUrl
462
592
  * @param {Array} paths - List of paths ['session', 'login']
463
593
  */
464
594
  _prepareRestUrl : function(paths) {
465
- if (Array.isArray(paths)) {
595
+ if (Array.isArray(paths) === true) {
466
596
  var output = this._rest.host + this._rest.basePath;
467
597
 
468
598
  output += paths.join("/");
@@ -522,38 +652,65 @@ Crisp.prototype = {
522
652
  },
523
653
 
524
654
  /**
525
- * Binds socket to the main object
655
+ * Binds broker to the main object
526
656
  * @memberof Crisp
527
657
  * @private
528
- * @method _prepareSocket
658
+ * @method _prepareBroker
529
659
  * @param {function} fnBindHook
530
660
  */
531
- _prepareSocket : function(fnBindHook) {
661
+ _prepareBroker : function(fnBindHook) {
532
662
  var self = this;
533
663
 
534
664
  return new Promise(function(resolve, reject) {
535
- var rtmHostOverride = self._rtm.host;
665
+ var rtmMode = self._rtm.mode,
666
+ rtmHostOverride = self._rtm.host;
536
667
 
537
668
  // Append bind hook to pending stack
538
- self._socketBindHooks.push(fnBindHook);
669
+ self._brokerBindHooks.push(fnBindHook);
539
670
 
540
- // Make sure to prepare socket once? (defer socket binding, waiting that \
671
+ // Make sure to prepare broker once? (defer broker binding, waiting that \
541
672
  // all listeners have been bound, that way we submit the list of \
542
673
  // filtered events to the RTM API once, and never again in the future)
543
- if (self._socketScheduler === null) {
544
- // Socket is already set? We should not even have entered there.
545
- if (self._socket) {
674
+ if (self._brokerScheduler === null) {
675
+ // Socket or loopback already set? We should not even have entered \
676
+ // there.
677
+ if (self._socket || self._loopback) {
546
678
  throw new Error(
547
- "[Crisp] prepareSocket: illegal call to prepare socket (tie break)"
679
+ "[Crisp] prepareBroker: illegal call to prepare broker (tie break)"
548
680
  );
549
681
  }
550
682
 
551
- self._socketScheduler = setTimeout(function() {
552
- // Connect to socket now
553
- self._connectSocket(rtmHostOverride)
554
- .then(resolve)
555
- .catch(reject);
556
- }, Crisp.DEFAULT_SOCKET_SCHEDULE);
683
+ self._brokerScheduler = setTimeout(function() {
684
+ switch (rtmMode) {
685
+ case Crisp.RTM_MODES.WebSockets: {
686
+ // Connect to socket now
687
+ // Notice: will unstack broker bind hooks once ready
688
+ self._connectSocket(rtmHostOverride)
689
+ .then(resolve)
690
+ .catch(reject);
691
+
692
+ break;
693
+ }
694
+
695
+ case Crisp.RTM_MODES.WebHooks: {
696
+ // Connect to loopback now
697
+ self._connectLoopback()
698
+ .then(resolve)
699
+ .catch(reject);
700
+
701
+ break;
702
+ }
703
+
704
+ default: {
705
+ var unsupportedError = new Error(
706
+ "[Crisp] prepareBroker: mode of RTM broker unsupported " +
707
+ "('" + rtmMode + "')"
708
+ );
709
+
710
+ reject(unsupportedError);
711
+ }
712
+ }
713
+ }, Crisp.DEFAULT_BROKER_SCHEDULE);
557
714
  } else {
558
715
  // Pass-through
559
716
  resolve();
@@ -562,7 +719,23 @@ Crisp.prototype = {
562
719
  },
563
720
 
564
721
  /**
565
- * Connects socket, using preferred RTM API host
722
+ * Connects loopback (used for Web Hooks)
723
+ * @memberof Crisp
724
+ * @private
725
+ * @method _connectLoopback
726
+ */
727
+ _connectLoopback : function() {
728
+ // Assign emitter to loopback
729
+ this._loopback = this._emitter;
730
+
731
+ // Unstack broker bind hooks immediately
732
+ this._unstackBrokerBindHooks(this._loopback);
733
+
734
+ return Promise.resolve();
735
+ },
736
+
737
+ /**
738
+ * Connects socket, using preferred RTM API host (used for WebSockets)
566
739
  * @memberof Crisp
567
740
  * @private
568
741
  * @method _connectSocket
@@ -630,11 +803,11 @@ Crisp.prototype = {
630
803
  randomizationFactor : Crisp.DEFAULT_SOCKET_RECONNECT_FACTOR
631
804
  });
632
805
 
633
- self._emitAuthenticate();
806
+ self._emitAuthenticateSocket();
634
807
 
635
808
  // Setup base socket event listeners
636
809
  self._socket.io.on("reconnect", function() {
637
- self._emitAuthenticate();
810
+ self._emitAuthenticateSocket();
638
811
  });
639
812
 
640
813
  self._socket.on("unauthorized", function() {
@@ -645,16 +818,68 @@ Crisp.prototype = {
645
818
  });
646
819
 
647
820
  // Setup user socket event listeners
648
- while (self._socketBindHooks.length > 0) {
649
- self._socketBindHooks.shift()(
650
- self._socket, self._emitter
651
- );
652
- }
821
+ self._unstackBrokerBindHooks(self._socket);
822
+
823
+ return Promise.resolve();
653
824
  });
654
825
  },
655
826
 
656
827
  /**
657
- * Perform a request
828
+ * Authenticates client (used for WebSockets)
829
+ * @memberof Crisp
830
+ * @private
831
+ * @method _emitAuthenticateSocket
832
+ */
833
+ _emitAuthenticateSocket : function() {
834
+ var auth = this.auth,
835
+ tier = this._tier,
836
+ boundEvents = Object.keys(this._boundEvents);
837
+
838
+ if (!this._socket) {
839
+ throw new Error(
840
+ "[Crisp] emitAuthenticateSocket: cannot listen for events as socket " +
841
+ "is not yet bound"
842
+ );
843
+ }
844
+ if (!auth.identifier || !auth.key) {
845
+ throw new Error(
846
+ "[Crisp] emitAuthenticateSocket: cannot listen for events as you " +
847
+ "did not authenticate"
848
+ );
849
+ }
850
+ if (boundEvents.length === 0) {
851
+ throw new Error(
852
+ "[Crisp] emitAuthenticateSocket: cannot listen for events as no " +
853
+ "event is being listened to"
854
+ );
855
+ }
856
+
857
+ this._socket.emit("authentication", {
858
+ username : auth.identifier,
859
+ password : auth.key,
860
+ tier : tier,
861
+ events : boundEvents
862
+ });
863
+ },
864
+
865
+ /**
866
+ * Unstacks pending broker bind hooks
867
+ * @memberof Crisp
868
+ * @private
869
+ * @method _unstackBrokerBindHooks
870
+ * @param {object} modeInstance
871
+ */
872
+ _unstackBrokerBindHooks : function(modeInstance) {
873
+ // Setup user socket event listeners
874
+ while (this._brokerBindHooks.length > 0) {
875
+ this._brokerBindHooks.shift()(
876
+ modeInstance, this._emitter
877
+ );
878
+ }
879
+ },
880
+
881
+ /**
882
+ * Performs a request to REST API
658
883
  * @memberof Crisp
659
884
  * @private
660
885
  * @method _request
@@ -666,21 +891,23 @@ Crisp.prototype = {
666
891
  * @param {function} reject
667
892
  */
668
893
  _request : function(resource, method, query, body, resolve, reject) {
894
+ var self = this;
895
+
669
896
  var requestParameters = {
670
897
  responseType : "json",
671
898
  timeout : Crisp.DEFAULT_REQUEST_TIMEOUT,
672
899
 
673
900
  headers : {
674
- "User-Agent" : this._useragent,
675
- "X-Crisp-Tier" : this._tier
901
+ "User-Agent" : self._useragent,
902
+ "X-Crisp-Tier" : self._tier
676
903
  },
677
904
 
678
905
  throwHttpErrors : false
679
906
  };
680
907
 
681
908
  // Add authorization?
682
- if (this.auth.token) {
683
- requestParameters.headers.Authorization = ("Basic " + this.auth.token);
909
+ if (self.auth.token) {
910
+ requestParameters.headers.Authorization = ("Basic " + self.auth.token);
684
911
  }
685
912
 
686
913
  // Add body?
@@ -720,7 +947,9 @@ Crisp.prototype = {
720
947
 
721
948
  // Response error?
722
949
  if (response.statusCode >= 400) {
723
- var reason_message = ((response.body || {}).reason || "http_error");
950
+ var reason_message = self._readErrorResponseReason(
951
+ method, response.statusCode, response
952
+ );
724
953
  var data_message = ((response.body || {}).data || {}).message;
725
954
 
726
955
  return reject({
@@ -746,40 +975,32 @@ Crisp.prototype = {
746
975
  },
747
976
 
748
977
  /**
749
- * Authenticate client
978
+ * Reads reason for error response
750
979
  * @memberof Crisp
751
980
  * @private
752
- * @method _emitAuthenticate
981
+ * @method _readErrorResponseReason
982
+ * @param {string} method
983
+ * @param {number} statusCode
984
+ * @param {object} response
753
985
  */
754
- _emitAuthenticate : function() {
755
- var auth = this.auth;
756
- var tier = this._tier;
986
+ _readErrorResponseReason : function(method, statusCode, response) {
987
+ // HEAD method? As HEAD requests do not expect any response body, then we \
988
+ // cannot map a reason from the response.
989
+ if (method === "head") {
990
+ // 5xx errors?
991
+ if (statusCode >= 500) {
992
+ return "server_error";
993
+ }
757
994
 
758
- if (!this._socket) {
759
- throw new Error(
760
- "[Crisp] emitAuthenticate: cannot listen for events as socket is " +
761
- "not yet bound"
762
- );
763
- }
764
- if (!auth.identifier || !auth.key) {
765
- throw new Error(
766
- "[Crisp] emitAuthenticate: cannot listen for events as you did not " +
767
- "authenticate"
768
- );
769
- }
770
- if (this._boundEvents.length === 0) {
771
- throw new Error(
772
- "[Crisp] emitAuthenticate: cannot listen for events as no event is " +
773
- "being listened to"
774
- );
995
+ // 4xx errors?
996
+ if (statusCode >= 400) {
997
+ return "route_error";
998
+ }
775
999
  }
776
1000
 
777
- this._socket.emit("authentication", {
778
- username : auth.identifier,
779
- password : auth.key,
780
- tier : tier,
781
- events : this._boundEvents
782
- });
1001
+ // Other methods must hold a response body, therefore we can fallback on \
1002
+ // an HTTP error if we fail to acquire any reason at all.
1003
+ return ((response.body || {}).reason || "http_error");
783
1004
  }
784
1005
  };
785
1006
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "crisp-api",
3
3
  "description": "Crisp API wrapper for Node - official, maintained by Crisp",
4
- "version": "6.4.0",
4
+ "version": "7.0.0",
5
5
  "homepage": "https://github.com/crisp-im/node-crisp-api",
6
6
  "license": "MIT",
7
7
  "author": {
@@ -9,6 +9,18 @@
9
9
  "email": "baptiste@crisp.chat",
10
10
  "url": "https://jamin.me/"
11
11
  },
12
+ "contributors": [
13
+ {
14
+ "name": "Valerian Saliou",
15
+ "email": "valerian@valeriansaliou.name",
16
+ "url": "https://valeriansaliou.name/"
17
+ },
18
+ {
19
+ "name": "Eliott Vincent",
20
+ "email": "eliott@crisp.chat",
21
+ "url": "https://www.eliottvincent.com/"
22
+ }
23
+ ],
12
24
  "repository": {
13
25
  "type": "git",
14
26
  "url": "git://github.com/crisp-im/node-crisp-api.git"
package/types/crisp.d.ts CHANGED
@@ -32,6 +32,7 @@ declare class Crisp {
32
32
  private _boundEvents;
33
33
  setRestHost: (host: string) => void;
34
34
  setRtmHost: (host: string) => void;
35
+ setRtmMode: (mode: string) => void;
35
36
  setTier: (tier: string) => void;
36
37
  authenticate: (identifier: string, key: string) => void;
37
38
  authenticateTier: (tier: string, identifier: string, key: string) => void;
@@ -41,18 +42,23 @@ declare class Crisp {
41
42
  patch: (resource: string, query: object, body: object) => any;
42
43
  put: (resource: string, query: object, body: object) => any;
43
44
  delete: (resource: string, query: object, body: object) => any;
44
- on: (event: string, callback: Function) => void;
45
- rebind: () => void;
45
+ on: (event: string, callback: Function) => any;
46
+ rebindSocket: () => any;
46
47
  _prepareRestUrl: (paths: any[]) => string;
47
48
  _prepareServices: () => void;
48
49
  _prepareResources: (serviceInstance: object, resources: any[]) => void;
49
- _prepareSocket: (fnBindHook: Function) => void;
50
+ _prepareBroker: (fnBindHook: Function) => any;
51
+ _connectLoopback: () => any;
52
+ _connectSocket: (rtmHostOverride: string) => any;
53
+ _emitAuthenticateSocket: () => void;
54
+ _unstackBrokerBindHooks: (modeInstance: object) => void;
50
55
  _request: (resource: string, method: string, query: object, body: object, resolve: Function, reject: Function) => void;
51
- _emitAuthenticate: () => void;
52
56
  }
53
57
  declare namespace Crisp {
54
- export { DEFAULT_REQUEST_TIMEOUT, DEFAULT_SOCKET_TIMEOUT, DEFAULT_SOCKET_RECONNECT_DELAY, DEFAULT_SOCKET_RECONNECT_DELAY_MAX, DEFAULT_SOCKET_RECONNECT_FACTOR, DEFAULT_SOCKET_SCHEDULE, DEFAULT_EVENT_REBIND_INTERVAL_MIN, DEFAULT_USERAGENT_PREFIX, DEFAULT_REST_HOST, DEFAULT_REST_BASE_PATH, DEFAULT_RTM_EVENTS, Crisp };
58
+ export { RTM_MODES, AVAILABLE_RTM_MODES, DEFAULT_REQUEST_TIMEOUT, DEFAULT_SOCKET_TIMEOUT, DEFAULT_SOCKET_RECONNECT_DELAY, DEFAULT_SOCKET_RECONNECT_DELAY_MAX, DEFAULT_SOCKET_RECONNECT_FACTOR, DEFAULT_SOCKET_SCHEDULE, DEFAULT_EVENT_REBIND_INTERVAL_MIN, DEFAULT_USERAGENT_PREFIX, DEFAULT_REST_HOST, DEFAULT_REST_BASE_PATH, DEFAULT_RTM_MODE, DEFAULT_RTM_EVENTS, Crisp };
55
59
  }
60
+ declare var RTM_MODES: object;
61
+ declare var AVAILABLE_RTM_MODES: string[];
56
62
  declare var DEFAULT_REQUEST_TIMEOUT: number;
57
63
  declare var DEFAULT_SOCKET_TIMEOUT: number;
58
64
  declare var DEFAULT_SOCKET_RECONNECT_DELAY: number;
@@ -63,4 +69,5 @@ declare var DEFAULT_EVENT_REBIND_INTERVAL_MIN: number;
63
69
  declare var DEFAULT_USERAGENT_PREFIX: string;
64
70
  declare var DEFAULT_REST_HOST: string;
65
71
  declare var DEFAULT_REST_BASE_PATH: string;
72
+ declare var DEFAULT_RTM_MODE: string;
66
73
  declare var DEFAULT_RTM_EVENTS: string[];