├── .gitignore ├── Client ├── .DS_Store ├── package-lock.json ├── package.json ├── public │ ├── index.html │ └── manifest.json ├── src │ ├── App.css │ ├── App.tsx │ ├── components │ │ ├── canvas-render.tsx │ │ └── styles.module.css │ ├── index.css │ ├── index.tsx │ └── react-app-env.d.ts └── tsconfig.json ├── README.md ├── Server ├── .DS_Store ├── .env ├── package-lock.json ├── package.json └── server.js ├── config-overrides.js ├── package-lock.json ├── package.json ├── public ├── index.html └── manifest.json ├── src ├── App.css ├── App.tsx ├── index.css ├── index.tsx └── react-app-env.d.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | -------------------------------------------------------------------------------- /Client/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeyGen-Official/StreamingAvatarTSDemo/d5b97a290f7ad4dd84a339df0bd3d30ceb10b7ab/Client/.DS_Store -------------------------------------------------------------------------------- /Client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@heygen/streaming-avatar": "^1.0.11", 7 | "@types/node": "^16.18.96", 8 | "@types/react": "^18.3.1", 9 | "@types/react-dom": "^18.3.0", 10 | "openai": "^4.51.0", 11 | "react": "^18.3.1", 12 | "react-dom": "^18.3.1", 13 | "react-scripts": "5.0.1", 14 | "typescript": "^4.9.5" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Heygen Streaming Avatar", 3 | "name": "Heygen Streaming Avatar Demo", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /Client/src/App.css: -------------------------------------------------------------------------------- 1 | .HeyGenStreamingAvatar { 2 | text-align: center; 3 | } 4 | 5 | @media (prefers-reduced-motion: no-preference) { 6 | .App-logo { 7 | animation: App-logo-spin infinite 20s linear; 8 | } 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | 35 | .MediaPlayer{ 36 | display: flex; 37 | width: 800px; 38 | height: 300px; 39 | justify-content: space-between; 40 | margin-top: 50px; 41 | } 42 | 43 | .Actions{ 44 | display: flex; 45 | flex-direction: row; 46 | gap: 10px; 47 | margin: 10px; 48 | } 49 | 50 | .InputField{ 51 | margin: 10px; 52 | width: 400px; 53 | } 54 | 55 | .InputField2 { 56 | margin-left: 10px; 57 | height: 20px; 58 | width: 400px; 59 | } 60 | 61 | .LabelPair{ 62 | height: 50px; 63 | display: row; 64 | flex-direction: row; 65 | align-items: start; 66 | } 67 | -------------------------------------------------------------------------------- /Client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useRef, useState} from 'react'; 2 | import { Configuration, NewSessionData, StreamingAvatarApi } from '@heygen/streaming-avatar'; 3 | import './App.css'; 4 | import OpenAI from 'openai'; 5 | import {CanvasRender} from "./components/canvas-render"; 6 | 7 | //Enter your OpenAI key here 8 | const openaiApiKey = "" 9 | 10 | // Set up OpenAI w/ API Key 11 | const openai = new OpenAI({ 12 | apiKey: openaiApiKey, 13 | dangerouslyAllowBrowser: true 14 | }); 15 | 16 | function App() { 17 | const [stream, setStream] = useState(); 18 | const [debug, setDebug] = useState(); 19 | const [text, setText] = useState(""); 20 | const [chatGPTText, setChatGPTText] = useState(""); 21 | const [avatarId, setAvatarId] = useState(""); 22 | const [voiceId, setVoiceId] = useState(""); 23 | const [data, setData] = useState(); 24 | const [initialized, setInitialized] = useState(false); // Track initialization 25 | const [recording, setRecording] = useState(false); // Track recording state 26 | const [audioBlob, setAudioBlob] = useState(null); // Store recorded audio 27 | const mediaStream = useRef(null); 28 | const avatar = useRef(null); 29 | const mediaRecorder = useRef(null); 30 | const audioChunks = useRef([]); 31 | 32 | const [canPlay, setCanPlay] = useState(false) 33 | 34 | async function fetchAccessToken() { 35 | try { 36 | const response = await fetch('http://localhost:3001/get-access-token', { 37 | method: 'POST' 38 | }); 39 | const result = await response.json(); 40 | const token = result.token; // Access the token correctly 41 | console.log('Access Token:', token); // Log the token to verify 42 | return token; 43 | } catch (error) { 44 | console.error('Error fetching access token:', error); 45 | return ''; 46 | } 47 | } 48 | 49 | async function grab() { 50 | await updateToken(); 51 | 52 | if (!avatar.current) { 53 | setDebug('Avatar API is not initialized'); 54 | return; 55 | } 56 | 57 | try { 58 | const res = await avatar.current.createStartAvatar( 59 | { 60 | newSessionRequest: { 61 | quality: "low", 62 | avatarName: avatarId, 63 | voice: { voiceId: voiceId } 64 | } 65 | }, setDebug); 66 | setData(res); 67 | setStream(avatar.current.mediaStream); 68 | } catch (error) { 69 | console.error('Error starting avatar session:', error); 70 | } 71 | }; 72 | 73 | async function updateToken() { 74 | const newToken = await fetchAccessToken(); 75 | console.log('Updating Access Token:', newToken); // Log token for debugging 76 | avatar.current = new StreamingAvatarApi( 77 | new Configuration({ accessToken: newToken }) 78 | ); 79 | 80 | const startTalkCallback = (e: any) => { 81 | console.log("Avatar started talking", e); 82 | }; 83 | 84 | const stopTalkCallback = (e: any) => { 85 | console.log("Avatar stopped talking", e); 86 | }; 87 | 88 | console.log('Adding event handlers:', avatar.current); 89 | avatar.current.addEventHandler("avatar_start_talking", startTalkCallback); 90 | avatar.current.addEventHandler("avatar_stop_talking", stopTalkCallback); 91 | 92 | setInitialized(true); 93 | } 94 | 95 | async function stop() { 96 | if (!initialized || !avatar.current) { 97 | setDebug('Avatar API not initialized'); 98 | return; 99 | } 100 | await avatar.current.stopAvatar({ stopSessionRequest: { sessionId: data?.sessionId } }, setDebug); 101 | } 102 | 103 | async function handleSpeak() { 104 | if (!initialized || !avatar.current) { 105 | setDebug('Avatar API not initialized'); 106 | return; 107 | } 108 | await avatar.current.speak({ taskRequest: { text: text, sessionId: data?.sessionId } }).catch((e) => { 109 | setDebug(e.message); 110 | }); 111 | } 112 | 113 | async function handleInterrupt() { 114 | if (!initialized || !avatar.current) { 115 | setDebug('Avatar API not initialized'); 116 | return; 117 | } 118 | await avatar.current?.interrupt({ interruptRequest: { sessionId: data?.sessionId } }).catch((e) => { 119 | setDebug(e.message); 120 | }); 121 | } 122 | 123 | async function handleChatGPT() { 124 | 125 | if (!chatGPTText) { 126 | setDebug('Please enter text to send to ChatGPT'); 127 | return; 128 | } 129 | 130 | try { 131 | const response = await openai.chat.completions.create({ //Send the user input to ChatGPT 132 | model: "gpt-4o", 133 | messages: [{ role: "system", content: "You are a helpful assistant." }, 134 | { role: "user", content: chatGPTText }], 135 | }); 136 | 137 | const chatGPTResponse = String(response.choices[0].message.content); 138 | console.log('ChatGPT Response:', chatGPTResponse); 139 | 140 | if (!initialized || !avatar.current) { 141 | setDebug('Avatar API not initialized'); 142 | return; 143 | } 144 | 145 | //send the ChatGPT response to the Streaming Avatar 146 | await avatar.current.speak({ taskRequest: { text: chatGPTResponse, sessionId: data?.sessionId } }).catch((e) => { 147 | setDebug(e.message); 148 | }); 149 | 150 | } catch (error) { 151 | console.error('Error communicating with ChatGPT:', error); 152 | } 153 | } 154 | 155 | useEffect(() => { 156 | async function init() { 157 | const newToken = await fetchAccessToken(); 158 | console.log('Initializing with Access Token:', newToken); // Log token for debugging 159 | avatar.current = new StreamingAvatarApi( 160 | new Configuration({ accessToken: newToken, jitterBuffer: 200 }) 161 | ); 162 | setInitialized(true); // Set initialized to true 163 | }; 164 | init(); 165 | }, []); 166 | 167 | useEffect(() => { 168 | if (stream && mediaStream.current) { 169 | mediaStream.current.srcObject = stream; 170 | mediaStream.current.onloadedmetadata = () => { 171 | mediaStream.current!.play(); 172 | setDebug("Playing"); 173 | } 174 | } 175 | }, [mediaStream, stream]); 176 | 177 | const startRecording = () => { 178 | navigator.mediaDevices.getUserMedia({ audio: true }) 179 | .then(stream => { 180 | mediaRecorder.current = new MediaRecorder(stream); 181 | mediaRecorder.current.ondataavailable = event => { 182 | audioChunks.current.push(event.data); 183 | }; 184 | mediaRecorder.current.onstop = () => { 185 | const audioBlob = new Blob(audioChunks.current, { type: 'audio/wav' }); 186 | setAudioBlob(audioBlob); 187 | audioChunks.current = []; 188 | transcribeAudio(audioBlob); 189 | }; 190 | mediaRecorder.current.start(); 191 | setRecording(true); 192 | }) 193 | .catch(error => { 194 | console.error('Error accessing microphone:', error); 195 | }); 196 | }; 197 | 198 | const stopRecording = () => { 199 | if (mediaRecorder.current) { 200 | mediaRecorder.current.stop(); 201 | setRecording(false); 202 | } 203 | }; 204 | 205 | const transcribeAudio = async (audioBlob: Blob) => { 206 | try { 207 | // Convert Blob to File 208 | const audioFile = new File([audioBlob], "recording.wav", { type: 'audio/wav' }); 209 | 210 | const response = await openai.audio.transcriptions.create({ 211 | model: "whisper-1", 212 | file: audioFile 213 | }); 214 | const transcription = response.text; 215 | console.log('Transcription:', transcription); 216 | setChatGPTText(transcription); 217 | console.log(chatGPTText); 218 | } catch (error) { 219 | console.error('Error transcribing audio:', error); 220 | } 221 | }; 222 | 223 | return ( 224 |
225 |
226 |

