xref: /aosp_15_r20/external/pigweed/pw_rpc/ts/client.ts (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker// Copyright 2022 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 Worker/** Provides a pw_rpc client for TypeScript. */
16*61c4878aSAndroid Build Coastguard Worker
17*61c4878aSAndroid Build Coastguard Workerimport { ProtoCollection } from 'pigweedjs/pw_protobuf_compiler';
18*61c4878aSAndroid Build Coastguard Workerimport { Status } from 'pigweedjs/pw_status';
19*61c4878aSAndroid Build Coastguard Workerimport { Message } from 'google-protobuf';
20*61c4878aSAndroid Build Coastguard Workerimport {
21*61c4878aSAndroid Build Coastguard Worker  PacketType,
22*61c4878aSAndroid Build Coastguard Worker  RpcPacket,
23*61c4878aSAndroid Build Coastguard Worker} from 'pigweedjs/protos/pw_rpc/internal/packet_pb';
24*61c4878aSAndroid Build Coastguard Worker
25*61c4878aSAndroid Build Coastguard Workerimport { Channel, Service } from './descriptors';
26*61c4878aSAndroid Build Coastguard Workerimport { MethodStub, methodStubFactory } from './method';
27*61c4878aSAndroid Build Coastguard Workerimport * as packets from './packets';
28*61c4878aSAndroid Build Coastguard Workerimport { PendingCalls, Rpc } from './rpc_classes';
29*61c4878aSAndroid Build Coastguard Worker
30*61c4878aSAndroid Build Coastguard Worker/**
31*61c4878aSAndroid Build Coastguard Worker * Object for managing RPC service and contained methods.
32*61c4878aSAndroid Build Coastguard Worker */
33*61c4878aSAndroid Build Coastguard Workerexport class ServiceClient {
34*61c4878aSAndroid Build Coastguard Worker  private service: Service;
35*61c4878aSAndroid Build Coastguard Worker  private methods: MethodStub[] = [];
36*61c4878aSAndroid Build Coastguard Worker  methodsByName = new Map<string, MethodStub>();
37*61c4878aSAndroid Build Coastguard Worker
38*61c4878aSAndroid Build Coastguard Worker  constructor(client: Client, channel: Channel, service: Service) {
39*61c4878aSAndroid Build Coastguard Worker    this.service = service;
40*61c4878aSAndroid Build Coastguard Worker    const methods = service.methods;
41*61c4878aSAndroid Build Coastguard Worker    methods.forEach((method) => {
42*61c4878aSAndroid Build Coastguard Worker      const stub = methodStubFactory(client.rpcs, channel, method);
43*61c4878aSAndroid Build Coastguard Worker      this.methods.push(stub);
44*61c4878aSAndroid Build Coastguard Worker      this.methodsByName.set(method.name, stub);
45*61c4878aSAndroid Build Coastguard Worker    });
46*61c4878aSAndroid Build Coastguard Worker  }
47*61c4878aSAndroid Build Coastguard Worker
48*61c4878aSAndroid Build Coastguard Worker  method(methodName: string): MethodStub | undefined {
49*61c4878aSAndroid Build Coastguard Worker    return this.methodsByName.get(methodName);
50*61c4878aSAndroid Build Coastguard Worker  }
51*61c4878aSAndroid Build Coastguard Worker
52*61c4878aSAndroid Build Coastguard Worker  get id(): number {
53*61c4878aSAndroid Build Coastguard Worker    return this.service.id;
54*61c4878aSAndroid Build Coastguard Worker  }
55*61c4878aSAndroid Build Coastguard Worker
56*61c4878aSAndroid Build Coastguard Worker  get name(): string {
57*61c4878aSAndroid Build Coastguard Worker    return this.service.name;
58*61c4878aSAndroid Build Coastguard Worker  }
59*61c4878aSAndroid Build Coastguard Worker}
60*61c4878aSAndroid Build Coastguard Worker
61*61c4878aSAndroid Build Coastguard Worker/**
62*61c4878aSAndroid Build Coastguard Worker * Object for managing RPC channel and contained services.
63*61c4878aSAndroid Build Coastguard Worker */
64*61c4878aSAndroid Build Coastguard Workerexport class ChannelClient {
65*61c4878aSAndroid Build Coastguard Worker  readonly channel: Channel;
66*61c4878aSAndroid Build Coastguard Worker  services = new Map<string, ServiceClient>();
67*61c4878aSAndroid Build Coastguard Worker
68*61c4878aSAndroid Build Coastguard Worker  constructor(client: Client, channel: Channel, services: Service[]) {
69*61c4878aSAndroid Build Coastguard Worker    this.channel = channel;
70*61c4878aSAndroid Build Coastguard Worker    services.forEach((service) => {
71*61c4878aSAndroid Build Coastguard Worker      const serviceClient = new ServiceClient(client, this.channel, service);
72*61c4878aSAndroid Build Coastguard Worker      this.services.set(service.name, serviceClient);
73*61c4878aSAndroid Build Coastguard Worker    });
74*61c4878aSAndroid Build Coastguard Worker  }
75*61c4878aSAndroid Build Coastguard Worker
76*61c4878aSAndroid Build Coastguard Worker  /**
77*61c4878aSAndroid Build Coastguard Worker   * Find a service client via its full name.
78*61c4878aSAndroid Build Coastguard Worker   *
79*61c4878aSAndroid Build Coastguard Worker   * For example:
80*61c4878aSAndroid Build Coastguard Worker   * `service = client.channel().service('the.package.FooService');`
81*61c4878aSAndroid Build Coastguard Worker   */
82*61c4878aSAndroid Build Coastguard Worker  service(serviceName: string): ServiceClient | undefined {
83*61c4878aSAndroid Build Coastguard Worker    return this.services.get(serviceName);
84*61c4878aSAndroid Build Coastguard Worker  }
85*61c4878aSAndroid Build Coastguard Worker
86*61c4878aSAndroid Build Coastguard Worker  /**
87*61c4878aSAndroid Build Coastguard Worker   * Find a method stub via its full name.
88*61c4878aSAndroid Build Coastguard Worker   *
89*61c4878aSAndroid Build Coastguard Worker   * For example:
90*61c4878aSAndroid Build Coastguard Worker   * `method = client.channel().methodStub('the.package.AService.AMethod');`
91*61c4878aSAndroid Build Coastguard Worker   */
92*61c4878aSAndroid Build Coastguard Worker  methodStub(name: string): MethodStub | undefined {
93*61c4878aSAndroid Build Coastguard Worker    const index = name.lastIndexOf('.');
94*61c4878aSAndroid Build Coastguard Worker    if (index <= 0) {
95*61c4878aSAndroid Build Coastguard Worker      console.error(`Malformed method name: ${name}`);
96*61c4878aSAndroid Build Coastguard Worker      return undefined;
97*61c4878aSAndroid Build Coastguard Worker    }
98*61c4878aSAndroid Build Coastguard Worker    const serviceName = name.slice(0, index);
99*61c4878aSAndroid Build Coastguard Worker    const methodName = name.slice(index + 1);
100*61c4878aSAndroid Build Coastguard Worker    const method = this.service(serviceName)?.method(methodName);
101*61c4878aSAndroid Build Coastguard Worker    if (method === undefined) {
102*61c4878aSAndroid Build Coastguard Worker      console.error(`Method not found: ${name}`);
103*61c4878aSAndroid Build Coastguard Worker      return undefined;
104*61c4878aSAndroid Build Coastguard Worker    }
105*61c4878aSAndroid Build Coastguard Worker    return method;
106*61c4878aSAndroid Build Coastguard Worker  }
107*61c4878aSAndroid Build Coastguard Worker}
108*61c4878aSAndroid Build Coastguard Worker
109*61c4878aSAndroid Build Coastguard Worker/**
110*61c4878aSAndroid Build Coastguard Worker * RPCs are invoked through a MethodStub. These can be found by name via
111*61c4878aSAndroid Build Coastguard Worker * methodStub(string name).
112*61c4878aSAndroid Build Coastguard Worker *
113*61c4878aSAndroid Build Coastguard Worker * ```
114*61c4878aSAndroid Build Coastguard Worker * method = client.channel(1).methodStub('the.package.FooService.SomeMethod')
115*61c4878aSAndroid Build Coastguard Worker * call = method.invoke(request);
116*61c4878aSAndroid Build Coastguard Worker * ```
117*61c4878aSAndroid Build Coastguard Worker */
118*61c4878aSAndroid Build Coastguard Workerexport class Client {
119*61c4878aSAndroid Build Coastguard Worker  private channelsById = new Map<number, ChannelClient>();
120*61c4878aSAndroid Build Coastguard Worker  readonly rpcs: PendingCalls;
121*61c4878aSAndroid Build Coastguard Worker  readonly services = new Map<number, Service>();
122*61c4878aSAndroid Build Coastguard Worker
123*61c4878aSAndroid Build Coastguard Worker  constructor(channels: Channel[], services: Service[]) {
124*61c4878aSAndroid Build Coastguard Worker    this.rpcs = new PendingCalls();
125*61c4878aSAndroid Build Coastguard Worker    services.forEach((service) => {
126*61c4878aSAndroid Build Coastguard Worker      this.services.set(service.id, service);
127*61c4878aSAndroid Build Coastguard Worker    });
128*61c4878aSAndroid Build Coastguard Worker
129*61c4878aSAndroid Build Coastguard Worker    channels.forEach((channel) => {
130*61c4878aSAndroid Build Coastguard Worker      this.channelsById.set(
131*61c4878aSAndroid Build Coastguard Worker        channel.id,
132*61c4878aSAndroid Build Coastguard Worker        new ChannelClient(this, channel, services),
133*61c4878aSAndroid Build Coastguard Worker      );
134*61c4878aSAndroid Build Coastguard Worker    });
135*61c4878aSAndroid Build Coastguard Worker  }
136*61c4878aSAndroid Build Coastguard Worker
137*61c4878aSAndroid Build Coastguard Worker  /**
138*61c4878aSAndroid Build Coastguard Worker   * Creates a client from a set of Channels and a library of Protos.
139*61c4878aSAndroid Build Coastguard Worker   *
140*61c4878aSAndroid Build Coastguard Worker   * @param {Channel[]} channels List of possible channels to use.
141*61c4878aSAndroid Build Coastguard Worker   * @param {ProtoCollection} protoSet ProtoCollection containing protos
142*61c4878aSAndroid Build Coastguard Worker   *     defining RPC services
143*61c4878aSAndroid Build Coastguard Worker   * and methods.
144*61c4878aSAndroid Build Coastguard Worker   */
145*61c4878aSAndroid Build Coastguard Worker  static fromProtoSet(channels: Channel[], protoSet: ProtoCollection): Client {
146*61c4878aSAndroid Build Coastguard Worker    let services: Service[] = [];
147*61c4878aSAndroid Build Coastguard Worker    const descriptors = protoSet.fileDescriptorSet.getFileList();
148*61c4878aSAndroid Build Coastguard Worker    descriptors.forEach((fileDescriptor) => {
149*61c4878aSAndroid Build Coastguard Worker      const packageName = fileDescriptor.getPackage()!;
150*61c4878aSAndroid Build Coastguard Worker      fileDescriptor.getServiceList().forEach((serviceDescriptor) => {
151*61c4878aSAndroid Build Coastguard Worker        services = services.concat(
152*61c4878aSAndroid Build Coastguard Worker          Service.fromProtoDescriptor(serviceDescriptor, protoSet, packageName),
153*61c4878aSAndroid Build Coastguard Worker        );
154*61c4878aSAndroid Build Coastguard Worker      });
155*61c4878aSAndroid Build Coastguard Worker    });
156*61c4878aSAndroid Build Coastguard Worker
157*61c4878aSAndroid Build Coastguard Worker    return new Client(channels, services);
158*61c4878aSAndroid Build Coastguard Worker  }
159*61c4878aSAndroid Build Coastguard Worker
160*61c4878aSAndroid Build Coastguard Worker  /**
161*61c4878aSAndroid Build Coastguard Worker   * Finds the channel with the provided id. Returns undefined if there are no
162*61c4878aSAndroid Build Coastguard Worker   * channels or no channel with a matching id.
163*61c4878aSAndroid Build Coastguard Worker   *
164*61c4878aSAndroid Build Coastguard Worker   * @param {number?} id If no id is specified, returns the first channel.
165*61c4878aSAndroid Build Coastguard Worker   */
166*61c4878aSAndroid Build Coastguard Worker  channel(id?: number): ChannelClient | undefined {
167*61c4878aSAndroid Build Coastguard Worker    if (id === undefined) {
168*61c4878aSAndroid Build Coastguard Worker      return this.channelsById.values().next().value;
169*61c4878aSAndroid Build Coastguard Worker    }
170*61c4878aSAndroid Build Coastguard Worker    return this.channelsById.get(id);
171*61c4878aSAndroid Build Coastguard Worker  }
172*61c4878aSAndroid Build Coastguard Worker
173*61c4878aSAndroid Build Coastguard Worker  /**
174*61c4878aSAndroid Build Coastguard Worker   * Creates a new RPC object holding channel, method, and service info.
175*61c4878aSAndroid Build Coastguard Worker   * Returns undefined if the service or method does not exist.
176*61c4878aSAndroid Build Coastguard Worker   */
177*61c4878aSAndroid Build Coastguard Worker  private rpc(
178*61c4878aSAndroid Build Coastguard Worker    packet: RpcPacket,
179*61c4878aSAndroid Build Coastguard Worker    channelClient: ChannelClient,
180*61c4878aSAndroid Build Coastguard Worker  ): Rpc | undefined {
181*61c4878aSAndroid Build Coastguard Worker    const service = this.services.get(packet.getServiceId());
182*61c4878aSAndroid Build Coastguard Worker    if (service == undefined) {
183*61c4878aSAndroid Build Coastguard Worker      return undefined;
184*61c4878aSAndroid Build Coastguard Worker    }
185*61c4878aSAndroid Build Coastguard Worker    const method = service.methods.get(packet.getMethodId());
186*61c4878aSAndroid Build Coastguard Worker    if (method == undefined) {
187*61c4878aSAndroid Build Coastguard Worker      return undefined;
188*61c4878aSAndroid Build Coastguard Worker    }
189*61c4878aSAndroid Build Coastguard Worker    return new Rpc(channelClient.channel, service, method);
190*61c4878aSAndroid Build Coastguard Worker  }
191*61c4878aSAndroid Build Coastguard Worker
192*61c4878aSAndroid Build Coastguard Worker  private decodeStatus(rpc: Rpc, packet: RpcPacket): Status | undefined {
193*61c4878aSAndroid Build Coastguard Worker    if (packet.getType() === PacketType.SERVER_STREAM) {
194*61c4878aSAndroid Build Coastguard Worker      return;
195*61c4878aSAndroid Build Coastguard Worker    }
196*61c4878aSAndroid Build Coastguard Worker    return packet.getStatus();
197*61c4878aSAndroid Build Coastguard Worker  }
198*61c4878aSAndroid Build Coastguard Worker
199*61c4878aSAndroid Build Coastguard Worker  private decodePayload(rpc: Rpc, packet: RpcPacket): Message | undefined {
200*61c4878aSAndroid Build Coastguard Worker    if (packet.getType() === PacketType.SERVER_ERROR) {
201*61c4878aSAndroid Build Coastguard Worker      return undefined;
202*61c4878aSAndroid Build Coastguard Worker    }
203*61c4878aSAndroid Build Coastguard Worker
204*61c4878aSAndroid Build Coastguard Worker    if (
205*61c4878aSAndroid Build Coastguard Worker      packet.getType() === PacketType.RESPONSE &&
206*61c4878aSAndroid Build Coastguard Worker      rpc.method.serverStreaming
207*61c4878aSAndroid Build Coastguard Worker    ) {
208*61c4878aSAndroid Build Coastguard Worker      return undefined;
209*61c4878aSAndroid Build Coastguard Worker    }
210*61c4878aSAndroid Build Coastguard Worker
211*61c4878aSAndroid Build Coastguard Worker    const payload = packet.getPayload_asU8();
212*61c4878aSAndroid Build Coastguard Worker    return packets.decodePayload(
213*61c4878aSAndroid Build Coastguard Worker      payload,
214*61c4878aSAndroid Build Coastguard Worker      rpc.method.responseType,
215*61c4878aSAndroid Build Coastguard Worker      rpc.method.customResponseSerializer,
216*61c4878aSAndroid Build Coastguard Worker    );
217*61c4878aSAndroid Build Coastguard Worker  }
218*61c4878aSAndroid Build Coastguard Worker
219*61c4878aSAndroid Build Coastguard Worker  private sendClientError(
220*61c4878aSAndroid Build Coastguard Worker    client: ChannelClient,
221*61c4878aSAndroid Build Coastguard Worker    packet: RpcPacket,
222*61c4878aSAndroid Build Coastguard Worker    error: Status,
223*61c4878aSAndroid Build Coastguard Worker  ) {
224*61c4878aSAndroid Build Coastguard Worker    client.channel.send(packets.encodeClientError(packet, error));
225*61c4878aSAndroid Build Coastguard Worker  }
226*61c4878aSAndroid Build Coastguard Worker
227*61c4878aSAndroid Build Coastguard Worker  /**
228*61c4878aSAndroid Build Coastguard Worker   * Processes an incoming packet.
229*61c4878aSAndroid Build Coastguard Worker   *
230*61c4878aSAndroid Build Coastguard Worker   * @param {Uint8Array} rawPacketData binary data for a pw_rpc packet.
231*61c4878aSAndroid Build Coastguard Worker   * @return {Status} The status of processing the packet.
232*61c4878aSAndroid Build Coastguard Worker   *    - OK: the packet was processed by the client
233*61c4878aSAndroid Build Coastguard Worker   *    - DATA_LOSS: the packet could not be decoded
234*61c4878aSAndroid Build Coastguard Worker   *    - INVALID_ARGUMENT: the packet is for a server, not a client
235*61c4878aSAndroid Build Coastguard Worker   *    - NOT_FOUND: the packet's channel ID is not known to this client
236*61c4878aSAndroid Build Coastguard Worker   */
237*61c4878aSAndroid Build Coastguard Worker  processPacket(rawPacketData: Uint8Array): Status {
238*61c4878aSAndroid Build Coastguard Worker    let packet;
239*61c4878aSAndroid Build Coastguard Worker    try {
240*61c4878aSAndroid Build Coastguard Worker      packet = packets.decode(rawPacketData);
241*61c4878aSAndroid Build Coastguard Worker    } catch (err) {
242*61c4878aSAndroid Build Coastguard Worker      console.warn(`Failed to decode packet: ${err}`);
243*61c4878aSAndroid Build Coastguard Worker      console.debug(`Raw packet: ${rawPacketData}`);
244*61c4878aSAndroid Build Coastguard Worker      return Status.DATA_LOSS;
245*61c4878aSAndroid Build Coastguard Worker    }
246*61c4878aSAndroid Build Coastguard Worker
247*61c4878aSAndroid Build Coastguard Worker    if (packets.forServer(packet)) {
248*61c4878aSAndroid Build Coastguard Worker      return Status.INVALID_ARGUMENT;
249*61c4878aSAndroid Build Coastguard Worker    }
250*61c4878aSAndroid Build Coastguard Worker
251*61c4878aSAndroid Build Coastguard Worker    const channelClient = this.channelsById.get(packet.getChannelId());
252*61c4878aSAndroid Build Coastguard Worker    if (channelClient == undefined) {
253*61c4878aSAndroid Build Coastguard Worker      console.warn(`Unrecognized channel ID: ${packet.getChannelId()}`);
254*61c4878aSAndroid Build Coastguard Worker      return Status.NOT_FOUND;
255*61c4878aSAndroid Build Coastguard Worker    }
256*61c4878aSAndroid Build Coastguard Worker
257*61c4878aSAndroid Build Coastguard Worker    const rpc = this.rpc(packet, channelClient);
258*61c4878aSAndroid Build Coastguard Worker    if (rpc == undefined) {
259*61c4878aSAndroid Build Coastguard Worker      this.sendClientError(channelClient, packet, Status.NOT_FOUND);
260*61c4878aSAndroid Build Coastguard Worker      console.warn('rpc service/method not found');
261*61c4878aSAndroid Build Coastguard Worker      return Status.OK;
262*61c4878aSAndroid Build Coastguard Worker    }
263*61c4878aSAndroid Build Coastguard Worker
264*61c4878aSAndroid Build Coastguard Worker    if (
265*61c4878aSAndroid Build Coastguard Worker      packet.getType() !== PacketType.RESPONSE &&
266*61c4878aSAndroid Build Coastguard Worker      packet.getType() !== PacketType.SERVER_STREAM &&
267*61c4878aSAndroid Build Coastguard Worker      packet.getType() !== PacketType.SERVER_ERROR
268*61c4878aSAndroid Build Coastguard Worker    ) {
269*61c4878aSAndroid Build Coastguard Worker      console.error(`${rpc}: Unexpected packet type ${packet.getType()}`);
270*61c4878aSAndroid Build Coastguard Worker      console.debug(`Packet: ${packet}`);
271*61c4878aSAndroid Build Coastguard Worker      return Status.OK;
272*61c4878aSAndroid Build Coastguard Worker    }
273*61c4878aSAndroid Build Coastguard Worker
274*61c4878aSAndroid Build Coastguard Worker    let status = this.decodeStatus(rpc, packet);
275*61c4878aSAndroid Build Coastguard Worker    let payload;
276*61c4878aSAndroid Build Coastguard Worker    try {
277*61c4878aSAndroid Build Coastguard Worker      payload = this.decodePayload(rpc, packet);
278*61c4878aSAndroid Build Coastguard Worker    } catch (error) {
279*61c4878aSAndroid Build Coastguard Worker      this.sendClientError(channelClient, packet, Status.DATA_LOSS);
280*61c4878aSAndroid Build Coastguard Worker      console.warn(`Failed to decode response: ${error}`);
281*61c4878aSAndroid Build Coastguard Worker      console.debug(`Raw payload: ${packet.getPayload()}`);
282*61c4878aSAndroid Build Coastguard Worker
283*61c4878aSAndroid Build Coastguard Worker      // Make this an error packet so the error handler is called.
284*61c4878aSAndroid Build Coastguard Worker      packet.setType(PacketType.SERVER_ERROR);
285*61c4878aSAndroid Build Coastguard Worker      status = Status.DATA_LOSS;
286*61c4878aSAndroid Build Coastguard Worker    }
287*61c4878aSAndroid Build Coastguard Worker
288*61c4878aSAndroid Build Coastguard Worker    const call = this.rpcs.getPending(rpc, packet.getCallId(), status);
289*61c4878aSAndroid Build Coastguard Worker    if (call === undefined) {
290*61c4878aSAndroid Build Coastguard Worker      this.sendClientError(channelClient, packet, Status.FAILED_PRECONDITION);
291*61c4878aSAndroid Build Coastguard Worker      console.debug(`Discarding response for ${rpc}, which is not pending`);
292*61c4878aSAndroid Build Coastguard Worker      return Status.OK;
293*61c4878aSAndroid Build Coastguard Worker    }
294*61c4878aSAndroid Build Coastguard Worker
295*61c4878aSAndroid Build Coastguard Worker    if (packet.getType() === PacketType.SERVER_ERROR) {
296*61c4878aSAndroid Build Coastguard Worker      if (status === Status.OK) {
297*61c4878aSAndroid Build Coastguard Worker        throw new Error('Unexpected OK status on SERVER_ERROR');
298*61c4878aSAndroid Build Coastguard Worker      }
299*61c4878aSAndroid Build Coastguard Worker      if (status === undefined) {
300*61c4878aSAndroid Build Coastguard Worker        throw new Error('Missing status on SERVER_ERROR');
301*61c4878aSAndroid Build Coastguard Worker      }
302*61c4878aSAndroid Build Coastguard Worker      console.warn(`${rpc}: invocation failed with status: ${Status[status]}`);
303*61c4878aSAndroid Build Coastguard Worker      call.handleError(status);
304*61c4878aSAndroid Build Coastguard Worker      return Status.OK;
305*61c4878aSAndroid Build Coastguard Worker    }
306*61c4878aSAndroid Build Coastguard Worker
307*61c4878aSAndroid Build Coastguard Worker    if (payload !== undefined) {
308*61c4878aSAndroid Build Coastguard Worker      call.handleResponse(payload);
309*61c4878aSAndroid Build Coastguard Worker    }
310*61c4878aSAndroid Build Coastguard Worker    if (status !== undefined) {
311*61c4878aSAndroid Build Coastguard Worker      call.handleCompletion(status);
312*61c4878aSAndroid Build Coastguard Worker    }
313*61c4878aSAndroid Build Coastguard Worker    return Status.OK;
314*61c4878aSAndroid Build Coastguard Worker  }
315*61c4878aSAndroid Build Coastguard Worker}
316