diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 3952335af48..7944e17375c 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -66,6 +66,7 @@ import { SshAgentPromptType } from "../../autofill/models/ssh-agent-setting"; import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service"; import { DesktopAutotypeService } from "../../autofill/services/desktop-autotype.service"; import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service"; +import { AutoStartService } from "../../platform/auto-start"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { DesktopPremiumUpgradePromptService } from "../../services/desktop-premium-upgrade-prompt.service"; import { NativeMessagingManifestService } from "../services/native-messaging-manifest.service"; @@ -220,6 +221,7 @@ export class SettingsComponent implements OnInit, OnDestroy { private changeDetectorRef: ChangeDetectorRef, private toastService: ToastService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private autoStartService: AutoStartService, ) { this.isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop; this.isLinux = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop; @@ -244,7 +246,9 @@ export class SettingsComponent implements OnInit, OnDestroy { this.startToTrayText = this.i18nService.t(startToTrayKey); this.startToTrayDescText = this.i18nService.t(startToTrayKey + "Desc"); - this.showOpenAtLoginOption = !ipc.platform.isWindowsStore; + // Only show the auto-start setting if it's supported on this platform. + // The service handles platform-specific checks (Windows Store, Snap, etc.) + this.showOpenAtLoginOption = this.autoStartService.shouldDisplaySetting(); // DuckDuckGo browser is only for macos initially this.showDuckDuckGoIntegrationOption = this.isMac; diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 4734288f3c1..404bd7edbc2 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -47,6 +47,7 @@ import { PowerMonitorMain } from "./main/power-monitor.main"; import { TrayMain } from "./main/tray.main"; import { UpdaterMain } from "./main/updater.main"; import { WindowMain } from "./main/window.main"; +import { AutoStartService, AutoStartStatus, DefaultAutoStartService } from "./platform/auto-start"; import { NativeAutofillMain } from "./platform/main/autofill/native-autofill.main"; import { ClipboardMain } from "./platform/main/clipboard.main"; import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener"; @@ -91,6 +92,7 @@ export class Main { sshAgentService: MainSshAgentService; sdkLoadService: SdkLoadService; mainDesktopAutotypeService: MainDesktopAutotypeService; + autoStartService: AutoStartService; constructor() { // Set paths for portable builds @@ -220,7 +222,12 @@ export class Main { this.mainCryptoFunctionService, ); - this.messagingMain = new MessagingMain(this, this.desktopSettingsService); + this.autoStartService = new DefaultAutoStartService(this.logService); + this.messagingMain = new MessagingMain( + this, + this.desktopSettingsService, + this.autoStartService, + ); this.updaterMain = new UpdaterMain(this.i18nService, this.logService, this.windowMain); const messageSubject = new Subject>>(); @@ -323,11 +330,13 @@ export class Main { this.migrationRunner.run().then( async () => { await this.toggleHardwareAcceleration(); - // Reset modal mode to make sure main window is displayed correctly - await this.desktopSettingsService.resetModalMode(); + + // Initialize and reset desktop settings to ensure consistency + await this.initializeDesktopSettings(); + await this.windowMain.init(); await this.i18nService.init(); - await this.messagingMain.init(); + this.messagingMain.init(); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.menuMain.init(); @@ -411,6 +420,29 @@ export class Main { }); } + /** + * Initializes desktop settings to ensure consistency between stored values and system state. + * This includes: + * - Resetting modal mode to prevent the app from being stuck in modal mode after force-close + * - Synchronizing openAtLogin with the actual system auto-start state (when queryable) + */ + private async initializeDesktopSettings(): Promise { + // Reset modal mode to make sure main window is displayed correctly + await this.desktopSettingsService.resetModalMode(); + + // Initialize the openAtLogin setting based on the current system state. + // This allows for cases where the user modifies the system state outside of our application. + const autoStartStatus = await this.autoStartService.isEnabled(); + + // Only sync when we can reliably determine the system state. + // For platforms like Flatpak/Snap where the state is unknown, trust the stored value + // and don't update from the system. + if (autoStartStatus !== AutoStartStatus.Unknown) { + const isEnabled = autoStartStatus === AutoStartStatus.Enabled; + await this.desktopSettingsService.setOpenAtLogin(isEnabled); + } + } + private async toggleHardwareAcceleration(): Promise { const hardwareAcceleration = await firstValueFrom( this.desktopSettingsService.hardwareAcceleration$, diff --git a/apps/desktop/src/main/messaging.main.ts b/apps/desktop/src/main/messaging.main.ts index bc8d9ae4685..b00ad8b75fb 100644 --- a/apps/desktop/src/main/messaging.main.ts +++ b/apps/desktop/src/main/messaging.main.ts @@ -1,16 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import * as fs from "fs"; -import * as path from "path"; - -import { app, ipcMain } from "electron"; +import { ipcMain } from "electron"; import { firstValueFrom } from "rxjs"; -import { autostart } from "@bitwarden/desktop-napi"; - import { Main } from "../main"; +import { AutoStartService } from "../platform/auto-start"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; -import { isFlatpak, isLinux, isSnapStore } from "../utils"; import { MenuUpdateRequest } from "./menu/menu.updater"; @@ -22,19 +17,11 @@ export class MessagingMain { constructor( private main: Main, private desktopSettingsService: DesktopSettingsService, + private autoStartService: AutoStartService, ) {} - async init() { + init() { this.scheduleNextSync(); - if (isLinux()) { - // Flatpak and snap don't have access to or use the startup file. On flatpak, the autostart portal is used - if (!isFlatpak() && !isSnapStore()) { - await this.desktopSettingsService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile())); - } - } else { - const loginSettings = app.getLoginItemSettings(); - await this.desktopSettingsService.setOpenAtLogin(loginSettings.openAtLogin); - } ipcMain.on( "messagingService", async (event: any, message: any) => await this.onMessage(message), @@ -78,10 +65,10 @@ export class MessagingMain { this.main.trayMain.hideToTray(); break; case "addOpenAtLogin": - this.addOpenAtLogin(); + await this.autoStartService.enable(); break; case "removeOpenAtLogin": - this.removeOpenAtLogin(); + await this.autoStartService.disable(); break; case "setFocus": this.setFocus(); @@ -126,49 +113,6 @@ export class MessagingMain { this.main.trayMain.updateContextMenu(); } - private addOpenAtLogin() { - if (process.platform === "linux") { - if (isFlatpak()) { - autostart.setAutostart(true, []).catch((e) => {}); - } else { - const data = `[Desktop Entry] - Type=Application - Version=${app.getVersion()} - Name=Bitwarden - Comment=Bitwarden startup script - Exec=${app.getPath("exe")} - StartupNotify=false - Terminal=false`; - - const dir = path.dirname(this.linuxStartupFile()); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - fs.writeFileSync(this.linuxStartupFile(), data); - } - } else { - app.setLoginItemSettings({ openAtLogin: true }); - } - } - - private removeOpenAtLogin() { - if (process.platform === "linux") { - if (isFlatpak()) { - autostart.setAutostart(false, []).catch((e) => {}); - } else { - if (fs.existsSync(this.linuxStartupFile())) { - fs.unlinkSync(this.linuxStartupFile()); - } - } - } else { - app.setLoginItemSettings({ openAtLogin: false }); - } - } - - private linuxStartupFile(): string { - return path.join(app.getPath("home"), ".config", "autostart", "bitwarden.desktop"); - } - private setFocus() { this.main.trayMain.restoreFromTray(); this.main.windowMain.win.focusOnWebView(); diff --git a/apps/desktop/src/platform/auto-start/auto-start.service.abstraction.ts b/apps/desktop/src/platform/auto-start/auto-start.service.abstraction.ts new file mode 100644 index 00000000000..71440144ffd --- /dev/null +++ b/apps/desktop/src/platform/auto-start/auto-start.service.abstraction.ts @@ -0,0 +1,42 @@ +/** + * Represents the state of the auto-start configuration. + */ +export const AutoStartStatus = { + /** Auto-start is enabled */ + Enabled: "enabled", + /** Auto-start is disabled */ + Disabled: "disabled", + /** Auto-start state cannot be determined (e.g., Flatpak/Snap) */ + Unknown: "unknown", +} as const; + +export type AutoStartStatus = (typeof AutoStartStatus)[keyof typeof AutoStartStatus]; + +/** + * Service for managing the application's auto-start behavior at system login. + */ +export abstract class AutoStartService { + /** + * Enables the application to automatically start when the user logs into their system. + */ + abstract enable(): Promise; + + /** + * Disables the application from automatically starting when the user logs into their system. + */ + abstract disable(): Promise; + + /** + * Checks whether the application is currently configured to start at login. + * @returns The auto-start status: `Enabled`, `Disabled`, or `Unknown` if the state cannot be determined. + */ + abstract isEnabled(): Promise; + + /** + * Determines whether the auto-start setting should be displayed in the application UI. + * Some platforms (e.g., Snap) manage auto-start externally via package configuration, + * so the setting should be hidden from the user. + * @returns `true` if the setting should be shown, `false` if it should be hidden. + */ + abstract shouldDisplaySetting(): boolean; +} diff --git a/apps/desktop/src/platform/auto-start/auto-start.service.spec.ts b/apps/desktop/src/platform/auto-start/auto-start.service.spec.ts new file mode 100644 index 00000000000..a6bdec4ad3d --- /dev/null +++ b/apps/desktop/src/platform/auto-start/auto-start.service.spec.ts @@ -0,0 +1,352 @@ +import * as fs from "fs"; +import * as path from "path"; + +import { app } from "electron"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { autostart } from "@bitwarden/desktop-napi"; + +import * as utils from "../../utils"; + +import { DefaultAutoStartService } from "./auto-start.service"; +import { AutoStartStatus } from "./auto-start.service.abstraction"; + +// Mock modules +jest.mock("fs"); +jest.mock("electron", () => ({ + app: { + getVersion: jest.fn(), + getPath: jest.fn(), + setLoginItemSettings: jest.fn(), + getLoginItemSettings: jest.fn(), + }, +})); +jest.mock("@bitwarden/desktop-napi", () => ({ + autostart: { + setAutostart: jest.fn(), + }, +})); +jest.mock("../../utils", () => ({ + isFlatpak: jest.fn(), + isSnapStore: jest.fn(), + isWindowsStore: jest.fn(), +})); + +describe("DefaultAutoStartService", () => { + let service: DefaultAutoStartService; + let logService: MockProxy; + let originalPlatform: NodeJS.Platform; + + beforeEach(() => { + logService = mock(); + service = new DefaultAutoStartService(logService); + originalPlatform = process.platform; + jest.clearAllMocks(); + + // Default mock implementations + (app.getVersion as jest.Mock).mockReturnValue("1.0.0"); + (app.getPath as jest.Mock).mockImplementation((name: string) => { + if (name === "exe") {return "/usr/bin/bitwarden";} + if (name === "home") {return "/home/user";} + return ""; + }); + (utils.isFlatpak as jest.Mock).mockReturnValue(false); + (utils.isSnapStore as jest.Mock).mockReturnValue(false); + (utils.isWindowsStore as jest.Mock).mockReturnValue(false); + }); + + afterEach(() => { + Object.defineProperty(process, "platform", { + value: originalPlatform, + }); + }); + + describe("Linux (Flatpak)", () => { + beforeEach(() => { + Object.defineProperty(process, "platform", { + value: "linux", + }); + (utils.isFlatpak as jest.Mock).mockReturnValue(true); + (utils.isSnapStore as jest.Mock).mockReturnValue(false); + }); + + it("should enable autostart using the portal", async () => { + (autostart.setAutostart as jest.Mock).mockResolvedValue(undefined); + + await service.enable(); + + expect(autostart.setAutostart).toHaveBeenCalledWith(true, []); + }); + + it("should disable autostart using the portal", async () => { + (autostart.setAutostart as jest.Mock).mockResolvedValue(undefined); + + await service.disable(); + + expect(autostart.setAutostart).toHaveBeenCalledWith(false, []); + }); + + it("should handle portal errors gracefully when enabling", async () => { + const error = new Error("Portal error"); + (autostart.setAutostart as jest.Mock).mockRejectedValue(error); + + await service.enable(); + + expect(logService.error).toHaveBeenCalledWith( + "Failed to enable autostart via portal:", + error, + ); + }); + + it("should handle portal errors gracefully when disabling", async () => { + const error = new Error("Portal error"); + (autostart.setAutostart as jest.Mock).mockRejectedValue(error); + + await service.disable(); + + expect(logService.error).toHaveBeenCalledWith( + "Failed to disable autostart via portal:", + error, + ); + }); + + it("should return Unknown for isEnabled (cannot query portal state)", async () => { + const result = await service.isEnabled(); + + expect(result).toBe(AutoStartStatus.Unknown); + }); + + it("should display setting in UI", () => { + const result = service.shouldDisplaySetting(); + + expect(result).toBe(true); + }); + }); + + describe("Linux (Snap)", () => { + beforeEach(() => { + Object.defineProperty(process, "platform", { + value: "linux", + }); + (utils.isFlatpak as jest.Mock).mockReturnValue(false); + (utils.isSnapStore as jest.Mock).mockReturnValue(true); + }); + + it("should not create desktop file when enabling (snap manages autostart)", async () => { + await service.enable(); + + expect(fs.writeFileSync).not.toHaveBeenCalled(); + expect(fs.mkdirSync).not.toHaveBeenCalled(); + }); + + it("should not remove desktop file when disabling (snap manages autostart)", async () => { + await service.disable(); + + expect(fs.unlinkSync).not.toHaveBeenCalled(); + }); + + it("should return Unknown for isEnabled (snap state cannot be queried)", async () => { + const result = await service.isEnabled(); + + expect(result).toBe(AutoStartStatus.Unknown); + }); + + it("should hide setting from UI (snap manages autostart)", () => { + const result = service.shouldDisplaySetting(); + + expect(result).toBe(false); + }); + }); + + describe("Linux (Standard)", () => { + beforeEach(() => { + Object.defineProperty(process, "platform", { + value: "linux", + }); + (utils.isFlatpak as jest.Mock).mockReturnValue(false); + (utils.isSnapStore as jest.Mock).mockReturnValue(false); + }); + + it("should create desktop file when enabling", async () => { + (fs.existsSync as jest.Mock).mockReturnValue(true); + + await service.enable(); + + const expectedPath = path.join("/home/user", ".config", "autostart", "bitwarden.desktop"); + const expectedContent = `[Desktop Entry] +Type=Application +Version=1.0.0 +Name=Bitwarden +Comment=Bitwarden startup script +Exec=/usr/bin/bitwarden +StartupNotify=false +Terminal=false`; + + expect(fs.writeFileSync).toHaveBeenCalledWith(expectedPath, expectedContent); + }); + + it("should create autostart directory if it does not exist", async () => { + (fs.existsSync as jest.Mock).mockReturnValue(false); + + await service.enable(); + + const expectedDir = path.join("/home/user", ".config", "autostart"); + expect(fs.mkdirSync).toHaveBeenCalledWith(expectedDir, { recursive: true }); + }); + + it("should remove desktop file when disabling", async () => { + (fs.existsSync as jest.Mock).mockReturnValue(true); + + await service.disable(); + + const expectedPath = path.join("/home/user", ".config", "autostart", "bitwarden.desktop"); + expect(fs.unlinkSync).toHaveBeenCalledWith(expectedPath); + }); + + it("should not throw error when removing non-existent desktop file", async () => { + (fs.existsSync as jest.Mock).mockReturnValue(false); + + await expect(service.disable()).resolves.not.toThrow(); + + expect(fs.unlinkSync).not.toHaveBeenCalled(); + }); + + it("should return Enabled when desktop file exists", async () => { + (fs.existsSync as jest.Mock).mockReturnValue(true); + + const result = await service.isEnabled(); + + expect(result).toBe(AutoStartStatus.Enabled); + const expectedPath = path.join("/home/user", ".config", "autostart", "bitwarden.desktop"); + expect(fs.existsSync).toHaveBeenCalledWith(expectedPath); + }); + + it("should return Disabled when desktop file does not exist", async () => { + (fs.existsSync as jest.Mock).mockReturnValue(false); + + const result = await service.isEnabled(); + + expect(result).toBe(AutoStartStatus.Disabled); + }); + + it("should display setting in UI", () => { + const result = service.shouldDisplaySetting(); + + expect(result).toBe(true); + }); + }); + + describe("macOS", () => { + beforeEach(() => { + Object.defineProperty(process, "platform", { + value: "darwin", + }); + }); + + it("should enable autostart using Electron API", async () => { + await service.enable(); + + expect(app.setLoginItemSettings).toHaveBeenCalledWith({ openAtLogin: true }); + }); + + it("should disable autostart using Electron API", async () => { + await service.disable(); + + expect(app.setLoginItemSettings).toHaveBeenCalledWith({ openAtLogin: false }); + }); + + it("should return Enabled when openAtLogin is enabled", async () => { + (app.getLoginItemSettings as jest.Mock).mockReturnValue({ + openAtLogin: true, + openAsHidden: false, + }); + + const result = await service.isEnabled(); + + expect(result).toBe(AutoStartStatus.Enabled); + }); + + it("should return Disabled when openAtLogin is disabled", async () => { + (app.getLoginItemSettings as jest.Mock).mockReturnValue({ + openAtLogin: false, + openAsHidden: false, + }); + + const result = await service.isEnabled(); + + expect(result).toBe(AutoStartStatus.Disabled); + }); + + it("should display setting in UI", () => { + const result = service.shouldDisplaySetting(); + + expect(result).toBe(true); + }); + }); + + describe("Windows", () => { + beforeEach(() => { + Object.defineProperty(process, "platform", { + value: "win32", + }); + (utils.isFlatpak as jest.Mock).mockReturnValue(false); + (utils.isSnapStore as jest.Mock).mockReturnValue(false); + }); + + it("should enable autostart using Electron API", async () => { + await service.enable(); + + expect(app.setLoginItemSettings).toHaveBeenCalledWith({ openAtLogin: true }); + }); + + it("should disable autostart using Electron API", async () => { + await service.disable(); + + expect(app.setLoginItemSettings).toHaveBeenCalledWith({ openAtLogin: false }); + }); + + it("should return Enabled when openAtLogin is enabled", async () => { + (app.getLoginItemSettings as jest.Mock).mockReturnValue({ + openAtLogin: true, + openAsHidden: false, + }); + + const result = await service.isEnabled(); + + expect(result).toBe(AutoStartStatus.Enabled); + }); + + it("should return Disabled when openAtLogin is disabled", async () => { + (app.getLoginItemSettings as jest.Mock).mockReturnValue({ + openAtLogin: false, + openAsHidden: false, + }); + + const result = await service.isEnabled(); + + expect(result).toBe(AutoStartStatus.Disabled); + }); + + it("should display setting in UI", () => { + const result = service.shouldDisplaySetting(); + + expect(result).toBe(true); + }); + }); + + describe("Windows Store", () => { + beforeEach(() => { + Object.defineProperty(process, "platform", { + value: "win32", + }); + (utils.isWindowsStore as jest.Mock).mockReturnValue(true); + }); + + it("should hide setting from UI (Windows Store doesn't support auto-start)", () => { + const result = service.shouldDisplaySetting(); + + expect(result).toBe(false); + }); + }); +}); diff --git a/apps/desktop/src/platform/auto-start/auto-start.service.ts b/apps/desktop/src/platform/auto-start/auto-start.service.ts new file mode 100644 index 00000000000..cdc9362e0e9 --- /dev/null +++ b/apps/desktop/src/platform/auto-start/auto-start.service.ts @@ -0,0 +1,130 @@ +import * as fs from "fs"; +import * as path from "path"; + +import { app } from "electron"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { autostart } from "@bitwarden/desktop-napi"; + +import { isFlatpak, isSnapStore, isWindowsStore } from "../../utils"; + +import { AutoStartService, AutoStartStatus } from "./auto-start.service.abstraction"; + +/** + * Default implementation of the AutoStartService for managing desktop auto-start behavior. + * + * The implementation varies by platform: + * - **Linux (Flatpak)**: Uses the XDG autostart portal via desktop-napi + * - **Linux (Standard)**: Creates a .desktop file in ~/.config/autostart/ + * - **Linux (Snap)**: Auto-start is managed by snap configuration (not handled here) + * - **macOS/Windows**: Uses Electron's app.setLoginItemSettings() API + */ +export class DefaultAutoStartService implements AutoStartService { + constructor(private logService: LogService) {} + + async enable(): Promise { + if (process.platform === "linux") { + if (isFlatpak()) { + // Use the XDG autostart portal for Flatpak + await autostart.setAutostart(true, []).catch((e) => { + this.logService.error("Failed to enable autostart via portal:", e); + }); + } else if (!isSnapStore()) { + // For standard Linux, create a .desktop file in autostart directory + // Snap auto-start is configured via electron-builder snap configuration + this.createDesktopFile(); + } + } else { + // macOS and Windows use Electron's native API + app.setLoginItemSettings({ openAtLogin: true }); + } + } + + async disable(): Promise { + if (process.platform === "linux") { + if (isFlatpak()) { + // Use the XDG autostart portal for Flatpak + await autostart.setAutostart(false, []).catch((e) => { + this.logService.error("Failed to disable autostart via portal:", e); + }); + } else if (!isSnapStore()) { + // For standard Linux, remove the .desktop file + // Snap auto-start is configured via electron-builder snap configuration + this.removeDesktopFile(); + } + } else { + // macOS and Windows use Electron's native API + app.setLoginItemSettings({ openAtLogin: false }); + } + } + + async isEnabled(): Promise { + if (process.platform === "linux") { + if (isFlatpak() || isSnapStore()) { + // For Flatpak/Snap, we can't reliably check the state from within the app. + // The autostart portal (Flatpak) and snap configuration don't provide query APIs. + return AutoStartStatus.Unknown; + } else { + // For standard Linux, check if the desktop file exists. + return fs.existsSync(this.getLinuxDesktopFilePath()) + ? AutoStartStatus.Enabled + : AutoStartStatus.Disabled; + } + } else { + // macOS and Windows use Electron's native API. + const loginSettings = app.getLoginItemSettings(); + return loginSettings.openAtLogin ? AutoStartStatus.Enabled : AutoStartStatus.Disabled; + } + } + + /** + * Creates the .desktop file for Linux autostart. + */ + private createDesktopFile(): void { + const desktopFileContent = `[Desktop Entry] +Type=Application +Version=${app.getVersion()} +Name=Bitwarden +Comment=Bitwarden startup script +Exec=${app.getPath("exe")} +StartupNotify=false +Terminal=false`; + + const filePath = this.getLinuxDesktopFilePath(); + const dir = path.dirname(filePath); + + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(filePath, desktopFileContent); + } + + /** + * Removes the .desktop file for Linux autostart. + */ + private removeDesktopFile(): void { + const filePath = this.getLinuxDesktopFilePath(); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + } + + shouldDisplaySetting(): boolean { + // Windows Store apps don't support auto-start functionality. + // On Snap, auto-start is managed by the snap configuration (electron-builder.json). + if (isWindowsStore() || isSnapStore()) { + return false; + } + + // All other platforms support user-configurable auto-start + return true; + } + + /** + * Gets the path to the Linux autostart .desktop file. + */ + private getLinuxDesktopFilePath(): string { + return path.join(app.getPath("home"), ".config", "autostart", "bitwarden.desktop"); + } +} diff --git a/apps/desktop/src/platform/auto-start/index.ts b/apps/desktop/src/platform/auto-start/index.ts new file mode 100644 index 00000000000..13d748b10f3 --- /dev/null +++ b/apps/desktop/src/platform/auto-start/index.ts @@ -0,0 +1,2 @@ +export { AutoStartService, AutoStartStatus } from "./auto-start.service.abstraction"; +export { DefaultAutoStartService } from "./auto-start.service";