• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / botbuilder-js / 7249230892

18 Dec 2023 02:09PM UTC coverage: 84.539% (-0.09%) from 84.629%
7249230892

Pull #4589

github

web-flow
Merge c8030b61d into f3db3e98b
Pull Request #4589: fix: Add ASE channel validation

9985 of 13088 branches covered (0.0%)

Branch coverage included in aggregate %.

62 of 90 new or added lines in 13 files covered. (68.89%)

99 existing lines in 4 files now uncovered.

20416 of 22873 relevant lines covered (89.26%)

7171.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.78
/libraries/botbuilder/src/botFrameworkAdapter.ts
1
/**
2
 * @module botbuilder
3
 */
4
/**
5
 * Copyright (c) Microsoft Corporation. All rights reserved.
6
 * Licensed under the MIT License.
7
 */
8

9
import * as z from 'zod';
2✔
10
import { BotFrameworkHttpAdapter } from './botFrameworkHttpAdapter';
11
import { ConnectorClientBuilder, Request, Response, ResponseT, WebRequest, WebResponse } from './interfaces';
2✔
12
import { HttpClient, RequestPolicyFactory, userAgentPolicy } from '@azure/core-http';
2✔
13
import { INodeBufferT, INodeSocketT, LogicT } from './zod';
2✔
14
import { arch, release, type } from 'os';
2✔
15
import { delay, retry } from 'botbuilder-stdlib';
2✔
16
import { validateAndFixActivity } from './activityValidator';
2✔
17

18
import {
2✔
19
    Activity,
20
    ActivityEventNames,
21
    ActivityTypes,
22
    BotAdapter,
23
    BotCallbackHandlerKey,
24
    CallerIdConstants,
25
    ChannelAccount,
26
    Channels,
27
    ConversationParameters,
28
    ConversationReference,
29
    ConversationsResult,
30
    CoreAppCredentials,
31
    DeliveryModes,
32
    ExpectedReplies,
33
    ExtendedUserTokenProvider,
34
    INVOKE_RESPONSE_KEY,
35
    InvokeResponse,
36
    ResourceResponse,
37
    StatusCodes,
38
    TokenResponse,
39
    TurnContext,
40
    conversationParametersObject,
41
} from 'botbuilder-core';
42

43
import {
2✔
44
    AppCredentials,
45
    AuthenticationConfiguration,
46
    AuthenticationConstants,
47
    AuthenticationError,
48
    CertificateAppCredentials,
49
    ChannelValidation,
50
    Claim,
51
    ClaimsIdentity,
52
    ConnectorClient,
53
    ConnectorClientOptions,
54
    EmulatorApiClient,
55
    GovernmentChannelValidation,
56
    GovernmentConstants,
57
    JwtTokenValidation,
58
    MicrosoftAppCredentials,
59
    MicrosoftGovernmentAppCredentials,
60
    SignInUrlResponse,
61
    SimpleCredentialProvider,
62
    SkillValidation,
63
    TokenApiClient,
64
    TokenApiModels,
65
    TokenExchangeRequest,
66
    TokenStatus,
67
} from 'botframework-connector';
68

69
import {
2✔
70
    INodeBuffer,
71
    INodeSocket,
72
    IReceiveRequest,
73
    ISocket,
74
    IStreamingTransportServer,
75
    NamedPipeServer,
76
    NodeWebSocketFactory,
77
    NodeWebSocketFactoryBase,
78
    RequestHandler,
79
    StreamingResponse,
80
    WebSocketServer,
81
} from 'botframework-streaming';
82

83
import {
2✔
84
    defaultPipeName,
85
    GET,
86
    POST,
87
    MESSAGES_PATH,
88
    StreamingHttpClient,
89
    TokenResolver,
90
    VERSION_PATH,
91
} from './streaming';
92

93
/**
94
 * @deprecated Use `CloudAdapter` with `ConfigurationBotFrameworkAuthentication` instead to configure bot runtime.
95
 * Contains settings used to configure a [BotFrameworkAdapter](xref:botbuilder.BotFrameworkAdapter) instance.
96
 */
97
export interface BotFrameworkAdapterSettings {
98
    /**
99
     * The ID assigned to your bot in the [Bot Framework Portal](https://dev.botframework.com/).
100
     */
101
    appId: string;
102

103
    /**
104
     * The password assigned to your bot in the [Bot Framework Portal](https://dev.botframework.com/).
105
     */
106
    appPassword: string;
107

108
    /**
109
     * Optional. The tenant to acquire the bot-to-channel token from.
110
     */
111
    channelAuthTenant?: string;
112

113
    /**
114
     * Optional. The OAuth API endpoint for your bot to use.
115
     */
116
    oAuthEndpoint?: string;
117

118
    /**
119
     * Optional. The OpenID Metadata endpoint for your bot to use.
120
     */
121
    openIdMetadata?: string;
122

123
    /**
124
     * Optional. The channel service option for this bot to validate connections from Azure or other channel locations.
125
     */
126
    channelService?: string;
127

128
    /**
129
     * Optional. Used to pass in a NodeWebSocketFactoryBase instance.
130
     */
131
    webSocketFactory?: NodeWebSocketFactoryBase;
132

133
    /**
134
     * Optional. Certificate thumbprint to authenticate the appId against AAD.
135
     */
136
    certificateThumbprint?: string;
137

138
    /**
139
     * Optional. Certificate key to authenticate the appId against AAD.
140
     */
141
    certificatePrivateKey?: string;
142

143
    /**
144
     * Optional. Used to require specific endorsements and verify claims. Recommended for Skills.
145
     */
146
    authConfig?: AuthenticationConfiguration;
147

148
    /**
149
     * Optional. Used when creating new ConnectorClients.
150
     */
151
    clientOptions?: ConnectorClientOptions;
152
}
153

154
// Retrieve additional information, i.e., host operating system, host OS release, architecture, Node.js version
155
const ARCHITECTURE = arch();
2✔
156
const TYPE = type();
2✔
157
const RELEASE = release();
2✔
158
const NODE_VERSION = process.version;
2✔
159