227 | {debug} 228 |

229 |
230 | 231 | setAvatarId(v.target.value)} /> 232 |
233 |
234 | 235 | setVoiceId(v.target.value)} /> 236 |
237 |
238 | setText(v.target.value)} /> 239 | 240 | 241 | 242 | 243 | 244 |
245 |
246 | setChatGPTText(v.target.value)} /> 247 | 248 | 251 |
252 |
253 |
258 |
259 |
260 | ); 261 | } 262 | 263 | export default App; 264 | -------------------------------------------------------------------------------- /Client/src/components/canvas-render.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useRef} from 'react'; 2 | import styles from './styles.module.css' 3 | 4 | type CanvasRenderProps = { 5 | style?: React.CSSProperties; 6 | videoRef: React.RefObject; 7 | }; 8 | 9 | export function CanvasRender(props: CanvasRenderProps) { 10 | const { videoRef, style } = props; 11 | const refCanvas = useRef(null); 12 | 13 | useEffect(() => { 14 | if (!refCanvas.current) return; 15 | if (!videoRef.current) return; 16 | 17 | // Set the canvas size to be the same as the video size 18 | refCanvas.current.width = videoRef.current.videoWidth; 19 | refCanvas.current.height = videoRef.current.videoHeight; 20 | const ctx = refCanvas.current?.getContext('2d'); 21 | let show = true; 22 | 23 | function processFrame() { 24 | if (!refCanvas.current) return; 25 | if (!videoRef.current) return; 26 | if (!ctx) return; 27 | if (!show) return; 28 | 29 | // Draw the current frame of the video on the canvas 30 | ctx.drawImage(videoRef.current, 0, 0, refCanvas.current.width, refCanvas.current.height); 31 | ctx.getContextAttributes().willReadFrequently = true; 32 | // Get image data from canvas 33 | const imageData = ctx.getImageData(0, 0, refCanvas.current.width, refCanvas.current.height); 34 | const data = imageData.data; 35 | 36 | // Process image data, remove green background 37 | for (let i = 0; i < data.length; i += 4) { 38 | const red = data[i]; 39 | const green = data[i + 1]; 40 | const blue = data[i + 2]; 41 | 42 | // Determine whether it is green, can be adjusted according to actual scenario 43 | if (green > 90 && red < 90 && blue < 90) { 44 | // Set the green background to transparent 45 | data[i + 3] = 0; 46 | } 47 | } 48 | 49 | // Put the processed image data back into the canvas 50 | ctx.putImageData(imageData, 0, 0); 51 | 52 | // Continue processing the next frame 53 | requestAnimationFrame(processFrame); 54 | } 55 | 56 | // Start processing video frames 57 | processFrame(); 58 | 59 | return () => { 60 | show = false; 61 | }; 62 | }, [videoRef]); 63 | 64 | return ; 65 | } 66 | -------------------------------------------------------------------------------- /Client/src/components/styles.module.css: -------------------------------------------------------------------------------- 1 | .wrap { 2 | width: 300px; 3 | } -------------------------------------------------------------------------------- /Client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | 15 | button { 16 | background-color: #7559ff; 17 | color: #e4e1ee; 18 | font-size: large; 19 | border: none; 20 | padding-left: 20px; 21 | padding-right: 20px; 22 | padding-top: 15px; 23 | padding-bottom: 15px; 24 | border-radius: 10px; 25 | } -------------------------------------------------------------------------------- /Client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | const root = ReactDOM.createRoot( 7 | document.getElementById('root') as HTMLElement 8 | ); 9 | root.render( 10 | 11 | 12 | 13 | ); -------------------------------------------------------------------------------- /Client/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /Client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repo will be deprecated 2 | 3 | Due to the deprecation of **Create-React-App**, which this repo uses, we will be updating a new starter project going forward. 4 | 5 | Please use our **NextJS Starter Project**. You can install it here: [https://github.com/HeyGen-Official/StreamingAvatarNextJSDemo] 6 | 7 | Both this repo and the NextJS repo make use of our Streaming Avatar SDK. Leave feedback for the SDK [here](https://github.com/HeyGen-Official/StreamingAvatarSDK/discussions). 8 | 9 | ### Setting up the Create-React-App demo 10 | 11 | 1. Clone this repo 12 | 13 | 2. Navigate to the containing folder in your terminal 14 | 15 | 3. Run `npm install` (assuming you have npm installed. If not, please follow these instructions: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm/) 16 | 17 | 4. Go to the 'Server' folder, and enter your HeyGen Enterprise API Token or Trial Token in the `.env` file. Replace `PLACEHOLDER-API-KEY` with your API key. This will allow the Client app to generate secure Access Tokens with which to create streaming sessions. 18 | 19 | You can retrieve either the API Key or Trial Token by logging in to HeyGen and navigating to this page in your settings: [https://app.heygen.com/settings?nav=API]. NOTE: use the trial token if you don't have an enterprise API token yet. 20 | 21 | 5. Run `npm start` 22 | 23 | ### Difference between Trial Token and Enterprise API Token 24 | 25 | The HeyGen Trial Token is available to all users, not just Enterprise users, and allows for testing of the Streaming API, as well as other HeyGen API endpoints. 26 | 27 | Each Trial Token is limited to 3 concurrent streaming sessions. However, every streaming session you create with the Trial Token is free of charge, no matter how many tasks are sent to the avatar. Please note that streaming sessions will automatically close after 10 minutes of no tasks sent. 28 | 29 | If you do not 'close' the streaming sessions and try to open more than 3, you will encounter errors including stuttering and freezing of the Streaming Avatar. Please endeavor to only have 3 sessions open at any time while you are testing the Streaming Avatar API with your Trial Token. 30 | 31 | ### Starting sessions 32 | 33 | NOTE: Make sure you have enter your token into the `.env` file and run `npm start`. 34 | 35 | To start your 'session' with a Streaming Avatar, first click the 'start' button. If your HeyGen API key is entered into the Server's .env file, then you should see our demo Streaming Avatar (Monica!) appear. 36 | 37 | After you see Monica appear on the screen, you can enter text into the input field with 'Type something to say' written, and then click the 'Speak' button. The Streaming Avatar will say the text you enter. 38 | 39 | If you want to see a different Avatar or try a different voice, you can close the session and enter the IDs and then 'start' the session again. Please see below for information on where to retrieve different Avatar and voice IDs that you can use. 40 | 41 | ### Connecting to OpenAI 42 | 43 | A common use case for a Streaming Avatar is to use it as the 'face' of an LLM that users can interact with. In this demo we have included functionality to showcase this by both accepting user input via voice (using OpenAI's Whisper library) and also sending that input to an OpenAI LLM model (using their Chat Completions endpoint). 44 | 45 | Both of these features of this demo require an OpenAI API Key. If you do not have a paid OpenAI account, you can learn more on their website: [https://openai.com/index/openai-api/] 46 | 47 | Without an OpenAI API Key, this functionality will not work, and the Streaming Avatar will only be able to repeat text input that you provide, and not demonstrate being the 'face' of an LLM. Regardless, this demo is meant to demonstrate what kinds of apps and experiences you can build with our Streaming Avatar SDK, so you can code your own connection to a different LLM if you so choose. 48 | 49 | To add your Open AI API Key, go to the 'Client' folder, open 'App.tsx', and put in your OpenAI API key where indicated. 50 | 51 | ### How does the integration with OpenAI / ChatGPT work? 52 | 53 | In this demo, we are calling the Chat Completions API from OpenAI in order to come up with some response to user input. You can see the relevant code in client/app.tsx. 54 | 55 | You can see that there are two 'messages' sent here in the call to the gpt-4o model. One has the Role parameter set to 'system', and the the other is a message with the role 'user'. 56 | 57 | In the 'system' message, you can replace the 'content' parameter with whatever 'knowledge base' or context that you would like the GPT-4o model to reply to the user's input with. 58 | 59 | You can explore this API and the different parameters and models available here: [https://platform.openai.com/docs/guides/text-generation/chat-completions-api] 60 | 61 | ### Which Avatars can I use with this project? 62 | 63 | By default, there are several Public Avatars that can be used in Streaming. (AKA Streaming Avatars.) You can find the Avatar IDs for these Public Avatars by navigating to [app.heygen.com/streaming-avatar](https://app.heygen.com/streaming-avatar) and clicking 'Select Avatar' and copying the avatar id. 64 | 65 | In order to use a private Avatar created under your own account in Streaming, it must be upgraded to be a Streaming Avatar. Only 1. Finetune Instant Avatars and 2. Studio Avatars are able to be upgraded to Streaming Avatars. This upgrade is a one-time fee and can be purchased by navigating to [app.heygen.com/streaming-avatar] and clicking 'Select Avatar'. 66 | 67 | Please note that Photo Avatars are not compatible with Streaming and cannot be used. 68 | 69 | ### Which voices can I use with my Streaming Avatar? 70 | 71 | Most of HeyGen's AI Voices can be used with the Streaming API. To find the Voice IDs that you can use, please use the List Voices v2 endpoint from HeyGen: [https://docs.heygen.com/reference/list-voices-v2] 72 | 73 | Please note that for voices that support Emotions, such as Christine and Tarquin, you need to pass in the Emotion string in the Voice Setting parameter: [https://docs.heygen.com/reference/new-session-copy#voicesetting] 74 | 75 | You can also set the speed at which the Streaming Avatar speaks by passing in a Rate in the Voice Setting. 76 | 77 | ### Other Features 78 | 79 | We have added callbacks for 'Start' and 'Stop' talking, so that you can take some action whenever the Streaming Avatar starts or stops speaking. 80 | 81 | We have also added Interruption functionality to the SDK. This enables you to interrupt the Avatar mid-sentence, for example if the user does not want to keep hearing the Avatar's speech. 82 | 83 | ### Where can I read more about enterprise-level usage of the Streaming API? 84 | 85 | Please read our Streaming Avatar 101 article for more information on pricing and how to increase your concurrent session limit: https://help.heygen.com/en/articles/9182113-streaming-avatar-101-your-ultimate-guide 86 | -------------------------------------------------------------------------------- /Server/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeyGen-Official/StreamingAvatarTSDemo/d5b97a290f7ad4dd84a339df0bd3d30ceb10b7ab/Server/.DS_Store -------------------------------------------------------------------------------- /Server/.env: -------------------------------------------------------------------------------- 1 | API_KEY=PLACEHOLDER-API-KEY -------------------------------------------------------------------------------- /Server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secure-api-server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "secure-api-server", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "^1.7.2", 13 | "cors": "^2.8.5", 14 | "dotenv": "^16.4.5", 15 | "express": "^4.19.2" 16 | } 17 | }, 18 | "node_modules/accepts": { 19 | "version": "1.3.8", 20 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 21 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 22 | "dependencies": { 23 | "mime-types": "~2.1.34", 24 | "negotiator": "0.6.3" 25 | }, 26 | "engines": { 27 | "node": ">= 0.6" 28 | } 29 | }, 30 | "node_modules/array-flatten": { 31 | "version": "1.1.1", 32 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 33 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 34 | }, 35 | "node_modules/asynckit": { 36 | "version": "0.4.0", 37 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 38 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 39 | }, 40 | "node_modules/axios": { 41 | "version": "1.7.2", 42 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", 43 | "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", 44 | "dependencies": { 45 | "follow-redirects": "^1.15.6", 46 | "form-data": "^4.0.0", 47 | "proxy-from-env": "^1.1.0" 48 | } 49 | }, 50 | "node_modules/body-parser": { 51 | "version": "1.20.2", 52 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", 53 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", 54 | "dependencies": { 55 | "bytes": "3.1.2", 56 | "content-type": "~1.0.5", 57 | "debug": "2.6.9", 58 | "depd": "2.0.0", 59 | "destroy": "1.2.0", 60 | "http-errors": "2.0.0", 61 | "iconv-lite": "0.4.24", 62 | "on-finished": "2.4.1", 63 | "qs": "6.11.0", 64 | "raw-body": "2.5.2", 65 | "type-is": "~1.6.18", 66 | "unpipe": "1.0.0" 67 | }, 68 | "engines": { 69 | "node": ">= 0.8", 70 | "npm": "1.2.8000 || >= 1.4.16" 71 | } 72 | }, 73 | "node_modules/bytes": { 74 | "version": "3.1.2", 75 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 76 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 77 | "engines": { 78 | "node": ">= 0.8" 79 | } 80 | }, 81 | "node_modules/call-bind": { 82 | "version": "1.0.7", 83 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 84 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 85 | "dependencies": { 86 | "es-define-property": "^1.0.0", 87 | "es-errors": "^1.3.0", 88 | "function-bind": "^1.1.2", 89 | "get-intrinsic": "^1.2.4", 90 | "set-function-length": "^1.2.1" 91 | }, 92 | "engines": { 93 | "node": ">= 0.4" 94 | }, 95 | "funding": { 96 | "url": "https://github.com/sponsors/ljharb" 97 | } 98 | }, 99 | "node_modules/combined-stream": { 100 | "version": "1.0.8", 101 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 102 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 103 | "dependencies": { 104 | "delayed-stream": "~1.0.0" 105 | }, 106 | "engines": { 107 | "node": ">= 0.8" 108 | } 109 | }, 110 | "node_modules/content-disposition": { 111 | "version": "0.5.4", 112 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 113 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 114 | "dependencies": { 115 | "safe-buffer": "5.2.1" 116 | }, 117 | "engines": { 118 | "node": ">= 0.6" 119 | } 120 | }, 121 | "node_modules/content-type": { 122 | "version": "1.0.5", 123 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 124 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 125 | "engines": { 126 | "node": ">= 0.6" 127 | } 128 | }, 129 | "node_modules/cookie": { 130 | "version": "0.6.0", 131 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 132 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 133 | "engines": { 134 | "node": ">= 0.6" 135 | } 136 | }, 137 | "node_modules/cookie-signature": { 138 | "version": "1.0.6", 139 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 140 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 141 | }, 142 | "node_modules/cors": { 143 | "version": "2.8.5", 144 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 145 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 146 | "dependencies": { 147 | "object-assign": "^4", 148 | "vary": "^1" 149 | }, 150 | "engines": { 151 | "node": ">= 0.10" 152 | } 153 | }, 154 | "node_modules/debug": { 155 | "version": "2.6.9", 156 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 157 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 158 | "dependencies": { 159 | "ms": "2.0.0" 160 | } 161 | }, 162 | "node_modules/define-data-property": { 163 | "version": "1.1.4", 164 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 165 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 166 | "dependencies": { 167 | "es-define-property": "^1.0.0", 168 | "es-errors": "^1.3.0", 169 | "gopd": "^1.0.1" 170 | }, 171 | "engines": { 172 | "node": ">= 0.4" 173 | }, 174 | "funding": { 175 | "url": "https://github.com/sponsors/ljharb" 176 | } 177 | }, 178 | "node_modules/delayed-stream": { 179 | "version": "1.0.0", 180 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 181 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 182 | "engines": { 183 | "node": ">=0.4.0" 184 | } 185 | }, 186 | "node_modules/depd": { 187 | "version": "2.0.0", 188 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 189 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 190 | "engines": { 191 | "node": ">= 0.8" 192 | } 193 | }, 194 | "node_modules/destroy": { 195 | "version": "1.2.0", 196 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 197 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 198 | "engines": { 199 | "node": ">= 0.8", 200 | "npm": "1.2.8000 || >= 1.4.16" 201 | } 202 | }, 203 | "node_modules/dotenv": { 204 | "version": "16.4.5", 205 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", 206 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", 207 | "engines": { 208 | "node": ">=12" 209 | }, 210 | "funding": { 211 | "url": "https://dotenvx.com" 212 | } 213 | }, 214 | "node_modules/ee-first": { 215 | "version": "1.1.1", 216 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 217 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 218 | }, 219 | "node_modules/encodeurl": { 220 | "version": "1.0.2", 221 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 222 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 223 | "engines": { 224 | "node": ">= 0.8" 225 | } 226 | }, 227 | "node_modules/es-define-property": { 228 | "version": "1.0.0", 229 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 230 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 231 | "dependencies": { 232 | "get-intrinsic": "^1.2.4" 233 | }, 234 | "engines": { 235 | "node": ">= 0.4" 236 | } 237 | }, 238 | "node_modules/es-errors": { 239 | "version": "1.3.0", 240 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 241 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 242 | "engines": { 243 | "node": ">= 0.4" 244 | } 245 | }, 246 | "node_modules/escape-html": { 247 | "version": "1.0.3", 248 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 249 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 250 | }, 251 | "node_modules/etag": { 252 | "version": "1.8.1", 253 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 254 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 255 | "engines": { 256 | "node": ">= 0.6" 257 | } 258 | }, 259 | "node_modules/express": { 260 | "version": "4.19.2", 261 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", 262 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", 263 | "dependencies": { 264 | "accepts": "~1.3.8", 265 | "array-flatten": "1.1.1", 266 | "body-parser": "1.20.2", 267 | "content-disposition": "0.5.4", 268 | "content-type": "~1.0.4", 269 | "cookie": "0.6.0", 270 | "cookie-signature": "1.0.6", 271 | "debug": "2.6.9", 272 | "depd": "2.0.0", 273 | "encodeurl": "~1.0.2", 274 | "escape-html": "~1.0.3", 275 | "etag": "~1.8.1", 276 | "finalhandler": "1.2.0", 277 | "fresh": "0.5.2", 278 | "http-errors": "2.0.0", 279 | "merge-descriptors": "1.0.1", 280 | "methods": "~1.1.2", 281 | "on-finished": "2.4.1", 282 | "parseurl": "~1.3.3", 283 | "path-to-regexp": "0.1.7", 284 | "proxy-addr": "~2.0.7", 285 | "qs": "6.11.0", 286 | "range-parser": "~1.2.1", 287 | "safe-buffer": "5.2.1", 288 | "send": "0.18.0", 289 | "serve-static": "1.15.0", 290 | "setprototypeof": "1.2.0", 291 | "statuses": "2.0.1", 292 | "type-is": "~1.6.18", 293 | "utils-merge": "1.0.1", 294 | "vary": "~1.1.2" 295 | }, 296 | "engines": { 297 | "node": ">= 0.10.0" 298 | } 299 | }, 300 | "node_modules/finalhandler": { 301 | "version": "1.2.0", 302 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 303 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 304 | "dependencies": { 305 | "debug": "2.6.9", 306 | "encodeurl": "~1.0.2", 307 | "escape-html": "~1.0.3", 308 | "on-finished": "2.4.1", 309 | "parseurl": "~1.3.3", 310 | "statuses": "2.0.1", 311 | "unpipe": "~1.0.0" 312 | }, 313 | "engines": { 314 | "node": ">= 0.8" 315 | } 316 | }, 317 | "node_modules/follow-redirects": { 318 | "version": "1.15.6", 319 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 320 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 321 | "funding": [ 322 | { 323 | "type": "individual", 324 | "url": "https://github.com/sponsors/RubenVerborgh" 325 | } 326 | ], 327 | "engines": { 328 | "node": ">=4.0" 329 | }, 330 | "peerDependenciesMeta": { 331 | "debug": { 332 | "optional": true 333 | } 334 | } 335 | }, 336 | "node_modules/form-data": { 337 | "version": "4.0.0", 338 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 339 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 340 | "dependencies": { 341 | "asynckit": "^0.4.0", 342 | "combined-stream": "^1.0.8", 343 | "mime-types": "^2.1.12" 344 | }, 345 | "engines": { 346 | "node": ">= 6" 347 | } 348 | }, 349 | "node_modules/forwarded": { 350 | "version": "0.2.0", 351 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 352 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 353 | "engines": { 354 | "node": ">= 0.6" 355 | } 356 | }, 357 | "node_modules/fresh": { 358 | "version": "0.5.2", 359 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 360 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 361 | "engines": { 362 | "node": ">= 0.6" 363 | } 364 | }, 365 | "node_modules/function-bind": { 366 | "version": "1.1.2", 367 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 368 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 369 | "funding": { 370 | "url": "https://github.com/sponsors/ljharb" 371 | } 372 | }, 373 | "node_modules/get-intrinsic": { 374 | "version": "1.2.4", 375 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 376 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 377 | "dependencies": { 378 | "es-errors": "^1.3.0", 379 | "function-bind": "^1.1.2", 380 | "has-proto": "^1.0.1", 381 | "has-symbols": "^1.0.3", 382 | "hasown": "^2.0.0" 383 | }, 384 | "engines": { 385 | "node": ">= 0.4" 386 | }, 387 | "funding": { 388 | "url": "https://github.com/sponsors/ljharb" 389 | } 390 | }, 391 | "node_modules/gopd": { 392 | "version": "1.0.1", 393 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 394 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 395 | "dependencies": { 396 | "get-intrinsic": "^1.1.3" 397 | }, 398 | "funding": { 399 | "url": "https://github.com/sponsors/ljharb" 400 | } 401 | }, 402 | "node_modules/has-property-descriptors": { 403 | "version": "1.0.2", 404 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 405 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 406 | "dependencies": { 407 | "es-define-property": "^1.0.0" 408 | }, 409 | "funding": { 410 | "url": "https://github.com/sponsors/ljharb" 411 | } 412 | }, 413 | "node_modules/has-proto": { 414 | "version": "1.0.3", 415 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 416 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 417 | "engines": { 418 | "node": ">= 0.4" 419 | }, 420 | "funding": { 421 | "url": "https://github.com/sponsors/ljharb" 422 | } 423 | }, 424 | "node_modules/has-symbols": { 425 | "version": "1.0.3", 426 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 427 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 428 | "engines": { 429 | "node": ">= 0.4" 430 | }, 431 | "funding": { 432 | "url": "https://github.com/sponsors/ljharb" 433 | } 434 | }, 435 | "node_modules/hasown": { 436 | "version": "2.0.2", 437 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 438 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 439 | "dependencies": { 440 | "function-bind": "^1.1.2" 441 | }, 442 | "engines": { 443 | "node": ">= 0.4" 444 | } 445 | }, 446 | "node_modules/http-errors": { 447 | "version": "2.0.0", 448 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 449 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 450 | "dependencies": { 451 | "depd": "2.0.0", 452 | "inherits": "2.0.4", 453 | "setprototypeof": "1.2.0", 454 | "statuses": "2.0.1", 455 | "toidentifier": "1.0.1" 456 | }, 457 | "engines": { 458 | "node": ">= 0.8" 459 | } 460 | }, 461 | "node_modules/iconv-lite": { 462 | "version": "0.4.24", 463 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 464 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 465 | "dependencies": { 466 | "safer-buffer": ">= 2.1.2 < 3" 467 | }, 468 | "engines": { 469 | "node": ">=0.10.0" 470 | } 471 | }, 472 | "node_modules/inherits": { 473 | "version": "2.0.4", 474 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 475 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 476 | }, 477 | "node_modules/ipaddr.js": { 478 | "version": "1.9.1", 479 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 480 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 481 | "engines": { 482 | "node": ">= 0.10" 483 | } 484 | }, 485 | "node_modules/media-typer": { 486 | "version": "0.3.0", 487 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 488 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 489 | "engines": { 490 | "node": ">= 0.6" 491 | } 492 | }, 493 | "node_modules/merge-descriptors": { 494 | "version": "1.0.1", 495 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 496 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 497 | }, 498 | "node_modules/methods": { 499 | "version": "1.1.2", 500 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 501 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 502 | "engines": { 503 | "node": ">= 0.6" 504 | } 505 | }, 506 | "node_modules/mime": { 507 | "version": "1.6.0", 508 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 509 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 510 | "bin": { 511 | "mime": "cli.js" 512 | }, 513 | "engines": { 514 | "node": ">=4" 515 | } 516 | }, 517 | "node_modules/mime-db": { 518 | "version": "1.52.0", 519 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 520 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 521 | "engines": { 522 | "node": ">= 0.6" 523 | } 524 | }, 525 | "node_modules/mime-types": { 526 | "version": "2.1.35", 527 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 528 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 529 | "dependencies": { 530 | "mime-db": "1.52.0" 531 | }, 532 | "engines": { 533 | "node": ">= 0.6" 534 | } 535 | }, 536 | "node_modules/ms": { 537 | "version": "2.0.0", 538 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 539 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 540 | }, 541 | "node_modules/negotiator": { 542 | "version": "0.6.3", 543 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 544 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 545 | "engines": { 546 | "node": ">= 0.6" 547 | } 548 | }, 549 | "node_modules/object-assign": { 550 | "version": "4.1.1", 551 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 552 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 553 | "engines": { 554 | "node": ">=0.10.0" 555 | } 556 | }, 557 | "node_modules/object-inspect": { 558 | "version": "1.13.1", 559 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 560 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 561 | "funding": { 562 | "url": "https://github.com/sponsors/ljharb" 563 | } 564 | }, 565 | "node_modules/on-finished": { 566 | "version": "2.4.1", 567 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 568 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 569 | "dependencies": { 570 | "ee-first": "1.1.1" 571 | }, 572 | "engines": { 573 | "node": ">= 0.8" 574 | } 575 | }, 576 | "node_modules/parseurl": { 577 | "version": "1.3.3", 578 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 579 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 580 | "engines": { 581 | "node": ">= 0.8" 582 | } 583 | }, 584 | "node_modules/path-to-regexp": { 585 | "version": "0.1.7", 586 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 587 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 588 | }, 589 | "node_modules/proxy-addr": { 590 | "version": "2.0.7", 591 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 592 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 593 | "dependencies": { 594 | "forwarded": "0.2.0", 595 | "ipaddr.js": "1.9.1" 596 | }, 597 | "engines": { 598 | "node": ">= 0.10" 599 | } 600 | }, 601 | "node_modules/proxy-from-env": { 602 | "version": "1.1.0", 603 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 604 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 605 | }, 606 | "node_modules/qs": { 607 | "version": "6.11.0", 608 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 609 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 610 | "dependencies": { 611 | "side-channel": "^1.0.4" 612 | }, 613 | "engines": { 614 | "node": ">=0.6" 615 | }, 616 | "funding": { 617 | "url": "https://github.com/sponsors/ljharb" 618 | } 619 | }, 620 | "node_modules/range-parser": { 621 | "version": "1.2.1", 622 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 623 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 624 | "engines": { 625 | "node": ">= 0.6" 626 | } 627 | }, 628 | "node_modules/raw-body": { 629 | "version": "2.5.2", 630 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 631 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 632 | "dependencies": { 633 | "bytes": "3.1.2", 634 | "http-errors": "2.0.0", 635 | "iconv-lite": "0.4.24", 636 | "unpipe": "1.0.0" 637 | }, 638 | "engines": { 639 | "node": ">= 0.8" 640 | } 641 | }, 642 | "node_modules/safe-buffer": { 643 | "version": "5.2.1", 644 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 645 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 646 | "funding": [ 647 | { 648 | "type": "github", 649 | "url": "https://github.com/sponsors/feross" 650 | }, 651 | { 652 | "type": "patreon", 653 | "url": "https://www.patreon.com/feross" 654 | }, 655 | { 656 | "type": "consulting", 657 | "url": "https://feross.org/support" 658 | } 659 | ] 660 | }, 661 | "node_modules/safer-buffer": { 662 | "version": "2.1.2", 663 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 664 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 665 | }, 666 | "node_modules/send": { 667 | "version": "0.18.0", 668 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 669 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 670 | "dependencies": { 671 | "debug": "2.6.9", 672 | "depd": "2.0.0", 673 | "destroy": "1.2.0", 674 | "encodeurl": "~1.0.2", 675 | "escape-html": "~1.0.3", 676 | "etag": "~1.8.1", 677 | "fresh": "0.5.2", 678 | "http-errors": "2.0.0", 679 | "mime": "1.6.0", 680 | "ms": "2.1.3", 681 | "on-finished": "2.4.1", 682 | "range-parser": "~1.2.1", 683 | "statuses": "2.0.1" 684 | }, 685 | "engines": { 686 | "node": ">= 0.8.0" 687 | } 688 | }, 689 | "node_modules/send/node_modules/ms": { 690 | "version": "2.1.3", 691 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 692 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 693 | }, 694 | "node_modules/serve-static": { 695 | "version": "1.15.0", 696 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 697 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 698 | "dependencies": { 699 | "encodeurl": "~1.0.2", 700 | "escape-html": "~1.0.3", 701 | "parseurl": "~1.3.3", 702 | "send": "0.18.0" 703 | }, 704 | "engines": { 705 | "node": ">= 0.8.0" 706 | } 707 | }, 708 | "node_modules/set-function-length": { 709 | "version": "1.2.2", 710 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 711 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 712 | "dependencies": { 713 | "define-data-property": "^1.1.4", 714 | "es-errors": "^1.3.0", 715 | "function-bind": "^1.1.2", 716 | "get-intrinsic": "^1.2.4", 717 | "gopd": "^1.0.1", 718 | "has-property-descriptors": "^1.0.2" 719 | }, 720 | "engines": { 721 | "node": ">= 0.4" 722 | } 723 | }, 724 | "node_modules/setprototypeof": { 725 | "version": "1.2.0", 726 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 727 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 728 | }, 729 | "node_modules/side-channel": { 730 | "version": "1.0.6", 731 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 732 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 733 | "dependencies": { 734 | "call-bind": "^1.0.7", 735 | "es-errors": "^1.3.0", 736 | "get-intrinsic": "^1.2.4", 737 | "object-inspect": "^1.13.1" 738 | }, 739 | "engines": { 740 | "node": ">= 0.4" 741 | }, 742 | "funding": { 743 | "url": "https://github.com/sponsors/ljharb" 744 | } 745 | }, 746 | "node_modules/statuses": { 747 | "version": "2.0.1", 748 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 749 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 750 | "engines": { 751 | "node": ">= 0.8" 752 | } 753 | }, 754 | "node_modules/toidentifier": { 755 | "version": "1.0.1", 756 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 757 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 758 | "engines": { 759 | "node": ">=0.6" 760 | } 761 | }, 762 | "node_modules/type-is": { 763 | "version": "1.6.18", 764 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 765 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 766 | "dependencies": { 767 | "media-typer": "0.3.0", 768 | "mime-types": "~2.1.24" 769 | }, 770 | "engines": { 771 | "node": ">= 0.6" 772 | } 773 | }, 774 | "node_modules/unpipe": { 775 | "version": "1.0.0", 776 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 777 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 778 | "engines": { 779 | "node": ">= 0.8" 780 | } 781 | }, 782 | "node_modules/utils-merge": { 783 | "version": "1.0.1", 784 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 785 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 786 | "engines": { 787 | "node": ">= 0.4.0" 788 | } 789 | }, 790 | "node_modules/vary": { 791 | "version": "1.1.2", 792 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 793 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 794 | "engines": { 795 | "node": ">= 0.8" 796 | } 797 | } 798 | } 799 | } 800 | -------------------------------------------------------------------------------- /Server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secure-api-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "axios": "^1.7.2", 14 | "cors": "^2.8.5", 15 | "dotenv": "^16.4.5", 16 | "express": "^4.19.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Server/server.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const express = require('express'); 3 | const axios = require('axios'); 4 | const cors = require('cors'); 5 | const app = express(); 6 | const port = 3001; 7 | 8 | const API_KEY = process.env.API_KEY; // Read the API key from environment variables 9 | 10 | app.use(cors()); // Enable CORS 11 | 12 | app.post('/get-access-token', async (req, res) => { 13 | try { 14 | //Ask the server for a secure Access Token 15 | const response = await axios.post('https://api.heygen.com/v1/streaming.create_token', {}, { 16 | headers: { 17 | 'x-api-key': API_KEY 18 | } 19 | }); 20 | res.json({token: response.data.data.token}); // Return the token in the expected structure 21 | } catch (error) { 22 | console.error('Error retrieving access token:', error); 23 | res.status(500).json({ error: 'Failed to retrieve access token' }); 24 | } 25 | }); 26 | 27 | 28 | app.listen(port, () => { 29 | console.log(`Server is running on http://localhost:${port}`); 30 | }); 31 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const path = require('path-browserify'); 2 | const os = require('os-browserify/browser'); 3 | const crypto = require('crypto-browserify'); 4 | 5 | module.exports = function override(config) { 6 | config.resolve.fallback = { 7 | ...config.resolve.fallback, 8 | path: path, 9 | os: os, 10 | crypto: crypto, 11 | }; 12 | return config; 13 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heygen-streaming-avatar-typescript-demo", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "postinstall": "npm install --prefix client && npm install --prefix server", 6 | "start": "concurrently \"npm run server\" \"npm run client\"", 7 | "client": "cd client && npm start", 8 | "server": "cd server && npm start" 9 | }, 10 | "devDependencies": { 11 | "concurrently": "^6.0.0", 12 | "react-app-rewired": "^2.1.8", 13 | "path-browserify": "^1.0.1", 14 | "os-browserify": "^0.3.0", 15 | "crypto-browserify": "^3.12.0" 16 | }, 17 | "dependencies": { 18 | "concurrently": "^6.0.0", 19 | "react-app-rewired": "^2.1.8", 20 | "path-browserify": "^1.0.1", 21 | "os-browserify": "^0.3.0", 22 | "crypto-browserify": "^3.12.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Heygen Streaming Avatar", 3 | "name": "Heygen Streaming Avatar Demo", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .HeyGenStreamingAvatar { 2 | text-align: center; 3 | } 4 | 5 | @media (prefers-reduced-motion: no-preference) { 6 | .App-logo { 7 | animation: App-logo-spin infinite 20s linear; 8 | } 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | 35 | .MediaPlayer{ 36 | width: 80%; 37 | height: 100%; 38 | background-color: black; 39 | margin-top: 50px; 40 | } 41 | 42 | .Actions{ 43 | display: flex; 44 | flex-direction: row; 45 | gap: 10px; 46 | margin: 10px; 47 | } 48 | 49 | .InputField{ 50 | margin: 10px; 51 | width: 400px; 52 | } 53 | 54 | .InputField2 { 55 | margin-left: 10px; 56 | height: 20px; 57 | width: 400px; 58 | } 59 | 60 | .LabelPair{ 61 | height: 50px; 62 | display: row; 63 | flex-direction: row; 64 | align-items: start; 65 | } 66 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Very basic react demo of how to use the HeyGen Streaming Avatar SDK 3 | */ 4 | import { useEffect, useRef, useState } from 'react'; 5 | import { Configuration, NewSessionData, StreamingAvatarApi } from '@heygen/streaming-avatar'; 6 | import './App.css'; 7 | 8 | function App() { 9 | const [stream, setStream] = useState(); 10 | const [debug, setDebug] = useState(); 11 | const avatar = useRef(null); 12 | 13 | const [text, setText] = useState(""); 14 | const [avatarId, setAvatarId] = useState(""); 15 | const [voiceId, setVoiceId] = useState(""); 16 | 17 | const [data, setData] = useState(); 18 | const mediaStream = useRef(null); 19 | 20 | useEffect(() => { 21 | 22 | const startTalkCallback = (e: any) => { 23 | console.log("Avatar started talking", e); 24 | }; 25 | 26 | const stopTalkCallback = (e: any) => { 27 | console.log("Avatar stopped talking", e); 28 | }; 29 | 30 | if (!avatar.current) { 31 | avatar.current = new StreamingAvatarApi( 32 | new Configuration({ accessToken: 'ACCESS_TOKEN' }) 33 | ); 34 | avatar.current.addEventHandler("avatar_start_talking", startTalkCallback); 35 | avatar.current.addEventHandler("avatar_stop_talking", stopTalkCallback); 36 | } 37 | 38 | return () => { 39 | if (avatar.current) { 40 | avatar.current.removeEventHandler("avatar_start_talking", startTalkCallback); 41 | avatar.current.removeEventHandler("avatar_stop_talking", stopTalkCallback); 42 | } 43 | }; 44 | }, []); // Empty dependency array means this effect runs only once when the component mounts 45 | 46 | async function grab() { 47 | try { 48 | const res = await avatar.current!.createStartAvatar( 49 | { 50 | newSessionRequest: { 51 | quality: "low", 52 | avatarName: avatarId, 53 | voice: { voiceId: voiceId } 54 | } 55 | }, 56 | setDebug 57 | ); 58 | setData(res); 59 | setStream(avatar.current!.mediaStream); 60 | } catch (error) { 61 | 62 | } 63 | }; 64 | 65 | async function stop() { 66 | await avatar.current?.stopAvatar({ stopSessionRequest: { sessionId: data?.sessionId } }, setDebug); 67 | } 68 | 69 | useEffect(() => { 70 | if (stream && mediaStream.current) { 71 | mediaStream.current.srcObject = stream; 72 | mediaStream.current.onloadedmetadata = () => { 73 | mediaStream.current!.play(); 74 | setDebug("Playing"); 75 | }; 76 | } 77 | }, [stream]); 78 | 79 | async function handleSpeak() { 80 | await avatar.current?.speak({ taskRequest: { text: text, sessionId: data?.sessionId } }).catch((e) => { 81 | setDebug(e.message); 82 | }); 83 | } 84 | 85 | return ( 86 |
87 |
88 |

{debug}

89 |
90 | 91 | setAvatarId(v.target.value)} /> 92 |
93 |
94 | 95 | setVoiceId(v.target.value)} /> 96 |
97 |
98 | setText(v.target.value)} /> 99 | 100 | 101 | 102 |
103 |
104 | 105 |
106 |
107 |
108 | ); 109 | } 110 | 111 | export default App; 112 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | 15 | button { 16 | background-color: #7559ff; 17 | color: #e4e1ee; 18 | font-size: large; 19 | border: none; 20 | padding-left: 20px; 21 | padding-right: 20px; 22 | padding-top: 15px; 23 | padding-bottom: 15px; 24 | border-radius: 10px; 25 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | const root = ReactDOM.createRoot( 7 | document.getElementById('root') as HTMLElement 8 | ); 9 | root.render( 10 | 11 | 12 | 13 | ); -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------