| 254 | } |
| 255 | |
| 256 | export class RuntimeLocalClient { |
| 257 | private nextId = 1 |
| 258 | private connected = false |
| 259 | private connecting: Promise<void> | null = null |
| 260 | private disposeMessageListener: (() => void) | null = null |
| 261 | private readonly listeners = new Set<NotificationListener>() |
| 262 | |
| 263 | constructor( |
| 264 | private readonly transport: RuntimeLocalTransport, |
| 265 | private readonly options: RuntimeLocalClientOptions = {} |
| 266 | ) {} |
| 267 | |
| 268 | async request<T>(method: string, params?: unknown): Promise<T> { |
| 269 | await this.connect() |
| 270 | return this.requestRaw<T>(method, params) |
| 271 | } |
| 272 | |
| 273 | private async requestRaw<T>(method: string, params?: unknown): Promise<T> { |
| 274 | const request: RuntimeRequest = params === undefined ? { id: this.nextId++, method } : { id: this.nextId++, method, params } |
| 275 | const response = validateRuntimeResponse(await this.transport.request(request)) |
| 276 | if (!response.ok) throw new RuntimeClientError(response.error.code, response.error.message, { path: response.error.path }) |
| 277 | if (response.value.error) throw new RuntimeClientError(response.value.error.code, response.value.error.message, response.value.error.data) |
| 278 | return response.value.result as T |
| 279 | } |
| 280 | |
| 281 | async connect(): Promise<void> { |
| 282 | if (this.connected) return |
| 283 | if (this.connecting) return this.connecting |
| 284 | this.connecting = this.connectOnce() |
| 285 | try { |
| 286 | await this.connecting |
| 287 | } finally { |
| 288 | this.connecting = null |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | private async connectOnce(): Promise<void> { |
| 293 | await this.transport.connect() |
| 294 | this.disposeMessageListener = this.transport.onMessage((message) => { |
| 295 | if (!isRuntimeNotification(message)) return |
| 296 | for (const listener of this.listeners) listener(message) |
| 297 | }) |
| 298 | await this.requestRaw("initialize", { |
| 299 | clientName: this.options.clientName ?? "Runtime Local Client", |
| 300 | clientPlatform: this.options.clientPlatform ?? "desktop", |
| 301 | ...(this.options.clientVersion ? { clientVersion: this.options.clientVersion } : {}), |
| 302 | protocolVersion: this.options.protocolVersion ?? 1, |
| 303 | }) |
| 304 | this.connected = true |
| 305 | } |
| 306 | |
| 307 | subscribe(listener: NotificationListener): () => void { |
| 308 | this.listeners.add(listener) |
| 309 | void this.connect() |
| 310 | return () => { |
| 311 | this.listeners.delete(listener) |
| 312 | } |
| 313 | } |
nothing calls this directly
no outgoing calls
no test coverage detected