1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | import axios from 'axios';
|
10 | import { Activity, BotFrameworkClient, ChannelAccount, InvokeResponse, RoleTypes } from 'botbuilder-core';
|
11 |
|
12 | import {
|
13 | AppCredentials,
|
14 | AuthenticationConstants,
|
15 | ConversationConstants,
|
16 | GovernmentConstants,
|
17 | ICredentialProvider,
|
18 | JwtTokenValidation,
|
19 | MicrosoftAppCredentials,
|
20 | } from 'botframework-connector';
|
21 |
|
22 | import { USER_AGENT } from './botFrameworkAdapter';
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | export class BotFrameworkHttpClient implements BotFrameworkClient {
|
28 | protected readonly channelService: string;
|
29 |
|
30 | |
31 |
|
32 |
|
33 |
|
34 | private static readonly appCredentialMapCache: Map<string, AppCredentials> = new Map<string, AppCredentials>();
|
35 | private readonly credentialProvider: ICredentialProvider;
|
36 |
|
37 | |
38 |
|
39 |
|
40 |
|
41 |
|
42 | public constructor(credentialProvider: ICredentialProvider, channelService?: string) {
|
43 | if (!credentialProvider) {
|
44 | throw new Error('BotFrameworkHttpClient(): missing credentialProvider');
|
45 | }
|
46 |
|
47 | this.credentialProvider = credentialProvider;
|
48 | this.channelService = channelService || process.env[AuthenticationConstants.ChannelService];
|
49 | }
|
50 |
|
51 | |
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | public async postActivity<T = any>(
|
64 | fromBotId: string,
|
65 | toBotId: string,
|
66 | toUrl: string,
|
67 | serviceUrl: string,
|
68 | conversationId: string,
|
69 | activity: Activity
|
70 | ): Promise<InvokeResponse<T>> {
|
71 | const appCredentials = await this.getAppCredentials(fromBotId, toBotId);
|
72 | if (!appCredentials) {
|
73 | throw new Error(
|
74 | 'BotFrameworkHttpClient.postActivity(): Unable to get appCredentials to connect to the skill'
|
75 | );
|
76 | }
|
77 |
|
78 | if (!activity) {
|
79 | throw new Error('BotFrameworkHttpClient.postActivity(): missing activity');
|
80 | }
|
81 |
|
82 | if (activity.conversation === undefined) {
|
83 | throw new Error('BotFrameworkHttpClient.postActivity(): Activity must have a ConversationReference');
|
84 | }
|
85 |
|
86 |
|
87 | const token = appCredentials.appId ? await appCredentials.getToken() : null;
|
88 |
|
89 |
|
90 |
|
91 | const originalConversationId = activity.conversation.id;
|
92 | const originalServiceUrl = activity.serviceUrl;
|
93 | const originalRelatesTo = activity.relatesTo;
|
94 | const originalRecipient = activity.recipient;
|
95 |
|
96 | try {
|
97 | activity.relatesTo = {
|
98 | serviceUrl: activity.serviceUrl,
|
99 | activityId: activity.id,
|
100 | channelId: activity.channelId,
|
101 | conversation: {
|
102 | id: activity.conversation.id,
|
103 | name: activity.conversation.name,
|
104 | conversationType: activity.conversation.conversationType,
|
105 | aadObjectId: activity.conversation.aadObjectId,
|
106 | isGroup: activity.conversation.isGroup,
|
107 | properties: activity.conversation.properties,
|
108 | role: activity.conversation.role,
|
109 | tenantId: activity.conversation.tenantId,
|
110 | },
|
111 | bot: null,
|
112 | };
|
113 | activity.conversation.id = conversationId;
|
114 | activity.serviceUrl = serviceUrl;
|
115 |
|
116 |
|
117 | if (!activity.recipient) {
|
118 | activity.recipient = {} as ChannelAccount;
|
119 | }
|
120 | activity.recipient.role = RoleTypes.Skill;
|
121 |
|
122 | const config: { headers: Record<string, string>; validateStatus: () => boolean } = {
|
123 | headers: {
|
124 | Accept: 'application/json',
|
125 | [ConversationConstants.ConversationIdHttpHeaderName]: conversationId,
|
126 | 'Content-Type': 'application/json',
|
127 | 'User-Agent': USER_AGENT,
|
128 | },
|
129 | validateStatus: (): boolean => true,
|
130 | };
|
131 |
|
132 | if (token) {
|
133 | config.headers.Authorization = `Bearer ${token}`;
|
134 | }
|
135 |
|
136 | const response = await axios.post<T>(toUrl, activity, config);
|
137 | return { status: response.status, body: response.data };
|
138 | } finally {
|
139 |
|
140 | activity.conversation.id = originalConversationId;
|
141 | activity.serviceUrl = originalServiceUrl;
|
142 | activity.relatesTo = originalRelatesTo;
|
143 | activity.recipient = originalRecipient;
|
144 | }
|
145 | }
|
146 |
|
147 | |
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | protected async buildCredentials(appId: string, oAuthScope?: string): Promise<AppCredentials> {
|
155 | const appPassword = await this.credentialProvider.getAppPassword(appId);
|
156 | if (JwtTokenValidation.isGovernment(this.channelService)) {
|
157 | const appCredentials = new MicrosoftAppCredentials(appId, appPassword, undefined, oAuthScope);
|
158 | appCredentials.oAuthEndpoint = GovernmentConstants.ToChannelFromBotLoginUrl;
|
159 | return appCredentials;
|
160 | } else {
|
161 | return new MicrosoftAppCredentials(appId, appPassword, undefined, oAuthScope);
|
162 | }
|
163 | }
|
164 |
|
165 | |
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | private async getAppCredentials(appId: string, oAuthScope?: string): Promise<AppCredentials> {
|
173 | if (!appId) {
|
174 | return new MicrosoftAppCredentials('', '');
|
175 | }
|
176 |
|
177 | const cacheKey = `${appId}${oAuthScope}`;
|
178 | let appCredentials = BotFrameworkHttpClient.appCredentialMapCache.get(cacheKey);
|
179 | if (appCredentials) {
|
180 | return appCredentials;
|
181 | }
|
182 |
|
183 |
|
184 | appCredentials = await this.buildCredentials(appId, oAuthScope);
|
185 |
|
186 |
|
187 | BotFrameworkHttpClient.appCredentialMapCache.set(cacheKey, appCredentials);
|
188 | return appCredentials;
|
189 | }
|
190 | }
|