Avoid popup blocker when opening new window that requires an async lookup - javascript

I've got a button in my Meteor application that does the following:
user clicks button > event calls method > method calls external api using http > external api returns single sign on link > method returns link > event opens new window (tab) with link as url
My problem is that the new tab is being blocked by a popup blocker even though it is based on user action
Here's the event code:
Template.welcome.events({
'click #accessLms': function(e) {
e.preventDefault();
​
var submitButton = $('#accessLms').button('loading');
​
Meteor.call('getLmsLink', function(error, portalLink) {
if(error) {
sAlert.error(error.message);
submitButton.button('reset');
} else if(portalLink) {
window.open(
portalLink,
'_blank'
);
submitButton.button('reset');
}
});
}
});
Here is the method:
Meteor.methods({
'getLmsLink': function () {
[set vars...]
try {
var response = HTTP.call( verb, wceaApiAddress + endPoint, {
headers: {
"Request-Time": timeStamp,
"Api-Key": key,
"Signature": hash
}
});
} catch(error) {
throw new Meteor.Error(501, 'There was a problem getting a link to the E-Learning Portal');
}
​
var result = JSON.parse(response.content);
var portalLink = result.records.accessLink;
return portalLink;
}
});

Basic approach:
On the click event in your app open a new window with a specific url to your own app
Include a route parameter that can be used in the new window, for example /redirect/token/
In the Template.onCreated event of the template used in that route, perform the method call and get the url and auth token to the 3rd party site.
Finally just set location = newSiteHref in that same code (in the new window) and redirect the user

Related

Cypress: Stub open window

