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