A Complete Project

This tutorial will touch on many of the other playground articles. Refer to these for more information: Drop-in UI, Instant Messaging and Single Sign-On.

This playground article aims to give you a better understanding of the process of adding Weavy to an existing host application, but your use-case will probably not look exactly like this one. Similarly, all applications will impose it's own set of design decisions and technical limitations. This should only be treated as an example.

The following steps are involved in this tutorial:

  • Plan for integrating Weavy components
  • Find and include the Weavy javascript library script.
  • Configure and add the Weavy client.
  • Add panels.
  • Configure Single Sign-On.
You can download the complete source for this example from here and just open it in your browser. You need to make a few changes for everything to work - consult the readme.txt file in the zip-package.

Scenario

You work for a company that sells a CRM system called Grip.

Your boss asked you to put together a live demo of Weavy integrated in Grip. He gave you this list of things to do:

  • Add Weavy instant messaging on all pages.
  • Display a files app on the customer overview page.
  • Enable and configure single sign-on.

Let's get to it! You have a job to do!

Planning

We need to figure out where the Weavy building blocks should be added in Grip. Which UI elements do we need to add? How can we best present the information from Weavy?

This is what Grip looks like:

Our goal here is to add the Weavy components in a way that feels seamless and fits well with our host application.

On the customer overview page, we are asked to add a files app for uploading and creating new files. Looking at the application it seems that the best place to put it is to simply add a new box below the customer address:

Looking at the top, we already have a notifications icon. That would be a good place to add an additional icon for the Weavy Messenger. A red dot signals unread conversations and clicking the icon will slide out the Messenger:

Ok, so we have a plan!

Implementation

Next steps will be to progressively add functionality and building blocks until everything is in place. We will start by adding the Weavy javascript library and the client-side script needed, then we will move on to the Server SDK, and finally we will be adding some custom CSS to complete the project.

You'll need to work with both the Client and Server SDK, so if you haven't already, start by forking our weavy-sln repo.

Adding the Weavy javascript library

Everything starts with the Weavy javascript library - it needs to be added to your application or website in order for Weavy to work. To obtain a copy of the Weavy javascript library you should point your browser to the client configurator in your Weavy installation, i.e. https://{weavyurl}/client.

You will be presented with a form where you can configure the client SDK. As you can see the URL to the client SDK can be modified to get different configurations of the SDK. In this case you should de-select the Extended version.

Now copy the script for Plain HTML and inject it somewhere in your application or website. It should look something like this:

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://{weavyurl}/javascript/weavy.js"></script>

Adding the Weavy Messenger

So, now when the Weavy javascript library is added, we can add the markup we need, instantiate a new Weavy client and load the messenger panel.

We'll start with adding two elements to our markup.

First, an icon that's added next to the existing notification icon in the top bar, so that we can toggle the Messenger. Add the CSS class toggle-messenger to the button, so that we can target it later on. The markup could look something like this:

<li class="notification-list topbar-dropdown d-lg-block">
<button class="nav-link dropdown-toggle arrow-none btn btn-link toggle-messenger">
<i class="dripicons-message noti-icon"></i>
     </button>
</li>

Let's also add a plain div that we can style and that will contain the markup of the messenger panel. Add an id or class attribute, so that we can reference the element later when we create the Weavy client.

<div id="weavy-messenger-container"></div>

Add the CSS below to make the panel slide in from the right.

#weavy-messenger-container {
    position: fixed;
    top: 70px;
    bottom: 0;
    z-index: 1000;
    transition: transform, opacity;
    transition-duration: 0.2s;
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    right: 0;
    width: 20rem;
    transform: translateX(20rem);
    pointer-events: none;
}

#weavy-messenger-container.open {
    transform: none;
    border-left: 1px solid #ccc;
}

Add the script below somewhere after the Weavy javascript library is loaded.

var weavyMessenger = new Weavy( { container: $("#weavy-messenger-container") } );
weavyMessenger.addPanel("messenger-panel", "messenger");

The code above will instantiate a new Weavy client and add a new panel named messenger-panel that displays the Messenger. All markup will be injected into the div we added earlier.

It's recommended to instantiate the Weavy client after the HTML document is loaded and parsed, e.g. after jquery.ready or DOMContentLoaded.

Next we need to hook up an event handler to toggle the panel when clicking the icon.

$(document).on("click", ".toggle-messenger", function() {             
    weavyMessenger.toggle("messenger-panel");
});
This example uses jQuery to hook up to the click event, but you are free to use vanilla javascript or any other javascript frameworks.

