All files / server rpc.ts

100% Statements 30/30
100% Branches 25/25
100% Functions 2/2
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101                                                                            1x 21x   1x 21x 1x     20x   20x   1x   1x   1x   1x   1x   1x   1x     1x   1x   1x   1x 1x     1x   1x   1x   1x     1x   1x 1x 1x       1x   1x     1x      
import { IPC_CHANNELS } from '@shared/contracts'
import {
  getCreateTaskRequest,
  getMonthStatsRequest,
  getOptionalTaskId,
  getReorderTasksRequest,
  getStartTimerRequest,
  getTaskId,
  getUpdateTaskRequest
} from '@main/ipc/requestValidation'
import { getSettingsUpdatePatch } from '@main/ipc/settingsUpdateRequest'
import type { SettingsService } from '@main/services/settingsService'
import type { StatsService } from '@main/services/statsService'
import type { TaskBoardService } from '@main/services/taskBoardService'
import type { TaskDeletionService } from '@main/services/taskDeletionService'
import type { TaskService } from '@main/services/taskService'
import type { TimerService } from '@main/services/timerService'
 
// The subset of services the web RPC endpoint needs. system.* channels are
// handled entirely on the client (window controls, theme, drag), so they are
// deliberately absent here.
export interface RpcServices {
  timer: Pick<
    TimerService,
    'getSnapshot' | 'start' | 'bindCurrentTask' | 'pause' | 'resume' | 'skip' | 'reset' | 'applySettings'
  >
  tasks: Pick<TaskService, 'list' | 'create' | 'update' | 'complete' | 'restore' | 'reorder'>
  taskBoard: Pick<TaskBoardService, 'get'>
  taskDeletion: Pick<TaskDeletionService, 'delete'>
  settings: Pick<SettingsService, 'get' | 'update'>
  stats: Pick<StatsService, 'get' | 'getMonth'>
}
 
export interface RpcRequest {
  channel: string
  payload?: unknown
}
 
const isRecord = (value: unknown): value is Record<string, unknown> =>
  typeof value === 'object' && value !== null && !Array.isArray(value)
 
export const dispatchRpc = async (services: RpcServices, request: RpcRequest): Promise<unknown> => {
  if (!isRecord(request)) {
    throw new Error('rpc request must be an object')
  }
 
  const { channel, payload } = request
 
  switch (channel) {
    case IPC_CHANNELS.timer.getSnapshot:
      return services.timer.getSnapshot()
    case IPC_CHANNELS.timer.start:
      return services.timer.start(getStartTimerRequest(payload))
    case IPC_CHANNELS.timer.bindCurrentTask:
      return services.timer.bindCurrentTask(getOptionalTaskId(payload, 'timer.bindCurrentTask'))
    case IPC_CHANNELS.timer.pause:
      return services.timer.pause()
    case IPC_CHANNELS.timer.resume:
      return services.timer.resume()
    case IPC_CHANNELS.timer.skip:
      return services.timer.skip()
    case IPC_CHANNELS.timer.reset:
      return services.timer.reset()
 
    case IPC_CHANNELS.tasks.getBoard:
      return services.taskBoard.get()
    case IPC_CHANNELS.tasks.list:
      return services.tasks.list()
    case IPC_CHANNELS.tasks.create:
      return services.tasks.create(getCreateTaskRequest(payload).title)
    case IPC_CHANNELS.tasks.update: {
      const validated = getUpdateTaskRequest(payload)
      return services.tasks.update(validated.id, validated.title)
    }
    case IPC_CHANNELS.tasks.complete:
      return services.tasks.complete(getTaskId(payload, 'tasks.complete'))
    case IPC_CHANNELS.tasks.restore:
      return services.tasks.restore(getTaskId(payload, 'tasks.restore'))
    case IPC_CHANNELS.tasks.reorder:
      return services.tasks.reorder(getReorderTasksRequest(payload).ids)
    case IPC_CHANNELS.tasks.delete:
      return services.taskDeletion.delete(getTaskId(payload, 'tasks.delete'))
 
    case IPC_CHANNELS.settings.get:
      return services.settings.get()
    case IPC_CHANNELS.settings.update: {
      const updated = await services.settings.update(getSettingsUpdatePatch(payload))
      await services.timer.applySettings(updated)
      return updated
    }
 
    case IPC_CHANNELS.stats.get:
      return services.stats.get()
    case IPC_CHANNELS.stats.getMonth:
      return services.stats.getMonth(getMonthStatsRequest(payload))
 
    default:
      throw new Error(`Unknown IPC channel: ${String(channel)}`)
  }
}