Skip to content

Commit

Permalink
fix: app should only have one instance (#99)
Browse files Browse the repository at this point in the history
## Motivation

<!-- List motivation and changes here -->

## Issues closed

<!-- List closed issues here -->
  • Loading branch information
pixelass committed Mar 25, 2024
1 parent 6d00844 commit c7e9d11
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 20 deletions.
40 changes: 27 additions & 13 deletions src/electron/background.ts
@@ -1,5 +1,6 @@
import { app } from "electron";

import { isTest } from "#/flags";
import { main } from "@/main";
import logger from "@/services/logger";
import { watchStores } from "@/stores/watchers";
Expand Down Expand Up @@ -56,21 +57,34 @@ import "@/ipc/vector-store";
// Import keys
import "@/ipc/keys";

// Obtain a lock to check if the app is locked (primary instance)
// If false, we close the app, as we want to prevent multiple instances of our app
// https://github.com/electron/electron/blob/v30.0.0-nightly.20240221/docs/api/app.md#apprequestsingleinstancelockadditionaldata
const gotTheLock = app.requestSingleInstanceLock();

let unsubscribe: (() => Promise<void>) | undefined;
// Initialize the application by calling the main function.
// Upon completion, log to the console indicating the application has started.
main().then(() => {
logger.info("Application started successfully");
unsubscribe = watchStores();
});

// Listen for the 'window-all-closed' event on the Electron app object.
// This event is emitted when all windows of the application have been closed.
// In response, quit the application to free up resources, adhering to typical desktop application
// behavior.
app.on("window-all-closed", () => {

if (gotTheLock || isTest) {
main().then(() => {
logger.info("Application started successfully");
unsubscribe = watchStores();
});

// Listen for the 'window-all-closed' event on the Electron app object.
// This event is emitted when all windows of the application have been closed.
// In response, quit the application to free up resources, adhering to typical desktop application
// behavior.
app.on("window-all-closed", () => {
app.quit();
if (unsubscribe) {
unsubscribe();
}
});
} else {
// The app is locked, so we force quit the new instance
console.log("App is locked by another instance. Closing app");
app.quit();
if (unsubscribe) {
unsubscribe();
}
});
}
22 changes: 21 additions & 1 deletion src/electron/future/main.ts
Expand Up @@ -302,7 +302,6 @@ export async function main() {

logger.info(`main(): app is upToDate ${isUpToDate} and ready ${isReady}`);

// Remove the default application menu in production
if (isProduction) {
Menu.setApplicationMenu(null);

Expand Down Expand Up @@ -337,6 +336,17 @@ export async function main() {
logger.info(`main(): listened to :open`);

if (isUpToDate && isReady) {
app.on("second-instance", async () => {
apps.core ||= await createCoreWindow();

if (apps.core) {
if (apps.core.isMinimized()) {
apps.core.restore();
}

apps.core.focus();
}
});
// Start the vector store and fill it with data
await initialize();
await reset();
Expand All @@ -353,6 +363,16 @@ export async function main() {
// Create and show installer window
const installerWindow = await createInstallerWindow();

app.on("second-instance", async () => {
apps.core ||= await createCoreWindow();

if (installerWindow.isMinimized()) {
installerWindow.restore();
}

installerWindow.focus();
});

// When the installer is done we open the prompt window
ipcMain.on(buildKey([ID.APP], { suffix: ":ready" }), async () => {
await runStartup();
Expand Down
3 changes: 3 additions & 0 deletions src/electron/future/utils/__tests__/path-helpers.test.ts
Expand Up @@ -24,6 +24,7 @@ jest.mock("electron", () => ({

jest.mock("#/flags", () => ({
isDevelopment: jest.requireActual("#/flags").isDevelopment,
isTest: jest.requireActual("#/flags").isTest,
}));

describe("Path Utilities", () => {
Expand All @@ -41,6 +42,7 @@ describe("Path Utilities", () => {
it("correctly sets resourcesDirectory in development mode", async () => {
jest.mock("#/flags", () => ({
isDevelopment: true,
isTest: true,
}));
const expectedDevelopmentPath = path.join(process.cwd(), "resources");
const { resourcesDirectory } = await import("../path-helpers");
Expand All @@ -50,6 +52,7 @@ describe("Path Utilities", () => {
it("correctly sets resourcesDirectory in production mode", async () => {
jest.mock("#/flags", () => ({
isDevelopment: false,
isTest: false,
}));

const { resourcesDirectory } = await import("../path-helpers");
Expand Down
9 changes: 5 additions & 4 deletions src/electron/future/utils/path-helpers.ts
Expand Up @@ -6,11 +6,12 @@ import path from "path";

import { app } from "electron";

import { isDevelopment } from "#/flags";
import { isDevelopment, isTest } from "#/flags";

export const resourcesDirectory = isDevelopment
? path.join(process.cwd(), "resources")
: path.join(app.getPath("exe"), "..", "resources", "app.asar.unpacked", "resources");
export const resourcesDirectory =
isTest || isDevelopment
? path.join(process.cwd(), "resources")
: path.join(app.getPath("exe"), "..", "resources", "app.asar.unpacked", "resources");

/**
* Combines the resources directory path with additional subpaths.
Expand Down
5 changes: 3 additions & 2 deletions src/shared/flags.ts
@@ -1,6 +1,7 @@
export const isProduction = process.env.NODE_ENV === "production";
export const isDevelopment =
export const isTest =
process.env.TEST_ENV === "local" ||
process.env.TEST_ENV === "test" ||
process.env.NODE_ENV === "development" ||
process.env.NODE_ENV === "test";

export const isDevelopment = process.env.NODE_ENV === "development";

0 comments on commit c7e9d11

Please sign in to comment.