in my app there is an recommendations list, which on click opens a new window with a dynamic address:
$window.open(_shopURL, '_blank');
Now I'm trying to stub the windows.open event as shown in https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/stubbing-spying__window/cypress/integration/window-stubbing.spec.js
Cypress.on('window:before:load', (win) => {
win.open = cy.stub().as('windowOpen')
})
describe('Shop integration', () => {
beforeEach(function () {
cy.visitHome(countryCode, resellerId)
})
it('can stub the window open event', function () {
cy.get(`.recommendations-list .recommendations-cover:nth-of-type(1)`)
.click()
cy.get('#windowOpen').should('be.calledWith', 'page1.html')
})
But it's always opening the new tab and the logs are wrong:
Cypress: stub open window
Does anybody has an idea why it's not working?
Cheers!
Code below will help you to stub window.open and further assert it that function has been triggered:
it('opens the about page', () => {
cy.visit('/')
cy.window().then(win => {
cy.stub(win, 'open').as('Open')
})
cy.get('.your-selector').click()
cy.get('#Open').should('have.been.calledOnceWithExactly', yourUrl)
})
You also can stub window.open in cy.on hook as you did, what helps you to yield new window object each time after page reload. However, if you want to actually open the new Url in existing tab instead of new one you can use this code below by passing "_self" param to overwrite old "_blank":
cy.window().then(win => {
cy.stub(win, 'open').callsFake((url) => {
return win.open.wrappedMethod.call(win, url, '_self');
}).as('Open');
});
callsFake function dynamically withdraws url which has been placed into original window.open(url, "_blank"), or you can manually change url inside .call(win, url, '_self'); with static one, so regardless on which link or button you clicked, which triggers window.open, they all will open the same url.
I'm using page-objects for every page I want to test. So in my parent page-object which gets inherited by every other PO I do the following when opening a url:
public navigateTo(url: string, defaultTimeout: number = 5000) {
return cy.visit(url, {
onBeforeLoad: (win: any) => {
cy.stub(win, 'open');
},
timeout: defaultTimeOut
});
}
This prevents window to open a new page.
You also can use this easy way:
const newUrl = 'your url';
cy.window().then((win) => {
cy.stub(win, 'open').callsFake(url => {
newUrl = url
}).as('windowOpen')
})
cy.get('your path').click()
cy.get('#windowOpen').should('be.called')
cy.visit(newUrl)

Popstate event not triggered after pushing twice to history using history.pushState()

I’m working on an eshop where items are opened on top of a page in iframes. I’m using
history.pushState(stateObj, "page 2", http://localhost:8888/product-category/tyger/vara-tyger/?view=product&item=test-4);
in order to let customers copy the current url and use it to go to the current page with the item opened in an iframe. In addition, I’m using
window.addEventListener('popstate', manageHistory);
function manageHistory(event) {
if (!has_gone_back) {
var iframeOpen = false;
has_gone_back = true;
}
else {
var iframeOpen = true;
has_gone_back = false;
}
}
in order to let customers use their browser’s back and forward buttons for navigation (closing and opening the iframe).
However, when opening one product (calling history.pushState once), using the browser’s back button, and opening another product (calling history.pushState again), and going back again, manageHistory() is not called. The customer is taken to the first opened product but if pressing back again, manageHistory() is called.
I want manageHistory() to be called when pressing back on the product page opened second in order to add code to redirect customers to the category's start page when pressing back.
I’ve tried both adding Event Listeners for both opened products and also for only the first one. Any ideas what the problem may be?
From https://developer.mozilla.org/en-US/docs/Web/Events/popstate
Note that just calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event is only triggered by doing a browser action such as a click on the back button (or calling history.back() in JavaScript).
You can overwrite popState and replaceState, but what is generally a better idea is to create a wrapper which sets the url and then triggers your handler function.
Something like this...
function urlChangeHandler() {
var url = window.location.href;
// Whatever you want to do...
}
// Handle initial url:
urlChangeHandler();
window.addEventListener('popstate', urlChangeHandler);
var urlState = {
push: function(url) {
window.history.pushState(null, null, url);
urlChangeHandler();
},
replace: function(url) {
window.history.replaceState(null, null, url);
urlChangeHandler();
}
}
I have a similar file in one of my projects which updates the datastore based on the #hash...
import tree from './state'
// No need for react-router for such a simple application.
function hashChangeHandler(commit) {
return () => {
const hash = window.location.hash.substr(1);
const cursor = tree.select('activeContactIndex');
const createCursor = tree.select('createNewContact');
cursor.set(null);
createCursor.set(false);
(() => {
if(!hash.length) {
// Clean up the url (remove the hash if there is nothing after it):
window.history.replaceState(null, null, window.location.pathname);
return;
}
if(hash === 'new') {
createCursor.set(true);
return;
}
const index = parseInt(hash, 10);
if(!isNaN(index)) {
cursor.set(index);
}
})();
commit && tree.commit();
}
}
// Handle initial url:
hashChangeHandler(true)();
// Handle manual changes of the hash in the url:
window.addEventListener('hashchange', hashChangeHandler(true));
function createHash(location) {
return (location !== null) ? `#${location}` : window.location.pathname;
}
module.exports = {
push: (location, commit=true) => {
window.history.pushState(null, null, createHash(location));
hashChangeHandler(commit)();
},
replace: (location, commit=true) => {
window.history.replaceState(null, null, createHash(location));
hashChangeHandler(commit)();
}
}

How to get get URL of new page using casperJS

I am using casperJS to get links when clicking a button. The links are returned with window.open in javaScript.
The code I have written logs all the pages after clicking button, but phantom is not exiting in terminal window. Also some pages only show about:blank, especially the last ones.
var casper = require('casper').create();
var page = require('webpage').create();
var address = 'http://www.example.com';
page.open(address, function() {
page.onPageCreated = function(newPage) {
newPage.onClosing = function(closingPage) {
console.log('A child page is closing: ' + closingPage.url);
/* if i set phantom.exit() it will only log the first page url.
Problem: I need all page urls. */
}
}
page.evaluate(function() {
$(".button").click();
});
}
The method "getCurrentUrl()" will return the current url. Here is an easy (not really meaningful) example:
casper.test.begin('My Test', 1 , function suite(test) {
casper.start().viewport(1600,1000).thenOpen("https://blabla.de", function() {
var mylink = this.getElementInfo("#linkid").text;
this.clickLabel(mylink);
});
casper.waitForSelector("#page2", function() {
this.echo(this.getCurrentUrl());
});
casper.run(function() {
test.done();
});
});
You can get the URL of the current page in CasperJS using one of the following methods:
casper.getCurrentUrl():
Retrieves the current page URL. Note that the URL will be URL-decoded.
casper.page.url:
Accesses the existing PhantomJS WebPage instance (page), and gets the current URL of the web page.
casper.evaluate(function () { return location.href; }):
Evaluates the page URL in the Page DOM Environment.

OAuth Flow not working when using Promise

I am building a simple chrome extension which integrates with Twitter using OAuth. I have slightly modified the Chrome OAuth Tutorial to integrate with Twitter. The extension is build in Reactjs+Flux.
When the user clicks on "Sign in with Twitter" button, an Action signin is triggered, which is declared as follows:
signin: function(){
ChromeUtils.connecttotwitter().then(alert("Step After Then"));
AppDispatcher.dispatch({actionType:TweetSmartActions.SIGN_IN,signedInTwitterUserId: response.user_id});
},
The ChromeUtils.connecttotwitter() is defined as follows:
var ChromeUtils = {
connecttotwitter: function () {
return new Promise(function(fulfill,reject){
var request = {
type : "background.twitterRequestToken",
};
alert("Before sendMessage");
chrome.runtime.sendMessage(request, function(response) {
if (response)
{
fulfill(response);
}
else
{
reject(response);
}
});
});
},
And the event listener onMessage is defined in the background.js as:
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
console.log("background.js: " + JSON.stringify(request));
var type = request.type;
if (type == "background.twitterRequestToken")
{
oauth.authorize(function(token,secret,userId,screenname){
sendResponse({success:true,userId:userId,screenName:screenname});
});
alert("Alerting before returning true");
return true;
}
When I click on the "Sign In With Twitter" button, the authentication flow does start and a new page opens. However, after I introduced the Promise, the new page does not redirect to the twitter oauth page. In fact, to debug that I have put the following alert statements in chrome_ex_oauth.js:
ChromeExOAuth.prototype.initOAuthFlow = function(callback) {
if (!this.hasToken()) {
var params = ChromeExOAuth.getQueryStringParams();
if (params['chromeexoauthcallback'] == 'true') {
var oauth_token = params['oauth_token'];
var oauth_verifier = params['oauth_verifier']
this.getAccessToken(oauth_token, oauth_verifier, callback);
} else {
var request_params = {
'url_callback_param' : 'chromeexoauthcallback'
}
this.getRequestToken(function(url) {
alert("Url after get request token " + url);
window.location.href = url;
alert(window.location.href);
}, request_params);
}
Here, the url in the first alert is the twitter oauth url but the second alert gives the Chrome extension Url -
chrome-extension://kiekipigbdldhggmlohbnhofnjhcbmem/chrome_ex_oauth.html
Why did the url not get assigned to window.location.href?
Any ideas on what might be happening?
The issue was not because I was using a Promise, but because when using Flux, the Action was being Dispatched before the response from the Promise was received and this was causing the app to hang somehow
signin: function(){
ChromeUtils.connecttotwitter().then(alert("Step After Then"));
AppDispatcher.dispatch({actionType:TweetSmartActions.SIGN_IN,signedInTwitterUserId: response.user_id});
},
In the above, the AppDispatcher.dispatch should be called in the function which is invoked on then.

Angular: always get popup blocker on create new tab after async call

I have following scenario:
User tries to login with wrong password
on failure I ask him if he wants to reset password
if user click 'OK' (pure confirm dialog) , I'll open new tab with some URL.
My problem is: I always get popup blocker because I generate window only after error callback. Here is relevant code login method:
$scope.login = function () {
$auth.login({
email: $scope.fields.email,
password: $scope.fields.password
})
.then(function () {
// ... login success
})
.catch(function () {
// login failed (my case)
if (confirm('ERROR: Invalid password, forgot Password? Click \'OK\' if you want to reset it')){
var url_ = 'http://meeter.me/#/forgot-pass/snaggs#gmail.com';
var myPopup = window.open ('', '_blank');
myPopup.location = url_;
if (myPopup && myPopup.focus) {
myPopup.focus();
}
}// if
});
};
If I move var myPopup = window.open ('', '_blank'); to next line under $scope.login = function () it will work but it will open new empty tab.
I want to open new tab only when get error on login
I use satellizer
Please help,
Plunker Demo
In demo i use single $timeout to simulate async call
I don't think it is going to be possible to open a new window using window.open if it is invoked asynchronously. Without user invocation, the call stack is always going to identify that window.open is triggered asynchly and going to block it. Maybe an option to show a new button if login is failed and let that button open up a new window?
http://plnkr.co/edit/QGfnxH484OdTvaaOvMaE?p=preview
<button ng-click="init();">press me</button><br />
<span ng-show="resetPwd" >
Login Failed!! <button ng-click="reset()">Reset Password</button>
</span>
$scope.init = function() {
$timeout(function() {
$scope.resetPwd = true;
}, 2000);
}

Categories