How to call method from .js file in .html file when creating node?

Hi There! I am trying to build a custom server-node and subscribe-event node which should be able to start a session in the websocket connection the server made. Then in this session each instance of the subscribe-event node should be able to fetch all available eventTypes and listen for 1-x.
Now I have a subscribe-event.html with a button "reload" this button should trigger a method which calls all available EventTypes from the API. However this method need authentication and needs to call a method in the subscribe-event.js. How do I do that? Below is my code attached, please also feel free to point out any other error / improvements (development ongoing) TY!

server.js

const fetch = require('node-fetch');
const WebSocket = require('ws');
let myconfig = {};

module.exports = function myNode(RED) {
  myconfig.RED = RED;
  // RED.nodes.registerType('sivideo-server', SiVideoServer, {});

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

  RED.nodes.registerType('sivideo-server', SiVideoServer);
};

// this is run once for each node instance of the node in flows
function SiVideoServer(config) {
  if (!myconfig.RED) {
    console.log('RED not set');
    return;
  }
  const RED = myconfig.RED;

  // Initialize "this"
  RED.nodes.createNode(this, config);
  const node = this;
  this.host = config.host;
  this.username = config.username;
  this.password = config.password;
  this.getToken = getToken;
  this.registerListener = registerListener;
  this.deRegisterListener = deRegisterListener;
  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) {
          node.ws = new WebSocket(`ws://${this.host}/api/ws/events/v1`, {
            headers: {
              Authorization: `Bearer ${token}`,
            },
          });
          console.log('Websocket connection created');
          startSession(this);
          // listen for events todo: delete later or refactor to method
          listenForEvents(this);
          // Register message event listener
          node.ws.on('message', (data) => {
            console.log(
              'Received message:',
              Buffer.from(data).toString('utf-8'),
            );
            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;
  }
}

function startSession(server) {
  // TODO this should most likely be in the subscribe event node
  // start own session so that every node has its own session
  // Start a session
  server.ws.on('open', () =>
    server.ws.send(
      JSON.stringify({
        command: 'startSession',
        commandId: 1,
        sessionId: '',
        eventId: '',
      }),
    ),
  );
  console.log('Session started');
}

function listenForEvents(server) {
  // TODO this should most likely be in the subscribe event node
  server.ws.on('open', () =>
    server.ws.send(
      JSON.stringify({
        command: 'addSubscription',
        commandId: 1,
        filters: [
          {
            modifier: 'include',
            resourceTypes: ['cameras'],
            sourceIds: ['*'],
            eventTypes: ['*'],
          },
        ],
      }),
    ),
  );
  console.log('Subscription added');
}

// Register and deregister listeners
function registerListener(NodeID, Callback) {
  // start own session so that every node has its own session
  console.log('Registering listener:', NodeID);
  Listeners[NodeID] = Callback;
}

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

server.html

<script type="text/javascript">
  RED.nodes.registerType('sivideo-server', {
    category: 'config',
    //paletteLabel: 'SiVideo Server',
    color: '#009999',
    defaults: {
      name: { value: '' },
      host: { value: '', required: true },
      username: { type: 'text' },
      password: { type: 'text' },
    },
    label: function () {
      return this.name || this.host + ':' + this.port;
    },
  });
</script>

<script type="text/html" data-template-name="sivideo-server">
  <div class="form-row">
    <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
    <input type="text" id="node-config-input-name" placeholder="Name" />
  </div>
  <div class="form-row">
    <label for="node-config-input-host"
      ><i class="fa fa-server"></i> Host</label
    >
    <input
      type="text"
      id="node-config-input-host"
      placeholder="Siveillance Video Client Address"
    />
  </div>
  <div class="form-row">
    <label for="node-config-input-username"><i class="fa fa-tag"></i> Username</label>
    <input type="text" id="node-config-input-username" />
  </div>
  <div class="form-row">
    <label for="node-config-input-password"><i class="fa fa-tag"></i> Password</label>
    <input type="text" id="node-config-input-password" />
  </div>
</script>

<script type="text/html" data-help-name="sivideo-server">
</script>

subscribe-event.js

// Note: This node is used to subscribe to events from the SiWall server
let myconfig = {};
module.exports = function myNode(RED) {
  myconfig.RED = RED;

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

  RED.nodes.registerType('sivideo-subscribe-event', SiVideoSubscribeEvent);
}

// this is run once for each node instance of the node in flows
function SiVideoSubscribeEvent(config) {
  if (!myconfig.RED) {
    console.log('RED not set');
    return;
  }
  var node = this;
  // globalThis instead of self
  console.log('Config-node-id:', config.server);
  node.myConfigNode = RED.nodes.getNode(config.server);
  console.log('Config Node:', node.myConfigNode);

  // Start registering the listener
  registerListenerWhenReady(node);

  node.on('input', function (msg) {
    console.log('working');
  });

  node.on('close', (removed, done) => {
    // Check if the config node is fully initialized before de-registering the listener
    if (
      node.myConfigNode &&
      typeof node.myConfigNode.deRegisterListener === 'function'
    ) {
      node.myConfigNode.deRegisterListener(node.id);
    }
    done();
  });
}

// Define a function to register the listener once the config node is fully initialized
function registerListenerWhenReady(node) {
  // Check if the config node is fully initialized
  if (
    node.myConfigNode &&
    typeof node.myConfigNode.registerListener === 'function'
  ) {
    console.log('Config node is ready');
    // Config node is ready, register the listener
    node.status({ fill: 'green', shape: 'dot', text: 'connected' });
    node.myConfigNode.registerListener(node.id, (data) => {
      node.send({ payload: data }); // Send the data to the next node
    });
    console.log('Listener registered');
  } else {
      // Config node is not ready yet, set status to indicate disconnected
      node.status({ fill: 'red', shape: 'ring', text: 'disconnected' });
      // Retry registration after a short delay
      setTimeout(registerListenerWhenReady(node), 1000); // Retry after 1 second
  }
}

subscribe-event.html

<script type="text/javascript">
    RED.nodes.registerType('sivideo-subscribe-event', {
        category: 'SiVideo',
        paletteLabel: 'Subscribe Event',
        color: '#009999',
        defaults: {
            name: { value: "" },
            server: { value: "", type: "sivideo-server" },
            eventTypes: { value: "" }
        },
        inputs: 1,
        outputs: 1,
        icon: "font-awesome/fa-rss",
        label: function () {
            return this.name || "SiVideo Subscribe Event";
        },
        oneditprepare: function () {

            var node = this;
            // click event for the button to reload the eventTypes
            $("#node-input-eventTypes-button").click(function () {
                // get the RED object
                if (!myconfig.RED) {
                    console.log("RED not found");
                    return;
                }
                const RED = myconfig.RED;
                const 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));
            });
        },
        // oneditsave create new websocket session with and listen for selecte eventTypes
        oneditsave: function () {
            // create session here
        },
        // when node is removed from palette we deregister the node
        onpaletteremove: function () {
            // close session here of websocket and deregister the node
        }
    }
    );
