boostlingo-js

The boostlingo-js javascript library enables web application developers to embed the Boostlingo caller directly into their own site. This can then be used for placing video or voice calls in the Boostlingo platform.

Getting Started

In order to place calls in Boostlingo, you must have a requestor account. You can then embed boostlingo-js into your front end web application, request a Boostlingo API token from your server, and start making calls.

Installation

Include the boostlingo javascript client in your web app's html. Releases are published and hosted so you can include them directly by using a <script>.

<script src="https://s3-us-west-2.amazonaws.com/connect.boostlingo.com/sdk/boostlingo-js/1.0.3/dist/boostlingo.min.js" type="text/javascript"></script>

Alternate versions of the library are also created that do not bundle twilio-client and twilio-video. We do not recommend this approach since we cannot guarantee version compatibility, but if you follow our guidelines on which twilio libs to include it will absolutely work. Please reach out to us to confirm twilio library versions.

<script src="https://media.twiliocdn.com/sdk/js/client/v1.9/twilio.min.js" type="text/javascript"></script>
<script src="https://media.twiliocdn.com/sdk/js/video/releases/2.0.0/twilio-video.min.js" type="text/javascript"></script>
<script src="https://s3-us-west-2.amazonaws.com/connect.boostlingo.com/sdk/boostlingo-js/1.0.3/dist/boostlingo-without-twilio.min.js" type="text/javascript"></script>

Usage

These steps will guide you through the basic process of placing calls through Boostlingo

Request Boostlingo authentication token

First step is to obtain a boostlingo authentication token from your server. Never store an API token or username/password in your front end code. Your server should be the one that logs the user in, obtains the authentication token, and passes it back down to the web application.

A typical way to do this using jQuery is with an authenticated/secure GET request to your server. Please note, you must use a requestor account credentials to obtain a proper token for placing calls.

var blJS;

$.getJSON("https://YOUR_SERVER.com/get-boostlingo-token")
    .then(data => {
        console.log("Using Boostlingo Token: " + data.authToken);

        // Create an instance of the boostlingo library
        blJS = new BoostLingo(data.authToken);
    });

Obtain Boostlingo authentication token via API endpoint

POST https://app.boostlingo.com/api/web/account/signin

Request Model

{
    "email": "<string>",
    "password": "<string>"
}

Response Model token is what will be needed by the boostlingo-js library

{
    "userAccountId": "<integer>",
    "role": "<string>",
    "token": "<string>",
    "companyAccountId": "<integer>"
}

Create instance of Boostlingo class and load dictionaries

We recommend you do this only once. The Boostlingo library will cache specific data and create instances of classes that do not need to be refreshed very frequently. Expanding on the example above, the next step is typically to pull down the call dictionaries. Whether you expose these directly or are just mapping languages and service types with your internal types, loading these lists will almost definitely be required. In this example we populate a series of select dropdown inputs.

var blJS;

$.getJSON("https://YOUR_SERVER.com/get-boostlingo-token")
    .then(data => {
        console.log("Using Boostlingo Token: " + data.authToken);

        // Create an instance of the boostlingo library
        blJS = new BoostLingo(data.authToken);

        return blJS.getCallDictionaries();
    })
    .then(dict => {
        console.log("Successfully retrieved dictionaries", dict)

        // populate dropdowns with language pairs and service types
        var langFrom = $("#select-lang-from");
        var langTo = $("#select-lang-to");
        dict.languages.forEach(item => {
            var displayName = item.name + (item.isSignLanguage ? " (video only)" : "");
            langFrom.append($("<option>", { value: item.id, text: displayName }));
            langTo.append($("<option>", { value: item.id, text: displayName }));
        });

        var serviceType = $("#select-service-type");
        dict.serviceTypes.forEach(item => {
            serviceType.append($("<option>", { value: item.id, text: item.name }));
        });

        // all loaded, show controls
        $("#call-controls").fadeIn();
    })
    .catch(error => console.log("failure loading library", error));

// Handle "Call" button click
$("#button-call").on("click", function () {
    // Pull call request details from the drop downs we previously populated
    var callReq = {
        languageFromId: parseInt($("#select-lang-from").val()),
        languageToId: parseInt($("#select-lang-to").val()),
        serviceTypeId: parseInt($("#select-service-type").val())
    };

    blJS.makeVoiceCall(callReq))
        .then(call => {
            // event that let's you know when remote participant has joined call
            call.on("callConnected", () => {
                console.log("Interpreter joined call");

                // UI should show call as active
            });

            // event fires when call is completed or cancelled 
            call.on("callCompleted", cancelled => {
                console.log("Call completed " + (cancelled ? "before": "after") + " connect");

                // reset UI back to inactive state
            });

            // event when the remote participant information is available
            call.on("interlocutorInfo", info => {
                console.log("Name:" + info.requiredName + " Company:" + info.companyName + " Rating:" + info.rating);

                // display whatever interpreter information you desire
            });
        })
        .catch(error => console.log("Error making call", error));
});