160
const pjson: Record<'version', string> = require('../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires
2✔
161
export const USER_AGENT = `Microsoft-BotFramework/3.1 BotBuilder/${pjson.version} (Node.js,Version=${NODE_VERSION}; ${TYPE} ${RELEASE}; ${ARCHITECTURE})`;
2✔
162

163
const OAUTH_ENDPOINT = 'https://api.botframework.com';
2✔
164
const US_GOV_OAUTH_ENDPOINT = 'https://api.botframework.azure.us';
2✔
165

166
/**
167
 * A [BotAdapter](xref:botbuilder-core.BotAdapter) that can connect a bot to a service endpoint.
168
 * Implements [IUserTokenProvider](xref:botbuilder-core.IUserTokenProvider).
169
 *
170
 * @remarks
171
 * The bot adapter encapsulates authentication processes and sends activities to and receives
172
 * activities from the Bot Connector Service. When your bot receives an activity, the adapter
173
 * creates a turn context object, passes it to your bot application logic, and sends responses
174
 * back to the user's channel.
175
 *
176
 * The adapter processes and directs incoming activities in through the bot middleware pipeline to
177
 * your bot logic and then back out again. As each activity flows in and out of the bot, each
178
 * piece of middleware can inspect or act upon the activity, both before and after the bot logic runs.
179
 * Use the [use](xref:botbuilder-core.BotAdapter.use) method to add [Middleware](xref:botbuilder-core.Middleware)
180
 * objects to your adapter's middleware collection.
181
 *
182
 * For more information, see the articles on
183
 * [How bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and
184
 * [Middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware).
185
 *
186
 * For example:
187
 * ```JavaScript
188
 * const { BotFrameworkAdapter } = require('botbuilder');
189
 *
190
 * const adapter = new BotFrameworkAdapter({
191
 *     appId: process.env.MicrosoftAppId,
192
 *     appPassword: process.env.MicrosoftAppPassword
193
 * });
194
 *
195
 * adapter.onTurnError = async (context, error) => {
196
 *     // Catch-all logic for errors.
197
 * };
198
 * ```
199
 */
200

201
/**
202
 * @deprecated Use `CloudAdapter` instead.
203
 */
204
export class BotFrameworkAdapter
2✔
205
    extends BotAdapter
206
    implements BotFrameworkHttpAdapter, ConnectorClientBuilder, ExtendedUserTokenProvider, RequestHandler {
207
    // These keys are public to permit access to the keys from the adapter when it's a being
208
    // from library that does not have access to static properties off of BotFrameworkAdapter.
209
    // E.g. botbuilder-dialogs
210
    readonly TokenApiClientCredentialsKey: symbol = Symbol('TokenApiClientCredentials');
430✔
211

212
    protected readonly credentials: AppCredentials;
213
    protected readonly credentialsProvider: SimpleCredentialProvider;
214
    protected readonly settings: BotFrameworkAdapterSettings;
215

216
    private isEmulatingOAuthCards: boolean;
217

218
    // Streaming-specific properties:
219
    private logic: (context: TurnContext) => Promise<void>;
220
    private streamingServer: IStreamingTransportServer;
221
    private webSocketFactory: NodeWebSocketFactoryBase;
222

223
    private authConfiguration: AuthenticationConfiguration;
224

225
    private namedPipeName?: string;
226

227
    /**
228
     * Creates a new instance of the [BotFrameworkAdapter](xref:botbuilder.BotFrameworkAdapter) class.
229
     *
230
     * @param settings Optional. The settings to use for this adapter instance.
231
     *
232
     * @remarks
233
     * If the `settings` parameter does not include
234
     * [channelService](xref:botbuilder.BotFrameworkAdapterSettings.channelService) or
235
     * [openIdMetadata](xref:botbuilder.BotFrameworkAdapterSettings.openIdMetadata) values, the
236
     * constructor checks the process' environment variables for these values. These values may be
237
     * set when a bot is provisioned on Azure and if so are required for the bot to work properly
238
     * in the global cloud or in a national cloud.
239
     *
240
     * The [BotFrameworkAdapterSettings](xref:botbuilder.BotFrameworkAdapterSettings) class defines
241
     * the available adapter settings.
242
     */
243
    constructor(settings?: Partial<BotFrameworkAdapterSettings>) {
244
        super();
430✔
245
        this.settings = { appId: '', appPassword: '', ...settings };
430✔
246

247
        // If settings.certificateThumbprint & settings.certificatePrivateKey are provided,
248
        // use CertificateAppCredentials.
249
        if (this.settings.certificateThumbprint && this.settings.certificatePrivateKey) {
430✔
250
            this.credentials = new CertificateAppCredentials(
6✔
251
                this.settings.appId,
252
                settings.certificateThumbprint,
253
                settings.certificatePrivateKey,
254
                this.settings.channelAuthTenant
255
            );
256
            this.credentialsProvider = new SimpleCredentialProvider(this.credentials.appId, '');
6✔
257
        } else {
258
            if (JwtTokenValidation.isGovernment(this.settings.channelService)) {
424✔
259
                this.credentials = new MicrosoftGovernmentAppCredentials(
6✔
260
                    this.settings.appId,
261
                    this.settings.appPassword || '',
12✔
262
                    this.settings.channelAuthTenant
263
                );
264
            }
265
            else{
266
                this.credentials = new MicrosoftAppCredentials(
418✔
267
                    this.settings.appId,
268
                    this.settings.appPassword || '',
706✔
269
                    this.settings.channelAuthTenant
270
                );
271
            }
272
            this.credentialsProvider = new SimpleCredentialProvider(
424✔
273
                this.credentials.appId,
274
                this.settings.appPassword || ''
718✔
275
            );
276
        }
277

278
        this.isEmulatingOAuthCards = false;
430✔
279

280
        // If no channelService or openIdMetadata values were passed in the settings, check the process' Environment Variables for values.
281
        // 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.
282
        this.settings.channelService =
430✔
283
            this.settings.channelService || process.env[AuthenticationConstants.ChannelService];
854✔
284
        this.settings.openIdMetadata =
430✔
285
            this.settings.openIdMetadata || process.env[AuthenticationConstants.BotOpenIdMetadataKey];
856✔
286

287
        this.authConfiguration = this.settings.authConfig || new AuthenticationConfiguration();
430✔
288

289
        if (this.settings.openIdMetadata) {
430✔
290
            ChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata;
6✔
291
            GovernmentChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata;
6✔
292
        }
293

294
        // If a NodeWebSocketFactoryBase was passed in, set it on the BotFrameworkAdapter.
295
        if (this.settings.webSocketFactory) {
430✔
296
            this.webSocketFactory = this.settings.webSocketFactory;
2✔
297
        }
298

299
        // Relocate the tenantId field used by MS Teams to a new location (from channelData to conversation)
300
        // This will only occur on activities from teams that include tenant info in channelData but NOT in conversation,
301
        // thus should be future friendly.  However, once the the transition is complete. we can remove this.
302
        this.use(
430✔
303
            async (context, next): Promise<void> => {
96✔
304
                if (
96✔
305
                    context.activity.channelId === 'msteams' &&
152✔
306
                    context.activity &&
307
                    context.activity.conversation &&
308
                    !context.activity.conversation.tenantId &&
309
                    context.activity.channelData &&
310
                    context.activity.channelData.tenant
311
                ) {
312
                    context.activity.conversation.tenantId = context.activity.channelData.tenant.id;
2✔
313
                }
314
                await next();
96✔
315
            }
316
        );
317
    }
318

319
    /**
320
     * Used in streaming contexts to check if the streaming connection is still open for the bot to send activities.
321
     *
322
     * @returns True if the streaming connection is open, otherwise false.
323
     */
324
    get isStreamingConnectionOpen(): boolean {
325
        return this.streamingServer?.isConnected ?? false;
14✔
326
    }
327

328
    /**
329
     * Asynchronously resumes a conversation with a user, possibly after some time has gone by.
330
     *
331
     * @param reference A reference to the conversation to continue.
332
     * @param oAuthScope The intended recipient of any sent activities.
333
     * @param logic The asynchronous method to call after the adapter middleware runs.
334
     *
335
     * @remarks
336
     * This is often referred to as a _proactive notification_, the bot can proactively
337
     * send a message to a conversation or user without waiting for an incoming message.
338
     * For example, a bot can use this method to send notifications or coupons to a user.
339
     *
340
     * To send a proactive message:
341
     * 1. Save a copy of a [ConversationReference](xref:botframework-schema.ConversationReference)
342
     *    from an incoming activity. For example, you can store the conversation reference in a database.
343
     * 1. Call this method to resume the conversation at a later time. Use the saved reference to access the conversation.
344
     * 1. On success, the adapter generates a [TurnContext](xref:botbuilder-core.TurnContext) object and calls the `logic` function handler.
345
     *    Use the `logic` function to send the proactive message.
346
     *
347
     * To copy the reference from any incoming activity in the conversation, use the
348
     * [TurnContext.getConversationReference](xref:botbuilder-core.TurnContext.getConversationReference) method.
349
     *
350
     * This method is similar to the [processActivity](xref:botbuilder.BotFrameworkAdapter.processActivity) method.
351
     * The adapter creates a [TurnContext](xref:botbuilder-core.TurnContext) and routes it through
352
     * its middleware before calling the `logic` handler. The created activity will have a
353
     * [type](xref:botframework-schema.Activity.type) of 'event' and a
354
     * [name](xref:botframework-schema.Activity.name) of 'continueConversation'.
355
     *
356
     * For example:
357
     * ```JavaScript
358
     * server.post('/api/notifyUser', async (req, res) => {
359
     *    // Lookup previously saved conversation reference.
360
     *    const reference = await findReference(req.body.refId);
361
     *
362
     *    // Proactively notify the user.
363
     *    if (reference) {
364
     *       await adapter.continueConversation(reference, async (context) => {
365
     *          await context.sendActivity(req.body.message);
366
     *       });
367
     *       res.send(200);
368
     *    } else {
369
     *       res.send(404);
370
     *    }
371
     * });
372
     * ```
373
     */
374
    async continueConversation(
375
        reference: Partial<ConversationReference>,
376
        logic: (context: TurnContext) => Promise<void>
377
    ): Promise<void>;
378

379
    /**
380
     * Asynchronously resumes a conversation with a user, possibly after some time has gone by.
381
     *
382
     * @param reference [ConversationReference](xref:botframework-schema.ConversationReference) of the conversation to continue.
383
     * @param oAuthScope The intended recipient of any sent activities or the function to call to continue the conversation.
384
     * @param logic Optional. The asynchronous method to call after the adapter middleware runs.
385
     */
386
    async continueConversation(
387
        reference: Partial<ConversationReference>,
388
        oAuthScope: string,
389
        logic: (context: TurnContext) => Promise<void>
390
    ): Promise<void>;
391

392
    /**
393
     * @internal
394
     */
395
    async continueConversation(
396
        reference: Partial<ConversationReference>,
397
        oAuthScopeOrlogic: string | ((context: TurnContext) => Promise<void>),
398
        maybeLogic?: (context: TurnContext) => Promise<void>
399
    ): Promise<void> {
400
        let audience: string;
401
        if (LogicT.safeParse(oAuthScopeOrlogic).success) {
34✔
402
            // Because the OAuthScope parameter was not provided, get the correct value via the channelService.
403
            // In this scenario, the ConnectorClient for the continued conversation can only communicate with
404
            // official channels, not with other bots.
405
            audience = JwtTokenValidation.isGovernment(this.settings.channelService)
32✔
406
                ? GovernmentConstants.ToChannelFromBotOAuthScope
32✔
407
                : AuthenticationConstants.ToChannelFromBotOAuthScope;
408
        } else {
409
            audience = z.string().parse(oAuthScopeOrlogic);
2✔
410
        }
411
        const logicParse = LogicT.safeParse(oAuthScopeOrlogic);
34✔
412
        const logic = logicParse.success ? logicParse.data : LogicT.parse(maybeLogic);
34✔
413

414
        let credentials = this.credentials;
34✔
415

416
        // For authenticated flows (where the bot has an AppId), the ConversationReference's serviceUrl needs
417
        // to be trusted for the bot to acquire a token when sending activities to the conversation.
418
        // For anonymous flows, the serviceUrl should not be trusted.
419
        if (credentials.appId) {
34!
420
            // If the provided OAuthScope doesn't match the current one on the instance's credentials, create
421
            // a new AppCredentials with the correct OAuthScope.
UNCOV
422
            if (credentials.oAuthScope !== audience) {
×
423
                // The BotFrameworkAdapter JavaScript implementation supports one Bot per instance, so get
424
                // the botAppId from the credentials.
UNCOV
425
                credentials = await this.buildCredentials(credentials.appId, audience);
×
426
            }
427
        }
428

429
        const connectorClient = this.createConnectorClientInternal(reference.serviceUrl, credentials);
34✔
430

431
        const request = TurnContext.applyConversationReference(
34✔
432
            { type: ActivityTypes.Event, name: ActivityEventNames.ContinueConversation },
433
            reference,
434
            true
435
        );
436

437
        const context = this.createContext(request);
34✔
438

439
        context.turnState.set(this.OAuthScopeKey, audience);
34✔
440
        context.turnState.set(this.ConnectorClientKey, connectorClient);
34✔
441

442
        await this.runMiddleware(context, logic);
34✔
443
    }
444

445
    /**
446
     * Asynchronously creates and starts a conversation with a user on a channel.
447
     *
448
     * @param {Partial<ConversationReference>} reference A reference for the conversation to create.
449
     * @param {(context: TurnContext) => Promise<void>} logic The asynchronous method to call after the adapter middleware runs.
450
     * @returns {Promise<void>} a promise representing the asynchronous operation
451
     *
452
     * @summary
453
     * To use this method, you need both the bot's and the user's account information on a channel.
454
     * The Bot Connector service supports the creating of group conversations; however, this
455
     * method and most channels only support initiating a direct message (non-group) conversation.
456
     *
457
     * To create and start a new conversation:
458
     * 1. Get a copy of a [ConversationReference](xref:botframework-schema.ConversationReference) from an incoming activity.
459
     * 1. Set the [user](xref:botframework-schema.ConversationReference.user) property to the
460
     *    [ChannelAccount](xref:botframework-schema.ChannelAccount) value for the intended recipient.
461
     * 1. Call this method to request that the channel create a new conversation with the specified user.
462
     * 1. On success, the adapter generates a turn context and calls the `logic` function handler.
463
     *
464
     * To get the initial reference, use the
465
     * [TurnContext.getConversationReference](xref:botbuilder-core.TurnContext.getConversationReference)
466
     * method on any incoming activity in the conversation.
467
     *
468
     * If the channel establishes the conversation, the generated event activity's
469
     * [conversation](xref:botframework-schema.Activity.conversation) property will contain the
470
     * ID of the new conversation.
471
     *
472
     * This method is similar to the [processActivity](xref:botbuilder.BotFrameworkAdapter.processActivity) method.
473
     * The adapter creates a [TurnContext](xref:botbuilder-core.TurnContext) and routes it through
474
     * middleware before calling the `logic` handler. The created activity will have a
475
     * [type](xref:botframework-schema.Activity.type) of 'event' and a
476
     * [name](xref:botframework-schema.Activity.name) of 'createConversation'.
477
     *
478
     * For example:
479
     * ```JavaScript
480
     * // Get group members conversation reference
481
     * const reference = TurnContext.getConversationReference(context.activity);
482
     *
483
     * // ...
484
     * // Start a new conversation with the user
485
     * await adapter.createConversation(reference, async (ctx) => {
486
     *    await ctx.sendActivity(`Hi (in private)`);
487
     * });
488
     * ```
489
     */
490
    createConversation(
491
        reference: Partial<ConversationReference>,
492
        logic: (context: TurnContext) => Promise<void>
493
    ): Promise<void>;
494

495
    /**
496
     * Asynchronously creates and starts a conversation with a user on a channel.
497
     *
498
     * @param {Partial<ConversationReference>} reference A reference for the conversation to create.
499
     * @param {Partial<ConversationParameters>} parameters Parameters used when creating the conversation
500
     * @param {(context: TurnContext) => Promise<void>} logic The asynchronous method to call after the adapter middleware runs.
501
     * @returns {Promise<void>} a promise representing the asynchronous operation
502
     */
503
    createConversation(
504
        reference: Partial<ConversationReference>,
505
        parameters: Partial<ConversationParameters>,
506
        logic: (context: TurnContext) => Promise<void>
507
    ): Promise<void>;
508

509
    /**
510
     * @internal
511
     */
512
    async createConversation(
513
        reference: Partial<ConversationReference>,
514
        parametersOrLogic: Partial<ConversationParameters> | ((context: TurnContext) => Promise<void>),
515
        maybeLogic?: (context: TurnContext) => Promise<void>
516
    ): Promise<void> {
517
        if (!reference.serviceUrl) {
10✔
518
            throw new Error('BotFrameworkAdapter.createConversation(): missing serviceUrl.');
2✔
519
        }
520
        const logicParse = LogicT.safeParse(parametersOrLogic);
8✔
521
        const parameterParse = conversationParametersObject.partial().safeParse(parametersOrLogic);
8✔
522

523
        const parameters = parameterParse.success ? parameterParse.data : {};
8✔
524

525
        const logic = logicParse.success ? logicParse.data : LogicT.parse(maybeLogic);
8!
526

527
        // Create conversation parameters, taking care to provide defaults that can be
528
        // overridden by passed in parameters
529
        const conversationParameters = Object.assign(
8✔
530
            {},
531
            {
532
                bot: reference.bot,
533
                members: [reference.user],
534
                isGroup: false,
535
                activity: null,
536
                channelData: null,
537
            },
538
            parameters
539
        );
540

541
        const client = this.createConnectorClient(reference.serviceUrl);
8✔
542

543
        // Mix in the tenant ID if specified. This is required for MS Teams.
544
        if (reference.conversation && reference.conversation.tenantId) {
8✔
545
            // Putting tenantId in channelData is a temporary solution while we wait for the Teams API to be updated
546
            conversationParameters.channelData = { tenant: { id: reference.conversation.tenantId } };
2✔
547

548
            // Permanent solution is to put tenantId in parameters.tenantId
549
            conversationParameters.tenantId = reference.conversation.tenantId;
2✔
550
        }
551

552
        const response = await client.conversations.createConversation(conversationParameters);
8✔
553

554
        // Initialize request and copy over new conversation ID and updated serviceUrl.
555
        const request = TurnContext.applyConversationReference(
8✔
556
            { type: ActivityTypes.Event, name: ActivityEventNames.CreateConversation },
557
            reference,
558
            true
559
        );
560

561
        request.conversation = {
8✔
562
            id: response.id,
563
            isGroup: conversationParameters.isGroup,
564
            conversationType: null,
565
            tenantId: reference.conversation.tenantId,
566
            name: null,
567
        };
568

569
        request.channelData = conversationParameters.channelData;
8✔
570

571
        if (response.serviceUrl) {
8✔
572
            request.serviceUrl = response.serviceUrl;
2✔
573
        }
574

575
        // Create context and run middleware
576
        const context = this.createContext(request);
8✔
577
        await this.runMiddleware(context, logic);
8✔
578
    }
579

580
    /**
581
     * Asynchronously deletes an existing activity.
582
     *
583
     * This interface supports the framework and is not intended to be called directly for your code.
584
     * Use [TurnContext.deleteActivity](xref:botbuilder-core.TurnContext.deleteActivity) to delete
585
     * an activity from your bot code.
586
     *
587
     * @param context The context object for the turn.
588
     * @param reference Conversation reference information for the activity to delete.
589
     *
590
     * @remarks
591
     * Not all channels support this operation. For channels that don't, this call may throw an exception.
592
     */
593
    async deleteActivity(context: TurnContext, reference: Partial<ConversationReference>): Promise<void> {
594
        if (!reference.serviceUrl) {
8✔
595
            throw new Error('BotFrameworkAdapter.deleteActivity(): missing serviceUrl');
2✔
596
        }
597
        if (!reference.conversation || !reference.conversation.id) {
6✔
598
            throw new Error('BotFrameworkAdapter.deleteActivity(): missing conversation or conversation.id');
2✔
599
        }
600
        if (!reference.activityId) {
4✔
601
            throw new Error('BotFrameworkAdapter.deleteActivity(): missing activityId');
2✔
602
        }
603
        const client: ConnectorClient = this.getOrCreateConnectorClient(
2✔
604
            context,
605
            reference.serviceUrl,
606
            this.credentials
607
        );
608
        await client.conversations.deleteActivity(reference.conversation.id, reference.activityId);
2✔
609
    }
610

611
    /**
612
     * Asynchronously removes a member from the current conversation.
613
     *
614
     * @param context The context object for the turn.
615
     * @param memberId The ID of the member to remove from the conversation.
616
     *
617
     * @remarks
618
     * Remove a member's identity information from the conversation.
619
     *
620
     * Not all channels support this operation. For channels that don't, this call may throw an exception.
621
     */
622
    async deleteConversationMember(context: TurnContext, memberId: string): Promise<void> {
623
        if (!context.activity.serviceUrl) {
8✔
624
            throw new Error('BotFrameworkAdapter.deleteConversationMember(): missing serviceUrl');
2✔
625
        }
626
        if (!context.activity.conversation || !context.activity.conversation.id) {
6✔
627
            throw new Error('BotFrameworkAdapter.deleteConversationMember(): missing conversation or conversation.id');
4✔
628
        }
629
        const serviceUrl: string = context.activity.serviceUrl;
2✔
630
        const conversationId: string = context.activity.conversation.id;
2✔
631
        const client: ConnectorClient = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials);
2✔
632
        await client.conversations.deleteConversationMember(conversationId, memberId);
2✔
633
    }
634

635
    /**
636
     * Asynchronously lists the members of a given activity.
637
     *
638
     * @param context The context object for the turn.
639
     * @param activityId Optional. The ID of the activity to get the members of. If not specified, the current activity ID is used.
640
     *
641
     * @returns An array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for
642
     * the users involved in a given activity.
643
     *
644
     * @remarks
645
     * Returns an array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for
646
     * the users involved in a given activity.
647
     *
648
     * This is different from [getConversationMembers](xref:botbuilder.BotFrameworkAdapter.getConversationMembers)
649
     * in that it will return only those users directly involved in the activity, not all members of the conversation.
650
     */
651
    async getActivityMembers(context: TurnContext, activityId?: string): Promise<ChannelAccount[]> {
652
        if (!activityId) {
10✔
653
            activityId = context.activity.id;
8✔
654
        }
655
        if (!context.activity.serviceUrl) {
10✔
656
            throw new Error('BotFrameworkAdapter.getActivityMembers(): missing serviceUrl');
2✔
657
        }
658
        if (!context.activity.conversation || !context.activity.conversation.id) {
8✔
659
            throw new Error('BotFrameworkAdapter.getActivityMembers(): missing conversation or conversation.id');
4✔
660
        }
661
        if (!activityId) {
4✔
662
            throw new Error(
2✔
663
                'BotFrameworkAdapter.getActivityMembers(): missing both activityId and context.activity.id'
664
            );
665
        }
666
        const serviceUrl: string = context.activity.serviceUrl;
2✔
667
        const conversationId: string = context.activity.conversation.id;
2✔
668
        const client: ConnectorClient = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials);
2✔
669

670
        return await client.conversations.getActivityMembers(conversationId, activityId);
2✔
671
    }
