Javascript Closure and WebRTC callback issues - javascript

I'm running into this problem where I'm creating a closure and stepping through with the debugger, the variable connectingClientId is set correctly within the closure callback (localOfferCreated). When the callback is called by createOffer the connectedClientId is undefined. How could this be the circumstance? Been banging my head against the wall all night on this one.
function publishStream(handShakeInitiator, connectingClientId) {
var localOfferCreated = offerClosure(connectingClientId);
var localIceCandidate = iceClosure(connectingClientId);
peerConnections[connectingClientId] = new RTCPeerConnection(peerConnectionConfig);
peerConnections[connectingClientId].onicecandidate = localIceCandidate;
peerConnections[connectingClientId].addStream(localStream);
if (handShakeInitiator) {
peerConnections[connectingClientId].createOffer(localOfferCreated, createOfferError, offerOptions);
}
}
function offerClosure(id) {
var connectingClientId = id;
function offerCreated(description) {
peerConnections[connectingClientId].setLocalDescription(description, function (connectingClientId) {
webSocket.send(JSON.stringify({
'control': signalConstants.sendToClient,
'cid': connectingClientId,
'sdp': description
}));
}, function () {
console.log('Error setting description.');
});
};
return offerCreated;
}
Note these from the debugger:
connectingClientId is set -
connectingClientId is unset upon call -
What am I missing here?

From RTCPeerConnection.setLocalDescription
successCallback
Is a Function without parameter which will be called
when the description has been successfully set. At this point, one can
send the offer to a remote server that can forward it to a remote
client
You are redefining connectingClientID by having it as an inner function parameter. Remember that a named function argument is an implicit variable declaration, and as what docs said, it'll be undefined as the success callback don't give any parameters. JavaScript functions have access to their outer scope, so your anonymous function does not need this arg to be passed, it can simply refer to it creating a closure.
function offerCreated(description) {
peerConnections[connectingClientId].setLocalDescription(description, function() {
webSocket.send(JSON.stringify({
control: signalConstants.sendToClient,
cid: connectingClientId,
sdp: description
}));
}, function () {
console.log('Error setting description.');
});
};

Related

JS: Rebound "this" in contextless function call

The function doSomethingElse in this example fails to execute since its this has been rebound to window or global (if in Node) due to a contextless call inside app.populateDatabase.
Is there any way to avoid this without referencing app inside every function?
loadDatabase function executes a callback according to a logic statement, if an imaginary database didn't exist, it populates it after loading, then the populateDatabase executes the callback it has been provided.
I cannot rebind the onLoaded argument to app since I don't know where it comes from, and the bind/apply/call abstraction overuse creates quite a mess.
var app = {};
app.loadDatabase = function(onLoaded) {
// If database already exists, only run a callback
var callback = onLoaded;
// If database doesn't exists, populate it, then run a callback.
if (!databaseExists) {
callback = this.populateDatabase.bind(this, onLoaded);
}
this.database = new sqlite.Database("file.db", function(error) {
if (error) { ... }
callback();
})
}
app.populateDatabase = function(onPopulated) {
// Contextless call here. <--------
onPopulated();
}
app.doSomethingElse = function() {
// this != app due to contextless call.
this.somethingElse();
}
app.run = function() {
// Load the database, then do something else.
this.loadDatabase(this.doSomethingElse);
}
app.run();
Just replace this.loadDatabase(this.doSomethingElse);
with this.loadDatabase(() => this.doSomethingElse());. This way you create a new arrow function but then doSomethingElse is called with the right this context.
You could also do .bind but I recommend the arrow function. Here with bind: this.loadDatabase(this.doSomethingElse.bind(this))
In general consider to move to promises & maybe async functions. Then do this:
this.loadDatabase().then(() => this.doSomethingElse());
or better with an async function:
await this.loadDatabase();
this.doSomethingElse();

JS - Calling function from function not working

I have a function that gets called, and in it, I call another function called:
updatePerson(name)
For some reason it never activates when the function below is called. Everything else in the function works.
function updateName(name) {
$.get('XXX',function (data) {
var results = $.parseJSON(data);
var matchName = String(results.data[0].first_name);
updatePerson(matchName);}
);
};
Has anyone got an idea what I am doing wrong?
If I run alert(matchName) I get Nick as a response.
If I run console.log(updateMap(matchAddress)) I get undefined
It could do with the fact that you're passing a parameter from a callback function. In Javascript, variables inside a Callback are not available outside the callback.
Try setting the value of String(results.data[0].first_name) to a variable declared outside of the updateName function (i.e a global variable) and then call the updatePerson function outside of update name, with the globally declared variable as a parameter. Like so
var globalMatchName = '';
function updateName(name) {
$.get('XXX',function (data) {
var results = $.parseJSON(data);
globalMatchName =String(results.data[0].first_name);
}
);
updatePerson(globalMatchName)
}

How do I pass a parameter to this Google ReCaptcha function?

