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