672

673
    /**
674
     * Asynchronously lists the members of the current conversation.
675
     *
676
     * @param context The context object for the turn.
677
     *
678
     * @returns An array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for
679
     * all users currently involved in a conversation.
680
     *
681
     * @remarks
682
     * Returns an array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for
683
     * all users currently involved in a conversation.
684
     *
685
     * This is different from [getActivityMembers](xref:botbuilder.BotFrameworkAdapter.getActivityMembers)
686
     * in that it will return all members of the conversation, not just those directly involved in a specific activity.
687
     */
688
    async getConversationMembers(context: TurnContext): Promise<ChannelAccount[]> {
689
        if (!context.activity.serviceUrl) {
8✔
690
            throw new Error('BotFrameworkAdapter.getConversationMembers(): missing serviceUrl');
2✔
691
        }
692
        if (!context.activity.conversation || !context.activity.conversation.id) {
6✔
693
            throw new Error('BotFrameworkAdapter.getConversationMembers(): missing conversation or conversation.id');
4✔
694
        }
695
        const serviceUrl: string = context.activity.serviceUrl;
2✔
696
        const conversationId: string = context.activity.conversation.id;
2✔
697
        const client: ConnectorClient = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials);
2✔
698

699
        return await client.conversations.getConversationMembers(conversationId);
2✔
700
    }
701

702
    /**
703
     * For the specified channel, asynchronously gets a page of the conversations in which this bot has participated.
704
     *
705
     * @param contextOrServiceUrl The URL of the channel server to query or a
706
     * [TurnContext](xref:botbuilder-core.TurnContext) object from a conversation on the channel.
707
     * @param continuationToken Optional. The continuation token from the previous page of results.
708
     * Omit this parameter or use `undefined` to retrieve the first page of results.
709
     *
710
     * @returns A [ConversationsResult](xref:botframework-schema.ConversationsResult) object containing a page of results
711
     * and a continuation token.
712
     *
713
     * @remarks
714
     * The the return value's [conversations](xref:botframework-schema.ConversationsResult.conversations) property contains a page of
715
     * [ConversationMembers](xref:botframework-schema.ConversationMembers) objects. Each object's
716
     * [id](xref:botframework-schema.ConversationMembers.id) is the ID of a conversation in which the bot has participated on this channel.
717
     * This method can be called from outside the context of a conversation, as only the bot's service URL and credentials are required.
718
     *
719
     * The channel batches results in pages. If the result's
720
     * [continuationToken](xref:botframework-schema.ConversationsResult.continuationToken) property is not empty, then
721
     * there are more pages to get. Use the returned token to get the next page of results.
722
     * If the `contextOrServiceUrl` parameter is a [TurnContext](xref:botbuilder-core.TurnContext), the URL of the channel server is
723
     * retrieved from
724
     * `contextOrServiceUrl`.[activity](xref:botbuilder-core.TurnContext.activity).[serviceUrl](xref:botframework-schema.Activity.serviceUrl).
725
     */
726
    async getConversations(
727
        contextOrServiceUrl: TurnContext | string,
728
        continuationToken?: string
729
    ): Promise<ConversationsResult> {
730
        let client: ConnectorClient;
731
        if (typeof contextOrServiceUrl === 'object') {
8✔
732
            const context: TurnContext = contextOrServiceUrl as TurnContext;
4✔
733
            client = this.getOrCreateConnectorClient(context, context.activity.serviceUrl, this.credentials);
4✔
734
        } else {
735
            client = this.createConnectorClient(contextOrServiceUrl as string);
4✔
736
        }
737

738
        return await client.conversations.getConversations(
4✔
739
            continuationToken ? { continuationToken: continuationToken } : undefined
4!
740
        );
741
    }
742

743
    /**
744
     * Asynchronously attempts to retrieve the token for a user that's in a login flow.
745
     *
746
     * @param context The context object for the turn.
747
     * @param connectionName The name of the auth connection to use.
748
     * @param magicCode Optional. The validation code the user entered.
749
     * @param oAuthAppCredentials AppCredentials for OAuth.
750
     *
751
     * @returns A [TokenResponse](xref:botframework-schema.TokenResponse) object that contains the user token.
752
     */
753
    async getUserToken(context: TurnContext, connectionName: string, magicCode?: string): Promise<TokenResponse>;
754
    async getUserToken(
755
        context: TurnContext,
756
        connectionName: string,
757
        magicCode?: string,
758
        oAuthAppCredentials?: CoreAppCredentials
759
    ): Promise<TokenResponse>;
760
    /**
761
     * Asynchronously attempts to retrieve the token for a user that's in a login flow.
762
     *
763
     * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn.
764
     * @param connectionName The name of the auth connection to use.
765
     * @param magicCode Optional. The validation code the user entered.
766
     * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth.
767
     *
768
     * @returns A [TokenResponse](xref:botframework-schema.TokenResponse) object that contains the user token.
769
     */
770
    async getUserToken(
771
        context: TurnContext,
772
        connectionName: string,
773
        magicCode?: string,
774
        oAuthAppCredentials?: AppCredentials
775
    ): Promise<TokenResponse> {
776
        if (!context.activity.from || !context.activity.from.id) {
8✔
777
            throw new Error('BotFrameworkAdapter.getUserToken(): missing from or from.id');
4✔
778
        }
779
        if (!connectionName) {
4✔
780
            throw new Error('getUserToken() requires a connectionName but none was provided.');
2✔
781
        }
782
        this.checkEmulatingOAuthCards(context);
2✔
783
        const userId: string = context.activity.from.id;
2✔
784
        const url: string = this.oauthApiUrl(context);
2✔
785
        const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
2✔
786
        context.turnState.set(this.TokenApiClientCredentialsKey, client);
2✔
787

788
        const result: TokenApiModels.UserTokenGetTokenResponse = await client.userToken.getToken(
2✔
789
            userId,
790
            connectionName,
791
            { code: magicCode, channelId: context.activity.channelId }
792
        );
793
        if (!result || !result.token || result._response.status == 404) {
2!
UNCOV
794
            return undefined;
×
795
        } else {
796
            return result as TokenResponse;
2✔
797
        }
798
    }
799

800
    /**
801
     * Asynchronously signs out the user from the token server.
802
     *
803
     * @param context The context object for the turn.
804
     * @param connectionName The name of the auth connection to use.
805
     * @param userId The ID of user to sign out.
806
     * @param oAuthAppCredentials AppCredentials for OAuth.
807
     */
808
    async signOutUser(context: TurnContext, connectionName?: string, userId?: string): Promise<void>;
809
    async signOutUser(
810
        context: TurnContext,
811
        connectionName?: string,
812
        userId?: string,
813
        oAuthAppCredentials?: CoreAppCredentials
814
    ): Promise<void>;
815
    /**
816
     * Asynchronously signs out the user from the token server.
817
     *
818
     * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn.
819
     * @param connectionName Optional. The name of the auth connection to use.
820
     * @param userId Optional. The ID of the user to sign out.
821
     * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth.
822
     */
823
    async signOutUser(
824
        context: TurnContext,
825
        connectionName?: string,
826
        userId?: string,
827
        oAuthAppCredentials?: AppCredentials
828
    ): Promise<void> {
829
        if (!context.activity.from || !context.activity.from.id) {
6✔
830
            throw new Error('BotFrameworkAdapter.signOutUser(): missing from or from.id');
4✔
831
        }
832
        if (!userId) {
2!
833
            userId = context.activity.from.id;
2✔
834
        }
835

836
        this.checkEmulatingOAuthCards(context);
2✔
837
        const url: string = this.oauthApiUrl(context);
2✔
838
        const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
2✔
839
        context.turnState.set(this.TokenApiClientCredentialsKey, client);
2✔
840

841
        await client.userToken.signOut(userId, {
2✔
842
            connectionName: connectionName,
843
            channelId: context.activity.channelId,
844
        });
845
    }
846

847
    /**
848
     * Asynchronously gets a sign-in link from the token server that can be sent as part
849
     * of a [SigninCard](xref:botframework-schema.SigninCard).
850
     *
851
     * @param context The context object for the turn.
852
     * @param connectionName The name of the auth connection to use.
853
     * @param oAuthAppCredentials AppCredentials for OAuth.
854
     * @param userId The user id that will be associated with the token.
855
     * @param finalRedirect The final URL that the OAuth flow will redirect to.
856
     */
