botbuilder-core
Advanced tools
Comparing version 4.0.0-preview1.2 to 4.0.6
@@ -8,4 +8,4 @@ /** | ||
*/ | ||
import { MiddlewareHandler, Middleware, Promiseable } from './middlewareSet'; | ||
import { Activity, ResourceResponse, ConversationReference } from 'botframework-schema'; | ||
import { Activity, ConversationReference, ResourceResponse } from 'botframework-schema'; | ||
import { Middleware, MiddlewareHandler } from './middlewareSet'; | ||
import { TurnContext } from './turnContext'; | ||
@@ -18,2 +18,3 @@ /** | ||
private middleware; | ||
private turnError; | ||
/** | ||
@@ -43,3 +44,4 @@ * Sends a set of activities to the user. An array of responses form the server will be | ||
*/ | ||
abstract continueConversation(reference: Partial<ConversationReference>, logic: (revocableContext: TurnContext) => Promiseable<void>): Promise<void>; | ||
abstract continueConversation(reference: Partial<ConversationReference>, logic: (revocableContext: TurnContext) => Promise<void>): Promise<void>; | ||
onTurnError: (context: TurnContext, error: Error) => Promise<void>; | ||
/** | ||
@@ -60,3 +62,3 @@ * Registers middleware handlers(s) with the adapter. | ||
*/ | ||
protected runMiddleware(context: TurnContext, next: (revocableContext: TurnContext) => Promiseable<void>): Promise<void>; | ||
protected runMiddleware(context: TurnContext, next: (revocableContext: TurnContext) => Promise<void>): Promise<void>; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* @module botbuilder | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
const internal_1 = require("./internal"); | ||
const middlewareSet_1 = require("./middlewareSet"); | ||
const internal_1 = require("./internal"); | ||
/** | ||
@@ -20,2 +13,8 @@ * Abstract base class for all adapter plugins. Adapters manage the communication between the bot | ||
} | ||
get onTurnError() { | ||
return this.turnError; | ||
} | ||
set onTurnError(value) { | ||
this.turnError = value; | ||
} | ||
/** | ||
@@ -42,8 +41,18 @@ * Registers middleware handlers(s) with the adapter. | ||
const pContext = internal_1.makeRevocable(context); | ||
return this.middleware.run(pContext.proxy, () => { | ||
// Call next with revocable context | ||
return next(pContext.proxy); | ||
}).then(() => { | ||
// Revoke use of context | ||
return new Promise((resolve, reject) => { | ||
this.middleware.run(pContext.proxy, () => { | ||
// Call next with revocable context | ||
return next(pContext.proxy); | ||
}).then(resolve, (err) => { | ||
if (this.onTurnError) { | ||
this.onTurnError(pContext.proxy, err) | ||
.then(resolve, reject); | ||
} | ||
else { | ||
reject(err); | ||
} | ||
}); | ||
}).then(() => pContext.revoke(), (err) => { | ||
pContext.revoke(); | ||
throw err; | ||
}); | ||
@@ -50,0 +59,0 @@ } |
@@ -8,5 +8,22 @@ /** | ||
*/ | ||
export * from 'botframework-schema'; | ||
export * from './autoSaveStateMiddleware'; | ||
export * from './botAdapter'; | ||
export * from './botState'; | ||
export * from './botStatePropertyAccessor'; | ||
export * from './botStateSet'; | ||
export * from './browserStorage'; | ||
export * from './cardFactory'; | ||
export * from './conversationState'; | ||
export * from './memoryStorage'; | ||
export * from './memoryTranscriptStore'; | ||
export * from './messageFactory'; | ||
export * from './middlewareSet'; | ||
export * from './privateConversationState'; | ||
export * from './propertyManager'; | ||
export * from './recognizerResult'; | ||
export * from './storage'; | ||
export * from './testAdapter'; | ||
export * from './transcriptLogger'; | ||
export * from './turnContext'; | ||
export * from 'botframework-schema'; | ||
export * from './userState'; |
@@ -13,6 +13,21 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__export(require("botframework-schema")); | ||
__export(require("./autoSaveStateMiddleware")); | ||
__export(require("./botAdapter")); | ||
__export(require("./botState")); | ||
__export(require("./botStatePropertyAccessor")); | ||
__export(require("./botStateSet")); | ||
__export(require("./browserStorage")); | ||
__export(require("./cardFactory")); | ||
__export(require("./conversationState")); | ||
__export(require("./memoryStorage")); | ||
__export(require("./memoryTranscriptStore")); | ||
__export(require("./messageFactory")); | ||
__export(require("./middlewareSet")); | ||
__export(require("./privateConversationState")); | ||
__export(require("./storage")); | ||
__export(require("./testAdapter")); | ||
__export(require("./transcriptLogger")); | ||
__export(require("./turnContext")); | ||
__export(require("botframework-schema")); | ||
__export(require("./userState")); | ||
//# sourceMappingURL=index.js.map |
@@ -11,8 +11,8 @@ /** | ||
* @private | ||
* @param target | ||
* @param handler | ||
* @param target a thing that will be made revocable | ||
* @param handler an object that defines the way the new revocable object works | ||
*/ | ||
export declare function makeRevocable<T extends Object>(target: T, handler?: ProxyHandler<T>): { | ||
proxy: T; | ||
revoke: () => void; | ||
revoke(): void; | ||
}; |
@@ -22,4 +22,4 @@ "use strict"; | ||
* @private | ||
* @param target | ||
* @param handler | ||
* @param target a thing that will be made revocable | ||
* @param handler an object that defines the way the new revocable object works | ||
*/ | ||
@@ -32,3 +32,5 @@ function makeRevocable(target, handler) { | ||
else { | ||
return { proxy: target, revoke: () => { } }; | ||
return { proxy: target, revoke: () => { | ||
// noop | ||
} }; | ||
} | ||
@@ -35,0 +37,0 @@ } |
import { TurnContext } from './turnContext'; | ||
/** | ||
* Type signature for a return value that can (Optionally) return its value | ||
* asynchronously using a Promise. | ||
* | ||
* ```TypeScript | ||
* type Promiseable <T = void> = Promise<T>|T; | ||
* ``` | ||
* @param T (Optional) type of value being returned. This defaults to `void`. | ||
*/ | ||
export declare type Promiseable<T = void> = Promise<T> | T; | ||
/** | ||
* Interface implemented by object based middleware. | ||
*/ | ||
export interface Middleware { | ||
onTurn(context: TurnContext, next: () => Promise<void>): Promiseable<void>; | ||
onTurn(context: TurnContext, next: () => Promise<void>): Promise<void>; | ||
} | ||
@@ -22,6 +12,6 @@ /** | ||
* ```TypeScript | ||
* type MiddlewareHandler = (context: TurnContext, next: () => Promise<void>) => Promiseable<void>; | ||
* type MiddlewareHandler = (context: TurnContext, next: () => Promise<void>) => Promise<void>; | ||
* ``` | ||
*/ | ||
export declare type MiddlewareHandler = (context: TurnContext, next: () => Promise<void>) => Promiseable<void>; | ||
export declare type MiddlewareHandler = (context: TurnContext, next: () => Promise<void>) => Promise<void>; | ||
/** | ||
@@ -75,3 +65,3 @@ * A set of `Middleware` plugins. | ||
*/ | ||
run(context: TurnContext, next: () => Promiseable<void>): Promise<void>; | ||
run(context: TurnContext, next: () => Promise<void>): Promise<void>; | ||
} |
@@ -0,0 +0,0 @@ "use strict"; |
@@ -8,5 +8,4 @@ /** | ||
*/ | ||
import { Activity, ResourceResponse, ConversationReference } from 'botframework-schema'; | ||
import { Activity, ConversationReference, ResourceResponse } from 'botframework-schema'; | ||
import { BotAdapter } from './botAdapter'; | ||
import { Promiseable } from './middlewareSet'; | ||
/** | ||
@@ -16,6 +15,6 @@ * Signature implemented by functions registered with `context.onSendActivity()`. | ||
* ```TypeScript | ||
* type SendActivitiesHandler = (context: TurnContext, activities: Partial<Activity>[], next: () => Promise<ResourceResponse[]>) => Promiseable<ResourceResponse[]>; | ||
* type SendActivitiesHandler = (context: TurnContext, activities: Partial<Activity>[], next: () => Promise<ResourceResponse[]>) => Promise<ResourceResponse[]>; | ||
* ``` | ||
*/ | ||
export declare type SendActivitiesHandler = (context: TurnContext, activities: Partial<Activity>[], next: () => Promise<ResourceResponse[]>) => Promiseable<ResourceResponse[]>; | ||
export declare type SendActivitiesHandler = (context: TurnContext, activities: Partial<Activity>[], next: () => Promise<ResourceResponse[]>) => Promise<ResourceResponse[]>; | ||
/** | ||
@@ -25,6 +24,6 @@ * Signature implemented by functions registered with `context.onUpdateActivity()`. | ||
* ```TypeScript | ||
* type UpdateActivityHandler = (context: TurnContext, activity: Partial<Activity>, next: () => Promise<void>) => Promiseable<void>; | ||
* type UpdateActivityHandler = (context: TurnContext, activity: Partial<Activity>, next: () => Promise<void>) => Promise<void>; | ||
* ``` | ||
*/ | ||
export declare type UpdateActivityHandler = (context: TurnContext, activity: Partial<Activity>, next: () => Promise<void>) => Promiseable<void>; | ||
export declare type UpdateActivityHandler = (context: TurnContext, activity: Partial<Activity>, next: () => Promise<void>) => Promise<void>; | ||
/** | ||
@@ -34,6 +33,6 @@ * Signature implemented by functions registered with `context.onDeleteActivity()`. | ||
* ```TypeScript | ||
* type DeleteActivityHandler = (context: TurnContext, reference: Partial<ConversationReference>, next: () => Promise<void>) => Promiseable<void>; | ||
* type DeleteActivityHandler = (context: TurnContext, reference: Partial<ConversationReference>, next: () => Promise<void>) => Promise<void>; | ||
* ``` | ||
*/ | ||
export declare type DeleteActivityHandler = (context: TurnContext, reference: Partial<ConversationReference>, next: () => Promise<void>) => Promiseable<void>; | ||
export declare type DeleteActivityHandler = (context: TurnContext, reference: Partial<ConversationReference>, next: () => Promise<void>) => Promise<void>; | ||
export interface TurnContext { | ||
@@ -71,3 +70,3 @@ } | ||
private _respondedRef; | ||
private _services; | ||
private _turnState; | ||
private _onSendActivities; | ||
@@ -84,68 +83,33 @@ private _onUpdateActivity; | ||
/** | ||
* Called when this TurnContext instance is passed into the constructor of a new TurnContext | ||
* instance. Can be overridden in derived classes. | ||
* @param context The context object to copy private members to. Everything should be copied by reference. | ||
*/ | ||
protected copyTo(context: TurnContext): void; | ||
/** | ||
* The adapter for this context. | ||
* Returns the conversation reference for an activity. | ||
* | ||
* @remarks | ||
* This example shows how to send a `typing` activity directly using the adapter. This approach | ||
* bypasses any middleware which sometimes has its advantages. The class to | ||
* `getConversationReference()` and `applyConversationReference()` are to ensure that the | ||
* outgoing activity is properly addressed: | ||
* This can be saved as a plain old JSON object and then later used to message the user | ||
* proactively. | ||
* | ||
* ```JavaScript | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* ``` | ||
* @param activity The activity to copy the conversation reference from | ||
*/ | ||
readonly adapter: BotAdapter; | ||
static getConversationReference(activity: Partial<Activity>): Partial<ConversationReference>; | ||
/** | ||
* The received activity. | ||
* Updates an activity with the delivery information from a conversation reference. | ||
* | ||
* @remarks | ||
* This example shows how to get the users trimmed utterance from the activity: | ||
* Calling this after [getConversationReference()](#getconversationreference) on an incoming | ||
* activity will properly address the reply to a received activity. | ||
* | ||
* ```JavaScript | ||
* const utterance = (context.activity.text || '').trim(); | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* ``` | ||
* @param activity Activity to copy delivery information to. | ||
* @param reference Conversation reference containing delivery information. | ||
* @param isIncoming (Optional) flag indicating whether the activity is an incoming or outgoing activity. Defaults to `false` indicating the activity is outgoing. | ||
*/ | ||
readonly activity: Activity; | ||
static applyConversationReference(activity: Partial<Activity>, reference: Partial<ConversationReference>, isIncoming?: boolean): Partial<Activity>; | ||
/** | ||
* If `true` at least one response has been sent for the current turn of conversation. | ||
* | ||
* @remarks | ||
* This is primarily useful for determining if a bot should run fallback routing logic: | ||
* | ||
* ```JavaScript | ||
* await routeActivity(context); | ||
* if (!context.responded) { | ||
* await context.sendActivity(`I'm sorry. I didn't understand.`); | ||
* } | ||
* ``` | ||
*/ | ||
responded: boolean; | ||
/** | ||
* Map of services and other values cached for the lifetime of the turn. | ||
* | ||
* @remarks | ||
* Middleware, other components, and services will typically use this to cache information | ||
* that could be asked for by a bot multiple times during a turn. The bots logic is free to | ||
* use this to pass information between its own components. | ||
* | ||
* ```JavaScript | ||
* const cart = await loadUsersShoppingCart(context); | ||
* context.services.set('cart', cart); | ||
* ``` | ||
* | ||
* > [!TIP] | ||
* > For middleware and third party components, consider using a `Symbol()` for your cache key | ||
* > to avoid potential naming collisions with the bots caching and other components. | ||
*/ | ||
readonly services: Map<any, any>; | ||
/** | ||
* Sends a single activity or message to the user. | ||
@@ -272,34 +236,69 @@ * | ||
onDeleteActivity(handler: DeleteActivityHandler): this; | ||
private emit<T>(handlers, arg, next); | ||
/** | ||
* Returns the conversation reference for an activity. | ||
* Called when this TurnContext instance is passed into the constructor of a new TurnContext | ||
* instance. Can be overridden in derived classes. | ||
* @param context The context object to copy private members to. Everything should be copied by reference. | ||
*/ | ||
protected copyTo(context: TurnContext): void; | ||
/** | ||
* The adapter for this context. | ||
* | ||
* @remarks | ||
* This can be saved as a plain old JSON object and then later used to message the user | ||
* proactively. | ||
* This example shows how to send a `typing` activity directly using the adapter. This approach | ||
* bypasses any middleware which sometimes has its advantages. The class to | ||
* `getConversationReference()` and `applyConversationReference()` are to ensure that the | ||
* outgoing activity is properly addressed: | ||
* | ||
* ```JavaScript | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* ``` | ||
* @param activity The activity to copy the conversation reference from | ||
*/ | ||
static getConversationReference(activity: Partial<Activity>): Partial<ConversationReference>; | ||
readonly adapter: BotAdapter; | ||
/** | ||
* Updates an activity with the delivery information from a conversation reference. | ||
* The received activity. | ||
* | ||
* @remarks | ||
* Calling this after [getConversationReference()](#getconversationreference) on an incoming | ||
* activity will properly address the reply to a received activity. | ||
* This example shows how to get the users trimmed utterance from the activity: | ||
* | ||
* ```JavaScript | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* const utterance = (context.activity.text || '').trim(); | ||
* ``` | ||
* @param activity Activity to copy delivery information to. | ||
* @param reference Conversation reference containing delivery information. | ||
* @param isIncoming (Optional) flag indicating whether the activity is an incoming or outgoing activity. Defaults to `false` indicating the activity is outgoing. | ||
*/ | ||
static applyConversationReference(activity: Partial<Activity>, reference: Partial<ConversationReference>, isIncoming?: boolean): Partial<Activity>; | ||
readonly activity: Activity; | ||
/** | ||
* If `true` at least one response has been sent for the current turn of conversation. | ||
* | ||
* @remarks | ||
* This is primarily useful for determining if a bot should run fallback routing logic: | ||
* | ||
* ```JavaScript | ||
* await routeActivity(context); | ||
* if (!context.responded) { | ||
* await context.sendActivity(`I'm sorry. I didn't understand.`); | ||
* } | ||
* ``` | ||
*/ | ||
responded: boolean; | ||
/** | ||
* Map of services and other values cached for the lifetime of the turn. | ||
* | ||
* @remarks | ||
* Middleware, other components, and services will typically use this to cache information | ||
* that could be asked for by a bot multiple times during a turn. The bots logic is free to | ||
* use this to pass information between its own components. | ||
* | ||
* ```JavaScript | ||
* const cart = await loadUsersShoppingCart(context); | ||
* context.turnState.set('cart', cart); | ||
* ``` | ||
* | ||
* > [!TIP] | ||
* > For middleware and third party components, consider using a `Symbol()` for your cache key | ||
* > to avoid potential naming collisions with the bots caching and other components. | ||
*/ | ||
readonly turnState: Map<any, any>; | ||
private emit<T>(handlers, arg, next); | ||
} |
@@ -40,6 +40,4 @@ "use strict"; | ||
constructor(adapterOrContext, request) { | ||
this._adapter = undefined; | ||
this._activity = undefined; | ||
this._respondedRef = { responded: false }; | ||
this._services = new Map(); | ||
this._turnState = new Map(); | ||
this._onSendActivities = []; | ||
@@ -57,86 +55,61 @@ this._onUpdateActivity = []; | ||
/** | ||
* Called when this TurnContext instance is passed into the constructor of a new TurnContext | ||
* instance. Can be overridden in derived classes. | ||
* @param context The context object to copy private members to. Everything should be copied by reference. | ||
*/ | ||
copyTo(context) { | ||
// Copy private member to other instance. | ||
['_adapter', '_activity', '_respondedRef', '_services', | ||
'_onSendActivities', '_onUpdateActivity', '_onDeleteActivity'].forEach((prop) => context[prop] = this[prop]); | ||
} | ||
/** | ||
* The adapter for this context. | ||
* Returns the conversation reference for an activity. | ||
* | ||
* @remarks | ||
* This example shows how to send a `typing` activity directly using the adapter. This approach | ||
* bypasses any middleware which sometimes has its advantages. The class to | ||
* `getConversationReference()` and `applyConversationReference()` are to ensure that the | ||
* outgoing activity is properly addressed: | ||
* This can be saved as a plain old JSON object and then later used to message the user | ||
* proactively. | ||
* | ||
* ```JavaScript | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* ``` | ||
* @param activity The activity to copy the conversation reference from | ||
*/ | ||
get adapter() { | ||
return this._adapter; | ||
static getConversationReference(activity) { | ||
return { | ||
activityId: activity.id, | ||
user: internal_1.shallowCopy(activity.from), | ||
bot: internal_1.shallowCopy(activity.recipient), | ||
conversation: internal_1.shallowCopy(activity.conversation), | ||
channelId: activity.channelId, | ||
serviceUrl: activity.serviceUrl | ||
}; | ||
} | ||
/** | ||
* The received activity. | ||
* Updates an activity with the delivery information from a conversation reference. | ||
* | ||
* @remarks | ||
* This example shows how to get the users trimmed utterance from the activity: | ||
* Calling this after [getConversationReference()](#getconversationreference) on an incoming | ||
* activity will properly address the reply to a received activity. | ||
* | ||
* ```JavaScript | ||
* const utterance = (context.activity.text || '').trim(); | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* ``` | ||
* @param activity Activity to copy delivery information to. | ||
* @param reference Conversation reference containing delivery information. | ||
* @param isIncoming (Optional) flag indicating whether the activity is an incoming or outgoing activity. Defaults to `false` indicating the activity is outgoing. | ||
*/ | ||
get activity() { | ||
return this._activity; | ||
} | ||
/** | ||
* If `true` at least one response has been sent for the current turn of conversation. | ||
* | ||
* @remarks | ||
* This is primarily useful for determining if a bot should run fallback routing logic: | ||
* | ||
* ```JavaScript | ||
* await routeActivity(context); | ||
* if (!context.responded) { | ||
* await context.sendActivity(`I'm sorry. I didn't understand.`); | ||
* } | ||
* ``` | ||
*/ | ||
get responded() { | ||
return this._respondedRef.responded; | ||
} | ||
set responded(value) { | ||
if (!value) { | ||
throw new Error(`TurnContext: cannot set 'responded' to a value of 'false'.`); | ||
static applyConversationReference(activity, reference, isIncoming = false) { | ||
activity.channelId = reference.channelId; | ||
activity.serviceUrl = reference.serviceUrl; | ||
activity.conversation = reference.conversation; | ||
if (isIncoming) { | ||
activity.from = reference.user; | ||
activity.recipient = reference.bot; | ||
if (reference.activityId) { | ||
activity.id = reference.activityId; | ||
} | ||
} | ||
this._respondedRef.responded = true; | ||
else { | ||
activity.from = reference.bot; | ||
activity.recipient = reference.user; | ||
if (reference.activityId) { | ||
activity.replyToId = reference.activityId; | ||
} | ||
} | ||
return activity; | ||
} | ||
/** | ||
* Map of services and other values cached for the lifetime of the turn. | ||
* | ||
* @remarks | ||
* Middleware, other components, and services will typically use this to cache information | ||
* that could be asked for by a bot multiple times during a turn. The bots logic is free to | ||
* use this to pass information between its own components. | ||
* | ||
* ```JavaScript | ||
* const cart = await loadUsersShoppingCart(context); | ||
* context.services.set('cart', cart); | ||
* ``` | ||
* | ||
* > [!TIP] | ||
* > For middleware and third party components, consider using a `Symbol()` for your cache key | ||
* > to avoid potential naming collisions with the bots caching and other components. | ||
*/ | ||
get services() { | ||
return this._services; | ||
} | ||
/** | ||
* Sends a single activity or message to the user. | ||
@@ -319,76 +292,103 @@ * | ||
} | ||
emit(handlers, arg, next) { | ||
const list = handlers.slice(); | ||
const context = this; | ||
function emitNext(i) { | ||
try { | ||
if (i < list.length) { | ||
return Promise.resolve(list[i](context, arg, () => emitNext(i + 1))); | ||
} | ||
return Promise.resolve(next()); | ||
} | ||
catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
return emitNext(0); | ||
/** | ||
* Called when this TurnContext instance is passed into the constructor of a new TurnContext | ||
* instance. Can be overridden in derived classes. | ||
* @param context The context object to copy private members to. Everything should be copied by reference. | ||
*/ | ||
copyTo(context) { | ||
// Copy private member to other instance. | ||
[ | ||
'_adapter', '_activity', '_respondedRef', '_services', | ||
'_onSendActivities', '_onUpdateActivity', '_onDeleteActivity' | ||
].forEach((prop) => context[prop] = this[prop]); | ||
} | ||
/** | ||
* Returns the conversation reference for an activity. | ||
* The adapter for this context. | ||
* | ||
* @remarks | ||
* This can be saved as a plain old JSON object and then later used to message the user | ||
* proactively. | ||
* This example shows how to send a `typing` activity directly using the adapter. This approach | ||
* bypasses any middleware which sometimes has its advantages. The class to | ||
* `getConversationReference()` and `applyConversationReference()` are to ensure that the | ||
* outgoing activity is properly addressed: | ||
* | ||
* ```JavaScript | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* ``` | ||
* @param activity The activity to copy the conversation reference from | ||
*/ | ||
static getConversationReference(activity) { | ||
return { | ||
activityId: activity.id, | ||
user: internal_1.shallowCopy(activity.from), | ||
bot: internal_1.shallowCopy(activity.recipient), | ||
conversation: internal_1.shallowCopy(activity.conversation), | ||
channelId: activity.channelId, | ||
serviceUrl: activity.serviceUrl | ||
}; | ||
get adapter() { | ||
return this._adapter; | ||
} | ||
/** | ||
* Updates an activity with the delivery information from a conversation reference. | ||
* The received activity. | ||
* | ||
* @remarks | ||
* Calling this after [getConversationReference()](#getconversationreference) on an incoming | ||
* activity will properly address the reply to a received activity. | ||
* This example shows how to get the users trimmed utterance from the activity: | ||
* | ||
* ```JavaScript | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* const utterance = (context.activity.text || '').trim(); | ||
* ``` | ||
* @param activity Activity to copy delivery information to. | ||
* @param reference Conversation reference containing delivery information. | ||
* @param isIncoming (Optional) flag indicating whether the activity is an incoming or outgoing activity. Defaults to `false` indicating the activity is outgoing. | ||
*/ | ||
static applyConversationReference(activity, reference, isIncoming = false) { | ||
activity.channelId = reference.channelId; | ||
activity.serviceUrl = reference.serviceUrl; | ||
activity.conversation = reference.conversation; | ||
if (isIncoming) { | ||
activity.from = reference.user; | ||
activity.recipient = reference.bot; | ||
if (reference.activityId) { | ||
activity.id = reference.activityId; | ||
} | ||
get activity() { | ||
return this._activity; | ||
} | ||
/** | ||
* If `true` at least one response has been sent for the current turn of conversation. | ||
* | ||
* @remarks | ||
* This is primarily useful for determining if a bot should run fallback routing logic: | ||
* | ||
* ```JavaScript | ||
* await routeActivity(context); | ||
* if (!context.responded) { | ||
* await context.sendActivity(`I'm sorry. I didn't understand.`); | ||
* } | ||
* ``` | ||
*/ | ||
get responded() { | ||
return this._respondedRef.responded; | ||
} | ||
set responded(value) { | ||
if (!value) { | ||
throw new Error(`TurnContext: cannot set 'responded' to a value of 'false'.`); | ||
} | ||
else { | ||
activity.from = reference.bot; | ||
activity.recipient = reference.user; | ||
if (reference.activityId) { | ||
activity.replyToId = reference.activityId; | ||
this._respondedRef.responded = true; | ||
} | ||
/** | ||
* Map of services and other values cached for the lifetime of the turn. | ||
* | ||
* @remarks | ||
* Middleware, other components, and services will typically use this to cache information | ||
* that could be asked for by a bot multiple times during a turn. The bots logic is free to | ||
* use this to pass information between its own components. | ||
* | ||
* ```JavaScript | ||
* const cart = await loadUsersShoppingCart(context); | ||
* context.turnState.set('cart', cart); | ||
* ``` | ||
* | ||
* > [!TIP] | ||
* > For middleware and third party components, consider using a `Symbol()` for your cache key | ||
* > to avoid potential naming collisions with the bots caching and other components. | ||
*/ | ||
get turnState() { | ||
return this._turnState; | ||
} | ||
emit(handlers, arg, next) { | ||
const list = handlers.slice(); | ||
const context = this; | ||
function emitNext(i) { | ||
try { | ||
if (i < list.length) { | ||
return Promise.resolve(list[i](context, arg, () => emitNext(i + 1))); | ||
} | ||
return Promise.resolve(next()); | ||
} | ||
catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
return activity; | ||
return emitNext(0); | ||
} | ||
@@ -395,0 +395,0 @@ } |
{ | ||
"name": "botbuilder-core", | ||
"author": "Microsoft Corp.", | ||
"description": "Bot Builder core library. Minimal interfaces and types needed by all components.", | ||
"version": "4.0.0-preview1.2", | ||
"description": "Core components for Microsoft Bot Builder. Components in this library can run either in a browser or on the server.", | ||
"version": "4.0.6", | ||
"license": "MIT", | ||
@@ -23,5 +23,4 @@ "keywords": [ | ||
"dependencies": { | ||
"@types/node": "^9.3.0", | ||
"botframework-schema": "4.0.0-preview1.2", | ||
"assert": "^1.4.1" | ||
"assert": "^1.4.1", | ||
"botframework-schema": "^4.0.6" | ||
}, | ||
@@ -38,4 +37,6 @@ "devDependencies": { | ||
"build": "tsc", | ||
"clean": "erase /q lib\\*.*" | ||
"build-docs": "typedoc --theme markdown --entryPoint botbuilder --excludePrivate --includeDeclarations --ignoreCompilerErrors --module amd --out ..\\..\\doc\\botbuilder .\\lib\\index.d.ts ..\\botbuilder-core\\lib\\index.d.ts ..\\botbuilder-core-extensions\\lib\\index.d.ts ..\\botframework-schema\\lib\\index.d.ts --hideGenerator --name \"Bot Builder SDK\" --readme none", | ||
"clean": "erase /q /s .\\lib", | ||
"set-version": "npm version --allow-same-version ${Version}" | ||
} | ||
} | ||
} |
@@ -1,3 +0,7 @@ | ||
Bot Builder core library. Minimal interfaces and types needed by all components. | ||
This library contains most of the core functionality for [Bot Builder](https://github.com/Microsoft/botbuilder-js/tree/master/libraries/botbuilder), | ||
but without any dependency on Node. As a result, this version can be used to build bots that run complete in a browser. | ||
Unless you are building a bot or component _without Node_, we recommend that you `botbuilder` your app | ||
instead of `botbuilder-core`. [Learn more here.](https://github.com/Microsoft/botbuilder-js/tree/master/libraries/botbuilder/README.md) | ||
- [Installing](#installing) | ||
@@ -9,16 +13,19 @@ - [Documentation](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) | ||
## Installing | ||
To add the preview version of this package to your bot be sure include the @preview tag: | ||
To add the latset published version of this package to your bot: | ||
```bash | ||
npm install --save botbuilder-core@preview | ||
npm install --save botbuilder-core | ||
``` | ||
While this package is in preview it's possible for updates to include build breaks. To avoid having any updates break your bot it's recommended that you update the dependency table of your bots `package.json` file to lock down the specific version of the package you're using: | ||
#### Use the Daily Build | ||
```JSON | ||
{ | ||
"dependencies": { | ||
"botbuilder-core": "4.0.0-preview1.2" | ||
} | ||
} | ||
To get access to the daily builds of this library, configure npm to use the MyGet feed before installing. | ||
```bash | ||
npm config set registry https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/ | ||
``` | ||
To reset the registry in order to get the latest published version, run: | ||
```bash | ||
npm config set registry https://registry.npmjs.org/ | ||
``` |
@@ -8,16 +8,17 @@ /** | ||
*/ | ||
import { MiddlewareSet, MiddlewareHandler, Middleware, Promiseable } from './middlewareSet'; | ||
import { ActivityTypes, Activity, ResourceResponse, ConversationReference } from 'botframework-schema'; | ||
import { Activity, ConversationReference, ResourceResponse } from 'botframework-schema'; | ||
import { makeRevocable } from './internal'; | ||
import { Middleware, MiddlewareHandler, MiddlewareSet } from './middlewareSet'; | ||
import { TurnContext } from './turnContext'; | ||
import { makeRevocable } from './internal'; | ||
/** | ||
* Abstract base class for all adapter plugins. Adapters manage the communication between the bot | ||
* Abstract base class for all adapter plugins. Adapters manage the communication between the bot | ||
* and a user over a specific channel, or set of channels. | ||
*/ | ||
export abstract class BotAdapter { | ||
private middleware = new MiddlewareSet(); | ||
private middleware: MiddlewareSet = new MiddlewareSet(); | ||
private turnError: (context: TurnContext, error: Error) => Promise<void>; | ||
/** | ||
* Sends a set of activities to the user. An array of responses form the server will be | ||
/** | ||
* Sends a set of activities to the user. An array of responses form the server will be | ||
* returned. | ||
@@ -29,13 +30,13 @@ * @param context Context for the current turn of conversation with the user. | ||
/** | ||
* Replaces an existing activity. | ||
/** | ||
* Replaces an existing activity. | ||
* @param context Context for the current turn of conversation with the user. | ||
* @param activity New replacement activity. The activity should already have it's ID information populated. | ||
* @param activity New replacement activity. The activity should already have it's ID information populated. | ||
*/ | ||
public abstract updateActivity(context: TurnContext, activity: Partial<Activity>): Promise<void>; | ||
/** | ||
* Deletes an existing activity. | ||
/** | ||
* Deletes an existing activity. | ||
* @param context Context for the current turn of conversation with the user. | ||
* @param reference Conversation reference of the activity being deleted. | ||
* @param reference Conversation reference of the activity being deleted. | ||
*/ | ||
@@ -45,8 +46,19 @@ public abstract deleteActivity(context: TurnContext, reference: Partial<ConversationReference>): Promise<void>; | ||
/** | ||
* Proactively continues an existing conversation. | ||
* @param reference Conversation reference of the conversation being continued. | ||
* @param logic Function to execute for performing the bots logic. | ||
* Proactively continues an existing conversation. | ||
* @param reference Conversation reference of the conversation being continued. | ||
* @param logic Function to execute for performing the bots logic. | ||
*/ | ||
public abstract continueConversation(reference: Partial<ConversationReference>, logic: (revocableContext: TurnContext) => Promiseable<void>): Promise<void>; | ||
public abstract continueConversation( | ||
reference: Partial<ConversationReference>, | ||
logic: (revocableContext: TurnContext | ||
) => Promise<void>): Promise<void>; | ||
public get onTurnError(): (context: TurnContext, error: Error) => Promise<void> { | ||
return this.turnError; | ||
} | ||
public set onTurnError(value: (context: TurnContext, error: Error) => Promise<void>) { | ||
this.turnError = value; | ||
} | ||
/** | ||
@@ -58,2 +70,3 @@ * Registers middleware handlers(s) with the adapter. | ||
MiddlewareSet.prototype.use.apply(this.middleware, middleware); | ||
return this; | ||
@@ -63,5 +76,5 @@ } | ||
/** | ||
* Called by the parent class to run the adapters middleware set and calls the passed in | ||
* `next()` handler at the end of the chain. While the context object is passed in from the | ||
* caller is created by the caller, what gets passed to the `next()` is a wrapped version of | ||
* Called by the parent class to run the adapters middleware set and calls the passed in | ||
* `next()` handler at the end of the chain. While the context object is passed in from the | ||
* caller is created by the caller, what gets passed to the `next()` is a wrapped version of | ||
* the context which will automatically be revoked upon completion of the turn. This causes | ||
@@ -73,13 +86,26 @@ * the bots logic to throw an error if it tries to use the context after the turn completes. | ||
*/ | ||
protected runMiddleware(context: TurnContext, next: (revocableContext: TurnContext) => Promiseable<void>): Promise<void> { | ||
protected runMiddleware(context: TurnContext, next: (revocableContext: TurnContext) => Promise<void>): Promise<void> { | ||
// Wrap context with revocable proxy | ||
const pContext = makeRevocable(context); | ||
return this.middleware.run(pContext.proxy, () => { | ||
// Call next with revocable context | ||
return next(pContext.proxy); | ||
}).then(() => { | ||
// Revoke use of context | ||
const pContext: { | ||
proxy: TurnContext; | ||
revoke(): void; | ||
} = makeRevocable(context); | ||
return new Promise((resolve: any, reject: any): void => { | ||
this.middleware.run(pContext.proxy, () => { | ||
// Call next with revocable context | ||
return next(pContext.proxy); | ||
}).then(resolve, (err: Error) => { | ||
if (this.onTurnError) { | ||
this.onTurnError(pContext.proxy, err) | ||
.then(resolve, reject); | ||
} else { | ||
reject(err); | ||
} | ||
}); | ||
}).then(() => pContext.revoke(), (err: Error) => { | ||
pContext.revoke(); | ||
throw err; | ||
}); | ||
} | ||
} |
@@ -5,9 +5,27 @@ /** | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
export * from 'botframework-schema'; | ||
export * from './autoSaveStateMiddleware'; | ||
export * from './botAdapter'; | ||
export * from './botState'; | ||
export * from './botStatePropertyAccessor'; | ||
export * from './botStateSet'; | ||
export * from './browserStorage'; | ||
export * from './cardFactory'; | ||
export * from './conversationState'; | ||
export * from './memoryStorage'; | ||
export * from './memoryTranscriptStore'; | ||
export * from './messageFactory'; | ||
export * from './middlewareSet'; | ||
export * from './privateConversationState'; | ||
export * from './propertyManager'; | ||
export * from './recognizerResult'; | ||
export * from './storage'; | ||
export * from './testAdapter'; | ||
export * from './transcriptLogger'; | ||
export * from './turnContext'; | ||
export * from 'botframework-schema'; | ||
export * from './userState'; | ||
@@ -9,5 +9,6 @@ /** | ||
*/ | ||
export function shallowCopy<T>(value: T): T { | ||
if (Array.isArray(value)) { return value.slice(0) as any } | ||
if (typeof value === 'object') { return Object.assign({}, value) } | ||
export function shallowCopy<T>(value: T): T { | ||
if (Array.isArray(value)) { return value.slice(0) as any; } | ||
if (typeof value === 'object') { return {...value as any}; } | ||
return value; | ||
@@ -18,6 +19,6 @@ } | ||
* @private | ||
* @param target | ||
* @param handler | ||
* @param target a thing that will be made revocable | ||
* @param handler an object that defines the way the new revocable object works | ||
*/ | ||
export function makeRevocable<T extends Object>(target: T, handler?: ProxyHandler<T>): { proxy: T; revoke: () => void; } { | ||
export function makeRevocable<T extends Object>(target: T, handler?: ProxyHandler<T>): { proxy: T; revoke(): void } { | ||
// Ensure proxy supported (some browsers don't) | ||
@@ -27,4 +28,6 @@ if (Proxy && Proxy.revocable) { | ||
} else { | ||
return { proxy: target, revoke: () => {} }; | ||
return { proxy: target, revoke: (): void => { | ||
// noop | ||
}}; | ||
} | ||
} | ||
} |
@@ -12,39 +12,28 @@ /** | ||
/** | ||
* Type signature for a return value that can (Optionally) return its value | ||
* asynchronously using a Promise. | ||
* | ||
* ```TypeScript | ||
* type Promiseable <T = void> = Promise<T>|T; | ||
* ``` | ||
* @param T (Optional) type of value being returned. This defaults to `void`. | ||
* Interface implemented by object based middleware. | ||
*/ | ||
export type Promiseable <T = void> = Promise<T>|T; | ||
/** | ||
* Interface implemented by object based middleware. | ||
*/ | ||
export interface Middleware { | ||
onTurn(context: TurnContext, next: () => Promise<void>): Promiseable<void>; | ||
onTurn(context: TurnContext, next: () => Promise<void>): Promise<void>; | ||
} | ||
/** | ||
* Signature implemented by function based middleware. | ||
* | ||
/** | ||
* Signature implemented by function based middleware. | ||
* | ||
* ```TypeScript | ||
* type MiddlewareHandler = (context: TurnContext, next: () => Promise<void>) => Promiseable<void>; | ||
* ``` | ||
* type MiddlewareHandler = (context: TurnContext, next: () => Promise<void>) => Promise<void>; | ||
* ``` | ||
*/ | ||
export type MiddlewareHandler = (context: TurnContext, next: () => Promise<void>) => Promiseable<void>; | ||
export type MiddlewareHandler = (context: TurnContext, next: () => Promise<void>) => Promise<void>; | ||
/** | ||
* A set of `Middleware` plugins. | ||
* | ||
* A set of `Middleware` plugins. | ||
* | ||
* @remarks | ||
* The set itself is middleware so you can easily package up a set of middleware that can be composed | ||
* into an adapter with a single `adapter.use(mySet)` call or even into another middleware set using | ||
* The set itself is middleware so you can easily package up a set of middleware that can be composed | ||
* into an adapter with a single `adapter.use(mySet)` call or even into another middleware set using | ||
* `set.use(mySet)`. | ||
* | ||
* | ||
* ```JavaScript | ||
* const { MiddlewareSet } = require('botbuilder'); | ||
* | ||
* | ||
* const set = new MiddlewareSet(); | ||
@@ -63,3 +52,3 @@ * set.use(async (context, next) => { | ||
* Creates a new MiddlewareSet instance. | ||
* @param middleware Zero or more middleware handlers(s) to register. | ||
* @param middleware Zero or more middleware handlers(s) to register. | ||
*/ | ||
@@ -76,3 +65,3 @@ constructor(...middleware: (MiddlewareHandler|Middleware)[]) { | ||
* Registers middleware handlers(s) with the set. | ||
* | ||
* | ||
* @remarks | ||
@@ -91,7 +80,7 @@ * This example adds a new piece of middleware to a set: | ||
public use(...middleware: (MiddlewareHandler|Middleware)[]): this { | ||
middleware.forEach((plugin) => { | ||
middleware.forEach((plugin: any) => { | ||
if (typeof plugin === 'function') { | ||
this.middleware.push(plugin); | ||
} else if (typeof plugin === 'object' && plugin.onTurn) { | ||
this.middleware.push((context, next) => plugin.onTurn(context, next)); | ||
this.middleware.push((context: TurnContext, next: Function) => plugin.onTurn(context, next)); | ||
} else { | ||
@@ -101,2 +90,3 @@ throw new Error(`MiddlewareSet.use(): invalid plugin type being added.`); | ||
}); | ||
return this; | ||
@@ -110,4 +100,4 @@ } | ||
*/ | ||
public run(context: TurnContext, next: () => Promiseable<void>): Promise<void> { | ||
const handlers = this.middleware.slice(); | ||
public run(context: TurnContext, next: () => Promise<void>): Promise<void> { | ||
const handlers: MiddlewareHandler[] = this.middleware.slice(); | ||
function runNext(i: number): Promise<void> { | ||
@@ -124,4 +114,5 @@ try { | ||
} | ||
return runNext(0); | ||
} | ||
} |
@@ -8,46 +8,53 @@ /** | ||
*/ | ||
import { Activity, ResourceResponse, ConversationReference, ActivityTypes, InputHints } from 'botframework-schema'; | ||
import { Activity, ActivityTypes, ConversationReference, InputHints, ResourceResponse } from 'botframework-schema'; | ||
import { BotAdapter } from './botAdapter'; | ||
import { shallowCopy } from './internal'; | ||
import { Promiseable } from './middlewareSet'; | ||
/** | ||
* Signature implemented by functions registered with `context.onSendActivity()`. | ||
* | ||
/** | ||
* Signature implemented by functions registered with `context.onSendActivity()`. | ||
* | ||
* ```TypeScript | ||
* type SendActivitiesHandler = (context: TurnContext, activities: Partial<Activity>[], next: () => Promise<ResourceResponse[]>) => Promiseable<ResourceResponse[]>; | ||
* ``` | ||
* type SendActivitiesHandler = (context: TurnContext, activities: Partial<Activity>[], next: () => Promise<ResourceResponse[]>) => Promise<ResourceResponse[]>; | ||
* ``` | ||
*/ | ||
export type SendActivitiesHandler = (context: TurnContext, activities: Partial<Activity>[], next: () => Promise<ResourceResponse[]>) => Promiseable<ResourceResponse[]>; | ||
export type SendActivitiesHandler = ( | ||
context: TurnContext, | ||
activities: Partial<Activity>[], | ||
next: () => Promise<ResourceResponse[]> | ||
) => Promise<ResourceResponse[]>; | ||
/** | ||
* Signature implemented by functions registered with `context.onUpdateActivity()`. | ||
* | ||
/** | ||
* Signature implemented by functions registered with `context.onUpdateActivity()`. | ||
* | ||
* ```TypeScript | ||
* type UpdateActivityHandler = (context: TurnContext, activity: Partial<Activity>, next: () => Promise<void>) => Promiseable<void>; | ||
* ``` | ||
* type UpdateActivityHandler = (context: TurnContext, activity: Partial<Activity>, next: () => Promise<void>) => Promise<void>; | ||
* ``` | ||
*/ | ||
export type UpdateActivityHandler = (context: TurnContext, activity: Partial<Activity>, next: () => Promise<void>) => Promiseable<void>; | ||
export type UpdateActivityHandler = (context: TurnContext, activity: Partial<Activity>, next: () => Promise<void>) => Promise<void>; | ||
/** | ||
* Signature implemented by functions registered with `context.onDeleteActivity()`. | ||
* | ||
/** | ||
* Signature implemented by functions registered with `context.onDeleteActivity()`. | ||
* | ||
* ```TypeScript | ||
* type DeleteActivityHandler = (context: TurnContext, reference: Partial<ConversationReference>, next: () => Promise<void>) => Promiseable<void>; | ||
* ``` | ||
* type DeleteActivityHandler = (context: TurnContext, reference: Partial<ConversationReference>, next: () => Promise<void>) => Promise<void>; | ||
* ``` | ||
*/ | ||
export type DeleteActivityHandler = (context: TurnContext, reference: Partial<ConversationReference>, next: () => Promise<void>) => Promiseable<void>; | ||
export type DeleteActivityHandler = ( | ||
context: TurnContext, | ||
reference: Partial<ConversationReference>, | ||
next: () => Promise<void> | ||
) => Promise<void>; | ||
export interface TurnContext { } | ||
export interface TurnContext {} | ||
/** | ||
* Context object containing information cached for a single turn of conversation with a user. | ||
* | ||
/** | ||
* Context object containing information cached for a single turn of conversation with a user. | ||
* | ||
* @remarks | ||
* This will typically be created by the adapter you're using and then passed to middleware and | ||
* This will typically be created by the adapter you're using and then passed to middleware and | ||
* your bots logic. | ||
* | ||
* | ||
* For TypeScript developers the `TurnContext` is also exposed as an interface which you can derive | ||
* from to better describe the actual shape of the context object being passed around. Middleware | ||
* can potentially extend the context object with additional members so in order to get intellisense | ||
* can potentially extend the context object with additional members so in order to get intellisense | ||
* for those added members you'll need to define them on an interface that extends TurnContext: | ||
@@ -59,7 +66,7 @@ * | ||
* readonly userState: MyUserState; | ||
* | ||
* | ||
* // Added by ConversationState middleware. | ||
* readonly conversationState: MyConversationState; | ||
* } | ||
* | ||
* | ||
* adapter.processActivity(req, res, (context: MyContext) => { | ||
@@ -71,6 +78,6 @@ * const state = context.conversationState; | ||
export class TurnContext { | ||
private _adapter: BotAdapter|undefined = undefined; | ||
private _activity: Activity| undefined = undefined; | ||
private _respondedRef: { responded: boolean; } = { responded: false }; | ||
private _services = new Map<any, any>(); | ||
private _adapter: BotAdapter | undefined; | ||
private _activity: Activity | undefined; | ||
private _respondedRef: { responded: boolean } = { responded: false }; | ||
private _turnState: Map<any, any> = new Map<any, any>(); | ||
private _onSendActivities: SendActivitiesHandler[] = []; | ||
@@ -97,96 +104,69 @@ private _onUpdateActivity: UpdateActivityHandler[] = []; | ||
/** | ||
* Called when this TurnContext instance is passed into the constructor of a new TurnContext | ||
* instance. Can be overridden in derived classes. | ||
* @param context The context object to copy private members to. Everything should be copied by reference. | ||
*/ | ||
protected copyTo(context: TurnContext): void { | ||
// Copy private member to other instance. | ||
['_adapter', '_activity', '_respondedRef', '_services', | ||
'_onSendActivities', '_onUpdateActivity', '_onDeleteActivity'].forEach((prop) => (context as any)[prop] = (this as any)[prop]); | ||
} | ||
/** | ||
* The adapter for this context. | ||
* Returns the conversation reference for an activity. | ||
* | ||
* @remarks | ||
* This example shows how to send a `typing` activity directly using the adapter. This approach | ||
* bypasses any middleware which sometimes has its advantages. The class to | ||
* `getConversationReference()` and `applyConversationReference()` are to ensure that the | ||
* outgoing activity is properly addressed: | ||
* This can be saved as a plain old JSON object and then later used to message the user | ||
* proactively. | ||
* | ||
* ```JavaScript | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* ``` | ||
* @param activity The activity to copy the conversation reference from | ||
*/ | ||
public get adapter(): BotAdapter { | ||
return this._adapter as BotAdapter; | ||
public static getConversationReference(activity: Partial<Activity>): Partial<ConversationReference> { | ||
return { | ||
activityId: activity.id, | ||
user: shallowCopy(activity.from), | ||
bot: shallowCopy(activity.recipient), | ||
conversation: shallowCopy(activity.conversation), | ||
channelId: activity.channelId, | ||
serviceUrl: activity.serviceUrl | ||
}; | ||
} | ||
/** | ||
* The received activity. | ||
* | ||
* @remarks | ||
* This example shows how to get the users trimmed utterance from the activity: | ||
/** | ||
* Updates an activity with the delivery information from a conversation reference. | ||
* | ||
* ```JavaScript | ||
* const utterance = (context.activity.text || '').trim(); | ||
* ``` | ||
*/ | ||
public get activity(): Activity { | ||
return this._activity as Activity; | ||
} | ||
/** | ||
* If `true` at least one response has been sent for the current turn of conversation. | ||
* | ||
* @remarks | ||
* This is primarily useful for determining if a bot should run fallback routing logic: | ||
* Calling this after [getConversationReference()](#getconversationreference) on an incoming | ||
* activity will properly address the reply to a received activity. | ||
* | ||
* ```JavaScript | ||
* await routeActivity(context); | ||
* if (!context.responded) { | ||
* await context.sendActivity(`I'm sorry. I didn't understand.`); | ||
* } | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* ``` | ||
* @param activity Activity to copy delivery information to. | ||
* @param reference Conversation reference containing delivery information. | ||
* @param isIncoming (Optional) flag indicating whether the activity is an incoming or outgoing activity. Defaults to `false` indicating the activity is outgoing. | ||
*/ | ||
public get responded(): boolean { | ||
return this._respondedRef.responded; | ||
} | ||
public static applyConversationReference( | ||
activity: Partial<Activity>, | ||
reference: Partial<ConversationReference>, | ||
isIncoming: boolean = false | ||
): Partial<Activity> { | ||
activity.channelId = reference.channelId; | ||
activity.serviceUrl = reference.serviceUrl; | ||
activity.conversation = reference.conversation; | ||
if (isIncoming) { | ||
activity.from = reference.user; | ||
activity.recipient = reference.bot; | ||
if (reference.activityId) { activity.id = reference.activityId; } | ||
} else { | ||
activity.from = reference.bot; | ||
activity.recipient = reference.user; | ||
if (reference.activityId) { activity.replyToId = reference.activityId; } | ||
} | ||
public set responded(value: boolean) { | ||
if (!value) { throw new Error(`TurnContext: cannot set 'responded' to a value of 'false'.`) } | ||
this._respondedRef.responded = true; | ||
return activity; | ||
} | ||
/** | ||
* Map of services and other values cached for the lifetime of the turn. | ||
* | ||
* @remarks | ||
* Middleware, other components, and services will typically use this to cache information | ||
* that could be asked for by a bot multiple times during a turn. The bots logic is free to | ||
* use this to pass information between its own components. | ||
* | ||
* ```JavaScript | ||
* const cart = await loadUsersShoppingCart(context); | ||
* context.services.set('cart', cart); | ||
* ``` | ||
* | ||
* > [!TIP] | ||
* > For middleware and third party components, consider using a `Symbol()` for your cache key | ||
* > to avoid potential naming collisions with the bots caching and other components. | ||
*/ | ||
public get services(): Map<any, any> { | ||
return this._services; | ||
} | ||
/** | ||
* Sends a single activity or message to the user. | ||
* | ||
* Sends a single activity or message to the user. | ||
* | ||
* @remarks | ||
* This ultimately calls [sendActivities()](#sendactivites) and is provided as a convenience to | ||
* This ultimately calls [sendActivities()](#sendactivites) and is provided as a convenience to | ||
* make formating and sending individual activities easier. | ||
* | ||
* | ||
* ```JavaScript | ||
@@ -203,12 +183,15 @@ * await context.sendActivity(`Hello World`); | ||
a = { text: activityOrText, inputHint: inputHint || InputHints.AcceptingInput }; | ||
if (speak) { a.speak = speak } | ||
if (speak) { a.speak = speak; } | ||
} else { | ||
a = activityOrText; | ||
} | ||
return this.sendActivities([a]).then((responses) => responses && responses.length > 0 ? responses[0] : undefined); | ||
return this.sendActivities([a]).then( | ||
(responses: ResourceResponse[]) => responses && responses.length > 0 ? responses[0] : undefined | ||
); | ||
} | ||
/** | ||
/** | ||
* Sends a set of activities to the user. An array of responses form the server will be returned. | ||
* | ||
* | ||
* @remarks | ||
@@ -219,3 +202,3 @@ * Prior to delivery, the activities will be updated with information from the `ConversationReference` | ||
* handlers and then passed to `adapter.sendActivities()`. | ||
* | ||
* | ||
* ```JavaScript | ||
@@ -230,16 +213,19 @@ * await context.sendActivities([ | ||
*/ | ||
public sendActivities(activities: Partial<Activity>[]): Promise<ResourceResponse[]> { | ||
let sentNonTraceActivity = false; | ||
const ref = TurnContext.getConversationReference(this.activity); | ||
const output = activities.map((a) => { | ||
const o = TurnContext.applyConversationReference(Object.assign({}, a), ref); | ||
if (!o.type) { o.type = ActivityTypes.Message } | ||
if (o.type !== ActivityTypes.Trace) { sentNonTraceActivity = true } | ||
public sendActivities(activities: Partial<Activity>[]): Promise<ResourceResponse[]> { | ||
let sentNonTraceActivity: boolean = false; | ||
const ref: Partial<ConversationReference> = TurnContext.getConversationReference(this.activity); | ||
const output: Partial<Activity>[] = activities.map((a: Partial<Activity>) => { | ||
const o: Partial<Activity> = TurnContext.applyConversationReference({...a}, ref); | ||
if (!o.type) { o.type = ActivityTypes.Message; } | ||
if (o.type !== ActivityTypes.Trace) { sentNonTraceActivity = true; } | ||
return o; | ||
}); | ||
return this.emit(this._onSendActivities, output, () => { | ||
return this.adapter.sendActivities(this, output) | ||
.then((responses) => { | ||
.then((responses: ResourceResponse[]) => { | ||
// Set responded flag | ||
if (sentNonTraceActivity) { this.responded = true } | ||
if (sentNonTraceActivity) { this.responded = true; } | ||
return responses; | ||
@@ -250,9 +236,9 @@ }); | ||
/** | ||
* Replaces an existing activity. | ||
* | ||
/** | ||
* Replaces an existing activity. | ||
* | ||
* @remarks | ||
* The activity will be routed through any registered [onUpdateActivity](#onupdateactivity) handlers | ||
* The activity will be routed through any registered [onUpdateActivity](#onupdateactivity) handlers | ||
* before being passed to `adapter.updateActivity()`. | ||
* | ||
* | ||
* ```JavaScript | ||
@@ -265,3 +251,3 @@ * const matched = /approve (.*)/i.exec(context.text); | ||
* ``` | ||
* @param activity New replacement activity. The activity should already have it's ID information populated. | ||
* @param activity New replacement activity. The activity should already have it's ID information populated. | ||
*/ | ||
@@ -272,9 +258,9 @@ public updateActivity(activity: Partial<Activity>): Promise<void> { | ||
/** | ||
* Deletes an existing activity. | ||
* | ||
/** | ||
* Deletes an existing activity. | ||
* | ||
* @remarks | ||
* The `ConversationReference` for the activity being deleted will be routed through any registered | ||
* The `ConversationReference` for the activity being deleted will be routed through any registered | ||
* [onDeleteActivity](#ondeleteactivity) handlers before being passed to `adapter.deleteActivity()`. | ||
* | ||
* | ||
* ```JavaScript | ||
@@ -297,11 +283,12 @@ * const matched = /approve (.*)/i.exec(context.text); | ||
} | ||
return this.emit(this._onDeleteActivity, reference, () => this.adapter.deleteActivity(this, reference)); | ||
} | ||
/** | ||
* Registers a handler to be notified of and potentially intercept the sending of activities. | ||
* | ||
/** | ||
* Registers a handler to be notified of and potentially intercept the sending of activities. | ||
* | ||
* @remarks | ||
* This example shows how to listen for and log outgoing `message` activities. | ||
* | ||
* | ||
* ```JavaScript | ||
@@ -311,3 +298,3 @@ * context.onSendActivities(await (ctx, activities, next) => { | ||
* await next(); | ||
* | ||
* | ||
* // Log sent messages | ||
@@ -317,12 +304,13 @@ * activities.filter(a => a.type === 'message').forEach(a => logSend(a)); | ||
* ``` | ||
* @param handler A function that will be called anytime [sendActivity()](#sendactivity) is called. The handler should call `next()` to continue sending of the activities. | ||
* @param handler A function that will be called anytime [sendActivity()](#sendactivity) is called. The handler should call `next()` to continue sending of the activities. | ||
*/ | ||
public onSendActivities(handler: SendActivitiesHandler): this { | ||
this._onSendActivities.push(handler); | ||
return this; | ||
} | ||
/** | ||
* Registers a handler to be notified of and potentially intercept an activity being updated. | ||
* | ||
/** | ||
* Registers a handler to be notified of and potentially intercept an activity being updated. | ||
* | ||
* @remarks | ||
@@ -335,3 +323,3 @@ * This example shows how to listen for and log updated activities. | ||
* await next(); | ||
* | ||
* | ||
* // Log update | ||
@@ -341,12 +329,13 @@ * logUpdate(activity); | ||
* ``` | ||
* @param handler A function that will be called anytime [updateActivity()](#updateactivity) is called. The handler should call `next()` to continue sending of the replacement activity. | ||
* @param handler A function that will be called anytime [updateActivity()](#updateactivity) is called. The handler should call `next()` to continue sending of the replacement activity. | ||
*/ | ||
public onUpdateActivity(handler: UpdateActivityHandler): this { | ||
this._onUpdateActivity.push(handler); | ||
return this; | ||
} | ||
/** | ||
* Registers a handler to be notified of and potentially intercept an activity being deleted. | ||
* | ||
/** | ||
* Registers a handler to be notified of and potentially intercept an activity being deleted. | ||
* | ||
* @remarks | ||
@@ -359,3 +348,3 @@ * This example shows how to listen for and log deleted activities. | ||
* await next(); | ||
* | ||
* | ||
* // Log delete | ||
@@ -365,82 +354,122 @@ * logDelete(activity); | ||
* ``` | ||
* @param handler A function that will be called anytime [deleteActivity()](#deleteactivity) is called. The handler should call `next()` to continue deletion of the activity. | ||
* @param handler A function that will be called anytime [deleteActivity()](#deleteactivity) is called. The handler should call `next()` to continue deletion of the activity. | ||
*/ | ||
public onDeleteActivity(handler: DeleteActivityHandler): this { | ||
this._onDeleteActivity.push(handler); | ||
return this; | ||
} | ||
private emit<T>(handlers: ((context: TurnContext, arg: T, next: () => Promise<any>) => Promiseable<any>)[], arg: T, next: () => Promise<any>): Promise<any> { | ||
const list = handlers.slice(); | ||
const context = this; | ||
function emitNext(i: number): Promise<void> { | ||
try { | ||
if (i < list.length) { | ||
return Promise.resolve(list[i](context, arg, () => emitNext(i + 1))); | ||
} | ||
return Promise.resolve(next()); | ||
} catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
return emitNext(0); | ||
/** | ||
* Called when this TurnContext instance is passed into the constructor of a new TurnContext | ||
* instance. Can be overridden in derived classes. | ||
* @param context The context object to copy private members to. Everything should be copied by reference. | ||
*/ | ||
protected copyTo(context: TurnContext): void { | ||
// Copy private member to other instance. | ||
[ | ||
'_adapter', '_activity', '_respondedRef', '_services', | ||
'_onSendActivities', '_onUpdateActivity', '_onDeleteActivity' | ||
].forEach((prop: string) => (context as any)[prop] = (this as any)[prop]); | ||
} | ||
/** | ||
* Returns the conversation reference for an activity. | ||
* | ||
* The adapter for this context. | ||
* | ||
* @remarks | ||
* This can be saved as a plain old JSON object and then later used to message the user | ||
* proactively. | ||
* This example shows how to send a `typing` activity directly using the adapter. This approach | ||
* bypasses any middleware which sometimes has its advantages. The class to | ||
* `getConversationReference()` and `applyConversationReference()` are to ensure that the | ||
* outgoing activity is properly addressed: | ||
* | ||
* ```JavaScript | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* ``` | ||
* @param activity The activity to copy the conversation reference from | ||
*/ | ||
static getConversationReference(activity: Partial<Activity>): Partial<ConversationReference> { | ||
return { | ||
activityId: activity.id, | ||
user: shallowCopy(activity.from), | ||
bot: shallowCopy(activity.recipient), | ||
conversation: shallowCopy(activity.conversation), | ||
channelId: activity.channelId, | ||
serviceUrl: activity.serviceUrl | ||
}; | ||
public get adapter(): BotAdapter { | ||
return this._adapter as BotAdapter; | ||
} | ||
/** | ||
* Updates an activity with the delivery information from a conversation reference. | ||
* | ||
* The received activity. | ||
* | ||
* @remarks | ||
* Calling this after [getConversationReference()](#getconversationreference) on an incoming | ||
* activity will properly address the reply to a received activity. | ||
* This example shows how to get the users trimmed utterance from the activity: | ||
* | ||
* ```JavaScript | ||
* // Send a typing indicator without going through an middleware listeners. | ||
* const reference = TurnContext.getConversationReference(context.request); | ||
* const activity = TurnContext.applyConversationReference({ type: 'typing' }, reference); | ||
* await context.adapter.sendActivities([activity]); | ||
* const utterance = (context.activity.text || '').trim(); | ||
* ``` | ||
* @param activity Activity to copy delivery information to. | ||
* @param reference Conversation reference containing delivery information. | ||
* @param isIncoming (Optional) flag indicating whether the activity is an incoming or outgoing activity. Defaults to `false` indicating the activity is outgoing. | ||
*/ | ||
static applyConversationReference(activity: Partial<Activity>, reference: Partial<ConversationReference>, isIncoming = false): Partial<Activity> { | ||
activity.channelId = reference.channelId; | ||
activity.serviceUrl = reference.serviceUrl; | ||
activity.conversation = reference.conversation; | ||
if (isIncoming) { | ||
activity.from = reference.user; | ||
activity.recipient = reference.bot; | ||
if (reference.activityId) { activity.id = reference.activityId } | ||
} else { | ||
activity.from = reference.bot; | ||
activity.recipient = reference.user; | ||
if (reference.activityId) { activity.replyToId = reference.activityId } | ||
public get activity(): Activity { | ||
return this._activity as Activity; | ||
} | ||
/** | ||
* If `true` at least one response has been sent for the current turn of conversation. | ||
* | ||
* @remarks | ||
* This is primarily useful for determining if a bot should run fallback routing logic: | ||
* | ||
* ```JavaScript | ||
* await routeActivity(context); | ||
* if (!context.responded) { | ||
* await context.sendActivity(`I'm sorry. I didn't understand.`); | ||
* } | ||
* ``` | ||
*/ | ||
public get responded(): boolean { | ||
return this._respondedRef.responded; | ||
} | ||
public set responded(value: boolean) { | ||
if (!value) { throw new Error(`TurnContext: cannot set 'responded' to a value of 'false'.`); } | ||
this._respondedRef.responded = true; | ||
} | ||
/** | ||
* Map of services and other values cached for the lifetime of the turn. | ||
* | ||
* @remarks | ||
* Middleware, other components, and services will typically use this to cache information | ||
* that could be asked for by a bot multiple times during a turn. The bots logic is free to | ||
* use this to pass information between its own components. | ||
* | ||
* ```JavaScript | ||
* const cart = await loadUsersShoppingCart(context); | ||
* context.turnState.set('cart', cart); | ||
* ``` | ||
* | ||
* > [!TIP] | ||
* > For middleware and third party components, consider using a `Symbol()` for your cache key | ||
* > to avoid potential naming collisions with the bots caching and other components. | ||
*/ | ||
public get turnState(): Map<any, any> { | ||
return this._turnState; | ||
} | ||
private emit<T>( | ||
handlers: ((context: TurnContext, arg: T, next: () => Promise<any>) => Promise<any>)[], | ||
arg: T, | ||
next: () => Promise<any> | ||
): Promise<any> { | ||
const list: ((context: TurnContext, arg: T, next: () => Promise<any>) => Promise<any>)[] = handlers.slice(); | ||
const context: TurnContext = this; | ||
function emitNext(i: number): Promise<void> { | ||
try { | ||
if (i < list.length) { | ||
return Promise.resolve(list[i](context, arg, () => emitNext(i + 1))); | ||
} | ||
return Promise.resolve(next()); | ||
} catch (err) { | ||
return Promise.reject(err); | ||
} | ||
} | ||
return activity; | ||
return emitNext(0); | ||
} | ||
} | ||
@@ -11,4 +11,6 @@ const assert = require('assert'); | ||
} | ||
} | ||
describe(`BotAdapter`, function () { | ||
@@ -26,2 +28,3 @@ this.timeout(5000); | ||
const adapter = new SimpleAdapter(); | ||
it(`should use() middleware individually.`, function (done) { | ||
@@ -43,2 +46,15 @@ adapter.use(middleware).use(middleware); | ||
}); | ||
it(`should reach onTurnError when error is thrown.`, function (done) { | ||
adapter.onTurnError = async (turnContext, error) => { | ||
assert(turnContext, `turnContext not found.`); | ||
assert(error, `error not found.`); | ||
assert.equal(error, 1, `unexpected error thrown.`); | ||
done(); | ||
} | ||
adapter.processRequest(testMessage, (turnContext) => { | ||
throw 1; | ||
}); | ||
}); | ||
}); |
@@ -0,0 +0,0 @@ const assert = require('assert'); |
@@ -15,2 +15,9 @@ const assert = require('assert'); | ||
const testTraceMessage = { | ||
type: 'trace', | ||
name: 'TestTrace', | ||
valueType: 'https://example.org/test/trace', | ||
label: 'Test Trace' | ||
}; | ||
class SimpleAdapter extends BotAdapter { | ||
@@ -101,5 +108,5 @@ sendActivities(context, activities) { | ||
const context = new TurnContext(new SimpleAdapter(), testMessage); | ||
assert(context.services.get('foo') === undefined, `invalid initial state.`); | ||
context.services.set('foo', 'bar'); | ||
assert(context.services.get('foo') === 'bar', `invalid value of "${context.services.get('foo')}" after set().`); | ||
assert(context.turnState.get('foo') === undefined, `invalid initial state.`); | ||
context.turnState.set('foo', 'bar'); | ||
assert(context.turnState.get('foo') === 'bar', `invalid value of "${context.turnState.get('foo')}" after set().`); | ||
done(); | ||
@@ -110,7 +117,7 @@ }); | ||
const context = new TurnContext(new SimpleAdapter(), testMessage); | ||
assert(!context.services.has('bar'), `invalid initial state for has().`); | ||
context.services.set('bar', 'foo'); | ||
assert(context.services.has('bar'), `invalid initial state for has() after set().`); | ||
context.services.set('bar', undefined); | ||
assert(context.services.has('bar'), `invalid initial state for has() after set(undefined).`); | ||
assert(!context.turnState.has('bar'), `invalid initial state for has().`); | ||
context.turnState.set('bar', 'foo'); | ||
assert(context.turnState.has('bar'), `invalid initial state for has() after set().`); | ||
context.turnState.set('bar', undefined); | ||
assert(context.turnState.has('bar'), `invalid initial state for has() after set(undefined).`); | ||
done(); | ||
@@ -122,7 +129,7 @@ }); | ||
const context = new TurnContext(new SimpleAdapter(), testMessage); | ||
assert(!context.services.has(key), `invalid initial state for has().`); | ||
context.services.set(key, 'bar'); | ||
assert(context.services.get(key) === 'bar', `invalid value of "${context.services.get(key)}" after set().`); | ||
context.services.set(key, undefined); | ||
assert(context.services.has(key), `invalid initial state for has() after set(undefined).`); | ||
assert(!context.turnState.has(key), `invalid initial state for has().`); | ||
context.turnState.set(key, 'bar'); | ||
assert(context.turnState.get(key) === 'bar', `invalid value of "${context.turnState.get(key)}" after set().`); | ||
context.turnState.set(key, undefined); | ||
assert(context.turnState.has(key), `invalid initial state for has() after set(undefined).`); | ||
done(); | ||
@@ -306,2 +313,30 @@ }); | ||
}); | ||
it(`should not set TurnContext.responded to true if Trace activity is sent.`, function (done) { | ||
const context = new TurnContext(new SimpleAdapter(), testMessage); | ||
context.sendActivities([testTraceMessage]).then((responses) => { | ||
assert(context.responded === false, `responded was set to true.`); | ||
done(); | ||
}); | ||
}); | ||
it(`should not set TurnContext.responded to true if multiple Trace activities are sent.`, function (done) { | ||
const context = new TurnContext(new SimpleAdapter(), testMessage); | ||
context.sendActivities([testTraceMessage, testTraceMessage]).then((responses) => { | ||
assert(context.responded === false, `responded was set to true.`); | ||
done(); | ||
}); | ||
}); | ||
it(`should set TurnContext.responded to true if Trace and message activities are sent.`, function (done) { | ||
const context = new TurnContext(new SimpleAdapter(), testMessage); | ||
context.sendActivities([testTraceMessage, testTraceMessage]).then((responses) => { | ||
assert(context.responded === false, `responded was set to true.`); | ||
}).then(() => { | ||
context.sendActivities([testMessage]).then((responses) => { | ||
assert(context.responded, `responded was not set to true.`); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -9,3 +9,3 @@ { | ||
"rootDir": "./src", | ||
"types" : ["node", "filenamify"] | ||
"types" : ["node"] | ||
}, | ||
@@ -12,0 +12,0 @@ "include": [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
462362
2
112
10022
1
30
2
1
+ Addedadaptivecards@1.2.3(transitive)
+ Addedbotframework-schema@4.23.1(transitive)
+ Addeduuid@10.0.0(transitive)
+ Addedzod@3.24.1(transitive)
- Removed@types/node@^9.3.0
- Removed@types/node@9.6.61(transitive)
- Removedbotframework-schema@4.0.0-preview1.2(transitive)
Updatedbotframework-schema@^4.0.6