Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

actionhero-client

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

actionhero-client - npm Package Compare versions

Comparing version
5.0.0
to
6.0.0
+3
circle.yml
machine:
node:
version: 8
const net = require('net')
const tls = require('tls')
const util = require('util')
const EventEmitter = require('events').EventEmitter
class ActionheroNodeClient extends EventEmitter {
constructor () {
super()
this.connected = false
this.connection = null
this.params = {}
this.details = null
this.lastLine = null
this.stream = ''
this.log = []
this.reconnectAttempts = 0
this.messageCount = 0
this.expectedResponses = {}
this.startingTimeStamps = {}
this.responseTimesouts = {}
}
defaults () {
return {
host: '127.0.0.1',
port: 5000,
delimiter: '\r\n',
logLength: 10,
secure: false,
timeout: 5000,
reconnectTimeout: 1000,
reconnectAttempts: 10
}
}
async connect (params) {
if (this.connection) { delete this.connection }
if (params) { this.params = params }
const defaults = this.defaults()
for (let i in defaults) {
if (this.params[i] === null || this.params[i] === undefined) { this.params[i] = defaults[i] }
}
await new Promise((resolve) => {
if (this.params.secure === true) {
this.connection = tls.connect(this.params.port, this.params.host)
} else {
this.connection = net.connect(this.params.port, this.params.host)
}
this.connection.setEncoding('utf8')
this.connection.on('data', (chunk) => {
this.emit('rawData', String(chunk))
this.stream += String(chunk)
let delimited = false
if (this.stream.indexOf(this.params.delimiter) >= 0) { delimited = true }
if (delimited === true) {
try {
let lines = this.stream.split(this.params.delimiter)
for (let j in lines) {
let line = lines[j]
if (line.length > 0) { this.handleData(line) }
}
this.stream = ''
} catch (e) {
this.lastLine = null
}
}
})
this.connection.on('connect', async (error) => {
if (error) { this.emit('error', error) }
this.connected = true
this.messageCount = 0
this.reconnectAttempts = 0
await this.detailsView()
this.emit('connected')
return resolve()
})
this.connection.on('error', (error) => {
this.emit('error', error)
this.reconnect()
})
this.connection.on('end', () => {
if (this.connected) { this.connected = false }
this.emit('end')
this.connection.removeAllListeners('data')
this.connection.removeAllListeners('error')
this.connection.removeAllListeners('end')
this.reconnect()
})
})
return this.details
}
async reconnect () {
this.reconnectAttempts++
if (this.reconnectAttempts > this.params.reconnectAttempts) {
this.emit('error', new Error('maximim reconnectAttempts reached'))
} else if (this.connected === false && this.params.reconnectTimeout && this.params.reconnectTimeout > 0) {
await util.promisify(setTimeout)(this.params.reconnectTimeout)
return this.connect()
}
}
async disconnect () {
this.connected = null
this.send('exit')
return util.promisify(setTimeout)(10)
}
handleData (data) {
let line
try {
line = JSON.parse(data)
this.lastLine = line
} catch (e) {
return this.emit('error', e, data)
}
this.emit('data', line)
this.addLog(line)
if (line.messageCount && line.messageCount > this.messageCount) {
this.messageCount = line.messageCount
}
// welcome message; indicates successfull connection
if (line.context === 'api' && line.welcome) {
return this.emit('welcome', line.welcome)
}
// "say" messages from other users
if (line.context === 'user') {
return this.emit('say', {
timeStamp: new Date(),
message: line.message,
from: line.from
})
}
// responses to your actions
if (line.context === 'response') {
const resolveResponse = this.expectedResponses[line.messageCount]
if (resolveResponse) {
clearTimeout(this.responseTimesouts[line.messageCount])
let delta = new Date().getTime() - this.startingTimeStamps[line.messageCount]
let error = null
if (line.error) {
error = new Error(line.error)
} else if (line.status && line.status !== 'OK') {
error = new Error(line.status)
}
resolveResponse({error, data: line, delta})
delete this.expectedResponses[line.messageCount]
delete this.startingTimeStamps[line.messageCount]
delete this.responseTimesouts[line.messageCount]
}
}
}
send (string) {
this.connection.write(string + '\r\n')
}
async awaitServerResponse (msg) {
if (!this.connected) { throw new Error('Not Connected') }
this.messageCount++
let responseId = this.messageCount
this.send(msg)
let response = await new Promise((resolve, reject) => {
this.expectedResponses[responseId] = resolve
this.startingTimeStamps[responseId] = new Date().getTime()
this.responseTimesouts[responseId] = setTimeout(
() => {
delete this.expectedResponses[responseId]
delete this.startingTimeStamps[responseId]
delete this.expectedResponses[responseId]
this.emit('timeout', msg)
reject(new Error(`Timeout reached (${msg})`))
}, this.params.timeout)
})
return response
}
async documentation () {
return this.awaitServerResponse('documentation')
}
async paramAdd (key, value) {
if (!key) { throw new Error('key is required') }
if (!value) { throw new Error('value is required') }
return this.awaitServerResponse(`paramAdd ${key}=${value}`)
}
async paramDelete (key) {
if (!key) { throw new Error('key is required') }
return this.awaitServerResponse(`paramDelete ${key}`)
}
async paramsDelete () {
return this.awaitServerResponse('paramsDelete')
}
async paramView (k) {
return this.awaitServerResponse(`paramView ${k}`)
}
async paramsView () {
return this.awaitServerResponse('paramsView')
}
async detailsView () {
const details = await this.awaitServerResponse('detailsView')
this.details = details.data
this.id = details.data.data.id
return details.data
}
async roomAdd (room) {
if (!room) { throw new Error('room is required') }
return this.awaitServerResponse(`roomAdd ${room}`)
}
async roomLeave (room) {
if (!room) { throw new Error('room is required') }
return this.awaitServerResponse(`roomLeave ${room}`)
}
async roomView (room) {
if (!room) { throw new Error('room is required') }
return this.awaitServerResponse(`roomView ${room}`)
}
async say (room, message) {
if (!room) { throw new Error('room is required') }
if (!message) { throw new Error('message is required') }
return this.awaitServerResponse(`say ${room} ${message}`)
}
async action (action) {
if (!action) { throw new Error('action is required') }
return this.awaitServerResponse(action)
}
async actionWithParams (action, params) {
if (!action) { throw new Error('action is required') }
if (!params) { throw new Error('params are required') }
return this.awaitServerResponse(JSON.stringify({action, params}))
}
async file (file) {
if (!file) { throw new Error('file is required') }
this.awaitServerResponse(`file ${file}`)
}
addLog (entry) {
this.log.push({
timestamp: new Date(),
data: entry
})
while (this.log.length > this.params.logLength) { this.log.splice(0, 1) }
}
}
module.exports = ActionheroNodeClient
const should = require('should')
const path = require('path')
const ActionheroNodeClient = require(path.join(__dirname, '/../lib/client.js'))
const ActionHero = require('actionhero')
const actionhero = new ActionHero.Process()
const port = 9000
process.env.ACTIONHERO_CONFIG = path.join(__dirname, '..', 'node_modules', 'actionhero', 'config')
const serverConfig = {
general: {
id: 'test-server-1',
workers: 1,
developmentMode: false,
startingChatRooms: {
'defaultRoom': {},
'otherRoom': {}
}
},
servers: {
web: { enabled: false },
websocket: { enabled: false },
socket: {
enabled: true,
secure: false,
port: port
}
}
}
const connectionParams = {
host: '0.0.0.0',
port: port,
timeout: 1000
}
let api
let client
describe('integration', () => {
before(async () => { api = await actionhero.start({configChanges: serverConfig}) })
after(async () => { await actionhero.stop() })
beforeEach(async () => {
client = new ActionheroNodeClient()
await client.connect(connectionParams)
})
afterEach(async () => {
await client.disconnect()
})
it('actionhero server should have booted', () => {
api.should.be.an.instanceOf(Object)
})
it('can get my connection details', async () => {
let details = await client.detailsView()
details.status.should.equal('OK')
details.context.should.equal('response')
details.data.totalActions.should.equal(0)
details.data.pendingActions.should.equal(0)
})
it('should log server messages internally', async () => {
client.log.length.should.be.above(0)
client.log[0].data.welcome.should.equal('Hello! Welcome to the actionhero api')
})
it('should be able to set params', async () => {
let {error, data} = await client.paramAdd('key', 'value')
should.not.exist(error)
data.status.should.equal('OK')
let {error: error2, data: data2} = await client.paramView('key')
should.not.exist(error2)
data2.data.should.equal('value')
let {error: error3, data: data3} = await client.paramsView()
should.not.exist(error3)
data3.data.key.should.equal('value')
})
it('can delete params and confirm they are gone', async () => {
let {error} = await client.paramAdd('key', 'value')
should.not.exist(error)
let {error: error2, data: data2} = await client.paramsDelete()
should.not.exist(error2)
data2.status.should.equal('OK')
let {error: error3, data: data3} = await client.paramsView()
should.not.exist(error3)
should.not.exist(data3.data.key)
})
it('can delete a param and confirm they are gone', async () => {
let {error: error1} = await client.paramAdd('key', 'v1')
should.not.exist(error1)
let {error: error2} = await client.paramAdd('value', 'v2')
should.not.exist(error2)
let {error: error3} = await client.paramDelete('key')
should.not.exist(error3)
let {error: error4, data} = await client.paramsView()
should.not.exist(error4)
Object.keys(data.data).length.should.equal(1)
data.data.value.should.equal('v2')
})
it('can run an action (success, simple params)', async () => {
let {error, data} = await client.action('status')
should.not.exist(error)
data.uptime.should.be.above(0)
data.context.should.equal('response')
})
it('can run an action (failure, simple params)', async () => {
let {error, data} = await client.action('cacheTest')
error.message.should.match(/key is a required parameter for this action/)
data.context.should.equal('response')
})
it('can run an action (success, complex params)', async () => {
var params = { key: 'mykey', value: 'myValue' }
let {error, data} = await client.actionWithParams('cacheTest', params)
should.not.exist(error)
data.context.should.equal('response')
data.cacheTestResults.saveResp.should.equal(true)
})
it('can run an action (failure, complex params)', async () => {
var params = { key: 'mykey', value: 'v' }
let {error, data} = await client.actionWithParams('cacheTest', params)
error.message.should.match(/inputs should be at least 3 letters long/)
data.context.should.equal('response')
})
it('can join a room', async () => {
await client.roomAdd('defaultRoom')
let {data} = await client.roomView('defaultRoom')
data.data.room.should.equal('defaultRoom')
data.data.membersCount.should.equal(1)
Object.keys(data.data.members).length.should.equal(1)
Object.keys(data.data.members)[0].should.equal(client.id)
})
it('can leave a room', async () => {
let {error: error1, data: data1} = await client.detailsView()
should.not.exist(error1)
data1.rooms.length.should.equal(0)
let {error: error2} = await client.roomAdd('defaultRoom')
should.not.exist(error2)
let {error: error3, data: data3} = await client.roomView('defaultRoom')
should.not.exist(error3)
Object.keys(data3.data.members).should.containEql(client.id)
let {error: error4} = await client.roomLeave('defaultRoom')
should.not.exist(error4)
let {error: error5, data: data5} = await client.detailsView()
should.not.exist(error5)
data5.rooms.length.should.equal(0)
})
it('will translate bad status to an error callback', async () => {
let {error} = await client.roomView('someCrazyRoom')
error.message.should.equal('connection not in this room (someCrazyRoom)')
})
it('will get SAY events', async () => {
await client.roomAdd('defaultRoom')
await new Promise((resolve) => {
client.on('say', (message) => {
client.removeAllListeners('say')
message.message.should.equal('TEST MESSAGE')
return resolve()
})
api.chatRoom.broadcast({}, 'defaultRoom', 'TEST MESSAGE')
})
})
it('will obey the server\'s simultaneousActions policy', async () => {
let count = 0
await new Promise(async (resolve) => {
function tryResolve (response) {
count++
if (count === 5) { resolve() }
}
client.actionWithParams('sleepTest', {sleepDuration: 100}).then(tryResolve)
client.actionWithParams('sleepTest', {sleepDuration: 200}).then(tryResolve)
client.actionWithParams('sleepTest', {sleepDuration: 300}).then(tryResolve)
client.actionWithParams('sleepTest', {sleepDuration: 400}).then(tryResolve)
client.actionWithParams('sleepTest', {sleepDuration: 500}).then(tryResolve)
let response6 = client.actionWithParams('sleepTest', {sleepDuration: 600})
await response6.then(({error, data}) => {
String(error).should.equal('Error: you have too many pending requests')
data.error.should.equal('you have too many pending requests')
})
})
})
it('will obey timeouts', async () => {
try {
await client.actionWithParams('sleepTest', {sleepDuration: 2 * 1000})
throw new Error('should not get here')
} catch (error) {
error.should.match(/Timeout reached/)
}
})
it('will obey timeouts again', async () => {
try {
await client.actionWithParams('sleepTest', {sleepDuration: 2 * 1000})
throw new Error('should not get here')
} catch (error) {
error.should.match(/Timeout reached/)
}
})
it('can reconnect')
})
+34
-41
var path = require('path')
const ActionheroNodeClient = require(path.join(__dirname, 'lib', 'client.js'))
var ActionheroClient = require(path.join(__dirname, '/lib/actionhero-client.js'))
var client = new ActionheroClient()
async function main () {
const client = new ActionheroNodeClient()
client.on('say', function (msgBlock) {
console.log(' > SAY: ' + msgBlock.message + ' | from: ' + msgBlock.from)
})
client.on('say', (message) => {
console.log(' > SAY: ' + message.message + ' | from: ' + message.from)
})
client.on('welcome', function (msg) {
console.log('WELCOME: ' + msg)
})
client.on('welcome', (welcome) => {
console.log('WELCOME: ' + welcome)
})
client.on('error', function (error, data) {
console.log('ERROR: ' + error)
if (data) { console.log(data) }
})
client.on('error', (error) => {
console.log('ERROR: ' + error)
})
client.on('end', function () {
console.log('Connection Ended')
})
client.on('end', () => {
console.log('Connection Ended')
})
client.on('timeout', function (error, request, caller) {
if (error) { throw error }
console.log(request + ' timed out')
})
client.on('timeout', (request, caller) => {
console.log(request + ' timed out')
})
client.connect({
host: '127.0.0.1',
port: '5000'
}, function () {
await client.connect({host: '127.0.0.1', port: '5000'})
// get details about myself
console.log(client.details)
console.log('My Details: ', client.details)
// try an action
var params = { key: 'mykey', value: 'myValue' }
client.actionWithParams('cacheTest', params, function (error, apiResponse, delta) {
if (error) { throw error }
console.log('cacheTest action response: ' + apiResponse.cacheTestResults.saveResp)
console.log(' ~ request duration: ' + delta + 'ms')
})
const params = { key: 'mykey', value: 'myValue' }
let {error, data, delta} = await client.actionWithParams('cacheTest', params)
if (error) { throw error }
console.log('cacheTest action response: ', data)
console.log(' ~ request duration: ', delta)
// join a chat room and talk
client.roomAdd('defaultRoom', function (error) {
if (error) { throw error }
client.say('defaultRoom', 'Hello from the actionheroClient')
client.roomLeave('defaultRoom')
})
await client.roomAdd('defaultRoom')
await client.say('defaultRoom', 'Hello from the actionheroClient')
await client.roomLeave('defaultRoom')
// leave
setTimeout(function () {
client.disconnect(function () {
console.log('all done!')
})
}, 1000)
})
await client.disconnect()
console.log('all done!')
}
main()
{
"author": "Evan Tahler <evantahler@gmail.com>",
"name": "actionhero-client",
"description": "actionhero client in JS for other node servers to use",
"version": "5.0.0",
"homepage": "https://github.com/evantahler/actionhero-client",
"description": "ActionHero client for other node.js servers to use",
"version": "6.0.0",
"homepage": "https://github.com/evantahler/actionhero-node-client",
"repository": {
"type": "git",
"url": "git://github.com/evantahler/actionhero-client.git"
"url": "git://github.com/evantahler/actionhero-node-client.git"
},
"main": "lib/actionhero-client.js",
"main": "lib/client.js",
"license": "Apache-2.0",

@@ -21,18 +21,24 @@ "keywords": [

"engines": {
"node": ">=4.0.0"
"node": ">=8.0.0"
},
"devDependencies": {
"actionhero": "git://github.com/evantahler/actionhero.git#master",
"mocha": "^3.2.0",
"should": "^11.1.1",
"standard": "^8.6.0"
"actionhero": "^18.0.0-beta.2",
"mocha": "^3.5.3",
"should": "^13.1.0",
"standard": "^10.0.3"
},
"standard": {
"globals":[
"it", "describe", "beforeEach"
"globals": [
"it",
"describe",
"beforeEach",
"before",
"after",
"afterEach"
]
},
"scripts": {
"test": "standard && mocha"
"pretest": "standard",
"test": "NODE_ENV=test mocha"
}
}
+92
-84

@@ -1,10 +0,11 @@

#ActionheroClient (for nodeJS)
# ActionheroNodeClient
For one node.js servers talking to another ActionHero server, over the socket protocol.
![NPM Version](https://img.shields.io/npm/v/actionhero-client.svg?style=flat) ![Node Version](https://img.shields.io/node/v/actionhero-client.svg?style=flat) [![Build Status](https://travis-ci.org/evantahler/actionhero-client.svg?branch=master)](https://travis-ci.org/evantahler/actionhero-client)
![NPM Version](https://img.shields.io/npm/v/actionhero-node-client.svg?style=flat) ![Node Version](https://img.shields.io/node/v/actionhero-node-client.svg?style=flat) [![Greenkeeper badge](https://badges.greenkeeper.io/actionhero/actionhero-node-client.svg)](https://greenkeeper.io/) [![Build Status](https://circleci.com/gh/actionhero/actionhero-node-client.png)](https://circleci.com/gh/actionhero/actionhero-node-client.png)
This library makes it easy for one nodeJS process to talk to a remote [actionhero](http://actionherojs.com/) server.
This library makes it easy for one nodeJS process to talk to a remote [actionhero](https://www.actionherojs.com/) server.
This library makes use of actionhero's TCP socket connections to enable fast, stateful connections. This library also allows for many concurrent and asynchronous requests to be running in parallel by making use of actionhero's message counter.
**note:** This Library is a server-server communication libary, and is NOT the same as the websocket client library that is generated via the actionhero server.
**note:** This Library is a server-server communication library, and is NOT the same as the websocket client library that is generated via the actionhero server.

@@ -16,3 +17,3 @@ ## Setup

```javascript
npm install --save actionhero-client
npm install --save actionhero-node-client
```

@@ -23,4 +24,4 @@

```javascript
var ActionheroClient = require("actionhero-client");
var client = new ActionheroClient();
var ActionheroNodeClient = require("actionhero-node-client");
var client = new ActionheroNodeClient();
```

@@ -31,6 +32,6 @@

```javascript
client.connect({
await client.connect({
host: "127.0.0.1",
port: "5000",
}, callback);
});
```

@@ -52,44 +53,54 @@

```
## Methods
One you are connected (by waiting for the "connected" event or using the `connect` callback), the following methods will be available to you:
* `async ActionheroNodeClient.connect()`
* `async ActionheroNodeClient.disconnect()`
* `{error, data, delta} = async ActionheroNodeClient.paramAdd(key, value)`
* remember that both key and value must pass JSON.stringify
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `{error, data, delta} = async ActionheroNodeClient.paramDelete(key)`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `{error, data, delta} = async ActionheroNodeClient.paramsDelete()`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `{error, data, delta} = async ActionheroNodeClient.paramView(key)`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `{error, data, delta} = async ActionheroNodeClient.paramsView()`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `{error, data, delta} = async ActionheroNodeClient.details()`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `{error, data, delta} = async ActionheroNodeClient.roomView(room)`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `{error, data, delta} = async ActionheroNodeClient.roomAdd(room)`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `{error, data, delta} = async ActionheroNodeClient.roomLeave(room)`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `{error, data, delta} = async ActionheroNodeClient.say(room, msg)`
* `msg` can be a string or an Object
* {error, data, delta} = await `ActionheroNodeClient.action(action)`
* this action method will not set or unset any params, and use those already set by `paramAdd`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
* `ActionheroNodeClient.actionWithParams(action, param)`
* this action will ignore any previously set params to the connection
* params is a hash of this form `{key: "myKey", value: "myValue"}`
* The return value will contain the response from the server (`data`), a possible error (`error`), and the response's duration (`delta`)
## Events
ActionheroClient will emit a few types of events (many of which are caught in the example below). Here are the events, and how you might catch them:
ActionheroNodeClient will emit a few types of events (many of which are caught in the example below). Here are the events, and how you might catch them:
* `client.on("connected", function(null){})`
* `client.on("end", function(null){})`
* `client.on("welcome", function(welcomeMessage){})`
* `client.on("connected")`
* `client.on("end")`
* `client.on("welcome", (welcomeMessage) => {})`
* welcomeMessage is a string
* `client.on("error", function(errorMessage){})`
* `client.on("error", (error) => {})`
* errorMessage is a string
* `client.on("say", function(messageBlock){})`
* messageBlock is a hash containing `timeStamp`, `room`, `from`, and `message`
* `client.on("timeout", function(err, request, caller){})`
* This is only emitted for connection errors, not errors to your requests/actions
* `client.on("say", (message) => {})`
* message is a hash containing `timeStamp`, `room`, `from`, and `message`
* `client.on("timeout", (error, request, caller) => {})`
* request is the string sent to the api
* caller (the calling function) is also returned to with an error
## Methods
One you are connected (by waiting for the "connected" event or using the `connect` callback), the following methods will be available to you:
* `ActionheroClient.disconnect(next)`
* `ActionheroClient.paramAdd(key,value,next)`
* remember that both key and value must pass JSON.stringify
* `ActionheroClient.paramDelete(key,next)`
* `ActionheroClient.paramsDelete(next)`
* `ActionheroClient.paramView(key,next)`
* `ActionheroClient.paramsView(next)`
* `ActionheroClient.details(next)`
* `ActionheroClient.roomView(room, next)`
* `ActionheroClient.roomAdd(room,next)`
* `ActionheroClient.roomLeave(room,next)`
* `ActionheroClient.say(room, msg, next)`
* `ActionheroClient.action(action, next)`
* this basic action method will not set or unset any params
* next will be passed (err, data, duration)
* `ActionheroClient.actionWithParams(action, params, next)`
* this action will ignore any previously set params to the connection
* params is a hash of this form `{key: "myKey", value: "myValue"}`
* next will be passed (err, data, duration)
Each callback will receive the full data hash returned from the server and a timestamp: `(err, data, duration)`
## Data

@@ -99,10 +110,10 @@

* `ActionheroClient.lastLine`
* `ActionheroNodeClient.lastLine`
* This is the last parsed JSON message received from the server (chronologically, not by messageID)
* `ActionheroClient.userMessages`
* `ActionheroNodeClient.userMessages`
* a hash which contains the latest `say` message from all users
* `ActionheroClient.log`
* `ActionheroNodeClient.log`
* An array of the last n parsable JSON replies from the server
* each entry is of the form {data, timeStamp} where data was the server's full response
* `ActionheroClient.messageCount`
* `ActionheroNodeClient.messageCount`
* An integer counting the number of messages received from the server

@@ -113,54 +124,51 @@

```javascript
var ActionheroClient = require("actionhero-client");
var client = new ActionheroClient();
var ActionheroNodeClient = require("actionhero-node-client");
client.on("say", function(msgBlock){
console.log(" > SAY: " + msgBlock.message + " | from: " + msgBlock.from);
});
async function main () {
const client = new ActionheroNodeClient()
client.on("welcome", function(msg){
console.log("WELCOME: " + msg);
});
client.on('say', (message) => {
console.log(' > SAY: ' + message.message + ' | from: ' + message.from)
})
client.on("error", function(err, data){
console.log("ERROR: " + err);
if(data){ console.log(data); }
});
client.on('welcome', (welcome) => {
console.log('WELCOME: ' + welcome)
})
client.on("end", function(){
console.log("Connection Ended");
});
client.on('error', (error) => {
console.log('ERROR: ' + error)
})
client.on("timeout", function(err, request, caller){
console.log(request + " timed out");
});
client.on('end', () => {
console.log('Connection Ended')
})
client.connect({
host: "127.0.0.1",
port: "5000",
}, function(){
client.on('timeout', (request, caller) => {
console.log(request + ' timed out')
})
await client.connect({host: '127.0.0.1', port: '5000'})
// get details about myself
console.log(client.details);
console.log('My Details: ', client.details)
// try an action
var params = { key: "mykey", value: "myValue" };
client.actionWithParams("cacheTest", params, function(err, apiResponse, delta){
console.log("cacheTest action response: " + apiResponse.cacheTestResults.saveResp);
console.log(" ~ request duration: " + delta + "ms");
});
const params = { key: 'mykey', value: 'myValue' }
let {error, data, delta} = await client.actionWithParams('cacheTest', params)
if (error) { throw error }
console.log('cacheTest action response: ', data)
console.log(' ~ request duration: ', delta)
// join a chat room and talk
client.roomAdd("defaultRoom", function(err){
client.say("defaultRoom", "Hello from the ActionheroClient");
client.roomLeave("defaultRoom");
});
await client.roomAdd('defaultRoom')
await client.say('defaultRoom', 'Hello from the actionheroClient')
await client.roomLeave('defaultRoom')
// leave
setTimeout(function(){
client.disconnect(function(){
console.log("all done!");
});
}, 1000);
await client.disconnect()
console.log('all done!')
}
});
main()
```

@@ -1,60 +0,60 @@

exports.setup = {
ServerPrototype: require('../node_modules/actionhero/actionhero.js'),
serverConfigChanges: {
general: {
id: 'test-server-1',
workers: 1,
developmentMode: false,
startingChatRooms: {
'defaultRoom': {},
'otherRoom': {}
}
},
logger: { transports: null },
// logger: {
// transports: [
// function(api, winston){
// return new (winston.transports.Console)({
// colorize: true,
// level: 'info',
// timestamp: api.utils.sqlDateTime
// });
// }
// ]
// },
servers: {
web: { enabled: false },
websocket: { enabled: false },
socket: {
enabled: true,
secure: false,
port: 9000
}
}
},
startServer: function (callback) {
var self = this
if (!self.server) {
process.env.ACTIONHERO_CONFIG = process.cwd() + '/node_modules/actionhero/config/'
self.server = new self.ServerPrototype()
self.server.start({configChanges: self.serverConfigChanges}, function (err, api) {
self.api = api
callback(err, self.api)
})
} else {
process.nextTick(function () {
callback()
})
}
},
stopServer: function (callback) {
var self = this
self.server.stop(function () {
callback()
})
}
}
// exports.setup = {
// ServerPrototype: require('../node_modules/actionhero/actionhero.js'),
// serverConfigChanges: {
// general: {
// id: 'test-server-1',
// workers: 1,
// developmentMode: false,
// startingChatRooms: {
// 'defaultRoom': {},
// 'otherRoom': {}
// }
// },
// logger: { transports: null },
// // logger: {
// // transports: [
// // function(api, winston){
// // return new (winston.transports.Console)({
// // colorize: true,
// // level: 'info',
// // timestamp: api.utils.sqlDateTime
// // });
// // }
// // ]
// // },
// servers: {
// web: { enabled: false },
// websocket: { enabled: false },
// socket: {
// enabled: true,
// secure: false,
// port: 9000
// }
// }
// },
//
// startServer: function (callback) {
// var self = this
//
// if (!self.server) {
// process.env.ACTIONHERO_CONFIG = process.cwd() + '/node_modules/actionhero/config/'
// self.server = new self.ServerPrototype()
// self.server.start({configChanges: self.serverConfigChanges}, function (err, api) {
// self.api = api
// callback(err, self.api)
// })
// } else {
// process.nextTick(function () {
// callback()
// })
// }
// },
//
// stopServer: function (callback) {
// var self = this
//
// self.server.stop(function () {
// callback()
// })
// }
// }
sudo: false
language: node_js
node_js:
- "4"
- "6"
- "7"
var net = require('net')
var tls = require('tls')
var util = require('util')
var EventEmitter = require('events').EventEmitter
var Defaults = {
host: '127.0.0.1',
port: '5000',
delimiter: '\r\n',
logLength: 100,
secure: false,
timeout: 5000,
reconnectTimeout: 1000,
reconnectAttempts: 10
}
var ActionheroClient = function () {
EventEmitter.call(this)
this.connected = false
this.connection = null
this.params = {}
this.details = null
this.connectCallback = null
this.lastLine = null
this.stream = ''
this.log = []
this.reconnectAttempts = 0
this.messageCount = 0
this.userMessages = {}
this.expectedResponses = {}
this.startingTimeStamps = {}
this.responseTimesouts = {}
this.defaults = Defaults
}
util.inherits(ActionheroClient, EventEmitter)
ActionheroClient.prototype.connect = function (params, next) {
var self = this
if (self.connection) { delete self.connection }
if (params) { self.params = params }
if (typeof next === 'function') {
self.connectCallback = next
}
for (var i in self.defaults) {
if (self.params[i] === null || self.params[i] === undefined) {
self.params[i] = self.defaults[i]
}
}
if (self.params.secure === true) {
self.connection = tls.connect(self.params.port, self.params.host)
} else {
self.connection = net.connect(self.params.port, self.params.host)
}
self.connection.setEncoding('utf8')
self.connection.on('data', function (chunk) {
self.emit('rawData', String(chunk))
self.stream += String(chunk)
var delimited = false
if (self.stream.indexOf(self.params.delimiter) >= 0) {
delimited = true
}
if (delimited === true) {
try {
var lines = self.stream.split(self.params.delimiter)
for (var j in lines) {
var line = lines[j]
if (line.length > 0) {
self.handleData(line)
}
}
self.stream = ''
} catch (e) {
self.lastLine = null
}
}
})
self.connection.on('connect', function (error) {
if (error) { self.emit('error', error) }
self.connected = true
self.messageCount = 0
self.reconnectAttempts = 0
self.detailsView(function () {
self.emit('connected')
if (typeof self.connectCallback === 'function') {
self.connectCallback(null, self.lastLine.welcome)
delete self.connectCallback
}
})
})
self.connection.on('error', function (error) {
self.emit('error', error)
self.reconnect()
})
self.connection.on('end', function () {
if (self.connected !== null) { self.connected = false }
self.emit('end', null)
self.connection.removeAllListeners('data')
self.connection.removeAllListeners('error')
self.connection.removeAllListeners('end')
self.reconnect()
})
}
ActionheroClient.prototype.reconnect = function () {
var self = this
self.reconnectAttempts++
if (self.reconnectAttempts > self.params.reconnectAttempts) {
self.emit('error', new Error('maximim reconnectAttempts reached'))
} else if (self.connected === false && self.params.reconnectTimeout && self.params.reconnectTimeout > 0) {
setTimeout(function () {
self.connect()
}, self.params.reconnectTimeout)
}
}
ActionheroClient.prototype.disconnect = function (next) {
var self = this
self.connected = null
process.nextTick(function () {
self.send('exit')
if (typeof next === 'function') {
next()
}
})
}
ActionheroClient.prototype.handleData = function (data) {
try {
this.lastLine = JSON.parse(data)
} catch (e) {
this.emit('error', e, data)
}
this.emit('data', this.lastLine)
this.addLog(this.lastLine)
if (this.lastLine.messageCount && this.lastLine.messageCount > this.messageCount) {
this.messageCount = this.lastLine.messageCount
}
if (this.lastLine.context === 'api' && this.lastLine.welcome) {
// welcome message; indicates successfull connection
this.emit('welcome', this.lastLine.welcome)
// "say" messages from other users
} else if (this.lastLine.context === 'user') {
this.userMessages[this.lastLine.from] = {
timeStamp: new Date(),
message: this.lastLine.message,
from: this.lastLine.from
}
this.emit('say', this.userMessages[this.lastLine.from])
// responses to your actions
} else if (this.lastLine.context === 'response') {
if (this.expectedResponses[this.lastLine.messageCount]) {
clearTimeout(this.responseTimesouts[this.lastLine.messageCount])
var next = this.expectedResponses[this.lastLine.messageCount]
var delta = new Date().getTime() - this.startingTimeStamps[this.lastLine.messageCount]
delete this.expectedResponses[this.lastLine.messageCount]
delete this.startingTimeStamps[this.lastLine.messageCount]
delete this.responseTimesouts[this.lastLine.messageCount]
var error = null
if (this.lastLine.error) {
error = new Error(this.lastLine.error)
} else if (this.lastLine.status && this.lastLine.status !== 'OK') {
error = new Error(this.lastLine.status)
}
next(error, this.lastLine, delta)
}
}
}
ActionheroClient.prototype.send = function (str) {
this.connection.write(str + '\r\n')
}
ActionheroClient.prototype.registerResponseAndCall = function (msg, next) {
var self = this
if (self.connection) {
self.messageCount++
var responseID = self.messageCount
if (typeof next === 'function') {
self.expectedResponses[responseID] = next
self.startingTimeStamps[responseID] = new Date().getTime()
self.responseTimesouts[responseID] = setTimeout(
function (msg, next) {
var error = new Error('Timeout reached')
self.emit('timeout', error, msg, next)
next(error)
delete self.startingTimeStamps[responseID]
delete self.expectedResponses[responseID]
},
self.params.timeout, msg, next)
}
process.nextTick(function () {
self.send(msg)
})
} else {
self.emit('error', new Error('Not Connected'))
}
}
ActionheroClient.prototype.documentation = function (next) {
this.registerResponseAndCall('documentation', next)
}
ActionheroClient.prototype.paramAdd = function (k, v, next) {
if (k !== null && v !== null) {
this.registerResponseAndCall('paramAdd ' + k + '=' + v, next)
} else {
if (typeof next === 'function') { next(new Error('key and value are required'), null) }
}
}
ActionheroClient.prototype.paramDelete = function (k, next) {
if (k !== null) {
this.registerResponseAndCall('paramDelete ' + k, next)
} else {
if (typeof next === 'function') { next(new Error('key is required'), null) }
}
}
ActionheroClient.prototype.paramsDelete = function (next) {
this.registerResponseAndCall('paramsDelete', next)
}
ActionheroClient.prototype.paramView = function (k, next) {
this.registerResponseAndCall('paramView ' + k, next)
}
ActionheroClient.prototype.paramsView = function (next) {
this.registerResponseAndCall('paramsView', next)
}
ActionheroClient.prototype.roomView = function (room, next) {
this.registerResponseAndCall('roomView ' + room, next)
}
ActionheroClient.prototype.detailsView = function (next) {
var self = this
self.registerResponseAndCall('detailsView', function (error, data, delta) {
if (!error) {
self.details = data.data
self.id = data.data.id
}
next(error, data, delta)
})
}
ActionheroClient.prototype.roomAdd = function (room, next) {
this.registerResponseAndCall('roomAdd ' + room, next)
}
ActionheroClient.prototype.roomLeave = function (room, next) {
this.registerResponseAndCall('roomLeave ' + room, next)
}
ActionheroClient.prototype.say = function (room, msg, next) {
this.registerResponseAndCall('say ' + room + ' ' + msg, next)
}
ActionheroClient.prototype.action = function (action, next) {
this.registerResponseAndCall(action, next)
}
ActionheroClient.prototype.file = function (file, next) {
this.registerResponseAndCall('file ' + file, next)
}
ActionheroClient.prototype.actionWithParams = function (action, params, next) {
var msg = {
action: action,
params: params
}
this.registerResponseAndCall(JSON.stringify(msg), next)
}
ActionheroClient.prototype.addLog = function (entry) {
this.log.push({
timestamp: new Date(),
data: entry
})
if (this.log.length > this.params.logLength) {
this.log.splice(0, 1)
}
}
module.exports = ActionheroClient
var should = require('should')
var path = require('path')
var setup = require(path.join(__dirname, '/setup.js')).setup
var ActionheroClient = require(path.join(__dirname, '/../lib/actionhero-client.js'))
var client
var connectionParams = {
host: '0.0.0.0',
port: setup.serverConfigChanges.servers.socket.port,
timeout: 1000
}
describe('integration', function () {
beforeEach(function (done) {
client = new ActionheroClient()
setup.startServer(function () {
client.connect(connectionParams, function () {
done()
})
})
})
it('actionhero server should have booted', function (done) {
setup.api.should.be.an.instanceOf(Object)
done()
})
it('can get my connection details', function (done) {
client.detailsView(function (erroror, details, delta) {
should.not.exist(erroror)
details.status.should.equal('OK')
details.context.should.equal('response')
details.data.totalActions.should.equal(0)
details.data.pendingActions.should.equal(0)
done()
})
})
it('should log server messages internally', function (done) {
client.log.length.should.equal(2)
client.log[0].data.welcome.should.equal('Hello! Welcome to the actionhero api')
done()
})
it('should be able to set params', function (done) {
client.paramAdd('key', 'value', function (erroror, response) {
should.not.exist(erroror)
response.status.should.equal('OK')
client.paramsView(function (erroror, params) {
params.data.key.should.equal('value')
done()
})
})
})
it('can delete params and confirm they are gone', function (done) {
client.paramAdd('key', 'value', function (erroror, response) {
should.not.exist(erroror)
client.paramsDelete(function (erroror, response) {
should.not.exist(erroror)
response.status.should.equal('OK')
client.paramsView(function (erroror, params) {
should.not.exist(erroror)
should.not.exist(params.data.key)
done()
})
})
})
})
it('can delete a param and confirm they are gone', function (done) {
client.paramAdd('key', 'v1', function (erroror, response) {
should.not.exist(erroror)
client.paramAdd('value', 'v2', function (erroror, response) {
should.not.exist(erroror)
client.paramDelete('key', function (erroror, response) {
should.not.exist(erroror)
client.paramsView(function (erroror, params) {
Object.keys(params.data).length.should.equal(1)
params.data.value.should.equal('v2')
done()
})
})
})
})
})
it('can run an action (simple params)', function (done) {
client.action('status', function (erroror, apiResponse) {
should.not.exist(erroror)
apiResponse.uptime.should.be.above(0)
apiResponse.context.should.equal('response')
done()
})
})
it('can run an action (complex params)', function (done) {
var params = { key: 'mykey', value: 'myValue' }
client.actionWithParams('cacheTest', params, function (erroror, apiResponse) {
should.not.exist(erroror)
apiResponse.context.should.equal('response')
apiResponse.cacheTestResults.saveResp.should.equal(true)
done()
})
})
it('can join a room', function (done) {
client.roomAdd('defaultRoom', function (erroror, data) {
client.roomView('defaultRoom', function (erroror, data) {
Object.keys(data.data.members).length.should.equal(1)
Object.keys(data.data.members)[0].should.equal(client.id)
done()
})
})
})
it('can leave a room', function (done) {
client.detailsView(function (error, data) {
should.not.exist(error)
data.data.rooms.length.should.equal(0)
client.roomAdd('defaultRoom', function (error, data) {
should.not.exist(error)
client.roomView('defaultRoom', function (error, data) {
should.not.exist(error)
Object.keys(data.data.members).should.containEql(client.id)
client.roomLeave('defaultRoom', function (error, data) {
should.not.exist(error)
client.detailsView(function (error, data) {
should.not.exist(error)
data.data.rooms.length.should.equal(0)
done()
})
})
})
})
})
})
it('will translate bad status to an error callback', function (done) {
client.roomView('someCrazyRoom', function (error, data) {
String(error).should.equal('Error: not member of room someCrazyRoom')
data.status.should.equal('not member of room someCrazyRoom')
done()
})
})
it('will get SAY events', function (done) {
var used = false
client.roomAdd('defaultRoom', function () {
client.on('say', function (msgBlock) {
if (used === false) {
used = true
msgBlock.message.should.equal('TEST MESSAGE')
done()
}
})
setup.api.chatRoom.broadcast({}, 'defaultRoom', 'TEST MESSAGE')
})
})
it('will obey the servers simultaneousActions policy', function (done) {
client.actionWithParams('sleepTest', {sleepDuration: 500})
client.actionWithParams('sleepTest', {sleepDuration: 500})
client.actionWithParams('sleepTest', {sleepDuration: 500})
client.actionWithParams('sleepTest', {sleepDuration: 500})
client.actionWithParams('sleepTest', {sleepDuration: 500})
client.actionWithParams('sleepTest', {sleepDuration: 500}, function (error, apiResponse) {
String(error).should.equal('Error: you have too many pending requests')
apiResponse.error.should.equal('you have too many pending requests')
done()
})
})
it('will obey timeouts', function (done) {
client.actionWithParams('sleepTest', {sleepDuration: 2 * 1000}, function (error, apiResponse) {
String(error).should.equal('Error: Timeout reached')
should.not.exist(apiResponse)
done()
})
})
})
describe('connection and reconnection', function () {
// TODO
})

Sorry, the diff of this file is not supported yet