Almost there! We need to add event handlers that responds to the open and close events of the Weavy client that toggle's a CSS class that will cause the messenger panel to slide in and out.

// handle client open / close
weavyMessenger.on("open", function() {
    $("#weavy-messenger-container").addClass("open");
});

weavyMessenger.on("close", function() {
    $("#weavy-messenger-container").removeClass("open");
});

Let's also add an event handler for the Weavy badge event. The event is triggered whenever the number of unread conversations change.

When there are unread conversations we will add the has-unread CSS class to the Messenger icon, which we will style later to display a little round dot overlaying the icon.

// hook up to the badge event
weavyMessenger.on("badge", function (e, data) {
    if (data.conversations === 0) {
        $(".toggle-messenger").removeClass("has-unread");
    } else {
        $(".toggle-messenger").addClass("has-unread");
    }
});

That should be it! Weavy messenger is added to Grip ;-)

Adding the Files App

Next order of business is to add the files app to the customer overview page. The files app lets users preview, download and upload a wide variety of file formats.

We need to create an API endpoint on the server side, that takes a unique id, our customer id, and returns a files app. We want to return separate apps for each customer.

Creating the Server API Endpoints

Make sure you have the Weavy solution open in Visual Studio.

You can place the API endpoints wherever you want, but we recommend that you add them to the existing API controller that is included in the solution. Locate and open the file ApiController.cs in the ~\src\Weavy\Api\ folder and add the method below.

[HttpPut]
[Route("customer/{id}/files")]
public FilesApp GetOrCreateFileApp(string id) {
    var space = SpaceService.GetByName(id);
    
    if (space == null) {
        space = SpaceService.Insert(new Space() { Name = id });
    }

    var app = AppService.GetApps(space.Id).FirstOrDefault(x => x is FilesApp) as FilesApp;

    if (app == null) {
        app = AppService.Insert(new FilesApp { Name = "Files" }, space);
    }
    return app;
}

The function will try to locate a FilesApp that is associated with the id you supply. If none is found a new space and app is created. Then the created or existing app is returned as a JSON object.

Don't forget to save and compile your project at this point!

Adding the Weavy Client

Next up is to add another Weavy Client, call our server API and display the files app in a panel.

Start by adding the Weavy Client. Because we will only show the files app on the customer overview page, you will need some way to figure out when to add it. That mechanism will depend on the host application, we'll just give you an example on how it could work.

// customer overview "detection"
var customerId = $(".customer-section").data("customer-id");

if (customerId && customerId.length) {
    // instantiate the client
    var weavyFiles = new Weavy({ container: $(".customer-section").find(".weavy-files-container") });

    $.ajax({
        url: Weavy.defaults.url + "api/customer/" + customerId + "/files",
        method: "PUT",
        contentType: "application/json",
        xhrFields: {
            withCredentials: true
        }
    }).done(function (app) {
        weavyFiles.addPanel("files-app", app.url);
        weavyFiles.open("files-app");
    });
    
} else {
    // we are not on the customer overview page - do nothing    
}

After a simple test to determine if we are in fact on a customer overview page, the Weavy Client is created. We have also made sure that our app is adding a div with a class named weavy-files-container where the Weavy Client's panel should be injected. That div is specified as the client's container.

Next, we make an ajax call to the Weavy Server API endpoint we created earlier, supplying the customerId. The files app to display will be returned.

Then we create a new panel, supplying the URL to the files app. Finally we open the panel.

That's it! We should now have a files app that is fetched and displayed every time a user visits a customer overview page in Grip. Allowing our customers to see and upload new documents that are related to the customer.

Single Sign-on

As a last step we are also adding SSO to seamlessly sign in Grip users into Weavy.

We are going to be creating and passing JSON Web Tokens (JWT) to securly send the needed information between the host application and Weavy.

You can read more about the SSO authentication flow in this playground article.

Configure a Shared Secret

The first thing to do is to decide for a secret string that should be used when signing the tokens. The secret is used by the host application (Grip in this case) when creating the JWT but also by Weavy when decoding the JWT. Hence, the secret that you decide to use must also be set in the weavy.jwt-secret application setting.

<appSettings>
    <add key="weavy.jwt-secret" value="a-shared-secret" />
</appSettings>

Creating a JSON Web Token

