Opening _blank links in Electron Browser - javascript

I'm creating a web browser with Electron (please don't answer that that is "pointless", "not smart", etc.). When my user navigates to a link with the _blank attribute, I want a new Electron Web Browser window to open. Or, as a second choice, open the URL in their default web browser (such as Chrome, Edge, Opera, etc.).
I've looked at several other StackOverflow questions but with zero luck every time.

const { app, BrowserWindow, shell } = require("electron");
...
mainWindow.webContents.on("new-window", (_, url) => {
_.preventDefault();
const protocol = require("url").parse(url).protocol;
if (protocol === "http:" || protocol === "https:") {
shell.openExternal(url);
}
});
This event listener will trigger the callback whenever the user attempts to open the URL on your renderer. This callback will open your target URL on the default browser.
And regarding your other approach, I'd recommend not opening a new electron browser instance for accessing the target URL. This will be the cause of heavy resource usage.

Related

ios PWA How to open external link on mobile default safari(not In app browser)

I'm making a PWA in IOS 13.2.
I need to launch another PWA from my app by clicking an external link.
The problem is that my current PWA opens all external links in the in-app browser instead of regular safari and there is no add to Home screen option in the in-app browser.
How can I force the PWA to open an external link in regular safari instead of in-app safari?
Things that doesn't work
Changing the scope in the manifest-file
Using rel="noreferrer"
Using target="_blank"
It's a bit hard to answer this question (and all the comments) as the use case isn't overly clear, but here goes...
On mobile devices, an "in-app browser" is NOT the same thing as a Progressive Web App running in full-screen mode.
If an iOS app runs and then displays HTML content inside of it, it's utilizing UIWebView or WKWebView. However, in the case of a PWA it's already running in Safari as a "full screen" experience. Defining which you're trying to break links out of is extremely important as they function differently.
target="_blank" will typically break a link out of a page using WebView. I believe this is the default functionality for links outside the current domain as well.
An "installed" PWA is running in something called "Stand Alone" mode. This makes it full screen and removes navbars, etc. As of this writing, Safari doesn't support the fullscreen API that other browsers are implementing. Chrome uses the App manifest file to determine this functionality. Safari basically ignores the manifest in favor of proprietary meta tags.
In this case <meta name="apple-mobile-web-app-capable" content="yes"> tells Apple to make the page a stand-alone app. Try setting content="no" (Safari caches things heavily so you might need to force a refresh) on the pages that should break out of stand-alone mode. You can check to see what mode the page thinks it's in by using this javascript boolean window.navigator.standalone.
Or you can use javascript to force a "new window" in Safari as long as you're targeting a different subdomain or HTTP instead of HTTPS.
// if app is hosted from https://example.com
if (("standalone" in window.navigator) || window.navigator.standalone ) {
window.open('http://example.com/page', '_blank');
}
Finally, Apple uses some special URL strings to cause native apps to handle some actions like emails, phone numbers, and youtube videos. You might be able to "hack" that functionality to get safari to open your link.
After quite thorough investigations i believe this isn't possible currently (between iOS 13.X and iOS 14.1).
The following javascript API's will use in-app browser:
window.open()
window.location.href =
Using an anchor tag will also use the in-app browser no matter what attributes it is assigned. Changing the scope in the manifest also doesn't help.
BUT i did find a way to at least prompt the user that they are in an in-app browser to make the UX a little less confusing.
There are two ways (maybe more?) to detect if the browser is standalone: window.matchMedia("(display-mode: standalone)").matches and window.navigator.standalone . And here is the weird part:
// regular safari
window.navigator.standalone -> false
window.matchMedia("(display-mode: standalone)").matches -> false
// full screen apps (only pwas, weirdly enough this doesn't apply to pre-pwa web apps on iOS)
window.navigator.standalone -> true
window.matchMedia("(display-mode: standalone)").matches -> true
// in-app browsers launched from within a PWA
window.navigator.standalone -> true
window.matchMedia("(display-mode: standalone)").matches -> false
I assume window.navigator.standalone represents the parent context of the pwa and window.matchMedia("(display-mode: standalone)").matches represents the context of the in-app browser.
So a naive implemention to check if your app is running in the in-app browser on iOS:
function isIOSInAppBrowser() {
// Check if device is iPad, iPhone or iPod (this bit is naive and should probably check additional stuff)
if (Boolean(window.navigator.userAgent.match(/iPad|iPhone|iPod/)) === false) return false;
// Check if navigator is standalone but display-mode isn't
if (window.navigator.standalone === true && window.matchMedia("(display-mode: standalone)").matches === false) {
return true;
} else {
return false;
}
}
note that this implementation isn't reliable and can easily produce false positive in future version of iOS
Tested with latest iOS-12/iPhone 7
If anyone could test with different device and iOS versions.
iOS is opening <a> tag as external url, launching Safari.
So, create a hidden tag:
HTML
<a hidden id='openSafari' href=''>click me</a>
script
let openSafari = document.getElementById('openSafari');
openSafari.setAttribute('href', 'https://netflix.com');
openSafari.click()
The way your app will work based on what did you configured in manifest.json file.
In manifest file the scope key will determines which link will open inside PWA and which link will open in browser.
For example route under myapp will open inside PWA and other would be outside.
{
"name": "My App",
"start_url": "/myapp",
"scope": "/myapp",
"display": "standalone"
}
I would personally try the target="_blank" to the href link. I would also add a rel="noreferrer external" to ensure this is consistent across different webview implementations. There should also be a way to do this via the manifest file although I'm not entirely sure how that works in iOS.
#IBAction func openURL(_ sender: Any) {
// check if website exists
guard let url = URL(string: "https://apple.com") else {
return
}
let safariVC = SFSafariViewController(url: url)
present(safariVC, animated: true, completion: nil)
}
This will work perfectly on all iOS versions with PWA support. (I know this because I am a longtime iOS user):
Add target="_blank" to all the links that you have. That will open them in the mobile default safari.
For example:
Page

