I'm using the Twilio JavaScript SDK to place and receive calls in the browser. As part of this I have a requirement to make and receive calls in a new popup window. This is so that a user can continue browsing the site without disconnecting the call.
I have got this working for outgoing calls (a user clicks a number, and on the back of this I call window.open which initiates the call).
However for incoming calls, I'm attempting to do the following in the initiating browser window:
Twilio.Device.incoming(function (connection) {
$('#callPopup').show();
$('.js-answer-call').on('click', function (event) {
event.preventDefault();
var receiveCallWindow = window.open('/call/incoming', '', 'width=350,height=200');
receiveCallWindow.connection = connection;
$('#callPopup').hide();
});
$('.js-reject-call').on('click', function (event) {
event.preventDefault();
connection.reject();
$('#callPopup').hide();
});
});
This passes the connection object to the popup window, and then the popup window runs the following code:
var connection = window.connection;
$(document).ready(function () {
connection.accept();
});
This does answer the call, however the call context is still within the parent window and if a user navigates away from that page it will end the call.
I understand that I can achieve this via a master iframe containing just the call logic, with the main web app inside the frame, however I think that's a very messy implementation and want to avoid that.
Twilio developer evangelist here.
Your problem in this case is indeed that the call context is in the parent window. The only thing that comes to mind to me right now is to popup the call window when your user logs on and is prepared to answer calls and then initialise the Twilio.Device in the popup window.
You could still connect this popup window to the code for outbound calls too.
Does this help at all?
Related
Information
I'm trying to implement OAuth with Twitter in a popup window, same implementation works for Google and Discord, currently what I am doing is, in a function named openOAuthWindow I am creating a new window popup with OAuth URL, then creating a Promise and saving resolve and reject functions in window object. After that, in the child window when the service redirects to my redirect_uri, my page calls saved resolve or reject in parent window (by reference from window.opener) depending on OAuth result and closes OAuth window. After child is closed I'm clearing resolve and reject functions from parent window and the Promise is correspondingly resolved or rejected. Also I've set an interval with setInterval which checks if oAuthWindow.closed === true (which indicates if the window has been closed). In case of oAuthWindow.closed === true and the Promise is not fulfilled that indicates that user closed the OAuth window manually and an OAuthError is returned as the result of Promise.
The problem
As I've mentioned earlier, everything is fine with Google and Discord, the problem is with Twitter. When the Twitter OAuth page is fully loaded oAuthWindow.closed returns true (while the window is still opened), and my logic fails (because of the error that I'm throwing indicating that user closed the OAuth window manually). Also when I go to DevTools in oAuthWindow, the window.opener property equals to null. So my reference to the parent window is also lost.
The actual questions
Is there an alternative way to check if window is opened or closed?
(I've tried to find some solution, but couldn't find one)
Are there any alternative solutions to implement OAuth in popup
window? (Any offers are appreciated)
Also are there any security drawbacks to avoid using OAuth 2 in
popup? (I've done research on this, I've done investigation on OAuth
security, the only security problem I have found could have been the
access to child window from website, but that link is not possible
due to same origin policy)
public static async openOAuthWindow(url: string, windowFeatures: string = "width=500,height=600,popup=true"): Promise<CodeAndState> {
if(!windowFeatures.includes("noopener")) {
const oAuthWindow: Window = window.open(url, "_blank", windowFeatures)!;
let fulfilled = false;
const removePromiseCallbacksAndFullfill = () => {
delete window.returnAuthCode;
delete window.returnOAuthError;
fulfilled = true;
};
let resultPromise: Promise<CodeAndState> = new Promise<CodeAndState>((resolve, reject) => {
window.returnAuthCode = resolve;
window.returnOAuthError = reject;
}).then((codeAndState: CodeAndState) => {
removePromiseCallbacksAndFullfill();
return codeAndState;
}).catch((error) => {
removePromiseCallbacksAndFullfill();
return Promise.reject(error);
});
let windowAliveInterval = setInterval(() => {
if (oAuthWindow.closed) {
clearInterval(windowAliveInterval);
if(!fulfilled) {
const error = new OAuthError("User closed the window", ERROR_STATUSES.USER_CLOSED_OAUTH_WINDOW);
window.returnOAuthError!(error);
}
}
}, 500);
return resultPromise;
}
throw new Error("OAuth window sends code to the parent window, so it must be opened without 'noopener' feature");
}
Thank you for your efforts, any answer/comment is appreciated!
I recently had this problem as well, where opening the Google OAuth prompt in a popup window was incorrectly considered "closed".
One solution you can consider looking at is to use a broadcast channel: When the parent window opens the oauth popup, also open a broadcast channel. After the OAuth login, redirect to a page that sends the parent page a message before the popup window closes itself.
If the parent window does not receive any messages on the broadcast channel then that indicates the login was closed before it succeeded.
I have an application where a resource is created when the user enters that page. If they do not properly save that resource an API call is called to the backend to delete it. Closing the browser before saving is one scenario.
Here I use WindowEventHandlers.onbeforeunload to listen for a close event and fire a function before the window closes.
$window.onbeforeunload = function (evt) {
if (notSavedFlag == true) {
//call an api function
$http.delete(...)
...
}
}
However, the API call does not fire when closing the browser window (and I put a breakpoint in the backend to check if it fires)
What is wrong with my code?
I need to call a JavaScript/jQuery function which has a few lines of code in it, on a PHP page when the user closes his window/tab or navigates away by clicking a link. I've tried the onbeforeunload function but only the return "blah blah blah;" part executes and everything else is ignored. I've also tried the .unload method from jQuery but for some reason this code doesn't run.
$(window).unload(function() {
alert('blah blah blah');
});
Please suggest alternatives. Thanks..
Here is a simple working example. Whatever you return from the unload callback will be displayed in a browser popup confirmation.
Working example sending Ajax request before unload
http://jsfiddle.net/alexflav23/hujQs/7/
The easiest way to do this:
window.onbeforeunload = function(event) {
// do stuff here
return "you have unsaved changes. Are you sure you want to navigate away?";
};
in jQuery:
$(window).on("beforeunload", function() {
$.ajax("someURL", {
async: false,
data: "test",
success: function(event) {
console.log("Ajax request executed");
}
});
return "This is a jQuery version";
});
Look into the Network tab of the browser. You will see how the request is being sent as you wanted to do. Just send the appropriate data.
Bear in mind all operations triggered must be synchronous, so you can only make synchronous ajax requests for instance. However, the above is not entirely reliable for any purpose.
Opt for periodic back-up of user data to localStorage and sync with the server automatically . Keep window.onbeforeunload just as an extra precaution, but not as a main mechanism. It's well known to cause problems.
This is an old question, but I wanted to share an alternative approach that has the benefit of working with high consistency:
Establish a WebSocket connection to the server, and when the client navigates away the WebSocket connection will be closed. Server-side, you can detect the closed connection in a callback and run whatever code you need on the server.
Executing Javascript on page unload is often unreliable (as discussed in the other answer) because it's inherently at odds with the user's intention. This method will always work, although it is admittedly quite a bit more cumbersome to implement.
This does change the context of your "run before leaving" code from client-side to server-side, but I imagine for most cases the difference is inconsequential. Anything you want to run client-side before the client leaves your page is probably not going to change anything the client sees, so it's probably fine to run it server side. If there is specific data you need from the client you can send it through the WebSocket to the server.
The only situation I can think of off the top of my head where this might cause unexpected behavior is if the user loses the WS connection without actually navigating away, e.g. they lose internet or put their computer to sleep. Whether or not that's a big deal is probably highly dependent on what kind of code you're trying to execute.
In many projects of mine, the mentioned methods here are instable. The only thing that works for me is to bind the event as original attribute on the body element.
<body onunload="my_function_unload()">
jQuery method:
$('body').attr('onunload', 'my_function_unload()');
From an iframe:
<body onunload="window.parent.my_function_unload()">
jQuery method:
$('<iframe />').load(function(){
$body = $(this).contents().find('body');
$body.attr('onunload', 'window.parent.my_function_unload()');
}
Also, important, no arguments in the attribute, and the function must be in the global window scope, otherwise nothing happens.
For example, common mistake If your my_function_unload() are wrapped inside a ;( function( $ ) {... OR $(document).ready(function(){... AS my_function_unload() must be outside that private scope. And dont forget to use jQuery instead of $ prefix then. (Working with Wordpress for example)
This is kind of a pain, as Chrome, at least in Version 92.0.4515.131, seems to be clamping the security screws on what you can get away with in beforeunload. I'm unable to make a synchronous ajax request, for example.
If there's any chance the user will be back to your site and you can wait until then to deal with their having closed a window (which does work in my use case), know that setting cookies does currently seem to be fair game during the beforeunload event. Even works when I close the browser. Covers most anything but power cycling the computer, it appears.
Here's a reference example (with getCookie stolen from this SO question):
function setCookie(name, value) {
document.cookie =
'{0}={1};expires=Fri, 31 Dec 9999 23:59:59 GMT;path=/;SameSite=Lax'
.replace("{0}", name)
.replace("{1}", value);
}
// https://stackoverflow.com/a/25490531/1028230
function getCookie(cookieName) {
var b = document.cookie.match('(^|;)\\s*' + cookieName + '\\s*=\\s*([^;]+)');
return b ? b.pop() : '';
}
window.addEventListener('beforeunload', function (e) {
console.log('cookie value before reset: ' + getCookie('whenItHappened'));
var now = +new Date();
console.log("value to be set: " + now);
setCookie('whenItHappened', now);
return "some string if you want the 'are you sure you want to leave' dialog to appear";
});
I'm trying to execute the following code
window.onunload = function () {
var sTag = document.createElement('script');
sTag.src = 'mysrc';
document.getElementsByTagName('head')[0].appendChild(sTag);
return false; };
}
Itseems to work fine in FF but in chrome I'm getting the download status as cancelled as soon as the unload event is fired. I saw some posts in SO with ajax solutions but I'm executing this script inside a cross domain iframe. Im just trying to log the time for which my api was live in a page per visitor. So, I'm sending some time log information on unload of the page. Is there any work around for the above?
For the purpose of what you described, you can have that script loaded in your page or parent window (you are saying it is an iframe right?) and run a function on window.unload:
window.onunload = function(){
window.top.logtime(); // if it is in the parent, or
window.logtime() //if it is in the same window
};
and don't return false, unload event cannot be cancelled. (in best cases, the user gets an alert dialog that will override the return false statement.)
I think what makes this different is how fast it carries out a new function, before the body gets unloaded. Manipulating the DOM is definitely much slower than making a call.
My javascript code is added to random websites. I would like to be able to report to my server when a (specific) link/button on the a website is clicked. However I want to do it without any possible interruption to the website execution under any circumstances (such as error in my code, or my server id down etc.). In other words I want the site to do its default action regardless of my code.
The simple way to do it is adding event listener to the click event, calling the server synchronously to make sure the call is registered and then to execute the click. But I don't want my site and code to be able to cause the click not to complete.
Any other ideas on how to do that?
As long as you don't return false; inside your callback and your AJAX is asynchronous I don't think you'll have any problems with your links not working.
$("a.track").mousedown(function(){ $.post("/tools/track.php") })
I would also suggest you encapsulating this whole logyc inside a try{} catch() block so that any errors encauntered will not prevent the normal click behaviour to continue.
Perhaps something like this? I haven't tested it so it may contain some typo's but the idea is the same...
<script type="text/javascript">
function mylinkwasclicked(id){
try{
//this function is called asynchronously
setTimeOut('handlingfunctionname('+id+');',10);
}catch(e){
//on whatever error occured nothing was interrupted
}
//always return true to allow the execution of the link
return true;
}
</script>
then your link could look like this:
<a id="linkidentifier" href="somelink.html" onclick="mylinkwasclicked(5)" >click me!</a>
or you could add the onclick dynamically:
<script type="text/javascript">
var link = document.getElementById('linkidentifier');
link.onclick=function(){mylinkwasclicked(5);};
</script>
Attach this function:
(new Image()).src = 'http://example.com/track?url=' + escape(this.href)
+ '&' + Math.random();
It is asynchronous (the 'pseudo image' is loaded in the background)
It can be cross domain (unlike ajax)
It uses the most basic Javascript functionalities
It can, however, miss some clicks, due to site unloading before the image request is done.
The click should be processed normally.
1) If your javascript code has an error, the page might show an error icon in the status bar but it will continue the processing, it won't hang.
2) If your ajax request is asynchronous, the page will make that request and process the click simultaneously. If your server was down and the ajax request happening in the background timed out, it won't cause the click event to not get processed.
If you do the request to your server synchronously, you'll block the execution of the original event handler until the response is received, if you do it asynchronously, the original behaviour of the link or button may be doing a form post or changing the url of the document, which will interrupt your asynchronous request.
Delay the page exit just long enough to ping your server url
function link_clicked(el)
{
try {
var img = new Image();
img.src = 'http://you?url=' + escape(el.href) + '&rand=' + math.random();
window.onbeforeunload = wait;
}catch(e){}
return true;
}
function wait()
{
for (var a=0; a<100000000; a++){}
// do not return anything or a message window will appear
}
so what we've done is add a small delay to the page exit to give the outbound ping enough time to register. You could also just call wait() in the click handler but that would add an unnecessary delay to links that don't exit the page. Set the delay to whatever gives good results without slowing down the user noticeably. Anything more than a second would be rude but a second is a long time for a request roundtrip that returns no data. Just make sure your server doesn't need any longer to process the request or simply dump to a log and process that later.