857
    async getSignInLink(
858
        context: TurnContext,
859
        connectionName: string,
860
        oAuthAppCredentials?: AppCredentials,
861
        userId?: string,
862
        finalRedirect?: string
863
    ): Promise<string>;
864
    async getSignInLink(
865
        context: TurnContext,
866
        connectionName: string,
867
        oAuthAppCredentials?: CoreAppCredentials,
868
        userId?: string,
869
        finalRedirect?: string
870
    ): Promise<string>;
871
    /**
872
     * Asynchronously gets a sign-in link from the token server that can be sent as part
873
     * of a [SigninCard](xref:botframework-schema.SigninCard).
874
     *
875
     * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn.
876
     * @param connectionName The name of the auth connection to use.
877
     * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth.
878
     * @param userId Optional. The user id that will be associated with the token.
879
     * @param finalRedirect Optional. The final URL that the OAuth flow will redirect to.
880
     * @returns The sign in link.
881
     */
882
    async getSignInLink(
883
        context: TurnContext,
884
        connectionName: string,
885
        oAuthAppCredentials?: AppCredentials,
886
        userId?: string,
887
        finalRedirect?: string
888
    ): Promise<string> {
889
        if (userId && userId != context.activity.from.id) {
8✔
890
            throw new ReferenceError(
2✔
891
                "cannot retrieve OAuth signin link for a user that's different from the conversation"
892
            );
893
        }
894

895
        this.checkEmulatingOAuthCards(context);
6✔
896
        const conversation: Partial<ConversationReference> = TurnContext.getConversationReference(context.activity);
6✔
897
        const url: string = this.oauthApiUrl(context);
6✔
898
        const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
6✔
899
        context.turnState.set(this.TokenApiClientCredentialsKey, client);
6✔
900
        const state: any = {
6✔
901
            ConnectionName: connectionName,
902
            Conversation: conversation,
903
            MsAppId: (client.credentials as AppCredentials).appId,
904
            RelatesTo: context.activity.relatesTo,
905
        };
906

907
        const finalState: string = Buffer.from(JSON.stringify(state)).toString('base64');
6✔
908
        return (
6✔
909
            await client.botSignIn.getSignInUrl(finalState, { channelId: context.activity.channelId, finalRedirect })
910
        )._response.bodyAsText;
911
    }
912

913
    /**
914
     * Asynchronously retrieves the token status for each configured connection for the given user.
915
     *
916
     * @param context The context object for the turn.
917
     * @param userId Optional. If present, the ID of the user to retrieve the token status for.
918
     *      Otherwise, the ID of the user who sent the current activity is used.
919
     * @param includeFilter Optional. A comma-separated list of connection's to include. If present,
920
     *      the `includeFilter` parameter limits the tokens this method returns.
921
     * @param oAuthAppCredentials AppCredentials for OAuth.
922
     *
923
     * @returns The [TokenStatus](xref:botframework-connector.TokenStatus) objects retrieved.
924
     */
925
    async getTokenStatus(context: TurnContext, userId?: string, includeFilter?: string): Promise<TokenStatus[]>;
926
    async getTokenStatus(
927
        context: TurnContext,
928
        userId?: string,
929
        includeFilter?: string,
930
        oAuthAppCredentials?: CoreAppCredentials
931
    ): Promise<TokenStatus[]>;
932
    /**
933
     * Asynchronously retrieves the token status for each configured connection for the given user.
934
     *
935
     * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn.
936
     * @param userId Optional. If present, the ID of the user to retrieve the token status for.
937
     * Otherwise, the ID of the user who sent the current activity is used.
938
     * @param includeFilter Optional. A comma-separated list of connection's to include. If present,
939
     * the `includeFilter` parameter limits the tokens this method returns.
940
     * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth.
941
     *
942
     * @returns The [TokenStatus](xref:botframework-connector.TokenStatus) objects retrieved.
943
     */
944
    async getTokenStatus(
945
        context: TurnContext,
946
        userId?: string,
947
        includeFilter?: string,
948
        oAuthAppCredentials?: AppCredentials
949
    ): Promise<TokenStatus[]> {
950
        if (!userId && (!context.activity.from || !context.activity.from.id)) {
8✔
951
            throw new Error('BotFrameworkAdapter.getTokenStatus(): missing from or from.id');
4✔
952
        }
953
        this.checkEmulatingOAuthCards(context);
4✔
954
        userId = userId || context.activity.from.id;
4!
955
        const url: string = this.oauthApiUrl(context);
4✔
956
        const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
4✔
957
        context.turnState.set(this.TokenApiClientCredentialsKey, client);
4✔
958

959
        return (
4✔
960
            await client.userToken.getTokenStatus(userId, {
961
                channelId: context.activity.channelId,
962
                include: includeFilter,
963
            })
964
        )._response.parsedBody;
965
    }
966

967
    /**
968
     * Asynchronously signs out the user from the token server.
969
     *
970
     * @param context The context object for the turn.
971
     * @param connectionName The name of the auth connection to use.
972
     * @param resourceUrls The list of resource URLs to retrieve tokens for.
973
     * @param oAuthAppCredentials AppCredentials for OAuth.
974
     *
975
     * @returns A map of the [TokenResponse](xref:botframework-schema.TokenResponse) objects by resource URL.
976
     */
977
    async getAadTokens(
978
        context: TurnContext,
979
        connectionName: string,
980
        resourceUrls: string[]
981
    ): Promise<{ [propertyName: string]: TokenResponse }>;
982
    async getAadTokens(
983
        context: TurnContext,
984
        connectionName: string,
985
        resourceUrls: string[],
986
        oAuthAppCredentials?: CoreAppCredentials
987
    ): Promise<{ [propertyName: string]: TokenResponse }>;
988
    /**
989
     * Asynchronously signs out the user from the token server.
990
     *
991
     * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn.
992
     * @param connectionName The name of the auth connection to use.
993
     * @param resourceUrls The list of resource URLs to retrieve tokens for.
994
     * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth.
995
     *
996
     * @returns A map of the [TokenResponse](xref:botframework-schema.TokenResponse) objects by resource URL.
997
     */
998
    async getAadTokens(
999
        context: TurnContext,
1000
        connectionName: string,
1001
        resourceUrls: string[],
1002
        oAuthAppCredentials?: AppCredentials
1003
    ): Promise<{ [propertyName: string]: TokenResponse }> {
1004
        if (!context.activity.from || !context.activity.from.id) {
6✔
1005
            throw new Error('BotFrameworkAdapter.getAadTokens(): missing from or from.id');
4✔
1006
        }
1007
        this.checkEmulatingOAuthCards(context);
2✔
1008
        const userId: string = context.activity.from.id;
2✔
1009
        const url: string = this.oauthApiUrl(context);
2✔
1010
        const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
2✔
1011
        context.turnState.set(this.TokenApiClientCredentialsKey, client);
2✔
1012

1013
        return (
2✔
1014
            await client.userToken.getAadTokens(
1015
                userId,
1016
                connectionName,
1017
                { resourceUrls: resourceUrls },
1018
                { channelId: context.activity.channelId }
1019
            )
1020
        )._response.parsedBody as { [propertyName: string]: TokenResponse };
1021
    }
1022

1023
    /**
1024
     * Asynchronously Get the raw signin resource to be sent to the user for signin.
1025
     *
1026
     * @param context The context object for the turn.
1027
     * @param connectionName The name of the auth connection to use.
1028
     * @param userId The user id that will be associated with the token.
1029
     * @param finalRedirect The final URL that the OAuth flow will redirect to.
1030
     * @param appCredentials Optional. The CoreAppCredentials for OAuth.
1031
     * @returns The [BotSignInGetSignInResourceResponse](xref:botframework-connector.BotSignInGetSignInResourceResponse) object.
1032
     */
1033
    async getSignInResource(
1034
        context: TurnContext,
1035
        connectionName: string,
1036
        userId?: string,
1037
        finalRedirect?: string,
1038
        appCredentials?: CoreAppCredentials
1039
    ): Promise<SignInUrlResponse> {
1040
        if (!connectionName) {
10✔
1041
            throw new Error('getUserToken() requires a connectionName but none was provided.');
2✔
1042
        }
1043

1044
        if (!context.activity.from || !context.activity.from.id) {
8✔
1045
            throw new Error('BotFrameworkAdapter.getSignInResource(): missing from or from.id');
4✔
1046
        }
1047

1048
        // The provided userId doesn't match the from.id on the activity. (same for finalRedirect)
1049
        if (userId && context.activity.from.id !== userId) {
4✔
1050
            throw new Error(
2✔
1051
                'BotFrameworkAdapter.getSiginInResource(): cannot get signin resource for a user that is different from the conversation'
1052
            );
1053
        }
1054

1055
        const url: string = this.oauthApiUrl(context);
2✔
1056
        const credentials = appCredentials as AppCredentials;
2✔
1057
        const client: TokenApiClient = this.createTokenApiClient(url, credentials);
2✔
1058
        const conversation: Partial<ConversationReference> = TurnContext.getConversationReference(context.activity);
2✔
1059

1060
        const state: any = {
2✔
1061
            ConnectionName: connectionName,
1062
            Conversation: conversation,
1063
            relatesTo: context.activity.relatesTo,
1064
            MSAppId: (client.credentials as AppCredentials).appId,
1065
        };
1066
        const finalState: string = Buffer.from(JSON.stringify(state)).toString('base64');
2✔
1067
        const options: TokenApiModels.BotSignInGetSignInResourceOptionalParams = { finalRedirect: finalRedirect };
2✔
1068

1069
        return await client.botSignIn.getSignInResource(finalState, options);
2✔
1070
    }
1071

1072
    /**
1073
     * Asynchronously Performs a token exchange operation such as for single sign-on.
1074
     *
1075
     * @param context Context for the current turn of conversation with the user.
1076
     * @param connectionName Name of the auth connection to use.
1077
     * @param userId The user id that will be associated with the token.
1078
     * @param tokenExchangeRequest The exchange request details, either a token to exchange or a uri to exchange.
1079
     * @param appCredentials Optional. The CoreAppCredentials for OAuth.
1080
     */
1081
    async exchangeToken(
1082
        context: TurnContext,
1083
        connectionName: string,
1084
        userId: string,
1085
        tokenExchangeRequest: TokenExchangeRequest,
1086
        appCredentials?: CoreAppCredentials
1087
    ): Promise<TokenResponse>;
1088
    /**
1089
     * Asynchronously Performs a token exchange operation such as for single sign-on.
1090
     *
1091
     * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn.
1092
     * @param connectionName Name of the auth connection to use.
1093
     * @param userId The user id that will be associated with the token.
1094
     * @param tokenExchangeRequest The [TokenExchangeRequest](xref:botbuilder-schema.TokenExchangeRequest), either a token to exchange or a uri to exchange.
1095
     * @param appCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth.
1096
     * @returns A `Promise` representing the exchanged [TokenResponse](xref:botframework-schema.TokenResponse).
1097
     */
1098
    async exchangeToken(
1099
        context: TurnContext,
1100
        connectionName: string,
1101
        userId: string,
1102
        tokenExchangeRequest: TokenExchangeRequest,
1103
        appCredentials?: AppCredentials
1104
    ): Promise<TokenResponse> {
1105
        if (!connectionName) {
8✔
1106
            throw new Error('exchangeToken() requires a connectionName but none was provided.');
2✔
1107
        }
1108

1109
        if (!userId) {
6✔
1110
            throw new Error('exchangeToken() requires an userId but none was provided.');
2✔
1111
        }
1112

1113
        if (tokenExchangeRequest && !tokenExchangeRequest.token && !tokenExchangeRequest.uri) {
4✔
1114
            throw new Error(
2✔
1115
                'BotFrameworkAdapter.exchangeToken(): Either a Token or Uri property is required on the TokenExchangeRequest'
1116
            );
1117
        }
1118

1119
        const url: string = this.oauthApiUrl(context);
2✔
1120
        const client: TokenApiClient = this.createTokenApiClient(url, appCredentials);
2✔
1121

1122
        return (
2✔
1123
            await client.userToken.exchangeAsync(
1124
                userId,
1125
                connectionName,
1126
                context.activity.channelId,
1127
                tokenExchangeRequest
1128
            )
1129
        )._response.parsedBody as TokenResponse;
1130
    }
1131