How can I add a custom chrome extension to my Electron app?

I am facing some trouble adding chrome addons into my Electron BrowserWindow.
Before creating my window (and after the ready event has fired), I try to add a devtools extension that my browser needs to do screen sharing.
BrowserWindow.addDevToolsExtension('/home/USER/.config/chromium/Default/Extensions/dkjdkjlcilokfaigbckcipicchgoazeg/1.5_0');
I followed this Electron guide, and it worked for their example (adding the react develop tool). When I do the exact same thing with my own chrome extension I have this error:
[4735:1116/163422.268391:ERROR:CONSOLE(7701)] "Skipping extension with invalid URL: chrome-extension://extension-name", source: chrome-devtools://devtools/bundled/shell.js (7701)
I don't really get why the error specified is "invalid URL" since I'm doing the exact same thing / process with the react addon without a problem. I also have no idea what to do. Is it possible that my chrome addon is not Electron-compatible?
It looks like you're trying to add a regular Chrome extension instead of a Dev Tools extension.
The BrowserWindow.addExtension(path) method is for regular Chrome extensions:
BrowserWindow.addExtension(path)
path String
Adds Chrome extension located at path, and returns extension's name.
The method will also not return if the extension's manifest is missing or incomplete.
Note: This API cannot be called before the ready event of the app module is emitted.
- https://electronjs.org/docs/api/browser-window#browserwindowaddextensionpath
Conversely, the BrowserWindow.addDevToolsExtension(path) method is for Dev Tools extensions:
BrowserWindow.addDevToolsExtension(path)
path String
Adds DevTools extension located at path, and returns extension's name.
The extension will be remembered so you only need to call this API once, this API is not for programming use. If you try to add an extension that has already been loaded, this method will not return and instead log a warning to the console.
The method will also not return if the extension's manifest is missing or incomplete.
Note: This API cannot be called before the ready event of the app module is emitted.
- https://electronjs.org/docs/api/browser-window#browserwindowadddevtoolsextensionpath
Note that in both cases you need to wait for the ready event from the app module to be emitted:
const { BrowserWindow, app } = require('electron')
let mainWindow = null
function main() {
BrowserWindow.addExtension('/path/to/extension')
mainWindow = new BrowserWindow()
mainWindow.loadURL('https://google.com')
mainWindow.on('close', event => {
mainWindow = null
})
}
app.on('ready', main)
Support for Chromium extensions in Electron is actively being worked on at the moment. The support isn't complete yet, but the GitHub issue seems to have regular updates being posted.
Fingers crossed!
A current pull request is open for 'just enough extensions [api] to load a simple ... extension'
Electron 9 has much more support for extensions!
To load them, use session.loadExtension: https://github.com/electron/electron/blob/master/docs/api/extensions.md
const { app, BrowserWindow, session } = require('electron')
// ... in your createWindow function, which is called after app.whenReady
const mainWindow = new BrowserWindow({...})
const ext = await session.defaultSession.loadExtension('/path/to/unpacked/chrome-ext')
console.log('ext', ext)
// outputs config file
// {
// id: 'dcpdbjjnmhhlnlbibpeeiambicbbndim',
// name: 'Up! – Free Social Bot',
// path: '/Users/caffeinum/Development/GramUp/chrome-ext',
// url: 'chrome-extension://dcpdbjjnmhhlnlbibpeeiambicbbndim/',
// version: '1.7.0',
// manifest: { ... }
// }
Read more: https://github.com/electron/electron/blob/master/docs/api/extensions.md
Also, there's another project that helps you do that, also adds additional functionality: https://github.com/sentialx/electron-extensions
While there is a documented method to register a normal extension, in majority of cases it won't do much, as Electron supports only an accessibility subset of the chrome.* APIs (apparently only the stuff required by Spectron and Devtron) and as they've stated a while ago, they don't have any plans to support Chrome extension APIs at a full scale.

