From 05337261ffb4abfb0fbbb9c775ccad0e28645692 Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Wed, 22 Oct 2014 08:35:44 -0400 Subject: Begin rewrite for new API --- .gitignore | 2 + .travis.yml | 3 ++ Gruntfile.js | 20 +++++++++ active911.js | 106 --------------------------------------------- examples/sample_app.js | 14 ++++++ lib/Agency.js | 45 +++++++++++++++++++ lib/Device.js | 12 +++++ lib/active911.js | 63 +++++++++++++++++++++++++++ lib/util.js | 3 ++ package.json | 19 +++++++- tests/active911.js | 105 ++++++++++++++++++++++++++++++++++++++++++++ tests/replies/agency.json | 24 ++++++++++ tests/replies/device.json | 19 ++++++++ tests/replies/devices.json | 0 tests/replies/error.json | 4 ++ 15 files changed, 331 insertions(+), 108 deletions(-) create mode 100644 .travis.yml create mode 100644 Gruntfile.js delete mode 100644 active911.js create mode 100644 examples/sample_app.js create mode 100644 lib/Agency.js create mode 100644 lib/Device.js create mode 100644 lib/active911.js create mode 100644 lib/util.js create mode 100644 tests/active911.js create mode 100644 tests/replies/agency.json create mode 100644 tests/replies/device.json create mode 100644 tests/replies/devices.json create mode 100644 tests/replies/error.json diff --git a/.gitignore b/.gitignore index eb5deef..5db9d70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ npm-debug.log +node_modules +.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..982fee7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "0.10" diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..34f8dc8 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,20 @@ +module.exports = function (grunt) { + + grunt.initConfig({ + jshint: { + all: ['lib/*.js', 'examples/*.js'] + }, + mochaTest: { + test: { + options: { + reporter: 'spec' + }, + src: ['tests/*.js'] + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-mocha-test'); + grunt.registerTask('default', ['jshint', 'mochaTest']); +}; diff --git a/active911.js b/active911.js deleted file mode 100644 index df2e2c6..0000000 --- a/active911.js +++ /dev/null @@ -1,106 +0,0 @@ -var http = require('https'); -var querystring = require('querystring'); - -module.exports = function (app_key, api_key) { - - // this is used to run all server commands - var server_command = function (data, callback) { - - // add keys to the POST request - data.app_key = app_key; - data.api_key = api_key; - - // convert the object to a querystring for POSTing - var post = querystring.stringify(data); - - // HTTP request configuration - var options = { - host: 'access.active911.com', - port: '443', - path: '/interface/open_api.php', - method: 'POST', - headers: { - 'Content-Length': post.length - } - }; - - // set up the request - var request = http.request(options, function (res) { - - res.setEncoding('utf8'); - - var json = ''; - res.on('data', function (chunk) { - json += chunk; - }); - - res.on('end', function () { - - var response = JSON.parse(json); - - if (response.result === 'error') { - callback(response.message); - } else { - return callback(false, response.message); - } - }); - - res.on('error', function (err) { - callback(err); - }); - }); - - request.write(post); - request.end(); - - }; - - return { - - ping: function (callback) { - var options = { - 'operation': 'ping' - }; - - server_command(options, callback); - }, - - getDevice: function (device_id, callback) { - var options = { - 'operation': 'get_device', - 'device_id': device_id - }; - - server_command(options, callback); - }, - - getAlert: function (alert_id, callback) { - var options = { - 'operation': 'get_alert', - 'alert_id': alert_id - }; - - server_command(options, callback); - }, - - getLocations: function (north, south, east, west, callback) { - var options = { - 'operation': 'get_locations', - 'north': north, - 'south': south, - 'east': east, - 'west': west - }; - - // use an intermediate callback to extract the location array - server_command(options, function (err, response) { - if (err) { - return callback(err); - } else { - return callback(err, response.locations); - } - }); - } - - }; -}; diff --git a/examples/sample_app.js b/examples/sample_app.js new file mode 100644 index 0000000..a08b039 --- /dev/null +++ b/examples/sample_app.js @@ -0,0 +1,14 @@ +var Active911 = require('../lib/active911').Active911; + +var oauth_id = '12345'; +var oauth_secret = 'asdf'; +var oauth_scope = 'read_agency read_alert read_device'; + +var api = new Active911(oauth_id, oauth_secret, oauth_scope); +var uri = api.getAuthorizationUri(); +console.log('Please go to ' + uri + ' to authenticate.'); + +api.ready(function() { + console.log('OAuth workflow complete'); + +}); diff --git a/lib/Agency.js b/lib/Agency.js new file mode 100644 index 0000000..c22a141 --- /dev/null +++ b/lib/Agency.js @@ -0,0 +1,45 @@ +var _util = require('./util'); + +module.exports.Agency = function(options) { + this._id = options.id; + this._name = options.name; + this._address = options.address; + this._city = options.city; + this._state = options.state; + this._latitude = options.latitude; + this._longitude = options.longitude; + this._devices = options.devices; + this._uri = options.uri || _util.API_BASE + '/agency/' + options.id; +}; + +module.exports.Agency.prototype.getId = function() { + return this._id; +}; + +module.exports.Agency.prototype.getName = function() { + return this._name; +}; + +module.exports.Agency.prototype.getAddress = function() { + return this._address; +}; + +module.exports.Agency.prototype.getCity = function() { + return this._city; +}; + +module.exports.Agency.prototype.getState = function() { + return this._state; +}; + +module.exports.Agency.prototype.getLatitude = function() { + return this._latitude; +}; + +module.exports.Agency.prototype.getLongitude = function() { + return this._longitude; +}; + +module.exports.Agency.prototype.getDevices = function() { + return this._devices; +}; diff --git a/lib/Device.js b/lib/Device.js new file mode 100644 index 0000000..bfc4d34 --- /dev/null +++ b/lib/Device.js @@ -0,0 +1,12 @@ +var _util = require('./util'); + +module.exports.Device = function(options) { + this._id = options.id; + this._name = options.name; + this._latitude = options.latitude; + this._longitude = options.longitude; + this._position_accuracy = options.position_accuracy; + this._position_timestamp = options.position_timestamp; + this._agencies = options.agencies; + this._uri = options.uri || _util.API_BASE + '/devices/' + options.id; +}; diff --git a/lib/active911.js b/lib/active911.js new file mode 100644 index 0000000..e75eaca --- /dev/null +++ b/lib/active911.js @@ -0,0 +1,63 @@ +var OAuth2 = require('oauth').OAuth2; +var http = require('http'); +var events = require('events'); +var querystring = require('querystring'); +var url = require('url'); +var Agency = require('./Agency.js').Agency; +var Device = require('./Device.js').Device; +var _util = require('./util'); + +module.exports.Agency = Agency; +module.exports.Device = Device; + +// Constructor +module.exports.Active911 = function(client_id, client_secret, scope) { + this._client_id = client_id; + this._client_secret = client_secret; + this._scope = scope; + this._oauth_complete = false; + this._emitter = new events.EventEmitter(); + + // Create the HTTP server that will receive the OAuth code after the user + // has authenticated to Active911. + this._http_server = new http.Server(); + + // The request handler + this._http_server.on('request', function(req, res) { + + // The only thing we care about is the /?code=xxx + var qs = url.parse(req.url).query; + + // If a code has been sent, and we don't already have one, we are good + // to go. + if (qs.split('=')[0] === 'code' && !this._oauth_complete) { + this._oauth_code = qs.split('=')[1]; + this._oauth_complete = true; + emitter.emit('oauth_complete'); + } + + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end(); + }); + + // Once the server is listening, store its port number for use in auth uri + this._http_server.listen(0, function() { + + }); +}; + +module.exports.Active911.prototype.getAuthorizationUri = function() { + + var qs = querystring.stringify({ + client_id: this._client_id, + response_type: 'code', + redirection_uri: 'http://localhost:' + this._oauth_server_port, + scope: this._scope + }); + + return _util.API_BASE_NAME + '/open_api/authorize_agency.php' + qs; +}; + +module.exports.Active911.prototype.ready = function(func) { + emitter.on('oauth_complete', func); +}; diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 0000000..08efbfb --- /dev/null +++ b/lib/util.js @@ -0,0 +1,3 @@ +module.exports.API_BASE_NAME = 'https://access.active911.com'; +module.exports.API_BASE_PATH = '/interface/open_api/api'; +module.exports.API_BASE = exports.API_BASE_NAME + exports.API_BASE_PATH; diff --git a/package.json b/package.json index 5388a38..2bf3062 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,23 @@ "version": "0.1.1", "author": "Ben Burwell ", "description": "A simple interface to Active911", - "tags": [ "active911", "cad" ], - "main": "./active911.js", + "tags": [ "active911", "cad", "fire", "ems", "dispatching" ], + "main": "./lib/active911.js", + "dependencies": { + "oauth": ">=0.9.11" + }, + "devDependencies": { + "mocha": "latest", + "nock": "latest", + "should": "latest", + "grunt-mocha-test": "latest", + "grunt": "latest", + "grunt-cli": "latest", + "grunt-contrib-jshint": "latest" + }, + "scripts": { + "test": "grunt --verbose" + }, "repository": { "type": "git", "url": "https://github.com/benburwell/active911.git" diff --git a/tests/active911.js b/tests/active911.js new file mode 100644 index 0000000..309aafb --- /dev/null +++ b/tests/active911.js @@ -0,0 +1,105 @@ +require('should'); +require('nock'); +Active911 = require('../lib/active911.js'); + +var a911; + +var testDevicesResponse = require('replies/devices.json'); +var testAgencyResponse = require('replies/agency.json'); +var testDeviceResponse = require('replies/device.json'); +var testErrorResponse = require('replies/error.json'); + +describe('Active911 API', function() { + + beforeEach(function(done) { + a911 = new Active911(); + }); + + describe('#getAgency', function() { + + it('Should return correct agency data', function() { + + nock('https://access.active911.com') + .get('/interface/open_api/api/') + .replyWithFile(200, __dirname + 'replies/agency.json'}); + + a911.getAgency(function(err, agency) { + should.not.exist(err); + agency.should.equal(testAgencyResponse.message.agency); + }); + }); + + it('Should give an error if the API gives an error', function() { + nock('https://access.active911.com') + .get('/interface/open_api/api/') + .replyWithFile(400, __dirname + 'replies/error.json'); + + a911.getAgency(function(err, agency) { + err.should.equal(testErrorResponse.message); + should.not.exist(agency); + }); + }); + + }); + + describe('#getDevices', function() { + it('Should return correct device data', function() { + + nock('https://access.active911.com') + .get('/interface/open_api/api/') + .replyWithFile(400, __dirname + 'replies/agency.json'); + + a911.getDevices(function(err, devices) { + should.not.exist(err); + devices.should.equal(testDevicesResponse.message.devices); + }); + }); + + it('Should give an error if the API gives an error', function() { + nock('https://access.active911.com') + .get('/interface/open_api/api/') + .replyWithFile(400, __dirname + 'replies/error.json'); + + a911.getDevices(function(err, devices) { + err.should.equal(testErrorResponse.message); + should.not.exist(devices); + }); + }); + }); + + describe('#getDevice', function() { + it('Should return correct device data (String id)', function() { + nock('https://access.active911.com') + .get('/interface/open_api/api/devices/1') + .replyWithFile(200, __dirname + 'replies/device.json'); + + a911.getDevice('1', function(err, device) { + should.not.exist(err); + device.should.equal(testDeviceResponse.message.device); + }); + }); + + it('Should return correct device data (Number id)', function() { + nock('https://access.active911.com') + .get('/interface/open_api/api/devices/1') + .replyWithFile(200, __dirname + 'replies/device.json'); + + a911.getDevice(1, function(err, device) { + should.not.exist(err); + device.should.equal(testDeviceResponse.message.device); + }); + }); + + it('Should give an error if the API gives an error', function() { + nock('https://access.active911.com') + .get('/interface/open_api/api/devices/1') + .replyWithFile(400, __dirname + 'replies/error.json'); + + a911.getDevice(1, function(err, device) { + err.should.equal(testErrorResponse.message); + should.not.exist(device); + }); + }); + }); + +}); diff --git a/tests/replies/agency.json b/tests/replies/agency.json new file mode 100644 index 0000000..6c1b365 --- /dev/null +++ b/tests/replies/agency.json @@ -0,0 +1,24 @@ +{ + "result": "success", + "message": { + "agency": { + "id": "12345", + "name": "Test Agency", + "address": "123 Main St.", + "city": "City", + "state": "AZ", + "latitude": 40, + "longitude": 70, + "devices": [ + { + "id": 1, + "uri": "https://access.active911.com/interface/open_api/api/devices/1" + }, + { + "id": 2, + "uri": "https://access.active911.com/interface/open_api/api/devices/2" + } + ] + } + } +} diff --git a/tests/replies/device.json b/tests/replies/device.json new file mode 100644 index 0000000..b3dc1ee --- /dev/null +++ b/tests/replies/device.json @@ -0,0 +1,19 @@ +{ + "result": "success", + "message": { + "device": { + "id": 1, + "name": "Test Device", + "latitude": 41, + "longitude": 71, + "position_accuracy": "100", + "position_timestamp": "2014-01-01 00:00:00", + "agencies": [ + { + "id": "12345", + "uri": "https://access.active911.com/interface/open_api/api/" + } + ] + } + } +} diff --git a/tests/replies/devices.json b/tests/replies/devices.json new file mode 100644 index 0000000..e69de29 diff --git a/tests/replies/error.json b/tests/replies/error.json new file mode 100644 index 0000000..c9ccbef --- /dev/null +++ b/tests/replies/error.json @@ -0,0 +1,4 @@ +{ + "result": "error", + "message": "Test Error Message" +} -- cgit v1.2.3