actionhero-client
Advanced tools
| machine: | ||
| node: | ||
| version: 8 |
+282
| 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 |
+231
| 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() |
+19
-13
| { | ||
| "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. | ||
|   [](https://travis-ci.org/evantahler/actionhero-client) | ||
|   [](https://greenkeeper.io/) [](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() | ||
| ``` |
+60
-60
@@ -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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
37736
6.87%169
4.97%521
-1.7%1
Infinity%