Electron: webview doesn't receive callback code from new created window

I've got desktop application that works as simple browser. It has tabs with webviews that show web pages.
Some websites have social sign-in option. And I really need it to be available for my users.
In general browser (e.g. chrome) when you sign in via Facebook, it creates new window with social authorization form. After submitting this form the new window closes and parent window receives callback code, reloads and sign you in.
In my app when you try to do the same, webview that contains the web page, creates new BrowserWindow with the same auth form. At this point everything works fine. When you submit form, the new window closes but nothing happens after. Parent window doesn't receive any callback, the code that should run isn't triggered at all. Or probably it has been received, but not triggered, since if I reload page, it shows that I'm signed in.
According to the electron documentation <webview> runs in a separate process than renderer process. So I think when new window closes, the callback is received by parent BrowserWindow (renderer process) and not runs exactly in webview frame.
While searching through different sources for solution, I found that this problem is also concerned with window.opener, that was not fully supported in the earliest versions of electron. But according to the issue#1865 on github full window.opener support is now available if I open window by window.open method.
To make this work I should prevent webview to open new BrowserWindow and create it by myself.
The following code fails:
// in renderer process
webview.addEventListener('new-window', function(event) {
event.preventDefault();
// I also tried 'event.defaultPrevented = true;'
// or 'return false;'
});
The listener is triggered, but I can't prevent new-window opening.
Someone wrote that new-window can be prevented in main process only.
So I add the following code in my main.js:
// in main process
var electron = require('electron');
var BrowserWindow = electron.BrowserWindow;
mainWindow = new BrowserWindow({
"width": 970,
"height": 500,
"center": true,
'title': 'Main window',
"autoHideMenuBar": true
});
mainWindow.loadURL('file://' + root + '/index.html');
mainWindow.webContents.on('new-window', function(event) {
console.log(event);
});
This time event 'new-window' is not triggered at all. I think this is because the event works only if renderer process requests to open a new window by window.open for example. But in my case new window is opened by <webview> tag, not by renderer process. So I failed to prevent new-window to open since these are two separate processes.
So I decided just to get new-window after it opens.
// in renderer process
var electron = require("electron");
var remote = electron.remote;
webview.addEventListener('new-window', function(event) {
var win = remote.BrowserWindow.getFocusedWindow();
var contents = win.webContents;
win.setIcon(root+"/img/favicon.ico");
win.setMenuBarVisibility(false);
contents.on('dom-ready', function() {
contents.openDevTools();
})
});
After that I really have no idea what to do next... how to append window.opener and solve my issue
I found many similar issues on github. But all of them are merged to one issue #1865 and there is no clear answer so far.
I know solution exists. Brave developers fixed this. Brave browser also made with electron works correctly the same as general browser does. But I can't find answer while looking through its sources.
They even made tests to check whether window.opener works correctly. But I failed to install Brave from git repository. After 'npm-start' it says 'Error: %1 is not valid Win32 application'. I have Windows 10. (just another reason makes me switch to Linux. Hope to make it soon.)
So, my question is: how to send callback data from new window back to webview that created this window. Thanks in advance.
First, to handles new-window popups yourself, you have to disable allowpopups (i.e. remove allowpopups attribute from the webview)
This way the new-window event will be triggered but no popup will be opened until you explicitelly code it.
For the window.opener feature which is required for some social or sso login, electron does not have enough support of it. See issue 1865
The Brave team have implemented it in a restricted way in their fork of Electron. See issue 4175.
You may try to either:
Use the fork of electron from Brave (not sure it will fit your needs as they have made some major changes)
Make a PR to electron to integrate the support of window.opener from the fork.
Make your own polyfill for the methods you need by injecting a script that implements missing window.opener methods. You can use window.opener.send to communicate with renderer process)

