Skip to content

Commit

Permalink
RPC implementation (#1282)
Browse files Browse the repository at this point in the history
* Move example app into a demo folder

* Move example app into a demo folder

* rpc example

* most of it

* all the code

* remove excess code

* sample

* almost working

* close...

* close...

* demo is working

* exfixes

* cleanup

* fmt

* fmt

* fmt

* README

* wip

* cleanup

* wip

* fix tests

* fixes

* fmt

* disconncect

* dots

* fmt

* fix

* Create beige-brooms-add.md

* fix

* fixes

* comments

* cleanups

* clean

* fixes

* typo

* Comment

* fixes

* Fix test

* ts

* fmt

* private

* better test

* fmt

* some fixes

* no p on ack

* fmt

* ensure full connection

* fmt

* Change to use identities

* fix

* fmt

* fix

* semver

* fmt

* v

* remove semver package

* Move data packet into lp

* rpc

* simplify

* fmt
  • Loading branch information
bcherry authored Oct 24, 2024
1 parent 12dec21 commit 09f031f
Show file tree
Hide file tree
Showing 21 changed files with 3,561 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-brooms-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"livekit-client": minor
---

RPC implementation
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,5 @@ docs/
pkg/
bin/
examples/**/build/

.env.local
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,66 @@ setLogExtension((level: LogLevel, msg: string, context: object) => {
});
```

### RPC

Perform your own predefined method calls from one participant to another.

This feature is especially powerful when used with [Agents](https://docs.livekit.io/agents), for instance to forward LLM function calls to your client application.

#### Registering an RPC method

The participant who implements the method and will receive its calls must first register support:

```typescript
room.localParticipant?.registerRpcMethod(
// method name - can be any string that makes sense for your application
'greet',

// method handler - will be called when the method is invoked by a RemoteParticipant
async (requestId: string, callerIdentity: string, payload: string, responseTimeoutMs: number) => {
console.log(`Received greeting from ${callerIdentity}: ${payload}`);
return `Hello, ${callerIdentity}!`;
}
);
```

In addition to the payload, your handler will also receive `responseTimeoutMs`, which informs you the maximum time available to return a response. If you are unable to respond in time, the call will result in an error on the caller's side.

#### Performing an RPC request

The caller may then initiate an RPC call like so:

```typescript
try {
const response = await room.localParticipant!.performRpc(
'recipient-identity',
'greet',
'Hello from RPC!'
);
console.log('RPC response:', response);
} catch (error) {
console.error('RPC call failed:', error);
}
```

You may find it useful to adjust the `responseTimeoutMs` parameter, which indicates the amount of time you will wait for a response. We recommend keeping this value as low as possible while still satisfying the constraints of your application.

#### Errors

LiveKit is a dynamic realtime environment and calls can fail for various reasons.

You may throw errors of the type `RpcError` with a string `message` in an RPC method handler and they will be received on the caller's side with the message intact. Other errors will not be transmitted and will instead arrive to the caller as `1500` ("Application Error"). Other built-in errors are detailed in `RpcError`.

## Examples

### Demo App

[examples/demo](examples/demo/) contains a demo webapp that uses the SDK. Run it with `pnpm install && pnpm examples:demo`

### RPC Demo

[examples/rpc](examples/rpc/) contains a demo webapp that uses the SDK to showcase the RPC capabilities. Run it with `pnpm install && pnpm dev` from the `examples/rpc` directory.

## Browser Support

| Browser | Desktop OS | Mobile OS |
Expand Down
2 changes: 1 addition & 1 deletion examples/demo/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "../../tsconfig",
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
Expand Down
13 changes: 13 additions & 0 deletions examples/rpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# RPC Demo

A working multi-participant live demo of the LiveKit RPC feature.

## Running the Demo

1. Create `.env.local` with `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET`, and `LIVEKIT_URL`
1. Install dependencies: `pnpm install`
2. Start server: `pnpm dev`
3. Open browser to local URL (typically http://localhost:5173)
4. Press the button to watch the demo run

For more detailed information on using RPC with LiveKit, refer to the [main README](../../README.md#rpc).
39 changes: 39 additions & 0 deletions examples/rpc/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import dotenv from 'dotenv';
import express from 'express';
import type { Express } from 'express';
import { AccessToken } from 'livekit-server-sdk';

dotenv.config({ path: '.env.local' });

const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY;
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET;
const LIVEKIT_URL = process.env.LIVEKIT_URL;

const app = express();
app.use(express.json());

app.post('/api/get-token', async (req, res) => {
const { identity, roomName } = req.body;

if (!LIVEKIT_API_KEY || !LIVEKIT_API_SECRET) {
res.status(500).json({ error: 'Server misconfigured' });
return;
}

const token = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
identity,
});
token.addGrant({
room: roomName,
roomJoin: true,
canPublish: true,
canSubscribe: true,
});

res.json({
token: await token.toJwt(),
url: LIVEKIT_URL,
});
});

export const handler: Express = app;
19 changes: 19 additions & 0 deletions examples/rpc/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LiveKit RPC Demo</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div class="container">
<h1>LiveKit RPC Demo</h1>
<div id="log-area">
<textarea id="log" rows="15" readonly></textarea>
</div>
<button id="run-demo" class="btn">Run Demo</button>
</div>
<script type="module" src="./rpc-demo.ts"></script>
</body>
</html>
26 changes: 26 additions & 0 deletions examples/rpc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "livekit-rpc-example",
"version": "1.0.0",
"description": "Example of using LiveKit RPC",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"livekit-server-sdk": "^2.7.0",
"vite": "^3.2.7",
"vite-plugin-mix": "^0.4.0"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"concurrently": "^8.2.0",
"tsx": "^4.7.0",
"typescript": "^5.4.5"
}
}
Loading

0 comments on commit 09f031f

Please sign in to comment.