Example

This is a working example that will demonstrate how to place voice and video calls.

html

index.html

<!doctype html>
<html>
    <head lang='en'>
        <meta charset='utf-8'>
        <meta name='viewport' content='width=device-width'>
        <title>Boostlingo-js Test Page</title>

        <style>
            body {
                font-family: 'Helvetica Neue', Arial, sans-serif;
                color: #333;
                font-weight: 300;
            }

            div {
                margin-top: 10px;
            }

            div video {
                border: 1px solid #000000;
                background-color: #000000;
            }

            div#local-media video {
                height: 100px;
            }

            textarea#log {
                width: 50%;
                height: 200px;
            }
        </style>
    </head>
    <body>
        <h1>Boostlingo-js Test Page</h1>

        <!-- use hosted library -->
        <script src="https://s3-us-west-2.amazonaws.com/connect.boostlingo.com/sdk/boostlingo-js/1.0.3/dist/boostlingo.min.js" type="text/javascript"></script>

        <!-- test code -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <script src="/test/test.js" type="text/javascript"></script>

        <div id="call-controls">
            <div>
                <label for="select-lang-from">Language From:</label>
                <select id="select-lang-from"></select>
            </div>

            <div>
                <label for="select-lang-to">Language To:</label>
                <select id="select-lang-to"></select>
            </div>

            <div>
                <label for="select-service-type">Service Type:</label>
                <select id="select-service-type"></select>
            </div>

            <div>
                <label for="select-gender">Gender:</label>
                <select id="select-gender"></select>
            </div>

            <div>
                <label><input type="radio" name="commType" value="voice" checked="checked"> Voice</label>
                <label><input type="radio" name="commType" value="video"> Video</label>
            </div>

            <div>
                <button id="button-call">Call</button>
                <button id="button-hangup">Hangup</button>
                <button id="button-mute">Mute audio</button>
                <button id="button-unmute">Unmute audio</button>
                <button id="button-pause">Pause video</button>
                <button id="button-unpause">Unpause video</button>
            </div>

            <div id="remote-media"></div>
            <div id="local-media"></div>
        </div>

        <div>
            <textarea id="log"></textarea>
        </div>

    </body>
</html>

Javascript

test.js

