Skip to content

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions LobbyCallSupport-Client/README.md
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).
Copy link
Contributor

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


# Design

![Lobby Call Support](./Resources/Lobby_Call_Support_Scenario.jpg)

## 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).
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions LobbyCallSupport-Client/index.html
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>
&nbsp;
<button id="accept-call-button" type="button" disabled="true">
Accept Call
</button>
&nbsp;
<button id="hang-up-button" type="button" disabled="true">
Hang Up
</button>
</div>
<script src="./main.js"></script>
</body>
</html>
199 changes: 199 additions & 0 deletions LobbyCallSupport-Client/index.js
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>";
Copy link
Contributor

Choose a reason for hiding this comment

The 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();
}
}
});
Loading