</script>

<script type="text/html" data-template-name="sivideo-subscribe-event">

<div class="form-row">
    <h2>Subscribe to events</h2>
</div>

    <div class="form-row">
        <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name">
    </div>
    <div class="form-row">
        <label for="node-input-server"><i class="fa fa-server"></i> Server</label>
        <input type="text" id="node-input-server" required>
    </div>
    <div class="form-row">
            <label for="node-input-eventTypes"><i class="fa fa-tag"></i> Event Types</label>
        <div style="display: inline-flex; width: 70%;">
            <input type="text" id="node-input-eventTypes" style="flex-grow: 1;">
            <button id="node-input-eventTypes-button" type="button" class="red-ui-button" style="margin-left: 10px;">Reload</button>
        </div>
    </div>

    <div class="form-row">
        <br/>
        <h3>Node Message output:</h3>
        <b>msg.payload = array of:</b><br/>
        &nbsp;&nbsp;&nbsp;<b>Id : </b>GUID<br/>
    </div>
</script>

<script type="text/html" data-help-name="sivideo-subscribe-event">
    <p>Node that gets all available screens from the current System.</p>

    <h3>Outputs</h3>
    <dl class="message-properties">
        <dt>payload<span class="property-type">object</span></dt>
        <dd>
            The screen(s) as an object as read from the system.<br>
            The Screen Id, Name and  fetched.
        </dd>
    </dl>
</script>```

To access server side stuff from the html (client) side, you need to implement a http admin endpoint in the js file.

Here is one such example: Get logged user information in custom node - #3 by knolleary

1 Like