如何从 Electron 前端调用数据库?
How do I make a database call from an Electron front end?
(这里是全新的 Electron,所以我确定这是一个基本问题,我遗漏了一些基本知识...)
如何从 Electron 应用程序前端与本地数据库(我正在使用 Sqlite)进行交互?我有一个非常基本的数据库管理器 class,在我的 Electron 应用程序的 index.js
文件中使用它没有问题。但是从前端(我正在使用 Svelte,但我可能可以翻译其他前端框架的解决方案)来看,如何与数据库交互?这似乎很基本,但我正在努力寻找一个基本示例。
因为一切都是本地的,所以似乎没有必要设置一个整体 API 来回编组数据,但也许是这样?但如果是这样,如何告诉 Electron“后端”(如果这是正确的术语)做某事并将结果 return 发送到前端?我看到了一些关于 IPC 的东西,但现在这没有多大意义,而且似乎有点过分了。
这是我的简单数据库管理器 class:
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;
我可以调用它并从 Electron 入口点做任何没有问题的事情 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 中解决这个问题,希望有人能给我指明正确的方向。
如果您绝对 100% 确定您的应用不会访问任何远程资源,那么您可以公开 require
以及您可能需要通过预加载脚本的任何其他内容,只需编写 const nodeRequire = require; window.require = nodeRequire;
.
这是一个相当广泛的主题,需要阅读一些内容。我会尽力为您提供入门知识和 link 一些资源。
Electron 在两个(如果您打开多个 windows)进程上运行 - 主进程和渲染器进程。主进程处理打开新的 windows、启动和关闭整个应用程序、托盘图标、window 可见性等事情,而渲染器进程基本上就像浏览器中的 JS 代码。 More on Electron processes.
默认情况下,渲染器进程无法访问 Node 运行时,但可以允许。您可以通过两种方式做到这一点,但有很多注意事项。
一种方法是在创建 BrowserWindow
时设置 webPreferences.nodeIntegration = true
(注意:nodeIntegration
is deprecated and weird。这允许您使用所有节点 API从你的前端代码,你的代码片段会工作。但你可能不应该这样做,因为 BrowserWindow
能够加载外部 URL,并且这些页面上包含的任何代码都能够在你的或您用户的机器。
另一种方法是使用预加载脚本。预加载脚本在渲染器进程中运行,但可以访问 Node 运行时以及浏览器的 window
对象(Node 全局变量在实际前端代码运行之前从作用域中移除,除非 nodeIntegration
为真).您可以简单地设置 window.require = require
并在您的前端文件中使用 Node 代码。但是你可能也不应该这样做,即使你对你暴露的东西很小心,因为仍然很容易留下一个漏洞并允许潜在的攻击者利用一些暴露的 API 进入完全访问, as demonstrated here. More on Electron security.
那么如何安全地做到这一点呢?将 webPreferences.contextIsolation
设置为 true
。这明确地将预加载脚本上下文与渲染器上下文分开,而不是 nodeIntegration: false
导致的节点 APIs 的不可靠剥离,因此您可以 almost 确定没有恶意代码可以完全访问 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) }
之类的方法,这就是为什么我说 almost sure.
你真的不需要使用 IPC 来处理文件或数据库,因为这些 APIs 在预加载中可用。仅当您想要操作 windows 或从渲染器进程触发主进程上的任何其他内容时才需要 IPC。
(这里是全新的 Electron,所以我确定这是一个基本问题,我遗漏了一些基本知识...)
如何从 Electron 应用程序前端与本地数据库(我正在使用 Sqlite)进行交互?我有一个非常基本的数据库管理器 class,在我的 Electron 应用程序的 index.js
文件中使用它没有问题。但是从前端(我正在使用 Svelte,但我可能可以翻译其他前端框架的解决方案)来看,如何与数据库交互?这似乎很基本,但我正在努力寻找一个基本示例。
因为一切都是本地的,所以似乎没有必要设置一个整体 API 来回编组数据,但也许是这样?但如果是这样,如何告诉 Electron“后端”(如果这是正确的术语)做某事并将结果 return 发送到前端?我看到了一些关于 IPC 的东西,但现在这没有多大意义,而且似乎有点过分了。
这是我的简单数据库管理器 class:
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;
我可以调用它并从 Electron 入口点做任何没有问题的事情 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 中解决这个问题,希望有人能给我指明正确的方向。
如果您绝对 100% 确定您的应用不会访问任何远程资源,那么您可以公开 require
以及您可能需要通过预加载脚本的任何其他内容,只需编写 const nodeRequire = require; window.require = nodeRequire;
.
这是一个相当广泛的主题,需要阅读一些内容。我会尽力为您提供入门知识和 link 一些资源。
Electron 在两个(如果您打开多个 windows)进程上运行 - 主进程和渲染器进程。主进程处理打开新的 windows、启动和关闭整个应用程序、托盘图标、window 可见性等事情,而渲染器进程基本上就像浏览器中的 JS 代码。 More on Electron processes.
默认情况下,渲染器进程无法访问 Node 运行时,但可以允许。您可以通过两种方式做到这一点,但有很多注意事项。
一种方法是在创建 BrowserWindow
时设置 webPreferences.nodeIntegration = true
(注意:nodeIntegration
is deprecated and weird。这允许您使用所有节点 API从你的前端代码,你的代码片段会工作。但你可能不应该这样做,因为 BrowserWindow
能够加载外部 URL,并且这些页面上包含的任何代码都能够在你的或您用户的机器。
另一种方法是使用预加载脚本。预加载脚本在渲染器进程中运行,但可以访问 Node 运行时以及浏览器的 window
对象(Node 全局变量在实际前端代码运行之前从作用域中移除,除非 nodeIntegration
为真).您可以简单地设置 window.require = require
并在您的前端文件中使用 Node 代码。但是你可能也不应该这样做,即使你对你暴露的东西很小心,因为仍然很容易留下一个漏洞并允许潜在的攻击者利用一些暴露的 API 进入完全访问, as demonstrated here. More on Electron security.
那么如何安全地做到这一点呢?将 webPreferences.contextIsolation
设置为 true
。这明确地将预加载脚本上下文与渲染器上下文分开,而不是 nodeIntegration: false
导致的节点 APIs 的不可靠剥离,因此您可以 almost 确定没有恶意代码可以完全访问 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) }
之类的方法,这就是为什么我说 almost sure.
你真的不需要使用 IPC 来处理文件或数据库,因为这些 APIs 在预加载中可用。仅当您想要操作 windows 或从渲染器进程触发主进程上的任何其他内容时才需要 IPC。