outgoing.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. const postCatchError = Meteor.wrapAsync((url, options, resolve) => {
  2. HTTP.post(url, options, (err, res) => {
  3. if (err) {
  4. resolve(null, err.response);
  5. } else {
  6. resolve(null, res);
  7. }
  8. });
  9. });
  10. const Lock = {
  11. _lock: {},
  12. _timer: {},
  13. echoDelay: 500, // echo should be happening much faster
  14. normalDelay: 1e3, // normally user typed comment will be much slower
  15. ECHO: 2,
  16. NORMAL: 1,
  17. NULL: 0,
  18. has(id, value) {
  19. const existing = this._lock[id];
  20. let ret = this.NULL;
  21. if (existing) {
  22. ret = existing === value ? this.ECHO : this.NORMAL;
  23. }
  24. return ret;
  25. },
  26. clear(id, delay) {
  27. const previous = this._timer[id];
  28. if (previous) {
  29. Meteor.clearTimeout(previous);
  30. }
  31. this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay);
  32. },
  33. set(id, value) {
  34. const state = this.has(id, value);
  35. let delay = this.normalDelay;
  36. if (state === this.ECHO) {
  37. delay = this.echoDelay;
  38. }
  39. if (!value) {
  40. // user commented, we set a lock
  41. value = 1;
  42. }
  43. this._lock[id] = value;
  44. this.clear(id, delay); // always auto reset the locker after delay
  45. },
  46. unset(id) {
  47. delete this._lock[id];
  48. },
  49. };
  50. const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES &&
  51. process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [
  52. 'cardId',
  53. 'listId',
  54. 'oldListId',
  55. 'boardId',
  56. 'comment',
  57. 'user',
  58. 'card',
  59. 'commentId',
  60. 'swimlaneId',
  61. ];
  62. const responseFunc = data => {
  63. const paramCommentId = data.commentId;
  64. const paramCardId = data.cardId;
  65. const paramBoardId = data.boardId;
  66. const newComment = data.comment;
  67. if (paramCardId && paramBoardId && newComment) {
  68. // only process data with the cardid, boardid and comment text, TODO can expand other functions here to react on returned data
  69. const comment = CardComments.findOne({
  70. _id: paramCommentId,
  71. cardId: paramCardId,
  72. boardId: paramBoardId,
  73. });
  74. const board = Boards.findOne(paramBoardId);
  75. const card = Cards.findOne(paramCardId);
  76. if (board && card) {
  77. if (comment) {
  78. Lock.set(comment._id, newComment);
  79. CardComments.direct.update(comment._id, {
  80. $set: {
  81. text: newComment,
  82. },
  83. });
  84. }
  85. } else {
  86. const userId = data.userId;
  87. if (userId) {
  88. const inserted = CardComments.direct.insert({
  89. text: newComment,
  90. userId,
  91. cardId,
  92. boardId,
  93. });
  94. Lock.set(inserted._id, newComment);
  95. }
  96. }
  97. }
  98. };
  99. Meteor.methods({
  100. outgoingWebhooks(integration, description, params) {
  101. check(integration, Object);
  102. check(description, String);
  103. check(params, Object);
  104. this.unblock();
  105. // label activity did not work yet, see wekan/models/activities.js
  106. const quoteParams = _.clone(params);
  107. const clonedParams = _.clone(params);
  108. [
  109. 'card',
  110. 'list',
  111. 'oldList',
  112. 'board',
  113. 'oldBoard',
  114. 'comment',
  115. 'checklist',
  116. 'swimlane',
  117. 'oldSwimlane',
  118. 'label',
  119. 'attachment',
  120. ].forEach(key => {
  121. if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
  122. });
  123. const userId = params.userId ? params.userId : integrations[0].userId;
  124. const user = Users.findOne(userId);
  125. const text = `${params.user} ${TAPi18n.__(
  126. description,
  127. quoteParams,
  128. user.getLanguage(),
  129. )}\n${params.url}`;
  130. if (text.length === 0) return;
  131. const value = {
  132. text: `${text}`,
  133. };
  134. webhooksAtbts.forEach(key => {
  135. if (params[key]) value[key] = params[key];
  136. });
  137. value.description = description;
  138. //integrations.forEach(integration => {
  139. const is2way = integration.type === Integrations.Const.TWOWAY;
  140. const token = integration.token || '';
  141. const headers = {
  142. 'Content-Type': 'application/json',
  143. };
  144. if (token) headers['X-Wekan-Token'] = token;
  145. const options = {
  146. headers,
  147. data: is2way ? { description, ...clonedParams } : value,
  148. };
  149. const url = integration.url;
  150. if (is2way) {
  151. const cid = params.commentId;
  152. const comment = params.comment;
  153. const lockState = cid && Lock.has(cid, comment);
  154. if (cid && lockState !== Lock.NULL) {
  155. // it's a comment and there is a previous lock
  156. return;
  157. } else if (cid) {
  158. Lock.set(cid, comment); // set a lock here
  159. }
  160. }
  161. const response = postCatchError(url, options);
  162. if (
  163. response &&
  164. response.statusCode &&
  165. response.statusCode >= 200 &&
  166. response.statusCode < 300
  167. ) {
  168. if (is2way) {
  169. const data = response.data; // only an JSON encoded response will be actioned
  170. if (data) {
  171. try {
  172. responseFunc(data);
  173. } catch (e) {
  174. throw new Meteor.Error('error-process-data');
  175. }
  176. }
  177. }
  178. return response; // eslint-disable-line consistent-return
  179. } else {
  180. throw new Meteor.Error('error-invalid-webhook-response');
  181. }
  182. //});
  183. },
  184. });