1132
    /**
1133
     * Asynchronously sends an emulated OAuth card for a channel.
1134
     *
1135
     * This method supports the framework and is not intended to be called directly for your code.
1136
     *
1137
     * @param contextOrServiceUrl The URL of the emulator.
1138
     * @param emulate `true` to send an emulated OAuth card to the emulator; or `false` to not send the card.
1139
     *
1140
     * @remarks
1141
     * When testing a bot in the Bot Framework Emulator, this method can emulate the OAuth card interaction.
1142
     */
1143
    async emulateOAuthCards(contextOrServiceUrl: TurnContext | string, emulate: boolean): Promise<void> {
1144
        this.isEmulatingOAuthCards = emulate;
×
1145
        const url: string = this.oauthApiUrl(contextOrServiceUrl);
×
UNCOV
1146
        await EmulatorApiClient.emulateOAuthCards(this.credentials, url, emulate);
×
1147
    }
1148

1149
    /**
1150
     * Asynchronously creates a turn context and runs the middleware pipeline for an incoming activity.
1151
     *
1152
     * @param req An Express or Restify style request object.
1153
     * @param res An Express or Restify style response object.
1154
     * @param logic The function to call at the end of the middleware pipeline.
1155
     *
1156
     * @remarks
1157
     * This is the main way a bot receives incoming messages and defines a turn in the conversation. This method:
1158
     *
1159
     * 1. Parses and authenticates an incoming request.
1160
     *    - The activity is read from the body of the incoming request. An error will be returned
1161
     *      if the activity can't be parsed.
1162
     *    - The identity of the sender is authenticated as either the Emulator or a valid Microsoft
1163
     *      server, using the bot's `appId` and `appPassword`. The request is rejected if the sender's
1164
     *      identity is not verified.
1165
     * 1. Creates a [TurnContext](xref:botbuilder-core.TurnContext) object for the received activity.
1166
     *    - This object is wrapped with a [revocable proxy](https://www.ecma-international.org/ecma-262/6.0/#sec-proxy.revocable).
1167
     *    - When this method completes, the proxy is revoked.
1168
     * 1. Sends the turn context through the adapter's middleware pipeline.
1169
     * 1. Sends the turn context to the `logic` function.
1170
     *    - The bot may perform additional routing or processing at this time.
1171
     *      Returning a promise (or providing an `async` handler) will cause the adapter to wait for any asynchronous operations to complete.
1172
     *    - After the `logic` function completes, the promise chain set up by the middleware is resolved.
1173
     *
1174
     * > [!TIP]
1175
     * > If you see the error `TypeError: Cannot perform 'set' on a proxy that has been revoked`
1176
     * > in your bot's console output, the likely cause is that an async function was used
1177
     * > without using the `await` keyword. Make sure all async functions use await!
1178
     *
1179
     * Middleware can _short circuit_ a turn. When this happens, subsequent middleware and the
1180
     * `logic` function is not called; however, all middleware prior to this point still run to completion.
1181
     * For more information about the middleware pipeline, see the
1182
     * [how bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and
1183
     * [middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware) articles.
1184
     * Use the adapter's [use](xref:botbuilder-core.BotAdapter.use) method to add middleware to the adapter.
1185
     *
1186
     * For example:
1187
     * ```JavaScript
1188
     * server.post('/api/messages', (req, res) => {
1189
     *    // Route received request to adapter for processing
1190
     *    adapter.processActivity(req, res, async (context) => {
1191
     *        // Process any messages received
1192
     *        if (context.activity.type === ActivityTypes.Message) {
1193
     *            await context.sendActivity(`Hello World`);
1194
     *        }
1195
     *    });
1196
     * });
1197
     * ```
1198
     */
1199
    async processActivity(
1200
        req: WebRequest,
1201
        res: WebResponse,
1202
        logic: (context: TurnContext) => Promise<any>
1203
    ): Promise<void> {
1204
        let body: any;
1205
        let status: number;
1206
        let processError: Error;
1207
        try {
42✔
1208
            // Parse body of request
1209
            status = 400;
42✔
1210
            const request = await parseRequest(req);
42✔
1211

1212
            // Authenticate the incoming request
1213
            status = 401;
38✔
1214
            const authHeader: string = req.headers.authorization || req.headers.Authorization || '';
38✔
1215

1216
            const identity = await this.authenticateRequestInternal(request, authHeader);
38✔
1217

1218
            // Set the correct callerId value and discard values received over the wire
1219
            request.callerId = await this.generateCallerId(identity);
36✔
1220

1221
            // Process received activity
1222
            status = 500;
36✔
1223
            const context: TurnContext = this.createContext(request);
36✔
1224
            context.turnState.set(this.BotIdentityKey, identity);
36✔
1225
            const connectorClient = await this.createConnectorClientWithIdentity(request.serviceUrl, identity);
36✔
1226
            context.turnState.set(this.ConnectorClientKey, connectorClient);
36✔
1227

1228
            const oAuthScope: string = SkillValidation.isSkillClaim(identity.claims)
36✔
1229
                ? JwtTokenValidation.getAppIdFromClaims(identity.claims)
36✔
1230
                : this.credentials.oAuthScope;
1231
            context.turnState.set(this.OAuthScopeKey, oAuthScope);
36✔
1232

1233
            context.turnState.set(BotCallbackHandlerKey, logic);
36✔
1234
            await this.runMiddleware(context, logic);
36✔
1235

1236
            // NOTE: The factoring of the code differs here when compared to C# as processActivity() returns Promise<void>.
1237
            //       This is due to the fact that the response to the incoming activity is sent from inside this implementation.
1238
            //       In C#, ProcessActivityAsync() returns Task<InvokeResponse> and ASP.NET handles sending of the response.
1239
            if (request.deliveryMode === DeliveryModes.ExpectReplies) {
34✔
1240
                // Handle "expectReplies" scenarios where all the activities have been buffered and sent back at once
1241
                // in an invoke response.
1242
                let activities = context.bufferedReplyActivities;
8✔
1243

1244
                // If the channel is not the emulator, do not send trace activities.
1245
                // Fixes: https://github.com/microsoft/botbuilder-js/issues/2732
1246
                if (request.channelId !== Channels.Emulator) {
8✔
1247
                    activities = activities.filter((a) => a.type !== ActivityTypes.Trace);
10✔
1248
                }
1249

1250
                body = { activities } as ExpectedReplies;
8✔
1251
                status = StatusCodes.OK;
8✔
1252
            } else if (request.type === ActivityTypes.Invoke) {
26✔
1253
                // Retrieve a cached Invoke response to handle Invoke scenarios.
1254
                // These scenarios deviate from the request/request model as the Bot should return a specific body and status.
1255
                const invokeResponse: any = context.turnState.get(INVOKE_RESPONSE_KEY);
4✔
1256
                if (invokeResponse && invokeResponse.value) {
4✔
1257
                    const value: InvokeResponse = invokeResponse.value;
2✔
1258
                    status = value.status;
2✔
1259
                    body = value.body;
2✔
1260
                } else {
1261
                    status = StatusCodes.NOT_IMPLEMENTED;
2✔
1262
                }
1263
            } else {
1264
                status = StatusCodes.OK;
22✔
1265
            }
1266
        } catch (err) {
1267
            // Catch the error to try and throw the stacktrace out of processActivity()
1268
            processError = err;
8✔
1269
            body = err.toString();
8✔
1270
        }
1271

1272
        // Return status
1273
        res.status(status);
42✔
1274
        if (body) {
42✔
1275
            res.send(body);
18✔
1276
        }
1277
        res.end();
42✔
1278

1279
        // Check for an error
1280
        if (status >= 400) {
42✔
1281
            if (processError && (processError as Error).stack) {
10✔
1282
                throw new Error(`BotFrameworkAdapter.processActivity(): ${status} ERROR\n ${processError.stack}`);
8✔
1283
            } else {
1284
                throw new Error(`BotFrameworkAdapter.processActivity(): ${status} ERROR`);
2✔
1285
            }
1286
        }
1287
    }
1288

1289
    /**
1290
     * Asynchronously creates a turn context and runs the middleware pipeline for an incoming activity.
1291
     *
1292
     * Use [CloudAdapter.processActivityDirect] instead.
1293
     *
1294
     * @param activity The activity to process.
1295
     * @param logic The function to call at the end of the middleware pipeline.
1296
     *
1297
     * @remarks
1298
     * This is the main way a bot receives incoming messages and defines a turn in the conversation. This method:
1299
     *
1300
     * 1. Creates a [TurnContext](xref:botbuilder-core.TurnContext) object for the received activity.
1301
     *    - This object is wrapped with a [revocable proxy](https://www.ecma-international.org/ecma-262/6.0/#sec-proxy.revocable).
1302
     *    - When this method completes, the proxy is revoked.
1303
     * 1. Sends the turn context through the adapter's middleware pipeline.
1304
     * 1. Sends the turn context to the `logic` function.
1305
     *    - The bot may perform additional routing or processing at this time.
1306
     *      Returning a promise (or providing an `async` handler) will cause the adapter to wait for any asynchronous operations to complete.
1307
     *    - After the `logic` function completes, the promise chain set up by the middleware is resolved.
1308
     *
1309
     * Middleware can _short circuit_ a turn. When this happens, subsequent middleware and the
1310
     * `logic` function is not called; however, all middleware prior to this point still run to completion.
1311
     * For more information about the middleware pipeline, see the
1312
     * [how bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and
1313
     * [middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware) articles.
1314
     * Use the adapter's [use](xref:botbuilder-core.BotAdapter.use) method to add middleware to the adapter.
1315
     */
1316
    async processActivityDirect(activity: Activity, logic: (context: TurnContext) => Promise<any>): Promise<void> {
1317
        let processError: Error;
1318
        try {
12✔
1319
            // Process activity
1320
            const context: TurnContext = this.createContext(activity);
12✔
1321
            context.turnState.set(BotCallbackHandlerKey, logic);
12✔
1322
            await this.runMiddleware(context, logic);
12✔
1323
        } catch (err) {
1324
            // Catch the error to try and throw the stacktrace out of processActivity()
1325
            processError = err;
4✔
1326
        }
1327

1328
        if (processError) {
12✔
1329
            if (processError && (processError as Error).stack) {
4✔
1330
                throw new Error(`BotFrameworkAdapter.processActivityDirect(): ERROR\n ${processError.stack}`);
2✔
1331
            } else {
1332
                throw new Error('BotFrameworkAdapter.processActivityDirect(): ERROR');
2✔
1333
            }
1334
        }
1335
    }
1336

1337
    /**
1338
     * Asynchronously sends a set of outgoing activities to a channel server.
1339
     *
1340
     * This method supports the framework and is not intended to be called directly for your code.
1341
     * Use the turn context's [sendActivity](xref:botbuilder-core.TurnContext.sendActivity) or
1342
     * [sendActivities](xref:botbuilder-core.TurnContext.sendActivities) method from your bot code.
1343
     *
1344
     * @param context The context object for the turn.
1345
     * @param activities The activities to send.
1346
     *
1347
     * @returns An array of [ResourceResponse](xref:)
1348
     *
1349
     * @remarks
1350
     * The activities will be sent one after another in the order in which they're received. A
1351
     * response object will be returned for each sent activity. For `message` activities this will
1352
     * contain the ID of the delivered message.
1353
     */
