UNPKG

botbuilder-dialogs-adaptive

Version:

Rule system for the Microsoft BotBuilder dialog system.

347 lines • 16.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OAuthInput = exports.channels = void 0; /** * @module botbuilder-dialogs-adaptive */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const inputDialog_1 = require("./inputDialog"); const templates_1 = require("../templates"); const telemetryLoggerConstants_1 = require("../telemetryLoggerConstants"); const botbuilder_1 = require("botbuilder"); const adaptive_expressions_1 = require("adaptive-expressions"); const botbuilder_dialogs_1 = require("botbuilder-dialogs"); exports.channels = { console: 'console', cortana: 'cortana', directline: 'directline', email: 'email', emulator: 'emulator', facebook: 'facebook', groupme: 'groupme', kik: 'kik', line: 'line', msteams: 'msteams', skype: 'skype', skypeforbusiness: 'skypeforbusiness', slack: 'slack', sms: 'sms', telegram: 'telegram', webchat: 'webchat', }; const persistedOptions = 'options'; const persistedState = 'state'; const persistedExpires = 'expires'; const attemptCountKey = 'attemptCount'; /** * OAuthInput prompts user to login. */ class OAuthInput extends inputDialog_1.InputDialog { /** * Initializes a new instance of the [OAuthInput](xref:botbuilder-dialogs-adaptive.OAuthInput) class * * @param connectionName Optional. Name of the OAuth connection being used. * @param title Optional. Title of the cards signin button. * @param text Optional. Additional text to include on the signin card. * @param timeout Optional. Number of milliseconds the prompt will wait for the user to authenticate. */ constructor(connectionName, title, text, timeout) { super(); /** * (Optional) number of milliseconds the prompt will wait for the user to authenticate. * Defaults to a value `900,000` (15 minutes.) */ this.timeout = new adaptive_expressions_1.IntExpression(900000); this.connectionName = new adaptive_expressions_1.StringExpression(connectionName); this.title = new adaptive_expressions_1.StringExpression(title); this.text = new adaptive_expressions_1.StringExpression(text); if (timeout) { this.timeout = new adaptive_expressions_1.IntExpression(timeout); } } /** * @param property The key of the conditional selector configuration. * @returns The converter for the selector configuration. */ getConverter(property) { switch (property) { case 'connectionName': return new adaptive_expressions_1.StringExpressionConverter(); case 'title': return new adaptive_expressions_1.StringExpressionConverter(); case 'text': return new adaptive_expressions_1.StringExpressionConverter(); case 'timeout': return new adaptive_expressions_1.IntExpressionConverter(); default: return super.getConverter(property); } } /** * Called when a prompt [Dialog](xref:botbuilder-dialogs.Dialog) is pushed onto the dialog stack and is being activated. * * @param dc The [DialogContext](xref:botbuilder-dialogs.DialogContext) for the current turn of conversation. * @param options Optional. Additional information to pass to the prompt being started. * @returns A [DialogTurnResult](xref:botbuilder-dialogs.DialogTurnResult) `Promise` representing the asynchronous operation. */ beginDialog(dc, options) { return __awaiter(this, void 0, void 0, function* () { if (this.disabled && this.disabled.getValue(dc.state)) { return yield dc.endDialog(); } // Ensure prompts have input hint set const o = Object.assign({}, options); if (o.prompt && typeof o.prompt === 'object' && typeof o.prompt.inputHint !== 'string') { o.prompt.inputHint = botbuilder_1.InputHints.AcceptingInput; } if (o.retryPrompt && typeof o.retryPrompt === 'object' && typeof o.retryPrompt.inputHint !== 'string') { o.retryPrompt.inputHint = botbuilder_1.InputHints.AcceptingInput; } const op = yield this.onInitializeOptions(dc, options); dc.state.setValue(botbuilder_dialogs_1.ThisPath.options, op); dc.state.setValue(inputDialog_1.InputDialog.TURN_COUNT_PROPERTY, 0); // If alwaysPrompt is set to true, then clear property value for turn 0. if (this.property && this.alwaysPrompt && this.alwaysPrompt.getValue(dc.state)) { dc.state.deleteValue(this.property.getValue(dc.state)); } // Initialize prompt state const state = dc.activeDialog.state; state[persistedOptions] = o; state[persistedState] = {}; state[persistedState][attemptCountKey] = 0; state[persistedExpires] = new Date().getTime() + this.timeout.getValue(dc.state) || 900000; // Attempt to get the users token const output = yield this.getUserToken(dc); if (output !== undefined) { // Set token into token property if (this.property) { dc.state.setValue(this.property.getValue(dc.state), output); } // Return token return yield dc.endDialog(output); } else { dc.state.setValue(inputDialog_1.InputDialog.TURN_COUNT_PROPERTY, 1); // Prompt user to login yield this.sendOAuthCard(dc, state.options.prompt); return botbuilder_dialogs_1.Dialog.EndOfTurn; } }); } /** * Called when a prompt [Dialog](xref:botbuilder-dialogs.Dialog) is the active dialog and the user replied with a new activity. * * @param dc The [DialogContext](xref:botbuilder-dialogs.DialogContext) for the current turn of conversation. * @returns A [DialogTurnResult](xref:botbuilder-dialogs.DialogTurnResult) `Promise` representing the asynchronous operation. */ continueDialog(dc) { return __awaiter(this, void 0, void 0, function* () { if (!dc) { throw new Error('Missing DialogContext'); } const interrupted = dc.state.getValue(botbuilder_dialogs_1.TurnPath.interrupted, false); const turnCount = dc.state.getValue(inputDialog_1.InputDialog.TURN_COUNT_PROPERTY, 0); // Recognize token const recognized = yield this.recognizeToken(dc); // Check for timeout const state = dc.activeDialog.state; const expires = state[persistedExpires]; const isMessage = dc.context.activity.type === botbuilder_1.ActivityTypes.Message; const isTimeoutActivityType = isMessage || this.isTokenResponseEvent(dc.context) || this.isTeamsVerificationInvoke(dc.context) || this.isTokenExchangeRequestInvoke(dc.context); const hasTimedOut = isTimeoutActivityType && new Date().getTime() > expires; if (hasTimedOut) { if (this.property) { dc.state.deleteValue(this.property.getValue(dc.state)); } return yield dc.endDialog(undefined); } else { const promptState = state[persistedState]; const promptOptions = state[persistedOptions]; promptState[attemptCountKey] += 1; // Validate the return value let inputState = inputDialog_1.InputState.invalid; if (recognized.succeeded) { inputState = inputDialog_1.InputState.valid; } // Return recognized value or re-prompt if (inputState === inputDialog_1.InputState.valid) { // Set token into token property if (this.property) { dc.state.setValue(this.property.getValue(dc.state), recognized.value); } return yield dc.endDialog(recognized.value); } else if (!this.maxTurnCount || turnCount < this.maxTurnCount.getValue(dc.state)) { if (!interrupted) { // increase the turnCount as last step dc.state.setValue(inputDialog_1.InputDialog.TURN_COUNT_PROPERTY, turnCount + 1); if (isMessage) { const prompt = yield this.onRenderPrompt(dc, inputState); yield dc.context.sendActivity(prompt); } } if (isMessage) { yield this.sendOAuthCard(dc, promptOptions === null || promptOptions === void 0 ? void 0 : promptOptions.prompt); } return botbuilder_dialogs_1.Dialog.EndOfTurn; } else { if (this.defaultValue) { const { value } = this.defaultValue.tryGetValue(dc.state); if (this.defaultValueResponse) { const response = yield this.defaultValueResponse.bind(dc, dc.state); const properties = { template: JSON.stringify(this.defaultValueResponse), result: response ? JSON.stringify(response) : '', context: telemetryLoggerConstants_1.TelemetryLoggerConstants.OAuthInputResultEvent, }; this.telemetryClient.trackEvent({ name: telemetryLoggerConstants_1.TelemetryLoggerConstants.GeneratorResultEvent, properties, }); yield dc.context.sendActivity(response); } // set output property dc.state.setValue(this.property.getValue(dc.state), value); return yield dc.endDialog(value); } } return yield dc.endDialog(); } }); } /** * Attempts to retrieve the stored token for the current user. * * @param dc Context reference the user that's being looked up. * @param code (Optional) login code received from the user. * @returns A promise representing the asynchronous operation. */ getUserToken(dc, code) { return new botbuilder_dialogs_1.OAuthPrompt(this.constructor.name, { title: undefined, connectionName: this.connectionName.getValue(dc.state), }).getUserToken(dc.context, code); } /** * Signs the user out of the service. * * @remarks * This example shows creating an instance of the prompt and then signing out the user. * * ```JavaScript * const prompt = new OAuthPrompt({ * connectionName: 'GitConnection', * title: 'Login To GitHub' * }); * await prompt.signOutUser(context); * ``` * @param dc Context referencing the user that's being signed out. * @returns A promise representing the asynchronous operation. */ signOutUser(dc) { return __awaiter(this, void 0, void 0, function* () { return new botbuilder_dialogs_1.OAuthPrompt(this.constructor.name, { title: undefined, connectionName: this.connectionName.getValue(dc.state), }).signOutUser(dc.context); }); } onComputeId() { return `OAuthInput[${this.prompt && this.prompt.toString()}]`; } /** * @protected * Called when input has been received. * @param _dc The [DialogContext](xref:botbuilder-dialogs.DialogContext) for the current turn of conversation. */ onRecognizeInput(_dc) { throw new Error('Method not implemented.'); } sendOAuthCard(dc, prompt) { var _a, _b, _c, _d, _e; return __awaiter(this, void 0, void 0, function* () { // Save state prior to sending OAuthCard: the invoke response for a token exchange from the root bot could come // in before this method ends or could land in another instance in scale-out scenarios, which means that if the // state is not saved, the OAuthInput would not be at the top of the stack, and the token exchange invoke would // get discarded. const conversationState = dc.context.turnState.get('ConversationState'); if (conversationState) { yield conversationState.saveChanges(dc.context, false); } // Prepare oauth card let title = (_a = (yield new templates_1.TextTemplate(this.title.expressionText).bind(dc, dc.state))) !== null && _a !== void 0 ? _a : this.title.getValue(dc.state); if (title === null || title === void 0 ? void 0 : title.startsWith('=')) { title = (_b = adaptive_expressions_1.Expression.parse(title).tryEvaluate(dc.state)) === null || _b === void 0 ? void 0 : _b.value; } let text = (_c = (yield new templates_1.TextTemplate(this.text.expressionText).bind(dc, dc.state))) !== null && _c !== void 0 ? _c : this.text.getValue(dc.state); if (text === null || text === void 0 ? void 0 : text.startsWith('=')) { text = (_d = adaptive_expressions_1.Expression.parse(text).tryEvaluate(dc.state)) === null || _d === void 0 ? void 0 : _d.value; } const settings = { connectionName: (_e = this.connectionName) === null || _e === void 0 ? void 0 : _e.getValue(dc.state), title, text, }; // Send OAuthCard to root bot. The root bot could attempt to do a token exchange or if it cannot do token // exchange for this connection it will let the card get to the user to allow them to sign in. return botbuilder_dialogs_1.OAuthPrompt.sendOAuthCard(settings, dc.context, prompt); }); } recognizeToken(dc) { return __awaiter(this, void 0, void 0, function* () { return new botbuilder_dialogs_1.OAuthPrompt(this.constructor.name, { title: undefined, connectionName: this.connectionName.getValue(dc.state), }).recognizeToken(dc); }); } isTokenResponseEvent(context) { const activity = context.activity; return activity.type === botbuilder_1.ActivityTypes.Event && activity.name === botbuilder_1.tokenResponseEventName; } isTeamsVerificationInvoke(context) { const activity = context.activity; return activity.type === botbuilder_1.ActivityTypes.Invoke && activity.name === botbuilder_1.verifyStateOperationName; } isTokenExchangeRequestInvoke(context) { const activity = context.activity; return activity.type === botbuilder_1.ActivityTypes.Invoke && activity.name === botbuilder_1.tokenExchangeOperationName; } isTokenExchangeRequest(obj) { if (Object.hasOwnProperty.call(obj, 'token')) { return true; } return false; } sendInvokeResponse(turnContext, status, body) { return __awaiter(this, void 0, void 0, function* () { yield turnContext.sendActivity({ type: 'invokeResponse', value: { status, body, }, }); }); } } exports.OAuthInput = OAuthInput; OAuthInput.$kind = 'Microsoft.OAuthInput'; //# sourceMappingURL=oauthInput.js.map