/ src / preload.ts
preload.ts
  1  import { contextBridge, ipcRenderer } from 'electron';
  2  import type {
  3    ChangedFile,
  4    GenerateReviewRequest,
  5    Preferences,
  6    PrSearchResult,
  7    PrStatus,
  8    ReviewGuide,
  9    ReviewHistoryEntry,
 10    SendSlideChatRequest,
 11    StartReviewResult,
 12    SubmitReviewRequest,
 13    FreshnessResult,
 14    UpdateInfo,
 15  } from '../lib/types';
 16  
 17  contextBridge.exposeInMainWorld('electronAPI', {
 18    startReview: (req: GenerateReviewRequest): Promise<StartReviewResult> => ipcRenderer.invoke('start-review', req),
 19    cancelReview: (reviewId: string): Promise<void> => ipcRenderer.invoke('cancel-review', reviewId),
 20    getConfig: (): Promise<{ githubToken: string | null }> => ipcRenderer.invoke('get-config'),
 21    startOAuth: (): Promise<void> => ipcRenderer.invoke('start-oauth'),
 22    getAuthState: (): Promise<{ authenticated: boolean; login: string | null }> => ipcRenderer.invoke('get-auth-state'),
 23    signOut: (): Promise<void> => ipcRenderer.invoke('sign-out'),
 24    savePat: (token: string): Promise<string> => ipcRenderer.invoke('save-pat', token),
 25    listReviews: (): Promise<ReviewHistoryEntry[]> => ipcRenderer.invoke('list-reviews'),
 26    loadReview: (id: string): Promise<ReviewGuide> => ipcRenderer.invoke('load-review', id),
 27    deleteReview: (id: string): Promise<void> => ipcRenderer.invoke('delete-review', id),
 28    deleteAllReviews: (): Promise<void> => ipcRenderer.invoke('delete-all-reviews'),
 29    onReviewProgress: (callback: (reviewId: string, chunk: string, isThinking: boolean) => void): void => {
 30      ipcRenderer.on(
 31        'review-progress',
 32        (_event, { reviewId, chunk, isThinking }: { reviewId: string; chunk: string; isThinking: boolean }) =>
 33          callback(reviewId, chunk, isThinking)
 34      );
 35    },
 36    offReviewProgress: (): void => {
 37      ipcRenderer.removeAllListeners('review-progress');
 38    },
 39    onReviewToolUse: (callback: (reviewId: string, toolName: string) => void): void => {
 40      ipcRenderer.on('review-tool-use', (_event, { reviewId, toolName }: { reviewId: string; toolName: string }) =>
 41        callback(reviewId, toolName)
 42      );
 43    },
 44    offReviewToolUse: (): void => {
 45      ipcRenderer.removeAllListeners('review-tool-use');
 46    },
 47    onReviewPhase: (callback: (reviewId: string, phase: string) => void): void => {
 48      ipcRenderer.on('review-phase', (_event, { reviewId, phase }: { reviewId: string; phase: string }) =>
 49        callback(reviewId, phase)
 50      );
 51    },
 52    offReviewPhase: (): void => {
 53      ipcRenderer.removeAllListeners('review-phase');
 54    },
 55    onReviewCompleted: (callback: (reviewId: string) => void): void => {
 56      ipcRenderer.on('review-completed', (_event, { reviewId }: { reviewId: string }) => callback(reviewId));
 57    },
 58    offReviewCompleted: (): void => {
 59      ipcRenderer.removeAllListeners('review-completed');
 60    },
 61    onReviewFailed: (callback: (reviewId: string, error: string) => void): void => {
 62      ipcRenderer.on('review-failed', (_event, { reviewId, error }: { reviewId: string; error: string }) =>
 63        callback(reviewId, error)
 64      );
 65    },
 66    offReviewFailed: (): void => {
 67      ipcRenderer.removeAllListeners('review-failed');
 68    },
 69    onReviewStats: (callback: (reviewId: string, inputBytes: number) => void): void => {
 70      ipcRenderer.on('review-stats', (_event, { reviewId, inputBytes }: { reviewId: string; inputBytes: number }) =>
 71        callback(reviewId, inputBytes)
 72      );
 73    },
 74    offReviewStats: (): void => {
 75      ipcRenderer.removeAllListeners('review-stats');
 76    },
 77    onReviewNavigate: (callback: (reviewId: string) => void): void => {
 78      ipcRenderer.on('review-navigate', (_event, { reviewId }: { reviewId: string }) => callback(reviewId));
 79    },
 80    offReviewNavigate: (): void => {
 81      ipcRenderer.removeAllListeners('review-navigate');
 82    },
 83    sendSlideChat: (req: SendSlideChatRequest): Promise<string> => ipcRenderer.invoke('send-slide-chat', req),
 84    onChatProgress: (callback: (chunk: string) => void): void => {
 85      ipcRenderer.on('chat-progress', (_event, { chunk }: { chunk: string }) => callback(chunk));
 86    },
 87    offChatProgress: (): void => {
 88      ipcRenderer.removeAllListeners('chat-progress');
 89    },
 90    onChatToolUse: (callback: (toolName: string) => void): void => {
 91      ipcRenderer.on('chat-tool-use', (_event, { toolName }: { toolName: string }) => callback(toolName));
 92    },
 93    offChatToolUse: (): void => {
 94      ipcRenderer.removeAllListeners('chat-tool-use');
 95    },
 96    submitReview: (req: SubmitReviewRequest): Promise<{ reviewUrl: string; droppedCommentCount: number }> =>
 97      ipcRenderer.invoke('submit-review', req),
 98    checkPrFreshness: (prUrl: string, headSha: string | undefined): Promise<FreshnessResult> =>
 99      ipcRenderer.invoke('check-pr-freshness', prUrl, headSha),
100    loadPreferences: (): Promise<Preferences> => ipcRenderer.invoke('load-preferences'),
101    savePreferences: (prefs: Preferences): Promise<void> => ipcRenderer.invoke('save-preferences', prefs),
102    searchPullRequests: (): Promise<PrSearchResult[]> => ipcRenderer.invoke('search-pull-requests'),
103    reRenderHunks: (review: ReviewGuide): Promise<ReviewGuide> => ipcRenderer.invoke('re-render-hunks', review),
104    getPrStatus: (prUrl: string): Promise<PrStatus> => ipcRenderer.invoke('get-pr-status', prUrl),
105    onUpdateAvailable: (callback: (info: UpdateInfo) => void): void => {
106      ipcRenderer.on('update-available', (_event, info: UpdateInfo) => callback(info));
107    },
108    offUpdateAvailable: (): void => {
109      ipcRenderer.removeAllListeners('update-available');
110    },
111    onUpdateReady: (callback: (version: string) => void): void => {
112      ipcRenderer.on('update-ready', (_event, version: string) => callback(version));
113    },
114    offUpdateReady: (): void => {
115      ipcRenderer.removeAllListeners('update-ready');
116    },
117    dismissUpdate: (version: string): Promise<void> => ipcRenderer.invoke('dismiss-update', version),
118    openExternal: (url: string): Promise<void> => ipcRenderer.invoke('open-external', url),
119    openLogsDirectory: (): Promise<void> => ipcRenderer.invoke('open-logs-directory'),
120    openReviewPrompt: (id: string): Promise<void> => ipcRenderer.invoke('open-review-prompt', id),
121    detectBinaryPath: (name: string): Promise<string> => ipcRenderer.invoke('detect-binary-path', name),
122    checkCliInstalled: (provider: string): Promise<{ installed: boolean; resolvedPath: string }> =>
123      ipcRenderer.invoke('check-cli-installed', provider),
124    onNewReviewInHistory: (callback: () => void): void => {
125      ipcRenderer.on('new-review-in-history', () => callback());
126    },
127    offNewReviewInHistory: (): void => {
128      ipcRenderer.removeAllListeners('new-review-in-history');
129    },
130    markReviewRead: (id: string): Promise<void> => ipcRenderer.invoke('mark-review-read', id),
131    getPrState: (prUrl: string): Promise<{ prState: 'open' | 'merged' | 'closed'; headSha: string }> =>
132      ipcRenderer.invoke('get-pr-state', prUrl),
133    getPrFiles: (prUrl: string): Promise<ChangedFile[]> => ipcRenderer.invoke('get-pr-files', prUrl),
134    platform: process.platform,
135    isPackaged: process.env.APP_IS_PACKAGED === '1',
136  });