├── README ├── bwpacksniffer.js └── xsi-events_monitor.js /README: -------------------------------------------------------------------------------- 1 | This is a Node.js server that sniffes xsi-events from/to Broadsoft BW. -------------------------------------------------------------------------------- /bwpacksniffer.js: -------------------------------------------------------------------------------- 1 | //*********************** variables and objects used in the proxy ****************** 2 | //no final, o object credentials sera um array porque vai ter varias ZD's apps conectando no proxy 3 | var credentials = [{ 4 | //url: 'xsp1.pbxl.net', <-- no need as it will be fixed for all apps 5 | username: 'BWS_Test.zentestuser1@pbxl.net', 6 | password: 'Borras123', 7 | appId: '', 8 | callhalf: '', 9 | subscriptionId: '', 10 | channelId: '', //probably will be necessary in the very near future.... 11 | zddomain: '', 12 | }, 13 | { 14 | //url: 'xsp1.pbxl.net', <-- no need as it will be fixed for all apps 15 | username: 'BWS_Test.zentestuser2@pbxl.net', 16 | password: 'Borras123', 17 | appId: '', 18 | callhalf: '', 19 | subscriptionId: '', 20 | channelId: '', //probably will be necessary in the very near future.... 21 | zddomain: '', 22 | }]; 23 | 24 | //this array stores all the BW groups from all clients. A subscription must be opened for each one 25 | var BW_groups = ['PBXL_Test',]; 26 | 27 | //this object stores data related to BW for each ZD's app. These data are unique to the application 28 | //as there is only one channel and subscription, that will receive all events from BW related to 29 | //all opened calls 30 | var bwconnection = { 31 | applicationId: 'bweventsniffer', 32 | channelSetId: 'bweventsnifferchannelset', 33 | channelId: '', 34 | heartbeatIntervalId: '', //TODO: this variable is global as there is only 1 streaming http 35 | subscriptionId: '', 36 | callhalf: '', 37 | }; 38 | 39 | //**************** global constants to be used by all ZD's apps ********************* 40 | var HEARTBEAT_INTERVAL = 15000; 41 | var BW_URL = 'xsp1.pbxl.net'; 42 | var ZD_URL = 'pbxltest.zendesk.com'; 43 | var EVENT_CLOSE = ''; 44 | var CHANNEL_CLOSE = ''; 45 | var HEARTBEAT_CLOSE = ''; 46 | 47 | //****************** required objects and libs *********************** 48 | var express = require('express'); 49 | var http = require('http'); 50 | var path = require('path'); 51 | //var https = require('https'); //the main http session(streaming http) 52 | var app = express(); 53 | var parseString = require('xml2js').parseString; 54 | var DOMParser = require('xmldom').DOMParser; 55 | var fs = require('fs'); 56 | var Log = require('log'); 57 | var log = new Log('debug', fs.createWriteStream('snifferlog.txt', {'flags':'a'})); 58 | 59 | //******************* setup the proxy *********************** 60 | // all environments 61 | app.set('port', process.env.PORT || 3000); 62 | app.use(express.favicon()); 63 | app.use(express.logger('dev')); 64 | app.use(express.bodyParser()); 65 | app.use(express.methodOverride()); 66 | 67 | app.use(express.static(path.join(__dirname, 'public'))); 68 | 69 | app.use(app.router); 70 | 71 | // development only 72 | if ('development' == app.get('env')) { 73 | app.use(express.errorHandler()); 74 | } 75 | 76 | //******************* commands from client(ZD app or test web page) ************************ 77 | app.all('*', function(req, res, next){ 78 | res.header("Access-Control-Allow-Origin", "https://pbxltest.zendesk.com"); 79 | res.header("Access-Control-Allow-Headers", "X-Requested-With, Access-Control-Allow-Credentials, Authorization"); 80 | res.header("Access-Control-Allow-Credentials", true); 81 | next(); 82 | }); 83 | 84 | app.all("/log_in/", function(req, res){ 85 | console.log("/register_user/ received " + req.query); 86 | log.info(", username: " + req.param('username') + 87 | ", password: " + req.param('password') + 88 | ", appId: " + req.param('appid') + 89 | ", zddomain: " + req.param('zedomain') + '\r\n'); 90 | 91 | res.writeHead(200, {'Content-Type': 'text/plain'}); 92 | res.end(); 93 | for(var index in credentials){ 94 | if(credentials[index].username === req.param('username')){ 95 | credentials[index].appId = req.param('appid'); 96 | credentials[index].zddomain = req.param('zddomain'); 97 | sendSignedInToZdApp(credentials[index].zddomain, credentials[index].appId, 'signedIn'); 98 | }else{ //this user is not registered so, send back an error message to check the credentials 99 | sendSignInErrorToZD(req.param('zddomain'), req.param('appid')); 100 | } 101 | } 102 | }); 103 | 104 | app.all("/make_call/", function(req, res){ 105 | console.log("/make_call/ received " + req.query); 106 | res.send("Received your request to make a call to " + req.param('destination') + ". Now, wait a bit!"); 107 | makeCall(req.param('destination'),req.param('username')); 108 | }); 109 | 110 | app.all('/accept_call', function(req, res){ 111 | console.log("/accept_call/ received" + req.query); 112 | res.send("Answering the call..."); 113 | acceptCall(req.param('username')); 114 | }); 115 | 116 | app.all('/disconnect_call/', function(req, res){ 117 | console.log("/disconnect_call/ received" + req.query); 118 | res.send("Disconnecting call...") 119 | disconnectCall(req.param('username')); 120 | }); 121 | 122 | app.all('/reject_call/', function(req, res){ 123 | console.log("/reject_call/ received" + req.query); 124 | res.send("Rejecting call...") 125 | rejectCall(); //TODO: implement rejectCall(); 126 | }); 127 | 128 | //********************** event processing work functions ************************* 129 | getName = function(){ 130 | console.log("INFO: getName ->"); 131 | var options = { 132 | host: BW_URL, 133 | path: "/com.broadsoft.xsi-actions/v2.0/user/" + credentials.username + "/profile", 134 | method: 'GET', 135 | auth: credentials.username + ':' + credentials.password 136 | }; 137 | var http = require('http'); 138 | var req = http.request(options, function(res) { 139 | log.info("< response from BW: " + res.statusCode + '\r\n'); 140 | if(res.statusCode === 200){ 141 | requestChannel(); 142 | }else{ 143 | sendSignInErrorToZD(credentials.appId); 144 | } 145 | }); 146 | 147 | req.on('error', function(e) { 148 | log.info('problem with request: ' + e.message + '\r\n'); 149 | }); 150 | 151 | req.end(); 152 | log.info('> GET ' + BW_URL + "/com.broadsoft.xsi-actions/v2.0/user/" + credentials.username + "/profile \r\n"); 153 | }; 154 | 155 | //TODO: probably, will have to open one channel for each user... 156 | //if so, the function must receive the username and password. 157 | requestChannel = function(){ 158 | console.log("INFO: requestChannel ->"); 159 | var options = { 160 | host: BW_URL, 161 | path: "/com.broadsoft.async/com.broadsoft.xsi-events/v2.0/channel", 162 | method: 'POST', 163 | //auth: username + ':' + password, 164 | auth: 'jp_zentest@pbxl.net:Borras123', 165 | headers: {'Content-Type': 'text/xml'} 166 | }; 167 | var req = http.request(options, function(res){ 168 | log.info("<- response from BW: " + res.statusCode + '\r\n'); 169 | if(res.statusCode != 200){ 170 | console.log("Error in requestChannel. Response status is " + res.statusCode); 171 | console.log("Will try again..."); 172 | log.error("<- response from BW: " + res.statusCode + '\r\n'); 173 | log.info("Will try again..."); 174 | requestChannel(); 175 | } 176 | res.setEncoding('utf8'); 177 | var resbody = ""; 178 | res.on('data', function (chunk) { 179 | console.log(chunk + '\r\n' + '\r\n'); 180 | log.info("< " + chunk + '\r\n'); 181 | resbody += chunk; 182 | if(resbody.indexOf(EVENT_CLOSE) >= 0 || resbody.indexOf(CHANNEL_CLOSE) >= 0 || resbody.indexOf(HEARTBEAT_CLOSE) >= 0){ 183 | parseChunk(resbody); 184 | resbody = ""; //prepares to receive a new event, if any! 185 | }else if(resbody.indexOf('= 0){ 186 | //do nothing here as it is only answer from the heartbeat command 187 | } 188 | }); 189 | res.on('end',function(){ 190 | /*console.log(" response data: " + resbody); 191 | parseString(resbody, function (err, result) { 192 | console.log(" Response's body: " + result); 193 | });*/ 194 | }); 195 | res.on('error', function(e){ 196 | console.log("Error on requestChannel. Status is " + e.status); 197 | console.log("Error message: " + e.message); 198 | console.log("Will try again..."); 199 | requestChannel(); 200 | }); 201 | }); 202 | 203 | req.on('error', function(e) { 204 | console.log('problem with requestChannel request: ' + e.message); 205 | }); 206 | console.log("channelSetId in requestChannel function is " + bwconnection.channelSetId); 207 | var xml_data = ''; 208 | xml_data = xml_data + ''; 209 | xml_data = xml_data + '' + bwconnection.channelSetId + ''; 210 | xml_data = xml_data + '1'; 211 | xml_data = xml_data + '100'; 212 | xml_data = xml_data + '3600'; 213 | xml_data = xml_data + '' + bwconnection.applicationId + ''; 214 | xml_data = xml_data + ''; 215 | 216 | req.write(xml_data); 217 | req.end(); 218 | log.info('> POST ' + BW_URL + '/com.broadsoft.async/com.broadsoft.xsi-events/v2.0/channel \r\n' + xml_data + '\r\n'); 219 | }; 220 | 221 | startHeartbeat = function(channelid){ 222 | console.log("INFO: startHeartbeat ->"); 223 | var options = { 224 | host: BW_URL, 225 | path: '/com.broadsoft.xsi-events/v2.0/channel/' + channelid + "/heartbeat", 226 | method: 'PUT', 227 | //auth: username + ':' + password 228 | auth: 'jp_zentest@pbxl.net:Borras123' //the correct is the above!!! 229 | }; 230 | var http = require('http'); 231 | var req = http.request(options, function(res) { 232 | log.info("< response from BW on heartbeat: " + res.statusCode + '\r\n'); 233 | }); 234 | 235 | req.on('error', function(e) { 236 | console.log('problem with heartbeat request: ' + e.message); 237 | }); 238 | 239 | req.end(); 240 | log.info('> PUT ' + BW_URL + '/com.broadsoft.xsi-events/v2.0/channel/' + bwconnection.channelId + "/heartbeat \r\n"); 241 | 242 | heartbeatIntervalId = setTimeout(function(){ 243 | if(bwconnection.channelId != ''){ 244 | startHeartbeat(); 245 | }else{ 246 | //if there is no channel, then start again from requesting a new one 247 | requestChannel(); 248 | } 249 | }, HEARTBEAT_INTERVAL); 250 | }; 251 | 252 | //need to make a subscription for each user registered 253 | eventSubscription = function(event, username, password){ 254 | console.log("INFO: eventSubscription to " + username + " ->"); 255 | var options = { 256 | host: BW_URL, 257 | //path: "/com.broadsoft.xsi-events/v2.0/user/" + username, 258 | path: "/com.broadsoft.xsi-events/v2.0/serviceprovider/PBXL%20Inc./group/PBXL_Test", 259 | method: 'POST', 260 | auth: 'jp_zentest@pbxl.net:Borras123', 261 | headers: {'Content-Type': 'text/xml'} 262 | }; 263 | var http = require('http'); 264 | var req = http.request(options, function(res){ 265 | console.log("<- response from BW: " + res.statusCode); 266 | res.setEncoding('utf8'); 267 | var resbody = ""; 268 | res.on('data', function (chunk) { 269 | log.info('< ' + chunk + '\r\n'); 270 | }); 271 | res.on('end',function(){ 272 | //TODO 273 | }); 274 | 275 | }); 276 | 277 | req.on('error', function(e) { 278 | console.log('problem with request: ' + e.message); 279 | }); 280 | 281 | console.log("channelSetId in eventSubscription function is " + bwconnection.channelSetId); 282 | var xml_data = ''; 283 | xml_data = xml_data + ''; 284 | xml_data = xml_data + "" + event + ""; 285 | xml_data = xml_data + "3600"; 286 | xml_data = xml_data + "" + bwconnection.channelSetId + ""; 287 | xml_data = xml_data + '' + bwconnection.applicationId + ''; 288 | xml_data = xml_data + ""; 289 | 290 | req.write(xml_data); 291 | req.end(); 292 | log.info('> POST ' + BW_URL + "/com.broadsoft.xsi-events/v2.0/serviceprovider/PBXL%20Inc./group/PBXL_Test" + '\r\n' + xml_data + '\r\n'); 293 | }; 294 | 295 | sendResponseEvent = function(eventId){ 296 | console.log("INFO: sendResponseEvent ->"); 297 | var options = { 298 | host: BW_URL, 299 | path: "/com.broadsoft.xsi-events/v2.0/channel/eventresponse", 300 | method: 'POST', 301 | auth: 'jp_zentest@pbxl.net:Borras123', 302 | //auth: username + ':' + password, 303 | headers: {'Content-Type': 'text/xml'} 304 | }; 305 | var http = require('http'); 306 | var req = http.request(options, function(res){ 307 | //console.log("<- response from BW: " + res.statusCode); 308 | }); 309 | 310 | req.on('error', function(e) { 311 | console.log(' problem with sendResponseEvent request: ' + e.message); 312 | }); 313 | 314 | var xml_data = ''; 315 | xml_data = xml_data + ""; 316 | xml_data = xml_data + "" + eventId + ""; 317 | xml_data = xml_data + "200"; 318 | xml_data = xml_data + "OK"; 319 | xml_data = xml_data + ""; 320 | 321 | req.write(xml_data); 322 | req.end(); 323 | 324 | log.info('> POST ' + BW_URL + "/com.broadsoft.xsi-events/v2.0/channel/eventresponse \r\n" + xml_data + '\r\n'); 325 | }; 326 | 327 | parseChunk = function(chunk){ //chunk is already string 328 | //now, look for what kind of event we received 329 | if(chunk.indexOf('= 0){ //= 0){ 337 | //TODO: for now do nothing as it is only answer from heartbeat 338 | }else if(chunk.indexOf('ChannelTerminatedEvent') >= 0){ 339 | console.log("WARNING: ChannelTerminatedEvent <-"); 340 | bwconnection.channelId = ''; 341 | }else if(chunk.indexOf('= 0){//xsi:Event received. Now see if it is channel disconnection 342 | //for every xsi:Event, needs to send event Response 343 | var xmldoc = new DOMParser().parseFromString(chunk,'text/xml'); 344 | var eventid = xmldoc.getElementsByTagName('xsi:eventID').item(0).firstChild.nodeValue; 345 | sendResponseEvent(eventid); 346 | var userid = xmldoc.getElementsByTagName('xsi:userId').item(0).firstChild.nodeValue; 347 | var targetid = xmldoc.getElementsByTagName('xsi:targetId').item(0).firstChild.nodeValue; 348 | var remoteparty = xmldoc.getElementsByTagName('xsi:address').item(0).firstChild.nodeValue.substring(5); 349 | var eventType = xmldoc.getElementsByTagName('xsi:eventData').item(0).getAttribute('xsi1:type').trim(); 350 | eventType = eventType.substring(4);//string off the prefix "xsi:" from the eventType 351 | switch(eventType){ 352 | case 'CallReceivedEvent': 353 | var countrycode = xmldoc.getElementsByTagName('xsi:address').item(0).getAttribute('countryCode'); 354 | if(remoteparty.indexOf(countrycode) >= 0){ 355 | remoteparty = remoteparty.replace(countrycode, '0'); 356 | } 357 | console.log("INFO: CallReceived(from: " + remoteparty + " to: " + targetid + ") <-"); 358 | break; 359 | case 'CallAnsweredEvent': 360 | console.log("INFO: CallAnsweredEvent(userid: " + remoteparty + ") <-"); 361 | break; 362 | case 'CallReleasedEvent': 363 | console.log("INFO: CallReleasedEvent(userid: " + remoteparty + ") <-"); 364 | break; 365 | case 'CallUpdatedEvent': 366 | console.log("INFO: CallUpdatedEvent <-"); 367 | break; 368 | case 'CallSubscriptionEvent': 369 | console.log("INFO: CallSubscriptionEvent <-"); 370 | break; 371 | case 'CallOriginatedEvent': 372 | console.log("INFO: CallOriginatedEvent(from: " + targetid + " to: " + remoteparty + ") <-"); 373 | break; 374 | case 'CallRetrievedEvent': 375 | console.log("INFO: CallRetrievedEvent(userid: " + remoteparty + ") <-"); 376 | break; 377 | case 'CallHeldEvent': 378 | console.log('INFO: CallHelEvent <-'); 379 | break; 380 | case 'CallTransferredEvent': 381 | console.log('INFO: CallTransferredEvent <-'); 382 | break; 383 | case 'CallRedirectedEvent': 384 | console.log('INFO: CallRedirectedEvent'); 385 | break; 386 | case 'CallHeldEvent': 387 | case 'DoNotDisturbEvent': 388 | case 'CallForwardingAlwaysEvent': 389 | case 'RemoteOfficeEvent': 390 | break; 391 | default: 392 | } 393 | } 394 | }; 395 | 396 | //**************** listen for incoming events *********************** 397 | requestChannel(); -------------------------------------------------------------------------------- /xsi-events_monitor.js: -------------------------------------------------------------------------------- 1 | //*********************** variables and objects used in the proxy ****************** 2 | //no final, o object credentials sera um array porque vai ter varias ZD's apps conectando no proxy 3 | var credentials = [{ 4 | username: 'BWS_Test.zentestuser1@pbxl.net', 5 | password: 'Borras123', 6 | }, 7 | { 8 | username: 'BWS_Test.zentestuser2@pbxl.net', 9 | password: 'Borras123', 10 | } 11 | ]; 12 | 13 | //this array stores all the BW groups from all clients. A subscription must be opened for each one 14 | var BW_groups = ['PBXL_Test',]; 15 | 16 | //this object stores data related to BW for each ZD's app. These data are unique to the application 17 | //as there is only one channel and subscription, that will receive all events from BW related to 18 | //all opened calls 19 | var bwconnection = { 20 | applicationId: 'xsisnifferchannel', 21 | channelSetId: 'xsisnifferchannelset', 22 | channelId: '', 23 | heartbeatIntervalId: '', 24 | channelUpdateIntervalId: '', 25 | subscriptionId: '', 26 | subscriptionUpdateIntervalId: '', 27 | callhalf: '', 28 | groupadmin: 'jp_zentest@pbxl.net', 29 | groupadminpassword: 'Borras123', 30 | serviceprovider: 'PBXL%20Inc.', 31 | groupId: 'PBXL_Test', 32 | }; 33 | 34 | //**************** global constants to be used by all ZD's apps ********************* 35 | var HEARTBEAT_INTERVAL = 15000; 36 | var CHANNEL_UPDATE_INTERVAL = 1800000; 37 | var SUBSCRIPTION_UPDATE_INTERVAL = 1600000; 38 | var BW_URL = 'xsp1.pbxl.net'; 39 | var SUBSCRIPTION_CLOSE = ''; 40 | var EVENT_CLOSE = ''; 41 | var CHANNEL_CLOSE = ''; 42 | var HEARTBEAT_CLOSE = ''; 43 | 44 | //****************** required objects and libs *********************** 45 | require('monitor').start(); 46 | var express = require('express'); 47 | var http = require('http'); //http object used to connect the proxy with BW server 48 | var path = require('path'); 49 | var app = express(); 50 | var parseString = require('xml2js').parseString; 51 | var DOMParser = require('xmldom').DOMParser; 52 | var fs = require('fs'); 53 | var Log = require('log'); 54 | var log = new Log('debug', fs.createWriteStream('xsi-events-monitor_log.txt', {'flags':'a'})); 55 | 56 | //******************* setup the proxy *********************** 57 | // all environments 58 | app.set('port', process.env.PORT || 5000); 59 | app.set('views', __dirname + '/views'); 60 | app.engine('html', require('ejs').renderFile); 61 | app.use(express.favicon()); 62 | app.use(express.logger('dev')); 63 | app.use(express.bodyParser()); 64 | app.use(express.methodOverride()); 65 | 66 | app.use(express.static(path.join(__dirname, 'public'))); 67 | 68 | app.use(app.router); 69 | 70 | // development only 71 | if ('development' == app.get('env')) { 72 | app.use(express.errorHandler()); 73 | } 74 | 75 | //******************* commands from client(ZD app or test web page) ************************ 76 | //TODO: the Access-Control is not working -> have to check it 77 | app.all('*', function(req, res, next){ 78 | //res.header("Access-Control-Allow-Origin", "https://pbxltest.zendesk.com"); 79 | //res.header("Access-Control-Allow-Origin", "https://ap.salesforce.com"); 80 | res.header("Access-Control-Allow-Origin", "*"); 81 | res.header("Access-Control-Allow-Headers", "X-Requested-With, Access-Control-Allow-Credentials, Authorization"); 82 | res.header("Access-Control-Allow-Credentials", true); 83 | next(); 84 | }); 85 | 86 | app.all('/', function(req, res, next){ 87 | console.log("/ received " + req.query); 88 | log.info('<- / received '); 89 | res.send('nothing here for you...'); 90 | }); 91 | 92 | //connect to be used for some web app to receive and display xsi events 93 | app.post("/connect/", function(req, res){ 94 | var username = req.param('username'); 95 | console.log("<- /connect/ called from user " + username); 96 | log.info('<- /connect/ called from user ' + username); 97 | for(var index in credentials){ 98 | if(credentials[index].username == username){ 99 | console.log("found user " + username + " in credentials and will now connect it"); 100 | credentials[index].appId = res; 101 | var connectresponse = ''; 102 | connectresponse += 'ConnectResponse'; 103 | connectresponse += ''; 104 | console.log("-> ConnectResponse to SFDC(" + username + ")"); 105 | log.info("-> ConnectResponse to SFDC(" + username + ")"); 106 | res.setHeader('Content-Type', 'text/xml; charset=UTF-8'); 107 | res.setHeader('Transfer-Encoding', 'chunked'); 108 | res.write(connectresponse, 'utf8'); 109 | }else{ 110 | console.log("Username " + username + " not in credentials"); 111 | } 112 | } 113 | }); 114 | 115 | //********************** event processing work functions ************************* 116 | requestChannel = function(){ 117 | console.log("-> INFO: requestChannel"); 118 | var options = { 119 | host: BW_URL, 120 | path: "/com.broadsoft.async/com.broadsoft.xsi-events/v2.0/channel", 121 | method: 'POST', 122 | auth: bwconnection.groupadmin + ':' + bwconnection.groupadminpassword, 123 | headers: {'Content-Type': 'text/xml'} 124 | }; 125 | var req = http.request(options, function(res){ 126 | if(res.statusCode != 200 && res.statusCode != 401 && res.statusCode != 403){//not auth problem 127 | console.log("Error in requestChannel. Response status is " + res.statusCode); 128 | log.error("<- response from BW: " + res.statusCode + '\r\n'); 129 | console.log("Will try again in 5 secs..."); 130 | log.info("Will try again in 5 secs..."); 131 | setTimeout(function(){ 132 | requestChannel(); 133 | },5000); 134 | } 135 | res.setEncoding('utf8'); 136 | var resbuffer = ''; 137 | var index = 0; 138 | var resbody = ""; 139 | res.on('data', function (chunk) { 140 | resbody += chunk; 141 | if(resbody.indexOf(EVENT_CLOSE) >= 0 || resbody.indexOf(CHANNEL_CLOSE) >= 0 || 142 | resbody.indexOf(HEARTBEAT_CLOSE) >= 0 || resbody.indexOf(SUBSCRIPTION_CLOSE) >= 0){ 143 | log.info("<- " + chunk + '\r\n'); 144 | console.log(chunk); 145 | parseChunk(resbody); 146 | resbody = ""; //prepares to receive a new event, if any! 147 | }else if(resbody.indexOf('= 0){ 148 | //do nothing here as it is only answer from the heartbeat command 149 | }else{ 150 | resbuffer += chunk; 151 | } 152 | }); 153 | res.on('error', function(e){ 154 | console.log("Error on requestChannel. Status is " + e.status); 155 | console.log("Error message: " + e.message); 156 | console.log("Will try again..."); 157 | requestChannel(); 158 | }); 159 | res.on('close', function(e){ 160 | console.log("ERROR: Main connection closed."); 161 | log.error("Main connection closed."); 162 | }); 163 | }); 164 | 165 | req.on('error', function(e) { 166 | console.log('problem with requestChannel request: ' + e.message); 167 | }); 168 | console.log("channelSetId in requestChannel function is " + bwconnection.channelSetId); 169 | var xml_data = ''; 170 | xml_data = xml_data + ''; 171 | xml_data = xml_data + '' + bwconnection.channelSetId + ''; 172 | xml_data = xml_data + '1'; 173 | xml_data = xml_data + '100'; 174 | xml_data = xml_data + '3600'; 175 | xml_data = xml_data + '' + bwconnection.applicationId + ''; 176 | xml_data = xml_data + ''; 177 | 178 | req.write(xml_data); 179 | req.end(); 180 | log.info('-> POST ' + BW_URL + '/com.broadsoft.async/com.broadsoft.xsi-events/v2.0/channel \r\n' + xml_data + '\r\n'); 181 | }; 182 | 183 | updateChannel = function(){ 184 | console.log("-> INFO: updateChannel ID " + bwconnection.channelId); 185 | log.info("-> updateChannel ID " + bwconnection.channelId); 186 | var options = { 187 | host: BW_URL, 188 | path: "/com.broadsoft.xsi-events/v2.0/channel/" + bwconnection.channelId, 189 | method: 'PUT', 190 | auth: bwconnection.groupadmin + ':' + bwconnection.groupadminpassword, 191 | headers: {'Content-Type': 'text/xml'} 192 | }; 193 | var http = require('http'); 194 | var req = http.request(options, function(res){ 195 | if(res.statusCode != 200){ 196 | console.log("Error in updateChannel. Response status is " + res.statusCode); 197 | log.error("<- response from BW: " + res.statusCode + '\r\n'); 198 | } 199 | }); 200 | req.on('error', function(e) { 201 | console.log('problem with updateChannel request: ' + e.message); 202 | }); 203 | console.log("channelSetId in requestChannel function is " + bwconnection.channelSetId); 204 | var xml_data = ''; 205 | xml_data = xml_data + ''; 206 | xml_data = xml_data + '3800'; 207 | xml_data = xml_data + ''; 208 | 209 | req.write(xml_data); 210 | req.end(); 211 | log.info('-> POST ' + BW_URL + '/com.broadsoft.xsi-events/v2.0/channel/' + bwconnection.channelId + '\r\n' + xml_data + '\r\n'); 212 | }; 213 | 214 | startHeartbeat = function(){ 215 | if(bwconnection.channelId != ''){ 216 | console.log("-> INFO: startHeartbeat"); 217 | var options = { 218 | host: BW_URL, 219 | path: '/com.broadsoft.xsi-events/v2.0/channel/' + bwconnection.channelId + "/heartbeat", 220 | method: 'PUT', 221 | auth: bwconnection.groupadmin + ':' + bwconnection.groupadminpassword, 222 | }; 223 | var http = require('http'); 224 | var req = http.request(options, function(res) { 225 | if(res.statusCode != 200){//some problems happened with the channel. Open a new one 226 | log.error("<- response from BW on heartbeat: " + res.statusCode + '\r\n'); 227 | } 228 | log.info("<- response from BW on heartbeat: " + res.statusCode + '\r\n'); 229 | }); 230 | 231 | req.on('error', function(e) { 232 | console.log('problem with heartbeat request: ' + e.message); 233 | }); 234 | 235 | req.end(); 236 | log.info('-> PUT ' + BW_URL + '/com.broadsoft.xsi-events/v2.0/channel/' + bwconnection.channelId + "/heartbeat \r\n"); 237 | }else{ 238 | console.log("WARNING: now heartbeat sent as there is no channel openned"); 239 | log.warning("WARNING: now heartbeat sent as there is no channel openned"); 240 | } 241 | }; 242 | 243 | //need to make a subscription for each user registered 244 | eventSubscription = function(event){ 245 | console.log("-> INFO: eventSubscription"); 246 | var options = { 247 | host: BW_URL, 248 | path: "/com.broadsoft.xsi-events/v2.0/serviceprovider/" + bwconnection.serviceprovider + 249 | "/group/" + bwconnection.groupId, 250 | method: 'POST', 251 | auth: bwconnection.groupadmin + ':' + bwconnection.groupadminpassword, 252 | headers: {'Content-Type': 'text/xml'} 253 | }; 254 | var http = require('http'); 255 | var req = http.request(options, function(res){ 256 | if(res.statusCode != 200){ 257 | console.log("ERROR: <- Subscription response from BW: " + res.statusCode); 258 | log.error("<- Subscription response from BW: " + res.statusCode); 259 | }else{ 260 | res.setEncoding('utf8'); 261 | res.on('data', function(response){ 262 | console.log("<- Subscription Response: " + response + '\r\n'); 263 | log.info("<- Subscription Response: " + response + '\r\n'); 264 | var xmldoc = new DOMParser().parseFromString(response,'text/xml'); 265 | bwconnection.subscriptionId = xmldoc.getElementsByTagName('subscriptionId').item(0).firstChild.nodeValue; 266 | bwconnection.subscriptionUpdateIntervalId = setInterval(updateSubscription, SUBSCRIPTION_UPDATE_INTERVAL); 267 | }) 268 | } 269 | }); 270 | 271 | req.on('error', function(e) { 272 | console.log('problem with request: ' + e.message); 273 | }); 274 | 275 | console.log("channelSEtId in eventSubscription function is " + bwconnection.channelSetId); 276 | var xml_data = ''; 277 | xml_data = xml_data + ''; 278 | xml_data = xml_data + "" + event + ""; 279 | xml_data = xml_data + "3600"; 280 | xml_data = xml_data + "" + bwconnection.channelSetId + ""; 281 | xml_data = xml_data + '' + bwconnection.applicationId + ''; 282 | xml_data = xml_data + ""; 283 | 284 | req.write(xml_data); 285 | req.end(); 286 | log.info('-> POST ' + BW_URL + "/com.broadsoft.xsi-events/v2.0/serviceprovider/" + bwconnection.serviceprovider + "/group/" + bwconnection.groupId + '\r\n' + xml_data + '\r\n'); 287 | }; 288 | 289 | updateSubscription = function(){ 290 | console.log("-> INFO: updateSubscription ID " + bwconnection.subscriptionId); 291 | log.info("-> updateSubscription ID " + bwconnection.subscriptionId); 292 | var options = { 293 | host: BW_URL, 294 | path: "/com.broadsoft.xsi-events/v2.0/subscription/" + bwconnection.subscriptionId , 295 | method: 'PUT', 296 | auth: bwconnection.groupadmin + ':' + bwconnection.groupadminpassword, 297 | headers: {'Content-Type': 'text/xml'} 298 | }; 299 | var http = require('http'); 300 | var req = http.request(options, function(res){ 301 | if(res.statusCode != 200){ 302 | console.log("<- Subscription Update response from BW: " + res.statusCode); 303 | log.error("<- Subscription Update response from BW: " + res.statusCode); 304 | } 305 | }); 306 | 307 | req.on('error', function(e) { 308 | console.log('problem with request: ' + e.message); 309 | }); 310 | 311 | var xml_data = ''; 312 | xml_data = xml_data + ''; 313 | xml_data = xml_data + "3800"; 314 | xml_data = xml_data + ""; 315 | 316 | req.write(xml_data); 317 | req.end(); 318 | log.info('-> POST ' + BW_URL + "/com.broadsoft.xsi-events/v2.0/subscription/" + bwconnection.subscriptionId + '\r\n' + xml_data + '\r\n'); 319 | }; 320 | 321 | userSubscription = function(username, password, event, callback){ 322 | console.log("-> INFO: userSubscription"); 323 | var options = { 324 | host: BW_URL, 325 | path: "/com.broadsoft.xsi-events/v2.0/user/" + username, 326 | method: 'POST', 327 | auth: 'jp_zentest@pbxl.net' + ':' + 'Borras123', 328 | headers: {'Content-Type': 'text/xml'} 329 | }; 330 | var http = require('http'); 331 | var req = http.request(options, function(res){ 332 | if(res.statusCode != 200){ 333 | console.log("ERROR: <- Subscription response from BW: " + res.statusCode); 334 | log.error("<- Subscription response from BW: " + res.statusCode); 335 | }else{ 336 | res.setEncoding('utf8'); 337 | res.on('data', function(response){ 338 | console.log("<- Subscription Response: " + response + '\r\n'); 339 | log.info("<- Subscription Response: " + response + '\r\n'); 340 | //var xmldoc = new DOMParser().parseFromString(response,'text/xml'); 341 | //bwconnection.subscriptionId = xmldoc.getElementsByTagName('subscriptionId').item(0).firstChild.nodeValue; 342 | //bwconnection.subscriptionUpdateIntervalId = setInterval(updateSubscription, SUBSCRIPTION_UPDATE_INTERVAL); 343 | }) 344 | } 345 | callback(res.statusCode); 346 | }); 347 | 348 | req.on('error', function(e) { 349 | console.log('problem with request: ' + e.message); 350 | }); 351 | 352 | console.log("channelSEtId in eventSubscription function is " + bwconnection.channelSetId); 353 | var xml_data = ''; 354 | xml_data = xml_data + ''; 355 | xml_data = xml_data + "" + event + ""; 356 | xml_data = xml_data + "3600"; 357 | xml_data = xml_data + "" + bwconnection.channelSetId + ""; 358 | xml_data = xml_data + '' + bwconnection.applicationId + ''; 359 | xml_data = xml_data + ""; 360 | 361 | req.write(xml_data); 362 | req.end(); 363 | log.info('-> POST ' + BW_URL + "/com.broadsoft.xsi-events/v2.0/user/" + username + '\r\n' + xml_data + '\r\n'); 364 | }; 365 | 366 | sendResponseEvent = function(eventId){ 367 | console.log("-> INFO: sendResponseEvent"); 368 | var options = { 369 | host: BW_URL, 370 | path: "/com.broadsoft.xsi-events/v2.0/channel/eventresponse", 371 | method: 'POST', 372 | auth: bwconnection.groupadmin + ':' + bwconnection.groupadminpassword, 373 | headers: {'Content-Type': 'text/xml'} 374 | }; 375 | var http = require('http'); 376 | var req = http.request(options, function(res){ 377 | if(res.statusCode != 200){ 378 | console.log("<- response from BW: " + res.statusCode); 379 | log.error("<- response from BW: " + res.statusCode); 380 | } 381 | }); 382 | 383 | req.on('error', function(e) { 384 | console.log('problem with sendResponseEvent request: ' + e.message); 385 | }); 386 | 387 | var xml_data = ''; 388 | xml_data = xml_data + ""; 389 | xml_data = xml_data + "" + eventId + ""; 390 | xml_data = xml_data + "200"; 391 | xml_data = xml_data + "OK"; 392 | xml_data = xml_data + ""; 393 | 394 | req.write(xml_data); 395 | req.end(); 396 | 397 | log.info('-> POST ' + BW_URL + "/com.broadsoft.xsi-events/v2.0/channel/eventresponse \r\n" + xml_data + '\r\n'); 398 | }; 399 | 400 | parseChunk = function(chunk){ //chunk is already string 401 | //now, look for what kind of event we received 402 | if(chunk.indexOf('= 0){ //= 0){ 424 | //TODO: for now do nothing as it is only answer from heartbeat 425 | }else if(chunk.indexOf('ChannelTerminatedEvent') >= 0){ 426 | console.log("WARNING: ChannelTerminatedEvent <-"); 427 | log.warning("ChannelTerminatedEvent <-"); 428 | bwconnection.channelId = ''; 429 | requestChannel(); 430 | }else if(chunk.indexOf('SubscriptionTerminatedEvent') >= 0){//will open a new subscription 431 | console.log('WARNING: SubscriptionTerminatedEvent <-'); 432 | log.warning('SubscriptionTerminatedEvent <-'); 433 | bwconnection.subscriptionId = ''; 434 | }else if(chunk.indexOf('= 0){//xsi:Event received. Now see if it is channel disconnection 435 | //for every xsi:Event, needs to send event Response 436 | try{ 437 | var xmldoc = new DOMParser().parseFromString(chunk,'text/xml'); 438 | var eventid = xmldoc.getElementsByTagName('xsi:eventID').item(0).firstChild.nodeValue; 439 | sendResponseEvent(eventid); 440 | }catch(error){ 441 | //TODO: for now, do nothing as it means that some event does not contains the 442 | //searched node 443 | } 444 | for(var index in credentials){ 445 | var responseobj = credentials[index].appId; 446 | try{ 447 | responseobj.write(chunk); 448 | } 449 | catch(error){} 450 | } 451 | } 452 | }; 453 | 454 | //**************** listen for incoming events *********************** 455 | var opts = {key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem')}; 456 | 457 | http.createServer(app).listen(app.get('port'), function() { 458 | console.log('Express server listening on port ' + app.get('port')); 459 | }); 460 | 461 | /*mainhttps = require('https'); //the https object to connect the client with the proxy 462 | mainhttps.createServer(opts, app).listen(app.get('port'), function(){ 463 | console.log('Express HTTPS server listening on port ' + app.get('port')); 464 | });*/ 465 | //**************** start the server by registering a channel in BW ************ 466 | requestChannel(); --------------------------------------------------------------------------------