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/>
<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>```