xref: /aosp_15_r20/external/pigweed/pw_web/log-viewer/src/repl-kernel.ts (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker// Copyright 2024 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker//
3*61c4878aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker// use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker// the License at
6*61c4878aSAndroid Build Coastguard Worker//
7*61c4878aSAndroid Build Coastguard Worker//     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker//
9*61c4878aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker// License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker// the License.
14*61c4878aSAndroid Build Coastguard Worker
15*61c4878aSAndroid Build Coastguard Workerimport { TemplateResult } from 'lit';
16*61c4878aSAndroid Build Coastguard Worker
17*61c4878aSAndroid Build Coastguard Workerexport interface RPCPayload {
18*61c4878aSAndroid Build Coastguard Worker  id: number;
19*61c4878aSAndroid Build Coastguard Worker  type: string;
20*61c4878aSAndroid Build Coastguard Worker  data: any;
21*61c4878aSAndroid Build Coastguard Worker  streaming?: boolean;
22*61c4878aSAndroid Build Coastguard Worker  close?: boolean;
23*61c4878aSAndroid Build Coastguard Worker}
24*61c4878aSAndroid Build Coastguard Workerexport type RPCStreamHandler = (data: any) => void;
25*61c4878aSAndroid Build Coastguard Workerexport type RPCCallback = (data: any, streaming?: boolean) => void;
26*61c4878aSAndroid Build Coastguard Workerexport type RPCUnsubscribeStream = () => void;
27*61c4878aSAndroid Build Coastguard Worker
28*61c4878aSAndroid Build Coastguard Workerexport abstract class RPCClient {
29*61c4878aSAndroid Build Coastguard Worker  private callbacks: { [key: number]: RPCCallback } = {};
30*61c4878aSAndroid Build Coastguard Worker  private latest_call_id = 1;
31*61c4878aSAndroid Build Coastguard Worker  public abstract send(payload: RPCPayload): void;
32*61c4878aSAndroid Build Coastguard Worker  public call(requestType: string, data?: any): Promise<any> {
33*61c4878aSAndroid Build Coastguard Worker    const payload: RPCPayload = {
34*61c4878aSAndroid Build Coastguard Worker      id: this.latest_call_id++,
35*61c4878aSAndroid Build Coastguard Worker      type: requestType,
36*61c4878aSAndroid Build Coastguard Worker      data,
37*61c4878aSAndroid Build Coastguard Worker    };
38*61c4878aSAndroid Build Coastguard Worker    return new Promise((resolve, _reject) => {
39*61c4878aSAndroid Build Coastguard Worker      this.callbacks[payload.id] = resolve;
40*61c4878aSAndroid Build Coastguard Worker      this.send(payload);
41*61c4878aSAndroid Build Coastguard Worker    });
42*61c4878aSAndroid Build Coastguard Worker  }
43*61c4878aSAndroid Build Coastguard Worker
44*61c4878aSAndroid Build Coastguard Worker  public openStream(
45*61c4878aSAndroid Build Coastguard Worker    requestType: string,
46*61c4878aSAndroid Build Coastguard Worker    data: any,
47*61c4878aSAndroid Build Coastguard Worker    streamHandler: RPCStreamHandler,
48*61c4878aSAndroid Build Coastguard Worker  ): Promise<RPCUnsubscribeStream> {
49*61c4878aSAndroid Build Coastguard Worker    const payload = {
50*61c4878aSAndroid Build Coastguard Worker      id: this.latest_call_id++,
51*61c4878aSAndroid Build Coastguard Worker      type: requestType,
52*61c4878aSAndroid Build Coastguard Worker      data,
53*61c4878aSAndroid Build Coastguard Worker    };
54*61c4878aSAndroid Build Coastguard Worker
55*61c4878aSAndroid Build Coastguard Worker    const unsub = () => {
56*61c4878aSAndroid Build Coastguard Worker      return new Promise((resolve, _reject) => {
57*61c4878aSAndroid Build Coastguard Worker        this.callbacks[payload.id] = resolve;
58*61c4878aSAndroid Build Coastguard Worker        this.send({ ...payload, close: true });
59*61c4878aSAndroid Build Coastguard Worker      });
60*61c4878aSAndroid Build Coastguard Worker    };
61*61c4878aSAndroid Build Coastguard Worker    return new Promise((resolve, _reject) => {
62*61c4878aSAndroid Build Coastguard Worker      this.callbacks[payload.id] = streamHandler;
63*61c4878aSAndroid Build Coastguard Worker      this.send(payload);
64*61c4878aSAndroid Build Coastguard Worker      resolve(unsub);
65*61c4878aSAndroid Build Coastguard Worker    });
66*61c4878aSAndroid Build Coastguard Worker  }
67*61c4878aSAndroid Build Coastguard Worker
68*61c4878aSAndroid Build Coastguard Worker  // Check if this is a response to RPC call
69*61c4878aSAndroid Build Coastguard Worker  handleResponse(response: RPCPayload) {
70*61c4878aSAndroid Build Coastguard Worker    try {
71*61c4878aSAndroid Build Coastguard Worker      if (response.id && this.callbacks[response.id]) {
72*61c4878aSAndroid Build Coastguard Worker        this.callbacks[response.id](response.data, response.streaming);
73*61c4878aSAndroid Build Coastguard Worker        if (!response.streaming) delete this.callbacks[response.id];
74*61c4878aSAndroid Build Coastguard Worker        return true;
75*61c4878aSAndroid Build Coastguard Worker      } else if (response.id) {
76*61c4878aSAndroid Build Coastguard Worker        console.error('callback not found for', response);
77*61c4878aSAndroid Build Coastguard Worker      }
78*61c4878aSAndroid Build Coastguard Worker    } catch (e) {
79*61c4878aSAndroid Build Coastguard Worker      console.log(e);
80*61c4878aSAndroid Build Coastguard Worker    }
81*61c4878aSAndroid Build Coastguard Worker    return false;
82*61c4878aSAndroid Build Coastguard Worker  }
83*61c4878aSAndroid Build Coastguard Worker}
84*61c4878aSAndroid Build Coastguard Worker
85*61c4878aSAndroid Build Coastguard Workerexport class WebSocketRPCClient extends RPCClient {
86*61c4878aSAndroid Build Coastguard Worker  private pendingCalls: RPCPayload[] = [];
87*61c4878aSAndroid Build Coastguard Worker  private isConnected = false;
88*61c4878aSAndroid Build Coastguard Worker  constructor(private ws: WebSocket) {
89*61c4878aSAndroid Build Coastguard Worker    super();
90*61c4878aSAndroid Build Coastguard Worker
91*61c4878aSAndroid Build Coastguard Worker    this.ws.onopen = () => {
92*61c4878aSAndroid Build Coastguard Worker      this.isConnected = true;
93*61c4878aSAndroid Build Coastguard Worker      if (this.pendingCalls.length > 0) {
94*61c4878aSAndroid Build Coastguard Worker        this.pendingCalls.forEach(this.send.bind(this));
95*61c4878aSAndroid Build Coastguard Worker      }
96*61c4878aSAndroid Build Coastguard Worker    };
97*61c4878aSAndroid Build Coastguard Worker
98*61c4878aSAndroid Build Coastguard Worker    this.ws.onclose = () => {
99*61c4878aSAndroid Build Coastguard Worker      this.isConnected = false;
100*61c4878aSAndroid Build Coastguard Worker    };
101*61c4878aSAndroid Build Coastguard Worker
102*61c4878aSAndroid Build Coastguard Worker    this.ws.onmessage = (event) => {
103*61c4878aSAndroid Build Coastguard Worker      this.handleResponse(JSON.parse(event.data));
104*61c4878aSAndroid Build Coastguard Worker    };
105*61c4878aSAndroid Build Coastguard Worker  }
106*61c4878aSAndroid Build Coastguard Worker  send(payload: RPCPayload) {
107*61c4878aSAndroid Build Coastguard Worker    if (!this.isConnected) {
108*61c4878aSAndroid Build Coastguard Worker      this.pendingCalls.push(payload);
109*61c4878aSAndroid Build Coastguard Worker      return;
110*61c4878aSAndroid Build Coastguard Worker    }
111*61c4878aSAndroid Build Coastguard Worker    this.ws.send(JSON.stringify(payload));
112*61c4878aSAndroid Build Coastguard Worker  }
113*61c4878aSAndroid Build Coastguard Worker}
114*61c4878aSAndroid Build Coastguard Worker
115*61c4878aSAndroid Build Coastguard Workerexport interface EvalOutput {
116*61c4878aSAndroid Build Coastguard Worker  stdin?: string;
117*61c4878aSAndroid Build Coastguard Worker  stdout?: string;
118*61c4878aSAndroid Build Coastguard Worker  stderr?: string;
119*61c4878aSAndroid Build Coastguard Worker  result?: string | TemplateResult;
120*61c4878aSAndroid Build Coastguard Worker}
121*61c4878aSAndroid Build Coastguard Worker
122*61c4878aSAndroid Build Coastguard Workerexport interface AutocompleteSuggestion {
123*61c4878aSAndroid Build Coastguard Worker  text: string;
124*61c4878aSAndroid Build Coastguard Worker  type: string;
125*61c4878aSAndroid Build Coastguard Worker}
126*61c4878aSAndroid Build Coastguard Worker
127*61c4878aSAndroid Build Coastguard Workerexport type LogHandler = (text: string) => void;
128*61c4878aSAndroid Build Coastguard Workerexport type UnsubscribeLogStream = () => void;
129*61c4878aSAndroid Build Coastguard Worker
130*61c4878aSAndroid Build Coastguard Workerexport abstract class ReplKernel {
131*61c4878aSAndroid Build Coastguard Worker  abstract eval(code: string): Promise<EvalOutput>;
132*61c4878aSAndroid Build Coastguard Worker  abstract autocomplete(
133*61c4878aSAndroid Build Coastguard Worker    code: string,
134*61c4878aSAndroid Build Coastguard Worker    cursorPos: number,
135*61c4878aSAndroid Build Coastguard Worker  ): Promise<AutocompleteSuggestion[]>;
136*61c4878aSAndroid Build Coastguard Worker}
137*61c4878aSAndroid Build Coastguard Worker
138*61c4878aSAndroid Build Coastguard Workerexport class WebSocketRPCReplKernel extends ReplKernel {
139*61c4878aSAndroid Build Coastguard Worker  constructor(private rpcClient: WebSocketRPCClient) {
140*61c4878aSAndroid Build Coastguard Worker    super();
141*61c4878aSAndroid Build Coastguard Worker  }
142*61c4878aSAndroid Build Coastguard Worker  eval(code: string) {
143*61c4878aSAndroid Build Coastguard Worker    return this.rpcClient.call('eval', { code });
144*61c4878aSAndroid Build Coastguard Worker  }
145*61c4878aSAndroid Build Coastguard Worker  autocomplete(code: string, cursorPos: number) {
146*61c4878aSAndroid Build Coastguard Worker    return this.rpcClient.call('autocomplete', { code, cursor_pos: cursorPos });
147*61c4878aSAndroid Build Coastguard Worker  }
148*61c4878aSAndroid Build Coastguard Worker}
149