crisp-im / node-crisp-api

:zap: Crisp API Node Wrapper
https://docs.crisp.chat/guides/rest-api/
MIT License
99 stars 38 forks source link

Cannot read properties of undefined (reading '_emitter') when using HTTP WebHooks #51

Closed ralph44 closed 1 year ago

ralph44 commented 1 year ago

Description

When using the Crisp library with the RTM_MODES.WebHooks mode, the following error is thrown:

TypeError: Cannot read properties of undefined (reading '_emitter') at \node_modules\crisp-api\lib\crisp.js:834:31

This error occurs in the _connectLoopback method of the crisp-api library, which is called by the _prepareBroker method. The problem appears to be that the this reference is not properly bound to the Crisp instance, which causes the _emitter property to be undefined.

Steps to Reproduce:

  1. Initialize a Crisp instance with the RTM_MODES.WebHooks mode.
  2. Call the on method to bind an event listener.
  3. Wait for the error to occur.

Expected Result:

No errors should occur, and the event listener should be properly bound to the Crisp instance.

Actual Result:

The error cannot read properties of undefined (reading '_emitter') is thrown, and the event listener is not properly bound to the Crisp instance.

Potential Solution:

It appears that adding the following line of code var self = this; to the _connectLoopback method in the crisp-api library could fix the problem:

_connectLoopback : function() {
    var self = this;

    return Promise.resolve()
      .then(function() {
        // Assign emitter to loopback
        self._loopback = self._emitter;

        // Unstack broker bind hooks immediately
        self._unstackBrokerBindHooks(self._loopback);

        return Promise.resolve();
      });
  },

This would create a local variable self that references the current Crisp instance and can be used to access its properties and methods.

Version Information:

crisp-api version: 7.4.1 Node.js version: v16.19.0 Operating System: Windows 10

Additional Information:

I'm not entirely certain that this is a proper solution, and there may be other underlying issues causing the error. Maybe it is the way i implemented the crisp-api into my service. It's also possible that adding the var self = this; line may have unintended side effects. But other functions are using the same line.

I tried to followed these instructions. I would loved to just use this example, but i did not want to mix the express code with raw http server code.

valeriansaliou commented 1 year ago

Hello! Could you provide the full code leading to the error?

ralph44 commented 1 year ago

My code is structured with OOP.

Therefore it is a bit hard, but i will try.

I start the express server in the Server.tsx-File with the following code:

import { Automation } from "../lib/Automation";
const app = express();
app.use(
  helmet.contentSecurityPolicy({
          << Security >>
  })
);
const port = 80;
const automationObject = new Automation(logger, false);
automationObject.initAPILayer();

app.use(express.json());

app.use(
  express.static(path.join(__dirname, "../public"), {
    index: false,
    extensions: ["html"],
  })
);

Automation Class has the following function:

  initAPILayer() {
    this.APILayer = new APILayer(this.production);
    this.APILayer._initPlugin()
    this.APILayer._initRTMListener()
  }

In APILayer these are functions being called:

class APILayer { 
  constructor(production: boolean) {
    this.crispClient = new Crisp();
  }

  _initPlugin() {
    this.crispClient.authenticateTier(
      "plugin",
      env.apiIdentifier,
      env.apiKey
    );
  }

  _initRTMListener() {
    this.crispClient.setRtmMode(Crisp.RTM_MODES.WebHooks);
    this.crispClient.on("plugin:settings:saved", function (message: any) {
      logger.debug("Got plugin settings saved event", message);
    });
  }

When the this.crispClient.on("plugin:settings:saved... is called the error appears.

I tried to change the port to 3997 and many other ways of implementing it. Node_modules reinstalled and other solutions.

What i have figured out is, that if i don't specify setRtmMode(Crisp.RTM_MODES.WebHooks) it works and i get the messages, but i cannot verify them in the http endpoint:

app.post("/subscription/hook", (request, res) => {
  res.sendStatus(200);

  let _timestamp = request.headers["x-crisp-request-timestamp"],
    _signature = request.headers["x-crisp-signature"];

  if (!_timestamp || !_signature) {
    console.error("Missing headers");
    return;
  }
  if (
    !_timestamp ||
    !_signature ||
    Array.isArray(_timestamp) ||
    Array.isArray(_signature)
  ) {
    logger.warn(
      "Timestamp or signature is not defined. Please check your headers."
    );
    return;
  }

  // Verify signature
  let _verified = automationObject.APILayer?.crispClient.verifyHook(
    env.signingsecret,
    request.body,
    _timestamp,
    _signature
  );

  console.log(
    `${_signature} |remote| <- SIGNATURE -> |local| ${_verified}`,
    _verified
  );
  // Receive Web Hook payload
  var error = automationObject.APILayer?.crispClient.receiveHook(request.body);

  if (error !== null) {
    console.error("Web Hook processing error", error);
  } else {
    console.log("Web Hook processed successfully");
  }

Maybe it is too complicated how i implemented everything and i made a silly mistake on my way, therefore I am not quite sure where the error originates from.

valeriansaliou commented 1 year ago

Fixed in v8.1.0, related to #58