refactor(main): organize main structure by setchy · Pull Request #2678 · gitify-app/gitify
39 changes: 39 additions & 0 deletions src/main/config.test.ts
This file contains hidden or 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,39 @@ | ||
| import { Paths, WindowConfig } from './config'; | ||
|
|
||
| vi.mock('./utils', () => ({ | ||
| isDevMode: vi.fn().mockReturnValue(false), | ||
| })); | ||
|
|
||
| vi.mock('electron', () => ({ | ||
| app: { | ||
| isPackaged: true, | ||
| }, | ||
| })); | ||
|
|
||
| describe('main/config.ts', () => { | ||
| it('exports Paths object with expected properties', () => { | ||
| expect(Paths.preload).toBeDefined(); | ||
| expect(Paths.preload).toContain('preload.js'); | ||
|
|
||
| expect(Paths.indexHtml).toBeDefined(); | ||
| expect(Paths.indexHtml).toContain('index.html'); | ||
|
|
||
| expect(Paths.notificationSound).toBeDefined(); | ||
| expect(Paths.notificationSound).toContain('.mp3'); | ||
|
|
||
| expect(Paths.twemojiFolder).toBeDefined(); | ||
| expect(Paths.twemojiFolder).toContain('twemoji'); | ||
| }); | ||
|
|
||
| it('exports WindowConfig with expected properties', () => { | ||
| expect(WindowConfig.width).toBe(500); | ||
| expect(WindowConfig.height).toBe(400); | ||
| expect(WindowConfig.minWidth).toBe(500); | ||
| expect(WindowConfig.minHeight).toBe(400); | ||
| expect(WindowConfig.resizable).toBe(false); | ||
| expect(WindowConfig.skipTaskbar).toBe(true); | ||
| expect(WindowConfig.webPreferences).toBeDefined(); | ||
| expect(WindowConfig.webPreferences.contextIsolation).toBe(true); | ||
| expect(WindowConfig.webPreferences.nodeIntegration).toBe(false); | ||
| }); | ||
| }); |
56 changes: 56 additions & 0 deletions src/main/config.ts
This file contains hidden or 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,56 @@ | ||
| import path from 'node:path'; | ||
| import { pathToFileURL } from 'node:url'; | ||
|
|
||
| import type { BrowserWindowConstructorOptions } from 'electron'; | ||
|
|
||
| import { APPLICATION } from '../shared/constants'; | ||
|
|
||
| import { isDevMode } from './utils'; | ||
|
|
||
| /** | ||
| * Resolved file-system and URL paths used throughout the main process. | ||
| */ | ||
| export const Paths = { | ||
| preload: path.resolve(__dirname, 'preload.js'), | ||
|
|
||
| get indexHtml(): string { | ||
| return isDevMode() | ||
| ? process.env.VITE_DEV_SERVER_URL || '' | ||
| : pathToFileURL(path.resolve(__dirname, 'index.html')).href; | ||
| }, | ||
|
|
||
| get notificationSound(): string { | ||
| return pathToFileURL( | ||
| path.resolve( | ||
| __dirname, | ||
| 'assets', | ||
| 'sounds', | ||
| APPLICATION.NOTIFICATION_SOUND, | ||
| ), | ||
| ).href; | ||
| }, | ||
|
|
||
| get twemojiFolder(): string { | ||
| return pathToFileURL(path.resolve(__dirname, 'assets', 'images', 'twemoji')) | ||
| .href; | ||
| }, | ||
| }; | ||
|
|
||
| /** | ||
| * Default browser window construction options for the menubar popup. | ||
| */ | ||
| export const WindowConfig: BrowserWindowConstructorOptions = { | ||
| width: 500, | ||
| height: 400, | ||
| minWidth: 500, | ||
| minHeight: 400, | ||
| resizable: false, | ||
| skipTaskbar: true, // Hide the app from the Windows taskbar | ||
| webPreferences: { | ||
| preload: Paths.preload, | ||
| contextIsolation: true, | ||
| nodeIntegration: false, | ||
| // Disable web security in development to allow CORS requests | ||
| webSecurity: !process.env.VITE_DEV_SERVER_URL, | ||
| }, | ||
| }; |
8 changes: 8 additions & 0 deletions src/main/events.test.ts
This file contains hidden or 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
25 changes: 15 additions & 10 deletions src/main/events.ts
This file contains hidden or 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
63 changes: 63 additions & 0 deletions src/main/handlers/app.test.ts
This file contains hidden or 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,63 @@ | ||
| import type { Menubar } from 'menubar'; | ||
|
|
||
| import { EVENTS } from '../../shared/events'; | ||
|
|
||
| import { registerAppHandlers } from './app'; | ||
|
|
||
| const handleMock = vi.fn(); | ||
| const onMock = vi.fn(); | ||
|
|
||
| vi.mock('electron', () => ({ | ||
| ipcMain: { | ||
| handle: (...args: unknown[]) => handleMock(...args), | ||
| on: (...args: unknown[]) => onMock(...args), | ||
| }, | ||
| app: { | ||
| getVersion: vi.fn(() => '1.0.0'), | ||
| }, | ||
| })); | ||
|
|
||
| vi.mock('../config', () => ({ | ||
| Paths: { | ||
| notificationSound: 'file:///path/to/notification.mp3', | ||
| twemojiFolder: 'file:///path/to/twemoji', | ||
| }, | ||
| })); | ||
|
|
||
| describe('main/handlers/app.ts', () => { | ||
| let menubar: Menubar; | ||
|
|
||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
|
|
||
| menubar = { | ||
| showWindow: vi.fn(), | ||
| hideWindow: vi.fn(), | ||
| app: { quit: vi.fn() }, | ||
| } as unknown as Menubar; | ||
| }); | ||
|
|
||
| describe('registerAppHandlers', () => { | ||
| it('registers handlers without throwing', () => { | ||
| expect(() => registerAppHandlers(menubar)).not.toThrow(); | ||
| }); | ||
|
|
||
| it('registers expected app IPC event handlers', () => { | ||
| registerAppHandlers(menubar); | ||
|
|
||
| const registeredHandlers = handleMock.mock.calls.map( | ||
| (call: [string]) => call[0], | ||
| ); | ||
| const registeredEvents = onMock.mock.calls.map( | ||
| (call: [string]) => call[0], | ||
| ); | ||
|
|
||
| expect(registeredHandlers).toContain(EVENTS.VERSION); | ||
| expect(registeredHandlers).toContain(EVENTS.NOTIFICATION_SOUND_PATH); | ||
| expect(registeredHandlers).toContain(EVENTS.TWEMOJI_DIRECTORY); | ||
| expect(registeredEvents).toContain(EVENTS.WINDOW_SHOW); | ||
| expect(registeredEvents).toContain(EVENTS.WINDOW_HIDE); | ||
| expect(registeredEvents).toContain(EVENTS.QUIT); | ||
| }); | ||
| }); | ||
| }); |
31 changes: 31 additions & 0 deletions src/main/handlers/app.ts
This file contains hidden or 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,31 @@ | ||
| import { app } from 'electron'; | ||
| import type { Menubar } from 'menubar'; | ||
|
|
||
| import { EVENTS } from '../../shared/events'; | ||
|
|
||
| import { Paths } from '../config'; | ||
| import { handleMainEvent, onMainEvent } from '../events'; | ||
|
|
||
| /** | ||
| * Register IPC handlers for general application queries and window/app control. | ||
| * | ||
| * @param mb - The menubar instance used for window visibility and app quit control. | ||
| */ | ||
| export function registerAppHandlers(mb: Menubar): void { | ||
| handleMainEvent(EVENTS.VERSION, () => app.getVersion()); | ||
|
|
||
| onMainEvent(EVENTS.WINDOW_SHOW, () => mb.showWindow()); | ||
|
|
||
| onMainEvent(EVENTS.WINDOW_HIDE, () => mb.hideWindow()); | ||
|
|
||
| onMainEvent(EVENTS.QUIT, () => mb.app.quit()); | ||
|
|
||
| // Path handlers for renderer queries about resource locations | ||
| handleMainEvent(EVENTS.NOTIFICATION_SOUND_PATH, () => { | ||
| return Paths.notificationSound; | ||
| }); | ||
|
|
||
| handleMainEvent(EVENTS.TWEMOJI_DIRECTORY, () => { | ||
| return Paths.twemojiFolder; | ||
| }); | ||
| } |
4 changes: 4 additions & 0 deletions src/main/handlers/index.ts
This file contains hidden or 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 * from './app'; | ||
| export * from './storage'; | ||
| export * from './system'; | ||
| export * from './tray'; |
43 changes: 43 additions & 0 deletions src/main/handlers/storage.test.ts
This file contains hidden or 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,43 @@ | ||
| import { EVENTS } from '../../shared/events'; | ||
|
|
||
| import { registerStorageHandlers } from './storage'; | ||
|
|
||
| const handleMock = vi.fn(); | ||
|
|
||
| vi.mock('electron', () => ({ | ||
| ipcMain: { | ||
| handle: (...args: unknown[]) => handleMock(...args), | ||
| }, | ||
| safeStorage: { | ||
| encryptString: vi.fn((str: string) => Buffer.from(str)), | ||
| decryptString: vi.fn((buf: Buffer) => buf.toString()), | ||
| }, | ||
| })); | ||
|
|
||
| const logErrorMock = vi.fn(); | ||
| vi.mock('../../shared/logger', () => ({ | ||
| logError: (...args: unknown[]) => logErrorMock(...args), | ||
| })); | ||
|
|
||
| describe('main/handlers/storage.ts', () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| describe('registerStorageHandlers', () => { | ||
| it('registers handlers without throwing', () => { | ||
| expect(() => registerStorageHandlers()).not.toThrow(); | ||
| }); | ||
|
|
||
| it('registers expected storage IPC event handlers', () => { | ||
| registerStorageHandlers(); | ||
|
|
||
| const registeredHandlers = handleMock.mock.calls.map( | ||
| (call: [string]) => call[0], | ||
| ); | ||
|
|
||
| expect(registeredHandlers).toContain(EVENTS.SAFE_STORAGE_ENCRYPT); | ||
| expect(registeredHandlers).toContain(EVENTS.SAFE_STORAGE_DECRYPT); | ||
| }); | ||
| }); | ||
| }); |
34 changes: 34 additions & 0 deletions src/main/handlers/storage.ts
This file contains hidden or 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,34 @@ | ||
| import { safeStorage } from 'electron'; | ||
|
|
||
| import { EVENTS } from '../../shared/events'; | ||
| import { logError } from '../../shared/logger'; | ||
|
|
||
| import { handleMainEvent } from '../events'; | ||
|
|
||
| /** | ||
| * Register IPC handlers for OS-level safe storage operations. | ||
| */ | ||
| export function registerStorageHandlers(): void { | ||
| /** | ||
| * Encrypt a string using Electron's safeStorage and return the encrypted value as a base64 string. | ||
| */ | ||
| handleMainEvent(EVENTS.SAFE_STORAGE_ENCRYPT, (_, value: string) => { | ||
| return safeStorage.encryptString(value).toString('base64'); | ||
| }); | ||
|
|
||
| /** | ||
| * Decrypt a base64-encoded string using Electron's safeStorage and return the decrypted value. | ||
| */ | ||
| handleMainEvent(EVENTS.SAFE_STORAGE_DECRYPT, (_, value: string) => { | ||
| try { | ||
| return safeStorage.decryptString(Buffer.from(value, 'base64')); | ||
| } catch (err) { | ||
| logError( | ||
| 'main:safe-storage-decrypt', | ||
| 'Failed to decrypt value - data may be from old build', | ||
| err, | ||
| ); | ||
| throw err; | ||
| } | ||
| }); | ||
| } |