index.ts5.3 KBView on GitHub
import { contextBridge, ipcRenderer } from 'electron'
import { IPC } from '@/main/ipc/channels'

const recordingBridge = {
  meetingRecorder: {
    // renderer → main (invoke)
    startRecording: (windowId: string, uploadToken=[redacted], sdkUploadId?: string): Promise<void> =>
      ipcRenderer.invoke(IPC.capture.start, windowId, uploadToken, sdkUploadId),
    stopRecording: (): Promise<void> =>
      ipcRenderer.invoke(IPC.capture.stop),
    getStatus: (): Promise<{
      isRecording: boolean
      permissionStatus: 'granted' | 'denied' | 'not-requested'
      detectedMeeting: { windowId: string; platform: string; title: string | null; url: string | null } | null
      permissions: { microphone: string; screen: string; accessibility: boolean }
    }> => ipcRenderer.invoke(IPC.capture.getStatus),
    requestPermissions: (): Promise<void> =>
      ipcRenderer.invoke(IPC.capture.requestPermissions),
    getCalendarDebug: (): Promise<Record<string, unknown>> =>
      ipcRenderer.invoke(IPC.capture.calendarDebug),

    // main → renderer (listen)
    onMeetingDetected: (cb: (data: { windowId: string; platform: string; title: string | null; url: string | null }) => void): void => {
      ipcRenderer.on(IPC.capture.meetingDetected, (_event, data) => cb(data))
    },
    onMeetingEnded: (cb: () => void): void => {
      ipcRenderer.on(IPC.capture.meetingEnded, () => cb())
    },
    onRecordingStatus: (cb: (data: { isRecording: boolean }) => void): void => {
      ipcRenderer.on(IPC.capture.recordingStatus, (_event, data) => cb(data))
    },
    // main → renderer: main process asks renderer to get an upload token via tRPC
    onPermissionsGranted: (cb: () => void): void => {
      ipcRenderer.on(IPC.capture.permissionsGranted, () => cb())
    },
    onUploadTokenRequest: (cb: () => void): void => {
      ipcRenderer.on(IPC.capture.requestUploadToken, () => cb())
    },
    // renderer → main: respond with the upload token and sdkUploadId
    respondUploadToken=[redacted] { uploadToken=[redacted]; sdkUploadId: string } | { error: string }): void => {
      ipcRenderer.invoke(IPC.capture.requestUploadToken, result)
    },

    // renderer → main: push upcoming calendar events with meeting info
    pushUpcomingEvents: (events: Array<{
      eventId: string; title: string; startTime: string; endTime: string;
      meetingUrl: string | null; platform: string | null; calendarEmail: string | null
    }>): void => {
      ipcRenderer.send(IPC.calendar.upcomingEvents, events)
    },
    // main → renderer: listen for calendar poll requests
    onPollRequest: (cb: () => void): void => {
      ipcRenderer.on(IPC.calendar.requestPoll, () => cb())
    },
    startManualSession: (): Promise<{ sessionId: string }> =>
      ipcRenderer.invoke(IPC.callUI.startManual),
  },

  callUI: {
    // main → renderer: open/close the call UI blocking modal
    onOpen: (cb: (data: { uploadId: string | null; title: string | null; eventId: string | null; meetingUrl: string | null }) => void): (() => void) => {
      const listener = (_event: Electron.IpcRendererEvent, data: Parameters<typeof cb>[0]) => cb(data)
      ipcRenderer.on(IPC.callUI.openModal, listener)
      return () => ipcRenderer.removeListener(IPC.callUI.openModal, listener)
    },
    onClose: (cb: () => void): (() => void) => {
      const listener = () => cb()
      ipcRenderer.on(IPC.callUI.closeModal, listener)
      return () => ipcRenderer.removeListener(IPC.callUI.closeModal, listener)
    },
    // main → renderer: realtime data
    onRealtimeEvent: (cb: (data: unknown) => void): void => {
      ipcRenderer.on(IPC.callUI.realtimeEvent, (_event, data) => cb(data))
    },
    onFlagsUpdate: (cb: (flags: unknown) => void): void => {
      ipcRenderer.on(IPC.callUI.flagsUpdate, (_event, flags) => cb(flags))
    },
    onRecordingState: (cb: (state: { isPaused: boolean; isRecording: boolean }) => void): void => {
      ipcRenderer.on(IPC.callUI.recordingState, (_event, state) => cb(state))
    },
    onGlobalFlag: (cb: (type: string) => void): void => {
      ipcRenderer.removeAllListeners(IPC.callUI.globalFlag)
      ipcRenderer.on(IPC.callUI.globalFlag, (_event, type) => cb(type))
    },
    // renderer → main (invoke): recording controls
    pause: (): Promise<void> => ipcRenderer.invoke(IPC.callUI.pause),
    resume: (): Promise<void> => ipcRenderer.invoke(IPC.callUI.resume),
    flagMoment: (type: string): Promise<void> => ipcRenderer.invoke(IPC.callUI.flagMoment, type),
    openKB: (): Promise<void> => ipcRenderer.invoke(IPC.callUI.openKB),
    stopRecording: (): Promise<void> => ipcRenderer.invoke(IPC.callUI.stopRecording),
  },
}

contextBridge.exposeInMainWorld('electronAPI', {
  platform: process.platform,

  openExternal: (url: string): Promise<void> =>
    ipcRenderer.invoke(IPC.shell.openExternal, url),

  doubleClickTitleBar: (): void => {
    ipcRenderer.invoke(IPC.window.doubleClickTitle)
  },

  notify: (title: string, body: string, tag?: string): void => {
    ipcRenderer.invoke(IPC.app.notify, title, body, tag)
  },

  onDeepLink: (cb: (url: string) => void): void => {
    ipcRenderer.on(IPC.protocol.deepLink, (_event, url: string) => cb(url))
  },

  onNavigate: (cb: (path: string) => void): void => {
    ipcRenderer.on(IPC.window.navigate, (_event, path: string) => cb(path))
  },

  ...(__RECORDING_ENABLED__ ? recordingBridge : {}),
})