botbuilder-dialogs
Version:
A dialog stack based conversation manager for Microsoft BotBuilder.
242 lines • 9.5 kB
JavaScript
/**
* @module botbuilder-dialogs
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
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.SettingsMemoryScope = void 0;
const dialogTurnStateConstants_1 = require("../../dialogTurnStateConstants");
const memoryScope_1 = require("./memoryScope");
const scopePath_1 = require("../scopePath");
/**
* The setting node.
*/
class Node {
/**
* Initializes a new instance of `Node`.
*
* @param {string} value Value of the node. If the node is not leaf, value represents the current path.
*/
constructor(value) {
this.value = value;
/**
* The child nodes of the node.
*/
this.children = [];
}
/**
* Indicates if the node is leaf node.
*
* @returns {boolean} If the node is leaf node or not.
*/
isLeaf() {
return this.children.length === 0;
}
}
/**
* SettingsMemoryScope maps "settings" -> dc.context.turnState['settings']
*/
class SettingsMemoryScope extends memoryScope_1.MemoryScope {
/**
* Initializes a new instance of the [SettingsMemoryScope](xref:botbuilder-dialogs.SettingsMemoryScope) class.
*
* @param initialSettings initial set of settings to supply
*/
constructor(initialSettings) {
super(scopePath_1.ScopePath.settings, false);
this.initialSettings = initialSettings;
}
/**
* Gets the backing memory for this scope.
*
* @param {DialogContext} dc The [DialogContext](xref:botbuilder-dialogs.DialogContext) object for this turn.
* @returns {Record<string, ?>} The memory for the scope.
*/
getMemory(dc) {
var _a, _b;
if (dc.context.turnState.has(scopePath_1.ScopePath.settings)) {
return (_a = dc.context.turnState.get(scopePath_1.ScopePath.settings)) !== null && _a !== void 0 ? _a : {};
}
const configuration = (_b = dc.context.turnState.get(dialogTurnStateConstants_1.DialogTurnStateConstants.configuration)) !== null && _b !== void 0 ? _b : {};
Object.entries(process.env).reduce((result, [key, value]) => {
result[`${key}`] = value;
return result;
}, configuration);
const settings = SettingsMemoryScope.loadSettings(configuration);
dc.context.turnState.set(scopePath_1.ScopePath.settings, settings);
return settings;
}
/**
* @param dc Current dialog context.
*/
load(dc) {
const _super = Object.create(null, {
load: { get: () => super.load }
});
return __awaiter(this, void 0, void 0, function* () {
if (this.initialSettings) {
// filter initialSettings
const filteredSettings = SettingsMemoryScope.filterSettings(this.initialSettings);
dc.context.turnState.set(scopePath_1.ScopePath.settings, filteredSettings);
}
yield _super.load.call(this, dc);
});
}
/**
* Build a dictionary view of configuration providers.
*
* @param {Record<string, string>} configuration The configuration that we are running with.
* @returns {Record<string, ?>} Projected dictionary for settings.
*/
static loadSettings(configuration) {
let settings = {};
if (configuration) {
// load configuration into settings
const root = this.convertFlattenSettingToNode(Object.entries(configuration));
settings = root.children.reduce((acc, child) => (Object.assign(Object.assign({}, acc), { [child.value]: this.convertNodeToObject(child) })), settings);
}
// filter env configuration settings
return this.filterSettings(settings);
}
/**
* Generate a node tree with the flatten settings.
* For example:
* {
* "array":["item1", "item2"],
* "object":{"array":["item1"], "2":"numberkey"}
* }
*
* Would generate a flatten settings like:
* array:0 item1
* array:1 item2
* object:array:0 item1
* object:2 numberkey
*
* After Converting it from flatten settings into node tree, would get:
*
* null
* | |
* array object
* | | | |
* 0 1 array 2
* | | | |
* item1 item2 0 numberkey
* |
* item1
* The result is a Tree.
*
* @param {Array<[string, string]>} kvs Configurations with key value pairs.
* @returns {Node} The root node of the tree.
*/
static convertFlattenSettingToNode(kvs) {
const root = new Node();
kvs.forEach(([key, value]) => {
const keyChain = key.split(':');
let currentNode = root;
keyChain.forEach((item) => {
const matchItem = currentNode.children.find((u) => (u === null || u === void 0 ? void 0 : u.value) === item);
if (!matchItem) {
// Remove all the leaf children
currentNode.children = currentNode.children.filter((u) => u.children.length !== 0);
// Append new child into current node
const node = new Node(item);
currentNode.children.push(node);
currentNode = node;
}
else {
currentNode = matchItem;
}
});
currentNode.children.push(new Node(value));
});
return root;
}
static convertNodeToObject(node) {
if (!node.children.length) {
return {};
}
// If the child is leaf node, return its value directly.
if (node.children.length === 1 && node.children[0].isLeaf()) {
return node.children[0].value;
}
// check if all the children are number format.
let pureNumberIndex = true;
const indexArray = [];
let indexMax = -1;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[Number(i)];
if (/^-?\d+$/.test(child.value)) {
const num = parseInt(child.value, 10);
if (!isNaN(num) && num >= 0) {
indexArray.push(num);
if (num > indexMax) {
indexMax = num;
}
continue;
}
}
pureNumberIndex = false;
break;
}
if (pureNumberIndex) {
// all children are int numbers, treat it as array.
const listResult = new Array(indexMax + 1);
node.children.forEach((child, index) => {
listResult[indexArray[Number(index)]] = this.convertNodeToObject(child);
});
return listResult;
}
// Convert all child into dictionary
return node.children.reduce((result, child) => {
result[child.value] = this.convertNodeToObject(child);
return result;
}, {});
}
static filterSettings(settings) {
const result = Object.assign({}, settings);
this.blockingList.forEach((path) => this.deletePropertyPath(result, path));
return result;
}
static deletePropertyPath(obj, path) {
if (!obj || !(path === null || path === void 0 ? void 0 : path.length)) {
return;
}
const pathArray = path.split(':');
for (let i = 0; i < pathArray.length - 1; i++) {
const realKey = Object.keys(obj).find((key) => key.toLowerCase() === pathArray[i].toLowerCase());
obj = obj[realKey];
if (typeof obj === 'undefined') {
return;
}
}
const lastPath = pathArray.pop().toLowerCase();
const lastKey = Object.keys(obj).find((key) => key.toLowerCase() === lastPath);
delete obj[lastKey];
}
}
exports.SettingsMemoryScope = SettingsMemoryScope;
SettingsMemoryScope.blockingList = [
'MicrosoftAppPassword',
'cosmosDb:authKey',
'blobStorage:connectionString',
'BlobsStorage:connectionString',
'CosmosDbPartitionedStorage:authKey',
'applicationInsights:connectionString',
'applicationInsights:InstrumentationKey',
'runtimeSettings:telemetry:options:connectionString',
'runtimeSettings:telemetry:options:instrumentationKey',
'runtimeSettings:features:blobTranscript:connectionString',
];
//# sourceMappingURL=settingsMemoryScope.js.map
;