1 /* 2 * Copyright (C) 2012 Doubango Telecom <http://www.doubango.org> 3 * License: GPLv3 4 * This file is part of Open Source sipML5 solution <http://www.sipml5.org> 5 */ 6 7 /** 8 @fileoverview This is SIPML5 "library" contains a lot of classes and functions. 9 10 @name sipML5 API 11 @author Doubango Telecom <http://www.doubango.org> 12 @version 1.0 13 */ 14 15 /** 16 @namespace 17 @description Root namesapce. 18 */ 19 SIPml = {}; 20 21 /** @private */SIPml.b_initialized = false; 22 /** @private */SIPml.b_initializing = false; 23 /** @private */SIPml.s_navigator_friendly_name = 'unknown'; 24 /** @private */SIPml.b_navigator_outdated = false; 25 /** @private */SIPml.s_navigator_version = 'unknown'; 26 /** @private */SIPml.s_system_friendly_name = 'unknown'; 27 /** @private */SIPml.b_webrtc4all_plugin_outdated = false; 28 /** @private */SIPml.b_webrtc4all_supported = false; 29 /** @private */SIPml.s_webrtc4all_version = 'unknown'; 30 /** @private */SIPml.b_have_media_stream = false; 31 /** @private */SIPml.b_webrtc_supported = false; 32 33 34 /** 35 Gets the version name of the installed <a href="http://code.google.com/p/webrtc4all/">webrtc4all plugin</a>. 36 You must <a href="#.init">initialize</a> the engine before calling this function. 37 @static 38 @returns {String} Version name (e.g. '1.12.756') 39 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>. 40 */ 41 SIPml.getWebRtc4AllVersion = function() { 42 if(!SIPml.isInitialized()){ 43 throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first"); 44 } 45 return SIPml.s_webrtc4all_version; 46 }; 47 48 /** 49 Gets the web browser version (e.g. <i>'1.5.beta'</i>). 50 You must <a href="#.init">initialize</a> the engine before calling this function. 51 @static 52 @returns {String} The the web browser version. 53 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>. 54 */ 55 SIPml.getNavigatorVersion = function() { 56 if(!SIPml.isInitialized()){ 57 throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first"); 58 } 59 return SIPml.s_navigator_version; 60 }; 61 62 /** 63 Gets the web browser friendly name (e.g. <i>'chrome'</i>, <i>'firefox'</i>, <i>'safari'</i>, <i>'opera'</i>, <i>'ie'</i> or <i>'netscape'</i>). 64 You must <a href="#.init">initialize</a> the engine before calling this function. 65 @static 66 @returns {String} The web browser friendly name. 67 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>. 68 */ 69 SIPml.getNavigatorFriendlyName = function() { 70 if(!SIPml.isInitialized()){ 71 throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first"); 72 } 73 return SIPml.s_navigator_friendly_name; 74 }; 75 76 /** 77 Gets the Operating System friendly name (e.g. <i>'windows'</i>, <i>'mac'</i>, <i>'lunix'</i>, <i>'solaris'</i>, <i>'sunos'</i> or <i>'powerpc'</i>). 78 You must <a href="#.init">initialize</a> the engine before calling this function. 79 @static 80 @returns {String} The Operating System friendly name. 81 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>. 82 */ 83 SIPml.getSystemFriendlyName = function() { 84 if(!SIPml.isInitialized()){ 85 throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first"); 86 } 87 return SIPml.s_system_friendly_name; 88 }; 89 90 /** 91 Checks whether the web browser supports WebRTC but is outdated. 92 You must <a href="#.init">initialize</a> the engine before calling this function. 93 @static 94 @returns {Boolean} <i>true</i> if outdated; otherwise <i>false</i> 95 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>. 96 */ 97 SIPml.isNavigatorOutdated= function () { 98 if(!SIPml.isInitialized()){ 99 throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first"); 100 } 101 return SIPml.b_navigator_outdated; 102 } 103 104 /** 105 Checks whether the <a href="http://code.google.com/p/webrtc4all/">webrtc4all plugin</a> is outdated or not. 106 You must <a href="#.init">initialize</a> the engine before calling this function. 107 @static 108 @returns {Boolean} <i>true</i> if outdated; otherwise <i>false</i> 109 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>. 110 */ 111 SIPml.isWebRtc4AllPluginOutdated = function(){ 112 if(!SIPml.isInitialized()){ 113 throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first"); 114 } 115 return SIPml.b_webrtc4all_plugin_outdated; 116 } 117 118 /** 119 Checks whether the <a href="http://code.google.com/p/webrtc4all/">webrtc4all plugin</a> is installed or not. 120 You must <a href="#.init">initialize</a> the engine before calling this function. 121 @static 122 @returns {Boolean} <i>true</i> if supported; otherwise <i>false</i> 123 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>. 124 */ 125 SIPml.isWebRtc4AllSupported = function(){ 126 if(!SIPml.isInitialized()){ 127 throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first"); 128 } 129 return SIPml.b_webrtc4all_supported; 130 } 131 132 /** 133 Checks whether WebRTC is supported or not. 134 You must <a href="#.init">initialize</a> the engine before calling this function. 135 @static 136 @returns {Boolean} <i>true</i> if supported; otherwise <i>false</i> 137 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>. 138 */ 139 SIPml.isWebRtcSupported = function () { 140 if(!SIPml.isInitialized()){ 141 throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first"); 142 } 143 return SIPml.b_webrtc_supported; 144 } 145 146 /** 147 Checks whether WebSocket is supported or not. 148 @static 149 @returns {Boolean} <i>true</i> if supported; otherwise <i>false</i> 150 */ 151 SIPml.isWebSocketSupported = function () { 152 return tsk_utils_have_websocket(); 153 } 154 155 /** 156 Checks whether the media engine have a valid stream or not. The stream is from <a href="https://developer.mozilla.org/en-US/docs/WebRTC/navigator.getUserMedia">getUserMedia</a>. 157 @static 158 @returns {Boolean} <i>true</i> if the engine have a valid stream; otherwise <i>false</i> 159 */ 160 SIPml.haveMediaStream = function () { 161 if(!SIPml.isInitialized()){ 162 throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first"); 163 } 164 return SIPml.b_have_media_stream; 165 } 166 167 /** 168 Checks whether the engine is ready to make/receive calls or not. <br /> 169 The engine is ready when: 170 <ul> 171 <li>engine is <a href="#.init">initialized</a></li> 172 <li>webrtc is supported</li> 173 <li>we got a valid media stream (from <a href="https://developer.mozilla.org/en-US/docs/WebRTC/navigator.getUserMedia">getUserMedia</a>)</li> 174 </ul> 175 @static 176 @returns {Boolean} <i>true</i> if the engine is ready; otherwise <i>false</i> 177 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>. 178 */ 179 SIPml.isReady = function () { 180 return (SIPml.isInitialized() && SIPml.isWebRtcSupported() && SIPml.haveMediaStream()); 181 } 182 183 /** 184 Checks whether the engine is initialized or not. To initialize the stack you must call <a href="#.init">init()</a> function. 185 @static 186 @returns {Boolean} <i>true</i> if the engine is initialized; otherwise <i>false</i> 187 */ 188 SIPml.isInitialized = function () { return SIPml.b_initialized; } 189 190 191 /** 192 Initialize the engine. <b>You must call this function before any other.</b>. 193 @param {CallbackFunction} [readyCallback] Optional callback function to call when the stack finish initializing and become ready. 194 @param {CallbackFunction} [errorCallback] Optional callback function to call when initialization fails. 195 196 @example 197 SIPml.init(function(e){ console.info('engine is ready'); }, function(e){ console.info('Error: ' + e.message); }); 198 @static 199 */ 200 SIPml.init = function (successCallback, errorCallback) { 201 if (!SIPml.b_initialized && !SIPml.b_initializing) { 202 SIPml.b_initializing = true; 203 tsk_utils_init_webrtc(); 204 205 tsk_utils_log_info('User-Agent=' + (navigator.userAgent || "unknown")); 206 207 SIPml.b_have_media_stream = tsk_utils_have_stream(); 208 SIPml.b_webrtc_supported = tsk_utils_have_webrtc(); 209 SIPml.b_webrtc4all_supported = tsk_utils_have_webrtc4all(); 210 SIPml.s_webrtc4all_version = tsk_utils_webrtc4all_get_version(); 211 SIPml.s_navigator_friendly_name = tsk_utils_get_navigator_friendly_name(); 212 SIPml.s_system_friendly_name = tsk_utils_get_system_friendly_name(); 213 214 // prints whether WebSocket is supported 215 tsk_utils_log_info("WebSocket supported = " + (SIPml.isWebSocketSupported() ? "yes" : "no")); 216 217 // check webrtc4all version 218 if (tsk_utils_have_webrtc4all()) { 219 tsk_utils_log_info("WebRTC type = " + WebRtc4all_GetType() + " version = " + tsk_utils_webrtc4all_get_version()); 220 if (SIPml.s_webrtc4all_version != '1.14.834') { 221 SIPml.b_webrtc4all_plugin_outdated = true; 222 } 223 } 224 225 // prints navigator friendly name 226 tsk_utils_log_info("Navigator friendly name = " + SIPml.s_navigator_friendly_name); 227 228 // gets navigator version 229 if(SIPml.s_navigator_friendly_name == 'ie'){ 230 var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); 231 if (re.exec(navigator.userAgent) != null) { 232 SIPml.s_navigator_version = RegExp.$1; 233 } 234 } 235 236 // prints OS friendly name 237 tsk_utils_log_info("OS friendly name = " + SIPml.s_system_friendly_name); 238 // prints support for WebRTC (native or plugin) 239 tsk_utils_log_info("Have WebRTC = " + (tsk_utils_have_webrtc() ? "yes" : "false")); 240 // prints support for getUserMedia 241 tsk_utils_log_info("Have GUM = " + (tsk_utils_have_stream() ? "yes" : "false")); 242 243 // checks for WebRTC support 244 if (!tsk_utils_have_webrtc()) { 245 // is it chrome? 246 if (SIPml.s_navigator_friendly_name == "chrome") { 247 SIPml.b_navigator_outdated = true; 248 return; 249 } 250 251 // for now the plugins (WebRTC4all only works on Windows) 252 if (SIPml.s_system_friendly_name == 'win' || SIPml.s_system_friendly_name == 'windows') { 253 // Internet explorer 254 if (SIPml.s_navigator_friendly_name == 'ie') { 255 // Check for IE version 256 var rv = -1; 257 var ua = navigator.userAgent; 258 var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); 259 if (re.exec(ua) != null) { 260 rv = parseFloat(RegExp.$1); 261 } 262 if (rv < 9.0) { 263 SIPml.b_navigator_outdated = true; 264 return; 265 } 266 267 // break page loading ('window.location' won't stop JS execution) 268 if (!tsk_utils_have_webrtc4all()) { 269 return; 270 } 271 } 272 } 273 } 274 275 if(SIPml.b_webrtc_supported && SIPml.b_have_media_stream){ 276 SIPml.b_initialized = true; 277 SIPml.b_initializing = false; 278 tsk_utils_log_info("Engine initialized"); 279 if(successCallback){ 280 successCallback({}); 281 } 282 } 283 else{ 284 if(errorCallback){ 285 var s_description = !SIPml.b_webrtc_supported ? "WebRTC not supported" : (!SIPml.b_have_media_stream ? "getUserMedia not supported" : "Internal error"); 286 errorCallback({description: s_description}); 287 } 288 } 289 } 290 } 291 292 // ================================== SIPml.EventTarget ========================================== 293 294 /** 295 @constructor 296 Defines an event target. You sould never create an event target object. 297 */ 298 SIPml.EventTarget = function () { 299 this.ao_listeners = []; 300 } 301 302 /** 303 Adds an event listener to the target object. <br /><br /> 304 <table border="1"> 305 <tr> 306 <td><b>Target classes<b></td> 307 <td><b>Supported event types<b></td> 308 <td><b>Raised event object<b></td> 309 <td><b>Remarques<b></td> 310 </tr> 311 <tr> 312 <td><a href="SIPml.Stack.html" name="SIPml.EventTarget.Stack">SIPml.Stack</a></td> 313 <td> 314 <b>*</b><br/> starting<br/> started<br/> stopping<br/> stopped<br/> failed_to_start<br/> failed_to_stop<br/> i_new_call<br /> i_new_message<br /> 315 m_permission_requested<br/> m_permission_accepted<br/> m_permission_refused 316 </td> 317 <td><a href="SIPml.Stack.Event.html">SIPml.Stack.Event</a></td> 318 <td>'*' is used to listen for all events</td> 319 </tr> 320 <tr> 321 <td> 322 <a href="SIPml.Session.html" name="SIPml.EventTarget.Session">SIPml.Session</a> 323 <ul> 324 <li><a href="SIPml.Session.Call.html">SIPml.Session.Call</a></li> 325 <li><a href="SIPml.Session.Message.html">SIPml.Session.Message</a></li> 326 <li><a href="SIPml.Session.Message.html">SIPml.Session.Registration</a></li> 327 <li><a href="SIPml.Session.Message.html">SIPml.Session.Subscribe</a></li> 328 <li><a href="SIPml.Session.Message.html">SIPml.Session.Publish</a></li> 329 <ul> 330 </td> 331 <td><b>*</b><br/> connecting<br/> connected<br/> terminating<br/> terminated<br/> 332 i_ao_request<br /> 333 media_added<br/> media_removed<br/> 334 i_request<br/> o_request<br/> cancelled_request<br/> sent_request<br/> 335 transport_error<br/> global_error<br/> message_error<br/> webrtc_error 336 </td> 337 <td><a href="SIPml.Session.Event.html">SIPml.Session.Event</a></td> 338 <td>'*' is used to listen for all events<br /></td> 339 </tr> 340 <tr> 341 <td><a href="SIPml.Session.Call.html" name="SIPml.EventTarget.Session.Call">SIPml.Session.Call</a></td> 342 <td> 343 m_early_media<br/> m_local_hold_ok<br/> m_local_hold_nok<br/> m_local_resume_ok<br/> m_local_resume_nok<br/> m_remote_hold<br/> m_remote_resume<br/> 344 m_stream_video_local_added<br /> m_stream_video_local_removed<br/> m_stream_video_remote_added<br/> m_stream_video_remote_removed <br /> 345 m_stream_audio_local_added<br /> m_stream_audio_local_removed<br/> m_stream_audio_remote_added<br/> m_stream_audio_remote_removed <br /> 346 i_ect_new_call<br/> o_ect_trying<br/> o_ect_accepted<br/> o_ect_completed<br/> i_ect_completed<br/> o_ect_failed<br/> i_ect_failed<br/> o_ect_notify<br/> i_ect_notify<br/> i_ect_requested 347 </td> 348 <td><a href="SIPml.Session.Event.html">SIPml.Session.Event</a></td> 349 <td>borrows all events supported by <a href="SIPml.Session.html">SIPml.Session</a></td> 350 </tr> 351 <tr> 352 <td><a href="SIPml.Session.Subscribe.html" name="SIPml.EventTarget.Session.Subscribe">SIPml.Session.Subscribe</a></td> 353 <td> 354 i_notify 355 </td> 356 <td><a href="SIPml.Session.Event.html">SIPml.Session.Event</a></td> 357 <td>borrows all events supported by <a href="SIPml.Session.html">SIPml.Session</a></td> 358 </tr> 359 </table> 360 @param {String|Array} type The event type/identifier. Must not be null or empty. Use <b>'*'</b> to listen for all events. 361 @param {function} listener The object that receives a notification when an event of the specified type occurs. This must be an object implementing the <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventListener">EventListener</a> interface, or simply a JavaScript function. 362 @example 363 // listen for a single event 364 this.addEventListener('started', function(e){ 365 console.info("'started' event fired"); 366 }); 367 // or listen for two or more events 368 this.addEventListener(['started', 'stopped'], function(e){ 369 console.info("'"+e.type+"' event fired"); 370 }); 371 // or listen for all events 372 this.addEventListener('*', function(e){ 373 console.info("'"+e.type+"' event fired"); 374 }); 375 @see <a href="#removeEventListener">removeEventListener</a> 376 @throws {ERR_INVALID_PARAMETER_VALUE|ERR_INVALID_PARAMETER_TYPE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_INVALID_PARAMETER_TYPE</font> 377 */ 378 SIPml.EventTarget.prototype.addEventListener = function (o_type, o_listener) { 379 if (!o_listener) { 380 throw new Error("ERR_INVALID_PARAMETER_VALUE: 'listener' must not be null"); 381 } 382 if (!o_type) { 383 throw new Error("ERR_INVALID_PARAMETER_VALUE: 'type' must not be null"); 384 } 385 if(!(o_type instanceof String || typeof o_type == "string" || o_type instanceof Array)){ 386 throw new Error("ERR_INVALID_PARAMETER_TYPE: 'type' must be a string or array"); 387 } 388 389 if(o_type instanceof Array){ 390 var This = this; 391 o_type.forEach(function (s_type) { 392 if (!tsk_string_is_null_or_empty(s_type) && tsk_string_is_string(s_type)) { 393 This.ao_listeners[s_type] = o_listener; 394 } 395 }); 396 } 397 else{ 398 this.ao_listeners[o_type] = o_listener; 399 } 400 } 401 402 /** 403 Removes an event listener from the target object. 404 @param {String} type The event type/identifier to stop listening for. 405 @see <a href="#addEventListener">addEventListener</a> 406 @exemple 407 this.removeEventListener('started'); 408 */ 409 SIPml.EventTarget.prototype.removeEventListener = function (s_type) { 410 if (tsk_string_is_string(s_type) && !tsk_string_is_null_or_empty(s_type)) { 411 this.ao_listeners[s_type] = undefined; 412 } 413 } 414 415 /** 416 @ignore 417 @private 418 @param {Object} event 419 */ 420 SIPml.EventTarget.prototype.dispatchEvent = function (o_event) { 421 var o_listener = (this.ao_listeners[o_event.s_type] || this.ao_listeners['*']); 422 if (o_listener) { 423 o_listener.call(this, o_event.o_value); 424 } 425 } 426 427 428 429 // ================================== SIPml.Event ========================================== 430 431 432 /** 433 SIP event object. You should never create an instance of this class by yourself. 434 @constructor 435 @param {String} type The event type or identifier. Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Session">this link</a> for more information about all supported session event types. 436 @param {tsip_event} [event] Private wrapped session object. 437 @property {String} type The event <a href="SIPml.EventTarget.html#SIPml.EventTarget.Session">type or identifier</a> (e.g. <i>'connected'</i>). 438 @property {String} description User-friendly description in english (e.g. <i>'Session is now connected'</i>). 439 */ 440 SIPml.Event = function (s_type, o_event) { 441 this.type = s_type; 442 this.description = o_event ? o_event.s_phrase : s_type; 443 this.o_event = o_event; 444 } 445 446 /** 447 Gets the SIP response code. 448 @returns {Integer} The SIP response code (e.g. 404). 449 */ 450 SIPml.Event.prototype.getSipResponseCode = function () { 451 var o_message = this.o_event ? this.o_event.get_message() : null; 452 if (o_message && o_message.is_response()) { 453 return o_message.get_response_code(); 454 } 455 return -1; 456 } 457 458 /** 459 Gets the SIP content associated to this event. This function could be called to get the content of the incoming SIP message ('i_new_message' event). 460 @returns {Object} SIP content. 461 @see <a href="#getContentType">getContentType</a>, <a href="#getContentString">getContentString</a> 462 */ 463 SIPml.Event.prototype.getContent = function () { 464 var o_message = this.o_event ? this.o_event.get_message() : null; 465 if (o_message) { 466 return o_message.get_content(); 467 } 468 return null; 469 } 470 471 /** 472 Gets the SIP content associated to this event. This function could be called to get the content of the incoming SIP message ('i_new_message' event). 473 @returns {String} SIP content. 474 @see <a href="#getContentType">getContentType</a>, <a href="#getContent">getContent</a> 475 */ 476 SIPml.Event.prototype.getContentString = function () { 477 var o_message = this.o_event ? this.o_event.get_message() : null; 478 if (o_message) { 479 return o_message.get_content_as_string(); 480 } 481 return null; 482 } 483 484 /** 485 Gets the SIP content-type associated to this event. This function could be called to get the content-type of the incoming SIP message ('i_new_message' event). 486 @returns {Object} SIP content-type. 487 @see <a href="#getContent">getContent</a> 488 */ 489 SIPml.Event.prototype.getContentType = function () { 490 var o_message = this.o_event ? this.o_event.get_message() : null; 491 if (o_message) { 492 return o_message.get_content_type(); 493 } 494 return null; 495 } 496 497 498 499 // ================================== SIPml.Stack ========================================== 500 501 502 /** 503 Anonymous SIP Stack configuration object. 504 @namespace SIPml.Stack.Configuration 505 @name SIPml.Stack.Configuration 506 @property {String} realm The domain name. Required for stack <a href="SIPml.Stack.html#constructor">constructor</a> but optional when used with <a href="SIPml.Stack.html#setConfiguration">setConfiguration</a>. <br /> 507 Example: <i>example.org</i> 508 @property {String} impi The authentication name. Required for stack <a href="SIPml.Stack.html#constructor">constructor</a> but optional when used with <a href="SIPml.Stack.html#setConfiguration">setConfiguration</a>.<br /> 509 Example: <i>+33600000000</i> or <i>bob</i>. 510 @property {string} impu The full SIP uri address. Required for stack <a href="SIPml.Stack.html#constructor">constructor</a> but optional when used with <a href="SIPml.Stack.html#setConfiguration">setConfiguration</a>.<br /> 511 Example: <i>sip:+33600000000@example.com</i> or <i>tel:+33600000000</i> or <i>sip:bob@example.com</i> 512 @property {String} [password] The password to use for SIP authentication.<br /> 513 Example: <i>mysecret</i> 514 @property {String} [display_name] The display name to use in SIP requests. This is the String displayed by the called party for incoming calls. <br /> 515 Example: <i>I Am Legend</i> 516 @property {String} [websocket_proxy_url] The websocket proxy url to connect to (SIP server or gateway address). If unset the stack will use sipml5.org as host and a random port. You should not set this value unless you know what you're doing.<br /> 517 Example: <i>ws://sipml5.org:5060</i> 518 @property {String} [outbound_proxy_url] The outbound Proxy URL is used to set the destination IP address and Port to use for all outgoing requests regardless the <i>domain name</i> (a.k.a <i>realm</i>). <br /> 519 This is a good option for developers using a SIP domain name without valid DNS A/NAPTR/SRV records. You should not set this value unless you know what you're doing. <br /> 520 Example: <i>udp://192.168.0.12:5060</i> 521 @property {Array} [ice_servers] The list of the STUN/TURN servers to use. The format must be as explained at <a target=_blank href="http://www.w3.org/TR/webrtc/#rtciceserver-type">http://www.w3.org/TR/webrtc/#rtciceserver-type</a>. 522 Example: <i>[{ url: 'stun:stun.l.google.com:19302'}, { url:'turn:user@numb.viagenie.ca', credential:'myPassword'}]</i> 523 @property {Boolean} [enable_rtcweb_breaker] Whether to enable the <a href="http://webrtc2sip.org/#aRTCWebBreaker" target=_blank>RTCWeb Breaker</a> module to allow calling SIP-legacy networks. 524 Example: <i>true</i> 525 @property {Boolean} [enable_click2call] Whether to enable the <a href="http://click2dial.org" target=_blank>Click2Call / Click2Dial</a> service. 526 <i>Available since version 1.2.181</i>. 527 Example: <i>true</i> 528 @property {Object} [events_listener] Object to subscribe to some events. 529 Example: 530 <ul> 531 <li><i>{ events: '*', listener: function(e){} }</i> </li> 532 <li><i>{ events: 'started', listener: function(e){} }</i></li> 533 <li><i>{ events: ['started', 'stopped'], listener: function(e){} }</i></li> 534 </ul> 535 You can also use <a href="#addEventListener">addEventListener</a> to add listeners to the stack. 536 @property {Array} [sip_headers] Stack-level SIP headers to add to all outgoing requests. Each header is an object with a <i>name</i> and <i>value</i> fields. <br /> 537 Example: <i>sip_headers: [{name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.89.0'}, {name: 'Organization', value: 'Doubango Telecom'}]</i> 538 539 @example 540 var configuration = { 541 realm: 'example.org', 542 impi: 'bob', 543 impu: 'sip:bob@example.org', 544 password: 'mysecret', // optional 545 display_name: 'I Am Legend', // optional 546 websocket_proxy_url: 'ws://192.168.0.10:5060', // optional 547 outbound_proxy_url: 'udp://192.168.0.12:5060', // optional 548 enable_rtcweb_breaker: true, // optional 549 enable_click2call: false, // optional 550 events_listener: { events: '*', listener: listenerFunc }, //optional 551 sip_headers: [ //optional 552 {name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.89.0'}, 553 {name: 'Organization', value: 'Doubango Telecom'} 554 ] 555 }; 556 */ 557 558 559 /** 560 This is the root object used by any other object to make/receive calls, messages or manage presence. 561 You have to create an instance of this class before anything else. 562 @extends SIPml.EventTarget 563 @constructor 564 @class 565 @param {SIPml.Stack.Configuration} configuration Configuration object. Could be updated later using <a href="#setConfiguration">setConfiguration</a>. 566 @throws {ERR_INVALID_PARAMETER_VALUE|ERR_INVALID_PARAMETER_TYPE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_INVALID_PARAMETER_TYPE</font> 567 @example 568 var listenerFunc = function(e){ 569 console.info('stack event = ' + e.type); 570 // Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Stack">this link</a> for more information on all supported events. 571 } 572 573 var o_stack = new SIPml.Stack({ 574 realm: 'example.org', 575 impi: 'bob', 576 impu: 'sip:bob@example.org', 577 password: 'mysecret', // optional 578 display_name: 'I Am Legend', // optional 579 websocket_proxy_url: 'ws://192.168.0.10:5060', // optional 580 outbound_proxy_url: 'udp://192.168.0.12:5060', // optional 581 ice_servers: [{ url: 'stun:stun.l.google.com:19302'}, { url:'turn:user@numb.viagenie.ca', credential:'myPassword'}], // optional 582 enable_rtcweb_breaker: true, // optional 583 enable_click2call: false, // optional 584 events_listener: { events: '*', listener: listenerFunc }, //optional 585 sip_headers: [ //optional 586 {name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.89.0'}, 587 {name: 'Organization', value: 'Doubango Telecom'} 588 ] 589 } 590 ); 591 592 @see <a href="#setConfiguration">setConfiguration</a> 593 */ 594 SIPml.Stack = function (o_conf) { 595 SIPml.init(); 596 SIPml.EventTarget.call(this); 597 /* 598 members: 599 - o_stack {tsip_stack} 600 */ 601 602 if (!o_conf) { 603 throw new Error("ERR_INVALID_PARAMETER_VALUE: null configuration value"); 604 } 605 if (tsk_string_is_null_or_empty(o_conf.realm)) { 606 throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + o_conf.realm + "' is not valid as realm value"); 607 } 608 if (tsk_string_is_null_or_empty(o_conf.impi)) { 609 throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + o_conf.impi + "' is not valid as impi value"); 610 } 611 if (tsk_string_is_null_or_empty(o_conf.impu)) { 612 throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + o_conf.impu + "' is not valid as impu value"); 613 } 614 // check IMPU validity 615 var o_impu = tsip_uri.prototype.Parse(o_conf.impu); 616 if (!o_impu || !o_impu.s_user_name || !o_impu.s_host) { 617 throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + o_conf.impu + "' is not valid as SIP Uri"); 618 } 619 620 var i_port; 621 var s_proxy; 622 623 if (!SIPml.isWebSocketSupported()) { 624 // port and host will be updated using the result from DNS SRV(NAPTR(realm)) 625 i_port = 5060; 626 s_proxy = o_conf.realm; 627 } 628 else { 629 // there are at least 5 servers running on the cloud. 630 // we will connect to one of them and let the balancer to choose the right one (less connected sockets) 631 // each port can accept up to 65K connections which means that the cloud can manage 325K active connections 632 // the number of port will be increased or decreased based on the current trafic 633 634 // webrtc2sip 2.2+ (Doubango): 635 // WS: 10060, 11060, 12060, 13060, 14060 636 // WSS: 10062, 11062, 12062, 13062, 14062 637 // 638 639 i_port = (o_conf.enable_rtcweb_breaker ? 10062 : 10060) + (((new Date().getTime()) % 5) * 1000); 640 s_proxy = "sipml5.org"; 641 } 642 643 // create the stack 644 this.o_stack = new tsip_stack(o_conf.realm, o_conf.impi, o_conf.impu, s_proxy, i_port); 645 this.o_stack.oStack = this; 646 // set configurations 647 this.setConfiguration(o_conf); 648 649 // listen for stack events 650 this.o_stack.on_event_stack = function(e) { 651 var s_type; 652 switch (e.i_code) { 653 case tsip_event_code_e.STACK_STARTING: s_type = 'starting'; break; 654 case tsip_event_code_e.STACK_STARTED: s_type = 'started'; break; 655 case tsip_event_code_e.STACK_STOPPING: s_type = 'stopping'; break; 656 case tsip_event_code_e.STACK_STOPPED: s_type = 'stopped'; break; 657 case tsip_event_code_e.STACK_FAILED_TO_START: s_type = 'failed_to_start'; break; 658 case tsip_event_code_e.STACK_FAILED_TO_STOP: s_type = 'failed_to_stop'; break; 659 } 660 if(s_type){ 661 e.o_stack.oStack.dispatchEvent({ s_type: s_type, o_value: new SIPml.Stack.Event(s_type, e) }); 662 } 663 } 664 665 666 // listen for dialog events 667 this.o_stack.on_event_dialog = function (e) { 668 var s_type = null; 669 var i_session_id = e.o_session.i_id; 670 var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id]; 671 if (!oSession) { 672 tsk_utils_log_warn('Cannot find session with id = ' + i_session_id); 673 return; 674 } 675 676 switch (e.i_code) { 677 case tsip_event_code_e.DIALOG_TRANSPORT_ERROR: s_type = 'transport_error'; break; 678 case tsip_event_code_e.DIALOG_GLOBAL_ERROR: s_type = 'global_error'; break; 679 case tsip_event_code_e.DIALOG_MESSAGE_ERROR: s_type = 'message_error'; break; 680 case tsip_event_code_e.DIALOG_WEBRTC_ERROR: s_type = 'webrtc_error'; break; 681 case tsip_event_code_e.DIALOG_REQUEST_INCOMING: s_type = 'i_request'; break; 682 case tsip_event_code_e.DIALOG_REQUEST_OUTGOING: s_type = 'o_request'; break; 683 case tsip_event_code_e.DIALOG_REQUEST_CANCELLED: s_type = 'cancelled_request'; break; 684 case tsip_event_code_e.DIALOG_REQUEST_SENT: s_type = 'sent_request'; break; 685 case tsip_event_code_e.DIALOG_MEDIA_ADDED: s_type = 'media_added'; break; 686 case tsip_event_code_e.DIALOG_MEDIA_REMOVED: s_type = 'media_removed'; break; 687 case tsip_event_code_e.DIALOG_CONNECTING: s_type = 'connecting'; break; 688 case tsip_event_code_e.DIALOG_CONNECTED: s_type = 'connected'; break; 689 case tsip_event_code_e.DIALOG_TERMINATING: s_type = 'terminating'; break; 690 case tsip_event_code_e.DIALOG_TERMINATED: 691 { 692 s_type = 'terminated'; 693 e.o_session.o_stack.oStack.ao_sessions[i_session_id] = undefined; 694 break; 695 } 696 default: break; 697 } 698 699 if (s_type) { 700 oSession.dispatchEvent({ s_type: s_type, o_value: new SIPml.Session.Event(oSession, s_type, e) }); 701 } 702 } 703 704 // listen for MESSAGE events 705 this.o_stack.on_event_message = function (e) { 706 var s_type = null; 707 var i_session_id = e.o_session.i_id; 708 var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id]; 709 710 switch (e.e_message_type) { 711 case tsip_event_message_type_e.I_MESSAGE: s_type = 'i_new_message'; break; 712 case tsip_event_message_type_e.AO_MESSAGE: s_type = 'i_ao_request'; break; 713 } 714 715 if (s_type) { 716 // 'i_new_call' is stack-level event 717 if (s_type == 'i_new_message') { 718 var oNewEvent = new SIPml.Stack.Event(s_type, e); 719 oNewEvent.newSession = new SIPml.Session.Message(e.o_session); 720 e.o_session.o_stack.oStack.ao_sessions[i_session_id] = oNewEvent.newSession; // save session 721 e.o_session.o_stack.oStack.dispatchEvent({ s_type: s_type, o_value: oNewEvent}); 722 } 723 else { 724 if(oSession){ 725 oSession.dispatchEvent({ s_type: s_type, o_value: new SIPml.Session.Event(oSession, s_type, e) }); 726 } 727 else{ 728 tsk_utils_log_warn('Cannot find session with id = ' + i_session_id + ' and event = ' + e.e_invite_type); 729 } 730 } 731 } 732 }; 733 734 // listen for PUBLISH events 735 this.o_stack.on_event_publish = function (e) { 736 var s_type = null; 737 var i_session_id = e.o_session.i_id; 738 var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id]; 739 if(!oSession){ 740 tsk_utils_log_warn('Cannot find session with id = ' + i_session_id + ' and event = ' + e.e_invite_type); 741 return; 742 } 743 744 switch(e.e_publish_type){ 745 case tsip_event_publish_type_e.I_PUBLISH: break; 746 case tsip_event_publish_type_e.I_UNPUBLISH: break; 747 case tsip_event_publish_type_e.AO_PUBLISH: 748 case tsip_event_publish_type_e.AO_UNPUBLISH: 749 { 750 s_type = 'i_ao_request'; 751 break; 752 } 753 } 754 if(s_type){ 755 oSession.dispatchEvent({ s_type: s_type, o_value: new SIPml.Session.Event(oSession, s_type, e) }); 756 } 757 } 758 759 // listen for SUBSCRIBE events 760 this.o_stack.on_event_subscribe = function (e) { 761 var s_type = null; 762 var i_session_id = e.o_session.i_id; 763 var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id]; 764 if(!oSession){ 765 tsk_utils_log_warn('Cannot find session with id = ' + i_session_id + ' and event = ' + e.e_invite_type); 766 return; 767 } 768 769 switch(e.e_subscribe_type){ 770 case tsip_event_subscribe_type_e.I_SUBSCRIBE: break; 771 case tsip_event_subscribe_type_e.I_UNSUBSRIBE: break; 772 case tsip_event_subscribe_type_e.AO_SUBSCRIBE: 773 case tsip_event_subscribe_type_e.AO_UNSUBSCRIBE: 774 case tsip_event_subscribe_type_e.AO_NOTIFY: 775 { 776 s_type = 'i_ao_request'; 777 break; 778 } 779 case tsip_event_subscribe_type_e.I_NOTIFY: 780 { 781 s_type = 'i_notify'; 782 break; 783 } 784 } 785 if(s_type){ 786 oSession.dispatchEvent({ s_type: s_type, o_value: new SIPml.Session.Event(oSession, s_type, e) }); 787 } 788 } 789 790 791 // listen for INVITE events 792 this.o_stack.on_event_invite = function (e) { 793 var s_type = null; 794 var i_session_id = e.o_session.i_id; 795 var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id]; 796 if (!oSession) { 797 switch (e.e_invite_type) { 798 case tsip_event_invite_type_e.I_NEW_CALL: 799 case tsip_event_invite_type_e.M_STREAM_LOCAL_REQUESTED: 800 case tsip_event_invite_type_e.M_STREAM_LOCAL_ACCEPTED: 801 case tsip_event_invite_type_e.M_STREAM_LOCAL_REFUSED: 802 break; 803 804 case tsip_event_invite_type_e.M_STREAM_LOCAL_ADDED: 805 case tsip_event_invite_type_e.M_STREAM_REMOTE_ADDED: 806 case tsip_event_invite_type_e.M_STREAM_LOCAL_REMOVED: 807 case tsip_event_invite_type_e.M_STREAM_REMOTE_REMOVED: 808 case tsip_event_invite_type_e.I_AO_REQUEST: 809 tsk_utils_log_info('Not notifying to session with id = ' + i_session_id + ' for event = ' + e.e_invite_type); 810 return; 811 812 default: 813 tsk_utils_log_warn('Cannot find session with id = ' + i_session_id + ' and event = ' + e.e_invite_type); 814 return; 815 } 816 } 817 818 819 820 var _setStream = function(o_view, o_stream, o_url, b_audio){ 821 if(o_stream){ 822 if(!b_audio && o_stream.videoTracks.length > 0){ 823 if (window.HTMLVideoElement && o_view instanceof window.HTMLVideoElement){ 824 if((o_view.src = o_url)){ 825 o_view.play(); 826 } 827 } 828 return true; 829 } 830 if(b_audio && o_stream.audioTracks.length > 0){ 831 if (window.HTMLAudioElement && o_view instanceof window.HTMLAudioElement){ 832 if((o_view.src = o_url)){ 833 o_view.play(); 834 } 835 } 836 return true; 837 } 838 } 839 } 840 841 var attachStream = function(bLocal){ 842 var o_stream = bLocal ? e.o_session.get_stream_local() : e.o_session.get_stream_remote(); 843 var o_url = bLocal ? e.o_session.get_url_local() : e.o_session.get_url_remote(); 844 if(_setStream((bLocal ? oSession.videoLocal : oSession.videoRemote), o_stream, o_url, false)){ 845 dispatchEvent(bLocal ? 'm_stream_video_local_added' : 'm_stream_video_remote_added'); 846 } 847 if(_setStream((bLocal ? oSession.audioLocal : oSession.audioRemote), o_stream, o_url, true)){ 848 dispatchEvent(bLocal ? 'm_stream_audio_local_added' : 'm_stream_audio_remote_added'); 849 } 850 } 851 var deattachStream = function(bLocal){ 852 var o_stream = bLocal ? e.o_session.get_stream_local() : e.o_session.get_stream_remote(); 853 if(_setStream((bLocal ? oSession.videoLocal : oSession.videoRemote), o_stream, null, false)){ 854 dispatchEvent(bLocal ? 'm_stream_video_local_removed' : 'm_stream_video_remote_removed'); 855 } 856 if(_setStream((bLocal ? oSession.audioLocal : oSession.audioRemote), o_stream, null, true)){ 857 dispatchEvent(bLocal ? 'm_stream_audio_local_removed' : 'm_stream_audio_remote_removed'); 858 } 859 } 860 861 var dispatchEvent = function (s_event_type) { 862 if (s_event_type) { 863 // 'i_new_call', 'm_permission_requested', 'm_permission_accepted' and 'm_permission_refused' are stack-level event 864 switch (s_event_type) { 865 case 'i_new_call': 866 case 'm_permission_requested': 867 case 'm_permission_accepted': 868 case 'm_permission_refused': 869 { 870 var oNewEvent = new SIPml.Stack.Event(s_event_type, e); 871 if(s_event_type == 'i_new_call'){ 872 oNewEvent.newSession = new SIPml.Session.Call(e.o_session); 873 e.o_session.o_stack.oStack.ao_sessions[i_session_id] = oNewEvent.newSession; // save session 874 } 875 e.o_session.o_stack.oStack.dispatchEvent({ s_type: s_event_type, o_value: oNewEvent }); 876 break; 877 } 878 default: 879 { 880 oSession.dispatchEvent({ s_type: s_event_type, o_value: new SIPml.Session.Event(oSession, s_event_type, e) }); 881 break; 882 } 883 } 884 } 885 } 886 887 switch (e.e_invite_type) { 888 case tsip_event_invite_type_e.I_NEW_CALL: s_type = 'i_new_call'; break; 889 case tsip_event_invite_type_e.I_ECT_NEW_CALL: s_type = 'i_ect_new_call'; break; 890 case tsip_event_invite_type_e.I_AO_REQUEST: s_type = 'i_ao_request'; break; 891 case tsip_event_invite_type_e.M_EARLY_MEDIA: s_type = 'm_early_media'; break; 892 case tsip_event_invite_type_e.M_STREAM_LOCAL_REQUESTED: s_type = 'm_permission_requested'; break; 893 case tsip_event_invite_type_e.M_STREAM_LOCAL_ACCEPTED: s_type = 'm_permission_accepted'; break; 894 case tsip_event_invite_type_e.M_STREAM_LOCAL_REFUSED: s_type = 'm_permission_refused'; break; 895 case tsip_event_invite_type_e.M_STREAM_LOCAL_ADDED: 896 { 897 return attachStream(true); 898 } 899 case tsip_event_invite_type_e.M_STREAM_LOCAL_REMOVED: 900 { 901 return deattachStream(true); 902 } 903 case tsip_event_invite_type_e.M_STREAM_REMOTE_ADDED: 904 { 905 return attachStream(false); 906 } 907 case tsip_event_invite_type_e.M_STREAM_REMOTE_REMOVED: 908 { 909 return deattachStream(false); 910 } 911 case tsip_event_invite_type_e.M_LOCAL_HOLD_OK: s_type = 'm_local_hold_ok'; break; 912 case tsip_event_invite_type_e.M_LOCAL_HOLD_NOK: s_type = 'm_local_hold_nok'; break; 913 case tsip_event_invite_type_e.M_LOCAL_RESUME_OK: s_type = 'm_local_resume_ok'; break; 914 case tsip_event_invite_type_e.M_LOCAL_RESUME_NOK: s_type = 'm_local_resume_nok'; break; 915 case tsip_event_invite_type_e.M_REMOTE_HOLD: s_type = 'm_remote_hold'; break; 916 case tsip_event_invite_type_e.M_REMOTE_RESUME: s_type = 'm_remote_resume'; break; 917 case tsip_event_invite_type_e.O_ECT_TRYING: s_type = 'o_ect_trying'; break; 918 case tsip_event_invite_type_e.O_ECT_ACCEPTED: s_type = 'o_ect_accepted'; break; 919 case tsip_event_invite_type_e.O_ECT_COMPLETED: s_type = 'o_ect_completed'; break; 920 case tsip_event_invite_type_e.I_ECT_COMPLETED: s_type = 'i_ect_completed'; break; 921 case tsip_event_invite_type_e.O_ECT_FAILED: s_type = 'o_ect_failed'; break; 922 case tsip_event_invite_type_e.I_ECT_FAILED: s_type = 'i_ect_failed'; break; 923 case tsip_event_invite_type_e.O_ECT_NOTIFY: s_type = 'o_ect_notify'; break; 924 case tsip_event_invite_type_e.I_ECT_NOTIFY: s_type = 'i_ect_notify'; break; 925 case tsip_event_invite_type_e.I_ECT_REQUESTED: s_type = 'i_ect_requested'; break; 926 default: break; 927 } 928 929 // dispatch event 930 dispatchEvent(s_type); 931 } 932 } 933 934 SIPml.Stack.prototype = Object.create(SIPml.EventTarget.prototype); 935 SIPml.Stack.prototype.ao_sessions = []; 936 937 /** 938 Updates configuration values. 939 @param {SIPml.Stack.Configuration} configuration Configuration object value. 940 @returns {Integer} 0 if successful; otherwise nonzero 941 @example 942 // add two new headers and change the <i>proxy_url</i> 943 o_stack.setConfiguration({ 944 proxy_url: 'ws://192.168.0.10:5060', 945 sip_headers: [ 946 {name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.89.0'}, 947 {name: 'Organization', value: 'Doubango Telecom'} 948 ] 949 } 950 ); 951 */ 952 SIPml.Stack.prototype.setConfiguration = function (o_conf) { 953 if (o_conf.realm && !tsk_string_is_string(o_conf.realm)) { 954 throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.realm + "' not a valid type for realm. String is expected"); 955 } 956 if (o_conf.impi && !tsk_string_is_string(o_conf.impi)) { 957 throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.impi + "' not a valid type for impi. String is expected"); 958 } 959 if (o_conf.impu && !tsk_string_is_string(o_conf.impu)) { 960 throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.impu + "' not a valid type for impu. String is expected"); 961 } 962 if (o_conf.password && !tsk_string_is_string(o_conf.password)) { 963 throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.password + "' not a valid type for password. String is expected"); 964 } 965 if (o_conf.display_name && !tsk_string_is_string(o_conf.display_name)) { 966 throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.display_name + "' not a valid type for display_name. String is expected"); 967 } 968 if (o_conf.websocket_proxy_url && !tsk_string_is_string(o_conf.websocket_proxy_url)) { 969 throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.websocket_proxy_url + "' not a valid type for websocket_proxy_url. String is expected"); 970 } 971 if (o_conf.outbound_proxy_url && !tsk_string_is_string(o_conf.outbound_proxy_url)) { 972 throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.outbound_proxy_url + "' not a valid type for outbound_proxy_url. String is expected"); 973 } 974 if (o_conf.sip_headers && typeof o_conf.sip_headers != "Array" && !(o_conf.sip_headers instanceof Array)) { 975 throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.sip_headers + "' not a valid type for sip_headers. Array is expected"); 976 } 977 978 // event-listener: must be first to be defined as other configs could raise events 979 if(o_conf.events_listener){ 980 this.addEventListener(o_conf.events_listener.events, o_conf.events_listener.listener); 981 } 982 983 var b_rtcweb_breaker_enabled = !!o_conf.enable_rtcweb_breaker; 984 var b_click2call_enabled = !!o_conf.enable_click2call; 985 var o_stack = this.o_stack; 986 tsk_utils_log_info("s_websocket_server_url=" + (o_conf.websocket_proxy_url || "(null)")); 987 tsk_utils_log_info("s_sip_outboundproxy_url=" + (o_conf.outbound_proxy_url || "(null)")); 988 tsk_utils_log_info("b_rtcweb_breaker_enabled=" + (b_rtcweb_breaker_enabled ? "yes" : "no")); 989 tsk_utils_log_info("b_click2call_enabled=" + (b_click2call_enabled ? "yes" : "no")); 990 991 o_stack.set(tsip_stack.prototype.SetPassword(o_conf.password), 992 tsip_stack.prototype.SetDisplayName(o_conf.display_name), 993 tsip_stack.prototype.SetProxyOutBoundUrl(o_conf.outbound_proxy_url), 994 tsip_stack.prototype.SetRTCWebBreakerEnabled(b_rtcweb_breaker_enabled), 995 tsip_stack.prototype.SetClick2CallEnabled(b_click2call_enabled), 996 tsip_stack.prototype.SetSecureTransportEnabled(b_rtcweb_breaker_enabled), // always use secure transport when RTCWebBreaker 997 tsip_stack.prototype.SetWebsocketServerUrl(o_conf.websocket_proxy_url), 998 tsip_stack.prototype.SetIceServers(o_conf.ice_servers)); 999 1000 // add sip headers 1001 if (o_conf.sip_headers) { 1002 o_conf.sip_headers.forEach(function (o_header) { 1003 if (o_header && !tsk_string_is_null_or_empty(o_header.name) && (!o_header.value || tsk_string_is_string(o_header.value))) { 1004 o_stack.set( 1005 tsip_stack.prototype.SetHeader(o_header.name, o_header.value) 1006 ); 1007 } 1008 }); 1009 } 1010 1011 return 0; 1012 } 1013 1014 1015 /** 1016 Starts the SIP stack and connect the network transport to the WebSocket server without sending any SIP request. 1017 This function must be be called before any attempt to make or receive calls/messages. This function is asynchronous which means that the stack will not be immediately started after the call. 1018 Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Stack">this link</a> for more information on all supported events. 1019 @returns {Integer} 0 if successful; otherwise nonzero 1020 @throws {ERR_INVALID_STATE} <font color="red">ERR_INVALID_STATE</font> 1021 */ 1022 SIPml.Stack.prototype.start = function () { 1023 return this.o_stack.start(); 1024 } 1025 1026 /** 1027 Stops the SIP stack and disconnect the network transport from the WebSocket server. This function will also hangup all calls and unregister the user from the SIP server. 1028 Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Stack">this link</a> for more information on all supported events. 1029 @param {Integer} [timeout] Optional parameter used to defined maximum time in milliseconds to take to stop the stack. 1030 Default value: 2000 millis 1031 @returns {Integer} 0 if successful; otherwise nonzero 1032 @throws {ERR_INVALID_STATE} <font color="red">ERR_INVALID_STATE</font> 1033 */ 1034 SIPml.Stack.prototype.stop = function (i_timeout) { 1035 return this.o_stack.stop(i_timeout); 1036 } 1037 1038 /** 1039 Create new SIP session. 1040 @param {String} type Session type. Supported values: <b>'register'</b>, <b>'call-audio'</b>, <b>'call-audiovideo'</b>, <b>'call-video'</b>, <b>'message'</b>, <b>'subscribe'</b> or <b>'publish'</b>. 1041 @param {SIPml.Session.Configuration} [configuration] Anonymous object used to configure the newly created session. 1042 @throws {ERR_INVALID_PARAMETER_VALUE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> <br> 1043 @returns {SIPml.Session} New session if successful; otherwise null.<br> The session type would be <a href="SIPml.Session.Registration.html">SIPml.Session.Registration</a>, <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> or <a href="SIPml.Session.Message.html">SIPml.Session.Message</a> <br> 1044 1045 @example 1046 var <a href="SIPml.Session.Registration.html">o_registration</a> = this.<a href="#newSession">newSession</a>('register', { 1047 expires: 200, 1048 sip_caps: [ 1049 {name: '+g.oma.sip-im'}, 1050 {name: '+audio'}, 1051 {name: 'language', value: '\"en,fr\"'} 1052 ], 1053 sip_headers: [ 1054 {name: 'What', value: 'Registration', session: false}, 1055 {name: 'Organization', value: 'Doubango Telecom', session: true} 1056 ] 1057 }); 1058 o_registration.<a href="SIPml.Session.Registration.html#register">register</a>(); 1059 1060 // or 1061 var <a href="SIPml.Session.Call.html">o_audiovideo</a> = this.<a href="#newSession">newSession</a>('call-audiovideo', { 1062 video_local: document.getElementById('video_local'), 1063 video_remote: document.getElementById('video_remote'), 1064 audio_remote: document.getElementById('audio_remote'), 1065 sip_caps: [ 1066 {name: '+g.oma.sip-im'}, 1067 {name: '+sip.ice'}, 1068 {name: 'language', value: '\"en,fr\"'} 1069 ], 1070 sip_headers: [ 1071 {name: 'What', value: 'Audio/Video call', session: false}, 1072 {name: 'Organization', value: 'Doubango Telecom', session: false} 1073 ] 1074 }); 1075 o_audiovideo.<a href="SIPml.Session.Call.html#call">call</a>('alice'); // call alice 1076 */ 1077 SIPml.Stack.prototype.newSession = function (s_type, o_conf) { 1078 var o_session; 1079 var cls; 1080 if (s_type == 'register') { 1081 o_session = new tsip_session_register(this.o_stack); 1082 cls = SIPml.Session.Registration; 1083 } 1084 else if (s_type == 'message') { 1085 o_session = new tsip_session_message(this.o_stack); 1086 cls = SIPml.Session.Message; 1087 } 1088 else if (s_type == 'publish') { 1089 o_session = new tsip_session_publish(this.o_stack); 1090 cls = SIPml.Session.Publish; 1091 } 1092 else if (s_type == 'subscribe') { 1093 o_session = new tsip_session_subscribe(this.o_stack); 1094 cls = SIPml.Session.Subscribe; 1095 } 1096 else if (s_type == 'call-audio' || s_type == 'call-audiovideo' || s_type == 'call-video') { 1097 o_session = new tsip_session_invite(this.o_stack); 1098 o_session.s_type = s_type; 1099 cls = SIPml.Session.Call; 1100 } 1101 else { 1102 throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + s_type + "' not valid as session type"); 1103 } 1104 1105 o_session.b_local = true; // locally created session 1106 var oSession = new cls(o_session, o_conf); 1107 this.ao_sessions[oSession.getId()] = oSession; 1108 return oSession; 1109 } 1110 1111 // ================================== SIPml.Stack.Event ========================================== 1112 1113 /** 1114 SIP Stack event object. You should never create an instance of this object. 1115 @constructor 1116 @extends SIPml.Event 1117 @param {String} type The event type/identifier. 1118 @param {tsip_event} [event] The wrapped event object. 1119 @property {String} type The event type or identifier (e.g. <i>'started'</i> or <i>'i_new_call'</i>). Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Stack">this link</a> for more information on all supported events. 1120 @property {String} description User-friendly description in english (e.g. <i>'Stack started'</i> or <i>'<b>I</b>ncoming <b>new</b> <b>call</b>'</i>). 1121 @property {SIPml.Session} [newSession] Optional session object only defined when the event is about a new session creation (e.g. <i>'i_new_call'</i>). 1122 The session type would be <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> or <a href="SIPml.Session.Message.html">SIPml.Session.Message</a> 1123 */ 1124 SIPml.Stack.Event = function (s_type, o_event) { 1125 SIPml.Event.call(this, s_type, o_event); 1126 } 1127 1128 SIPml.Stack.Event.prototype = Object.create(SIPml.Event.prototype); 1129 1130 // ================================== SIPml.Session ========================================== 1131 1132 /** 1133 Anonymous SIP Session configuration object. 1134 @namespace SIPml.Session.Configuration 1135 @name SIPml.Session.Configuration 1136 @property {Integer} [expires] Session timeout in seconds. 1137 @property {HTMLVideoElement} [video_local] <a href="https://developer.mozilla.org/en-US/docs/DOM/HTMLVideoElement">HTMLVideoElement<a> where to display the local video preview. This propety should be only used for <a href="SIPml.Session.Call.html">video sessions</a>. 1138 @property {HTMLVideoElement} [video_remote] <a href="https://developer.mozilla.org/en-US/docs/DOM/HTMLVideoElement">HTMLVideoElement<a> where to display the remote video stream. This propety should be only used for <a href="SIPml.Session.Call.html">video sessions</a>. 1139 @property {HTMLAudioElement} [audio_remote] <a href="https://developer.mozilla.org/en-US/docs/DOM/HTMLAudioElement">HTMLAudioElement<a> used to playback the remote audio stream. This propety should be only used for <a href="SIPml.Session.Call.html">audio sessions</a>. 1140 @property {Array} [sip_caps] <i>{name,value}</i> pairs defining the SIP capabilities associated to this session. The capabilities are added to the Contact header. Please refer to <a href="http://tools.ietf.org/html/rfc3840">rfc3840</a> and <a href="http://tools.ietf.org/html/rfc3841">rfc3841</a> for more information. 1141 @property {String} [from] Set the source uri string to be used in the <i>From</i> header (available since API version 1.2.170). 1142 @property {Array} [sip_headers] <i>{name,value,session}</i> trios defining the SIP headers associated to this session. <i>session</i> is a boolean defining whether the header have to be added to all outgoing request or not (initial only). 1143 @example 1144 var configuration = 1145 { 1146 expires: 200, 1147 audio_remote: document.getElementById('audio_remote'), 1148 video_local: document.getElementById('video_local'), 1149 video_remote: document.getElementById('video_remote'), 1150 sip_caps: [ 1151 {name: '+g.oma.sip-im'}, 1152 {name: '+sip.ice'}, 1153 {name: 'language', value: '\"en,fr\"'} 1154 ], 1155 sip_headers: [ 1156 {name: 'What', value: 'Audio/Video call', session: false}, 1157 {name: 'Organization', value: 'Doubango Telecom', session: false} 1158 ] 1159 } 1160 */ 1161 1162 /** 1163 Base (abstract) SIP session. You should never create an instance of this class by yourself. You have to use <a href="SIPml.Stack.html#newSession"> newSession()</a> to create a new instance. 1164 This is a base class for <a href="SIPml.Session.Registration.html">SIPml.Session.Registration</a>, <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> and <a href="SIPml.Session.Message.html">SIPml.Session.Message</a>. 1165 @constructor 1166 @extends SIPml.EventTarget 1167 @param {tsip_session_t} session Private wrapped session object. 1168 @param {SIPml.Session.Configuration} [configuration] Optional configuration object. 1169 @throws {ERR_INVALID_PARAMETER_VALUE|ERR_INVALID_PARAMETER_TYPE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_INVALID_PARAMETER_TYPE</font> 1170 */ 1171 SIPml.Session = function (o_session, o_conf) { 1172 SIPml.EventTarget.call(this); 1173 /* 1174 - o_configuration: [] 1175 - o_session: tsip_session_xxx 1176 */ 1177 if (!o_session) { 1178 throw new Error("ERR_INVALID_PARAMETER_VALUE: Invalid session value"); 1179 } 1180 1181 this.o_session = o_session; 1182 this.setConfiguration(o_conf); 1183 } 1184 1185 SIPml.Session.prototype = Object.create(SIPml.EventTarget.prototype); 1186 SIPml.Session.prototype.o_session = null; 1187 SIPml.Session.prototype.o_session = null; 1188 SIPml.Session.prototype.o_configuration = null; 1189 1190 /** 1191 Gets the session unique identifier. 1192 @returns {Integer} Read-only session unique identifier. 1193 */ 1194 SIPml.Session.prototype.getId = function(){ 1195 return this.o_session.get_id(); 1196 } 1197 1198 /** 1199 Updates or sets the session configuration. 1200 @param {SIPml.Session.Configuration} configuration 1201 */ 1202 SIPml.Session.prototype.setConfiguration = function(o_conf){ 1203 if(!o_conf){ 1204 return; 1205 } 1206 1207 var o_session = this.o_session; 1208 1209 // event-listener: must be first to be defined as other configs could raise events 1210 if(o_conf.events_listener){ 1211 this.addEventListener(o_conf.events_listener.events, o_conf.events_listener.listener); 1212 } 1213 1214 if(this instanceof SIPml.Session.Call){ 1215 this.videoLocal = o_conf.video_local; 1216 this.videoRemote = o_conf.video_remote; 1217 this.audioRemote = o_conf.audio_remote; 1218 this.audioLocal = o_conf.audio_local; 1219 1220 var _addStream = function(o_view, o_stream, o_url, b_audio){ 1221 if(o_stream){ 1222 if(!b_audio && o_stream.videoTracks.length > 0){ 1223 if (window.HTMLVideoElement && o_view instanceof window.HTMLVideoElement){ 1224 if(o_view.src == o_url) return false; // unchanged 1225 else if(o_view.src = o_url) o_view.play(); 1226 } 1227 return true; 1228 } 1229 if(b_audio && o_stream.audioTracks.length > 0){ 1230 if (window.HTMLAudioElement && o_view instanceof window.HTMLAudioElement){ 1231 if(o_view.src == o_url) return false; // unchanged 1232 else if(o_view.src = o_url) o_view.play(); 1233 } 1234 return true; 1235 } 1236 } 1237 } 1238 1239 if(_addStream(this.videoLocal, o_session.get_stream_local(), o_session.get_url_local(), false)){ 1240 this.dispatchEvent({ s_type: 'm_stream_video_local_added', o_value: new SIPml.Session.Event(this, 'm_stream_video_local_added') }); 1241 } 1242 if(_addStream(this.videoRemote, o_session.get_stream_remote(), o_session.get_url_remote(), false)){ 1243 this.dispatchEvent({ s_type: 'm_stream_video_remote_added', o_value: new SIPml.Session.Event(this, 'm_stream_video_remote_added') }); 1244 } 1245 if(_addStream(this.audioLocal, o_session.get_stream_local(), o_session.get_url_local(), false)){ 1246 this.dispatchEvent({ s_type: 'm_stream_audio_local_added', o_value: new SIPml.Session.Event(this, 'm_stream_audio_local_added') }); 1247 } 1248 if(_addStream(this.audioRemote, o_session.get_stream_remote(), o_session.get_url_remote(), false)){ 1249 this.dispatchEvent({ s_type: 'm_stream_audio_remote_added', o_value: new SIPml.Session.Event(this, 'm_stream_audio_remote_added') }); 1250 } 1251 } 1252 1253 // headers 1254 if (o_conf.sip_headers) { 1255 o_conf.sip_headers.forEach(function (o_header) { 1256 if (o_header && !tsk_string_is_null_or_empty(o_header.name) && (!o_header.value || tsk_string_is_string(o_header.value))) { 1257 o_session.set( 1258 tsip_session.prototype.SetHeader(o_header.name, o_header.value) 1259 ); 1260 } 1261 }); 1262 } 1263 // caps 1264 if (o_conf.sip_caps) { 1265 o_conf.sip_caps.forEach(function (o_cap) { 1266 if (o_cap && !tsk_string_is_null_or_empty(o_cap.name) && (!o_cap.value || tsk_string_is_string(o_cap.value))) { 1267 o_session.set( 1268 tsip_session.prototype.SetCaps(o_cap.name, o_cap.value) 1269 ); 1270 } 1271 }); 1272 } 1273 // expires 1274 if (o_conf.expires) { 1275 o_session.set(tsip_session.prototype.SetExpires(o_conf.expires)); 1276 } 1277 // from 1278 if (o_conf.from) { 1279 o_session.set(tsip_session.prototype.SetFromStr(o_conf.from)); 1280 } 1281 } 1282 1283 /** 1284 Gets the remote party SIP Uri (e.g. sip:john.doe@example.com). This Uri could be used a match an incoming call to a contact from your address book. 1285 @returns {String} The remote party SIP Uri. 1286 @see <a href="#getRemoteFriendlyName">getRemoteFriendlyName</a> 1287 */ 1288 SIPml.Session.prototype.getRemoteUri = function(){ 1289 return (this.o_session.b_local ? this.o_session.o_uri_to : this.o_session.o_uri_from).toString(); 1290 } 1291 1292 /** 1293 Gets the remote party friendly name (e.g. 'John Doe'). 1294 @returns {String} The remote party friendly name. 1295 @see <a href="#getRemoteUri">getRemoteUri</a> 1296 */ 1297 SIPml.Session.prototype.getRemoteFriendlyName = function(){ 1298 var o_uri = this.o_session.b_local ? this.o_session.o_uri_to : this.o_session.o_uri_from; 1299 return o_uri.s_display_name ? o_uri.s_display_name : o_uri.s_user_name; 1300 } 1301 1302 /** 1303 Rejects an incoming SIP MESSAGE (SMS-like) or audio/video call. 1304 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1305 @returns {Integer} 0 if successful; otherwise nonzero 1306 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font> 1307 */ 1308 SIPml.Session.prototype.reject = function (o_conf) { 1309 this.setConfiguration(o_conf); 1310 return this.o_session.reject(); 1311 } 1312 1313 /** 1314 Accepts an incoming SIP MESSAGE (SMS-like) or audio/video call. 1315 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1316 @returns {Integer} 0 if successful; otherwise nonzero 1317 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font> 1318 */ 1319 SIPml.Session.prototype.accept = function (o_conf) { 1320 this.setConfiguration(o_conf); 1321 return this.o_session.accept(); 1322 } 1323 1324 1325 1326 // ================================== SIPml.Session.Event ================================== 1327 1328 1329 /** 1330 SIP session event object. You should never create an instance of this class by yourself. 1331 @constructor 1332 @extends SIPml.Event 1333 @param {SIPml.Session} [session] The session type would be <a href="SIPml.Session.Registration.html">SIPml.Session.Registration</a>, <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> or <a href="SIPml.Session.Message.html">SIPml.Session.Message</a>. 1334 @param {String} type The event type or identifier. Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Session">this link</a> for more information about all supported session event types. 1335 @param {tsip_event} [event] Private wrapped session object. 1336 @property {SIPml.Session} session Session associated to this event. Would be <a href="SIPml.Session.Registration.html">SIPml.Session.Registration</a>, <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> or <a href="SIPml.Session.Message.html">SIPml.Session.Message</a>. 1337 */ 1338 SIPml.Session.Event = function (o_session, s_type, o_event) { 1339 SIPml.Event.call(this, s_type, o_event); 1340 this.session = o_session; 1341 } 1342 1343 SIPml.Session.Event.prototype = Object.create(SIPml.Event.prototype); 1344 1345 1346 /** 1347 Gets the name of destination for the current call transfer. 1348 @returns {String} The name of destination for the current call transfer (e.g. 'John Doe'). 1349 */ 1350 SIPml.Session.Event.prototype.getTransferDestinationFriendlyName = function () { 1351 var o_message = this.o_event ? this.o_event.get_message() : null; 1352 if (o_message) { 1353 var o_hdr_Refer_To = o_message.get_header(tsip_header_type_e.Refer_To); 1354 if(o_hdr_Refer_To && o_hdr_Refer_To.o_uri){ 1355 return (o_hdr_Refer_To.s_display_name ? o_hdr_Refer_To.s_display_name : o_hdr_Refer_To.o_uri.s_user_name); 1356 } 1357 } 1358 return null; 1359 } 1360 1361 // ================================== SIPml.Registration ================================== 1362 1363 /** 1364 SIP session registration class. You should never create an instance of this class by yourself. 1365 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a new registration session. 1366 @constructor 1367 @extends SIPml.Session 1368 @param {tsip_session} session Private wrapped session object 1369 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1370 */ 1371 SIPml.Session.Registration = function (o_session, o_configuration) { 1372 SIPml.Session.call(this, o_session, o_configuration); 1373 } 1374 1375 SIPml.Session.Registration.prototype = Object.create(SIPml.Session.prototype); 1376 1377 /** 1378 Sends SIP REGISTER request to login the user. Refreshing requests will be automatically done based on the expiration time. 1379 @param {SIPml.Session.Configuration} [configuration] Optional configuration value. 1380 @example 1381 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('register', { 1382 expires: 200, 1383 events_listener: { events: '*', listener: onSipEventSession }, 1384 sip_caps: [ 1385 { name: '+g.oma.sip-im', value: null }, 1386 { name: '+audio', value: null }, 1387 { name: 'language', value: '\"en,fr\"' } 1388 ] 1389 }); 1390 session.register(); 1391 1392 @see <a href="#unregister">unregister</a> 1393 @throws {ERR_INVALID_STATE} <font color="red">ERR_INVALID_STATE</font> 1394 */ 1395 SIPml.Session.Registration.prototype.register = function (o_conf) { 1396 // FIXME: apply o_configuration 1397 // FIXME: raise error if stack not started 1398 this.setConfiguration(o_conf); 1399 return this.o_session.register(); 1400 } 1401 1402 /** 1403 Sends SIP REGISTER (expires=0) request to logout the user. 1404 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1405 @see <a href="#register">register</a> 1406 @throws {ERR_INVALID_STATE} <font color="red">ERR_INVALID_STATE</font> 1407 */ 1408 SIPml.Session.Registration.prototype.unregister = function (o_conf) { 1409 // FIXME: raise error if stack not started 1410 this.setConfiguration(o_conf); 1411 return this.o_session.unregister(); 1412 } 1413 1414 // ================================== SIPml.Call ========================================== 1415 1416 /** 1417 SIP audio/video call session class. You should never create an instance of this class by yourself. 1418 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a new audio/video session. 1419 @constructor 1420 @extends SIPml.Session 1421 @param {tsip_session} session Private wrapped session object 1422 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1423 @example 1424 var listenerFunc = function(e){ 1425 console.info('session event = ' + e.type); 1426 } 1427 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('call-audiovideo', { 1428 video_local: document.getElementById('video-local'), 1429 video_remote: document.getElementById('video-remote'), 1430 audio_remote: document.getElementById('audio-remote'), 1431 events_listener: { events: '*', listener: listenerFunc }, 1432 sip_caps: [ 1433 { name: '+g.oma.sip-im' }, 1434 { name: '+sip.ice' }, 1435 { name: 'language', value: '\"en,fr\"' } 1436 ] 1437 }); 1438 @throws {ERR_INVALID_PARAMETER_VALUE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> 1439 */ 1440 SIPml.Session.Call = function (o_session, o_conf) { 1441 SIPml.Session.call(this, o_session, o_conf); 1442 1443 switch (o_session.s_type) { 1444 case 'call-audio': this.mediaType = tmedia_type_e.AUDIO; break; 1445 case 'call-audiovideo': this.mediaType = tmedia_type_e.AUDIO_VIDEO; break; 1446 case 'call-video': this.mediaType = tmedia_type_e.VIDEO; break; 1447 } 1448 } 1449 1450 SIPml.Session.Call.prototype = Object.create(SIPml.Session.prototype); 1451 SIPml.Session.Call.prototype.videoLocal = null; 1452 SIPml.Session.Call.prototype.videoRemote = null; 1453 1454 /** 1455 Makes audio/video call. 1456 @param {String} to Destination name, uri, phone number or identifier (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000'). 1457 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1458 @returns {Integer} 0 if successful; otherwise nonzero 1459 @example 1460 var listenerFunc = function(e){ 1461 console.info('session event = ' + e.type); 1462 } 1463 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('call-audiovideo'); 1464 session.call('johndoe', { 1465 video_local: document.getElementById('video-local'), 1466 video_remote: document.getElementById('video-remote'), 1467 audio_remote: document.getElementById('audio-remote'), 1468 events_listener: { events: '*', listener: listenerFunc }, 1469 sip_caps: [ 1470 { name: '+g.oma.sip-im' }, 1471 { name: '+sip.ice' }, 1472 { name: 'language', value: '\"en,fr\"' } 1473 ] 1474 }); 1475 1476 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font> 1477 */ 1478 SIPml.Session.Call.prototype.call = function (s_to, o_conf) { 1479 if (tsk_string_is_null_or_empty(s_to)) { 1480 throw new Error("ERR_INVALID_PARAMETER_VALUE: 'to' must not be null"); 1481 } 1482 if (!SIPml.haveMediaStream()) { 1483 throw new Error("ERR_NOT_READY: Media engine not ready yet"); 1484 } 1485 // set destination 1486 this.o_session.set(tsip_session.prototype.SetToStr(s_to)); 1487 // set conf 1488 this.setConfiguration(o_conf); 1489 // make call 1490 return this.o_session.call(this.mediaType); 1491 } 1492 1493 /** 1494 Terminates the audio/video call. 1495 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1496 @returns {Integer} 0 if successful; otherwise nonzero 1497 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font> 1498 */ 1499 SIPml.Session.Call.prototype.hangup = function (o_conf) { 1500 this.setConfiguration(o_conf); 1501 return this.o_session.hangup(); 1502 } 1503 1504 /** 1505 Holds the audio/video call. 1506 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1507 @returns {Integer} 0 if successful; otherwise nonzero 1508 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font> 1509 */ 1510 SIPml.Session.Call.prototype.hold = function (o_conf) { 1511 this.setConfiguration(o_conf); 1512 return this.o_session.hold(this.mediaType); 1513 } 1514 1515 /** 1516 Resumes the audio/video call. 1517 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1518 @returns {Integer} 0 if successful; otherwise nonzero 1519 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font> 1520 */ 1521 SIPml.Session.Call.prototype.resume = function (o_conf) { 1522 this.setConfiguration(o_conf); 1523 return this.o_session.resume(this.mediaType); 1524 } 1525 1526 /** 1527 Sends SIP INFO message. 1528 @param {Object|String} [content] SIP INFO request content. 1529 @param {String} [contentType] Content Type. 1530 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1531 @returns {Integer} 0 if successful; otherwise nonzero 1532 @example 1533 session.info('Device orientation: portrait', 'doubango/device-orientation.xml'); 1534 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font> 1535 */ 1536 SIPml.Session.Call.prototype.info = function (o_content, s_content_type, o_conf) { 1537 this.setConfiguration(o_conf); 1538 return this.o_session.info(o_content, s_content_type); 1539 } 1540 1541 /** 1542 Sends a SIP DTMF digit. 1543 @param {Char} [digit] The digit to send. 1544 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1545 @returns {Integer} 0 if successful; otherwise nonzero 1546 @example 1547 session.dtmf('#'); 1548 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font> 1549 */ 1550 SIPml.Session.Call.prototype.dtmf = function (c_digit, o_conf) { 1551 this.setConfiguration(o_conf); 1552 return this.o_session.dtmf(c_digit); 1553 } 1554 1555 /** 1556 Transfers the call to a new destination. 1557 @param {String} to Transfer destination name, uri, phone number or identifier (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000'). 1558 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1559 @returns {Integer} 0 if successful; otherwise nonzero 1560 @example 1561 session.transfer('johndoe'); 1562 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font> 1563 */ 1564 SIPml.Session.Call.prototype.transfer = function (s_to, o_conf) { 1565 this.setConfiguration(o_conf); 1566 return this.o_session.transfer(s_to); 1567 } 1568 1569 /** 1570 Accepts incoming transfer request. 1571 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1572 @returns {Integer} 0 if successful; otherwise nonzero 1573 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font> 1574 */ 1575 SIPml.Session.Call.prototype.acceptTransfer = function (o_conf) { 1576 this.setConfiguration(o_conf); 1577 return this.o_session.transfer_accept(); 1578 } 1579 1580 /** 1581 Rejects incoming transfer request. 1582 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1583 @returns {Integer} 0 if successful; otherwise nonzero 1584 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font> 1585 */ 1586 SIPml.Session.Call.prototype.rejectTransfer = function (o_conf) { 1587 this.setConfiguration(o_conf); 1588 return this.o_session.transfer_reject(); 1589 } 1590 1591 // ================================== SIPml.Session.Message ========================================== 1592 1593 /** 1594 SIP MESSAGE (SMS) session class. You should never create an instance of this class by yourself. 1595 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a messaging/IM session. 1596 @constructor 1597 @param {tsip_session} session Private session object. 1598 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1599 @example 1600 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('message'); 1601 */ 1602 SIPml.Session.Message = function (o_session, o_conf) { 1603 SIPml.Session.call(this, o_session, o_conf); 1604 1605 } 1606 1607 SIPml.Session.Message.prototype = Object.create(SIPml.Session.prototype); 1608 1609 /** 1610 Sends a SIP MESSAGE (SMS-like) request. 1611 @param {String} to Destination name, uri, phone number or identifier (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000'). 1612 @param {Object|String} [content] The message content. 1613 @param {String} [contentType] The content type. 1614 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1615 @returns {Integer} 0 if successful; otherwise nonzero 1616 @example 1617 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('message'); 1618 session.send('johndoe', 'Pêche à la moule', 'text/plain;charset=utf8',{ 1619 sip_caps: [ 1620 { name: '+g.oma.sip-im' }, 1621 { name: '+sip.ice' }, 1622 { name: 'language', value: '\"en,fr\"' } 1623 ], 1624 sip_headers: [ 1625 { name: 'What', value: 'Sending SMS' }, 1626 { name: 'My-Organization', value: 'Doubango Telecom' } 1627 ] 1628 }); 1629 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font> 1630 */ 1631 SIPml.Session.Message.prototype.send = function (s_to, o_content, s_content_type, o_conf){ 1632 if (tsk_string_is_null_or_empty(s_to)) { 1633 throw new Error("ERR_INVALID_PARAMETER_VALUE: 'to' must not be null"); 1634 } 1635 1636 // apply configuration values 1637 this.setConfiguration(o_conf); 1638 // set destination 1639 this.o_session.set(tsip_session.prototype.SetToStr(s_to)); 1640 // sends the message 1641 return this.o_session.send(o_content, s_content_type); 1642 } 1643 1644 1645 1646 // ================================== SIPml.Session.Publish ========================================== 1647 1648 1649 /** 1650 SIP PUBLISH (for presence status publication) session class.You should never create an instance of this class by yourself. 1651 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a new presence publication session. 1652 @constructor 1653 @extends SIPml.Session 1654 @since version 1.1.0 1655 @param {tsip_session} session Private session object. 1656 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1657 @example 1658 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('publish', { 1659 expires: 200, 1660 events_listener: { events: '*', listener: function(e){} }, 1661 sip_headers: [ 1662 { name: 'Event', value: 'presence' } // very important 1663 ], 1664 sip_caps: [ 1665 { name: '+g.oma.sip-im', value: null }, 1666 { name: '+audio', value: null }, 1667 { name: 'language', value: '\"en,fr\"' } 1668 ] 1669 }); 1670 */ 1671 SIPml.Session.Publish = function (o_session, o_conf) { 1672 SIPml.Session.call(this, o_session, o_conf); 1673 1674 } 1675 1676 SIPml.Session.Publish.prototype = Object.create(SIPml.Session.prototype); 1677 1678 /** 1679 Sends a SIP PUBLISH (for presence status publication) request. 1680 @since version 1.1.0 1681 @param {Object|String} [content] The request content. 1682 @param {String} [contentType] The content type. 1683 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1684 @returns {Integer} 0 if successful; otherwise nonzero 1685 @example 1686 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('publish'); 1687 var contentType = 'application/pidf+xml'; 1688 var content = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n' + 1689 '<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\n' + 1690 ' xmlns:im=\"urn:ietf:params:xml:ns:pidf:im\"' + 1691 ' entity=\"sip:bob@example.com\">\n' + 1692 '<tuple id=\"s8794\">\n' + 1693 '<status>\n'+ 1694 ' <basic>open</basic>\n' + 1695 ' <im:im>away</im:im>\n' + 1696 '</status>\n' + 1697 '<contact priority=\"0.8\">tel:+33600000000</contact>\n' + 1698 '<note xml:lang=\"fr\">Bonjour de Paris :)</note>\n' + 1699 '</tuple>\n' + 1700 '</presence>'; 1701 1702 session.publish(content, contentType,{ 1703 expires: 200, 1704 sip_caps: [ 1705 { name: '+g.oma.sip-im' }, 1706 { name: '+sip.ice' }, 1707 { name: 'language', value: '\"en,fr\"' } 1708 ], 1709 sip_headers: [ 1710 { name: 'Event', value: 'presence' }, 1711 { name: 'Organization', value: 'Doubango Telecom' } 1712 ] 1713 }); 1714 @returns {Integer} 0 if successful; otherwise nonzero 1715 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font> 1716 @see <a href="#unpublish">unpublish</a> 1717 */ 1718 SIPml.Session.Publish.prototype.publish = function (o_content, s_content_type, o_conf){ 1719 // apply configuration values 1720 this.setConfiguration(o_conf); 1721 // sends the PUBLISH request 1722 return this.o_session.publish(o_content, s_content_type); 1723 } 1724 1725 /** 1726 Remove/unpublish presence data from the server. 1727 @since version 1.1.0 1728 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1729 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font> 1730 @see <a href="#publish">publish</a> 1731 */ 1732 SIPml.Session.Publish.prototype.unpublish = function (o_conf){ 1733 // apply configuration values 1734 this.setConfiguration(o_conf); 1735 // sends the PUBLISH request (expires = 0) 1736 return this.o_session.unpublish(); 1737 } 1738 1739 1740 1741 1742 1743 // ================================== SIPml.Session.Subscribe ========================================== 1744 1745 1746 /** 1747 SIP SUBSCRIBE (for presence status subscription) session class.You should never create an instance of this class by yourself. 1748 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a new presence subscription session. 1749 @constructor 1750 @extends SIPml.Session 1751 @since version 1.1.0 1752 @param {tsip_session} session Private session object. 1753 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1754 @example 1755 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('subscribe', { 1756 expires: 200, 1757 events_listener: { events: '*', listener: function(e){} }, 1758 sip_headers: [ 1759 { name: 'Event', value: 'presence' }, 1760 { name: 'Accept', value: 'application/pidf+xml' } 1761 ], 1762 sip_caps: [ 1763 { name: '+g.oma.sip-im', value: null }, 1764 { name: '+audio', value: null }, 1765 { name: 'language', value: '\"en,fr\"' } 1766 ] 1767 }); 1768 */ 1769 SIPml.Session.Subscribe = function (o_session, o_conf) { 1770 SIPml.Session.call(this, o_session, o_conf); 1771 1772 } 1773 1774 SIPml.Session.Subscribe.prototype = Object.create(SIPml.Session.prototype); 1775 1776 /** 1777 Sends a SIP SUBSCRIBE (for presence status subscription) request. 1778 @since version 1.1.0 1779 @param {String} to Destination name, uri, phone number or identifier (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000'). 1780 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1781 @returns {Integer} 0 if successful; otherwise nonzero 1782 @example 1783 var onEvent = function(e){ 1784 if(e.type == 'i_notify'){ 1785 // process incoming NOTIFY request 1786 } 1787 } 1788 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('subscribe', { 1789 expires: 200, 1790 events_listener: { events: '*', listener: onEvent }, 1791 sip_headers: [ 1792 { name: 'Event', value: 'presence' }, 1793 { name: 'Accept', value: 'application/pidf+xml' } 1794 ], 1795 sip_caps: [ 1796 { name: '+g.oma.sip-im', value: null }, 1797 { name: '+audio', value: null }, 1798 { name: 'language', value: '\"en,fr\"' } 1799 ] 1800 }); 1801 session.subscribe('johndoe'); // watch for johndoe's presence status 1802 1803 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font> 1804 @see <a href="#unsubscribe">unsubscribe</a> 1805 */ 1806 SIPml.Session.Subscribe.prototype.subscribe = function (s_to, o_conf){ 1807 if (tsk_string_is_null_or_empty(s_to)) { 1808 throw new Error("ERR_INVALID_PARAMETER_VALUE: 'to' must not be null"); 1809 } 1810 // set destination 1811 this.o_session.set(tsip_session.prototype.SetToStr(s_to)); 1812 // apply configuration values 1813 this.setConfiguration(o_conf); 1814 // sends the PUBLISH request 1815 return this.o_session.subscribe(); 1816 } 1817 1818 /** 1819 Unsubscribe. 1820 @since version 1.1.0 1821 @param {SIPml.Session.Configuration} [configuration] Configuration value. 1822 @returns {Integer} 0 if successful; otherwise nonzero 1823 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font> 1824 @see <a href="#subscribe">subscribe</a> 1825 */ 1826 SIPml.Session.Subscribe.prototype.unsubscribe = function (o_conf){ 1827 // apply configuration values 1828 this.setConfiguration(o_conf); 1829 // sends the SUBSCRIBE request (expires = 0) 1830 return this.o_session.unsubscribe(); 1831 } 1832