2
0

outgoing.js 5.7 KB

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