1354
    async sendActivities(context: TurnContext, activities: Partial<Activity>[]): Promise<ResourceResponse[]> {
1355
        const responses: ResourceResponse[] = [];
34✔
1356
        for (let i = 0; i < activities.length; i++) {
34✔
1357
            const activity: Partial<Activity> = activities[i];
44✔
1358
            switch (activity.type) {
44✔
1359
                case 'delay':
44✔
1360
                    await delay(typeof activity.value === 'number' ? activity.value : 1000);
4✔
1361
                    responses.push({} as ResourceResponse);
4✔
1362
                    break;
4✔
1363
                case ActivityTypes.InvokeResponse:
1364
                    // Cache response to context object. This will be retrieved when turn completes.
1365
                    context.turnState.set(INVOKE_RESPONSE_KEY, activity);
8✔
1366
                    responses.push({} as ResourceResponse);
8✔
1367
                    break;
8✔
1368
                default: {
1369
                    if (!activity.serviceUrl) {
32✔
1370
                        throw new Error('BotFrameworkAdapter.sendActivity(): missing serviceUrl.');
2✔
1371
                    }
1372
                    if (!activity.conversation || !activity.conversation.id) {
30✔
1373
                        throw new Error('BotFrameworkAdapter.sendActivity(): missing conversation id.');
2✔
1374
                    }
1375
                    if (activity && BotFrameworkAdapter.isStreamingServiceUrl(activity.serviceUrl)) {
28✔
1376
                        if (!this.isStreamingConnectionOpen) {
2!
1377
                            throw new Error(
2✔
1378
                                'BotFrameworkAdapter.sendActivities(): Unable to send activity as Streaming connection is closed.'
1379
                            );
1380
                        }
UNCOV
1381
                        TokenResolver.checkForOAuthCards(this, context, activity as Activity);
×
1382
                    }
1383
                    const client = this.getOrCreateConnectorClient(context, activity.serviceUrl, this.credentials);
26✔
1384
                    if (activity.type === ActivityTypes.Trace && activity.channelId !== Channels.Emulator) {
26!
1385
                        // Just eat activity
UNCOV
1386
                        responses.push({} as ResourceResponse);
×
1387
                    } else if (activity.replyToId) {
26✔
1388
                        responses.push(
24✔
1389
                            await client.conversations.replyToActivity(
1390
                                activity.conversation.id,
1391
                                activity.replyToId,
1392
                                activity
1393
                            )
1394
                        );
1395
                    } else {
1396
                        responses.push(
2✔
1397
                            await client.conversations.sendToConversation(activity.conversation.id, activity)
1398
                        );
1399
                    }
1400
                    break;
24✔
1401
                }
1402
            }
1403
        }
1404
        return responses;
26✔
1405
    }
1406

1407
    /**
1408
     * Asynchronously replaces a previous activity with an updated version.
1409
     *
1410
     * This interface supports the framework and is not intended to be called directly for your code.
1411
     * Use [TurnContext.updateActivity](xref:botbuilder-core.TurnContext.updateActivity) to update
1412
     * an activity from your bot code.
1413
     *
1414
     * @param context The context object for the turn.
1415
     * @param activity The updated version of the activity to replace.
1416
     * @returns A `Promise` representing the [ResourceResponse](xref:botframework-schema.ResourceResponse) for the operation.
1417
     * @remarks
1418
     * Not all channels support this operation. For channels that don't, this call may throw an exception.
1419
     */
1420
    async updateActivity(context: TurnContext, activity: Partial<Activity>): Promise<ResourceResponse | void> {
1421
        if (!activity.serviceUrl) {
8✔
1422
            throw new Error('BotFrameworkAdapter.updateActivity(): missing serviceUrl');
2✔
1423
        }
1424
        if (!activity.conversation || !activity.conversation.id) {
6✔
1425
            throw new Error('BotFrameworkAdapter.updateActivity(): missing conversation or conversation.id');
2✔
1426
        }
1427
        if (!activity.id) {
4✔
1428
            throw new Error('BotFrameworkAdapter.updateActivity(): missing activity.id');
2✔
1429
        }
1430

1431
        const client = this.getOrCreateConnectorClient(context, activity.serviceUrl, this.credentials);
2✔
1432
        return client.conversations.updateActivity(activity.conversation.id, activity.id, activity);
2✔
1433
    }
1434

1435
    /**
1436
     * Creates a connector client.
1437
     *
1438
     * @param serviceUrl The client's service URL.
1439
     * @returns The [ConnectorClient](xref:botbuilder-connector.ConnectorClient) instance.
1440
     * @remarks
1441
     * Override this in a derived class to create a mock connector client for unit testing.
1442
     */
1443
    createConnectorClient(serviceUrl: string): ConnectorClient {
1444
        return this.createConnectorClientInternal(serviceUrl, this.credentials);
86✔
1445
    }
1446

1447
    /**
1448
     * Create a ConnectorClient with a ClaimsIdentity.
1449
     *
1450
     * @remarks
1451
     * If the ClaimsIdentity contains the claims for a Skills request, create a ConnectorClient for use with Skills.
1452
     * Derives the correct audience from the ClaimsIdentity, or the instance's credentials property.
1453
     * @param serviceUrl The client's service URL.
1454
     * @param identity ClaimsIdentity
1455
     */
1456
    async createConnectorClientWithIdentity(serviceUrl: string, identity: ClaimsIdentity): Promise<ConnectorClient>;
1457
    /**
1458
     * Create a ConnectorClient with a ClaimsIdentity and an explicit audience.
1459
     *
1460
     * @remarks
1461
     * If the trimmed audience is not a non-zero length string, the audience will be derived from the ClaimsIdentity or
1462
     * the instance's credentials property.
1463
     * @param serviceUrl The client's service URL.
1464
     * @param identity ClaimsIdentity
1465
     * @param audience The recipient of the ConnectorClient's messages. Normally the Bot Framework Channel Service or the AppId of another bot.
1466
     */
1467
    async createConnectorClientWithIdentity(
1468
        serviceUrl: string,
1469
        identity: ClaimsIdentity,
1470
        audience: string
1471
    ): Promise<ConnectorClient>;
1472
    /**
1473
     * Create a [ConnectorClient](xref:botbuilder-connector.ConnectorClient) with a [ClaimsIdentity](xref:botbuilder-connector.ClaimsIdentity).
1474
     *
1475
     * @remarks
1476
     * If the [ClaimsIdentity](xref:botbuilder-connector.ClaimsIdentity) contains the claims for a Skills request, create a [ConnectorClient](xref:botbuilder-connector.ConnectorClient) for use with Skills.
1477
     * Derives the correct audience from the [ClaimsIdentity](xref:botbuilder-connector.ClaimsIdentity), or the instance's credentials property.
1478
     * @param serviceUrl The client's service URL.
1479
     * @param identity [ClaimsIdentity](xref:botbuilder-connector.ClaimsIdentity).
1480
     * @param audience Optional. The recipient of the [ConnectorClient](xref:botbuilder-connector.ConnectorClient)'s messages. Normally the Bot Framework Channel Service or the AppId of another bot.
1481
     * @returns The client.
1482
     */
1483
    async createConnectorClientWithIdentity(
1484
        serviceUrl: string,
1485
        identity: ClaimsIdentity,
1486
        audience?: string
1487
    ): Promise<ConnectorClient> {
1488
        if (!identity) {
22✔
1489
            throw new Error('BotFrameworkAdapter.createConnectorClientWithIdentity(): invalid identity parameter.');
2✔
1490
        }
1491

1492
        const botAppId =
1493
            identity.getClaimValue(AuthenticationConstants.AudienceClaim) ||
20✔
1494
            identity.getClaimValue(AuthenticationConstants.AppIdClaim);
1495
        // Check if the audience is a string and when trimmed doesn't have a length of 0.
1496
        const validAudience = typeof audience === 'string' && audience.trim().length > 0;
20✔
1497
        const oAuthScope = validAudience ? audience : await this.getOAuthScope(botAppId, identity.claims);
20✔
1498
        const credentials = await this.buildCredentials(botAppId, oAuthScope);
20✔
1499

1500
        const client: ConnectorClient = this.createConnectorClientInternal(serviceUrl, credentials);
20✔
1501
        return client;
20✔
1502
    }
1503

1504
    private createConnectorClientInternal(serviceUrl: string, credentials: AppCredentials): ConnectorClient {
1505
        if (BotFrameworkAdapter.isStreamingServiceUrl(serviceUrl)) {
130✔
1506
            // Check if we have a streaming server. Otherwise, requesting a connector client
1507
            // for a non-existent streaming connection results in an error
1508
            if (!this.streamingServer) {
4!
UNCOV
1509
                throw new Error(
×
1510
                    `Cannot create streaming connector client for serviceUrl ${serviceUrl} without a streaming connection. Call 'useWebSocket' or 'useNamedPipe' to start a streaming connection.`
1511
                );
1512
            }
1513

1514
            const clientOptions = this.getClientOptions(serviceUrl, new StreamingHttpClient(this.streamingServer));
4✔
1515
            return new ConnectorClient(credentials, clientOptions);
4✔
1516
        }
1517

1518
        const clientOptions = this.getClientOptions(serviceUrl);
126✔
1519
        return new ConnectorClient(credentials, clientOptions);
126✔
1520
    }
1521

1522
    private getClientOptions(serviceUrl: string, httpClient?: HttpClient): ConnectorClientOptions {
1523
        const { requestPolicyFactories, ...clientOptions } = this.settings.clientOptions ?? {};
130✔
1524

1525
        const options: ConnectorClientOptions = Object.assign({}, { baseUri: serviceUrl }, clientOptions);
130✔
1526

1527
        if (httpClient) {
130✔
1528
            options.httpClient = httpClient;
4✔
1529
        }
1530

1531
        const userAgent = typeof options.userAgent === 'function' ? options.userAgent(USER_AGENT) : options.userAgent;
130!
1532
        const setUserAgent = userAgentPolicy({
130✔
1533
            value: `${USER_AGENT}${userAgent ?? ''}`,
390✔
1534
        });
1535

1536
        const acceptHeader: RequestPolicyFactory = {
130✔
1537
            create: (nextPolicy) => ({
28✔
1538
                sendRequest: (httpRequest) => {
1539
                    if (!httpRequest.headers.contains('accept')) {
28!
1540
                        httpRequest.headers.set('accept', '*/*');
28✔
1541
                    }
1542
                    return nextPolicy.sendRequest(httpRequest);
28✔
1543
                },
1544
            }),
1545
        };
1546

1547
        // Resolve any user request policy factories, then include our user agent via a factory policy
1548
        options.requestPolicyFactories = (defaultRequestPolicyFactories) => {
130✔
1549
            let defaultFactories = [];
130✔
1550

1551
            if (requestPolicyFactories) {
130✔
1552
                if (typeof requestPolicyFactories === 'function') {
4!
1553
                    const newDefaultFactories = requestPolicyFactories(defaultRequestPolicyFactories);
×
1554
                    if (newDefaultFactories) {
×
UNCOV
1555
                        defaultFactories = newDefaultFactories;
×
1556
                    }
1557
                } else if (requestPolicyFactories) {
4!
1558
                    defaultFactories = [...requestPolicyFactories];
4✔
1559
                }
1560

1561
                // If the user has supplied custom factories, allow them to optionally set user agent
1562
                // before we do.
1563
                defaultFactories = [...defaultFactories, acceptHeader, setUserAgent];
4✔
1564
            } else {
1565
                // In the case that there are no user supplied factories, inject our user agent as
1566
                // the first policy to ensure none of the default policies override it.
1567
                defaultFactories = [acceptHeader, setUserAgent, ...defaultRequestPolicyFactories];
126✔
1568
            }
1569

1570
            return defaultFactories;
130✔
1571
        };
1572

1573
        return options;
130✔
1574
    }
1575

1576
    // Retrieves the ConnectorClient from the TurnContext or creates a new ConnectorClient with the provided serviceUrl and credentials.
1577
    private getOrCreateConnectorClient(
1578
        context: TurnContext,
1579
        serviceUrl: string,
1580
        credentials: AppCredentials
1581
    ): ConnectorClient {
1582
        if (!context || !context.turnState) throw new Error('invalid context parameter');
10!
1583
        if (!serviceUrl) throw new Error('invalid serviceUrl');
10!
1584
        if (!credentials) throw new Error('invalid credentials');
10!
1585

1586
        let client: ConnectorClient = context.turnState.get(this.ConnectorClientKey);
10✔
1587
        // Inspect the retrieved client to confirm that the serviceUrl is correct, if it isn't, create a new one.
1588
        if (!client || client['baseUri'] !== serviceUrl) {
10✔
1589
            client = this.createConnectorClientInternal(serviceUrl, credentials);
2✔
1590
        }
1591

1592
        return client;
10✔
1593
    }
1594

1595
    /**
1596
     * Returns the correct [OAuthScope](xref:botframework-connector.AppCredentials.OAuthScope) for [AppCredentials](xref:botframework-connector.AppCredentials).
1597
     *
1598
     * @param botAppId The bot's AppId.
1599
     * @param claims The [Claim](xref:botbuilder-connector.Claim) list to check.
1600
     *
1601
     * @returns The current credentials' OAuthScope.
1602
     */
1603
    private async getOAuthScope(botAppId: string, claims: Claim[]): Promise<string> {
1604
        // If the Claims are for skills, we need to create an AppCredentials instance with
1605
        // the correct scope for communication between the caller and the skill.
1606
        if (botAppId && SkillValidation.isSkillClaim(claims)) {
30✔
1607
            return JwtTokenValidation.getAppIdFromClaims(claims);
4✔
1608
        }
1609

1610
        // Return the current credentials' OAuthScope.
1611
        return this.credentials.oAuthScope;
26✔
1612
    }
1613

