1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const axios_1 = require("axios");
|
4 | const async_1 = require("async");
|
5 | const Promise = require("bluebird");
|
6 | const http_1 = require("http");
|
7 | const https_1 = require("https");
|
8 | const lru_cache_1 = require("lru-cache");
|
9 | const moment_1 = require("moment");
|
10 | const socket_io_client_1 = require("socket.io-client");
|
11 | const stream_1 = require("stream");
|
12 | const winston_1 = require("winston");
|
13 | const util_1 = require("./util");
|
14 | function defer() {
|
15 | let resolve;
|
16 | let reject;
|
17 | const promise = new Promise((promiseResolve, promiseReject) => {
|
18 | resolve = promiseResolve;
|
19 | reject = promiseReject;
|
20 | });
|
21 | return {
|
22 |
|
23 | resolve,
|
24 |
|
25 | reject,
|
26 | promise
|
27 | };
|
28 | }
|
29 | class CommunibaseError extends Error {
|
30 | constructor(data, task) {
|
31 | super(data.message);
|
32 | this.name = "CommunibaseError";
|
33 | this.code = data.code || 500;
|
34 | this.message = data.message || "";
|
35 | this.errors = data.errors || {};
|
36 |
|
37 | }
|
38 | }
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | class Connector {
|
47 | constructor(key) {
|
48 | this.getByIdQueue = {};
|
49 | this.getByIdPrimed = false;
|
50 | this.key = key;
|
51 | this.token = "";
|
52 | this.serviceUrl =
|
53 | process.env.COMMUNIBASE_API_URL || "https://api.communibase.nl/0.1/";
|
54 | this.serviceUrlIsHttps = this.serviceUrl.indexOf("https") === 0;
|
55 | this.queue = async_1.default.queue((task, callback) => {
|
56 | function fail(error) {
|
57 | if (error instanceof Error) {
|
58 | task.deferred.reject(error);
|
59 | }
|
60 | else {
|
61 | task.deferred.reject(new CommunibaseError(error, task));
|
62 | }
|
63 | callback();
|
64 | return;
|
65 | }
|
66 | if (!this.key && !this.token) {
|
67 | fail(new Error("Missing key or token for Communibase Connector: please set COMMUNIBASE_KEY environment" +
|
68 | " variable, or spawn a new instance using require('communibase-connector-js').clone('<" +
|
69 | "your api key>')"));
|
70 | return;
|
71 | }
|
72 | if (!task.options.headers) {
|
73 | task.options.headers = {
|
74 | Accept: "application/json",
|
75 | "Content-Type": "application/json"
|
76 | };
|
77 | }
|
78 | if (process.env.COMMUNIBASE_API_HOST) {
|
79 | task.options.headers.Host = process.env.COMMUNIBASE_API_HOST;
|
80 | }
|
81 | if (this.key) {
|
82 | task.options.headers["x-api-key"] = this.key;
|
83 | }
|
84 | if (this.token) {
|
85 | task.options.headers["x-access-token"] = this.token;
|
86 | }
|
87 | axios_1.default
|
88 | .request(Object.assign({ url: task.url }, task.options))
|
89 | .then(result => {
|
90 | const { deferred } = task;
|
91 | let records = result.data;
|
92 | if (records.metadata && records.records) {
|
93 | deferred.promise.metadata = records.metadata;
|
94 |
|
95 | records = records.records;
|
96 | }
|
97 | deferred.resolve(records);
|
98 | callback();
|
99 | return null;
|
100 | })
|
101 | .catch((err) => {
|
102 | var _a;
|
103 | fail(((_a = err.response) === null || _a === void 0 ? void 0 : _a.data) || err);
|
104 | });
|
105 | }, 8);
|
106 | }
|
107 | setServiceUrl(newServiceUrl) {
|
108 | if (!newServiceUrl) {
|
109 | throw new Error("Cannot set empty service-url");
|
110 | }
|
111 | this.serviceUrl = newServiceUrl;
|
112 | this.serviceUrlIsHttps = newServiceUrl.indexOf("https") === 0;
|
113 | }
|
114 | |
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | getById(objectType, objectId, params, versionId) {
|
124 | if (typeof objectId !== "string" || objectId.length !== 24) {
|
125 | return Promise.reject(new Error("Invalid objectId"));
|
126 | }
|
127 |
|
128 | if (versionId || (params && params.fields)) {
|
129 | const deferred = defer();
|
130 | this.queue.push({
|
131 | deferred,
|
132 | url: `${this.serviceUrl + objectType}.json/${versionId ? `history/${objectId}/${versionId}` : `crud/${objectId}`}`,
|
133 | options: {
|
134 | method: "GET",
|
135 | params
|
136 | }
|
137 | });
|
138 | return deferred.promise;
|
139 | }
|
140 |
|
141 | if (this.cache && this.cache.isAvailable(objectType, objectId)) {
|
142 | return this.cache.objectCache[objectType][objectId];
|
143 | }
|
144 |
|
145 | if (this.getByIdQueue[objectType] === undefined) {
|
146 | this.getByIdQueue[objectType] = {};
|
147 | }
|
148 | if (this.getByIdQueue[objectType][objectId]) {
|
149 |
|
150 | return this.getByIdQueue[objectType][objectId].promise;
|
151 | }
|
152 | this.getByIdQueue[objectType][objectId] = defer();
|
153 | if (this.cache) {
|
154 | if (this.cache.objectCache[objectType] === undefined) {
|
155 | this.cache.objectCache[objectType] = {};
|
156 | }
|
157 | this.cache.objectCache[objectType][objectId] = this.getByIdQueue[objectType][objectId].promise;
|
158 | }
|
159 | if (!this.getByIdPrimed) {
|
160 | process.nextTick(() => {
|
161 | this.spoolQueue();
|
162 | });
|
163 | this.getByIdPrimed = true;
|
164 | }
|
165 | return this.getByIdQueue[objectType][objectId].promise;
|
166 | }
|
167 | |
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 | getByIds(objectType, objectIds, params) {
|
177 | if (objectIds.length === 0) {
|
178 | return Promise.resolve([]);
|
179 | }
|
180 |
|
181 | if (params && params.fields) {
|
182 | return this.privateGetByIds(objectType, objectIds, params);
|
183 | }
|
184 | return Promise.map(objectIds, (objectId) => this.getById(objectType, objectId, params).reflect()).then(inspections => {
|
185 | const result = [];
|
186 | let error = null;
|
187 | inspections.forEach(inspection => {
|
188 | if (inspection.isRejected()) {
|
189 | error = inspection.reason();
|
190 | return;
|
191 | }
|
192 | result.push(inspection.value());
|
193 | });
|
194 | if (result.length) {
|
195 | return result;
|
196 | }
|
197 | if (error) {
|
198 | throw new Error(error);
|
199 | }
|
200 |
|
201 | return result;
|
202 | });
|
203 | }
|
204 | |
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | getAll(objectType, params) {
|
212 | if (this.cache && !(params && params.fields)) {
|
213 | return this.search(objectType, {}, params);
|
214 | }
|
215 | const deferred = defer();
|
216 | this.queue.push({
|
217 | deferred,
|
218 | url: `${this.serviceUrl + objectType}.json/crud`,
|
219 | options: {
|
220 | method: "GET",
|
221 | params
|
222 | }
|
223 | });
|
224 | return deferred.promise;
|
225 | }
|
226 | |
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 | getIds(objectType, selector, params) {
|
235 | let hash;
|
236 | let result;
|
237 | if (this.cache) {
|
238 | hash = JSON.stringify([objectType, selector, params]);
|
239 | if (!this.cache.getIdsCaches[objectType]) {
|
240 | this.cache.getIdsCaches[objectType] = new lru_cache_1.default(1000);
|
241 | }
|
242 | const objectTypeGetIdCache = this.cache.getIdsCaches[objectType];
|
243 | result = objectTypeGetIdCache ? objectTypeGetIdCache.get(hash) : null;
|
244 | if (result) {
|
245 | return Promise.resolve(result);
|
246 | }
|
247 | }
|
248 | const resultPromise = this.search(objectType, selector || {}, Object.assign({ fields: "_id" }, params)).then(results => results.map(obj => obj._id));
|
249 | if (this.cache) {
|
250 | return resultPromise.then(ids => {
|
251 | if (this.cache) {
|
252 | const objectTypeGetIdCache = this.cache.getIdsCaches[objectType];
|
253 | if (objectTypeGetIdCache) {
|
254 | objectTypeGetIdCache.set(hash, ids);
|
255 | }
|
256 | }
|
257 | return ids;
|
258 | });
|
259 | }
|
260 | return resultPromise;
|
261 | }
|
262 | |
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 | getId(objectType, selector) {
|
270 | return this.getIds(objectType, selector, { limit: 1 }).then(ids => ids.pop() || null);
|
271 | }
|
272 | |
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | search(objectType, selector, params) {
|
280 | if (this.cache && !(params && params.fields)) {
|
281 | return this.getIds(objectType, selector, params).then(ids => this.getByIds(objectType, ids));
|
282 | }
|
283 | if (selector &&
|
284 | typeof selector === "object" &&
|
285 | Object.keys(selector).length) {
|
286 | return this.queueSearch(objectType, selector, params);
|
287 | }
|
288 | return this.getAll(objectType, params);
|
289 | }
|
290 | |
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 | update(objectType, object) {
|
298 | const deferred = defer();
|
299 | const operation = object._id && object._id.length > 0 ? "PUT" : "POST";
|
300 | if (object._id &&
|
301 | this.cache &&
|
302 | this.cache.objectCache &&
|
303 | this.cache.objectCache[objectType] &&
|
304 | this.cache.objectCache[objectType][object._id]) {
|
305 | this.cache.objectCache[objectType][object._id] = null;
|
306 | }
|
307 | this.queue.push({
|
308 | deferred,
|
309 | url: `${this.serviceUrl + objectType}.json/crud${operation === "PUT" ? `/${object._id}` : ""}`,
|
310 | options: {
|
311 | method: operation,
|
312 | data: object
|
313 | }
|
314 | });
|
315 | return deferred.promise;
|
316 | }
|
317 | |
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 | destroy(objectType, objectId) {
|
325 | const deferred = defer();
|
326 | if (this.cache &&
|
327 | this.cache.objectCache &&
|
328 | this.cache.objectCache[objectType] &&
|
329 | this.cache.objectCache[objectType][objectId]) {
|
330 | this.cache.objectCache[objectType][objectId] = null;
|
331 | }
|
332 | this.queue.push({
|
333 | deferred,
|
334 | url: `${this.serviceUrl + objectType}.json/crud/${objectId}`,
|
335 | options: {
|
336 | method: "DELETE"
|
337 | }
|
338 | });
|
339 | return deferred.promise;
|
340 | }
|
341 | |
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 | undelete(objectType, objectId) {
|
349 | const deferred = defer();
|
350 | this.queue.push({
|
351 | deferred,
|
352 | url: `${this.serviceUrl + objectType}.json/history/undelete/${objectId}`,
|
353 | options: {
|
354 | method: "POST"
|
355 | }
|
356 | });
|
357 | return deferred.promise;
|
358 | }
|
359 | |
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 | createReadStream(fileId) {
|
366 | const request = this.serviceUrlIsHttps ? https_1.request : http_1.request;
|
367 | const fileStream = new stream_1.PassThrough();
|
368 | const req = request(`${this.serviceUrl}File.json/binary/${fileId}?api_key=${this.key}`, (res) => {
|
369 | if (res.statusCode === 200) {
|
370 | res.pipe(fileStream);
|
371 | return;
|
372 | }
|
373 | fileStream.emit("error", new Error(http_1.STATUS_CODES[res.statusCode || 500]));
|
374 | fileStream.emit("end");
|
375 | });
|
376 | if (process.env.COMMUNIBASE_API_HOST) {
|
377 | req.setHeader("Host", process.env.COMMUNIBASE_API_HOST);
|
378 | }
|
379 | req.end();
|
380 | req.on("error", (err) => {
|
381 | fileStream.emit("error", err);
|
382 | });
|
383 | return fileStream;
|
384 | }
|
385 | |
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 |
|
397 | updateBinary(resource, name, destinationPath, id) {
|
398 | const metaData = {
|
399 | path: destinationPath
|
400 | };
|
401 | return util_1.getResourceBufferPromise(resource).then((buffer) => {
|
402 | if (id) {
|
403 |
|
404 |
|
405 | return this.update("File", {
|
406 | _id: id,
|
407 | filename: name,
|
408 | length: buffer.length,
|
409 | uploadDate: moment_1.default().format(),
|
410 | metadata: metaData,
|
411 | content: buffer
|
412 | });
|
413 | }
|
414 |
|
415 | const deferred = defer();
|
416 | const formData = new FormData();
|
417 | let stringOrBlob = buffer;
|
418 |
|
419 |
|
420 |
|
421 | if (typeof window !== "undefined" && window.Blob) {
|
422 | stringOrBlob = new Blob([buffer]);
|
423 | }
|
424 | formData.append("File", stringOrBlob, name);
|
425 | formData.append("metadata", JSON.stringify(metaData));
|
426 | this.queue.push({
|
427 | deferred,
|
428 | url: `${this.serviceUrl}File.json/binary`,
|
429 | options: {
|
430 | method: "POST",
|
431 | data: formData,
|
432 | headers: {
|
433 | Accept: "application/json"
|
434 | }
|
435 | }
|
436 | });
|
437 | return deferred.promise;
|
438 | });
|
439 | }
|
440 | |
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 | clone(apiKey) {
|
447 | return new Connector(apiKey);
|
448 | }
|
449 | |
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 | getHistory(objectType, objectId) {
|
463 | const deferred = defer();
|
464 | this.queue.push({
|
465 | deferred,
|
466 | url: `${this.serviceUrl + objectType}.json/history/${objectId}`,
|
467 | options: {
|
468 | method: "GET"
|
469 | }
|
470 | });
|
471 | return deferred.promise;
|
472 | }
|
473 | |
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 | historySearch(objectType, selector) {
|
480 | const deferred = defer();
|
481 | this.queue.push({
|
482 | deferred,
|
483 | url: `${this.serviceUrl + objectType}.json/history/search`,
|
484 | options: {
|
485 | method: "POST",
|
486 | data: selector
|
487 | }
|
488 | });
|
489 | return deferred.promise;
|
490 | }
|
491 | |
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 |
|
501 |
|
502 |
|
503 |
|
504 |
|
505 |
|
506 |
|
507 |
|
508 | getByRef(ref, parentDocument) {
|
509 | if (!(ref &&
|
510 | ref.rootDocumentEntityType &&
|
511 | (ref.rootDocumentId || parentDocument))) {
|
512 | return Promise.reject(new Error("Please provide a documentReference object with a type and id"));
|
513 | }
|
514 | const rootDocumentEntityTypeParts = ref.rootDocumentEntityType.split(".");
|
515 | let parentDocumentPromise;
|
516 | if (rootDocumentEntityTypeParts[0] !== "parent") {
|
517 | parentDocumentPromise = this.getById(ref.rootDocumentEntityType, ref.rootDocumentId);
|
518 | }
|
519 | else {
|
520 | parentDocumentPromise = Promise.resolve(parentDocument);
|
521 | }
|
522 | if (!(ref.path && ref.path.length && ref.path.length > 0)) {
|
523 | return parentDocumentPromise;
|
524 | }
|
525 |
|
526 | return parentDocumentPromise.then((result) => {
|
527 | ref.path.some(pathNibble => {
|
528 | if (result && result[pathNibble.field]) {
|
529 | if (!result[pathNibble.field].some((subDocument) => {
|
530 | if (subDocument._id === pathNibble.objectId) {
|
531 | result = subDocument;
|
532 | return true;
|
533 | }
|
534 | return false;
|
535 | })) {
|
536 | result = null;
|
537 | return true;
|
538 | }
|
539 | return false;
|
540 | }
|
541 | result = null;
|
542 | return true;
|
543 | });
|
544 | if (result) {
|
545 | return result;
|
546 | }
|
547 | throw new Error("The referred object within it's parent could not be found");
|
548 | });
|
549 |
|
550 | }
|
551 | |
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 | aggregate(objectType, aggregationPipeline) {
|
564 | if (!aggregationPipeline || !aggregationPipeline.length) {
|
565 | return Promise.reject(new Error("Please provide a valid Aggregation Pipeline."));
|
566 | }
|
567 | let hash;
|
568 | if (this.cache) {
|
569 | hash = JSON.stringify([objectType, aggregationPipeline]);
|
570 | let objectTypeAggregateCache = this.cache.aggregateCaches[objectType];
|
571 | if (!objectTypeAggregateCache) {
|
572 | objectTypeAggregateCache = lru_cache_1.default(1000);
|
573 | this.cache.aggregateCaches[objectType] = objectTypeAggregateCache;
|
574 | }
|
575 | const result = objectTypeAggregateCache.get(hash);
|
576 | if (result) {
|
577 | return Promise.resolve(result);
|
578 | }
|
579 | }
|
580 | const deferred = defer();
|
581 | this.queue.push({
|
582 | deferred,
|
583 | url: `${this.serviceUrl + objectType}.json/aggregate`,
|
584 | options: {
|
585 | method: "POST",
|
586 | data: aggregationPipeline
|
587 | }
|
588 | });
|
589 | const resultPromise = deferred.promise;
|
590 | if (this.cache) {
|
591 | return resultPromise.then(result => {
|
592 | if (this.cache) {
|
593 | const objectTypeAggregateCache = this.cache.aggregateCaches[objectType];
|
594 | if (objectTypeAggregateCache) {
|
595 | objectTypeAggregateCache.set(hash, result);
|
596 | }
|
597 | }
|
598 | return result;
|
599 | });
|
600 | }
|
601 | return resultPromise;
|
602 | }
|
603 | |
604 |
|
605 |
|
606 |
|
607 |
|
608 |
|
609 | finalizeInvoice(invoiceId) {
|
610 | const deferred = defer();
|
611 | this.queue.push({
|
612 | deferred,
|
613 | url: `${this.serviceUrl}Invoice.json/finalize/${invoiceId}`,
|
614 | options: {
|
615 | method: "POST"
|
616 | }
|
617 | });
|
618 | return deferred.promise;
|
619 | }
|
620 | |
621 |
|
622 |
|
623 |
|
624 | enableCache(communibaseAdministrationId, socketServiceUrl) {
|
625 | this.cache = {
|
626 | getIdsCaches: {},
|
627 | aggregateCaches: {},
|
628 | dirtySock: socket_io_client_1.default.connect(socketServiceUrl, { port: "443" }),
|
629 | objectCache: {},
|
630 | isAvailable(objectType, objectId) {
|
631 | if (!cache) {
|
632 | return false;
|
633 | }
|
634 | return !!(cache.objectCache[objectType] &&
|
635 | cache.objectCache[objectType][objectId]);
|
636 | }
|
637 | };
|
638 | const { cache } = this;
|
639 | cache.dirtySock.on("connect", () => {
|
640 | cache.dirtySock.emit("join", `${communibaseAdministrationId}_dirty`);
|
641 | });
|
642 | cache.dirtySock.on("message", (dirtyness) => {
|
643 | const dirtyInfo = dirtyness.split("|");
|
644 | if (dirtyInfo.length !== 2) {
|
645 | winston_1.default.warn(`${new Date()}: Got weird dirty sock data? ${dirtyness}`);
|
646 | return;
|
647 | }
|
648 | cache.getIdsCaches[dirtyInfo[0]] = null;
|
649 | cache.aggregateCaches[dirtyInfo[0]] = null;
|
650 | if (dirtyInfo.length === 2 && cache.objectCache[dirtyInfo[0]]) {
|
651 | cache.objectCache[dirtyInfo[0]][dirtyInfo[1]] = null;
|
652 | }
|
653 | });
|
654 | }
|
655 | queueSearch(objectType, selector, params) {
|
656 | const deferred = defer();
|
657 | this.queue.push({
|
658 | deferred,
|
659 | url: `${this.serviceUrl + objectType}.json/search`,
|
660 | options: {
|
661 | method: "POST",
|
662 | data: selector,
|
663 | params
|
664 | }
|
665 | });
|
666 | return deferred.promise;
|
667 | }
|
668 | |
669 |
|
670 |
|
671 |
|
672 | privateGetByIds(objectType, objectIds, params) {
|
673 | return this.queueSearch(objectType, {
|
674 | _id: { $in: objectIds }
|
675 | }, params);
|
676 | }
|
677 | |
678 |
|
679 |
|
680 | spoolQueue() {
|
681 | Object.keys(this.getByIdQueue).forEach(objectType => {
|
682 | const deferredsById = this.getByIdQueue[objectType];
|
683 | const objectIds = Object.keys(deferredsById);
|
684 | this.getByIdQueue[objectType] = {};
|
685 | this.privateGetByIds(objectType, objectIds).then(objects => {
|
686 | const objectHash = objects.reduce((previousValue, object) => {
|
687 | if (object._id) {
|
688 | previousValue[object._id] = object;
|
689 | }
|
690 | return previousValue;
|
691 | }, {});
|
692 | objectIds.forEach((objectId) => {
|
693 | if (objectHash[objectId]) {
|
694 | deferredsById[objectId].resolve(objectHash[objectId]);
|
695 | return;
|
696 | }
|
697 | deferredsById[objectId].reject(new Error(`${objectId} is not found`));
|
698 | });
|
699 | }, err => {
|
700 | objectIds.forEach(objectId => {
|
701 | deferredsById[objectId].reject(err);
|
702 | });
|
703 | });
|
704 | });
|
705 | this.getByIdPrimed = false;
|
706 | }
|
707 | }
|
708 | exports.Connector = Connector;
|
709 |
|
710 | exports.default = new Connector(process.env.COMMUNIBASE_KEY);
|
711 |
|
\ | No newline at end of file |