UNPKG

botbuilder

Version:

Bot Builder is a framework for building rich bots on virtually any platform.

155 lines (144 loc) 6.31 kB
/** * @module botbuilder */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ import { v4 as uuidv4 } from 'uuid'; import { BotFrameworkAdapter } from '../botFrameworkAdapter'; import { Activity, ActivityTypes, BotCallbackHandlerKey, CardFactory, ConversationReference, OAuthCard, OAuthLoginTimeoutMsValue, TokenResponse, TokenPollingSettings, TokenPollingSettingsKey, TurnContext, tokenResponseEventName, } from 'botbuilder-core'; /** * Looks for OAuthCards in Activity attachments and takes action on them */ export class TokenResolver { private static readonly PollingIntervalMs: number = 1000; /** * Checks if we have token responses from OAuth cards. * * @param adapter The [BotFrameworkAdapter](xref:botbuilder.BotFrameworkAdapter). * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for this turn. * @param activity The [Activity](xref:botframework-schema.Activity) to be checked. * @param log Optional. The log to write on. */ static checkForOAuthCards(adapter: BotFrameworkAdapter, context: TurnContext, activity: Activity, log?: string[]) { if (!activity || !activity.attachments) { return; } for (const attachment of activity.attachments) { if (attachment.contentType == CardFactory.contentTypes.oauthCard) { const oauthCard = <OAuthCard>attachment.content; if (!oauthCard.connectionName) { throw new Error("The OAuthPrompt's ConnectionName property is missing a value."); } let pollingTimeoutMs = context.turnState.get(TokenPollingSettingsKey); if (!pollingTimeoutMs) { pollingTimeoutMs = OAuthLoginTimeoutMsValue; } const pollingTimeout: Date = new Date(); pollingTimeout.setMilliseconds(pollingTimeout.getMilliseconds() + pollingTimeoutMs); setTimeout( () => this.pollForToken(adapter, context, activity, oauthCard.connectionName, pollingTimeout, log), TokenResolver.PollingIntervalMs, ); } } } /** * @private */ private static pollForToken( adapter: BotFrameworkAdapter, context: TurnContext, activity: Activity, connectionName: string, pollingTimeout: Date, log?: string[], ) { if (pollingTimeout > new Date()) { const tokenApiClientCredentials = context.turnState.get(adapter.TokenApiClientCredentialsKey); adapter .getUserToken(context, connectionName, null, tokenApiClientCredentials) .then((tokenResponse: TokenResponse) => { let pollingIntervalMs = TokenResolver.PollingIntervalMs; if (tokenResponse) { if (tokenResponse.token) { const logic = context.turnState.get(BotCallbackHandlerKey); const eventActivity = <Activity>( TokenResolver.createTokenResponseActivity( TurnContext.getConversationReference(activity), tokenResponse.token, connectionName, ) ); // received a token, send it to the bot and end polling adapter .processActivityDirect(eventActivity, logic) .then(() => {}) .catch((reason) => { adapter.onTurnError(context, new Error(reason)).then(() => {}); }); if (log) log.push('Returned token'); return; } else if (tokenResponse.properties && tokenResponse.properties[TokenPollingSettingsKey]) { const pollingSettings = <TokenPollingSettings>( tokenResponse.properties[TokenPollingSettingsKey] ); if (pollingSettings.timeout <= 0) { // end polling if (log) log.push('End polling'); return; } if (pollingSettings.interval > 0) { // reset the polling interval if (log) log.push(`Changing polling interval to ${pollingSettings.interval}`); pollingIntervalMs = pollingSettings.interval; } } } if (log) log.push('Polling again'); setTimeout( () => this.pollForToken(adapter, context, activity, connectionName, pollingTimeout), pollingIntervalMs, ); }); } } private static createTokenResponseActivity( relatesTo: Partial<ConversationReference>, token: string, connectionName: string, ): Partial<Activity> { const tokenResponse: Partial<Activity> = { id: uuidv4(), timestamp: new Date(), type: ActivityTypes.Event, serviceUrl: relatesTo.serviceUrl, from: relatesTo.user, recipient: relatesTo.bot, replyToId: relatesTo.activityId, channelId: relatesTo.channelId, conversation: relatesTo.conversation, name: tokenResponseEventName, relatesTo: <ConversationReference>relatesTo, value: { token: token, connectionName: connectionName, }, }; return tokenResponse; } }