I am having an issue getting the count parameter to pass into the verifyCallback in the following piece of code:
var CaptchaCallback = function () {
console.log('CaptchaCallback run');
var count = 0;
$('.g-recaptcha').each(function (index, el) {
grecaptcha.render(el, {
'sitekey': 'xxx',
'callback': verifyCallback(count)
});
count++;
});
};
If I remove the parameter everything works as it should, only the parameter can't be passed for an essential part of the function.
If I add the parameter the function runs straight away without waiting for the ReCaptcha to be verified.
I want to be able to pass the parameter and then have the function run when the ReCaptcha is verified.
Here is the function that the parameter is passed to if it helps:
function verifyCallback(formNumber) {
//var formNumber = 1;
console.log('verifyCallback');
$('#submitBtn_' + formNumber).prop('disabled', false);
console.log('#submitBtn_' + formNumber);
}
Edit: When I use the parameter it doesn't bring the count through, it brings back the response from Google...
Thank-you
The issue is because you're calling the verifyCallback function immediately and assigning the returned value of that function to the callback property.
To fix this, wrap the function call in an anonymous function which is then provided as a reference to the callback. Also note that you can use the index value from the each() handler instead of manually maintaining the count variable. Using this method will also mean that you don't need to use a closure to keep the count value in scope of the current iteration. Try this:
var CaptchaCallback = function () {
console.log('CaptchaCallback run');
$('.g-recaptcha').each(function (index, el) {
grecaptcha.render(el, {
sitekey: 'xxx',
callback: function() {
verifyCallback(index)
});
});
count++;
});
};

Globally defined variable not being found

I have declared a global variable, var linkArray=[], but it is not being picked up inside of a phantomJS function. The error message is: phantom stdout: ReferenceError: Can't find variable: linkArray. How can I make this be found? I've tried declaring it with window.linkArray, but since this is a headless application, I then get a different error, ReferenceError: window is not defined.
Thus, I need a way to make var linkArray=[] global.
var phantom = require('phantom');
var linkArray=[];
phantom.create(function (ph) {
ph.createPage(function (page) {
var main_file="file:///C:/whatever/index.html";
page.open(main_file, function (status) {
console.log("opened " + main_file +"\n",status+"\n");
page.evaluate(function () {
for (var i=0; i < document.getElementsByTagName('a').length; i++) {
linkArray.push(document.getElementsByTagName('a')[i].href)
}
return linkArray;
}
, function (result) {
console.log(result)
ph.exit();
});
});
});
}, {
dnodeOpts: {
weak: false
}
});
PhantomJS has a page context and outer context. The page context is sandboxed, so you need to explicitly pass the variable into it. It is passed by value. The docs say:
Evaluates the given function in the context of the web page. The execution is sandboxed, the web page has no access to the phantom object and it can't probe its own setting.
But also note the note.
Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine.
Closures, functions, DOM nodes, etc. will not work!
To solve this, the outer variable has to be passed into the page context (evaluate) and returned
page.evaluate(function(linkArray) {
// page context, linkArray is a local variable now
for (var i=0; i < document.getElementsByTagName('a').length; i++) {
linkArray.push(document.getElementsByTagName('a')[i].href)
}
return linkArray;
}, function finished(result) {
// outer context
console.log(result)
linkArray = result;
ph.exit();
}, linkArray); // pass values for the page context as last parameters

Immediate object initialization confusion

I am working on a js file that makes use of JScroll. The callback for the jsp-scroll-y event is defined in the following function
function initWall() {
//callback from jqueryscrollpane
Scroll_TimeLine_Instance = function (event, scrollPositionY, isAtTop, isAtBottom){
//get more content
if (isAtBottom) {
GetMoreDate(objid, vwrsid, secid, orgid, incflwr, qty, mintlid, maxtlid, successGetTimeLineCallback, failureGetTimeLineCallback);
}
}();
}
Another function is defined that then binds this callback to the jsScroll
function reapplyScroll() {
Utilities.DestroyScrollBar($(_target).closest('.widgetBody'));
Utilities.ApplyScrollBar($(_target).closest('.widgetBody'), false, Scroll_TimeLine_Instance);
}
Utilities.ApplyScrollBar = function (element, showScrollBar, scrollCallback) {
$(element).jScrollPane({
horizontalGutter: 5,
verticalGutter: 5,
'showArrows': false
}).bind('jsp-scroll-y', scrollCallback);
if (!showScrollBar) {
$(element).find('.jspDrag').hide();
}
}
The callback was never called, and I found this was because it was undefined. If I remove the Immediate object initialization (); from after the creation of the function everything works fine.
Can anyone explain this? I don't understand why it was being called immediate anyway, so i assume this is an error on the part of whoever created it, and I have no idea why it would cause this variable to be undefined?
It is undefined because the function (that is immediately called) does not return any value
So it seems indeed that this is a bug of the library..
Either remove the (); at the end, or if you want to call it right there as well just invoke it in the following line
function initWall() {
//callback from jqueryscrollpane
Scroll_TimeLine_Instance = function (event, scrollPositionY, isAtTop, isAtBottom){
//get more content
if (isAtBottom) {
GetMoreDate(objid, vwrsid, secid, orgid, incflwr, qty, mintlid, maxtlid, successGetTimeLineCallback, failureGetTimeLineCallback);
}
}; /// this is assigning the function to our variable
Scroll_TimeLine_Instance (); // this is the invokation
}

Categories