diff --git a/.changeset/beige-brooms-add.md b/.changeset/beige-brooms-add.md
new file mode 100644
index 0000000000..2d49e1c07c
--- /dev/null
+++ b/.changeset/beige-brooms-add.md
@@ -0,0 +1,5 @@
+---
+"livekit-client": minor
+---
+
+RPC implementation
diff --git a/.gitignore b/.gitignore
index e2c8972cdb..fa07350e35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -112,3 +112,5 @@ docs/
pkg/
bin/
examples/**/build/
+
+.env.local
\ No newline at end of file
diff --git a/README.md b/README.md
index 3c051cc7b8..d161e0ea44 100644
--- a/README.md
+++ b/README.md
@@ -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 |
diff --git a/examples/demo/tsconfig.json b/examples/demo/tsconfig.json
index 0c15fdd693..4d3333f991 100644
--- a/examples/demo/tsconfig.json
+++ b/examples/demo/tsconfig.json
@@ -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'. */,
diff --git a/examples/rpc/README.md b/examples/rpc/README.md
new file mode 100644
index 0000000000..a90faa4b49
--- /dev/null
+++ b/examples/rpc/README.md
@@ -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).
diff --git a/examples/rpc/api.ts b/examples/rpc/api.ts
new file mode 100644
index 0000000000..eeb6dab6fa
--- /dev/null
+++ b/examples/rpc/api.ts
@@ -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;
diff --git a/examples/rpc/index.html b/examples/rpc/index.html
new file mode 100644
index 0000000000..1b8cbc4573
--- /dev/null
+++ b/examples/rpc/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ LiveKit RPC Demo
+
+
+
+
+
LiveKit RPC Demo
+
+
+
+
Run Demo
+
+
+
+
\ No newline at end of file
diff --git a/examples/rpc/package.json b/examples/rpc/package.json
new file mode 100644
index 0000000000..9174c3d773
--- /dev/null
+++ b/examples/rpc/package.json
@@ -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"
+ }
+}
diff --git a/examples/rpc/pnpm-lock.yaml b/examples/rpc/pnpm-lock.yaml
new file mode 100644
index 0000000000..ff08cf322f
--- /dev/null
+++ b/examples/rpc/pnpm-lock.yaml
@@ -0,0 +1,2202 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ cors:
+ specifier: ^2.8.5
+ version: 2.8.5
+ dotenv:
+ specifier: ^16.4.5
+ version: 16.4.5
+ express:
+ specifier: ^4.21.1
+ version: 4.21.1
+ livekit-server-sdk:
+ specifier: ^2.7.0
+ version: 2.7.0
+ vite:
+ specifier: ^3.2.7
+ version: 3.2.11(@types/node@20.16.11)
+ vite-plugin-mix:
+ specifier: ^0.4.0
+ version: 0.4.0(vite@3.2.11(@types/node@20.16.11))
+ devDependencies:
+ '@types/cors':
+ specifier: ^2.8.17
+ version: 2.8.17
+ '@types/express':
+ specifier: ^5.0.0
+ version: 5.0.0
+ concurrently:
+ specifier: ^8.2.0
+ version: 8.2.2
+ tsx:
+ specifier: ^4.7.0
+ version: 4.19.1
+ typescript:
+ specifier: ^5.4.5
+ version: 5.6.3
+
+packages:
+
+ '@babel/runtime@7.25.7':
+ resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==}
+ engines: {node: '>=6.9.0'}
+
+ '@bufbuild/protobuf@1.10.0':
+ resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==}
+
+ '@esbuild/aix-ppc64@0.23.1':
+ resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.23.1':
+ resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.15.18':
+ resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-arm@0.23.1':
+ resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.23.1':
+ resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.23.1':
+ resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.23.1':
+ resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.23.1':
+ resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.23.1':
+ resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.23.1':
+ resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.23.1':
+ resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.23.1':
+ resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.15.18':
+ resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.23.1':
+ resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.23.1':
+ resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.23.1':
+ resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.23.1':
+ resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.23.1':
+ resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.23.1':
+ resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.23.1':
+ resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.23.1':
+ resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.23.1':
+ resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.23.1':
+ resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.23.1':
+ resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@livekit/protocol@1.24.0':
+ resolution: {integrity: sha512-9dCsqnkMn7lvbI4NGh18zhLDsrXyUcpS++TEFgEk5Xv1WM3R2kT3EzqgL1P/mr3jaabM6rJ8wZA/KJLuQNpF5w==}
+
+ '@types/body-parser@1.19.5':
+ resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
+
+ '@types/connect@3.4.38':
+ resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
+
+ '@types/cors@2.8.17':
+ resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
+
+ '@types/express-serve-static-core@5.0.0':
+ resolution: {integrity: sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==}
+
+ '@types/express@5.0.0':
+ resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==}
+
+ '@types/http-errors@2.0.4':
+ resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
+
+ '@types/mime@1.3.5':
+ resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
+
+ '@types/node@20.16.11':
+ resolution: {integrity: sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==}
+
+ '@types/qs@6.9.16':
+ resolution: {integrity: sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==}
+
+ '@types/range-parser@1.2.7':
+ resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
+
+ '@types/send@0.17.4':
+ resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
+
+ '@types/serve-static@1.15.7':
+ resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
+
+ '@vercel/nft@0.10.1':
+ resolution: {integrity: sha512-xhINCdohfeWg/70QLs3De/rfNFcO2+Sw4tL9oqgFl4zQzhogT3q0MjH6Hda5uM2KuFGndRPs6VkKJphAhWmymg==}
+ hasBin: true
+
+ abbrev@1.1.1:
+ resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
+
+ accepts@1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+
+ acorn-class-fields@1.0.0:
+ resolution: {integrity: sha512-l+1FokF34AeCXGBHkrXFmml9nOIRI+2yBnBpO5MaVAaTIJ96irWLtcCxX+7hAp6USHFCe+iyyBB4ZhxV807wmA==}
+ engines: {node: '>=4.8.2'}
+ peerDependencies:
+ acorn: ^6 || ^7 || ^8
+
+ acorn-private-class-elements@1.0.0:
+ resolution: {integrity: sha512-zYNcZtxKgVCg1brS39BEou86mIao1EV7eeREG+6WMwKbuYTeivRRs6S2XdWnboRde6G9wKh2w+WBydEyJsJ6mg==}
+ engines: {node: '>=4.8.2'}
+ peerDependencies:
+ acorn: ^6.1.0 || ^7 || ^8
+
+ acorn-static-class-features@1.0.0:
+ resolution: {integrity: sha512-XZJECjbmMOKvMHiNzbiPXuXpLAJfN3dAKtfIYbk1eHiWdsutlek+gS7ND4B8yJ3oqvHo1NxfafnezVmq7NXK0A==}
+ engines: {node: '>=4.8.2'}
+ peerDependencies:
+ acorn: ^6.1.0 || ^7 || ^8
+
+ acorn@8.12.1:
+ resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ ansi-regex@2.1.1:
+ resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
+ engines: {node: '>=0.10.0'}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ aproba@1.2.0:
+ resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==}
+
+ are-we-there-yet@1.1.7:
+ resolution: {integrity: sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==}
+ deprecated: This package is no longer supported.
+
+ array-flatten@1.1.1:
+ resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ bindings@1.5.0:
+ resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+
+ body-parser@1.20.3:
+ resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
+ brace-expansion@1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
+ call-bind@1.0.7:
+ resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
+ engines: {node: '>= 0.4'}
+
+ camelcase-keys@9.1.3:
+ resolution: {integrity: sha512-Rircqi9ch8AnZscQcsA1C47NFdaO3wukpmIRzYcDOrmvgt78hM/sj5pZhZNec2NM12uk5vTwRHZ4anGcrC4ZTg==}
+ engines: {node: '>=16'}
+
+ camelcase@8.0.0:
+ resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
+ engines: {node: '>=16'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ chownr@1.1.4:
+ resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
+ code-point-at@1.1.0:
+ resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==}
+ engines: {node: '>=0.10.0'}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ concurrently@8.2.2:
+ resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==}
+ engines: {node: ^14.13.0 || >=16.0.0}
+ hasBin: true
+
+ console-control-strings@1.1.0:
+ resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
+
+ content-disposition@0.5.4:
+ resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
+ engines: {node: '>= 0.6'}
+
+ content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+
+ cookie-signature@1.0.6:
+ resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
+
+ cookie@0.7.1:
+ resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
+ engines: {node: '>= 0.6'}
+
+ core-util-is@1.0.3:
+ resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+
+ cors@2.8.5:
+ resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
+ engines: {node: '>= 0.10'}
+
+ date-fns@2.30.0:
+ resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
+ engines: {node: '>=0.11'}
+
+ debug@2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-extend@0.6.0:
+ resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
+ engines: {node: '>=4.0.0'}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ delegates@1.0.0:
+ resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
+
+ depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+
+ destroy@1.2.0:
+ resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
+ detect-libc@1.0.3:
+ resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+
+ dotenv@16.4.5:
+ resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
+ engines: {node: '>=12'}
+
+ ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ encodeurl@1.0.2:
+ resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
+ engines: {node: '>= 0.8'}
+
+ encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+
+ es-define-property@1.0.0:
+ resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ esbuild-android-64@0.15.18:
+ resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ esbuild-android-arm64@0.15.18:
+ resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ esbuild-darwin-64@0.15.18:
+ resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ esbuild-darwin-arm64@0.15.18:
+ resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ esbuild-freebsd-64@0.15.18:
+ resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ esbuild-freebsd-arm64@0.15.18:
+ resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ esbuild-linux-32@0.15.18:
+ resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ esbuild-linux-64@0.15.18:
+ resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ esbuild-linux-arm64@0.15.18:
+ resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ esbuild-linux-arm@0.15.18:
+ resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ esbuild-linux-mips64le@0.15.18:
+ resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ esbuild-linux-ppc64le@0.15.18:
+ resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ esbuild-linux-riscv64@0.15.18:
+ resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ esbuild-linux-s390x@0.15.18:
+ resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ esbuild-netbsd-64@0.15.18:
+ resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ esbuild-openbsd-64@0.15.18:
+ resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ esbuild-sunos-64@0.15.18:
+ resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ esbuild-windows-32@0.15.18:
+ resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ esbuild-windows-64@0.15.18:
+ resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ esbuild-windows-arm64@0.15.18:
+ resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ esbuild@0.15.18:
+ resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ esbuild@0.23.1:
+ resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
+ estree-walker@0.6.1:
+ resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
+
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
+ express@4.21.1:
+ resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==}
+ engines: {node: '>= 0.10.0'}
+
+ file-uri-to-path@1.0.0:
+ resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ finalhandler@1.3.1:
+ resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
+ engines: {node: '>= 0.8'}
+
+ forwarded@0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+
+ fresh@0.5.2:
+ resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
+ engines: {node: '>= 0.6'}
+
+ fs-minipass@1.2.7:
+ resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==}
+
+ fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ gauge@2.7.4:
+ resolution: {integrity: sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==}
+ deprecated: This package is no longer supported.
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.2.4:
+ resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+ engines: {node: '>= 0.4'}
+
+ get-tsconfig@4.8.1:
+ resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==}
+
+ glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Glob versions prior to v9 are no longer supported
+
+ gopd@1.0.1:
+ resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.0.3:
+ resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.0.3:
+ resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+ engines: {node: '>= 0.4'}
+
+ has-unicode@2.0.1:
+ resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ http-errors@2.0.0:
+ resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+ engines: {node: '>= 0.8'}
+
+ iconv-lite@0.4.24:
+ resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+ engines: {node: '>=0.10.0'}
+
+ ignore-walk@3.0.4:
+ resolution: {integrity: sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==}
+
+ inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ ini@1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
+ ipaddr.js@1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+
+ is-core-module@2.15.1:
+ resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
+ engines: {node: '>= 0.4'}
+
+ is-fullwidth-code-point@1.0.0:
+ resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==}
+ engines: {node: '>=0.10.0'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ isarray@1.0.0:
+ resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+
+ jose@5.9.3:
+ resolution: {integrity: sha512-egLIoYSpcd+QUF+UHgobt5YzI2Pkw/H39ou9suW687MY6PmCwPmkNV/4TNjn1p2tX5xO3j0d0sq5hiYE24bSlg==}
+
+ livekit-server-sdk@2.7.0:
+ resolution: {integrity: sha512-UdP8FVIOrAJg9dhPNSWGAYV2ZlWFkSroYW3A2BKn8HMlaFMbuXjMouITiU4HuoiH/40/5gE6e+1YPj3aebdi4g==}
+ engines: {node: '>=19'}
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ map-obj@5.0.0:
+ resolution: {integrity: sha512-2L3MIgJynYrZ3TYMriLDLWocz15okFakV6J12HXvMXDHui2x/zgChzg1u9mFFGbbGWE+GsLpQByt4POb9Or+uA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ media-typer@0.3.0:
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+ engines: {node: '>= 0.6'}
+
+ merge-descriptors@1.0.3:
+ resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
+
+ methods@1.1.2:
+ resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
+ engines: {node: '>= 0.6'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mime@1.6.0:
+ resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ minipass@2.9.0:
+ resolution: {integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==}
+
+ minizlib@1.3.3:
+ resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==}
+
+ mkdirp@0.5.6:
+ resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+ hasBin: true
+
+ ms@2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.7:
+ resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ needle@2.9.1:
+ resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==}
+ engines: {node: '>= 4.4.x'}
+ hasBin: true
+
+ negotiator@0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+
+ node-gyp-build@4.8.2:
+ resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==}
+ hasBin: true
+
+ node-pre-gyp@0.13.0:
+ resolution: {integrity: sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==}
+ deprecated: 'Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future'
+ hasBin: true
+
+ nopt@4.0.3:
+ resolution: {integrity: sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==}
+ hasBin: true
+
+ npm-bundled@1.1.2:
+ resolution: {integrity: sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==}
+
+ npm-normalize-package-bin@1.0.1:
+ resolution: {integrity: sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==}
+
+ npm-packlist@1.4.8:
+ resolution: {integrity: sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==}
+
+ npmlog@4.1.2:
+ resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==}
+ deprecated: This package is no longer supported.
+
+ number-is-nan@1.0.1:
+ resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
+ engines: {node: '>=0.10.0'}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-inspect@1.13.2:
+ resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==}
+ engines: {node: '>= 0.4'}
+
+ on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ os-homedir@1.0.2:
+ resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==}
+ engines: {node: '>=0.10.0'}
+
+ os-tmpdir@1.0.2:
+ resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
+ engines: {node: '>=0.10.0'}
+
+ osenv@0.1.5:
+ resolution: {integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==}
+ deprecated: This package is no longer supported.
+
+ parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+
+ path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-to-regexp@0.1.10:
+ resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
+
+ picocolors@1.1.0:
+ resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ postcss@8.4.47:
+ resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ process-nextick-args@2.0.1:
+ resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+
+ proxy-addr@2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+
+ qs@6.13.0:
+ resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
+ engines: {node: '>=0.6'}
+
+ quick-lru@6.1.2:
+ resolution: {integrity: sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==}
+ engines: {node: '>=12'}
+
+ range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+
+ raw-body@2.5.2:
+ resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
+ engines: {node: '>= 0.8'}
+
+ rc@1.2.8:
+ resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
+ hasBin: true
+
+ readable-stream@2.3.8:
+ resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
+
+ regenerator-runtime@0.14.1:
+ resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ resolve@1.22.8:
+ resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+ hasBin: true
+
+ rimraf@2.7.1:
+ resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
+ rollup-pluginutils@2.8.2:
+ resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
+
+ rollup@2.79.2:
+ resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+
+ rxjs@7.8.1:
+ resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
+
+ safe-buffer@5.1.2:
+ resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ sax@1.4.1:
+ resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
+
+ semver@5.7.2:
+ resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
+ hasBin: true
+
+ send@0.19.0:
+ resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
+ engines: {node: '>= 0.8.0'}
+
+ serve-static@1.16.2:
+ resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
+ engines: {node: '>= 0.8.0'}
+
+ set-blocking@2.0.0:
+ resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
+ shell-quote@1.8.1:
+ resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
+
+ side-channel@1.0.6:
+ resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
+ engines: {node: '>= 0.4'}
+
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ spawn-command@0.0.2:
+ resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
+
+ statuses@2.0.1:
+ resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+ engines: {node: '>= 0.8'}
+
+ string-width@1.0.2:
+ resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==}
+ engines: {node: '>=0.10.0'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string_decoder@1.1.1:
+ resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+
+ strip-ansi@3.0.1:
+ resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
+ engines: {node: '>=0.10.0'}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-json-comments@2.0.1:
+ resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+ engines: {node: '>=0.10.0'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ tar@4.4.19:
+ resolution: {integrity: sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==}
+ engines: {node: '>=4.5'}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+
+ tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+
+ tslib@2.7.0:
+ resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
+
+ tsx@4.19.1:
+ resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ type-fest@4.26.1:
+ resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==}
+ engines: {node: '>=16'}
+
+ type-is@1.6.18:
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+ engines: {node: '>= 0.6'}
+
+ typescript@5.6.3:
+ resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.19.8:
+ resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+
+ unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ utils-merge@1.0.1:
+ resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
+ engines: {node: '>= 0.4.0'}
+
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
+ vite-plugin-mix@0.4.0:
+ resolution: {integrity: sha512-9X8hiwhl0RbtEXBB0XqnQ5suheAtP3VHn794WcWwjU5ziYYWdlqpMh/2J8APpx/YdpvQ2CZT7dlcGGd/31ya3w==}
+ peerDependencies:
+ vite: ^3
+
+ vite@3.2.11:
+ resolution: {integrity: sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': '>= 14'
+ less: '*'
+ sass: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+
+ wide-align@1.1.5:
+ resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
+snapshots:
+
+ '@babel/runtime@7.25.7':
+ dependencies:
+ regenerator-runtime: 0.14.1
+
+ '@bufbuild/protobuf@1.10.0': {}
+
+ '@esbuild/aix-ppc64@0.23.1':
+ optional: true
+
+ '@esbuild/android-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/android-arm@0.15.18':
+ optional: true
+
+ '@esbuild/android-arm@0.23.1':
+ optional: true
+
+ '@esbuild/android-x64@0.23.1':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/darwin-x64@0.23.1':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-arm@0.23.1':
+ optional: true
+
+ '@esbuild/linux-ia32@0.23.1':
+ optional: true
+
+ '@esbuild/linux-loong64@0.15.18':
+ optional: true
+
+ '@esbuild/linux-loong64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.23.1':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-s390x@0.23.1':
+ optional: true
+
+ '@esbuild/linux-x64@0.23.1':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/sunos-x64@0.23.1':
+ optional: true
+
+ '@esbuild/win32-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/win32-ia32@0.23.1':
+ optional: true
+
+ '@esbuild/win32-x64@0.23.1':
+ optional: true
+
+ '@livekit/protocol@1.24.0':
+ dependencies:
+ '@bufbuild/protobuf': 1.10.0
+
+ '@types/body-parser@1.19.5':
+ dependencies:
+ '@types/connect': 3.4.38
+ '@types/node': 20.16.11
+
+ '@types/connect@3.4.38':
+ dependencies:
+ '@types/node': 20.16.11
+
+ '@types/cors@2.8.17':
+ dependencies:
+ '@types/node': 20.16.11
+
+ '@types/express-serve-static-core@5.0.0':
+ dependencies:
+ '@types/node': 20.16.11
+ '@types/qs': 6.9.16
+ '@types/range-parser': 1.2.7
+ '@types/send': 0.17.4
+
+ '@types/express@5.0.0':
+ dependencies:
+ '@types/body-parser': 1.19.5
+ '@types/express-serve-static-core': 5.0.0
+ '@types/qs': 6.9.16
+ '@types/serve-static': 1.15.7
+
+ '@types/http-errors@2.0.4': {}
+
+ '@types/mime@1.3.5': {}
+
+ '@types/node@20.16.11':
+ dependencies:
+ undici-types: 6.19.8
+
+ '@types/qs@6.9.16': {}
+
+ '@types/range-parser@1.2.7': {}
+
+ '@types/send@0.17.4':
+ dependencies:
+ '@types/mime': 1.3.5
+ '@types/node': 20.16.11
+
+ '@types/serve-static@1.15.7':
+ dependencies:
+ '@types/http-errors': 2.0.4
+ '@types/node': 20.16.11
+ '@types/send': 0.17.4
+
+ '@vercel/nft@0.10.1':
+ dependencies:
+ acorn: 8.12.1
+ acorn-class-fields: 1.0.0(acorn@8.12.1)
+ acorn-static-class-features: 1.0.0(acorn@8.12.1)
+ bindings: 1.5.0
+ estree-walker: 0.6.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ micromatch: 4.0.8
+ mkdirp: 0.5.6
+ node-gyp-build: 4.8.2
+ node-pre-gyp: 0.13.0
+ resolve-from: 5.0.0
+ rollup-pluginutils: 2.8.2
+ transitivePeerDependencies:
+ - supports-color
+
+ abbrev@1.1.1: {}
+
+ accepts@1.3.8:
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+
+ acorn-class-fields@1.0.0(acorn@8.12.1):
+ dependencies:
+ acorn: 8.12.1
+ acorn-private-class-elements: 1.0.0(acorn@8.12.1)
+
+ acorn-private-class-elements@1.0.0(acorn@8.12.1):
+ dependencies:
+ acorn: 8.12.1
+
+ acorn-static-class-features@1.0.0(acorn@8.12.1):
+ dependencies:
+ acorn: 8.12.1
+ acorn-private-class-elements: 1.0.0(acorn@8.12.1)
+
+ acorn@8.12.1: {}
+
+ ansi-regex@2.1.1: {}
+
+ ansi-regex@5.0.1: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ aproba@1.2.0: {}
+
+ are-we-there-yet@1.1.7:
+ dependencies:
+ delegates: 1.0.0
+ readable-stream: 2.3.8
+
+ array-flatten@1.1.1: {}
+
+ balanced-match@1.0.2: {}
+
+ bindings@1.5.0:
+ dependencies:
+ file-uri-to-path: 1.0.0
+
+ body-parser@1.20.3:
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.5
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ on-finished: 2.4.1
+ qs: 6.13.0
+ raw-body: 2.5.2
+ type-is: 1.6.18
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ brace-expansion@1.1.11:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ bytes@3.1.2: {}
+
+ call-bind@1.0.7:
+ dependencies:
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.4
+ set-function-length: 1.2.2
+
+ camelcase-keys@9.1.3:
+ dependencies:
+ camelcase: 8.0.0
+ map-obj: 5.0.0
+ quick-lru: 6.1.2
+ type-fest: 4.26.1
+
+ camelcase@8.0.0: {}
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chownr@1.1.4: {}
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ code-point-at@1.1.0: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ concat-map@0.0.1: {}
+
+ concurrently@8.2.2:
+ dependencies:
+ chalk: 4.1.2
+ date-fns: 2.30.0
+ lodash: 4.17.21
+ rxjs: 7.8.1
+ shell-quote: 1.8.1
+ spawn-command: 0.0.2
+ supports-color: 8.1.1
+ tree-kill: 1.2.2
+ yargs: 17.7.2
+
+ console-control-strings@1.1.0: {}
+
+ content-disposition@0.5.4:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ content-type@1.0.5: {}
+
+ cookie-signature@1.0.6: {}
+
+ cookie@0.7.1: {}
+
+ core-util-is@1.0.3: {}
+
+ cors@2.8.5:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
+ date-fns@2.30.0:
+ dependencies:
+ '@babel/runtime': 7.25.7
+
+ debug@2.6.9:
+ dependencies:
+ ms: 2.0.0
+
+ debug@3.2.7:
+ dependencies:
+ ms: 2.1.3
+
+ deep-extend@0.6.0: {}
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
+ gopd: 1.0.1
+
+ delegates@1.0.0: {}
+
+ depd@2.0.0: {}
+
+ destroy@1.2.0: {}
+
+ detect-libc@1.0.3: {}
+
+ dotenv@16.4.5: {}
+
+ ee-first@1.1.1: {}
+
+ emoji-regex@8.0.0: {}
+
+ encodeurl@1.0.2: {}
+
+ encodeurl@2.0.0: {}
+
+ es-define-property@1.0.0:
+ dependencies:
+ get-intrinsic: 1.2.4
+
+ es-errors@1.3.0: {}
+
+ esbuild-android-64@0.15.18:
+ optional: true
+
+ esbuild-android-arm64@0.15.18:
+ optional: true
+
+ esbuild-darwin-64@0.15.18:
+ optional: true
+
+ esbuild-darwin-arm64@0.15.18:
+ optional: true
+
+ esbuild-freebsd-64@0.15.18:
+ optional: true
+
+ esbuild-freebsd-arm64@0.15.18:
+ optional: true
+
+ esbuild-linux-32@0.15.18:
+ optional: true
+
+ esbuild-linux-64@0.15.18:
+ optional: true
+
+ esbuild-linux-arm64@0.15.18:
+ optional: true
+
+ esbuild-linux-arm@0.15.18:
+ optional: true
+
+ esbuild-linux-mips64le@0.15.18:
+ optional: true
+
+ esbuild-linux-ppc64le@0.15.18:
+ optional: true
+
+ esbuild-linux-riscv64@0.15.18:
+ optional: true
+
+ esbuild-linux-s390x@0.15.18:
+ optional: true
+
+ esbuild-netbsd-64@0.15.18:
+ optional: true
+
+ esbuild-openbsd-64@0.15.18:
+ optional: true
+
+ esbuild-sunos-64@0.15.18:
+ optional: true
+
+ esbuild-windows-32@0.15.18:
+ optional: true
+
+ esbuild-windows-64@0.15.18:
+ optional: true
+
+ esbuild-windows-arm64@0.15.18:
+ optional: true
+
+ esbuild@0.15.18:
+ optionalDependencies:
+ '@esbuild/android-arm': 0.15.18
+ '@esbuild/linux-loong64': 0.15.18
+ esbuild-android-64: 0.15.18
+ esbuild-android-arm64: 0.15.18
+ esbuild-darwin-64: 0.15.18
+ esbuild-darwin-arm64: 0.15.18
+ esbuild-freebsd-64: 0.15.18
+ esbuild-freebsd-arm64: 0.15.18
+ esbuild-linux-32: 0.15.18
+ esbuild-linux-64: 0.15.18
+ esbuild-linux-arm: 0.15.18
+ esbuild-linux-arm64: 0.15.18
+ esbuild-linux-mips64le: 0.15.18
+ esbuild-linux-ppc64le: 0.15.18
+ esbuild-linux-riscv64: 0.15.18
+ esbuild-linux-s390x: 0.15.18
+ esbuild-netbsd-64: 0.15.18
+ esbuild-openbsd-64: 0.15.18
+ esbuild-sunos-64: 0.15.18
+ esbuild-windows-32: 0.15.18
+ esbuild-windows-64: 0.15.18
+ esbuild-windows-arm64: 0.15.18
+
+ esbuild@0.23.1:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.23.1
+ '@esbuild/android-arm': 0.23.1
+ '@esbuild/android-arm64': 0.23.1
+ '@esbuild/android-x64': 0.23.1
+ '@esbuild/darwin-arm64': 0.23.1
+ '@esbuild/darwin-x64': 0.23.1
+ '@esbuild/freebsd-arm64': 0.23.1
+ '@esbuild/freebsd-x64': 0.23.1
+ '@esbuild/linux-arm': 0.23.1
+ '@esbuild/linux-arm64': 0.23.1
+ '@esbuild/linux-ia32': 0.23.1
+ '@esbuild/linux-loong64': 0.23.1
+ '@esbuild/linux-mips64el': 0.23.1
+ '@esbuild/linux-ppc64': 0.23.1
+ '@esbuild/linux-riscv64': 0.23.1
+ '@esbuild/linux-s390x': 0.23.1
+ '@esbuild/linux-x64': 0.23.1
+ '@esbuild/netbsd-x64': 0.23.1
+ '@esbuild/openbsd-arm64': 0.23.1
+ '@esbuild/openbsd-x64': 0.23.1
+ '@esbuild/sunos-x64': 0.23.1
+ '@esbuild/win32-arm64': 0.23.1
+ '@esbuild/win32-ia32': 0.23.1
+ '@esbuild/win32-x64': 0.23.1
+
+ escalade@3.2.0: {}
+
+ escape-html@1.0.3: {}
+
+ estree-walker@0.6.1: {}
+
+ etag@1.8.1: {}
+
+ express@4.21.1:
+ dependencies:
+ accepts: 1.3.8
+ array-flatten: 1.1.1
+ body-parser: 1.20.3
+ content-disposition: 0.5.4
+ content-type: 1.0.5
+ cookie: 0.7.1
+ cookie-signature: 1.0.6
+ debug: 2.6.9
+ depd: 2.0.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 1.3.1
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ merge-descriptors: 1.0.3
+ methods: 1.1.2
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ path-to-regexp: 0.1.10
+ proxy-addr: 2.0.7
+ qs: 6.13.0
+ range-parser: 1.2.1
+ safe-buffer: 5.2.1
+ send: 0.19.0
+ serve-static: 1.16.2
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ type-is: 1.6.18
+ utils-merge: 1.0.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ file-uri-to-path@1.0.0: {}
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ finalhandler@1.3.1:
+ dependencies:
+ debug: 2.6.9
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.1
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ forwarded@0.2.0: {}
+
+ fresh@0.5.2: {}
+
+ fs-minipass@1.2.7:
+ dependencies:
+ minipass: 2.9.0
+
+ fs.realpath@1.0.0: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ gauge@2.7.4:
+ dependencies:
+ aproba: 1.2.0
+ console-control-strings: 1.1.0
+ has-unicode: 2.0.1
+ object-assign: 4.1.1
+ signal-exit: 3.0.7
+ string-width: 1.0.2
+ strip-ansi: 3.0.1
+ wide-align: 1.1.5
+
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.2.4:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ has-proto: 1.0.3
+ has-symbols: 1.0.3
+ hasown: 2.0.2
+
+ get-tsconfig@4.8.1:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ glob@7.2.3:
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+
+ gopd@1.0.1:
+ dependencies:
+ get-intrinsic: 1.2.4
+
+ graceful-fs@4.2.11: {}
+
+ has-flag@4.0.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.0
+
+ has-proto@1.0.3: {}
+
+ has-symbols@1.0.3: {}
+
+ has-unicode@2.0.1: {}
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ http-errors@2.0.0:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ toidentifier: 1.0.1
+
+ iconv-lite@0.4.24:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ ignore-walk@3.0.4:
+ dependencies:
+ minimatch: 3.1.2
+
+ inflight@1.0.6:
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+
+ inherits@2.0.4: {}
+
+ ini@1.3.8: {}
+
+ ipaddr.js@1.9.1: {}
+
+ is-core-module@2.15.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-fullwidth-code-point@1.0.0:
+ dependencies:
+ number-is-nan: 1.0.1
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-number@7.0.0: {}
+
+ isarray@1.0.0: {}
+
+ jose@5.9.3: {}
+
+ livekit-server-sdk@2.7.0:
+ dependencies:
+ '@livekit/protocol': 1.24.0
+ camelcase-keys: 9.1.3
+ jose: 5.9.3
+
+ lodash@4.17.21: {}
+
+ map-obj@5.0.0: {}
+
+ media-typer@0.3.0: {}
+
+ merge-descriptors@1.0.3: {}
+
+ methods@1.1.2: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mime@1.6.0: {}
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.11
+
+ minimist@1.2.8: {}
+
+ minipass@2.9.0:
+ dependencies:
+ safe-buffer: 5.2.1
+ yallist: 3.1.1
+
+ minizlib@1.3.3:
+ dependencies:
+ minipass: 2.9.0
+
+ mkdirp@0.5.6:
+ dependencies:
+ minimist: 1.2.8
+
+ ms@2.0.0: {}
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.7: {}
+
+ needle@2.9.1:
+ dependencies:
+ debug: 3.2.7
+ iconv-lite: 0.4.24
+ sax: 1.4.1
+ transitivePeerDependencies:
+ - supports-color
+
+ negotiator@0.6.3: {}
+
+ node-gyp-build@4.8.2: {}
+
+ node-pre-gyp@0.13.0:
+ dependencies:
+ detect-libc: 1.0.3
+ mkdirp: 0.5.6
+ needle: 2.9.1
+ nopt: 4.0.3
+ npm-packlist: 1.4.8
+ npmlog: 4.1.2
+ rc: 1.2.8
+ rimraf: 2.7.1
+ semver: 5.7.2
+ tar: 4.4.19
+ transitivePeerDependencies:
+ - supports-color
+
+ nopt@4.0.3:
+ dependencies:
+ abbrev: 1.1.1
+ osenv: 0.1.5
+
+ npm-bundled@1.1.2:
+ dependencies:
+ npm-normalize-package-bin: 1.0.1
+
+ npm-normalize-package-bin@1.0.1: {}
+
+ npm-packlist@1.4.8:
+ dependencies:
+ ignore-walk: 3.0.4
+ npm-bundled: 1.1.2
+ npm-normalize-package-bin: 1.0.1
+
+ npmlog@4.1.2:
+ dependencies:
+ are-we-there-yet: 1.1.7
+ console-control-strings: 1.1.0
+ gauge: 2.7.4
+ set-blocking: 2.0.0
+
+ number-is-nan@1.0.1: {}
+
+ object-assign@4.1.1: {}
+
+ object-inspect@1.13.2: {}
+
+ on-finished@2.4.1:
+ dependencies:
+ ee-first: 1.1.1
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ os-homedir@1.0.2: {}
+
+ os-tmpdir@1.0.2: {}
+
+ osenv@0.1.5:
+ dependencies:
+ os-homedir: 1.0.2
+ os-tmpdir: 1.0.2
+
+ parseurl@1.3.3: {}
+
+ path-is-absolute@1.0.1: {}
+
+ path-parse@1.0.7: {}
+
+ path-to-regexp@0.1.10: {}
+
+ picocolors@1.1.0: {}
+
+ picomatch@2.3.1: {}
+
+ postcss@8.4.47:
+ dependencies:
+ nanoid: 3.3.7
+ picocolors: 1.1.0
+ source-map-js: 1.2.1
+
+ process-nextick-args@2.0.1: {}
+
+ proxy-addr@2.0.7:
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+
+ qs@6.13.0:
+ dependencies:
+ side-channel: 1.0.6
+
+ quick-lru@6.1.2: {}
+
+ range-parser@1.2.1: {}
+
+ raw-body@2.5.2:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+
+ rc@1.2.8:
+ dependencies:
+ deep-extend: 0.6.0
+ ini: 1.3.8
+ minimist: 1.2.8
+ strip-json-comments: 2.0.1
+
+ readable-stream@2.3.8:
+ dependencies:
+ core-util-is: 1.0.3
+ inherits: 2.0.4
+ isarray: 1.0.0
+ process-nextick-args: 2.0.1
+ safe-buffer: 5.1.2
+ string_decoder: 1.1.1
+ util-deprecate: 1.0.2
+
+ regenerator-runtime@0.14.1: {}
+
+ require-directory@2.1.1: {}
+
+ resolve-from@5.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ resolve@1.22.8:
+ dependencies:
+ is-core-module: 2.15.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ rimraf@2.7.1:
+ dependencies:
+ glob: 7.2.3
+
+ rollup-pluginutils@2.8.2:
+ dependencies:
+ estree-walker: 0.6.1
+
+ rollup@2.79.2:
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ rxjs@7.8.1:
+ dependencies:
+ tslib: 2.7.0
+
+ safe-buffer@5.1.2: {}
+
+ safe-buffer@5.2.1: {}
+
+ safer-buffer@2.1.2: {}
+
+ sax@1.4.1: {}
+
+ semver@5.7.2: {}
+
+ send@0.19.0:
+ dependencies:
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ mime: 1.6.0
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ serve-static@1.16.2:
+ dependencies:
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 0.19.0
+ transitivePeerDependencies:
+ - supports-color
+
+ set-blocking@2.0.0: {}
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.4
+ gopd: 1.0.1
+ has-property-descriptors: 1.0.2
+
+ setprototypeof@1.2.0: {}
+
+ shell-quote@1.8.1: {}
+
+ side-channel@1.0.6:
+ dependencies:
+ call-bind: 1.0.7
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ object-inspect: 1.13.2
+
+ signal-exit@3.0.7: {}
+
+ source-map-js@1.2.1: {}
+
+ spawn-command@0.0.2: {}
+
+ statuses@2.0.1: {}
+
+ string-width@1.0.2:
+ dependencies:
+ code-point-at: 1.1.0
+ is-fullwidth-code-point: 1.0.0
+ strip-ansi: 3.0.1
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string_decoder@1.1.1:
+ dependencies:
+ safe-buffer: 5.1.2
+
+ strip-ansi@3.0.1:
+ dependencies:
+ ansi-regex: 2.1.1
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-json-comments@2.0.1: {}
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-color@8.1.1:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ tar@4.4.19:
+ dependencies:
+ chownr: 1.1.4
+ fs-minipass: 1.2.7
+ minipass: 2.9.0
+ minizlib: 1.3.3
+ mkdirp: 0.5.6
+ safe-buffer: 5.2.1
+ yallist: 3.1.1
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ toidentifier@1.0.1: {}
+
+ tree-kill@1.2.2: {}
+
+ tslib@2.7.0: {}
+
+ tsx@4.19.1:
+ dependencies:
+ esbuild: 0.23.1
+ get-tsconfig: 4.8.1
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ type-fest@4.26.1: {}
+
+ type-is@1.6.18:
+ dependencies:
+ media-typer: 0.3.0
+ mime-types: 2.1.35
+
+ typescript@5.6.3: {}
+
+ undici-types@6.19.8: {}
+
+ unpipe@1.0.0: {}
+
+ util-deprecate@1.0.2: {}
+
+ utils-merge@1.0.1: {}
+
+ vary@1.1.2: {}
+
+ vite-plugin-mix@0.4.0(vite@3.2.11(@types/node@20.16.11)):
+ dependencies:
+ '@vercel/nft': 0.10.1
+ vite: 3.2.11(@types/node@20.16.11)
+ transitivePeerDependencies:
+ - supports-color
+
+ vite@3.2.11(@types/node@20.16.11):
+ dependencies:
+ esbuild: 0.15.18
+ postcss: 8.4.47
+ resolve: 1.22.8
+ rollup: 2.79.2
+ optionalDependencies:
+ '@types/node': 20.16.11
+ fsevents: 2.3.3
+
+ wide-align@1.1.5:
+ dependencies:
+ string-width: 4.2.3
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrappy@1.0.2: {}
+
+ y18n@5.0.8: {}
+
+ yallist@3.1.1: {}
+
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
diff --git a/examples/rpc/rpc-demo.ts b/examples/rpc/rpc-demo.ts
new file mode 100644
index 0000000000..1009b436d2
--- /dev/null
+++ b/examples/rpc/rpc-demo.ts
@@ -0,0 +1,313 @@
+import { Room, type RoomConnectOptions, RoomEvent, RpcError } from '../../src/index';
+
+let startTime: number;
+
+async function main() {
+ startTime = Date.now();
+ const logArea = document.getElementById('log') as HTMLTextAreaElement;
+ if (logArea) {
+ logArea.value = '';
+ }
+
+ const roomName = `rpc-demo-${Math.random().toString(36).substring(7)}`;
+
+ console.log(`Connecting participants to room: ${roomName}`);
+
+ const [callersRoom, greetersRoom, mathGeniusRoom] = await Promise.all([
+ connectParticipant('caller', roomName),
+ connectParticipant('greeter', roomName),
+ connectParticipant('math-genius', roomName),
+ ]);
+
+ console.log('All participants connected, starting demo.');
+
+ await registerReceiverMethods(greetersRoom, mathGeniusRoom);
+
+ try {
+ console.log('\n\nRunning greeting example...');
+ await Promise.all([performGreeting(callersRoom)]);
+ } catch (error) {
+ console.error('Error:', error);
+ }
+
+ try {
+ console.log('\n\nRunning error handling example...');
+ await Promise.all([performDivision(callersRoom)]);
+ } catch (error) {
+ console.error('Error:', error);
+ }
+
+ try {
+ console.log('\n\nRunning math example...');
+ await Promise.all([
+ performSquareRoot(callersRoom)
+ .then(() => new Promise((resolve) => setTimeout(resolve, 2000)))
+ .then(() => performQuantumHypergeometricSeries(callersRoom)),
+ ]);
+ } catch (error) {
+ console.error('Error:', error);
+ }
+
+ try {
+ console.log('\n\nRunning disconnection example...');
+ const disconnectionAfterPromise = disconnectAfter(greetersRoom, 1000);
+ const disconnectionRpcPromise = performDisconnection(callersRoom);
+
+ await Promise.all([disconnectionAfterPromise, disconnectionRpcPromise]);
+ } catch (error) {
+ console.error('Unexpected error:', error);
+ }
+
+ console.log('participants done, disconnecting');
+ await Promise.all([
+ callersRoom.disconnect(),
+ greetersRoom.disconnect(),
+ mathGeniusRoom.disconnect(),
+ ]);
+
+ console.log('\n\nParticipants disconnected. Example completed.');
+}
+
+const registerReceiverMethods = async (greetersRoom: Room, mathGeniusRoom: Room): Promise => {
+ await greetersRoom.localParticipant?.registerRpcMethod(
+ 'arrival',
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async (
+ requestId: string,
+ callerIdentity: string,
+ payload: string,
+ responseTimeoutMs: number,
+ ) => {
+ console.log(`[Greeter] Oh ${callerIdentity} arrived and said "${payload}"`);
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ return 'Welcome and have a wonderful day!';
+ },
+ );
+
+ await mathGeniusRoom.localParticipant?.registerRpcMethod(
+ 'square-root',
+ async (
+ requestId: string,
+ callerIdentity: string,
+ payload: string,
+ responseTimeoutMs: number,
+ ) => {
+ const jsonData = JSON.parse(payload);
+ const number = jsonData.number;
+ console.log(
+ `[Math Genius] I guess ${callerIdentity} wants the square root of ${number}. I've only got ${responseTimeoutMs / 1000} seconds to respond but I think I can pull it off.`,
+ );
+
+ console.log(`[Math Genius] *doing math*…`);
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+
+ const result = Math.sqrt(number);
+ console.log(`[Math Genius] Aha! It's ${result}`);
+ return JSON.stringify({ result });
+ },
+ );
+
+ await mathGeniusRoom.localParticipant?.registerRpcMethod(
+ 'divide',
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async (
+ requestId: string,
+ callerIdentity: string,
+ payload: string,
+ responseTimeoutMs: number,
+ ) => {
+ const jsonData = JSON.parse(payload);
+ const { numerator, denominator } = jsonData;
+
+ console.log(
+ `[Math Genius] ${callerIdentity} wants to divide ${numerator} by ${denominator}. Let me think...`,
+ );
+
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+
+ if (denominator === 0) {
+ throw new Error('Cannot divide by zero');
+ }
+
+ const result = numerator / denominator;
+ console.log(`[Math Genius] ${numerator} / ${denominator} = ${result}`);
+ return JSON.stringify({ result });
+ },
+ );
+};
+
+const performGreeting = async (room: Room): Promise => {
+ console.log("[Caller] Letting the greeter know that I've arrived");
+ try {
+ const response = await room.localParticipant!.performRpc('greeter', 'arrival', 'Hello');
+ console.log(`[Caller] That's nice, the greeter said: "${response}"`);
+ } catch (error) {
+ console.error('[Caller] RPC call failed:', error);
+ throw error;
+ }
+};
+
+const performDisconnection = async (room: Room): Promise => {
+ console.log('[Caller] Checking back in on the greeter...');
+ try {
+ const response = await room.localParticipant!.performRpc(
+ 'greeter',
+ 'arrival',
+ 'You still there?',
+ );
+ console.log(`[Caller] That's nice, the greeter said: "${response}"`);
+ } catch (error) {
+ if (error instanceof RpcError && error.code === RpcError.ErrorCode.RECIPIENT_DISCONNECTED) {
+ console.log('[Caller] The greeter disconnected during the request.');
+ } else {
+ console.error('[Caller] Unexpected error:', error);
+ throw error;
+ }
+ }
+};
+
+const performSquareRoot = async (room: Room): Promise => {
+ console.log("[Caller] What's the square root of 16?");
+ try {
+ const response = await room.localParticipant!.performRpc(
+ 'math-genius',
+ 'square-root',
+ JSON.stringify({ number: 16 }),
+ );
+ const parsedResponse = JSON.parse(response);
+ console.log(`[Caller] Nice, the answer was ${parsedResponse.result}`);
+ } catch (error) {
+ console.error('[Caller] RPC call failed:', error);
+ throw error;
+ }
+};
+
+const performQuantumHypergeometricSeries = async (room: Room): Promise => {
+ console.log("[Caller] What's the quantum hypergeometric series of 42?");
+ try {
+ const response = await room.localParticipant!.performRpc(
+ 'math-genius',
+ 'quantum-hypergeometric-series',
+ JSON.stringify({ number: 42 }),
+ );
+ const parsedResponse = JSON.parse(response);
+ console.log(`[Caller] genius says ${parsedResponse.result}!`);
+ } catch (error) {
+ if (error instanceof RpcError) {
+ if (error.code === RpcError.ErrorCode.UNSUPPORTED_METHOD) {
+ console.log(`[Caller] Aww looks like the genius doesn't know that one.`);
+ return;
+ }
+ }
+
+ console.error('[Caller] Unexpected error:', error);
+ throw error;
+ }
+};
+
+const performDivision = async (room: Room): Promise => {
+ console.log("[Caller] Let's try dividing 10 by 0");
+ try {
+ const response = await room.localParticipant!.performRpc(
+ 'math-genius',
+ 'divide',
+ JSON.stringify({ numerator: 10, denominator: 0 }),
+ );
+ const parsedResponse = JSON.parse(response);
+ console.log(`[Caller] The result is ${parsedResponse.result}`);
+ } catch (error) {
+ if (error instanceof RpcError) {
+ if (error.code === RpcError.ErrorCode.APPLICATION_ERROR) {
+ console.log(`[Caller] Oops! I guess that didn't work. Let's try something else.`);
+ } else {
+ console.error('[Caller] Unexpected RPC error:', error);
+ }
+ } else {
+ console.error('[Caller] Unexpected error:', error);
+ }
+ }
+};
+const connectParticipant = async (identity: string, roomName: string): Promise => {
+ const room = new Room();
+ const { token, url } = await fetchToken(identity, roomName);
+
+ room.on(RoomEvent.Disconnected, () => {
+ console.log(`[${identity}] Disconnected from room`);
+ });
+
+ await room.connect(url, token, {
+ autoSubscribe: true,
+ } as RoomConnectOptions);
+
+ await new Promise((resolve) => {
+ if (room.state === 'connected') {
+ resolve();
+ } else {
+ room.once(RoomEvent.Connected, () => resolve());
+ }
+ });
+
+ console.log(`${identity} connected.`);
+
+ return room;
+};
+
+const fetchToken = async (
+ identity: string,
+ roomName: string,
+): Promise<{ token: string; url: string }> => {
+ const response = await fetch('/api/get-token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ identity, roomName }),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch token');
+ }
+
+ const data = await response.json();
+ return { token: data.token, url: data.url };
+};
+
+(window as any).runRpcDemo = main;
+
+const logToUI = (message: string) => {
+ const logArea = document.getElementById('log') as HTMLTextAreaElement;
+ logArea.value += message + '\n';
+ logArea.scrollTop = logArea.scrollHeight;
+};
+
+const originalConsoleLog = console.log;
+console.log = (...args) => {
+ const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(3);
+ const formattedMessage = `[+${elapsedTime}s] ${args.join(' ')}`;
+ originalConsoleLog.apply(console, [formattedMessage]);
+ logToUI(formattedMessage);
+};
+
+const originalConsoleError = console.error;
+console.error = (...args) => {
+ const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(3);
+ const formattedMessage = `[+${elapsedTime}s] ERROR: ${args.join(' ')}`;
+ originalConsoleError.apply(console, [formattedMessage]);
+ logToUI(formattedMessage);
+};
+
+document.addEventListener('DOMContentLoaded', () => {
+ const runDemoButton = document.getElementById('run-demo') as HTMLButtonElement;
+ if (runDemoButton) {
+ runDemoButton.addEventListener('click', async () => {
+ runDemoButton.disabled = true;
+ await (window as any).runRpcDemo();
+ runDemoButton.disabled = false;
+ });
+ }
+});
+
+const disconnectAfter = async (room: Room, delay: number): Promise => {
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ await room.disconnect();
+};
diff --git a/examples/rpc/styles.css b/examples/rpc/styles.css
new file mode 100644
index 0000000000..0c9d96af85
--- /dev/null
+++ b/examples/rpc/styles.css
@@ -0,0 +1,77 @@
+body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ background-color: #f0f2f5;
+ color: #333;
+ line-height: 1.6;
+ margin: 0;
+ padding: 0;
+}
+
+.container {
+ max-width: 800px;
+ margin: 40px auto;
+ padding: 30px;
+ background-color: #ffffff;
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
+ border-radius: 12px;
+}
+
+h1 {
+ text-align: center;
+ color: #2c3e50;
+ margin-bottom: 30px;
+ font-weight: 600;
+}
+
+#log-area {
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+#log {
+ box-sizing: border-box;
+ width: 100%;
+ height: 300px;
+ padding: 10px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-family: monospace;
+ font-size: 14px;
+ resize: vertical;
+}
+
+.btn {
+ display: block;
+ width: 200px;
+ padding: 10px 20px;
+ background-color: #3498db;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ font-size: 16px;
+ cursor: pointer;
+ transition: background-color 0.3s, transform 0.1s;
+ margin: 0 auto;
+ font-weight: 500;
+}
+
+.btn:hover {
+ background-color: #2980b9;
+ transform: translateY(-2px);
+}
+
+.btn:active {
+ transform: translateY(0);
+}
+
+.btn:disabled {
+ background-color: #bdc3c7;
+ color: #7f8c8d;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.btn:disabled:hover {
+ background-color: #bdc3c7;
+ transform: none;
+}
diff --git a/examples/rpc/tsconfig.json b/examples/rpc/tsconfig.json
new file mode 100644
index 0000000000..dc6943da35
--- /dev/null
+++ b/examples/rpc/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "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'. */,
+ "outDir": "build",
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "strict": true /* Enable all strict type-checking options. */,
+ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
+ "skipLibCheck": true /* Skip type checking of declaration files. */,
+ "noUnusedLocals": true,
+ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
+ "moduleResolution": "node",
+ "resolveJsonModule": true
+ },
+ "include": ["../../src/**/*", "rpc-demo.ts", "api.ts"],
+ "exclude": ["**/*.test.ts", "build/**/*"]
+}
diff --git a/examples/rpc/vite.config.js b/examples/rpc/vite.config.js
new file mode 100644
index 0000000000..9b2f3d7ccd
--- /dev/null
+++ b/examples/rpc/vite.config.js
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vite';
+import mix from 'vite-plugin-mix';
+
+export default defineConfig({
+ plugins: [
+ mix.default({
+ handler: './api.ts',
+ }),
+ ],
+});
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1d6033c33a..9b0bb66087 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3276,8 +3276,8 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- typescript@5.7.0-dev.20241015:
- resolution: {integrity: sha512-nBl/xbQGBZrOmLVFbywn5KVStnsVqGsdjU7jVU+wMAp+bpF0iSrF2ts9pUVFyHdP702zINLxfhlnPr66yXB/ew==}
+ typescript@5.7.0-dev.20241022:
+ resolution: {integrity: sha512-Z8PXMDow1rJGCzBQ9FEeNQHBDEGwqSMAlaM00C9qn/DlUH7DC5dS/pNNEcrQBktPbbIwOjR4XuXYOddJUH2klQ==}
engines: {node: '>=14.17'}
hasBin: true
@@ -5516,7 +5516,7 @@ snapshots:
dependencies:
semver: 7.6.0
shelljs: 0.8.5
- typescript: 5.7.0-dev.20241015
+ typescript: 5.7.0-dev.20241022
electron-to-chromium@1.5.4: {}
@@ -7141,7 +7141,7 @@ snapshots:
typescript@5.6.2: {}
- typescript@5.7.0-dev.20241015: {}
+ typescript@5.7.0-dev.20241022: {}
uc.micro@2.1.0: {}
diff --git a/src/index.ts b/src/index.ts
index a5edc53fee..b96a089830 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -39,6 +39,8 @@ import {
} from './room/utils';
import { getBrowser } from './utils/browserParser';
+export { RpcError } from './room/rpc';
+
export * from './connectionHelper/ConnectionCheck';
export * from './connectionHelper/checks/Checker';
export * from './e2ee';
diff --git a/src/room/Room.ts b/src/room/Room.ts
index 9434af429d..8a0bd97619 100644
--- a/src/room/Room.ts
+++ b/src/room/Room.ts
@@ -1414,6 +1414,7 @@ class Room extends (EventEmitter as new () => TypedEmitter)
participant.unpublishTrack(publication.trackSid, true);
});
this.emit(RoomEvent.ParticipantDisconnected, participant);
+ this.localParticipant?.handleParticipantDisconnected(participant.identity);
}
// updates are sent only when there's a change to speaker ordering
diff --git a/src/room/participant/LocalParticipant.test.ts b/src/room/participant/LocalParticipant.test.ts
new file mode 100644
index 0000000000..a7329a2c65
--- /dev/null
+++ b/src/room/participant/LocalParticipant.test.ts
@@ -0,0 +1,297 @@
+import { DataPacket, DataPacket_Kind } from '@livekit/protocol';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import type { InternalRoomOptions } from '../../options';
+import type RTCEngine from '../RTCEngine';
+import { RpcError } from '../rpc';
+import LocalParticipant from './LocalParticipant';
+import { ParticipantKind } from './Participant';
+import RemoteParticipant from './RemoteParticipant';
+
+describe('LocalParticipant', () => {
+ describe('registerRpcMethod', () => {
+ let localParticipant: LocalParticipant;
+ let mockEngine: RTCEngine;
+ let mockRoomOptions: InternalRoomOptions;
+ let mockSendDataPacket: ReturnType;
+
+ beforeEach(() => {
+ mockSendDataPacket = vi.fn();
+ mockEngine = {
+ client: {
+ sendUpdateLocalMetadata: vi.fn(),
+ },
+ on: vi.fn().mockReturnThis(),
+ sendDataPacket: mockSendDataPacket,
+ } as unknown as RTCEngine;
+
+ mockRoomOptions = {} as InternalRoomOptions;
+
+ localParticipant = new LocalParticipant(
+ 'test-sid',
+ 'test-identity',
+ mockEngine,
+ mockRoomOptions,
+ );
+ });
+
+ it('should register an RPC method handler', async () => {
+ const methodName = 'testMethod';
+ const handler = vi.fn().mockResolvedValue('test response');
+
+ localParticipant.registerRpcMethod(methodName, handler);
+
+ const mockCaller = new RemoteParticipant(
+ {} as any,
+ 'remote-sid',
+ 'remote-identity',
+ 'Remote Participant',
+ '',
+ undefined,
+ ParticipantKind.STANDARD,
+ );
+
+ await localParticipant.handleIncomingRpcRequest(
+ mockCaller.identity,
+ 'test-request-id',
+ methodName,
+ 'test payload',
+ 5000,
+ );
+
+ expect(handler).toHaveBeenCalledWith(
+ 'test-request-id',
+ mockCaller.identity,
+ 'test payload',
+ 5000,
+ );
+
+ // Check if sendDataPacket was called twice (once for ACK and once for response)
+ expect(mockSendDataPacket).toHaveBeenCalledTimes(2);
+
+ // Check if the first call was for ACK
+ expect(mockSendDataPacket.mock.calls[0][0].value.case).toBe('rpcAck');
+ expect(mockSendDataPacket.mock.calls[0][1]).toBe(DataPacket_Kind.RELIABLE);
+
+ // Check if the second call was for response
+ expect(mockSendDataPacket.mock.calls[1][0].value.case).toBe('rpcResponse');
+ expect(mockSendDataPacket.mock.calls[1][1]).toBe(DataPacket_Kind.RELIABLE);
+ });
+
+ it('should catch and transform unhandled errors in the RPC method handler', async () => {
+ const methodName = 'errorMethod';
+ const errorMessage = 'Test error';
+ const handler = vi.fn().mockRejectedValue(new Error(errorMessage));
+
+ localParticipant.registerRpcMethod(methodName, handler);
+
+ const mockCaller = new RemoteParticipant(
+ {} as any,
+ 'remote-sid',
+ 'remote-identity',
+ 'Remote Participant',
+ '',
+ undefined,
+ ParticipantKind.STANDARD,
+ );
+
+ await localParticipant.handleIncomingRpcRequest(
+ mockCaller.identity,
+ 'test-error-request-id',
+ methodName,
+ 'test payload',
+ 5000,
+ );
+
+ expect(handler).toHaveBeenCalledWith(
+ 'test-error-request-id',
+ mockCaller.identity,
+ 'test payload',
+ 5000,
+ );
+
+ // Check if sendDataPacket was called twice (once for ACK and once for error response)
+ expect(mockSendDataPacket).toHaveBeenCalledTimes(2);
+
+ // Check if the second call was for error response
+ const errorResponse = mockSendDataPacket.mock.calls[1][0].value.value.value.value;
+ expect(errorResponse.code).toBe(RpcError.ErrorCode.APPLICATION_ERROR);
+ });
+
+ it('should pass through RpcError thrown by the RPC method handler', async () => {
+ const methodName = 'rpcErrorMethod';
+ const errorCode = 101;
+ const errorMessage = 'some-error-message';
+ const handler = vi.fn().mockRejectedValue(new RpcError(errorCode, errorMessage));
+
+ localParticipant.registerRpcMethod(methodName, handler);
+
+ const mockCaller = new RemoteParticipant(
+ {} as any,
+ 'remote-sid',
+ 'remote-identity',
+ 'Remote Participant',
+ '',
+ undefined,
+ ParticipantKind.STANDARD,
+ );
+
+ await localParticipant.handleIncomingRpcRequest(
+ mockCaller.identity,
+ 'test-rpc-error-request-id',
+ methodName,
+ 'test payload',
+ 5000,
+ );
+
+ expect(handler).toHaveBeenCalledWith(
+ 'test-rpc-error-request-id',
+ mockCaller.identity,
+ 'test payload',
+ 5000,
+ );
+
+ // Check if sendDataPacket was called twice (once for ACK and once for error response)
+ expect(mockSendDataPacket).toHaveBeenCalledTimes(2);
+
+ // Check if the second call was for error response
+ const errorResponse = mockSendDataPacket.mock.calls[1][0].value.value.value.value;
+ expect(errorResponse.code).toBe(errorCode);
+ expect(errorResponse.message).toBe(errorMessage);
+ });
+ });
+
+ describe('performRpc', () => {
+ let localParticipant: LocalParticipant;
+ let mockRemoteParticipant: RemoteParticipant;
+ let mockEngine: RTCEngine;
+ let mockRoomOptions: InternalRoomOptions;
+ let mockSendDataPacket: ReturnType;
+
+ beforeEach(() => {
+ mockSendDataPacket = vi.fn();
+ mockEngine = {
+ client: {
+ sendUpdateLocalMetadata: vi.fn(),
+ },
+ on: vi.fn().mockReturnThis(),
+ sendDataPacket: mockSendDataPacket,
+ } as unknown as RTCEngine;
+
+ mockRoomOptions = {} as InternalRoomOptions;
+
+ localParticipant = new LocalParticipant(
+ 'local-sid',
+ 'local-identity',
+ mockEngine,
+ mockRoomOptions,
+ );
+
+ mockRemoteParticipant = new RemoteParticipant(
+ {} as any,
+ 'remote-sid',
+ 'remote-identity',
+ 'Remote Participant',
+ '',
+ undefined,
+ ParticipantKind.STANDARD,
+ );
+ });
+
+ it('should send RPC request and receive successful response', async () => {
+ const method = 'testMethod';
+ const payload = 'testPayload';
+ const responsePayload = 'responsePayload';
+
+ mockSendDataPacket.mockImplementationOnce((packet: DataPacket) => {
+ const requestId = packet.value.value.id;
+ setTimeout(() => {
+ localParticipant.handleIncomingRpcAck(requestId);
+ setTimeout(() => {
+ localParticipant.handleIncomingRpcResponse(requestId, responsePayload, null);
+ }, 10);
+ }, 10);
+ });
+
+ const result = await localParticipant.performRpc(
+ mockRemoteParticipant.identity,
+ method,
+ payload,
+ );
+
+ expect(mockSendDataPacket).toHaveBeenCalledTimes(1);
+ expect(result).toBe(responsePayload);
+ });
+
+ it('should handle RPC request timeout', async () => {
+ const method = 'timeoutMethod';
+ const payload = 'timeoutPayload';
+
+ const timeoutMs = 50;
+
+ const resultPromise = localParticipant.performRpc(
+ mockRemoteParticipant.identity,
+ method,
+ payload,
+ timeoutMs,
+ );
+
+ mockSendDataPacket.mockImplementationOnce(() => {
+ return new Promise((resolve) => {
+ setTimeout(resolve, timeoutMs + 10);
+ });
+ });
+
+ const startTime = Date.now();
+
+ await expect(resultPromise).rejects.toThrow('Response timeout');
+
+ const elapsedTime = Date.now() - startTime;
+ expect(elapsedTime).toBeGreaterThanOrEqual(timeoutMs);
+ expect(elapsedTime).toBeLessThan(timeoutMs + 50); // Allow some margin for test execution
+
+ expect(mockSendDataPacket).toHaveBeenCalledTimes(1);
+ });
+
+ it('should handle RPC error response', async () => {
+ const method = 'errorMethod';
+ const payload = 'errorPayload';
+ const errorCode = 101;
+ const errorMessage = 'Test error message';
+
+ mockSendDataPacket.mockImplementationOnce((packet: DataPacket) => {
+ const requestId = packet.value.value.id;
+ setTimeout(() => {
+ localParticipant.handleIncomingRpcAck(requestId);
+ localParticipant.handleIncomingRpcResponse(
+ requestId,
+ null,
+ new RpcError(errorCode, errorMessage),
+ );
+ }, 10);
+ });
+
+ await expect(
+ localParticipant.performRpc(mockRemoteParticipant.identity, method, payload),
+ ).rejects.toThrow(errorMessage);
+ });
+
+ it('should handle participant disconnection during RPC request', async () => {
+ const method = 'disconnectMethod';
+ const payload = 'disconnectPayload';
+
+ mockSendDataPacket.mockImplementationOnce(() => Promise.resolve());
+
+ const resultPromise = localParticipant.performRpc(
+ mockRemoteParticipant.identity,
+ method,
+ payload,
+ );
+
+ // Simulate a small delay before disconnection
+ await new Promise((resolve) => setTimeout(resolve, 200));
+ localParticipant.handleParticipantDisconnected(mockRemoteParticipant.identity);
+
+ await expect(resultPromise).rejects.toThrow('Recipient disconnected');
+ });
+ });
+});
diff --git a/src/room/participant/LocalParticipant.ts b/src/room/participant/LocalParticipant.ts
index ef7373fd1c..1697561c22 100644
--- a/src/room/participant/LocalParticipant.ts
+++ b/src/room/participant/LocalParticipant.ts
@@ -9,6 +9,9 @@ import {
ParticipantPermission,
RequestResponse,
RequestResponse_Reason,
+ RpcAck,
+ RpcRequest,
+ RpcResponse,
SimulcastCodec,
SipDTMF,
SubscribedQualityUpdate,
@@ -29,6 +32,7 @@ import {
UnexpectedConnectionState,
} from '../errors';
import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
+import { MAX_PAYLOAD_BYTES, RpcError, byteLength } from '../rpc';
import LocalAudioTrack from '../track/LocalAudioTrack';
import LocalTrack from '../track/LocalTrack';
import LocalTrackPublication from '../track/LocalTrackPublication';
@@ -54,6 +58,7 @@ import {
import type { ChatMessage, DataPublishOptions } from '../types';
import {
Future,
+ compareVersions,
isE2EESimulcastSupported,
isFireFox,
isSVCCodec,
@@ -119,6 +124,26 @@ export default class LocalParticipant extends Participant {
private enabledPublishVideoCodecs: Codec[] = [];
+ private rpcHandlers: Map<
+ string,
+ (
+ requestId: string,
+ callerIdentity: string,
+ payload: string,
+ responseTimeoutMs: number,
+ ) => Promise
+ > = new Map();
+
+ private pendingAcks = new Map void; participantIdentity: string }>();
+
+ private pendingResponses = new Map<
+ string,
+ {
+ resolve: (payload: string | null, error: RpcError | null) => void;
+ participantIdentity: string;
+ }
+ >();
+
/** @internal */
constructor(sid: string, identity: string, engine: RTCEngine, options: InternalRoomOptions) {
super(sid, identity, undefined, undefined, {
@@ -187,7 +212,8 @@ export default class LocalParticipant extends Participant {
.on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished)
.on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate)
.on(EngineEvent.Disconnected, this.handleDisconnected)
- .on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse);
+ .on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse)
+ .on(EngineEvent.DataPacketReceived, this.handleDataPacket);
}
private handleReconnecting = () => {
@@ -221,6 +247,37 @@ export default class LocalParticipant extends Participant {
}
};
+ private handleDataPacket = (packet: DataPacket) => {
+ switch (packet.value.case) {
+ case 'rpcRequest':
+ let rpcRequest = packet.value.value as RpcRequest;
+ this.handleIncomingRpcRequest(
+ packet.participantIdentity,
+ rpcRequest.id,
+ rpcRequest.method,
+ rpcRequest.payload,
+ rpcRequest.responseTimeoutMs,
+ );
+ break;
+ case 'rpcResponse':
+ let rpcResponse = packet.value.value as RpcResponse;
+ let payload: string | null = null;
+ let error: RpcError | null = null;
+
+ if (rpcResponse.value.case === 'payload') {
+ payload = rpcResponse.value.value;
+ } else if (rpcResponse.value.case === 'error') {
+ error = RpcError.fromProto(rpcResponse.value.value);
+ }
+ this.handleIncomingRpcResponse(rpcResponse.requestId, payload, error);
+ break;
+ case 'rpcAck':
+ let rpcAck = packet.value.value as RpcAck;
+ this.handleIncomingRpcAck(rpcAck.requestId);
+ break;
+ }
+ };
+
/**
* Sets and updates the metadata of the local participant.
* Note: this requires `canUpdateOwnMetadata` permission.
@@ -1415,6 +1472,138 @@ export default class LocalParticipant extends Participant {
return msg;
}
+ /**
+ * Initiate an RPC call to a remote participant.
+ * @param destinationIdentity - The `identity` of the destination participant
+ * @param method - The method name to call
+ * @param payload - The method payload
+ * @param responseTimeoutMs - Timeout for receiving a response after initial connection
+ * @returns A promise that resolves with the response payload or rejects with an error.
+ * @throws Error on failure. Details in `message`.
+ */
+ async performRpc(
+ destinationIdentity: string,
+ method: string,
+ payload: string,
+ responseTimeoutMs: number = 10000,
+ ): Promise {
+ const maxRoundTripLatencyMs = 2000;
+
+ return new Promise(async (resolve, reject) => {
+ if (byteLength(payload) > MAX_PAYLOAD_BYTES) {
+ reject(RpcError.builtIn('REQUEST_PAYLOAD_TOO_LARGE'));
+ return;
+ }
+
+ if (
+ this.engine.latestJoinResponse?.serverInfo?.version &&
+ compareVersions(this.engine.latestJoinResponse?.serverInfo?.version, '1.8.0') < 0
+ ) {
+ reject(RpcError.builtIn('UNSUPPORTED_SERVER'));
+ return;
+ }
+
+ const id = crypto.randomUUID();
+ await this.publishRpcRequest(
+ destinationIdentity,
+ id,
+ method,
+ payload,
+ responseTimeoutMs - maxRoundTripLatencyMs,
+ );
+
+ const ackTimeoutId = setTimeout(() => {
+ this.pendingAcks.delete(id);
+ reject(RpcError.builtIn('CONNECTION_TIMEOUT'));
+ this.pendingResponses.delete(id);
+ clearTimeout(responseTimeoutId);
+ }, maxRoundTripLatencyMs);
+
+ this.pendingAcks.set(id, {
+ resolve: () => {
+ clearTimeout(ackTimeoutId);
+ },
+ participantIdentity: destinationIdentity,
+ });
+
+ const responseTimeoutId = setTimeout(() => {
+ this.pendingResponses.delete(id);
+ reject(RpcError.builtIn('RESPONSE_TIMEOUT'));
+ }, responseTimeoutMs);
+
+ this.pendingResponses.set(id, {
+ resolve: (responsePayload: string | null, responseError: RpcError | null) => {
+ clearTimeout(responseTimeoutId);
+ if (this.pendingAcks.has(id)) {
+ console.warn('RPC response received before ack', id);
+ this.pendingAcks.delete(id);
+ clearTimeout(ackTimeoutId);
+ }
+
+ if (responseError) {
+ reject(responseError);
+ } else {
+ resolve(responsePayload ?? '');
+ }
+ },
+ participantIdentity: destinationIdentity,
+ });
+ });
+ }
+
+ /**
+ * Establishes the participant as a receiver for calls of the specified RPC method.
+ * Will overwrite any existing callback for the same method.
+ *
+ * @param method - The name of the indicated RPC method
+ * @param handler - Will be invoked when an RPC request for this method is received
+ * @returns A promise that resolves when the method is successfully registered
+ *
+ * @example
+ * ```typescript
+ * room.localParticipant?.registerRpcMethod(
+ * 'greet',
+ * async (requestId: string, callerIdentity: string, payload: string, responseTimeoutMs: number) => {
+ * console.log(`Received greeting from ${callerIdentity}: ${payload}`);
+ * return `Hello, ${callerIdentity}!`;
+ * }
+ * );
+ * ```
+ *
+ * The handler receives the following parameters:
+ * - `requestId`: A unique identifier for this RPC request
+ * - `callerIdentity`: The identity of the RemoteParticipant who initiated the RPC call
+ * - `payload`: The data sent by the caller (as a string)
+ * - `responseTimeoutMs`: The maximum time available to return a response
+ *
+ * The handler should return a Promise that resolves to a string.
+ * If unable to respond within `responseTimeoutMs`, the request will result in an error on the caller's side.
+ *
+ * You may throw errors of type `RpcError` with a string `message` in the handler,
+ * and they will be received on the caller's side with the message intact.
+ * Other errors thrown in your handler will not be transmitted as-is, and will instead arrive to the caller as `1500` ("Application Error").
+ */
+ registerRpcMethod(
+ method: string,
+ handler: (
+ requestId: string,
+ callerIdentity: string,
+ payload: string,
+ responseTimeoutMs: number,
+ ) => Promise,
+ ) {
+ this.rpcHandlers.set(method, handler);
+ }
+
+ /**
+ * Unregisters a previously registered RPC method.
+ *
+ * @param method - The name of the RPC method to unregister
+ */
+ unregisterRpcMethod(method: string) {
+ this.rpcHandlers.delete(method);
+ }
+
/**
* Control who can subscribe to LocalParticipant's published tracks.
*
@@ -1443,6 +1632,157 @@ export default class LocalParticipant extends Participant {
}
}
+ private handleIncomingRpcAck(requestId: string) {
+ const handler = this.pendingAcks.get(requestId);
+ if (handler) {
+ handler.resolve();
+ this.pendingAcks.delete(requestId);
+ } else {
+ console.error('Ack received for unexpected RPC request', requestId);
+ }
+ }
+
+ private handleIncomingRpcResponse(
+ requestId: string,
+ payload: string | null,
+ error: RpcError | null,
+ ) {
+ const handler = this.pendingResponses.get(requestId);
+ if (handler) {
+ handler.resolve(payload, error);
+ this.pendingResponses.delete(requestId);
+ } else {
+ console.error('Response received for unexpected RPC request', requestId);
+ }
+ }
+
+ private async handleIncomingRpcRequest(
+ callerIdentity: string,
+ requestId: string,
+ method: string,
+ payload: string,
+ responseTimeoutMs: number,
+ ) {
+ await this.publishRpcAck(callerIdentity, requestId);
+
+ const handler = this.rpcHandlers.get(method);
+
+ if (!handler) {
+ await this.publishRpcResponse(
+ callerIdentity,
+ requestId,
+ null,
+ RpcError.builtIn('UNSUPPORTED_METHOD'),
+ );
+ return;
+ }
+
+ let responseError: RpcError | null = null;
+ let responsePayload: string | null = null;
+
+ try {
+ const response = await handler(requestId, callerIdentity, payload, responseTimeoutMs);
+ if (byteLength(response) > MAX_PAYLOAD_BYTES) {
+ responseError = RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE');
+ console.warn(`RPC Response payload too large for ${method}`);
+ } else {
+ responsePayload = response;
+ }
+ } catch (error) {
+ if (error instanceof RpcError) {
+ responseError = error;
+ } else {
+ console.warn(
+ `Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
+ error,
+ );
+ responseError = RpcError.builtIn('APPLICATION_ERROR');
+ }
+ }
+ await this.publishRpcResponse(callerIdentity, requestId, responsePayload, responseError);
+ }
+
+ /** @internal */
+ private async publishRpcRequest(
+ destinationIdentity: string,
+ requestId: string,
+ method: string,
+ payload: string,
+ responseTimeoutMs: number,
+ ) {
+ const packet = new DataPacket({
+ destinationIdentities: [destinationIdentity],
+ kind: DataPacket_Kind.RELIABLE,
+ value: {
+ case: 'rpcRequest',
+ value: new RpcRequest({
+ id: requestId,
+ method,
+ payload,
+ responseTimeoutMs,
+ }),
+ },
+ });
+
+ await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
+ }
+
+ /** @internal */
+ private async publishRpcResponse(
+ destinationIdentity: string,
+ requestId: string,
+ payload: string | null,
+ error: RpcError | null,
+ ) {
+ const packet = new DataPacket({
+ destinationIdentities: [destinationIdentity],
+ kind: DataPacket_Kind.RELIABLE,
+ value: {
+ case: 'rpcResponse',
+ value: new RpcResponse({
+ requestId,
+ value: error
+ ? { case: 'error', value: error.toProto() }
+ : { case: 'payload', value: payload ?? '' },
+ }),
+ },
+ });
+
+ await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
+ }
+
+ /** @internal */
+ private async publishRpcAck(destinationIdentity: string, requestId: string) {
+ const packet = new DataPacket({
+ destinationIdentities: [destinationIdentity],
+ kind: DataPacket_Kind.RELIABLE,
+ value: {
+ case: 'rpcAck',
+ value: new RpcAck({
+ requestId,
+ }),
+ },
+ });
+
+ await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
+ }
+
+ /** @internal */
+ handleParticipantDisconnected(participantIdentity: string) {
+ for (const [id, { participantIdentity: pendingIdentity }] of this.pendingAcks) {
+ if (pendingIdentity === participantIdentity) {
+ this.pendingAcks.delete(id);
+ }
+ }
+
+ for (const [id, { participantIdentity: pendingIdentity, resolve }] of this.pendingResponses) {
+ if (pendingIdentity === participantIdentity) {
+ resolve(null, RpcError.builtIn('RECIPIENT_DISCONNECTED'));
+ this.pendingResponses.delete(id);
+ }
+ }
+ }
+
/** @internal */
setEnabledPublishCodecs(codecs: Codec[]) {
this.enabledPublishVideoCodecs = codecs.filter(
diff --git a/src/room/rpc.ts b/src/room/rpc.ts
new file mode 100644
index 0000000000..437661373b
--- /dev/null
+++ b/src/room/rpc.ts
@@ -0,0 +1,133 @@
+// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+import { RpcError as RpcError_Proto } from '@livekit/protocol';
+
+/**
+ * Specialized error handling for RPC methods.
+ *
+ * Instances of this type, when thrown in a method handler, will have their `message`
+ * serialized and sent across the wire. The sender will receive an equivalent error on the other side.
+ *
+ * Built-in types are included but developers may use any string, with a max length of 256 bytes.
+ */
+
+export class RpcError extends Error {
+ static MAX_MESSAGE_BYTES = 256;
+
+ static MAX_DATA_BYTES = 15360; // 15 KB
+
+ code: number;
+
+ data?: string;
+
+ /**
+ * Creates an error object with the given code and message, plus an optional data payload.
+ *
+ * If thrown in an RPC method handler, the error will be sent back to the caller.
+ *
+ * Error codes 1001-1999 are reserved for built-in errors (see RpcError.ErrorCode for their meanings).
+ */
+ constructor(code: number, message: string, data?: string) {
+ super(message);
+ this.code = code;
+ this.message = truncateBytes(message, RpcError.MAX_MESSAGE_BYTES);
+ this.data = data ? truncateBytes(data, RpcError.MAX_DATA_BYTES) : undefined;
+ }
+
+ /**
+ * @internal
+ */
+ static fromProto(proto: RpcError_Proto) {
+ return new RpcError(proto.code, proto.message, proto.data);
+ }
+
+ /**
+ * @internal
+ */
+ toProto() {
+ return new RpcError_Proto({
+ code: this.code as number,
+ message: this.message,
+ data: this.data,
+ });
+ }
+
+ static ErrorCode = {
+ APPLICATION_ERROR: 1500,
+ CONNECTION_TIMEOUT: 1501,
+ RESPONSE_TIMEOUT: 1502,
+ RECIPIENT_DISCONNECTED: 1503,
+ RESPONSE_PAYLOAD_TOO_LARGE: 1504,
+ SEND_FAILED: 1505,
+
+ UNSUPPORTED_METHOD: 1400,
+ RECIPIENT_NOT_FOUND: 1401,
+ REQUEST_PAYLOAD_TOO_LARGE: 1402,
+ UNSUPPORTED_SERVER: 1403,
+ } as const;
+
+ /**
+ * @internal
+ */
+ static ErrorMessage: Record = {
+ APPLICATION_ERROR: 'Application error in method handler',
+ CONNECTION_TIMEOUT: 'Connection timeout',
+ RESPONSE_TIMEOUT: 'Response timeout',
+ RECIPIENT_DISCONNECTED: 'Recipient disconnected',
+ RESPONSE_PAYLOAD_TOO_LARGE: 'Response payload too large',
+ SEND_FAILED: 'Failed to send',
+
+ UNSUPPORTED_METHOD: 'Method not supported at destination',
+ RECIPIENT_NOT_FOUND: 'Recipient not found',
+ REQUEST_PAYLOAD_TOO_LARGE: 'Request payload too large',
+ UNSUPPORTED_SERVER: 'RPC not supported by server',
+ } as const;
+
+ /**
+ * Creates an error object from the code, with an auto-populated message.
+ *
+ * @internal
+ */
+ static builtIn(key: keyof typeof RpcError.ErrorCode, data?: string): RpcError {
+ return new RpcError(RpcError.ErrorCode[key], RpcError.ErrorMessage[key], data);
+ }
+}
+
+/*
+ * Maximum payload size for RPC requests and responses. If a payload exceeds this size,
+ * the RPC call will fail with a REQUEST_PAYLOAD_TOO_LARGE(1402) or RESPONSE_PAYLOAD_TOO_LARGE(1504) error.
+ */
+export const MAX_PAYLOAD_BYTES = 15360; // 15 KB
+
+/**
+ * @internal
+ */
+export function byteLength(str: string): number {
+ const encoder = new TextEncoder();
+ return encoder.encode(str).length;
+}
+
+/**
+ * @internal
+ */
+export function truncateBytes(str: string, maxBytes: number): string {
+ if (byteLength(str) <= maxBytes) {
+ return str;
+ }
+
+ let low = 0;
+ let high = str.length;
+ const encoder = new TextEncoder();
+
+ while (low < high) {
+ const mid = Math.floor((low + high + 1) / 2);
+ if (encoder.encode(str.slice(0, mid)).length <= maxBytes) {
+ low = mid;
+ } else {
+ high = mid - 1;
+ }
+ }
+
+ return str.slice(0, low);
+}
diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json
index a7726ac324..c55ea0e2c3 100644
--- a/tsconfig.eslint.json
+++ b/tsconfig.eslint.json
@@ -10,5 +10,5 @@
"rollup.config.worker.js",
"vite.config.mjs"
],
- "exclude": ["dist/**"]
+ "exclude": ["dist/**", "examples/**/dist"]
}
diff --git a/tsconfig.json b/tsconfig.json
index a04ef15eb4..54622bcc39 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -20,7 +20,7 @@
"ignoreDeprecations": "5.0"
},
"exclude": ["dist", "**/*.test.ts"],
- "include": ["src/**/*"],
+ "include": ["src/**/*.ts"],
"typedocOptions": {
"entryPoints": ["src/index.ts"],
"excludeInternal": true,