├── appId.js
├── .DS_Store
├── demo
├── avatars
│ ├── male-1.png
│ ├── male-2.png
│ ├── male-3.png
│ ├── male-4.png
│ ├── male-5.png
│ ├── female-1.png
│ ├── female-2.png
│ ├── female-3.png
│ ├── female-4.png
│ └── female-5.png
├── package.json
├── icons
│ ├── leave.svg
│ ├── mic.svg
│ └── mic-off.svg
├── .gitignore
├── public
│ └── vite.svg
├── index.html
├── style.css
├── main.js
└── package-lock.json
├── images
├── avatars
│ ├── brad.png
│ ├── male-1.png
│ ├── male-2.png
│ ├── male-3.png
│ ├── male-4.png
│ ├── male-5.png
│ ├── female-1.png
│ ├── female-2.png
│ ├── female-3.png
│ ├── female-4.png
│ └── female-5.png
├── room-preview.png
├── lobby-preview.png
└── icons
│ ├── leave.svg
│ ├── mic.svg
│ └── mic-off.svg
├── 7-Displaying-Avatars
├── avatars
│ ├── male-1.png
│ ├── male-2.png
│ ├── male-3.png
│ ├── male-4.png
│ ├── male-5.png
│ ├── female-1.png
│ ├── female-2.png
│ ├── female-3.png
│ ├── female-4.png
│ └── female-5.png
├── package.json
├── icons
│ ├── leave.svg
│ ├── mic.svg
│ └── mic-off.svg
├── .gitignore
├── public
│ └── vite.svg
├── index.html
├── style.css
├── guide.md
└── main.js
├── 1-Basic-Voice-Chat
├── package.json
├── .gitignore
├── icons
│ ├── leave.svg
│ ├── mic.svg
│ └── mic-off.svg
├── index.html
├── public
│ └── vite.svg
├── style.css
├── main.js
├── guide.md
└── package-lock.json
├── 3-Mic-Mute-Toggle
├── package.json
├── .gitignore
├── icons
│ ├── leave.svg
│ ├── mic.svg
│ └── mic-off.svg
├── index.html
├── public
│ └── vite.svg
├── guide.md
├── style.css
└── main.js
├── 6-Creating-Rooms
├── package.json
├── .gitignore
├── icons
│ ├── leave.svg
│ ├── mic.svg
│ └── mic-off.svg
├── index.html
├── public
│ └── vite.svg
├── style.css
├── guide.md
└── main.js
├── 4-RTM-RTC-Integration
├── package.json
├── .gitignore
├── icons
│ ├── leave.svg
│ ├── mic.svg
│ └── mic-off.svg
├── index.html
├── public
│ └── vite.svg
├── style.css
├── main.js
└── guide.md
├── 5-Displaying-User-Names
├── package.json
├── .gitignore
├── icons
│ ├── leave.svg
│ ├── mic.svg
│ └── mic-off.svg
├── index.html
├── public
│ └── vite.svg
├── style.css
├── main.js
└── guide.md
├── 2-Active-Speaker-Indicator
├── package.json
├── .gitignore
├── icons
│ ├── leave.svg
│ ├── mic.svg
│ └── mic-off.svg
├── index.html
├── public
│ └── vite.svg
├── style.css
├── guide.md
└── main.js
└── readme.md
/appId.js:
--------------------------------------------------------------------------------
1 | const appid = ""
2 |
3 | export default appid;
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/.DS_Store
--------------------------------------------------------------------------------
/demo/avatars/male-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/male-1.png
--------------------------------------------------------------------------------
/demo/avatars/male-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/male-2.png
--------------------------------------------------------------------------------
/demo/avatars/male-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/male-3.png
--------------------------------------------------------------------------------
/demo/avatars/male-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/male-4.png
--------------------------------------------------------------------------------
/demo/avatars/male-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/male-5.png
--------------------------------------------------------------------------------
/images/avatars/brad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/brad.png
--------------------------------------------------------------------------------
/images/room-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/room-preview.png
--------------------------------------------------------------------------------
/demo/avatars/female-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/female-1.png
--------------------------------------------------------------------------------
/demo/avatars/female-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/female-2.png
--------------------------------------------------------------------------------
/demo/avatars/female-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/female-3.png
--------------------------------------------------------------------------------
/demo/avatars/female-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/female-4.png
--------------------------------------------------------------------------------
/demo/avatars/female-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/demo/avatars/female-5.png
--------------------------------------------------------------------------------
/images/avatars/male-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/male-1.png
--------------------------------------------------------------------------------
/images/avatars/male-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/male-2.png
--------------------------------------------------------------------------------
/images/avatars/male-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/male-3.png
--------------------------------------------------------------------------------
/images/avatars/male-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/male-4.png
--------------------------------------------------------------------------------
/images/avatars/male-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/male-5.png
--------------------------------------------------------------------------------
/images/lobby-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/lobby-preview.png
--------------------------------------------------------------------------------
/images/avatars/female-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/female-1.png
--------------------------------------------------------------------------------
/images/avatars/female-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/female-2.png
--------------------------------------------------------------------------------
/images/avatars/female-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/female-3.png
--------------------------------------------------------------------------------
/images/avatars/female-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/female-4.png
--------------------------------------------------------------------------------
/images/avatars/female-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/images/avatars/female-5.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/male-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/male-1.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/male-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/male-2.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/male-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/male-3.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/male-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/male-4.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/male-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/male-5.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/female-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/female-1.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/female-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/female-2.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/female-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/female-3.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/female-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/female-4.png
--------------------------------------------------------------------------------
/7-Displaying-Avatars/avatars/female-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Voice-Chat-Rooms/HEAD/7-Displaying-Avatars/avatars/female-5.png
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^4.0.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^4.0.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^4.0.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/6-Creating-Rooms/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^4.0.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/4-RTM-RTC-Integration/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^4.0.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/5-Displaying-User-Names/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^4.0.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/7-Displaying-Avatars/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^4.0.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^4.0.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/demo/icons/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/images/icons/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/icons/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/icons/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/6-Creating-Rooms/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/6-Creating-Rooms/icons/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/7-Displaying-Avatars/icons/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/4-RTM-RTC-Integration/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/4-RTM-RTC-Integration/icons/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/5-Displaying-User-Names/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/5-Displaying-User-Names/icons/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/7-Displaying-Avatars/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/icons/leave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/icons/mic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/icons/mic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/6-Creating-Rooms/icons/mic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/icons/mic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/icons/mic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/4-RTM-RTC-Integration/icons/mic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/7-Displaying-Avatars/icons/mic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/icons/mic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/5-Displaying-User-Names/icons/mic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/icons/mic-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/icons/mic-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/6-Creating-Rooms/icons/mic-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/icons/mic-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/icons/mic-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/4-RTM-RTC-Integration/icons/mic-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/7-Displaying-Avatars/icons/mic-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/icons/mic-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/5-Displaying-User-Names/icons/mic-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Voice Rooms Chat App
2 |
3 | A simple voice chat app using the Agora RTC And RTM Web SDK.
4 |
5 | Watch the complete video course [here!](https://youtu.be/abka2ug0kbs)
6 |
7 | **Room**
8 |
9 |
10 | **Lobby**
11 |
12 |
13 |
14 | ### Installation
15 |
16 | > Ensure you have Node JS installed
17 |
18 |
19 | ```
20 | git clone https://github.com/divanov11/Voice-Chat-Rooms
21 |
22 | cd Voice-Chat-Rooms/demo
23 |
24 | npm install
25 | ```
26 |
27 | Add you APP ID Inside of `appid.js`
28 |
29 | > NOTE: Get this from your Agora console when you initiate a new project.
30 |
31 | ```js
32 | //appId.js
33 | const appid = "YOU AGORA APP ID"
34 |
35 | export default appid;
36 | ```
37 |
38 | Start Development server
39 |
40 | ```
41 | npm run dev
42 | ```
43 |
44 | ### Features
45 | - Create/Join breakout rooms
46 | - Active speaker volume indicator
47 | - Display user names and avatars
48 |
49 |
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/4-RTM-RTC-Integration/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/5-Displaying-User-Names/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/6-Creating-Rooms/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
21 |
22 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/demo/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/6-Creating-Rooms/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/7-Displaying-Avatars/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/4-RTM-RTC-Integration/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/5-Displaying-User-Names/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/guide.md:
--------------------------------------------------------------------------------
1 | # Mic Mute Toggle
2 |
3 | Adding the ability to toggle mute and unmute during a conversation.
4 |
5 |
6 | ### Toggle Mic Method
7 |
8 | Before getting started we want to add an initial state to our mic.
9 |
10 | **Mic Initial State**
11 |
12 | At the top of our `main.js` file create a variable for our mic state and set it to `true`, as in the mic is muted.
13 |
14 | ```js
15 | let micMuted = true
16 | ```
17 |
18 | To set the initial state for the mic let's add the following inside of the `initRtc` method, just underneath the `createMicrophoneAudioTrack`. This will ensure that our mic is off when we first join a room:
19 |
20 | ```js
21 | const initRtc = async () => {
22 | //..
23 | //audioTracks.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
24 | audioTracks.localAudioTrack.setMuted(micMuted)
25 | //..
26 | }
27 | ```
28 |
29 | **Toggle Mic Method**
30 |
31 | This method will toggle our mic, our mic state, and the mic icon to show an active/inactive state.
32 |
33 | The logic here is simple:
34 |
35 | - When this method is triggered, if the mic is muted, update the mic icon image and color, change the state to reflect the opposite status, then call the `setMuted()` method.
36 |
37 | `setMuted` takes the value of either true or false and changes the status of our audio.
38 |
39 | We also want to add an event handler to trigger this function.
40 |
41 |
42 | ```js
43 | const toggleMic = async (e) => {
44 | if (micMuted){
45 | e.target.src = 'icons/mic.svg'
46 | e.target.style.backgroundColor = 'ivory'
47 | micMuted = false
48 | }else{
49 | e.target.src = 'icons/mic-off.svg'
50 | e.target.style.backgroundColor = 'indianred'
51 |
52 | micMuted = true
53 | }
54 | audioTracks.localAudioTrack.setMuted(micMuted)
55 | }
56 |
57 | document.getElementById('mic-icon').addEventListener('click', toggleMic)
58 | ```
59 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
21 |
22 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/7-Displaying-Avatars/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
21 |
22 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
2 |
3 | :root {
4 | font-family: 'Roboto', sans-serif;
5 | font-size: 16px;
6 | color-scheme: dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 | }
10 |
11 | a {
12 | color: #646cff;
13 | text-decoration: none;
14 | }
15 | a:hover {
16 | color: #535bf2;
17 | }
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | #container{
24 | max-width: 800px;
25 | margin: 0 auto;
26 | padding: 1em;
27 | }
28 |
29 | #room-header{
30 | justify-content: space-between;
31 | align-items: center;
32 | padding: 1em 0;
33 | display: none;
34 | }
35 |
36 | #room-header-controls{
37 | display: flex;
38 | }
39 |
40 | #room-header-controls > img {
41 | margin: 0 2px;
42 | }
43 |
44 | .control-icon{
45 | background-color: indianred;
46 | border: none;
47 | padding: 0.5em;
48 | height: 20px;
49 | cursor: pointer;
50 | border-radius: 5px;
51 | }
52 |
53 |
54 |
55 | #members{
56 | display: flex;
57 | flex-wrap: wrap;
58 | }
59 |
60 | .speaker{
61 | border:2px solid #fff;
62 | display: flex;
63 | flex-direction: column;
64 | align-items: center;
65 | width: 120px;
66 | text-align: center;
67 | margin: 0.5em;
68 | }
69 |
70 | #avatars{
71 | display: flex;
72 | flex-wrap: wrap;
73 | margin: 1em 0;
74 | }
75 |
76 | .avatar-selection{
77 | height: 50px;
78 | width: 50px;
79 | object-fit: contain;
80 | border: 2px solid #FFF;
81 | border-radius: 50%;
82 | opacity: .5;
83 | cursor: pointer;
84 | margin: 0.25em;
85 | }
86 |
87 | .user-avatar{
88 | height: 75px;
89 | width: 75px;
90 | object-fit: contain;
91 | border: 2px solid #FFF;
92 | border-radius: 50%;
93 | }
94 |
95 | #form input{
96 | width: 100%;
97 | padding: 1em;
98 | margin-bottom:2em;
99 | box-sizing: border-box;
100 | }
101 |
102 | #form label{
103 | margin-bottom: 0.5em;
104 | }
105 |
106 | #form-fields{
107 | display: flex;
108 | flex-direction: column;
109 | }
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
2 |
3 | :root {
4 | font-family: 'Roboto', sans-serif;
5 | font-size: 16px;
6 | color-scheme: dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 | }
10 |
11 | a {
12 | color: #646cff;
13 | text-decoration: none;
14 | }
15 | a:hover {
16 | color: #535bf2;
17 | }
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | #container{
24 | max-width: 800px;
25 | margin: 0 auto;
26 | padding: 1em;
27 | }
28 |
29 | #room-header{
30 | justify-content: space-between;
31 | align-items: center;
32 | padding: 1em 0;
33 | display: none;
34 | }
35 |
36 | #room-header-controls{
37 | display: flex;
38 | }
39 |
40 | #room-header-controls > img {
41 | margin: 0 2px;
42 | }
43 |
44 | .control-icon{
45 | background-color: indianred;
46 | border: none;
47 | padding: 0.5em;
48 | height: 20px;
49 | cursor: pointer;
50 | border-radius: 5px;
51 | }
52 |
53 |
54 |
55 | #members{
56 | display: flex;
57 | flex-wrap: wrap;
58 | }
59 |
60 | .speaker{
61 | border:2px solid #fff;
62 | display: flex;
63 | flex-direction: column;
64 | align-items: center;
65 | width: 120px;
66 | text-align: center;
67 | margin: 0.5em;
68 | }
69 |
70 | #avatars{
71 | display: flex;
72 | flex-wrap: wrap;
73 | margin: 1em 0;
74 | }
75 |
76 | .avatar-selection{
77 | height: 50px;
78 | width: 50px;
79 | object-fit: contain;
80 | border: 2px solid #FFF;
81 | border-radius: 50%;
82 | opacity: .5;
83 | cursor: pointer;
84 | margin: 0.25em;
85 | }
86 |
87 | .user-avatar{
88 | height: 75px;
89 | width: 75px;
90 | object-fit: contain;
91 | border: 2px solid #FFF;
92 | border-radius: 50%;
93 | }
94 |
95 | #form input{
96 | width: 100%;
97 | padding: 1em;
98 | margin-bottom:2em;
99 | box-sizing: border-box;
100 | }
101 |
102 | #form label{
103 | margin-bottom: 0.5em;
104 | }
105 |
106 | #form-fields{
107 | display: flex;
108 | flex-direction: column;
109 | }
--------------------------------------------------------------------------------
/6-Creating-Rooms/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
2 |
3 | :root {
4 | font-family: 'Roboto', sans-serif;
5 | font-size: 16px;
6 | color-scheme: dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 | }
10 |
11 | a {
12 | color: #646cff;
13 | text-decoration: none;
14 | }
15 | a:hover {
16 | color: #535bf2;
17 | }
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | #container{
24 | max-width: 800px;
25 | margin: 0 auto;
26 | padding: 1em;
27 | }
28 |
29 | #room-header{
30 | justify-content: space-between;
31 | align-items: center;
32 | padding: 1em 0;
33 | display: none;
34 | }
35 |
36 | #room-header-controls{
37 | display: flex;
38 | }
39 |
40 | #room-header-controls > img {
41 | margin: 0 2px;
42 | }
43 |
44 | .control-icon{
45 | background-color: indianred;
46 | border: none;
47 | padding: 0.5em;
48 | height: 20px;
49 | cursor: pointer;
50 | border-radius: 5px;
51 | }
52 |
53 |
54 |
55 | #members{
56 | display: flex;
57 | flex-wrap: wrap;
58 | }
59 |
60 | .speaker{
61 | border:2px solid #fff;
62 | display: flex;
63 | flex-direction: column;
64 | align-items: center;
65 | width: 120px;
66 | text-align: center;
67 | margin: 0.5em;
68 | }
69 |
70 | #avatars{
71 | display: flex;
72 | flex-wrap: wrap;
73 | margin: 1em 0;
74 | }
75 |
76 | .avatar-selection{
77 | height: 50px;
78 | width: 50px;
79 | object-fit: contain;
80 | border: 2px solid #FFF;
81 | border-radius: 50%;
82 | opacity: .5;
83 | cursor: pointer;
84 | margin: 0.25em;
85 | }
86 |
87 | .user-avatar{
88 | height: 75px;
89 | width: 75px;
90 | object-fit: contain;
91 | border: 2px solid #FFF;
92 | border-radius: 50%;
93 | }
94 |
95 | #form input{
96 | width: 100%;
97 | padding: 1em;
98 | margin-bottom:2em;
99 | box-sizing: border-box;
100 | }
101 |
102 | #form label{
103 | margin-bottom: 0.5em;
104 | }
105 |
106 | #form-fields{
107 | display: flex;
108 | flex-direction: column;
109 | }
--------------------------------------------------------------------------------
/4-RTM-RTC-Integration/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
2 |
3 | :root {
4 | font-family: 'Roboto', sans-serif;
5 | font-size: 16px;
6 | color-scheme: dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 | }
10 |
11 | a {
12 | color: #646cff;
13 | text-decoration: none;
14 | }
15 | a:hover {
16 | color: #535bf2;
17 | }
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | #container{
24 | max-width: 800px;
25 | margin: 0 auto;
26 | padding: 1em;
27 | }
28 |
29 | #room-header{
30 | justify-content: space-between;
31 | align-items: center;
32 | padding: 1em 0;
33 | display: none;
34 | }
35 |
36 | #room-header-controls{
37 | display: flex;
38 | }
39 |
40 | #room-header-controls > img {
41 | margin: 0 2px;
42 | }
43 |
44 | .control-icon{
45 | background-color: indianred;
46 | border: none;
47 | padding: 0.5em;
48 | height: 20px;
49 | cursor: pointer;
50 | border-radius: 5px;
51 | }
52 |
53 |
54 |
55 | #members{
56 | display: flex;
57 | flex-wrap: wrap;
58 | }
59 |
60 | .speaker{
61 | border:2px solid #fff;
62 | display: flex;
63 | flex-direction: column;
64 | align-items: center;
65 | width: 120px;
66 | text-align: center;
67 | margin: 0.5em;
68 | }
69 |
70 | #avatars{
71 | display: flex;
72 | flex-wrap: wrap;
73 | margin: 1em 0;
74 | }
75 |
76 | .avatar-selection{
77 | height: 50px;
78 | width: 50px;
79 | object-fit: contain;
80 | border: 2px solid #FFF;
81 | border-radius: 50%;
82 | opacity: .5;
83 | cursor: pointer;
84 | margin: 0.25em;
85 | }
86 |
87 | .user-avatar{
88 | height: 75px;
89 | width: 75px;
90 | object-fit: contain;
91 | border: 2px solid #FFF;
92 | border-radius: 50%;
93 | }
94 |
95 | #form input{
96 | width: 100%;
97 | padding: 1em;
98 | margin-bottom:2em;
99 | box-sizing: border-box;
100 | }
101 |
102 | #form label{
103 | margin-bottom: 0.5em;
104 | }
105 |
106 | #form-fields{
107 | display: flex;
108 | flex-direction: column;
109 | }
--------------------------------------------------------------------------------
/5-Displaying-User-Names/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
2 |
3 | :root {
4 | font-family: 'Roboto', sans-serif;
5 | font-size: 16px;
6 | color-scheme: dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 | }
10 |
11 | a {
12 | color: #646cff;
13 | text-decoration: none;
14 | }
15 | a:hover {
16 | color: #535bf2;
17 | }
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | #container{
24 | max-width: 800px;
25 | margin: 0 auto;
26 | padding: 1em;
27 | }
28 |
29 | #room-header{
30 | justify-content: space-between;
31 | align-items: center;
32 | padding: 1em 0;
33 | display: none;
34 | }
35 |
36 | #room-header-controls{
37 | display: flex;
38 | }
39 |
40 | #room-header-controls > img {
41 | margin: 0 2px;
42 | }
43 |
44 | .control-icon{
45 | background-color: indianred;
46 | border: none;
47 | padding: 0.5em;
48 | height: 20px;
49 | cursor: pointer;
50 | border-radius: 5px;
51 | }
52 |
53 |
54 |
55 | #members{
56 | display: flex;
57 | flex-wrap: wrap;
58 | }
59 |
60 | .speaker{
61 | border:2px solid #fff;
62 | display: flex;
63 | flex-direction: column;
64 | align-items: center;
65 | width: 120px;
66 | text-align: center;
67 | margin: 0.5em;
68 | }
69 |
70 | #avatars{
71 | display: flex;
72 | flex-wrap: wrap;
73 | margin: 1em 0;
74 | }
75 |
76 | .avatar-selection{
77 | height: 50px;
78 | width: 50px;
79 | object-fit: contain;
80 | border: 2px solid #FFF;
81 | border-radius: 50%;
82 | opacity: .5;
83 | cursor: pointer;
84 | margin: 0.25em;
85 | }
86 |
87 | .user-avatar{
88 | height: 75px;
89 | width: 75px;
90 | object-fit: contain;
91 | border: 2px solid #FFF;
92 | border-radius: 50%;
93 | }
94 |
95 | #form input{
96 | width: 100%;
97 | padding: 1em;
98 | margin-bottom:2em;
99 | box-sizing: border-box;
100 | }
101 |
102 | #form label{
103 | margin-bottom: 0.5em;
104 | }
105 |
106 | #form-fields{
107 | display: flex;
108 | flex-direction: column;
109 | }
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
2 |
3 | :root {
4 | font-family: 'Roboto', sans-serif;
5 | font-size: 16px;
6 | color-scheme: dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 | }
10 |
11 | a {
12 | color: #646cff;
13 | text-decoration: none;
14 | }
15 | a:hover {
16 | color: #535bf2;
17 | }
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | #container{
24 | max-width: 800px;
25 | margin: 0 auto;
26 | padding: 1em;
27 | }
28 |
29 | #room-header{
30 | justify-content: space-between;
31 | align-items: center;
32 | padding: 1em 0;
33 | display: none;
34 | }
35 |
36 | #room-header-controls{
37 | display: flex;
38 | }
39 |
40 | #room-header-controls > img {
41 | margin: 0 2px;
42 | }
43 |
44 | .control-icon{
45 | background-color: indianred;
46 | border: none;
47 | padding: 0.5em;
48 | height: 20px;
49 | cursor: pointer;
50 | border-radius: 5px;
51 | }
52 |
53 |
54 |
55 | #members{
56 | display: flex;
57 | flex-wrap: wrap;
58 | }
59 |
60 | .speaker{
61 | border:2px solid #fff;
62 | display: flex;
63 | flex-direction: column;
64 | align-items: center;
65 | width: 120px;
66 | text-align: center;
67 | margin: 0.5em;
68 | }
69 |
70 | #avatars{
71 | display: flex;
72 | flex-wrap: wrap;
73 | margin: 1em 0;
74 | }
75 |
76 | .avatar-selection{
77 | height: 50px;
78 | width: 50px;
79 | object-fit: contain;
80 | border: 2px solid #FFF;
81 | border-radius: 50%;
82 | opacity: .5;
83 | cursor: pointer;
84 | margin: 0.25em;
85 | }
86 |
87 | .user-avatar{
88 | height: 75px;
89 | width: 75px;
90 | object-fit: contain;
91 | border: 2px solid #FFF;
92 | border-radius: 50%;
93 | }
94 |
95 | #form input{
96 | width: 100%;
97 | padding: 1em;
98 | margin-bottom:2em;
99 | box-sizing: border-box;
100 | }
101 |
102 | #form label{
103 | margin-bottom: 0.5em;
104 | }
105 |
106 | #form-fields{
107 | display: flex;
108 | flex-direction: column;
109 | }
--------------------------------------------------------------------------------
/demo/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
2 |
3 | :root {
4 | font-family: 'Roboto', sans-serif;
5 | font-size: 16px;
6 | color-scheme: dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 | }
10 |
11 | a {
12 | color: #646cff;
13 | text-decoration: none;
14 | }
15 | a:hover {
16 | color: #535bf2;
17 | }
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | #container{
24 | max-width: 800px;
25 | margin: 0 auto;
26 | padding: 1em;
27 | }
28 |
29 | #room-header{
30 | justify-content: space-between;
31 | align-items: center;
32 | padding: 1em 0;
33 | display: none;
34 | }
35 |
36 | #room-header-controls{
37 | display: flex;
38 | }
39 |
40 | #room-header-controls > img {
41 | margin: 0 2px;
42 | }
43 |
44 | .control-icon{
45 | background-color: indianred;
46 | border: none;
47 | padding: 0.5em;
48 | height: 20px;
49 | cursor: pointer;
50 | border-radius: 5px;
51 | }
52 |
53 |
54 |
55 | #members{
56 | display: flex;
57 | flex-wrap: wrap;
58 | }
59 |
60 | .speaker{
61 | /* border:2px solid #fff;
62 | border-radius: 50%; */
63 |
64 | margin: 0.5em;
65 | display: flex;
66 | flex-direction: column;
67 | align-items: center;
68 | width: 120px;
69 | text-align: center;
70 | }
71 |
72 | #avatars{
73 | display: flex;
74 | flex-wrap: wrap;
75 | margin: 1em 0;
76 | }
77 |
78 | .avatar-selection{
79 | height: 50px;
80 | width: 50px;
81 | object-fit: contain;
82 | border: 2px solid #FFF;
83 | border-radius: 50%;
84 | opacity: .5;
85 | cursor: pointer;
86 | margin: 0.25em;
87 | }
88 |
89 | .user-avatar{
90 | height: 75px;
91 | width: 75px;
92 | object-fit: contain;
93 | border: 2px solid #FFF;
94 | border-radius: 50%;
95 | }
96 |
97 | #form input{
98 | width: 100%;
99 | padding: 1em;
100 | margin-bottom:2em;
101 | box-sizing: border-box;
102 | }
103 |
104 | #form label{
105 | margin-bottom: 0.5em;
106 | }
107 |
108 | #form-fields{
109 | display: flex;
110 | flex-direction: column;
111 | }
--------------------------------------------------------------------------------
/7-Displaying-Avatars/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
2 |
3 | :root {
4 | font-family: 'Roboto', sans-serif;
5 | font-size: 16px;
6 | color-scheme: dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 | }
10 |
11 | a {
12 | color: #646cff;
13 | text-decoration: none;
14 | }
15 | a:hover {
16 | color: #535bf2;
17 | }
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | #container{
24 | max-width: 800px;
25 | margin: 0 auto;
26 | padding: 1em;
27 | }
28 |
29 | #room-header{
30 | justify-content: space-between;
31 | align-items: center;
32 | padding: 1em 0;
33 | display: none;
34 | }
35 |
36 | #room-header-controls{
37 | display: flex;
38 | }
39 |
40 | #room-header-controls > img {
41 | margin: 0 2px;
42 | }
43 |
44 | .control-icon{
45 | background-color: indianred;
46 | border: none;
47 | padding: 0.5em;
48 | height: 20px;
49 | cursor: pointer;
50 | border-radius: 5px;
51 | }
52 |
53 |
54 |
55 | #members{
56 | display: flex;
57 | flex-wrap: wrap;
58 | }
59 |
60 | .speaker{
61 | /* border:2px solid #fff;
62 | border-radius: 50%; */
63 |
64 | margin: 0.5em;
65 | display: flex;
66 | flex-direction: column;
67 | align-items: center;
68 | width: 120px;
69 | text-align: center;
70 | }
71 |
72 | #avatars{
73 | display: flex;
74 | flex-wrap: wrap;
75 | margin: 1em 0;
76 | }
77 |
78 | .avatar-selection{
79 | height: 50px;
80 | width: 50px;
81 | object-fit: contain;
82 | border: 2px solid #FFF;
83 | border-radius: 50%;
84 | opacity: .5;
85 | cursor: pointer;
86 | margin: 0.25em;
87 | }
88 |
89 | .user-avatar{
90 | height: 75px;
91 | width: 75px;
92 | object-fit: contain;
93 | border: 2px solid #FFF;
94 | border-radius: 50%;
95 | }
96 |
97 | #form input{
98 | width: 100%;
99 | padding: 1em;
100 | margin-bottom:2em;
101 | box-sizing: border-box;
102 | }
103 |
104 | #form label{
105 | margin-bottom: 0.5em;
106 | }
107 |
108 | #form-fields{
109 | display: flex;
110 | flex-direction: column;
111 | }
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/main.js:
--------------------------------------------------------------------------------
1 | import './style.css'
2 | import AgoraRTC from "agora-rtc-sdk-ng"
3 |
4 | import appid from '../appId.js'
5 |
6 | const token = null
7 | const rtcUid = Math.floor(Math.random() * 2032)
8 |
9 | let roomId = "main"
10 |
11 | let audioTracks = {
12 | localAudioTrack: null,
13 | remoteAudioTracks: {},
14 | };
15 |
16 | let rtcClient;
17 |
18 |
19 | const initRtc = async () => {
20 | rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
21 |
22 |
23 | rtcClient.on('user-joined', handleUserJoined)
24 | rtcClient.on("user-published", handleUserPublished)
25 | rtcClient.on("user-left", handleUserLeft);
26 |
27 |
28 | await rtcClient.join(appid, roomId, token, rtcUid)
29 | audioTracks.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
30 | await rtcClient.publish(audioTracks.localAudioTrack);
31 |
32 |
33 | document.getElementById('members').insertAdjacentHTML('beforeend', ``)
34 | }
35 |
36 |
37 | let handleUserJoined = async (user) => {
38 | console.log('USER:', user)
39 | document.getElementById('members').insertAdjacentHTML('beforeend', ``)
40 | }
41 |
42 | let handleUserPublished = async (user, mediaType) => {
43 | await rtcClient.subscribe(user, mediaType);
44 |
45 | if (mediaType == "audio"){
46 | audioTracks.remoteAudioTracks[user.uid] = [user.audioTrack]
47 | user.audioTrack.play();
48 | }
49 | }
50 |
51 | let handleUserLeft = async (user) => {
52 | delete audioTracks.remoteAudioTracks[user.uid]
53 | document.getElementById(user.uid).remove()
54 | }
55 |
56 |
57 | let lobbyForm = document.getElementById('form')
58 |
59 | const enterRoom = async (e) => {
60 | e.preventDefault()
61 | initRtc()
62 |
63 | lobbyForm.style.display = 'none'
64 | document.getElementById('room-header').style.display = "flex"
65 | }
66 |
67 | let leaveRoom = async () => {
68 | audioTracks.localAudioTrack.stop()
69 | audioTracks.localAudioTrack.close()
70 | rtcClient.unpublish()
71 | rtcClient.leave()
72 |
73 | document.getElementById('form').style.display = 'block'
74 | document.getElementById('room-header').style.display = 'none'
75 | document.getElementById('members').innerHTML = ''
76 | }
77 |
78 | lobbyForm.addEventListener('submit', enterRoom)
79 | document.getElementById('leave-icon').addEventListener('click', leaveRoom)
80 |
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/guide.md:
--------------------------------------------------------------------------------
1 | # Active Speaker Volume Indicator
2 |
3 | Adding active speaker indicators to view who is currently talking in a voice chat room.
4 |
5 |
6 | ### Create and Initialize Volume Indicator
7 |
8 | To add active speaker recognition we have a method called `enableAudioVolumeIndicator` which reports the volume of each user in the channel. This is reported by triggering the `AgoraRTCClient.on("volume-indicator")` call back every two seconds.
9 |
10 | Checking the volume every two seconds is too long of a delay to get an active reading so we will use the `AgoraRTC.setParameter('AUDIO_VOLUME_INDICATION_INTERVAL')` method to customize the callback to trigger every 200ms. This will give us a more accurate reading
11 |
12 | 1. Customize the volume indicator with the `setParameter` method and then enable the indicator with `enableAudioVolumeIndicator`.
13 |
14 | 2. On each callback loop through each volume and console out the volume level and user id to visually see what's happening. What we are looking for is a reading above the resting rate.
15 |
16 | I found that a reading every `200ms` gives me a resting volume at about 30 for anything over 50 means someone is speaking. You should play with these settings and see what works best for you
17 |
18 | 3. Last thing we want to do with this indicator is get the current user's id. Let's find the HTML element that represents them in the dom, and either add a green border to their wrapper or set this back to its original color.
19 |
20 | > NOTE: AgoraRTC.setParameter(...) is part of agoras internal API and not officially documented as the public docs/SDK so you will see a warning when you use it.
21 |
22 |
23 | ```js
24 | let initVolumeIndicator = async () => {
25 |
26 | //1
27 | AgoraRTC.setParameter('AUDIO_VOLUME_INDICATION_INTERVAL', 200);
28 | rtcClient.enableAudioVolumeIndicator();
29 |
30 | //2
31 | rtcClient.on("volume-indicator", volumes => {
32 | volumes.forEach((volume) => {
33 | console.log(`UID ${volume.uid} Level ${volume.level}`);
34 |
35 | //3
36 | try{
37 | let item = document.getElementsByClassName(`user-rtc-${volume.uid}`)[0]
38 |
39 | if (volume.level >= 50){
40 | item.style.borderColor = '#00ff00'
41 | }else{
42 | item.style.borderColor = "#fff"
43 | }
44 | }catch(error){
45 | console.error(error)
46 | }
47 |
48 |
49 | });
50 | })
51 | }
52 | ```
53 |
54 | This method should be initialized at the bottom of the `initRtc` function.
55 |
56 | ```js
57 | const initRtc = async () => {
58 | //...
59 | initVolumeIndicator()
60 | }
61 | ```
--------------------------------------------------------------------------------
/2-Active-Speaker-Indicator/main.js:
--------------------------------------------------------------------------------
1 | import './style.css'
2 | import AgoraRTC from "agora-rtc-sdk-ng"
3 |
4 | import appid from '../appId.js'
5 |
6 | const token = null
7 | const rtcUid = Math.floor(Math.random() * 2032)
8 |
9 | let roomId = "main"
10 |
11 | let audioTracks = {
12 | localAudioTrack: null,
13 | remoteAudioTracks: {},
14 | };
15 |
16 | let rtcClient;
17 |
18 |
19 | const initRtc = async () => {
20 | rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
21 |
22 |
23 | rtcClient.on('user-joined', handleUserJoined)
24 | rtcClient.on("user-published", handleUserPublished)
25 | rtcClient.on("user-left", handleUserLeft);
26 |
27 |
28 | await rtcClient.join(appid, roomId, token, rtcUid)
29 | audioTracks.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
30 | await rtcClient.publish(audioTracks.localAudioTrack);
31 |
32 |
33 | document.getElementById('members').insertAdjacentHTML('beforeend', ``)
34 |
35 | initVolumeIndicator()
36 | }
37 |
38 | let initVolumeIndicator = async () => {
39 |
40 | //1
41 | AgoraRTC.setParameter('AUDIO_VOLUME_INDICATION_INTERVAL', 200);
42 | rtcClient.enableAudioVolumeIndicator();
43 |
44 | //2
45 | rtcClient.on("volume-indicator", volumes => {
46 | volumes.forEach((volume) => {
47 | console.log(`UID ${volume.uid} Level ${volume.level}`);
48 |
49 | //3
50 | try{
51 | let item = document.getElementsByClassName(`user-rtc-${volume.uid}`)[0]
52 |
53 | if (volume.level >= 50){
54 | item.style.borderColor = '#00ff00'
55 | }else{
56 | item.style.borderColor = "#fff"
57 | }
58 | }catch(error){
59 | console.error(error)
60 | }
61 |
62 |
63 | });
64 | })
65 | }
66 |
67 |
68 | let handleUserJoined = async (user) => {
69 | console.log('USER:', user)
70 | document.getElementById('members').insertAdjacentHTML('beforeend', ``)
71 | }
72 |
73 | let handleUserPublished = async (user, mediaType) => {
74 | await rtcClient.subscribe(user, mediaType);
75 |
76 | if (mediaType == "audio"){
77 | audioTracks.remoteAudioTracks[user.uid] = [user.audioTrack]
78 | user.audioTrack.play();
79 | }
80 | }
81 |
82 | let handleUserLeft = async (user) => {
83 | delete audioTracks.remoteAudioTracks[user.uid]
84 | document.getElementById(user.uid).remove()
85 | }
86 |
87 |
88 | let lobbyForm = document.getElementById('form')
89 |
90 | const enterRoom = async (e) => {
91 | e.preventDefault()
92 | initRtc()
93 |
94 | lobbyForm.style.display = 'none'
95 | document.getElementById('room-header').style.display = "flex"
96 | }
97 |
98 | let leaveRoom = async () => {
99 | audioTracks.localAudioTrack.stop()
100 | audioTracks.localAudioTrack.close()
101 | rtcClient.unpublish()
102 | rtcClient.leave()
103 |
104 | document.getElementById('form').style.display = 'block'
105 | document.getElementById('room-header').style.display = 'none'
106 | document.getElementById('members').innerHTML = ''
107 | }
108 |
109 | lobbyForm.addEventListener('submit', enterRoom)
110 | document.getElementById('leave-icon').addEventListener('click', leaveRoom)
111 |
--------------------------------------------------------------------------------
/6-Creating-Rooms/guide.md:
--------------------------------------------------------------------------------
1 | # Creating Rooms
2 |
3 | Creating dynamic from names from login form.
4 |
5 | **Form room name**
6 |
7 | The first thing we'll need to do is add a room name field to the login form. We will then take this field value and update our `roomId` variable. Currently, this is set to `let roomId = 'main'` and we want to change this so we can have users join more than one room.
8 |
9 | For a little styling let's also add some `` tags and a div around our form fields with the ID of `form-fields`.
10 |
11 | ```html
12 |
23 | ```
24 |
25 | **Setting new room name on submit**
26 |
27 | Now when we submit our form, what we want to do is extract this `roomname` value from our form and set it as the `roomId`. This can be done in the `enterRoom` function just before we call the `initRtc` & `initRtm` functions.
28 |
29 | ```js
30 | const enterRoom = async (e) => {
31 | e.preventDefault()
32 |
33 | roomId = e.target.roomname.value.toLowerCase();
34 | initRtc()
35 | initRtm(e.target.displayname.value)
36 | ...
37 | }
38 | ```
39 |
40 | And now we can update the initial `roomId` value to be `undefined` until the `enterRoom` function is call.
41 |
42 | ```js
43 | let roomId;
44 | ```
45 |
46 | **Getting and setting the room name in url**
47 |
48 | A convenient way to share a room with another participant is to simply grab the url and send it over so they can join, especially if we have a long or hard-to-spell room name.
49 |
50 | In this step what we will do first is make sure that when we submit our first the room name gets added to the URL params.
51 |
52 | We'll use the `replaceSate` method and append `?room=${roomId}` as the last parameter. This will update our url to reflect this value.
53 |
54 | ```js
55 | const enterRoom = async (e) => {
56 | ...
57 |
58 | roomId = e.target.roomname.value.toLowerCase();
59 | window.history.replaceState(null, null, `?room=${roomId}`);
60 | ...
61 | }
62 | ```
63 |
64 | The next step here would be to ensure that we are retrieving what's in the URL parameter if there is a value there. We'll create a function that will check this value and set its return value to the initial `roomId` value.
65 |
66 | Let's create this function at the top of our `main.js` just above where we set our inital `roomId` variable.
67 |
68 | This method will extract what's in the URL and set it to that value or just leave it as null.
69 |
70 | ```js
71 | const getRoomId = () => {
72 | const queryString = window.location.search;
73 | const urlParams = new URLSearchParams(queryString);
74 |
75 | if (urlParams.get('room')){
76 | return urlParams.get('room').toLowerCase()
77 | }
78 | }
79 |
80 | let roomId = getRoomId() || null
81 | ```
82 |
83 | The last thing to do is to make sure that if there is a room parameter in the URL that it gets set in the form field as the initial value on load. We can add this just under the `roomId` variable when it's set.
84 |
85 | ```js
86 | ...
87 | let roomId = getRoomId() || null
88 | document.getElementById('form').roomname.value = roomId
89 | ...
90 | ```
91 |
--------------------------------------------------------------------------------
/3-Mic-Mute-Toggle/main.js:
--------------------------------------------------------------------------------
1 | import './style.css'
2 | import AgoraRTC from "agora-rtc-sdk-ng"
3 |
4 | import appid from '../appId.js'
5 |
6 |
7 | const token = null
8 | const rtcUid = Math.floor(Math.random() * 2032)
9 |
10 | let roomId = "main"
11 |
12 | let audioTracks = {
13 | localAudioTrack: null,
14 | remoteAudioTracks: {},
15 | };
16 |
17 | let micMuted = true
18 |
19 | let rtcClient;
20 |
21 |
22 | const initRtc = async () => {
23 | rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
24 |
25 |
26 | rtcClient.on('user-joined', handleUserJoined)
27 | rtcClient.on("user-published", handleUserPublished)
28 | rtcClient.on("user-left", handleUserLeft);
29 |
30 |
31 | await rtcClient.join(appid, roomId, token, rtcUid)
32 | audioTracks.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
33 | audioTracks.localAudioTrack.setMuted(micMuted)
34 | await rtcClient.publish(audioTracks.localAudioTrack);
35 |
36 |
37 | document.getElementById('members').insertAdjacentHTML('beforeend', ``)
38 |
39 | initVolumeIndicator()
40 | }
41 |
42 | let initVolumeIndicator = async () => {
43 |
44 | //1
45 | AgoraRTC.setParameter('AUDIO_VOLUME_INDICATION_INTERVAL', 200);
46 | rtcClient.enableAudioVolumeIndicator();
47 |
48 | //2
49 | rtcClient.on("volume-indicator", volumes => {
50 | volumes.forEach((volume) => {
51 | console.log(`UID ${volume.uid} Level ${volume.level}`);
52 |
53 | //3
54 | try{
55 | let item = document.getElementsByClassName(`user-rtc-${volume.uid}`)[0]
56 |
57 | if (volume.level >= 50){
58 | item.style.borderColor = '#00ff00'
59 | }else{
60 | item.style.borderColor = "#fff"
61 | }
62 | }catch(error){
63 | console.error(error)
64 | }
65 |
66 |
67 | });
68 | })
69 | }
70 |
71 |
72 | let handleUserJoined = async (user) => {
73 | console.log('USER:', user)
74 | document.getElementById('members').insertAdjacentHTML('beforeend', ``)
75 | }
76 |
77 | let handleUserPublished = async (user, mediaType) => {
78 | await rtcClient.subscribe(user, mediaType);
79 |
80 | if (mediaType == "audio"){
81 | audioTracks.remoteAudioTracks[user.uid] = [user.audioTrack]
82 | user.audioTrack.play();
83 | }
84 | }
85 |
86 | let handleUserLeft = async (user) => {
87 | delete audioTracks.remoteAudioTracks[user.uid]
88 | document.getElementById(user.uid).remove()
89 | }
90 |
91 | const toggleMic = async (e) => {
92 | if (micMuted){
93 | e.target.src = 'icons/mic.svg'
94 | e.target.style.backgroundColor = 'ivory'
95 | micMuted = false
96 | }else{
97 | e.target.src = 'icons/mic-off.svg'
98 | e.target.style.backgroundColor = 'indianred'
99 |
100 | micMuted = true
101 | }
102 | audioTracks.localAudioTrack.setMuted(micMuted)
103 | }
104 |
105 |
106 | let lobbyForm = document.getElementById('form')
107 |
108 | const enterRoom = async (e) => {
109 | e.preventDefault()
110 | initRtc()
111 |
112 | lobbyForm.style.display = 'none'
113 | document.getElementById('room-header').style.display = "flex"
114 | }
115 |
116 | let leaveRoom = async () => {
117 | audioTracks.localAudioTrack.stop()
118 | audioTracks.localAudioTrack.close()
119 | rtcClient.unpublish()
120 | rtcClient.leave()
121 |
122 | document.getElementById('form').style.display = 'block'
123 | document.getElementById('room-header').style.display = 'none'
124 | document.getElementById('members').innerHTML = ''
125 | }
126 |
127 | lobbyForm.addEventListener('submit', enterRoom)
128 | document.getElementById('leave-icon').addEventListener('click', leaveRoom)
129 | document.getElementById('mic-icon').addEventListener('click', toggleMic)
--------------------------------------------------------------------------------
/7-Displaying-Avatars/guide.md:
--------------------------------------------------------------------------------
1 | # Displaying Avatars
2 |
3 | Adding and displaying user avatars.
4 |
5 | **Add Avatars to Form**
6 |
7 | Before we do anything here we need to add our avatars to our form so users can select them. In the real world, we would have a way of uploading our own images or have some kind of authentication system that remembers our profile picture but this will do for now.
8 |
9 | Attached in the source code in section #7 is a folder called "avatars" with some images in there so if you want to use that go ahead and bring that into your project.
10 |
11 | Using these images let's go ahead and add the following inside of our HTML form, at the top just above the form-fields div.
12 |
13 | ```html
14 |
49 | ```
50 |
51 | Now inside of the `enterRoom` method before we call the `initRtm` method, lets set the value of the `displayName` variable to the form input field.
52 |
53 | ```js
54 | const enterRoom = async (e) => {
55 | ...
56 |
57 | let displayName = e.target.displayname.value;
58 | initRtm(displayName)
59 | ...
60 | }
61 | ```
62 |
63 | This name will now be passed through into the custom user attribute we set.
64 |
65 | **Retrieving Users Display Name**
66 |
67 | Now on the receiving end lets make sure that all remote users can see this name.
68 |
69 | Let's first jump to `handleMemberJoined` and get the name of the user that just joined.
70 |
71 | After you call `getUserAttributesByKeys` make sure to update the paragraph tag to display the name: `${name}
`
72 |
73 | ```js
74 | let handleMemberJoined = async (MemberId) => {
75 |
76 | let {name} = await rtmClient.getUserAttributesByKeys(MemberId, ['name'])
77 |
78 | let newMember = `
79 | `
82 |
83 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
84 |
85 | }
86 | ```
87 |
88 | Let's go ahead and do the same in the `getChannelMemebers` function.
89 |
90 | ```js
91 | let getChannelMembers = async () => {
92 | let members = await channel.getMembers()
93 | for (let i = 0; members.length > i; i++){
94 | let {name} = await rtmClient.getUserAttributesByKeys(members[i], ['name'])
95 |
96 | let newMember = `
97 | `
100 |
101 |
102 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
103 | }
104 | }
105 | ```
106 |
107 | ### Passing Through More Attributes
108 |
109 | Ok so now that we know how to pass in custom attributes we need to pass in the user's rtcUid along with their name. We need this value because of how the volume indicator highlights speaking users.
110 |
111 | To summarize, the volume indicator looks for a class that starts with `user-rtc-` and ends with the users RTC UID. To make this work we will pass in this uid, retrieve it and set it in the class name so we can re-activate our volume indicator.
112 |
113 | **Pass Users RTC UID As Attribute**
114 |
115 | Back in the `initRtm` method lets add in one more attribute to our users.
116 |
117 | Let's give the key the name of `userRtcUid` and turn the uid value into a string since we cannot pass integers here.
118 |
119 | ```js
120 | let initRtm = async (name) => {
121 | ...
122 | await rtmClient.addOrUpdateLocalUserAttributes({'name':name, 'userRtcUid':rtcUid.toString()})
123 | ...
124 | }
125 | ```
126 | Now on the receiving end lets get this value in the `getChannelMembers` & `handleMemberJoined` functions.
127 |
128 | After getting the values make sure to update the `user-rtc-` class with this new id: `user-rtc-${userRtcUid}"`
129 |
130 | ```js
131 | let getChannelMembers = async () => {
132 | ...
133 | let {name, userRtcUid} = await rtmClient.getUserAttributesByKeys(members[i], ['name', 'userRtcUid'])
134 |
135 | let newMember = `
136 | `
139 | ...
140 |
141 | }
142 | ```
143 |
144 | ```js
145 |
146 | let handleMemberJoined = async (MemberId) => {
147 |
148 | let {name, userRtcUid} = await rtmClient.getUserAttributesByKeys(MemberId, ['name', 'userRtcUid'])
149 |
150 | let newMember = `
151 | `
154 |
155 | ...
156 | }
157 | ```
158 |
159 | Now you can go ahead and uncomment the `initVolumeIndicator` instance inside of `initRtc`
160 |
161 | ```js
162 | const initRtc = async () => {
163 | ...
164 | initVolumeIndicator()
165 | }
166 | ```
167 |
--------------------------------------------------------------------------------
/6-Creating-Rooms/main.js:
--------------------------------------------------------------------------------
1 | import './style.css'
2 | import AgoraRTC from "agora-rtc-sdk-ng"
3 | import AgoraRTM from "agora-rtm-sdk"
4 |
5 | import appid from '../appId.js'
6 | const token = null
7 |
8 | const rtcUid = Math.floor(Math.random() * 2032)
9 | const rtmUid = String(Math.floor(Math.random() * 2032))
10 |
11 | const getRoomId = () => {
12 | const queryString = window.location.search;
13 | const urlParams = new URLSearchParams(queryString);
14 |
15 | if (urlParams.get('room')){
16 | return urlParams.get('room').toLowerCase()
17 | }
18 | }
19 |
20 | let roomId = getRoomId() || null
21 | document.getElementById('form').roomname.value = roomId
22 |
23 |
24 | let audioTracks = {
25 | localAudioTrack: null,
26 | remoteAudioTracks: {},
27 | };
28 |
29 | let micMuted = true
30 |
31 | let rtcClient;
32 | let rtmClient;
33 | let channel;
34 |
35 |
36 | const initRtm = async (name) => {
37 |
38 | rtmClient = AgoraRTM.createInstance(appid)
39 | await rtmClient.login({'uid':rtmUid, 'token':token})
40 |
41 | channel = rtmClient.createChannel(roomId)
42 | await channel.join()
43 |
44 | await rtmClient.addOrUpdateLocalUserAttributes({'name':name, 'userRtcUid':rtcUid.toString()})
45 |
46 | getChannelMembers()
47 |
48 | window.addEventListener('beforeunload', leaveRtmChannel)
49 |
50 | channel.on('MemberJoined', handleMemberJoined)
51 | channel.on('MemberLeft', handleMemberLeft)
52 | }
53 |
54 |
55 |
56 | const initRtc = async () => {
57 | rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
58 |
59 |
60 | //rtcClient.on('user-joined', handleUserJoined)
61 | rtcClient.on("user-published", handleUserPublished)
62 | rtcClient.on("user-left", handleUserLeft);
63 |
64 |
65 | await rtcClient.join(appid, roomId, token, rtcUid)
66 | audioTracks.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
67 | audioTracks.localAudioTrack.setMuted(micMuted)
68 | await rtcClient.publish(audioTracks.localAudioTrack);
69 |
70 |
71 | //document.getElementById('members').insertAdjacentHTML('beforeend', ``)
72 |
73 | initVolumeIndicator()
74 | }
75 |
76 | let initVolumeIndicator = async () => {
77 |
78 | //1
79 | AgoraRTC.setParameter('AUDIO_VOLUME_INDICATION_INTERVAL', 200);
80 | rtcClient.enableAudioVolumeIndicator();
81 |
82 | //2
83 | rtcClient.on("volume-indicator", volumes => {
84 | volumes.forEach((volume) => {
85 | console.log(`UID ${volume.uid} Level ${volume.level}`);
86 |
87 | //3
88 | try{
89 | let item = document.getElementsByClassName(`user-rtc-${volume.uid}`)[0]
90 |
91 | if (volume.level >= 50){
92 | item.style.borderColor = '#00ff00'
93 | }else{
94 | item.style.borderColor = "#fff"
95 | }
96 | }catch(error){
97 | console.error(error)
98 | }
99 |
100 |
101 | });
102 | })
103 | }
104 |
105 |
106 | // let handleUserJoined = async (user) => {
107 | // document.getElementById('members').insertAdjacentHTML('beforeend', ``)
108 | // }
109 |
110 | let handleUserPublished = async (user, mediaType) => {
111 | await rtcClient.subscribe(user, mediaType);
112 |
113 | if (mediaType == "audio"){
114 | audioTracks.remoteAudioTracks[user.uid] = [user.audioTrack]
115 | user.audioTrack.play();
116 | }
117 | }
118 |
119 | let handleUserLeft = async (user) => {
120 | delete audioTracks.remoteAudioTracks[user.uid]
121 | //document.getElementById(user.uid).remove()
122 | }
123 |
124 | let handleMemberJoined = async (MemberId) => {
125 |
126 | let {name, userRtcUid} = await rtmClient.getUserAttributesByKeys(MemberId, ['name', 'userRtcUid'])
127 |
128 | let newMember = `
129 | `
132 |
133 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
134 | }
135 |
136 | let handleMemberLeft = async (MemberId) => {
137 | document.getElementById(MemberId).remove()
138 | }
139 |
140 | let getChannelMembers = async () => {
141 | let members = await channel.getMembers()
142 |
143 | for (let i = 0; members.length > i; i++){
144 |
145 | let {name, userRtcUid} = await rtmClient.getUserAttributesByKeys(members[i], ['name', 'userRtcUid'])
146 |
147 | let newMember = `
148 | `
151 |
152 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
153 | }
154 | }
155 |
156 | const toggleMic = async (e) => {
157 | if (micMuted){
158 | e.target.src = 'icons/mic.svg'
159 | e.target.style.backgroundColor = 'ivory'
160 | micMuted = false
161 | }else{
162 | e.target.src = 'icons/mic-off.svg'
163 | e.target.style.backgroundColor = 'indianred'
164 |
165 | micMuted = true
166 | }
167 | audioTracks.localAudioTrack.setMuted(micMuted)
168 | }
169 |
170 |
171 | let lobbyForm = document.getElementById('form')
172 |
173 | const enterRoom = async (e) => {
174 | e.preventDefault()
175 |
176 | roomId = e.target.roomname.value.toLowerCase();
177 | window.history.replaceState(null, null, `?room=${roomId}`);
178 |
179 | initRtc()
180 |
181 | let displayName = e.target.displayname.value;
182 | initRtm(displayName)
183 |
184 | lobbyForm.style.display = 'none'
185 | document.getElementById('room-header').style.display = "flex"
186 | }
187 |
188 | let leaveRtmChannel = async () => {
189 | await channel.leave()
190 | await rtmClient.logout()
191 | }
192 |
193 | let leaveRoom = async () => {
194 | audioTracks.localAudioTrack.stop()
195 | audioTracks.localAudioTrack.close()
196 | rtcClient.unpublish()
197 | rtcClient.leave()
198 |
199 | leaveRtmChannel()
200 |
201 | document.getElementById('form').style.display = 'block'
202 | document.getElementById('room-header').style.display = 'none'
203 | document.getElementById('members').innerHTML = ''
204 | }
205 |
206 | lobbyForm.addEventListener('submit', enterRoom)
207 | document.getElementById('leave-icon').addEventListener('click', leaveRoom)
208 | document.getElementById('mic-icon').addEventListener('click', toggleMic)
--------------------------------------------------------------------------------
/demo/main.js:
--------------------------------------------------------------------------------
1 | import './style.css'
2 | import AgoraRTC from "agora-rtc-sdk-ng"
3 | import AgoraRTM from "agora-rtm-sdk"
4 |
5 | import appid from '../appId.js'
6 |
7 |
8 | const token = null
9 |
10 | const rtcUid = Math.floor(Math.random() * 2032)
11 | const rtmUid = String(Math.floor(Math.random() * 2032))
12 |
13 | const getRoomId = () => {
14 | const queryString = window.location.search;
15 | const urlParams = new URLSearchParams(queryString);
16 |
17 | if (urlParams.get('room')){
18 | return urlParams.get('room').toLowerCase()
19 | }
20 | }
21 |
22 | let roomId = getRoomId() || null
23 | document.getElementById('form').roomname.value = roomId
24 |
25 |
26 | let audioTracks = {
27 | localAudioTrack: null,
28 | remoteAudioTracks: {},
29 | };
30 |
31 | let micMuted = true
32 |
33 | let rtcClient;
34 | let rtmClient;
35 | let channel;
36 |
37 | let avatar;
38 |
39 |
40 | const initRtm = async (name) => {
41 |
42 | rtmClient = AgoraRTM.createInstance(appid)
43 | await rtmClient.login({'uid':rtmUid, 'token':token})
44 |
45 | channel = rtmClient.createChannel(roomId)
46 | await channel.join()
47 |
48 | await rtmClient.addOrUpdateLocalUserAttributes({'name':name, 'userRtcUid':rtcUid.toString(), 'userAvatar':avatar})
49 |
50 | getChannelMembers()
51 |
52 | window.addEventListener('beforeunload', leaveRtmChannel)
53 |
54 | channel.on('MemberJoined', handleMemberJoined)
55 | channel.on('MemberLeft', handleMemberLeft)
56 | }
57 |
58 |
59 |
60 | const initRtc = async () => {
61 | rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
62 |
63 |
64 | //rtcClient.on('user-joined', handleUserJoined)
65 | rtcClient.on("user-published", handleUserPublished)
66 | rtcClient.on("user-left", handleUserLeft);
67 |
68 |
69 | await rtcClient.join(appid, roomId, token, rtcUid)
70 | audioTracks.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
71 | audioTracks.localAudioTrack.setMuted(micMuted)
72 | await rtcClient.publish(audioTracks.localAudioTrack);
73 |
74 |
75 | //document.getElementById('members').insertAdjacentHTML('beforeend', ``)
76 |
77 | initVolumeIndicator()
78 | }
79 |
80 | let initVolumeIndicator = async () => {
81 |
82 | //1
83 | AgoraRTC.setParameter('AUDIO_VOLUME_INDICATION_INTERVAL', 200);
84 | rtcClient.enableAudioVolumeIndicator();
85 |
86 | //2
87 | rtcClient.on("volume-indicator", volumes => {
88 | volumes.forEach((volume) => {
89 | console.log(`UID ${volume.uid} Level ${volume.level}`);
90 |
91 | //3
92 | try{
93 | let item = document.getElementsByClassName(`avatar-${volume.uid}`)[0]
94 |
95 | if (volume.level >= 50){
96 | item.style.borderColor = '#00ff00'
97 | }else{
98 | item.style.borderColor = "#fff"
99 | }
100 | }catch(error){
101 | console.error(error)
102 | }
103 |
104 |
105 | });
106 | })
107 | }
108 |
109 |
110 | // let handleUserJoined = async (user) => {
111 | // document.getElementById('members').insertAdjacentHTML('beforeend', ``)
112 | // }
113 |
114 | let handleUserPublished = async (user, mediaType) => {
115 | await rtcClient.subscribe(user, mediaType);
116 |
117 | if (mediaType == "audio"){
118 | audioTracks.remoteAudioTracks[user.uid] = [user.audioTrack]
119 | user.audioTrack.play();
120 | }
121 | }
122 |
123 | let handleUserLeft = async (user) => {
124 | delete audioTracks.remoteAudioTracks[user.uid]
125 | //document.getElementById(user.uid).remove()
126 | }
127 |
128 | let handleMemberJoined = async (MemberId) => {
129 |
130 | let {name, userRtcUid, userAvatar} = await rtmClient.getUserAttributesByKeys(MemberId, ['name', 'userRtcUid', 'userAvatar'])
131 |
132 | let newMember = `
133 |
134 |
135 |
${name}
136 |
`
137 |
138 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
139 | }
140 |
141 | let handleMemberLeft = async (MemberId) => {
142 | document.getElementById(MemberId).remove()
143 | }
144 |
145 | let getChannelMembers = async () => {
146 | let members = await channel.getMembers()
147 |
148 | for (let i = 0; members.length > i; i++){
149 |
150 | let {name, userRtcUid, userAvatar} = await rtmClient.getUserAttributesByKeys(members[i], ['name', 'userRtcUid', 'userAvatar'])
151 |
152 | let newMember = `
153 |
154 |
155 |
${name}
156 |
`
157 |
158 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
159 | }
160 | }
161 |
162 | const toggleMic = async (e) => {
163 | if (micMuted){
164 | e.target.src = 'icons/mic.svg'
165 | e.target.style.backgroundColor = 'ivory'
166 | micMuted = false
167 | }else{
168 | e.target.src = 'icons/mic-off.svg'
169 | e.target.style.backgroundColor = 'indianred'
170 |
171 | micMuted = true
172 | }
173 | audioTracks.localAudioTrack.setMuted(micMuted)
174 | }
175 |
176 |
177 | let lobbyForm = document.getElementById('form')
178 |
179 | const enterRoom = async (e) => {
180 | e.preventDefault()
181 |
182 | if (!avatar){
183 | alert('Please select an avatar')
184 | return
185 | }
186 |
187 | roomId = e.target.roomname.value.toLowerCase();
188 | window.history.replaceState(null, null, `?room=${roomId}`);
189 |
190 | initRtc()
191 |
192 | let displayName = e.target.displayname.value;
193 | initRtm(displayName)
194 |
195 | lobbyForm.style.display = 'none'
196 | document.getElementById('room-header').style.display = "flex"
197 | document.getElementById('room-name').innerText = roomId
198 | }
199 |
200 | let leaveRtmChannel = async () => {
201 | await channel.leave()
202 | await rtmClient.logout()
203 | }
204 |
205 | let leaveRoom = async () => {
206 | audioTracks.localAudioTrack.stop()
207 | audioTracks.localAudioTrack.close()
208 | rtcClient.unpublish()
209 | rtcClient.leave()
210 |
211 | leaveRtmChannel()
212 |
213 | document.getElementById('form').style.display = 'block'
214 | document.getElementById('room-header').style.display = 'none'
215 | document.getElementById('members').innerHTML = ''
216 | }
217 |
218 | lobbyForm.addEventListener('submit', enterRoom)
219 | document.getElementById('leave-icon').addEventListener('click', leaveRoom)
220 | document.getElementById('mic-icon').addEventListener('click', toggleMic)
221 |
222 |
223 | const avatars = document.getElementsByClassName('avatar-selection')
224 |
225 | for (let i=0; avatars.length > i; i++){
226 |
227 | avatars[i].addEventListener('click', ()=> {
228 | for (let i=0; avatars.length > i; i++){
229 | avatars[i].style.borderColor = "#fff"
230 | avatars[i].style.opacity = .5
231 | }
232 |
233 | avatar = avatars[i].src
234 | avatars[i].style.borderColor = "#00ff00"
235 | avatars[i].style.opacity = 1
236 | })
237 | }
--------------------------------------------------------------------------------
/7-Displaying-Avatars/main.js:
--------------------------------------------------------------------------------
1 | import './style.css'
2 | import AgoraRTC from "agora-rtc-sdk-ng"
3 | import AgoraRTM from "agora-rtm-sdk"
4 |
5 | import appid from '../appId.js'
6 |
7 | const token = null
8 |
9 | const rtcUid = Math.floor(Math.random() * 2032)
10 | const rtmUid = String(Math.floor(Math.random() * 2032))
11 |
12 | const getRoomId = () => {
13 | const queryString = window.location.search;
14 | const urlParams = new URLSearchParams(queryString);
15 |
16 | if (urlParams.get('room')){
17 | return urlParams.get('room').toLowerCase()
18 | }
19 | }
20 |
21 | let roomId = getRoomId() || null
22 | document.getElementById('form').roomname.value = roomId
23 |
24 |
25 | let audioTracks = {
26 | localAudioTrack: null,
27 | remoteAudioTracks: {},
28 | };
29 |
30 | let micMuted = true
31 |
32 | let rtcClient;
33 | let rtmClient;
34 | let channel;
35 |
36 | let avatar;
37 |
38 |
39 | const initRtm = async (name) => {
40 |
41 | rtmClient = AgoraRTM.createInstance(appid)
42 | await rtmClient.login({'uid':rtmUid, 'token':token})
43 |
44 | channel = rtmClient.createChannel(roomId)
45 | await channel.join()
46 |
47 | await rtmClient.addOrUpdateLocalUserAttributes({'name':name, 'userRtcUid':rtcUid.toString(), 'userAvatar':avatar})
48 |
49 | getChannelMembers()
50 |
51 | window.addEventListener('beforeunload', leaveRtmChannel)
52 |
53 | channel.on('MemberJoined', handleMemberJoined)
54 | channel.on('MemberLeft', handleMemberLeft)
55 | }
56 |
57 |
58 |
59 | const initRtc = async () => {
60 | rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
61 |
62 |
63 | //rtcClient.on('user-joined', handleUserJoined)
64 | rtcClient.on("user-published", handleUserPublished)
65 | rtcClient.on("user-left", handleUserLeft);
66 |
67 |
68 | await rtcClient.join(appid, roomId, token, rtcUid)
69 | audioTracks.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
70 | audioTracks.localAudioTrack.setMuted(micMuted)
71 | await rtcClient.publish(audioTracks.localAudioTrack);
72 |
73 |
74 | //document.getElementById('members').insertAdjacentHTML('beforeend', ``)
75 |
76 | initVolumeIndicator()
77 | }
78 |
79 | let initVolumeIndicator = async () => {
80 |
81 | //1
82 | AgoraRTC.setParameter('AUDIO_VOLUME_INDICATION_INTERVAL', 200);
83 | rtcClient.enableAudioVolumeIndicator();
84 |
85 | //2
86 | rtcClient.on("volume-indicator", volumes => {
87 | volumes.forEach((volume) => {
88 | console.log(`UID ${volume.uid} Level ${volume.level}`);
89 |
90 | //3
91 | try{
92 | let item = document.getElementsByClassName(`avatar-${volume.uid}`)[0]
93 |
94 | if (volume.level >= 50){
95 | item.style.borderColor = '#00ff00'
96 | }else{
97 | item.style.borderColor = "#fff"
98 | }
99 | }catch(error){
100 | console.error(error)
101 | }
102 |
103 |
104 | });
105 | })
106 | }
107 |
108 |
109 | // let handleUserJoined = async (user) => {
110 | // document.getElementById('members').insertAdjacentHTML('beforeend', ``)
111 | // }
112 |
113 | let handleUserPublished = async (user, mediaType) => {
114 | await rtcClient.subscribe(user, mediaType);
115 |
116 | if (mediaType == "audio"){
117 | audioTracks.remoteAudioTracks[user.uid] = [user.audioTrack]
118 | user.audioTrack.play();
119 | }
120 | }
121 |
122 | let handleUserLeft = async (user) => {
123 | delete audioTracks.remoteAudioTracks[user.uid]
124 | //document.getElementById(user.uid).remove()
125 | }
126 |
127 | let handleMemberJoined = async (MemberId) => {
128 |
129 | let {name, userRtcUid, userAvatar} = await rtmClient.getUserAttributesByKeys(MemberId, ['name', 'userRtcUid', 'userAvatar'])
130 |
131 | let newMember = `
132 |
133 |
134 |
${name}
135 |
`
136 |
137 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
138 | }
139 |
140 | let handleMemberLeft = async (MemberId) => {
141 | document.getElementById(MemberId).remove()
142 | }
143 |
144 | let getChannelMembers = async () => {
145 | let members = await channel.getMembers()
146 |
147 | for (let i = 0; members.length > i; i++){
148 |
149 | let {name, userRtcUid, userAvatar} = await rtmClient.getUserAttributesByKeys(members[i], ['name', 'userRtcUid', 'userAvatar'])
150 |
151 | let newMember = `
152 |
153 |
154 |
${name}
155 |
`
156 |
157 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
158 | }
159 | }
160 |
161 | const toggleMic = async (e) => {
162 | if (micMuted){
163 | e.target.src = 'icons/mic.svg'
164 | e.target.style.backgroundColor = 'ivory'
165 | micMuted = false
166 | }else{
167 | e.target.src = 'icons/mic-off.svg'
168 | e.target.style.backgroundColor = 'indianred'
169 |
170 | micMuted = true
171 | }
172 | audioTracks.localAudioTrack.setMuted(micMuted)
173 | }
174 |
175 |
176 | let lobbyForm = document.getElementById('form')
177 |
178 | const enterRoom = async (e) => {
179 | e.preventDefault()
180 |
181 | if (!avatar){
182 | alert('Please select an avatar')
183 | return
184 | }
185 |
186 | roomId = e.target.roomname.value.toLowerCase();
187 | window.history.replaceState(null, null, `?room=${roomId}`);
188 |
189 | initRtc()
190 |
191 | let displayName = e.target.displayname.value;
192 | initRtm(displayName)
193 |
194 | lobbyForm.style.display = 'none'
195 | document.getElementById('room-header').style.display = "flex"
196 | document.getElementById('room-name').innerText = roomId
197 | }
198 |
199 | let leaveRtmChannel = async () => {
200 | await channel.leave()
201 | await rtmClient.logout()
202 | }
203 |
204 | let leaveRoom = async () => {
205 | audioTracks.localAudioTrack.stop()
206 | audioTracks.localAudioTrack.close()
207 | rtcClient.unpublish()
208 | rtcClient.leave()
209 |
210 | leaveRtmChannel()
211 |
212 | document.getElementById('form').style.display = 'block'
213 | document.getElementById('room-header').style.display = 'none'
214 | document.getElementById('members').innerHTML = ''
215 | }
216 |
217 | lobbyForm.addEventListener('submit', enterRoom)
218 | document.getElementById('leave-icon').addEventListener('click', leaveRoom)
219 | document.getElementById('mic-icon').addEventListener('click', toggleMic)
220 |
221 |
222 | const avatars = document.getElementsByClassName('avatar-selection')
223 |
224 | for (let i=0; avatars.length > i; i++){
225 |
226 | avatars[i].addEventListener('click', ()=> {
227 | for (let i=0; avatars.length > i; i++){
228 | avatars[i].style.borderColor = "#fff"
229 | avatars[i].style.opacity = .5
230 | }
231 |
232 | avatar = avatars[i].src
233 | avatars[i].style.borderColor = "#00ff00"
234 | avatars[i].style.opacity = 1
235 | })
236 | }
--------------------------------------------------------------------------------
/4-RTM-RTC-Integration/guide.md:
--------------------------------------------------------------------------------
1 | # RTM-RTC Integrations
2 |
3 | In order to add more functionality to our app like displaying user names & avatars, we need to configure our app to use the agoras RTM SDK and re-think our approach a little.
4 |
5 | Agora RTC takes care of transmiting audio and video data, and it does that will. Using the Agora RTM (Signaling) we will be able to create a room chat room enviroment with settings to pass data back and fourth as needed, this can be for displaying the room participants, number of participants in a room, sending group or peir to peir messages and more.
6 |
7 | ### Comparing RTC and RTM (Signaling) Events and Methods
8 |
9 | **Client Instance**
10 |
11 | ```js
12 | //RTC
13 | rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
14 |
15 | //RTM
16 | rtmClient = AgoraRTM.createInstance(appid)
17 | await rtmClient.login({'uid':rtmUid, 'token':token})
18 | ```
19 |
20 | **Joining Channel**
21 |
22 | ```js
23 | //RTC
24 | await rtcClient.join(appid, roomId, token, rtcUid)
25 |
26 | //RTM
27 | channel = rtmClient.createChannel(roomId)
28 | await channel.join()
29 | ```
30 |
31 | **Join Channel Event Listener**
32 |
33 | ```js
34 | //RTC
35 | rtcClient.on('user-joined', handleUserJoined)
36 |
37 | //RTM
38 | channel.on('MemberJoined', handleMemberJoined)
39 | ```
40 |
41 | **Leaving Channel**
42 |
43 | ```js
44 | //RTC
45 | audioTracks.localAudioTrack.stop()
46 | audioTracks.localAudioTrack.close()
47 |
48 | rtcClient.unpublish()
49 | rtcClient.leave()
50 |
51 | //RTM
52 | channel.leave()
53 | rtmClient.logout()
54 | ```
55 |
56 | **Leave Channel Event Listener**
57 |
58 | ```js
59 | //RTC
60 | rtcClient.on("user-left", handleUserLeft);
61 |
62 | //RTM
63 | channel.on('MemberLeft', handleMemberLeft)
64 | ```
65 |
66 | ### Rethinking Our Approach.
67 |
68 | Let's start by looking at some events and code that should be taken care of by agora rtm instead of rtc
69 |
70 | **Remove handleUserJoined**
71 |
72 | The only thing this event does is find the member that just joined and add them to the dom. The only problem here is that we only have the user uid here so we cant display anything more. Agoro RTM has a better version of this callback that gives us more data so let's just comment this one out for now:
73 |
74 | ```js
75 | /*
76 | let handleUserJoined = async (user) => {
77 | document.getElementById('members').insertAdjacentHTML('beforeend', ``)
78 | }
79 | */
80 | ```
81 |
82 | We can also comment out the callback listener for this method inside of `initRtc`
83 |
84 | ```js
85 | const initRtc = async () => {
86 | ...
87 | //rtcClient.on('user-joined', handleUserJoined)
88 | ...
89 | }
90 | ```
91 |
92 | **Modify handleUserLeft**
93 |
94 | When a user leaves we still want to delete their audio tracks so RTC will take care of that, but we want the RTM SDK to handle the actual DOM update so let's comment out the `remove` method
95 |
96 | ```js
97 | let handleUserLeft = async (user) => {
98 | delete audioTracks.remoteAudioTracks[user.uid]
99 | //document.getElementById(user.uid).remove()
100 | }
101 | ```
102 |
103 | **Temporarily comment out the initiateVolumeIndicator method**
104 |
105 | Because agora RTC will no longer take care of any DOM updates we should comment out the `initiateVolumeIndicator` call inside of `initRtc` to avoid any errors that come from our incomplete code. Don't worry, we'll turn this on again soon.
106 |
107 | ```js
108 | const initRtc = async () => {
109 | ...
110 | //initVolumeIndicator()
111 | ...
112 | }
113 | ```
114 |
115 |
116 | **Remove Current User**
117 |
118 | When we first join a room we have a method that gets our current local user and adds us to the dom. This will be done in a new way with Agora RTM so let's comment this out as well.
119 |
120 | ```js
121 | const initRtc = async () => {
122 | ...
123 | //document.getElementById('members').insertAdjacentHTML('beforeend', ``)
124 | ...
125 | }
126 | ```
127 |
128 | > NOTE: At this point, everything should work as before beside any DOM updates you should still be able to join a room, toggle your mic and hear other users, but you won't see them.
129 |
130 |
131 |
132 | ### Integrating Agora RTM
133 |
134 | Install Agora RTC SDK
135 |
136 | ```
137 | npm install agora-rtm-sdk
138 | ```
139 |
140 | Import AgoraRTM at the top of `main.js`
141 |
142 | ```js
143 | import AgoraRTM from "agora-rtm-sdk"
144 | ```
145 |
146 | Add RTM UID
147 |
148 | An RTM UID must be a string, so let's use the same method that we used to create an RTC UID and create a rtmUid with the `string()` method.
149 |
150 | ```js
151 | const rtmUid = String(Math.floor(Math.random() * 2032))
152 | ```
153 |
154 | RTM Client and Channel placeholder
155 |
156 | With Agora RTM we need to first create a channel (AKA room), then join that channel with our rtmClient. For the room name we will use the same as we did with RTC, `roomId`.
157 |
158 | ```js
159 | let rtmClient;
160 | let channel;
161 | ```
162 |
163 |
164 | **Initiating An Rtm Client and Channel**
165 |
166 |
167 | 1. The `initRtm` function will take in a user display name and will trigger our initial client configuration and channel login
168 |
169 | 2. Create rtm client instance (Using rtmUid and the same token as with rtc)
170 |
171 | 3. create and join channel
172 |
173 | ```js
174 | //1
175 | const initRtm = async (name) => {
176 | //2
177 | rtmClient = AgoraRTM.createInstance(appid)
178 | await rtmClient.login({'uid':rtmUid, 'token':token})
179 |
180 | //3
181 | channel = rtmClient.createChannel(roomId)
182 | await channel.join()
183 | }
184 | ```
185 |
186 |
187 | Call this method inside of the `enterRoom` method just underneath the initRtc method. We'll create a variable called `displayName` with no value for now and pass it into the initRtm method as a parameter.
188 |
189 |
190 | ```js
191 | const enterRoom = async (e) => {
192 | ...
193 | //initRtc()
194 | let displayName;
195 | initRtm(displayName)
196 | ...
197 | }
198 | ```
199 |
200 | **Leaving Channel**
201 |
202 | Unlike the RTC SDK we don't automatically leave a channel when we close our browser. So before we do anything else and run into errors lets create a method that leaves our RTM channel when we click the exit icon and when the dom unloads.
203 |
204 | First let's create a method called `leaveRtmChannel`
205 |
206 | Here we will first leave the channel, then log out our client.
207 |
208 | ```js
209 | let leaveRtmChannel = async () => {
210 | await channel.leave()
211 | await rtmClient.logout()
212 | }
213 | ```
214 |
215 |
216 | Next, we want to call this method in the leave room function since we already have this configured with the on-click event for the exit icon. This should be placed just underneath the `rtcClient` leave method so we can first close out the rtc channel.
217 |
218 | ```js
219 | let leaveRoom = async () => {
220 | ...
221 | leaveRtmChannel()
222 | ...
223 | }
224 | ```
225 |
226 | last, we need to consider what happens when a user closes their browser without clicking the "leave" icon. For this, all we need to do is add a `beforeunload` event listener to the window. We can add this at the bottom of the `initRtm` method.
227 |
228 |
229 | ```js
230 | let initRtm = async (name) => {
231 | window.addEventListener('beforeunload', leaveRtmChannel)
232 | }
233 | ```
234 |
235 | **Handle Member Joined**
236 |
237 | Agora RTM has a similar call-back method to the RTC `user-joined` event. Just like when a rtcClient joins, the `user-joined` callback is triggered, so does the `MemberJoined` callback get triggered when a rtmClient joins a channel. This is the RTM version of that same callback, only now we have more control over our user which you will see soon.
238 |
239 |
240 | Create our `handleMemberJoined` method.
241 |
242 | When this event is called we get access to the rtmUid of the remote user that just joined. We will use this uid to identify the user in the HTML when we add it to the dom.
243 |
244 | > Note: Leave the "user-rtc-" class with "---" as the last part of the class for now.
245 |
246 | At this point, we only have access to the new member's rtmUid and not the rtcUid. So until we retrieve the value, just add dashes to the `user-rtc-` class.
247 |
248 | ```js
249 | let handleMemberJoined = async (MemberId) => {
250 |
251 | let newMember = `
252 | `
255 |
256 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
257 |
258 | }
259 | ```
260 |
261 | To trigger this callback add the following at the bottom of the `initRtm` method
262 |
263 | ```js
264 | let initRtm = async (name) => {
265 | ...
266 | channel.on('MemberJoined', handleMemberJoined)
267 | ...
268 | }
269 | ```
270 |
271 | **Handle Member Left**
272 |
273 | Just like the handle member left function, we have an alternative to the 'user-left' callback from the RTC SDK. The Agora RTM version of this4 is called `MemberLeft`.
274 |
275 | So let's first create our function.
276 |
277 | All this function will do is find the users in the dom and remove them:
278 |
279 | ```js
280 | let handleMemberLeft = async (MemberId) => {
281 | document.getElementById(MemberId).remove()
282 | }
283 | ```
284 |
285 | And not call it inside of `initRtm`:
286 |
287 | ```js
288 | channel.on('MemberLeft', handleMemberLeft)
289 | ```
290 |
291 | > NOTE: If you give this a test you'll notice that you only see the remote user and not the local user. You will also only see users that join after you joined. We will fix this next
292 |
293 | **Retrieving All Members In A Channel**
294 |
295 | When we first join a channel we want to display all users currently in that channel, including ourselves, then let the `MemberJoined` and `MemeberLeft` methods take care of updating the dom while we are in the room.
296 |
297 | With Agora RTM we can call the `getMembers()` on the channels calls and this will return every active user in this channel.
298 |
299 | After we call this method we will loop through every member and add them to the DOM.
300 |
301 | Let's start by creating a function called `getChannelMemebers`.
302 |
303 | 1. Get all memebers with `channel.getMemebers()`
304 |
305 | 2. Loop through the response and create HTML element for each user and add them to the dom
306 |
307 | ```js
308 | let getChannelMembers = async () => {
309 | //1
310 | let members = await channel.getMembers()
311 |
312 | //2
313 | for (let i = 0; members.length > i; i++){
314 |
315 | let newMember = `
316 |
317 |
${members[i]}
318 |
`
319 |
320 | document.getElementById("members").insertAdjacentHTML('beforeend', newMember)
321 | }
322 | }
323 | ```
324 |
325 | Now the last thing we need to do is call this function inside of `initRtm` right after we join the channel.
326 |
327 | ```js
328 | let initRtm = async () => {
329 | ...
330 | getChannelMembers()
331 | ...
332 | }
333 | ```
334 |
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/guide.md:
--------------------------------------------------------------------------------
1 | # Basic Voice Chat
2 |
3 | Adding basic voice calling features to our website. Building from scratch using Vite.
4 |
5 |
6 | ### Basic App Setup
7 |
8 | Create a new vite app.
9 |
10 | `npm create vite@latest`
11 |
12 | Basic Installs
13 |
14 | ```
15 | cd your-app-name
16 | npm install
17 | npm run dev
18 | ```
19 |
20 | Remove unnecessary files
21 |
22 | - ~~javascript.jpg~~
23 | - ~~counter.js~~
24 |
25 | Clean out file contents
26 |
27 | - **style.css** - Delete Everything inside
28 | - **main.js** - Leave only the css import: `import './style.css'`
29 | - **index.html** - Remove "app" div: `
`
30 |
31 |
32 | Add in basic HTML:
33 |
34 | ```html
35 |
36 |
37 |
46 |
47 |
51 |
52 |
53 |
54 |
55 |
56 | ```
57 |
58 | Add Icons:
59 |
60 | Inside `room-header-controls` in our HTML file we reference two icons:
61 |
62 | - `mic-off.svg`
63 | - `leave.svg`
64 |
65 | You can find these files within the source code provided within the "icons" folder.
66 |
67 |
68 | Add pre-built css
69 |
70 | ```css
71 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
72 |
73 | :root {
74 | font-family: 'Roboto', sans-serif;
75 | font-size: 16px;
76 | color-scheme: dark;
77 | color: rgba(255, 255, 255, 0.87);
78 | background-color: #242424;
79 | }
80 |
81 | a {
82 | color: #646cff;
83 | text-decoration: none;
84 | }
85 | a:hover {
86 | color: #535bf2;
87 | }
88 |
89 | body {
90 | margin: 0;
91 | }
92 |
93 | #container{
94 | max-width: 800px;
95 | margin: 0 auto;
96 | padding: 1em;
97 | }
98 |
99 | #room-header{
100 | justify-content: space-between;
101 | align-items: center;
102 | padding: 1em 0;
103 | /* display: none; */
104 | }
105 |
106 | #room-header-controls{
107 | display: flex;
108 | }
109 |
110 | #room-header-controls > img {
111 | margin: 0 2px;
112 | }
113 |
114 | .control-icon{
115 | background-color: indianred;
116 | border: none;
117 | padding: 0.5em;
118 | height: 20px;
119 | cursor: pointer;
120 | border-radius: 5px;
121 | }
122 |
123 |
124 | #members{
125 | display: flex;
126 | flex-wrap: wrap;
127 | }
128 |
129 | .speaker{
130 | border:2px solid #fff;
131 | display: flex;
132 | flex-direction: column;
133 | align-items: center;
134 | width: 120px;
135 | text-align: center;
136 | margin: 0.5em;
137 | }
138 |
139 | #avatars{
140 | display: flex;
141 | flex-wrap: wrap;
142 | margin: 1em 0;
143 | }
144 |
145 | .avatar-selection{
146 | height: 50px;
147 | width: 50px;
148 | object-fit: contain;
149 | border: 2px solid #FFF;
150 | border-radius: 50%;
151 | opacity: .5;
152 | cursor: pointer;
153 | margin: 0.25em;
154 | }
155 |
156 | .user-avatar{
157 | height: 75px;
158 | width: 75px;
159 | object-fit: contain;
160 | border: 2px solid #FFF;
161 | border-radius: 50%;
162 | }
163 |
164 | #form input{
165 | width: 100%;
166 | padding: 1em;
167 | margin-bottom:2em;
168 | box-sizing: border-box;
169 | }
170 |
171 | #form label{
172 | margin-bottom: 0.5em;
173 | }
174 |
175 | #form-fields{
176 | display: flex;
177 | flex-direction: column;
178 | }
179 | ```
180 |
181 | ### Getting Started With Agora
182 |
183 | Prerequisite's
184 | - Have an account with agora.io
185 | - Have an app ready: Ensure auth is set to APP ID only.
186 |
187 | Install Agora RTC:
188 |
189 | ```
190 | npm install agora-rtc-sdk-ng
191 | ```
192 |
193 | **Configuring Agora RTC In Our App**
194 |
195 | Inside `main.js` we will start by setting initial values for our app connection and RTC client.
196 |
197 | 1. Import the `AgoraRTC` class. This is the global entry point for all the methods provided by the Agora Web SDK. You can read more [here](https://api-ref.agora.io/en/voice-sdk/web/4.x/index.html#core-methods)
198 |
199 |
200 |
201 | 2. Basic Credentials and user settings
202 | - `appid` - Your project app id from the agora console
203 | - `token` - Authentication token - Leave as `null` if you set the authentication mechanism to "APP ID only" when you created your app.
204 | - `rtcUid` - A user ID (UID) identifies a user in a channel. Each user in a channel should have a unique user ID. An RTC UID should be an integer (anything up to a 32 bit integer). In therory, this could be turned into a string but it is not recommended as it won't with certain agora products such as cloud recording.
205 | - `roomId` - Agora requires a "channel name" for creating separate rooms users can join. We will use the name `roomId`, and just hard code this value as `main`. We will make this dynamic later so users can join different rooms.
206 |
207 |
208 |
209 | 3. `audioTracks` will store our local tracks when we join a channel and all remote users who join later. Remote users will be stored as a key-value pair and will be identified by their unique id.
210 |
211 |
212 |
213 | 4. `rtcClient` - Our client object which will be set once we initiate and join a channel. This will be our entry point to all the methods we need to join & leave rooms, toggle our mic, listen for key events, etc.
214 |
215 | ```js
216 | //1
217 | import AgoraRTC from "agora-rtc-sdk-ng"
218 |
219 | //2
220 | const appid = "YOUR-APP-ID"
221 | const token = null
222 | const rtcUid = Math.floor(Math.random() * 2032)
223 |
224 | let roomId = "main"
225 |
226 | //3
227 | let audioTracks = {
228 | localAudioTrack: null,
229 | remoteAudioTracks: {},
230 | };
231 |
232 | //5
233 | let rtcClient;
234 | ```
235 |
236 |
237 | **Initiate RTC Client & Join Channel**
238 |
239 |
240 | The `initRtc` method will be responsible for the core configuration we need for joining a channel
241 |
242 |
243 | 1. Initiate client and join channel
244 | - `createClient` - initiates our RTC client and requires that we set the mode (live OR rtc) and encoding config
245 | - `join` - Creates or joins a room with our credentials (App ID, Room Name, token and user UID)
246 |
247 |
248 |
249 | 2. Getting Mic Audio Tracks
250 | - `createMicrophoneAudioTrack` gets our local audio tracks which we will assign to the `localAudioTrack` key in our `audioTracks` object.
251 |
252 |
253 |
254 | 3. Adding User To Dom - Create an HTML element and add it to dom with UID value. For now, this is the only thing we have to identify the user. We will add user names and avatars later.
255 |
256 | `main.js`
257 |
258 | ```js
259 |
260 | const initRtc = async () => {
261 | //1
262 | rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
263 |
264 | await rtcClient.join(appid, roomId, token, rtcUid)
265 |
266 | //2
267 | audioTracks.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
268 | await rtcClient.publish(audioTracks.localAudioTrack);
269 |
270 | //3
271 | document.getElementById('members').insertAdjacentHTML('beforeend', ``)
272 | }
273 | ```
274 |
275 | **Enter Room**
276 |
277 | We want to join a room by initiating the `initRtc` method when our form is submitted. When this event fires we want to hide the form (button) and display our header so the "leave" button is visible
278 |
279 | `main.js`
280 |
281 |
282 | ```js
283 | let lobbyForm = document.getElementById('form')
284 |
285 | const enterRoom = async (e) => {
286 | e.preventDefault()
287 | initRtc()
288 |
289 | lobbyForm.style.display = 'none'
290 | document.getElementById('room-header').style.display = "flex"
291 | }
292 |
293 | lobbyForm.addEventListener('submit', enterRoom)
294 | ```
295 |
296 | **Leave Room**
297 |
298 | When a user clicks the leave icon/button we want to stop all local audio tracks and leave the stream.
299 |
300 | 1. Stop and Close Tracks
301 | - `stop` - Stops playing the media track.
302 | - `close` - Closes a local track and releases the audio and video resources that it occupies. Once you close a local track, you can no longer reuse it.
303 |
304 | 2. Unpublish Tracks and Leave Channel
305 | - `unpublish` Unpublishes the local audio and/or video tracks. Calling this method will trigger the AgoraRTCClient.on("user-unpublished") event for all remote users in the channel
306 | - `leave` - Leaves a channel. Calling this method will trigger the AgoraRTCClient.on("user-left") event for all remote users in the channel
307 |
308 | 3. Remove members from DOM, display the form again and hide the header so the "leave" button is not visible.
309 |
310 | ```js
311 |
312 | let leaveRoom = async () => {
313 | //1
314 | audioTracks.localAudioTrack.stop()
315 | audioTracks.localAudioTrack.close()
316 |
317 | //2
318 | rtcClient.unpublish()
319 | rtcClient.leave()
320 |
321 | //3
322 | document.getElementById('form').style.display = 'block'
323 | document.getElementById('room-header').style.display = 'none'
324 | document.getElementById('members').innerHTML = ''
325 | }
326 |
327 | document.getElementById('leave-icon').addEventListener('click', leaveRoom)
328 | ```
329 |
330 |
331 | **Remote User Event Listeners**
332 |
333 | Joining a channel and publishing a stream doesn't mean that other users in the same channel will begin to hear or see your stream. Before we can get to that point we need to create event listeners that listen for key channel events like when a user joins the channel, publishes, and unpublishes their stream leaves the stream.
334 |
335 | When these events occur we will respond by subscribing/unsubscribing to their audio tracks and playing them locally.
336 |
337 | They can events we will listen for are:
338 | - `user-joined` --> triggered when a remote user calls the `join()` event.
339 | - `user-published` --> triggered when a remote user calls the `publish()` method.
340 | - `user-left` --> triggered when a remote user calls the `leave()` method.
341 |
342 | When these callbacks are called we want to trigger functions on the receiving end that respond. We use the `on` method which the client object gives us.
343 |
344 | Inside the `initRtc` function let's add these event listeners, then create the corresponding functions:
345 |
346 | ```js
347 | const initRtc = async () => {
348 | //...
349 | rtcClient.on('user-joined', handleUserJoined)
350 | rtcClient.on("user-published", handleUserPublished)
351 | rtcClient.on("user-left", handleUserLeft);
352 | //...
353 | }
354 | ```
355 |
356 | Next, we need to create the 3 functions that fire off when these callbacks are triggered:
357 |
358 | 1. `handleUserJoined` - will fire off when a new user joins. We will have access to the remote user object as a parameter. To better understand what the user object is comprised of try adding `console.log("USER:", user)` to see the object.
359 |
360 | 2. Create a new HTML element and add it to the dom so we can visually represent the joined user. To uniquely identify this user we will set the ID by accessing the `user.uid` attribute and also passing this into the paragraph tags.
361 |
362 | 3. `handleUserPublished` fires when a remote user publishes their audio and or video tracks. This method will give us access to the user object and the `mediaType` which will either be "audio" or "video".
363 |
364 | 4. When a new user publishes their track the first thing we want to do is call the `subscribe` method so we can see and or hear them.
365 |
366 | 5. Once we are subscribed to their audio/video tracks we want to add these tracks to our remote users' key in `audioTracks` and then `play()` the track.
367 |
368 | 6. When a user calls the `leave()` method we want to remove their audio tracks from our local state and remove them from the dom. We find the user in the dom by the specific user id we passed in to the HTML element
369 |
370 | ```js
371 | //1
372 | let handleUserJoined = async (user) => {
373 | //2
374 | document.getElementById('members').insertAdjacentHTML('beforeend', ``)
375 | }
376 |
377 | //3
378 | let handleUserPublished = async (user, mediaType) => {
379 | //4
380 | await rtcClient.subscribe(user, mediaType);
381 |
382 | //5
383 | if (mediaType == "audio"){
384 | audioTracks.remoteAudioTracks[user.uid] = [user.audioTrack]
385 | user.audioTrack.play();
386 | }
387 | }
388 |
389 | //6
390 | let handleUserLeft = async (user) => {
391 | delete audioTracks.remoteAudioTracks[user.uid]
392 | document.getElementById(user.uid).remove()
393 | }
394 |
395 | ```
396 |
--------------------------------------------------------------------------------
/demo/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "version": "0.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "1-basic-voice-chat",
9 | "version": "0.0.0",
10 | "devDependencies": {
11 | "vite": "^4.0.0"
12 | }
13 | },
14 | "node_modules/@esbuild/android-arm": {
15 | "version": "0.16.17",
16 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
17 | "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
18 | "cpu": [
19 | "arm"
20 | ],
21 | "dev": true,
22 | "optional": true,
23 | "os": [
24 | "android"
25 | ],
26 | "engines": {
27 | "node": ">=12"
28 | }
29 | },
30 | "node_modules/@esbuild/android-arm64": {
31 | "version": "0.16.17",
32 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
33 | "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
34 | "cpu": [
35 | "arm64"
36 | ],
37 | "dev": true,
38 | "optional": true,
39 | "os": [
40 | "android"
41 | ],
42 | "engines": {
43 | "node": ">=12"
44 | }
45 | },
46 | "node_modules/@esbuild/android-x64": {
47 | "version": "0.16.17",
48 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
49 | "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
50 | "cpu": [
51 | "x64"
52 | ],
53 | "dev": true,
54 | "optional": true,
55 | "os": [
56 | "android"
57 | ],
58 | "engines": {
59 | "node": ">=12"
60 | }
61 | },
62 | "node_modules/@esbuild/darwin-arm64": {
63 | "version": "0.16.17",
64 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
65 | "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
66 | "cpu": [
67 | "arm64"
68 | ],
69 | "dev": true,
70 | "optional": true,
71 | "os": [
72 | "darwin"
73 | ],
74 | "engines": {
75 | "node": ">=12"
76 | }
77 | },
78 | "node_modules/@esbuild/darwin-x64": {
79 | "version": "0.16.17",
80 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
81 | "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
82 | "cpu": [
83 | "x64"
84 | ],
85 | "dev": true,
86 | "optional": true,
87 | "os": [
88 | "darwin"
89 | ],
90 | "engines": {
91 | "node": ">=12"
92 | }
93 | },
94 | "node_modules/@esbuild/freebsd-arm64": {
95 | "version": "0.16.17",
96 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
97 | "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
98 | "cpu": [
99 | "arm64"
100 | ],
101 | "dev": true,
102 | "optional": true,
103 | "os": [
104 | "freebsd"
105 | ],
106 | "engines": {
107 | "node": ">=12"
108 | }
109 | },
110 | "node_modules/@esbuild/freebsd-x64": {
111 | "version": "0.16.17",
112 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
113 | "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
114 | "cpu": [
115 | "x64"
116 | ],
117 | "dev": true,
118 | "optional": true,
119 | "os": [
120 | "freebsd"
121 | ],
122 | "engines": {
123 | "node": ">=12"
124 | }
125 | },
126 | "node_modules/@esbuild/linux-arm": {
127 | "version": "0.16.17",
128 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
129 | "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
130 | "cpu": [
131 | "arm"
132 | ],
133 | "dev": true,
134 | "optional": true,
135 | "os": [
136 | "linux"
137 | ],
138 | "engines": {
139 | "node": ">=12"
140 | }
141 | },
142 | "node_modules/@esbuild/linux-arm64": {
143 | "version": "0.16.17",
144 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
145 | "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
146 | "cpu": [
147 | "arm64"
148 | ],
149 | "dev": true,
150 | "optional": true,
151 | "os": [
152 | "linux"
153 | ],
154 | "engines": {
155 | "node": ">=12"
156 | }
157 | },
158 | "node_modules/@esbuild/linux-ia32": {
159 | "version": "0.16.17",
160 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
161 | "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
162 | "cpu": [
163 | "ia32"
164 | ],
165 | "dev": true,
166 | "optional": true,
167 | "os": [
168 | "linux"
169 | ],
170 | "engines": {
171 | "node": ">=12"
172 | }
173 | },
174 | "node_modules/@esbuild/linux-loong64": {
175 | "version": "0.16.17",
176 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
177 | "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
178 | "cpu": [
179 | "loong64"
180 | ],
181 | "dev": true,
182 | "optional": true,
183 | "os": [
184 | "linux"
185 | ],
186 | "engines": {
187 | "node": ">=12"
188 | }
189 | },
190 | "node_modules/@esbuild/linux-mips64el": {
191 | "version": "0.16.17",
192 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
193 | "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
194 | "cpu": [
195 | "mips64el"
196 | ],
197 | "dev": true,
198 | "optional": true,
199 | "os": [
200 | "linux"
201 | ],
202 | "engines": {
203 | "node": ">=12"
204 | }
205 | },
206 | "node_modules/@esbuild/linux-ppc64": {
207 | "version": "0.16.17",
208 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
209 | "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
210 | "cpu": [
211 | "ppc64"
212 | ],
213 | "dev": true,
214 | "optional": true,
215 | "os": [
216 | "linux"
217 | ],
218 | "engines": {
219 | "node": ">=12"
220 | }
221 | },
222 | "node_modules/@esbuild/linux-riscv64": {
223 | "version": "0.16.17",
224 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
225 | "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
226 | "cpu": [
227 | "riscv64"
228 | ],
229 | "dev": true,
230 | "optional": true,
231 | "os": [
232 | "linux"
233 | ],
234 | "engines": {
235 | "node": ">=12"
236 | }
237 | },
238 | "node_modules/@esbuild/linux-s390x": {
239 | "version": "0.16.17",
240 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
241 | "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
242 | "cpu": [
243 | "s390x"
244 | ],
245 | "dev": true,
246 | "optional": true,
247 | "os": [
248 | "linux"
249 | ],
250 | "engines": {
251 | "node": ">=12"
252 | }
253 | },
254 | "node_modules/@esbuild/linux-x64": {
255 | "version": "0.16.17",
256 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
257 | "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
258 | "cpu": [
259 | "x64"
260 | ],
261 | "dev": true,
262 | "optional": true,
263 | "os": [
264 | "linux"
265 | ],
266 | "engines": {
267 | "node": ">=12"
268 | }
269 | },
270 | "node_modules/@esbuild/netbsd-x64": {
271 | "version": "0.16.17",
272 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
273 | "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
274 | "cpu": [
275 | "x64"
276 | ],
277 | "dev": true,
278 | "optional": true,
279 | "os": [
280 | "netbsd"
281 | ],
282 | "engines": {
283 | "node": ">=12"
284 | }
285 | },
286 | "node_modules/@esbuild/openbsd-x64": {
287 | "version": "0.16.17",
288 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
289 | "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
290 | "cpu": [
291 | "x64"
292 | ],
293 | "dev": true,
294 | "optional": true,
295 | "os": [
296 | "openbsd"
297 | ],
298 | "engines": {
299 | "node": ">=12"
300 | }
301 | },
302 | "node_modules/@esbuild/sunos-x64": {
303 | "version": "0.16.17",
304 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
305 | "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
306 | "cpu": [
307 | "x64"
308 | ],
309 | "dev": true,
310 | "optional": true,
311 | "os": [
312 | "sunos"
313 | ],
314 | "engines": {
315 | "node": ">=12"
316 | }
317 | },
318 | "node_modules/@esbuild/win32-arm64": {
319 | "version": "0.16.17",
320 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
321 | "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
322 | "cpu": [
323 | "arm64"
324 | ],
325 | "dev": true,
326 | "optional": true,
327 | "os": [
328 | "win32"
329 | ],
330 | "engines": {
331 | "node": ">=12"
332 | }
333 | },
334 | "node_modules/@esbuild/win32-ia32": {
335 | "version": "0.16.17",
336 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
337 | "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
338 | "cpu": [
339 | "ia32"
340 | ],
341 | "dev": true,
342 | "optional": true,
343 | "os": [
344 | "win32"
345 | ],
346 | "engines": {
347 | "node": ">=12"
348 | }
349 | },
350 | "node_modules/@esbuild/win32-x64": {
351 | "version": "0.16.17",
352 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
353 | "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
354 | "cpu": [
355 | "x64"
356 | ],
357 | "dev": true,
358 | "optional": true,
359 | "os": [
360 | "win32"
361 | ],
362 | "engines": {
363 | "node": ">=12"
364 | }
365 | },
366 | "node_modules/esbuild": {
367 | "version": "0.16.17",
368 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz",
369 | "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==",
370 | "dev": true,
371 | "hasInstallScript": true,
372 | "bin": {
373 | "esbuild": "bin/esbuild"
374 | },
375 | "engines": {
376 | "node": ">=12"
377 | },
378 | "optionalDependencies": {
379 | "@esbuild/android-arm": "0.16.17",
380 | "@esbuild/android-arm64": "0.16.17",
381 | "@esbuild/android-x64": "0.16.17",
382 | "@esbuild/darwin-arm64": "0.16.17",
383 | "@esbuild/darwin-x64": "0.16.17",
384 | "@esbuild/freebsd-arm64": "0.16.17",
385 | "@esbuild/freebsd-x64": "0.16.17",
386 | "@esbuild/linux-arm": "0.16.17",
387 | "@esbuild/linux-arm64": "0.16.17",
388 | "@esbuild/linux-ia32": "0.16.17",
389 | "@esbuild/linux-loong64": "0.16.17",
390 | "@esbuild/linux-mips64el": "0.16.17",
391 | "@esbuild/linux-ppc64": "0.16.17",
392 | "@esbuild/linux-riscv64": "0.16.17",
393 | "@esbuild/linux-s390x": "0.16.17",
394 | "@esbuild/linux-x64": "0.16.17",
395 | "@esbuild/netbsd-x64": "0.16.17",
396 | "@esbuild/openbsd-x64": "0.16.17",
397 | "@esbuild/sunos-x64": "0.16.17",
398 | "@esbuild/win32-arm64": "0.16.17",
399 | "@esbuild/win32-ia32": "0.16.17",
400 | "@esbuild/win32-x64": "0.16.17"
401 | }
402 | },
403 | "node_modules/fsevents": {
404 | "version": "2.3.2",
405 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
406 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
407 | "dev": true,
408 | "hasInstallScript": true,
409 | "optional": true,
410 | "os": [
411 | "darwin"
412 | ],
413 | "engines": {
414 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
415 | }
416 | },
417 | "node_modules/function-bind": {
418 | "version": "1.1.1",
419 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
420 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
421 | "dev": true
422 | },
423 | "node_modules/has": {
424 | "version": "1.0.3",
425 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
426 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
427 | "dev": true,
428 | "dependencies": {
429 | "function-bind": "^1.1.1"
430 | },
431 | "engines": {
432 | "node": ">= 0.4.0"
433 | }
434 | },
435 | "node_modules/is-core-module": {
436 | "version": "2.11.0",
437 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
438 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
439 | "dev": true,
440 | "dependencies": {
441 | "has": "^1.0.3"
442 | },
443 | "funding": {
444 | "url": "https://github.com/sponsors/ljharb"
445 | }
446 | },
447 | "node_modules/nanoid": {
448 | "version": "3.3.4",
449 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
450 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
451 | "dev": true,
452 | "bin": {
453 | "nanoid": "bin/nanoid.cjs"
454 | },
455 | "engines": {
456 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
457 | }
458 | },
459 | "node_modules/path-parse": {
460 | "version": "1.0.7",
461 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
462 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
463 | "dev": true
464 | },
465 | "node_modules/picocolors": {
466 | "version": "1.0.0",
467 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
468 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
469 | "dev": true
470 | },
471 | "node_modules/postcss": {
472 | "version": "8.4.21",
473 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
474 | "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
475 | "dev": true,
476 | "funding": [
477 | {
478 | "type": "opencollective",
479 | "url": "https://opencollective.com/postcss/"
480 | },
481 | {
482 | "type": "tidelift",
483 | "url": "https://tidelift.com/funding/github/npm/postcss"
484 | }
485 | ],
486 | "dependencies": {
487 | "nanoid": "^3.3.4",
488 | "picocolors": "^1.0.0",
489 | "source-map-js": "^1.0.2"
490 | },
491 | "engines": {
492 | "node": "^10 || ^12 || >=14"
493 | }
494 | },
495 | "node_modules/resolve": {
496 | "version": "1.22.1",
497 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
498 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
499 | "dev": true,
500 | "dependencies": {
501 | "is-core-module": "^2.9.0",
502 | "path-parse": "^1.0.7",
503 | "supports-preserve-symlinks-flag": "^1.0.0"
504 | },
505 | "bin": {
506 | "resolve": "bin/resolve"
507 | },
508 | "funding": {
509 | "url": "https://github.com/sponsors/ljharb"
510 | }
511 | },
512 | "node_modules/rollup": {
513 | "version": "3.10.0",
514 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.10.0.tgz",
515 | "integrity": "sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==",
516 | "dev": true,
517 | "bin": {
518 | "rollup": "dist/bin/rollup"
519 | },
520 | "engines": {
521 | "node": ">=14.18.0",
522 | "npm": ">=8.0.0"
523 | },
524 | "optionalDependencies": {
525 | "fsevents": "~2.3.2"
526 | }
527 | },
528 | "node_modules/source-map-js": {
529 | "version": "1.0.2",
530 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
531 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
532 | "dev": true,
533 | "engines": {
534 | "node": ">=0.10.0"
535 | }
536 | },
537 | "node_modules/supports-preserve-symlinks-flag": {
538 | "version": "1.0.0",
539 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
540 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
541 | "dev": true,
542 | "engines": {
543 | "node": ">= 0.4"
544 | },
545 | "funding": {
546 | "url": "https://github.com/sponsors/ljharb"
547 | }
548 | },
549 | "node_modules/vite": {
550 | "version": "4.0.4",
551 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
552 | "integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
553 | "dev": true,
554 | "dependencies": {
555 | "esbuild": "^0.16.3",
556 | "postcss": "^8.4.20",
557 | "resolve": "^1.22.1",
558 | "rollup": "^3.7.0"
559 | },
560 | "bin": {
561 | "vite": "bin/vite.js"
562 | },
563 | "engines": {
564 | "node": "^14.18.0 || >=16.0.0"
565 | },
566 | "optionalDependencies": {
567 | "fsevents": "~2.3.2"
568 | },
569 | "peerDependencies": {
570 | "@types/node": ">= 14",
571 | "less": "*",
572 | "sass": "*",
573 | "stylus": "*",
574 | "sugarss": "*",
575 | "terser": "^5.4.0"
576 | },
577 | "peerDependenciesMeta": {
578 | "@types/node": {
579 | "optional": true
580 | },
581 | "less": {
582 | "optional": true
583 | },
584 | "sass": {
585 | "optional": true
586 | },
587 | "stylus": {
588 | "optional": true
589 | },
590 | "sugarss": {
591 | "optional": true
592 | },
593 | "terser": {
594 | "optional": true
595 | }
596 | }
597 | }
598 | },
599 | "dependencies": {
600 | "@esbuild/android-arm": {
601 | "version": "0.16.17",
602 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
603 | "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
604 | "dev": true,
605 | "optional": true
606 | },
607 | "@esbuild/android-arm64": {
608 | "version": "0.16.17",
609 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
610 | "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
611 | "dev": true,
612 | "optional": true
613 | },
614 | "@esbuild/android-x64": {
615 | "version": "0.16.17",
616 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
617 | "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
618 | "dev": true,
619 | "optional": true
620 | },
621 | "@esbuild/darwin-arm64": {
622 | "version": "0.16.17",
623 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
624 | "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
625 | "dev": true,
626 | "optional": true
627 | },
628 | "@esbuild/darwin-x64": {
629 | "version": "0.16.17",
630 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
631 | "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
632 | "dev": true,
633 | "optional": true
634 | },
635 | "@esbuild/freebsd-arm64": {
636 | "version": "0.16.17",
637 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
638 | "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
639 | "dev": true,
640 | "optional": true
641 | },
642 | "@esbuild/freebsd-x64": {
643 | "version": "0.16.17",
644 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
645 | "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
646 | "dev": true,
647 | "optional": true
648 | },
649 | "@esbuild/linux-arm": {
650 | "version": "0.16.17",
651 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
652 | "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
653 | "dev": true,
654 | "optional": true
655 | },
656 | "@esbuild/linux-arm64": {
657 | "version": "0.16.17",
658 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
659 | "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
660 | "dev": true,
661 | "optional": true
662 | },
663 | "@esbuild/linux-ia32": {
664 | "version": "0.16.17",
665 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
666 | "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
667 | "dev": true,
668 | "optional": true
669 | },
670 | "@esbuild/linux-loong64": {
671 | "version": "0.16.17",
672 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
673 | "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
674 | "dev": true,
675 | "optional": true
676 | },
677 | "@esbuild/linux-mips64el": {
678 | "version": "0.16.17",
679 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
680 | "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
681 | "dev": true,
682 | "optional": true
683 | },
684 | "@esbuild/linux-ppc64": {
685 | "version": "0.16.17",
686 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
687 | "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
688 | "dev": true,
689 | "optional": true
690 | },
691 | "@esbuild/linux-riscv64": {
692 | "version": "0.16.17",
693 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
694 | "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
695 | "dev": true,
696 | "optional": true
697 | },
698 | "@esbuild/linux-s390x": {
699 | "version": "0.16.17",
700 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
701 | "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
702 | "dev": true,
703 | "optional": true
704 | },
705 | "@esbuild/linux-x64": {
706 | "version": "0.16.17",
707 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
708 | "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
709 | "dev": true,
710 | "optional": true
711 | },
712 | "@esbuild/netbsd-x64": {
713 | "version": "0.16.17",
714 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
715 | "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
716 | "dev": true,
717 | "optional": true
718 | },
719 | "@esbuild/openbsd-x64": {
720 | "version": "0.16.17",
721 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
722 | "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
723 | "dev": true,
724 | "optional": true
725 | },
726 | "@esbuild/sunos-x64": {
727 | "version": "0.16.17",
728 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
729 | "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
730 | "dev": true,
731 | "optional": true
732 | },
733 | "@esbuild/win32-arm64": {
734 | "version": "0.16.17",
735 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
736 | "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
737 | "dev": true,
738 | "optional": true
739 | },
740 | "@esbuild/win32-ia32": {
741 | "version": "0.16.17",
742 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
743 | "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
744 | "dev": true,
745 | "optional": true
746 | },
747 | "@esbuild/win32-x64": {
748 | "version": "0.16.17",
749 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
750 | "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
751 | "dev": true,
752 | "optional": true
753 | },
754 | "esbuild": {
755 | "version": "0.16.17",
756 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz",
757 | "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==",
758 | "dev": true,
759 | "requires": {
760 | "@esbuild/android-arm": "0.16.17",
761 | "@esbuild/android-arm64": "0.16.17",
762 | "@esbuild/android-x64": "0.16.17",
763 | "@esbuild/darwin-arm64": "0.16.17",
764 | "@esbuild/darwin-x64": "0.16.17",
765 | "@esbuild/freebsd-arm64": "0.16.17",
766 | "@esbuild/freebsd-x64": "0.16.17",
767 | "@esbuild/linux-arm": "0.16.17",
768 | "@esbuild/linux-arm64": "0.16.17",
769 | "@esbuild/linux-ia32": "0.16.17",
770 | "@esbuild/linux-loong64": "0.16.17",
771 | "@esbuild/linux-mips64el": "0.16.17",
772 | "@esbuild/linux-ppc64": "0.16.17",
773 | "@esbuild/linux-riscv64": "0.16.17",
774 | "@esbuild/linux-s390x": "0.16.17",
775 | "@esbuild/linux-x64": "0.16.17",
776 | "@esbuild/netbsd-x64": "0.16.17",
777 | "@esbuild/openbsd-x64": "0.16.17",
778 | "@esbuild/sunos-x64": "0.16.17",
779 | "@esbuild/win32-arm64": "0.16.17",
780 | "@esbuild/win32-ia32": "0.16.17",
781 | "@esbuild/win32-x64": "0.16.17"
782 | }
783 | },
784 | "fsevents": {
785 | "version": "2.3.2",
786 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
787 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
788 | "dev": true,
789 | "optional": true
790 | },
791 | "function-bind": {
792 | "version": "1.1.1",
793 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
794 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
795 | "dev": true
796 | },
797 | "has": {
798 | "version": "1.0.3",
799 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
800 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
801 | "dev": true,
802 | "requires": {
803 | "function-bind": "^1.1.1"
804 | }
805 | },
806 | "is-core-module": {
807 | "version": "2.11.0",
808 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
809 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
810 | "dev": true,
811 | "requires": {
812 | "has": "^1.0.3"
813 | }
814 | },
815 | "nanoid": {
816 | "version": "3.3.4",
817 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
818 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
819 | "dev": true
820 | },
821 | "path-parse": {
822 | "version": "1.0.7",
823 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
824 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
825 | "dev": true
826 | },
827 | "picocolors": {
828 | "version": "1.0.0",
829 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
830 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
831 | "dev": true
832 | },
833 | "postcss": {
834 | "version": "8.4.21",
835 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
836 | "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
837 | "dev": true,
838 | "requires": {
839 | "nanoid": "^3.3.4",
840 | "picocolors": "^1.0.0",
841 | "source-map-js": "^1.0.2"
842 | }
843 | },
844 | "resolve": {
845 | "version": "1.22.1",
846 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
847 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
848 | "dev": true,
849 | "requires": {
850 | "is-core-module": "^2.9.0",
851 | "path-parse": "^1.0.7",
852 | "supports-preserve-symlinks-flag": "^1.0.0"
853 | }
854 | },
855 | "rollup": {
856 | "version": "3.10.0",
857 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.10.0.tgz",
858 | "integrity": "sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==",
859 | "dev": true,
860 | "requires": {
861 | "fsevents": "~2.3.2"
862 | }
863 | },
864 | "source-map-js": {
865 | "version": "1.0.2",
866 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
867 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
868 | "dev": true
869 | },
870 | "supports-preserve-symlinks-flag": {
871 | "version": "1.0.0",
872 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
873 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
874 | "dev": true
875 | },
876 | "vite": {
877 | "version": "4.0.4",
878 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
879 | "integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
880 | "dev": true,
881 | "requires": {
882 | "esbuild": "^0.16.3",
883 | "fsevents": "~2.3.2",
884 | "postcss": "^8.4.20",
885 | "resolve": "^1.22.1",
886 | "rollup": "^3.7.0"
887 | }
888 | }
889 | }
890 | }
891 |
--------------------------------------------------------------------------------
/1-Basic-Voice-Chat/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "1-basic-voice-chat",
3 | "version": "0.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "1-basic-voice-chat",
9 | "version": "0.0.0",
10 | "devDependencies": {
11 | "vite": "^4.0.0"
12 | }
13 | },
14 | "node_modules/@esbuild/android-arm": {
15 | "version": "0.16.17",
16 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
17 | "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
18 | "cpu": [
19 | "arm"
20 | ],
21 | "dev": true,
22 | "optional": true,
23 | "os": [
24 | "android"
25 | ],
26 | "engines": {
27 | "node": ">=12"
28 | }
29 | },
30 | "node_modules/@esbuild/android-arm64": {
31 | "version": "0.16.17",
32 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
33 | "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
34 | "cpu": [
35 | "arm64"
36 | ],
37 | "dev": true,
38 | "optional": true,
39 | "os": [
40 | "android"
41 | ],
42 | "engines": {
43 | "node": ">=12"
44 | }
45 | },
46 | "node_modules/@esbuild/android-x64": {
47 | "version": "0.16.17",
48 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
49 | "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
50 | "cpu": [
51 | "x64"
52 | ],
53 | "dev": true,
54 | "optional": true,
55 | "os": [
56 | "android"
57 | ],
58 | "engines": {
59 | "node": ">=12"
60 | }
61 | },
62 | "node_modules/@esbuild/darwin-arm64": {
63 | "version": "0.16.17",
64 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
65 | "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
66 | "cpu": [
67 | "arm64"
68 | ],
69 | "dev": true,
70 | "optional": true,
71 | "os": [
72 | "darwin"
73 | ],
74 | "engines": {
75 | "node": ">=12"
76 | }
77 | },
78 | "node_modules/@esbuild/darwin-x64": {
79 | "version": "0.16.17",
80 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
81 | "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
82 | "cpu": [
83 | "x64"
84 | ],
85 | "dev": true,
86 | "optional": true,
87 | "os": [
88 | "darwin"
89 | ],
90 | "engines": {
91 | "node": ">=12"
92 | }
93 | },
94 | "node_modules/@esbuild/freebsd-arm64": {
95 | "version": "0.16.17",
96 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
97 | "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
98 | "cpu": [
99 | "arm64"
100 | ],
101 | "dev": true,
102 | "optional": true,
103 | "os": [
104 | "freebsd"
105 | ],
106 | "engines": {
107 | "node": ">=12"
108 | }
109 | },
110 | "node_modules/@esbuild/freebsd-x64": {
111 | "version": "0.16.17",
112 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
113 | "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
114 | "cpu": [
115 | "x64"
116 | ],
117 | "dev": true,
118 | "optional": true,
119 | "os": [
120 | "freebsd"
121 | ],
122 | "engines": {
123 | "node": ">=12"
124 | }
125 | },
126 | "node_modules/@esbuild/linux-arm": {
127 | "version": "0.16.17",
128 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
129 | "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
130 | "cpu": [
131 | "arm"
132 | ],
133 | "dev": true,
134 | "optional": true,
135 | "os": [
136 | "linux"
137 | ],
138 | "engines": {
139 | "node": ">=12"
140 | }
141 | },
142 | "node_modules/@esbuild/linux-arm64": {
143 | "version": "0.16.17",
144 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
145 | "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
146 | "cpu": [
147 | "arm64"
148 | ],
149 | "dev": true,
150 | "optional": true,
151 | "os": [
152 | "linux"
153 | ],
154 | "engines": {
155 | "node": ">=12"
156 | }
157 | },
158 | "node_modules/@esbuild/linux-ia32": {
159 | "version": "0.16.17",
160 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
161 | "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
162 | "cpu": [
163 | "ia32"
164 | ],
165 | "dev": true,
166 | "optional": true,
167 | "os": [
168 | "linux"
169 | ],
170 | "engines": {
171 | "node": ">=12"
172 | }
173 | },
174 | "node_modules/@esbuild/linux-loong64": {
175 | "version": "0.16.17",
176 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
177 | "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
178 | "cpu": [
179 | "loong64"
180 | ],
181 | "dev": true,
182 | "optional": true,
183 | "os": [
184 | "linux"
185 | ],
186 | "engines": {
187 | "node": ">=12"
188 | }
189 | },
190 | "node_modules/@esbuild/linux-mips64el": {
191 | "version": "0.16.17",
192 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
193 | "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
194 | "cpu": [
195 | "mips64el"
196 | ],
197 | "dev": true,
198 | "optional": true,
199 | "os": [
200 | "linux"
201 | ],
202 | "engines": {
203 | "node": ">=12"
204 | }
205 | },
206 | "node_modules/@esbuild/linux-ppc64": {
207 | "version": "0.16.17",
208 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
209 | "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
210 | "cpu": [
211 | "ppc64"
212 | ],
213 | "dev": true,
214 | "optional": true,
215 | "os": [
216 | "linux"
217 | ],
218 | "engines": {
219 | "node": ">=12"
220 | }
221 | },
222 | "node_modules/@esbuild/linux-riscv64": {
223 | "version": "0.16.17",
224 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
225 | "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
226 | "cpu": [
227 | "riscv64"
228 | ],
229 | "dev": true,
230 | "optional": true,
231 | "os": [
232 | "linux"
233 | ],
234 | "engines": {
235 | "node": ">=12"
236 | }
237 | },
238 | "node_modules/@esbuild/linux-s390x": {
239 | "version": "0.16.17",
240 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
241 | "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
242 | "cpu": [
243 | "s390x"
244 | ],
245 | "dev": true,
246 | "optional": true,
247 | "os": [
248 | "linux"
249 | ],
250 | "engines": {
251 | "node": ">=12"
252 | }
253 | },
254 | "node_modules/@esbuild/linux-x64": {
255 | "version": "0.16.17",
256 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
257 | "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
258 | "cpu": [
259 | "x64"
260 | ],
261 | "dev": true,
262 | "optional": true,
263 | "os": [
264 | "linux"
265 | ],
266 | "engines": {
267 | "node": ">=12"
268 | }
269 | },
270 | "node_modules/@esbuild/netbsd-x64": {
271 | "version": "0.16.17",
272 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
273 | "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
274 | "cpu": [
275 | "x64"
276 | ],
277 | "dev": true,
278 | "optional": true,
279 | "os": [
280 | "netbsd"
281 | ],
282 | "engines": {
283 | "node": ">=12"
284 | }
285 | },
286 | "node_modules/@esbuild/openbsd-x64": {
287 | "version": "0.16.17",
288 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
289 | "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
290 | "cpu": [
291 | "x64"
292 | ],
293 | "dev": true,
294 | "optional": true,
295 | "os": [
296 | "openbsd"
297 | ],
298 | "engines": {
299 | "node": ">=12"
300 | }
301 | },
302 | "node_modules/@esbuild/sunos-x64": {
303 | "version": "0.16.17",
304 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
305 | "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
306 | "cpu": [
307 | "x64"
308 | ],
309 | "dev": true,
310 | "optional": true,
311 | "os": [
312 | "sunos"
313 | ],
314 | "engines": {
315 | "node": ">=12"
316 | }
317 | },
318 | "node_modules/@esbuild/win32-arm64": {
319 | "version": "0.16.17",
320 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
321 | "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
322 | "cpu": [
323 | "arm64"
324 | ],
325 | "dev": true,
326 | "optional": true,
327 | "os": [
328 | "win32"
329 | ],
330 | "engines": {
331 | "node": ">=12"
332 | }
333 | },
334 | "node_modules/@esbuild/win32-ia32": {
335 | "version": "0.16.17",
336 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
337 | "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
338 | "cpu": [
339 | "ia32"
340 | ],
341 | "dev": true,
342 | "optional": true,
343 | "os": [
344 | "win32"
345 | ],
346 | "engines": {
347 | "node": ">=12"
348 | }
349 | },
350 | "node_modules/@esbuild/win32-x64": {
351 | "version": "0.16.17",
352 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
353 | "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
354 | "cpu": [
355 | "x64"
356 | ],
357 | "dev": true,
358 | "optional": true,
359 | "os": [
360 | "win32"
361 | ],
362 | "engines": {
363 | "node": ">=12"
364 | }
365 | },
366 | "node_modules/esbuild": {
367 | "version": "0.16.17",
368 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz",
369 | "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==",
370 | "dev": true,
371 | "hasInstallScript": true,
372 | "bin": {
373 | "esbuild": "bin/esbuild"
374 | },
375 | "engines": {
376 | "node": ">=12"
377 | },
378 | "optionalDependencies": {
379 | "@esbuild/android-arm": "0.16.17",
380 | "@esbuild/android-arm64": "0.16.17",
381 | "@esbuild/android-x64": "0.16.17",
382 | "@esbuild/darwin-arm64": "0.16.17",
383 | "@esbuild/darwin-x64": "0.16.17",
384 | "@esbuild/freebsd-arm64": "0.16.17",
385 | "@esbuild/freebsd-x64": "0.16.17",
386 | "@esbuild/linux-arm": "0.16.17",
387 | "@esbuild/linux-arm64": "0.16.17",
388 | "@esbuild/linux-ia32": "0.16.17",
389 | "@esbuild/linux-loong64": "0.16.17",
390 | "@esbuild/linux-mips64el": "0.16.17",
391 | "@esbuild/linux-ppc64": "0.16.17",
392 | "@esbuild/linux-riscv64": "0.16.17",
393 | "@esbuild/linux-s390x": "0.16.17",
394 | "@esbuild/linux-x64": "0.16.17",
395 | "@esbuild/netbsd-x64": "0.16.17",
396 | "@esbuild/openbsd-x64": "0.16.17",
397 | "@esbuild/sunos-x64": "0.16.17",
398 | "@esbuild/win32-arm64": "0.16.17",
399 | "@esbuild/win32-ia32": "0.16.17",
400 | "@esbuild/win32-x64": "0.16.17"
401 | }
402 | },
403 | "node_modules/fsevents": {
404 | "version": "2.3.2",
405 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
406 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
407 | "dev": true,
408 | "hasInstallScript": true,
409 | "optional": true,
410 | "os": [
411 | "darwin"
412 | ],
413 | "engines": {
414 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
415 | }
416 | },
417 | "node_modules/function-bind": {
418 | "version": "1.1.1",
419 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
420 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
421 | "dev": true
422 | },
423 | "node_modules/has": {
424 | "version": "1.0.3",
425 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
426 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
427 | "dev": true,
428 | "dependencies": {
429 | "function-bind": "^1.1.1"
430 | },
431 | "engines": {
432 | "node": ">= 0.4.0"
433 | }
434 | },
435 | "node_modules/is-core-module": {
436 | "version": "2.11.0",
437 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
438 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
439 | "dev": true,
440 | "dependencies": {
441 | "has": "^1.0.3"
442 | },
443 | "funding": {
444 | "url": "https://github.com/sponsors/ljharb"
445 | }
446 | },
447 | "node_modules/nanoid": {
448 | "version": "3.3.4",
449 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
450 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
451 | "dev": true,
452 | "bin": {
453 | "nanoid": "bin/nanoid.cjs"
454 | },
455 | "engines": {
456 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
457 | }
458 | },
459 | "node_modules/path-parse": {
460 | "version": "1.0.7",
461 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
462 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
463 | "dev": true
464 | },
465 | "node_modules/picocolors": {
466 | "version": "1.0.0",
467 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
468 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
469 | "dev": true
470 | },
471 | "node_modules/postcss": {
472 | "version": "8.4.21",
473 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
474 | "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
475 | "dev": true,
476 | "funding": [
477 | {
478 | "type": "opencollective",
479 | "url": "https://opencollective.com/postcss/"
480 | },
481 | {
482 | "type": "tidelift",
483 | "url": "https://tidelift.com/funding/github/npm/postcss"
484 | }
485 | ],
486 | "dependencies": {
487 | "nanoid": "^3.3.4",
488 | "picocolors": "^1.0.0",
489 | "source-map-js": "^1.0.2"
490 | },
491 | "engines": {
492 | "node": "^10 || ^12 || >=14"
493 | }
494 | },
495 | "node_modules/resolve": {
496 | "version": "1.22.1",
497 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
498 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
499 | "dev": true,
500 | "dependencies": {
501 | "is-core-module": "^2.9.0",
502 | "path-parse": "^1.0.7",
503 | "supports-preserve-symlinks-flag": "^1.0.0"
504 | },
505 | "bin": {
506 | "resolve": "bin/resolve"
507 | },
508 | "funding": {
509 | "url": "https://github.com/sponsors/ljharb"
510 | }
511 | },
512 | "node_modules/rollup": {
513 | "version": "3.10.0",
514 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.10.0.tgz",
515 | "integrity": "sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==",
516 | "dev": true,
517 | "bin": {
518 | "rollup": "dist/bin/rollup"
519 | },
520 | "engines": {
521 | "node": ">=14.18.0",
522 | "npm": ">=8.0.0"
523 | },
524 | "optionalDependencies": {
525 | "fsevents": "~2.3.2"
526 | }
527 | },
528 | "node_modules/source-map-js": {
529 | "version": "1.0.2",
530 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
531 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
532 | "dev": true,
533 | "engines": {
534 | "node": ">=0.10.0"
535 | }
536 | },
537 | "node_modules/supports-preserve-symlinks-flag": {
538 | "version": "1.0.0",
539 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
540 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
541 | "dev": true,
542 | "engines": {
543 | "node": ">= 0.4"
544 | },
545 | "funding": {
546 | "url": "https://github.com/sponsors/ljharb"
547 | }
548 | },
549 | "node_modules/vite": {
550 | "version": "4.0.4",
551 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
552 | "integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
553 | "dev": true,
554 | "dependencies": {
555 | "esbuild": "^0.16.3",
556 | "postcss": "^8.4.20",
557 | "resolve": "^1.22.1",
558 | "rollup": "^3.7.0"
559 | },
560 | "bin": {
561 | "vite": "bin/vite.js"
562 | },
563 | "engines": {
564 | "node": "^14.18.0 || >=16.0.0"
565 | },
566 | "optionalDependencies": {
567 | "fsevents": "~2.3.2"
568 | },
569 | "peerDependencies": {
570 | "@types/node": ">= 14",
571 | "less": "*",
572 | "sass": "*",
573 | "stylus": "*",
574 | "sugarss": "*",
575 | "terser": "^5.4.0"
576 | },
577 | "peerDependenciesMeta": {
578 | "@types/node": {
579 | "optional": true
580 | },
581 | "less": {
582 | "optional": true
583 | },
584 | "sass": {
585 | "optional": true
586 | },
587 | "stylus": {
588 | "optional": true
589 | },
590 | "sugarss": {
591 | "optional": true
592 | },
593 | "terser": {
594 | "optional": true
595 | }
596 | }
597 | }
598 | },
599 | "dependencies": {
600 | "@esbuild/android-arm": {
601 | "version": "0.16.17",
602 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
603 | "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
604 | "dev": true,
605 | "optional": true
606 | },
607 | "@esbuild/android-arm64": {
608 | "version": "0.16.17",
609 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
610 | "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
611 | "dev": true,
612 | "optional": true
613 | },
614 | "@esbuild/android-x64": {
615 | "version": "0.16.17",
616 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
617 | "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
618 | "dev": true,
619 | "optional": true
620 | },
621 | "@esbuild/darwin-arm64": {
622 | "version": "0.16.17",
623 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
624 | "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
625 | "dev": true,
626 | "optional": true
627 | },
628 | "@esbuild/darwin-x64": {
629 | "version": "0.16.17",
630 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
631 | "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
632 | "dev": true,
633 | "optional": true
634 | },
635 | "@esbuild/freebsd-arm64": {
636 | "version": "0.16.17",
637 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
638 | "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
639 | "dev": true,
640 | "optional": true
641 | },
642 | "@esbuild/freebsd-x64": {
643 | "version": "0.16.17",
644 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
645 | "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
646 | "dev": true,
647 | "optional": true
648 | },
649 | "@esbuild/linux-arm": {
650 | "version": "0.16.17",
651 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
652 | "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
653 | "dev": true,
654 | "optional": true
655 | },
656 | "@esbuild/linux-arm64": {
657 | "version": "0.16.17",
658 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
659 | "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
660 | "dev": true,
661 | "optional": true
662 | },
663 | "@esbuild/linux-ia32": {
664 | "version": "0.16.17",
665 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
666 | "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
667 | "dev": true,
668 | "optional": true
669 | },
670 | "@esbuild/linux-loong64": {
671 | "version": "0.16.17",
672 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
673 | "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
674 | "dev": true,
675 | "optional": true
676 | },
677 | "@esbuild/linux-mips64el": {
678 | "version": "0.16.17",
679 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
680 | "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
681 | "dev": true,
682 | "optional": true
683 | },
684 | "@esbuild/linux-ppc64": {
685 | "version": "0.16.17",
686 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
687 | "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
688 | "dev": true,
689 | "optional": true
690 | },
691 | "@esbuild/linux-riscv64": {
692 | "version": "0.16.17",
693 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
694 | "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
695 | "dev": true,
696 | "optional": true
697 | },
698 | "@esbuild/linux-s390x": {
699 | "version": "0.16.17",
700 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
701 | "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
702 | "dev": true,
703 | "optional": true
704 | },
705 | "@esbuild/linux-x64": {
706 | "version": "0.16.17",
707 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
708 | "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
709 | "dev": true,
710 | "optional": true
711 | },
712 | "@esbuild/netbsd-x64": {
713 | "version": "0.16.17",
714 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
715 | "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
716 | "dev": true,
717 | "optional": true
718 | },
719 | "@esbuild/openbsd-x64": {
720 | "version": "0.16.17",
721 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
722 | "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
723 | "dev": true,
724 | "optional": true
725 | },
726 | "@esbuild/sunos-x64": {
727 | "version": "0.16.17",
728 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
729 | "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
730 | "dev": true,
731 | "optional": true
732 | },
733 | "@esbuild/win32-arm64": {
734 | "version": "0.16.17",
735 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
736 | "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
737 | "dev": true,
738 | "optional": true
739 | },
740 | "@esbuild/win32-ia32": {
741 | "version": "0.16.17",
742 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
743 | "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
744 | "dev": true,
745 | "optional": true
746 | },
747 | "@esbuild/win32-x64": {
748 | "version": "0.16.17",
749 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
750 | "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
751 | "dev": true,
752 | "optional": true
753 | },
754 | "esbuild": {
755 | "version": "0.16.17",
756 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz",
757 | "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==",
758 | "dev": true,
759 | "requires": {
760 | "@esbuild/android-arm": "0.16.17",
761 | "@esbuild/android-arm64": "0.16.17",
762 | "@esbuild/android-x64": "0.16.17",
763 | "@esbuild/darwin-arm64": "0.16.17",
764 | "@esbuild/darwin-x64": "0.16.17",
765 | "@esbuild/freebsd-arm64": "0.16.17",
766 | "@esbuild/freebsd-x64": "0.16.17",
767 | "@esbuild/linux-arm": "0.16.17",
768 | "@esbuild/linux-arm64": "0.16.17",
769 | "@esbuild/linux-ia32": "0.16.17",
770 | "@esbuild/linux-loong64": "0.16.17",
771 | "@esbuild/linux-mips64el": "0.16.17",
772 | "@esbuild/linux-ppc64": "0.16.17",
773 | "@esbuild/linux-riscv64": "0.16.17",
774 | "@esbuild/linux-s390x": "0.16.17",
775 | "@esbuild/linux-x64": "0.16.17",
776 | "@esbuild/netbsd-x64": "0.16.17",
777 | "@esbuild/openbsd-x64": "0.16.17",
778 | "@esbuild/sunos-x64": "0.16.17",
779 | "@esbuild/win32-arm64": "0.16.17",
780 | "@esbuild/win32-ia32": "0.16.17",
781 | "@esbuild/win32-x64": "0.16.17"
782 | }
783 | },
784 | "fsevents": {
785 | "version": "2.3.2",
786 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
787 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
788 | "dev": true,
789 | "optional": true
790 | },
791 | "function-bind": {
792 | "version": "1.1.1",
793 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
794 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
795 | "dev": true
796 | },
797 | "has": {
798 | "version": "1.0.3",
799 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
800 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
801 | "dev": true,
802 | "requires": {
803 | "function-bind": "^1.1.1"
804 | }
805 | },
806 | "is-core-module": {
807 | "version": "2.11.0",
808 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
809 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
810 | "dev": true,
811 | "requires": {
812 | "has": "^1.0.3"
813 | }
814 | },
815 | "nanoid": {
816 | "version": "3.3.4",
817 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
818 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
819 | "dev": true
820 | },
821 | "path-parse": {
822 | "version": "1.0.7",
823 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
824 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
825 | "dev": true
826 | },
827 | "picocolors": {
828 | "version": "1.0.0",
829 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
830 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
831 | "dev": true
832 | },
833 | "postcss": {
834 | "version": "8.4.21",
835 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
836 | "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
837 | "dev": true,
838 | "requires": {
839 | "nanoid": "^3.3.4",
840 | "picocolors": "^1.0.0",
841 | "source-map-js": "^1.0.2"
842 | }
843 | },
844 | "resolve": {
845 | "version": "1.22.1",
846 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
847 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
848 | "dev": true,
849 | "requires": {
850 | "is-core-module": "^2.9.0",
851 | "path-parse": "^1.0.7",
852 | "supports-preserve-symlinks-flag": "^1.0.0"
853 | }
854 | },
855 | "rollup": {
856 | "version": "3.10.0",
857 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.10.0.tgz",
858 | "integrity": "sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==",
859 | "dev": true,
860 | "requires": {
861 | "fsevents": "~2.3.2"
862 | }
863 | },
864 | "source-map-js": {
865 | "version": "1.0.2",
866 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
867 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
868 | "dev": true
869 | },
870 | "supports-preserve-symlinks-flag": {
871 | "version": "1.0.0",
872 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
873 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
874 | "dev": true
875 | },
876 | "vite": {
877 | "version": "4.0.4",
878 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
879 | "integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
880 | "dev": true,
881 | "requires": {
882 | "esbuild": "^0.16.3",
883 | "fsevents": "~2.3.2",
884 | "postcss": "^8.4.20",
885 | "resolve": "^1.22.1",
886 | "rollup": "^3.7.0"
887 | }
888 | }
889 | }
890 | }
891 |
--------------------------------------------------------------------------------