Check for custom URL scheme in browser

I have an application that registers a new URL scheme when it's installed and I'm looking for a reliable way to launch this application from our web interface.
Right now I have an IFrame hidden away which I will update it's source after a button is clicked to launch the application, but it seems that in Internet Explorer the parent window always captures the URL scheme not recognized error and navigates to the location it can't understand anyway.
So my question: Is there a way for a programmer to check to see if a scheme is supported by a browser before attempting to navigate to the new URI to direct a user to download the application first?
You cannot check for URL schemes installed on the device.
What you can do is this:
setTimeout(function () { window.location = "appCustomUrlSchemeHere"; }, 25);
window.location = "fallbackUrlHere";
But I imagine that will still cause issues in IE as it will attempt to open the URL scheme.

How can I detect if an app is installed on an android device from within a web page

Here is the situation:
I have a web page that needs to check through JavaScript if my app is already installed on the android device it is currently running on.
If the app is installed the page will show a link (with custom protocol) to launch the app,
otherwise the page should show a link to the android market.
I can manage the links to app and to market. The only remaining step is to detect the presence of the app on the device from within JavaScript code (or perhaps try to catch a possible error of unsupported protocol as an indication of not existing app).
When I
click on a web link with
my custom app protocol and
the app is not yet installed on the device
I can see that the android environment generates an "protocol is not supported" type error.
Unfortunately, I am not able to capture that error in the JavaScript code to divert the user to the android market.
I guess both direct detection and error detection are valid methods if they exist at all.
Any ideas how can I accomplish that?
Thanks for help.
Don't use a custom protocol.
Instead, set up your <intent-filter> to watch for a well-known URL (e.g., set the scheme, host, and path). That URL should point to the page where your "download the app" instructions lie.
Then, if the user clicks the link to the well-known URL from someplace else, it will launch your app if installed, or will bring up your Web page if not.
I also had the same question, but I solve it like this:
var ua = navigator.userAgent.toLowerCase();
var isAndroid = ua.indexOf("android") > -1;
if(isAndroid) { // if is android
// your app address for local
var ifrSrc = 'intent://example.com#Intent;scheme=my_scheme;action=android.intent.action.VIEW;end';
var ifr = document.createElement('iframe');
ifr.src = ifrSrc ;
ifr.onload = function() { // if app is not installed, then will go this function
window.location = 'http://your.app_download_page.com';
};
ifr.style.display = 'none';
document.body.appendChild(ifr);
setTimeout(function(){
document.body.removeChild(ifr); // remove the iframe element
}, 1000);
} else { // if is not android
window.location = 'http://google.com';
}
Hope this can help someone may has this problem.
You can use an iframe which src url is the custom protocol of your app while at the same time you can display your webpage if the custom protocol is not handled. You can also set the iframe to invisble using frameborder="0" and width and height = 0;

Categories

Resources