Build a live call tracker with the sipgate.io node library

David
30.07.2020 0 14:02 min

What is sipgate.io?

sipgate.io is a collection of APIs, which enables sipgate’s customers to build flexible integrations matching their individual needs.
Among other things, it provides interfaces for sending and receiving text messages and faxes, monitoring the call history, as well as initiating and manipulating calls.
The webhook capability of sipgate.io allows for the real-time processing of call data and implementation of applications like IVRs (interactive voice recordings).

In this tutorial

In this tutorial we can learn about the webhook functionality in a real life scenario.
We will build a live call tracker with contact data from a database.
Therefore we will receive incoming calls via the webhook module from the sipgate.io library and look for contact data in a database to display this information on a simple website.

Before we begin

Before we dive into the code, let’s quickly set up your sipgate account for sipgate.io.
For infos on how to do that, check out www.sipgate.io/get-started

To activate webhooks for incoming and outgoing calls, go to console.sipgate.com and select „Webhooks“ from the sidebar on the left.
The „URLs“ tab lets you configure target URLs for these webhooks.
It distinguishes incoming and outgoing calls and also allows for the explicit selection of phonelines that should trigger webhooks.
By default all phonelines are activated.

For the purpose of this application we will only be using the „Incoming“ URL since we don’t want to see outgoing calls on our website.
It can be any publicly accessible URL on the web.
Local addresses like localhost or 127.0.0.1 and any other local network address will not work.

When using webhooks in production you will want to run your code on a proper webserver with a proper address.
However, for the purpose of development we recommend using a service that makes your local environment accessible via the internet.
This makes it much easier to rapidly test out your written code.

There are a various free services that can be used to accomplish this.
Some examples are localhost.run or ngrok.
Either one supplies you with a public URL that you can paste to the sipgate.io console.
Just make sure that you forward the correct port (in this tutorial we’ll be using port 8080, more on that later) and that the provider that you choose offers secure connections through HTTPS.

Set up a project

Now that your sipgate account is all set up, it’s time to start coding.

For this project we will be using the official sipgate.io Node.js library.
It makes working with sipgate’s APIs much easier and also provides a convenient way to set up a webhook server.

But first, let’s create a new Node project.
To do that, create a new directory that will hold our project and inside run npm init -y.

This creates a package.json file containing some metadata for the project.
Open it up and add

"scripts": {
  "start": "node src/index.js"
}

inside the outer curly braces to register a start script that can later be called from the command line.

Now, to install the sipgate.io Node library run npm install -S sipgateio.

That’s it for now.
We will come back and install some more dependencies later.

Webhook server

Let’s get coding!

Create a new file with the name index.js in a src folder.
The following snippet creates an instance of sipgate.io’s webhook module and uses that to create a webhook server on a port configured with webhookServerPort.
Since that port can vary (especially between your local machine and the production server) it may be a good idea to read it from an environment variable.
The serverAddress can be undefined in this case, as we do not want to subscribe to follow up events like the AnswerEvent or the HangUpEvent.

/* src/index.js */
const { createWebhookModule } = require('sipgateio');

const webhookServerPort = process.env.SIPGATE_WEBHOOK_SERVER_PORT || 8080;

const webhookModule = createWebhookModule();

webhookModule
    .createServer({
        port: webhookServerPort,
        serverAddress: undefined,
    })

The createServer function returns a promise of a WebhookServer which can then be used to register callback functions for various call events:

webhookModule
    .createServer({
        port: webhookServerPort,
        serverAddress: undefined,
    })
    .then((webhookServer) => {
        console.log(`Webhook server running\n` + 'Ready for calls 📞');

        webhookServer.onNewCall((newCallEvent) => {
            console.log(`New call from ${newCallEvent.from} to ${newCallEvent.to}`);
        });
    });

Now it’s time for a test drive!

Start up the server with npm start and wait for the message on the console saying that the server is running.

Then call your sipgate number.
You should now see the log of the newCall event.

Now we will be notified of any new call.
However, since we are only interested in those calls, that are not directed to the voicemail, we need to filter those calls.

/* src/index.js */
webhookModule
    .createServer({
        port: webhookServerPort,
        serverAddress: undefined,
    })
    .then((webhookServer) => {
        console.log(`Webhook server running\n` + 'Ready for calls 📞');

        webhookServer.onNewCall((newCallEvent) => {
            if (newCallEvent.user.includes('voicemail')) {
                return;
            }
            console.log(`New call from ${newCallEvent.from} to ${newCallEvent.to}`);
        });
    });

Contacts database

If the callers data exists in the database, we will get his information and later send it to the website.
But first we need to set up the database.
For the sake of simplicity, we choose sqlite3 as a database and fill it with some exemplary data.

For that, install sqlite3 with npm install -S sqlite3 and create the following init_db.js file.

/* init_db.js */
const path = require('path');

const { Database } = require('sqlite3').verbose();

const database = new Database(path.join(__dirname, './database.db'));

