@haystacks/async
Version:
A framework to build any number or any kind of native application or automation solution.
176 lines (150 loc) • 6.46 kB
JavaScript
/**
* @file socketsClient.js
* @module socketsClient
* @description Creates a socket client to connect and send messages through the server to other processes
* @requires @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants}
* @requires {@link https://www.npmjs.com/package/path|path}
* @author Seth Hollingsead
* @NOTE: Original author Karl-Edward FP Jean-Mehu
* @date 2025-01-02 - Originally 2023/12/29
* @copyright Copyright © 2025-… by Seth Hollingsead. All rights reserved
*/
// Internal imports
import ruleBroker from '../brokers/ruleBroker.js';
// External imports
import { Socket } from 'net';
import process from 'process'
import { promisify } from 'util';
import hayConst from '@haystacks/constants';
import path from 'path';
const {bas, biz, gen, msg, sys, wrd} = hayConst;
const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url));
// framework.executrix.loggers.
// eslint-disable-next-line no-unused-vars
const namespacePrefix = wrd.cframework + bas.cDot + wrd.cexecutrix + bas.cDot + baseFileName + bas.cDot;
// Host and port to which the socket
// connection will be listening to
const SOCKET = {
host: '127.0.0.1',
port: 3000
}
// Get current development mode
const isDevMode =
!process.env[sys.cNODE_ENV] ||
process.env[sys.cNODE_ENV].toLowerCase() !== wrd.cproduction;
const messageDelimiter = bas.cHash.repeat(2) + wrd.cEND + bas.cHash.repeat(2); // ##END##
const chunkDelimiter = bas.cPipe + bas.cTilde + bas.cPipe; // |~|
const maxMessageLength = 840;
/**
* @name socketsClient
* @description Creates a socket client instance and handles communication to the server
* @author Karl-Edward FP Jean-Mehu
* @date 2023/12/29
*/
export default async function socketsClient() {
try {
const socket = Socket({});
const writeAsync = promisify(socket.write.bind(socket));
// Keep connection live
socket.setKeepAlive(true);
// Connect to server
let isConnected = false;
// Connections has been refused or connection is in use.
let connectionRefusedOrInUse = false;
if (isConnected == false) {
socket.connect(SOCKET.port, SOCKET.host);
socket.setNoDelay(true); // Disable Nagle's algorithm to avoid packet batching
isConnected = true;
}
// Error handler
socket.on(wrd.cerror, (error) => {
if (error.code === gen.cECONNREFUSED || error.code === gen.cEADDRINUSE){
connectionRefusedOrInUse = true;
// Server is offline! Connection is already in use or has been denied.
// console.log(bas.cCarriageReturn + bas.cNewLine + msg.csocketsClientServerOffline + bas.cCarriageReturn + bas.cNewLine);
}else{
console.log(bas.cCarriageReturn + bas.cNewLine + msg.cErrorOnClient + error.message);
}
});
// Handle connection being closed
socket.on(wrd.cclose, async () => {
// Connection closed!
if (!connectionRefusedOrInUse) console.log(msg.cConnectionClosed);
});
// Handles actions to take when the server establishes a connection
// Client connection established!
socket.on(wrd.cconnect, async () => console.log(msg.cClientConnectionEstablished));
// Handle receiving data
socket.on(wrd.cdata, async (chunk) => {
// console.log(`Received data, ${JSON.parse(chunk)}`);
});
/**
* Sends data through to the socket server.
* Parses and sends test result as well.
* @param { string } message - Message to be logged.
*/
socket.send = async (message) => {
if (isConnected) {
// Create timestamp
const timestamp = await ruleBroker.processRules([gen.cYYYYMMDD_HHmmss_SSS, ''], [biz.cgetNowMoment]);
let broadcastMessage = JSON.stringify({ message: `${message}`, timestamp }) + messageDelimiter;
// Write data to socket
await writeAsync(broadcastMessage, async (error) => {
// Assuming app is called locally / directly. Display only debug logs on failed
// stream writes
if (error) {
if (error.code === gen.ERR_STREAM_DESTROYED) {
console.log(bas.cCarriageReturn + bas.cNewLine + msg.cConnectionClosedFailedSendingChunk + broadcastMessage +
bas.cCarriageReturn + bas.cNewLine);
} else {
console.log(namespacePrefix + wrd.csend + bas.cSpace + wrd.cchunk + bas.cColon + bas.cSpace + broadcastMessage +
bas.cCarriageReturn + bas.cNewLine);
console.log(namespacePrefix + wrd.csend + bas.cSpace + wrd.cerror + bas.cColon + bas.cSpace + error.message +
bas.cCarriageReturn + bas.cNewLine);
}
} // End-if (error)
});
// Parse broadcastMessage for and return test results
const REGEX = /((TestResultsLog)\s:\s(Test_\w+)\s:)\s(\w)+/g;
const validResponses = [wrd.cPass, wrd.cFail, wrd.cWarning];
const matchResult = broadcastMessage.match(REGEX);
if (matchResult) {
// Testing for match
console.log(msg.cTestingForMatch + matchResult[0]);
const testResultsMatch = matchResult[0].split(':').map(v => v.trim())[2];
if (!testResultsMatch) {
console.log({testResultsMatch});
return;
}
console.log({ testResultsMatch });
const testResult = validResponses.includes(( testResultsMatch || "").toLowerCase()) ? testResultsMatch[2] : wrd.cFail
await writeAsync(JSON.stringify({ testResult }));
}
} else {
return;
}
};
// Return connection status
socket.isConnected = async () => {
await new Promise(resolve => {
resolve(isConnected);
});
};
// Closes the connection with the server
socket.close = async () => {
isConnected = false;
await promisify(socket.end.bind(socket))();
// await promisify(socket.exit.bind(socket))();
await promisify(socket.destroy.bind(socket))();
};
return socket;
} catch (error) {
if (error.code === gen.cECONNREFUSED || error.code === gen.cEADDRINUSE){
// Socket server offline!
console.log(bas.cCarriageReturn + bas.cNewLine + msg.cSocketServerOffline + bas.cCarriageReturn + bas.cNewLine);
} else {
// Socket client failed:
console.log(bas.cCarriageReturn + bas.cNewLine + msg.cSocketClientFailed + error.message);
}
}
}