File size: 3,736 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import {
  RemoteControlAlreadyRunningError,
  type RemoteControlManager,
  type RemoteControlStatusInfo,
} from '@moonshot-ai/remote-control';

import { errEnvelope, okEnvelope } from '../envelope';
import { requestLog } from '../lib/requestLog';
import { defineRoute } from '../middleware/defineRoute';
import { ErrorCode } from '../protocol/error-codes';
import {
  remoteControlStatusSchema,
  setRemoteControlRequestSchema,
  type RemoteControlStatusResponse,
} from '../protocol/rest-remote-control';

interface RemoteControlRouteHost {
  get(
    path: string,
    options: { preHandler: unknown[]; schema?: Record<string, unknown> },
    handler: (
      req: { id: string },
      reply: { send(payload: unknown): unknown },
    ) => Promise<void> | void,
  ): unknown;
  post(
    path: string,
    options: { preHandler: unknown[]; schema?: Record<string, unknown> },
    handler: (
      req: { id: string; body: unknown },
      reply: { send(payload: unknown): unknown },
    ) => Promise<void> | void,
  ): unknown;
}

export interface RemoteControlRouteOptions {
  readonly service: RemoteControlManager;
  readonly staticEnableError?: string;
}

export function registerRemoteControlRoutes(
  app: RemoteControlRouteHost,
  opts: RemoteControlRouteOptions,
): void {
  const getRoute = defineRoute(
    {
      method: 'GET',
      path: '/remote-control',
      success: { data: remoteControlStatusSchema },
      description: 'Get the Remote Control tunnel status',
      tags: ['remote-control'],
    },
    async (req, reply) => {
      reply.send(okEnvelope(toRemoteControlStatusResponse(opts.service.status()), req.id));
    },
  );
  app.get(
    getRoute.path,
    getRoute.options,
    getRoute.handler as Parameters<RemoteControlRouteHost['get']>[2],
  );

  const setRoute = defineRoute(
    {
      method: 'POST',
      path: '/remote-control',
      body: setRemoteControlRequestSchema,
      success: { data: remoteControlStatusSchema },
      errors: {
        [ErrorCode.VALIDATION_FAILED]: {},
        [ErrorCode.REMOTE_CONTROL_ALREADY_RUNNING]: {},
        [ErrorCode.INTERNAL_ERROR]: {},
      },
      description: 'Start or stop the Remote Control tunnel',
      tags: ['remote-control'],
    },
    async (req, reply) => {
      const { enabled } = req.body as { enabled: boolean };
      if (!enabled) {
        const status = await opts.service.disable();
        reply.send(okEnvelope(toRemoteControlStatusResponse(status), req.id));
        return;
      }
      if (opts.staticEnableError !== undefined) {
        reply.send(errEnvelope(ErrorCode.VALIDATION_FAILED, opts.staticEnableError, req.id));
        return;
      }
      try {
        const status = await opts.service.enable();
        reply.send(okEnvelope(toRemoteControlStatusResponse(status), req.id));
      } catch (error) {
        if (error instanceof RemoteControlAlreadyRunningError) {
          reply.send(
            errEnvelope(ErrorCode.REMOTE_CONTROL_ALREADY_RUNNING, error.message, req.id),
          );
          return;
        }
        const message = error instanceof Error ? error.message : String(error);
        requestLog(req)?.error({ err: error }, 'remote-control enable failed');
        reply.send(errEnvelope(ErrorCode.INTERNAL_ERROR, message, req.id));
      }
    },
  );
  app.post(
    setRoute.path,
    setRoute.options,
    setRoute.handler as Parameters<RemoteControlRouteHost['post']>[2],
  );
}

function toRemoteControlStatusResponse(
  status: RemoteControlStatusInfo,
): RemoteControlStatusResponse {
  return {
    enabled: status.enabled,
    state: status.state,
    url: status.url,
    device_id: status.deviceId,
    device_name: status.deviceName,
    error: status.error,
  };
}