database.serialize(() => {
    database.run(`
        DROP TABLE IF EXISTS callers
    `).run(`
        CREATE TABLE callers (
            phonenumber VARCHAR(50) PRIMARY KEY,
            lastname VARCHAR(255),
            firstname VARCHAR(255),
            company VARCHAR(255),
            postal_code VARCHAR(10),
            city VARCHAR(255),
            country VARCHAR(255),
            note CLOB
        );
    `).run(`
        INSERT INTO callers VALUES
            ("+461234567890", "Salander", "Lisbeth", "Milton Security", "104 65", "Stockholm", "Sweden", NULL),
            ("+492116355550", "Mois", "Tim", "sipgate GmbH", "40219", "Düsseldorf", "Germany", "Had some phone issues last time")
            ("+109876543210", NULL, "Neo", "Meta Cortex", "00000", "Capital City", "Matrix", "Is actually asleep"),
        ;
    `)
});

database.close();

In this database initialisation script we just create the needed table and fill the database with some data.
You need to exchange the phone numbers for ones that suits you, so that you can test the application properly later on.
Add the following code snipped to the package.json, then run the command npm run init_db to initialize the database.

"scripts": {
  "start": "node src/index.js"
  "init_db": "node init_db.js",
}

Now there should be a database.db file in the root folder of the project, where the data is stored.

We can now connect our server to the database and write a function that gets the contact data for a given phone number.
The connection to the database is established with:

/* src/index.js */
const path = require('path');
const { Database } = require('sqlite3').verbose();

const database = new Database(path.join(__dirname, '../database.db'));

Please make sure the database is already set up by running npm run init_db.

To get the caller data from the database we need the following function findCaller that returns a Promise so that we obtain the database entry.
When there is no available phone number in the database, findCaller will return undefined.
We can handle that case in the frontend.

/* src/index.js */
const findCaller = (phonenumber) => {
    const query = `SELECT *
        FROM callers
        WHERE phonenumber = ?`;

    return new Promise((resolve, reject) => {
        database.get(query, [phonenumber], (err, row) => {
            err ? reject(err.message) : resolve(row || { phonenumber });
        });
    });
};

We can now use this function to search in the database whenever a new call comes in.
To check the database we need to call it in the event handler function for the NewCallEvent.
Beforehand we want to sanitize the field newCallEvent.from as it does not contain the international calling code prefix +.
For now we print the data to the console.
Later on, we are going to send the data to the website.

/* src/index.js */
webhookModule
    .createServer({
        port: webhookServerPort,
        serverAddress: undefined,
    })
    .then((webhookServer) => {
        console.log(`Webhook server running\n` + 'Ready for calls 📞');

        webhookServer.onNewCall((newCallEvent) => {
            if (newCallEvent.user.includes('voicemail')) {
                return;
            }

            const rawCallerPhonenumber = newCallEvent.from;
            const callerPhonenumber =
                rawCallerPhonenumber === 'anonymous'
                    ? rawCallerPhonenumber
                    : '+' + rawCallerPhonenumber;

            findCaller(callerPhonenumber).then(console.log).catch(console.error);
        });
    });

Let’s check our progress so far.
We recognize incoming calls.
We have a contacts database with some data.
On new calls we check the database for the contact information from the caller.

The next step is to build a little website to display some information about the caller.

Serving the frontend with express.js

As we want to visualize the current caller information on a website, we are going to use an express web server to serve the frontend.

At first, we need to install express.js with npm install -S express.
Then we can create the server in index.js that only has the endpoint GET / which returns an index.html.
We are going to create the necessary files for the website in the next step.

/* src/index.js */
const express = require('express');

const app = express();

app.use(express.static(path.resolve(__dirname, '../public')));

app.get('/', (_, res) => {
    res.sendFile(path.resolve(__dirname, '../public/index.html'));
});

const frontendPort = process.env.FRONTEND_PORT || 3000;
const server = app.listen(frontendPort, () => {
    console.log(`Frontend running at http://localhost:${frontendPort}\n`);
});

Frontend

For the frontend we want a website that is separated into two sections.
One will show the current caller information in a sort of business card.
The other is a table of the call history.

Create a public folder for all the necessary frontend files (index.html, style.css, script.js)

<!-- public/index.html -->

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Who is calling</title>
        <link rel="stylesheet" href="/style.css" />
    </head>
    <body>
        <div>
            <div id="current-caller-card">
                <h1>Who is calling?</h1>
                <div id="current-caller-container">
                    No caller yet...
                </div>
            </div>
            <table>
                <thead>
                    <tr>
                        <th>Time</th>
                        <th>Phonenumber</th>
                        <th>Name</th>
                        <th>Company</th>
                    </tr>
                </thead>
                <tbody id="history-container"></tbody>
            </table>
        </div>

        <script src="/script.js"></script>
    </body>
</html>
/* public/style.css */
body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    font-family: sans-serif;
    padding: 4rem;
    color: #334;
    background-color: #f7f7f7;
}

