From 25ae4e9c132d6e68ddb389808b8f22c8f43b17cd Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Mon, 14 Dec 2015 22:23:28 -0500 Subject: Begin rewrite for new OAuth API --- .editorconfig | 11 +++ .npmignore | 3 + Gruntfile.js | 20 ------ LICENSE.md | 25 ++++--- README.md | 164 ++++++++++++--------------------------------- examples/oauth.js | 16 +++++ examples/sample_app.js | 14 ---- lib/Agency.js | 45 ------------- lib/Device.js | 12 ---- lib/active911.js | 63 +---------------- lib/clients/refresh.js | 118 ++++++++++++++++++++++++++++++++ lib/util.js | 3 - package.json | 67 +++++++++--------- tests/active911.js | 128 +++++++++-------------------------- tests/replies/devices.json | 0 15 files changed, 275 insertions(+), 414 deletions(-) create mode 100644 .editorconfig create mode 100644 .npmignore delete mode 100644 Gruntfile.js create mode 100644 examples/oauth.js delete mode 100644 examples/sample_app.js delete mode 100644 lib/Agency.js delete mode 100644 lib/Device.js create mode 100644 lib/clients/refresh.js delete mode 100644 lib/util.js delete mode 100644 tests/replies/devices.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5ed6bbc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +max_line_length = 80 + diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..a7abe3b --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +tests +examples + diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 34f8dc8..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,20 +0,0 @@ -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/LICENSE.md b/LICENSE.md index a77e2bf..29a4317 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,13 +1,18 @@ -Copyright 2013 Ben Burwell +The MIT License (MIT) Copyright (c) 2013-2015 Ben Burwell - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 9f3048e..6ec5caa 100644 --- a/README.md +++ b/README.md @@ -1,137 +1,57 @@ Active911 for Node.js ===================== -*Note:* This package was built for an older version of the Active911 API. It will be updated for the new version in the coming weeks. - -by Ben Burwell +by Ben Burwell Installation ------------ -Installation is simple: `npm install active911`. - -You can use it in your project by adding `active911` to your dependencies and running `npm install`. Sample ping: - - var active911 = require('active911')('YOUR_APP_KEY', 'YOUR_API_KEY'); - - active911.ping(function (err) { - if (err) { - return console.log('Could not connect to Active911: ' + err); - } - - console.log('Connection to Active911 established.'); - }); - -Usage ------ - -The `active911` package provides support for the current Active911 API functions. We follow the standard Node.js pattern of `function(options, callback(error, data))`. If an error occurs, the first parameter of the callback will contain the error message. Otherwise, the first parameter will be false and the second parameter will contain the API response. - -### `ping(callback)` - -The `ping` command checks that your API and app keys are valid on the server. It takes only a callback function with a parameter for errors. - -### `getDevice(device_id, callback)` - -Returns some data about the device associated with the `device_id`. - -Example response: - - { - "response_action" : "watch", - "response_alert_id" : 4681099, - "lat" : 44.541404, - "name" : "Joseph Sullivan", - "position_accuracy" : 30, - "response_stamp" : 1371315741, - "device_id" : 2435, - "position_stamp" : 1371463606, - "device_type" : "smartphone", - "lon" : -123.364192 - } - -### `getAlert(alert_id, callback)` - -Returns data about the alert associated with the `alert_id`. - -Example response: - - { - "source" : "", - "priority" : "F3", - "cad_code" : "", - "lat" : 44.5379997, - "place" : "", - "agency_id" : 3, - "state" : "OR", - "map_code" : "", - "city" : "Philomath", - "timestamp" : 1369781684, - "address" : "100 S. 16th ST", - "description" : "Cat in tree", - "details" : "", - "unit" : "", - "lon" : -123.3634479, - "cross_street" : "", - "alert_id" : 4264162, - "units" : "" - } - -### `getLocations(north, south, east, west, callback)` - -Returns an array of locations that exist within the area bounded by `north`, `south`, `east`, and `west`. The parameters should be floating point numbers representing the latitude or longitude, whichever is appropriate for the compass direction. - -For example, - - active911.getLocations(46.12345, 44.54321, -122.00021, -124.132132, function (err, locations) { - if (err) { - return console.log('Unable to retrieve locations: ' + err); - } - - console.log('Returned ' + locations.length + ' locations.'); - }); - -Example response: - - [ - { - "resources" : [], - "lat" : 44.540121, - "name" : "Pinehurst Memorial", - "agency_id" : 3, - "icon_filename" : "icon-flag.png", - "description" : "Main Station Hosp.\nNear the depot\n5 entrances", - "id" : 175, - "lon" : -123.367601, - "icon_color" : "blue" - }, - { - "resources" : [ - { - "extension" : "jpg", - "title" : "Photo", - "id" : 5, - "details" : "", - "size" : 963979 - } - ], - "lat" : 44.541252, - "name" : "Hydrant", - "agency_id" : 3, - "icon_filename" : "icon-hydrant.png", - "description" : "15th & College", - "id" : 700, - "lon" : -123.364689, - "icon_color" : "red" - } - ] +Installation is simple: `npm install --save active911`. + +Basic Usage +----------- + +```javascript +var Active911 = require('active911'); +var client = new Active911.RefreshClient('YOUR REFRESH TOKEN'); + +client.getAgency().then(function(agency) { + console.log(agency.name); +}).catch(function(err) { + console.log('Problem getting Agency details:', err); +}); +``` + +API Methods +----------- + +The following public API methods are available: + +* `getAgency()` +* `getDevice(device_id)` +* `getAlerts()` +* `getDeviceAlerts(device_id)` +* `getAlert(alert_id)` +* `getLocations()` +* `getLocation(location_id)` +* `getResource(resource_id)` + +Each method returns a promise for a result, which will resolve as either an +object or an array, depending on the cardinality (e.g. `getAlerts` resolves as +an array, while `getAlert` resolves as an object). + +For details on the format of the result, please see the [Active911 API +wiki](http://wiki.active911.com/wiki/index.php/Accessing_the_API). Contributing ------------ -Contributions are encouraged. For a list of open issues, see . +Contributions are encouraged. For a list of open issues, see +. More Information ---------------- -More information about the API is available on [the Active911 Github wiki](https://github.com/active911/open-api/wiki). +More information about the API is available on [the Active911 +wiki](http://wiki.active911.com/wiki/index.php/Active911_Developer_API). + diff --git a/examples/oauth.js b/examples/oauth.js new file mode 100644 index 0000000..575d0d0 --- /dev/null +++ b/examples/oauth.js @@ -0,0 +1,16 @@ +var Active911 = require('../'); +var client = new Active911.RefreshClient('YOUR REFRESH TOKEN'); + +client.getAgency().then(function(agency) { + console.log(agency.name); + agency.devices.forEach(function(deviceInfo) { + client.getDevice(deviceInfo.id).then(function(device) { + console.log(device.name); + }).catch(function(err) { + console.log('Error retrieving device info for #' + deviceInfo.id); + }); + }); +}).catch(function(err) { + console.log('Error retrieving agency info:', err); +}); + diff --git a/examples/sample_app.js b/examples/sample_app.js deleted file mode 100644 index a08b039..0000000 --- a/examples/sample_app.js +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index c22a141..0000000 --- a/lib/Agency.js +++ /dev/null @@ -1,45 +0,0 @@ -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 deleted file mode 100644 index bfc4d34..0000000 --- a/lib/Device.js +++ /dev/null @@ -1,12 +0,0 @@ -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 index e75eaca..42e51d0 100644 --- a/lib/active911.js +++ b/lib/active911.js @@ -1,63 +1,6 @@ -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() { - - }); +var Active911 = { + RefreshClient: require('./clients/refresh.js') }; -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 - }); +module.exports = Active911; - 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/clients/refresh.js b/lib/clients/refresh.js new file mode 100644 index 0000000..b4a3930 --- /dev/null +++ b/lib/clients/refresh.js @@ -0,0 +1,118 @@ +var request = require('request'); + +var RefreshClient = function(refreshString) { + if (typeof refreshString !== 'string') { + throw new Error('Refresh token must be supplied'); + } + this._refreshToken = refreshString; + this._accessToken = ''; + this._accessTokenExpiration = Math.floor( new Date()/1000 ); +}; + +RefreshClient.prototype._doRequest = function(url) { + var self = this; + var base = 'https://access.active911.com/interface/open_api/api'; + return new Promise(function(fulfill, reject) { + self._getAccessToken().then(function(token) { + request({ + method: 'GET', + url: base + url, + headers: { + 'Authorization': 'Bearer ' + token + } + }, function(err, res, body) { + if (err) { + reject(err); + } else { + var result = JSON.parse(body); + if (result.result == 'success') { + var properties = Object.getOwnPropertyNames(result.message); + if (properties.length == 0) { + fulfill({}); + } else { + var propertyToReturn = properties[0]; + fulfill(result.message[propertyToReturn]); + } + } else { + reject(result.message); + } + } + }); + }).catch(function(err) { + reject(err); + }); + }); +}; + +RefreshClient.prototype._getAccessToken = function() { + var self = this; + return new Promise(function(fulfill, reject) { + var currentTime = Math.floor( new Date()/1000 ); + if (self._accessTokenExpiration - currentTime <= 10) { + fulfill(self._refreshAccessToken()); + } else { + fulfill(self._accessToken); + } + }); +}; + +RefreshClient.prototype._refreshAccessToken = function() { + var self = this; + return new Promise(function(fulfill, reject) { + request({ + url: 'https://www.active911.com/interface/dev/api_access.php', + method: 'POST', + form: { + 'refresh_token': self._refreshToken + } + }, function(err, res, body) { + if (err) { + reject(err); + } else { + var response = JSON.parse(body); + if (response.access_token && response.expiration) { + self._accessToken = response.access_token; + self._accessTokenExpiration = response.expiration; + fulfill(response.access_token); + } else { + reject(new Error('Malformed access token response')); + } + } + }); + }); +}; + +RefreshClient.prototype.getAgency = function() { + return this._doRequest('/'); +}; + +RefreshClient.prototype.getDevice = function(id) { + return this._doRequest('/devices/' + id); +}; + +RefreshClient.prototype.getAlerts = function() { + return this._doRequest('/alerts'); +}; + +RefreshClient.prototype.getAlert = function(id) { + return this._doRequest('/alerts/' + id); +}; + +RefreshClient.prototype.getDeviceAlerts = function(id) { + return this._doRequest('/devices/' + id + '/alerts'); +}; + +RefreshClient.prototype.getLocations = function() { + return this._doRequest('/locations'); +}; + +RefreshClient.prototype.getLocation = function(id) { + return this._doRequest('/locations/' + id); +}; + +RefreshClient.prototype.getResource = function(id) { + return this._doRequest('/resources/' + id); +}; + +module.exports = RefreshClient; + diff --git a/lib/util.js b/lib/util.js deleted file mode 100644 index 08efbfb..0000000 --- a/lib/util.js +++ /dev/null @@ -1,3 +0,0 @@ -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 2bf3062..6473beb 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,38 @@ { - "name": "active911", - "version": "0.1.1", - "author": "Ben Burwell ", - "description": "A simple interface to Active911", - "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" - }, - "bugs": { - "url": "https://github.com/benburwell/active911/issues" - }, - "engines": { - "node": ">=0.10" - } + "name": "active911", + "version": "0.2.0", + "description": "A simple client for the Active911 API", + "main": "./lib/active911.js", + "scripts": { + "test": "mocha tests" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/benburwell/active911.git" + }, + "keywords": [ + "active911", + "client", + "ems", + "fire", + "response", + "api" + ], + "author": "Ben Burwell", + "license": "MIT", + "bugs": { + "url": "https://github.com/benburwell/active911/issues" + }, + "homepage": "https://github.com/benburwell/active911#readme", + "dependencies": { + "request": "^2.67.0" + }, + "engines": { + "node": ">=4.0.0" + }, + "devDependencies": { + "mocha": "^2.3.4", + "nock": "^3.4.0", + "should": "^8.0.1" + } } diff --git a/tests/active911.js b/tests/active911.js index 309aafb..c0a9e1b 100644 --- a/tests/active911.js +++ b/tests/active911.js @@ -1,105 +1,39 @@ -require('should'); -require('nock'); -Active911 = require('../lib/active911.js'); +var should = require('should'); +var nock = require('nock'); +var 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'); +var testDeviceResponse = require('./replies/device.json'); +var testAgencyResponse = require('./replies/agency.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); - }); - }); + beforeEach(function(done) { + a911 = new Active911.RefreshClient('CLIENT'); + done(); + }); + + 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().then(function(agency) { + agency.should.equal(testAgencyResponse.message.agency); + }).catch(function(err) { + should.fail(); + }); }); - 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); - }); - }); + 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().then(function(err, agency) { + should.fail(); + }).catch(function(err) { + err.should.equal(testErrorResponse.message); + }); }); - + }); }); diff --git a/tests/replies/devices.json b/tests/replies/devices.json deleted file mode 100644 index e69de29..0000000 -- cgit v1.2.3