Compare commits
234 Commits
update-lay
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
029802787a | ||
|
|
ad413d2791 | ||
|
|
d025a35e15 | ||
|
|
1761517afc | ||
|
|
9278260837 | ||
|
|
53f62708ce | ||
|
|
ba0d0b10c4 | ||
|
|
8953e9891c | ||
|
|
02703b1f83 | ||
|
|
c808e98820 | ||
|
|
56d5126acb | ||
|
|
ac3a728591 | ||
|
|
90a1c6fde9 | ||
|
|
7378a8f53e | ||
|
|
3142f9fe79 | ||
|
|
2a1575768f | ||
|
|
396c23aa3c | ||
|
|
96e441726c | ||
|
|
a853aa02a2 | ||
|
|
366632741c | ||
|
|
1046b5a0dd | ||
|
|
59d722f882 | ||
|
|
b08bb10f63 | ||
|
|
177648e6d5 | ||
|
|
b059b88be4 | ||
|
|
4e634dac54 | ||
|
|
b0c7dcbc9a | ||
|
|
ba7600bfc5 | ||
|
|
accb35c7e1 | ||
|
|
1add921ce0 | ||
|
|
2fe720c90b | ||
|
|
3f91e281b3 | ||
|
|
4ac182c244 | ||
|
|
43f7ff5001 | ||
|
|
30bd4b5a41 | ||
|
|
00433c75a4 | ||
|
|
becf3070b0 | ||
|
|
21e939e09c | ||
|
|
659cdcaf73 | ||
|
|
dbcc9bbb25 | ||
|
|
215b11e93f | ||
|
|
55aab084b0 | ||
|
|
96b5cd249e | ||
|
|
4751e7e989 | ||
|
|
0a56a74433 | ||
|
|
cb12d9a8fe | ||
|
|
9ae27bf32a | ||
|
|
f677b18879 | ||
|
|
f95b02e42b | ||
|
|
5f8af67ac6 | ||
|
|
1ef813e509 | ||
|
|
011e44b4f9 | ||
|
|
ed057612a0 | ||
|
|
5cdc49d90c | ||
|
|
073f0dc640 | ||
|
|
bbd4d5fbaf | ||
|
|
5f722c36e7 | ||
|
|
b5ccd7c087 | ||
|
|
13a339eb87 | ||
|
|
1d5cd9be26 | ||
|
|
68a10ff901 | ||
|
|
eabb559a82 | ||
|
|
e70dc6619f | ||
|
|
520816b983 | ||
|
|
b24992ad24 | ||
|
|
d9e064e971 | ||
|
|
8af00ab6ee | ||
|
|
2ec42f701d | ||
|
|
f77630d1e0 | ||
|
|
3cb163deee | ||
|
|
4ecd086f21 | ||
|
|
a1acc9ba22 | ||
|
|
0368ab83e6 | ||
|
|
082aa8480c | ||
|
|
ca2d41b05e | ||
|
|
db62cf0e1c | ||
|
|
3eb06e41e2 | ||
|
|
56e025d23d | ||
|
|
6c730a6dbc | ||
|
|
163e0d5f99 | ||
|
|
834dc2be42 | ||
|
|
7cddb59e2d | ||
|
|
8afed3a2f8 | ||
|
|
dac64bb1a9 | ||
|
|
c42a3ce1cf | ||
|
|
6455a4937c | ||
|
|
4bee373e85 | ||
|
|
e0c0453a02 | ||
|
|
7a83cc57fd | ||
|
|
7d6f61e12c | ||
|
|
6f841eb254 | ||
|
|
be5e3ffb1d | ||
|
|
0ba9d0b297 | ||
|
|
caad4bc550 | ||
|
|
450aa85b88 | ||
|
|
86af733b37 | ||
|
|
8d685e6ae5 | ||
|
|
05980df465 | ||
|
|
744fd05724 | ||
|
|
d1431687f0 | ||
|
|
87b86652dc | ||
|
|
115993fe69 | ||
|
|
f65f5dd33d | ||
|
|
d78eae9810 | ||
|
|
8fbe8fb716 | ||
|
|
6a9bd2be95 | ||
|
|
320135dd39 | ||
|
|
0ca445a89b | ||
|
|
76ea36cf69 | ||
|
|
b0b95f38a8 | ||
|
|
6ad700f538 | ||
|
|
11dcd1238a | ||
|
|
8f858267b4 | ||
|
|
cea8301e00 | ||
|
|
f2c4df0de6 | ||
|
|
eb3e11c37c | ||
|
|
f5b805f3a3 | ||
|
|
1c955c60d0 | ||
|
|
113dbe4f88 | ||
|
|
f6d685a158 | ||
|
|
60cb1afdf3 | ||
|
|
b4f482f9d7 | ||
|
|
3a83efa668 | ||
|
|
0ab6a48e13 | ||
|
|
9a853f285b | ||
|
|
1d552c9098 | ||
|
|
c806e90e42 | ||
|
|
3a2ce89a3d | ||
|
|
7c75a9ddd0 | ||
|
|
4d3adc1922 | ||
|
|
d925cb7843 | ||
|
|
59ea1f881b | ||
|
|
d059872667 | ||
|
|
bd1c67ba31 | ||
|
|
f178f2a611 | ||
|
|
477119a06f | ||
|
|
cbe07bcf4f | ||
|
|
34f50d0dee | ||
|
|
9ff200fa88 | ||
|
|
d7cefdfd47 | ||
|
|
c93b369a4f | ||
|
|
cf3f03bf3c | ||
|
|
2761e68dd8 | ||
|
|
a3d4fda6ae | ||
|
|
1abdc45ff3 | ||
|
|
5ef051658a | ||
|
|
c51a5173dd | ||
|
|
07b22a0d29 | ||
|
|
1663b008ed | ||
|
|
f930bf1447 | ||
|
|
8e9443351c | ||
|
|
6c7bfd4d3f | ||
|
|
68c5ce7cd2 | ||
|
|
5638025211 | ||
|
|
da7759d249 | ||
|
|
ba374ce229 | ||
|
|
cf84de4221 | ||
|
|
39a9b7da02 | ||
|
|
e990c19672 | ||
|
|
66b7f6026b | ||
|
|
21971e1895 | ||
|
|
246d6e91a4 | ||
|
|
d20571b0ef | ||
|
|
f6abd1cb4c | ||
|
|
bd021c9576 | ||
|
|
c3ca84ad66 | ||
|
|
153af9c673 | ||
|
|
c8cfb6598e | ||
|
|
b177b3b02e | ||
|
|
f83a5d8942 | ||
|
|
40475dc372 | ||
|
|
fd998e7b6b | ||
|
|
8ccc5d1a8b | ||
|
|
f71b567823 | ||
|
|
a9360ef452 | ||
|
|
3aaf976964 | ||
|
|
73813feb38 | ||
|
|
bdbc6d02ad | ||
|
|
848cf2ca17 | ||
|
|
49b44d0353 | ||
|
|
0d6838019d | ||
|
|
253b435fbe | ||
|
|
447a1fc9e5 | ||
|
|
5234b28917 | ||
|
|
69df748002 | ||
|
|
b711840349 | ||
|
|
69399bb2ff | ||
|
|
77528b28f4 | ||
|
|
fad38b696d | ||
|
|
e3fe104b05 | ||
|
|
4f9116707c | ||
|
|
3f25ba6f74 | ||
|
|
496591695a | ||
|
|
45ee463bc6 | ||
|
|
3ae55d0814 | ||
|
|
f74b50d5c8 | ||
|
|
0f237af827 | ||
|
|
e30aa5f1a5 | ||
|
|
f62e59168d | ||
|
|
99283ab63d | ||
|
|
6b3ef47f08 | ||
|
|
152a877054 | ||
|
|
3665c70105 | ||
|
|
294bd1b7ec | ||
|
|
8d12105b91 | ||
|
|
28b58a80bf | ||
|
|
a518b16b6b | ||
|
|
6eb33c6198 | ||
|
|
a56a119993 | ||
|
|
26bc6cdfc1 | ||
|
|
f3de3e0fa3 | ||
|
|
72119685ad | ||
|
|
84b74ea0f3 | ||
|
|
9fe20f6e87 | ||
|
|
27094be54e | ||
|
|
d377b016e2 | ||
|
|
5b94728bc9 | ||
|
|
5131f9e717 | ||
|
|
32a9a31d8b | ||
|
|
ae5907cbbd | ||
|
|
d9a31edb60 | ||
|
|
d3554aa6cb | ||
|
|
3ec9b43ce4 | ||
|
|
253ad630c7 | ||
|
|
d72149c97d | ||
|
|
449f9cbf25 | ||
|
|
b055ef0333 | ||
|
|
1d55311757 | ||
|
|
d52524241f | ||
|
|
3fdf2144ab | ||
|
|
e6d04aca16 | ||
|
|
684d575c08 | ||
|
|
7dd368476e | ||
|
|
fd905286a6 |
@ -141,8 +141,6 @@
|
||||
**/*.mov
|
||||
**/*.mkv
|
||||
**/*.webm
|
||||
**/*.mp3
|
||||
**/*.wav
|
||||
**/*.flac
|
||||
|
||||
# ====================================================
|
||||
|
||||
53
.github/workflows/backend-integration-test.yaml
vendored
53
.github/workflows/backend-integration-test.yaml
vendored
@ -2,14 +2,14 @@ name: Backend Integration Tests
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'backend/src/**'
|
||||
- 'backend/package.json'
|
||||
- 'backend/pnpm-lock.yaml'
|
||||
- 'backend/tests/**'
|
||||
- 'meet-ce/backend/src/**'
|
||||
- 'meet-ce/backend/package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'meet-ce/backend/tests/**'
|
||||
- '.github/workflows/backend-integration-test.yaml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'backend/src/**'
|
||||
- 'meet-ce/backend/src/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
use-aws:
|
||||
@ -22,7 +22,7 @@ on:
|
||||
jobs:
|
||||
build-components:
|
||||
name: Build OpenVidu Components Angular
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
steps:
|
||||
- name: Build Components
|
||||
id: build
|
||||
@ -33,26 +33,19 @@ jobs:
|
||||
test-api:
|
||||
name: ${{ matrix.test-name }}
|
||||
needs: build-components
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- test-name: 'Rooms API Tests'
|
||||
test-script: 'test:integration-backend-rooms'
|
||||
- test-name: 'Room Management API Tests (Rooms, Meetings)'
|
||||
test-script: 'test:integration-backend-room-management'
|
||||
- test-name: 'Webhook Tests'
|
||||
test-script: 'test:integration-backend-webhooks'
|
||||
- test-name: 'Security API Tests'
|
||||
test-script: 'test:integration-backend-security'
|
||||
azure-container: 'openvidu-appdata-security'
|
||||
- test-name: 'Global Config API Tests'
|
||||
test-script: 'test:integration-backend-global-config'
|
||||
- test-name: 'Participants API Tests'
|
||||
test-script: 'test:integration-backend-participants'
|
||||
- test-name: 'Meetings API Tests'
|
||||
test-script: 'test:integration-backend-meetings'
|
||||
- test-name: 'Users API Tests'
|
||||
test-script: 'test:integration-backend-users'
|
||||
- test-name: 'Auth & Security API Tests (Security, Auth, API Keys, Users)'
|
||||
test-script: 'test:integration-backend-auth-security'
|
||||
- test-name: 'Config & Analytics API Tests (Global Config, Analytics)'
|
||||
test-script: 'test:integration-backend-config-analytics'
|
||||
steps:
|
||||
- name: Install LK CLI
|
||||
run: curl -sSL https://get.livekit.io/cli | bash
|
||||
@ -85,13 +78,11 @@ jobs:
|
||||
with:
|
||||
build_components_angular: 'true'
|
||||
components_artifact_name: ${{ needs.build-components.outputs.artifact_name }}
|
||||
env:
|
||||
MEET_AZURE_CONTAINER_NAME: ${{ matrix.azure-container || '' }}
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm run ${{ matrix.test-script }}
|
||||
env:
|
||||
JEST_JUNIT_OUTPUT_DIR: './backend/reports/'
|
||||
JEST_JUNIT_OUTPUT_DIR: './meet-ce/backend/reports/'
|
||||
|
||||
- name: Upload OpenVidu Meet logs
|
||||
if: failure()
|
||||
@ -115,7 +106,7 @@ jobs:
|
||||
|
||||
start-aws-runner-s3:
|
||||
name: Prepare AWS runner (S3)
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
if: ${{ inputs.use-aws != 'false' }}
|
||||
outputs:
|
||||
label: ${{ steps.start-ec2-runner.outputs.label }}
|
||||
@ -138,7 +129,7 @@ jobs:
|
||||
|
||||
start-aws-runner-abs:
|
||||
name: Prepare AWS runner (ABS)
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
if: ${{ inputs.use-aws != 'false' }}
|
||||
outputs:
|
||||
label: ${{ steps.start-ec2-runner.outputs.label }}
|
||||
@ -161,7 +152,7 @@ jobs:
|
||||
|
||||
start-aws-runner-gcs:
|
||||
name: Prepare AWS runner (GCS)
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
if: ${{ inputs.use-aws != 'false' }}
|
||||
outputs:
|
||||
label: ${{ steps.start-ec2-runner.outputs.label }}
|
||||
@ -190,7 +181,7 @@ jobs:
|
||||
- start-aws-runner-gcs
|
||||
- build-components
|
||||
if: ${{ always() && (needs.start-aws-runner-s3.result == 'success' || needs.start-aws-runner-s3.result == 'skipped') && (needs.start-aws-runner-abs.result == 'success' || needs.start-aws-runner-abs.result == 'skipped') && (needs.start-aws-runner-gcs.result == 'success' || needs.start-aws-runner-gcs.result == 'skipped') }}
|
||||
runs-on: ${{ (matrix.storage-provider == 's3' && needs.start-aws-runner-s3.outputs.label) || (matrix.storage-provider == 'abs' && needs.start-aws-runner-abs.outputs.label) || (matrix.storage-provider == 'gcs' && needs.start-aws-runner-gcs.outputs.label) || 'ov-actions-runner' }}
|
||||
runs-on: ${{ (matrix.storage-provider == 's3' && needs.start-aws-runner-s3.outputs.label) || (matrix.storage-provider == 'abs' && needs.start-aws-runner-abs.outputs.label) || (matrix.storage-provider == 'gcs' && needs.start-aws-runner-gcs.outputs.label) || vars.LABEL_WORKER_SELFHOSTED }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -292,7 +283,7 @@ jobs:
|
||||
- name: Run tests
|
||||
run: pnpm run test:integration-backend-recordings
|
||||
env:
|
||||
JEST_JUNIT_OUTPUT_DIR: './backend/reports/'
|
||||
JEST_JUNIT_OUTPUT_DIR: './meet-ce/backend/reports/'
|
||||
MEET_BLOB_STORAGE_MODE: ${{ matrix.storage-provider }}
|
||||
# ABS variables
|
||||
MEET_AZURE_ACCOUNT_NAME: ${{ matrix.storage-provider == 'abs' && vars.MEET_AZURE_ACCOUNT_NAME || '' }}
|
||||
@ -315,7 +306,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: recordings-test-${{ matrix.storage-provider }}-openvidu-meet-logs
|
||||
path: backend/meet_backend.log
|
||||
path: meet_backend.log
|
||||
retention-days: 2
|
||||
|
||||
- name: Clean up credentials
|
||||
@ -335,7 +326,7 @@ jobs:
|
||||
- start-aws-runner-abs
|
||||
- start-aws-runner-gcs
|
||||
- test-recordings
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
if: ${{ always() }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -359,7 +350,7 @@ jobs:
|
||||
- build-components
|
||||
- test-api
|
||||
- test-recordings
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
if: ${{ always() }}
|
||||
steps:
|
||||
- name: Remove Artifact
|
||||
|
||||
2
.github/workflows/backend-unit-test.yaml
vendored
2
.github/workflows/backend-unit-test.yaml
vendored
@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
unit-test:
|
||||
name: Backend Unit Tests
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
|
||||
10
.github/workflows/wc-e2e-test.yaml
vendored
10
.github/workflows/wc-e2e-test.yaml
vendored
@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
e2e-tests:
|
||||
name: WebComponent E2E Tests
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
@ -17,6 +17,8 @@ jobs:
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.18.3
|
||||
- name: Install LK CLI
|
||||
run: curl -sSL https://get.livekit.io/cli | bash
|
||||
- name: Setup OpenVidu Local Deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
with:
|
||||
@ -56,7 +58,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: room-test-results
|
||||
path: frontend/webcomponent/test-results/
|
||||
path: meet-ce/frontend/webcomponent/test-results/
|
||||
retention-days: 2
|
||||
|
||||
- name: Upload TestApp logs on failure
|
||||
@ -64,7 +66,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: room-test-testapp-logs
|
||||
path: testapp/testapp.log
|
||||
path: testapp.log
|
||||
retention-days: 2
|
||||
|
||||
- name: Upload OpenVidu Meet logs on failure
|
||||
@ -72,7 +74,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: room-test-openvidu-meet-logs
|
||||
path: backend/meet_backend.log
|
||||
path: meet_backend.log
|
||||
retention-days: 2
|
||||
|
||||
- name: Clean up
|
||||
|
||||
2
.github/workflows/wc-unit-test.yaml
vendored
2
.github/workflows/wc-unit-test.yaml
vendored
@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
unit-test:
|
||||
name: WebComponent Unit Tests
|
||||
runs-on: ov-actions-runner
|
||||
runs-on: ${{ vars.LABEL_WORKER_SELFHOSTED }}
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -37,7 +37,7 @@ pnpm-debug.log*
|
||||
|
||||
|
||||
**/**/test-results
|
||||
**/**/public/
|
||||
**/backend/public/
|
||||
|
||||
**/*/coverage
|
||||
**/**/test-results
|
||||
|
||||
52
README.md
52
README.md
@ -8,8 +8,8 @@ OpenVidu Meet is a fully featured video conferencing application built with Angu
|
||||
2. [Prerequisites](#prerequisites)
|
||||
3. [Getting Started](#getting-started)
|
||||
4. [Development](#development)
|
||||
- [Development Mode](#development-mode)
|
||||
- [Manual Development Setup](#manual-development-setup)
|
||||
- [Development Mode](#development-mode)
|
||||
- [Manual Development Setup](#manual-development-setup)
|
||||
5. [Building](#building)
|
||||
6. [Testing](#testing)
|
||||
7. [Documentation](#documentation)
|
||||
@ -26,12 +26,12 @@ The OpenVidu Meet application is a monorepo managed with **pnpm workspaces** and
|
||||
### Core Components
|
||||
|
||||
- **Frontend** (`frontend/`): Angular 20 application providing the user interface
|
||||
- **shared-meet-components**: Reusable Angular library with shared components for administration and preferences
|
||||
- Integrates [openvidu-components-angular](https://github.com/OpenVidu/openvidu/tree/master/openvidu-components-angular) for core video conferencing functionality
|
||||
- **shared-meet-components**: Reusable Angular library with shared components for administration and preferences
|
||||
- Integrates [openvidu-components-angular](https://github.com/OpenVidu/openvidu/tree/master/openvidu-components-angular) for core video conferencing functionality
|
||||
|
||||
- **Backend** (`backend/`): Node.js/TypeScript REST API server
|
||||
- Manages rooms, participants, recordings, and authentication
|
||||
- Serves the compiled frontend in production
|
||||
- Manages rooms, participants, recordings, and authentication
|
||||
- Serves the compiled frontend in production
|
||||
|
||||
- **Typings** (`typings/`): Shared TypeScript type definitions used across frontend and backend
|
||||
|
||||
@ -46,10 +46,9 @@ Before starting, ensure you have the following installed:
|
||||
- **Node.js**: Version 22 or higher
|
||||
- **pnpm**: Package manager (will be installed automatically by meet.sh if missing)
|
||||
- **LiveKit**: For local testing (optional)
|
||||
```bash
|
||||
curl -sSL https://get.livekit.io/cli | bash
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -sSL https://get.livekit.io/cli | bash
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
@ -82,7 +81,7 @@ cd openvidu-meet
|
||||
|
||||
Then, the application will be available at [http://localhost:6080](http://localhost:6080).
|
||||
|
||||
> **Note:** Livereload is also available at [http://localhost:5080](http://localhost:5080).
|
||||
> **Note:** Livereload is also available at [http://localhost:6081](http://localhost:6081).
|
||||
|
||||
## Development
|
||||
|
||||
@ -95,6 +94,7 @@ The recommended way to develop is using the integrated development mode that wat
|
||||
```
|
||||
|
||||
This command starts concurrent watchers for:
|
||||
|
||||
- **openvidu-components-angular**: Core Angular components library
|
||||
- **Typings**: Shared type definitions with automatic sync
|
||||
- **Backend**: Node.js server with nodemon auto-restart
|
||||
@ -103,6 +103,7 @@ This command starts concurrent watchers for:
|
||||
|
||||
> [!NOTE]
|
||||
> The backend uses `backend/.env.development` for environment variables during development. Configure your LiveKit credentials there:
|
||||
>
|
||||
> ```env
|
||||
> LIVEKIT_URL=ws://localhost:7880
|
||||
> LIVEKIT_API_KEY=your-api-key
|
||||
@ -170,6 +171,7 @@ The `meet.sh` script supports flags to optimize CI/CD pipelines:
|
||||
```
|
||||
|
||||
**Available flags:**
|
||||
|
||||
- `--skip-install`: Skip dependency installation
|
||||
- `--skip-build`: Skip build steps
|
||||
- `--skip-typings`: Skip typings build (use when already built)
|
||||
@ -225,8 +227,9 @@ The test app will be available at [http://localhost:5080](http://localhost:5080)
|
||||
```
|
||||
|
||||
Documentation files will be generated in:
|
||||
|
||||
- **Webcomponent**: `docs/webcomponent-*.md` (events, commands, attributes)
|
||||
- **REST API**: `backend/public/openapi/public.html`
|
||||
- **REST API**: `meet-ce/backend/public/openapi/public.html`
|
||||
|
||||
If you specify an output directory, the documentation will be copied there.
|
||||
|
||||
@ -289,35 +292,41 @@ openvidu-meet/
|
||||
│ ├── src/
|
||||
│ │ ├── api-key.ts
|
||||
│ │ ├── auth-config.ts
|
||||
│ │ ├── participant.ts
|
||||
│ │ ├── event.model.ts
|
||||
│ │ ├── room.ts
|
||||
│ │ └── ...
|
||||
│ └── package.json
|
||||
│
|
||||
├── frontend/ # Angular frontend application
|
||||
├── frontend/ # Angular frontend application
|
||||
│ ├── src/ # Main application source
|
||||
│ ├── projects/
|
||||
│ │ └── shared-meet-components/ # Reusable Angular library
|
||||
│ └── webcomponent/ # Web component build
|
||||
│
|
||||
├── backend/ # Node.js/Express backend
|
||||
├── backend/ # Node.js/Express backend
|
||||
│ ├── src/
|
||||
│ │ ├── config/ # Configuration files
|
||||
│ │ ├── controllers/ # REST API controllers
|
||||
│ │ ├── services/ # Business logic
|
||||
│ │ ├── helpers/ # Helper functions
|
||||
│ │ ├── middleware/ # Express middleware
|
||||
│ │ ├── migrations/ # Database migration scripts
|
||||
│ │ ├── models/ # Domain models
|
||||
│ │ ├── repositories/ # Database interaction
|
||||
│ │ ├── routes/ # API route definitions
|
||||
│ │ ├── services/ # Business logic
|
||||
│ │ ├── utils/ # Utility functions
|
||||
│ │ └── environment.ts # Environment configuration
|
||||
│ ├── openapi/ # OpenAPI specifications
|
||||
│ └── public/ # Static files (includes built frontend)
|
||||
│
|
||||
├── testapp/ # Testing application
|
||||
├── testapp/ # Testing application
|
||||
│ ├── src/
|
||||
│ └── public/
|
||||
│
|
||||
├── docker/ # Docker build files
|
||||
├── docker/ # Docker build files
|
||||
│ └── create_image.sh
|
||||
│
|
||||
├── docs/ # Generated documentation
|
||||
├── scripts/ # Build and utility scripts
|
||||
├── docs/ # Generated documentation
|
||||
├── scripts/ # Build and utility scripts
|
||||
└── openvidu-meet-pro/ # Professional Edition (separate license)
|
||||
```
|
||||
|
||||
@ -412,6 +421,7 @@ Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for detai
|
||||
|
||||
- [OpenVidu Website](https://openvidu.io/)
|
||||
- [OpenVidu Meet](https://openvidu.io/latest/meet/)
|
||||
|
||||
---
|
||||
|
||||
For questions and support, visit our [community forum](https://openvidu.discourse.group/).
|
||||
|
||||
3
meet-ce/.vscode/settings.json
vendored
3
meet-ce/.vscode/settings.json
vendored
@ -1,8 +1,9 @@
|
||||
{
|
||||
"jest.jestCommandLine": "node --experimental-vm-modules ../../node_modules/.bin/jest",
|
||||
"jest.jestCommandLine": "node --experimental-vm-modules ../../node_modules/.bin/jest --config jest.integration.config.mjs",
|
||||
"jest.rootPath": "backend",
|
||||
"jest.nodeEnv": {
|
||||
"NODE_OPTIONS": "--experimental-vm-modules"
|
||||
},
|
||||
"jest.runMode": "on-demand"
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
USE_HTTPS=false
|
||||
MEET_LOG_LEVEL=verbose
|
||||
MEET_LOG_LEVEL=debug
|
||||
SERVER_CORS_ORIGIN=*
|
||||
MEET_INITIAL_API_KEY=meet-api-key
|
||||
MEET_INITIAL_WEBHOOK_ENABLED=true
|
||||
MEET_INITIAL_WEBHOOK_URL=http://localhost:5080/webhook
|
||||
MEET_INITIAL_WEBHOOK_URL=http://localhost:5080/webhook
|
||||
MEETING_DEPARTURE_TIMEOUT=1s
|
||||
@ -23,9 +23,26 @@ pnpm install
|
||||
pnpm run build:prod
|
||||
```
|
||||
|
||||
## Storage Structure
|
||||
## Storage Architecture
|
||||
|
||||
The OpenVidu Meet backend uses an S3 bucket to store all application data, including rooms, recordings, user information, and system config. The bucket follows a hierarchical structure organized as follows:
|
||||
The OpenVidu Meet backend uses **MongoDB** as its primary data storage system for all application data, including rooms, recordings, user information, API keys, and system configuration.
|
||||
|
||||
### MongoDB Collections
|
||||
|
||||
The application manages the following MongoDB collections:
|
||||
|
||||
- **`meetglobalconfigs`**: System-wide configuration (singleton collection)
|
||||
- **`meetusers`**: User accounts with authentication and roles
|
||||
- **`meetapikeys`**: API keys for authentication
|
||||
- **`meetrooms`**: Room configurations and metadata
|
||||
- **`meetrecordings`**: Recording metadata and access information
|
||||
- **`meetmigrations`**: Migration tracking for data and schema migrations
|
||||
|
||||
Each document in these collections includes a `schemaVersion` field for schema evolution tracking (internal use only, not exposed via API).
|
||||
|
||||
### Legacy Storage (S3/ABS/GCS)
|
||||
|
||||
Prior versions of OpenVidu Meet used cloud object storage (S3, Azure Blob Storage, or Google Cloud Storage) for data persistence. The legacy storage structure followed this organization:
|
||||
|
||||
### Bucket Structure
|
||||
|
||||
@ -109,6 +126,45 @@ Where:
|
||||
|
||||
This naming convention ensures uniqueness and provides traceability between the recording file, its metadata, and the originating room session.
|
||||
|
||||
---
|
||||
|
||||
## Data Migration System
|
||||
|
||||
OpenVidu Meet includes a comprehensive migration system to handle data persistence changes and schema evolution.
|
||||
|
||||
### Legacy Storage to MongoDB Migration
|
||||
|
||||
On first startup, the application automatically migrates existing data from legacy storage (S3/Azure Blob Storage/Google Cloud Storage) to MongoDB. This migration:
|
||||
|
||||
- **Runs automatically** on application startup if legacy storage is configured
|
||||
- **Is idempotent** - safe to run multiple times (skips already migrated data)
|
||||
- **Preserves all data** - rooms, recordings, users, API keys, and global config
|
||||
- **Tracks progress** in the `meetmigrations` collection
|
||||
- **Is HA-safe** using distributed locks to prevent concurrent migrations
|
||||
|
||||
### MongoDB Schema Migration System
|
||||
|
||||
The application uses a schema versioning system to safely evolve MongoDB document structures over time. This system:
|
||||
|
||||
- **Runs automatically** at startup before accepting requests
|
||||
- **Tracks schema versions** via the `schemaVersion` field in each document
|
||||
- **Supports forward-only migrations** (v1 → v2 → v3)
|
||||
- **Processes in batches** for efficiency with large collections
|
||||
- **Is HA-safe** using distributed locks
|
||||
- **Validates before execution** to ensure migration safety
|
||||
|
||||
Schema migrations handle scenarios like:
|
||||
|
||||
- Adding new required fields with default values
|
||||
- Removing deprecated fields
|
||||
- Renaming or restructuring fields
|
||||
- Data type transformations
|
||||
|
||||
For detailed information about creating and managing schema migrations, see:
|
||||
📖 **[Schema Migration Documentation](./src/migrations/README.md)**
|
||||
|
||||
---
|
||||
|
||||
## Recordings
|
||||
|
||||
The recording feature is based on the following key concepts:
|
||||
@ -140,10 +196,10 @@ flowchart TD
|
||||
L -- "Error (recording not found, already stopped,\nor unknown error)" --> O["Reject Request"] --> J
|
||||
```
|
||||
|
||||
4. **Failure handling**:
|
||||
3. **Failure handling**:
|
||||
If an OpenVidu instance crashes while a recording is active, the lock remains in place. This scenario can block subsequent recording attempts if the lock is not released promptly. To mitigate this issue, a lock garbage collector is implemented to periodically clean up orphaned locks.
|
||||
|
||||
The garbage collector runs when the OpenVidu deployment starts, and then every 30 minutes.
|
||||
The garbage collector runs when the OpenVidu deployment starts, and then every 15 minutes.
|
||||
|
||||
```mermaid
|
||||
graph TD;
|
||||
@ -169,46 +225,53 @@ graph TD;
|
||||
L --> M
|
||||
M -->|More rooms| E
|
||||
M -->|No more rooms| N[Process completed]
|
||||
|
||||
```
|
||||
|
||||
5. **Stale recordings cleanup**:
|
||||
To handle recordings that become stale due to network issues, LiveKit or Egress crashes, or other unexpected situations, a separate cleanup process runs every 15 minutes to identify and abort recordings that haven't been updated within a configured threshold (5 minutes by default).
|
||||
4. **Stale recordings cleanup**:
|
||||
To handle recordings that become stale due to network issues, LiveKit or Egress crashes, or other unexpected situations, a separate cleanup process runs every 14 minutes to identify and abort recordings that haven't been updated within a configured threshold (5 minutes by default).
|
||||
|
||||
```mermaid
|
||||
graph TD;
|
||||
A[Initiate stale recordings cleanup] --> B[Get all in-progress recordings from LiveKit]
|
||||
A[Initiate stale recordings cleanup] --> B[Get all active recordings from database<br/>ACTIVE or ENDING status]
|
||||
B -->|Error| C[Log error and exit]
|
||||
B -->|No recordings found| D[Log and exit]
|
||||
B -->|Recordings found| E[Process recordings in batches of 10]
|
||||
|
||||
E --> F[For each recording in batch]
|
||||
F --> G[Extract recording ID and updatedAt]
|
||||
G --> H[Get recording status from storage]
|
||||
F --> G[Extract recordingId, roomId and egressId]
|
||||
G --> H[Check for corresponding egress in LiveKit]
|
||||
|
||||
H -->|Recording already ABORTED| I[Mark as already processed]
|
||||
H -->|Recording active| J[Check if updatedAt exists]
|
||||
H -->|No egress found| I[Recording is stale - no egress exists]
|
||||
H -->|Egress exists| J[Extract updatedAt from egress]
|
||||
|
||||
J -->|No updatedAt timestamp| K[Keep as fresh - log warning]
|
||||
J -->|Has updatedAt| L[Calculate if stale]
|
||||
I --> K[Update status to ABORTED in database]
|
||||
K --> L[Log successful abort - no egress found]
|
||||
|
||||
L -->|Still fresh| M[Log as fresh]
|
||||
L -->|Is stale| N[Abort stale recording]
|
||||
J -->|No updatedAt timestamp| M[Keep as fresh - log warning]
|
||||
J -->|Has updatedAt| N[Check if recording age is stale]
|
||||
|
||||
N --> O[Update status to ABORTED in storage]
|
||||
N --> P[Stop egress in LiveKit]
|
||||
O --> Q[Log successful abort]
|
||||
P --> Q
|
||||
N -->|Age not stale| O[Log as fresh]
|
||||
N -->|Age is stale| P[Check room existence]
|
||||
|
||||
I --> R[Continue to next recording]
|
||||
K --> R
|
||||
M --> R
|
||||
Q --> R
|
||||
P -->|Room does not exist| Q[Mark as stale]
|
||||
P -->|Room exists| R[Check if room has participants]
|
||||
|
||||
R -->|More recordings in batch| F
|
||||
R -->|Batch complete| S[Process next batch]
|
||||
S -->|More batches| E
|
||||
S -->|All batches processed| T[Log completion metrics]
|
||||
T --> U[Process completed]
|
||||
R -->|No participants| Q
|
||||
R -->|Has participants| O
|
||||
|
||||
Q --> S[Update status to ABORTED in database]
|
||||
Q --> T[Stop egress in LiveKit]
|
||||
S --> U[Log successful abort]
|
||||
T --> U
|
||||
|
||||
L --> V[Continue to next recording]
|
||||
M --> V
|
||||
O --> V
|
||||
U --> V
|
||||
|
||||
V -->|More recordings in batch| F
|
||||
V -->|Batch complete| W[Process next batch]
|
||||
W -->|More batches| E
|
||||
W -->|All batches processed| X[Log completion metrics]
|
||||
X --> Y[Process completed]
|
||||
```
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
export * from './src/routes/index.js';
|
||||
export * from './src/controllers/index.js';
|
||||
export * from './src/services/index.js';
|
||||
export * from './src/models/index.js';
|
||||
export * from './src/helpers/index.js';
|
||||
// Main entry point for @openvidu-meet/backend package
|
||||
export * from './src/config/internal-config.js';
|
||||
export * from './src/environment.js';
|
||||
export * from './src/server.js';
|
||||
|
||||
// Export other modules as needed
|
||||
export * from './src/config/index.js';
|
||||
export * from './src/controllers/index.js';
|
||||
export * from './src/helpers/index.js';
|
||||
export * from './src/middlewares/index.js';
|
||||
export * from './src/models/index.js';
|
||||
export * from './src/routes/index.js';
|
||||
export * from './src/services/index.js';
|
||||
export * from './src/utils/index.js';
|
||||
|
||||
|
||||
@ -15,16 +15,19 @@ const jestConfig = {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1' // Allow importing js files and resolving to ts files
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.tsx?$': ['ts-jest', {
|
||||
tsconfig: {
|
||||
module: 'esnext',
|
||||
moduleResolution: 'node16',
|
||||
esModuleInterop: true,
|
||||
allowSyntheticDefaultImports: true,
|
||||
isolatedModules: true
|
||||
},
|
||||
useESM: true
|
||||
}]
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: {
|
||||
module: 'esnext',
|
||||
moduleResolution: 'node16',
|
||||
esModuleInterop: true,
|
||||
allowSyntheticDefaultImports: true,
|
||||
isolatedModules: true
|
||||
},
|
||||
useESM: true
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
12
meet-ce/backend/jest.integration.config.mjs
Normal file
12
meet-ce/backend/jest.integration.config.mjs
Normal file
@ -0,0 +1,12 @@
|
||||
import baseConfig from './jest.config.mjs';
|
||||
|
||||
const integrationConfig = {
|
||||
...baseConfig,
|
||||
|
||||
runInBand: true,
|
||||
forceExit: true,
|
||||
detectOpenHandles: true,
|
||||
testMatch: ['**/tests/integration/**/*.(spec|test).ts'],
|
||||
};
|
||||
|
||||
export default integrationConfig;
|
||||
@ -1,6 +0,0 @@
|
||||
description: >
|
||||
The cookie containing the access token.
|
||||
This cookie is used to authenticate the user in subsequent requests.
|
||||
schema:
|
||||
type: string
|
||||
example: 'OvMeetAccessToken=token_123456; Path=/; HttpOnly; SameSite=Strict'
|
||||
@ -1,6 +0,0 @@
|
||||
description: >
|
||||
The cookie containing the participant token.
|
||||
This cookie is used to authenticate the participant in the room.
|
||||
schema:
|
||||
type: string
|
||||
example: 'OvMeetParticipantToken=token_123456; Path=/; HttpOnly; SameSite=Strict'
|
||||
@ -1,6 +0,0 @@
|
||||
description: >
|
||||
The cookie containing the recording token.
|
||||
This cookie is used to access the recordings in a room.
|
||||
schema:
|
||||
type: string
|
||||
example: 'OvMeetRecordingToken=token_123456; Path=/; HttpOnly; SameSite=Strict'
|
||||
@ -1,6 +0,0 @@
|
||||
description: >
|
||||
The cookie containing the refresh token.
|
||||
This cookie is used to refresh the access token when it expires.
|
||||
schema:
|
||||
type: string
|
||||
example: 'OvMeetRefreshToken=token_123456; Path=/meet/internal-api/v1/auth; HttpOnly; SameSite=Strict'
|
||||
@ -1,7 +0,0 @@
|
||||
name: participantIdentity
|
||||
in: path
|
||||
required: true
|
||||
description: The identity of the participant.
|
||||
schema:
|
||||
type: string
|
||||
example: 'Alice'
|
||||
@ -1,6 +1,6 @@
|
||||
name: secret
|
||||
in: path
|
||||
required: true
|
||||
description: The secret value from the room URL used to connect to the room.
|
||||
description: The secret value from the room URL used to access the room.
|
||||
schema:
|
||||
type: string
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
name: x-participant-role
|
||||
in: header
|
||||
description: |
|
||||
The role of the participant in the meeting. It can be one of the following values:
|
||||
- `moderator`: Can manage the room and its participants.
|
||||
- `speaker`: Can publish media streams to the room.
|
||||
|
||||
This is required to distinguish roles when multiple are present in the participant token
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum: ['moderator', 'speaker']
|
||||
@ -1,12 +1,7 @@
|
||||
name: status
|
||||
in: query
|
||||
required: false
|
||||
description: |
|
||||
Filter recordings by their status.
|
||||
|
||||
You can provide multiple statuses as a comma-separated list (e.g., `status=active,failed`).
|
||||
|
||||
> ⚠️ **Note:** Using this filter may impact performance for large datasets.
|
||||
description: Filter recordings by their status.
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
|
||||
@ -2,7 +2,8 @@ name: roomName
|
||||
in: query
|
||||
required: false
|
||||
description: >
|
||||
The name of the room.
|
||||
Filter rooms by name. The search is case-insensitive and matches rooms that contain the specified text.
|
||||
For example, 'room' will match 'MyRoom', 'room123', and 'Conference Room'.
|
||||
schema:
|
||||
type: string
|
||||
example: 'room'
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
name: status
|
||||
in: query
|
||||
required: false
|
||||
description: Filter rooms by their status.
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- open
|
||||
- active_meeting
|
||||
- closed
|
||||
@ -0,0 +1,6 @@
|
||||
name: sortField
|
||||
in: query
|
||||
required: false
|
||||
description: The field by which to sort the results.
|
||||
schema:
|
||||
type: string
|
||||
@ -0,0 +1,9 @@
|
||||
name: sortOrder
|
||||
in: query
|
||||
required: false
|
||||
description: The order in which to sort the results. Use "asc" for ascending order and "desc" for descending order.
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- asc
|
||||
- desc
|
||||
@ -0,0 +1,6 @@
|
||||
description: Create AI assistant activation request
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/ai-assistant-create-request.yaml'
|
||||
@ -1,6 +0,0 @@
|
||||
description: Participant details
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/meet-participant-options.yaml'
|
||||
@ -0,0 +1,6 @@
|
||||
description: Room member token options
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/room-member-token-options.yaml'
|
||||
@ -1,11 +0,0 @@
|
||||
description: Room to record
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
roomId:
|
||||
type: string
|
||||
description: The unique identifier of the room to record.
|
||||
example: 'room-123'
|
||||
@ -0,0 +1,35 @@
|
||||
description: Room to record
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
roomId:
|
||||
type: string
|
||||
description: The unique identifier of the room to record.
|
||||
example: 'room-123'
|
||||
config:
|
||||
type: object
|
||||
description: |
|
||||
Optional configuration to override the room's recording configuration for this specific recording.
|
||||
If not provided, the recording will use the configuration defined in the room's config.
|
||||
properties:
|
||||
layout:
|
||||
type: string
|
||||
enum:
|
||||
- grid
|
||||
- speaker
|
||||
- single-speaker
|
||||
example: speaker
|
||||
description: |
|
||||
Defines the layout of the recording. This will override the room's default recording layout.
|
||||
Options are:
|
||||
- `grid`: All participants are shown in a grid layout.
|
||||
- `speaker`: The active speaker is shown prominently, with other participants in smaller thumbnails.
|
||||
- `single-speaker`: Only the active speaker is shown in the recording.
|
||||
encoding:
|
||||
description: Defines the encoding settings for the recording. This will override the room's default recording encoding.
|
||||
oneOf:
|
||||
- $ref: '../schemas/meet-room-config.yaml#/MeetRecordingEncodingPreset'
|
||||
- $ref: '../schemas/meet-room-config.yaml#/MeetRecordingEncodingOptions'
|
||||
@ -11,6 +11,7 @@ content:
|
||||
chat:
|
||||
enabled: true
|
||||
recording:
|
||||
enabled: false
|
||||
enabled: true
|
||||
encoding: H264_720P_30
|
||||
virtualBackground:
|
||||
enabled: true
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
description: New room status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- open
|
||||
- active_meeting
|
||||
- closed
|
||||
example: closed
|
||||
description: |
|
||||
The new status of the room. Options are:
|
||||
- open: The room will be open for new participants to join.
|
||||
- closed: The room will be closed to new participants.
|
||||
@ -2,7 +2,7 @@ description: Conflict — The recording cannot be started due to resource state
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/error.yaml'
|
||||
$ref: '../schemas/error.yaml'
|
||||
examples:
|
||||
already_recording:
|
||||
summary: Room is already being recorded
|
||||
@ -2,7 +2,7 @@ description: Conflict — The recording is starting or already stopped
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/error.yaml'
|
||||
$ref: '../schemas/error.yaml'
|
||||
examples:
|
||||
starting_recording:
|
||||
summary: Recording is starting
|
||||
@ -0,0 +1,8 @@
|
||||
description: Room not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/error.yaml'
|
||||
example:
|
||||
error: 'Room Error'
|
||||
message: 'Room "room_123" has an active meeting'
|
||||
@ -2,7 +2,7 @@ description: Service Unavailable — The recording service is unavailable
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/error.yaml'
|
||||
$ref: '../schemas/error.yaml'
|
||||
examples:
|
||||
starting_timeout:
|
||||
summary: Recording service timed out
|
||||
@ -1,4 +1,4 @@
|
||||
description: Forbidden — Insufficient permissions
|
||||
description: Forbidden - Insufficient Permissions
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
description: Forbidden
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/error.yaml'
|
||||
examples:
|
||||
forbidden_error:
|
||||
summary: Forbidden Error Example
|
||||
value:
|
||||
error: Authorization Error
|
||||
message: 'Insufficient permissions to access this resource'
|
||||
recording_not_allowed:
|
||||
summary: Recording Not Allowed in Room Example
|
||||
value:
|
||||
error: Recording Error
|
||||
message: 'Recording is disabled for room room-123'
|
||||
@ -1,8 +0,0 @@
|
||||
description: Invalid participant role provided
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/error.yaml'
|
||||
example:
|
||||
error: Participant Error
|
||||
message: 'No valid participant role provided'
|
||||
@ -1,8 +0,0 @@
|
||||
description: Conflict — The participant already exists in the room
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/error.yaml'
|
||||
example:
|
||||
error: 'Participant Error'
|
||||
message: 'Participant "Alice" already exists in room "room_123"'
|
||||
@ -1,8 +0,0 @@
|
||||
description: Room metadata not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/error.yaml'
|
||||
example:
|
||||
error: 'Room Error'
|
||||
message: 'Room metadata for "room_123" not found. Room "room_123" does not exist or has no recordings associated'
|
||||
@ -0,0 +1,5 @@
|
||||
description: Successfully created or reused AI assistant activation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/ai-assistant-create-response.yaml'
|
||||
@ -1,13 +0,0 @@
|
||||
description: Successfully generated the participant token
|
||||
# headers:
|
||||
# Set-Cookie:
|
||||
# $ref: '../../headers/set-cookie-participant-token.yaml'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: >
|
||||
The token to authenticate the participant.
|
||||
@ -1,13 +0,0 @@
|
||||
description: Successfully generated the recording token
|
||||
# headers:
|
||||
# Set-Cookie:
|
||||
# $ref: '../../headers/set-cookie-recording-token.yaml'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: >
|
||||
The token to access the recordings in the specified OpenVidu Meet room.
|
||||
@ -0,0 +1,10 @@
|
||||
description: Successfully generated the room member token
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: >
|
||||
The token to authenticate the user to access the room and its resources.
|
||||
@ -0,0 +1,5 @@
|
||||
description: Analytics data retrieved successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/meet-analytics.yaml'
|
||||
@ -0,0 +1,5 @@
|
||||
description: Successfully retrieved captions config
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/global-captions-config.yaml'
|
||||
@ -2,4 +2,4 @@ description: Successfully retrieved user profile
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/user.yaml'
|
||||
$ref: '../../schemas/internal/meet-user.yaml'
|
||||
|
||||
@ -2,4 +2,4 @@ description: Successfully retrieved the room role and associated permissions
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/internal/meet-room-role-permissions.yaml'
|
||||
$ref: '../../schemas/internal/room-member-role-permissions.yaml'
|
||||
@ -4,7 +4,7 @@ content:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '../../schemas/internal/meet-room-role-permissions.yaml'
|
||||
$ref: '../../schemas/internal/room-member-role-permissions.yaml'
|
||||
example:
|
||||
- role: 'moderator'
|
||||
permissions:
|
||||
@ -17,6 +17,8 @@ content:
|
||||
canUpdateOwnMetadata: true
|
||||
openvidu:
|
||||
canRecord: true
|
||||
canRetrieveRecordings: true
|
||||
canDeleteRecordings: true
|
||||
canChat: true
|
||||
canChangeVirtualBackground: true
|
||||
- role: 'speaker'
|
||||
@ -30,5 +32,7 @@ content:
|
||||
canUpdateOwnMetadata: true
|
||||
openvidu:
|
||||
canRecord: false
|
||||
canRetrieveRecordings: true
|
||||
canDeleteRecordings: false
|
||||
canChat: true
|
||||
canChangeVirtualBackground: true
|
||||
@ -6,4 +6,4 @@ content:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example: Participant 'Alice' kicked successfully from room 'room-123'
|
||||
example: Participant 'Alice' kicked successfully from meeting in room 'room-123'
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
description: Successfully refreshed the access token
|
||||
# headers:
|
||||
# Set-Cookie:
|
||||
# $ref: '../../headers/set-cookie-access-token.yaml'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
description: Successfully logged in
|
||||
# headers:
|
||||
# Set-Cookie:
|
||||
# $ref: '../../headers/set-cookie-access-token.yaml'
|
||||
# Set-Cookie*:
|
||||
# $ref: '../../headers/set-cookie-refresh-token.yaml'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
|
||||
@ -1,11 +1,4 @@
|
||||
description: Successfully logged out
|
||||
# headers:
|
||||
# Set-Cookie:
|
||||
# description: >
|
||||
# Clears the access and refresh token cookie.
|
||||
# schema:
|
||||
# type: string
|
||||
# example: 'OvMeetAccessToken=; Path=/; HttpOnly; SameSite=Strict'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
|
||||
@ -11,6 +11,7 @@ content:
|
||||
roomId: 'room-123'
|
||||
roomName: 'room'
|
||||
status: 'complete'
|
||||
layout: 'grid'
|
||||
filename: 'room-123--XX445.mp4'
|
||||
startDate: 1600000000000
|
||||
endDate: 1600000003600
|
||||
@ -25,5 +26,6 @@ content:
|
||||
roomId: 'room-456'
|
||||
roomName: 'room'
|
||||
status: 'active'
|
||||
layout: 'grid'
|
||||
filename: 'room-456--QR789.mp4'
|
||||
startDate: 1682500000000
|
||||
|
||||
@ -19,6 +19,7 @@ content:
|
||||
roomId: 'room-123'
|
||||
roomName: 'room'
|
||||
status: 'active'
|
||||
layout: 'grid'
|
||||
filename: 'room-123--XX445.mp4'
|
||||
startDate: 1620000000000
|
||||
endDate: 1620000003600
|
||||
@ -29,6 +30,7 @@ content:
|
||||
roomId: 'room-456'
|
||||
roomName: 'room'
|
||||
status: 'complete'
|
||||
layout: 'grid'
|
||||
filename: 'room-456--XX678.mp4'
|
||||
startDate: 1625000000000
|
||||
endDate: 1625000007200
|
||||
|
||||
@ -19,8 +19,15 @@ content:
|
||||
enabled: true
|
||||
recording:
|
||||
enabled: false
|
||||
layout: grid
|
||||
encoding: H264_720P_30
|
||||
allowAccessTo: admin_moderator_speaker
|
||||
virtualBackground:
|
||||
enabled: true
|
||||
e2ee:
|
||||
enabled: false
|
||||
captions:
|
||||
enabled: true
|
||||
moderatorUrl: 'http://localhost:6080/room/room-123?secret=123456'
|
||||
speakerUrl: 'http://localhost:6080/room/room-123?secret=654321'
|
||||
status: open
|
||||
@ -43,8 +50,21 @@ content:
|
||||
enabled: true
|
||||
recording:
|
||||
enabled: false
|
||||
layout: grid
|
||||
encoding:
|
||||
video:
|
||||
width: 1920
|
||||
height: 1080
|
||||
framerate: 30
|
||||
codec: H264_MAIN
|
||||
audio:
|
||||
codec: OPUS
|
||||
bitrate: 128
|
||||
allowAccessTo: admin_moderator_speaker
|
||||
virtualBackground:
|
||||
enabled: true
|
||||
e2ee:
|
||||
enabled: false
|
||||
|
||||
fields=moderatorUrl,speakerUrl:
|
||||
summary: Response containing only moderator and speaker URLs
|
||||
|
||||
@ -28,8 +28,15 @@ content:
|
||||
enabled: true
|
||||
recording:
|
||||
enabled: false
|
||||
layout: grid
|
||||
encoding: H264_720P_30
|
||||
allowAccessTo: admin_moderator_speaker
|
||||
virtualBackground:
|
||||
enabled: true
|
||||
e2ee:
|
||||
enabled: false
|
||||
captions:
|
||||
enabled: true
|
||||
moderatorUrl: 'http://localhost:6080/room/room-123?secret=123456'
|
||||
speakerUrl: 'http://localhost:6080/room/room-123?secret=654321'
|
||||
status: open
|
||||
@ -46,8 +53,21 @@ content:
|
||||
enabled: false
|
||||
recording:
|
||||
enabled: true
|
||||
layout: grid
|
||||
encoding:
|
||||
video:
|
||||
width: 1280
|
||||
height: 720
|
||||
framerate: 60
|
||||
codec: H264_HIGH
|
||||
audio:
|
||||
codec: AAC
|
||||
bitrate: 192
|
||||
allowAccessTo: admin_moderator_speaker
|
||||
virtualBackground:
|
||||
enabled: false
|
||||
e2ee:
|
||||
enabled: false
|
||||
moderatorUrl: 'http://localhost:6080/room/room-456?secret=789012'
|
||||
speakerUrl: 'http://localhost:6080/room/room-456?secret=210987'
|
||||
status: open
|
||||
@ -80,6 +100,8 @@ content:
|
||||
enabled: false
|
||||
virtualBackground:
|
||||
enabled: true
|
||||
e2ee:
|
||||
enabled: false
|
||||
- roomId: 'room-456'
|
||||
roomName: 'room'
|
||||
creationDate: 1620001000000
|
||||
@ -91,6 +113,8 @@ content:
|
||||
enabled: true
|
||||
virtualBackground:
|
||||
enabled: false
|
||||
e2ee:
|
||||
enabled: false
|
||||
pagination:
|
||||
isTruncated: true
|
||||
nextPageToken: 'abc123'
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
description: Success response for scheduling room closure
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example:
|
||||
message: Room 'room-123' scheduled to be closed when the meeting ends
|
||||
@ -1,12 +0,0 @@
|
||||
description: >
|
||||
All specified rooms were marked for deletion (due to active participants)
|
||||
and will be removed once all participants leave.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example:
|
||||
message: Rooms 'room-123, room-456' marked for deletion
|
||||
@ -2,12 +2,13 @@ description: Successfully created the OpenVidu Meet recording
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/meet-recording.yaml'
|
||||
$ref: '../schemas/meet-recording.yaml'
|
||||
example:
|
||||
recordingId: 'room-123--EG_XYZ--XX445'
|
||||
roomId: 'room-123'
|
||||
roomName: 'room'
|
||||
status: 'active'
|
||||
layout: 'speaker'
|
||||
filename: 'room-123--XX445.mp4'
|
||||
startDate: 1600000000000
|
||||
headers:
|
||||
@ -8,12 +8,13 @@ headers:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../../schemas/meet-recording.yaml'
|
||||
$ref: '../schemas/meet-recording.yaml'
|
||||
example:
|
||||
recordingId: 'room-123--EG_XYZ--XX445'
|
||||
roomId: 'room-123'
|
||||
roomName: 'room'
|
||||
status: 'ending'
|
||||
layout: 'speaker'
|
||||
filename: 'room-123--XX445.mp4'
|
||||
startDate: 1600000000000
|
||||
details: 'End reason: StopEgress API'
|
||||
@ -0,0 +1,10 @@
|
||||
description: Success response for updating room status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example:
|
||||
message: Room 'room-123' closed successfully
|
||||
@ -0,0 +1,37 @@
|
||||
type: object
|
||||
required:
|
||||
# - scope
|
||||
- capabilities
|
||||
properties:
|
||||
# scope:
|
||||
# type: object
|
||||
# required:
|
||||
# - resourceType
|
||||
# - resourceIds
|
||||
# properties:
|
||||
# resourceType:
|
||||
# type: string
|
||||
# enum: ['meeting']
|
||||
# description: Scope resource type where assistant will be activated.
|
||||
# example: meeting
|
||||
# resourceIds:
|
||||
# type: array
|
||||
# minItems: 1
|
||||
# items:
|
||||
# type: string
|
||||
# minLength: 1
|
||||
# description: List of target resource ids.
|
||||
# example: ['meeting_123']
|
||||
capabilities:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
enum: ['live_captions']
|
||||
description: AI capability to activate.
|
||||
example: live_captions
|
||||
@ -0,0 +1,14 @@
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- status
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Identifier of the assistant activation.
|
||||
example: asst_123
|
||||
status:
|
||||
type: string
|
||||
enum: ['active']
|
||||
description: Current assistant activation state.
|
||||
example: active
|
||||
@ -0,0 +1,8 @@
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Indicates whether captions are enabled in the system
|
||||
example: true
|
||||
required:
|
||||
- enabled
|
||||
@ -0,0 +1,24 @@
|
||||
type: object
|
||||
description: Usage analytics data for OpenVidu Meet
|
||||
properties:
|
||||
totalRooms:
|
||||
type: integer
|
||||
description: Total number of rooms created
|
||||
example: 42
|
||||
activeRooms:
|
||||
type: integer
|
||||
description: Number of rooms currently with an active meeting
|
||||
example: 3
|
||||
totalRecordings:
|
||||
type: integer
|
||||
description: Total number of recordings created
|
||||
example: 128
|
||||
completeRecordings:
|
||||
type: integer
|
||||
description: Number of recordings that are complete and playable
|
||||
example: 125
|
||||
required:
|
||||
- totalRooms
|
||||
- activeRooms
|
||||
- totalRecordings
|
||||
- completeRecordings
|
||||
@ -1,21 +0,0 @@
|
||||
type: object
|
||||
required:
|
||||
- roomId
|
||||
- secret
|
||||
properties:
|
||||
roomId:
|
||||
type: string
|
||||
description: The unique identifier of the room where the participant will join.
|
||||
example: 'room-123'
|
||||
secret:
|
||||
type: string
|
||||
description: The secret token from the room Url
|
||||
example: 'abc123456'
|
||||
participantName:
|
||||
type: string
|
||||
description: The name of the participant.
|
||||
example: 'Alice'
|
||||
participantIdentity:
|
||||
type: string
|
||||
description: The unique identity of the participant.
|
||||
example: 'Alice'
|
||||
@ -4,10 +4,10 @@ properties:
|
||||
type: string
|
||||
enum: ['moderator', 'speaker']
|
||||
description: |
|
||||
A role that a participant can have in a room.
|
||||
The role determines the permissions of the participant in the room.
|
||||
- `moderator`: Can manage the room and its participants.
|
||||
- `speaker`: Can publish media streams to the room.
|
||||
A role that a user can have as a member of a room.
|
||||
The role determines the permissions of the user in the room.
|
||||
- `moderator`: Can manage the room resources and meeting participants.
|
||||
- `speaker`: Can publish media streams to the meeting.
|
||||
example: 'moderator'
|
||||
permissions:
|
||||
type: object
|
||||
@ -50,15 +50,25 @@ properties:
|
||||
canRecord:
|
||||
type: boolean
|
||||
description: >
|
||||
Indicates whether the participant can record the room.
|
||||
Indicates whether the user can record a meeting in the room.
|
||||
example: true
|
||||
canRetrieveRecordings:
|
||||
type: boolean
|
||||
description: >
|
||||
Indicates whether the user can retrieve and play recordings of meetings in the room.
|
||||
example: true
|
||||
canDeleteRecordings:
|
||||
type: boolean
|
||||
description: >
|
||||
Indicates whether the user can delete recordings of meetings in the room.
|
||||
example: true
|
||||
canChat:
|
||||
type: boolean
|
||||
description: >
|
||||
Indicates whether the participant can send and receive chat messages in the room.
|
||||
Indicates whether the user can send and receive chat messages in the room.
|
||||
example: true
|
||||
canChangeVirtualBackground:
|
||||
type: boolean
|
||||
description: >
|
||||
Indicates whether the participant can change their own virtual background.
|
||||
Indicates whether the user can change their own virtual background.
|
||||
example: true
|
||||
@ -0,0 +1,20 @@
|
||||
type: object
|
||||
required:
|
||||
- secret
|
||||
properties:
|
||||
secret:
|
||||
type: string
|
||||
description: A secret key for room access. Determines the member's role.
|
||||
example: 'abc123456'
|
||||
grantJoinMeetingPermission:
|
||||
type: boolean
|
||||
description: Whether to grant permission to join the meeting. If true, participantName must be provided.
|
||||
example: true
|
||||
participantName:
|
||||
type: string
|
||||
description: The name of the participant when joining the meeting. Required if `grantJoinMeetingPermission` is true and this is a new token (not a refresh).
|
||||
example: 'Alice'
|
||||
participantIdentity:
|
||||
type: string
|
||||
description: The identity of the participant in the meeting. Required when refreshing an existing token with meeting permissions.
|
||||
example: 'Alice'
|
||||
@ -26,6 +26,10 @@ MeetRoomTheme:
|
||||
minLength: 1
|
||||
maxLength: 50
|
||||
example: "Default Theme"
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Whether the theme is enabled
|
||||
example: true
|
||||
baseTheme:
|
||||
$ref: '#/MeetRoomThemeMode'
|
||||
description: Base theme mode (light or dark)
|
||||
@ -51,6 +55,7 @@ MeetRoomTheme:
|
||||
example: "#CCCCCC"
|
||||
required:
|
||||
- name
|
||||
- enabled
|
||||
- baseTheme
|
||||
|
||||
MeetRoomThemeMode:
|
||||
|
||||
@ -22,6 +22,58 @@ properties:
|
||||
enum: ['starting', 'active', 'ending', 'complete', 'failed', 'aborted', 'limit_reached']
|
||||
example: 'active'
|
||||
description: The status of the recording.
|
||||
layout:
|
||||
type: string
|
||||
example: 'grid'
|
||||
description: The layout of the recording.
|
||||
encoding:
|
||||
oneOf:
|
||||
- type: string
|
||||
enum: ['H264_720P_30', 'H264_720P_60', 'H264_1080P_30', 'H264_1080P_60', 'PORTRAIT_H264_720P_30', 'PORTRAIT_H264_720P_60', 'PORTRAIT_H264_1080P_30', 'PORTRAIT_H264_1080P_60']
|
||||
description: Encoding preset
|
||||
- type: object
|
||||
properties:
|
||||
video:
|
||||
type: object
|
||||
properties:
|
||||
width:
|
||||
type: integer
|
||||
example: 1920
|
||||
height:
|
||||
type: integer
|
||||
example: 1080
|
||||
framerate:
|
||||
type: integer
|
||||
example: 30
|
||||
codec:
|
||||
type: string
|
||||
enum: ['DEFAULT_VC', 'H264_BASELINE', 'H264_MAIN', 'H264_HIGH', 'VP8']
|
||||
bitrate:
|
||||
type: integer
|
||||
example: 4500
|
||||
keyFrameInterval:
|
||||
type: number
|
||||
example: 2
|
||||
depth:
|
||||
type: integer
|
||||
example: 24
|
||||
audio:
|
||||
type: object
|
||||
properties:
|
||||
codec:
|
||||
type: string
|
||||
enum: ['DEFAULT_AC', 'OPUS', 'AAC', 'AC_MP3']
|
||||
bitrate:
|
||||
type: integer
|
||||
example: 128
|
||||
frequency:
|
||||
type: integer
|
||||
example: 48000
|
||||
description: Advanced encoding options
|
||||
description: |
|
||||
The encoding configuration used for this recording.
|
||||
Can be either a preset string or advanced encoding options.
|
||||
example: 'H264_720P_30'
|
||||
filename:
|
||||
type: string
|
||||
example: 'room-123--XX445.mp4'
|
||||
|
||||
@ -10,6 +10,12 @@ MeetRoomConfig:
|
||||
virtualBackground:
|
||||
$ref: '#/MeetVirtualBackgroundConfig'
|
||||
description: Config for virtual background in the room.
|
||||
e2ee:
|
||||
$ref: '#/MeetE2EEConfig'
|
||||
description: Config for End-to-End Encryption (E2EE) in the room.
|
||||
captions:
|
||||
$ref: '#/MeetCaptionsConfig'
|
||||
description: Config for live captions in the room.
|
||||
MeetChatConfig:
|
||||
type: object
|
||||
properties:
|
||||
@ -26,6 +32,29 @@ MeetRecordingConfig:
|
||||
default: true
|
||||
example: true
|
||||
description: If true, the room will be allowed to record the video of the participants.
|
||||
layout:
|
||||
type: string
|
||||
enum:
|
||||
- grid
|
||||
- speaker
|
||||
- single-speaker
|
||||
# - grid-light
|
||||
# - speaker-light
|
||||
# - single-speaker-light
|
||||
default: grid
|
||||
example: grid
|
||||
description: |
|
||||
Defines the layout of the recording. Options are:
|
||||
- `grid`: All participants are shown in a grid layout.
|
||||
- `speaker`: The active speaker is shown prominently, with other participants in smaller thumbnails.
|
||||
- `single-speaker`: Only the active speaker is shown in the recording.
|
||||
# - `grid-light`: Similar to `grid` but with a light-themed background.
|
||||
# - `speaker-light`: Similar to `speaker` but with a light-themed background.
|
||||
# - `single-speaker-light`: Similar to `single-speaker` but with a light-themed background.
|
||||
encoding:
|
||||
oneOf:
|
||||
- $ref: '#/MeetRecordingEncodingPreset'
|
||||
- $ref: '#/MeetRecordingEncodingOptions'
|
||||
allowAccessTo:
|
||||
type: string
|
||||
enum:
|
||||
@ -47,3 +76,184 @@ MeetVirtualBackgroundConfig:
|
||||
default: true
|
||||
example: true
|
||||
description: If true, the room will be allowed to use virtual background.
|
||||
MeetE2EEConfig:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
default: false
|
||||
example: false
|
||||
description: >
|
||||
If true, the room will have End-to-End Encryption (E2EE) enabled.<br/>
|
||||
This ensures that the media streams are encrypted from the sender to the receiver, providing enhanced privacy and security for the participants.<br/>
|
||||
**Enabling E2EE will disable the recording feature for the room**.
|
||||
MeetCaptionsConfig:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
default: true
|
||||
example: true
|
||||
description: >
|
||||
If true, the room will have live captions enabled.<br/>
|
||||
This allows participants to see real-time captions of the all participants' speech during the meeting.<br/>
|
||||
MeetRecordingEncodingPreset:
|
||||
type: string
|
||||
enum:
|
||||
- H264_720P_30
|
||||
- H264_720P_60
|
||||
- H264_1080P_30
|
||||
- H264_1080P_60
|
||||
- PORTRAIT_H264_720P_30
|
||||
- PORTRAIT_H264_720P_60
|
||||
- PORTRAIT_H264_1080P_30
|
||||
- PORTRAIT_H264_1080P_60
|
||||
description: |
|
||||
Predefined encoding presets for recordings. Each preset defines a combination of resolution, frame rate, and codec:
|
||||
- `H264_720P_30`: 1280x720, 30fps, 3000kbps, H.264_MAIN / OPUS **(default)**
|
||||
- `H264_720P_60`: 1280x720, 60fps, 4500kbps, H.264_MAIN / OPUS
|
||||
- `H264_1080P_30`: 1920x1080, 30fps, 4500kbps, H.264_MAIN / OPUS
|
||||
- `H264_1080P_60`: 1920x1080, 60fps, 6000kbps, H.264_MAIN / OPUS
|
||||
- `PORTRAIT_H264_720P_30`: 720x1280, 30fps, 3000kbps, H.264_MAIN / OPUS
|
||||
- `PORTRAIT_H264_720P_60`: 720x1280, 60fps, 4500kbps, H.264_MAIN / OPUS
|
||||
- `PORTRAIT_H264_1080P_30`: 1080x1920, 30fps, 4500kbps, H.264_MAIN / OPUS
|
||||
- `PORTRAIT_H264_1080P_60`: 1080x1920, 60fps, 6000kbps, H.264_MAIN / OPUS
|
||||
example: H264_720P_30
|
||||
MeetRecordingVideoCodec:
|
||||
type: string
|
||||
enum:
|
||||
- DEFAULT_VC
|
||||
- H264_BASELINE
|
||||
- H264_MAIN
|
||||
- H264_HIGH
|
||||
- VP8
|
||||
description: |
|
||||
Video codec options for recording encoding:
|
||||
- `DEFAULT_VC`: Use the default video codec (H.264_MAIN)
|
||||
- `H264_BASELINE`: H.264 Baseline profile
|
||||
- `H264_MAIN`: H.264 Main profile
|
||||
- `H264_HIGH`: H.264 High profile
|
||||
- `VP8`: VP8 codec
|
||||
example: H264_MAIN
|
||||
MeetRecordingAudioCodec:
|
||||
type: string
|
||||
enum:
|
||||
- DEFAULT_AC
|
||||
- OPUS
|
||||
- AAC
|
||||
- AC_MP3
|
||||
description: |
|
||||
Audio codec options for recording encoding:
|
||||
- `DEFAULT_AC`: Use the default audio codec (OPUS)
|
||||
- `OPUS`: Opus codec
|
||||
- `AAC`: AAC codec
|
||||
- `AC_MP3`: MP3 codec
|
||||
example: OPUS
|
||||
MeetRecordingVideoEncodingOptions:
|
||||
type: object
|
||||
required:
|
||||
- width
|
||||
- height
|
||||
- framerate
|
||||
- codec
|
||||
- bitrate
|
||||
- keyFrameInterval
|
||||
- depth
|
||||
properties:
|
||||
width:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 1280
|
||||
description: |
|
||||
Video width in pixels
|
||||
height:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 720
|
||||
description: |
|
||||
Video height in pixels
|
||||
framerate:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 30
|
||||
description: |
|
||||
Frame rate in fps
|
||||
codec:
|
||||
$ref: '#/MeetRecordingVideoCodec'
|
||||
description: |
|
||||
Video codec
|
||||
bitrate:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 4500
|
||||
description: |
|
||||
Video bitrate in kbps
|
||||
keyframeInterval:
|
||||
type: number
|
||||
minimum: 0
|
||||
example: 4
|
||||
description: |
|
||||
Keyframe interval in seconds
|
||||
depth:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 24
|
||||
description: |
|
||||
Video depth (pixel format) in bits
|
||||
description: |
|
||||
Advanced video encoding options for recordings.
|
||||
MeetRecordingAudioEncodingOptions:
|
||||
type: object
|
||||
required:
|
||||
- codec
|
||||
- bitrate
|
||||
- frequency
|
||||
properties:
|
||||
codec:
|
||||
$ref: '#/MeetRecordingAudioCodec'
|
||||
description: |
|
||||
Audio codec (required when audio is provided)
|
||||
bitrate:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 128
|
||||
description: |
|
||||
Audio bitrate in kbps (required when audio is provided)
|
||||
frequency:
|
||||
type: integer
|
||||
minimum: 1
|
||||
example: 44100
|
||||
description: |
|
||||
Audio sample rate in Hz (required when audio is provided)
|
||||
description: |
|
||||
Advanced audio encoding options for recordings.
|
||||
When audio encoding is provided, all fields are required.
|
||||
MeetRecordingEncodingOptions:
|
||||
type: object
|
||||
required:
|
||||
- video
|
||||
- audio
|
||||
properties:
|
||||
video:
|
||||
$ref: '#/MeetRecordingVideoEncodingOptions'
|
||||
description: Video encoding configuration
|
||||
audio:
|
||||
$ref: '#/MeetRecordingAudioEncodingOptions'
|
||||
description: Audio encoding configuration
|
||||
description: |
|
||||
Advanced encoding options for recordings.
|
||||
Use this for fine-grained control over video and audio encoding parameters.
|
||||
Both video and audio configurations are required when using advanced options.
|
||||
For common scenarios, consider using encoding presets instead.
|
||||
example:
|
||||
video:
|
||||
width: 1280
|
||||
height: 720
|
||||
framerate: 30
|
||||
codec: H264_MAIN
|
||||
bitrate: 3000
|
||||
keyFrameInterval: 4
|
||||
audio:
|
||||
codec: OPUS
|
||||
bitrate: 128
|
||||
frequency: 44100
|
||||
|
||||
@ -71,14 +71,14 @@ properties:
|
||||
type: string
|
||||
example: 'http://localhost:6080/room/room-123?secret=123456'
|
||||
description: >
|
||||
The URL for the moderator participants to join the room. The moderator role has special permissions to manage the
|
||||
room and participants.
|
||||
The URL for moderator room members to access the room. The moderator role has special permissions to manage the
|
||||
room resources and meeting participants.
|
||||
speakerUrl:
|
||||
type: string
|
||||
example: 'http://localhost:6080/room/room-123?secret=654321'
|
||||
description: >
|
||||
The URL for the speaker participants to join the room. The speaker role has permissions to publish audio and
|
||||
video streams to the room.
|
||||
The URL for speaker room members to access the room. The speaker role has permissions to publish audio and
|
||||
video streams to the meeting.
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
|
||||
@ -22,6 +22,11 @@ properties:
|
||||
status:
|
||||
type: string
|
||||
description: The status of the recording.
|
||||
example: active
|
||||
layout:
|
||||
type: string
|
||||
description: The layout of the recording.
|
||||
example: grid
|
||||
filename:
|
||||
type: string
|
||||
description: The name of the recording file.
|
||||
|
||||
@ -4,51 +4,21 @@ apiKeyHeader:
|
||||
in: header
|
||||
description: >
|
||||
The API key to authenticate the request. This key is required to access the OpenVidu Meet API.
|
||||
# accessTokenCookie:
|
||||
# type: apiKey
|
||||
# name: OvMeetAccessToken
|
||||
# in: cookie
|
||||
# description: >
|
||||
# The JWT token to authenticate the request in case of consuming the API from the OpenVidu Meet frontend.
|
||||
accessTokenHeader:
|
||||
type: apiKey
|
||||
name: Authorization
|
||||
in: header
|
||||
description: >
|
||||
The JWT token to authenticate the request in case of consuming the API from the OpenVidu Meet frontend.
|
||||
# refreshTokenCookie:
|
||||
# type: apiKey
|
||||
# name: OvMeetRefreshToken
|
||||
# in: cookie
|
||||
# description: >
|
||||
# The JWT token to refresh the access token when it expires.
|
||||
refreshTokenHeader:
|
||||
type: apiKey
|
||||
name: X-Refresh-Token
|
||||
in: header
|
||||
description: >
|
||||
The JWT token to refresh the access token when it expires.
|
||||
# participantTokenCookie:
|
||||
# type: apiKey
|
||||
# name: OvMeetParticipantToken
|
||||
# in: cookie
|
||||
# description: >
|
||||
# The JWT token to authenticate the participant when entering the room.
|
||||
participantTokenHeader:
|
||||
roomMemberTokenHeader:
|
||||
type: apiKey
|
||||
name: X-Participant-Token
|
||||
name: X-Room-Member-Token
|
||||
in: header
|
||||
description: >
|
||||
The JWT token to authenticate the participant when entering the room.
|
||||
# recordingTokenCookie:
|
||||
# type: apiKey
|
||||
# name: OvMeetRecordingToken
|
||||
# in: cookie
|
||||
# description: >
|
||||
# The JWT token containing permissions to access the recordings in a room.
|
||||
recordingTokenHeader:
|
||||
type: apiKey
|
||||
name: X-Recording-Token
|
||||
in: header
|
||||
description: >
|
||||
The JWT token containing permissions to access the recordings in a room.
|
||||
The JWT token to authenticate a room member when accessing room and its resources.
|
||||
|
||||
@ -15,6 +15,8 @@ paths:
|
||||
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}'
|
||||
/rooms/{roomId}/config:
|
||||
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1config'
|
||||
/rooms/{roomId}/status:
|
||||
$ref: './paths/rooms.yaml#/~1rooms~1{roomId}~1status'
|
||||
/recordings:
|
||||
$ref: './paths/recordings.yaml#/~1recordings'
|
||||
/recordings/download:
|
||||
@ -25,6 +27,8 @@ paths:
|
||||
$ref: './paths/recordings.yaml#/~1recordings~1{recordingId}~1media'
|
||||
/recordings/{recordingId}/url:
|
||||
$ref: './paths/recordings.yaml#/~1recordings~1{recordingId}~1url'
|
||||
/recordings/{recordingId}/stop:
|
||||
$ref: './paths/recordings.yaml#/~1recordings~1{recordingId}~1stop'
|
||||
components:
|
||||
securitySchemes:
|
||||
$ref: './components/security.yaml'
|
||||
@ -37,7 +41,7 @@ components:
|
||||
$ref: './components/schemas/meet-room-config.yaml#/MeetRoomConfig'
|
||||
MeetRecording:
|
||||
$ref: components/schemas/meet-recording.yaml
|
||||
Error:
|
||||
$ref: components/schemas/error.yaml
|
||||
MeetWebhookEvent:
|
||||
$ref: components/schemas/meet-webhook.yaml#/MeetWebhookEvent
|
||||
Error:
|
||||
$ref: components/schemas/error.yaml
|
||||
|
||||
@ -14,8 +14,8 @@ paths:
|
||||
$ref: './paths/internal/auth.yaml#/~1auth~1logout'
|
||||
/auth/refresh:
|
||||
$ref: './paths/internal/auth.yaml#/~1auth~1refresh'
|
||||
/auth/api-keys:
|
||||
$ref: './paths/internal/auth.yaml#/~1auth~1api-keys'
|
||||
/api-keys:
|
||||
$ref: './paths/internal/api-keys.yaml#/~1auth~1api-keys'
|
||||
/users/profile:
|
||||
$ref: './paths/internal/users.yaml#/~1users~1profile'
|
||||
/users/change-password:
|
||||
@ -28,33 +28,35 @@ paths:
|
||||
$ref: './paths/internal/meet-global-config.yaml#/~1config~1security'
|
||||
/config/rooms/appearance:
|
||||
$ref: './paths/internal/meet-global-config.yaml#/~1config~1rooms~1appearance'
|
||||
/rooms/{roomId}/recording-token:
|
||||
$ref: './paths/internal/rooms.yaml#/~1rooms~1{roomId}~1recording-token'
|
||||
/config/captions:
|
||||
$ref: './paths/internal/meet-global-config.yaml#/~1config~1captions'
|
||||
/rooms/{roomId}/token:
|
||||
$ref: './paths/internal/rooms.yaml#/~1rooms~1{roomId}~1token'
|
||||
/rooms/{roomId}/roles:
|
||||
$ref: './paths/internal/rooms.yaml#/~1rooms~1{roomId}~1roles'
|
||||
/rooms/{roomId}/roles/{secret}:
|
||||
$ref: './paths/internal/rooms.yaml#/~1rooms~1{roomId}~1roles~1{secret}'
|
||||
/recordings:
|
||||
$ref: './paths/internal/recordings.yaml#/~1recordings'
|
||||
/recordings/{recordingId}/stop:
|
||||
$ref: './paths/internal/recordings.yaml#/~1recordings~1{recordingId}~1stop'
|
||||
/participants/token:
|
||||
$ref: './paths/internal/participants.yaml#/~1participants~1token'
|
||||
/participants/token/refresh:
|
||||
$ref: './paths/internal/participants.yaml#/~1participants~1token~1refresh'
|
||||
/meetings/{roomId}:
|
||||
$ref: './paths/internal/meetings.yaml#/~1meetings~1{roomId}'
|
||||
/meetings/{roomId}/participants/{participantIdentity}:
|
||||
$ref: './paths/internal/meetings.yaml#/~1meetings~1{roomId}~1participants~1{participantIdentity}'
|
||||
/meetings/{roomId}/participants/{participantIdentity}/role:
|
||||
$ref: './paths/internal/meetings.yaml#/~1meetings~1{roomId}~1participants~1{participantIdentity}~1role'
|
||||
/ai/assistants:
|
||||
$ref: './paths/internal/ai-assistant.yaml#/~1ai~1assistants'
|
||||
/ai/assistants/{assistantId}:
|
||||
$ref: './paths/internal/ai-assistant.yaml#/~1ai~1assistants~1{assistantId}'
|
||||
/analytics:
|
||||
$ref: './paths/internal/analytics.yaml#/~1analytics'
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
$ref: components/security.yaml
|
||||
schemas:
|
||||
User:
|
||||
$ref: components/schemas/internal/user.yaml
|
||||
MeetApiKey:
|
||||
$ref: components/schemas/internal/meet-api-key.yaml
|
||||
MeetUser:
|
||||
$ref: components/schemas/internal/meet-user.yaml
|
||||
WebhooksConfig:
|
||||
$ref: components/schemas/internal/webhooks-config.yaml
|
||||
SecurityConfig:
|
||||
@ -65,9 +67,15 @@ components:
|
||||
$ref: components/schemas/meet-room-options.yaml
|
||||
MeetRoomConfig:
|
||||
$ref: components/schemas/meet-room-config.yaml#/MeetRoomConfig
|
||||
MeetRoomRoleAndPermissions:
|
||||
$ref: components/schemas/internal/meet-room-role-permissions.yaml
|
||||
MeetRoomMemberRoleAndPermissions:
|
||||
$ref: components/schemas/internal/room-member-role-permissions.yaml
|
||||
MeetAnalytics:
|
||||
$ref: components/schemas/internal/meet-analytics.yaml
|
||||
MeetRecording:
|
||||
$ref: components/schemas/meet-recording.yaml
|
||||
AiAssistantCreateRequest:
|
||||
$ref: components/schemas/internal/ai-assistant-create-request.yaml
|
||||
AiAssistantCreateResponse:
|
||||
$ref: components/schemas/internal/ai-assistant-create-response.yaml
|
||||
Error:
|
||||
$ref: components/schemas/error.yaml
|
||||
|
||||
56
meet-ce/backend/openapi/paths/internal/ai-assistant.yaml
Normal file
56
meet-ce/backend/openapi/paths/internal/ai-assistant.yaml
Normal file
@ -0,0 +1,56 @@
|
||||
/ai/assistants:
|
||||
post:
|
||||
operationId: createAiAssistant
|
||||
summary: Create AI assistant
|
||||
description: |
|
||||
Activates AI assistance.
|
||||
|
||||
> Currently only meeting AI Assistand and `live_captions` capability is supported.
|
||||
tags:
|
||||
- Internal API - AI Assistants
|
||||
security:
|
||||
- roomMemberTokenHeader: []
|
||||
requestBody:
|
||||
$ref: '../../components/requestBodies/internal/create-ai-assistant-request.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-create-ai-assistant.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
$ref: '../../components/responses/forbidden-error.yaml'
|
||||
'422':
|
||||
$ref: '../../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
|
||||
/ai/assistants/{assistantId}:
|
||||
delete:
|
||||
operationId: cancelAiAssistant
|
||||
summary: Cancel AI assistant
|
||||
description: |
|
||||
Cancels AI assistant.
|
||||
|
||||
The assistant process (live_captions) is stopped only when the last participant cancels it.
|
||||
tags:
|
||||
- Internal API - AI Assistants
|
||||
security:
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- in: path
|
||||
name: assistantId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Identifier of the assistant activation returned by create operation.
|
||||
example: asst_123
|
||||
responses:
|
||||
'204':
|
||||
description: AI assistant canceled successfully.
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'422':
|
||||
$ref: '../../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
17
meet-ce/backend/openapi/paths/internal/analytics.yaml
Normal file
17
meet-ce/backend/openapi/paths/internal/analytics.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
/analytics:
|
||||
get:
|
||||
operationId: getAnalytics
|
||||
summary: Get usage analytics
|
||||
description: >
|
||||
Retrieves usage analytics for OpenVidu Meet, including metrics for rooms and recordings.
|
||||
tags:
|
||||
- Internal API - Analytics
|
||||
security:
|
||||
- accessTokenHeader: []
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-get-analytics.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
49
meet-ce/backend/openapi/paths/internal/api-keys.yaml
Normal file
49
meet-ce/backend/openapi/paths/internal/api-keys.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
/auth/api-keys:
|
||||
post:
|
||||
operationId: createApiKey
|
||||
summary: Create a new API key
|
||||
description: |
|
||||
Creates a new API key, returning it in the response.
|
||||
|
||||
> **Note:** Only one API key can be created in the system.
|
||||
> If an API key already exists, it will be replaced with the new one.
|
||||
tags:
|
||||
- Internal API - API Keys
|
||||
security:
|
||||
- accessTokenHeader: []
|
||||
responses:
|
||||
'201':
|
||||
$ref: '../../components/responses/internal/success-create-api-key.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
get:
|
||||
operationId: getApiKeys
|
||||
summary: Get API keys
|
||||
description: |
|
||||
Retrieves the existing API keys.
|
||||
|
||||
> **Note:** Only one API key can exist in the system.
|
||||
> If no API key exists, an empty array will be returned.
|
||||
tags:
|
||||
- Internal API - API Keys
|
||||
security:
|
||||
- accessTokenHeader: []
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-get-api-keys.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
delete:
|
||||
operationId: deleteApiKeys
|
||||
summary: Delete API keys
|
||||
description: >
|
||||
Deletes the existing API keys.
|
||||
tags:
|
||||
- Internal API - API Keys
|
||||
security:
|
||||
- accessTokenHeader: []
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-delete-api-key.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
@ -46,53 +46,3 @@
|
||||
$ref: '../../components/responses/internal/error-invalid-refresh-token.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
|
||||
/auth/api-keys:
|
||||
post:
|
||||
operationId: createApiKey
|
||||
summary: Create a new API key
|
||||
description: |
|
||||
Creates a new API key, returning it in the response.
|
||||
|
||||
> **Note:** Only one API key can be created in the system.
|
||||
> If an API key already exists, it will be replaced with the new one.
|
||||
tags:
|
||||
- Internal API - Authentication
|
||||
security:
|
||||
- accessTokenHeader: []
|
||||
responses:
|
||||
'201':
|
||||
$ref: '../../components/responses/internal/success-create-api-key.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
get:
|
||||
operationId: getApiKeys
|
||||
summary: Get API keys
|
||||
description: |
|
||||
Retrieves the existing API keys.
|
||||
|
||||
> **Note:** Only one API key can exist in the system.
|
||||
> If no API key exists, an empty array will be returned.
|
||||
tags:
|
||||
- Internal API - Authentication
|
||||
security:
|
||||
- accessTokenHeader: []
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-get-api-keys.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
delete:
|
||||
operationId: deleteApiKeys
|
||||
summary: Delete API keys
|
||||
description: >
|
||||
Deletes the existing API keys.
|
||||
tags:
|
||||
- Internal API - Authentication
|
||||
security:
|
||||
- accessTokenHeader: []
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-delete-api-key.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
|
||||
@ -139,3 +139,20 @@
|
||||
$ref: '../../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
|
||||
/config/captions:
|
||||
get:
|
||||
operationId: getCaptionsConfig
|
||||
summary: Get captions config
|
||||
description: >
|
||||
Retrieves the captions configuration from the environment variable MEET_CAPTIONS_ENABLED.
|
||||
This endpoint returns whether captions are enabled in the system.
|
||||
tags:
|
||||
- Internal API - Global Config
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-get-captions-config.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
|
||||
@ -9,15 +9,12 @@
|
||||
tags:
|
||||
- Internal API - Meetings
|
||||
security:
|
||||
- participantTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../../components/parameters/room-id-path.yaml'
|
||||
- $ref: '../../components/parameters/internal/x-participant-role.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-end-meeting.yaml'
|
||||
'400':
|
||||
$ref: '../../components/responses/internal/error-invalid-participant-role.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
@ -35,16 +32,12 @@
|
||||
tags:
|
||||
- Internal API - Meetings
|
||||
security:
|
||||
- participantTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../../components/parameters/room-id-path.yaml'
|
||||
- $ref: '../../components/parameters/internal/participant-identity.yaml'
|
||||
- $ref: '../../components/parameters/internal/x-participant-role.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-kick-participant.yaml'
|
||||
'400':
|
||||
$ref: '../../components/responses/internal/error-invalid-participant-role.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
@ -62,18 +55,14 @@
|
||||
tags:
|
||||
- Internal API - Meetings
|
||||
security:
|
||||
- participantTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../../components/parameters/room-id-path.yaml'
|
||||
- $ref: '../../components/parameters/internal/participant-identity.yaml'
|
||||
- $ref: '../../components/parameters/internal/x-participant-role.yaml'
|
||||
requestBody:
|
||||
$ref: '../../components/requestBodies/internal/update-participant-role-request.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-update-participant-role.yaml'
|
||||
'400':
|
||||
$ref: '../../components/responses/internal/error-invalid-participant-role.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
/participants/token:
|
||||
post:
|
||||
operationId: generateParticipantToken
|
||||
summary: Generate token for participant
|
||||
description: >
|
||||
Generates a token for a participant to join an OpenVidu Meet room.
|
||||
tags:
|
||||
- Internal API - Participant
|
||||
security:
|
||||
- accessTokenHeader: []
|
||||
requestBody:
|
||||
$ref: '../../components/requestBodies/internal/participant-token-request.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-generate-participant-token.yaml'
|
||||
'400':
|
||||
$ref: '../../components/responses/internal/error-invalid-room-secret.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
$ref: '../../components/responses/forbidden-error.yaml'
|
||||
'404':
|
||||
$ref: '../../components/responses/error-room-not-found.yaml'
|
||||
'409':
|
||||
$ref: '../../components/responses/internal/error-room-closed.yaml'
|
||||
'422':
|
||||
$ref: '../../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
/participants/token/refresh:
|
||||
post:
|
||||
operationId: refreshParticipantToken
|
||||
summary: Refresh token for participant
|
||||
description: >
|
||||
Refresh a token for a participant in an OpenVidu Meet room.
|
||||
tags:
|
||||
- Internal API - Participant
|
||||
security:
|
||||
- accessTokenHeader: []
|
||||
requestBody:
|
||||
$ref: '../../components/requestBodies/internal/participant-token-request.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-generate-participant-token.yaml'
|
||||
'400':
|
||||
$ref: '../../components/responses/internal/error-invalid-room-secret.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
$ref: '../../components/responses/forbidden-error.yaml'
|
||||
'404':
|
||||
$ref: '../../components/responses/internal/error-room-participant-not-found.yaml'
|
||||
'409':
|
||||
$ref: '../../components/responses/internal/error-room-closed.yaml'
|
||||
'422':
|
||||
$ref: '../../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
@ -1,65 +0,0 @@
|
||||
/recordings:
|
||||
post:
|
||||
operationId: startRecording
|
||||
summary: Start a recording
|
||||
description: >
|
||||
Start a new recording for an OpenVidu Meet room with the specified room ID.
|
||||
tags:
|
||||
- Internal API - Recordings
|
||||
security:
|
||||
- participantTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../../components/parameters/internal/x-participant-role.yaml'
|
||||
requestBody:
|
||||
$ref: '../../components/requestBodies/internal/start-recording-request.yaml'
|
||||
responses:
|
||||
'201':
|
||||
$ref: '../../components/responses/internal/success-start-recording.yaml'
|
||||
'400':
|
||||
$ref: '../../components/responses/internal/error-invalid-participant-role.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
$ref: '../../components/responses/forbidden-error.yaml'
|
||||
'404':
|
||||
$ref: '../../components/responses/error-room-not-found.yaml'
|
||||
'409':
|
||||
$ref: '../../components/responses/internal/error-recording-conflict.yaml'
|
||||
'422':
|
||||
$ref: '../../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
'503':
|
||||
$ref: '../../components/responses/internal/error-service-unavailable.yaml'
|
||||
/recordings/{recordingId}/stop:
|
||||
post:
|
||||
operationId: stopRecording
|
||||
summary: Stop a recording
|
||||
description: |
|
||||
Stops a recording with the specified recording ID.
|
||||
|
||||
> **Note:** The recording must be in an `active` state; otherwise, a 409 error is returned.
|
||||
tags:
|
||||
- Internal API - Recordings
|
||||
security:
|
||||
- participantTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../../components/parameters/recording-id.yaml'
|
||||
- $ref: '../../components/parameters/internal/x-participant-role.yaml'
|
||||
responses:
|
||||
'202':
|
||||
$ref: '../../components/responses/internal/success-stop-recording.yaml'
|
||||
'400':
|
||||
$ref: '../../components/responses/internal/error-invalid-participant-role.yaml'
|
||||
'401':
|
||||
$ref: '../../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
$ref: '../../components/responses/forbidden-error.yaml'
|
||||
'404':
|
||||
$ref: '../../components/responses/error-recording-not-found.yaml'
|
||||
'409':
|
||||
$ref: '../../components/responses/internal/error-recording-not-active.yaml'
|
||||
'422':
|
||||
$ref: '../../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
@ -1,10 +1,9 @@
|
||||
/rooms/{roomId}/recording-token:
|
||||
/rooms/{roomId}/token:
|
||||
post:
|
||||
operationId: generateRecordingToken
|
||||
summary: Generate recording token
|
||||
operationId: generateRoomMemberToken
|
||||
summary: Generate room member token
|
||||
description: >
|
||||
Generates a token with recording permissions for a specified OpenVidu Meet room.
|
||||
This token can be used to access the recordings in a room.
|
||||
Generates a token for a user to access an OpenVidu Meet room and its resources.
|
||||
tags:
|
||||
- Internal API - Rooms
|
||||
security:
|
||||
@ -12,10 +11,10 @@
|
||||
parameters:
|
||||
- $ref: '../../components/parameters/room-id-path.yaml'
|
||||
requestBody:
|
||||
$ref: '../../components/requestBodies/internal/recording-token-request.yaml'
|
||||
$ref: '../../components/requestBodies/internal/room-member-token-request.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-generate-recording-token.yaml'
|
||||
$ref: '../../components/responses/internal/success-generate-room-member-token.yaml'
|
||||
'400':
|
||||
$ref: '../../components/responses/internal/error-invalid-room-secret.yaml'
|
||||
'401':
|
||||
@ -23,24 +22,26 @@
|
||||
'403':
|
||||
$ref: '../../components/responses/forbidden-error.yaml'
|
||||
'404':
|
||||
$ref: '../../components/responses/internal/error-room-metadata-not-found.yaml'
|
||||
$ref: '../../components/responses/internal/error-room-participant-not-found.yaml'
|
||||
'409':
|
||||
$ref: '../../components/responses/internal/error-room-closed.yaml'
|
||||
'422':
|
||||
$ref: '../../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../../components/responses/internal-server-error.yaml'
|
||||
/rooms/{roomId}/roles:
|
||||
get:
|
||||
operationId: getRoomRolesAndPermissions
|
||||
summary: Get room roles and permissions
|
||||
operationId: getRoomMemberRolesAndPermissions
|
||||
summary: Get room member roles and permissions
|
||||
description: >
|
||||
Retrieves the roles and associated permissions that a participant can have in a specified OpenVidu Meet room.
|
||||
Retrieves the roles and associated permissions that a user can have as a member of a specified OpenVidu Meet room.
|
||||
tags:
|
||||
- Internal API - Rooms
|
||||
parameters:
|
||||
- $ref: '../../components/parameters/room-id-path.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-get-room-roles.yaml'
|
||||
$ref: '../../components/responses/internal/success-get-room-member-roles.yaml'
|
||||
'404':
|
||||
$ref: '../../components/responses/error-room-not-found.yaml'
|
||||
'422':
|
||||
@ -52,10 +53,10 @@
|
||||
operationId: getRoomRoleAndPermissions
|
||||
summary: Get room role and permissions
|
||||
description: |
|
||||
Retrieves the role and associated permissions that a participant will have in a specified OpenVidu Meet room
|
||||
when using the URL thant contains the given secret value.
|
||||
Retrieves the role and associated permissions that a user will have as a member of a specified OpenVidu Meet room
|
||||
when using the URL that contains the given secret value.
|
||||
|
||||
This endpoint is useful for checking the participant's role and permissions before joining the room.
|
||||
This endpoint is useful for checking the user's role and permissions before accessing the room.
|
||||
tags:
|
||||
- Internal API - Rooms
|
||||
parameters:
|
||||
@ -63,7 +64,7 @@
|
||||
- $ref: '../../components/parameters/internal/secret.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../../components/responses/internal/success-get-room-role.yaml'
|
||||
$ref: '../../components/responses/internal/success-get-room-member-role.yaml'
|
||||
'400':
|
||||
$ref: '../../components/responses/internal/error-invalid-room-secret.yaml'
|
||||
'404':
|
||||
|
||||
@ -1,4 +1,42 @@
|
||||
/recordings:
|
||||
post:
|
||||
operationId: startRecording
|
||||
summary: Start a recording
|
||||
description: >
|
||||
Start a new recording for an OpenVidu Meet room with the specified room ID.
|
||||
|
||||
|
||||
By default, the recording will use the configuration defined in the room's settings.
|
||||
However, you can optionally provide a configuration override in the request body to customize
|
||||
the recording settings (e.g., layout) for this specific recording session.
|
||||
|
||||
|
||||
If a configuration override is provided, those values will take precedence over the room's configuration.
|
||||
tags:
|
||||
- OpenVidu Meet - Recordings
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
requestBody:
|
||||
$ref: '../components/requestBodies/start-recording-request.yaml'
|
||||
responses:
|
||||
'201':
|
||||
$ref: '../components/responses/success-start-recording.yaml'
|
||||
'401':
|
||||
$ref: '../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
$ref: '../components/responses/forbidden-not-allowed-error.yaml'
|
||||
'404':
|
||||
$ref: '../components/responses/error-room-not-found.yaml'
|
||||
'409':
|
||||
$ref: '../components/responses/error-recording-conflict.yaml'
|
||||
'422':
|
||||
$ref: '../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../components/responses/internal-server-error.yaml'
|
||||
'503':
|
||||
$ref: '../components/responses/error-service-unavailable.yaml'
|
||||
|
||||
get:
|
||||
operationId: getRecordings
|
||||
summary: Get all recordings
|
||||
@ -6,20 +44,25 @@
|
||||
Retrieves a paginated list of all recordings available in the system.
|
||||
You can apply filters to narrow down the results based on specific criteria.
|
||||
|
||||
> **Note:** If this endpoint is called using the `recordingTokenHeader` authentication method,
|
||||
By default, the recordings are sorted by start date in descending order (newest first).
|
||||
|
||||
> **Note:** If this endpoint is called using the `roomMemberTokenHeader` authentication method,
|
||||
> the `roomId` filter will be ignored and only recordings associated with the room included in the token will be returned.
|
||||
tags:
|
||||
- OpenVidu Meet - Recordings
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
- recordingTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
# - $ref: '../components/parameters/recording-status.yaml'
|
||||
- $ref: '../components/parameters/recording-fields.yaml'
|
||||
- $ref: '../components/parameters/room-id-query.yaml'
|
||||
- $ref: '../components/parameters/room-name.yaml'
|
||||
- $ref: '../components/parameters/recording-status.yaml'
|
||||
- $ref: '../components/parameters/recording-fields.yaml'
|
||||
- $ref: '../components/parameters/max-items.yaml'
|
||||
- $ref: '../components/parameters/next-page-token.yaml'
|
||||
- $ref: '../components/parameters/sort-field.yaml'
|
||||
- $ref: '../components/parameters/sort-order.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../components/responses/success-get-recordings.yaml'
|
||||
@ -38,7 +81,7 @@
|
||||
description: |
|
||||
Deletes multiple recordings at once with the specified recording IDs.
|
||||
|
||||
> **Note:** If this endpoint is called using the `recordingTokenHeader` authentication method,
|
||||
> **Note:** If this endpoint is called using the `roomMemberTokenHeader` authentication method,
|
||||
> all specified recordings must belong to the same room included in the token.
|
||||
> If a recording does not belong to that room, it will not be deleted.
|
||||
tags:
|
||||
@ -46,7 +89,7 @@
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
- recordingTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/recording-ids.yaml'
|
||||
responses:
|
||||
@ -71,7 +114,7 @@
|
||||
Downloads multiple recordings as a ZIP file with the specified recording IDs.
|
||||
The ZIP file will contain all recordings in MP4 format.
|
||||
|
||||
> **Note:** If this endpoint is called using the `recordingTokenHeader` authentication method,
|
||||
> **Note:** If this endpoint is called using the `roomMemberTokenHeader` authentication method,
|
||||
> all specified recordings must belong to the same room included in the token.
|
||||
> If a recording does not belong to that room, it will not be included in the ZIP file.
|
||||
tags:
|
||||
@ -79,7 +122,7 @@
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
- recordingTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/recording-ids.yaml'
|
||||
responses:
|
||||
@ -120,7 +163,7 @@
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
- recordingTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/recording-id.yaml'
|
||||
- $ref: '../components/parameters/recording-secret.yaml'
|
||||
@ -152,7 +195,7 @@
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
- recordingTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/recording-id.yaml'
|
||||
responses:
|
||||
@ -185,7 +228,7 @@
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
- recordingTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/recording-id.yaml'
|
||||
- $ref: '../components/parameters/recording-secret.yaml'
|
||||
@ -244,6 +287,36 @@
|
||||
$ref: '../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../components/responses/internal-server-error.yaml'
|
||||
/recordings/{recordingId}/stop:
|
||||
post:
|
||||
operationId: stopRecording
|
||||
summary: Stop a recording
|
||||
description: |
|
||||
Stops a recording with the specified recording ID.
|
||||
|
||||
> **Note:** The recording must be in an `active` state; otherwise, a 409 error is returned.
|
||||
tags:
|
||||
- OpenVidu Meet - Recordings
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/recording-id.yaml'
|
||||
responses:
|
||||
'202':
|
||||
$ref: '../components/responses/success-stop-recording.yaml'
|
||||
'401':
|
||||
$ref: '../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
$ref: '../components/responses/forbidden-error.yaml'
|
||||
'404':
|
||||
$ref: '../components/responses/error-recording-not-found.yaml'
|
||||
'409':
|
||||
$ref: '../components/responses/error-recording-not-active.yaml'
|
||||
'422':
|
||||
$ref: '../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../components/responses/internal-server-error.yaml'
|
||||
/recordings/{recordingId}/url:
|
||||
get:
|
||||
operationId: getRecordingUrl
|
||||
@ -259,7 +332,7 @@
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
- recordingTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/recording-id.yaml'
|
||||
# - $ref: '../components/parameters/private-access.yaml'
|
||||
|
||||
@ -29,6 +29,8 @@
|
||||
description: >
|
||||
Retrieves a paginated list of all rooms available in the system.
|
||||
You can apply filters to narrow down the results based on specific criteria.
|
||||
|
||||
By default, the rooms are sorted by creation date in descending order (newest first).
|
||||
tags:
|
||||
- OpenVidu Meet - Rooms
|
||||
security:
|
||||
@ -36,9 +38,12 @@
|
||||
- accessTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/room-name.yaml'
|
||||
- $ref: '../components/parameters/room-status.yaml'
|
||||
- $ref: '../components/parameters/room-fields.yaml'
|
||||
- $ref: '../components/parameters/max-items.yaml'
|
||||
- $ref: '../components/parameters/next-page-token.yaml'
|
||||
- $ref: '../components/parameters/sort-field.yaml'
|
||||
- $ref: '../components/parameters/sort-order.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../components/responses/success-get-rooms.yaml'
|
||||
@ -56,10 +61,10 @@
|
||||
description: |
|
||||
Delete multiple OpenVidu Meet rooms at once with the specified room IDs.
|
||||
|
||||
If any of the rooms have active meetings or recordings,
|
||||
If any of the rooms have active meetings or recordings,
|
||||
deletion behavior is determined by the provided `withMeeting` and `withRecordings` deletion policies.
|
||||
|
||||
Depending on these policies, the rooms may be deleted/closed immediately, scheduled to be deleted/closed once the meetings end,
|
||||
Depending on these policies, the rooms may be deleted/closed immediately, scheduled to be deleted/closed once the meetings end,
|
||||
or the operation may fail if deletion is not permitted.
|
||||
tags:
|
||||
- OpenVidu Meet - Rooms
|
||||
@ -94,16 +99,13 @@
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
- participantTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/room-id-path.yaml'
|
||||
- $ref: '../components/parameters/room-fields.yaml'
|
||||
- $ref: '../components/parameters/internal/x-participant-role.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../components/responses/success-get-room.yaml'
|
||||
'400':
|
||||
$ref: '../components/responses/internal/error-invalid-participant-role.yaml'
|
||||
'401':
|
||||
$ref: '../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
@ -120,10 +122,10 @@
|
||||
description: |
|
||||
Deletes the specified OpenVidu Meet room by its room ID.
|
||||
|
||||
If the room has an active meeting or existing recordings,
|
||||
If the room has an active meeting or existing recordings,
|
||||
deletion behavior is determined by the provided `withMeeting` and `withRecordings` deletion policies.
|
||||
|
||||
Depending on these policies, the room may be deleted/closed immediately, scheduled to be deleted/closed once the meeting ends,
|
||||
Depending on these policies, the room may be deleted/closed immediately, scheduled to be deleted/closed once the meeting ends,
|
||||
or the operation may fail if deletion is not permitted.
|
||||
tags:
|
||||
- OpenVidu Meet - Rooms
|
||||
@ -162,15 +164,12 @@
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
- participantTokenHeader: []
|
||||
- roomMemberTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/room-id-path.yaml'
|
||||
- $ref: '../components/parameters/internal/x-participant-role.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../components/responses/success-get-room-config.yaml'
|
||||
'400':
|
||||
$ref: '../components/responses/internal/error-invalid-participant-role.yaml'
|
||||
'401':
|
||||
$ref: '../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
@ -204,6 +203,39 @@
|
||||
$ref: '../components/responses/forbidden-error.yaml'
|
||||
'404':
|
||||
$ref: '../components/responses/error-room-not-found.yaml'
|
||||
'409':
|
||||
$ref: '../components/responses/error-room-active-meeting.yaml'
|
||||
'422':
|
||||
$ref: '../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
$ref: '../components/responses/internal-server-error.yaml'
|
||||
/rooms/{roomId}/status:
|
||||
put:
|
||||
operationId: updateRoomStatus
|
||||
summary: Update room status
|
||||
description: >
|
||||
Updates the status of an OpenVidu Meet room with the specified room ID.
|
||||
This can be used to open or close the room for new participants.
|
||||
tags:
|
||||
- OpenVidu Meet - Rooms
|
||||
security:
|
||||
- apiKeyHeader: []
|
||||
- accessTokenHeader: []
|
||||
parameters:
|
||||
- $ref: '../components/parameters/room-id-path.yaml'
|
||||
requestBody:
|
||||
$ref: '../components/requestBodies/update-room-status-request.yaml'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '../components/responses/success-update-room-status.yaml'
|
||||
'202':
|
||||
$ref: '../components/responses/success-room-schedule-closure.yaml'
|
||||
'401':
|
||||
$ref: '../components/responses/unauthorized-error.yaml'
|
||||
'403':
|
||||
$ref: '../components/responses/forbidden-error.yaml'
|
||||
'404':
|
||||
$ref: '../components/responses/error-room-not-found.yaml'
|
||||
'422':
|
||||
$ref: '../components/responses/validation-error.yaml'
|
||||
'500':
|
||||
|
||||
@ -4,15 +4,19 @@
|
||||
description: Operations related to managing OpenVidu Meet recordings
|
||||
- name: Internal API - Authentication
|
||||
description: Authentication operations
|
||||
- name: Internal API - API Keys
|
||||
description: Operations related to managing API keys in OpenVidu Meet
|
||||
- name: Internal API - Analytics
|
||||
description: Operations related to usage analytics in OpenVidu Meet
|
||||
- name: Internal API - Users
|
||||
description: Operations related to managing users in OpenVidu Meet
|
||||
- name: Internal API - Global Config
|
||||
description: Operations related to managing global config in OpenVidu Meet
|
||||
- name: Internal API - Rooms
|
||||
description: Operations related to managing OpenVidu Meet rooms
|
||||
- name: Internal API - Participant
|
||||
description: Operations related to managing participants in OpenVidu Meet rooms
|
||||
- name: Internal API - Meetings
|
||||
description: Operations related to managing meetings in OpenVidu Meet rooms
|
||||
- name: Internal API - AI Assistants
|
||||
description: High-level operations to manage AI assistance capabilities in meetings
|
||||
- name: Internal API - Recordings
|
||||
description: Operations related to managing OpenVidu Meet recordings
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openvidu-meet/backend",
|
||||
"version": "3.4.1",
|
||||
"version": "3.6.0",
|
||||
"description": "OpenVidu Meet Backend",
|
||||
"author": "OpenVidu",
|
||||
"license": "Apache-2.0",
|
||||
@ -27,7 +27,7 @@
|
||||
"package.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.prod.json",
|
||||
"build": "tsc -p tsconfig.prod.json && pnpm run doc:api",
|
||||
"build:watch": "tsc -p tsconfig.prod.json --watch",
|
||||
"doc:api": "mkdir -p public/openapi && cd openapi && openapi-generate-html -i openvidu-meet-api.yaml --ui=stoplight --theme=light --title 'OpenVidu Meet REST API' --description 'OpenVidu Meet REST API' -o ../public/openapi/public.html",
|
||||
"doc:internal-api": "mkdir -p public/openapi && cd openapi && openapi-generate-html -i openvidu-meet-internal-api.yaml --ui=stoplight --theme=dark --title 'OpenVidu Meet Internal REST API' --description 'OpenVidu Meet Internal REST API' -o ../public/openapi/internal.html",
|
||||
@ -35,23 +35,29 @@
|
||||
"start:dev": "NODE_ENV=development concurrently -k -n server,typecheck -c cyan,yellow \"pnpm tsx watch --clear-screen=false --include src ./src/server.ts\" \"pnpm run dev:typecheck\"",
|
||||
"dev:typecheck": "node ../../scripts/dev/backend-type-checker.mjs",
|
||||
"package:build": "pnpm run build:prod && pnpm pack",
|
||||
"test:integration-rooms": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand --forceExit --testPathPattern 'tests/integration/api/rooms' --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-recordings": "node --experimental-vm-modules ../../node_modules/.bin/jest --maxWorkers=1 --maxConcurrency=1 --forceExit --testPathPattern \"tests/integration/api/recordings\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-webhooks": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/webhooks\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-security": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/security\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-global-config": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/global-config\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-participants": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/participants\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-room-management": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand --forceExit --testPathPattern \"tests/integration/api/(rooms|meetings)\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-rooms": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand --forceExit --testPathPattern 'tests/integration/api/rooms' --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-meetings": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/meetings\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-webhooks": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/webhooks\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-auth-security": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/(security|auth|api-keys|users)\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-security": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/security\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-auth": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/auth\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-api-keys": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/api-keys\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-users": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/users\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-config-analytics": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/(global-config|analytics)\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-global-config": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/global-config\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-analytics": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/integration/api/analytics\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:integration-recordings": "node --experimental-vm-modules ../../node_modules/.bin/jest --maxWorkers=1 --maxConcurrency=1 --forceExit --testPathPattern \"tests/integration/api/recordings\" --ci --reporters=default --reporters=jest-junit",
|
||||
"test:unit": "node --experimental-vm-modules ../../node_modules/.bin/jest --runInBand --forceExit --testPathPattern \"tests/unit\" --ci --reporters=default --reporters=jest-junit",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"format:code": "prettier --ignore-path .gitignore --write '**/*.{ts,js,json,md}'"
|
||||
"format:code": "prettier --ignore-path .gitignore --write '**/*.{ts,js,json,md}'",
|
||||
"clean": "rm -rf node_modules dist public test-results"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openvidu-meet/typings": "workspace:*",
|
||||
"@aws-sdk/client-s3": "3.846.0",
|
||||
"@azure/storage-blob": "12.27.0",
|
||||
"@google-cloud/storage": "7.17.1",
|
||||
"@google-cloud/storage": "7.17.3",
|
||||
"@openvidu-meet/typings": "workspace:*",
|
||||
"@sesamecare-oss/redlock": "1.4.0",
|
||||
"archiver": "7.0.1",
|
||||
"bcrypt": "5.1.1",
|
||||
@ -59,14 +65,16 @@
|
||||
"chalk": "5.6.2",
|
||||
"cookie-parser": "1.4.7",
|
||||
"cors": "2.8.5",
|
||||
"cron": "4.3.3",
|
||||
"cron": "4.3.5",
|
||||
"dotenv": "16.6.1",
|
||||
"express": "4.21.2",
|
||||
"express-rate-limit": "7.5.1",
|
||||
"inversify": "6.2.2",
|
||||
"ioredis": "5.6.1",
|
||||
"jwt-decode": "4.0.0",
|
||||
"livekit-server-sdk": "2.13.1",
|
||||
"livekit-server-sdk": "2.13.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"mongoose": "8.19.4",
|
||||
"ms": "2.1.3",
|
||||
"uid": "2.0.2",
|
||||
"winston": "3.18.3",
|
||||
@ -74,14 +82,15 @@
|
||||
"zod": "3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "6.0.3",
|
||||
"@types/archiver": "6.0.4",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/cookie-parser": "1.4.9",
|
||||
"@types/cors": "2.8.19",
|
||||
"@types/express": "4.17.23",
|
||||
"@types/express": "4.17.25",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/lodash.merge": "4.6.9",
|
||||
"@types/ms": "2.1.0",
|
||||
"@types/node": "22.16.4",
|
||||
"@types/node": "22.16.5",
|
||||
"@types/supertest": "6.0.3",
|
||||
"@types/unzipper": "0.10.11",
|
||||
"@types/validator": "13.15.2",
|
||||
@ -90,7 +99,7 @@
|
||||
"@typescript-eslint/parser": "6.21.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-config-prettier": "9.1.2",
|
||||
"jest": "29.7.0",
|
||||
"jest-fetch-mock": "3.0.3",
|
||||
"jest-junit": "16.0.0",
|
||||
@ -100,7 +109,7 @@
|
||||
"supertest": "7.1.3",
|
||||
"ts-jest": "29.4.0",
|
||||
"ts-jest-resolver": "2.0.1",
|
||||
"tsx": "4.20.3",
|
||||
"tsx": "4.20.6",
|
||||
"typescript": "5.9.2",
|
||||
"unzipper": "0.12.3"
|
||||
},
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import { ParticipantRole, User } from '@openvidu-meet/typings';
|
||||
import { ClaimGrants } from 'livekit-server-sdk';
|
||||
|
||||
// Override the Express Request type to include a session object with user and token properties
|
||||
// This will allow controllers to access the user and token information from the request object in a type-safe manner
|
||||
declare module 'express' {
|
||||
interface Request {
|
||||
session?: {
|
||||
user?: User;
|
||||
tokenClaims?: ClaimGrants;
|
||||
participantRole?: ParticipantRole;
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,35 +1,60 @@
|
||||
import { Container } from 'inversify';
|
||||
import { MEET_BLOB_STORAGE_MODE } from '../environment.js';
|
||||
import {
|
||||
ABSService,
|
||||
ABSStorageProvider,
|
||||
AuthService,
|
||||
DistributedEventService,
|
||||
FrontendEventService,
|
||||
GCSService,
|
||||
GCSStorageProvider,
|
||||
HttpContextService,
|
||||
LiveKitService,
|
||||
LivekitWebhookService,
|
||||
LoggerService,
|
||||
MeetStorageService,
|
||||
MutexService,
|
||||
OpenViduWebhookService,
|
||||
ParticipantNameService,
|
||||
ParticipantService,
|
||||
RecordingService,
|
||||
RedisService,
|
||||
RoomService,
|
||||
S3KeyBuilder,
|
||||
S3Service,
|
||||
S3StorageProvider,
|
||||
StorageFactory,
|
||||
StorageKeyBuilder,
|
||||
StorageProvider,
|
||||
TaskSchedulerService,
|
||||
TokenService,
|
||||
UserService
|
||||
} from '../services/index.js';
|
||||
import { MEET_ENV } from '../environment.js';
|
||||
|
||||
import { ApiKeyRepository } from '../repositories/api-key.repository.js';
|
||||
import { BaseRepository } from '../repositories/base.repository.js';
|
||||
import { GlobalConfigRepository } from '../repositories/global-config.repository.js';
|
||||
import { MigrationRepository } from '../repositories/migration.repository.js';
|
||||
import { RecordingRepository } from '../repositories/recording.repository.js';
|
||||
import { RoomRepository } from '../repositories/room.repository.js';
|
||||
import { UserRepository } from '../repositories/user.repository.js';
|
||||
|
||||
/*
|
||||
* Services should be imported in order of use, starting with services
|
||||
* without dependencies and then the services that depend on others. This
|
||||
* helps avoid dependency cycles and ensures constructors receive the
|
||||
* dependencies already registered in the container.
|
||||
*/
|
||||
import { LoggerService } from '../services/logger.service.js';
|
||||
import { RedisService } from '../services/redis.service.js';
|
||||
import { DistributedEventService } from '../services/distributed-event.service.js';
|
||||
import { MutexService } from '../services/mutex.service.js';
|
||||
import { TaskSchedulerService } from '../services/task-scheduler.service.js';
|
||||
import { BaseUrlService } from '../services/base-url.service.js';
|
||||
import { RequestSessionService } from '../services/request-session.service.js';
|
||||
|
||||
import { TokenService } from '../services/token.service.js';
|
||||
import { UserService } from '../services/user.service.js';
|
||||
import { ApiKeyService } from '../services/api-key.service.js';
|
||||
import { GlobalConfigService } from '../services/global-config.service.js';
|
||||
|
||||
import { S3Service } from '../services/storage/providers/s3/s3.service.js';
|
||||
import { S3KeyBuilder } from '../services/storage/providers/s3/s3-storage-key.builder.js';
|
||||
import { S3StorageProvider } from '../services/storage/providers/s3/s3-storage.provider.js';
|
||||
import { ABSService } from '../services/storage/providers/abs/abs.service.js';
|
||||
import { ABSStorageProvider } from '../services/storage/providers/abs/abs-storage.provider.js';
|
||||
import { GCSService } from '../services/storage/providers/gcp/gcs.service.js';
|
||||
import { GCSStorageProvider } from '../services/storage/providers/gcp/gcs-storage.provider.js';
|
||||
|
||||
import { MongoDBService } from '../services/storage/mongodb.service.js';
|
||||
import { StorageInitService } from '../services/storage/storage-init.service.js';
|
||||
import { StorageKeyBuilder, StorageProvider } from '../services/storage/storage.interface.js';
|
||||
import { StorageFactory } from '../services/storage/storage.factory.js';
|
||||
import { BlobStorageService } from '../services/storage/blob-storage.service.js';
|
||||
|
||||
import { MigrationService } from '../services/migration.service.js';
|
||||
import { LiveKitService } from '../services/livekit.service.js';
|
||||
import { FrontendEventService } from '../services/frontend-event.service.js';
|
||||
import { RecordingService } from '../services/recording.service.js';
|
||||
import { RoomService } from '../services/room.service.js';
|
||||
import { ParticipantNameService } from '../services/participant-name.service.js';
|
||||
import { RoomMemberService } from '../services/room-member.service.js';
|
||||
import { OpenViduWebhookService } from '../services/openvidu-webhook.service.js';
|
||||
import { LivekitWebhookService } from '../services/livekit-webhook.service.js';
|
||||
import { RoomScheduledTasksService } from '../services/room-scheduled-tasks.service.js';
|
||||
import { RecordingScheduledTasksService } from '../services/recording-scheduled-tasks.service.js';
|
||||
import { AnalyticsService } from '../services/analytics.service.js';
|
||||
import { AiAssistantService } from '../services/ai-assistant.service.js';
|
||||
|
||||
export const container: Container = new Container();
|
||||
|
||||
@ -53,24 +78,43 @@ export const registerDependencies = () => {
|
||||
container.bind(DistributedEventService).toSelf().inSingletonScope();
|
||||
container.bind(MutexService).toSelf().inSingletonScope();
|
||||
container.bind(TaskSchedulerService).toSelf().inSingletonScope();
|
||||
container.bind(HttpContextService).toSelf().inSingletonScope();
|
||||
container.bind(BaseUrlService).toSelf().inSingletonScope();
|
||||
// RequestSessionService uses AsyncLocalStorage for request isolation
|
||||
// It's a singleton but provides per-request data isolation automatically
|
||||
container.bind(RequestSessionService).toSelf().inSingletonScope();
|
||||
|
||||
configureStorage(MEET_BLOB_STORAGE_MODE);
|
||||
container.bind(StorageFactory).toSelf().inSingletonScope();
|
||||
container.bind(MeetStorageService).toSelf().inSingletonScope();
|
||||
container.bind(MongoDBService).toSelf().inSingletonScope();
|
||||
container.bind(BaseRepository).toSelf().inSingletonScope();
|
||||
container.bind(RoomRepository).toSelf().inSingletonScope();
|
||||
container.bind(UserRepository).toSelf().inSingletonScope();
|
||||
container.bind(ApiKeyRepository).toSelf().inSingletonScope();
|
||||
container.bind(GlobalConfigRepository).toSelf().inSingletonScope();
|
||||
container.bind(RecordingRepository).toSelf().inSingletonScope();
|
||||
container.bind(MigrationRepository).toSelf().inSingletonScope();
|
||||
|
||||
container.bind(TokenService).toSelf().inSingletonScope();
|
||||
container.bind(UserService).toSelf().inSingletonScope();
|
||||
container.bind(AuthService).toSelf().inSingletonScope();
|
||||
container.bind(ApiKeyService).toSelf().inSingletonScope();
|
||||
container.bind(GlobalConfigService).toSelf().inSingletonScope();
|
||||
|
||||
configureStorage(MEET_ENV.BLOB_STORAGE_MODE);
|
||||
container.bind(StorageFactory).toSelf().inSingletonScope();
|
||||
container.bind(BlobStorageService).toSelf().inSingletonScope();
|
||||
container.bind(StorageInitService).toSelf().inSingletonScope();
|
||||
container.bind(MigrationService).toSelf().inSingletonScope();
|
||||
|
||||
container.bind(FrontendEventService).toSelf().inSingletonScope();
|
||||
container.bind(LiveKitService).toSelf().inSingletonScope();
|
||||
container.bind(RecordingService).toSelf().inSingletonScope();
|
||||
container.bind(RoomService).toSelf().inSingletonScope();
|
||||
container.bind(ParticipantNameService).toSelf().inSingletonScope();
|
||||
container.bind(ParticipantService).toSelf().inSingletonScope();
|
||||
container.bind(RoomMemberService).toSelf().inSingletonScope();
|
||||
container.bind(OpenViduWebhookService).toSelf().inSingletonScope();
|
||||
container.bind(LivekitWebhookService).toSelf().inSingletonScope();
|
||||
container.bind(RoomScheduledTasksService).toSelf().inSingletonScope();
|
||||
container.bind(RecordingScheduledTasksService).toSelf().inSingletonScope();
|
||||
container.bind(AnalyticsService).toSelf().inSingletonScope();
|
||||
container.bind(AiAssistantService).toSelf().inSingletonScope();
|
||||
};
|
||||
|
||||
const configureStorage = (storageMode: string) => {
|
||||
@ -100,13 +144,24 @@ const configureStorage = (storageMode: string) => {
|
||||
};
|
||||
|
||||
export const initializeEagerServices = async () => {
|
||||
// Force the creation of services that need to be initialized at startup
|
||||
container.get(RecordingService);
|
||||
// Connect to MongoDB and check health
|
||||
const mongoService = container.get(MongoDBService);
|
||||
await mongoService.connect();
|
||||
await mongoService.checkHealth();
|
||||
|
||||
// Perform comprehensive health checks before initializing other services
|
||||
const storageService = container.get(MeetStorageService);
|
||||
await storageService.checkStartupHealth();
|
||||
// Perform blob storage health check
|
||||
const blobStorageService = container.get(BlobStorageService);
|
||||
await blobStorageService.checkHealth();
|
||||
|
||||
// Initialize storage after health checks pass
|
||||
await storageService.initializeStorage();
|
||||
// Run migrations
|
||||
const migrationService = container.get(MigrationService);
|
||||
await migrationService.runMigrations();
|
||||
|
||||
// Initialize storage
|
||||
const storageInitService = container.get(StorageInitService);
|
||||
await storageInitService.initializeStorage();
|
||||
|
||||
// Initialize scheduled tasks services to register their cron jobs
|
||||
container.get(RecordingScheduledTasksService);
|
||||
container.get(RoomScheduledTasksService);
|
||||
};
|
||||
|
||||
@ -1,33 +1,21 @@
|
||||
import { StringValue } from 'ms';
|
||||
import { SchemaVersion } from '../models/migration.model.js';
|
||||
|
||||
export const INTERNAL_CONFIG = {
|
||||
// Base paths for the API
|
||||
API_BASE_PATH_V1: '/api/v1',
|
||||
INTERNAL_API_BASE_PATH_V1: '/internal-api/v1',
|
||||
|
||||
// Cookie names
|
||||
ACCESS_TOKEN_COOKIE_NAME: 'OvMeetAccessToken',
|
||||
REFRESH_TOKEN_COOKIE_NAME: 'OvMeetRefreshToken',
|
||||
PARTICIPANT_TOKEN_COOKIE_NAME: 'OvMeetParticipantToken',
|
||||
RECORDING_TOKEN_COOKIE_NAME: 'OvMeetRecordingToken',
|
||||
|
||||
// Headers names
|
||||
API_KEY_HEADER: 'x-api-key',
|
||||
ACCESS_TOKEN_HEADER: 'authorization',
|
||||
REFRESH_TOKEN_HEADER: 'x-refresh-token',
|
||||
PARTICIPANT_TOKEN_HEADER: 'x-participant-token',
|
||||
PARTICIPANT_ROLE_HEADER: 'x-participant-role',
|
||||
RECORDING_TOKEN_HEADER: 'x-recording-token',
|
||||
ROOM_MEMBER_TOKEN_HEADER: 'x-room-member-token',
|
||||
|
||||
// Token expiration times
|
||||
ACCESS_TOKEN_EXPIRATION: '2h',
|
||||
REFRESH_TOKEN_EXPIRATION: '1d',
|
||||
PARTICIPANT_TOKEN_EXPIRATION: '2h',
|
||||
RECORDING_TOKEN_EXPIRATION: '2h',
|
||||
|
||||
// Participant name reservations
|
||||
PARTICIPANT_MAX_CONCURRENT_NAME_REQUESTS: '20', // Maximum number of request by the same name at the same time allowed
|
||||
PARTICIPANT_NAME_RESERVATION_TTL: '12h' as StringValue, // Time-to-live for participant name reservations
|
||||
ROOM_MEMBER_TOKEN_EXPIRATION: '2h',
|
||||
|
||||
// Authentication usernames
|
||||
ANONYMOUS_USER: 'anonymous',
|
||||
@ -36,25 +24,43 @@ export const INTERNAL_CONFIG = {
|
||||
// S3 configuration
|
||||
S3_MAX_RETRIES_ATTEMPTS_ON_SAVE_ERROR: '5',
|
||||
S3_INITIAL_RETRY_DELAY_MS: '100',
|
||||
S3_ROOMS_PREFIX: 'rooms',
|
||||
S3_RECORDINGS_PREFIX: 'recordings',
|
||||
S3_USERS_PREFIX: 'users',
|
||||
S3_API_KEYS_PREFIX: 'api_keys',
|
||||
|
||||
// Garbage collection and recording intervals
|
||||
ROOM_GC_INTERVAL: '1h' as StringValue, // e.g. garbage collector interval for rooms
|
||||
RECORDING_LOCK_TTL: '6h' as StringValue, // TTL for recording lock in Redis
|
||||
RECORDING_STARTED_TIMEOUT: '20s' as StringValue, // Timeout for recording start
|
||||
RECORDING_LOCK_GC_INTERVAL: '30m' as StringValue, // Garbage collection interval for recording locks
|
||||
RECORDING_ORPHANED_LOCK_GRACE_PERIOD: '1m' as StringValue, // Grace period for orphaned recording locks
|
||||
RECORDING_STALE_CLEANUP_INTERVAL: '15m' as StringValue, // Cleanup interval for stale recordings
|
||||
RECORDING_STALE_AFTER: '5m' as StringValue, // Maximum allowed time since the last recording update before marking as stale
|
||||
// Cron job configuration
|
||||
CRON_JOB_LOCK_TTL: '59s' as StringValue, // Default TTL for cron job locks to avoid overlapping executions
|
||||
|
||||
// Timing and cleanup settings for room lifecycle management
|
||||
ROOM_EXPIRED_GC_INTERVAL: '1h' as StringValue, // Interval for processing and deleting expired rooms
|
||||
ROOM_ACTIVE_VERIFICATION_GC_INTERVAL: '15m' as StringValue, // Interval for checking room 'active_meeting' status consistency
|
||||
|
||||
// Timing and cleanup settings for recording lifecycle management
|
||||
RECORDING_STARTED_TIMEOUT: '20s' as StringValue, // Timeout for recording to be marked as started
|
||||
RECORDING_ACTIVE_LOCK_TTL: '7d' as StringValue, // Redis Lock TTL for active recording in a room
|
||||
RECORDING_ACTIVE_LOCK_GC_INTERVAL: '15m' as StringValue, // Interval for cleaning up stale active recording locks
|
||||
RECORDING_ORPHANED_ACTIVE_LOCK_GRACE_PERIOD: '30s' as StringValue, // Grace period to consider an active recording lock as orphaned (should be greater than RECORDING_STARTED_TIMEOUT)
|
||||
RECORDING_STALE_GC_INTERVAL: '14m' as StringValue, // Interval for cleaning up stale recordings (not updated recently)
|
||||
RECORDING_STALE_GRACE_PERIOD: '5m' as StringValue, // Maximum allowed time since the last recording update before marking it as stale
|
||||
|
||||
CRON_JOB_MIN_LOCK_TTL: '59s' as StringValue, // Minimum TTL for cron job locks
|
||||
// Additional intervals
|
||||
MIN_FUTURE_TIME_FOR_ROOM_AUTODELETION_DATE: '1h' as StringValue, // Minimum time for room auto-deletion date
|
||||
MEETING_EMPTY_TIMEOUT: '' as StringValue, // Seconds to keep the meeting (LK room) open until the first participant joins
|
||||
MEETING_DEPARTURE_TIMEOUT: '' as StringValue // Seconds to keep the meeting (LK room) open after the last participant leaves
|
||||
MIN_ROOM_AUTO_DELETE_DURATION: '1h' as StringValue, // Minimum duration before a room can be auto-deleted
|
||||
MEETING_EMPTY_TIMEOUT: (process.env.MEETING_EMPTY_TIMEOUT || '20s') as StringValue, // Seconds to keep the meeting (LK room) open until the first participant joins
|
||||
MEETING_DEPARTURE_TIMEOUT: (process.env.MEETING_DEPARTURE_TIMEOUT || '20s') as StringValue, // Seconds to keep the meeting (LK room) open after the last participant leaves
|
||||
|
||||
// Participant name reservation
|
||||
PARTICIPANT_MAX_CONCURRENT_NAME_REQUESTS: '20', // Maximum number of request by the same name at the same time allowed
|
||||
PARTICIPANT_NAME_RESERVATION_TTL: '12h' as StringValue, // Time-to-live for participant name reservations
|
||||
|
||||
CAPTIONS_AGENT_NAME: 'speech-processing',
|
||||
|
||||
// MongoDB Schema Versions
|
||||
// These define the current schema version for each collection
|
||||
// Increment when making breaking changes to the schema structure
|
||||
// IMPORTANT: whenever you increment a schema version, update the MIGRATION_REV timestamp too.
|
||||
// This helps surface merge conflicts when multiple branches create schema migrations concurrently.
|
||||
GLOBAL_CONFIG_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054
|
||||
USER_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054
|
||||
API_KEY_SCHEMA_VERSION: 1 as SchemaVersion, // MIGRATION_REV: 1771328577054
|
||||
ROOM_SCHEMA_VERSION: 2 as SchemaVersion, // MIGRATION_REV: 1771328577054
|
||||
RECORDING_SCHEMA_VERSION: 2 as SchemaVersion // MIGRATION_REV: 1771328577054
|
||||
};
|
||||
|
||||
// This function is used to set private configuration values for testing purposes.
|
||||
|
||||
69
meet-ce/backend/src/controllers/ai-assistant.controller.ts
Normal file
69
meet-ce/backend/src/controllers/ai-assistant.controller.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { container } from '../config/dependency-injector.config.js';
|
||||
import { handleError } from '../models/error.model.js';
|
||||
import { AiAssistantService } from '../services/ai-assistant.service.js';
|
||||
import { LoggerService } from '../services/logger.service.js';
|
||||
import { RequestSessionService } from '../services/request-session.service.js';
|
||||
import { TokenService } from '../services/token.service.js';
|
||||
import { getRoomMemberToken } from '../utils/token.utils.js';
|
||||
|
||||
const getRoomMemberIdentityFromRequest = async (req: Request): Promise<string> => {
|
||||
const tokenService = container.get(TokenService);
|
||||
const token = getRoomMemberToken(req);
|
||||
|
||||
if (!token) {
|
||||
throw new Error('Room member token not found');
|
||||
}
|
||||
|
||||
const claims = await tokenService.verifyToken(token);
|
||||
|
||||
if (!claims.sub) {
|
||||
throw new Error('Room member token does not include participant identity');
|
||||
}
|
||||
|
||||
return claims.sub;
|
||||
};
|
||||
|
||||
export const createAssistant = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const requestSessionService = container.get(RequestSessionService);
|
||||
const aiAssistantService = container.get(AiAssistantService);
|
||||
// const payload: MeetCreateAssistantRequest = req.body;
|
||||
const roomId = requestSessionService.getRoomIdFromToken();
|
||||
|
||||
if (!roomId) {
|
||||
return handleError(res, new Error('Could not resolve room from token'), 'creating assistant');
|
||||
}
|
||||
|
||||
try {
|
||||
const participantIdentity = await getRoomMemberIdentityFromRequest(req);
|
||||
logger.verbose(`Creating assistant for participant '${participantIdentity}' in room '${roomId}'`);
|
||||
const assistant = await aiAssistantService.createLiveCaptionsAssistant(roomId, participantIdentity);
|
||||
return res.status(200).json(assistant);
|
||||
} catch (error) {
|
||||
handleError(res, error, `creating assistant in room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
export const cancelAssistant = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const requestSessionService = container.get(RequestSessionService);
|
||||
const aiAssistantService = container.get(AiAssistantService);
|
||||
const { assistantId } = req.params;
|
||||
const roomId = requestSessionService.getRoomIdFromToken();
|
||||
|
||||
if (!roomId) {
|
||||
return handleError(res, new Error('Could not resolve room from token'), 'canceling assistant');
|
||||
}
|
||||
|
||||
try {
|
||||
const participantIdentity = await getRoomMemberIdentityFromRequest(req);
|
||||
logger.verbose(
|
||||
`Canceling assistant '${assistantId}' for participant '${participantIdentity}' in room '${roomId}'`
|
||||
);
|
||||
await aiAssistantService.cancelAssistant(assistantId, roomId, participantIdentity);
|
||||
return res.status(204).send();
|
||||
} catch (error) {
|
||||
handleError(res, error, `canceling assistant '${assistantId}' in room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
18
meet-ce/backend/src/controllers/analytics.controller.ts
Normal file
18
meet-ce/backend/src/controllers/analytics.controller.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { container } from '../config/dependency-injector.config.js';
|
||||
import { handleError } from '../models/error.model.js';
|
||||
import { AnalyticsService } from '../services/analytics.service.js';
|
||||
import { LoggerService } from '../services/logger.service.js';
|
||||
|
||||
export const getAnalytics = async (req: Request, res: Response): Promise<void> => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.verbose('Analytics request received');
|
||||
|
||||
try {
|
||||
const analyticsService = container.get(AnalyticsService);
|
||||
const analytics = await analyticsService.getAnalytics();
|
||||
res.status(200).json(analytics);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'getting analytics');
|
||||
}
|
||||
};
|
||||
47
meet-ce/backend/src/controllers/api-key.controller.ts
Normal file
47
meet-ce/backend/src/controllers/api-key.controller.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { container } from '../config/dependency-injector.config.js';
|
||||
import { handleError } from '../models/error.model.js';
|
||||
import { ApiKeyService } from '../services/api-key.service.js';
|
||||
import { LoggerService } from '../services/logger.service.js';
|
||||
|
||||
export const createApiKey = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.verbose('Create API key request received');
|
||||
|
||||
const apiKeyService = container.get(ApiKeyService);
|
||||
|
||||
try {
|
||||
const apiKey = await apiKeyService.createApiKey();
|
||||
return res.status(201).json(apiKey);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'creating API key');
|
||||
}
|
||||
};
|
||||
|
||||
export const getApiKeys = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.verbose('Get API keys request received');
|
||||
|
||||
const apiKeyService = container.get(ApiKeyService);
|
||||
|
||||
try {
|
||||
const apiKeys = await apiKeyService.getApiKeys();
|
||||
return res.status(200).json(apiKeys);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'getting API keys');
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteApiKeys = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.verbose('Delete API keys request received');
|
||||
|
||||
const apiKeyService = container.get(ApiKeyService);
|
||||
|
||||
try {
|
||||
await apiKeyService.deleteApiKeys();
|
||||
return res.status(200).json({ message: 'API keys deleted successfully' });
|
||||
} catch (error) {
|
||||
handleError(res, error, 'deleting API keys');
|
||||
}
|
||||
};
|
||||
@ -1,8 +1,6 @@
|
||||
import { AuthTransportMode } from '@openvidu-meet/typings';
|
||||
import { Request, Response } from 'express';
|
||||
import { ClaimGrants } from 'livekit-server-sdk';
|
||||
import { container } from '../config/index.js';
|
||||
import { INTERNAL_CONFIG } from '../config/internal-config.js';
|
||||
import { container } from '../config/dependency-injector.config.js';
|
||||
import {
|
||||
errorInvalidCredentials,
|
||||
errorInvalidRefreshToken,
|
||||
@ -11,16 +9,18 @@ import {
|
||||
handleError,
|
||||
rejectRequestFromMeetError
|
||||
} from '../models/error.model.js';
|
||||
import { AuthService, LoggerService, TokenService, UserService } from '../services/index.js';
|
||||
import { getAuthTransportMode, getCookieOptions, getRefreshToken } from '../utils/index.js';
|
||||
import { LoggerService } from '../services/logger.service.js';
|
||||
import { TokenService } from '../services/token.service.js';
|
||||
import { UserService } from '../services/user.service.js';
|
||||
import { getRefreshToken } from '../utils/token.utils.js';
|
||||
|
||||
export const login = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.verbose('Login request received');
|
||||
const { username, password } = req.body as { username: string; password: string };
|
||||
|
||||
const authService = container.get(AuthService);
|
||||
const user = await authService.authenticateUser(username, password);
|
||||
const userService = container.get(UserService);
|
||||
const user = await userService.authenticateUser(username, password);
|
||||
|
||||
if (!user) {
|
||||
logger.warn('Login failed');
|
||||
@ -34,49 +34,19 @@ export const login = async (req: Request, res: Response) => {
|
||||
const refreshToken = await tokenService.generateRefreshToken(user);
|
||||
|
||||
logger.info(`Login succeeded for user '${username}'`);
|
||||
const transportMode = await getAuthTransportMode();
|
||||
|
||||
if (transportMode === AuthTransportMode.HEADER) {
|
||||
// Send tokens in response body for header mode
|
||||
return res.status(200).json({
|
||||
message: `User '${username}' logged in successfully`,
|
||||
accessToken,
|
||||
refreshToken
|
||||
});
|
||||
} else {
|
||||
// Send tokens as cookies for cookie mode
|
||||
res.cookie(
|
||||
INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME,
|
||||
accessToken,
|
||||
getCookieOptions('/', INTERNAL_CONFIG.ACCESS_TOKEN_EXPIRATION)
|
||||
);
|
||||
res.cookie(
|
||||
INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME,
|
||||
refreshToken,
|
||||
getCookieOptions(
|
||||
`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth`,
|
||||
INTERNAL_CONFIG.REFRESH_TOKEN_EXPIRATION
|
||||
)
|
||||
);
|
||||
return res.status(200).json({ message: `User '${username}' logged in successfully` });
|
||||
}
|
||||
return res.status(200).json({
|
||||
message: `User '${username}' logged in successfully`,
|
||||
accessToken,
|
||||
refreshToken
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(res, error, 'generating access and refresh tokens');
|
||||
}
|
||||
};
|
||||
|
||||
export const logout = async (_req: Request, res: Response) => {
|
||||
const transportMode = await getAuthTransportMode();
|
||||
|
||||
if (transportMode === AuthTransportMode.COOKIE) {
|
||||
// Clear cookies only in cookie mode
|
||||
res.clearCookie(INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME);
|
||||
res.clearCookie(INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME, {
|
||||
path: `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth`
|
||||
});
|
||||
}
|
||||
|
||||
// In header mode, the client is responsible for clearing localStorage
|
||||
// The client is responsible for clearing tokens from localStorage,
|
||||
// so just respond with success
|
||||
return res.status(200).json({ message: 'Logout successful' });
|
||||
};
|
||||
|
||||
@ -118,66 +88,11 @@ export const refreshToken = async (req: Request, res: Response) => {
|
||||
const accessToken = await tokenService.generateAccessToken(user);
|
||||
|
||||
logger.info(`Access token refreshed for user '${username}'`);
|
||||
const transportMode = await getAuthTransportMode();
|
||||
|
||||
if (transportMode === AuthTransportMode.HEADER) {
|
||||
// Send access token in response body for header mode
|
||||
return res.status(200).json({
|
||||
message: `Access token for user '${username}' successfully refreshed`,
|
||||
accessToken
|
||||
});
|
||||
} else {
|
||||
// Send access token as cookie for cookie mode
|
||||
res.cookie(
|
||||
INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME,
|
||||
accessToken,
|
||||
getCookieOptions('/', INTERNAL_CONFIG.ACCESS_TOKEN_EXPIRATION)
|
||||
);
|
||||
return res.status(200).json({ message: `Access token for user '${username}' successfully refreshed` });
|
||||
}
|
||||
return res.status(200).json({
|
||||
message: `Access token for user '${username}' successfully refreshed`,
|
||||
accessToken
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(res, error, 'refreshing token');
|
||||
}
|
||||
};
|
||||
|
||||
export const createApiKey = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.verbose('Create API key request received');
|
||||
|
||||
const authService = container.get(AuthService);
|
||||
|
||||
try {
|
||||
const apiKey = await authService.createApiKey();
|
||||
return res.status(201).json(apiKey);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'creating API key');
|
||||
}
|
||||
};
|
||||
|
||||
export const getApiKeys = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.verbose('Get API keys request received');
|
||||
|
||||
const authService = container.get(AuthService);
|
||||
|
||||
try {
|
||||
const apiKeys = await authService.getApiKeys();
|
||||
return res.status(200).json(apiKeys);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'getting API keys');
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteApiKeys = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.verbose('Delete API keys request received');
|
||||
|
||||
const authService = container.get(AuthService);
|
||||
|
||||
try {
|
||||
await authService.deleteApiKeys();
|
||||
return res.status(200).json({ message: 'API keys deleted successfully' });
|
||||
} catch (error) {
|
||||
handleError(res, error, 'deleting API keys');
|
||||
}
|
||||
};
|
||||
|
||||
124
meet-ce/backend/src/controllers/global-config.controller.ts
Normal file
124
meet-ce/backend/src/controllers/global-config.controller.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { MeetAppearanceConfig, SecurityConfig, WebhookConfig } from '@openvidu-meet/typings';
|
||||
import { Request, Response } from 'express';
|
||||
import { container } from '../config/dependency-injector.config.js';
|
||||
import { MEET_ENV } from '../environment.js';
|
||||
import { handleError } from '../models/error.model.js';
|
||||
import { GlobalConfigService } from '../services/global-config.service.js';
|
||||
import { LoggerService } from '../services/logger.service.js';
|
||||
import { OpenViduWebhookService } from '../services/openvidu-webhook.service.js';
|
||||
|
||||
export const updateWebhookConfig = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const configService = container.get(GlobalConfigService);
|
||||
|
||||
logger.info(`Updating webhooks config: ${JSON.stringify(req.body)}`);
|
||||
const webhookConfig = req.body as WebhookConfig;
|
||||
|
||||
try {
|
||||
await configService.updateWebhookConfig(webhookConfig);
|
||||
return res.status(200).json({ message: 'Webhooks config updated successfully' });
|
||||
} catch (error) {
|
||||
handleError(res, error, 'updating webhooks config');
|
||||
}
|
||||
};
|
||||
|
||||
export const getWebhookConfig = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const configService = container.get(GlobalConfigService);
|
||||
|
||||
logger.verbose('Getting webhooks config');
|
||||
|
||||
try {
|
||||
const webhookConfig = await configService.getWebhookConfig();
|
||||
return res.status(200).json(webhookConfig);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'getting webhooks config');
|
||||
}
|
||||
};
|
||||
|
||||
export const testWebhook = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const webhookService = container.get(OpenViduWebhookService);
|
||||
|
||||
logger.verbose(`Testing webhook URL: ${req.body.url}`);
|
||||
const url = req.body.url;
|
||||
|
||||
try {
|
||||
await webhookService.testWebhookUrl(url);
|
||||
logger.info(`Webhook URL '${url}' is valid`);
|
||||
return res.status(200).json({ message: 'Webhook URL is valid' });
|
||||
} catch (error) {
|
||||
handleError(res, error, 'testing webhook URL');
|
||||
}
|
||||
};
|
||||
|
||||
export const updateSecurityConfig = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const configService = container.get(GlobalConfigService);
|
||||
|
||||
logger.verbose(`Updating security config: ${JSON.stringify(req.body)}`);
|
||||
const securityConfig = req.body as SecurityConfig;
|
||||
|
||||
try {
|
||||
await configService.updateSecurityConfig(securityConfig);
|
||||
return res.status(200).json({ message: 'Security config updated successfully' });
|
||||
} catch (error) {
|
||||
handleError(res, error, 'updating security config');
|
||||
}
|
||||
};
|
||||
|
||||
export const getSecurityConfig = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const configService = container.get(GlobalConfigService);
|
||||
|
||||
logger.verbose('Getting security config');
|
||||
|
||||
try {
|
||||
const securityConfig = await configService.getSecurityConfig();
|
||||
return res.status(200).json(securityConfig);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'getting security config');
|
||||
}
|
||||
};
|
||||
|
||||
export const updateRoomsAppearanceConfig = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const configService = container.get(GlobalConfigService);
|
||||
|
||||
logger.verbose(`Updating rooms appearance config: ${JSON.stringify(req.body)}`);
|
||||
const appearanceConfig = req.body as { appearance: MeetAppearanceConfig };
|
||||
|
||||
try {
|
||||
await configService.updateRoomsAppearanceConfig(appearanceConfig);
|
||||
return res.status(200).json({ message: 'Rooms appearance config updated successfully' });
|
||||
} catch (error) {
|
||||
handleError(res, error, 'updating rooms appearance config');
|
||||
}
|
||||
};
|
||||
|
||||
export const getRoomsAppearanceConfig = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const configService = container.get(GlobalConfigService);
|
||||
|
||||
logger.verbose(`Getting rooms appearance config`);
|
||||
|
||||
try {
|
||||
const appearanceConfig = await configService.getRoomsAppearanceConfig();
|
||||
return res.status(200).json(appearanceConfig);
|
||||
} catch (error) {
|
||||
handleError(res, error, 'getting rooms appearance config');
|
||||
}
|
||||
};
|
||||
|
||||
export const getCaptionsConfig = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
|
||||
logger.verbose('Getting captions config');
|
||||
|
||||
try {
|
||||
const captionsEnabled = MEET_ENV.CAPTIONS_ENABLED === 'true';
|
||||
return res.status(200).json({ enabled: captionsEnabled });
|
||||
} catch (error) {
|
||||
handleError(res, error, 'getting captions config');
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user