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 +24 -8
- package/README.md +35 -3
- package/lib/crisp.js +314 -93
- package/package.json +13 -1
- package/types/crisp.d.ts +12 -5
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
|
-
|
|
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
|
-
|
|
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="#
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
33
|
-
Crisp.DEFAULT_REST_BASE_PATH
|
|
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.
|
|
194
|
+
this._loopback = null;
|
|
179
195
|
|
|
180
196
|
/** @private */
|
|
181
197
|
this._lastEventRebind = null;
|
|
182
198
|
|
|
183
199
|
/** @private */
|
|
184
|
-
this.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
395
|
-
// prevents bogous integrations from sending
|
|
396
|
-
// the RTM API.
|
|
397
|
-
|
|
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:
|
|
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
|
|
408
|
-
if (this._boundEvents
|
|
409
|
-
this.
|
|
410
|
-
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
559
|
+
rebindSocket : function() {
|
|
431
560
|
if (!this._socket) {
|
|
432
561
|
throw new Error(
|
|
433
|
-
"[Crisp]
|
|
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]
|
|
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
|
-
*
|
|
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
|
|
655
|
+
* Binds broker to the main object
|
|
526
656
|
* @memberof Crisp
|
|
527
657
|
* @private
|
|
528
|
-
* @method
|
|
658
|
+
* @method _prepareBroker
|
|
529
659
|
* @param {function} fnBindHook
|
|
530
660
|
*/
|
|
531
|
-
|
|
661
|
+
_prepareBroker : function(fnBindHook) {
|
|
532
662
|
var self = this;
|
|
533
663
|
|
|
534
664
|
return new Promise(function(resolve, reject) {
|
|
535
|
-
var
|
|
665
|
+
var rtmMode = self._rtm.mode,
|
|
666
|
+
rtmHostOverride = self._rtm.host;
|
|
536
667
|
|
|
537
668
|
// Append bind hook to pending stack
|
|
538
|
-
self.
|
|
669
|
+
self._brokerBindHooks.push(fnBindHook);
|
|
539
670
|
|
|
540
|
-
// Make sure to prepare
|
|
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.
|
|
544
|
-
// Socket
|
|
545
|
-
|
|
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]
|
|
679
|
+
"[Crisp] prepareBroker: illegal call to prepare broker (tie break)"
|
|
548
680
|
);
|
|
549
681
|
}
|
|
550
682
|
|
|
551
|
-
self.
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
|
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.
|
|
806
|
+
self._emitAuthenticateSocket();
|
|
634
807
|
|
|
635
808
|
// Setup base socket event listeners
|
|
636
809
|
self._socket.io.on("reconnect", function() {
|
|
637
|
-
self.
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
);
|
|
652
|
-
}
|
|
821
|
+
self._unstackBrokerBindHooks(self._socket);
|
|
822
|
+
|
|
823
|
+
return Promise.resolve();
|
|
653
824
|
});
|
|
654
825
|
},
|
|
655
826
|
|
|
656
827
|
/**
|
|
657
|
-
*
|
|
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" :
|
|
675
|
-
"X-Crisp-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 (
|
|
683
|
-
requestParameters.headers.Authorization = ("Basic " +
|
|
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 = (
|
|
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
|
-
*
|
|
978
|
+
* Reads reason for error response
|
|
750
979
|
* @memberof Crisp
|
|
751
980
|
* @private
|
|
752
|
-
* @method
|
|
981
|
+
* @method _readErrorResponseReason
|
|
982
|
+
* @param {string} method
|
|
983
|
+
* @param {number} statusCode
|
|
984
|
+
* @param {object} response
|
|
753
985
|
*/
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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": "
|
|
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) =>
|
|
45
|
-
|
|
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
|
-
|
|
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[];
|