Electron:如何在同一个 window 中打开一个 url 并在用户执行某些操作后返回同一个 window 中的应用程序

Electron: how to open an url in the same window and after the user do some action go back to application in the same window

我有一个使用 BrowserWindow 显示的 Electron 应用程序。我想在同一个 window 中打开一个外部 URL 以便用户可以登录(到外部网站)并且在用户登录后它应该再次显示 Electron 应用程序而不是外部网站用户用来登录的。

我已经能够在同一个 window 中打开外部 url,方法是:

<a href="https://loginsite-example.com" target="_blank" rel="noreferrer">
  site where you have to log in
</a>

但是,我不知道如何在用户成功登录外部网站后再次显示Electron应用程序。另外,我想保留来自外部网站的会话,以便我可以在电子应用程序中使用它的 API。

在 window 源之间移动,无论是本地(文件)还是远程(URL)都可以通过调用 window.loadFile(...)window.loadURL(...) 来完成,但仅在 window 的实例创建之后。

main.js(主线程)

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

const nodePath = require("path");

// Prevent garbage collection
let window;

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

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

function showLoginWindow() {
    // window.loadURL('https://www.your-site.com/login')
    window.loadFile('login.html') // For testing purposes only
        .then(() => { window.show(); })
}

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

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

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

// ----- IPC -----

electronIpcMain.on('message:loginShow', (event) => {
    showLoginWindow();
})

electronIpcMain.on('message:loginSuccessful', (event, session) => {
    showMainWindow();
})

index.html(渲染线程)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Main Window</title>
    </head>

    <body>
        <div>Main Window</div><hr>

        <button type="button" id="show-login">Login</button>
    </body>

    <script>
        document.getElementById('show-login').addEventListener('click', () => {
            window.ipcRender.send('message:loginShow');
        });
    </script>
</html>

login.html(渲染线程)

仅用于测试目的,因为我们无法访问真实的登录页面。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Login Window</title>
    </head>

    <body>
        <div>Login Window</div><hr>

        <label for="username">Username: </label>
        <input type="text" id="username"><br>

        <label for="password">Password: </label>
        <input type="password" id="password"><br>

        <button type="button" id="login">Login</button>
    </body>

    <script>
        // For testing purposes only.
        document.getElementById('login').addEventListener('click', () => {
            window.ipcRender.send('message:loginSuccessful');
        });
    </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': [
            'message:loginShow',
            'message:loginSuccessful'
        ],
        // From main to render.
        'receive': [],
        // 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);
            }
        }
    }
);

需要克服的一个问题是,一旦您将登录凭据提交给服务器,您将如何获取会话数据?您的 html Javascript 将需要检测必要的会话数据是否可用以及何时可用。一旦会话数据可用,就很容易通过 IPC 将会话从渲染线程传输到主线程。要弄清楚这一点,需要一些额外的信息并了解您的登录系统(一个单独的 Whosebug 问题)。

作为 pre-cursor,我怀疑需要在主线程中检测何时通过 window.webContents.on('did-navigate', ...) 之类的方式将登录页面提交给服务器。一旦检测到,快速检查下一个加载的页面以查看会话是否存在。如果是,获取它,将它发送到主线程,然后重定向回 index.html 页面。

我认为如果您可以通过 API 登录,应该有更简单的方法。那么整个过程就可以在你的Electron应用中self-contained了。 IE:显示本地(文件)login.html,向服务器提交数据并等待“成功”或“失败”响应。如果成功,则在响应中传递会话数据。如果不成功,显示相应的错误消息。