Casperjs Google Login (V2) Not working - javascript

I´m using casperjs (so phantomjs in the middle) to access some google utilities but, before accessing them we should be logged in google. For V1 google authentication, we are using the following script:
var casper = require('casper').create();
url = 'https://accounts.google.com/ServiceLogin?passive=1209600&continue=https%3A%2F%2Faccounts.google.com%2FManageAccount&followup=https%3A%2F%2Faccounts.google.com%2FManageAccount&flowName=GlifWebSignIn&flowEntry=ServiceLogin&nojavascript=1#identifier';
casper.start(url, function() {
this.fillSelectors('form#gaia_loginform', {
'input[name="Email"]': 'your#email',
}); //Fills the email box with email
this.click("#next");
this.wait(500, function() { //Wait for next page to load
this.waitForSelector("#Passwd", //Wait for password box
function success() {
console.log("SUCCESS...");
this.fillSelectors('form#gaia_loginform', {
'input[name="Passwd"]': 'yourPassw',
}); //Fill password box with PASSWORD
this.click("#signIn"); //Click sign in button
this.wait(500, function() {}); //Wait for it fully sigin
casper.thenOpen('http://utility.google.com/', function() {
this.wait(2000, function() {
this.capture('media/status.png', undefined, {
format: 'png',
quality: 100`enter code here`
});
});
});
},
function fail() {
console.log("FAIL...");
}
);
});
});
casper.run();
We have changed the way that we manipulate the form and fill the fields and It's working so far. The problem with V2 authentication is that triggering the mouse events isn't possible, that means we can't click using this.click("#next") and this.click("#signIn"). I tried doing post over the form, using different mouse events and also trying to manipulate directly the jsaction events. Nothing works.
Someone has an idea on how to solve this issue? Thank you so much!

Casper use PhantomJS, and Phantom itself is unable to login in google account login. It seems to use any ES6 feature not supported in phantomjs it fails silently.
Maybe you can have more luck with beta phantomjs 2.5.
Anyway, phantomjs is deprecated in favor of chrome headless. As said by phantom maintainer Vitaly Slobodin
https://groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE
The good news is that you can start chrome in headless mode as: /opt/google/chrome/chrome --headless --disable-gpu --repl and do whatever you want.
You can replace the --repl with --remote-debugging-port=9224 to control it in any remote code, like a program in node...
There is libraries to control it like phantomjs.
High level (like phantom): https://github.com/GoogleChrome/puppeteer
and lower levels to have more control: https://github.com/cyrus-and/chrome-remote-interface#clientdomainmethodparams-callback
Currently I had no luck with puppeteer but with chrome-remote-interface I was able to login in google account.
const CDP = require('chrome-remote-interface');
const argv = require('minimist')(process.argv.slice(2));
const file = require('fs');
// CLI Args
const url = argv.url || 'https://accounts.google.com';
const format = argv.format === 'jpeg' ? 'jpeg' : 'png';
const viewportWidth = argv.viewportWidth || 1440;
const viewportHeight = argv.viewportHeight || 900;
let delay = argv.delay || 0;
const userAgent = argv.userAgent;
const fullPage = argv.full;
// Start the Chrome Debugging Protocol
CDP(async function(client) {
// Extract used DevTools domains.
const {DOM, Emulation, Network, Page, Runtime} = client;
// Enable events on domains we are interested in.
await Page.enable();
await DOM.enable();
await Network.enable();
// If user agent override was specified, pass to Network domain
if (userAgent) {
await Network.setUserAgentOverride({userAgent});
}
// Set up viewport resolution, etc.
const deviceMetrics = {
width: viewportWidth,
height: viewportHeight,
deviceScaleFactor: 0,
mobile: false,
fitWindow: false,
};
await Emulation.setDeviceMetricsOverride(deviceMetrics);
await Emulation.setVisibleSize({width: viewportWidth, height: viewportHeight});
// Navigate to target page
await Page.navigate({url});
// Wait for page load event to take screenshot
Page.loadEventFired(async () => {
// If the `full` CLI option was passed, we need to measure the height of
// the rendered page and use Emulation.setVisibleSize
if (fullPage) {
const {root: {nodeId: documentNodeId}} = await DOM.getDocument();
const {nodeId: bodyNodeId} = await DOM.querySelector({
selector: 'body',
nodeId: documentNodeId,
});
const {model: {height}} = await DOM.getBoxModel({nodeId: bodyNodeId});
await Emulation.setVisibleSize({width: viewportWidth, height: height});
// This forceViewport call ensures that content outside the viewport is
// rendered, otherwise it shows up as grey. Possibly a bug?
await Emulation.forceViewport({x: 0, y: 0, scale: 1});
}
let expr="document.querySelector('input[type=email]').value='YOUREMAIL#gmail.com';";
expr+="document.querySelectorAll('div[role=button]')[0].click();";
setTimeout
let x=await Runtime.evaluate({expression: expr});
console.log('******' + JSON.stringify(x));
setTimeout(async function(){
expr="document.querySelector('input[type=password]').value='YOUR_PASSWORD';";
expr+="document.querySelectorAll('div[role=button]')[1].click()";
x=await Runtime.evaluate({expression: expr});
console.log('******' + JSON.stringify(x));
x=await ( async function() {
let expr="document.querySelector('input[type=password]')";
return Runtime.evaluate({expression: expr});
})()
console.log('**' + JSON.stringify(x));
}, 2000);
delay=5000
setTimeout(async function() {
const screenshot = await Page.captureScreenshot({format});
const buffer = new Buffer(screenshot.data, 'base64');
file.writeFile('output.png', buffer, 'base64', function(err) {
if (err) {
console.error(err);
} else {
console.log('Screenshot saved');
}
client.close();
});
}, delay);
});
}).on('error', err => {
console.error('Cannot connect to browser:', err);
});
References:
https://medium.com/#dschnr/using-headless-chrome-as-an-automated-screenshot-tool-4b07dffba79a
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#browserwsendpoint
https://developers.google.com/web/updates/2017/04/headless-chrome

