我正在使用Electron(以前称为Atom-Shell)来创建现有Angular Web应用程序的桌面版本。大多数东西开箱即用,但我遇到了Dropbox Chooser的一些问题。
我的网络应用允许用户使用选择器从Dropbox导入文件。在Electron中,这会导致为选择器创建新的BrowserWindow
。但是,新窗口的window.opener
属性为null,这基本上使Chooser窗口无法与原始窗口进行通信。这使得它无用,因为有效地选择文件什么都不做。
我知道Slack桌面应用使用Electron,但他们已经能够克服这个问题(Dropbox Chooser 在Slack内部工作)。
是否有人知道我是否可以/如何在电子应用中使用Dropbox选择器?
tl; dr我无法在Electron应用程序中使用Dropbox Chooser,因为它会打开一个新的BrowserWindow,window.opener
设置为null。
答案 0 :(得分:0)
好的,我设法解决了这个问题。 window.opener
实际上已设置,问题之一是您在Chooser代码中没有正确的来源,以确保window.opener.postMessage()
正常工作并且消息到达父窗口。还有更多。
1。电子的BrowserWindow
仅当BrowserWindow
且nodeIntegration
设置为false时,Dropbox Chooser弹出窗口才在电子webSecurity
中起作用。现在,这很棘手,因为如果您从现有的BrowserWindow
中打开子窗口,则无法再在子窗口中更改webSecurity
了。您可以通过在第三个参数中传递nodeIntegration
来更改window.open()
调用中的nodeIntegration=no
。
例如:
window.open('chooser-window.html', '_blank', 'resizable,scrollbars,nodeIntegration=no')
但是我想出了一个更好的解决方案。从main
进程中打开选择器窗口看起来更有希望,因为我可以控制这两个参数(以及许多其他参数)。此外,我可以通过window.opener
轻松地破解基于目标/起源的通信(在第2步中有更多介绍)。创建没有节点集成的BrowserWindow
使其无法与主进程通信。 require
方法和其他节点的好东西不可用。但是,您可以将预加载脚本传递到该浏览器窗口,在该窗口中可以使用节点资料,并且可以重新公开ipcRenderer
服务,以便再次建立与主进程的通信。
在出于Dropbox选择器的目的从主流程创建BrowserWindow
时,请按以下方式创建它:
const dropboxProxyWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
webSecurity: false,
preload: path.join(__dirname, 'dropbox-proxy-preload.js'),
},
})
并在与dropbox-proxy-preload.js
相同的目录中创建一个main.js
:
// NOTE: This file preloads ipc to hidden dropbox proxy window
// where nodeIntegration is set to false.
global.ipcRenderer = require('electron').ipcRenderer
这样,我们将有一个BrowserWindow
可以通过ipc与主进程进行通信,而不是可以通过window.opener.postMessage()
与父窗口进行通信的子窗口。 BrowserWindow
只是我们电子应用程序的帮助窗口,仅是可确保实际Dropbox选择器可以与我们的应用程序通信的代理。
2。 Dropbox选择器按钮和“选择器”窗口
在Dropbox中,您会看到一个漂亮的按钮,您可以将其粘贴到JS / HTML中,然后一切都将立即可用(在浏览器中)。单击按钮后,将打开一个新窗口,选择文件,它们将进入JS中的回调。看起来像这样:
// 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);
dropins.js
脚本可确保与选择器窗口的通信正常进行。 Dropbox通过使用由targetOrigin
脚本自动填充的第二个参数dropins.js
调用window.opener.postMessage()来与您的窗口通信。此原始URL必须与您在Dropbox开发人员应用管理中定义的任何内容匹配。
为了将其移植到电子版,我们需要“破解”传递到选择器窗口的源,因为电子版HTML文件中的window.location
不是可在Dropbox管理中设置的URL。 我们将通过在隐藏的BrowserWindow中打开一个远程HTML文件来打开选择器。我们将隐藏的BrowserWindow称为代理窗口。远程HTML将位于我们将添加到Dropbox管理员的域中,并且它将能够与Chooser通信。将使用preload
脚本启动该脚本,以确保与电子主过程的通信。从那里我们可以将数据发送到我们的应用程序。加载隐藏的代理窗口后,我们将自动单击该按钮,以便选择器打开。
3。覆盖dropins.js
不过,还有一个问题。如果我们将代理窗口隐藏起来,从那里打开的所有BrowserWindow
也会被隐藏。因此,我们需要覆盖此选项。我们将在dropins.js中的window.open()
调用中的第三个参数中进行操作。我们将添加show=1
。由于dropins.js
在默认情况下是最小的,因此我使用Chrome DevTools整理了代码,然后进行了必要的更改。 Here it is in a Gist。
最终代码
在/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')
})
在/dropbox-proxy-preload.js
中:
global.ipcRenderer = require('electron').ipcRenderer
远程https://cdn.yourapp.com/static/dropins.js
:gist
远程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>