$(function () {

    log("Using Boostlingo library version", BoostLingo.version());
    var blJS;
    var currentCall;

    // initially hide all call controls
    $("#call-controls").hide();
    resetCallButtons();

    log("Acquiring Boostlingo auth token...");

    $.getJSON("https://YOUR_SERVER.com/get-boostlingo-token")
        .then(data => {
            console.log("Boostlingo test using Token: " + data.authToken);
            log("Loading boostlingo dictionaries...");

            blJS = new BoostLingo(data.authToken);

            return blJS.getCallDictionaries();
        })
        .then(dict => {
            log("Successfully retrieved dictionaries", dict)

            setupLanguageLists();

            var serviceType = $("#select-service-type");
            dict.serviceTypes.forEach(item => {
                serviceType.append($("<option>", { value: item.id, text: item.name }));
            });

            var gender = $("#select-gender");
            dict.genders.forEach(item => {
                gender.append($("<option>", { value: item.id, text: item.name }));
            });

            // all loaded, show controls
            $("#call-controls").fadeIn();
        })
        .catch(error => log("failed to get dictionaries", error));

    $("#button-call").on("click", function () {
        var callReq = {
            languageFromId: parseInt($("#select-lang-from").val()),
            languageToId: parseInt($("#select-lang-to").val()),
            serviceTypeId: parseInt($("#select-service-type").val()),
            genderId: parseInt($("#select-gender").val())
        };

        var isVideo = $("input[name=commType]:checked").val() === "video";

        log("Placing " + (isVideo ? "video" : "voice") + " call", callReq);

        (isVideo
            ? blJS.makeVideoCall(callReq, $("#remote-media")[0], $("#local-media")[0])
            : blJS.makeVoiceCall(callReq))
        .then(call => {
            log("Successfully made call", call);

            currentCall = call;
            $("#button-call").hide();
            $("#button-hangup").show();

            // event that let's you know when remote participant has joined call
            call.on("callConnected", () => log("Interpreter joined call"));

            // event fires when call is completed or cancelled 
            call.on("callCompleted", cancelled => {
                log("Call completed " + (cancelled ? "before": "after") + " connect");
                currentCall = null;
                resetCallButtons();
            });

            // event when the remote participant information is available
            // once available, can also be obtained with call.getInterlocutorInfo())
            call.on("interlocutorInfo", info => {
                log("Interpreter info available", info);
                log("Name:" + info.requiredName + " Company:" + info.companyName + " Rating:" + info.rating);
            });

            // events for when local and remote audio is shared and/or attached to DOM
            call.on("localAudioShared", isEnabled => {
                log("Started sharing local audio", isEnabled);
                // should just be one button, but showing both for testing purposes
                $("#button-mute").fadeIn();
                $("#button-unmute").fadeIn();
            });

            call.on("remoteAudioShared", isEnabled => log("Started sharing remote audio", isEnabled));

            // events for when local and remote audio is unshared and/or removed from DOM
            call.on("localAudioUnshared", () => log("Local audio unshared"));
            call.on("remoteAudioUnshared", () => log("Remote audio unshared"));

            // events for when local and remote audio is enabled (unmuted)
            // does not fire initially if shared in isEnabled state
            call.on("localAudioEnabled", () => log("Local audio enabled"));  // could toggle mute/unmute
            call.on("remoteAudioEnabled", () => log("Remote audio enabled"));

            // events for when local and remote audio is disabled (muted)
            call.on("localAudioDisabled", () => log("Local audio disabled"));
            call.on("remoteAudioDisabled", () => log("Remote audio disabled"));

            /*
             * Video specific events
             * wouldn't hurt to listen for voice calls, but why bother
             */
            if (call.isVideo()) {
                // events for when local and remote video is shared and/or attached to DOM
                call.on("localVideoShared", isEnabled => {
                    log("Started sharing local video", isEnabled);
                    $("#button-pause").fadeIn();
                    $("#button-unpause").fadeIn();
                });
                call.on("remoteVideoShared", isEnabled => log("Started sharing remote video", isEnabled));

                // events for when local and remote video is unshared and/or removed from DOM
                call.on("localVideoUnshared", () => log("Local video unshared"));
                call.on("remoteVideoUnshared", () => log("Remote video unshared"));

                // events for when local and remote video is enabled (unmuted)
                // does not fire initially if shared in isEnabled state
                call.on("localVideoEnabled", () => log("Local video enabled"));
                call.on("remoteVideoEnabled", () => log("Remote video enabled"));

                // events for when local and remote video is disabled (muted)
                call.on("localVideoDisabled", () => log("Local video disabled"));
                call.on("remoteVideoDisabled", () => log("Remote video disabled"));
            }
        })
        .catch(error => log("Error making call", error));
    });

    $("#button-hangup").on("click", function () {
        log("Hanging up...");
        // current call can also be obtained with blJS.getCurrentCall()
        if (currentCall) {
            currentCall.hangup();
        }
    });

    $("#button-mute").on("click", function () {
        log("Mute...");
        if (currentCall) {
            currentCall.disableAudio();
        }
    });

    $("#button-unmute").on("click", function () {
        log("Unmute...");
        if (currentCall) {
            currentCall.enableAudio();
        }
    });

    $("#button-pause").on("click", function () {
        log("Pause...");
        if (currentCall) {
            currentCall.disableVideo();
        }
    });

    $("#button-unpause").on("click", function () {
        log("Unpause...");
        if (currentCall) {
            currentCall.enableVideo();
        }
    });

    $("input[name=commType]").change(setupLanguageLists);

    function setupLanguageLists() {
        if (!blJS)
            return;

        var commType = $("input[name=commType]:checked").val();
        log("Setting up language lists for", commType);
        (commType === "video" ? blJS.getVideoLanguages() : blJS.getVoiceLanguages())
            .then(langs => { 
                var langFrom = $("#select-lang-from");
                var langTo = $("#select-lang-to");

                // First empty lists
                langFrom.empty();
                langTo.empty();

                // Then repopulate with current list
                langs.forEach(item => {
                    var displayName = item.name + (item.isSignLanguage ? " (sign language)" : "");
                    langFrom.append($("<option>", { value: item.id, text: displayName }));
                    langTo.append($("<option>", { value: item.id, text: displayName }));
                });
            });
    }

    function resetCallButtons() {
        $("#button-call").show();
        $("#button-hangup").hide();
        $("#button-mute").hide();
        $("#button-unmute").hide();
        $("#button-pause").hide();
        $("#button-unpause").hide();
    }

    function log(message, data) {
        if (data != null)
            console.log(message, data);
        else
            console.log(message);

        if (typeof data === "string")
            message += ": " + data;

        var logArea = $("#log");
        logArea.append("> " + message + "\n");
        logArea.scrollTop(logArea[0].scrollHeight - logArea.height());
    }
});