├── img
└── jabberPresence.png
├── public
└── home
│ ├── favicon.ico
│ ├── static
│ └── css
│ │ ├── main.65027555.css
│ │ └── main.65027555.css.map
│ ├── asset-manifest.json
│ ├── manifest.json
│ ├── index.html
│ └── service-worker.js
├── users
├── user.js
├── verifyToken.js
├── authController.js
└── userController.js
├── space
└── space.js
├── .eslintrc.json
├── endpoints
├── endpoints.js
└── endpointController.js
├── model
├── db.js
├── space.js
├── appController.js
└── cart.js
├── myutils
├── changelog.js
├── tpxml.js
├── pollCUCM.js
├── excel.js
├── myutils.js
├── help.js
├── ping.js
├── tpXapi.js
└── netTools.js
├── gulpfile.js
├── LICENSE.md
├── endpointScripts
├── AutoDialByTOD.js
├── dialpad Macro.js
├── SpeakerTrackDiagnosticMode.js
├── RoomAvailability Macro.js
├── proximityControl.js
└── in-room-controls.xml
├── .gitignore
├── certs
├── cert.pem
└── key.pem
├── package.json
├── svrConfig
└── logger.js
├── status
└── statusController.js
├── broadcast
└── broadcastController.js
├── server.js
├── flintConversations
├── conversationNewCart.js
├── conversations.js
└── conversationFunctions.js
├── flintServer
└── flintConfig.js
└── README.md
/img/jabberPresence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voipnorm/telehealthPresence/HEAD/img/jabberPresence.png
--------------------------------------------------------------------------------
/public/home/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/voipnorm/telehealthPresence/HEAD/public/home/favicon.ico
--------------------------------------------------------------------------------
/public/home/static/css/main.65027555.css:
--------------------------------------------------------------------------------
1 | body{margin:0;padding:0;font-family:sans-serif}
2 | /*# sourceMappingURL=main.65027555.css.map*/
--------------------------------------------------------------------------------
/public/home/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "main.css": "static/css/main.65027555.css",
3 | "main.css.map": "static/css/main.65027555.css.map",
4 | "main.js": "static/js/main.ca670417.js",
5 | "main.js.map": "static/js/main.ca670417.js.map"
6 | }
--------------------------------------------------------------------------------
/users/user.js:
--------------------------------------------------------------------------------
1 | //user DB schema
2 |
3 | var mongoose = require('mongoose');
4 |
5 | var UserSchema = new mongoose.Schema({
6 | name: String,
7 | email: String,
8 | password: String
9 | });
10 |
11 | mongoose.model('User', UserSchema);
12 |
13 | module.exports = mongoose.model('User');
14 |
--------------------------------------------------------------------------------
/public/home/static/css/main.65027555.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["index.css"],"names":[],"mappings":"AAAA,KACE,SACA,UACA,sBAAwB","file":"static/css/main.65027555.css","sourcesContent":["body {\n margin: 0;\n padding: 0;\n font-family: sans-serif;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/index.css"],"sourceRoot":""}
--------------------------------------------------------------------------------
/space/space.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var SpaceSchema = new mongoose.Schema({
3 | "spaceId" : String,
4 | "spaceActive" : String,
5 | "setup" : String,
6 | "dnsServer" : String,
7 | "conversationState" : {"conversation": String},
8 | "webUrls": String
9 |
10 | });
11 | mongoose.model('Space', SpaceSchema);
12 |
13 | module.exports = mongoose.model('Space');
--------------------------------------------------------------------------------
/public/home/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:import/errors",
6 | "plugin:import/warnings"
7 | ],
8 | "parserOptions": {
9 | "ecmaVersion": 7,
10 | "sourceType": "module"
11 | },
12 | "env": {
13 | "browser": true,
14 | "node": true,
15 | "mocha": true
16 | },
17 | "rules": {
18 | "no-console": 0
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/endpoints/endpoints.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var EndpointSchema = new mongoose.Schema({
3 | cartName: String,
4 | xmppJID: String,
5 | xmppServer: String,
6 | cartIP: String,
7 | mac: String,
8 | peopleTest: String,
9 | location: String,
10 | version: String,
11 |
12 | });
13 | mongoose.model('Endpoint', EndpointSchema);
14 |
15 | module.exports = mongoose.model('Endpoint');
16 |
--------------------------------------------------------------------------------
/model/db.js:
--------------------------------------------------------------------------------
1 | var log = require('../svrConfig/logger');
2 | var myutils = require('../myutils/myutils');
3 | var mongoose = require('mongoose');
4 | var db = mongoose.connection;
5 | mongoose.connect('mongodb://localhost/presenceDB');
6 |
7 |
8 | db.on('error', function(err){
9 | log.error("DB Connection Failure: "+err);
10 | myutils.sparkPost("DB Connection Failure: "+err, process.env.SPARK_ROOM_ID)
11 | });
12 | db.once('open', function() {
13 | log.info('Successfully connected to MongoDB!!');
14 | });
--------------------------------------------------------------------------------
/public/home/index.html:
--------------------------------------------------------------------------------
1 |
React App
--------------------------------------------------------------------------------
/myutils/changelog.js:
--------------------------------------------------------------------------------
1 | var changeLog =
2 | "1-Jan-2018 App is created.
"+
3 | "26-June-2018 API with JOSN Web Tokens.
"+
4 | "26-June-2018 Mongo DB support.
"+
5 | "26-June-2018 Webex Teams Chat bot support.
"+
6 | "26-June-2018 Support for People presence and People count.
"+
7 | "26-June-2018 CSV upload via chatbot support.
"+
8 | "26-June-2018 Web admin interface.
"+
9 | "end."
10 |
11 | exports.printChangeLog = function(callback){
12 | callback(changeLog)
13 | };
14 |
15 | var roadMap =
16 | "2018 - Undefined ";
17 |
18 | exports.printRoadmap = function(callback){
19 | callback(roadMap)
20 | };
--------------------------------------------------------------------------------
/myutils/tpxml.js:
--------------------------------------------------------------------------------
1 | //communication with video TP units though XML API.
2 | const log = require('../svrConfig/logger');
3 | const jsxapi = require('jsxapi');
4 |
5 | exports.broadcastMessage = function(cart, title, text , duration, cb) {
6 | const xapi = jsxapi.connect('ssh://'+cart.ipAddress, {
7 | username: cart.username,
8 | password: cart.password
9 | });
10 | xapi.on('error', (err) => {
11 | return log.error(err);
12 |
13 | });
14 | return xapi.command('UserInterface Message Alert Display', {
15 | Title: title,
16 | Text: text,
17 | Duration: duration,
18 | })
19 | .then(()=> {
20 | log.info("xapi session closed.");
21 | return xapi.close();
22 | })
23 | .catch(err=> cb(err, null));
24 | };
25 |
--------------------------------------------------------------------------------
/users/verifyToken.js:
--------------------------------------------------------------------------------
1 | //verify tokens being processed by API
2 |
3 | var jwt = require('jsonwebtoken');
4 | var secret = process.env.SECRETTOKEN;
5 | var log = require('../svrConfig/logger');
6 |
7 | function verifyToken(req, res, next) {
8 | log.info("Header Token "+req.headers['x-access-token']);
9 | var token = req.headers['x-access-token'];
10 | if (!token)
11 | return res.status(403).send({ auth: false, message: 'No token provided.' });
12 | jwt.verify(token, secret, function(err, decoded) {
13 | if (err)
14 | return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });
15 | // if everything good, save to request for use in other routes
16 | req.userId = decoded.id;
17 | next();
18 | });
19 | }
20 |
21 | module.exports = verifyToken;
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var nodemon = require('gulp-nodemon');
3 | var gulpMocha = require('gulp-mocha');
4 | var eslint = require('gulp-eslint');
5 | var env = require('gulp-env');
6 |
7 | gulp.task('default',['lint','test'], function(){
8 | nodemon({
9 | exec: 'DEBUG=flint*,sparky* node',
10 | script: 'server.js',
11 | ext: 'js',
12 | env: {
13 | PORT: 8080
14 | },
15 | ignore: ['./node_modules/']
16 | })
17 | .on('restart',['test'], function(){
18 | console.log('We have restarted');
19 | })
20 | });
21 |
22 | gulp.task('test', function(){
23 | env({vars:{ENV:'Test'}});
24 | gulp.src(['Tests/singleFunctionTest.js','Tests/cityUpdateTest.js','Tests/activeUpdateTest.js'/*,'Tests/unitUpdateTest.js'*/])
25 | .pipe(gulpMocha({reporter: 'nyan'}));
26 | });
27 |
28 | gulp.task('lint', function () {
29 | return gulp.src(['**/*.js','!node_modules/**','!code graveyard/**','!Tests/**','!public/**'])
30 | .pipe(eslint())
31 | .pipe(eslint.format());
32 | });
33 |
34 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2017
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/endpointScripts/AutoDialByTOD.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Wakes the system up from standby at 8.00 in the morning
3 | * on weekdays and auto dials another endpoint. Other endpoint needs to be setup to Auto Answer.
4 | */
5 |
6 | // library for communicating with video system
7 | const xapi = require('xapi');
8 |
9 | // how often to check time
10 | const intervalSec = 60;
11 |
12 | // Standard javascript built-ins such as date and timers are included
13 | function checkTime() {
14 | const now = new Date();
15 | const weekday = now.getDay() > 0 && now.getDay() < 6;
16 | const wakeupNow = now.getHours() === 8 && now.getMinutes() < 2 && weekday;
17 | const sleepNow = now.getHours() === 17 && now.getMinutes() < 2 && weekday;
18 |
19 | if (wakeupNow) {xapi.command('standby deactivate');
20 | xapi.command("dial", {Number:"OTHERENDPOINT@YOURDOMAIN.COM"});
21 | console.log("Call Connecting");
22 | }
23 |
24 | if(sleepNow){
25 | xapi.command("call disconnect");
26 | xapi.command('standby activate');
27 | console.log("Call disconnecting");
28 | }
29 |
30 | }
31 |
32 | setInterval(checkTime, intervalSec * 1000);
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Cloud 9 files
2 | .c9/
3 | # Space date
4 | oldCode/space.json
5 | cart.json
6 | #client websocket file
7 | device.json
8 | #access log
9 | access.log
10 |
11 | certs
12 | # Logs
13 | logs
14 | *.log
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 | # Runtime data
20 | pids
21 | *.pid
22 | *.seed
23 | *.pid.lock
24 |
25 | # Directory for instrumented libs generated by jscoverage/JSCover
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 | coverage
30 |
31 | # nyc test coverage
32 | .nyc_output
33 |
34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (https://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 | jspm_packages/
49 |
50 | # Typescript v1 declaration files
51 | typings/
52 |
53 | # Optional npm cache directory
54 | .npm
55 |
56 | # Optional eslint cache
57 | .eslintcache
58 |
59 | # Optional REPL history
60 | .node_repl_history
61 |
62 | # Output of 'npm pack'
63 | *.tgz
64 |
65 | # Yarn Integrity file
66 | .yarn-integrity
67 |
68 | # dotenv environment variables file
69 | .env
70 |
--------------------------------------------------------------------------------
/certs/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIID2zCCAsOgAwIBAgIJAM3n4ZcnPnYUMA0GCSqGSIb3DQEBCwUAMIGDMQswCQYD
3 | VQQGEwJVUzEPMA0GA1UECAwGUmVudG9uMQ8wDQYDVQQHDAZSZW50b24xDTALBgNV
4 | BAoMBGhvbWUxDDAKBgNVBAsMA2hvbTESMBAGA1UEAwwJbG9jYWxob3N0MSEwHwYJ
5 | KoZIhvcNAQkBFhJjaHJpc3Rub0BjaXNjby5jb20wHhcNMTgwNTI0MjE1MTQzWhcN
6 | MjEwMjE2MjE1MTQzWjCBgzELMAkGA1UEBhMCVVMxDzANBgNVBAgMBlJlbnRvbjEP
7 | MA0GA1UEBwwGUmVudG9uMQ0wCwYDVQQKDARob21lMQwwCgYDVQQLDANob20xEjAQ
8 | BgNVBAMMCWxvY2FsaG9zdDEhMB8GCSqGSIb3DQEJARYSY2hyaXN0bm9AY2lzY28u
9 | Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQHZSlwL19Wia/OR
10 | CHtnKIWBha0UhFfXlxi1pCs9XB4FT8v/XamZZ2erPoDXwiGx2npphQWkNFkLLfR5
11 | q2dxy1F/7ar1dbeBaG5OkBgGrhlKLMreWODlhHx0SHFtvlFkfFS1M+ti07Fb9yWx
12 | JAJgW3LjthUIDkMTYMwm6952FT0EzULK54Ra+ttF8/hfJoM0WdXn7CzjWnTdy3LL
13 | oOWS2oGiub31I5h3/gGQ3dieoffv0z0UF6/gE4COInVgFwI5H2pYW4VRcJA9hAzY
14 | cz5A2f8PKtd7BY6//6cnYckaq/M9wmhoAhFE3zhaESTrBJfo6c9jitmmxGFtWoTi
15 | xo/W3QIDAQABo1AwTjAdBgNVHQ4EFgQU3DCh164ASer0z75481YKShOeiGkwHwYD
16 | VR0jBBgwFoAU3DCh164ASer0z75481YKShOeiGkwDAYDVR0TBAUwAwEB/zANBgkq
17 | hkiG9w0BAQsFAAOCAQEAE07lgvqC0fhuhrm5cwO8M9B6JFqrKjkPzhrPWjEk99ca
18 | CYrpsxCssWDJgBwwFCtsrb/wXl1riDWfpyr6BYSVoQQWleq/LLfXcFD3EwHS7qwc
19 | yqXqbm7JsxWntudSoL8H/gF93ZzQnvE9wyyJ2E1GKVBfdhQoeZlqvPWeWw7i3cAm
20 | M2/NPFvUkkYRXNeG3A5isZPRlbvC/Pshz5Eo43lq6BtNmO/TT8mTddKSf4pfPM5b
21 | AmmHSkNwNro+E6lCxiw0eRdaL86ZZaKS/lTRaAw2FRO7xzCNs98ahrtBuVfhlx7j
22 | nC3qbcM1vy3KzYfof4DenADiYNov/LDKbx1NAc2bvA==
23 | -----END CERTIFICATE-----
24 |
--------------------------------------------------------------------------------
/endpointScripts/dialpad Macro.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi');
2 |
3 | var did = "";
4 | function listenToGui() {
5 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
6 |
7 | if (event.Type === 'pressed') {
8 | console.log('Unknown button pressed', event);
9 | if(event.WidgetId === "dialButton"){
10 | xapi.command("dial", {Number:did});
11 | did="";
12 | textBoxUpdate(did);
13 | }else if(event.WidgetId==="backSpace"){
14 | backSpaceString(did);
15 | }else{
16 | const newdigit = event.Value;
17 | did = did+newdigit;
18 | console.log("DID new Value :"+did);
19 | textBoxUpdate(did);
20 | resetButton(event.WidgetId);
21 | }
22 | }
23 | if(event.Type === "released"){
24 | resetButton(event.WidgetId);
25 | console.log("release triggered");
26 | }
27 | });
28 | }
29 | function resetButton(widgetID){
30 | xapi.command('UserInterface Extensions Widget UnsetValue', {
31 | WidgetId: widgetID,
32 | });
33 |
34 | console.log("button reset done");
35 | }
36 | function backSpaceString(string){
37 | did = string.slice(0, -1);
38 | textBoxUpdate(did);
39 | }
40 |
41 | function textBoxUpdate(stringValue){
42 | xapi.command('UserInterface Extensions Widget SetValue', {
43 | WidgetId: "textBox",
44 | Value: stringValue,
45 | });
46 | }
47 |
48 | listenToGui();
--------------------------------------------------------------------------------
/myutils/pollCUCM.js:
--------------------------------------------------------------------------------
1 |
2 | require('dotenv').config();
3 | const util = require('util');
4 | const EventEmitter = require('events').EventEmitter;
5 | const request = require('request');
6 | const log = require('../svrConfig/logger');
7 | const ris = require('cucm-risdevice-query').RisQuery;
8 | const cucmIp = process.env.CUCMIPADDRESS;
9 | const cucmAdmin = process.env.CUCMPRESENCEACCOUNT;
10 | const cucmPwd = process.env.CUCMPRESENCEPWD;
11 |
12 | function CUCMPoll(mac){
13 | this.mac = mac;
14 | }
15 | util.inherits(CUCMPoll,EventEmitter);
16 |
17 | CUCMPoll.prototype.checkIP = function(callback){
18 | var self = this;
19 | const devices = [this.mac];
20 | const risReqXml = ris.createRisDoc({
21 | version: process.env.CUCMVERSION,
22 | query: devices
23 | });
24 | log.info('Processing Mac: '+JSON.stringify(risReqXml));
25 | const url = `https://${cucmIp}:8443` + ris.risPath;
26 | request({
27 | url: url,
28 | method: 'POST',
29 | body: risReqXml,
30 | headers: {
31 | 'Content-Type': 'text/xml'
32 | },
33 | auth: {
34 | username: cucmAdmin,
35 | password: cucmPwd
36 | },
37 | strictSSL: false
38 | }, (err, resp, body) => {
39 | if(err) return log.error("CUCM Error : "+err);
40 | log.info(JSON.stringify(body))
41 | const parsedResponse = ris.parseResponse(body);
42 | log.info(JSON.stringify(parsedResponse));
43 | return callback(parsedResponse);
44 | });
45 | };
46 |
47 |
48 | module.exports = CUCMPoll;
49 |
--------------------------------------------------------------------------------
/endpointScripts/SpeakerTrackDiagnosticMode.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Macro companion to the SpeakerTrack Diagnostic mode
4 | */
5 |
6 | const xapi = require('xapi');
7 | const stdID = 'STDOnOff';
8 | let stdStatus = "Stop";
9 |
10 | function listenToGui() {
11 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
12 | //console.log(event);
13 | if (event.WidgetId === stdID) {
14 | //console.log('Unknown togglebutton', event);
15 | if(event.Value === "on"){
16 | stdStatus="Start";
17 | textBoxUpdate(stdStatus);
18 | setSTD(stdStatus);
19 |
20 | }else if(event.Value === "off"){
21 | stdStatus="Stop";
22 | textBoxUpdate(stdStatus);
23 | setSTD(stdStatus);
24 |
25 | }else{
26 | console.log("Macro error");
27 | }
28 | }
29 |
30 | });
31 | }
32 |
33 | function setSTD(status){
34 | console.log(status);
35 | if(status==="Start"){
36 | xapi.command('Cameras SpeakerTrack Diagnostics Start')
37 | .catch((error) => { console.error(error); });
38 | }else{
39 | xapi.command('Cameras SpeakerTrack Diagnostics Stop')
40 | .catch((error) => { console.error(error); });
41 | }
42 |
43 | }
44 |
45 | function textBoxUpdate(stringValue){
46 | xapi.command('UserInterface Extensions Widget SetValue', {
47 | WidgetId: "textBoxSTD",
48 | Value: "Speaker Track Diagnostics Mode: "+stringValue,
49 | });
50 | }
51 |
52 |
53 |
54 | listenToGui();
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node_mon",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "author": "Chris Norman",
7 | "dependencies": {
8 | "apiai": "^4.0.3",
9 | "assert": "^1.4.1",
10 | "async": "~0.2.8",
11 | "bcryptjs": "^2.4.3",
12 | "bluebird": "^3.5.3",
13 | "body-parser": "^1.18.3",
14 | "bottleneck": "^1.15.1",
15 | "ciscospark-websocket-events": "^1.2.0",
16 | "cors": "^2.8.5",
17 | "cucm-risdevice-query": "^1.1.1",
18 | "debug": "^2.2.0",
19 | "dotenv": "^4.0.0",
20 | "exceljs": "^0.8.5",
21 | "express": "^4.16.4",
22 | "express-range": "^2.0.1",
23 | "fwsp-hydra-express": "^1.0.0",
24 | "http-server": "^0.9.0",
25 | "hydra-express": "^1.7.0",
26 | "jsonwebtoken": "^8.4.0",
27 | "jsxapi": "^4.2.0",
28 | "lodash": "^4.17.11",
29 | "mongoose": "^5.3.12",
30 | "node-flint": "^4.7.0",
31 | "node-schedule": "^1.2.5",
32 | "node-sparkclient": "^0.1.4",
33 | "nodemon": "^1.18.6",
34 | "portscanner": "^2.2.0",
35 | "prettyjson": "^1.2.1",
36 | "react-admin": "^2.4.2",
37 | "request": "^2.88.0",
38 | "simple-xmpp": "^1.3.0",
39 | "tz-lookup": "^6.1.8",
40 | "valid-url": "^1.0.9",
41 | "winston": "^3.1.0"
42 | },
43 | "devDependencies": {
44 | "eslint": "^4.19.1",
45 | "eslint-plugin-import": "^2.14.0",
46 | "gulp": "^3.9.1",
47 | "gulp-env": "^0.4.0",
48 | "gulp-eslint": "^4.0.0",
49 | "gulp-mocha": "^4.3.1",
50 | "gulp-nodemon": "^2.4.1",
51 | "should": "^13.2.3",
52 | "supertest": "^3.3.0"
53 | },
54 | "license": "ISC"
55 | }
56 |
--------------------------------------------------------------------------------
/svrConfig/logger.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | //Logging configuration
3 | const winston = require('winston');
4 | const env = process.env.NODE_ENV;
5 | const {createLogger, format, transports} = require('winston');
6 | const {combine, timestamp, label, printf, colorize} = format;
7 |
8 |
9 | const myFormat = printf(info => {
10 | return `${info.timestamp} ${info.level}: ${info.message}`;
11 | });
12 |
13 |
14 | const logger =
15 | winston.createLogger({
16 | format: combine(
17 | format.splat(),
18 | colorize({ all: true }),
19 | timestamp(),
20 | myFormat
21 | ),
22 | transports: [
23 | //
24 | // - Write to all logs with level `info` and below to `combined.log`
25 | // - Write all logs error (and below) to `error.log`.
26 | //
27 | new winston.transports.File({ filename: './logs/error.log', level: 'error' }),
28 | new winston.transports.File({ filename: './logs/combined.log' })
29 | ],
30 | exceptionHandlers: [
31 | new winston.transports.File( {
32 | filename: 'logs/exceptions.log'
33 | } ),
34 | new winston.transports.Console( {
35 | colorize: true
36 | } ),
37 | ]
38 | });
39 |
40 | //
41 | // If we're not in production then log to the `console` with the format:
42 | // `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
43 | //
44 | if (env !== 'production') {
45 | logger.add(new winston.transports.Console());
46 | }
47 |
48 | //logger.info('Hello, this is a logging event with a custom pretty print', { 'foo': 'bar' });
49 | //logger.info('Hello, this is a logging event with a custom pretty print2', { 'foo': 'bar' });
50 |
51 | module.exports = logger;
52 |
53 |
--------------------------------------------------------------------------------
/status/statusController.js:
--------------------------------------------------------------------------------
1 | //rest api for admin interface or general use
2 | var crud = require('../model/appController');
3 | var log = require('../svrConfig/logger');
4 | var carts = crud.cartDataObj;
5 | var range = require('express-range');
6 | var VerifyToken = require('../users/VerifyToken');
7 | var express = require('express');
8 | var router = express.Router();
9 | var bodyParser = require('body-parser');
10 | var cors = require('cors');
11 | router.use(bodyParser.urlencoded({ extended: false }));
12 | router.use(bodyParser.json());
13 | router.use(range({
14 | accept: 'endpoints',
15 | limit: 10,
16 | }));
17 | router.use(cors({
18 | origin: '*',
19 | credentials: false,
20 | exposedHeaders: 'content-range',
21 |
22 | }));
23 |
24 | //get status of all endpoints
25 | router.get("/",VerifyToken, function(req, res) {
26 | let endpoints = [];
27 | let num = carts.length;
28 | res.header("Access-Control-Allow-Origin", "*");
29 | log.info("Carts API request : "+carts.length);
30 | if (isFinite(num) && num > 0) {
31 | for (var i = 0; i <= num - 1; i++) {
32 | endpoints.push({
33 | _id: i,
34 | cartName: carts[i].cartName,
35 | cartIP: carts[i].cartIP,
36 | status: carts[i].cartStatus,
37 | location: carts[i].location,
38 | version: carts[i].version
39 | });
40 | }
41 | res.range({
42 | first: req.range.first,
43 | last: req.range.last,
44 | length: endpoints.length
45 | });
46 | res.json({data:endpoints.slice(req.range.first, req.range.last + 1),total:endpoints.length});
47 |
48 | }else {
49 | res.status(400).send({ message: 'invalid number supplied' });
50 | }
51 | });
52 |
53 | module.exports = router;
--------------------------------------------------------------------------------
/certs/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN ENCRYPTED PRIVATE KEY-----
2 | MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIHx5wltr22M0CAggA
3 | MBQGCCqGSIb3DQMHBAgmKaS79F6tvgSCBMiY1waLjBefyIV5L5E3HXbrl0RoUxbS
4 | amKVZyAluJd/dGhTz79Z+Z/NYSVgt3nxCA3s+tpVewmm/qgAYR8//esoFdfRVCvy
5 | x+rlygrnI9hvEu2V1emCZYNwBv8vhs6YWQ+gZRR2wtx5v/jESYHm55DS/2lkeEC/
6 | qH8zkPepIq/5zWrOeUQKqjr/b5AtMW5lYEdvKYRByku+g4ahzzQqAiQoHC88CQg7
7 | JnFJuXpxfwcteT1OpuA8+m5imHLfP6jqFjzuQOFREtyIU3aEHwQRSnAxlI1qZgqo
8 | DKJoVi/uzjC4OwGQcw1woeo5Sp3wBg5GmXi+SUaVqDh2hu4jCDdpl3u0oZTi8Hqr
9 | GgY8p/AfRb+GuB2afPspa2v127rYLO7jK/+OUWjzJMUX50KFXsXSCczmTMoyyS7A
10 | FCW+kCoHrVujbPhnuOZwkrVx3y8OdzjAUw1JUnEQwKLq/RZfAdLMY2ARmikPw/EG
11 | ccQpALF63Ng++y5Q9JFDZc2tTG7+/pi72zzzMFP6A/cm+n1hNn61H2VMq7c77HCP
12 | 6mUFL3X2kGH1UndX83hoix4Oz6OcXwXSISagdeYHzdNVffFArv5eAFzukA85QN8X
13 | jDm+vCC4sQXCk7JlzClb3RpE5FT+5cS1r3DToj6vKJ4VK6ZbpVSDyw/aurryEiUo
14 | wiAqtqhYfXqGd7f8D52YkQAb3n9r/B9cIkZUUEnlwJhZ9xjWyb+xONbGx3CpFz7D
15 | hFKaPSzmdqcyFwAb7nnOM2tCFgtzCg3scSl8CdfaU49nf1CI+nOFxqCZedg8N2np
16 | 2+dfrArHH70tHeHWL/b/4nhEC8h8+6ZIPhh1tVbi0WhqBqkeRBXbB50SGI9K2JSH
17 | ZA+bubBPf9BIep2Pd50HNmyXRUH3NXUuyKt1set4pNIppsbv7xRJ11JKSlRwixf7
18 | 4OOKC6Li2TX2QaOGud9jP8Kd4AwtU11tanwRDZOE2YVNssz219g69hoWXCrVzANP
19 | 5e03GIPeoRUugQOXbRnpds7nC6KgHUVNwX5OqX8QwM94xSiO8TGAYtWJUcCLo8DM
20 | 1JKFoY7sGrEENsx6U/LQLfpyNiUeGeECee67YX2hh4G5Rj2m3Ognozh9JtejAd6M
21 | GenBNIJePtdalIVLUc4fHzSJ7GinKXniQIFiojLUJFqI4DYVHXFLqM03JTrNnK6z
22 | 7jZAx8ip/8dgEe+/bzBU26SXFYV8FqufHlBUuKRMlPD5RQuL+djLrQYDyVoFQp45
23 | EPDEgqN0k7++Q47tdvcxBdcret0EU4h8HQ8fXvq4vZIAbIKAEoYAGc/wXVHdb3ag
24 | EWUvIZGHebZBs9agyu/gvXM/M8GwLscU/OLmij7Fr3hM1isu1aBcgFsDd7EDZusE
25 | DIT5Lq/dppl3wAAZO2DY7QVfBsiXVU+Wh1b+D89GkX1UPT7GosEjerg3tIPXA7fE
26 | s80F2jeeMyo0aMgQCV+SjLBq72HcHqOc15cd6rIEz/DVUYxyMloMlO8H5oaGBsa+
27 | 2TBpsQAC0pM7wgkJBU9IB523S6iVvknlyC35bs2TRXfC9q2yLgMlFb6hH19IlVvL
28 | g7zsiruOfQtkDCNv3vKek2ZZzTKbddE6YVBMwZVquEG7TFKf+34iHhX+gYVs39Rf
29 | Y0M=
30 | -----END ENCRYPTED PRIVATE KEY-----
31 |
--------------------------------------------------------------------------------
/model/space.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var EventEmitter = require('events').EventEmitter;
3 | var crud = require('./appController');
4 | var log = require('../svrConfig/logger');
5 | //pass in object versus single values
6 | function Space(data){
7 | //space ID
8 | this.spaceId = data.spaceId;
9 | //Disable space but not delete on bot removal
10 | this.spaceActive = data.spaceActive;
11 | //Setup currently not in use
12 | this.setup = data.setup;
13 | //setting not in use
14 | this.dnsServer = null;
15 | //setting not in use
16 | this.conversationState = data.conversationState;
17 | }
18 |
19 | util.inherits(Space,EventEmitter);
20 |
21 | Space.prototype.updateConversationState = function(obj){
22 | var self = this;
23 | self.conversationState = {
24 | conversation: obj.conversation,
25 | state: obj.state,
26 | cartName: null,
27 | ipAddress: null
28 | };
29 | return self;
30 | };
31 |
32 |
33 |
34 | Space.prototype.updatednsServer = function(param){
35 | var self = this;
36 | self.dnsServer = param;
37 | return self;
38 | };
39 |
40 | Space.prototype.updateActive = function(param){
41 | var self = this;
42 | self.spaceActive = param;
43 | return self;
44 | };
45 |
46 | Space.prototype.updateSetup = function(param){
47 | var self = this;
48 | self.spaceSetup = param;
49 | return self;
50 | };
51 | Space.prototype.writeToFile = function(){
52 | var self = this;
53 | crud.writeToJSON(function(){
54 | log.info("spaceObj.Writing to file......");
55 | self.emit('writeComplete');
56 | });
57 | return;
58 | };
59 | Space.prototype.writeCartToFile = function(){
60 | var self = this;
61 | crud.writeCartToJSON(function(){
62 | log.info("spaceObj.Writing to file......");
63 | self.emit('writeComplete');
64 | });
65 | return;
66 | };
67 |
68 | module.exports = Space;
--------------------------------------------------------------------------------
/broadcast/broadcastController.js:
--------------------------------------------------------------------------------
1 | //API for sending broadcast messages to all endpoints.
2 |
3 | //rest api for admin interface or general use
4 | var crud = require('../model/appController');
5 | var log = require('../svrConfig/logger');
6 | var carts = crud.cartDataObj;
7 | var range = require('express-range');
8 | var VerifyToken = require('../users/VerifyToken');
9 | var express = require('express');
10 | var router = express.Router();
11 | var bodyParser = require('body-parser');
12 | var cors = require('cors');
13 | var tpxml = require('../myutils/tpxml');
14 | var _= require('lodash');
15 | router.use(bodyParser.urlencoded({ extended: false }));
16 | router.use(bodyParser.json());
17 | router.use(range({
18 | accept: 'endpoints',
19 | limit: 10,
20 | }));
21 | router.use(cors({
22 | origin: '*',
23 | credentials: false,
24 | exposedHeaders: 'content-range',
25 |
26 | }));
27 |
28 | //broadcast message to all endpoints.
29 | router.post("/",VerifyToken, function(req, res) {
30 |
31 | res.header("Access-Control-Allow-Origin", "*");
32 | log.info("Carts API request : "+carts.length);
33 | _.forEach(crud.cartDataObj, function(cart) {
34 | if(cart.cartStatus === "online"){
35 | let message = req.body.message;
36 | var cartObj = {
37 | "username":process.env.TPADMIN,
38 | "password":cart.endpointPwd,
39 | "ipAddress":cart.cartIP
40 | };
41 | tpxml.broadcastMessage(cartObj,"Important",message,"20", function(err, message){
42 | if(err)log.error("conversationFunction.broadcast: error "+err);
43 | log.info("conversationFunctions.broadcast: broadcast to endpoints success")
44 | });
45 | }else{
46 | log.info('Broadcast failed endpoint offline: '+cart.cartIP);
47 | }
48 | })
49 | return res.status(200).send("Broadcast complete.");
50 | });
51 |
52 | module.exports = router;
--------------------------------------------------------------------------------
/myutils/excel.js:
--------------------------------------------------------------------------------
1 | //module for reading CSV file downloaded from Spark for uploading bulk TP endpoints - needs work on adding validy of CSV format
2 |
3 | var Excel = require('exceljs');
4 | var fs = require('fs');
5 | var workbook = new Excel.Workbook();
6 | var log = require('../svrConfig/logger');
7 | var crud = require('../model/appController');
8 |
9 | function readcsv(filename, callback){
10 | workbook.csv.readFile(filename)
11 | .then(function(worksheet) {
12 |
13 | for(var i = 0; i {
56 | if(err) return log.error("CUCM Error : "+err);
57 | const parsedResponse = ris.parseResponse(body);
58 | log.info(JSON.stringify(parsedResponse));
59 | return callback(parsedResponse);
60 | });
61 | }
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | var flint = require('./flintServer/flintConfig');
3 | var webhook = require('node-flint/webhook');
4 | var express = require('express');
5 | var https = require('https');
6 | var path = require('path');
7 | var log = require('./svrConfig/logger');
8 | var crud = require('./model/appController');
9 | var bodyParser = require('body-parser');
10 | var db = require('./model/db');
11 | var AuthController = require('./users/authController');
12 | var endpointController = require('./endpoints/endpointController');
13 | var userController = require('./users/userController');
14 | var statusController = require('./status/statusController');
15 | var broadcastController = require('./broadcast/broadcastController');
16 | var fs = require('fs');
17 |
18 | var app = express();
19 |
20 | app.use(bodyParser.json());
21 |
22 | // define express path for incoming webhooks
23 | app.post('/flint',webhook(flint));
24 | //CORS Configuration
25 | app.use(function(req, res, next) {
26 | res.header("Access-Control-Allow-Origin", "*");
27 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
28 | next();
29 | });
30 |
31 | //rest api configuration
32 | app.use('/api/auth', AuthController);
33 | app.use('/api/endpoints', endpointController);
34 | app.use('/api/users',userController);
35 | app.use('/api/status', statusController);
36 | app.use('/api/broadcast', broadcastController);
37 | //Serve up admin interface
38 | app.use('/', express.static(__dirname + '/public/home', { index: 'index.html' }));
39 |
40 |
41 | var sslOptions = {
42 | key: fs.readFileSync('./certs/key.pem'),
43 | cert: fs.readFileSync('./certs/cert.pem'),
44 | passphrase: process.env.SECRETTOKEN
45 | };
46 | crud.startUp();
47 |
48 | https.createServer(sslOptions, app).listen(process.env.SECUREWEBPORT);
49 |
50 | var server = app.listen(process.env.WEBPORT, function () {
51 | log.info('server : Chatbot listening on port %s', process.env.WEBPORT);
52 | });
53 |
54 |
55 |
56 | // gracefully shutdown (ctrl-c)
57 | process.on('SIGINT', function() {
58 | log.info('server : stoppping...');
59 | server.close();
60 | flint.stop().then(function() {
61 | process.exit();
62 | });
63 | });
64 |
65 | module.exports = app;
--------------------------------------------------------------------------------
/flintConversations/conversationNewCart.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var log = require('../svrConfig/logger');
4 | var crud = require('../model/appController');
5 | //setup conversation file
6 | module.exports = {
7 | //Welcome conversation flow
8 | convo1: function(request, bot, trigger, spData){
9 | bot.say("Please enter the IP Address of the cart you would like presence updates for.");
10 | spData.conversationState.conversation = "newCart";
11 | spData.conversationState.state = "1";
12 | spData.conversationState.cartName = request;
13 | return;
14 | },
15 | convo2: function(request, bot , trigger, spData){
16 | bot.say("Please enter the JID address of the cart.");
17 | spData.conversationState.state="2";
18 | spData.conversationState.ipAddress = request;
19 | return;
20 | },
21 | convo3: function(request, bot , trigger, spData){
22 |
23 | spData.conversationState.state="3";
24 | spData.conversationState.JID = request;
25 |
26 | return bot.say({markdown:"Your setup is nearly complete. If you would like to commit these changes just type **yes**, if not type **no**."});
27 | },
28 | convo4: function(request, bot , trigger, spData){
29 | log.info("conversationSetup.setupConvo4 :"+ JSON.stringify(spData.conversationState));
30 |
31 | if(request === "yes") {
32 | var cart =
33 | {
34 | cartName:spData.conversationState.cartName,
35 | cartIP: spData.conversationState.ipAddress,
36 | JID: spData.conversationState.JID
37 | };
38 | crud.createCart(cart, function(){
39 | spData.updateConversationState({conversation: "commands", state:""});
40 | return bot.say({markdown: "Thank you, your setup is now complete."});
41 | })
42 | }else if(request === "no"){
43 | spData.updateConversationState({conversation: "commands", state:""});
44 | return bot.say({markdown: "Your new cart has been cancelled. To start over please enter **/newCart**."});
45 | }else {
46 | bot.say("Oh no, something went wrong. Lets try this again." +
47 | " Please enter yes or no");
48 | return;
49 | }
50 | }
51 | };
--------------------------------------------------------------------------------
/endpointScripts/RoomAvailability Macro.js:
--------------------------------------------------------------------------------
1 | const xapi = require('xapi');
2 |
3 |
4 | var dnd = "Room Available";
5 | var tpDNDStatus = "Inactive";
6 | var tpAAStatus = "On";
7 |
8 |
9 | function listenToGui() {
10 | xapi.status.on('Conference DoNotDisturb', DNDChanged);
11 | xapi.event.on('UserInterface Extensions Widget Action', (event) => {
12 | //console.log(event);
13 | if (event.WidgetId === 'DNDToggle') {
14 | //console.log('Unknown togglebutton', event);
15 | if(event.Value === "on"){
16 | dnd="Room Occupied";
17 | tpDNDStatus = "Activate";
18 | tpAAStatus = "Off";
19 |
20 | textBoxUpdate(dnd);
21 | setDND(tpDNDStatus);
22 |
23 |
24 | setAutoAnswer(tpAAStatus);
25 | }else if(event.Value === "off"){
26 | dnd="Room Available";
27 | textBoxUpdate(dnd);
28 | tpDNDStatus = "Inactive";
29 | setDND(tpDNDStatus);
30 | tpAAStatus = "On";
31 | setAutoAnswer(tpAAStatus);
32 | }else{
33 | console.log("Macro error");
34 | }
35 | }
36 |
37 | });
38 | }
39 |
40 | function setDND(status){
41 | console.log(status);
42 | if(status==="Activate"){
43 | xapi.command('Conference DoNotDisturb activate')
44 | .catch((error) => { console.error(error); });
45 | }else{
46 | xapi.command('Conference DoNotDisturb deactivate')
47 | .catch((error) => { console.error(error); });
48 | }
49 |
50 | }
51 | function toggleUpdate(status){
52 | xapi.command('UserInterface Extensions Widget SetValue', {
53 | WidgetId: "DNDToggle",
54 | Value: status,
55 | });
56 | }
57 |
58 | function setAutoAnswer(status){
59 | //console.log(status);
60 | xapi.config.set('Conference AutoAnswer Mode', status);
61 | }
62 |
63 | function textBoxUpdate(stringValue){
64 | xapi.command('UserInterface Extensions Widget SetValue', {
65 | WidgetId: "textBoxDND",
66 | Value: stringValue,
67 | });
68 | }
69 |
70 | function DNDChanged(status){
71 | console.log("This is the status: "+status);
72 | if(status === "Active"){
73 | dnd="Room Occupied";
74 | tpDNDStatus = "Activate";
75 | tpAAStatus = "Off";
76 | toggleUpdate('on');
77 | textBoxUpdate(dnd);
78 | }else{
79 | dnd="Room Available";
80 | tpDNDStatus = "Inactive";
81 | tpAAStatus = "on";
82 | toggleUpdate('off');
83 | textBoxUpdate(dnd)
84 | }
85 |
86 | }
87 |
88 | listenToGui();
--------------------------------------------------------------------------------
/endpointScripts/proximityControl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Macro companion to the Ultrasound Control
3 | * - lets users toggle Proximity Mode to On/Off
4 | * - displays the current MaxVolume level
5 | */
6 |
7 | const xapi = require('xapi')
8 | const proximityID = 'ProximityOnOff';
9 | const textBoxId = 'textBoxProximity';
10 |
11 | // Change proximity mode to "On" or "Off"
12 | function switchProximityMode(mode) {
13 | console.debug(`switching proximity mode to: ${mode}`)
14 |
15 | xapi.config.set('Proximity Mode', mode)
16 | .then(() => {
17 | console.info(`turned proximity mode: ${mode}`)
18 | })
19 | .catch((err) => {
20 | console.error(`could not turn proximity mode: ${mode} ${err}`)
21 | })
22 | }
23 |
24 | // React to UI events
25 | function onGui(event) {
26 | // Proximity Mode Switch
27 | if ((event.Type === 'changed') && (event.WidgetId === proximityID)) {
28 | switchProximityMode(event.Value)
29 | return;
30 | }
31 | }
32 | xapi.event.on('UserInterface Extensions Widget Action', onGui);
33 |
34 |
35 | //
36 | // Proximity Services Availability
37 | //
38 |
39 | // Update Toogle if proximity mode changes
40 | function updateProximityToggle(mode) {
41 | console.debug(`switching toggle to ${mode}`)
42 |
43 | xapi.command("UserInterface Extensions Widget SetValue", {
44 | WidgetId: proximityID,
45 | Value: mode
46 | })
47 | }
48 | xapi.config.on("Proximity Mode", mode => {
49 | console.log(`proximity mode changed to: ${mode}`)
50 |
51 | // Update toggle
52 | // [WORKAROUND] Configuration is On or Off, needs to be turned to lowercase
53 | updateProximityToggle(mode.toLowerCase());
54 | textBoxUpdate(mode);
55 | })
56 |
57 | // Refresh Toggle state
58 | function refreshProximityToggle() {
59 | xapi.status.get("Proximity Services Availability")
60 | .then(availability => {
61 | console.debug(`current proximity mode is ${availability}`)
62 | switch (availability) {
63 | case 'Available':
64 | updateProximityToggle('on');
65 | textBoxUpdate('On');
66 | return;
67 |
68 | case 'Disabled':
69 | default:
70 | updateProximityToggle('off');
71 | textBoxUpdate('Off');
72 | return;
73 | }
74 | })
75 | .catch((err) => {
76 | console.error(`could not read current proximity mode, err: ${err.message}`)
77 | })
78 | }
79 |
80 | function textBoxUpdate(stringValue){
81 | xapi.command('UserInterface Extensions Widget SetValue', {
82 | WidgetId: textBoxId,
83 | Value: "Proximity "+stringValue,
84 | });
85 | }
86 | // Initialize at widget deployment
87 | refreshProximityToggle();
--------------------------------------------------------------------------------
/users/authController.js:
--------------------------------------------------------------------------------
1 | /*
2 | Generate web token, register users and enable login to admin web interface
3 | */
4 | var jwt = require('jsonwebtoken');
5 | var bcrypt = require('bcryptjs');
6 | var User = require('./user');
7 | var secret = process.env.SECRETTOKEN;
8 | var express = require('express');
9 | var router = express.Router();
10 | var bodyParser = require('body-parser');
11 | var log = require('../svrConfig/logger');
12 | var cors = require('cors');
13 | router.use(bodyParser.urlencoded({ extended: false }));
14 | router.use(bodyParser.json());
15 | router.use(cors({
16 | origin: '*',
17 | credentials: false,
18 | exposedHeaders: 'content-range',
19 |
20 | }));
21 |
22 | //register users. Admin account token is only allowed to register users using this method.
23 |
24 | router.post('/register', function(req, res) {
25 | var token = req.headers['x-access-token'];
26 | if (!token) return res.status(401).send({ auth: false, message: 'No token provided.' });
27 | if(token === process.env.WEBADMINTOKEN){
28 | var hashedPassword = bcrypt.hashSync(req.body.password, 8);
29 | var newUser = {
30 | name : req.body.name,
31 | email : req.body.email,
32 | password : hashedPassword
33 | };
34 | User.create(newUser, function (err, user) {
35 | if (err) return res.status(500).send("There was a problem registering the user.");
36 | // create a token
37 | var token = jwt.sign({ id: user._id }, secret, {
38 | expiresIn: 86400 // expires in 24 hours
39 | });
40 | res.status(200).send({ auth: true, token: token });
41 | });
42 | }
43 |
44 | });
45 | //Returns user details based on token
46 | router.get('/me', function(req, res) {
47 | var token = req.headers['x-access-token'];
48 | log.info("Header sent"+req.headers['x-access-token']);
49 | if (!token) return res.status(401).send({ auth: false, message: 'No token provided.' });
50 |
51 | jwt.verify(token, secret, function(err, decoded) {
52 | if (err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });
53 |
54 | res.status(200).send(decoded);
55 | });
56 | });
57 | //User login
58 | router.post('/login', function(req, res) {
59 | log.info("Login request from "+req.body.email);
60 | User.findOne({ email: req.body.email }, function (err, user) {
61 | if (err) return res.status(500).send('Error on the server.');
62 | if (!user) return res.status(404).send('No user found.');
63 | var passwordIsValid = bcrypt.compareSync(req.body.password, user.password);
64 | if (!passwordIsValid) return res.status(401).send({ auth: false, token: null });
65 | var token = jwt.sign({ id: user._id }, secret, {
66 | expiresIn: 86400 // expires in 24 hours
67 | });
68 | res.status(200).send({ auth: true, token: token });
69 | });
70 | });
71 |
72 | module.exports = router;
--------------------------------------------------------------------------------
/public/home/service-worker.js:
--------------------------------------------------------------------------------
1 | "use strict";var precacheConfig=[["/index.html","1e2b910f185de5c6962dab7a066eecfe"],["/static/css/main.65027555.css","41e5e45b9b5d9ecaa09b72c11eed3386"],["/static/js/main.ca670417.js","cefe6e20ec1914a717e21246341e20b2"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(t){return t.redirected?("body"in t?Promise.resolve(t.body):t.blob()).then(function(e){return new Response(e,{headers:t.headers,status:t.status,statusText:t.statusText})}):Promise.resolve(t)},createCacheKey=function(e,t,n,r){var a=new URL(e);return r&&a.pathname.match(r)||(a.search+=(a.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),a.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,n){var t=new URL(e);return t.hash="",t.search=t.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(t){return n.every(function(e){return!e.test(t[0])})}).map(function(e){return e.join("=")}).join("&"),t.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],r=new URL(t,self.location),a=createCacheKey(r,hashParamName,n,/\.\w{8}\./);return[r.toString(),a]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(r){return setOfCachedUrls(r).then(function(n){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(t){if(!n.has(t)){var e=new Request(t,{credentials:"same-origin"});return fetch(e).then(function(e){if(!e.ok)throw new Error("Request for "+t+" returned a response with status "+e.status);return cleanResponse(e).then(function(e){return r.put(t,e)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var n=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(t){return t.keys().then(function(e){return Promise.all(e.map(function(e){if(!n.has(e.url))return t.delete(e)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(t){if("GET"===t.request.method){var e,n=stripIgnoredUrlParameters(t.request.url,ignoreUrlParametersMatching),r="index.html";(e=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,r),e=urlsToCacheKeys.has(n));var a="/index.html";!e&&"navigate"===t.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],t.request.url)&&(n=new URL(a,self.location).toString(),e=urlsToCacheKeys.has(n)),e&&t.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(e){return console.warn('Couldn\'t serve response for "%s" from cache: %O',t.request.url,e),fetch(t.request)}))}});
--------------------------------------------------------------------------------
/flintServer/flintConfig.js:
--------------------------------------------------------------------------------
1 | //Flint server configuration
2 | require('dotenv').config();
3 | var SparkWebSocket = require('ciscospark-websocket-events');
4 | var accessToken = process.env.SPARK_BOT;
5 | var webHookUrl = "http://localhost:8080/flint";
6 | var Flint = require('node-flint');
7 | var log = require('../svrConfig/logger');
8 | var crud = require('../model/appController');
9 | var conversation = require('../flintConversations/conversations');
10 |
11 |
12 | // Spark Websocket Intialization - websocket support is limited for bots
13 | var sparkwebsocket = new SparkWebSocket(accessToken);
14 | sparkwebsocket.connect(function(err,res){
15 | if (!err){
16 | log.info('flintConfig.websockets : '+res);
17 | sparkwebsocket.setWebHookURL(webHookUrl);
18 |
19 | }else{
20 | log.error("flintConfig.websockets Startup: "+err);
21 | }
22 | });
23 |
24 | // flint options
25 | var config = {
26 | token: accessToken,
27 | port: process.env.WEBPORT,
28 | removeWebhooksOnStart: true,
29 | requeueMinTime: 500,
30 | requeueMaxRetry: 6,
31 | maxConcurrent: 5,
32 | minTime: 50
33 | };
34 |
35 | // init flint
36 | var flint = new Flint(config);
37 | flint.start();
38 |
39 | //control authorization to the bot
40 | function myAuthorizer(bot, trigger) {
41 | if(trigger.personEmail === process.env.APP_ADMIN) {
42 | return true;
43 | }
44 | else {
45 | bot.say("You are not authorized for use of this bot. I am outta here.");
46 | log.info("flintConfig.flint Access Info - unauthorized access : "+trigger.personEmail);
47 | bot.exit();
48 | return false;
49 | }
50 | }
51 |
52 | flint.setAuthorizer(myAuthorizer);
53 |
54 | // add flint event listeners
55 | flint.on('message', function(bot, trigger) {
56 | log.info('flintConfig : "%s" said "%s" in room "%s"', trigger.personEmail, trigger.text, trigger.roomTitle);
57 | log.info("flintConfig : This is the room ID:"+ trigger.roomId);
58 | });
59 |
60 | flint.on('initialized', function() {
61 | log.info('flintConfig : initialized %s rooms', flint.bots.length);
62 |
63 |
64 | });
65 | flint.on('spawn', function(bot) {
66 | log.info('new bot spawned in room: %s', bot.room.id);
67 | var spaceId = bot.room.id;
68 | crud.findSpace(spaceId, function(err,spDetails){
69 | if(err){
70 | //creates new room and writes new room to JOSN
71 | crud.createSpace(spaceId, function(data){
72 | log.info('flintConfig : Room created: '+ JSON.stringify(data));
73 | return bot.say({markdown:"This is a personal bot monitor tool. Please remove if you are not authorized."});
74 | });}
75 | if(spDetails.spaceActive === 'true') {
76 | return log.info("flintConfig.spawn: Room active.");
77 | }});
78 | bot.repeat;
79 | });
80 | flint.on('despawn', function(bot){
81 | log.info('flintConfig : Bot removed room: '+bot.room.id);
82 | var spaceId = bot.room.id;
83 | crud.findSpace(spaceId, function(err,spDetails){
84 | if(err) return log.error("flintConfig.despawn :"+err);
85 | crud.deleteSpace(spaceId, function(err, indexNo){
86 | if(err) return log.error("flint.despawn : "+err);
87 | log.info("flint.despawn : "+indexNo)
88 | return spDetails.writeToFile();
89 |
90 | })
91 |
92 | });
93 | });
94 | conversation(flint);
95 |
96 | module.exports = flint;
--------------------------------------------------------------------------------
/users/userController.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var bodyParser = require('body-parser');
4 | var range = require('express-range');
5 | var verifyToken = require('./verifyToken');
6 | var log= require('../svrConfig/logger');
7 | var bcrypt = require('bcryptjs');
8 | router.use(bodyParser.urlencoded({ extended: true }));
9 | router.use(bodyParser.json());
10 | var User = require('./user');
11 | var cors = require('cors');
12 | router.use(range({
13 | accept: 'users',
14 | limit: 10,
15 | }));
16 |
17 | router.use(cors({
18 | origin: '*',
19 | credentials: false,
20 | exposedHeaders: 'content-range',
21 |
22 | }));
23 |
24 |
25 | router.route('/')
26 | //AUTH USER
27 | .all((req, res, next) => verifyToken(req, res, next))
28 | // CREATES A NEW USER
29 | .post((req, res) => {
30 | log.info("User Post:" + JSON.stringify(req.body));
31 | var hashedPassword = bcrypt.hashSync(req.body.password, 8);
32 | var newUser = {
33 | name: req.body.name,
34 | email: req.body.email,
35 | password: hashedPassword,
36 | };
37 | User.create(newUser, function (err, user) {
38 | if (err) return res.status(500).send("There was a problem adding the information to the database.");
39 | res.status(200).send(user);
40 | });
41 | })
42 | // RETURNS ALL THE USERS IN THE DATABASE
43 | .get((req, res) => {
44 | log.info("User GET:" + JSON.stringify(req.body));
45 | User.find({}, function (err, users) {
46 | if (err) return res.status(500).send("There was a problem finding the users.");
47 | res.range({
48 | first: req.range.first,
49 | last: req.range.last,
50 | length: users.length
51 | });
52 | res.json({data: users.slice(req.range.first, req.range.last + 1), total: users.length});
53 | });
54 | });
55 |
56 | router.route('/:id')
57 | //AUTH USER
58 | .all((req, res, next) => verifyToken(req, res, next))
59 | // GETS A SINGLE USER FROM THE DATABASE
60 | .get((req, res) => {
61 | log.info("User GET/:ID:" + JSON.stringify(req.params.id));
62 | User.find({_id: req.params.id}, function (err, user) {
63 | if (err) return res.status(500).send("There was a problem finding the user.");
64 | if (!user) return res.status(404).send("No user found.");
65 | res.status(200).json({data: user});
66 | });
67 | })
68 |
69 | // DELETES A USER FROM THE DATABASE
70 | .delete((req, res) => {
71 | log.info("User DELETE/:ID:" + JSON.stringify(req.body));
72 | User.findOneAndDelete({_id: req.params.id}, function (err, user) {
73 | if (err) return res.status(500).send("There was a problem deleting the user.");
74 | log.info("User: " + user.name + " was deleted.");
75 | res.status(200).send("User: " + user.name + " was deleted.");
76 | });
77 | })
78 |
79 | // UPDATES A SINGLE USER IN THE DATABASE --- TBD
80 | .put((req, res) => {
81 | log.info("User PUT/:ID:" + JSON.stringify(req.body));
82 | log.info("URL ID: " + req.params.id);
83 | delete req.body.id;
84 | log.info("User PUT/:ID:" + JSON.stringify(req.body));
85 |
86 | User.findById(req.params.id, function (err, user) {
87 | var userPassword = user.password;
88 | if (userPassword != req.body.password) {
89 | userPassword = bcrypt.hashSync(req.body.password, 8)
90 | }
91 | if (err) return res.status(500).send("There was a problem updating the user.");
92 | user.name = req.body.name;
93 | user.email = req.body.email;
94 | user.password = userPassword;
95 | user.save(function (err) {
96 | if (err) {
97 | return res.status(500).send("There was a problem updating the user.");
98 | }
99 | log.info("Update success: " + JSON.stringify(user));
100 | return res.status(200).json({data: user});
101 | })
102 |
103 | });
104 | });
105 |
106 | module.exports = router;
107 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Telehealth Presence Application
2 |
3 | A mix of Webex Teams chatbot and XMPP application, TPA allows additional XMPP presence states for Cisco video codecs inside of a Jabber XMPP environment.
4 |
5 | There are a number of components to this application:
6 | * XMPP account registration for telehealth carts
7 | * Webex Teams chat bot to do reporting and control of application(bulk uploads and application troubleshooting)
8 | * Tracking availability of video endpoint administration pages to verify endpoint availability for enhanced presence status
9 | * Poll video endpoints API for people presence and other information such as DND.
10 | * Automatically update IP changes from CUCM using MAC information.
11 | * MongoDB backend holds user, endpoint and space collections.
12 | * Web admin interface for adding and removing users and endpoints.
13 | * REST API service that allows administration and publish endpoint status.
14 | All pieces work together to create an application that can add additional presence states for video endpoints similar to Cisco Movi.
15 |
16 | []
17 |
18 | ## Getting Started
19 |
20 | The following applications and hardware are required:
21 |
22 | * Cisco Unified Communications Manager
23 | * Cisco Presence Server
24 | * Cisco Video endpoint
25 | * Cisco Webex Teams
26 | * Nodejs
27 | * MongoDb
28 |
29 | ### Prerequisites
30 |
31 | Configuration required:
32 |
33 | * Video endpoint registration in CUCM
34 | * CUCM end user account enabled for Presence Server for each video endpoint
35 | * Endpoint and end user accounts paired with each other within CUCM
36 | * Cisco Webex Teams Bot account
37 | * Cisco Webex Teams Space ID to send application messages.
38 | * DNS FQDN videoPresence.:3001 eith via DNS record or host record.
39 | * install and configure Nodejs and MongoDb
40 | * Optional: MongoDb Compass
41 | https://www.mongodb.com/download-center#compass
42 |
43 | ### Installing
44 |
45 | #### Via Git
46 | ```bash
47 | mkdir myproj
48 | cd myproj
49 | git clone https://github.com/voipnorm/telehealthPresence.git
50 | npm install
51 | ```
52 |
53 | Set the following environment variables...
54 |
55 | ```
56 | SPARK_ROOM_ID=
57 | SPARK_BOT=
58 | WEBPORT=8080
59 | NODE_ENV=development
60 | SPARK_BOT_STRING=
61 | ALLOW_DOMAIN=
62 | APP_ADMIN=
63 | XMPPSERVER=
64 | XMPPCARTPWD=