如何从电子前端进行数据库调用?

2022-06-12 00:00:00 electron sqlite svelte

(这里是全新的学习电子,所以我相信这是一个基本的问题,我遗漏了一些基本的东西...)

如何从电子应用程序前端与本地数据库(我使用的是Sqlite)交互?我有一个非常基本的数据库管理器类,在我的Electron应用程序的index.js文件中使用它没有问题。但是从前端(我使用的是Svelte,但我可能可以翻译来自其他前端框架的解决方案),如何与数据库交互?这似乎很基本,但我正在努力寻找一个基本的例子。

既然所有东西都是本地的,似乎不应该仅仅为了来回封送数据而设置整个API,但也许它是必要的?但如果是这样的话,人们该如何告诉电子后端(如果这是正确的说法)去做一些事情,并将结果返回给前端呢?我看到了一些关于IPC的东西,但现在没有太大的意义,看起来有点矫枉过正。

以下是我的简单数据库管理器类:

const sqlite3 = require("sqlite3").verbose();

class DbManager {
  #db;
  open() {
    this.#db = new sqlite3.Database("testing.db", sqlite3.OPEN_READWRITE);
  }
  close() {
    this.#db.close();
  }
  run(sql, param) {
    this.#db.run(sql, param);
    return this;
  }
}

const manager = new DbManager();
module.exports = manager;

我可以从电子入口点调用它并执行任何没有问题的操作index.js

const { app, BrowserWindow, screen } = require("electron");
require("electron-reload")(__dirname);
const db = require("./src/repository/db");

const createWindow = () => {
   ...
};

let window = null;

app.whenReady().then(() => {
  db.open();
  createWindow();
});

app.on("window-all-closed", () => {
  db.close();
  app.quit();
});

但是如何处理我的组件?

<script>
  // this won't work, and I wouldn't expect it to, but not sure what the alternative is
  const db = require("./repository/db");  
  let accountName;
  function addAccount() {
    db.run("INSERT INTO accounts (name) VALUES ($name);", { $name: accountName });
  }
</script>

<main>
  <form>
    <label for="account_name">Account name</label>
    <input id="account_name" bind:value={accountName} />
    <button on:click={addAccount}>Add account</button>
  </form>
</main>

如果有人知道一个样板实现可以做类似的事情,那将是非常有帮助的。显然,这就像这里的应用程序101;我只是还不确定如何在Electron中进行这项工作,如果有人给我指路的话,我会很感激的。


解决方案

如果您百分之百确定您的应用程序不会访问任何远程资源,则只需公开require以及通过预加载脚本可能需要的任何其他内容,只需编写const nodeRequire = require; window.require = nodeRequire;


这是一个相当广泛的主题,需要一些阅读。我将尝试为您提供入门知识并链接一些资源。

电子在两个(如果打开多个窗口)进程上运行-主进程和呈现器进程。主进程处理打开新窗口、启动和关闭整个应用程序、任务栏图标、窗口可见性等事务,而呈现器进程基本上就像浏览器中的JS代码。More on Electron processes。

默认情况下,呈现器进程不能访问Node运行时,但可以允许它访问。您可以通过两种方式做到这一点,但有许多注意事项。

一种方法是在创建BrowserWindow(注:nodeIntegrationis deprecated and weird时设置webPreferences.nodeIntegration = true。这允许您在前端代码中使用所有NodeAPI,并且您的代码片段可以工作。但您可能不应该这样做,因为BrowserWindow能够加载外部URL,而这些页面上包含的任何代码都能够在您或您用户的计算机上执行任意代码。

另一种方法是使用预加载脚本。预加载脚本在呈现器进程中运行,但可以访问Node运行时和浏览器的window对象(除非nodeIntegration为真,否则Node全局变量将在实际的前端代码运行之前从作用域中移除)。您只需设置window.require = require,并在您的前端文件中使用Node代码。但您可能也不应该这样做,即使您非常小心要公开的内容,因为仍然很容易留下漏洞,并允许潜在攻击者利用一些公开的API进入完全访问权限,as demonstrated here。More on Electron security。

那么如何安全地做到这一点呢?将webPreferences.contextIsolation设置为true。这最终将预加载脚本上下文与呈现器上下文分开,而不是由nodeIntegration: false导致的不可靠的Node API剥离,因此您几乎可以确定没有恶意代码具有对Node的完全访问权限。

然后您可以通过contextBridge.exposeInMainWorld从预加载向前端公开特定的函数。例如:

contextBridge.exposeInMainWorld('Accounts', {
  addAccount: async (accountData) => {
    // validate & sanitize...
    const success = await db.run('...');
    return success;
  }
}

这会安全地将window中指定方法的Accounts对象公开在前端。因此,您可以在组件中编写:

const accountAdded = await Accounts.addAccount({id: 123, username: 'foo'});

请注意,您仍然可以公开runDbCommand(command) { db.run(command) }甚至evalInNode(code) { eval(code) }这样的方法,这就是为什么我说几乎是确定的。

您实际上不需要使用IPC来处理文件或数据库之类的事情,因为这些API在预加载中可用。仅当您要从呈现器进程操作窗口或在主进程上触发任何其他内容时,才需要IPC。

相关文章