I am using Electron (formerly Atom-Shell) to create a desktop version of an existing Angular web app. Most things work pretty well out of the box, but I have encountered some problems with the Dropbox Chooser.
My web app allows the user to import files from Dropbox using the chooser. In Electron this causes a new BrowserWindow
to be created for the chooser. However the window.opener
property of the new window is null, which basically makes it impossible for the Chooser window to communicate with the original window. This makes it useless because selecting a file effectively does nothing.
I know the Slack desktop app uses Electron and somehow they have been able to overcome this problem (the Dropbox Chooser does work inside Slack).
Does anyone know if/how I can use the Dropbox Chooser from inside of an Electron app?
tl;dr I can't use the Dropbox Chooser from inside an Electron app because it opens a new BrowserWindow with window.opener
set to null.
Okay, I managed to get this working. The window.opener
actually gets set, one of the problems is that you don't have a proper origin in the Chooser code that will ensure the window.opener.postMessage()
works and messages arrive to the parent window. There is more though.
1. Electron's BrowserWindow
s
Dropbox Chooser popup window works in electron's BrowserWindow
only if there is nodeIntegration
and webSecurity
set to false. Now, these are tricky, because if you open a child window from within your existing BrowserWindow
, you can't change webSecurity
in the child window anymore. You can change nodeIntegration
in window.open()
call by passing nodeIntegration=no
in the 3rd argument.
For example:
window.open('chooser-window.html', '_blank', 'resizable,scrollbars,nodeIntegration=no')
However I came up with a better solution. Opening the chooser window from main
process looks more promising as I can control both these parameters (and many others as well). Furthemore, I can hack around the target/origin based communication via window.opener
easily (more in step 2.). Creating a BrowserWindow
without node integration makes it unable to communicate with the main process. The require
method and other node's goodies are unavailable. However, you can pass a preload script to this BrowserWindow, where the node stuff is available, and you can re-expose the ipcRenderer
service in order to establish a communication to main process again.
When creating a BrowserWindow
from main process for purposes of Dropbox Chooser, create it like this:
const dropboxProxyWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
webSecurity: false,
preload: path.join(__dirname, 'dropbox-proxy-preload.js'),
},
})
and create a dropbox-proxy-preload.js
in the same directory as main.js
:
// NOTE: This file preloads ipc to hidden dropbox proxy window
// where nodeIntegration is set to false.
global.ipcRenderer = require('electron').ipcRenderer
This way, we'll have a BrowserWindow
that can communicate to the main process via ipc instead of a child window that communicates with parent window via window.opener.postMessage()
. This BrowserWindow
will be just a helper window for our electron app, only a proxy that will ensure the actual Dropbox Chooser can communicate back to our app.
2. Dropbox Chooser button and Chooser window
From Dropbox you'll get a nice button that you can paste into your JS/HTML and everything'll work out of the box (in browser). After clicking the button a new windows opens, you select files and they will get to your callback in JS. It looks something like this:
// in HTML
<script type="text/javascript" src="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="YOUR-APP-KEY"></script>
// in JS via button
var button = Dropbox.createChooseButton(options);
document.getElementById("container").appendChild(button);
// or in JS directly
Dropbox.choose(options);
The dropins.js
script ensures the communication to the Chooser window works as it should. Dropbox communicates to your window by calling window.opener.postMessage() with second argument targetOrigin
prefilled automatically by dropins.js
script. This origin URL has to match whatever you define in your Dropbox developers app administration.
In order to port this to electron, we need to "hack" the origin that is passed to the Chooser window, since the window.location
in electron's HTML files is not URL you can set in Dropbox administration. We'll do this by opening a remote HTML file in a hidden BrowserWindow that will open the Chooser. Let's call the hidden BrowserWindow a proxy window. The remote HTML will be on a domain that we'll add to Dropbox admin and it will be able to communicate with Chooser. It'll be launched with a preload
script that will ensure the communication to electron's main process. From there we can send the data to our application. After loading the hidden proxy window, we'll automatically click on the button so the Chooser opens.
3. Overriding dropins.js
There is one more catch though. If we'll have our proxy window hidden, all BrowserWindow
s opened from there will be hidden as well. So we need to override this option. We'll do it in dropins.js, in window.open()
calls, in third argument. We'll add show=1
. Since dropins.js
is minified by default, I used Chrome DevTools to prettify the code and then I made the required changes. Here it is in a Gist.
Final code
In /main.js
const dropboxProxyWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
webSecurity: false,
preload: path.join(__dirname, 'dropbox-proxy-preload.js'),
},
show: false,
})
const DROPBOX_CHOOSER_LINK = 'https://cdn.yourapp.com/static/dropbox-chooser.html'
dropboxProxyWindow.loadURL(DROPBOX_CHOOSER_LINK)
// NOTE: Catch data from proxy window and send to main.
ipc.on('dropbox-chooser-data', (event, data) => {
mainWindow.webContents.send('dropbox-chooser-data', data)
})
ipc.on('dropbox-chooser-cancel', () => {
mainWindow.webContents.send('dropbox-chooser-cancel')
})
In /dropbox-proxy-preload.js
:
global.ipcRenderer = require('electron').ipcRenderer
Remote in https://cdn.yourapp.com/static/dropins.js
: gist
Remote in https://cdn.yourapp.com/static/dropbox-chooser.html
:
<html>
<head>
<title>Dropbox Chooser</title>
<script type="text/javascript" src="dropins.js" id="dropboxjs" data-app-key="xxx"></script>
</head>
<body>
<div id="container"></div>
<script type="text/javascript">
var options = {
success: function(files) {
console.debug('Files from dropbox:', files)
if (!window.ipcRenderer || typeof window.ipcRenderer.send !== 'function') {
console.warn('Unable to send Dropbox data to App.')
return
}
window.ipcRenderer.send('dropbox-chooser-data', JSON.stringify(files))
},
cancel: function() {
if (!window.ipcRenderer || typeof window.ipcRenderer.send !== 'function') {
console.warn('Unable to send Dropbox data to App.')
return
}
window.ipcRenderer.send('dropbox-chooser-cancel')
},
linkType: "preview",
multiselect: true,
folderselect: false,
};
var button = Dropbox.createChooseButton(options);
document.getElementById("container").appendChild(button);
button.click() // automatically open click on the button so the Chooser opens
</script>
</body>
</html>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With