outgoing.js 6.0 KB

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