1614
    /**
1615
     *
1616
     * @remarks
1617
     * When building credentials for bot-to-bot communication, oAuthScope must be passed in.
1618
     * @param appId The application id.
1619
     * @param oAuthScope The optional OAuth scope.
1620
     * @returns The app credentials to be used to acquire tokens.
1621
     */
1622
    protected async buildCredentials(appId: string, oAuthScope?: string): Promise<AppCredentials> {
1623
        // There is no cache for AppCredentials in JS as opposed to C#.
1624
        // Instead of retrieving an AppCredentials from the Adapter instance, generate a new one
1625
        const appPassword = await this.credentialsProvider.getAppPassword(appId);
18✔
1626

1627
        let credentials: AppCredentials;
1628
        if (this.settings.certificateThumbprint && this.settings.certificatePrivateKey) {
18✔
1629
            credentials = new CertificateAppCredentials(
2✔
1630
                appId,
1631
                this.settings.certificateThumbprint,
1632
                this.settings.certificatePrivateKey,
1633
                this.settings.channelAuthTenant
1634
            );
1635
        } else {
1636
            if (JwtTokenValidation.isGovernment(this.settings.channelService)) {
16✔
1637
                credentials = new MicrosoftGovernmentAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope);
2✔
1638
            }
1639
            else{
1640
                credentials = new MicrosoftAppCredentials(appId, appPassword, this.settings.channelAuthTenant, oAuthScope);
14✔
1641
            }
1642
        }
1643

1644
        return credentials;
18✔
1645
    }
1646

1647
    /**
1648
     * Creates an OAuth API client.
1649
     *
1650
     * @param serviceUrl The client's service URL.
1651
     * @param oAuthAppCredentials AppCredentials for OAuth.
1652
     *
1653
     * @remarks
1654
     * Override this in a derived class to create a mock OAuth API client for unit testing.
1655
     */
1656
    protected createTokenApiClient(serviceUrl: string, oAuthAppCredentials?: CoreAppCredentials): TokenApiClient;
1657
    /**
1658
     * Creates an OAuth API client.
1659
     *
1660
     * @param serviceUrl The client's service URL.
1661
     * @param oAuthAppCredentials Optional. The [AppCredentials](xref:botframework-connector.AppCredentials)for OAuth.
1662
     *
1663
     * @remarks
1664
     * Override this in a derived class to create a mock OAuth API client for unit testing.
1665
     * @returns The client.
1666
     */
1667
    protected createTokenApiClient(serviceUrl: string, oAuthAppCredentials?: AppCredentials): TokenApiClient {
UNCOV
1668
        const tokenApiClientCredentials = oAuthAppCredentials ? oAuthAppCredentials : this.credentials;
×
UNCOV
1669
        const client = new TokenApiClient(tokenApiClientCredentials, { baseUri: serviceUrl, userAgent: USER_AGENT });
×
1670

UNCOV
1671
        return client;
×
1672
    }
1673

1674
    /**
1675
     * Allows for the overriding of authentication in unit tests.
1676
     *
1677
     * @param request Received request.
1678
     * @param authHeader Received authentication header.
1679
     */
1680
    protected async authenticateRequest(request: Partial<Activity>, authHeader: string): Promise<void> {
1681
        const identity = await this.authenticateRequestInternal(request, authHeader);
4✔
1682
        if (!identity.isAuthenticated) {
4!
UNCOV
1683
            throw new Error('Unauthorized Access. Request is not authorized');
×
1684
        }
1685

1686
        // Set the correct callerId value and discard values received over the wire
1687
        request.callerId = await this.generateCallerId(identity);
4✔
1688
    }
1689

1690
    /**
1691
     * @ignore
1692
     * @private
1693
     * Returns the actual ClaimsIdentity from the JwtTokenValidation.authenticateRequest() call.
1694
     * @remarks
1695
     * This method is used instead of authenticateRequest() in processActivity() to obtain the ClaimsIdentity for caching in the TurnContext.turnState.
1696
     *
1697
     * @param request Received request.
1698
     * @param authHeader Received authentication header.
1699
     */
1700
    private authenticateRequestInternal(request: Partial<Activity>, authHeader: string): Promise<ClaimsIdentity> {
1701
        return JwtTokenValidation.authenticateRequest(
16✔
1702
            request,
1703
            authHeader,
1704
            this.credentialsProvider,
1705
            this.settings.channelService,
1706
            this.authConfiguration
1707
        );
1708
    }
1709

1710
    /**
1711
     * Generates the CallerId property for the activity based on
1712
     * https://github.com/microsoft/botframework-obi/blob/main/protocols/botframework-activity/botframework-activity.md#appendix-v---caller-id-values.
1713
     *
1714
     * @param identity The inbound claims.
1715
     * @returns {Promise<string>} a promise representing the generated callerId.
1716
     */
1717
    private async generateCallerId(identity: ClaimsIdentity): Promise<string> {
1718
        if (!identity) {
40!
UNCOV
1719
            throw new TypeError('BotFrameworkAdapter.generateCallerId(): Missing identity parameter.');
×
1720
        }
1721

1722
        // Is the bot accepting all incoming messages?
1723
        const isAuthDisabled = await this.credentialsProvider.isAuthenticationDisabled();
40✔
1724
        if (isAuthDisabled) {
40✔
1725
            // Return undefined so that the callerId is cleared.
1726
            return;
32✔
1727
        }
1728

1729
        // Is the activity from another bot?
1730
        if (SkillValidation.isSkillClaim(identity.claims)) {
8✔
1731
            const callerId = JwtTokenValidation.getAppIdFromClaims(identity.claims);
2✔
1732
            return `${CallerIdConstants.BotToBotPrefix}${callerId}`;
2✔
1733
        }
1734

1735
        // Is the activity from Public Azure?
1736
        if (!this.settings.channelService || this.settings.channelService.length === 0) {
6✔
1737
            return CallerIdConstants.PublicAzureChannel;
4✔
1738
        }
1739

1740
        // Is the activity from Azure Gov?
1741
        if (JwtTokenValidation.isGovernment(this.settings.channelService)) {
2!
1742
            return CallerIdConstants.USGovChannel;
2✔
1743
        }
1744

1745
        // Return undefined so that the callerId is cleared.
1746
    }
1747

1748
    /**
1749
     * Gets the OAuth API endpoint.
1750
     *
1751
     * @param contextOrServiceUrl The URL of the channel server to query or
1752
     * a [TurnContext](xref:botbuilder-core.TurnContext). For a turn context, the context's
1753
     * [activity](xref:botbuilder-core.TurnContext.activity).[serviceUrl](xref:botframework-schema.Activity.serviceUrl)
1754
     * is used for the URL.
1755
     * @returns The endpoint used for the API requests.
1756
     * @remarks
1757
     * Override this in a derived class to create a mock OAuth API endpoint for unit testing.
1758
     */
1759
    protected oauthApiUrl(contextOrServiceUrl: TurnContext | string): string {
1760
        return this.isEmulatingOAuthCards
22✔
1761
            ? typeof contextOrServiceUrl === 'object'
22!
1762
                ? contextOrServiceUrl.activity.serviceUrl
×
1763
                : contextOrServiceUrl
1764
            : this.settings.oAuthEndpoint
1765
            ? this.settings.oAuthEndpoint
22✔
1766
            : JwtTokenValidation.isGovernment(this.settings.channelService)
1767
            ? US_GOV_OAUTH_ENDPOINT
20!
1768
            : OAUTH_ENDPOINT;
1769
    }
1770

1771
    /**
1772
     * Checks the environment and can set a flag to emulate OAuth cards.
1773
     *
1774
     * @param context The context object for the turn.
1775
     *
1776
     * @remarks
1777
     * Override this in a derived class to control how OAuth cards are emulated for unit testing.
1778
     */
1779
    protected checkEmulatingOAuthCards(context: TurnContext): void {
1780
        if (!this.isEmulatingOAuthCards && context.activity.channelId === 'emulator' && !this.credentials.appId) {
16!
UNCOV
1781
            this.isEmulatingOAuthCards = true;
×
1782
        }
1783
    }
1784

1785
    /**
1786
     * Creates a turn context.
1787
     *
1788
     * @param request An incoming request body.
1789
     * @returns A new [TurnContext](xref:botbuilder-core.TurnContext) instance.
1790
     * @remarks
1791
     * Override this in a derived class to modify how the adapter creates a turn context.
1792
     */
1793
    protected createContext(request: Partial<Activity>): TurnContext {
1794
        return new TurnContext(this, request);
86✔
1795
    }
1796

1797
    /**
1798
     * Checks the validity of the request and attempts to map it the correct virtual endpoint,
1799
     * then generates and returns a response if appropriate.
1800
     *
1801
     * @param request A ReceiveRequest from the connected channel.
1802
     * @returns A response created by the BotAdapter to be sent to the client that originated the request.
1803
     */
1804
    async processRequest(request: IReceiveRequest): Promise<StreamingResponse> {
1805
        const response = new StreamingResponse();
26✔
1806

1807
        if (!request) {
26!
UNCOV
1808
            response.statusCode = StatusCodes.BAD_REQUEST;
×
UNCOV
1809
            response.setBody('No request provided.');
×
UNCOV
1810
            return response;
×
1811
        }
1812

1813
        if (!request.verb || !request.path) {
26✔
1814
            response.statusCode = StatusCodes.BAD_REQUEST;
4✔
1815
            response.setBody(`Request missing verb and/or path. Verb: ${request.verb}. Path: ${request.path}`);
4✔
1816
            return response;
4✔
1817
        }
1818

1819
        if (request.verb.toLocaleUpperCase() !== POST && request.verb.toLocaleUpperCase() !== GET) {
22✔
1820
            response.statusCode = StatusCodes.METHOD_NOT_ALLOWED;
2✔
1821
            response.setBody(`Invalid verb received. Only GET and POST are accepted. Verb: ${request.verb}`);
2✔
1822
        }
1823

1824
        if (request.path.toLocaleLowerCase() === VERSION_PATH) {
22✔
1825
            return await this.handleVersionRequest(request, response);
8✔
1826
        }
1827

1828
        // Convert the StreamingRequest into an activity the Adapter can understand.
1829
        let body: Activity;
1830
        try {
14✔
1831
            body = await this.readRequestBodyAsString(request);
14✔
1832
        } catch (error) {
1833
            response.statusCode = StatusCodes.BAD_REQUEST;
2✔
1834
            response.setBody(`Request body missing or malformed: ${error}`);
2✔
1835
            return response;
2✔
1836
        }
1837

1838
        if (request.path.toLocaleLowerCase() !== MESSAGES_PATH) {
12✔
1839
            response.statusCode = StatusCodes.NOT_FOUND;
2✔
1840
            response.setBody(`Path ${request.path.toLocaleLowerCase()} not not found. Expected ${MESSAGES_PATH}}.`);
2✔
1841
            return response;
2✔
1842
        }
1843

1844
        if (request.verb.toLocaleUpperCase() !== POST) {
10!
UNCOV
1845
            response.statusCode = StatusCodes.METHOD_NOT_ALLOWED;
×
UNCOV
1846
            response.setBody(
×
1847
                `Invalid verb received for ${request.verb.toLocaleLowerCase()}. Only GET and POST are accepted. Verb: ${
1848
                    request.verb
1849
                }`
1850
            );
1851

UNCOV
1852
            return response;
×
1853
        }
1854

1855
        try {
10✔
1856
            const context = new TurnContext(this, body);
10✔
1857
            await this.runMiddleware(context, this.logic);
10✔
1858

1859
            if (body.type === ActivityTypes.Invoke) {
6✔
1860
                const invokeResponse: any = context.turnState.get(INVOKE_RESPONSE_KEY);
4✔
1861
                if (invokeResponse && invokeResponse.value) {
4!
1862
                    const value: InvokeResponse = invokeResponse.value;
4✔
1863
                    response.statusCode = value.status;
4✔
1864
                    if (value.body) {
4!
UNCOV
1865
                        response.setBody(value.body);
×
1866
                    }
1867
                } else {
UNCOV
1868
                    response.statusCode = StatusCodes.NOT_IMPLEMENTED;
×
1869
                }
1870
            } else if (body.deliveryMode === DeliveryModes.ExpectReplies) {
2!
UNCOV
1871
                const replies: ExpectedReplies = { activities: context.bufferedReplyActivities as Activity[] };
×
UNCOV
1872
                response.setBody(replies);
×
1873
                response.statusCode = StatusCodes.OK;
×
1874
            } else {
1875
                response.statusCode = StatusCodes.OK;
2✔
1876
            }
1877
        } catch (error) {
1878
            response.statusCode = StatusCodes.INTERNAL_SERVER_ERROR;
4✔
1879
            response.setBody(error);
4✔
1880
            return response;
4✔
1881
        }
1882

1883
        return response;
6✔
1884
    }
