|  | @@ -1,192 +1,199 @@
 | 
	
		
			
				|  |  | -const postCatchError = Meteor.wrapAsync((url, options, resolve) => {
 | 
	
		
			
				|  |  | -  HTTP.post(url, options, (err, res) => {
 | 
	
		
			
				|  |  | -    if (err) {
 | 
	
		
			
				|  |  | -      resolve(null, err.response);
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      resolve(null, res);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +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 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',
 | 
	
		
			
				|  |  | -];
 | 
	
		
			
				|  |  | -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: {
 | 
	
		
			
				|  |  | +  const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES &&
 | 
	
		
			
				|  |  | +    process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [
 | 
	
		
			
				|  |  | +    'cardId',
 | 
	
		
			
				|  |  | +    'listId',
 | 
	
		
			
				|  |  | +    'oldListId',
 | 
	
		
			
				|  |  | +    'boardId',
 | 
	
		
			
				|  |  | +    'comment',
 | 
	
		
			
				|  |  | +    'user',
 | 
	
		
			
				|  |  | +    'card',
 | 
	
		
			
				|  |  | +    'commentId',
 | 
	
		
			
				|  |  | +    'swimlaneId',
 | 
	
		
			
				|  |  | +  ];
 | 
	
		
			
				|  |  | +  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,
 | 
	
		
			
				|  |  | -          },
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      const userId = data.userId;
 | 
	
		
			
				|  |  | -      if (userId) {
 | 
	
		
			
				|  |  | -        const inserted = CardComments.direct.insert({
 | 
	
		
			
				|  |  | -          text: newComment,
 | 
	
		
			
				|  |  | -          userId,
 | 
	
		
			
				|  |  | -          cardId,
 | 
	
		
			
				|  |  | -          boardId,
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -        Lock.set(inserted._id, newComment);
 | 
	
		
			
				|  |  | +            userId,
 | 
	
		
			
				|  |  | +            cardId,
 | 
	
		
			
				|  |  | +            boardId,
 | 
	
		
			
				|  |  | +          });
 | 
	
		
			
				|  |  | +          Lock.set(inserted._id, newComment);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -Meteor.methods({
 | 
	
		
			
				|  |  | -  outgoingWebhooks(integration, description, params) {
 | 
	
		
			
				|  |  | -    check(integration, Object);
 | 
	
		
			
				|  |  | -    check(description, String);
 | 
	
		
			
				|  |  | -    check(params, Object);
 | 
	
		
			
				|  |  | -    this.unblock();
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +  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',
 | 
	
		
			
				|  |  | -    ].forEach(key => {
 | 
	
		
			
				|  |  | -      if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
 | 
	
		
			
				|  |  | -    });
 | 
	
		
			
				|  |  | +        // 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',
 | 
	
		
			
				|  |  | +        ].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}`;
 | 
	
		
			
				|  |  | +        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;
 | 
	
		
			
				|  |  | +        if (text.length === 0) return;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    const value = {
 | 
	
		
			
				|  |  | -      text: `${text}`,
 | 
	
		
			
				|  |  | -    };
 | 
	
		
			
				|  |  | +        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,
 | 
	
		
			
				|  |  | -    };
 | 
	
		
			
				|  |  | -    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);
 | 
	
		
			
				|  |  | +        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 (
 | 
	
		
			
				|  |  | -      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');
 | 
	
		
			
				|  |  | +        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');
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  | -      return response; // eslint-disable-line consistent-return
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      throw new Meteor.Error('error-invalid-webhook-response');
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    //});
 | 
	
		
			
				|  |  | -  },
 | 
	
		
			
				|  |  | -});
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +  });
 | 
	
		
			
				|  |  | +}
 |