UNPKG

node-red-contrib-sitx

Version:

Node-RED Nodes for sending & receiving Cursor on Target TAK Data with PAR Sit(x).

233 lines (200 loc) 6.59 kB
#!/usr/bin/env node /** * PAR Sit(x) Node-RED Node. * * @module node-red-contrib-sitx * @author Greg Albrecht <gba@snstac.com> * @copyright 2023 Sensors & Signals LLC * @license Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* jslint node: true */ /* jslint white: true */ const sitxLib = require('./sitxLib'); const makeSitxNode = (RED) => { function SitxNode(config) { const WebSocket = require('ws'); RED.nodes.createNode(this, config); const node = this; node.sitx_config = RED.nodes.getNode(config.sitx_config); node.accessKeyID = node.sitx_config.credentials.accessKeyID; node.secretKey = node.sitx_config.credentials.secretKey; node.subdomain = node.sitx_config.credentials.subdomain; node.scope = node.sitx_config.credentials.scope; node.group = node.sitx_config.credentials.group; node.status({ fill: 'red', shape: 'dot', text: 'Initializing' }); node.on('input', (msg) => { if (node.server) { const status = { fill: 'yellow', shape: 'dot', text: 'Transmitting', event: 'connect', }; node.status(status); node.server.send(msg.payload); } }); node.on('close', (done) => { if (node.heartbeatInterval) { clearInterval(node.heartbeatInterval); } node.closing = true; if (node.server) { node.server.close(); } // wait 20*50 (1000ms max) for ws to close. // call done when readyState === ws.CLOSED (or 1000ms, whichever comes fist) const closeMonitorInterval = 20; let closeMonitorCount = 50; const si = setInterval(() => { if ( node.server == null || node.server.readyState === WebSocket.CLOSED || closeMonitorCount <= 0 ) { if (node.tout) { clearTimeout(node.tout); node.tout = null; } clearInterval(si); return done(); } closeMonitorCount--; }, closeMonitorInterval); }); node.on('nrSocketOpened', () => { const status = { fill: 'green', shape: 'dot', text: 'Connected', }; node.status(status); }); node.on('nrSocketError', (event) => { node.log(`nrSocketError(${JSON.stringify(event)})`); const status = { fill: 'red', shape: 'ring', text: event.err.message, }; node.status(status); }); node.on('nrSocketClosed', (event) => { node.log(`nrSocketClosed(${JSON.stringify(event)})`); const status = { fill: 'red', shape: 'ring', text: 'Socket Closed', }; node.status(status); }); function startBridge() { const scope = `${node.subdomain}.takserver.parteamconnect.com/${node.group}/.${node.scope}`; let wsURL = `wss://${node.subdomain}.takserver.parteamconnect.com/${node.group}`; sitxLib .auth(node.subdomain, node.accessKeyID, node.secretKey, scope) .then((res) => { startSocket(wsURL, res.access_token); }) .catch((reason) => localErrorHandler(reason)); } function localErrorHandler(err) { node.log(`localErrorHandler(${JSON.stringify(err)})`); clearInterval(node.heartbeatInterval); node.emit('nrSocketError', { err }); if (!node.closing && !node.isServer) { clearTimeout(node.tout); node.tout = setTimeout(function () { startBridge(); }, 3000); // try to reconnect every 3 secs... bit fast ? } } function startSocket(wsURL, authToken) { const authHeader = `Bearer ${authToken}`; const socket = new WebSocket(wsURL, [], { headers: { Authorization: authHeader } }); const redId = RED.util.generateId(); socket.nrId = redId; socket.nrPendingHeartbeat = false; socket.on('open', () => { if (node.heartbeat) { clearInterval(node.heartbeatInterval); node.heartbeatInterval = setInterval(() => { if (socket.nrPendingHeartbeat) { socket.terminate(); socket.nrErrorHandler(new Error('WebSocket Heartbeat Timeout')); return; } socket.nrPendingHeartbeat = true; try { socket.ping(); } catch (err) {} }, node.heartbeat); } node.emit('nrSocketOpened'); }); socket.on('message', (msg) => handleMessage(msg)); function handleMessage(msg) { const msgToSend = { payload: msg.toString(), websocket: socket.send.bind(socket), }; node.send(msgToSend); } socket.on('error', localErrorHandler); socket.on('connect_error', localErrorHandler); socket.on('connect_timeout', localErrorHandler); socket.on('close', () => { clearInterval(node.heartbeatInterval); node.emit('nrSocketClosed'); if (!node.closing) { clearTimeout(node.tout); node.log('Sit(x) Bridge connection closed. Retrying...'); node.tout = setTimeout(function () { startBridge(); }, 3000); } }); socket.on('ping', function () { socket.nrPendingHeartbeat = false; const status = { fill: 'green', shape: 'dot', text: "Connected (RX'd Ping)", }; node.status(status); }); socket.on('pong', function () { socket.nrPendingHeartbeat = false; const status = { fill: 'green', shape: 'dot', text: 'PONG RX', }; node.status(status); }); node.server = socket; } node.closing = false; startBridge(); } RED.nodes.registerType('Sit(x) Node', SitxNode, { credentials: { accessKeyID: { type: 'text' }, secretKey: { type: 'text' }, subdomain: { type: 'text' }, scope: { type: 'text' }, group: { type: 'text' }, }, }); }; module.exports = makeSitxNode;