I have found that almost every app for SharePoint can benefit from leveraging a social platform.  It might be as simple as using a social platform for basic profile information/pictures.  The most obvious choice for many app developers is to leverage SharePoint as the social platform.  After all, the app manifest allows developers to request permissions to the social features of SharePoint.  However, Microsoft offers a more feature-rich social platform in Yammer.  Microsoft’s future social investments will be heavily concentrated on Yammer and the Yammer/SharePoint integration.  A glimpse into these investments are detailed in Jared Spataro’s post on the enterprise social roadmap.  By targeting both social platforms, SharePoint app developers greatly increase their customer reach and better position their apps for the direction of the SharePoint platform.  In this post, I will outline some patterns and lessons learned from developing Social Nucleus, an app for SharePoint that can leverage SharePoint or Yammer as its social platform.

App Catalogs

Both SharePoint and Yammer have public and private catalogs for apps.  In SharePoint, internal/private apps can be published in an app catalog site collection and made available within an organization.  Public SharePoint apps can be found in the SharePoint Store, a public marketplace for solutions that have been tested and validated by Microsoft.  In Yammer, the only difference between a public and private app is a “Global” flag.  Apps without this global flag are only available to the home network of the app developer.  Apps that are deployed to the Global App Directory go through a vetting process with Yammer, and if approved, are marked "Global" and listed in directory.  App developers can build one app and have it listed in two app catalogs...very cool!  This is the pattern I took with Social Nucleus, which is listed FREE in both the SharePoint Store and Yammer’s Global App Directory.

SharePoint StoreYammer Global Catalog
   

Yammer Development

If you are interested in developing apps against Yammer, I highly recommend reviewing their API Documentation.  You will find it thorough, very similar to SharePoint, and easy to use.

Authentication

Both SharePoint and Yammer app models leverage OAuth to allow an app to access platform resources on behalf of the user.  When you register a new Yammer app, it looks VERY similar to the appregnew.aspx page used to register apps for SharePoint:

SharePoint App RegistrationYammer App Registration
   

Yammer provides both client-side and server-side authentication approaches, but perhaps the easiest is to leverage the Yammer Login Button in the JavaScript SDK.  Referencing this script (with an app client id) will handle the entire OAuth process against Yammer (including API calls).

Yammer Script Include (note the app client id)

<script type='text/javascript' data-app-id='PRO_APP_ID' src='https://assets.yammer.com/platform/yam.js'></script>

 

Building a hybrid social app with SharePoint/Yammer brings some interesting considerations when it comes to authentication.  This is especially true when an app has two entry points…entry from SharePoint and entry from Yammer.  SharePoint OAuth will require us to know the specific things about how the app is installed in SharePoint, such as the host web URL and app web URL.  These values are typically passed to the app as URL parameters from SharePoint.  Without these parameters, our app will automatically assume the user navigated from Yammer (or entered the app URL directly) and default to Yammer as the social platform.

Check if user came from SharePoint

//get sharepoint app params
var shpt = decodeURIComponent(getQueryStringParameter('shpt'));
var hostweburl = decodeURIComponent(getQueryStringParameter('SPHostUrl'));
appweburl = decodeURIComponent(getQueryStringParameter('SPAppWebUrl'));
if (appweburl != 'null') {
    //only display SharePoint social option if we came from SharePoint
    $('#divShptButton').css('display', 'block');
    $('#divShptButton').click(function () {
        window.location = hostweburl;
    })
}

 

When the app is accessed from SharePoint, we know the user has already authenticated to SharePoint.  However, we don’t know if the user is already authenticated to Yammer (regardless of single sign-on configuration).  As such, our app will check the login status (yam.getLoginStatus) of the user in Yammer and display Yammer’s Login Button accordingly.

Check user's Yammer login status

 