For every request in the host application where Weavy is loaded and initialized, you should create a JWT and pass it as an option to the SSO plugin. Depending on what technologies your host application is based on, there are numerous of libraries to help you with the creation of a JWT. Look at the jwt.io page for more information.

The token should never by created with client side scripting since that will risk exposing the secret string.

In this example, we are using JWT.Net library to create the token.

public static class JWTHelper{

    private const string SHARED_SECRET = "mysecretstring123!#";

    public static string GenerateJWT(User user) {
        return new JWT.Builder.JwtBuilder()
                .WithAlgorithm(new HMACSHA256Algorithm())
                .WithSecret(SHARED_SECRET)
                .AddClaim("iss", "unique_host_system_id")
                .AddClaim("sub", user.Id) 
                .AddClaim("email", user.Email)
                .AddClaim("exp", DateTime.UtcNow.AddSeconds(10).ToUnixTime().ToString())
                .Build();    
    }
}

In the example above, a couple of required claims are added to the token. These are:

Claim Description
iss This is a unique id for the host application. This id is used in Weavy to check if the user has an existing Login for that system.
sub This is the user identifier. Should be a identifier for the user in the host application, usually the user id.
email The user's email in the host application. This is used to check if a user exists in Weavy or if the user login should be created.
exp How long the token will be valid. For security reasons this should be set to a short time.

Initialize Weavy Clients

After the token has been created you should supply it to the Weavy script with the SSO plugin.

Let's update our Weavy Client initialization script so the token is specified when creating the Weavy Clients.

// weavyMessenger initalization 
var weavyMessenger = new Weavy({
    container: $("#weavy-messenger-container"),
    plugins: {
        sso: {
            jwt: "@JWTHelper.GenerateJWT()"
        }
    }
});

...

// weavyFiles initialization 
var weavyFiles = new Weavy({ 
    container: $("section.customer").find(".weavy-files-container"),
    plugins: {
        sso: {
            jwt: "@JWTHelper.GenerateJWT()"
        }
    }
});

Conclusion

Hopefully this has given you an idea on how you can plan for and integrate Weavy features into your application!

Below you can find the complete script and CSS for this project.

$(function() {
    var weavyMessenger = new Weavy({
        container: $("#weavy-messenger-container"),
        plugins: {
            sso: {
                jwt: "{YourServerSideGeneratedToken}"
            }
        }
    });
    weavyMessenger.addPanel("messenger-panel", "messenger");

    $(document).on("click", ".toggle-messenger", function() {
        weavyMessenger.toggle("messenger-panel");
    });

    // handle client open / close
    weavyMessenger.on("open", function() {
        $("#weavy-messenger-container").addClass("open");
    });

    weavyMessenger.on("close", function() {
        $("#weavy-messenger-container").removeClass("open");
    });

    // hook up to the badge event
    weavyMessenger.on("badge", function(e, data) {
        if (data.conversations === 0) {
            $(".toggle-messenger").removeClass("has-unread");
        } else {
            $(".toggle-messenger").addClass("has-unread");
        }
    });

    // customer overview "detection"
    var customerId = $(".section-customer").data("id");

    if (customerId && customerId.length) {
        // instantiate the client
        var weavyFiles = new Weavy({
            container: $(".section-customer"),
            plugins: {
                sso: {
                    jwt: "{YourServerSideGeneratedToken}"
                }
            }
        });

        $.ajax({
            url: Weavy.defaults.url + "api/customer/" + customerId + "/files",
            method: "PUT",
            contentType: "application/json",
            xhrFields: {
                withCredentials: true
            }
        }).done(function(app) {
            weavyFiles.addPanel("files-app", app.url);
            weavyFiles.open("files-app");
        });

    } else {
        // we are not on the customer overview page - do nothing    
    }
});
#weavy-messenger-container {
    position: fixed;
    top: 70px;
    bottom: 0;
    z-index: 1000;
    transition: transform, opacity;
    transition-duration: 0.2s;
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
    right: 0;
    width: 20rem;
    transform: translateX(20rem);
    pointer-events: none;
}
        
#weavy-messenger-container.open {
    transform: none;
    border-left: 1px solid #ccc;
}
        
.section-customer {
    width: 100%;
    height: 400px;
}
        
.has-unread {
    position: relative;
}
        
.has-unread::after {
    content: '';
    position: absolute;
    top: 1rem;
    right: 1rem;
    width: .5rem;
    height: .5rem;
    background-color: #e17637;
    border-radius: 50%;
}