Skip to content

ganeshrvel/tutorial-electron-window-switching

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 

Repository files navigation

Prevent duplicate instances of the same window, invoked by different processes, in an electron app.

The challenge

In an electron app, two or more operating system level processes run concurrently — the "main" and "renderer" processes.

Because these processes are isolated from each other, child window instance, has to be loaded separately for both - main and renderer processes. This phenomenon raises another issue— duplicate windows for the same URL origin

I have spent a significant amount of time researching on preventing the issue of duplication of windows for the same URL origin. This was originally implemented inside OpenMTP - Advanced Android File Transfer Application for macOS.

Implementation:

Here, as an example, I will be creating a privacy policy window which can be invoked by both menu item and as well as an anchor tag.

  • Install packages
$ npm install react-helmet

or

$ yarn add react-helmet
  • Create a file create-windows.js and add the below code
import { BrowserWindow, remote } from 'electron';
const PRIVACY_POLICY_PAGE_TITLE = `Privacy Policy`; // this will be used as an identifier to capture the same browser instance
let privacyPolicyWindow = null;

/**
 * Privacy Policy Window
 */

const undefinedOrNull = _var => {
  return typeof _var === "undefined" || _var === null;
};

const loadExistingWindow = (allWindows, title) => {
  if (!undefinedOrNull(allWindows)) {
    for (let i = 0; i < allWindows.length; i += 1) {
      const item = allWindows[i];
      if (item.getTitle().indexOf(title) !== -1) {
        item.focus();
        item.show();

        return item;
      }
    }
  }

  return null;
};

const createWindow = isRenderedPage => {
  const config = {
    width: 800,
    height: 600,
    minWidth: 600,
    minHeight: 400,
    show: false,
    resizable: true,
    title: `${PRIVACY_POLICY_PAGE_TITLE}`,
    minimizable: true,
    fullscreenable: true,
    webPreferences: {
      nodeIntegration: true
    }
  };

  // incoming call from a rendered page
  if (isRenderedPage) {
    const allWindows = remote.BrowserWindow.getAllWindows();

    return loadExistingWindow(allWindows, PRIVACY_POLICY_PAGE_TITLE)
      ? null
      : new remote.BrowserWindow(config);
  }

  // incoming call from the main process
  const allWindows = BrowserWindow.getAllWindows();

  return loadExistingWindow(allWindows, PRIVACY_POLICY_PAGE_TITLE)
    ? null
    : new BrowserWindow(config);
};

export const privacyPolicyCreateWindow = (isRenderedPage = false) => {
  try {
    if (privacyPolicyWindow) {
      privacyPolicyWindow.focus();
      privacyPolicyWindow.show();
      return privacyPolicyWindow;
    }

    // show the existing privacyPolicyWindow
    const _privacyPolicyWindowTemp = createWindow(isRenderedPage);
    if (!_privacyPolicyWindowTemp) {
      return privacyPolicyWindow;
    }

    privacyPolicyWindow = _privacyPolicyWindowTemp;
    privacyPolicyWindow.loadURL(
      `file://path/to/my-app/app/index.html#privacyPolicyPage`
    ); // @todo: change the path accordingly
	
    privacyPolicyWindow.webContents.on("did-finish-load", () => {
      privacyPolicyWindow.show();
      privacyPolicyWindow.focus();
    });

    privacyPolicyWindow.onerror = error => {
      console.error(error, `createWindows -> privacyPolicyWindow -> onerror`);
    };

    privacyPolicyWindow.on("closed", () => {
      privacyPolicyWindow = null;
    });

    return privacyPolicyWindow;
  } catch (e) {
    console.error(e, `createWindows -> privacyPolicyWindow`);
  }
};
  • Edit your menu.js file
import { privacyPolicyWindow } from './create-windows';

// Add to your menu
submenu: [
	{
		label: 'Privacy Policy from main process',
		click: () => {
			privacyPolicyWindow(false);
		}
	}
]
  • The PrivacyPolicyPage/index.jsx
import React, { Component } from 'react';
import { Helmet } from 'react-helmet';

const PRIVACY_POLICY_PAGE_TITLE = `Privacy Policy`;

class PrivacyPolicyPage extends Component {
  render() {
    return (
      <div>
        <Helmet titleTemplate={`%s`}>
          <title>{PRIVACY_POLICY_PAGE_TITLE}</title>
        </Helmet>
		<div>
			<p>Window body</p>
		</div>
      </div>
    );
  }
}

export default PrivacyPolicyPage;
  • Edit your router file
import PrivacyPolicyPage from './PrivacyPolicyPage';

//Add the route

<Switch>
	<Route
		key={PrivacyPolicyPage}
		path='/privacyPolicyPage'
		exact
		component={PrivacyPolicyPage}
		/>
</Switch>
  • Add an anchor tag inside your home page
import { privacyPolicyWindow } from './create-windows';

<a
	className={styles.a}
	onClick={() => {
	privacyPolicyWindow(true);
	}}
	>
	Privacy Policy from the renderer process
</a>

Conclusion

  • call privacyPolicyCreateWindow(true) from renderer process and call privacyPolicyCreateWindow(false) from the main process.
  • privacyPolicyCreateWindow method will look up for the existing windows which match the title string and it returns the pre-existing window if it finds any.

Clone

$ git clone --depth 1 --single-branch --branch master https://github.com/ganeshrvel/tutorial-electron-window-switching.git

$ cd tutorial-electron-window-switching

Contribute

  • Fork the repo and create your branch from master.
  • Ensure that the changes pass linting.
  • Update the documentation if needed.
  • Make sure your code lints.
  • Issue a pull request!

When you submit code changes, your submissions are understood to be under the same MIT License that covers the project. Feel free to contact the maintainers if that's a concern.

Buy me a coffee

Help me keep the app FREE and open for all. Paypal me: paypal.me/ganeshrvel

Contacts

Please feel free to contact me at ganeshrvel@outlook.com

More repos

License

tutorial-electron-window-switching is released under MIT License.

Copyright © 2018-Present Ganesh Rathinavel

About

Prevent duplicate window instance invoked by the main and renderer processes in an electron app

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published