Accessing/Updating SharePoint Online Data In Azure Chat Bot Framework With NodeJS
Why would we need to connect the SharePoint bot?
High-level design of the solution
- Chatbot using Bot Framework using Form flow template in Node JS
- Node module ‘csom-node’ to connect to SharePoint. The library provides a SharePoint Client Object Model (CSOM) API for Node.js applications.
- Authenticate via user credentials or authenticate with app principal credentials (client id & client secret). We will use the first approach via user credential, though we will also see how to do it via app principal credentials.
- Login to Azure portal
- Select your Web App Bot
- Click on Build under Bot Management from the left pane.
- Use Open online code Editor.
- Open kudu console
- It will open the console in a new window like below.
- Go to path – cd D:\home\site\wwwroot>
- Run install command
npm i csom-node
Alternatively, if you want to use app principal install it via the below command. This is a module from my GitHub repo. The reason behind using my repo is explained at the end of the article.
npm install --save csom-node@siddharth-vaghasia/CSOMNode#master
After installation you will get a success message, for me, as it was already installed, it says it updated 1 package.
We could also ask the user to enter username and password one by one and then use it rather than hardcoding in code, but as it would have increase dialog flow code, I have kept it simple.
N//Connect to SharePoint, take url as parameters and return Site Title
function ConnectToSP(siteurl, fn) {
var csomapi = require('csom-node');
var settings = {
url: siteurl,
username: "[email protected]",
password: "JokesOnYou"
};
csomapi.setLoaderOptions({ url: settings.url }); //set CSOM library settings
var authCtx = new AuthenticationContext(settings.url);
authCtx.acquireTokenForUser(settings.username, settings.password, function (err, data) {
console.log(JSON.stringify(data));
var ctx = new SP.ClientContext(siteurl); //SM
authCtx.setAuthenticationCookie(ctx); //authenticate
//retrieve SP.Web client object
var web = ctx.get_web();
ctx.load(web);
ctx.executeQueryAsync(function () {
console.log(web.get_title());
fn(web.get_title());
},
function (sender, args) {
console.log('An error occured: ' + args.get_message());
});
});
}
bot.dialog('/', [
function (session) {
builder.Prompts.text(session, "Hello... What's your name?");
},
function (session, results) {
session.userData.name = results.response;
session.send("Hi " + results.response);
builder.Prompts.text(session,"Please enter SharePoint Online Site Colllection URL");
},
function (session, results) {
session.userData.siteurl = results.response;
ConnectToSP(session.userData.siteurl, function (siteTitle) {
session.send("Connected - Your site name is " + siteTitle);
});
}
]);
Test your bot again and if everything is correct, it will show the below output.
function GetLists(siteurl, fn)
{
var csomapi = require('csom-node');
var settings = {
url: siteurl,
username: "[email protected]",
password: "JokesOnYou"
};
csomapi.setLoaderOptions({ url: settings.url }); //set CSOM library settings
var authCtx = new AuthenticationContext(settings.url);
authCtx.acquireTokenForUser(settings.username, settings.password, function (err, data) {
var ctx = new SP.ClientContext(siteurl); //SM
authCtx.setAuthenticationCookie(ctx); //authenticate
var web = ctx.get_web();
var lists = web.get_lists();
var libraries = [];
ctx.load(lists);
ctx.executeQueryAsync(
function(){
var listEnumerator = lists.getEnumerator();
while (listEnumerator.moveNext())
{
var oListItem = listEnumerator.get_current();
if(!oListItem.get_hidden()){ // do not add hidden list
//Document Library Template = 101, Custom Lists Template = 100
// This is just to demostrate we can use other properties as well available in CSOM
if (oListItem.get_baseTemplate() == 101) {
var libname = oListItem.get_title();
libraries.push(libname);
}
else if (oListItem.get_baseTemplate() == 100) {
var listname = oListItem.get_title();
libraries.push(listname);
}
}
}
fn(libraries);
},
logError);
function logError(sender,args){
console.log(args.get_message());
}
});
}
bot.dialog('/', [
function (session) {
builder.Prompts.text(session, "Hello... What's your name?");
},
function (session, results) {
session.userData.name = results.response;
session.send("Hi " + results.response);
builder.Prompts.text(session,"Please enter SharePoint Online Site Colllection URL");
},
function (session, results) {
session.userData.siteurl = results.response;
ConnectToSP(session.userData.siteurl, function (siteTitle) {
session.send("Connected - Your site name is " + siteTitle);
builder.Prompts.choice(session, "Do you want to get name of list and libraries?", ['Yes','No']);
});
},
function (session, results) {
if(results.response.entity == 'Yes'){
GetLists(session.userData.siteurl, function (arrayListLibraries) {
builder.Prompts.choice(session, "Below are list and libraries in you site.", arrayListLibraries,{ listStyle: builder.ListStyle.button });
});
}
else{
session.send("Thank you, have a nice day.");
}
},
function (session, results) {
session.send("Connected - You have selected - " + results.response.entity);
session.send("Thank you, have a nice day.");
}
]);
Test the bot again. If everything seems correct, you will get the below output. The screenshot is after the bot is connected to SharePoint.
- Register new app on https://tenant.sharepoint.com/_layouts/15/appregnew.aspx
- Granted permission on https://tenant-admin.sharepoint.com/_layouts/15/appinv.aspx ( by using client Id generated from step 1)
function ConnectToSPViaAppPrincipal(siteurl, fn) {
var csomapi = require('csom-node');
var settings = {
url: siteurl,
clientId: "CLIENTID HERE",
clientSecret: "CLIENT SECRET HERE"
};
// MAKE SURE SITE URL IS ENDING WITH '/', THERE IS BUG IN THIS MODULE, WHERE DUE TO SLASS MISSING acquireTokenForApp
// method was giving 404 file not found.
csomapi.setLoaderOptions({ url: settings.url }); //set CSOM library settings
var authCtx = new AuthenticationContext(settings.url);
authCtx.acquireTokenForApp(settings.clientId, settings.clientSecret, function (err, data) {
console.log("got context");
var ctx = new SP.ClientContext(siteurl); //set it '/' for tenant root web
authCtx.setAuthenticationCookie(ctx); //authenticate
//retrieve SP.Web client object
var web = ctx.get_web();
ctx.load(web);
ctx.executeQueryAsync(function () {
console.log("query ");
console.log(web.get_title());
fn(web.get_title());
},
function (sender, args) {
console.log('An error occured: ' + args.get_message() + args.get_stackTrace());
});
});
}
Note
Please make sure site URL passed to the above method ends with ‘slash’. If the URL does not end with slash ‘/’, the acquireTokenForApp method will return a “404 file not found” error. I spent around 2 hours debugging this issue. So to save others’ time, created a new branch with a fix. If you are going to use App Principal, install csom-node using the below command.
npm install --save csom-node@siddharth-vaghasia/CSOMNode#master
Conclusion
- How to connect Azure bot with SharePoint online.
- What is Node JS FormFlow template?
- How to install node module in Azure bot framework.
- How to connect SharePoint online via User Credentials and via App only Principal.
Note – This article was originally published at this link.