How to use cookie obtained in BrowserWindow within webview - javascript

I have searched all available information on this and the documentation is not helping me.
I have an Electron app (React internals) where a web page is to be shown in a webview.
The domain e.g. https://root.domain.com stays the same but the rest of the URL will be different depending on props passed to the component showing the webview.
I found some code here linked from a Youtube video from BuildingXwithJS on testing which will use a BrowserWindow to allow a user login, then save the authenticated cookie for use within the app.
auth() {
const remote = electron.remote;
// Get a partitioned session to use with webview
const ses = remote.session.fromPartition("myPartition");
// create new electron browser window
const BrowserWindow = remote.BrowserWindow;
let win = new BrowserWindow({width: 800, height: 600});
// cleanup on close
win.on('closed', () => {
win = null;
});
// wait for page to finish loading
win.webContents.on('did-finish-load', () => {
// if auth was succesful
if (win.webContents.getURL() === 'https://root.domain.com/Home/') {
// get all cookies
win.webContents.session.cookies.get({}, async (error, cookies) => {
if (error) {
console.error('Error getting cookies:', error);
return;
}
// store cookies
cookies.forEach(c => {
if(c.domain.includes('root.domain.com')){
ses.cookies.set(c,(e) => e? console.log("Failed: %s",e.message):null})
}
// close window
win.close();
});
}
});
// Load login page
win.loadURL(loginURL);
}
Now when I view a page in webview in a separate component like this:
<webview
ref={a => (this.view = a)}
partition="myPartition"
src={`https://root.domain.com/${providedURL}`}
style={{
display: "flex",
width: "100%",
height: "100%"
}}
/>
I get the login page.
So I tried to following to set the cookies on the webview when it finishes loading, then reload:
this.view.addEventListener("did-finish-load", e => {
// Get partitioned session
const ses = remote.session.fromPartition("myPartition");
// Loop through cookies and add them to the webview
ses.cookies.get({}, (error, cookies) => {
cookies.forEach(cookie => {
// this.view doesn't return the webview in here so I use the event
e.target.getWebContents().session.cookies.set(cookie,(e)=> e? console.log(e):null);
});
// Reload with cookies
e.target.getWebContents().reload();
});
this.setState({ loading: false, failure: false });
});
But still I get login page every time!
The frustrating thing about this is that the auth() function defined above (in another wrapper component) shows a logged in screen every time it runs after the user logs in once so the login session is active and the cookie is stored somewhere - but where? And how can I make this webview see it? Would I be better with an Iframe ?? My requirement to show an external url inline is a must have for my stakeholders so I need some way to make it work.
I am far from proficient in cookie management and the session management is not clear to me in Electron because there are a number of ways of accessing sessions BrowserWindow.webContents().session orBrowserWindow.webContents().defaultSession or
import { session } from "electron".
Also, when I look at the application tab in devtools, these specific cookies are not showing anywhere (but others are). Why is this?

Related

Check connection loss in JavaScript on the browser [duplicate]

This question already has answers here:
navigator.onLine not always working
(2 answers)
Closed last month.
I referred to this Detect the Internet connection is offline? question to find out how to check if the browser is offline or online.
So I used window.navigator.onLine to find that out.
The problem is that, no matter what I do, window.navigator.onLine is always true.
I am using brave browser, but I'm not sure if that's related to the issue, it's chromium based.
I'm on Ubuntu Linux, desktop.
I just want to detect when the browser becomes offline to show a small message "connection lost".
In react the code looks as follows:
const online = window.navigator.onLine
useEffect(() => {
if (online) return
console.log("Connection lost!")
}, [online])
try to toggle your wifi on and on to see the console logs
Here's a stack blitz instance to try it out, it's a pretty small code, (Click me)
The property sends updates whenever the browser's ability to connect to the network changes. The update occurs when the user follows links or when a script requests a remote page.
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
So the value won't update unless you make a request of some sort.
There are also some implementation specific notes on the same url.
In Chrome and Safari, if the browser is not able to connect to a local area network (LAN) or a router, it is offline; all other conditions return true.
In other words, if there is any sort of network access it will be true, even if you are not connected to the internet.
So the best way to check this is probably to just make a request to an API endpoint or other resource that is only available while online and base your status on if the request was successful or not. Since in most cases just being "online" isn't worth much if your API is inaccessible this would probably provide better information to your users as well.
Need to use the event listener for this: window.addEventListener('online', () => { ...});.
Inside the callback for listener, do setState to check online off-line toggle.
here is small hook i created in reactjs to handle online offline states:
import { useEffect, useState } from 'react';
// toastr alert messages
import { showOffline, showOnline } from 'utils/alerts';
const useNetworkStatus = () => {
const [state, setState] = useState(true);
async function isOnline() {
// if its offline and to check if window?.navigator is supported.
if (!window?.navigator?.onLine) {
setState(false);
return false;
}
// Failover case:
// navigator.onLine cannot be trusted: there's situation where you appear to be online (connect to a network with no internet)
// but still cannot access the internet.
// So to fix: we request to our own origin to avoid CORS errors
const url = new URL(window.location.origin);
// with random value to prevent cached responses
url.searchParams.set('rand', Date.now());
try {
const response = await fetch(url.toString(), { method: 'HEAD' });
setState(true);
return response.ok;
} catch {
setState(false);
return false;
}
}
useEffect(() => {
const setOnlineOnVisibleChange = async () => {
// if its page is visible and state was offline
if (!document?.hidden && !state) {
if (await isOnline()) showOnline();
}
};
// on visiting the page again if the state is offline and network is online, then show online alert
if ('hidden' in document)
document.addEventListener('visibilitychange', setOnlineOnVisibleChange, false);
return () => document.removeEventListener('visibilitychange', setOnlineOnVisibleChange, false);
}, [state]);
useEffect(() => {
async function changeStatus() {
if (await isOnline()) showOnline();
else showOffline();
}
// Listen for the page to be finished loading
window.addEventListener('load', () => {
// if its offline
if (!isOnline()) showOffline();
});
window.addEventListener('online', changeStatus);
window.addEventListener('offline', changeStatus);
return () => {
window.removeEventListener('online', changeStatus);
window.removeEventListener('offline', changeStatus);
};
}, []);
};
export default useNetworkStatus;

Unable to load a specific URL with Cypress

I’m unable to load the following URL with Cypress. Getting timeout error. I have set the page load time to 2 mins, still same issue. General URLs eg. (https://www.google.co.nz/) works fine.
it(‘First Test’, () => {
cy.visit(‘https://shop.countdown.co.nz/‘)
})
Here's a way, not the best, could be improved...
The Countdown site has an aversion to being run in an iframe, but it can be tested in a child window, see custom command here Cypress using child window
Cypress.Commands.add('openWindow', (url, features) => {
const w = Cypress.config('viewportWidth')
const h = Cypress.config('viewportHeight')
if (!features) {
features = `width=${w}, height=${h}`
}
console.log('openWindow %s "%s"', url, features)
return new Promise(resolve => {
if (window.top.aut) {
console.log('window exists already')
window.top.aut.close()
}
// https://developer.mozilla.org/en-US/docs/Web/API/Window/open
window.top.aut = window.top.open(url, 'aut', features)
// letting page enough time to load and set "document.domain = localhost"
// so we can access it
setTimeout(() => {
cy.state('document', window.top.aut.document)
cy.state('window', window.top.aut)
resolve()
}, 10000)
})
})
Can test with that like this
cy.openWindow('https://shop.countdown.co.nz/').then(() => {
cy.contains('Recipes').click()
cy.contains('Saved Recipes', {timeout:10000}) // if this is there, have navigated
})
I bumped the setTimeout() in custom command to 10 seconds, cause this site drags it's feet a bit.
Configuration:
// cypress.json
{
"baseUrl": "https://shop.countdown.co.nz/",
"chromeWebSecurity": false,
"defaultCommandTimeout": 20000 // see below for better way
}
Command timeout error
Using Gleb's child window command, there's a timeout error that I can't track the source of.
To avoid it I set "defaultCommandTimeout": 20000 in config, but since it's only needed for the openWindow call it's better to remove the global setting and use this instead
cy.then({timeout:20000}, () => {
cy.openWindow('https://shop.countdown.co.nz/', {}).then(() => {
cy.contains('Recipes').click()
cy.contains('Saved Recipes', {timeout:10000}) // if this is there, have navigated
})
})
To check if the long command timeout only applies once, break one of the inner test commands and check that that it times out in the standard 4000 ms.
cy.then({timeout:20000}, () => {
cy.openWindow('https://shop.countdown.co.nz/', {}).then(() => {
cy.contains('Will not find this').click() // Timed out retrying after 4000ms
The quotes are wrong. Try the below code:
it('First Test', ()=>{ cy.visit('https://shop.countdown.co.nz/') })
On trying to visit the URL I am getting the error:
cy.visit() failed trying to load:
https://shop.countdown.co.nz/
We attempted to make an http request to this URL but the request
failed without a response.
We received this error at the network level:
Error: ESOCKETTIMEDOUT
Common situations why this would fail:
you don't have internet access
you forgot to run / boot your web server
your web server isn't accessible
you have weird network configuration settings on your computer
Error Screenshot:
Lets look into the common situations where this might happen:
you don't have internet access: I have a internet access, so this can be ruled out.
you forgot to run / boot your web server - Your site is accessible from a normal browser, this can be ruled out as well.
your web server isn't accessible - This is a possibility where may be there are firewall settings at the server end because of which cypress is not getting any response when accessing the site.
you have weird network configuration settings on your computer - This can be ruled out as well.
I had a similar issue, so what I observed in my case was that the URL was not getting added to the iframe src property and hence cy.visit() was getting timed out each time.
So, I added the URL manually to the src property of the iframe.
Here's my custom command for reference:
Cypress.Commands.add('goto', url => {
return new Promise(res => {
setTimeout(() => {
const frame = window.top.document.getElementsByClassName('aut-iframe')[0];
frame.src = url;
var evt = window.top.document.createEvent('Event');
evt.initEvent('load', false, false);
window.dispatchEvent(evt);
res();
}, 300);
});
});
Now use cy.goto('https://yoururl.com') and you are good to go.

Issue with Apple Pay integration in iframe

I recently started integrating Apple Pay into my website which actually loads the payment view in an iframe. Here the catch is my iframe loads in a different domain than my website. I followed the developer.apple.com site to integrate the Apple Pay into my website and created all the certificates and identifiers necessary.
Actual issue is when I'm trying to create the Apple Pay session, I'm receiving an error InvalidAccessError: Trying to start an Apple Pay session from a document with an different security origin than its top-level frame." I've not seen anyone facing this before.
Below is the code that I tried to create the Apple session.
var merchantIdentifier = 'my merchant identifier registered in developer account';
var promise = ApplePaySession.canMakePaymentsWithActiveCard(merchantIdentifier);
var canMakePayments = false;
promise.then(function (canMakePayments) {
if(canMakePayments) {
var session = new ApplePaySession(3, request);
}
}, function(error) {
alert(error);
});
As soon as the canMakePayments true line hits the code is trying to create an ApplePaySession and that is where I'm receiving the error.
Only way is running some JS on the top frame and communicate between them using messages.
E.g. in the top frame
window.addEventListener('message', (event) => {
if(event.data.type === 'applepay') {
const session = new ApplePaySession(...);
...
session.onpaymentauthorized = (event) => {
event.source.postMessage({ type: 'paymentauthorized', payment: event.payment});
}
}
});
in the iframe
window.addEventListener('message', (event) => {
if(event.data.type ==== 'paymentauthorized') {
// do something with the event.data.payment data you received
}
});
iframe.postMessage({type: 'applepay' });

Problems with Testcafe LocalStorage

everyone!
I'm new to TestCafe and I need some help on something I want to achieve.
I have a React website where I put a Facebook Login. Normally, when you enter the page and click on Login with facebook a popup window opens and enter your credentials normally. After that, you are redirected to the page and the token is saved in a localStorage variable for the page to consult later on.
However, when I run test for login process, Testcafe instead of opening a popup window, opens the facebook form on the same page and never redirects to the page.
Also, I tried to set some dummy token on the localstorage using the ClientFunction (and also Roles) and my website can never reach that token because testcafe seems to put this variable on a key called hammerhead
So, my question here is, how could I enter this token on the test or manually so my website can read it and make some functions with it?
This is what I have so far.
/* global test, fixture */
import { WelcomePage } from './pages/welcome-page'
import {ClientFunction, Role} from 'testcafe';
const welcomePage = new WelcomePage()
const setLocalStorageItem = ClientFunction((prop, value) => {
localStorage.setItem(prop, value);
});
const facebookAccUser = Role(`https//mypage.net/`, async t => {
await setLocalStorageItem('token', 'my-token');
}, { preserveUrl: true });
fixture`Check certain elements`.page(`https//mypage.net/`)
test('Check element is there', async (t) => {
await t
.navigateTo(`https//mypage.net/`)
.wait(4000)
.useRole(facebookAccUser)
.expect(cetainElementIfLoggedIn)
.eql(certainValue)
.wait(10000)
})
Any help would be highly appreciated
Thanks for your time.
UPDATE FROM FEB 2021
TestCafe now supports multiple browser windows and you can log-in via the Facebook popup form without any issues. Refer to the Multiple Browser Windows topic for more information.
Currently, TestCafe does not support multiple browser windows. So it's impossible to log in via the Facebook popup form.
However, there is a workaround. Please refer to the following thread https://github.com/DevExpress/testcafe-hammerhead/issues/1428.
My working test look like this:
import { Selector, ClientFunction } from 'testcafe';
const patchAuth = ClientFunction(() => {
window['op' + 'en'] = function (url) {
var iframe = document.createElement('iframe');
iframe.style.position = 'fixed';
iframe.style.left = '200px';
iframe.style.top = '150px';
iframe.style.width = '400px';
iframe.style.height = '300px';
iframe.style['z-index'] = '99999999';
iframe.src = url;
iframe.id = 'auth-iframe';
document.body.appendChild(iframe);
};
});
fixture `fixture`
.page `https://www.soudfa.com/signup`;
test('test', async t => {
await patchAuth();
await t
.click('button.facebook')
.switchToIframe('#auth-iframe')
.typeText('#email', '****')
.typeText('#pass', '****')
.click('#u_0_0')
.wait(30e3);
});
Please keep in mind that manipulations with x-frame-options in the testcafe-hammerhead module are required.
In addition, I would like to mention that Testing in Multiple browser windows is one of our priority tasks, which is a part of TestCafe Roadmap

How should I render web pages with Electron without showing them to the user?

I need to render a web page in an Electron app and take a screenshot without showing this web page to the user. How should I do it? What's the best method?
I tried creating a webview element and hiding by giving it an absolute positioning and -99999px top and left, but every now and then the capturePage method stalls forever. When I make it visible by using the inspector to remove that CSS, it looks blank but immediately the page renders and the callback is called.
I tried offscreen rendering starting a BrowserWindow, but it actually creates another window, with no title bar, that looks like this:
Any ideas how to make any of these work or another method?
Try this. This example saves the file as a png to the local computer.
const { app, BrowserWindow } = require('electron')
const fs = require("fs")
app.disableHardwareAcceleration()
let win
app.once('ready', () => {
win = new BrowserWindow({
webPreferences: {
offscreen: true
}
})
win.loadURL('http://github.com')
win.webContents.on('paint', (event, dirty, image) => {
// Example Code
fs.writeFile('ex.png', image.toPNG(), (err) => {
if (err) throw err;
console.log('The file has been saved!');
})
})
win.webContents.setFrameRate(30)
})

Categories