# @therms/rpc-client

![rpc_logo.png](https://bitbucket.org/thermsio/rpc-server-ts/raw/a9f154178cbc69e9b821ed22fbe7bfec125cdf86/rpc_logo.png)

A _Remote Procedure Call_ framework for Javascript environments (Node.js & Browser).

- Android/Kotlin client (https://bitbucket.org/thermsio/rpc-client-kotlin)
- iOS/Swift client (https://bitbucket.org/thermsio/rpc-client-swift)
- Node.js RPC server (https://bitbucket.org/thermsio/rpc-server-ts)

```bash
npm i @therms/rpc-client
```

UMD browser environment, the exports will be available on global name: `RPCClient`.

```bash
<body>
  ...

  <script src="https://unpkg.com/@therms/rpc-client/dist/umd.js" type="text/javascript"></script>
  
  <script>
    const  rpcClient = new RPCClient.RPCClient({ ... })
  </script>
</body>
```

## Client

### Basic Usage

`import { RPCClient } from '@therms/rpc-client'`

```js
/* Create a client instance */
const rpc = new RPCClient({
  hosts: {
    http: 'http://localhost:3995/rpc',
    websocket: 'ws://localhost:3996', // optional
  },
})

/* Make a remote procedure call */
const {
  code, // http status code, ie: 200
  data, // any data returned from the remote/server
  message, // a human readable message from the remote/server
  success, // boolean, true|false if the procedure was succesful
} = await rpc.call({
  // optional
  args: {
    email: 'me@gmail.com',
    password: '1234567',
  },
  procedure: 'login',
  // optional
  scope: 'auth',
  // optional
  version: '1',
})
```

> Note: `await rpc.call({ ... })` will `throw` when the `CallResponse.success` is not true.

#### Shorthand Request

This client lib provides the option to use a request string for shorthand calls:

```js
const { data } = await rpc.call('scope::procedure::version')

// 2nd arg is optionally "args" passed with request
const { data } = await rpc.call('users::get-users::1', { active: true })
```

#### Catch Failed Calls

```js
try {
    // RPCClient.call method will throw if `success` is not `true`
    const { code, data } = await rpc.call({ ... })
} catch (callResponse) {
    console.log(callResponse.code) // number
    console.log(callResponse.message) // message
    console.log(callResponse.success) // false
}
```

### Auth & RPCClientIdentity

The RPC server implementation accepts a property with all RPC calls `identity` that contains information about the client's authentication info.

The `identity` property schema:

```typescript
{
  authorization: string
  deviceName?: string
  metadata?: { [string]: any }
}
```

The client library implementation is responsible for sending the `identity` property with RPC's to the back-end. The `identity` information can be set with the client like this:

```typescript
rpcClient.setIdentity({ authorization: 'jwt-token-string' })
```

Once the RPC client instance identity is set, it will be maintained in-memory until explicitly changed with `setIdentity(identity: RPCClientIdentity)`.

The `identity` can be overridden for a single request be passing an `identity` object to the method `call(request: RequestDTO)` when making a _call_. This **does not** override the maintained `identity` state that is set with `setIdentity`.

#### Http RPCClientIdentity State

When the client lib sends a RPC request over HTTP, the RPC will always include the `identity` property when the back-end requires authentication/authorization for the specific procedure.

#### WebSocket RPCClientIdentity State

When the client lib makes a connection with the remote, the RPC client lib will immediately send the `identity` information to the remote and can expect the remote to maintain it's `identity` information as long as the WebSocket connection remains alive. The `identity` information will be sent everytime a new WebSocket connection is opened with the remote.

Websocket connections can also communicate with the server via 2-way messaging.

```js
// send message
rpcClient.sendClientMessageToServer(data: any)

// receive messages
rpcClient.subscribeToServerMessages((msg: any) => void)
rpcClient.unsubscribeFromServerMessages((msg: any) => void)
```

### Advanced Usage

#### Create a client instance w/ cache and call deadline/timeout

```js
const rpc = new RPCClient({
  cacheMaxAgeMs: 5 * 60 * 1000, // optional
  deadlineMs: 5000, // optional
  hosts: {
    http: 'localhost:3995/rpc',
  },
})
```

#### Create request interceptor

```js
const rpc = new RPCClient({
  hosts: {
    http: 'localhost:3995/rpc',
  },
  requestInterceptor: (request) => {
    request.args.count = request.args.count + 1

    return request
  },
})

const { data } = await rpc.call({
  args: { count: 1 },
  procedure: 'intercept-request',
})

console.log(data.count) // => 2
```

#### Create response interceptor

```js
const rpc = new RPCClient({
  hosts: {
    http: 'localhost:3995/rpc',
  },
  responseInterceptor: (response) => {
    response.data.count = 100

    return response
  },
})

const { data } = await rpc.call({
  args: { count: 1 },
  procedure: 'intercept-request',
})

console.log(data.count) // => 100
```

#### Create a client instance w/ custom transports

First, setup a custom `Transport`:

> see the required interface in `src/client/Transports/Transport.ts`

```js
class CustomHTTPTransport {
  isConnected() {
    return true
  }

  name: 'CustomHTTPTransport'

  async sendRequest(call) {
    const response = await fetch('localhost:3901/rpc', {
      data: call,
    }).then((res) => res.json())

    return response
  }

  setIdentity(identity) {
    this.identity = identity
  }
}
```

Then, use the `CustomHTTPTransport` when you create a client instance:

```js
const rpc = new RPCClient({
  cacheMaxAgeMs: 5 * 60 * 1000, // optional
  deadlineMs: 5000, // optional
  transports: {
    http: new CustomHTTPTransport(),
  },
})
```
