├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── package-lock.json ├── package.json ├── readme.md ├── src ├── client.js ├── constants │ ├── enum-types.js │ ├── webex-services.js │ └── xml-encodings.js ├── helpers │ ├── check-status.js │ ├── to-webex-time.js │ ├── valid-type.js │ └── validate.js ├── parsers │ ├── body │ │ ├── access-control.js │ │ ├── assist-service.js │ │ ├── attendees-mapper.js │ │ ├── order.js │ │ ├── partials │ │ │ └── attendee.js │ │ ├── participants.js │ │ ├── remind.js │ │ ├── repeat.js │ │ ├── schedule.js │ │ ├── support-center.js │ │ └── tracking.js │ ├── header │ │ └── security-context.js │ └── index.js ├── xml-builder.js └── xml-request.js └── tests ├── client.test.js ├── create-meeting.test.js ├── create-teleconference-session.test.js ├── del-meeting.test.js ├── fixtures ├── create-meeting.js ├── create-teleconference-session.js ├── delete-meeting.js ├── get-meeting.js ├── get-teleconference-session.js ├── gethosturl-meeting.js ├── getjoinurl-meeting.js ├── lst-summary-meeting.js └── set-meeting.js ├── get-meeting.test.js ├── get-teleconference-session.test.js ├── gethosturl-meeting.test.js ├── getjoinurl-meeting.test.js ├── libs ├── check-status.test.js ├── to-webex-time.test.js ├── valid-type.test.js └── validate.test.js ├── lst-summary-meeting.test.js ├── parsers ├── body │ ├── access-control.test.js │ ├── assist-service.test.js │ ├── order.test.js │ ├── participants.test.js │ ├── remind.test.js │ ├── repeat.test.js │ ├── schedule.test.js │ ├── support-center.test.js │ └── tracking.test.js └── header │ └── security-context.test.js ├── set-meeting.test.js └── telepresences.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | coverage 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | after_script: 5 | - 'cat coverage/lcov.info | ./node_modules/.bin/coveralls' 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) brh55 (github.com/cisco-ie/webex-api-client) 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webex-api-client", 3 | "version": "0.2.2", 4 | "description": "A node module to simplify interacting with Cisco WebEx XML-based APIs from the browser or server", 5 | "license": "MIT", 6 | "repository": "cisco-ie/webex-api-client", 7 | "author": { 8 | "name": "brh55", 9 | "email": "brhim@cisco.com", 10 | "url": "github.com/cisco-ie/webex-api-client" 11 | }, 12 | "main": "src/client.js", 13 | "engines": { 14 | "node": ">=4" 15 | }, 16 | "scripts": { 17 | "test": "xo && nyc ava" 18 | }, 19 | "files": [ 20 | "src" 21 | ], 22 | "keywords": [ 23 | "webex", 24 | "cisco client", 25 | "webex API" 26 | ], 27 | "dependencies": { 28 | "isomorphic-fetch": "^2.2.1", 29 | "lodash.mapkeys": "^4.6.0", 30 | "lodash.pick": "^4.4.0", 31 | "moment": "^2.18.1", 32 | "xml2js": "^0.4.17" 33 | }, 34 | "devDependencies": { 35 | "ava": "^0.17.0", 36 | "coveralls": "^2.11.12", 37 | "nock": "^9.0.13", 38 | "nyc": "^10.0.0", 39 | "xo": "^0.17.0" 40 | }, 41 | "nyc": { 42 | "reporter": [ 43 | "lcov", 44 | "text" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # webex-api-client [![Build Status](https://travis-ci.org/cisco-ie/webex-api-client.svg?branch=master)](https://travis-ci.org/cisco-ie/webex-api-client) [![Coverage Status](https://coveralls.io/repos/github/cisco-ie/webex-api-client/badge.svg?branch=master)](https://coveralls.io/github/cisco-ie/webex-api-client?branch=master) 2 | 3 | > A node module to simplify interacting with Cisco WebEx XML-based APIs from the browser or server 4 | 5 | > *🚨 Disclaimer 🚨* 6 | > 7 | > Due to the extent of the XML WebEx APIs, this repository is no longer being actively developed, and is given to be used "as-is". However, the innovation edge team will continue to take additional pull-request for fixes or new features, but we appologize for the inconvenience. 8 | 9 | The nature of XML-based WebEx APIs requires the construction of many intricate XML elements, which can be tediuous to build in a robust, succinct fashion. The `webex-api-client` alleviates these pain points through a `Builder` that provides flatter and simplified objects to be used for XML construction. In addition, the client offers some level of validation for enumerated types, required properties, and value constraints to help prevent malformed request prior to being sent to the WebEx services. 10 | 11 | **Nutshell Features:** 12 | - ✅ `Builder` pattern to create complicated XML in a DRY and partial application fashion 13 | - ✅ Some validation for XML values, constraints, and WebEx enumerated type 14 | - ✅ Built-in parsers that provide simpler, flatter objects to create heavily nested, and redudant XML trees 15 | - ✅ Robust and well-tested code built in a test-driven fashion with more than 95% coverage 16 | 17 | > **Note:** 18 | > 19 | > Not all WebEx services are completely supported by the client, refer to [Meeting Service](#meeting-service) for the available services. If there is something you would like to be added, submit a issue and we can consider it as a feature request developed by our team, otherwise we are open to taking additional Pull Requests for additional features :). 20 | 21 | ## Install 22 | 23 | ``` 24 | $ npm install --save webex-api-client 25 | ``` 26 | 27 | ## Usage 28 | ### Basic Usage: Get Site Information 29 | ```js 30 | const WebExClient = require('webex-api-client'); 31 | const securityContext = { 32 | webExID: 'Test User', 33 | password: 'pass123', 34 | siteName: 'hello-world' 35 | }; 36 | 37 | const requestBuilder = new WebExClient.Builder(securityContext, 'https://hello-world.webex.com/WBXService/XMLService'); 38 | 39 | const getSite = 40 | requestBuilder 41 | .setService('GetSite') 42 | .build(); 43 | 44 | getSite.exec().then(console.log) // XML WebEx Site Information 45 | ``` 46 | 47 | ### Advance Usage: Create Meetings 48 | ```js 49 | const WebExClient = require('webex-api-client'); 50 | const securityContext = { 51 | webExID: 'Test User', 52 | password: 'pass123', 53 | siteId: 'hello-world' 54 | }; 55 | 56 | const requestBuilder = new WebExClient.Builder(securityContext, 'https://hello-world.webex.com/WBXService/XMLService'); 57 | 58 | const createMeeting = 59 | requestBuilder 60 | .metaData({ 61 | confName: 'Sample Meeting', 62 | meetingType: 1, 63 | }) 64 | .participants({ 65 | attendees: [ 66 | { 67 | name: 'Jane Doe', 68 | email: 'jdoe@gmail.com' 69 | } 70 | ] 71 | }) 72 | .schedule({ 73 | startDate: new Date(), // today 74 | openTime: 900, 75 | duration: 30, 76 | timezoneID: 5 77 | }) 78 | .setService('CreateMeeting') 79 | .build(); 80 | 81 | // Initiate meeting whenever you are ready 82 | createMeeting 83 | .exec() 84 | .then((resp) => console.log('success')); 85 | ``` 86 | 87 | ## Builder(securityContext, serviceUrl) 88 | `const Builder = new Client.Builder(securityContext, serviceUrl)` 89 | 90 | Returns a [request object](#request) that is executed with `.exec()` 91 | 92 | #### securityContext 93 | 94 | Type: `object` 95 | 96 | WebEx Security Context ([`securityContext`](https://developer.cisco.com/site/webex-developer/develop-test/xml-api/schema/ 97 | )) 98 | 99 | 100 | 101 | #### Using a SessionTicket from Common Identity Webex Teams site 102 | 103 | First use an oauth2 flow from Webex Teams integration to obtain a token. This user will need to be an administrator of the webex meeting site. 104 | 105 | Use that token below to get a session token from the webex meetings api. 106 | 107 | ```js 108 | const getSessionToken = 109 | requestBuilder 110 | .accessToken('token from common identity webex teams user, previously generated from oauth2 flow') 111 | .setService('AuthenticateUser') 112 | .build(); 113 | 114 | getSessionToken.exec().then(console.log); 115 | ``` 116 | 117 | This token will be used as **sessionTicket** in the security context headers. 118 | 119 | #### serviceUrl 120 | **Type:** `string` 121 | 122 | The url for the WebEx service for the request to be sent to 123 | 124 | ### build() 125 | Construct the final XML and returns a `Request` 126 | 127 | ## Builder XML WebEx Elements 128 | All XML WebEx elements are passed a JSON representation of the XML equivalent unless specified, please refer to the schemas provided for more details. To prevent deeply nested JSON, specific methods will document simpler expected structures that will be converted by the Builder. 129 | 130 | #### Element Method Signature 131 | `Builder.elementName(XMLObj)` 132 | 133 | ### accessControl 134 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/accesscontrol.html) 135 | 136 | ### assistService 137 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/assistservice.html) 138 | 139 | ### attendeeName 140 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/attendeename.html) 141 | 142 | ### attendeeOptions 143 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/attendeeoptions.html) 144 | 145 | ### enableOptions 146 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/enableoptions.html) 147 | 148 | ### fullAccessAttendees(attendees) 149 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/fullaccessattendees.html) 150 | 151 | #### attendees 152 | **Type:** `array` of modified [attendee](#attendee) 153 | 154 | ### limitedAccessAttendees 155 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/fullaccessattendees.html) 156 | 157 | #### attendees 158 | Type: `array` of modified [attendee](#attendee) 159 | 160 | ### listControl 161 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/listcontrol.html) 162 | 163 | ### order(listOfOrderSettings) 164 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/order.html) 165 | 166 | #### listOfOrderSettings 167 | **Type:** `array` of order settings `{ orderBy, orderAD }`
168 | **Example:** 169 | ``` 170 | [ 171 | { 172 | orderBy: 'STATUS', 173 | orderAD: 'DESC' 174 | }, 175 | { 176 | orderBy: 'HOSTNAME', 177 | orderAD: 'ASC' 178 | } 179 | ] 180 | ``` 181 | 182 | ### metaData 183 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/metadata.html) 184 | 185 | ### meetingKey 186 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/meetingkey.html) 187 | 188 | ### order(input) 189 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/order.html) 190 | 191 | #### Input 192 | **Type:** `array` containing `{ orderBy: ENUM_VALUE, orderAD: ENUM_VALUE }` 193 | **Example:** 194 | ```js 195 | // Input Example 196 | [ 197 | { 198 | orderBy: 'STATUS', 199 | orderAD: 'ASC' 200 | } 201 | ] 202 | ``` 203 | 204 | ### participants 205 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/participants.html) 206 | 207 | #### XMLObj.attendees 208 | **Type:** `array` of modified [attendee](#attendee)
209 | **Example:** 210 | 211 | ```js 212 | attendees: [ 213 | { 214 | name: 'James Kirk', 215 | email: 'Jkirk@sz.webex.com', 216 | joinStatus: 'REGISTER' 217 | }, 218 | { 219 | email: 'Jdoe@sz.webex.com', 220 | name: 'Jane Doe', 221 | firstName: 'Jane', 222 | lastName: 'Doe', 223 | notes: 'Testing', 224 | joinStatus: 'INVITE' 225 | } 226 | ] 227 | ``` 228 | 229 | ### repeat 230 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/repeat.html) 231 | 232 | #### XMLObj.expirationDate 233 | **Type:** `Date`, RFC 2822, ISO-8601
234 | **Example:** `new Date()` 235 | 236 | #### XMLObj.dayInWeek 237 | **Type:** `Array` of Day
238 | **Example:** `['MONDAY', 'TUESDAY', 'THURSDAY']` 239 | 240 | ### remind 241 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/remind.html) 242 | 243 | #### XMLObj.emails 244 | **Type:** `Array` of emails
245 | **Example:** `['test@test.com', 'helloWorld@gmail.com']` 246 | 247 | ### sessionKey 248 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/sessionkey.html) 249 | 250 | ### schedule 251 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/schedule.html) 252 | 253 | ### telephony 254 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/telephony.html) 255 | 256 | ### teleconference 257 | *Not all required properties are validated*
258 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/teleconference.html) 259 | 260 | ### tracking(input) 261 | [XML Schema](https://developer.cisco.com/media/webex-xml-api-schemas-update/tracking.html) 262 | 263 | #### input 264 | **Type:** `Array` of trackingCodes
265 | **Example:** `['1', 'code231', 'code4516', '3']` 266 | 267 | ## Modified Types 268 | ### Attendee 269 | The [attendeeType](https://developer.cisco.com/media/webex-xml-api-schemas-update/attendeetype.html) has a nested person object, in the client, this is denested and abstracted for easier use. 270 | 271 | **Properties:** 272 | - name 273 | - firstName 274 | - lastName 275 | - title 276 | - company 277 | - webExID 278 | - address 279 | - phones 280 | - email 281 | - notes 282 | - url 283 | - type 284 | - sendReminder 285 | - joinStatus 286 | 287 | ## XML Misc Options 288 | ### setEncoding(encoding) 289 | #### encoding 290 | **Type:** `ENUM` 291 | 292 | Any of the following encodings: 'UTF-8', 'ISO-8859-1', 'BIG5', 'Shift_JIS', 'EUC-KR', 'GB2312'. 293 | 294 | ### setService(WebExService) 295 | **Type:** `ENUM` 296 | 297 | A matching WebEx service type, currently `webex-api-client` only supports the following: 298 | #### Meeting Service 299 | - `CreateMeeting` 300 | - `DelMeeting` 301 | - `GethosturlMeeting` 302 | - `GetjoinurlMeeting` 303 | - `GetMeeting` 304 | - `GetTeleconferenceSession` 305 | - `SetMeeting` 306 | - `LstsummaryMeeting` 307 | - `GetSite` 308 | 309 | ## Request 310 | ### exec() 311 | Executes the XML request and sends it to the `serviceUrl` 312 | 313 | ### toBuilder() 314 | Return to `Builder` retaining all elements used during construction 315 | 316 | **Example Usage:** 317 | ```js 318 | const WebExBuilder = Client.Builder(accessControl, 'http://test.webex.com/') 319 | const FirstMeeting = WebExBuilder 320 | .metaData({ 321 | confName: 'First Meeting' 322 | }) 323 | .schedule({ 324 | startDate: new Date(2017, 0, 20), 325 | openTime: 100, 326 | duration: 20 327 | }) 328 | .setService('CreateMeeting') 329 | .build(); 330 | 331 | const SecondMeeting = CreateMeeting 332 | .toBuilder() 333 | .metaData({ confName: 'Second Meeting' }) 334 | .build(); 335 | 336 | // Create both meetings 337 | const f1Promise = FirstMeeting.exec(); 338 | const f2Promise = SecondMeeting.exec(); 339 | 340 | // Log when both meetings are created 341 | Promise.all([f1Promise, f2Promise]) 342 | .then((responses) => { console.log('meetings created!') }) 343 | .catch(console.log); 344 | ``` 345 | 346 | ### newBuilder([securityContext, serviceUrl]) 347 | Destroys the previous builder with all XML elements, and returns a new Builder object with the existing security context and service url set. This can be overridden by the optional parameters passed in. 348 | 349 | **Example Usage:** 350 | ```js 351 | const WebExBuilder = Client.Builder(accessControl, 'http://test.webex.com/'); 352 | const CreateMeeting = WebExBuilder 353 | .metaData({ 354 | confName: 'First Meeting' 355 | }) 356 | .schedule({ 357 | startDate: new Date(2017, 0, 20), 358 | openTime: 100, 359 | duration: 20 360 | }) 361 | .setService('CreateMeeting') 362 | .build(); 363 | 364 | const delMeeting = CreateMeeting 365 | .newBuilder() 366 | .meetingKey(12345) 367 | .setService('DelMeeting') 368 | .build(); 369 | 370 | delMeeting.exec(); 371 | ``` 372 | 373 | ## Related 374 | If you found this client useful, don't forget to star this repository and check other related open-source Cisco modules by the Innovation Edge team: 375 | 376 | - [cisco-tp-client](https://github.com/cisco-ie/cisco-tp-client) - A node API client to ease interactions with Cisco WebEx Telepresence-enabled endpoints / codecs. 377 | - [rrule-to-webex](https://github.com/cisco-ie/rrule-to-webex) - Converts a RRULE (iCalendar RFC-5545) to Cisco's WebEx recurrence repeat XML tree. 378 | - [webex-time-zones](https://github.com/cisco-ie/webex-time-zones) - 🌐 An enumerated list of Cisco WebEx supported time zones 379 | - [webex-date](https://github.com/cisco-ie/webex-date) - 🕰 Convert a JavaScript date type, RFC 2822, ISO-8601 to the WebEx XML API supported format. 380 | - [webex-enum-types](https://github.com/cisco-ie/webex-enum-types) - 🍭 A JSON mapping of enumerated types for Cisco's WebEx XML API 381 | 382 | ## License 383 | 384 | MIT © [Cisco Innovation Edge](https://github.com/cisco-ie/webex-api-client) 385 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | const fetch = require('isomorphic-fetch'); 2 | const XMLRequest = require('./xml-request'); 3 | const checkStatus = require('./helpers/check-status'); 4 | 5 | class Client { 6 | constructor(Builder) { 7 | Builder = Builder || {}; 8 | this._Builder = Builder; 9 | this.payload = Builder.xml; 10 | this.url = Builder.url; 11 | } 12 | 13 | toBuilder() { 14 | return this._Builder; 15 | } 16 | 17 | newBuilder(credentials, url) { 18 | credentials = credentials || this._Builder.creds; 19 | url = url || this._Builder.url; 20 | return new Client.Builder(credentials, url); 21 | } 22 | 23 | exec() { 24 | return fetch(this.url, { 25 | method: 'POST', 26 | headers: { 27 | 'Content-Type': 'text/xml' 28 | }, 29 | body: this.payload 30 | }) 31 | .then(checkStatus) 32 | .then(r => r.text()); 33 | } 34 | } 35 | 36 | // A typical builder fashion, with the exception of storing 37 | // raw data into a data store 38 | Client.Builder = class { 39 | constructor(creds, url) { 40 | this.creds = creds; 41 | this.data = {}; 42 | this.xml = ''; 43 | this.url = url; 44 | this.encoding = 'UTF-8'; 45 | } 46 | 47 | accessControl(accessControl) { 48 | this.data.accessControl = accessControl; 49 | return this; 50 | } 51 | accessToken(accessToken) { 52 | this.data.accessToken = accessToken; 53 | return this; 54 | } 55 | assistService(assistService) { 56 | this.data.assistService = assistService; 57 | return this; 58 | } 59 | attendeeOptions(attendeeOptions) { 60 | this.data.attendeeOptions = attendeeOptions; 61 | return this; 62 | } 63 | attendeeName(attendeeName) { 64 | this.data.attendeeName = attendeeName; 65 | return this; 66 | } 67 | enableOptions(enableOptions) { 68 | this.data.enableOptions = enableOptions; 69 | return this; 70 | } 71 | fullAccessAttendees(attendees) { 72 | this.data.fullAccessAttendees = attendees; 73 | return this; 74 | } 75 | limitedAccessAttendees(attendees) { 76 | this.data.limitedAccessAttendees = attendees; 77 | return this; 78 | } 79 | listControl(listControl) { 80 | this.data.listControl = listControl; 81 | return this; 82 | } 83 | order(order) { 84 | this.data.order = order; 85 | return this; 86 | } 87 | metaData(metaData) { 88 | this.data.metaData = metaData; 89 | return this; 90 | } 91 | meetingKey(meetingKey) { 92 | this.data.meetingKey = meetingKey; 93 | return this; 94 | } 95 | participants(participants) { 96 | // Pass the participants properties into a nested participants object 97 | // {} => {particiants: {}} 98 | this.data.participants = participants; 99 | return this; 100 | } 101 | repeat(repeat) { 102 | this.data.repeat = repeat; 103 | return this; 104 | } 105 | remind(remind) { 106 | this.data.remind = remind; 107 | return this; 108 | } 109 | schedule(schedule) { 110 | this.data.schedule = schedule; 111 | return this; 112 | } 113 | setService(service) { 114 | this.serviceName = service; 115 | return this; 116 | } 117 | setEncoding(encoding) { 118 | this.encoding = encoding; 119 | return this; 120 | } 121 | sessionKey(sessionKey) { 122 | this.data.sessionKey = sessionKey; 123 | return this; 124 | } 125 | telephony(telephony) { 126 | this.data.telephony = telephony; 127 | return this; 128 | } 129 | teleconference(teleconference) { 130 | // Not fully supported for the required properties 131 | this.data.teleconference = teleconference; 132 | return this; 133 | } 134 | tracking(trackingList) { 135 | this.data.tracking = trackingList; 136 | return this; 137 | } 138 | 139 | build() { 140 | this.request = new XMLRequest(this.creds); 141 | this.request.append(this.data); 142 | this.xml = this.request.xml(this.serviceName, this.encoding); 143 | return new Client(this); 144 | } 145 | }; 146 | 147 | module.exports = Client; 148 | -------------------------------------------------------------------------------- /src/constants/enum-types.js: -------------------------------------------------------------------------------- 1 | // @TODO: When https://github.com/cisco-ie/webex-enum-types is public, use this instead 2 | module.exports = { 3 | addressType: ['PERSONAL', 'GLOBAL'], 4 | assistConfirm: ['Pending', 'Confirmed', 'Cancelled'], 5 | assistRequest: ['None', 'Dry Run', 'Consult', 'Live Event Support', 'Audio Streaming', 'Video'], 6 | day: ['SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY'], 7 | entryExitTone: ['BEEP', 'NOTONE', 'ANNOUNCENAME'], 8 | joinStatus: ['REGISTER', 'INVITE', 'REJECT', 'ACCEPT'], 9 | listMethod: ['AND', 'OR'], 10 | listStatus: ['PUBLIC', 'PRIVATE', 'UNLISTED'], 11 | orderBy: ['HOSTNAME', 'STATUS'], 12 | orderAD: ['ASC', 'DESC'], 13 | personType: ['VISITOR', 'MEMBER', 'PANELIST', 'SME', 'SALESTEAM'], 14 | registrationStatus: ['FULL', 'CLOSED', 'WAITLIST', 'REGISTER'], 15 | repeatType: ['WEEKLY', 'DAILY', 'NO_REPEAT', 'CONSTANT', 'MONTHLY', 'YEARLY'], 16 | role: ['ATTENDEE', 'PRESENTER', 'HOST', 'LIMITED'] 17 | }; 18 | -------------------------------------------------------------------------------- /src/constants/webex-services.js: -------------------------------------------------------------------------------- 1 | const JAVAPREFIX = 'java:com.webex.service.binding'; 2 | 3 | module.exports = { 4 | CreateMeeting: `${JAVAPREFIX}.meeting.CreateMeeting`, 5 | CreateTeleconferenceSession: `${JAVAPREFIX}.meeting.auo.CreateTeleconferenceSession`, 6 | DelMeeting: `${JAVAPREFIX}.meeting.DelMeeting`, 7 | GethosturlMeeting: `${JAVAPREFIX}.meeting.GethosturlMeeting`, 8 | GetjoinurlMeeting: `${JAVAPREFIX}.meeting.GetjoinurlMeeting`, 9 | GetMeeting: `${JAVAPREFIX}.meeting.GetMeeting`, 10 | GetTeleconferenceSession: `${JAVAPREFIX}.meeting.auo.GetTeleconferenceSession`, 11 | SetMeeting: `${JAVAPREFIX}.meeting.SetMeeting`, 12 | LstsummaryMeeting: `${JAVAPREFIX}.meeting.LstsummaryMeeting`, 13 | GetSite: `${JAVAPREFIX}.site.GetSite`, 14 | AuthenticateUser: `${JAVAPREFIX}.user.AuthenticateUser` 15 | }; 16 | -------------------------------------------------------------------------------- /src/constants/xml-encodings.js: -------------------------------------------------------------------------------- 1 | module.exports = ['UTF-8', 'ISO-8859-1', 'BIG5', 'Shift_JIS', 'EUC-KR', 'GB2312']; 2 | -------------------------------------------------------------------------------- /src/helpers/check-status.js: -------------------------------------------------------------------------------- 1 | module.exports = response => { 2 | if (response.status >= 200 && response.status < 300) { 3 | return response; 4 | } 5 | 6 | var error = new Error(response.statusText); 7 | error.response = response; 8 | throw error; 9 | }; 10 | -------------------------------------------------------------------------------- /src/helpers/to-webex-time.js: -------------------------------------------------------------------------------- 1 | // TODO: use https://github.com/cisco-ie/webex-time when public 2 | const moment = require('moment'); 3 | 4 | /** 5 | * Converts a Date Object to a MM/DD/YYYY HH:mm:ss format for WebEx 6 | * @param {[type]} time [description] 7 | * @return {[type]} [description] 8 | */ 9 | module.exports = time => { 10 | if (!moment(time).isValid()) { 11 | throw new Error(`Expected valid time format, instead recieved ${time}`); 12 | } 13 | return moment(time).format('MM/DD/YYYY HH:mm:ss'); 14 | }; 15 | -------------------------------------------------------------------------------- /src/helpers/valid-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks against a list of valid options throws error if not 3 | * @param {Array} constants valid type list 4 | * @param {?} input input 5 | * @return void, throws error if not valid 6 | */ 7 | module.exports = (constants, input) => { 8 | constants = constants || []; 9 | const invalidStatus = constants.indexOf(input) === -1; 10 | if (invalidStatus) { 11 | throw new Error(`Expected a valid type (${constants.toString().replace(/,/g, ', ')}), received ${input}`); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/helpers/validate.js: -------------------------------------------------------------------------------- 1 | // This function will return a list of required keys that are missing 2 | module.exports = (srcObj, keys, elementIn) => { 3 | // Show the ones that aren't available in the object 4 | // handles lowercase 5 | const srcObjKeys = Object.keys(srcObj).map(item => item.toLowerCase()); 6 | const has = key => srcObjKeys.indexOf(key) === -1; 7 | // If the key exist in the src object, then remove it from the list 8 | const normalizeKeys = keys.map(k => k.toLowerCase()); 9 | const missingReqKeys = normalizeKeys.filter(k => has(k)); 10 | 11 | if (missingReqKeys.length > 0) { 12 | const missingKeys = missingReqKeys.toString().replace(/,/g, ', '); 13 | const errorMessage = `Missing required keys: ${missingKeys}`; 14 | if (elementIn) { 15 | throw new Error(`${errorMessage} in ${elementIn}`); 16 | } else { 17 | throw new Error(errorMessage); 18 | } 19 | } 20 | 21 | return null; 22 | }; 23 | -------------------------------------------------------------------------------- /src/parsers/body/access-control.js: -------------------------------------------------------------------------------- 1 | // Per Schema all elements are optional 2 | const validType = require('../../helpers/valid-type'); 3 | const validate = require('../../helpers/validate'); 4 | const ENUMS = require('../../constants/enum-types'); 5 | 6 | const LISTSTATUS = ENUMS.listStatus; 7 | const JOINSTATUS = ENUMS.joinStatus; 8 | const REGSTATUS = ENUMS.registrationStatus; 9 | 10 | /** 11 | * Creates a schedule XML 12 | * @param {Object} elements 13 | * @param {String} elements.sessionPassword 14 | * @param {ListingType} elements.listStatus 15 | * @param {Bool} elements.registration 16 | * @param {String} elements.registrationURL 17 | * @param {Bool} elements.passwordReq 18 | * @param {String} elements.registrationURLForMobile 19 | * @param {Number} elements.registrationID 20 | * @param {JoinStatusType} elements.joinStatus 21 | * @param {SessionRegisterStatusType} elements.registrationStatus 22 | * @param {Bool} elements.isRegisterIDRequired 23 | * @param {String} elements.audioPassword 24 | * @param {Bool} elements.isEnforceAudioPassword 25 | * @param {Bool} elements.isEnforceAudioLogin 26 | * 27 | * @return {String} accessControl XML 28 | */ 29 | module.exports = elements => { 30 | validate(elements, [], 'accessControl'); 31 | 32 | let elCopy = Object.assign({}, elements); 33 | 34 | if (elements.sessionPassword) { 35 | if (elements.sessionPassword.length > 16) { 36 | throw new Error('Expected elements.sessionPassword to be shorter than 16 characters'); 37 | } 38 | } 39 | 40 | if (elements.audioPassword) { 41 | if (elements.audioPassword.length > 16) { 42 | throw new Error('Expected elements.audioPassword to be shorter than 16 characters'); 43 | } 44 | } 45 | 46 | if (elements.joinStatus) { 47 | validType(JOINSTATUS, elements.joinStatus); 48 | } 49 | 50 | return elCopy; 51 | }; 52 | -------------------------------------------------------------------------------- /src/parsers/body/assist-service.js: -------------------------------------------------------------------------------- 1 | const ENUMS = require('../../constants/enum-types'); 2 | const validType = require('../../helpers/valid-type'); 3 | 4 | const ASSIST_REQUEST = ENUMS.assistRequest; 5 | const ASSIST_CONFIRM = ENUMS.assistConfirm; 6 | 7 | // Enum check against assistService 8 | module.exports = elements => { 9 | if (elements.assistRequest) { 10 | validType(ASSIST_REQUEST, elements.assistRequest); 11 | } 12 | 13 | if (elements.assistConfirm) { 14 | validType(ASSIST_CONFIRM, elements.assistConfirm); 15 | } 16 | 17 | return elements; 18 | }; 19 | -------------------------------------------------------------------------------- /src/parsers/body/attendees-mapper.js: -------------------------------------------------------------------------------- 1 | // This is applied to both fullAccessAttendees or limitedAccessAttendees 2 | // since the logic is the same 3 | const assert = require('assert'); 4 | 5 | module.exports = attendees => { 6 | assert(Array.isArray(attendees), 'Expected input to be an array ([attendee])'); 7 | 8 | return attendees.map(attendee => { 9 | assert(attendee.email, 'Expected attendee to have email property defined'); 10 | 11 | return { 12 | attendee 13 | }; 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/parsers/body/order.js: -------------------------------------------------------------------------------- 1 | const ENUMS = require('../../constants/enum-types'); 2 | const validType = require('../../helpers/valid-type'); 3 | 4 | const ORDER_BY = ENUMS.orderBy; 5 | const ORDER_AD = ENUMS.orderAD; 6 | 7 | // Enum check against assistService 8 | module.exports = elements => { 9 | if (!Array.isArray(elements)) { 10 | throw new Error('Expected order to receive an array ([ {orderBy, orderAD}, {orderBy, orderAD} ])'); 11 | } 12 | // Place into object { orderBy: [], orderAD: [] } 13 | // Might need to look to see if order is dependent 14 | return elements.reduce((acc, element) => { 15 | if (element.orderBy) { 16 | validType(ORDER_BY, element.orderBy); 17 | // Copy + add last element 18 | acc.orderBy = [...acc.orderBy, element.orderBy]; 19 | } 20 | 21 | if (element.orderAD) { 22 | validType(ORDER_AD, element.orderAD); 23 | acc.orderAD = [...acc.orderAD, element.orderAD]; 24 | } 25 | 26 | return acc; 27 | }, {orderBy: [], orderAD: []}); 28 | }; 29 | -------------------------------------------------------------------------------- /src/parsers/body/partials/attendee.js: -------------------------------------------------------------------------------- 1 | // Unsure why eslint is not recogonizing this dep, ignoring rule for now 2 | const pick = require('lodash.pick'); // eslint-disable-line import/no-extraneous-dependencies 3 | const validType = require('../../../helpers/valid-type'); 4 | const ENUMS = require('../../../constants/enum-types'); 5 | 6 | const ROLES = ENUMS.role; 7 | const PERSONTYPES = ENUMS.personType; 8 | const JOINSTATUSES = ENUMS.joinStatus; 9 | 10 | module.exports = attendee => { 11 | if (!attendee.email) { 12 | throw new Error(`Expected email property for attendee: ${JSON.stringify(attendee)}`); 13 | } 14 | 15 | // Pull out all the possible properties 16 | const person = pick(attendee, [ 17 | 'email', 'name', 'firstName', 'lastName', 'title', 'company', 18 | 'webExId', 'address', 'phones', 'notes', 'url', 'type', 'sendReminder']); 19 | 20 | if (person.type) { 21 | validType(PERSONTYPES, person.type); 22 | } 23 | 24 | const misc = pick(attendee, ['contactID', 'joinStatus', 'role']); 25 | 26 | if (misc.role) { 27 | validType(ROLES, misc.role); 28 | } 29 | 30 | if (misc.joinStatus) { 31 | validType(JOINSTATUSES, misc.joinStatus); 32 | } 33 | // { person }, contactID, joinStatus, role 34 | return Object.assign({}, {person}, misc); 35 | }; 36 | -------------------------------------------------------------------------------- /src/parsers/body/participants.js: -------------------------------------------------------------------------------- 1 | const transformAttendee = require('./partials/attendee'); 2 | 3 | module.exports = elements => { 4 | let elCopy = Object.assign({}, elements); 5 | 6 | if (elements.attendees) { 7 | const attendees = elements.attendees || []; 8 | const transformedAttendees = attendees.map(attendee => transformAttendee(attendee)); 9 | 10 | elCopy = Object.assign({}, elCopy, {attendees: {attendee: transformedAttendees}}); 11 | } 12 | 13 | return elCopy; 14 | }; 15 | -------------------------------------------------------------------------------- /src/parsers/body/remind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses the tracking code list 3 | * @param {list} elements 4 | * @return {object} transformedElements 5 | */ 6 | module.exports = elements => { 7 | const emails = elements.emails; 8 | if (emails && Array.isArray(emails)) { 9 | const elCopy = Object.assign({}, elements); 10 | elCopy.emails = { 11 | email: emails 12 | }; 13 | return elCopy; 14 | } 15 | 16 | return elements; 17 | }; 18 | -------------------------------------------------------------------------------- /src/parsers/body/repeat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates the support center XML 3 | * @param {object} elements 4 | * @param {list} elements.orderTabs 5 | * @param {boolean} elements.serviceDesk 6 | * @return {object} transformedElements 7 | */ 8 | 9 | const ENUMS = require('../../constants/enum-types'); 10 | const validType = require('../../helpers/valid-type'); 11 | const toWebExTime = require('../../helpers/to-webex-time'); 12 | 13 | const REPEAT_TYPES = ENUMS.repeatType; 14 | const DAYS = ENUMS.day; 15 | 16 | module.exports = input => { 17 | const inputCopy = Object.assign({}, input); 18 | 19 | // @TODO: This will be implemented once rrule-to-webex is available 20 | // to public 21 | // 22 | // if (typeof input === 'string') { 23 | // // return convert(input); 24 | // } 25 | // 26 | 27 | if (input.repeatType) { 28 | validType(REPEAT_TYPES, input.repeatType); 29 | } 30 | 31 | if (input.expirationDate) { 32 | inputCopy.expirationDate = toWebExTime(input.expirationDate); 33 | } 34 | 35 | const weekDays = input.dayInWeek; 36 | if (weekDays && Array.isArray(weekDays)) { 37 | weekDays.forEach(day => validType(DAYS, day)); 38 | inputCopy.dayInWeek = { 39 | day: weekDays 40 | }; 41 | } 42 | 43 | return inputCopy; 44 | }; 45 | -------------------------------------------------------------------------------- /src/parsers/body/schedule.js: -------------------------------------------------------------------------------- 1 | // Per Schema all elements are optional 2 | const toTime = require('../../helpers/to-webex-time'); 3 | const ENUMS = require('../../constants/enum-types'); 4 | const validType = require('../../helpers/valid-type'); 5 | 6 | const ENTRYEXITTONES = ENUMS.entryExitTone; 7 | 8 | /** 9 | * Creates a schedule XML 10 | * @param {Object} elements 11 | * @param {Date} elements.startDate 12 | * @param {Number} elements.duration 13 | * @param {Number} elements.openTime 14 | * @param {String} elements.hostWebExID 15 | * @param {String} elements.entryExitTone 16 | * @param {String} elements.extURL 17 | * @param {Number} elements.extNotifyTime 18 | * @param {String} elements.joinNotifyURL 19 | * @param {Bool} elements.joinTeleconfBeforeHost 20 | * @param {Bool} elements.timeZoneID 21 | * @param {Bool} elements.timeZone 22 | * @param {String} elements.showFilePath 23 | * @param {Bool} elements.showFileStartMode 24 | * @param {Bool} elements.showFileContPlayFlag 25 | * @param {Number} elements.showFileInterVal 26 | * @param {Bool} elements.firstAttendeeAsPresenter 27 | * 28 | * @return {Object} Parsed schedule object 29 | */ 30 | module.exports = elements => { 31 | let elCopy = Object.assign({}, elements); 32 | 33 | if (elements.startDate) { 34 | elCopy.startDate = toTime(elements.startDate); 35 | } 36 | 37 | if (elements.entryExitTone) { 38 | validType(ENTRYEXITTONES, elements.entryExitTone); 39 | } 40 | 41 | return elCopy; 42 | }; 43 | -------------------------------------------------------------------------------- /src/parsers/body/support-center.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates the support center XML 3 | * @param {object} elements 4 | * @param {list} elements.orderTabs 5 | * @param {boolean} elements.serviceDesk 6 | * @return {object} transformedElements 7 | */ 8 | module.exports = elements => { 9 | if (elements.orderTabs.length > 4) { 10 | throw new Error(`Expected maximum number of tabs to be 4, received ${elements.orderTabs.length}`); 11 | } 12 | if (typeof (elements.serviceDesk) !== 'boolean') { 13 | throw new Error(`Expected serviceDesk to be of type boolean, received ${typeof (elements.serviceDesk)}`); 14 | } 15 | 16 | const transformedElements = { 17 | orderTabs: [ 18 | { 19 | tab: elements.orderTabs 20 | } 21 | ], 22 | serviceDesk: { 23 | enable: elements.serviceDesk 24 | } 25 | }; 26 | 27 | return transformedElements; 28 | }; 29 | -------------------------------------------------------------------------------- /src/parsers/body/tracking.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | /** 3 | * Parses the tracking code list 4 | * @param {list} elements 5 | * @return {object} transformedElements 6 | */ 7 | module.exports = elements => { 8 | assert(Array.isArray(elements), `Expected tracking to be an array, receieved ${typeof elements}`); 9 | assert(elements.length < 11, `Expected tracking length to be 10 or less, received ${elements.length}`); 10 | 11 | return elements.reduce((acc, value, index) => { 12 | assert(typeof value === 'string' || typeof value === 'number', `Expected tracking item to be a string or number, received a ${typeof value}`); 13 | if (typeof value === 'string') { 14 | assert(value.length < 129, `Expected tracking item to be 128 characters or less, received ${value.length} characters`); 15 | } 16 | 17 | const trackingCode = { 18 | [`trackingCode${index + 1}`]: value 19 | }; 20 | 21 | return Object.assign(acc, trackingCode); 22 | }, {}); 23 | }; 24 | -------------------------------------------------------------------------------- /src/parsers/header/security-context.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Security context provides some pre-validation 3 | // to prevent unnecessary request sent to the wire 4 | // create note about passwords in clear text should be done through HTTPS 5 | // LINK: https://developer.cisco.com/site/webex-developer/develop-test/xml-api/api-guide/#global-request-elements-in-security-context 6 | 7 | //TODO: add body validation to allow AuthenticateUser service without password or sessionTicket in header 8 | 9 | const mapKeys = require('lodash.mapkeys'); 10 | 11 | /** 12 | * Generates the security context XML 13 | * @param {object} elements 14 | * @param {string} elements.siteName || elements.siteID 15 | * @param {string} elements.webExID || elements.email 16 | * @param {string} elements.password || elements.sessionTicket 17 | * @param {string} elements.partnerID 18 | * @return {string} xml string 19 | */ 20 | module.exports = elements => { 21 | const eCopy = mapKeys(elements, (v, k) => k.toLowerCase()); 22 | 23 | // Throw error if one of the two don't exists 24 | if (!eCopy.sitename && !eCopy.siteid) { 25 | throw new Error('Missing required keys: siteName or siteID'); 26 | } 27 | 28 | if (!eCopy.webexid && !eCopy.email) { 29 | throw new Error('Missing required keys: email or webExID'); 30 | } 31 | 32 | if (!eCopy.password && !eCopy.sessionTicket) { 33 | throw new Error('Missing required keys: password or sessionTicket'); 34 | } 35 | 36 | return elements; 37 | }; 38 | -------------------------------------------------------------------------------- /src/parsers/index.js: -------------------------------------------------------------------------------- 1 | // @TODO: Automate with requireAll 2 | module.exports = { 3 | accessControl: require('./body/access-control'), 4 | assistService: require('./body/assist-service'), 5 | fullAccessAttendees: require('./body/attendees-mapper'), 6 | limitedAccessAttendees: require('./body/attendees-mapper'), 7 | order: require('./body/order'), 8 | participants: require('./body/participants'), 9 | remind: require('./body/remind'), 10 | repeat: require('./body/repeat'), 11 | schedule: require('./body/schedule'), 12 | securityContext: require('./header/security-context'), 13 | supportCenter: require('./body/support-center'), 14 | tracking: require('./body/tracking') 15 | }; 16 | -------------------------------------------------------------------------------- /src/xml-builder.js: -------------------------------------------------------------------------------- 1 | // This returns a xml builder with our settings 2 | const xml2js = require('xml2js'); 3 | 4 | module.exports = (root, encoding) => { 5 | const options = { 6 | headless: false, 7 | xmldec: { 8 | encoding 9 | }, 10 | renderOpts: { 11 | pretty: false 12 | } 13 | }; 14 | 15 | if (typeof root === 'object') { 16 | // Mock the xmlBuilder if we receive a JS xml object for the root 17 | // this will provide a function that will wrap it's initial object with the root 18 | return { 19 | buildObject: function (jsObject) { 20 | const rootName = Object.keys(root)[0]; 21 | const xmlBuilder = new xml2js.Builder(options); 22 | const newObject = { 23 | [rootName]: { 24 | ...root[rootName], 25 | ...jsObject 26 | } 27 | }; 28 | return xmlBuilder.buildObject(newObject); 29 | } 30 | }; 31 | } 32 | 33 | const xmlBuilder = new xml2js.Builder({ 34 | ...options, 35 | rootName: root 36 | }); 37 | 38 | return xmlBuilder; 39 | }; 40 | -------------------------------------------------------------------------------- /src/xml-request.js: -------------------------------------------------------------------------------- 1 | const createBuilder = require('./xml-builder'); 2 | const parsers = require('./parsers/index'); 3 | const validType = require('./helpers/valid-type'); 4 | const WEBEXSERVICE = require('./constants/webex-services'); 5 | const ENCODINGS = require('./constants/xml-encodings'); 6 | 7 | module.exports = class XMLRequest { 8 | constructor(creds) { 9 | this.header = { 10 | securityContext: parsers.securityContext(creds) 11 | }; 12 | this.body = {}; 13 | } 14 | 15 | _validateAndTransform(data) { 16 | // Attempt to find a parser/validator 17 | // Parse the object contents, then return 18 | // the same key with the parsed object 19 | const elementRoots = Object.keys(data); 20 | 21 | // Reduce each property in the object to be passed through the 22 | // parser 23 | const parsed = elementRoots.reduce((acc, elementKey) => { 24 | const element = data[elementKey]; 25 | const parser = parsers[elementKey]; 26 | if (parser) { 27 | let parsedObj = { 28 | [elementKey]: parser(element) 29 | }; 30 | return Object.assign(acc, parsedObj); 31 | } 32 | return Object.assign(acc, {[elementKey]: element}); 33 | }, {}); 34 | 35 | return parsed; 36 | } 37 | 38 | append(object) { 39 | const parsed = this._validateAndTransform(object); 40 | this.body = Object.assign({}, this.body, parsed); 41 | } 42 | 43 | get full() { 44 | return { 45 | header: this.header, 46 | body: this.body 47 | }; 48 | } 49 | 50 | xml(service, encoding) { 51 | if (encoding) { 52 | validType(ENCODINGS, encoding); 53 | } 54 | const xmlBuilder = createBuilder( 55 | { 56 | 'serv:message': { 57 | $: { 58 | 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance' 59 | } 60 | } 61 | }, encoding); 62 | 63 | const desiredService = WEBEXSERVICE[service]; 64 | if (!desiredService) { 65 | throw new Error(`Recieved ${service} as WebEx service, expected a valid service in order to build request.`); 66 | } 67 | 68 | const attr = { 69 | $: { 70 | 'xsi:type': desiredService 71 | } 72 | }; 73 | 74 | const bodyContent = Object.assign({}, attr, this.body); 75 | const body = { 76 | bodyContent 77 | }; 78 | 79 | const xmlObj = { 80 | header: this.header, 81 | body 82 | }; 83 | 84 | return xmlBuilder.buildObject(xmlObj); 85 | } 86 | }; 87 | -------------------------------------------------------------------------------- /tests/client.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import Client from '../src/client'; 3 | 4 | const TESTURL = 'https://test.com'; 5 | 6 | test('Client toBuilder method', async t => { 7 | const requestBuilder = new Client.Builder({ 8 | webExID: 'testuser', 9 | password: 'password123', 10 | siteId: 'tester' 11 | }, TESTURL + '/webex'); 12 | 13 | const webExMeeting = requestBuilder 14 | .metaData({ 15 | confName: 'Sample Meeting', 16 | meetingType: 1, 17 | agenda: 'Test' 18 | }) 19 | .meetingKey(123456) 20 | .setService('CreateMeeting') 21 | .build(); 22 | 23 | const delMeetingBuilder = webExMeeting.toBuilder(); 24 | const delMeeting = delMeetingBuilder.setService('DelMeeting').build(); 25 | 26 | t.is(delMeeting.payload, '
testuserpassword123tester
Sample Meeting1Test123456
'); 27 | }); 28 | 29 | test('Client newBuilder method', async t => { 30 | const requestBuilder = new Client.Builder({ 31 | webExID: 'testuser', 32 | password: 'password123', 33 | siteId: 'tester' 34 | }, TESTURL + '/webex'); 35 | 36 | const webExMeeting = requestBuilder 37 | .metaData({ 38 | confName: 'Sample Meeting', 39 | meetingType: 1, 40 | agenda: 'Test' 41 | }) 42 | .meetingKey(123456) 43 | .setService('DelMeeting') 44 | .build(); 45 | 46 | const newMeeting = webExMeeting 47 | .newBuilder() 48 | .metaData({ 49 | confName: 'Sample Meeting 2' 50 | }) 51 | .setService('CreateMeeting') 52 | .build(); 53 | 54 | // It should clear out the old elements and only contain the new metaData 55 | t.is(newMeeting.payload, '
testuserpassword123tester
Sample Meeting 2
'); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/create-meeting.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import nock from 'nock'; 3 | 4 | import Client from '../src/client'; 5 | import mock from './fixtures/create-meeting'; 6 | 7 | const TESTURL = 'https://test.com'; 8 | 9 | nock('https://test.com') 10 | .post('/webex', mock) 11 | .reply(200, ''); 12 | 13 | test('Create Meeting', async t => { 14 | t.plan(1); 15 | 16 | const requestBuilder = new Client.Builder({ 17 | webExID: 'testuser', 18 | password: 'password123', 19 | siteId: 'tester' 20 | }, TESTURL + '/webex'); 21 | 22 | const webExMeeting = requestBuilder 23 | .metaData({ 24 | confName: 'Sample Meeting', 25 | meetingType: 1, 26 | agenda: 'Test' 27 | }) 28 | .participants({ 29 | maxUserNumber: 4, 30 | attendees: [ 31 | { 32 | name: 'James Kirk', 33 | email: 'JKirk@sz.webex.com' 34 | } 35 | ] 36 | }) 37 | .schedule({ 38 | startDate: new Date(2004, 4, 31, 10, 10, 10), 39 | openTime: 900, 40 | joinTeleconfBeforeHost: true, 41 | duration: 20, 42 | timezoneID: 4 43 | }) 44 | .remind({ 45 | emails: ['test@test.com', 'test2@test.com'], 46 | sendEmail: true 47 | }) 48 | .tracking(['trackSig1', 'trackSig3']) 49 | .assistService({ 50 | assistRequest: 'Live Event Support' 51 | }) 52 | .setEncoding('ISO-8859-1') 53 | .setService('CreateMeeting') 54 | .build(); 55 | 56 | try { 57 | const resp = await webExMeeting.exec(); 58 | 59 | t.is(resp, ''); 60 | } catch (err) { 61 | console.log(err); 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /tests/create-teleconference-session.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import nock from 'nock'; 3 | 4 | import Client from '../src/client'; 5 | import mock from './fixtures/create-teleconference-session'; 6 | 7 | const TESTURL = 'https://test.com'; 8 | 9 | nock('https://test.com') 10 | .post('/webex', mock) 11 | .reply(200, ''); 12 | 13 | test('Create Teleconference Session', async t => { 14 | t.plan(1); 15 | 16 | const requestBuilder = new Client.Builder({ 17 | webExID: 'testuser', 18 | password: 'password123', 19 | siteId: 'tester' 20 | }, TESTURL + '/webex'); 21 | 22 | const createTeleconferenceSession = requestBuilder 23 | .accessControl({ 24 | listStatus: 'PUBLIC', 25 | sessionPassword: 123456, 26 | registration: true 27 | }) 28 | .metaData({ 29 | confName: 'Sample Teleconference-only meeting' 30 | }) 31 | .fullAccessAttendees([ 32 | { 33 | name: 1, 34 | email: '1@1.com' 35 | } 36 | ]) 37 | .limitedAccessAttendees([ 38 | { 39 | name: 2, 40 | email: '2@2.com' 41 | } 42 | ]) 43 | .schedule({ 44 | startDate: new Date(2005, 3, 18, 15, 8, 51), 45 | timeZoneID: 45, 46 | entryExitTone: 'ANNOUNCENAME' 47 | }) 48 | .teleconference({ 49 | extTelephonyDescription: 'xml' 50 | }) 51 | .tracking([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 52 | .repeat({ 53 | repeatType: 'DAILY', 54 | interval: 1 55 | }) 56 | .attendeeOptions({ 57 | emailInvitations: true 58 | }) 59 | .setService('CreateTeleconferenceSession') 60 | .build(); 61 | 62 | try { 63 | const resp = await createTeleconferenceSession.exec(); 64 | t.is(resp, ''); 65 | } catch (err) { 66 | console.log(err); 67 | } 68 | }); 69 | -------------------------------------------------------------------------------- /tests/del-meeting.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import nock from 'nock'; 3 | 4 | import Client from '../src/client'; 5 | import mock from './fixtures/delete-meeting'; 6 | 7 | const TESTURL = 'https://test.com'; 8 | 9 | nock('https://test.com') 10 | .post('/webex', mock) 11 | .reply(200, ''); 12 | 13 | test('DeleteMeeting', async t => { 14 | t.plan(1); 15 | 16 | const requestBuilder = new Client.Builder({ 17 | webExID: 'testuser', 18 | password: 'password123', 19 | siteId: 'tester' 20 | }, TESTURL + '/webex'); 21 | 22 | const deleteRequest = requestBuilder 23 | .meetingKey(48591508) 24 | .setService('DelMeeting') 25 | .build(); 26 | 27 | try { 28 | const resp = await deleteRequest.exec(); 29 | t.is(resp, ''); 30 | } catch (err) { 31 | console.log(err); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /tests/fixtures/create-meeting.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 |
4 | 5 | testuser 6 | password123 7 | tester 8 | 9 |
10 | 11 | 12 | 13 | Sample Meeting 14 | 1 15 | Test 16 | 17 | 18 | 4 19 | 20 | 21 | 22 | JKirk@sz.webex.com 23 | James Kirk 24 | 25 | 26 | 27 | 28 | 29 | 05/31/2004 10:10:10 30 | 900 31 | true 32 | 20 33 | 4 34 | 35 | 36 | 37 | test@test.com 38 | test2@test.com 39 | 40 | true 41 | 42 | 43 | trackSig1 44 | trackSig3 45 | 46 | 47 | Live Event Support 48 | 49 | 50 | 51 |
`.replace(/(\r\n|\n|\r|\t|\s{2,})/gm, ''); 52 | // Remove tabs/spaces 2 or more/new line break 53 | -------------------------------------------------------------------------------- /tests/fixtures/create-teleconference-session.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 |
4 | 5 | testuser 6 | password123 7 | tester 8 | 9 |
10 | 11 | 12 | 13 | PUBLIC 14 | 123456 15 | true 16 | 17 | 18 | Sample Teleconference-only meeting 19 | 20 | 21 | 22 | 1 23 | 1@1.com 24 | 25 | 26 | 27 | 28 | 2 29 | 2@2.com 30 | 31 | 32 | 33 | 04/18/2005 15:08:51 34 | 45 35 | ANNOUNCENAME 36 | 37 | 38 | xml 39 | 40 | 41 | 1 42 | 2 43 | 3 44 | 4 45 | 5 46 | 6 47 | 7 48 | 8 49 | 9 50 | 10 51 | 52 | 53 | DAILY 54 | 1 55 | 56 | 57 | true 58 | 59 | 60 | 61 |
`.replace(/(\r\n|\n|\r|\t|\s{2,})/gm, ''); 62 | // Remove tabs/spaces 2 or more/new line break 63 | -------------------------------------------------------------------------------- /tests/fixtures/delete-meeting.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 |
4 | 5 | testuser 6 | password123 7 | tester 8 | 9 |
10 | 11 | 12 | 48591508 13 | 14 | 15 |
`.replace(/(\r\n|\n|\r|\t|\s{2,})/gm, ''); 16 | // Remove tabs/spaces 2 or more/new line break 17 | -------------------------------------------------------------------------------- /tests/fixtures/get-meeting.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 |
4 | 5 | testuser 6 | password123 7 | tester 8 | 9 |
10 | 11 | 12 | 48591508 13 | 14 | 15 |
`.replace(/(\r\n|\n|\r|\t|\s{2,})/gm, ''); 16 | // Remove tabs/spaces 2 or more/new line break 17 | -------------------------------------------------------------------------------- /tests/fixtures/get-teleconference-session.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 |
4 | 5 | testuser 6 | password123 7 | tester 8 | 9 |
10 | 11 | 12 | 37480497 13 | 14 | 15 |
`.replace(/(\r\n|\n|\r|\t|\s{2,})/gm, ''); 16 | // Remove tabs/spaces 2 or more/new line break 17 | -------------------------------------------------------------------------------- /tests/fixtures/gethosturl-meeting.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 |
4 | 5 | testuser 6 | password123 7 | tester 8 | 9 |
10 | 11 | 12 | 48591508 13 | 14 | 15 |
`.replace(/(\r\n|\n|\r|\t|\s{2,})/gm, ''); 16 | // Remove tabs/spaces 2 or more/new line break 17 | -------------------------------------------------------------------------------- /tests/fixtures/getjoinurl-meeting.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 |
4 | 5 | testuser 6 | password123 7 | tester 8 | 9 |
10 | 11 | 12 | 48591508 13 | James Kirk 14 | 15 | 16 |
`.replace(/(\r\n|\n|\r|\t|\s{2,})/gm, ''); 17 | // Remove tabs/spaces 2 or more/new line break 18 | -------------------------------------------------------------------------------- /tests/fixtures/lst-summary-meeting.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 |
4 | 5 | testuser 6 | password123 7 | tester 8 | 9 |
10 | 11 | 12 | 13 | 1 14 | 10 15 | OR 16 | 17 | 18 | HOSTNAME 19 | STATUS 20 | ASC 21 | ASC 22 | 23 | 24 | 25 |
`.replace(/(\r\n|\n|\r|\t|\s{2,})/gm, ''); 26 | // Remove tabs/spaces 2 or more/new line break 27 | -------------------------------------------------------------------------------- /tests/fixtures/set-meeting.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 |
4 | 5 | testuser 6 | password123 7 | tester 8 | 9 |
10 | 11 | 12 | 13 | SetMeeting 14 | 1 15 | Test 16 | 17 | 18 | 4 19 | 20 | 21 | false 22 | true 23 | true 24 | 25 | 26 | 06/01/2004 23:06:27 27 | 60 28 | GMT-05:00, S. America Pacific (Bogota) 29 | 30 | 31 | 0 32 | NONE 33 | 34 | 35 | true 36 | 37 | user@user.com 38 | 39 | 40 | 41 | true 42 | 43 | 48591508 44 | 45 | 46 |
`.replace(/(\r\n|\n|\r|\t|\s{2,})/gm, ''); 47 | // Remove tabs/spaces 2 or more/new line break 48 | -------------------------------------------------------------------------------- /tests/get-meeting.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import nock from 'nock'; 3 | 4 | import Client from '../src/client'; 5 | import mock from './fixtures/get-meeting'; 6 | 7 | const TESTURL = 'https://test.com'; 8 | 9 | nock('https://test.com') 10 | .post('/webex', mock) 11 | .reply(200, ''); 12 | 13 | test('GetMeeting', async t => { 14 | t.plan(1); 15 | 16 | const requestBuilder = new Client.Builder({ 17 | webExID: 'testuser', 18 | password: 'password123', 19 | siteId: 'tester' 20 | }, TESTURL + '/webex'); 21 | 22 | const deleteRequest = requestBuilder 23 | .meetingKey(48591508) 24 | .setService('GetMeeting') 25 | .build(); 26 | 27 | try { 28 | const resp = await deleteRequest.exec(); 29 | t.is(resp, ''); 30 | } catch (err) { 31 | console.log(err); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /tests/get-teleconference-session.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import nock from 'nock'; 3 | 4 | import Client from '../src/client'; 5 | import mock from './fixtures/get-teleconference-session'; 6 | 7 | const TESTURL = 'https://test.com'; 8 | 9 | nock('https://test.com') 10 | .post('/webex', mock) 11 | .reply(200, ''); 12 | 13 | test('GetTeleconferenceSession Test', async t => { 14 | t.plan(1); 15 | 16 | const requestBuilder = new Client.Builder({ 17 | webExID: 'testuser', 18 | password: 'password123', 19 | siteId: 'tester' 20 | }, TESTURL + '/webex'); 21 | 22 | const getTeleconf = requestBuilder 23 | .sessionKey(37480497) 24 | .setService('GetTeleconferenceSession') 25 | .build(); 26 | 27 | try { 28 | const resp = await getTeleconf.exec(); 29 | t.is(resp, ''); 30 | } catch (err) { 31 | console.log(err); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /tests/gethosturl-meeting.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import nock from 'nock'; 3 | 4 | import Client from '../src/client'; 5 | import mock from './fixtures/gethosturl-meeting'; 6 | 7 | const TESTURL = 'https://test.com'; 8 | 9 | nock('https://test.com') 10 | .post('/webex', mock) 11 | .reply(200, ''); 12 | 13 | test('GethosturlMeeting', async t => { 14 | t.plan(1); 15 | 16 | const requestBuilder = new Client.Builder({ 17 | webExID: 'testuser', 18 | password: 'password123', 19 | siteId: 'tester' 20 | }, TESTURL + '/webex'); 21 | 22 | const gethosturl = requestBuilder 23 | .sessionKey(48591508) 24 | .setService('GethosturlMeeting') 25 | .build(); 26 | 27 | try { 28 | const resp = await gethosturl.exec(); 29 | t.is(resp, ''); 30 | } catch (err) { 31 | console.log(err); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /tests/getjoinurl-meeting.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import nock from 'nock'; 3 | 4 | import Client from '../src/client'; 5 | import mock from './fixtures/getjoinurl-meeting'; 6 | 7 | const TESTURL = 'https://test.com'; 8 | 9 | nock('https://test.com') 10 | .post('/webex', mock) 11 | .reply(200, ''); 12 | 13 | test('GetjoinurlMeeting', async t => { 14 | t.plan(1); 15 | 16 | const requestBuilder = new Client.Builder({ 17 | webExID: 'testuser', 18 | password: 'password123', 19 | siteId: 'tester' 20 | }, TESTURL + '/webex'); 21 | 22 | const getjoinurl = requestBuilder 23 | .sessionKey(48591508) 24 | .attendeeName('James Kirk') 25 | .setService('GetjoinurlMeeting') 26 | .build(); 27 | 28 | try { 29 | const resp = await getjoinurl.exec(); 30 | t.is(resp, ''); 31 | } catch (err) { 32 | console.log(err); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /tests/libs/check-status.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import checkStatus from '../../src/helpers/check-status'; 3 | 4 | test('checkStatus', t => { 5 | const failResp = { 6 | status: 404, 7 | statusText: 'Error out' 8 | }; 9 | const err = t.throws(() => checkStatus(failResp)); 10 | t.is(err.message, 'Error out'); 11 | 12 | const passResp = { 13 | status: 200, 14 | body: 'Pass' 15 | }; 16 | 17 | t.deepEqual(checkStatus(passResp), passResp); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/libs/to-webex-time.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import toWebExTime from '../../src/helpers/to-webex-time'; 3 | 4 | test('WebEx Time', t => { 5 | t.is(toWebExTime(new Date(2000, 1, 20, 12)), '02/20/2000 12:00:00'); 6 | }); 7 | -------------------------------------------------------------------------------- /tests/libs/valid-type.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import validType from '../../src/helpers/valid-type'; 3 | 4 | test('Valid Type', t => { 5 | const validTypes = [1, 2, 3]; 6 | const error = t.throws(() => validType(validTypes, 4)); 7 | t.is(error.message, 'Expected a valid type (1, 2, 3), received 4'); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/libs/validate.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import validate from '../../src/helpers/validate'; 3 | 4 | test('Missing key throw error', t => { 5 | const error = t.throws(() => validate({ 6 | hello: 'world', 7 | webExID: 'world2' 8 | }, ['universe', 'hello', 'galaxy'])); 9 | t.is(error.message, 'Missing required keys: universe, galaxy'); 10 | }); 11 | 12 | test('Valid object', t => { 13 | t.is(validate({ 14 | hello: 'world', 15 | webExID: 'world2', 16 | UnIVeRSE: 'big bang' 17 | }, ['universe']), null); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/lst-summary-meeting.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import nock from 'nock'; 3 | 4 | import Client from '../src/client'; 5 | import mock from './fixtures/lst-summary-meeting'; 6 | 7 | const TESTURL = 'https://test.com'; 8 | 9 | nock('https://test.com') 10 | .post('/webex', mock) 11 | .reply(200, ''); 12 | 13 | test('ListsummaryMeeting Test', async t => { 14 | t.plan(1); 15 | 16 | const requestBuilder = new Client.Builder({ 17 | webExID: 'testuser', 18 | password: 'password123', 19 | siteId: 'tester' 20 | }, TESTURL + '/webex'); 21 | 22 | const listSummary = requestBuilder 23 | .listControl({ 24 | startFrom: 1, 25 | maximumNum: 10, 26 | listMethod: 'OR' 27 | }) 28 | .order([ 29 | { 30 | orderBy: 'HOSTNAME', 31 | orderAD: 'ASC' 32 | }, 33 | { 34 | orderBy: 'STATUS', 35 | orderAD: 'ASC' 36 | } 37 | ]) 38 | .setService('LstsummaryMeeting') 39 | .build(); 40 | 41 | try { 42 | const resp = await listSummary.exec(); 43 | t.is(resp, ''); 44 | } catch (err) { 45 | console.log(err); 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /tests/parsers/body/access-control.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import accessControl from '../../../src/parsers/body/access-control'; 3 | 4 | const LONGPASSWORDMOCK = 'asjdlkasjdklaiowqdjoiqwdlkawndlkansdasdjasjdklasjdlkas'; 5 | 6 | const validSettings = { 7 | sessionPassword: '1234523', 8 | listStatus: 'PUBLIC', 9 | registration: true, 10 | passwordReq: true, 11 | isRegisterIDRequired: true, 12 | registrationStatus: 'FULL' 13 | }; 14 | 15 | test('Validate sessionPassword', t => { 16 | const error = t.throws(() => accessControl({ 17 | registration: true, 18 | sessionPassword: LONGPASSWORDMOCK 19 | })); 20 | 21 | t.is(error.message, 'Expected elements.sessionPassword to be shorter than 16 characters'); 22 | }); 23 | 24 | test('Validate audioPassword', t => { 25 | const error = t.throws(() => accessControl({ 26 | registration: true, 27 | audioPassword: LONGPASSWORDMOCK 28 | })); 29 | 30 | t.is(error.message, 'Expected elements.audioPassword to be shorter than 16 characters'); 31 | }); 32 | 33 | test('Validate join status', t => { 34 | // Additional Error Checks 35 | const invalidSettings = Object.assign({}, validSettings, {joinStatus: '111'}); 36 | const error = t.throws(() => accessControl(invalidSettings)); 37 | t.is(error.message, 'Expected a valid type (REGISTER, INVITE, REJECT, ACCEPT), received 111'); 38 | 39 | const errorReq = t.throws(() => accessControl({})); 40 | t.is(errorReq.message, 'Missing required keys: registration in accessControl'); 41 | }); 42 | 43 | test('Validate required keys', t => { 44 | const errorReq = t.throws(() => accessControl({})); 45 | t.is(errorReq.message, 'Missing required keys: registration in accessControl'); 46 | }); 47 | -------------------------------------------------------------------------------- /tests/parsers/body/assist-service.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import assistService from '../../../src/parsers/body/assist-service'; 3 | 4 | test('AssistService Parser', t => { 5 | const result = assistService({ 6 | assistRequest: 'None' 7 | }); 8 | 9 | t.deepEqual(result, {assistRequest: 'None'}); 10 | }); 11 | 12 | test('Validate assistRequest', t => { 13 | const error = t.throws(() => 14 | assistService({ 15 | assistRequest: 'Nonz' 16 | }) 17 | ); 18 | 19 | t.is(error.message, 'Expected a valid type (None, Dry Run, Consult, Live Event Support, Audio Streaming, Video), received Nonz'); 20 | }); 21 | 22 | test('Validate assistConfirm', t => { 23 | const error = t.throws(() => 24 | assistService({ 25 | assistConfirm: 'Pendz' 26 | }) 27 | ); 28 | 29 | t.is(error.message, 'Expected a valid type (Pending, Confirmed, Cancelled), received Pendz'); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/parsers/body/order.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import order from '../../../src/parsers/body/order'; 3 | 4 | test('Order Parser', t => { 5 | const result = order([ 6 | { 7 | orderBy: 'STATUS', 8 | orderAD: 'ASC' 9 | }, 10 | { 11 | orderBy: 'HOSTNAME', 12 | orderAD: 'DESC' 13 | } 14 | ]); 15 | 16 | t.deepEqual(result, { 17 | orderBy: ['STATUS', 'HOSTNAME'], 18 | orderAD: ['ASC', 'DESC'] 19 | }); 20 | 21 | const err = t.throws(() => order([{orderBy: 'z'}])); 22 | t.is(err.message, 'Expected a valid type (HOSTNAME, STATUS), received z'); 23 | 24 | const err2 = t.throws(() => order([{orderAD: 'a'}])); 25 | t.is(err2.message, 'Expected a valid type (ASC, DESC), received a'); 26 | 27 | const err3 = t.throws(() => order('hello')); 28 | t.is(err3.message, 'Expected order to receive an array ([ {orderBy, orderAD}, {orderBy, orderAD} ])'); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/parsers/body/participants.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import participants from '../../../src/parsers/body/participants'; 3 | 4 | test('Participants Parser', t => { 5 | const parsed = participants({ 6 | maxUserNumber: 4, 7 | attendees: [ 8 | { 9 | name: 'James Kirk', 10 | email: 'Jkirk@sz.webex.com', 11 | joinStatus: 'REGISTER' 12 | }, 13 | { 14 | email: 'Jdoe@sz.webex.com', 15 | name: 'Jane Doe', 16 | firstName: 'Jane', 17 | lastName: 'Doe', 18 | notes: 'Testing', 19 | joinStatus: 'INVITE' 20 | } 21 | ] 22 | }); 23 | 24 | t.deepEqual(parsed, { 25 | maxUserNumber: 4, 26 | attendees: { 27 | attendee: [ 28 | { 29 | person: { 30 | name: 'James Kirk', 31 | email: 'Jkirk@sz.webex.com' 32 | }, 33 | joinStatus: 'REGISTER' 34 | }, 35 | { 36 | person: { 37 | email: 'Jdoe@sz.webex.com', 38 | name: 'Jane Doe', 39 | firstName: 'Jane', 40 | lastName: 'Doe', 41 | notes: 'Testing' 42 | }, 43 | joinStatus: 'INVITE' 44 | } 45 | ] 46 | } 47 | }); 48 | }); 49 | 50 | test('Validate joinStatus', t => { 51 | const error = t.throws(() => participants({ 52 | attendees: [ 53 | { 54 | email: 'cool@beans.com', 55 | joinStatus: 'coolbeans' 56 | } 57 | ] 58 | })); 59 | t.is(error.message, 'Expected a valid type (REGISTER, INVITE, REJECT, ACCEPT), received coolbeans'); 60 | }); 61 | 62 | test('Validate role', t => { 63 | const error = t.throws(() => participants({ 64 | attendees: [ 65 | { 66 | email: 'cool@beans.com', 67 | role: 'governer' 68 | } 69 | ] 70 | })); 71 | t.is(error.message, 'Expected a valid type (ATTENDEE, PRESENTER, HOST, LIMITED), received governer'); 72 | }); 73 | 74 | test('Validate email', t => { 75 | const error = t.throws(() => participants({ 76 | attendees: [ 77 | { 78 | role: 'governer' 79 | } 80 | ] 81 | })); 82 | t.is(error.message, 'Expected email property for attendee: {\"role\":\"governer\"}'); // eslint-disable-line no-useless-escape 83 | }); 84 | 85 | test('Validate personType', t => { 86 | const error = t.throws(() => participants({ 87 | attendees: [ 88 | { 89 | email: 'cool@beans.com', 90 | type: 'universe' 91 | } 92 | ] 93 | })); 94 | t.is(error.message, 'Expected a valid type (VISITOR, MEMBER, PANELIST, SME, SALESTEAM), received universe'); 95 | }); 96 | -------------------------------------------------------------------------------- /tests/parsers/body/remind.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import remind from '../../../src/parsers/body/remind'; 3 | 4 | test('Remind Parser', t => { 5 | const result = remind({ 6 | emails: [ 7 | 'test@test.com', 8 | 'test1@test.com' 9 | ], 10 | sendEmail: true 11 | }); 12 | t.deepEqual(result, { 13 | emails: { 14 | email: [ 15 | 'test@test.com', 16 | 'test1@test.com' 17 | ] 18 | }, 19 | sendEmail: true 20 | }); 21 | 22 | // No emails should return same elements 23 | const element = { 24 | sendEmail: true 25 | }; 26 | t.deepEqual(remind(element), element); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/parsers/body/repeat.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import repeat from '../../../src/parsers/body/repeat'; 3 | 4 | test('Repeat Parser', t => { 5 | const result = repeat({ 6 | dayInWeek: ['MONDAY', 'TUESDAY'], 7 | interval: 4, 8 | expirationDate: new Date(2017, 0, 31) 9 | }); 10 | t.deepEqual(result, { 11 | expirationDate: '01/31/2017 00:00:00', 12 | interval: 4, 13 | dayInWeek: { 14 | day: ['MONDAY', 'TUESDAY'] 15 | } 16 | }); 17 | }); 18 | 19 | test('Validate repeatType', t => { 20 | const err = t.throws(() => repeat({ 21 | repeatType: 'Hello world' 22 | })); 23 | 24 | t.is(err.message, 'Expected a valid type (WEEKLY, DAILY, NO_REPEAT, CONSTANT, MONTHLY, YEARLY), received Hello world'); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/parsers/body/schedule.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import schedule from '../../../src/parsers/body/schedule'; 3 | 4 | test('Schedule XML', t => { 5 | const result = schedule({ 6 | startDate: new Date(2004, 4, 31, 10, 10, 10), 7 | entryExitTone: 'BEEP', 8 | duration: 20, 9 | joinTeleconfBeforeHost: true, 10 | openTime: 900, 11 | timeZoneID: 4 12 | }); 13 | 14 | const expected = { 15 | startDate: '05/31/2004 10:10:10', 16 | entryExitTone: 'BEEP', 17 | duration: 20, 18 | joinTeleconfBeforeHost: true, 19 | openTime: 900, 20 | timeZoneID: 4 21 | }; 22 | 23 | t.deepEqual(result, expected); 24 | }); 25 | 26 | test('Validate entryExitTone', t => { 27 | const error = t.throws(() => schedule({entryExitTone: 'hi'})); 28 | t.is(error.message, 'Expected a valid type (BEEP, NOTONE, ANNOUNCENAME), received hi'); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/parsers/body/support-center.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import supportCenter from '../../../src/parsers/body/support-center'; 3 | 4 | test('Support Center', t => { 5 | const elements = { 6 | orderTabs: ['Tools', 'Desktop', 'Application'], 7 | serviceDesk: true 8 | }; 9 | const result = supportCenter(elements); 10 | const expected = { 11 | orderTabs: [ 12 | { 13 | tab: ['Tools', 'Desktop', 'Application'] 14 | } 15 | ], 16 | serviceDesk: { 17 | enable: true 18 | } 19 | }; 20 | t.deepEqual(result, expected); 21 | }); 22 | 23 | test('Valid throw tabs error', t => { 24 | const elements = { 25 | orderTabs: ['Tools', 'Desktop', 'Application', 'Session'], 26 | serviceDesk: 'True' 27 | }; 28 | const result = t.throws(() => supportCenter(elements)); 29 | t.is(result.message, `Expected serviceDesk to be of type boolean, received ${typeof (elements.serviceDesk)}`); 30 | }); 31 | 32 | test('Valid throw boolean error', t => { 33 | const elements = { 34 | orderTabs: ['Tools', 'Desktop', 'Application', 'Session', 'Period'], 35 | serviceDesk: true 36 | }; 37 | const result = t.throws(() => supportCenter(elements)); 38 | t.is(result.message, `Expected maximum number of tabs to be 4, received ${elements.orderTabs.length}`); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/parsers/body/tracking.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import tracking from '../../../src/parsers/body/tracking'; 3 | 4 | test('Tracking Parser', t => { 5 | const elements = ['1', 'code231', 'code4516', '3']; 6 | const result = tracking(elements); 7 | const expected = { 8 | trackingCode1: '1', 9 | trackingCode2: 'code231', 10 | trackingCode3: 'code4516', 11 | trackingCode4: '3' 12 | }; 13 | t.deepEqual(result, expected); 14 | }); 15 | 16 | test('Valid arrays error', t => { 17 | // Insert blank object 18 | const error = t.throws(() => tracking({})); 19 | t.is(error.message, 'Expected tracking to be an array, receieved object'); 20 | }); 21 | 22 | test('Valid length', t => { 23 | const error = t.throws(() => tracking(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'])); 24 | t.is(error.message, 'Expected tracking length to be 10 or less, received 11'); 25 | }); 26 | 27 | test('Valid tracking type', t => { 28 | const errorType = t.throws(() => tracking([{}, 'code1'])); 29 | t.is(errorType.message, 'Expected tracking item to be a string or number, received a object'); 30 | 31 | const LONGER_STRING = 'Ppby2DgRz5O31cuDaYHiEUPHLHcEwenvuC8Ve2u4Z2knGb7Cie5wcS1j2rA2xsjBCsZ7YHsCrf5tpnQcgzJbaavOlWKDg0DrKoFG4MovenqvWSi51lUnUNHtvP6SoLM9u'; 32 | const errorLength = t.throws(() => tracking([LONGER_STRING])); 33 | t.is(errorLength.message, 'Expected tracking item to be 128 characters or less, received 129 characters'); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/parsers/header/security-context.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import securityContext from '../../../src/parsers/header/security-context'; 3 | 4 | test('Ensure sitename/siteid and email/webexid checks', t => { 5 | const errorSite = t.throws(() => securityContext({ 6 | nothing: 'here' 7 | })); 8 | t.is(errorSite.message, 'Missing required keys: siteName or siteID'); 9 | 10 | const errorUser = t.throws(() => securityContext({ 11 | siteID: 'tester' 12 | })); 13 | t.is(errorUser.message, 'Missing required keys: email or webExID'); 14 | 15 | const errorCreds = t.throws(() => securityContext({ 16 | siteID: 'tester', 17 | email: 'test@test.com' 18 | })); 19 | t.is(errorCreds.message, 'Missing required keys: password or sessionTicket'); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/set-meeting.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import nock from 'nock'; 3 | 4 | import Client from '../src/client'; 5 | import mock from './fixtures/set-meeting'; 6 | 7 | const TESTURL = 'https://test.com'; 8 | 9 | nock('https://test.com') 10 | .post('/webex', mock) 11 | .reply(200, ''); 12 | 13 | test('SetMeeting Test', async t => { 14 | t.plan(1); 15 | 16 | const requestBuilder = new Client.Builder({ 17 | webExID: 'testuser', 18 | password: 'password123', 19 | siteId: 'tester' 20 | }, TESTURL + '/webex'); 21 | 22 | const setMeeting = requestBuilder 23 | .metaData({ 24 | confName: 'SetMeeting', 25 | meetingType: 1, 26 | agenda: 'Test' 27 | }) 28 | .participants({ 29 | maxUserNumber: 4 30 | }) 31 | .enableOptions({ 32 | chat: false, 33 | poll: true, 34 | audioVideo: true 35 | }) 36 | .schedule({ 37 | startDate: new Date(2004, 5, 1, 23, 6, 27), 38 | duration: 60, 39 | timeZone: 'GMT-05:00, S. America Pacific (Bogota)' 40 | }) 41 | .telephony({ 42 | numPhoneLines: 0, 43 | telephonySupport: 'NONE' 44 | }) 45 | .remind({ 46 | enableReminder: true, 47 | emails: ['user@user.com'] 48 | }) 49 | .attendeeOptions({ 50 | auto: true 51 | }) 52 | .meetingKey(48591508) 53 | .setService('SetMeeting') 54 | .build(); 55 | 56 | try { 57 | const resp = await setMeeting.exec(); 58 | t.is(resp, ''); 59 | } catch (err) { 60 | console.log(err); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /tests/telepresences.js: -------------------------------------------------------------------------------- 1 | // import test from 'ava'; 2 | // import WebExClient from '.'; 3 | // 4 | // const webex = new WebExClient({ 5 | // siteID: '', 6 | // partnerID: 999, 7 | // webExID: 'hostid' 8 | // }); 9 | // 10 | // test('Create a teleconference session', t => { 11 | // const config = { 12 | // accessControl: { 13 | // listing: 'PUBLIC', 14 | // sessionPassword: 12345 15 | // }, 16 | // metaData: { 17 | // confName: "Sample teleconference meeting" 18 | // }, 19 | // fullAccessAttendees: [ 20 | // { 21 | // name: 1, 22 | // phones: { 23 | // phone: '', 24 | // mobilePhone: '', 25 | // fax: '' 26 | // } 27 | // } 28 | // ], 29 | // limitedAccessAttendees: [ 30 | // { 31 | // name: 2, 32 | // phones: { 33 | // phone: '', 34 | // mobilePhone: '', 35 | // fax: '' 36 | // } 37 | // } 38 | // ], 39 | // schedule: { 40 | // startDate: '04/18/2005 15:08:51', 41 | // timeZoneID: 45, 42 | // entryExitTone: 'ANNOUNCENAME' 43 | // }, 44 | // teleconference: { 45 | // extTelephonyDescription: 'xml' 46 | // }, 47 | // tracking: [ 1, 2, 3, 4, 5], 48 | // attendeeOptions: { 49 | // emailInvitations: true 50 | // } 51 | // } 52 | // }); 53 | --------------------------------------------------------------------------------