Skip to content
Last updated

Handle incoming calls

In the previous section you added the ability to initiate a call. This section describes how to get notified of and handle incoming calls.

If you have not yet initiated a call, follow the steps in Make an audio call.

Listen for incoming calls

  1. In the call-container of index.html, add the following HTML:

    <button id="answer" style="display: none">Answer</button>
    <button id="hangup" style="display: none">Hang up</button>
  2. Inside the SinchClientWrapper #sinchClientListener onClientStarted callback, add a listener for incoming calls:

    class SinchClientWrapper {
      // ...
      #sinchClientListener() {
        return {
          // ...
          onClientStarted: (sinchClient) => {
            console.log("Sinch - Start client succeded");
            const { callClient } = sinchClient;
            callClient.addListener({
              onIncomingCall: (client, call) => {
                this.ui.onIncomingCall(call);
                this.#callListeners(call);
              },
            });
          },
        };
      }
    }
  3. Inside the UI class, add functions to handle incoming calls and show/hide Answer and Hang Up buttons:

    class UI {
      // ...
      onIncomingCall(call) {
        console.log("Incoming call", call);
        this.audio.srcObject = call.incomingStream;
        this.onAnswer = this.#handleCall("answer", call, this.onAnswer);
        this.onHangup = this.#handleCall("hangup", call, this.onHangup);
      }
    
      onCallEstablished(call) {
        console.log("Call established", call);
        this.#hideElement("answer");
        this.#showElement("hangup");
      }
    
      onCallEnded(call) {
        console.log("Call ended", call);
        this.#hideElement("answer");
        this.#hideElement("hangup");
      }
    
      #handleCall(id, call, oldOnClickFunction) {
        const element = document.getElementById(id);
        element.removeEventListener("click", oldOnClickFunction);
        element.style = "display: block";
        const onClickFunction = () => call[id]();
        element.addEventListener("click", onClickFunction);
        return onClickFunction;
      }
      // ...
    }
  4. If you followed all the steps, the files should look like this:

    index.html

    <html>
      <head>
        <script src="https://cdn.sinch.com/latest/sinch-rtc-min.js"></script>
      </head>
      <body>
        <div id="login-container">
          <input id="userid" placeholder="Enter user id" type="text" />
          <button id="login">Login</button>
        </div>
        <div id="call-container" style="display: none">
          <input id="callee" placeholder="Enter callee" type="text" />
          <button id="call">Call</button>
          <button id="answer" style="display: none">Answer</button>
          <button id="hangup" style="display: none">Hang up</button>
        </div>
        <script type="module" src="index.js"></script>
      </body>
    </html>

    index.js

    const APP_KEY = "enter-application-key";
    const APP_SECRET = "enter-application-secret";
    const ENVIRONMENT_HOST = "ocra.api.sinch.com";
    
    class SinchClientWrapper {
      constructor(userId, ui) {
        this.userId = userId;
        this.ui = ui;
    
        const sinchClient = Sinch.getSinchClientBuilder()
          .applicationKey(APP_KEY)
          .userId(userId)
          .environmentHost(ENVIRONMENT_HOST)
          .build();
    
        sinchClient.addListener(this.#sinchClientListener());
        sinchClient.setSupportManagedPush();
        sinchClient.start();
    
        this.sinchClient = sinchClient;
      }
    
      async makeCall(callee) {
        const call = await this.sinchClient.callClient.callUser(callee);
        this.#callListeners(call);
        return call;
      }
    
      #callListeners(currentCall) {
        currentCall.addListener({
          onCallProgressing: (call) => {
            this.ui.onCallProgressing(call);
          },
          onCallEstablished: (call) => {
            this.ui.onCallEstablished(call);
          },
          onCallEnded: (call) => {
            this.ui.onCallEnded(call);
          },
        });
      }
    
      #sinchClientListener() {
        return {
          onClientStarted: (sinchClient) => {
            console.log("Sinch - Start client succeded");
            const { callClient } = sinchClient;
            callClient.addListener({
              onIncomingCall: (client, call) => {
                this.ui.onIncomingCall(call);
                this.#callListeners(call);
              },
            });
          },
    
          onClientFailed: (sinchClient, error) => {
            console.log("Sinch - Start client failed");
            console.error(error);
          },
    
          /**
           * The recommended way to implement this authentication scheme
           * is that the Application Secret should be kept securely on your
           * server-side backend, the signed token should be created and
           * signed on your server, then passed via a secure channel to
           * the application instance and Sinch client running on a device.
           */
          onCredentialsRequired: (sinchClient, clientRegistration) => {
            new JWT(APP_KEY, APP_SECRET, this.userId)
              .toJwt()
              .then(clientRegistration.register)
              .catch((error) => {
                clientRegistration.registerFailed();
                console.error(error);
              });
          },
        };
      }
    }
    
    class UI {
      constructor() {
        this.#handleLogin();
        this.audio = new Audio();
        this.audio.autoplay = true;
        console.log("UI started");
      }
    
      onIncomingCall(call) {
        console.log("Incoming call", call);
        this.audio.srcObject = call.incomingStream;
        this.onAnswer = this.#handleCall("answer", call, this.onAnswer);
        this.onHangup = this.#handleCall("hangup", call, this.onHangup);
      }
    
      onCallProgressing(call) {
        console.log("Call progressing", call);
      }
    
      onCallEstablished(call) {
        console.log("Call established", call);
        this.#hideElement("answer");
        this.#showElement("hangup");
      }
    
      onCallEnded(call) {
        console.log("Call ended", call);
        this.#hideElement("answer");
        this.#hideElement("hangup");
      }
    
      #handleCall(id, call, oldOnClickFunction) {
        const element = document.getElementById(id);
        element.removeEventListener("click", oldOnClickFunction);
        element.style = "display: block";
        const onClickFunction = () => call[id]();
        element.addEventListener("click", onClickFunction);
        return onClickFunction;
      }
    
      #hideElement(id) {
        const element = document.getElementById(id);
        element.style = "display: none";
      }
    
      #showElement(id) {
        const element = document.getElementById(id);
        element.style = "display: block";
      }
    
      #handleLogin() {
        document.getElementById("login").addEventListener("click", () => {
          const userId = document.getElementById("userid").value;
          this.#hideElement("login-container");
          this.sinchClientWrapper = new SinchClientWrapper(userId, this);
          this.#handleMakeCallClick();
          this.#showElement("call-container");
        });
      }
    
      #handleMakeCallClick() {
        document.getElementById("call").addEventListener("click", async () => {
          const callee = document.getElementById("callee").value;
          const call = await this.sinchClientWrapper.makeCall(callee);
          this.audio.srcObject = call.incomingStream;
          this.onHangup = this.#handleCall("hangup", call, this.onHangup);
        });
      }
    }
    
    new UI();
  5. Open two tabs and log in as two different users. Enter the second user ID as the callee in the first tab and press Call. Click Answer and you should see Call established in the console and hear audio confirmation. Press Hang Up to end or decline a call.

Next steps

Now that you've built a simple app to make and receive calls, learn more about the JavaScript SDK.