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