You can achieve this by wrapping the Graph API call and result into the result of the AAD login process. The following code is based off of the BotBuilder-Samples 24.bot-authentication-msgraph
sample and BotFramework-WebChat 17.chat-send-history
sample using React.Component.
(Please be aware that the Graph sample currently located in the master branch does not include obtaining the AAD login user's photo. I have a PR which adds this feature into the sample, however I have included it here, as well. I used the WebChat sample as a reference for building the below.)
WebChat
You will need these files from sample #17, followed by the App.js file that needs altering:
- public [folder]
- favicon.ico
- index.html
- manifest.json
- src [folder]
- App.js
- index.css
- index.js
- .env
- package.json
App.js:
Note: I generate the direct line token locally in a separate project. This assumes an AAD profile has a photo.
import React from 'react';
import ReactWebChat, { createDirectLine, createStore} from 'botframework-webchat';
export default class extends React.Component {
constructor(props) {
super(props);
this.state = {
directLine: null,
avatarState: false,
styleOptions: {
botAvatarImage: 'https://docs.microsoft.com/en-us/azure/bot-service/v4sdk/media/logo_bot.svg?view=azure-bot-service-4.0',
botAvatarInitials: 'BF',
userAvatarImage: 'https://github.com/compulim.png?size=64',
userAvatarInitials: 'WC'
}
};
this.store = createStore(
{},
() => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
}
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY'
&& action.payload.activity.attachments
&& 0 in action.payload.activity.attachments
&& this.state.avatarState === false) {
let attachments = action.payload.activity.attachments;
if ('content' in attachments[0] && 'images' in attachments[0].content) {
this.setState(() => ({
styleOptions: {
userAvatarImage: attachments[0].content.images[0].contentUrl
},
avatarState: true
}));
}
}
return next(action);
}
)
}
componentDidMount() {
this.fetchToken();
}
async fetchToken() {
const res = await fetch('http://localhost:3979/directline/token', { method: 'POST' });
const { token } = await res.json();
this.setState(() => ({
directLine: createDirectLine({ token })
}));
}
render() {
return (
this.state.directLine ?
<ReactWebChat
className="chat"
directLine={ this.state.directLine }
styleOptions={ this.state.styleOptions }
store={ this.store }
{ ...this.props }
/>
:
<div>Connecting to bot…</div>
);
}
}
package.json
{
"name": "change-avatar",
"version": "0.1.0",
"private": true,
"homepage": "",
"dependencies": {
"botframework-webchat": ">= 0.0.0-0",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-scripts": "2.1.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
MS Graph Bot
Update the following files in sample #24:
bot.js:
Replace async processStep
with:
async processStep(step) {
const tokenResponse = step.result;
if (tokenResponse !== undefined) {
let parts = await this.commandState.get(step.context);
if (!parts) {
parts = step.context.activity.text;
}
const command = parts.split(' ')[0].toLowerCase();
if (command === 'me') {
await OAuthHelpers.listMe(step.context, tokenResponse);
} else if (command === 'send') {
await OAuthHelpers.sendMail(step.context, tokenResponse, parts.split(' ')[1].toLowerCase());
} else if (command === 'recent') {
await OAuthHelpers.listRecentMail(step.context, tokenResponse);
} else {
let photoResponse = await OAuthHelpers.loginData(step.context, tokenResponse);
const card = CardFactory.heroCard(
`Welcome ${ photoResponse.displayName }, you are now logged in.`,
[photoResponse],
[]
);
const reply = ({ type: ActivityTypes.Message });
reply.attachments = [card];
await step.context.sendActivity(reply);
}
} else {
await step.context.sendActivity(`We couldn't log you in. Please try again later.`);
}
return await step.endDialog();
};
oauth-helpers.js:
Add static async loginData
:
static async loginData(turnContext, tokenResponse) {
if (!turnContext) {
throw new Error('OAuthHelpers.loginData(): `turnContext` cannot be undefined.');
}
if (!tokenResponse) {
throw new Error('OAuthHelpers.loginData(): `tokenResponse` cannot be undefined.');
}
try {
const client = new SimpleGraphClient(tokenResponse.token);
const me = await client.getMe();
const photoResponse = await client.getPhoto();
if (photoResponse != null) {
let replyAttachment;
const base64 = Buffer.from(photoResponse, 'binary').toString('base64');
replyAttachment = {
contentType: 'image/jpeg',
contentUrl: `data:image/jpeg;base64,${ base64 }`
};
replyAttachment.displayName = me.displayName;
return (replyAttachment);
}
} catch (error) {
throw error;
}
}
simple-graph-client.js:
Add async getPhoto
:
async getPhoto() {
return await this.graphClient
.api('/me/photo/$value')
.responseType('ArrayBuffer')
.version('beta')
.get()
.then((res) => {
return res;
})
.catch((err) => {
console.log(err);
});
}
package.json:
Be sure the @microsoft/microsoft-graph-client
installs version 1.0.0 due to breaking changes around AAD 'displayName' acquisition in subsequent versions.
Once the above code was implemented, I was able to login which, upon success, immediately updated the user avatar.
Hope of help!