使用Electron app里面的Dropbox Chooser

时间:2016-01-14 20:22:26

标签: javascript dropbox electron

我正在使用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。

1 个答案:

答案 0 :(得分:0)

好的,我设法解决了这个问题。 window.opener实际上已设置,问题之一是您在Chooser代码中没有正确的来源,以确保window.opener.postMessage()正常工作并且消息到达父窗口。还有更多。

1。电子的BrowserWindow

仅当BrowserWindownodeIntegration设置为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.jsgist

远程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>