yam.getLoginStatus(function (response) {
    //set the yam auth response so we can later toggle the yammer login button
    if (response.authResponse) {
        yamAuth = true;
    }

 

    //check if we came from SharePoint
    if (appweburl != 'null') {
        if (!yamAuth) {
            //display the login control
            yam.connect.loginButton('#divYammerLogin', function (resp) {
                if (!auth) //this is a hack for double return from the login button
                {
                    auth = true;
                    if (resp.authResponse) {
                        display_type = 'yam';
                        application_start_yammer();
                    }
                }
            });
        }

 

 

APIs

Yammer’s REST APIs are very similar to the REST APIs available in SharePoint.  Both platforms have basic APIs for getting profile information, following/followers, posts, etc.  To minimize the platform-specific logic, I added a layer of abstraction between the social platforms and the user interface using platform independent functions and objects.  The key was to determine the properties my app needed for each entity and how those properties mapped to the json returned from the APIs.  Below is a sample of how I did this for a user.

Function abstraction for users

//################ Get User ################
function get_user(user_id, callback) {
    if (display_type == 'yam')
        get_yammer_user(user_id, function (user) {
            callback(user);
        });
    else
        get_sharepoint_user(user_id, function (user) {
            callback(user);
        });
}
function get_yammer_user(user_id, callback) {
    yam.request({
        url: 'https://www.yammer.com/api/v1/users/' + user_id + '.json',
        method: 'GET',
        success: function (user) {
            callback(parse_yammer_entity(user));
        },
        error: function (err) {
            callback(null)
        }
    });
}
function get_sharepoint_user(user_id, callback) {
    executor.executeAsync({
        url: appweburl + "/_api/SP.UserProfiles.PeopleManager/GetPropertiesFor(accountName=@v)?@v='" + user_id.replace('#', '%23') + "'",
        method: 'GET',
        headers: { 'Accept': 'application/json; odata=verbose' },
        success: function (data) {
            var user = $.parseJSON(data.body);
            callback(parse_sharepoint_entity(user.d));
        },
        error: function (err) {
            callback(null);
        }
    });
}

 

Parse Yammer and SharePoint data into common entity

function parse_yammer_entity(entity) {
    return {
        'name': entity.full_name,
        'id': entity.id,
        'type': entity.type,
        'pic': entity.mugshot_url_template,
        'privacy': entity.privacy,
        'followers': (entity.stats != null) ? entity.stats.followers : null,
        'followers_loaded': 0,
        'followers_more': false,
        'following': (entity.stats != null) ? entity.stats.following : null,
        'following_loaded': 0,
        'following_more': false,
        'members': (entity.stats != null) ? entity.stats.members : null,
        'members_loaded': 0,
        'members_more': false
    };
}
function parse_sharepoint_entity(entity) {
    return {
        'name': entity.DisplayName,
        'id': entity.AccountName,
        'type': 'user',
        'pic': (entity.PictureUrl == null) ? '/style/images/nopic.png' : entity.PictureUrl,
        'privacy': null,
        'followers': 0,
        'followers_loaded': 0,
        'followers_more': false,
        'following': 0,
        'following_loaded': 0,
        'following_more': false,
        'members': 0,
        'members_loaded': 0,
        'members_more': false
    };
}

 

As you can see in the sample above, both platforms have their own APIs for making cross-domain REST calls.  In SharePoint, we use the executeAsync method of the RequestExecutor class (found in the SP.RequestEcecutor.js library).  In Yammer, we use the yam.request method.  Both of these methods do more than just handle cross-domain calls, they also handle all the OAuth/token stuff so that it is transparent to the developer.

Pitfalls and Lessons Learned

Although most of my development on this hybrid app was smooth sailing, there were a few pitfalls I ran into that you can learn from:

Cross-domain Conflicts
It turns out that SharePoint’s RequestExecutor and Yammer’s JavaScript SDK conflict with each other on event listeners used in processing the response on cross-domain REST requests.  I spent hours trying to unregister the conflicts, but ultimately found an easier way to address the issue.  I decided to load the Yammer scripts by default (so I can check for Yammer login status) and redirect the user to the same page (but without the Yammer scripts) if they select SharePoint as the social platform.  This means that only one platform’s libraries are registered at any given time.

Redirect for SharePoint

//handle SharePoint login click
$('#divShptLogin').click(function () {
    //redirect back to page and allow code-behind to strip out Yammer include
    window.location = window.location + '&shpt=1';
});

 

Throttling
Yammer institutes rate limits on their REST APIs.  These limits vary, but are throttled at 10 request in 10 seconds for the profile and following APIs I leveraged in Social Nucleus.  This comes into play for APIs that are paged.  For example, popular users or groups could have thousands of followers in Yammer.  The API for followers brings back blocks of 50 at a time.  I originally looked to load all followers recursively, but quickly ran into these throttled limits.  Instead, I notify the user that that the first 50 are loaded and allow them to load more.

Global Catalog
Before an app for Yammer is flagged as “Global”, it will only function against the home network of the developer.  Many organizations might have multiple networks (ex: divisions) but still want the app private.  Yammer has the ability to mark apps with a hidden “Global” flag so the app can function against multiple networks, but not display in the global app catalog.  If you have this scenario, I would suggest posting a message to the Yammer Developer Network.  Another related roadblock exists with app submissions to the SharePoint Store.  During the validation process for the SharePoint Store, testers will need to validate the app against a test/demo network in Yammer in order to approve the app.  For this to occur, you will likely need Yammer app validation prior to SharePoint app validation

Redirects
Since Yammer uses a static URL redirect during OAuth, I originally found it difficult to manage development and production environments.  My solution was to register two Yammer apps…a development app that redirects to localhost and a production app that redirects my product web application running in Windows Azure.  I used a simple DEBUG check to spit out the correct include.  If you look at the code below, you might find it odd that I used LiteralControl objects instead of .NET's ScriptManager.  Yammer requires an additional attribute (data-app-id) on the script include that required me to do it this way.

Handle Dev and Prod app ids for Yammer

protected void Page_Load(object sender, EventArgs e)
{
    if (Page.Request["shpt"] == null || Page.Request["shpt"] != "1")
    {
        #if DEBUG
            Page.Header.Controls.Add(new LiteralControl("<script type='text/javascript' data-app-id='DEV_APP_ID' src='https://assets.yammer.com/platform/yam.js'></script>"));
        #else
            Page.Header.Controls.Add(new LiteralControl("<script type='text/javascript' data-app-id='PRO_APP_ID' src='https://assets.yammer.com/platform/yam.js'></script>"));
        #endif
    }
}

 

IE Security Zones
IT and InfoSec Pros might love Internet Explorer’s security zones, but I despise them as an app developer.  When SharePoint/Yammer and your app live in different security zones, users can experience inconsistent results (ex: pictures fail to load since they live in another zone).  Even worse…when your app defaults to the “Internet” zone, the entire Yammer OAuth process breaks (popups and cross-window communication is blocked).  The only client-side solution I have found is to put the app in the “trusted sites” security zone.  Chrome and Firefox don't have this security "feature" and thus don't have the issue.  I plan on writing a post on these security zone hardships with apps in the future, but be warned…

Final Thoughts

I hope this post helped illustrate the ease of developing apps for SharePoint AND Yammer.  Yammer is a significant investment to SharePoint and will drive Microsoft’s social direction in the platform.  If you develop apps for SharePoint, consider Yammer in those solutions if they have a social play.  Providing Yammer hooks will give the app greater visibility/reach and better position it for the future.