Socket
Socket
Sign inDemoInstall

actionhero-socket-server

Package Overview
Dependencies
1
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.1.3 to 3.0.0

.github/workflows/test.yml

2

__tests__/servers/socket.ts

@@ -51,3 +51,3 @@ import * as uuid from "uuid";

return new Promise((resolve) => {
const conn = net.connect(config.servers.socket.port);
const conn = net.connect(config.socket.port?.toString());
// conn.data = "";

@@ -54,0 +54,0 @@ conn.on("connect", () => {

@@ -1,2 +0,7 @@

import { Server } from "actionhero";
/// <reference types="node" />
import * as net from "net";
import { Connection, Server } from "actionhero";
declare type RawConnection = net.Socket | (net.Socket & {
socketDataString: string;
});
export declare class SocketServer extends Server {

@@ -7,11 +12,12 @@ constructor();

stop(): Promise<void>;
sendMessage(connection: any, message: any, messageId: any): Promise<void>;
goodbye(connection: any): Promise<void>;
sendFile(connection: any, error: any, fileStream: any): Promise<void>;
handleConnection(rawConnection: any): void;
onConnection(connection: any): Promise<void>;
parseLine(connection: any, line: any): Promise<void>;
parseRequest(connection: any, line: any): Promise<void>;
checkBreakChars(chunk: any): boolean;
sendMessage(connection: Connection, message: Record<string, any>, messageId: string | number): Promise<void>;
goodbye(connection: Connection): Promise<void>;
sendFile(connection: Connection, error: NodeJS.ErrnoException, fileStream: any): Promise<void>;
handleConnection(rawConnection: RawConnection): void;
onConnection(connection: Connection): Promise<void>;
parseLine(connection: Connection, line: string): Promise<void>;
parseRequest(connection: Connection, line: string): Promise<void>;
checkBreakChars(chunk: Buffer): boolean;
gracefulShutdown(alreadyShutdown?: boolean): any;
}
export {};

@@ -92,6 +92,3 @@ "use strict";

try {
connection.rawConnection.end(JSON.stringify({
status: connection.localize("actionhero.goodbyeMessage"),
context: "api",
}) + "\r\n");
connection.rawConnection.end(JSON.stringify({ status: "Bye", context: "api" }) + "\r\n");
}

@@ -114,3 +111,3 @@ catch (e) {

}
rawConnection.socketDataString = "";
rawConnection["socketDataString"] = "";
const id = uuid.v4();

@@ -140,3 +137,4 @@ this.buildConnection({

const data = connection.rawConnection.socketDataString.slice(0, index);
connection.rawConnection.socketDataString = connection.rawConnection.socketDataString.slice(index + d.length);
connection.rawConnection.socketDataString =
connection.rawConnection.socketDataString.slice(index + d.length);
const lines = data.split(d);

@@ -143,0 +141,0 @@ for (const i in lines) {

@@ -5,3 +5,3 @@ {

"description": "A TCP and JSON server for actionhero",
"version": "2.1.3",
"version": "3.0.0",
"homepage": "http://www.actionherojs.com",

@@ -35,12 +35,11 @@ "license": "Apache-2.0",

},
"dependencies": {},
"devDependencies": {
"@types/jest": "^26.0.22",
"@types/node": "^14.14.37",
"actionhero": "^25.0.8",
"jest": "^26.6.3",
"prettier": "^2.2.1",
"ts-jest": "^26.5.4",
"ts-node-dev": "^1.1.6",
"typescript": "^4.2.3"
"@types/jest": "^27.0.1",
"@types/node": "^16.7.10",
"actionhero": "^28.0.0",
"jest": "^27.1.0",
"prettier": "^2.3.2",
"ts-jest": "^27.0.5",
"ts-node-dev": "^1.1.8",
"typescript": "^4.4.2"
},

@@ -55,3 +54,6 @@ "scripts": {

"prepare": "npm run lint && npm run build"
},
"dependencies": {
"uuid": "^8.3.2"
}
}
# Actionhero Socket Server
[![CircleCI](https://circleci.com/gh/actionhero/actionhero-socket-server.svg?style=svg)](https://circleci.com/gh/actionhero/actionhero-socket-server)
[![test](https://github.com/actionhero/actionhero-socket-server/actions/workflows/test.yml/badge.svg)](https://github.com/actionhero/actionhero-socket-server/actions/workflows/test.yml)
As of Actionhero v21, the socket server is not included with Actionhero by default. You can add it (this package) via `npm install actionhero-socket-server`.
As of version `3.0.0` of this package, Actionhero v28+ is required.
```shell

@@ -16,3 +18,3 @@ ❯ telnet localhost 5000

exit
{"status":"actionhero.goodbyeMessage","context":"api"}
{"status":"Bye","context":"api"}
Connection closed by foreign host.

@@ -24,3 +26,3 @@ ```

1. Add the package to your actionhero project: `npm install actionhero-socket-server --save`
2. Copy the config file into your project `cp ./node_modules/actionhero-socket-server/src/config/servers/socket.ts src/config/servers/socket.ts`
2. Copy the config file into your project `cp ./node_modules/actionhero-socket-server/src/config/socket.ts src/config/socket.ts`
3. Enable the plugin:

@@ -69,25 +71,31 @@

```ts
const namespace = "socket";
declare module "actionhero" {
export interface ActionheroConfigInterface {
[namespace]: ReturnType<typeof DEFAULT[typeof namespace]>;
}
}
export const DEFAULT = {
servers: {
socket: (config) => {
return {
enabled: true,
// TCP or TLS?
secure: false,
// Passed to tls.createServer if secure=true. Should contain SSL certificates
serverOptions: {},
// Port or Socket
port: 5000,
// Which IP to listen on (use 0.0.0.0 for all)
bindIP: "0.0.0.0",
// Enable TCP KeepAlive pings on each connection?
setKeepAlive: false,
// Delimiter string for incoming messages
delimiter: "\n",
// Maximum incoming message string length in Bytes (use 0 for Infinite)
maxDataLength: 0,
};
},
[namespace]: () => {
return {
enabled: true,
// TCP or TLS?
secure: false,
// Passed to tls.createServer if secure=true. Should contain SSL certificates
serverOptions: {},
// Port or Socket
port: 5000,
// Which IP to listen on (use 0.0.0.0 for all)
bindIP: "0.0.0.0",
// Enable TCP KeepAlive pings on each connection?
setKeepAlive: false,
// Delimiter string for incoming messages
delimiter: "\n",
// Maximum incoming message string length in Bytes (use 0 for Infinite)
maxDataLength: 0,
};
},
};
```
import { api, id, task, Action, actionheroVersion } from "actionhero";
import * as path from "path";
const packageJSON = require(path.normalize(
path.join(__dirname, "..", "..", "package.json")
));
import * as fs from "fs";
// These values are probably good starting points, but you should expect to tweak them for your application
const maxEventLoopDelay = process.env.eventLoopDelay || 10;
const maxMemoryAlloted = process.env.maxMemoryAlloted || 500;
const maxResqueQueueLength = process.env.maxResqueQueueLength || 1000;
module.exports = class Status extends Action {
enum StatusMessages {
healthy = "Node Healthy",
unhealthy = "Node Unhealthy",
}
const packageJSON = JSON.parse(
fs
.readFileSync(
path.normalize(path.join(__dirname, "..", "..", "package.json"))
)
.toString()
);
export class Status extends Action {
constructor() {

@@ -24,18 +34,14 @@ super();

async checkRam(data) {
async run() {
let nodeStatus = StatusMessages.healthy;
const problems: string[] = [];
const consumedMemoryMB =
Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100;
data.response.consumedMemoryMB = consumedMemoryMB;
if (consumedMemoryMB > maxMemoryAlloted) {
data.response.nodeStatus = data.connection.localize("Unhealthy");
data.response.problems.push(
data.connection.localize([
"Using more than {{maxMemoryAlloted}} MB of RAM/HEAP",
{ maxMemoryAlloted: maxMemoryAlloted },
])
);
nodeStatus = StatusMessages.unhealthy;
problems.push(`Using more than ${maxMemoryAlloted} MB of RAM/HEAP`);
}
}
async checkResqueQueues(data) {
let resqueTotalQueueLength = 0;
const details = await task.details();

@@ -46,30 +52,22 @@ let length = 0;

});
resqueTotalQueueLength = length;
data.response.resqueTotalQueueLength = length;
if (length > maxResqueQueueLength) {
data.response.nodeStatus = data.connection.localize("Node Unhealthy");
data.response.problems.push(
data.connection.localize([
"Resque Queues over {{maxResqueQueueLength}} jobs",
{ maxResqueQueueLength: maxResqueQueueLength },
])
);
nodeStatus = StatusMessages.unhealthy;
problems.push(`Resque Queues over ${maxResqueQueueLength} jobs`);
}
}
async run(data) {
data.response.uptime = new Date().getTime() - api.bootTime;
data.response.nodeStatus = data.connection.localize("Node Healthy");
data.response.problems = [];
data.response.id = id;
data.response.actionheroVersion = actionheroVersion;
data.response.name = packageJSON.name;
data.response.description = packageJSON.description;
data.response.version = packageJSON.version;
await this.checkRam(data);
await this.checkResqueQueues(data);
return {
id: id,
actionheroVersion: actionheroVersion,
name: packageJSON.name as string,
description: packageJSON.description as string,
version: packageJSON.version as string,
uptime: new Date().getTime() - api.bootTime,
consumedMemoryMB,
resqueTotalQueueLength,
nodeStatus,
problems,
};
}
};
}
import * as path from "path";
import * as fs from "fs";
import { PackageJson } from "type-fest";
import { ActionheroLogLevel } from "actionhero";
const namespace = "general";
declare module "actionhero" {
export interface ActionheroConfigInterface {
[namespace]: ReturnType<typeof DEFAULT[typeof namespace]>;
}
}
export const DEFAULT = {
general: (config) => {
const packageJSON = JSON.parse(
[namespace]: () => {
const packageJSON: PackageJson = JSON.parse(
fs

@@ -15,2 +25,5 @@ .readFileSync(path.join(__dirname, "..", "..", "package.json"))

serverName: packageJSON.name,
// you can manually set the server id (not recommended)
id: undefined as string,
welcomeMessage: `Welcome to the ${packageJSON.name} api`,
// A unique token to your application that servers will use to authenticate to each other

@@ -32,6 +45,6 @@ serverToken: "change-me",

enableResponseLogging: false,
// params you would like hidden from any logs
filteredParams: [],
// responses you would like hidden from any logs
filteredResponse: [],
// params you would like hidden from any logs. Can be an array of strings or a method that returns an array of strings.
filteredParams: [] as string[] | (() => string[]),
// responses you would like hidden from any logs. Can be an array of strings or a method that returns an array of strings.
filteredResponse: [] as string[] | (() => string[]),
// values that signify missing params

@@ -42,3 +55,3 @@ missingParamChecks: [null, "", undefined],

// What log-level should we use for file requests?
fileRequestLogLevel: "info",
fileRequestLogLevel: "info" as ActionheroLogLevel,
// The default priority level given to middleware of all types (action, connection, say, and task)

@@ -63,3 +76,2 @@ defaultMiddlewarePriority: 100,

plugin: [path.join(process.cwd(), "node_modules")],
locale: [path.join(process.cwd(), "locales")],
test: [path.join(process.cwd(), "__tests__")],

@@ -81,3 +93,3 @@ // for the src and dist paths, assume we are running in compiled mode from `dist`

export const test = {
general: (config) => {
[namespace]: () => {
return {

@@ -89,5 +101,2 @@ serverToken: `serverToken-${process.env.JEST_WORKER_ID || 0}`,

},
paths: {
locale: [path.join(process.cwd(), "locales")],
},
rpcTimeout: 3000,

@@ -99,3 +108,3 @@ };

export const production = {
general: (config) => {
[namespace]: () => {
return {

@@ -102,0 +111,0 @@ fileRequestLogLevel: "debug",

@@ -0,6 +1,19 @@

import { ActionProcessor, Connection } from "actionhero";
const namespace = "errors";
declare module "actionhero" {
export interface ActionheroConfigInterface {
[namespace]: ReturnType<typeof DEFAULT[typeof namespace]>;
}
}
export const DEFAULT = {
errors: (config) => {
[namespace]: () => {
return {
_toExpand: false,
// Should error types of "unknownAction" be included to the Exception handlers?
reportUnknownActions: false,
// ///////////////

@@ -12,3 +25,3 @@ // SERIALIZERS //

servers: {
web: (error) => {
web: (error: NodeJS.ErrnoException) => {
if (error.message) {

@@ -20,3 +33,3 @@ return String(error.message);

},
websocket: (error) => {
websocket: (error: NodeJS.ErrnoException) => {
if (error.message) {

@@ -28,3 +41,3 @@ return String(error.message);

},
socket: (error) => {
socket: (error: NodeJS.ErrnoException) => {
if (error.message) {

@@ -36,3 +49,3 @@ return String(error.message);

},
specHelper: (error) => {
specHelper: (error: NodeJS.ErrnoException) => {
if (error.message) {

@@ -46,3 +59,4 @@ return "Error: " + String(error.message);

// See ActionProcessor#applyDefaultErrorLogLineFormat to see an example of how to customize
actionProcessor: null,
actionProcessor:
null as ActionProcessor<any>["applyDefaultErrorLogLineFormat"],
},

@@ -55,33 +69,28 @@

// When a params for an action is invalid
invalidParams: (data, validationErrors) => {
if (validationErrors.length >= 0) {
return validationErrors[0];
}
return data.connection.localize("actionhero.errors.invalidParams");
invalidParams: (
data: ActionProcessor<any>,
validationErrors: Array<string | Error>
) => {
if (validationErrors.length >= 0) return validationErrors[0];
return "validation error";
},
// When a required param for an action is not provided
missingParams: (data, missingParams) => {
return data.connection.localize([
"actionhero.errors.missingParams",
{ param: missingParams[0] },
]);
missingParams: (data: ActionProcessor<any>, missingParams: string[]) => {
return `${missingParams[0]} is a required parameter for this action`;
},
// user requested an unknown action
unknownAction: (data) => {
return data.connection.localize("actionhero.errors.unknownAction");
unknownAction: (data: ActionProcessor<any>) => {
return `unknown action or invalid apiVersion`;
},
// action not useable by this client/server type
unsupportedServerType: (data) => {
return data.connection.localize([
"actionhero.errors.unsupportedServerType",
{ type: data.connection.type },
]);
unsupportedServerType: (data: ActionProcessor<any>) => {
return `this action does not support the ${data.connection.type} connection type`;
},
// action failed because server is mid-shutdown
serverShuttingDown: (data) => {
return data.connection.localize("actionhero.errors.serverShuttingDown");
serverShuttingDown: (data: ActionProcessor<any>) => {
return `the server is shutting down`;
},

@@ -91,6 +100,4 @@

// limit defined in api.config.general.simultaneousActions
tooManyPendingActions: (data) => {
return data.connection.localize(
"actionhero.errors.tooManyPendingActions"
);
tooManyPendingActions: (data: ActionProcessor<any>) => {
return `you have too many pending requests`;
},

@@ -101,3 +108,6 @@

// an error to the client. Response can be edited here, status codes changed, etc.
async genericError(data, error) {
async genericError(
data: ActionProcessor<any>,
error: NodeJS.ErrnoException
) {
return error;

@@ -112,17 +122,14 @@ },

// You may want to load in the content of 404.html or similar
fileNotFound: (connection) => {
return connection.localize(["actionhero.errors.fileNotFound"]);
fileNotFound: (connection: Connection) => {
return `that file is not found`;
},
// user didn't request a file
fileNotProvided: (connection) => {
return connection.localize("actionhero.errors.fileNotProvided");
fileNotProvided: (connection: Connection) => {
return `file is a required param to send a file`;
},
// something went wrong trying to read the file
fileReadError: (connection, error) => {
return connection.localize([
"actionhero.errors.fileReadError",
{ error: String(error) },
]);
fileReadError: (connection: Connection, error: NodeJS.ErrnoException) => {
return `error reading file: ${error?.message ?? error}`;
},

@@ -134,49 +141,35 @@

verbNotFound: (connection, verb) => {
return connection.localize([
"actionhero.errors.verbNotFound",
{ verb: verb },
]);
verbNotFound: (connection: Connection, verb: string) => {
return `verb not found or not allowed (${verb})`;
},
verbNotAllowed: (connection, verb) => {
return connection.localize([
"actionhero.errors.verbNotAllowed",
{ verb: verb },
]);
verbNotAllowed: (connection: Connection, verb: string) => {
return `verb not found or not allowed (${verb})`;
},
connectionRoomAndMessage: (connection) => {
return connection.localize(
"actionhero.errors.connectionRoomAndMessage"
);
connectionRoomAndMessage: (connection: Connection) => {
return `both room and message are required`;
},
connectionNotInRoom: (connection, room) => {
return connection.localize([
"actionhero.errors.connectionNotInRoom",
{ room: room },
]);
connectionNotInRoom: (connection: Connection, room: string) => {
return `connection not in this room (${room})`;
},
connectionAlreadyInRoom: (connection, room) => {
return connection.localize([
"actionhero.errors.connectionAlreadyInRoom",
{ room: room },
]);
connectionAlreadyInRoom: (connection: Connection, room: string) => {
return `connection already in this room (${room})`;
},
connectionRoomHasBeenDeleted: (room) => {
connectionRoomHasBeenDeleted: (room: string) => {
return "this room has been deleted";
},
connectionRoomNotExist: (room) => {
connectionRoomNotExist: (room: string) => {
return "room does not exist";
},
connectionRoomExists: (room) => {
connectionRoomExists: (room: string) => {
return "room exists";
},
connectionRoomRequired: (room) => {
connectionRoomRequired: () => {
return "a room is required";

@@ -183,0 +176,0 @@ },

import * as net from "net";
import * as tls from "tls";
import * as uuid from "uuid";
import { config, Server } from "actionhero";
import { ActionProcessor, config, Connection, Server } from "actionhero";
type RawConnection = net.Socket | (net.Socket & { socketDataString: string });
export class SocketServer extends Server {

@@ -56,3 +58,3 @@ constructor() {

this.server.on("error", (error) => {
this.server.on("error", (error: NodeJS.ErrnoException) => {
throw new Error(

@@ -67,7 +69,7 @@ `Cannot start socket server @ ${this.config.bindIP}:${this.config.port} => ${error.message}`

this.on("connection", async (connection) => {
this.on("connection", async (connection: Connection) => {
await this.onConnection(connection);
});
this.on("actionComplete", (data) => {
this.on("actionComplete", (data: ActionProcessor<any>) => {
if (data.toRender === true) {

@@ -84,3 +86,7 @@ data.response.context = "response";

async sendMessage(connection, message, messageId) {
async sendMessage(
connection: Connection,
message: Record<string, any>,
messageId: string | number
) {
if (message.error) {

@@ -108,9 +114,6 @@ message.error = config.errors.serializers.servers

// @ts-ignore
async goodbye(connection) {
async goodbye(connection: Connection) {
try {
connection.rawConnection.end(
JSON.stringify({
status: connection.localize("actionhero.goodbyeMessage"),
context: "api",
}) + "\r\n"
JSON.stringify({ status: "Bye", context: "api" }) + "\r\n"
);

@@ -122,3 +125,7 @@ } catch (e) {

async sendFile(connection, error, fileStream) {
async sendFile(
connection: Connection,
error: NodeJS.ErrnoException,
fileStream: any
) {
if (error) {

@@ -131,7 +138,7 @@ this.sendMessage(connection, error, connection.messageId);

handleConnection(rawConnection) {
handleConnection(rawConnection: RawConnection) {
if (this.config.setKeepAlive === true) {
rawConnection.setKeepAlive(true);
}
rawConnection.socketDataString = "";
rawConnection["socketDataString"] = "";
const id = uuid.v4();

@@ -147,3 +154,3 @@ this.buildConnection({

async onConnection(connection) {
async onConnection(connection: Connection) {
connection.params = {};

@@ -169,5 +176,4 @@

);
connection.rawConnection.socketDataString = connection.rawConnection.socketDataString.slice(
index + d.length
);
connection.rawConnection.socketDataString =
connection.rawConnection.socketDataString.slice(index + d.length);
const lines = data.split(d);

@@ -187,3 +193,3 @@ for (const i in lines) {

connection.rawConnection.on("error", (e) => {
connection.rawConnection.on("error", (e: NodeJS.ErrnoException) => {
if (connection.destroyed !== true) {

@@ -199,3 +205,3 @@ this.log("socket error: " + e, "error");

async parseLine(connection, line) {
async parseLine(connection: Connection, line: string) {
if (this.config.maxDataLength > 0) {

@@ -223,3 +229,3 @@ const blen = Buffer.byteLength(line, "utf8");

async parseRequest(connection, line) {
async parseRequest(connection: Connection, line: string) {
const words = line.split(" ");

@@ -275,3 +281,3 @@ const verb = words.shift();

checkBreakChars(chunk) {
checkBreakChars(chunk: Buffer) {
let found = false;

@@ -278,0 +284,0 @@ const hexChunk = chunk.toString("hex", 0, chunk.length);

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc