-
Notifications
You must be signed in to change notification settings - Fork 205
Lobby Call Confirm Sample - web client app to assist server app for confirmation #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
650ffaf
fc1c3bf
43df418
d5d2ece
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
| page_type | languages | products | | ||
| --------- | --------------------------------------- | --------------------------------------------------------------------------- | | ||
| sample | <table><tr><td>DotNet</tr></td></table> | <table><tr><td>azure</td><td>azure-communication-services</td></tr></table> | | ||
|
||
# Call Automation - Lobby Call Support Client Sample | ||
|
||
In this sample, we cover how you can use Call Automation SDK to support Lobby Call where we join Lobby call users to a target call upon confirmation of admin in the target call(from this client app). | ||
|
||
# Design | ||
|
||
 | ||
|
||
## Prerequisites | ||
|
||
- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?WT.mc_id=A261C142F). | ||
- A deployed Communication Services resource. [Create a Communication Services resource](https://docs.microsoft.com/azure/communication-services/quickstarts/create-communication-resource). | ||
- Create Azure AI Multi Service resource. For details, see [Create an Azure AI Multi service](https://learn.microsoft.com/en-us/azure/cognitive-services/cognitive-services-apis-create-account). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not required here |
||
- Create and host a Azure Dev Tunnel. Instructions [here](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need the devtunnel here? |
||
- A server application at [DotNet-LobbyCallSupportSample](https://github.com/Azure-Samples/communication-services-dotnet-quickstarts/tree/users/v-kuppu/LobbyCallSupportSample/LobbyCallSupportSample) that can handle incoming call events and send notifications to the client application. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we might to add these information on the description part to add more details on the purpose of this application |
||
|
||
## Before running the sample for the first time | ||
|
||
1. Open the web client app and sign in with your Azure Communication Services identity. | ||
2. Clone the sample repository by running `git clone https://github.com/Azure-Samples/communication-services-javascript-quickstarts.git`. | ||
3. Navigate to the `LobbyCallSupport-Client` folder in the cloned repository. | ||
```bash | ||
cd communication-services-javascript-quickstarts/LobbyCallSupport-Client | ||
``` | ||
4. Run the application and observe logs at console, keep this application running. | ||
|
||
``` | ||
npx webpack serve --config webpack.config.js | ||
``` | ||
|
||
### Configuring application | ||
|
||
1. `<token>`: Azure Communication Service resource's connection string. | ||
2. `<socket-url>`: Cognitive Service resource's endpoint. | ||
- This is used to play media to the participants in the call. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value description is not relevant to the key. |
||
- For more information, see [Create an Azure AI Multi service](https://learn.microsoft.com/en-us/azure/cognitive-services/cognitive-services-apis-create-account). | ||
|
||
## Run app locally | ||
|
||
1. Run the application with | ||
```npx webpack serve --config webpack.config.js```. | ||
2. Enter the `<token>` in the input box and click on `Connect` button. | ||
3. Once connected, you can start making calls to the target user. | ||
4. Also start a lobby call by entering the `<acsGeneratedIdForLobbyCallReceiver>` in other ACS Test client app at `https://acssampleapp.azurewebsites.net/`. | ||
5. Once the lobby call is started, you can hear the lobby call message followed by a confirm dialog saying `You are currently in a lobby call, we will notify the admin that you are waiting.` in the application. | ||
6. The web socket configured in the application sends your answer to the server app which determines whether move the lobby call participant to the target call running in this session. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Communication Client - Calling Sample</title> | ||
</head> | ||
<body> | ||
<h4>Azure Communication Services</h4> | ||
<h1>Calling Quickstart</h1> | ||
<input | ||
id="token-input" | ||
type="text" | ||
placeholder="User access token" | ||
style="margin-bottom:1em; width: 200px;" | ||
/> | ||
</div> | ||
<button id="token-submit" type="button"> | ||
Submit | ||
</button> | ||
<input | ||
id="callee-id-input" | ||
type="text" | ||
placeholder="Who would you like to call?" | ||
style="margin-bottom:1em; width: 200px; display: block;" | ||
/> | ||
<div> | ||
<button id="call-button" type="button" disabled="true"> | ||
Start Call | ||
</button> | ||
| ||
<button id="accept-call-button" type="button" disabled="true"> | ||
Accept Call | ||
</button> | ||
| ||
<button id="hang-up-button" type="button" disabled="true"> | ||
Hang Up | ||
</button> | ||
</div> | ||
<script src="./main.js"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import { CallClient } from "@azure/communication-calling"; | ||
import { AzureCommunicationTokenCredential } from '@azure/communication-common'; | ||
|
||
let call; | ||
let incomingCall; | ||
let callAgent; | ||
let deviceManager; | ||
let tokenCredential; | ||
const userToken = document.getElementById("token-input"); | ||
const calleeInput = document.getElementById("callee-id-input"); | ||
const submitToken = document.getElementById("token-submit"); | ||
const callButton = document.getElementById("call-button"); | ||
const hangUpButton = document.getElementById("hang-up-button"); | ||
const acceptCallButton = document.getElementById('accept-call-button'); | ||
let callStarted = false; | ||
|
||
submitToken.addEventListener("click", async () => { | ||
const callClient = new CallClient(); | ||
const userTokenCredential = userToken.value; | ||
try { | ||
tokenCredential = new AzureCommunicationTokenCredential(userTokenCredential); | ||
callAgent = await callClient.createCallAgent(tokenCredential); | ||
deviceManager = await callClient.getDeviceManager(); | ||
await deviceManager.askDevicePermission({ audio: true }); | ||
callButton.disabled = false; | ||
submitToken.disabled = true; | ||
// Listen for an incoming call to accept. | ||
callAgent.on('incomingCall', async (args) => { | ||
try { | ||
incomingCall = args.incomingCall; | ||
acceptCallButton.disabled = false; | ||
callButton.disabled = true; // tracked call state | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
}); | ||
} catch(error) { | ||
window.alert("Please submit a valid token!"); | ||
} | ||
}) | ||
|
||
callButton.addEventListener("click", () => { | ||
// start a call | ||
const userToCall = calleeInput.value; | ||
call = callAgent.startCall( | ||
[{ id: userToCall }], | ||
{} | ||
); | ||
callStarted = true; | ||
// toggle button states | ||
hangUpButton.disabled = false; | ||
callButton.disabled = true; | ||
}); | ||
|
||
hangUpButton.addEventListener("click", () => { | ||
// end the current call | ||
call.hangUp({ forEveryone: true }); | ||
|
||
// toggle button states | ||
hangUpButton.disabled = true; | ||
callButton.disabled = callStarted = false; // tracked call state | ||
submitToken.disabled = false; | ||
acceptCallButton.disabled = true; | ||
|
||
// Optionally close WebSocket when call ends | ||
// closeWebSocket(); | ||
}); | ||
|
||
acceptCallButton.onclick = async () => { | ||
try { | ||
call = await incomingCall.accept(); | ||
acceptCallButton.disabled = true; | ||
hangUpButton.disabled = false; | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
} | ||
|
||
// Web socket implementation for moving participants from the lobby to the call | ||
// Set this to false if you don't have a WebSocket server running | ||
const enableWebSocket = true; | ||
let url = "<web-socket-url>"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add the websocket url in the env. I dont se the .env file here |
||
let socket = null; | ||
let isConnecting = false; | ||
let reconnectAttempts = 0; | ||
const maxReconnectAttempts = 5; | ||
const reconnectDelay = 3000; // 3 seconds | ||
|
||
function connectWebSocket() { | ||
if (isConnecting || (socket && socket.readyState === WebSocket.OPEN)) { | ||
return; | ||
} | ||
|
||
console.log(`🔄 Attempting to connect to WebSocket: ${url}`); | ||
isConnecting = true; | ||
|
||
try { | ||
socket = new WebSocket(url); | ||
} catch (error) { | ||
console.error("❌ Failed to create WebSocket connection:", error); | ||
isConnecting = false; | ||
return; | ||
} | ||
|
||
socket.onopen = () => { | ||
console.log("✅ WebSocket connected successfully"); | ||
isConnecting = false; | ||
reconnectAttempts = 0; | ||
}; | ||
|
||
socket.onmessage = (event) => { | ||
console.log("🟡 Server says:", event.data); | ||
let reply = "no"; | ||
|
||
try { | ||
if (!callStarted) { | ||
console.log("Call is not active, so no confirm dialog"); | ||
} else { | ||
// Show confirm dialog | ||
console.log("Call is active, showing confirm dialog"); | ||
|
||
// Show confirm dialog | ||
const approved = confirm(event.data); | ||
reply = approved ? "yes" : "no"; | ||
} | ||
|
||
// Send response back only if socket is still open | ||
if (socket && socket.readyState === WebSocket.OPEN) { | ||
socket.send(reply); | ||
} | ||
} catch (error) { | ||
console.error("Error processing message:", error); | ||
} | ||
}; | ||
|
||
socket.onerror = (err) => { | ||
console.error("❌ WebSocket connection error:", err); | ||
console.log("💡 Make sure the WebSocket server is running at:", url); | ||
isConnecting = false; | ||
}; | ||
|
||
socket.onclose = (event) => { | ||
console.log(`❌ WebSocket closed - Code: ${event.code}, Reason: ${event.reason || 'No reason provided'}`); | ||
isConnecting = false; | ||
|
||
// Provide more detailed information about the close reason | ||
if (event.code === 1006) { | ||
console.log("💡 Connection was closed abnormally - server might be unavailable"); | ||
} else if (event.code !== 1000) { | ||
console.log("💡 Connection closed unexpectedly"); | ||
} | ||
|
||
// Only attempt reconnection if it wasn't a clean close (code 1000) | ||
if (event.code !== 1000 && reconnectAttempts < maxReconnectAttempts) { | ||
reconnectAttempts++; | ||
console.log(`🔄 Attempting to reconnect... (${reconnectAttempts}/${maxReconnectAttempts}) in ${reconnectDelay/1000} seconds`); | ||
setTimeout(() => { | ||
connectWebSocket(); | ||
}, reconnectDelay); | ||
} else if (reconnectAttempts >= maxReconnectAttempts) { | ||
console.log("❌ Max reconnection attempts reached. WebSocket functionality will be disabled."); | ||
console.log("💡 To enable WebSocket features, ensure your server is running and refresh the page."); | ||
} | ||
}; | ||
} | ||
|
||
// Function to safely close WebSocket | ||
function closeWebSocket() { | ||
if (socket && socket.readyState === WebSocket.OPEN) { | ||
console.log("🔄 Closing WebSocket connection..."); | ||
socket.close(1000, "Client closing connection"); | ||
} | ||
} | ||
|
||
// Initialize WebSocket connection only if enabled | ||
if (enableWebSocket) { | ||
connectWebSocket(); | ||
} else { | ||
console.log("💡 WebSocket is disabled. To enable lobby features, set enableWebSocket = true and ensure your server is running."); | ||
} | ||
|
||
// Handle page unload to properly close WebSocket | ||
window.addEventListener('beforeunload', () => { | ||
closeWebSocket(); | ||
}); | ||
|
||
// Handle visibility change to manage connection | ||
document.addEventListener('visibilitychange', () => { | ||
if (document.hidden) { | ||
// Page is hidden, optionally close connection | ||
// console.log("📱 Page hidden"); | ||
} else { | ||
// Page is visible, ensure connection is active | ||
// console.log("📱 Page visible"); | ||
if (!socket || socket.readyState === WebSocket.CLOSED) { | ||
// connectWebSocket(); | ||
} | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide the description which is specific to UI application