crisp-api 9.12.1 → 10.0.2
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 +14 -0
- package/README.md +6 -2
- package/dist/crisp.d.ts +167 -0
- package/dist/crisp.js +764 -0
- package/dist/resources/BaseResource.d.ts +15 -0
- package/dist/resources/BaseResource.js +20 -0
- package/dist/resources/BucketURL.d.ts +28 -0
- package/dist/resources/BucketURL.js +29 -0
- package/dist/resources/MediaAnimation.d.ts +14 -0
- package/dist/resources/MediaAnimation.js +32 -0
- package/dist/resources/PluginConnect.d.ts +50 -0
- package/dist/resources/PluginConnect.js +73 -0
- package/dist/resources/PluginSubscription.d.ts +103 -0
- package/dist/resources/PluginSubscription.js +122 -0
- package/dist/resources/WebsiteAnalytics.d.ts +14 -0
- package/dist/resources/WebsiteAnalytics.js +29 -0
- package/dist/resources/WebsiteAvailability.d.ts +31 -0
- package/dist/resources/WebsiteAvailability.js +36 -0
- package/dist/resources/WebsiteBase.d.ts +60 -0
- package/dist/resources/WebsiteBase.js +71 -0
- package/dist/resources/WebsiteBatch.d.ts +52 -0
- package/dist/resources/WebsiteBatch.js +70 -0
- package/dist/resources/WebsiteCampaign.d.ts +199 -0
- package/dist/resources/WebsiteCampaign.js +194 -0
- package/dist/resources/WebsiteConversation.d.ts +701 -0
- package/dist/resources/WebsiteConversation.js +595 -0
- package/dist/resources/WebsiteHelpdesk.d.ts +347 -0
- package/dist/resources/WebsiteHelpdesk.js +587 -0
- package/dist/resources/WebsiteOperator.d.ts +79 -0
- package/dist/resources/WebsiteOperator.js +93 -0
- package/dist/resources/WebsitePeople.d.ts +248 -0
- package/dist/resources/WebsitePeople.js +276 -0
- package/dist/resources/WebsiteSettings.d.ts +159 -0
- package/dist/resources/WebsiteSettings.js +36 -0
- package/dist/resources/WebsiteVerify.d.ts +38 -0
- package/dist/resources/WebsiteVerify.js +50 -0
- package/dist/resources/WebsiteVisitors.d.ts +113 -0
- package/dist/resources/WebsiteVisitors.js +88 -0
- package/dist/resources/index.d.ts +17 -0
- package/dist/resources/index.js +40 -0
- package/dist/services/bucket.d.ts +13 -0
- package/dist/services/bucket.js +28 -0
- package/dist/services/media.d.ts +13 -0
- package/dist/services/media.js +28 -0
- package/dist/services/plugin.d.ts +14 -0
- package/dist/services/plugin.js +30 -0
- package/dist/services/website.d.ts +24 -0
- package/dist/services/website.js +50 -0
- package/eslint.config.mjs +208 -0
- package/lib/crisp.ts +957 -0
- package/lib/resources/BaseResource.ts +29 -0
- package/lib/resources/BucketURL.ts +49 -0
- package/lib/resources/MediaAnimation.ts +34 -0
- package/lib/resources/PluginConnect.ts +128 -0
- package/lib/resources/PluginSubscription.ts +208 -0
- package/lib/resources/WebsiteAnalytics.ts +31 -0
- package/lib/resources/WebsiteAvailability.ts +54 -0
- package/lib/resources/WebsiteBase.ts +108 -0
- package/lib/resources/WebsiteBatch.ts +96 -0
- package/lib/resources/WebsiteCampaign.ts +399 -0
- package/lib/resources/WebsiteConversation.ts +1416 -0
- package/lib/resources/WebsiteHelpdesk.ts +982 -0
- package/lib/resources/WebsiteOperator.ts +161 -0
- package/lib/resources/WebsitePeople.ts +527 -0
- package/lib/resources/WebsiteSettings.ts +192 -0
- package/lib/resources/WebsiteVerify.ts +76 -0
- package/lib/resources/WebsiteVisitors.ts +196 -0
- package/lib/resources/index.ts +25 -0
- package/lib/services/bucket.ts +28 -0
- package/lib/services/media.ts +28 -0
- package/lib/services/plugin.ts +32 -0
- package/lib/services/website.ts +62 -0
- package/package.json +16 -11
- package/tsconfig.json +12 -5
- package/lib/crisp.js +0 -1168
- package/lib/resources/BucketURL.js +0 -34
- package/lib/resources/MediaAnimation.js +0 -41
- package/lib/resources/PluginConnect.js +0 -119
- package/lib/resources/PluginSubscription.js +0 -234
- package/lib/resources/WebsiteAnalytics.js +0 -37
- package/lib/resources/WebsiteAvailability.js +0 -48
- package/lib/resources/WebsiteBase.js +0 -100
- package/lib/resources/WebsiteBatch.js +0 -92
- package/lib/resources/WebsiteCampaign.js +0 -396
- package/lib/resources/WebsiteConversation.js +0 -1261
- package/lib/resources/WebsiteHelpdesk.js +0 -1198
- package/lib/resources/WebsiteOperator.js +0 -167
- package/lib/resources/WebsitePeople.js +0 -516
- package/lib/resources/WebsiteSettings.js +0 -50
- package/lib/resources/WebsiteVerify.js +0 -79
- package/lib/resources/WebsiteVisitors.js +0 -148
- package/lib/services/Bucket.js +0 -28
- package/lib/services/Media.js +0 -28
- package/lib/services/Plugin.js +0 -29
- package/lib/services/Website.js +0 -39
- package/types/crisp.d.ts +0 -151
- package/types/resources/BucketURL.d.ts +0 -15
- package/types/resources/MediaAnimation.d.ts +0 -15
- package/types/resources/PluginConnect.d.ts +0 -15
- package/types/resources/PluginSubscription.d.ts +0 -15
- package/types/resources/WebsiteAnalytics.d.ts +0 -15
- package/types/resources/WebsiteAvailability.d.ts +0 -15
- package/types/resources/WebsiteBase.d.ts +0 -15
- package/types/resources/WebsiteBatch.d.ts +0 -15
- package/types/resources/WebsiteCampaign.d.ts +0 -15
- package/types/resources/WebsiteConversation.d.ts +0 -15
- package/types/resources/WebsiteHelpdesk.d.ts +0 -15
- package/types/resources/WebsiteOperator.d.ts +0 -15
- package/types/resources/WebsitePeople.d.ts +0 -15
- package/types/resources/WebsiteSettings.d.ts +0 -15
- package/types/resources/WebsiteVerify.d.ts +0 -15
- package/types/resources/WebsiteVisitors.d.ts +0 -15
- package/types/services/Bucket.d.ts +0 -14
- package/types/services/Media.d.ts +0 -14
- package/types/services/Plugin.d.ts +0 -14
- package/types/services/Website.d.ts +0 -14
package/dist/crisp.js
ADDED
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* node-crisp-api
|
|
4
|
+
*
|
|
5
|
+
* Copyright 2022, Crisp IM SAS
|
|
6
|
+
* Author: Baptiste Jamin <baptiste@crisp.chat>
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
20
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
|
+
};
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.Crisp = void 0;
|
|
27
|
+
/**************************************************************************
|
|
28
|
+
* IMPORTS
|
|
29
|
+
***************************************************************************/
|
|
30
|
+
// NPM
|
|
31
|
+
const got_1 = __importDefault(require("got"));
|
|
32
|
+
const socket_io_client_1 = require("socket.io-client");
|
|
33
|
+
const url_1 = require("url");
|
|
34
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
35
|
+
const mitt_1 = __importDefault(require("mitt"));
|
|
36
|
+
// PROJECT: SERVICES
|
|
37
|
+
const bucket_1 = __importDefault(require("./services/bucket"));
|
|
38
|
+
const media_1 = __importDefault(require("./services/media"));
|
|
39
|
+
const plugin_1 = __importDefault(require("./services/plugin"));
|
|
40
|
+
const website_1 = __importDefault(require("./services/website"));
|
|
41
|
+
// PROJECT: MAIN
|
|
42
|
+
// Export all types from resources
|
|
43
|
+
__exportStar(require("./resources"), exports);
|
|
44
|
+
const AVAILABLE_RTM_MODES = [
|
|
45
|
+
"websockets",
|
|
46
|
+
"webhooks"
|
|
47
|
+
];
|
|
48
|
+
const VERSION = "10.0.2";
|
|
49
|
+
// Base configuration
|
|
50
|
+
const DEFAULT_REQUEST_TIMEOUT = 10000;
|
|
51
|
+
const DEFAULT_SOCKET_TIMEOUT = 10000;
|
|
52
|
+
const DEFAULT_SOCKET_RECONNECT_DELAY = 5000;
|
|
53
|
+
const DEFAULT_SOCKET_RECONNECT_DELAY_MAX = 10000;
|
|
54
|
+
const DEFAULT_SOCKET_RECONNECT_FACTOR = 0.75;
|
|
55
|
+
const DEFAULT_BROKER_SCHEDULE = 500;
|
|
56
|
+
const DEFAULT_EVENT_REBIND_INTERVAL_MIN = 2500;
|
|
57
|
+
const DEFAULT_USERAGENT_PREFIX = "node-crisp-api/";
|
|
58
|
+
// REST API defaults
|
|
59
|
+
const DEFAULT_REST_HOST = "https://api.crisp.chat";
|
|
60
|
+
const DEFAULT_REST_BASE_PATH = "/v1/";
|
|
61
|
+
// RTM API defaults
|
|
62
|
+
const DEFAULT_RTM_MODE = "websockets";
|
|
63
|
+
const DEFAULT_RTM_EVENTS = [
|
|
64
|
+
// Session Events
|
|
65
|
+
"session:update_availability",
|
|
66
|
+
"session:update_verify",
|
|
67
|
+
"session:request:initiated",
|
|
68
|
+
"session:set_email",
|
|
69
|
+
"session:set_phone",
|
|
70
|
+
"session:set_address",
|
|
71
|
+
"session:set_subject",
|
|
72
|
+
"session:set_avatar",
|
|
73
|
+
"session:set_nickname",
|
|
74
|
+
"session:set_origin",
|
|
75
|
+
"session:set_data",
|
|
76
|
+
"session:sync:pages",
|
|
77
|
+
"session:sync:events",
|
|
78
|
+
"session:sync:capabilities",
|
|
79
|
+
"session:sync:geolocation",
|
|
80
|
+
"session:sync:system",
|
|
81
|
+
"session:sync:network",
|
|
82
|
+
"session:sync:timezone",
|
|
83
|
+
"session:sync:locales",
|
|
84
|
+
"session:sync:rating",
|
|
85
|
+
"session:sync:topic",
|
|
86
|
+
"session:set_state",
|
|
87
|
+
"session:set_block",
|
|
88
|
+
"session:set_segments",
|
|
89
|
+
"session:set_opened",
|
|
90
|
+
"session:set_closed",
|
|
91
|
+
"session:set_participants",
|
|
92
|
+
"session:set_mentions",
|
|
93
|
+
"session:set_routing",
|
|
94
|
+
"session:set_inbox",
|
|
95
|
+
"session:removed",
|
|
96
|
+
"session:error",
|
|
97
|
+
// Message Events
|
|
98
|
+
"message:updated",
|
|
99
|
+
"message:send",
|
|
100
|
+
"message:received",
|
|
101
|
+
"message:removed",
|
|
102
|
+
"message:compose:send",
|
|
103
|
+
"message:compose:receive",
|
|
104
|
+
"message:acknowledge:read:send",
|
|
105
|
+
"message:acknowledge:read:received",
|
|
106
|
+
"message:acknowledge:unread:send",
|
|
107
|
+
"message:acknowledge:delivered",
|
|
108
|
+
"message:acknowledge:ignored",
|
|
109
|
+
"message:notify:unread:send",
|
|
110
|
+
"message:notify:unread:received",
|
|
111
|
+
// Spam Events
|
|
112
|
+
"spam:message",
|
|
113
|
+
"spam:decision",
|
|
114
|
+
// People Events
|
|
115
|
+
"people:profile:created",
|
|
116
|
+
"people:profile:updated",
|
|
117
|
+
"people:profile:removed",
|
|
118
|
+
"people:bind:session",
|
|
119
|
+
"people:sync:profile",
|
|
120
|
+
"people:import:progress",
|
|
121
|
+
"people:import:done",
|
|
122
|
+
// Campaign Events
|
|
123
|
+
"campaign:progress",
|
|
124
|
+
"campaign:dispatched",
|
|
125
|
+
"campaign:running",
|
|
126
|
+
// Browsing Events
|
|
127
|
+
"browsing:request:initiated",
|
|
128
|
+
"browsing:request:rejected",
|
|
129
|
+
// Call Events
|
|
130
|
+
"call:request:initiated",
|
|
131
|
+
"call:request:rejected",
|
|
132
|
+
// Identity Events
|
|
133
|
+
"identity:verify:request",
|
|
134
|
+
// Status Events
|
|
135
|
+
"status:health:changed",
|
|
136
|
+
// Website Event
|
|
137
|
+
"website:update_visitors_count",
|
|
138
|
+
"website:update_operators_availability",
|
|
139
|
+
"website:users:available",
|
|
140
|
+
// Bucket Events
|
|
141
|
+
"bucket:url:upload:generated",
|
|
142
|
+
"bucket:url:avatar:generated",
|
|
143
|
+
"bucket:url:website:generated",
|
|
144
|
+
"bucket:url:campaign:generated",
|
|
145
|
+
"bucket:url:helpdesk:generated",
|
|
146
|
+
"bucket:url:status:generated",
|
|
147
|
+
"bucket:url:processing:generated",
|
|
148
|
+
"bucket:url:crawler:generated",
|
|
149
|
+
// Media Events
|
|
150
|
+
"media:animation:listed",
|
|
151
|
+
// Email Event
|
|
152
|
+
"email:subscribe",
|
|
153
|
+
"email:track:view",
|
|
154
|
+
// Plugin Events
|
|
155
|
+
"plugin:channel",
|
|
156
|
+
"plugin:event",
|
|
157
|
+
"plugin:settings:saved"
|
|
158
|
+
];
|
|
159
|
+
// REST API services
|
|
160
|
+
const services = {
|
|
161
|
+
Bucket: bucket_1.default,
|
|
162
|
+
Media: media_1.default,
|
|
163
|
+
Plugin: plugin_1.default,
|
|
164
|
+
Website: website_1.default
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Crisp API Library
|
|
168
|
+
*/
|
|
169
|
+
class Crisp {
|
|
170
|
+
/**
|
|
171
|
+
* Constructor
|
|
172
|
+
*/
|
|
173
|
+
constructor() {
|
|
174
|
+
this.bucket = new bucket_1.default();
|
|
175
|
+
this.media = new media_1.default();
|
|
176
|
+
this.plugin = new plugin_1.default();
|
|
177
|
+
this.website = new website_1.default();
|
|
178
|
+
this.auth = {
|
|
179
|
+
tier: "user",
|
|
180
|
+
identifier: null,
|
|
181
|
+
key: null,
|
|
182
|
+
token: null
|
|
183
|
+
};
|
|
184
|
+
this._rest = {
|
|
185
|
+
host: DEFAULT_REST_HOST,
|
|
186
|
+
basePath: DEFAULT_REST_BASE_PATH
|
|
187
|
+
};
|
|
188
|
+
this._rtm = {
|
|
189
|
+
host: "",
|
|
190
|
+
mode: DEFAULT_RTM_MODE
|
|
191
|
+
};
|
|
192
|
+
this._useragent = (DEFAULT_USERAGENT_PREFIX + VERSION);
|
|
193
|
+
this._emitter = (0, mitt_1.default)();
|
|
194
|
+
this._socket = null;
|
|
195
|
+
this._loopback = null;
|
|
196
|
+
this._lastEventRebind = null;
|
|
197
|
+
this._brokerScheduler = null;
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-unused-vars
|
|
199
|
+
this._brokerBindHooks = [];
|
|
200
|
+
this._boundEvents = {};
|
|
201
|
+
this._prepareServices();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Sets the REST API host
|
|
205
|
+
*/
|
|
206
|
+
setRestHost(host) {
|
|
207
|
+
if (typeof host === "string") {
|
|
208
|
+
this._rest.host = host;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
throw new Error("[Crisp] setRestHost: parameter host should be a string");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Sets the RTM API host
|
|
216
|
+
*/
|
|
217
|
+
setRtmHost(host) {
|
|
218
|
+
if (typeof host === "string") {
|
|
219
|
+
this._rtm.host = host;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
throw new Error("[Crisp] setRtmHost: parameter host should be a string");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Sets the RTM channel mode (ie. WebSockets or Web Hooks)
|
|
227
|
+
*/
|
|
228
|
+
setRtmMode(mode) {
|
|
229
|
+
if (AVAILABLE_RTM_MODES.indexOf(mode) !== -1) {
|
|
230
|
+
this._rtm.mode = mode;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
throw new Error("[Crisp] setRtmMode: parameter mode value should be one of: " +
|
|
234
|
+
AVAILABLE_RTM_MODES.join(", "));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Sets the authentication tier
|
|
239
|
+
*/
|
|
240
|
+
setTier(tier) {
|
|
241
|
+
this.auth.tier = (tier || "user");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Authenticates
|
|
245
|
+
*/
|
|
246
|
+
authenticate(identifier, key) {
|
|
247
|
+
// Store credentials
|
|
248
|
+
this.auth.identifier = identifier;
|
|
249
|
+
this.auth.key = key;
|
|
250
|
+
// Assign pre-computed authentication token
|
|
251
|
+
this.auth.token = Buffer.from(identifier + ":" + key).toString("base64");
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Authenticates (with tier)
|
|
255
|
+
*/
|
|
256
|
+
authenticateTier(tier, identifier, key) {
|
|
257
|
+
this.setTier(tier);
|
|
258
|
+
this.authenticate(identifier, key);
|
|
259
|
+
}
|
|
260
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
261
|
+
/**
|
|
262
|
+
* Method wrapper to HEAD a resource
|
|
263
|
+
*/
|
|
264
|
+
head(resource, query) {
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
this.request(resource, "head", (query || {}), null, resolve, reject);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Method wrapper to GET a resource
|
|
271
|
+
*/
|
|
272
|
+
get(resource, query) {
|
|
273
|
+
return new Promise((resolve, reject) => {
|
|
274
|
+
this.request(resource, "get", (query || {}), null, resolve, reject);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Method wrapper to POST a resource
|
|
279
|
+
*/
|
|
280
|
+
post(resource, query, body) {
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
this.request(resource, "post", (query || {}), (body || {}), resolve, reject);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Method wrapper to PATCH a resource
|
|
287
|
+
*/
|
|
288
|
+
patch(resource, query, body) {
|
|
289
|
+
return new Promise((resolve, reject) => {
|
|
290
|
+
this.request(resource, "patch", (query || {}), (body || {}), resolve, reject);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Method wrapper to PUT a resource
|
|
295
|
+
*/
|
|
296
|
+
put(resource, query, body) {
|
|
297
|
+
return new Promise((resolve, reject) => {
|
|
298
|
+
this.request(resource, "put", (query || {}), (body || {}), resolve, reject);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Method wrapper to DELETE a resource
|
|
303
|
+
*/
|
|
304
|
+
delete(resource, query, body) {
|
|
305
|
+
return new Promise((resolve, reject) => {
|
|
306
|
+
this.request(resource, "delete", (query || {}), (body || null), resolve, reject);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
310
|
+
/**
|
|
311
|
+
* Binds RTM event
|
|
312
|
+
*/
|
|
313
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-unused-vars
|
|
314
|
+
on(event, callback) {
|
|
315
|
+
// Ensure all input arguments are set
|
|
316
|
+
if (typeof event !== "string") {
|
|
317
|
+
throw new Error("[Crisp] on: parameter event should be a string");
|
|
318
|
+
}
|
|
319
|
+
if (typeof callback !== "function") {
|
|
320
|
+
throw new Error("[Crisp] on: parameter callback should be a function");
|
|
321
|
+
}
|
|
322
|
+
// Disallow unrecognized event names
|
|
323
|
+
if (DEFAULT_RTM_EVENTS.indexOf(event) === -1) {
|
|
324
|
+
throw new Error("[Crisp] on: parameter event value is not recognized: '" + event + "'");
|
|
325
|
+
}
|
|
326
|
+
// Important: we do not allow .on() to be called once socket is connected, \
|
|
327
|
+
// or loopback is bound as we consider event listeners must be bound \
|
|
328
|
+
// once all together. This prevents bogous integrations from sending \
|
|
329
|
+
// flood of 'socket:bind'` to the RTM API, if using WebSockets. Web \
|
|
330
|
+
// Hooks follows the same scheme for consistency's sake.
|
|
331
|
+
if (this._socket || this._loopback) {
|
|
332
|
+
throw new Error("[Crisp] on: connector is already bound, please listen to event " +
|
|
333
|
+
"earlier on: '" + event + "'");
|
|
334
|
+
}
|
|
335
|
+
// Add listener to emitter
|
|
336
|
+
this._emitter.on(event, callback);
|
|
337
|
+
// Subscribe event on the broker
|
|
338
|
+
if (this._boundEvents[event] !== true) {
|
|
339
|
+
let rtmMode = this._rtm.mode;
|
|
340
|
+
// Mark event as bound
|
|
341
|
+
this._boundEvents[event] = true;
|
|
342
|
+
// Broker not connected? Connect now.
|
|
343
|
+
return this.prepareBroker((instance, emitter) => {
|
|
344
|
+
// Listen for event? (once instance is bound)
|
|
345
|
+
switch (rtmMode) {
|
|
346
|
+
case "websockets": {
|
|
347
|
+
// Listen on socket event
|
|
348
|
+
instance.on(event, (data) => {
|
|
349
|
+
emitter.emit(event, data);
|
|
350
|
+
});
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return Promise.resolve();
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Receives a raw event and dispatches it to the listener (used for Web Hooks)
|
|
360
|
+
*/
|
|
361
|
+
receiveHook(body) {
|
|
362
|
+
if (this._loopback) {
|
|
363
|
+
// Ensure payload is readable
|
|
364
|
+
if (!body || typeof body !== "object") {
|
|
365
|
+
return new Error("[Crisp] receiveHook: empty hook payload");
|
|
366
|
+
}
|
|
367
|
+
// Ensure payload is properly formatted
|
|
368
|
+
if (!body.event || !body.data ||
|
|
369
|
+
typeof body.event !== "string" || typeof body.data !== "object") {
|
|
370
|
+
return new Error("[Crisp] receiveHook: malformatted hook payload");
|
|
371
|
+
}
|
|
372
|
+
// Check if event is subscribed to? (in routing table)
|
|
373
|
+
// Notice: if not in routing table, then silently discard the event w/o \
|
|
374
|
+
// any error, as we do not want an HTTP failure status to be sent in \
|
|
375
|
+
// response by the implementor.
|
|
376
|
+
if (this._boundEvents[body.event] !== true) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
// Dispatch event to event bus
|
|
380
|
+
// Notice: go asynchronous, so that the event is processed ASAP and \
|
|
381
|
+
// dispatched on the event bus later, as the hook might be received \
|
|
382
|
+
// synchronously over HTTP.
|
|
383
|
+
process.nextTick(() => {
|
|
384
|
+
this._loopback.emit(body.event, body.data);
|
|
385
|
+
});
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
return new Error("[Crisp] receiveHook: hook loopback not bound");
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Verifies an event string and checks that signatures match (used for Web \
|
|
392
|
+
* Hooks)
|
|
393
|
+
*/
|
|
394
|
+
verifyHook(secret, body, timestamp, signature) {
|
|
395
|
+
if (this._loopback) {
|
|
396
|
+
return this.verifySignature(secret, body, timestamp, signature);
|
|
397
|
+
}
|
|
398
|
+
// Default: not verified (loopback not /yet?/ bound)
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Verifies an event string and checks that signatures match (used for \
|
|
403
|
+
* Widgets)
|
|
404
|
+
*/
|
|
405
|
+
verifyWidget(secret, body, timestamp, signature) {
|
|
406
|
+
return this.verifySignature(secret, body, timestamp, signature);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Rebinds socket events (used for WebSockets)
|
|
410
|
+
*/
|
|
411
|
+
rebindSocket() {
|
|
412
|
+
if (!this._socket) {
|
|
413
|
+
throw new Error("[Crisp] rebindSocket: cannot rebind a socket that is not yet bound");
|
|
414
|
+
}
|
|
415
|
+
// Make sure that the library user is not rebinding too frequently (which \
|
|
416
|
+
// is illegal)
|
|
417
|
+
const nowTime = Date.now();
|
|
418
|
+
if (this._lastEventRebind !== null &&
|
|
419
|
+
((nowTime - this._lastEventRebind) <
|
|
420
|
+
DEFAULT_EVENT_REBIND_INTERVAL_MIN)) {
|
|
421
|
+
throw new Error("[Crisp] rebindSocket: cannot rebind, last rebind was requested too " +
|
|
422
|
+
"recently");
|
|
423
|
+
}
|
|
424
|
+
return Promise.resolve()
|
|
425
|
+
.then(() => {
|
|
426
|
+
// Rebind to socket events (eg. newly bound websites)
|
|
427
|
+
this._lastEventRebind = nowTime;
|
|
428
|
+
this._socket.emit("socket:bind", {});
|
|
429
|
+
return Promise.resolve();
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Prepares a URI based from path segments
|
|
434
|
+
*/
|
|
435
|
+
prepareRestUrl(paths) {
|
|
436
|
+
if (Array.isArray(paths) === true) {
|
|
437
|
+
let output = this._rest.host + this._rest.basePath;
|
|
438
|
+
output += paths.join("/");
|
|
439
|
+
return output;
|
|
440
|
+
}
|
|
441
|
+
throw new Error("[Crisp] prepareRestUrl: parameter host should be an Array");
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Binds services to the main object
|
|
445
|
+
*/
|
|
446
|
+
_prepareServices() {
|
|
447
|
+
// Bind services
|
|
448
|
+
for (const name in services) {
|
|
449
|
+
const serviceInstance = new services[name]();
|
|
450
|
+
// Acquire service map
|
|
451
|
+
const serviceMap = this[(name[0].toLowerCase() + name.substring(1))];
|
|
452
|
+
// No resources defined in service?
|
|
453
|
+
if (!serviceInstance.__resources ||
|
|
454
|
+
serviceInstance.__resources.length === 0) {
|
|
455
|
+
throw new Error("[Crisp] prepareServices: service '" + name + "' has no resources " +
|
|
456
|
+
"defined");
|
|
457
|
+
}
|
|
458
|
+
// Prepare all resources (for service)
|
|
459
|
+
this.prepareResources(serviceMap, serviceInstance.__resources);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Binds resources to the service object
|
|
464
|
+
*/
|
|
465
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
466
|
+
prepareResources(serviceMap, resources) {
|
|
467
|
+
for (let i = 0; i < resources.length; i++) {
|
|
468
|
+
const resourceConstructor = resources[i];
|
|
469
|
+
const resourceInstance = new resourceConstructor(this);
|
|
470
|
+
// Bind each method of the resource instance to the service map
|
|
471
|
+
for (const methodName of Object.getOwnPropertyNames(Object.getPrototypeOf(resourceInstance))) {
|
|
472
|
+
if (methodName !== "constructor") {
|
|
473
|
+
serviceMap[methodName] = resourceInstance[methodName].bind(resourceInstance);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Binds broker to the main object
|
|
480
|
+
*/
|
|
481
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-unused-vars
|
|
482
|
+
prepareBroker(fnBindHook) {
|
|
483
|
+
return new Promise((resolve, reject) => {
|
|
484
|
+
const rtmMode = this._rtm.mode;
|
|
485
|
+
const rtmHostOverride = this._rtm.host;
|
|
486
|
+
// Append bind hook to pending stack
|
|
487
|
+
this._brokerBindHooks.push(fnBindHook);
|
|
488
|
+
// Make sure to prepare broker once? (defer broker binding, waiting that \
|
|
489
|
+
// all listeners have been bound, that way we submit the list of \
|
|
490
|
+
// filtered events to the RTM API once, and never again in the future)
|
|
491
|
+
if (this._brokerScheduler === null) {
|
|
492
|
+
// Socket or loopback already set? We should not even have entered \
|
|
493
|
+
// there.
|
|
494
|
+
if (this._socket || this._loopback) {
|
|
495
|
+
throw new Error("[Crisp] prepareBroker: illegal call to prepare broker (tie break)");
|
|
496
|
+
}
|
|
497
|
+
// @ts-ignore
|
|
498
|
+
this._brokerScheduler = setTimeout(() => {
|
|
499
|
+
switch (rtmMode) {
|
|
500
|
+
case "websockets": {
|
|
501
|
+
// Connect to socket now
|
|
502
|
+
// Notice: will unstack broker bind hooks once ready
|
|
503
|
+
this.connectSocket(rtmHostOverride)
|
|
504
|
+
.then(resolve)
|
|
505
|
+
.catch(reject);
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
case "webhooks": {
|
|
509
|
+
// Connect to loopback now
|
|
510
|
+
this.connectLoopback()
|
|
511
|
+
.then(resolve)
|
|
512
|
+
.catch(reject);
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
default: {
|
|
516
|
+
const unsupportedError = new Error("[Crisp] prepareBroker: mode of RTM broker unsupported " +
|
|
517
|
+
"('" + rtmMode + "')");
|
|
518
|
+
reject(unsupportedError);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}, DEFAULT_BROKER_SCHEDULE);
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
// Pass-through
|
|
525
|
+
resolve(true);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Connects loopback (used for Web Hooks)
|
|
531
|
+
*/
|
|
532
|
+
connectLoopback() {
|
|
533
|
+
return Promise.resolve()
|
|
534
|
+
.then(() => {
|
|
535
|
+
// Assign emitter to loopback
|
|
536
|
+
this._loopback = this._emitter;
|
|
537
|
+
// Unstack broker bind hooks immediately
|
|
538
|
+
this.unstackBrokerBindHooks(this._loopback);
|
|
539
|
+
return Promise.resolve();
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Connects socket, using preferred RTM API host (used for WebSockets)
|
|
544
|
+
*/
|
|
545
|
+
connectSocket(rtmHostOverride) {
|
|
546
|
+
return Promise.resolve()
|
|
547
|
+
.then(() => {
|
|
548
|
+
// Any override RTM API host?
|
|
549
|
+
if (rtmHostOverride) {
|
|
550
|
+
return Promise.resolve({
|
|
551
|
+
socket: {
|
|
552
|
+
app: rtmHostOverride
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
// Acquire RTM API URL from remote
|
|
557
|
+
let restUrlSegments;
|
|
558
|
+
switch (this.auth.tier) {
|
|
559
|
+
case "plugin": {
|
|
560
|
+
restUrlSegments = ["plugin", "connect", "endpoints"];
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
default: {
|
|
564
|
+
restUrlSegments = ["user", "connect", "endpoints"];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return this.get(this.prepareRestUrl(restUrlSegments))
|
|
568
|
+
.catch(() => {
|
|
569
|
+
// Void error (consider as empty response)
|
|
570
|
+
return Promise.resolve({});
|
|
571
|
+
});
|
|
572
|
+
})
|
|
573
|
+
.then((endpoints) => {
|
|
574
|
+
var _a, _b, _c;
|
|
575
|
+
// @ts-ignore
|
|
576
|
+
const rtmHostAffinity = (((_a = endpoints === null || endpoints === void 0 ? void 0 : endpoints.socket) === null || _a === void 0 ? void 0 : _a.app) || null);
|
|
577
|
+
// No RTM API host acquired?
|
|
578
|
+
if (rtmHostAffinity === null) {
|
|
579
|
+
throw new Error("[Crisp] connectSocket: could not acquire target host to " +
|
|
580
|
+
"connect to, is your session valid for tier?");
|
|
581
|
+
}
|
|
582
|
+
// Parse target RTM API host as an URL object
|
|
583
|
+
const rtmHostUrl = new url_1.URL(rtmHostAffinity);
|
|
584
|
+
// Connect to socket
|
|
585
|
+
// @ts-ignore
|
|
586
|
+
this._socket = (0, socket_io_client_1.io)(rtmHostUrl.origin, {
|
|
587
|
+
path: (rtmHostUrl.pathname || "/"),
|
|
588
|
+
transports: ["websocket"],
|
|
589
|
+
timeout: DEFAULT_SOCKET_TIMEOUT,
|
|
590
|
+
reconnection: true,
|
|
591
|
+
reconnectionDelay: DEFAULT_SOCKET_RECONNECT_DELAY,
|
|
592
|
+
reconnectionDelayMax: DEFAULT_SOCKET_RECONNECT_DELAY_MAX,
|
|
593
|
+
randomizationFactor: DEFAULT_SOCKET_RECONNECT_FACTOR
|
|
594
|
+
});
|
|
595
|
+
this.emitAuthenticateSocket();
|
|
596
|
+
// Setup base socket event listeners
|
|
597
|
+
(_b = this._socket) === null || _b === void 0 ? void 0 : _b.io.on("reconnect", () => {
|
|
598
|
+
this.emitAuthenticateSocket();
|
|
599
|
+
});
|
|
600
|
+
(_c = this._socket) === null || _c === void 0 ? void 0 : _c.on("unauthorized", () => {
|
|
601
|
+
throw new Error("[Crisp] connectSocket: cannot listen for events as " +
|
|
602
|
+
"authentication is invalid");
|
|
603
|
+
});
|
|
604
|
+
// Setup user socket event listeners
|
|
605
|
+
this.unstackBrokerBindHooks(this._socket);
|
|
606
|
+
return Promise.resolve();
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Authenticates client (used for WebSockets)
|
|
611
|
+
*/
|
|
612
|
+
emitAuthenticateSocket() {
|
|
613
|
+
const auth = this.auth;
|
|
614
|
+
const boundEvents = Object.keys(this._boundEvents);
|
|
615
|
+
if (!this._socket) {
|
|
616
|
+
throw new Error("[Crisp] emitAuthenticateSocket: cannot listen for events as socket " +
|
|
617
|
+
"is not yet bound");
|
|
618
|
+
}
|
|
619
|
+
if (!auth.identifier || !auth.key) {
|
|
620
|
+
throw new Error("[Crisp] emitAuthenticateSocket: cannot listen for events as you " +
|
|
621
|
+
"did not authenticate");
|
|
622
|
+
}
|
|
623
|
+
if (boundEvents.length === 0) {
|
|
624
|
+
throw new Error("[Crisp] emitAuthenticateSocket: cannot listen for events as no " +
|
|
625
|
+
"event is being listened to");
|
|
626
|
+
}
|
|
627
|
+
this._socket.emit("authentication", {
|
|
628
|
+
username: auth.identifier,
|
|
629
|
+
password: auth.key,
|
|
630
|
+
tier: auth.tier,
|
|
631
|
+
events: boundEvents
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Unstacks pending broker bind hooks
|
|
636
|
+
*/
|
|
637
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
638
|
+
unstackBrokerBindHooks(modeInstance) {
|
|
639
|
+
var _a;
|
|
640
|
+
// Setup user socket event listeners
|
|
641
|
+
while (this._brokerBindHooks.length > 0) {
|
|
642
|
+
(_a = this._brokerBindHooks.shift()) === null || _a === void 0 ? void 0 : _a(modeInstance, this._emitter);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Performs a request to REST API
|
|
647
|
+
*/
|
|
648
|
+
request(resource, method, query, body,
|
|
649
|
+
// eslint-disable-next-line no-unused-vars
|
|
650
|
+
resolve,
|
|
651
|
+
// eslint-disable-next-line no-unused-vars
|
|
652
|
+
reject) {
|
|
653
|
+
let requestParameters = {
|
|
654
|
+
responseType: "json",
|
|
655
|
+
timeout: DEFAULT_REQUEST_TIMEOUT,
|
|
656
|
+
headers: {
|
|
657
|
+
"User-Agent": this._useragent,
|
|
658
|
+
"X-Crisp-Tier": this.auth.tier
|
|
659
|
+
},
|
|
660
|
+
throwHttpErrors: false
|
|
661
|
+
};
|
|
662
|
+
// Add authorization?
|
|
663
|
+
if (this.auth.token) {
|
|
664
|
+
// @ts-ignore
|
|
665
|
+
requestParameters.headers.Authorization = ("Basic " + this.auth.token);
|
|
666
|
+
}
|
|
667
|
+
// Add body?
|
|
668
|
+
if (body) {
|
|
669
|
+
// @ts-ignore
|
|
670
|
+
requestParameters.json = body;
|
|
671
|
+
}
|
|
672
|
+
// Add query?
|
|
673
|
+
if (query) {
|
|
674
|
+
// @ts-ignore
|
|
675
|
+
requestParameters.searchParams = query;
|
|
676
|
+
}
|
|
677
|
+
// Proceed request
|
|
678
|
+
got_1.default[method](resource, requestParameters)
|
|
679
|
+
.catch((error) => {
|
|
680
|
+
return Promise.resolve(error);
|
|
681
|
+
})
|
|
682
|
+
.then((response) => {
|
|
683
|
+
var _a, _b, _c;
|
|
684
|
+
// Request error?
|
|
685
|
+
if (!response.statusCode) {
|
|
686
|
+
return reject({
|
|
687
|
+
reason: "error",
|
|
688
|
+
message: "internal_error",
|
|
689
|
+
code: 500,
|
|
690
|
+
data: {
|
|
691
|
+
namespace: "request",
|
|
692
|
+
message: ("Got request error: " + (response.name || "Unknown"))
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
// Response error?
|
|
697
|
+
if (response.statusCode >= 400) {
|
|
698
|
+
let reasonMessage = this.readErrorResponseReason(method, response.statusCode, response);
|
|
699
|
+
const dataMessage = (((_b = (_a = response === null || response === void 0 ? void 0 : response.body) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) || "");
|
|
700
|
+
return reject({
|
|
701
|
+
reason: "error",
|
|
702
|
+
message: reasonMessage,
|
|
703
|
+
code: response.statusCode,
|
|
704
|
+
data: {
|
|
705
|
+
namespace: "response",
|
|
706
|
+
message: ("Got response error: " + (dataMessage || reasonMessage))
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
// Regular response
|
|
711
|
+
return resolve((((_c = response === null || response === void 0 ? void 0 : response.body) === null || _c === void 0 ? void 0 : _c.data) || {}));
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Reads reason for error response
|
|
716
|
+
*/
|
|
717
|
+
readErrorResponseReason(method, statusCode, response) {
|
|
718
|
+
var _a;
|
|
719
|
+
// HEAD method? As HEAD requests do not expect any response body, then we \
|
|
720
|
+
// cannot map a reason from the response.
|
|
721
|
+
if (method === "head") {
|
|
722
|
+
// 5xx errors?
|
|
723
|
+
if (statusCode >= 500) {
|
|
724
|
+
return "server_error";
|
|
725
|
+
}
|
|
726
|
+
// 4xx errors?
|
|
727
|
+
if (statusCode >= 400) {
|
|
728
|
+
return "route_error";
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// Other methods must hold a response body, therefore we can fallback on \
|
|
732
|
+
// an HTTP error if we fail to acquire any reason at all.
|
|
733
|
+
// @ts-ignore
|
|
734
|
+
return ((((_a = response === null || response === void 0 ? void 0 : response.body) === null || _a === void 0 ? void 0 : _a.reason) || "http_error"));
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Verifies an event string and checks that signatures match
|
|
738
|
+
*/
|
|
739
|
+
verifySignature(secret, body, timestamp, signature) {
|
|
740
|
+
// Ensure all provided data is valid
|
|
741
|
+
if (!secret || !signature || !body || typeof body !== "object" ||
|
|
742
|
+
!timestamp || isNaN(timestamp) === true) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
// Compute local trace
|
|
746
|
+
let localTrace = ("[" + timestamp + ";" + JSON.stringify(body) + "]");
|
|
747
|
+
// Create local HMAC
|
|
748
|
+
let localMac = crypto_1.default.createHmac("sha256", secret);
|
|
749
|
+
localMac.update(localTrace);
|
|
750
|
+
// Compute local signature, and compare
|
|
751
|
+
let localSignature = localMac.digest("hex");
|
|
752
|
+
return ((signature === localSignature) ? true : false);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
exports.Crisp = Crisp;
|
|
756
|
+
/**
|
|
757
|
+
* @deprecated Use import { RTM_MODES } instead
|
|
758
|
+
*/
|
|
759
|
+
Crisp.RTM_MODES = {
|
|
760
|
+
WebSockets: "websockets",
|
|
761
|
+
WebHooks: "webhooks"
|
|
762
|
+
};
|
|
763
|
+
;
|
|
764
|
+
exports.default = Crisp;
|