-
-
Notifications
You must be signed in to change notification settings - Fork 247
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: autostart options when installed through Windows Store (#1231)
- Loading branch information
1 parent
93234a8
commit d00d9f5
Showing
8 changed files
with
350 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export interface AutostartManager { | ||
setAutostartOptions(openAtLogin: boolean): void; | ||
autostartIsEnabled(): boolean; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import type { App } from "electron"; | ||
import { describe, expect, it, vi } from "vitest"; | ||
import { DefaultAutostartManager } from "./DefaultAutostartManager"; | ||
|
||
describe(DefaultAutostartManager, () => { | ||
describe(DefaultAutostartManager.prototype.setAutostartOptions, () => { | ||
const testSetAutostartOptions = ({ openAtLogin }: { openAtLogin: boolean }) => { | ||
const process = <NodeJS.Process>{ execPath: "execPath" }; | ||
const setLoginItemSettingsMock = vi.fn(); | ||
const app = <App>{ setLoginItemSettings: (s) => setLoginItemSettingsMock(s) }; | ||
|
||
new DefaultAutostartManager(app, process).setAutostartOptions(openAtLogin); | ||
|
||
expect(setLoginItemSettingsMock).toHaveBeenCalledOnce(); | ||
expect(setLoginItemSettingsMock).toHaveBeenCalledWith({ args: [], openAtLogin, path: "execPath" }); | ||
}; | ||
|
||
it("should set autostart the correct login items when openAtLogin is true", () => | ||
testSetAutostartOptions({ openAtLogin: true })); | ||
|
||
it("should set autostart the correct login items when openAtLogin is false", () => | ||
testSetAutostartOptions({ openAtLogin: false })); | ||
}); | ||
|
||
describe(DefaultAutostartManager.prototype.autostartIsEnabled, () => { | ||
it("should return true when openAtLogin is true", () => { | ||
const getLoginItemSettingsMock = vi.fn().mockReturnValue({ openAtLogin: true }); | ||
const app = <App>{ getLoginItemSettings: () => getLoginItemSettingsMock() }; | ||
expect(new DefaultAutostartManager(app, <NodeJS.Process>{}).autostartIsEnabled()).toBe(true); | ||
expect(getLoginItemSettingsMock).toHaveBeenCalledOnce(); | ||
}); | ||
|
||
it("should return true when openAtLogin is true", () => { | ||
const getLoginItemSettingsMock = vi.fn().mockReturnValue({ openAtLogin: false }); | ||
const app = <App>{ getLoginItemSettings: () => getLoginItemSettingsMock() }; | ||
expect(new DefaultAutostartManager(app, <NodeJS.Process>{}).autostartIsEnabled()).toBe(false); | ||
expect(getLoginItemSettingsMock).toHaveBeenCalledOnce(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { App } from "electron"; | ||
import type { AutostartManager } from "./AutostartManager"; | ||
|
||
export class DefaultAutostartManager implements AutostartManager { | ||
public constructor( | ||
private readonly app: App, | ||
private readonly process: NodeJS.Process, | ||
) {} | ||
|
||
public setAutostartOptions(openAtLogin: boolean): void { | ||
this.app.setLoginItemSettings({ | ||
args: [], | ||
openAtLogin, | ||
path: this.process.execPath, | ||
}); | ||
} | ||
|
||
public autostartIsEnabled(): boolean { | ||
return this.app.getLoginItemSettings().openAtLogin; | ||
} | ||
} |
202 changes: 202 additions & 0 deletions
202
src/main/Core/Autostart/WindowsStoreAutostartManager.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
import type { FileSystemUtility } from "@Core/FileSystemUtility"; | ||
import type { Logger } from "@Core/Logger"; | ||
import type { App, Shell } from "electron"; | ||
import { join } from "path"; | ||
import { describe, expect, it, vi } from "vitest"; | ||
import { WindowsStoreAutostartManager } from "./WindowsStoreAutostartManager"; | ||
|
||
describe(WindowsStoreAutostartManager, () => { | ||
describe(WindowsStoreAutostartManager.prototype.autostartIsEnabled, () => { | ||
const testAutostartIsEnabled = ({ | ||
execPath, | ||
shortcutFileExists, | ||
shortcutFileReadError, | ||
shortcutTarget, | ||
expected, | ||
}: { | ||
execPath: string; | ||
shortcutFileExists: boolean; | ||
shortcutFileReadError?: string; | ||
shortcutTarget?: string; | ||
expected: boolean; | ||
}) => { | ||
const getPathMock = vi.fn().mockReturnValue("AppData"); | ||
const app = <App>{ getPath: (p) => getPathMock(p) }; | ||
|
||
const existsSyncMock = vi.fn().mockReturnValue(shortcutFileExists); | ||
const fileSystemUtility = <FileSystemUtility>{ existsSync: (p) => existsSyncMock(p) }; | ||
|
||
const readShortcutLinkMock = shortcutFileReadError | ||
? vi.fn().mockImplementationOnce(() => { | ||
throw new Error(shortcutFileReadError); | ||
}) | ||
: vi.fn().mockReturnValue({ target: shortcutTarget }); | ||
|
||
const shell = <Shell>{ readShortcutLink: (p) => readShortcutLinkMock(p) }; | ||
|
||
const logErrorMock = vi.fn(); | ||
const logger = <Logger>{ error: (m) => logErrorMock(m) }; | ||
|
||
const process = <NodeJS.Process>{ execPath }; | ||
|
||
expect( | ||
new WindowsStoreAutostartManager(app, shell, process, fileSystemUtility, logger).autostartIsEnabled(), | ||
).toBe(expected); | ||
|
||
expect(getPathMock).toHaveBeenCalledOnce(); | ||
expect(getPathMock).toHaveBeenCalledWith("appData"); | ||
|
||
const shortcutFilePath = join( | ||
"AppData", | ||
"Microsoft", | ||
"Windows", | ||
"Start Menu", | ||
"Programs", | ||
"Startup", | ||
"Ueli.lnk", | ||
); | ||
|
||
expect(existsSyncMock).toHaveBeenCalledOnce(); | ||
expect(existsSyncMock).toHaveBeenCalledWith(shortcutFilePath); | ||
|
||
if (shortcutFileReadError) { | ||
expect(logErrorMock).toHaveBeenCalledOnce(); | ||
expect(logErrorMock).toHaveBeenCalledWith( | ||
`Failed to read shortcut link "${shortcutFilePath}". Reason: ${new Error(shortcutFileReadError)}`, | ||
); | ||
} | ||
}; | ||
|
||
it("should return false when shortcut file does not exist", () => { | ||
testAutostartIsEnabled({ | ||
shortcutFileExists: false, | ||
expected: false, | ||
execPath: "execPath", | ||
}); | ||
}); | ||
|
||
it("should return false when shortcut file exists but cant be read", () => { | ||
testAutostartIsEnabled({ | ||
shortcutFileExists: true, | ||
expected: false, | ||
shortcutFileReadError: "some error", | ||
execPath: "execPath", | ||
}); | ||
}); | ||
|
||
it("should return false when shortcut file exists but target is not current process exec path", () => { | ||
testAutostartIsEnabled({ | ||
shortcutFileExists: true, | ||
shortcutTarget: "other target", | ||
execPath: "execPath", | ||
expected: false, | ||
}); | ||
}); | ||
|
||
it("should return true when shortcut file exists and target is same as current process exec path", () => { | ||
testAutostartIsEnabled({ | ||
shortcutFileExists: true, | ||
shortcutTarget: "execPath", | ||
execPath: "execPath", | ||
expected: true, | ||
}); | ||
}); | ||
}); | ||
|
||
describe(WindowsStoreAutostartManager.prototype.setAutostartOptions, () => { | ||
const testSetAutostartOptions = ({ | ||
openAtLogin, | ||
shortcutFileExists, | ||
expectedShortcutOperation, | ||
expectShortcutFileRemoval, | ||
}: { | ||
openAtLogin: boolean; | ||
shortcutFileExists: boolean; | ||
expectedShortcutOperation?: "create" | "replace"; | ||
expectShortcutFileRemoval: boolean; | ||
}) => { | ||
const getPathMock = vi.fn().mockReturnValue("AppData"); | ||
const app = <App>{ getPath: (p) => getPathMock(p) }; | ||
|
||
const existsSyncMock = vi.fn().mockReturnValue(shortcutFileExists); | ||
const removeFileSyncMock = vi.fn(); | ||
const fileSystemUtility = <FileSystemUtility>{ | ||
existsSync: (p) => existsSyncMock(p), | ||
removeFileSync: (p) => removeFileSyncMock(p), | ||
}; | ||
|
||
const writeShortcutLinkMock = vi.fn(); | ||
const shell = <Shell>{ | ||
writeShortcutLink: (path, operation, options) => writeShortcutLinkMock(path, operation, options), | ||
}; | ||
|
||
const process = <NodeJS.Process>{ execPath: "execPath" }; | ||
|
||
const shortcutFilePath = join( | ||
"AppData", | ||
"Microsoft", | ||
"Windows", | ||
"Start Menu", | ||
"Programs", | ||
"Startup", | ||
"Ueli.lnk", | ||
); | ||
|
||
new WindowsStoreAutostartManager(app, shell, process, fileSystemUtility, null).setAutostartOptions( | ||
openAtLogin, | ||
); | ||
|
||
expect(getPathMock).toHaveBeenCalledOnce(); | ||
expect(getPathMock).toHaveBeenCalledWith("appData"); | ||
|
||
expect(existsSyncMock).toHaveBeenCalledOnce(); | ||
expect(existsSyncMock).toHaveBeenCalledWith(shortcutFilePath); | ||
|
||
if (expectShortcutFileRemoval) { | ||
expect(removeFileSyncMock).toHaveBeenCalledOnce(); | ||
expect(removeFileSyncMock).toHaveBeenCalledWith(shortcutFilePath); | ||
} | ||
|
||
if (expectedShortcutOperation) { | ||
expect(writeShortcutLinkMock).toHaveBeenCalledOnce(); | ||
expect(writeShortcutLinkMock).toHaveBeenCalledWith(shortcutFilePath, expectedShortcutOperation, { | ||
target: "execPath", | ||
}); | ||
} | ||
}; | ||
|
||
it("should create autostart shortcut when openAtLogin is true and shortcut does not exist yet", () => { | ||
testSetAutostartOptions({ | ||
openAtLogin: true, | ||
shortcutFileExists: false, | ||
expectedShortcutOperation: "create", | ||
expectShortcutFileRemoval: false, | ||
}); | ||
}); | ||
|
||
it("should update autostart shortcut when openAtLogin is true and shortcut already exists", () => { | ||
testSetAutostartOptions({ | ||
openAtLogin: true, | ||
shortcutFileExists: true, | ||
expectedShortcutOperation: "replace", | ||
expectShortcutFileRemoval: false, | ||
}); | ||
}); | ||
|
||
it("should remove shortcut file when openAtLogin is false and shorcut file exists", () => { | ||
testSetAutostartOptions({ | ||
openAtLogin: false, | ||
shortcutFileExists: true, | ||
expectShortcutFileRemoval: true, | ||
}); | ||
}); | ||
|
||
it("should do nothing when openAtLogin is false and shorcut file does notexists", () => { | ||
testSetAutostartOptions({ | ||
openAtLogin: false, | ||
shortcutFileExists: false, | ||
expectShortcutFileRemoval: false, | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import type { FileSystemUtility } from "@Core/FileSystemUtility"; | ||
import type { Logger } from "@Core/Logger"; | ||
import type { App, Shell } from "electron"; | ||
import { join } from "path"; | ||
import type { AutostartManager } from "./AutostartManager"; | ||
|
||
export class WindowsStoreAutostartManager implements AutostartManager { | ||
public constructor( | ||
private readonly app: App, | ||
private readonly shell: Shell, | ||
private readonly process: NodeJS.Process, | ||
private readonly fileSystemUtility: FileSystemUtility, | ||
private readonly logger: Logger, | ||
) {} | ||
|
||
public setAutostartOptions(openAtLogin: boolean): void { | ||
const shortcutFilePath = this.getShortcutFilePath(); | ||
|
||
if (openAtLogin) { | ||
this.createAutostartShortcut(shortcutFilePath); | ||
} else { | ||
this.removeAutostartShortcut(shortcutFilePath); | ||
} | ||
} | ||
|
||
public autostartIsEnabled(): boolean { | ||
const shortcutFilePath = this.getShortcutFilePath(); | ||
|
||
if (!this.fileSystemUtility.existsSync(shortcutFilePath)) { | ||
return false; | ||
} | ||
|
||
try { | ||
const shortcutLink = this.shell.readShortcutLink(shortcutFilePath); | ||
return shortcutLink.target === this.process.execPath; | ||
} catch (error) { | ||
this.logger.error(`Failed to read shortcut link "${shortcutFilePath}". Reason: ${error}`); | ||
return false; | ||
} | ||
} | ||
|
||
private getShortcutFilePath(): string { | ||
return join( | ||
this.app.getPath("appData"), | ||
"Microsoft", | ||
"Windows", | ||
"Start Menu", | ||
"Programs", | ||
"Startup", | ||
"Ueli.lnk", | ||
); | ||
} | ||
|
||
private createAutostartShortcut(shortcutFilePath: string): void { | ||
const shortcutFileExists = this.fileSystemUtility.existsSync(shortcutFilePath); | ||
|
||
this.shell.writeShortcutLink(shortcutFilePath, shortcutFileExists ? "replace" : "create", { | ||
target: this.process.execPath, | ||
}); | ||
} | ||
|
||
private removeAutostartShortcut(shortcutFilePath: string): void { | ||
if (this.fileSystemUtility.existsSync(shortcutFilePath)) { | ||
this.fileSystemUtility.removeFileSync(shortcutFilePath); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.