| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 | if (Meteor.isServer) {  const postCatchError = Meteor.wrapAsync((url, options, resolve) => {    HTTP.post(url, options, (err, res) => {      if (err) {        resolve(null, err.response);      } else {        resolve(null, res);      }    });  });  const Lock = {    _lock: {},    _timer: {},    echoDelay: 500, // echo should be happening much faster    normalDelay: 1e3, // normally user typed comment will be much slower    ECHO: 2,    NORMAL: 1,    NULL: 0,    has(id, value) {      const existing = this._lock[id];      let ret = this.NULL;      if (existing) {        ret = existing === value ? this.ECHO : this.NORMAL;      }      return ret;    },    clear(id, delay) {      const previous = this._timer[id];      if (previous) {        Meteor.clearTimeout(previous);      }      this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay);    },    set(id, value) {      const state = this.has(id, value);      let delay = this.normalDelay;      if (state === this.ECHO) {        delay = this.echoDelay;      }      if (!value) {        // user commented, we set a lock        value = 1;      }      this._lock[id] = value;      this.clear(id, delay); // always auto reset the locker after delay    },    unset(id) {      delete this._lock[id];    },  };  const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES &&    process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [    'cardId',    'listId',    'oldListId',    'boardId',    'comment',    'user',    'card',    'commentId',    'swimlaneId',    'customField',    'customFieldValue',    'attachmentId',  ];  const responseFunc = data => {    const paramCommentId = data.commentId;    const paramCardId = data.cardId;    const paramBoardId = data.boardId;    const newComment = data.comment;    if (paramCardId && paramBoardId && newComment) {      // only process data with the cardid, boardid and comment text, TODO can expand other functions here to react on returned data      const comment = CardComments.findOne({        _id: paramCommentId,        cardId: paramCardId,        boardId: paramBoardId,      });      const board = Boards.findOne(paramBoardId);      const card = Cards.findOne(paramCardId);      if (board && card) {        if (comment) {          Lock.set(comment._id, newComment);          CardComments.direct.update(comment._id, {            $set: {              text: newComment,            },          });        }      } else {        const userId = data.userId;        if (userId) {          const inserted = CardComments.direct.insert({            text: newComment,            userId,            cardId,            boardId,          });          Lock.set(inserted._id, newComment);        }      }    }  };  Meteor.methods({    outgoingWebhooks(integration, description, params) {      if (Meteor.user()) {        check(integration, Object);        check(description, String);        check(params, Object);        this.unblock();        // label activity did not work yet, see wekan/models/activities.js        const quoteParams = _.clone(params);        const clonedParams = _.clone(params);        [          'card',          'list',          'oldList',          'board',          'oldBoard',          'comment',          'checklist',          'swimlane',          'oldSwimlane',          'label',          'attachment',          'attachmentId',        ].forEach(key => {          if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;        });        const userId = params.userId ? params.userId : integrations[0].userId;        const user = Users.findOne(userId);        const text = `${params.user} ${TAPi18n.__(          description,          quoteParams,          user.getLanguage(),        )}\n${params.url}`;        if (text.length === 0) return;        const value = {          text: `${text}`,        };        webhooksAtbts.forEach(key => {          if (params[key]) value[key] = params[key];        });        value.description = description;        //integrations.forEach(integration => {        const is2way = integration.type === Integrations.Const.TWOWAY;        const token = integration.token || '';        const headers = {          'Content-Type': 'application/json',        };        if (token) headers['X-Wekan-Token'] = token;        const options = {          headers,          data: is2way ? { description, ...clonedParams } : value,        };        if (!Integrations.findOne({ url: integration.url })) return;        const url = integration.url;        if (is2way) {          const cid = params.commentId;          const comment = params.comment;          const lockState = cid && Lock.has(cid, comment);          if (cid && lockState !== Lock.NULL) {            // it's a comment  and there is a previous lock            return;          } else if (cid) {            Lock.set(cid, comment); // set a lock here          }        }        const response = postCatchError(url, options);        if (          response &&          response.statusCode &&          response.statusCode >= 200 &&          response.statusCode < 300        ) {          if (is2way) {            const data = response.data; // only an JSON encoded response will be actioned            if (data) {              try {                responseFunc(data);              } catch (e) {                throw new Meteor.Error('error-process-data');              }            }          }          return response; // eslint-disable-line consistent-return        } else {          throw new Meteor.Error('error-invalid-webhook-response');        }      }    },  });}
 |