I also trying same and I found that the click is working with this.click('#identifierNext'); and Google loaders start working. if you use following code after click to take screenshots you can see loader comes up but after that instead of going to Password screen it comes back to email screen.
Screenshot code
this.wait(200, function(){
this.capture('1.jpg',{
top: 0,
left: 0,
width: 4608,
height: 3456,
quality:20
});
});
this.wait(100, function(){
this.capture('2.jpg',{
top: 0,
left: 0,
width: 4608,
height: 3456,
quality:20
});
});
this.wait(100, function(){
this.capture('3.jpg',{
top: 0,
left: 0,
width: 4608,
height: 3456,
quality:20
});
});
this.wait(100, function(){
this.capture('4.jpg',{
top: 0,
left: 0,
width: 4608,
height: 3456,
quality:20
});
});
But I am also not able to reach Password screen, if with this help you can come with any idea let me know.

Related

Why does Puppeteer not save new textarea value?

This is a Puppeteer script running on Apify, designed to enter values into a form and save it. The fields are an input and a textarea, respectively.
The problem is - while the values of both fields are getting successfully updated in the front end (evidenced by taking screenshots), the value of the textarea is not actually saved when the Save <button> is .clicked, even though the input value alone is saved.
Why is this happening, and how do I overcome it?
I have tried several different methods of entering the textarea text:
Unlike the input, I have opted against using .type for the textarea (even though it worked fine for small amounts of text) due to the volume of the text being entered - it was causing timeouts.
I have tried using wait times, artificially changing focus and clicking into the textarea followed by .typeing an arbitrary value. In no circumstance does the textarea value get saved.
I have read on Stackoverflow that textareas should be edited via .innerText rather than .value, but this seems to make no difference in my case.
FYI - the form only becomes visible and active when the user/script clicks an Edit button (before this, the page had showed the form fields as static content). After the single Edit is clicked, two Save buttons appear (.edit-video-info becomes .save-video-info At the top, and then another .save-video-info appears. But a) the script simply uses the first one found and b) the Save operation is otherwise successful, as borne out by the input value getting saved.
import { Actor } from 'apify';
import { PuppeteerCrawler } from 'crawlee'; // "puppeteer" must be included in package.json dependencies
await Actor.init();
console.log('Actor initialised.')
console.log('Welcome. Only run if input was passed...')
const input = await Actor.getInput();
if (input) {
console.log('Input was passed...')
console.log(input);
// Create an instance of the PuppeteerCrawler class - a crawler
// that automatically loads the URLs in headless Chrome / Puppeteer.
const crawler = new PuppeteerCrawler({
// Here you can set options that are passed to the launchPuppeteer() function.
launchContext: {
launchOptions: {
headless: true,
// Other Puppeteer options
defaultViewport: { width: 1800, height: 2100 }
},
},
requestHandlerTimeoutSecs: 180,
// Stop crawling after several pages
maxRequestsPerCrawl: 50,
async requestHandler({ request, page, enqueueLinks }) {
/************************************
* Function: Long-text workaround - used for body
************************************/
// workaround cf. https://github.com/puppeteer/puppeteer/issues/4192#issuecomment-475241477
async function setSelectVal(sel, val) {
page.evaluate((data) => {
return document.querySelector(data.sel).value = data.val
}, { sel, val })
}
console.log(`Processing ${request.url}`);
/************************************
* Authentication
************************************/
let isLoggedIn = false;
// Is site logged-in? true/false
const loggedCheck = async (page) => {
try {
await page.waitForSelector('input#user', { timeout: 10000 });
console.log('Logged-in: true.');
return true;
}
catch(err) {
console.log('Logged-in: false.');
return false;
}
};
isLoggedIn = await loggedCheck(page);
if (!isLoggedIn) {
console.log('Need to do login...');
const SECRET_EMAIL = 'MYEMAILADDRESS'
const SECRET_PASSWORD = 'MYPASSWORD'
await page.waitForSelector('#signinButton')
console.log('Entering email...');
await page.type('input#email', SECRET_EMAIL)
console.log('Entering password...');
await page.type('input#password', SECRET_PASSWORD)
console.log('Clicking #signinButton...');
await page.click('#signinButton')
console.log('Clicked #signinButton.');
isLoggedIn = await loggedCheck(page);
console.log('Login should now be true.');
console.log(isLoggedIn);
}
/************************************
* Kicking off...
************************************/
console.log('Do stuff when #video-details loads...');
await page.waitForSelector('#video-details', { timeout: 30000 })
// Increase browser size
await page.setViewport({
width: 1200,
height: 2400,
deviceScaleFactor: 1,
});
console.log('Existing video title...');
console.log(await page.evaluate(() => document.evaluate('normalize-space(//div[#data-test-name="video-name"]/div[2])', document, null, XPathResult.STRING_TYPE, null).stringValue));
console.log('Echoed.');
/************************************
* Edit video details...
************************************/
console.log('Clicking Edit button...');
await page.click('button[data-test-name=edit-video-info]')
console.log('Clicked Edit button.');
/************************************
* 1. Video title
************************************/
console.log('Wait for input field...');
await page.waitForSelector('label[data-test-name=video-name-input]', { timeout: 30000 })
console.log('OK.');
console.log('Changing video title...')
if (input.title) {
console.log('input.title is available')
const titlebox = await page.$('label[data-test-name=video-name-input] input');
await titlebox.click({ clickCount: 3 })
await titlebox.type(input.title);
console.log('OK')
} else {
console.log('No input.title - will not change.')
}
/************************************
* 2. Body text
************************************/
console.log('Wait for body textarea...');
await page.waitForSelector('label[data-test-name=long-description-textarea] textarea', { timeout: 30000 })
console.log('OK.');
console.log('Changing body text...')
if (input.body) {
console.log('input.body is available')
const bodytext = input.body;
console.log(bodytext);
await setSelectVal('label[data-test-name=long-description-textarea] textarea', bodytext)
console.log('OK')
} else {
console.log('No input.body - will not change.')
}
/* Screenshot */
const screen07typedinput = await page.screenshot();
await Actor.setValue('screen07typedinput.png', screen07typedinput, { contentType: 'image/png' });
/************************************
* Save video details
************************************/
// await page.focus('button[data-test-name=save-video-info]')[1];
await page.waitForTimeout(3000)
console.log('Clicking Save button...');
await page.click('button[data-test-name=save-video-info]')
console.log('Did stuff');
/* Screenshot */
const screen08aftersave = await page.screenshot();
await Actor.setValue('screen08aftersave.png', screen08aftersave, { contentType: 'image/png' });
/************************************
* Logout
************************************/
console.log('Trying logout...')
await page.goto('https://signin.example.com/logout');
console.log('Finished.');
},
// This function is called if the page processing failed more than maxRequestRetries+1 times.
failedRequestHandler({ request }) {
console.log(`Request ${request.url} failed too many times.`);
},
});
// Run the crawler and wait for it to finish.
// await crawler.run(['https://news.ycombinator.com/']);
await crawler.run(['https://site.example.com/products/videocloud/media/videos/'+input.assetid]);
console.log('Crawler finished.');
} else {
console.log('No input. Abandon.')
}
await Actor.exit();
The button markup is...
<button class="Button-blueSteel-4_1_4-3Skod Button-btn-4_1_4-QuPwe Button-primary-4_1_4-2YCmt Button-small-4_1_4-1Wurh" data-test-name="save-video-info" role="button" tabindex="0" type="button" aria-pressed="true">
<span class>Save</span>
</button>
An id attribute is absent, so I can't use getElementByID.
This works...
I had not .clicked inside the <textarea>.
Whilst I had .clicked the <input> field prior to .typeing into it, I had not done this with the <textarea> - because I wasn't using .type there; I was having to set the `.values.
In addition to that, for good measure, I .click Back in to the <input> field.
Also, I `.types an arbitrary value.
I think it was the clicks. Without having clicked the <textarea>, only the apparent .value was getting set.
await page.click('label[data-test-name=long-description-textarea] textarea')
await setSelectVal('label[data-test-name=long-description-textarea] textarea', bodytext)
await page.click('label[data-test-name=long-description-textarea] textarea')
await page.type('label[data-test-name=long-description-textarea] textarea',' ')
await page.click('label[data-test-name=video-name-input] input')

How to listen for iframe creation before running javascript

I'm trying to wait for an iframe to be created before running some javascript, and i've tried a few methods but none seem to actually wait for the iframe - the error i'm getting in the console is Uncaught (in promise) TypeError: Cannot read properties of null (reading 'src').
The current code, below, tries to include the waitForElm function from this SO post, but 'm still getting the same error.
I need a value from within the iframe, so I want to wait for the iframe to load and then get the src of the iframe and use fetch() to call, parse with DOMParser and then get the item barcode.
It's for implementation of the Google Books Viewer API.
I also don't have access to the main html for the page, or directly to the iframe - which is why, for example, the creation of a script tag for the google books api is via a function.
I'm quite new to javascript so apologies if there's some basic errors going on here.
// creates a script tag to load google books api
(function(){
const gb = document.createElement('script');
gb.type = 'text/javascript';
gb.src = 'https://www.google.com/books/jsapi.js';
gb.addEventListener('load', () => google.books.load());
document.head.appendChild(gb);
})();
//function to wait for iframe to load
function waitForElm(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
app.controller('prmAlmaMashupAfterController', [function() {
this.$onInit = async function() {
const frame = document.querySelector("#iFrameResizer0");
fetch(frame.src)
.then(res => res.text())
.then(text => new DOMParser().parseFromString(text, 'text/html'))
.then(document => {
const bCode = document.getElementsByClassName('itemBarcode')[0].innerText || '';
if (bCode) {
const canvas = document.getElementById('viewerCanvas');
const viewer = new google.books.DefaultViewer(canvas);
viewer.load('NLS:' + bCode);
console.log(bCode);
} else {
console.log('No barcode');
}
})
.catch( e => {
console.log('ERROR', e);
});
}
}]);
//creates a div called viewerCanvas to hold the viewer
app.component('prmAlmaMashupAfter', {
bindings: { parentCtrl: '<' },
controller: 'prmAlmaMashupAfterController',
// template: '<div id="viewerCanvas" style="height: 500px; width: 600px; border: 1px solid blue"></div>'
template: '<div id="viewerCanvas" style="height: 500px; width: 100%; margin: 1% auto !important;"></div>'
});
You can wait for the iframe to load by adding a load event listener to the iframe:
frame.addEventListener("load", function() {
// loaded
});

Clicking on anchor tag will only excute part of a function

I am currently facing an issue where I have event listeners running for my client's website which track all hover and clicks on the website. We have 4 clients and our code works fine on 3 of them but it does not work correctly on the 1 client's website.
We are running the following code when a click is triggered:
async function objectClick(obj) {
var tag = obj.target;
if (tag.nodeName == 'IMG') {
objectName = tag.src;
} else {
objectName = tag.innerText || tag.textContent || tag.value;
}
var data_tag = tag.getAttribute("data-*");
if (data_tag != null) {
if (data_tag.includes("abc")) {
var layout = JSON.parse(window.localStorage.getItem(page));
layout.ctr = 1;
window.localStorage.setItem(page, JSON.stringify(layout))
await sendToHistory(uid, url, JSON.stringify(layout));
} else if (data_tag.includes("reward")) {
var layout = JSON.parse(window.localStorage.getItem(page));
layout.reward = 1;
window.localStorage.setItem(page, JSON.stringify(layout))
await sendToHistory(uid, url, JSON.stringify(layout));
}
}
if (data_tag === "abc" || data_tag === "reward") {
await sendToJourney(uid, "Clicked", -1, tag.nodeName, JSON.stringify({ text: objectName, data_nostra: null }), url, page);
} else {
await sendToJourney(uid, "Clicked", -1, tag.nodeName, JSON.stringify({ text: objectName, data_nostra: data_nostra_tag }), url, page);
}
}
For most of our clients, all the code runs in the function runs, including the sendToJourney function. For this client, after sendToHistory runs, the page switches and sendToJourney is not triggered. Do you know why this is?
sendToJourney and sendToHistory are functions that send some data to an API. Let me know if you need more information. Lastly, the client's website is created using Elementor which is a WordPress plugin. 2 of the other 4 clients also use Elementor but the function is fully executed for them but just not for this 1 client. Is there something that can be preventing the code from fully executing?
We have tried using obj.preventDefault but we cannot get the event to be triggered afterwards once our code is executed so what solution could be use here?
I call objectClick() by attaching an event listener as such:
(async function (document) {
await initData()
var trackable = document.querySelectorAll("img,button,p,h1,h2,h3,h4,h5,h6,a,span,input[type='submit'],[data-*]");
for (var i = 0; i < trackable.length; i++) {
trackable[i].addEventListener('mouseover', onHover, false);
trackable[i].addEventListener('mouseout', offHover, false);
trackable[i].addEventListener('click', objectClick, false);
}
})(document);

jsartoolkit fails with typeerror: artoolkit.setup is not a function

I've created a cordova Android app where users tap a link and it starts jsartoolkit. It all works fine the first time, but if I tap the back button then open the link again, after a few times it fails with an error:
"typeerror: artoolkit.setup is not a function"
It's happening when it tries to load the ARCameraParam (Data/camera_para.dat) - I've tried loading it both directly into the arcontroller ie var arController = new ARController(video, 'Data/camera_para.dat') and also loading it first ie arCamera = new ARCameraParam.
This error suggests that artoolkit.min.js isn't loading after a few times but I'm bamboozled as to why that would be.
It's not a caching issue - if I clear the app cache when it happens then try again, it still won't load. It always works fine the first time I've installed the app and also if I force stop the app, which suggests there's a process running from a previous session I'm not aware of. I've tried calling dispose on both the arcontroller and the arcameraparam when I tap the back button but that hasn't fixed it.
Does anyone know what processes jsartoolkit might be running that might cause an error like this? Any advice is very gratefully received.
Relevant code:
window.ARThreeOnLoad = function() {
function gotStream(stream) {
camvideo.src = window.URL.createObjectURL(stream);
nextBit(camvideo);
track = stream.getVideoTracks()[0];
camvideo.onerror = function(e)
{ stream.stop(); };
stream.onended = noStream;
}
function noStream(e) {
alert("no stream " + e);
var msg = 'No camera available.';
if (e.code == 1) {
msg = 'User denied access to use camera.'; }
document.getElementById('errorMessage').textContent = msg;
}
var camvideo = document.getElementById('monitor');
var constraints = { video: {facingMode: {exact: "environment"}, width: 1280, height: 960} };
navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(noStream);
}
function nextBit(video) {
var arController = new ARController(video, 'Data/camera_para.dat');
arController.onload = function() {
arController.image = video;

How do I open a url from <a> on default OS browser in Electron app? [duplicate]

I'm using the BrowserWindow to display an app and I would like to force the external links to be opened in the default browser. Is that even possible or I have to approach this differently?
I came up with this, after checking the solution from the previous answer.
mainWindow.webContents.on('new-window', function(e, url) {
e.preventDefault();
require('electron').shell.openExternal(url);
});
According to the electron spec, new-window is fired when external links are clicked.
NOTE: Requires that you use target="_blank" on your anchor tags.
new-window is now deprecated in favor of setWindowOpenHandler in Electron 12 (see https://github.com/electron/electron/pull/24517).
So a more up to date answer would be:
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
Improved from the accepted answer ;
the link must be target="_blank" ;
add in background.js(or anywhere you created your window) :
window.webContents.on('new-window', function(e, url) {
// make sure local urls stay in electron perimeter
if('file://' === url.substr(0, 'file://'.length)) {
return;
}
// and open every other protocols on the browser
e.preventDefault();
shell.openExternal(url);
});
Note : To ensure this behavior across all application windows, this code should be run after each window creation.
If you're not using target="_blank" in your anchor elements, this might work for you:
const shell = require('electron').shell;
$(document).on('click', 'a[href^="http"]', function(event) {
event.preventDefault();
shell.openExternal(this.href);
});
I haven't tested this but I assume this is should work:
1) Get WebContents of the your BrowserWindow
var wc = browserWindow.webContents;
2) Register for will-navigate of WebContent and intercept navigation/link clicks:
wc.on('will-navigate', function(e, url) {
/* If url isn't the actual page */
if(url != wc.getURL()) {
e.preventDefault();
openBrowser(url);
}
}
3) Implement openBrowser using child_process. An example for Linux desktops:
var openBrowser(url) {
require('child_process').exec('xdg-open ' + url);
}
let me know if this works for you!
For anybody coming by.
My use case:
I was using SimpleMDE in my app and it's preview mode was opening links in the same window. I wanted all links to open in the default OS browser. I put this snippet, based on the other answers, inside my main.js file. It calls it after it creates the new BrowserWindow instance. My instance is called mainWindow
let wc = mainWindow.webContents
wc.on('will-navigate', function (e, url) {
if (url != wc.getURL()) {
e.preventDefault()
electron.shell.openExternal(url)
}
})
Check whether the requested url is an external link. If yes then use shell.openExternal.
mainWindow.webContents.on('will-navigate', function(e, reqUrl) {
let getHost = url=>require('url').parse(url).host;
let reqHost = getHost(reqUrl);
let isExternal = reqHost && reqHost != getHost(wc.getURL());
if(isExternal) {
e.preventDefault();
electron.shell.openExternal(reqUrl);
}
}
Put this in renderer side js file. It'll open http, https links in user's default browser.
No JQuery attached! no target="_blank" required!
let shell = require('electron').shell
document.addEventListener('click', function (event) {
if (event.target.tagName === 'A' && event.target.href.startsWith('http')) {
event.preventDefault()
shell.openExternal(event.target.href)
}
})
For Electron 5, this is what worked for me:
In main.js (where you create your browser window), include 'shell' in your main require statement (usually at the top of the file), e.g.:
// Modules to control application life and create native browser window
const {
BrowserWindow,
shell
} = require('electron');
Inside the createWindow() function, after mainWindow = new BrowserWindow({ ... }), add these lines:
mainWindow.webContents.on('new-window', function(e, url) {
e.preventDefault();
shell.openExternal(url);
});
I solved the problem by the following step
Add shell on const {app, BrowserWindow} = require('electron')
const {app, BrowserWindow, shell} = require('electron')
Set nativeWindowOpen is true
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1350,
height: 880,
webPreferences: {
nativeWindowOpen: true,
preload: path.join(__dirname, 'preload.js')
},
icon: path.join(__dirname, './img/icon.icns')
})
Add the following listener code
mainWindow.webContents.on('will-navigate', function(e, reqUrl) {
let getHost = url=>require('url').parse(url).host;
let reqHost = getHost(reqUrl);
let isExternal = reqHost && reqHost !== getHost(wc.getURL());
if(isExternal) {
e.preventDefault();
shell.openExternal(reqUrl, {});
}
})
reference https://stackoverflow.com/a/42570770/7458156 by cuixiping
I tend to use these lines in external .js script:
let ele = document.createElement("a");
let url = "https://google.com";
ele.setAttribute("href", url);
ele.setAttribute("onclick", "require('electron').shell.openExternal('" + url + "')");

Categories

Resources