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