SockExchange
Simple TCP Client/Server for NodeJS
SockExchange is a lightweight, zero-dependency abstraction layer around NodeJS sockets to setup quick TCP communication channels among your other Node apps. Normally, I'd use Redis or Apache Kafka for more advanced message brokering, but the benefit of sockets is that you get an immediate response.
Install
npm i sockexchange
yarn add sockexchange
Usage
import { Client, Server } from "sockexchange"
Logging
since 1.2.0
Sockexchange will only log messages if a logger is provided by you. This decision stems from the Java convention whereby the user is left to decide how logging is implemented. To implement logging, you must provide a class or object whose interface matches ours:
debug(...args: any[]): void
info(...args: any[]): void
warn(...args: any[]): void
error(...args: any[]): void
Set your log binding like this:
import { Log, ProvidedLogger } from 'sockexchange'
class MyLogger implements ProvidedLogger {
debug(...args: any[]) {
....
}
...
}
Log.setLogger(new MyLogger())
Server and Cient
There are two facets to this package: Server and Client.
Server
new Server(port: number, [options: ServerOptions])
To setup your TCP server, choose your port number and accept one or more "directives":
new Server(5555)
.accept("give-me-the-things", () => "the things")
.start()
You can chain as many server.accept()
calls together as you like. The callback accepts either a string, object or a promise that returns a string or object. Objects are automatically serialized to JSON strings for easy transport:
new Server(5555)
.accept("gimme-the-things", () => "the things")
.accept("gimme-some-json", gimmeJson)
.accept("something-async", gimmePromise)
.start()
function gimmeJson() {
return {
foo: "bar",
quux: 42,
baz: true
}
}
function gimmePromise() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(gimmeJson()), 1000)
})
}
Or, you can call server.acceptAny()
, and the input string isn't parsed. Instead, the same function is called for any accepted socket connection. Useful if your use case is to send a JSON string for processing and have the app hosting the server do the heavy lifting. If you use this, any server.accept()
directives are ignored.
new Server(5555)
.acceptAny(data => {
const fancyJson = delegateFn.processIncoming(data)
return fancyJson
})
.start()
There are event callbacks that you can use:
new Server(5555)
.onStart(() => console.log("Server started"))
.onClientConnected((port, host) => console.log(`Connection from ${host}:${port}`))
.onClientRejected((port, host) => console.log(`Rejected: ${host}:${port}`))
.onStop(() => console.log("Server stopped..."))
.accept("mahna-mahna", () => "doot-doo-da-doo-doo")
.start()
By default, only connections from localhost (127.0.0.1) are accepted. All other client connections are rejected.
Client
new Client(port: number, [options: ClientOptions])
Sending a request to your socket server is simple. Request the socket response as a string or JSON.
new Client(5555)
.requestString("gimme-the-things")
.then(str => console.log(str)) // "the things"
.catch(console.error)
Socket responses are promises. Perfect for async/await
.
async function getJsonFromSocket() {
const client = new Client(5555)
const json = await client.requestJson("gimme-json") // { foo: "bar", quux: 42, baz: true }
console.log(json.foo) // bar
console.log(json.quux) // 42
}
You can also listen for specific events. Connection errors are sent via callback, other errors will accumulate in the catch()
block.
new Client(5555, { logActivity: true })
.onConnect(err => err && console.error("Connection error"))
.onDestroy(() => console.log("Client disconnected"))
.requestJson("gimme-json")
.then(doTheThings)
.catch(console.error)
API
Server
constructor(port, [options])
port number/Uint16
- Port on which to bind your server
options ServerOptions
ServerOptions
-
logActivity
bool
Outputs logging to stdout [defaultfalse
] -
ipStack
number
IP version to use. Values4
or6
[default4
] -
localhostOnly
bool
Only grant connections from localhost [defaulttrue
] -
whitelist
string[]
List of IP addresses allowed to connect iflocalhostOnly
isfalse
. No subnet masks, e.g.1.2.3.4/8
, plain IP addresses only (for now) -
encoding
string
Encoding for inbound data. [defaultutf8
] -
clientRejectionMessage
string
Message to send to client when connection is rejected. [defaultConnection rejected
]
server.accept(directive, callback) : Server
Apply callback to accepted client connection that matches directive
-
directive
string
Incoming string to match to callback -
callback
function() => string | object | Promise<string|object>
callback to apply todirective
. Must return a string, object orPromise<string|object>
. Objects are automatically serialized to JSON
server.acceptAny(callback) : Server
Apply the same callback to all accepted client connections
-
callback
function(data: string) => string | object | Promise<string|object>
callback to apply todata
. Useful for processing data with a delegate function. Must return a string or object. Objects are automatically serialized to JSON
server.onStart(callback) : Server
Callback called when the server is successfully started
-
callback
function() => void
server.onClientConnected(callback): Server
Called when a client has successfully connected
-
callback
function(port?: number, host?: string) => void
server.onClientRejected(callback): Server
Called when a client connection has been rejected
-
callback
function(port?: number, host?: string) => void
server.onStop(callback): Server
Called when server has stopped
-
callback
function() => void
server.start() : void
Start the server
Client
constructor(port, [options])
port number/Uint16
- TCP Port to which target server is bound
options ClientOptions
ClientOptions
-
logActivity
bool
Outputs logging to stdout [defaultfalse
] -
ipStack
number
IP version to use. Values4
or6
[default4
] -
outboundEncoding
string
Encoding for outbound data. [defaultutf8
] -
inboundEncoding
string
Encoding for inbound data. [defaultutf8
]
client.onConnect(err) : Client
Called when the client fails to connect
-
err
Error | undefined
Error thrown by connection attempt.
client.onDestroy() : Client
Called when the client socket has been destroyed
client.requestString(directive) : Promise<string>
Request the socket server return a string based on directive
-
directive
string
Directive to send the server. If the server employsserver.acceptAny()
, you may pass an empty string... or anything, really.
client.requestJson(directive) : Promise<object>
Request the socket server return a JSON object. In reality, the server always returns a Buffer
, but this buffer is parsed for JSON. An error is thrown if the JSON is invalid. Make sure to catch()
.
-
directive
string
Directive to send the server
client.request(directive) : Promise<SocketResponse>
Make a request to the server. This is the primitive method that you can use if you want to inspect the actual string that your socket server emits. Call toString()
or toJson()
on the SocketResponse object to parse your data.
-
directive
string
Directive to send the server
socketResponse.toString() : string
Returns the string representation of the data sent to the client. Encoded as directed by clientOptions.inboundEncoding
. This is the same value returned from client.requestString()
socketResponse.toJson() : object
Returns the parsed JSON from inbound data. This returns the same value as client.requestJson()
client.destroy() : void
Destroy (close) the client socket
License
MIT