1885

1886
    /**
1887
     * Process a web request by applying a logic function.
1888
     *
1889
     * @param req An incoming HTTP [Request](xref:botbuilder.Request)
1890
     * @param res The corresponding HTTP [Response](xref:botbuilder.Response)
1891
     * @param logic The logic function to apply
1892
     * @returns a promise representing the asynchronous operation.
1893
     */
1894
    async process(req: Request, res: Response, logic: (context: TurnContext) => Promise<void>): Promise<void>;
1895

1896
    /**
1897
     * Handle a web socket connection by applying a logic function to
1898
     * each streaming request.
1899
     *
1900
     * @param req An incoming HTTP [Request](xref:botbuilder.Request)
1901
     * @param socket The corresponding [INodeSocket](xref:botframework-streaming.INodeSocket)
1902
     * @param head The corresponding [INodeBuffer](xref:botframework-streaming.INodeBuffer)
1903
     * @param logic The logic function to apply
1904
     * @returns a promise representing the asynchronous operation.
1905
     */
1906
    async process(
1907
        req: Request,
1908
        socket: INodeSocket,
1909
        head: INodeBuffer,
1910
        logic: (context: TurnContext) => Promise<void>
1911
    ): Promise<void>;
1912

1913
    /**
1914
     * @internal
1915
     */
1916
    async process(
1917
        req: Request,
1918
        resOrSocket: Response | INodeSocket,
1919
        logicOrHead: ((context: TurnContext) => Promise<void>) | INodeBuffer,
1920
        maybeLogic?: (context: TurnContext) => Promise<void>
1921
    ): Promise<void> {
UNCOV
1922
        if (maybeLogic) {
×
UNCOV
1923
            return this.useWebSocket(
×
1924
                req,
1925
                INodeSocketT.parse(resOrSocket),
1926
                INodeBufferT.parse(logicOrHead),
1927
                LogicT.parse(maybeLogic)
1928
            );
1929
        } else {
1930
            return this.processActivity(req, ResponseT.parse(resOrSocket), LogicT.parse(logicOrHead));
×
1931
        }
1932
    }
1933

1934
    /**
1935
     * Connects the handler to a Named Pipe server and begins listening for incoming requests.
1936
     *
1937
     * @param logic The logic that will handle incoming requests.
1938
     * @param pipeName The name of the named pipe to use when creating the server.
1939
     * @param retryCount Number of times to attempt to bind incoming and outgoing pipe
1940
     * @param onListen Optional callback that fires once when server is listening on both incoming and outgoing pipe
1941
     */
1942
    async useNamedPipe(
1943
        logic: (context: TurnContext) => Promise<any>,
1944
        pipeName = defaultPipeName,
×
1945
        retryCount = 7,
10✔
1946
        onListen?: () => void
1947
    ): Promise<void> {
1948
        if (!logic) {
10!
UNCOV
1949
            throw new Error('Bot logic needs to be provided to `useNamedPipe`');
×
1950
        }
1951

1952
        if (this.isStreamingConnectionOpen) {
10✔
1953
            if (this.namedPipeName === pipeName) {
4✔
1954
                // Idempotent operation
1955
                return;
2✔
1956
            } else {
1957
                // BotFrameworkAdapters are scoped to one streaming connection.
1958
                // Switching streams is an advanced scenario, one not innately supported by the SDK.
1959
                // Each BotFrameworkAdapter instance is scoped to a stream, so switching streams
1960
                // results in dropped conversations that the bot cannot reconnect to.
1961
                throw new Error(
2✔
1962
                    'This BotFrameworkAdapter instance is already connected to a different stream. Use a new instance to connect to the provided pipeName.'
1963
                );
1964
            }
1965
        }
1966

1967
        this.logic = logic;
6✔
1968

1969
        await retry(() => this.startNamedPipeServer(pipeName, onListen), retryCount);
6✔
1970
    }
1971

1972
    /**
1973
     * Process the initial request to establish a long lived connection via a streaming server.
1974
     *
1975
     * @param req The connection request.
1976
     * @param socket The raw socket connection between the bot (server) and channel/caller (client).
1977
     * @param head The first packet of the upgraded stream.
1978
     * @param logic The logic that handles incoming streaming requests for the lifetime of the WebSocket connection.
1979
     */
1980
    async useWebSocket(
1981
        req: WebRequest,
1982
        socket: INodeSocket,
1983
        head: INodeBuffer,
1984
        logic: (context: TurnContext) => Promise<any>
1985
    ): Promise<void> {
1986
        // Use the provided NodeWebSocketFactoryBase on BotFrameworkAdapter construction,
1987
        // otherwise create a new NodeWebSocketFactory.
1988
        const webSocketFactory = this.webSocketFactory || new NodeWebSocketFactory();
14✔
1989

1990
        if (!logic) {
14✔
1991
            throw new Error('Streaming logic needs to be provided to `useWebSocket`');
2✔
1992
        }
1993

1994
        this.logic = logic;
12✔
1995

1996
        try {
12✔
1997
            await this.authenticateConnection(req, this.settings.channelService);
12✔
1998
        } catch (err) {
1999
            abortWebSocketUpgrade(socket, err);
4✔
2000
            throw err;
4✔
2001
        }
2002

2003
        const nodeWebSocket = await webSocketFactory.createWebSocket(req, socket, head);
8✔
2004

2005
        await this.startWebSocket(nodeWebSocket);
8✔
2006
    }
2007

2008
    private async startNamedPipeServer(pipeName: string, onListen?: () => void): Promise<void> {
2009
        this.namedPipeName = pipeName;
2✔
2010
        this.streamingServer = new NamedPipeServer(pipeName, this);
2✔
2011

2012
        try {
2✔
2013
            await this.streamingServer.start(onListen);
2✔
2014
        } finally {
UNCOV
2015
            this.namedPipeName = undefined;
×
2016
        }
2017
    }
2018

2019
    private async authenticateConnection(req: WebRequest, channelService?: string): Promise<void> {
2020
        if (!this.credentials.appId) {
12✔
2021
            // auth is disabled
2022
            return;
8✔
2023
        }
2024

2025
        const authHeader: string = req.headers.authorization || req.headers.Authorization || '';
4✔
2026
        const channelIdHeader: string = req.headers.channelid || req.headers.ChannelId || req.headers.ChannelID || '';
4✔
2027
        // Validate the received Upgrade request from the channel.
2028
        const claims = await JwtTokenValidation.validateAuthHeader(
4✔
2029
            authHeader,
2030
            this.credentialsProvider,
2031
            channelService,
2032
            channelIdHeader
2033
        );
2034

UNCOV
2035
        if (!claims.isAuthenticated) {
×
UNCOV
2036
            throw new AuthenticationError('Unauthorized Access. Request is not authorized', StatusCodes.UNAUTHORIZED);
×
2037
        }
2038
    }
2039

2040
    /**
2041
     * Connects the handler to a WebSocket server and begins listening for incoming requests.
2042
     *
2043
     * @param socket The socket to use when creating the server.
2044
     */
2045
    private async startWebSocket(socket: ISocket): Promise<void> {
2046
        this.streamingServer = new WebSocketServer(socket, this);
8✔
2047
        await this.streamingServer.start();
8✔
2048
    }
2049

2050
    private async readRequestBodyAsString(request: IReceiveRequest): Promise<Activity> {
2051
        const [activityStream, ...attachmentStreams] = request.streams;
14✔
2052

2053
        const activity = await activityStream.readAsJson<Activity>();
12✔
2054

2055
        activity.attachments = await Promise.all(
12✔
UNCOV
2056
            attachmentStreams.map(async (attachmentStream) => {
×
UNCOV
2057
                const contentType = attachmentStream.contentType;
×
2058

2059
                const content =
UNCOV
2060
                    contentType === 'application/json'
×
2061
                        ? await attachmentStream.readAsJson()
×
2062
                        : await attachmentStream.readAsString();
2063

2064
                return { contentType, content };
×
2065
            })
2066
        );
2067

2068
        return activity;
12✔
2069
    }
2070

2071
    private async handleVersionRequest(
2072
        request: IReceiveRequest,
2073
        response: StreamingResponse
2074
    ): Promise<StreamingResponse> {
2075
        if (request.verb.toLocaleUpperCase() === GET) {
8✔
2076
            response.statusCode = StatusCodes.OK;
6✔
2077

2078
            if (!this.credentials.appId) {
6!
2079
                response.setBody({ UserAgent: USER_AGENT });
6✔
2080
                return response;
6✔
2081
            }
2082

UNCOV
2083
            let token = '';
×
UNCOV
2084
            try {
×
UNCOV
2085
                token = await this.credentials.getToken();
×
2086
            } catch (err) {
2087
                /**
2088
                 * In reality a missing BotToken will cause the channel to close the connection,
2089
                 * but we still send the response and allow the channel to make that decision
2090
                 * instead of proactively disconnecting. This allows the channel to know why
2091
                 * the connection has been closed and make the choice not to make endless reconnection
2092
                 * attempts that will end up right back here.
2093
                 */
UNCOV
2094
                console.error(err.message);
×
2095
            }
UNCOV
2096
            response.setBody({ UserAgent: USER_AGENT, BotToken: token });
×
2097
        } else {
2098
            response.statusCode = StatusCodes.METHOD_NOT_ALLOWED;
2✔
2099
            response.setBody(
2✔
2100
                `Invalid verb received for path: ${request.path}. Only GET is accepted. Verb: ${request.verb}`
2101
            );
2102
        }
2103

2104
        return response;
2✔
2105
    }
2106

2107
    /**
2108
     * Determine if the serviceUrl was sent via an Http/Https connection or Streaming
2109
     * This can be determined by looking at the ServiceUrl property:
2110
     *   (1) All channels that send messages via http/https are not streaming
2111
     *   (2) Channels that send messages via streaming have a ServiceUrl that does not begin with http/https.
2112
     *
2113
     * @param serviceUrl the serviceUrl provided in the resquest.
2114
     * @returns True if the serviceUrl is a streaming url, otherwise false.
2115
     */
2116
    private static isStreamingServiceUrl(serviceUrl: string): boolean {
2117
        return serviceUrl && !serviceUrl.toLowerCase().startsWith('http');
166✔
2118
    }
2119
}
2120

2121
/**
2122
 * Handles incoming webhooks from the botframework
2123
 * @private
2124
 * @param req incoming web request
2125
 */
2126
function parseRequest(req: WebRequest): Promise<Activity> {
2127
    return new Promise((resolve: any, reject: any): void => {
42✔
2128
        if (req.body) {
42✔
2129
            try {
8✔
2130
                const activity = validateAndFixActivity(req.body);
8✔
2131
                resolve(activity);
8✔
2132
            } catch (err) {
UNCOV
2133
                reject(err);
×
2134
            }
2135
        } else {
2136
            let requestData = '';
34✔
2137
            req.on('data', (chunk: string): void => {
34✔
2138
                requestData += chunk;
34✔
2139
            });
2140
            req.on('end', (): void => {
34✔
2141
                try {
34✔
2142
                    req.body = JSON.parse(requestData);
34✔
2143
                    const activity = validateAndFixActivity(req.body);
34✔
2144
                    resolve(activity);
30✔
2145
                } catch (err) {
2146
                    reject(err);
4✔
2147
                }
2148
            });
2149
        }
2150
    });
2151
}
2152

2153
/**
2154
 * Creates an error message with status code to write to socket, then closes the connection.
2155
 *
2156
 * @param socket The raw socket connection between the bot (server) and channel/caller (client).
2157
 * @param err The error. If the error includes a status code, it will be included in the message written to the socket.
2158
 */
2159
function abortWebSocketUpgrade(socket: INodeSocket, err: any): void {
2160
    if (socket.writable) {
4!
2161
        const connectionHeader = "Connection: 'close'\r\n";
4✔
2162

2163
        let message = '';
4✔
2164
        AuthenticationError.isStatusCodeError(err)
4✔
2165
            ? (message = `HTTP/1.1 ${err.statusCode} ${StatusCodes[err.statusCode]}\r\n${
4!
2166
                  err.message
2167
              }\r\n${connectionHeader}\r\n`)
2168
            : (message = AuthenticationError.determineStatusCodeAndBuildMessage(err));
2169

2170
        socket.write(message);
4✔
2171
    }
2172
    socket.destroy();
4✔
2173
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc