{"version":3,"file":"ota-host.mjs","names":[],"sources":["../../src/hosts/ota-host.ts"],"sourcesContent":["/* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD */\n/* SPDX-License-Identifier: Apache-2.0 */\n\n/**\n * In-memory BrookesiaHost for OTA — Node/vitest only, not used on device.\n * Import: `@brookesia/service/hosts/ota`\n */\n\nimport type { BrookesiaHost } from '../common/brookesia-host.ts';\nimport { serializeRpcError, serializeRpcResult } from '../common/rpc-json.ts';\nimport { OTA_FUNCTIONS, OTA_SERVICE_NAME } from '../ota/ota-client.ts';\nimport type {\n  OtaAssetsState,\n  OtaCheckResult,\n  OtaHistoryEntry,\n  OtaProgress,\n  OtaSource,\n  OtaStorageConfig,\n} from '../ota/ota-types.ts';\n\ntype OtaMockState = {\n  progress: OtaProgress;\n  history: OtaHistoryEntry[];\n  assetsState: OtaAssetsState;\n  storageConfig: OtaStorageConfig;\n  jobCounter: number;\n};\n\nlet nextHandle = 1;\nconst handles = new Map<number, OtaMockState>();\n\nconst DEFAULT_STORAGE_CONFIG: OtaStorageConfig = {\n  staging_root: '/littlefs/.ota',\n  state_file: '/littlefs/.ota/state.json',\n  assets_root: '/littlefs/system/ota',\n  factory_root: '/littlefs/system/factory',\n  max_retained_roots: 2,\n};\n\nconst DEFAULT_ASSETS_STATE: OtaAssetsState = {\n  current_root: '/littlefs/system/ota/current',\n  prev_root: '/littlefs/system/ota/prev',\n  pending_root: '',\n  package_id: 'com.example.app',\n  version: '1.0.0',\n  has_pending_transaction: false,\n};\n\nfunction defaultState(): OtaMockState {\n  return {\n    progress: { state: 'Idle', error: 'Ok', message: '' },\n    history: [],\n    assetsState: { ...DEFAULT_ASSETS_STATE },\n    storageConfig: { ...DEFAULT_STORAGE_CONFIG },\n    jobCounter: 0,\n  };\n}\n\nfunction getState(handle: number): OtaMockState {\n  const state = handles.get(handle);\n  if (!state) {\n    throw new Error(`OTA memory host: invalid handle ${handle}`);\n  }\n  return state;\n}\n\nfunction makeCheckResult(source: OtaSource): OtaCheckResult {\n  return {\n    manifest: {\n      schema_version: 1,\n      package_id: 'com.example.ota',\n      version: '1.1.0',\n      target: { systems: ['core'], chips: [], boards: [] },\n      artifacts: [],\n    },\n    current_version: '1.0.0',\n    target_version: '1.1.0',\n    current_target: { system: 'core', chip: 'esp32', board: 'demo' },\n    decision: source.force ? 'Forced' : 'UpdateAvailable',\n    target_compatible: true,\n    update_available: true,\n    force: Boolean(source.force),\n    target_reason: '',\n    reason: '',\n  };\n}\n\nexport type OtaHostOptions = {\n  storageConfig?: OtaStorageConfig;\n  assetsState?: OtaAssetsState;\n  checkAvailable?: boolean;\n};\n\nexport function createOtaHost(options?: OtaHostOptions): BrookesiaHost {\n  const checkAvailable = options?.checkAvailable ?? true;\n\n  return {\n    service_available(name: string): boolean {\n      return name === OTA_SERVICE_NAME;\n    },\n\n    start_service(name: string): number {\n      if (name !== OTA_SERVICE_NAME) {\n        throw new Error(`OTA memory host: unknown service ${name}`);\n      }\n      const handle = nextHandle++;\n      const state = defaultState();\n      if (options?.storageConfig) {\n        state.storageConfig = { ...options.storageConfig };\n      }\n      if (options?.assetsState) {\n        state.assetsState = { ...options.assetsState };\n      }\n      handles.set(handle, state);\n      return handle;\n    },\n\n    stop_service(handle: number): void {\n      handles.delete(handle);\n    },\n\n    call_function(handle: number, functionName: string, paramsJson: string): string {\n      const state = getState(handle);\n      const params = JSON.parse(paramsJson) as Record<string, unknown>;\n\n      try {\n        if (functionName === OTA_FUNCTIONS.CheckUpdate) {\n          const source = (params.Source ?? {}) as OtaSource;\n          state.progress = { state: 'Checking', error: 'Ok', message: 'Checking manifest' };\n          const result = makeCheckResult(source);\n          if (!checkAvailable && !source.force) {\n            result.update_available = false;\n            result.decision = 'SameVersion';\n            result.reason = 'No update available';\n          }\n          state.progress = { state: 'Idle', error: 'Ok', message: 'Manifest checked' };\n          return serializeRpcResult(result);\n        }\n\n        if (functionName === OTA_FUNCTIONS.StartUpdate) {\n          const source = (params.Source ?? {}) as OtaSource;\n          const check = makeCheckResult(source);\n          if (!check.update_available) {\n            return serializeRpcError(check.reason ?? 'No update available');\n          }\n          state.jobCounter += 1;\n          const jobId = `job-${state.jobCounter}`;\n          state.progress = {\n            job_id: jobId,\n            state: 'Downloading',\n            error: 'Ok',\n            message: 'Downloading',\n            package_id: check.manifest?.package_id,\n            version: check.manifest?.version,\n            overall_percent: 10,\n          };\n          return serializeRpcResult({ ...state.progress });\n        }\n\n        if (functionName === OTA_FUNCTIONS.CancelUpdate) {\n          state.progress = { state: 'Canceled', error: 'Canceled', message: 'Canceled' };\n          return serializeRpcResult();\n        }\n\n        if (functionName === OTA_FUNCTIONS.GetState || functionName === OTA_FUNCTIONS.GetProgress) {\n          return serializeRpcResult({ ...state.progress });\n        }\n\n        if (functionName === OTA_FUNCTIONS.GetHistory) {\n          return serializeRpcResult([...state.history]);\n        }\n\n        if (functionName === OTA_FUNCTIONS.ConfirmPendingBoot) {\n          state.assetsState.has_pending_transaction = false;\n          state.progress = { state: 'Idle', error: 'Ok', message: 'Boot confirmed' };\n          return serializeRpcResult();\n        }\n\n        if (functionName === OTA_FUNCTIONS.GetAssetsState) {\n          return serializeRpcResult({ ...state.assetsState });\n        }\n\n        if (functionName === OTA_FUNCTIONS.GetStorageConfig) {\n          return serializeRpcResult({ ...state.storageConfig });\n        }\n\n        if (functionName === OTA_FUNCTIONS.SetStorageConfig) {\n          state.storageConfig = { ...(params.Config as OtaStorageConfig) };\n          return serializeRpcResult();\n        }\n\n        if (functionName === OTA_FUNCTIONS.ResetFailure) {\n          if (state.progress.state === 'Failed' || state.progress.state === 'Canceled') {\n            state.progress = { state: 'Idle', error: 'Ok', message: '' };\n          }\n          return serializeRpcResult();\n        }\n\n        return serializeRpcError(`unknown function ${functionName}`);\n      } catch (err) {\n        const message = err instanceof Error ? err.message : String(err);\n        return serializeRpcError(message);\n      }\n    },\n  };\n}\n\nexport function resetOtaHostState(): void {\n  handles.clear();\n  nextHandle = 1;\n}\n"],"mappings":"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"}