UNPKG

71.2 kBJavaScriptView Raw
1"use strict";
2/**
3 * @module botbuilder
4 */
5/**
6 * Copyright (c) Microsoft Corporation. All rights reserved.
7 * Licensed under the MIT License.
8 */
9var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
10 return new (P || (P = Promise))(function (resolve, reject) {
11 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
14 step((generator = generator.apply(thisArg, _arguments || [])).next());
15 });
16};
17Object.defineProperty(exports, "__esModule", { value: true });
18const os_1 = require("os");
19const botbuilder_core_1 = require("botbuilder-core");
20const botframework_connector_1 = require("botframework-connector");
21const botframework_streaming_1 = require("botframework-streaming");
22const streaming_1 = require("./streaming");
23const activityValidator_1 = require("./activityValidator");
24// Retrieve additional information, i.e., host operating system, host OS release, architecture, Node.js version
25const ARCHITECTURE = os_1.arch();
26const TYPE = os_1.type();
27const RELEASE = os_1.release();
28const NODE_VERSION = process.version;
29// eslint-disable-next-line @typescript-eslint/no-var-requires
30const pjson = require('../package.json');
31exports.USER_AGENT = `Microsoft-BotFramework/3.1 BotBuilder/${pjson.version} ` +
32 `(Node.js,Version=${NODE_VERSION}; ${TYPE} ${RELEASE}; ${ARCHITECTURE})`;
33const OAUTH_ENDPOINT = 'https://api.botframework.com';
34const US_GOV_OAUTH_ENDPOINT = 'https://api.botframework.azure.us';
35/**
36 * A [BotAdapter](xref:botbuilder-core.BotAdapter) that can connect a bot to a service endpoint.
37 * Implements [IUserTokenProvider](xref:botbuilder-core.IUserTokenProvider).
38 *
39 * @remarks
40 * The bot adapter encapsulates authentication processes and sends activities to and receives
41 * activities from the Bot Connector Service. When your bot receives an activity, the adapter
42 * creates a turn context object, passes it to your bot application logic, and sends responses
43 * back to the user's channel.
44 *
45 * The adapter processes and directs incoming activities in through the bot middleware pipeline to
46 * your bot logic and then back out again. As each activity flows in and out of the bot, each
47 * piece of middleware can inspect or act upon the activity, both before and after the bot logic runs.
48 * Use the [use](xref:botbuilder-core.BotAdapter.use) method to add [Middleware](xref:botbuilder-core.Middleware)
49 * objects to your adapter's middleware collection.
50 *
51 * For more information, see the articles on
52 * [How bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and
53 * [Middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware).
54 *
55 * For example:
56 * ```JavaScript
57 * const { BotFrameworkAdapter } = require('botbuilder');
58 *
59 * const adapter = new BotFrameworkAdapter({
60 * appId: process.env.MicrosoftAppId,
61 * appPassword: process.env.MicrosoftAppPassword
62 * });
63 *
64 * adapter.onTurnError = async (context, error) => {
65 * // Catch-all logic for errors.
66 * };
67 * ```
68 */
69class BotFrameworkAdapter extends botbuilder_core_1.BotAdapter {
70 /**
71 * Creates a new instance of the [BotFrameworkAdapter](xref:botbuilder.BotFrameworkAdapter) class.
72 *
73 * @param settings Optional. The settings to use for this adapter instance.
74 *
75 * @remarks
76 * If the `settings` parameter does not include
77 * [channelService](xref:botbuilder.BotFrameworkAdapterSettings.channelService) or
78 * [openIdMetadata](xref:botbuilder.BotFrameworkAdapterSettings.openIdMetadata) values, the
79 * constructor checks the process' environment variables for these values. These values may be
80 * set when a bot is provisioned on Azure and if so are required for the bot to work properly
81 * in the global cloud or in a national cloud.
82 *
83 * The [BotFrameworkAdapterSettings](xref:botbuilder.BotFrameworkAdapterSettings) class defines
84 * the available adapter settings.
85 */
86 constructor(settings) {
87 super();
88 // These keys are public to permit access to the keys from the adapter when it's a being
89 // from library that does not have access to static properties off of BotFrameworkAdapter.
90 // E.g. botbuilder-dialogs
91 this.ConnectorClientKey = Symbol('ConnectorClient');
92 this.TokenApiClientCredentialsKey = Symbol('TokenApiClientCredentials');
93 this.settings = Object.assign({ appId: '', appPassword: '' }, settings);
94 // If settings.certificateThumbprint & settings.certificatePrivateKey are provided,
95 // use CertificateAppCredentials.
96 if (this.settings.certificateThumbprint && this.settings.certificatePrivateKey) {
97 this.credentials = new botframework_connector_1.CertificateAppCredentials(this.settings.appId, settings.certificateThumbprint, settings.certificatePrivateKey, this.settings.channelAuthTenant);
98 this.credentialsProvider = new botframework_connector_1.SimpleCredentialProvider(this.credentials.appId, '');
99 }
100 else {
101 this.credentials = new botframework_connector_1.MicrosoftAppCredentials(this.settings.appId, this.settings.appPassword || '', this.settings.channelAuthTenant);
102 this.credentialsProvider = new botframework_connector_1.SimpleCredentialProvider(this.credentials.appId, this.settings.appPassword || '');
103 }
104 this.isEmulatingOAuthCards = false;
105 // If no channelService or openIdMetadata values were passed in the settings, check the process' Environment Variables for values.
106 // These values may be set when a bot is provisioned on Azure and if so are required for the bot to properly work in Public Azure or a National Cloud.
107 this.settings.channelService = this.settings.channelService || process.env[botframework_connector_1.AuthenticationConstants.ChannelService];
108 this.settings.openIdMetadata = this.settings.openIdMetadata || process.env[botframework_connector_1.AuthenticationConstants.BotOpenIdMetadataKey];
109 this.authConfiguration = this.settings.authConfig || new botframework_connector_1.AuthenticationConfiguration();
110 if (this.settings.openIdMetadata) {
111 botframework_connector_1.ChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata;
112 botframework_connector_1.GovernmentChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata;
113 }
114 if (botframework_connector_1.JwtTokenValidation.isGovernment(this.settings.channelService)) {
115 this.credentials.oAuthEndpoint = botframework_connector_1.GovernmentConstants.ToChannelFromBotLoginUrl;
116 this.credentials.oAuthScope = botframework_connector_1.GovernmentConstants.ToChannelFromBotOAuthScope;
117 }
118 // If a NodeWebSocketFactoryBase was passed in, set it on the BotFrameworkAdapter.
119 if (this.settings.webSocketFactory) {
120 this.webSocketFactory = this.settings.webSocketFactory;
121 }
122 // Relocate the tenantId field used by MS Teams to a new location (from channelData to conversation)
123 // This will only occur on activities from teams that include tenant info in channelData but NOT in conversation,
124 // thus should be future friendly. However, once the the transition is complete. we can remove this.
125 this.use((context, next) => __awaiter(this, void 0, void 0, function* () {
126 if (context.activity.channelId === 'msteams' && context.activity && context.activity.conversation && !context.activity.conversation.tenantId && context.activity.channelData && context.activity.channelData.tenant) {
127 context.activity.conversation.tenantId = context.activity.channelData.tenant.id;
128 }
129 yield next();
130 }));
131 }
132 /**
133 * Used in streaming contexts to check if the streaming connection is still open for the bot to send activities.
134 */
135 get isStreamingConnectionOpen() {
136 return this.streamingServer.isConnected;
137 }
138 continueConversation(reference, oAuthScopeOrlogic, logic) {
139 return __awaiter(this, void 0, void 0, function* () {
140 let audience = oAuthScopeOrlogic;
141 let callback = typeof oAuthScopeOrlogic === 'function' ? oAuthScopeOrlogic : logic;
142 if (typeof oAuthScopeOrlogic === 'function') {
143 // Because the OAuthScope parameter was not provided, get the correct value via the channelService.
144 // In this scenario, the ConnectorClient for the continued conversation can only communicate with
145 // official channels, not with other bots.
146 audience = botframework_connector_1.JwtTokenValidation.isGovernment(this.settings.channelService) ? botframework_connector_1.GovernmentConstants.ToChannelFromBotOAuthScope : botframework_connector_1.AuthenticationConstants.ToChannelFromBotOAuthScope;
147 }
148 let credentials = this.credentials;
149 // For authenticated flows (where the bot has an AppId), the ConversationReference's serviceUrl needs
150 // to be trusted for the bot to acquire a token when sending activities to the conversation.
151 // For anonymous flows, the serviceUrl should not be trusted.
152 if (credentials.appId) {
153 botframework_connector_1.AppCredentials.trustServiceUrl(reference.serviceUrl);
154 // If the provided OAuthScope doesn't match the current one on the instance's credentials, create
155 // a new AppCredentials with the correct OAuthScope.
156 if (credentials.oAuthScope !== audience) {
157 // The BotFrameworkAdapter JavaScript implementation supports one Bot per instance, so get
158 // the botAppId from the credentials.
159 credentials = yield this.buildCredentials(credentials.appId, audience);
160 }
161 }
162 const connectorClient = this.createConnectorClientInternal(reference.serviceUrl, credentials);
163 const request = botbuilder_core_1.TurnContext.applyConversationReference({ type: 'event', name: 'continueConversation' }, reference, true);
164 const context = this.createContext(request);
165 context.turnState.set(this.OAuthScopeKey, audience);
166 context.turnState.set(this.ConnectorClientKey, connectorClient);
167 yield this.runMiddleware(context, callback);
168 });
169 }
170 /**
171 * Asynchronously creates and starts a conversation with a user on a channel.
172 *
173 * @param reference A reference for the conversation to create.
174 * @param logic The asynchronous method to call after the adapter middleware runs.
175 *
176 * @remarks
177 * To use this method, you need both the bot's and the user's account information on a channel.
178 * The Bot Connector service supports the creating of group conversations; however, this
179 * method and most channels only support initiating a direct message (non-group) conversation.
180 *
181 * To create and start a new conversation:
182 * 1. Get a copy of a [ConversationReference](xref:botframework-schema.ConversationReference) from an incoming activity.
183 * 1. Set the [user](xref:botframework-schema.ConversationReference.user) property to the
184 * [ChannelAccount](xref:botframework-schema.ChannelAccount) value for the intended recipient.
185 * 1. Call this method to request that the channel create a new conversation with the specified user.
186 * 1. On success, the adapter generates a turn context and calls the `logic` function handler.
187 *
188 * To get the initial reference, use the
189 * [TurnContext.getConversationReference](xref:botbuilder-core.TurnContext.getConversationReference)
190 * method on any incoming activity in the conversation.
191 *
192 * If the channel establishes the conversation, the generated event activity's
193 * [conversation](xref:botframework-schema.Activity.conversation) property will contain the
194 * ID of the new conversation.
195 *
196 * This method is similar to the [processActivity](xref:botbuilder.BotFrameworkAdapter.processActivity) method.
197 * The adapter creates a [TurnContext](xref:botbuilder-core.TurnContext) and routes it through
198 * middleware before calling the `logic` handler. The created activity will have a
199 * [type](xref:botframework-schema.Activity.type) of 'event' and a
200 * [name](xref:botframework-schema.Activity.name) of 'createConversation'.
201 *
202 * For example:
203 * ```JavaScript
204 * // Get group members conversation reference
205 * const reference = TurnContext.getConversationReference(context.activity);
206 *
207 * // ...
208 * // Start a new conversation with the user
209 * await adapter.createConversation(reference, async (ctx) => {
210 * await ctx.sendActivity(`Hi (in private)`);
211 * });
212 * ```
213 */
214 createConversation(reference, logic) {
215 return __awaiter(this, void 0, void 0, function* () {
216 if (!reference.serviceUrl) {
217 throw new Error(`BotFrameworkAdapter.createConversation(): missing serviceUrl.`);
218 }
219 // Create conversation
220 const parameters = { bot: reference.bot, members: [reference.user], isGroup: false, activity: null, channelData: null };
221 const client = this.createConnectorClient(reference.serviceUrl);
222 // Mix in the tenant ID if specified. This is required for MS Teams.
223 if (reference.conversation && reference.conversation.tenantId) {
224 // Putting tenantId in channelData is a temporary solution while we wait for the Teams API to be updated
225 parameters.channelData = { tenant: { id: reference.conversation.tenantId } };
226 // Permanent solution is to put tenantId in parameters.tenantId
227 parameters.tenantId = reference.conversation.tenantId;
228 }
229 const response = yield client.conversations.createConversation(parameters);
230 // Initialize request and copy over new conversation ID and updated serviceUrl.
231 const request = botbuilder_core_1.TurnContext.applyConversationReference({ type: 'event', name: 'createConversation' }, reference, true);
232 const conversation = {
233 id: response.id,
234 isGroup: false,
235 conversationType: null,
236 tenantId: reference.conversation.tenantId,
237 name: null,
238 };
239 request.conversation = conversation;
240 request.channelData = parameters.channelData;
241 if (response.serviceUrl) {
242 request.serviceUrl = response.serviceUrl;
243 }
244 // Create context and run middleware
245 const context = this.createContext(request);
246 yield this.runMiddleware(context, logic);
247 });
248 }
249 /**
250 * Asynchronously deletes an existing activity.
251 *
252 * This interface supports the framework and is not intended to be called directly for your code.
253 * Use [TurnContext.deleteActivity](xref:botbuilder-core.TurnContext.deleteActivity) to delete
254 * an activity from your bot code.
255 *
256 * @param context The context object for the turn.
257 * @param reference Conversation reference information for the activity to delete.
258 *
259 * @remarks
260 * Not all channels support this operation. For channels that don't, this call may throw an exception.
261 */
262 deleteActivity(context, reference) {
263 return __awaiter(this, void 0, void 0, function* () {
264 if (!reference.serviceUrl) {
265 throw new Error(`BotFrameworkAdapter.deleteActivity(): missing serviceUrl`);
266 }
267 if (!reference.conversation || !reference.conversation.id) {
268 throw new Error(`BotFrameworkAdapter.deleteActivity(): missing conversation or conversation.id`);
269 }
270 if (!reference.activityId) {
271 throw new Error(`BotFrameworkAdapter.deleteActivity(): missing activityId`);
272 }
273 const client = this.getOrCreateConnectorClient(context, reference.serviceUrl, this.credentials);
274 yield client.conversations.deleteActivity(reference.conversation.id, reference.activityId);
275 });
276 }
277 /**
278 * Asynchronously removes a member from the current conversation.
279 *
280 * @param context The context object for the turn.
281 * @param memberId The ID of the member to remove from the conversation.
282 *
283 * @remarks
284 * Remove a member's identity information from the conversation.
285 *
286 * Not all channels support this operation. For channels that don't, this call may throw an exception.
287 */
288 deleteConversationMember(context, memberId) {
289 return __awaiter(this, void 0, void 0, function* () {
290 if (!context.activity.serviceUrl) {
291 throw new Error(`BotFrameworkAdapter.deleteConversationMember(): missing serviceUrl`);
292 }
293 if (!context.activity.conversation || !context.activity.conversation.id) {
294 throw new Error(`BotFrameworkAdapter.deleteConversationMember(): missing conversation or conversation.id`);
295 }
296 const serviceUrl = context.activity.serviceUrl;
297 const conversationId = context.activity.conversation.id;
298 const client = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials);
299 yield client.conversations.deleteConversationMember(conversationId, memberId);
300 });
301 }
302 /**
303 * Asynchronously lists the members of a given activity.
304 *
305 * @param context The context object for the turn.
306 * @param activityId Optional. The ID of the activity to get the members of. If not specified, the current activity ID is used.
307 *
308 * @returns An array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for
309 * the users involved in a given activity.
310 *
311 * @remarks
312 * Returns an array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for
313 * the users involved in a given activity.
314 *
315 * This is different from [getConversationMembers](xref:botbuilder.BotFrameworkAdapter.getConversationMembers)
316 * in that it will return only those users directly involved in the activity, not all members of the conversation.
317 */
318 getActivityMembers(context, activityId) {
319 return __awaiter(this, void 0, void 0, function* () {
320 if (!activityId) {
321 activityId = context.activity.id;
322 }
323 if (!context.activity.serviceUrl) {
324 throw new Error(`BotFrameworkAdapter.getActivityMembers(): missing serviceUrl`);
325 }
326 if (!context.activity.conversation || !context.activity.conversation.id) {
327 throw new Error(`BotFrameworkAdapter.getActivityMembers(): missing conversation or conversation.id`);
328 }
329 if (!activityId) {
330 throw new Error(`BotFrameworkAdapter.getActivityMembers(): missing both activityId and context.activity.id`);
331 }
332 const serviceUrl = context.activity.serviceUrl;
333 const conversationId = context.activity.conversation.id;
334 const client = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials);
335 return yield client.conversations.getActivityMembers(conversationId, activityId);
336 });
337 }
338 /**
339 * Asynchronously lists the members of the current conversation.
340 *
341 * @param context The context object for the turn.
342 *
343 * @returns An array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for
344 * all users currently involved in a conversation.
345 *
346 * @remarks
347 * Returns an array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for
348 * all users currently involved in a conversation.
349 *
350 * This is different from [getActivityMembers](xref:botbuilder.BotFrameworkAdapter.getActivityMembers)
351 * in that it will return all members of the conversation, not just those directly involved in a specific activity.
352 */
353 getConversationMembers(context) {
354 return __awaiter(this, void 0, void 0, function* () {
355 if (!context.activity.serviceUrl) {
356 throw new Error(`BotFrameworkAdapter.getConversationMembers(): missing serviceUrl`);
357 }
358 if (!context.activity.conversation || !context.activity.conversation.id) {
359 throw new Error(`BotFrameworkAdapter.getConversationMembers(): missing conversation or conversation.id`);
360 }
361 const serviceUrl = context.activity.serviceUrl;
362 const conversationId = context.activity.conversation.id;
363 const client = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials);
364 return yield client.conversations.getConversationMembers(conversationId);
365 });
366 }
367 /**
368 * For the specified channel, asynchronously gets a page of the conversations in which this bot has participated.
369 *
370 * @param contextOrServiceUrl The URL of the channel server to query or a
371 * [TurnContext](xref:botbuilder-core.TurnContext) object from a conversation on the channel.
372 * @param continuationToken Optional. The continuation token from the previous page of results.
373 * Omit this parameter or use `undefined` to retrieve the first page of results.
374 *
375 * @returns A [ConversationsResult](xref:botframework-schema.ConversationsResult) object containing a page of results
376 * and a continuation token.
377 *
378 * @remarks
379 * The the return value's [conversations](xref:botframework-schema.ConversationsResult.conversations) property contains a page of
380 * [ConversationMembers](xref:botframework-schema.ConversationMembers) objects. Each object's
381 * [id](xref:botframework-schema.ConversationMembers.id) is the ID of a conversation in which the bot has participated on this channel.
382 * This method can be called from outside the context of a conversation, as only the bot's service URL and credentials are required.
383 *
384 * The channel batches results in pages. If the result's
385 * [continuationToken](xref:botframework-schema.ConversationsResult.continuationToken) property is not empty, then
386 * there are more pages to get. Use the returned token to get the next page of results.
387 * If the `contextOrServiceUrl` parameter is a [TurnContext](xref:botbuilder-core.TurnContext), the URL of the channel server is
388 * retrieved from
389 * `contextOrServiceUrl`.[activity](xref:botbuilder-core.TurnContext.activity).[serviceUrl](xref:botframework-schema.Activity.serviceUrl).
390 */
391 getConversations(contextOrServiceUrl, continuationToken) {
392 return __awaiter(this, void 0, void 0, function* () {
393 let client;
394 if (typeof contextOrServiceUrl === 'object') {
395 const context = contextOrServiceUrl;
396 client = this.getOrCreateConnectorClient(context, context.activity.serviceUrl, this.credentials);
397 }
398 else {
399 client = this.createConnectorClient(contextOrServiceUrl);
400 }
401 return yield client.conversations.getConversations(continuationToken ? { continuationToken: continuationToken } : undefined);
402 });
403 }
404 getUserToken(context, connectionName, magicCode, oAuthAppCredentials) {
405 return __awaiter(this, void 0, void 0, function* () {
406 if (!context.activity.from || !context.activity.from.id) {
407 throw new Error(`BotFrameworkAdapter.getUserToken(): missing from or from.id`);
408 }
409 if (!connectionName) {
410 throw new Error('getUserToken() requires a connectionName but none was provided.');
411 }
412 this.checkEmulatingOAuthCards(context);
413 const userId = context.activity.from.id;
414 const url = this.oauthApiUrl(context);
415 const client = this.createTokenApiClient(url, oAuthAppCredentials);
416 context.turnState.set(this.TokenApiClientCredentialsKey, client);
417 const result = yield client.userToken.getToken(userId, connectionName, { code: magicCode, channelId: context.activity.channelId });
418 if (!result || !result.token || result._response.status == 404) {
419 return undefined;
420 }
421 else {
422 return result;
423 }
424 });
425 }
426 signOutUser(context, connectionName, userId, oAuthAppCredentials) {
427 return __awaiter(this, void 0, void 0, function* () {
428 if (!context.activity.from || !context.activity.from.id) {
429 throw new Error(`BotFrameworkAdapter.signOutUser(): missing from or from.id`);
430 }
431 if (!userId) {
432 userId = context.activity.from.id;
433 }
434 this.checkEmulatingOAuthCards(context);
435 const url = this.oauthApiUrl(context);
436 const client = this.createTokenApiClient(url, oAuthAppCredentials);
437 context.turnState.set(this.TokenApiClientCredentialsKey, client);
438 yield client.userToken.signOut(userId, { connectionName: connectionName, channelId: context.activity.channelId });
439 });
440 }
441 getSignInLink(context, connectionName, oAuthAppCredentials, userId, finalRedirect) {
442 return __awaiter(this, void 0, void 0, function* () {
443 if (userId && userId != context.activity.from.id) {
444 throw new ReferenceError(`cannot retrieve OAuth signin link for a user that's different from the conversation`);
445 }
446 this.checkEmulatingOAuthCards(context);
447 const conversation = botbuilder_core_1.TurnContext.getConversationReference(context.activity);
448 const url = this.oauthApiUrl(context);
449 const client = this.createTokenApiClient(url, oAuthAppCredentials);
450 context.turnState.set(this.TokenApiClientCredentialsKey, client);
451 const state = {
452 ConnectionName: connectionName,
453 Conversation: conversation,
454 MsAppId: client.credentials.appId,
455 RelatesTo: context.activity.relatesTo
456 };
457 const finalState = Buffer.from(JSON.stringify(state)).toString('base64');
458 return (yield client.botSignIn.getSignInUrl(finalState, { channelId: context.activity.channelId, finalRedirect }))._response.bodyAsText;
459 });
460 }
461 getTokenStatus(context, userId, includeFilter, oAuthAppCredentials) {
462 return __awaiter(this, void 0, void 0, function* () {
463 if (!userId && (!context.activity.from || !context.activity.from.id)) {
464 throw new Error(`BotFrameworkAdapter.getTokenStatus(): missing from or from.id`);
465 }
466 this.checkEmulatingOAuthCards(context);
467 userId = userId || context.activity.from.id;
468 const url = this.oauthApiUrl(context);
469 const client = this.createTokenApiClient(url, oAuthAppCredentials);
470 context.turnState.set(this.TokenApiClientCredentialsKey, client);
471 return (yield client.userToken.getTokenStatus(userId, { channelId: context.activity.channelId, include: includeFilter }))._response.parsedBody;
472 });
473 }
474 getAadTokens(context, connectionName, resourceUrls, oAuthAppCredentials) {
475 return __awaiter(this, void 0, void 0, function* () {
476 if (!context.activity.from || !context.activity.from.id) {
477 throw new Error(`BotFrameworkAdapter.getAadTokens(): missing from or from.id`);
478 }
479 this.checkEmulatingOAuthCards(context);
480 const userId = context.activity.from.id;
481 const url = this.oauthApiUrl(context);
482 const client = this.createTokenApiClient(url, oAuthAppCredentials);
483 context.turnState.set(this.TokenApiClientCredentialsKey, client);
484 return (yield client.userToken.getAadTokens(userId, connectionName, { resourceUrls: resourceUrls }, { channelId: context.activity.channelId }))._response.parsedBody;
485 });
486 }
487 /**
488 * Asynchronously Get the raw signin resource to be sent to the user for signin.
489 *
490 * @param context The context object for the turn.
491 * @param connectionName The name of the auth connection to use.
492 * @param userId The user id that will be associated with the token.
493 * @param finalRedirect The final URL that the OAuth flow will redirect to.
494 *
495 * @returns The [BotSignInGetSignInResourceResponse](xref:botframework-connector.BotSignInGetSignInResourceResponse) object.
496 */
497 getSignInResource(context, connectionName, userId, finalRedirect, appCredentials) {
498 return __awaiter(this, void 0, void 0, function* () {
499 if (!connectionName) {
500 throw new Error('getUserToken() requires a connectionName but none was provided.');
501 }
502 if (!context.activity.from || !context.activity.from.id) {
503 throw new Error(`BotFrameworkAdapter.getSignInResource(): missing from or from.id`);
504 }
505 // The provided userId doesn't match the from.id on the activity. (same for finalRedirect)
506 if (userId && context.activity.from.id !== userId) {
507 throw new Error('BotFrameworkAdapter.getSiginInResource(): cannot get signin resource for a user that is different from the conversation');
508 }
509 const url = this.oauthApiUrl(context);
510 const credentials = appCredentials;
511 const client = this.createTokenApiClient(url, credentials);
512 const conversation = botbuilder_core_1.TurnContext.getConversationReference(context.activity);
513 const state = {
514 ConnectionName: connectionName,
515 Conversation: conversation,
516 relatesTo: context.activity.relatesTo,
517 MSAppId: client.credentials.appId
518 };
519 const finalState = Buffer.from(JSON.stringify(state)).toString('base64');
520 const options = { finalRedirect: finalRedirect };
521 return yield (client.botSignIn.getSignInResource(finalState, options));
522 });
523 }
524 exchangeToken(context, connectionName, userId, tokenExchangeRequest, appCredentials) {
525 return __awaiter(this, void 0, void 0, function* () {
526 if (!connectionName) {
527 throw new Error('exchangeToken() requires a connectionName but none was provided.');
528 }
529 if (!userId) {
530 throw new Error('exchangeToken() requires an userId but none was provided.');
531 }
532 if (tokenExchangeRequest && !tokenExchangeRequest.token && !tokenExchangeRequest.uri) {
533 throw new Error('BotFrameworkAdapter.exchangeToken(): Either a Token or Uri property is required on the TokenExchangeRequest');
534 }
535 const url = this.oauthApiUrl(context);
536 const client = this.createTokenApiClient(url, appCredentials);
537 return (yield client.userToken.exchangeAsync(userId, connectionName, context.activity.channelId, tokenExchangeRequest))._response.parsedBody;
538 });
539 }
540 /**
541 * Asynchronously sends an emulated OAuth card for a channel.
542 *
543 * This method supports the framework and is not intended to be called directly for your code.
544 *
545 * @param contextOrServiceUrl The URL of the emulator.
546 * @param emulate `true` to send an emulated OAuth card to the emulator; or `false` to not send the card.
547 *
548 * @remarks
549 * When testing a bot in the Bot Framework Emulator, this method can emulate the OAuth card interaction.
550 */
551 emulateOAuthCards(contextOrServiceUrl, emulate) {
552 return __awaiter(this, void 0, void 0, function* () {
553 this.isEmulatingOAuthCards = emulate;
554 const url = this.oauthApiUrl(contextOrServiceUrl);
555 yield botframework_connector_1.EmulatorApiClient.emulateOAuthCards(this.credentials, url, emulate);
556 });
557 }
558 /**
559 * Asynchronously creates a turn context and runs the middleware pipeline for an incoming activity.
560 *
561 * @param req An Express or Restify style request object.
562 * @param res An Express or Restify style response object.
563 * @param logic The function to call at the end of the middleware pipeline.
564 *
565 * @remarks
566 * This is the main way a bot receives incoming messages and defines a turn in the conversation. This method:
567 *
568 * 1. Parses and authenticates an incoming request.
569 * - The activity is read from the body of the incoming request. An error will be returned
570 * if the activity can't be parsed.
571 * - The identity of the sender is authenticated as either the Emulator or a valid Microsoft
572 * server, using the bot's `appId` and `appPassword`. The request is rejected if the sender's
573 * identity is not verified.
574 * 1. Creates a [TurnContext](xref:botbuilder-core.TurnContext) object for the received activity.
575 * - This object is wrapped with a [revocable proxy](https://www.ecma-international.org/ecma-262/6.0/#sec-proxy.revocable).
576 * - When this method completes, the proxy is revoked.
577 * 1. Sends the turn context through the adapter's middleware pipeline.
578 * 1. Sends the turn context to the `logic` function.
579 * - The bot may perform additional routing or processing at this time.
580 * Returning a promise (or providing an `async` handler) will cause the adapter to wait for any asynchronous operations to complete.
581 * - After the `logic` function completes, the promise chain set up by the middleware is resolved.
582 *
583 * > [!TIP]
584 * > If you see the error `TypeError: Cannot perform 'set' on a proxy that has been revoked`
585 * > in your bot's console output, the likely cause is that an async function was used
586 * > without using the `await` keyword. Make sure all async functions use await!
587 *
588 * Middleware can _short circuit_ a turn. When this happens, subsequent middleware and the
589 * `logic` function is not called; however, all middleware prior to this point still run to completion.
590 * For more information about the middleware pipeline, see the
591 * [how bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and
592 * [middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware) articles.
593 * Use the adapter's [use](xref:botbuilder-core.BotAdapter.use) method to add middleware to the adapter.
594 *
595 * For example:
596 * ```JavaScript
597 * server.post('/api/messages', (req, res) => {
598 * // Route received request to adapter for processing
599 * adapter.processActivity(req, res, async (context) => {
600 * // Process any messages received
601 * if (context.activity.type === ActivityTypes.Message) {
602 * await context.sendActivity(`Hello World`);
603 * }
604 * });
605 * });
606 * ```
607 */
608 processActivity(req, res, logic) {
609 return __awaiter(this, void 0, void 0, function* () {
610 let body;
611 let status;
612 let processError;
613 try {
614 // Parse body of request
615 status = 400;
616 const request = yield parseRequest(req);
617 // Authenticate the incoming request
618 status = 401;
619 const authHeader = req.headers.authorization || req.headers.Authorization || '';
620 const identity = yield this.authenticateRequestInternal(request, authHeader);
621 // Set the correct callerId value and discard values received over the wire
622 request.callerId = yield this.generateCallerId(identity);
623 // Process received activity
624 status = 500;
625 const context = this.createContext(request);
626 context.turnState.set(this.BotIdentityKey, identity);
627 const connectorClient = yield this.createConnectorClientWithIdentity(request.serviceUrl, identity);
628 context.turnState.set(this.ConnectorClientKey, connectorClient);
629 const oAuthScope = botframework_connector_1.SkillValidation.isSkillClaim(identity.claims) ? botframework_connector_1.JwtTokenValidation.getAppIdFromClaims(identity.claims) : this.credentials.oAuthScope;
630 context.turnState.set(this.OAuthScopeKey, oAuthScope);
631 context.turnState.set(botbuilder_core_1.BotCallbackHandlerKey, logic);
632 yield this.runMiddleware(context, logic);
633 // NOTE: The factoring of the code differs here when compared to C# as processActivity() returns Promise<void>.
634 // This is due to the fact that the response to the incoming activity is sent from inside this implementation.
635 // In C#, ProcessActivityAsync() returns Task<InvokeResponse> and ASP.NET handles sending of the response.
636 if (request.deliveryMode === botbuilder_core_1.DeliveryModes.ExpectReplies) {
637 // Handle "expectReplies" scenarios where all the activities have been buffered and sent back at once
638 // in an invoke response.
639 const expectedReplies = { activities: context.bufferedReplyActivities };
640 body = expectedReplies;
641 status = botbuilder_core_1.StatusCodes.OK;
642 }
643 else if (request.type === botbuilder_core_1.ActivityTypes.Invoke) {
644 // Retrieve a cached Invoke response to handle Invoke scenarios.
645 // These scenarios deviate from the request/request model as the Bot should return a specific body and status.
646 const invokeResponse = context.turnState.get(botbuilder_core_1.INVOKE_RESPONSE_KEY);
647 if (invokeResponse && invokeResponse.value) {
648 const value = invokeResponse.value;
649 status = value.status;
650 body = value.body;
651 }
652 else {
653 status = botbuilder_core_1.StatusCodes.NOT_IMPLEMENTED;
654 }
655 }
656 else {
657 status = botbuilder_core_1.StatusCodes.OK;
658 }
659 }
660 catch (err) {
661 // Catch the error to try and throw the stacktrace out of processActivity()
662 processError = err;
663 body = err.toString();
664 }
665 // Return status
666 res.status(status);
667 if (body) {
668 res.send(body);
669 }
670 res.end();
671 // Check for an error
672 if (status >= 400) {
673 if (processError && processError.stack) {
674 throw new Error(`BotFrameworkAdapter.processActivity(): ${status} ERROR\n ${processError.stack}`);
675 }
676 else {
677 throw new Error(`BotFrameworkAdapter.processActivity(): ${status} ERROR`);
678 }
679 }
680 });
681 }
682 /**
683 * Asynchronously creates a turn context and runs the middleware pipeline for an incoming activity.
684 *
685 * @param activity The activity to process.
686 * @param logic The function to call at the end of the middleware pipeline.
687 *
688 * @remarks
689 * This is the main way a bot receives incoming messages and defines a turn in the conversation. This method:
690 *
691 * 1. Creates a [TurnContext](xref:botbuilder-core.TurnContext) object for the received activity.
692 * - This object is wrapped with a [revocable proxy](https://www.ecma-international.org/ecma-262/6.0/#sec-proxy.revocable).
693 * - When this method completes, the proxy is revoked.
694 * 1. Sends the turn context through the adapter's middleware pipeline.
695 * 1. Sends the turn context to the `logic` function.
696 * - The bot may perform additional routing or processing at this time.
697 * Returning a promise (or providing an `async` handler) will cause the adapter to wait for any asynchronous operations to complete.
698 * - After the `logic` function completes, the promise chain set up by the middleware is resolved.
699 *
700 * Middleware can _short circuit_ a turn. When this happens, subsequent middleware and the
701 * `logic` function is not called; however, all middleware prior to this point still run to completion.
702 * For more information about the middleware pipeline, see the
703 * [how bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and
704 * [middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware) articles.
705 * Use the adapter's [use](xref:botbuilder-core.BotAdapter.use) method to add middleware to the adapter.
706 */
707 processActivityDirect(activity, logic) {
708 return __awaiter(this, void 0, void 0, function* () {
709 let processError;
710 try {
711 // Process activity
712 const context = this.createContext(activity);
713 context.turnState.set(botbuilder_core_1.BotCallbackHandlerKey, logic);
714 yield this.runMiddleware(context, logic);
715 }
716 catch (err) {
717 // Catch the error to try and throw the stacktrace out of processActivity()
718 processError = err;
719 }
720 if (processError) {
721 if (processError && processError.stack) {
722 throw new Error(`BotFrameworkAdapter.processActivityDirect(): ERROR\n ${processError.stack}`);
723 }
724 else {
725 throw new Error(`BotFrameworkAdapter.processActivityDirect(): ERROR`);
726 }
727 }
728 });
729 }
730 /**
731 * Asynchronously sends a set of outgoing activities to a channel server.
732 *
733 * This method supports the framework and is not intended to be called directly for your code.
734 * Use the turn context's [sendActivity](xref:botbuilder-core.TurnContext.sendActivity) or
735 * [sendActivities](xref:botbuilder-core.TurnContext.sendActivities) method from your bot code.
736 *
737 * @param context The context object for the turn.
738 * @param activities The activities to send.
739 *
740 * @returns An array of [ResourceResponse](xref:)
741 *
742 * @remarks
743 * The activities will be sent one after another in the order in which they're received. A
744 * response object will be returned for each sent activity. For `message` activities this will
745 * contain the ID of the delivered message.
746 */
747 sendActivities(context, activities) {
748 return __awaiter(this, void 0, void 0, function* () {
749 const responses = [];
750 for (let i = 0; i < activities.length; i++) {
751 const activity = activities[i];
752 switch (activity.type) {
753 case 'delay':
754 yield delay(typeof activity.value === 'number' ? activity.value : 1000);
755 responses.push({});
756 break;
757 case 'invokeResponse':
758 // Cache response to context object. This will be retrieved when turn completes.
759 context.turnState.set(botbuilder_core_1.INVOKE_RESPONSE_KEY, activity);
760 responses.push({});
761 break;
762 default:
763 if (!activity.serviceUrl) {
764 throw new Error(`BotFrameworkAdapter.sendActivity(): missing serviceUrl.`);
765 }
766 if (!activity.conversation || !activity.conversation.id) {
767 throw new Error(`BotFrameworkAdapter.sendActivity(): missing conversation id.`);
768 }
769 if (activity && BotFrameworkAdapter.isStreamingServiceUrl(activity.serviceUrl)) {
770 if (!this.isStreamingConnectionOpen) {
771 throw new Error('BotFrameworkAdapter.sendActivities(): Unable to send activity as Streaming connection is closed.');
772 }
773 streaming_1.TokenResolver.checkForOAuthCards(this, context, activity);
774 }
775 const client = this.getOrCreateConnectorClient(context, activity.serviceUrl, this.credentials);
776 if (activity.type === 'trace' && activity.channelId !== 'emulator') {
777 // Just eat activity
778 responses.push({});
779 }
780 else if (activity.replyToId) {
781 responses.push(yield client.conversations.replyToActivity(activity.conversation.id, activity.replyToId, activity));
782 }
783 else {
784 responses.push(yield client.conversations.sendToConversation(activity.conversation.id, activity));
785 }
786 break;
787 }
788 }
789 return responses;
790 });
791 }
792 /**
793 * Asynchronously replaces a previous activity with an updated version.
794 *
795 * This interface supports the framework and is not intended to be called directly for your code.
796 * Use [TurnContext.updateActivity](xref:botbuilder-core.TurnContext.updateActivity) to update
797 * an activity from your bot code.
798 *
799 * @param context The context object for the turn.
800 * @param activity The updated version of the activity to replace.
801 *
802 * @remarks
803 * Not all channels support this operation. For channels that don't, this call may throw an exception.
804 */
805 updateActivity(context, activity) {
806 return __awaiter(this, void 0, void 0, function* () {
807 if (!activity.serviceUrl) {
808 throw new Error(`BotFrameworkAdapter.updateActivity(): missing serviceUrl`);
809 }
810 if (!activity.conversation || !activity.conversation.id) {
811 throw new Error(`BotFrameworkAdapter.updateActivity(): missing conversation or conversation.id`);
812 }
813 if (!activity.id) {
814 throw new Error(`BotFrameworkAdapter.updateActivity(): missing activity.id`);
815 }
816 const client = this.getOrCreateConnectorClient(context, activity.serviceUrl, this.credentials);
817 yield client.conversations.updateActivity(activity.conversation.id, activity.id, activity);
818 });
819 }
820 /**
821 * Creates a connector client.
822 *
823 * @param serviceUrl The client's service URL.
824 *
825 * @remarks
826 * Override this in a derived class to create a mock connector client for unit testing.
827 */
828 createConnectorClient(serviceUrl) {
829 return this.createConnectorClientInternal(serviceUrl, this.credentials);
830 }
831 createConnectorClientWithIdentity(serviceUrl, identity, audience) {
832 return __awaiter(this, void 0, void 0, function* () {
833 if (!identity) {
834 throw new Error('BotFrameworkAdapter.createConnectorClientWithIdentity(): invalid identity parameter.');
835 }
836 const botAppId = identity.getClaimValue(botframework_connector_1.AuthenticationConstants.AudienceClaim) ||
837 identity.getClaimValue(botframework_connector_1.AuthenticationConstants.AppIdClaim);
838 // Check if the audience is a string and when trimmed doesn't have a length of 0.
839 const validAudience = typeof audience === 'string' && audience.trim().length > 0;
840 const oAuthScope = validAudience ? audience : yield this.getOAuthScope(botAppId, identity.claims);
841 const credentials = yield this.buildCredentials(botAppId, oAuthScope);
842 const client = this.createConnectorClientInternal(serviceUrl, credentials);
843 return client;
844 });
845 }
846 /**
847 * @private
848 * @param serviceUrl The client's service URL.
849 * @param credentials AppCredentials instance to construct the ConnectorClient with.
850 */
851 createConnectorClientInternal(serviceUrl, credentials) {
852 if (BotFrameworkAdapter.isStreamingServiceUrl(serviceUrl)) {
853 // Check if we have a streaming server. Otherwise, requesting a connector client
854 // for a non-existent streaming connection results in an error
855 if (!this.streamingServer) {
856 throw new Error(`Cannot create streaming connector client for serviceUrl ${serviceUrl} without a streaming connection. Call 'useWebSocket' or 'useNamedPipe' to start a streaming connection.`);
857 }
858 const clientOptions = this.getClientOptions(serviceUrl, new streaming_1.StreamingHttpClient(this.streamingServer));
859 return new botframework_connector_1.ConnectorClient(credentials, clientOptions);
860 }
861 const clientOptions = this.getClientOptions(serviceUrl);
862 return new botframework_connector_1.ConnectorClient(credentials, clientOptions);
863 }
864 /**
865 * @private
866 * @param serviceUrl The service URL to use for the new ConnectorClientOptions.
867 * @param httpClient Optional. The @azure/ms-rest-js.HttpClient to use for the new ConnectorClientOptions.
868 */
869 getClientOptions(serviceUrl, httpClient) {
870 const options = Object.assign({ baseUri: serviceUrl }, this.settings.clientOptions);
871 if (httpClient) {
872 options.httpClient = httpClient;
873 }
874 options.userAgent = `${exports.USER_AGENT}${options.userAgent || ''}`;
875 return options;
876 }
877 /**
878 * @private
879 * Retrieves the ConnectorClient from the TurnContext or creates a new ConnectorClient with the provided serviceUrl and credentials.
880 * @param context
881 * @param serviceUrl
882 * @param credentials
883 */
884 getOrCreateConnectorClient(context, serviceUrl, credentials) {
885 if (!context || !context.turnState)
886 throw new Error('invalid context parameter');
887 if (!serviceUrl)
888 throw new Error('invalid serviceUrl');
889 if (!credentials)
890 throw new Error('invalid credentials');
891 let client = context.turnState.get(this.ConnectorClientKey);
892 // Inspect the retrieved client to confirm that the serviceUrl is correct, if it isn't, create a new one.
893 if (!client || client['baseUri'] !== serviceUrl) {
894 client = this.createConnectorClientInternal(serviceUrl, credentials);
895 }
896 return client;
897 }
898 getOAuthScope(botAppId, claims) {
899 return __awaiter(this, void 0, void 0, function* () {
900 // If the Claims are for skills, we need to create an AppCredentials instance with
901 // the correct scope for communication between the caller and the skill.
902 if (botAppId && botframework_connector_1.SkillValidation.isSkillClaim(claims)) {
903 return botframework_connector_1.JwtTokenValidation.getAppIdFromClaims(claims);
904 }
905 // Return the current credentials' OAuthScope.
906 return this.credentials.oAuthScope;
907 });
908 }
909 /**
910 *
911 * @remarks
912 * When building credentials for bot-to-bot communication, oAuthScope must be passed in.
913 * @param appId
914 * @param oAuthScope
915 */
916 buildCredentials(appId, oAuthScope) {
917 return __awaiter(this, void 0, void 0, function* () {
918 // There is no cache for AppCredentials in JS as opposed to C#.
919 // Instead of retrieving an AppCredentials from the Adapter instance, generate a new one
920 const appPassword = yield this.credentialsProvider.getAppPassword(appId);
921 const credentials = new botframework_connector_1.MicrosoftAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope);
922 if (botframework_connector_1.JwtTokenValidation.isGovernment(this.settings.channelService)) {
923 credentials.oAuthEndpoint = botframework_connector_1.GovernmentConstants.ToChannelFromBotLoginUrl;
924 credentials.oAuthScope = oAuthScope || botframework_connector_1.GovernmentConstants.ToChannelFromBotOAuthScope;
925 }
926 return credentials;
927 });
928 }
929 createTokenApiClient(serviceUrl, oAuthAppCredentials) {
930 const tokenApiClientCredentials = oAuthAppCredentials ? oAuthAppCredentials : this.credentials;
931 const client = new botframework_connector_1.TokenApiClient(tokenApiClientCredentials, { baseUri: serviceUrl, userAgent: exports.USER_AGENT });
932 return client;
933 }
934 /**
935 * Allows for the overriding of authentication in unit tests.
936 * @param request Received request.
937 * @param authHeader Received authentication header.
938 */
939 authenticateRequest(request, authHeader) {
940 return __awaiter(this, void 0, void 0, function* () {
941 const identity = yield this.authenticateRequestInternal(request, authHeader);
942 if (!identity.isAuthenticated) {
943 throw new Error('Unauthorized Access. Request is not authorized');
944 }
945 // Set the correct callerId value and discard values received over the wire
946 request.callerId = yield this.generateCallerId(identity);
947 });
948 }
949 /**
950 * @ignore
951 * @private
952 * Returns the actual ClaimsIdentity from the JwtTokenValidation.authenticateRequest() call.
953 * @remarks
954 * This method is used instead of authenticateRequest() in processActivity() to obtain the ClaimsIdentity for caching in the TurnContext.turnState.
955 *
956 * @param request Received request.
957 * @param authHeader Received authentication header.
958 */
959 authenticateRequestInternal(request, authHeader) {
960 return botframework_connector_1.JwtTokenValidation.authenticateRequest(request, authHeader, this.credentialsProvider, this.settings.channelService, this.authConfiguration);
961 }
962 /**
963 * Generates the CallerId property for the activity based on
964 * https://github.com/microsoft/botframework-obi/blob/master/protocols/botframework-activity/botframework-activity.md#appendix-v---caller-id-values.
965 * @param identity
966 */
967 generateCallerId(identity) {
968 return __awaiter(this, void 0, void 0, function* () {
969 if (!identity) {
970 throw new TypeError('BotFrameworkAdapter.generateCallerId(): Missing identity parameter.');
971 }
972 // Is the bot accepting all incoming messages?
973 const isAuthDisabled = yield this.credentialsProvider.isAuthenticationDisabled();
974 if (isAuthDisabled) {
975 // Return undefined so that the callerId is cleared.
976 return;
977 }
978 // Is the activity from another bot?
979 if (botframework_connector_1.SkillValidation.isSkillClaim(identity.claims)) {
980 const callerId = botframework_connector_1.JwtTokenValidation.getAppIdFromClaims(identity.claims);
981 return `${botbuilder_core_1.CallerIdConstants.BotToBotPrefix}${callerId}`;
982 }
983 // Is the activity from Public Azure?
984 if (!this.settings.channelService || this.settings.channelService.length === 0) {
985 return botbuilder_core_1.CallerIdConstants.PublicAzureChannel;
986 }
987 // Is the activity from Azure Gov?
988 if (botframework_connector_1.JwtTokenValidation.isGovernment(this.settings.channelService)) {
989 return botbuilder_core_1.CallerIdConstants.USGovChannel;
990 }
991 // Return undefined so that the callerId is cleared.
992 });
993 }
994 /**
995 * Gets the OAuth API endpoint.
996 *
997 * @param contextOrServiceUrl The URL of the channel server to query or
998 * a [TurnContext](xref:botbuilder-core.TurnContext). For a turn context, the context's
999 * [activity](xref:botbuilder-core.TurnContext.activity).[serviceUrl](xref:botframework-schema.Activity.serviceUrl)
1000 * is used for the URL.
1001 *
1002 * @remarks
1003 * Override this in a derived class to create a mock OAuth API endpoint for unit testing.
1004 */
1005 oauthApiUrl(contextOrServiceUrl) {
1006 return this.isEmulatingOAuthCards ?
1007 (typeof contextOrServiceUrl === 'object' ? contextOrServiceUrl.activity.serviceUrl : contextOrServiceUrl) :
1008 (this.settings.oAuthEndpoint ? this.settings.oAuthEndpoint :
1009 botframework_connector_1.JwtTokenValidation.isGovernment(this.settings.channelService) ?
1010 US_GOV_OAUTH_ENDPOINT : OAUTH_ENDPOINT);
1011 }
1012 /**
1013 * Checks the environment and can set a flag to emulate OAuth cards.
1014 *
1015 * @param context The context object for the turn.
1016 *
1017 * @remarks
1018 * Override this in a derived class to control how OAuth cards are emulated for unit testing.
1019 */
1020 checkEmulatingOAuthCards(context) {
1021 if (!this.isEmulatingOAuthCards &&
1022 context.activity.channelId === 'emulator' &&
1023 (!this.credentials.appId)) {
1024 this.isEmulatingOAuthCards = true;
1025 }
1026 }
1027 /**
1028 * Creates a turn context.
1029 *
1030 * @param request An incoming request body.
1031 *
1032 * @remarks
1033 * Override this in a derived class to modify how the adapter creates a turn context.
1034 */
1035 createContext(request) {
1036 return new botbuilder_core_1.TurnContext(this, request);
1037 }
1038 /**
1039 * Checks the validity of the request and attempts to map it the correct virtual endpoint,
1040 * then generates and returns a response if appropriate.
1041 * @param request A ReceiveRequest from the connected channel.
1042 * @returns A response created by the BotAdapter to be sent to the client that originated the request.
1043 */
1044 processRequest(request) {
1045 return __awaiter(this, void 0, void 0, function* () {
1046 let response = new botframework_streaming_1.StreamingResponse();
1047 if (!request) {
1048 response.statusCode = botbuilder_core_1.StatusCodes.BAD_REQUEST;
1049 response.setBody(`No request provided.`);
1050 return response;
1051 }
1052 if (!request.verb || !request.path) {
1053 response.statusCode = botbuilder_core_1.StatusCodes.BAD_REQUEST;
1054 response.setBody(`Request missing verb and/or path. Verb: ${request.verb}. Path: ${request.path}`);
1055 return response;
1056 }
1057 if (request.verb.toLocaleUpperCase() !== streaming_1.POST && request.verb.toLocaleUpperCase() !== streaming_1.GET) {
1058 response.statusCode = botbuilder_core_1.StatusCodes.METHOD_NOT_ALLOWED;
1059 response.setBody(`Invalid verb received. Only GET and POST are accepted. Verb: ${request.verb}`);
1060 }
1061 if (request.path.toLocaleLowerCase() === streaming_1.VERSION_PATH) {
1062 return yield this.handleVersionRequest(request, response);
1063 }
1064 // Convert the StreamingRequest into an activity the Adapter can understand.
1065 let body;
1066 try {
1067 body = yield this.readRequestBodyAsString(request);
1068 }
1069 catch (error) {
1070 response.statusCode = botbuilder_core_1.StatusCodes.BAD_REQUEST;
1071 response.setBody(`Request body missing or malformed: ${error}`);
1072 return response;
1073 }
1074 if (request.path.toLocaleLowerCase() !== streaming_1.MESSAGES_PATH) {
1075 response.statusCode = botbuilder_core_1.StatusCodes.NOT_FOUND;
1076 response.setBody(`Path ${request.path.toLocaleLowerCase()} not not found. Expected ${streaming_1.MESSAGES_PATH}}.`);
1077 return response;
1078 }
1079 if (request.verb.toLocaleUpperCase() !== streaming_1.POST) {
1080 response.statusCode = botbuilder_core_1.StatusCodes.METHOD_NOT_ALLOWED;
1081 response.setBody(`Invalid verb received for ${request.verb.toLocaleLowerCase()}. Only GET and POST are accepted. Verb: ${request.verb}`);
1082 return response;
1083 }
1084 try {
1085 let context = new botbuilder_core_1.TurnContext(this, body);
1086 yield this.runMiddleware(context, this.logic);
1087 if (body.type === botbuilder_core_1.ActivityTypes.Invoke) {
1088 let invokeResponse = context.turnState.get(botbuilder_core_1.INVOKE_RESPONSE_KEY);
1089 if (invokeResponse && invokeResponse.value) {
1090 const value = invokeResponse.value;
1091 response.statusCode = value.status;
1092 if (value.body) {
1093 response.setBody(value.body);
1094 }
1095 }
1096 else {
1097 response.statusCode = botbuilder_core_1.StatusCodes.NOT_IMPLEMENTED;
1098 }
1099 }
1100 else if (body.deliveryMode === botbuilder_core_1.DeliveryModes.ExpectReplies) {
1101 const replies = { activities: context.bufferedReplyActivities };
1102 response.setBody(replies);
1103 response.statusCode = botbuilder_core_1.StatusCodes.OK;
1104 }
1105 else {
1106 response.statusCode = botbuilder_core_1.StatusCodes.OK;
1107 }
1108 }
1109 catch (error) {
1110 response.statusCode = botbuilder_core_1.StatusCodes.INTERNAL_SERVER_ERROR;
1111 response.setBody(error);
1112 return response;
1113 }
1114 return response;
1115 });
1116 }
1117 healthCheck(context) {
1118 return __awaiter(this, void 0, void 0, function* () {
1119 const healthResults = {
1120 success: true,
1121 "user-agent": exports.USER_AGENT,
1122 messages: ['Health check succeeded.']
1123 };
1124 if (!(yield this.credentialsProvider.isAuthenticationDisabled())) {
1125 const credentials = context.turnState.get(this.ConnectorClientKey).credentials || this.credentials;
1126 const token = yield credentials.getToken();
1127 healthResults.authorization = `Bearer ${token}`;
1128 }
1129 return { healthResults: healthResults };
1130 });
1131 }
1132 /**
1133 * Connects the handler to a Named Pipe server and begins listening for incoming requests.
1134 * @param pipeName The name of the named pipe to use when creating the server.
1135 * @param logic The logic that will handle incoming requests.
1136 */
1137 useNamedPipe(logic, pipeName = streaming_1.defaultPipeName) {
1138 return __awaiter(this, void 0, void 0, function* () {
1139 if (!logic) {
1140 throw new Error('Bot logic needs to be provided to `useNamedPipe`');
1141 }
1142 this.logic = logic;
1143 this.streamingServer = new botframework_streaming_1.NamedPipeServer(pipeName, this);
1144 yield this.streamingServer.start();
1145 });
1146 }
1147 /**
1148 * Process the initial request to establish a long lived connection via a streaming server.
1149 * @param req The connection request.
1150 * @param socket The raw socket connection between the bot (server) and channel/caller (client).
1151 * @param head The first packet of the upgraded stream.
1152 * @param logic The logic that handles incoming streaming requests for the lifetime of the WebSocket connection.
1153 */
1154 useWebSocket(req, socket, head, logic) {
1155 return __awaiter(this, void 0, void 0, function* () {
1156 // Use the provided NodeWebSocketFactoryBase on BotFrameworkAdapter construction,
1157 // otherwise create a new NodeWebSocketFactory.
1158 const webSocketFactory = this.webSocketFactory || new botframework_streaming_1.NodeWebSocketFactory();
1159 if (!logic) {
1160 throw new Error('Streaming logic needs to be provided to `useWebSocket`');
1161 }
1162 this.logic = logic;
1163 try {
1164 yield this.authenticateConnection(req, this.settings.channelService);
1165 }
1166 catch (err) {
1167 abortWebSocketUpgrade(socket, err);
1168 throw err;
1169 }
1170 const nodeWebSocket = yield webSocketFactory.createWebSocket(req, socket, head);
1171 yield this.startWebSocket(nodeWebSocket);
1172 });
1173 }
1174 authenticateConnection(req, channelService) {
1175 return __awaiter(this, void 0, void 0, function* () {
1176 if (!this.credentials.appId) {
1177 // auth is disabled
1178 return;
1179 }
1180 const authHeader = req.headers.authorization || req.headers.Authorization || '';
1181 const channelIdHeader = req.headers.channelid || req.headers.ChannelId || req.headers.ChannelID || '';
1182 // Validate the received Upgrade request from the channel.
1183 const claims = yield botframework_connector_1.JwtTokenValidation.validateAuthHeader(authHeader, this.credentialsProvider, channelService, channelIdHeader);
1184 // Add serviceUrl from claim to static cache to trigger token refreshes.
1185 const serviceUrl = claims.getClaimValue(botframework_connector_1.AuthenticationConstants.ServiceUrlClaim);
1186 botframework_connector_1.AppCredentials.trustServiceUrl(serviceUrl);
1187 if (!claims.isAuthenticated) {
1188 throw new botframework_connector_1.AuthenticationError('Unauthorized Access. Request is not authorized', botbuilder_core_1.StatusCodes.UNAUTHORIZED);
1189 }
1190 });
1191 }
1192 /**
1193 * Connects the handler to a WebSocket server and begins listening for incoming requests.
1194 * @param socket The socket to use when creating the server.
1195 */
1196 startWebSocket(socket) {
1197 return __awaiter(this, void 0, void 0, function* () {
1198 this.streamingServer = new botframework_streaming_1.WebSocketServer(socket, this);
1199 yield this.streamingServer.start();
1200 });
1201 }
1202 readRequestBodyAsString(request) {
1203 return __awaiter(this, void 0, void 0, function* () {
1204 const contentStream = request.streams[0];
1205 return yield contentStream.readAsJson();
1206 });
1207 }
1208 handleVersionRequest(request, response) {
1209 return __awaiter(this, void 0, void 0, function* () {
1210 if (request.verb.toLocaleUpperCase() === streaming_1.GET) {
1211 response.statusCode = botbuilder_core_1.StatusCodes.OK;
1212 if (!this.credentials.appId) {
1213 response.setBody({ UserAgent: exports.USER_AGENT });
1214 return response;
1215 }
1216 let token = '';
1217 try {
1218 token = yield this.credentials.getToken();
1219 }
1220 catch (err) {
1221 /**
1222 * In reality a missing BotToken will cause the channel to close the connection,
1223 * but we still send the response and allow the channel to make that decision
1224 * instead of proactively disconnecting. This allows the channel to know why
1225 * the connection has been closed and make the choice not to make endless reconnection
1226 * attempts that will end up right back here.
1227 */
1228 console.error(err.message);
1229 }
1230 response.setBody({ UserAgent: exports.USER_AGENT, BotToken: token });
1231 }
1232 else {
1233 response.statusCode = botbuilder_core_1.StatusCodes.METHOD_NOT_ALLOWED;
1234 response.setBody(`Invalid verb received for path: ${request.path}. Only GET is accepted. Verb: ${request.verb}`);
1235 }
1236 return response;
1237 });
1238 }
1239 /**
1240 * Determine if the serviceUrl was sent via an Http/Https connection or Streaming
1241 * This can be determined by looking at the ServiceUrl property:
1242 * (1) All channels that send messages via http/https are not streaming
1243 * (2) Channels that send messages via streaming have a ServiceUrl that does not begin with http/https.
1244 * @param serviceUrl the serviceUrl provided in the resquest.
1245 */
1246 static isStreamingServiceUrl(serviceUrl) {
1247 return serviceUrl && !serviceUrl.toLowerCase().startsWith('http');
1248 }
1249}
1250exports.BotFrameworkAdapter = BotFrameworkAdapter;
1251/**
1252 * Handles incoming webhooks from the botframework
1253 * @private
1254 * @param req incoming web request
1255 */
1256function parseRequest(req) {
1257 return new Promise((resolve, reject) => {
1258 if (req.body) {
1259 try {
1260 const activity = activityValidator_1.validateAndFixActivity(req.body);
1261 resolve(activity);
1262 }
1263 catch (err) {
1264 reject(err);
1265 }
1266 }
1267 else {
1268 let requestData = '';
1269 req.on('data', (chunk) => {
1270 requestData += chunk;
1271 });
1272 req.on('end', () => {
1273 try {
1274 req.body = JSON.parse(requestData);
1275 const activity = activityValidator_1.validateAndFixActivity(req.body);
1276 resolve(activity);
1277 }
1278 catch (err) {
1279 reject(err);
1280 }
1281 });
1282 }
1283 });
1284}
1285function delay(timeout) {
1286 return new Promise((resolve) => {
1287 setTimeout(resolve, timeout);
1288 });
1289}
1290/**
1291* Creates an error message with status code to write to socket, then closes the connection.
1292*
1293* @param socket The raw socket connection between the bot (server) and channel/caller (client).
1294* @param err The error. If the error includes a status code, it will be included in the message written to the socket.
1295*/
1296function abortWebSocketUpgrade(socket, err) {
1297 if (socket.writable) {
1298 const connectionHeader = `Connection: 'close'\r\n`;
1299 let message = '';
1300 botframework_connector_1.AuthenticationError.isStatusCodeError(err) ?
1301 message = `HTTP/1.1 ${err.statusCode} ${botbuilder_core_1.StatusCodes[err.statusCode]}\r\n${err.message}\r\n${connectionHeader}\r\n`
1302 : message = botframework_connector_1.AuthenticationError.determineStatusCodeAndBuildMessage(err);
1303 socket.write(message);
1304 }
1305 socket.destroy();
1306}
1307//# sourceMappingURL=botFrameworkAdapter.js.map
\No newline at end of file