Cannot call functions of config node

Hi there!

I am trying to access a function namely getToken() defined in my config node in another node. However if I do it like the following:

var server = RED.nodes.node(node.server);
                console.log("server", server, "host", server.host);
                console.log("Server token ", server.getToken(server.host, server.username, server.password));

I get the error that this function is not defined. My server file looks like the following:

const fetch = require('node-fetch');
const WebSocket = require('ws');

module.exports = function (RED) {
  RED.nodes.registerType('sivideo-server', SiVideoServer, {});
  function SiVideoServer(config) {
    RED.nodes.createNode(this, config);
    const node = this;
    this.host = config.host;
    this.username = config.username;
    this.password = config.password;
    const Listeners = {};

    // Log the credentials for debugging purposes
    console.log('Credentials:', this.host, this.username, this.password);
    // Get the bearer token for websocket authentication
    if (this.host && this.username && this.password) {
      getToken(this.host, this.username, this.password)
        .then((token) => {
          node.context().set('token', token);
          // If token obtained, create websocket connection
          if (token && false) {
            node.ws = new WebSocket(`ws://${this.host}/api/ws/events/v1`, {
              headers: {
                Authorization: `Bearer ${token}`,
              },
            });

            console.log('Websocket connection created');
            // Register message event listener
            node.ws.on('message', (data) => {
              for (const [id, cb] of Object.entries(Listeners)) {
                cb(data);
              }
            });
          } else {
            console.log('No token obtained');
          }
        })
        .catch((error) => {
          console.error('Error fetching token:', error);
          throw error;
        });
    } else {
      console.log('No host, username, or password provided');
    }
  }

  // get the token from the server
  async function getToken(host, username, password) {
    try {
      const formData = new URLSearchParams();
      formData.append('grant_type', 'password');
      formData.append('client_id', 'GrantValidatorClient');
      formData.append('username', username);
      formData.append('password', password);

      console.log('Form Data:', formData.toString());
      const response = await fetch(`http://${host}/idp/connect/token`, {
        method: 'POST',
        headers: {
          'Content-type': 'application/x-www-form-urlencoded',
        },
        body: formData,
      });

      if (response.ok) {
        const data = await response.json();
        console.log('Token:', data.access_token);
        return data.access_token;
      } else {
        console.error('HTTP Error:', response.status);
        return null;
      }
    } catch (error) {
      console.error('Error fetching token:', error);
      throw error;
    }
  }

  // Register and deregister listeners
  function registerListener(NodeID, Callback) {
    console.log('Registering listener:', NodeID);
    Listeners[NodeID] = Callback;
  }

  function deRegisterListener(NodeID) {
    console.log('De-registering listener:', NodeID);
    delete Listeners[NodeID];
  }
};

Hi @hansimeister

Your getToken function is not attached to your config node in any way that would allow other nodes to access it - its just a function in the same file as your node.

You could add the following to your SiVideoServer function to expose the function on your node object:

this.getToken = getToken

Tried it like below but unfortunately I still get the same error? @knolleary

module.exports = function (RED) {
  RED.nodes.registerType('sivideo-server', SiVideoServer, {});
  function SiVideoServer(config) {
    RED.nodes.createNode(this, config);
    const node = this;
    this.host = config.host;
    this.username = config.username;
    this.password = config.password;
    this.getToken = getToken;
    const Listeners = {};

I think it would help if you shared a slightly more complete view of the code.

Personally, I prefer to separate out the various functions so that the logic is a lot easier to follow. It tends to help you avoid these issues.

let myconfig = {}

// This is run once when Node-RED Starts
module.exports = function myNode(RED) {
   // In case you want to reference RED in other functions
   myconfig.RED = RED

   // ... any setup functions here ...

   RED.nodes.registerType('myNode', nodeInstance)
})

// This is run once for each instance of the node in flows
function nodeInstance(config) {
   // if you need to reference red
   if ( !myconfig.RED ) return
   const RED = myconfig.RED
   
   // Initialise `this`
   RED.nodes.createNode(this, config)

   this.name = config.name
   this.topic = config.topic
   // ....

   this.getToken = getToken
   // Or, if you need to run it once for every instance:
   // this.token = getToken(host, username, password)

   // If you need to handle inbound msgs
   this.on('input', inputMsgHandler)

   // If you need to shut things down
   this.on('close', (removed, done) => {
      // ...
   }
}

// If needed (NB: Inherits `this` from `nodeInstance`)
inputMsgHandler(msg, send, done) {
   // if you need to reference red (quite likely in this fn)
   if ( !myconfig.RED ) return
   const RED = myconfig.RED

   // ...
}

async function getToken(host, username, password) {
   // ...
}

Then, if you want your getToken fn, you can simply add as another function at the end, reference myconfig.RED if you need to (which you indicate you don't here). And use it in any of the other functions as needed.