JsSIP clients not entering freeswitch conference with 'conference_set_auto_outcall' - freeswitch

I am having an issue with setting up a conference in FreeSwitch with JsSIP clients. I have the conference_set_auto_outcall set in the dialplan to call 1000 and 1001. When using MicroSIP (software SIP phone) everything works as expected and the 2 clients pick up the conference however when the originating phone is a JsSIP instance none of the clients are placed in the conference (running conference 23456 play <wav-file> doesn't play on any of them).
Checking the remote stream that is recieved by the remote clients shows the video track has no dimension details (height or width) and a framerate of 0.
Calls from the one web-app to the other work as expected and using originate <user> &conference(<conf-id>) to place all of the users into the conference also works as expected.
Any help would be greatly appreciated, please let me know if there is any other information that might be helpful!
The dialplan can be seen below
<extension name="Demonstrate Conference">
<condition field="destination_number" expression="^(23456)$">
<action application="answer" />
<action application="set" data="conference_auto_outcall_timeout=30" />
<action application="set" data="conference_utils_auto_outcall_flags=mute"/>
<action application="set" data="conference_auto_outcall_prefix={sip_auto_answer=true,execute_on_answer='bind_meta_app 2 a s1 transfer::intercept:${uuid} inline'}"/>
<!-- Set caller ID for incoming calls -->
<action application="set" data="conference_auto_outcall_caller_id_name=Emergency" />
<action application="set" data="conference_auto_outcall_caller_id_number=9876543210" />
<action application="set" data="conference_auto_outcall_profile=default" />
<!-- When the call initiator leaves the conference ends -->
<action application="set" data="[conference_member_flags=endconf]user/${caller_id_number}#$${domain}" />
<!-- Autocall 1000 and 1001 -->
<action application="conference_set_auto_outcall" data="user/1000#$${domain}"/>
<action application="conference_set_auto_outcall" data="user/1001#$${domain}"/>
<action application="conference" data="$1#default+flags{endconf}" />
</condition>
</extension>
Both instances share most of the same userAgent event detection
userAgent.on('newRTCSession', function (data) {
console.info('onNewRTCSession:', data);
//Call incoming
if (data.originator == 'remote') {
console.info("incomingSession, answer the call----------------------");
incomingSession = data.session;
var status = document.getElementById("status");
status.innerHTML = "Incoming call";
setTimeout(() => {
data.session.answer({
'mediaConstraints': get_constraints(), 'mediaStream': null
})
updateUI();
}, 1000);
}else {
console.info("outgoingSession");
outgoingSession = data.session;
outgoingSession.on('connecting', function (data) {
console.info('onConnecting-', data.request);
currentSession = outgoingSession;
outgoingSession = null;
});
}
data.session.on('accepted', function (data) {
console.info('onAccepted-', data);
if (data.originator == 'remote' && currentSession == null) {
currentSession = incomingSession;
incomingSession = null;
console.info("setCurrentSession-", currentSession);
}
});
data.session.on('confirmed', function (data) {
console.info('onConfirmed-', data);
if (data.originator == 'remote' && currentSession == null) {
currentSession = incomingSession;
incomingSession = null;
console.info("setCurrentSession-", currentSession);
}
});
data.session.on('sdp', function (data) {
console.info('onSDP, type-', data.type, 'sdp-', data.sdp);
});
data.session.on('progress', function (data) {
console.info('onProgress-', data.originator);
if (data.originator == 'remote') {
console.info('onProgress, response-', data.response);
}
});
data.session.on('peerconnection', function (data) {
console.info('onPeerconnection-', data.peerconnection);
data.peerconnection.onaddstream = function (ev) {
console.info('onaddstream from remote -----------', ev);
videoView.srcObject = ev.stream;
remote_stream = ev.stream;
};
});
data.session.on('ended', function (data) {
console.info('onEnded-', data);
if (data.originator == 'remote') {
console.info('onEnded, response-', data.response);
}
currentSession = null;
updateUI();
});
});
Full logs and HTML pages can be seen in the pastebins below
Logs
Call placer - https://pastebin.com/5HdjPADH
Call reciever - https://pastebin.com/64eb9jUT
FreeSwitch Logs - https://pastebin.com/p7MtTxvm
HTML Pages
Call placer - https://pastebin.com/f7E71wJh
Call reciever - https://pastebin.com/22wD9NRp
EDIT: If one of the client machines (1000) is a microSIP client, 1001 will be able to see the camera of 1000 however the call initiator will not be able to see anything.
EDIT 2: Having implemented the same pages in sipJS the same issue is occuring suggesting the problem might be with WebRTC in FreeSwitch.

Related

Calling ASP.NET Web Service with XMLHttpRequest

I have a web service defined as:
[ScriptService]
public class LoginController : ApiController
{
[HttpGet]
[WebMethod]
public IEnumerable<string> getUsers()
{
try
{
using (var context = new questionanswerEntities())
{
var users = context.users.ToList();
string[] result = new string[users.Count];
for (int i = 0; i < users.Count; ++i)
{
user u = users[i];
result[i] = u.id + " " + u.name + " " + u.email;
}
return result;
}
}
catch (MySqlException ex)
{
throw (ex);
}
catch (EntityException ex)
{
throw (ex);
}
}
}
I have published this service to my localhost and I am able to call and test it with a browser and the result is:
<ArrayOfstring>
<string>1 Olcay aaa#aaa.com</string>
<string>2 Mukaddes aaa#aaa.com</string>
<string>3 Saduman sadumanertas#gmail.com</string>
<string>4 Bernam bernella#bb.com</string>
<string>8 Bernella bernella#gmail.com</string>
<string>9 Bernella bernella#gmail.com</string>
<string>10 Bernella bernella#gmail.com</string>
<string>11 Bernella bernella#gmail.com</string>
<string>12 Bernella bernella#gmail.com</string>
<string>13 Bernella bernella#gmail.com</string>
<string>14 lala loorrrrr#gmail.com</string>
<string>15 lala loorrrrr#gmail.com</string>
</ArrayOfstring>
What I need is to call this service from JavaScript. I am trying this:
function callService() {
var url = "http://localhost:1903/QATest/login/getUsers";
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader("Content-type", "application/json");
xhr.onload = function () {
var response = xhr.responseText;
console.log("XHR - onload - Result of getUsers: " + response);
document.getElementById('response').innerHTML = "Response: " + response;
};
xhr.onerror = function () {
console.log("XHR - onerror - Web servis cagirilirken hata olustu!");
};
xhr.send();
};
But getting this error:
XMLHttpRequest cannot load http://localhost:1903/QATest/login/getUsers. Invalid HTTP status code 405
My ASP.NET service is hosted on my localhost and client side codes are hosted at my remote host at 'www.olcayertas.com'.
What am I doing wrong?
UPDATE:
I have started this project as ASP.NET Web application and then selected Web API option. So there is no any asmx files as web service projects. But Exposing Web Services to Client Script suggests adding following to client side for ASP.NET web pages:
<asp:ScriptManager runat="server" ID="scriptManager">
<Services>
<asp:ServiceReference
path="~/WebServices/SimpleWebService.asmx" />
</Services>
</asp:ScriptManager>
But I have to call my services using JavaScript from a PHP page.
How can I do that?
You are being blocked because you are attempting a Cross-Origin XMLHttpRequest which fails because of the Same-origin policy
You will need to configure CORS on you local host to allow requests from 'www.olcayertas.com'. With IIS you might just need to add this to the web.config;
<add name="Access-Control-Allow-Origin" value="*" />
Be careful though as this opens up your service to requests from any domain. You should probably only allow requests from 'www.olcayertas.com' if you know this is the only consumer of your web service.

SIGNALR: $.connection.hub.start() results in "Error parsing negotiate response."

I had basic SignalR functionality implemented and working in my MVC5/AngularJS application recently, but after shelfing and unshelfing the changes I am now getting an error when the connection is negotiated in $.connection.hub.start().
I've stripped down the code to the very basics, but still get this error. Poking around in the jquery.signalR-2.2.0.js where the negotiate request is made, I found that the result returned from the ajax request to http://localhost:44379/signalr/negotiate?clientProtocol=1.5&connectionData=[] is returning the HTML of the page instead of JSON data!
connection._.negotiateRequest = /* This is on line 659 */ signalR.transports._logic.ajax(connection, {
url: url, // http://localhost:44379/signalr/negotiate?clientProtocol=1.5&connectionData=%5B%5D
error: function (error, statusText) {
// Irrelevant code removed.
},
success: function (result) { // We get here at least...
var res,
keepAliveData,
protocolError,
transports = [],
supportedTransports = [];
try {
res = connection._parseResponse(result); // This fails because result contains HTML.
} catch (error) {
// error.message is "Unexpected token <"
onFailed(signalR._.error(resources.errorParsingNegotiateResponse, error), connection);
return;
}
Here is my javascript for establishing the hub/connection:
$(function () {
var hub = $.connection.testHub;
if (hub)
console.log("SignalR hub initialized.");
$.connection.hub.start().done(function () {
console.log("SignalR connection established.");
}).fail(function (err) {
console.log("Error starting SignalR connection: " + err); // Ends up here.
});
});
And the script references (I have the signalr code in a separate js file named messaging.js):
<script src="~/assets/js/signalr/jquery.signalR-2.2.0.js"></script>
<script src="~/Scripts/messaging/messaging.js"></script>
<script src="~/signalr/hubs"></script>
I don't really understand why the ajax response from signalr/negotiate would be returning HTML instead of JSON. I've stripped down the server side hub code to an empty class with [AllowAnonymous] to ensure nothing in there was causing the problem. I have the app.MapSignalR() call in Startup.cs in place. My first thought, since this occurred after shelfing and unshelfing, was that something didn't make it into the shelf and was lost, but I can't seem to find anything missing...
Anyone have any ideas?
I found the problem while playing with the rules in web.config.
Previously, I had this rule for signalr:
<add input="{REQUEST_URI}" matchType="Pattern" pattern="/signalr/hubs" negate="true" />
Changing the pattern allowed communication with /signalr/negotiate, I think:
<add input="{REQUEST_URI}" matchType="Pattern" pattern="^/(signalr)" negate="true"/>
...I have no idea how this worked before.

$http error handling: distinguishing user offline from other errors

I have a simple contact form, which Angular submits via AJAX to my app on Google App Engine. (A POST handler uses the send_mail function to email the website owner). This is the client code:
$http.post('', jQuery.param(user), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).success(function(data, status, headers, config) {
//...
}).error(function(data, status, headers, config) {
alert('Please check your Internet connection and try again.');
});
Obviously the alert() is handling all errors. Assuming no errors in my server-side code, I'm guessing the chances of App Engine returning anything other than an HTTP status code of 200 is low. However I would still like to distinguish between server errors and the user having lost their connection.
I was thinking of using XMLHttpRequest's textStatus as per this SO answer, but it doesn't appear to be available to the $http.error() callback. I also thought of using Offline.js but I don't need most of what it does so that would seem like wasted bytes in this case.
The $http.error() status I get when offline is 0, but I'm not sure how cross-browser reliable that's going to be. What should I be using?
Before giving you the solution I just wanted to highlight the problems with browser provided is-offline flag
Different browser's offline flag has different meanging
a. for some browsers it means internet is not there
b. for some browsers it means intranet is not there
c. some browsers do allow offline mode, so even if there is no internet, you are not actually offline.
not all browsers support offline in consistent way
Another problem I had with using browser based flag is development scenario, where having internet is not necessary, and that should not trigger the offline mode (for me I block all user interaction If my website goes offline). You can solve this problem by having another indicator telling you if you are in dev/prod, etc.
And most imp part, why do we care to find if browser is in offline mode, is because we do care only if our website is reachable or not, we don't actually care if the internet is there or not. So even if browser tell us it is offline, it is not exactly what we want. there is a tiny difference between what we want and what browser provides.
So considering all of above, I have solved the problem using an offline directive which I am using to block user interaction if user is offline
csapp.directive('csOffline', ["$http", '$interval', "$timeout", "$modal", function ($http, $interval, $timeout, $modal) {
var linkFn = function (scope) {
scope.interval = 10; //seconds
var checkConnection = function () {
$http({
method: 'HEAD',
url: document.location.pathname + "?rand=" + Math.floor((1 + Math.random()) * 0x10000)
})
.then(function (response) {
$scope.isOnline = true;
}).catch(function () {
$scope.isOnline = false;
});
};
scope.isOnline = true;
$interval(checkConnection, scope.interval * 1000);
scope.$watch('isOnline', function (newVal) {
console.log("isOnline: ", newVal);
//custom logic here
});
};
return {
scope: {},
restrict: 'E',
link: linkFn,
};
}]);
I was about to use offline.js, it was too much and most of which I didnt need, so this is the solution I came up with which is purely in angular.js.
please note that http interceptor is invoked during these calls, I wanted to avoid that, hence I had used $.ajax for the calls
$.ajax({
type: "HEAD",
url: document.location.pathname + "?rand=" + Math.floor((1 + Math.random()) * 0x10000),
contentType: "application/json",
error: function () {
scope.isOnline = false;
},
success: function (data, textStatus, xhr) {
var status = xhr.status;
scope.isOnline = status >= 200 && status < 300 || status === 304;
}
}
);
you can replace the logic inside isOnline true/false, with whatever custom logic you want.
I'd go with response.status === 0 check. I've tested it using the code below (needs to be put on a webserver that you can switch on/off at will) and I'm getting 0 in current versions of Google Chrome, Mozilla Firefox, and Internet Explorer. You may use it to test all browsers you want to support.
Code for testing connection status:
<!DOCTYPE html>
<html>
<head>
<title>Connection status test</title>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
<script type="text/javascript">
var log = [],
pendingCount = 0,
pendingLimit = 5;
angular.module('app', [])
.run(function ($rootScope) {
$rootScope.log = log;
})
.run(function ($http, $interval, $rootScope) {
$interval(function () {
if (pendingCount >= pendingLimit) {
return;
}
var item = {
time: Date.now(),
text: 'Pending...'
};
++pendingCount;
$http.get('.', {})
.then(function () {
item.text = 'Done';
}, function (response) {
item.text = 'Done (status ' + response.status + ')';
})
.finally(function () {
--pendingCount;
});
log.unshift(item);
}, 1000);
});
</script>
<style rel="stylesheet" type="text/css">
ul {
list-style: none;
padding: 0;
}
</style>
</head>
<body ng-app="app">
<ul>
<li ng-repeat="item in log">{{item.time | date:'HH:mm:ss'}}: {{item.text}}</li>
</ul>
</body>
</html>
try this one
$http.post('', jQuery.param(user), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).complete(function(data, status, headers, config) {
//...
});

Why might Google Fusion Tables sqlGet queries stop working after Google+ sign-in through gapi.auth?

I'm writing a page that uses OAuth 2.0 via gapi.auth.authorize to authenticate a Google+ user and gapi.client.request to run Google Fusion Tables sqlGet queries. I find that my queries run fine before authentication, but then fail with a 403 "Insufficient Permission" error when run more than 30 seconds
after authentication.
The problem is demonstrated with this page:
https://googledrive.com/host/0B5Urq1jZb1MYSWloU3NTY2M4Qnc/test3b.htm
Please follow these steps:
Click "Query" to run a gapi.client.request Google Fusion Table SQL-get query returning a count of rows. This will run successfully until OAuth is used in steps 2 and 3.
Click "Start OAuth" to run an immediate:true authorization against Google+. If you are currently signed into Google+, your user name and ID will be displayed in the third button.
If your Google+ user name is not displayed in the third button, click the button ("Authorize") and sign into Google+.
Click the "Query" button again. The query will run without error when pressed within about 30 seconds of OAuth authorization. After that, the query fails with a 403 error. WHY?
Here is the source for the demo page:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Test3b</title>
<style type="text/css">
</style>
<script src="scripts/jquery-1.10.2.min.js" type="text/javascript"></script>
<script type="text/javascript">
var g_domIsReady = false;
var g_gapiIsReady = false;
$(function () {
log("#$(function())");
g_domIsReady = true;
start();
});
function gapiIsReady() {
log("#gapiIsReady");
g_gapiIsReady = true;
start();
}
function start() {
// Make sure both the gapi.client and the DOM (per jquery) are ready.
if (!(g_gapiIsReady && g_domIsReady)) return;
// Define members.
log("#start - gapi and DOM are ready");
var m_apiKey = "AIzaSyAvb0NHQMwyPbMJRtz2zRL4wTiVjZDiois"; // Points to Google account (including Google Drive) at paloalto#geodesy.net.
var m_clientId = "868768273487-q295tdfr54uvo98v74891qakcr9ci0pf.apps.googleusercontent.com";
var m_scopes = "https://www.googleapis.com/auth/plus.me";
// Wire buttons.
var queryButton = document.getElementById('query-button');
queryButton.onclick = function () { runGetRequest(); return false; };
var startOAuthButton = document.getElementById('startOAuth-button');
startOAuthButton.onclick = function () { startOAuth(); return false; };
// Set-up the gapi.
gapi.client.setApiKey(m_apiKey);
//----------------------------------------------------------------------------
// gapi.client.request query functions.
//----------------------------------------------------------------------------
function runGetRequest() {
log("#runGetRequest");
var tableId = "1VZgvKyuh9uHXkQawpxg1MU8AlO8Mngl-sx7SP74"; // TR_TREE_E
var sql = "select count(GID) from " + tableId + " where GID > 50000";
var path = "/fusiontables/v1/query";
var restRequest = gapi.client.request({
path: path,
params: { 'sql': sql }
});
restRequest.execute(jsonCallback);
}
function jsonCallback(json) {
log("#jsonCallback");
var output = JSON.stringify(json);
log(output);
alert(output);
}
//----------------------------------------------------------------------------
// OAuth functions.
//----------------------------------------------------------------------------
function startOAuth() {
log("#startOAuth");
var authorizeButton = document.getElementById('authorize-button');
window.setTimeout(checkAuth, 1); // check auth in 1 ms
function checkAuth() {
log("#checkAuth");
gapi.auth.authorize({
client_id: m_clientId,
scope: m_scopes,
immediate: true
}, handleAuthResult);
}
function handleAuthResult(authResult) {
log("#handleAuthResult");
if (authResult && !authResult.error) {
log("#handleAuthResult - authResult=true");
log(authResult); // authResult is a token (with 3600 second expiration).
authorizeButton.disabled = true;
useAuthResults();
} else {
log("#handleAuthResult - authResult=false");
authorizeButton.disabled = false;
authorizeButton.onclick = handleAuthClick;
}
}
function handleAuthClick() {
log("#handleAuthClick");
gapi.auth.authorize({
client_id: m_clientId,
scope: m_scopes,
immediate: false
}, handleAuthResult);
return false;
}
function useAuthResults() {
log("#useAuthResults");
// Get the Google+ user's ID and name (member info).
gapi.client.load('plus', 'v1', function () {
log("#gapi.client.load callback");
var request = gapi.client.plus.people.get({ 'userId': 'me' });
request.execute(function (aInfo) {
log("#request.execute callback");
if (aInfo.code !== undefined) {
alert('Google+ API returned ' + aInfo.code + ': ' + aInfo.message);
} else {
// Here with successful sign-in. Display the user name.
log('Google+ user id, name: ' + aInfo.id + ', ' + aInfo.displayName);
authorizeButton.value = aInfo.displayName + " +" + aInfo.id;
}
});
});
}
}
}
function log(msg) {
if (console) console.log(msg);
}
</script>
<script src="https://apis.google.com/js/client.js?onload=gapiIsReady" type="text/javascript"></script>
</head>
<body>
<h1>Test3a</h1>
<p>This pages demonstrates a problem I am having using gapi.client.request with gapi.auth.</p>
<input type="button" id="query-button" value="Query"><br>
<input type="button" id="startOAuth-button" value="Start OAuth"><br>
<input type="button" id="authorize-button" value="Authorize"><br>
<p>Steps...</p>
<p>1. Click "Query" to run a gapi.client.request Google Fusion Table SQL-get query returning
a count of rows. This will run successfully until OAuth is used in steps 2 and 3.</p>
<p>2. Click "Start OAuth" to run an immediate:true authorization against Google+. If you
are currently signed into Google+, your user name will be displayed in the third button.</p>
<p>3. If your Google+ user name is not displayed in the third button, press it ("Authorize")
and sign into Google+.</p>
<p>4. Click the "Query" button again.
The query will run without error when pressed within about 30 seconds of OAuth authorization.
After that, the query fails with a 403 error. WHY?</p>
</body>
</html>
Please note that I intend to use the Google+ sign-in to track page usage specifics by user, not to enable the Fusion Tables queries.
I'm new to OAuth and gapi.client.request so this may be a simple misunderstanding on my part.
Thanks for any insights.
I don't have all the answers for you, but here I think are some that may help:
Before you have the user sign-in with G+, the gapi.client.request object is adding a "key=yourAPIKey" parameter to each request.
After you have the user sign-in with G+, the gapi.client.request object is adding a "key=yourAPIKey" parameter to each request and is sending an "Authorization: Bearer ya.xxxxxx" header with each request, representing an access token for the user that is logged in.
I think the reason you're seeing a 403 is because the access token is being sent to the server, but the token does not include a scope authorizating access to FusionTables data. When no access token is sent - this validation is not performed.
If you actually wanted to access data that the user owned, then you need to have the user consent to give your application access to their data by including an appropriate scope in your gapi.auth.authorize call (e.g. "https://www.googleapis.com/auth/fusiontables").
However since I don't think you are trying to access data on behalf of a specific user, what I think you really want to do is prevent the "Authorization" header being sent at all during your call to Fusion Table API.
I can't see an easy way to prevent the gapi.client.request library from sending that header when the user is logged in, so an alternate solution might be to instead create an HTTP object not using the gapi.client.request library (e.g. use XMLHttpRequest directly) - and manually include the "key=yourAPIKey" in each request.
(What I can't explain is why you're seeing 30 seconds of differing behavior...)

socketio client: How to handle socketio server down

I've got a socketio server/client working well together, however I want to start writing events for when the server is offline on page load or during normal run.
I'm including the remote socket.io code in my header:
<script src="<?=NODE_HOST?>/socket.io/socket.io.js"></script>
<script>
var nodeHost = '<?=NODE_HOST?>';
</script>
And in my client controller I have
if(typeof io != 'undefined')
this.socket = io.connect(this.settings.server);
else
this.handleDisconnect();
The function I have to attempt to re-connect over and over if a) A socket disconnect occurs during normal operation, or b) the server is down on page load
botController.prototype.handleDisconnect = function() {
$.getScript(nodeHost+"/socket.io/socket.io.js").done(function(script, textStatus) {
bot.control.socket = io.connect(bot.control.settings.server);
}).fail(function(jqxhr, settings, exception) {
setTimeout(function() {
bot.control.handleDisconnect();
}, 5000);
});
}
Am I going about this the correct way?
The main issue I have right now (which made me create this question) is my code errors on page load when the server is down because I have functions like:
socket.on(...
When socket doesn't yet exist. I could wrap those in a function and call it when I detect the global socket object exists on successful reconnection? Would it matter if that function that contains socket.on... is called multiple times (if the server goes down more than once during operation)?
OK I managed to come up with this solution that seems to work well using yepnope which I already had using Modernizr (it handles the cross domain issue for me too).
<script src="<?=NODE_HOST?>/socket.io/socket.io.js"></script>
<script>
var nodeHost = '<?=NODE_HOST?>';
</script>
// Attempt to connect to nodejs server
botController.prototype.start = function() {
// Is our nodejs server up yet?
if(typeof io != 'undefined') {
this.socket = io.connect(this.settings.server);
this.startSocketEvents();
} else {
this.handleDisconnect();
}
}
// Our connection to the server has been lost, we need to keep
// trying to get it back until we have it!
botController.prototype.handleDisconnect = function(destroySocketObject) {
if(destroySocketObject === undefined)
destroySocketObject = true;
// Destroy any cached io object before requesting the script again
if(destroySocketObject)
io = undefined;
yepnope.injectJs(nodeHost+"/socket.io/socket.io.js",
function(result) {
// Did it actually download the script OK?
if(typeof io != 'undefined') {
bot.control.socket = io.connect(bot.control.settings.server);
bot.control.startSocketEvents();
} else {
setTimeout(function() {
bot.control.handleDisconnect(false);
}, 5000);
}
}
);
Where startSocketEvents() function contains all of my socket.on events

Resources