无法调用 API 从 preload.js 获取 cpu 数据 |电子,cpu-utils

Can't call API to fetch cpu data from preload.js | Electron, cpu-utils

我尝试制作一个应用程序,通过 OS-utils 获取 CPU 使用情况并在屏幕上更新它,但我没有从 API 从 preload.jsindex.html。只要我将 os.cpuUsage 保留在 renderer.js 文档中,该应用程序就可以正常运行,但我无法在 index.html

中更新任何内容

版本: 电子:17.1.2 os-实用程序:0.0.14

// index.js

const { app, BrowserWindow, Menu, Tray, ipcMain } = require("electron");
const os = require("os-utils");
const path = require("path");

const createWindow = () => {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 1000,
    height: 600,
    frame: true,
    autoHideMenuBar: true,
    icon: __dirname + "/icon.ico",
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
    },
  });
};

在这里,我将三个 ipcRenderer.send() 分配给 cpu 键,发送它由 ipcMain.on()

index.js 中获取
// preload.js
const { contextBridge, ipcRenderer } = require("electron");
const os = require("os-utils");

// sending cpu, mem and total-mem from cpuUsage through api channel
const API = {
  cpu: () => {
    os.cpuUsage(function (v) {
      ipcRenderer.send("cpu", v * 100);
      ipcRenderer.send("mem", os.freememPercentage() * 100);
      ipcRenderer.send("total-mem", os.totalmem() / 1024);
    });
  },
};

contextBridge.exposeInMainWorld("api", API);

我正在尝试从频道 cpu mem & total-mem 获取 return 并使用新的统计值更新 index.html 然后我最终每秒调用 cpu 键函数来更新 preload.js 中的值,然后由 index.js 更新为 index.html

// renderer.js

const { app } = require("electron");

app.whenReady().then(() => {
  ipcMain.on("cpu", (event, data) => {
    document.getElementById("cpu").innerHTML = data.toFixed(2);
  });
  ipcMain.on("mem", (event, data) => {
    document.getElementById("mem").innerHTML = data.toFixed(2);
  });
  ipcMain.on("total-mem", (event, data) => {
    document.getElementById("total-mem").innerHTML = data.toFixed(2);
  });

  setInterval(() => {
    window.api.cpu();
  }, 1000);
});
<!-- index.html -->


<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>CPU Stats Monitor</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="box">
      <span class="label">CPU (%)</span>
      <span id="cpu">-</span>
    </div>
    <div class="box">
      <span class="label">Free Mem (%)</span>
      <span id="mem">-</span>
    </div>
    <div class="box">
      <span class="label">Total Mem (GB)</span>
      <span id="total-mem">-</span>
    </div>
    <script src="./renderer.js"></script>
  </body>
</html>

我希望能够获取 os.cpuUsage 数据并通过 ipcRenderer 将其发送给 renderer.js 进行管理和更新到 index.html 但 preload.js。甚至没有 console.log(v)。所以我想根本没有调用 cpu 键。我可以登出

  setInterval(() => {
    window.api.cpu();
  }, 1000);

只要是pos里面的index.js但是那里window就不再定义了。

对 TLDR 进行故障排除;我只能调用 window.api.cpu() ,其中 window 未定义。

我在尝试实现有效 preload.js 脚本的人中看到的一个常见问题是,他们尝试在其中实现具体功能。我更喜欢采用仅使用 preload.js 脚本将可以在主线程和渲染线程之间通信的通道名称列入白名单的方法。通过将您的关注点分离到单独的文件中,它不仅大大简化了 preload.js 脚本的设计、配置和可读性,而且还简化了其余代码。


我认为将 CPU 统计数据的生成移动到主线程中是明智的。然后,这会减轻您的渲染线程的负担 window 以执行其他任务,并将任何常规轮询/繁重的功能放入您的主线程中。

在您的 index.js 文件中:

  1. 创建一个单独的函数来管理您的 cpuStats
  2. 使用setInterval,调用cpuStats函数并通过IPC将结果发送到window

index.js(主线程)

const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;

const nodePath = require("path");

const os = require('os-utils');

let window;

function createWindow() {
    const window = new electronBrowserWindow({
        x: 0,
        y: 0,
        width: 800,
        height: 600,
        show: false,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            preload: nodePath.join(__dirname, 'preload.js')
        }
    });

    window.loadFile('index.html')
        .then(() => { window.show(); });

    return window;
}

