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