1.. _module-pw_rpc-ts: 2 3----------------- 4TypeScript client 5----------------- 6.. pigweed-module-subpage:: 7 :name: pw_rpc 8 9Pigweed TypeScript client provides two ways to call RPCs. The :ref:`Device <module-pw_web-device>` API is 10easier to work with if you are using the RPC via HDLC over WebSerial. 11 12If the ``device`` abstraction is not a good fit, Pigweed provides ``pw_rpc`` module, 13which makes it possible to call Pigweed RPCs from TypeScript. The module includes 14client library to facilitate handling RPCs. 15 16Creating an RPC Client 17====================== 18The RPC client is instantiated from a list of channels and a set of protos. 19 20.. code-block:: typescript 21 22 import { ProtoCollection } from 'pigweedjs/protos/collection'; 23 24 const channels = [new Channel(1, savePacket), new Channel(5)]; 25 const client = Client.fromProtoSet(channels, new ProtoCollection()); 26 27 function savePacket(packetBytes: Uint8Array): void { 28 const packet = RpcPacket.deserializeBinary(packetBytes); 29 ... 30 } 31 32To generate a ProtoSet/ProtoCollection from your own ``.proto`` files, use 33``pw_proto_compiler`` in your ``package.json`` like this: 34 35.. code-block:: javascript 36 37 ... 38 "scripts": { 39 "build-protos": "pw_proto_compiler -p protos/rpc1.proto -p protos/rpc2.proto --out dist/protos", 40 41This will generate a `collection.js` file which can be used similar to above 42example. 43 44Finding an RPC Method 45===================== 46Once the client is instantiated with the correct proto library, the target RPC 47method is found by searching based on the full name: 48``{packageName}.{serviceName}.{methodName}`` 49 50.. code-block:: typescript 51 52 const channel = client.channel()!; 53 unaryStub = channel.methodStub('pw.rpc.test1.TheTestService.SomeUnary')! 54 as UnaryMethodStub; 55 56The four possible RPC stubs are ``UnaryMethodStub``, 57``ServerStreamingMethodStub``, ``ClientStreamingMethodStub``, and 58``BidirectionalStreamingMethodStub``. Note that ``channel.methodStub()`` 59returns a general stub. Since each stub type has different invoke 60parameters, the general stub should be typecast before using. 61 62Invoke an RPC with callbacks 63============================ 64 65.. code-block:: typescript 66 67 invoke(request?: Message, 68 onNext: Callback = () => {}, 69 onCompleted: Callback = () => {}, 70 onError: Callback = () => {}, 71 maxResponses: number = DEFAULT_MAX_STREAM_RESPONSES): Call 72 73All RPC methods can be invoked with a set of callbacks that are triggered when 74either a response is received, the RPC is completed, or an error occurs. The 75example below demonstrates registering these callbacks on a Bidirectional RPC. 76Other RPC types can be invoked in a similar fashion. The request parameter may 77differ slightly between RPC types. 78 79Server streaming and bidirectional streaming methods can receive many responses 80from the server. The client limits the maximum number of responses it stores for 81a single RPC call to avoid unbounded memory usage in long-running streams. Once 82the limit is reached, the oldest responses will be replaced as new ones arrives. 83By default, the limit is set to ``DEFAULT_MAX_STREAM_RESPONSES (=16384)``, but 84this can be configured on a per-call basis. 85 86.. code-block:: typescript 87 88 bidiRpc = client.channel()?.methodStub( 89 'pw.rpc.test1.TheTestService.SomeBidi')! 90 as BidirectionalStreamingMethodStub; 91 92 // Configure callback functions 93 const onNext = (response: Message) => { 94 console.log(response); 95 } 96 const onComplete = (status: Status) => { 97 console.log('completed!'); 98 } 99 const onError = (error: Error) => { 100 console.log(); 101 } 102 103 bidiRpc.invoke(request, onNext, onComplete, onError); 104 105Open an RPC: ignore initial errors 106===================================== 107 108Open allows you to start and register an RPC without crashing on errors. This 109is useful for starting an RPC before the server is ready. For instance, starting 110a logging RPC while the device is booting. 111 112.. code-block:: typescript 113 114 open(request?: Message, 115 onNext: Callback = () => {}, 116 onCompleted: Callback = () => {}, 117 onError: Callback = () => {}): Call 118 119Blocking RPCs: promise API 120========================== 121 122Each MethodStub type provides an call() function that allows sending requests 123and awaiting responses through the promise API. The timeout field is optional. 124If no timeout is specified, the RPC will wait indefinitely. 125 126Unary RPC 127--------- 128.. code-block:: typescript 129 130 unaryRpc = client.channel()?.methodStub( 131 'pw.rpc.test1.TheTestService.SomeUnary')! 132 as UnaryMethodStub; 133 const request = new unaryRpc.requestType(); 134 request.setFooProperty(4); 135 const timeout = 2000 // 2 seconds 136 const [status, response] = await unaryRpc.call(request, timeout); 137 138Server Streaming RPC 139-------------------- 140.. code-block:: typescript 141 142 serverStreamRpc = client.channel()?.methodStub( 143 'pw.rpc.test1.TheTestService.SomeServerStreaming')! 144 as ServerStreamingMethodStub; 145 146 const call = serverStreamRpc.invoke(); 147 const timeout = 2000 148 for await (const response of call.getResponses(2, timeout)) { 149 console.log(response); 150 } 151 const responses = call.getResponses() // All responses until stream end. 152 while (!responses.done) { 153 console.log(await responses.value()); 154 } 155 156 157Client Streaming RPC 158-------------------- 159.. code-block:: typescript 160 161 clientStreamRpc = client.channel()!.methodStub( 162 'pw.rpc.test1.TheTestService.SomeClientStreaming')! 163 as ClientStreamingMethodStub; 164 clientStreamRpc.invoke(); 165 const request = new clientStreamRpc.method.requestType(); 166 request.setFooProperty('foo_test'); 167 clientStreamRpc.send(request); 168 169 // Send three more requests, end the stream, and wait for a response. 170 const timeout = 2000 // 2 seconds 171 request.finishAndWait([request, request, request], timeout) 172 .then((status, response) => { 173 console.log(`Client stream finished successfully: ${response}`); 174 }) 175 .catch((reason) => { 176 console.log(`Client stream error: ${reason}`); 177 }); 178 179Bidirectional Stream RPC 180------------------------ 181.. code-block:: typescript 182 183 bidiStreamingRpc = client.channel()!.methodStub( 184 'pw.rpc.test1.TheTestService.SomeBidiStreaming')! 185 as BidirectionalStreamingMethodStub; 186 bidiStreamingRpc.invoke(); 187 const request = new bidiStreamingRpc.method.requestType(); 188 request.setFooProperty('foo_test'); 189 190 // Send requests 191 bidiStreamingRpc.send(request); 192 193 // Receive responses 194 const timeout = 2000 // 2 seconds 195 for await (const response of call.getResponses(1, timeout)) { 196 console.log(response); 197 } 198 199 // Send three more requests, end the stream, and wait for a response. 200 request.finishAndWait([request, request, request], timeout) 201 .then(() => { 202 console.log('Bidirectional stream finished successfully'); 203 }) 204 .catch((reason) => { 205 console.log(`Bidirectional stream error: ${reason}`); 206 }); 207