function cpuStats() {
    return new Promise((resolve) => {
        os.cpuUsage((value) => {
            let data = {
                'cpu': (value * 100).toFixed(2),
                'mem': (os.freememPercentage() * 100).toFixed(2),
                'totalMem': (os.totalmem() / 1024).toFixed(2)
            }

            resolve(data);
        })
    })
}

electronApp.on('ready', () => {
    window = createWindow();

    setInterval(() => {
        cpuStats()
            .then((data) => {
                window.webContents.send('cpuStats:update', data);
                console.log(data) // Testing
            })
    }, 1000);
});

electronApp.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        electronApp.quit();
    }
});

electronApp.on('activate', () => {
    if (electronBrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

在您的 index.html 文件中,侦听来自主线程的 IPC 消息并相应地更新 DOM。

PS: For the sake of simplicity, I added the Javascript into <script> tags at the bottom of the document.

index.html(渲染线程)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>CPU Stats Monitor</title>
    </head>

    <body>
        <div class="box">
            <span class="label">CPU (%)</span>
            <span id="cpu">-</span>
        </div>

        <div class="box">
            <span class="label">Free Mem (%)</span>
            <span id="mem">-</span>
        </div>

        <div class="box">
            <span class="label">Total Mem (GB)</span>
            <span id="total-mem">-</span>
        </div>
    </body>

    <script>
        let cpu = document.getElementById('cpu');
        let mem = document.getElementById('mem');
        let totalMem = document.getElementById('total-mem');

        window.ipcRender.receive('cpuStats:update', (data) => {
            cpu.innerText = data.cpu;
            mem.innerText = data.mem;
            totalMem.innerText = data.totalMem;

            console.log(data); // Testing
        })
    </script>
</html>

最后,preload.js 脚本的简单“仅通信”用例。

preload.js(主线程)

// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;

// White-listed channels.
const ipc = {
    'render': {
        // From render to main.
        'send': [],
        // From main to render.
        'receive': [
            'cpuStats:update'
        ],
        // From render to main and back again.
        'sendReceive': []
    }
};

// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
    // Allowed 'ipcRenderer' methods.
    'ipcRender', {
        // From render to main.
        send: (channel, args) => {
            let validChannels = ipc.render.send;
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, args);
            }
        },
        // From main to render.
        receive: (channel, listener) => {
            let validChannels = ipc.render.receive;
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender`.
                ipcRenderer.on(channel, (event, ...args) => listener(...args));
            }
        },
        // From render to main and back again.
        invoke: (channel, args) => {
            let validChannels = ipc.render.sendReceive;
            if (validChannels.includes(channel)) {
                return ipcRenderer.invoke(channel, args);
            }
        }
    }
);

了解 Electron 的 Context Isolation, Inter-Process Communications and the render's process contextBridge 是必不可少的。

您的预加载 apiKey(s) 暴露给渲染线程中的 window 对象。

PS: You should be already use to using the window object in normal html / Javascript programming. EG: let cpu = window.getElementById('cpu');.

在这种情况下,公开给 window 对象的 apiKey 包括 ipcRender.sendipcRender.receiveipcRender.invoke。因此,要在渲染线程中使用/访问这些 apiKey 函数,您将分别调用 window.ipcRender.send(...)window.ipcRender.receive(...)window.ipcRender.invoke(...)

现在,例如,让我们仔细看看 ipcRender.send 功能。

// Allowed 'ipcRenderer' methods.
'ipcRender', {
    // From render to main.
    send: (channel, args) => {
        let validChannels = ipc.render.send;
        if (validChannels.includes(channel)) {
            ipcRenderer.send(channel, args);
        }
    },
    ...
}

这个 window.ipcRender.send 函数的函数参数(在渲染线程中使用)是 channelargs

channel 是您希望提供的任何(字符串)名称来标识您的“频道”。如果您熟悉 Node 的 emitter.emit(eventName[,args]),请将“通道”名称视为等同于“eventName”。

在主线程和渲染线程之间传递的

args 可以是任何可以用 Structured Clone Algorithm, specifically these supported types 序列化的东西。尝试发送 Functions、Promises、Symbols、WeakMaps 或 WeakSets 将不起作用并导致异常。

let validChannels = ipc.render.send 从先前声明的 const ipc = {...} 变量中提取列入白名单的频道名称。

最后,if 使用的 channel 名称“包含”在白名单 validChannels 名称列表中,然后调用 Electron 的 ipcRenderer.send(channel, ...args) 函数。

同样适用于其他 2 个函数,receiveinvoke,注意 receive 使用 ipcRenderer.on(channel, listener) function and invoke uses the ipcRenderer.invoke(channel, ...args) 函数。