div p {
    margin: 0;
}

h1 {
    margin-top: 0;
}

table {
    border-collapse: collapse;
}

th {
    border-bottom: 2px solid #d746b9;
    vertical-align: bottom;
    font-weight: bold;
    background: #fff;
}

th,
td {
    padding: 1rem 2rem;
}

tr:nth-child(odd) {
    background-color: #fff;
}

table,
#current-caller-card {
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
    border-radius: 3px;
}

#current-caller-card {
    box-sizing: border-box;
    width: 100%;
    /* min-height: 300px; */
    margin: 2rem 0;
    padding: 3rem;
    background: #fff;
    text-align: center;
    line-height: 1.5rem;
}

#current-caller-container {
    margin-top: 2rem;
}

.name {
    margin-bottom: 0.5rem;
}

.note {
    margin-top: 0.5rem;
}
/* public/script.js

this file is empty for now.
some code will follow in the section "Connect frontend and backend"

 */

Before we continue with the next step, let’s test the frontend delivery.
Start the server with npm run start and visit localhost:${frontendPort}/ in your browser, where ${frontendPort} is either the port you set up in the environment variable FRONTEND_PORT or 3000.
You should then see the layout of the website.

Connect frontend and backend

Until now, the frontend and the backend are not connected.
So calls that come in are not presented on the website.
We will change that with socket.io.
Socket.io is a library for real time, event based communication.
To install it call npm install -S socket.io.
Set up a websocket server in src/index.js like this:

/* src/index.js */
const socketIo = require('socket.io');

const websocketServer = socketIo(server, {
    serveClient: true,
    origins: '*:*',
});

On every NewCallEvent we want to send the found caller data from the database to the frontend.
For that we use the emit function from the websocketServer and wrap it, so that we can call it in the event handler for a NewCallEvent.

/* src/index.js */
const emitCaller = (callerData) => {
    websocketServer.emit('caller', callerData);
};
/* src/index.js */
webhookModule
    .createServer({
        port: webhookServerPort,
        serverAddress: undefined,
    })
    .then((webhookServer) => {
        console.log(`Webhook server running\n` + 'Ready for calls 📞');

        webhookServer.onNewCall((newCallEvent) => {
            if (newCallEvent.user.includes('voicemail')) {
                return;
            }

            const rawCallerPhonenumber = newCallEvent.from;
            const callerPhonenumber =
                rawCallerPhonenumber === 'anonymous'
                    ? rawCallerPhonenumber
                    : '+' + rawCallerPhonenumber;

            /* new: here we call the function `emitCaller` instead of `console.log`*/
            findCaller(callerPhonenumber).then(emitCaller).catch(console.error);
        });
    });

Now that the backend sends the caller information on every NewCallEvent we need to receive it in the frontend to display the information in real time.

That’s where public/script.js comes into play.

First we listen for new data on the channel caller with the function socket.on(‚caller‘, callbackFunction) and process the caller name for a better visualisation when information are missing.

/* public/script.js */
const socket = io('/');

socket.on('caller', (caller) => {
    caller.name =
        caller.firstname && caller.lastname
            ? `${caller.firstname} ${caller.lastname}`
            : caller.firstname || caller.lastname || 'Unknown Caller';

    updateCurrentCaller(caller);
    updateHistory(caller);
});

Within the callback function for socket.on we update the sections for the current caller and the call history respectively.
Let’s dive into these two update functions.

The updateCurrentCaller function fills the current caller information into the caller card by setting the innerHTML of the currentCallerContainer.

/* public/script.js */
const currentCallerContainer = document.querySelector(
    '#current-caller-container'
);

const updateCurrentCaller = (caller) => {
    currentCallerContainer.innerHTML = `
        <p class="name">
            <strong>${caller.name}</strong>
        </p>
        <div class="caller-details">
            <p class="phonenumber">${caller.phonenumber}</p>
            <div class="address">
                <p class="company">${caller.company || ''}</p>
                <p>${caller.postal_code || ''} ${caller.city || ''}</p>
                <p>${caller.country || ''}</p>
                <p class="note">${caller.note || ''}</p>
            </div>
        </div>
    `;
};

The updateHistory function on the other hand prepends a row to the table with historyContainer.prepend(callerRow), so that the more recent calls show up on top of the table.

/* public/script.js */
const historyContainer = document.querySelector('#history-container');

const updateHistory = (caller) => {
    const callerRow = document.createElement('tr');
    callerRow.innerHTML = `
        <td>${new Date().toLocaleTimeString()}</td>
        <td>${caller.phonenumber}</td>
        <td>${caller.name}</td>
        <td>${caller.company || '–'}</td>
    `;
    historyContainer.prepend(callerRow);
};

That’s it! Start up the project with npm run start open up localhost:${frontendPort} with the correct port.
As soon as you call yourself, the caller information will be shown in the browser.

You can check out the whole project here: https://github.com/sipgate-io/io-labs-webhook-who-is-calling

Keine Kommentare


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.