@microsoft/teamsfx 1.1.2-alpha.7eddd6cf4.0 → 1.1.2-alpha.8d60b4f8e.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,6 +69,30 @@ exports.ErrorCode = void 0;
69
69
  * Channel is not supported error.
70
70
  */
71
71
  ErrorCode["ChannelNotSupported"] = "ChannelNotSupported";
72
+ /**
73
+ * Failed to retrieve sso token
74
+ */
75
+ ErrorCode["FailedToRetrieveSsoToken"] = "FailedToRetrieveSsoToken";
76
+ /**
77
+ * Failed to process sso handler
78
+ */
79
+ ErrorCode["FailedToProcessSsoHandler"] = "FailedToProcessSsoHandler";
80
+ /**
81
+ * Cannot find command
82
+ */
83
+ ErrorCode["CannotFindCommand"] = "CannotFindCommand";
84
+ /**
85
+ * Failed to run sso step
86
+ */
87
+ ErrorCode["FailedToRunSsoStep"] = "FailedToRunSsoStep";
88
+ /**
89
+ * Failed to run dedup step
90
+ */
91
+ ErrorCode["FailedToRunDedupStep"] = "FailedToRunDedupStep";
92
+ /**
93
+ * Sso activity handler is undefined
94
+ */
95
+ ErrorCode["SsoActivityHandlerIsUndefined"] = "SsoActivityHandlerIsUndefined";
72
96
  /**
73
97
  * Runtime is not supported error.
74
98
  */
@@ -124,6 +148,15 @@ ErrorMessage.NodejsRuntimeNotSupported = "{0} is not supported in Node.";
124
148
  ErrorMessage.FailToAcquireTokenOnBehalfOfUser = "Failed to acquire access token on behalf of user: {0}";
125
149
  // ChannelNotSupported Error
126
150
  ErrorMessage.OnlyMSTeamsChannelSupported = "{0} is only supported in MS Teams Channel";
151
+ ErrorMessage.FailedToProcessSsoHandler = "Failed to process sso handler: {0}";
152
+ // FailedToRetrieveSsoToken Error
153
+ ErrorMessage.FailedToRetrieveSsoToken = "Failed to retrieve sso token, user failed to finish the AAD consent flow.";
154
+ // CannotFindCommand Error
155
+ ErrorMessage.CannotFindCommand = "Cannot find command: {0}";
156
+ ErrorMessage.FailedToRunSsoStep = "Failed to run dialog to retrieve sso token: {0}";
157
+ ErrorMessage.FailedToRunDedupStep = "Failed to run dialog to remove duplicated messages: {0}";
158
+ // SsoActivityHandlerIsUndefined Error
159
+ ErrorMessage.SsoActivityHandlerIsNull = "Sso command can only be used or added when sso activity handler is not undefined";
127
160
  // IdentityTypeNotSupported Error
128
161
  ErrorMessage.IdentityTypeNotSupported = "{0} identity is not supported in {1}";
129
162
  // AuthorizationInfoError
@@ -134,6 +167,7 @@ ErrorMessage.EmptyParameter = "Parameter {0} is empty";
134
167
  ErrorMessage.DuplicateHttpsOptionProperty = "Axios HTTPS agent already defined value for property {0}";
135
168
  ErrorMessage.DuplicateApiKeyInHeader = "The request already defined api key in request header with name {0}.";
136
169
  ErrorMessage.DuplicateApiKeyInQueryParam = "The request already defined api key in query parameter with name {0}.";
170
+ ErrorMessage.OnlySupportInQueryActivity = "The handleMessageExtensionQueryWithToken only support in handleTeamsMessagingExtensionQuery with composeExtension/query type.";
137
171
  /**
138
172
  * Error class with code and message thrown by the SDK.
139
173
  */
@@ -2192,43 +2226,84 @@ class CardActionBot {
2192
2226
  * @internal
2193
2227
  */
2194
2228
  class CommandResponseMiddleware {
2195
- constructor(handlers) {
2229
+ constructor(handlers, ssoHandlers, activityHandler) {
2196
2230
  this.commandHandlers = [];
2197
- if (handlers && handlers.length > 0) {
2198
- this.commandHandlers.push(...handlers);
2231
+ this.ssoCommandHandlers = [];
2232
+ handlers = handlers !== null && handlers !== void 0 ? handlers : [];
2233
+ ssoHandlers = ssoHandlers !== null && ssoHandlers !== void 0 ? ssoHandlers : [];
2234
+ this.hasSsoCommand = ssoHandlers.length > 0;
2235
+ this.ssoActivityHandler = activityHandler;
2236
+ if (this.hasSsoCommand && !this.ssoActivityHandler) {
2237
+ internalLogger.error(ErrorMessage.SsoActivityHandlerIsNull);
2238
+ throw new ErrorWithCode(ErrorMessage.SsoActivityHandlerIsNull, exports.ErrorCode.SsoActivityHandlerIsUndefined);
2199
2239
  }
2240
+ this.commandHandlers.push(...handlers);
2241
+ for (const ssoHandler of ssoHandlers) {
2242
+ this.addSsoCommand(ssoHandler);
2243
+ }
2244
+ }
2245
+ addSsoCommand(ssoHandler) {
2246
+ var _a;
2247
+ (_a = this.ssoActivityHandler) === null || _a === void 0 ? void 0 : _a.addCommand((context, tokenResponse, message) => tslib.__awaiter(this, void 0, void 0, function* () {
2248
+ const matchResult = this.shouldTrigger(ssoHandler.triggerPatterns, message.text);
2249
+ message.matches = Array.isArray(matchResult) ? matchResult : void 0;
2250
+ const response = yield ssoHandler.handleCommandReceived(context, message, tokenResponse);
2251
+ yield this.processResponse(context, response);
2252
+ }), ssoHandler.triggerPatterns);
2253
+ this.ssoCommandHandlers.push(ssoHandler);
2254
+ this.commandHandlers.push(ssoHandler);
2255
+ this.hasSsoCommand = true;
2200
2256
  }
2201
2257
  onTurn(context, next) {
2258
+ var _a, _b;
2202
2259
  return tslib.__awaiter(this, void 0, void 0, function* () {
2203
2260
  if (context.activity.type === botbuilder.ActivityTypes.Message) {
2204
2261
  // Invoke corresponding command handler for the command response
2205
2262
  const commandText = this.getActivityText(context.activity);
2206
- const message = {
2207
- text: commandText,
2208
- };
2209
2263
  for (const handler of this.commandHandlers) {
2210
2264
  const matchResult = this.shouldTrigger(handler.triggerPatterns, commandText);
2211
2265
  // It is important to note that the command bot will stop processing handlers
2212
2266
  // when the first command handler is matched.
2213
2267
  if (!!matchResult) {
2214
- message.matches = Array.isArray(matchResult) ? matchResult : void 0;
2215
- const response = yield handler.handleCommandReceived(context, message);
2216
- if (typeof response === "string") {
2217
- yield context.sendActivity(response);
2268
+ if (this.isSsoExecutionHandler(handler)) {
2269
+ yield ((_a = this.ssoActivityHandler) === null || _a === void 0 ? void 0 : _a.run(context));
2218
2270
  }
2219
2271
  else {
2220
- const replyActivity = response;
2221
- if (replyActivity) {
2222
- yield context.sendActivity(replyActivity);
2223
- }
2272
+ const message = {
2273
+ text: commandText,
2274
+ };
2275
+ message.matches = Array.isArray(matchResult) ? matchResult : void 0;
2276
+ const response = yield handler.handleCommandReceived(context, message);
2277
+ yield this.processResponse(context, response);
2224
2278
  }
2225
2279
  break;
2226
2280
  }
2227
2281
  }
2228
2282
  }
2283
+ else {
2284
+ if (this.hasSsoCommand) {
2285
+ yield ((_b = this.ssoActivityHandler) === null || _b === void 0 ? void 0 : _b.run(context));
2286
+ }
2287
+ }
2229
2288
  yield next();
2230
2289
  });
2231
2290
  }
2291
+ processResponse(context, response) {
2292
+ return tslib.__awaiter(this, void 0, void 0, function* () {
2293
+ if (typeof response === "string") {
2294
+ yield context.sendActivity(response);
2295
+ }
2296
+ else {
2297
+ const replyActivity = response;
2298
+ if (replyActivity) {
2299
+ yield context.sendActivity(replyActivity);
2300
+ }
2301
+ }
2302
+ });
2303
+ }
2304
+ isSsoExecutionHandler(handler) {
2305
+ return this.ssoCommandHandlers.indexOf(handler) >= 0;
2306
+ }
2232
2307
  matchPattern(pattern, text) {
2233
2308
  if (text) {
2234
2309
  if (typeof pattern === "string") {
@@ -2278,14 +2353,15 @@ class CommandBot {
2278
2353
  * @param adapter The bound `BotFrameworkAdapter`.
2279
2354
  * @param options - initialize options
2280
2355
  */
2281
- constructor(adapter, options) {
2282
- this.middleware = new CommandResponseMiddleware(options === null || options === void 0 ? void 0 : options.commands);
2356
+ constructor(adapter, options, ssoCommandActivityHandler, ssoConfig) {
2357
+ this.ssoConfig = ssoConfig;
2358
+ this.middleware = new CommandResponseMiddleware(options === null || options === void 0 ? void 0 : options.commands, options === null || options === void 0 ? void 0 : options.ssoCommands, ssoCommandActivityHandler);
2283
2359
  this.adapter = adapter.use(this.middleware);
2284
2360
  }
2285
2361
  /**
2286
2362
  * Registers a command into the command bot.
2287
2363
  *
2288
- * @param command The command to registered.
2364
+ * @param command The command to register.
2289
2365
  */
2290
2366
  registerCommand(command) {
2291
2367
  if (command) {
@@ -2295,13 +2371,41 @@ class CommandBot {
2295
2371
  /**
2296
2372
  * Registers commands into the command bot.
2297
2373
  *
2298
- * @param commands The command to registered.
2374
+ * @param commands The commands to register.
2299
2375
  */
2300
2376
  registerCommands(commands) {
2301
2377
  if (commands) {
2302
2378
  this.middleware.commandHandlers.push(...commands);
2303
2379
  }
2304
2380
  }
2381
+ /**
2382
+ * Registers a sso command into the command bot.
2383
+ *
2384
+ * @param command The command to register.
2385
+ */
2386
+ registerSsoCommand(ssoCommand) {
2387
+ this.validateSsoActivityHandler();
2388
+ this.middleware.addSsoCommand(ssoCommand);
2389
+ }
2390
+ /**
2391
+ * Registers commands into the command bot.
2392
+ *
2393
+ * @param commands The commands to register.
2394
+ */
2395
+ registerSsoCommands(ssoCommands) {
2396
+ if (ssoCommands.length > 0) {
2397
+ this.validateSsoActivityHandler();
2398
+ for (const ssoCommand of ssoCommands) {
2399
+ this.middleware.addSsoCommand(ssoCommand);
2400
+ }
2401
+ }
2402
+ }
2403
+ validateSsoActivityHandler() {
2404
+ if (!this.middleware.ssoActivityHandler) {
2405
+ internalLogger.error(ErrorMessage.SsoActivityHandlerIsNull);
2406
+ throw new ErrorWithCode(ErrorMessage.SsoActivityHandlerIsNull, exports.ErrorCode.SsoActivityHandlerIsUndefined);
2407
+ }
2408
+ }
2305
2409
  }
2306
2410
 
2307
2411
  // Copyright (c) Microsoft Corporation.
@@ -2809,6 +2913,10 @@ class TeamsBotInstallation {
2809
2913
  */
2810
2914
  channels() {
2811
2915
  return tslib.__awaiter(this, void 0, void 0, function* () {
2916
+ const channels = [];
2917
+ if (this.type !== exports.NotificationTargetType.Channel) {
2918
+ return channels;
2919
+ }
2812
2920
  let teamsChannels = [];
2813
2921
  yield this.adapter.continueConversation(this.conversationReference, (context) => tslib.__awaiter(this, void 0, void 0, function* () {
2814
2922
  const teamId = getTeamsBotInstallationId(context);
@@ -2816,7 +2924,6 @@ class TeamsBotInstallation {
2816
2924
  teamsChannels = yield botbuilder.TeamsInfo.getTeamChannels(context, teamId);
2817
2925
  }
2818
2926
  }));
2819
- const channels = [];
2820
2927
  for (const channel of teamsChannels) {
2821
2928
  channels.push(new Channel(this, channel));
2822
2929
  }
@@ -2844,6 +2951,26 @@ class TeamsBotInstallation {
2844
2951
  return members;
2845
2952
  });
2846
2953
  }
2954
+ /**
2955
+ * Get team details from this bot installation
2956
+ *
2957
+ * @returns the team details if bot is installed into a team, otherwise returns undefined.
2958
+ */
2959
+ getTeamDetails() {
2960
+ return tslib.__awaiter(this, void 0, void 0, function* () {
2961
+ if (this.type !== exports.NotificationTargetType.Channel) {
2962
+ return undefined;
2963
+ }
2964
+ let teamDetails;
2965
+ yield this.adapter.continueConversation(this.conversationReference, (context) => tslib.__awaiter(this, void 0, void 0, function* () {
2966
+ const teamId = getTeamsBotInstallationId(context);
2967
+ if (teamId !== undefined) {
2968
+ teamDetails = yield botbuilder.TeamsInfo.getTeamDetails(context, teamId);
2969
+ }
2970
+ }));
2971
+ return teamDetails;
2972
+ });
2973
+ }
2847
2974
  }
2848
2975
  /**
2849
2976
  * Provide utilities to send notification to varies targets (e.g., member, group, channel).
@@ -2905,6 +3032,488 @@ class NotificationBot {
2905
3032
  return targets;
2906
3033
  });
2907
3034
  }
3035
+ /**
3036
+ * Returns the first {@link Member} where predicate is true, and undefined otherwise.
3037
+ *
3038
+ * @param predicate find calls predicate once for each member of the installation,
3039
+ * until it finds one where predicate returns true. If such a member is found, find
3040
+ * immediately returns that member. Otherwise, find returns undefined.
3041
+ * @param scope the scope to find members from the installations
3042
+ * (personal chat, group chat, Teams channel).
3043
+ * @returns the first {@link Member} where predicate is true, and undefined otherwise.
3044
+ */
3045
+ findMember(predicate, scope) {
3046
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3047
+ for (const target of yield this.installations()) {
3048
+ if (this.matchSearchScope(target, scope)) {
3049
+ for (const member of yield target.members()) {
3050
+ if (yield predicate(member)) {
3051
+ return member;
3052
+ }
3053
+ }
3054
+ }
3055
+ }
3056
+ return;
3057
+ });
3058
+ }
3059
+ /**
3060
+ * Returns the first {@link Channel} where predicate is true, and undefined otherwise.
3061
+ *
3062
+ * @param predicate find calls predicate once for each channel of the installation,
3063
+ * until it finds one where predicate returns true. If such a channel is found, find
3064
+ * immediately returns that channel. Otherwise, find returns undefined.
3065
+ * @returns the first {@link Channel} where predicate is true, and undefined otherwise.
3066
+ */
3067
+ findChannel(predicate) {
3068
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3069
+ for (const target of yield this.installations()) {
3070
+ if (target.type === exports.NotificationTargetType.Channel) {
3071
+ const teamDetails = yield target.getTeamDetails();
3072
+ for (const channel of yield target.channels()) {
3073
+ if (yield predicate(channel, teamDetails)) {
3074
+ return channel;
3075
+ }
3076
+ }
3077
+ }
3078
+ }
3079
+ return;
3080
+ });
3081
+ }
3082
+ /**
3083
+ * Returns all {@link Member} where predicate is true, and empty array otherwise.
3084
+ *
3085
+ * @param predicate find calls predicate for each member of the installation.
3086
+ * @param scope the scope to find members from the installations
3087
+ * (personal chat, group chat, Teams channel).
3088
+ * @returns an array of {@link Member} where predicate is true, and empty array otherwise.
3089
+ */
3090
+ findAllMembers(predicate, scope) {
3091
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3092
+ const members = [];
3093
+ for (const target of yield this.installations()) {
3094
+ if (this.matchSearchScope(target, scope)) {
3095
+ for (const member of yield target.members()) {
3096
+ if (yield predicate(member)) {
3097
+ members.push(member);
3098
+ }
3099
+ }
3100
+ }
3101
+ }
3102
+ return members;
3103
+ });
3104
+ }
3105
+ /**
3106
+ * Returns all {@link Channel} where predicate is true, and empty array otherwise.
3107
+ *
3108
+ * @param predicate find calls predicate for each channel of the installation.
3109
+ * @returns an array of {@link Channel} where predicate is true, and empty array otherwise.
3110
+ */
3111
+ findAllChannels(predicate) {
3112
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3113
+ const channels = [];
3114
+ for (const target of yield this.installations()) {
3115
+ if (target.type === exports.NotificationTargetType.Channel) {
3116
+ const teamDetails = yield target.getTeamDetails();
3117
+ for (const channel of yield target.channels()) {
3118
+ if (yield predicate(channel, teamDetails)) {
3119
+ channels.push(channel);
3120
+ }
3121
+ }
3122
+ }
3123
+ }
3124
+ return channels;
3125
+ });
3126
+ }
3127
+ matchSearchScope(target, scope) {
3128
+ scope = scope !== null && scope !== void 0 ? scope : exports.SearchScope.All;
3129
+ return ((target.type === exports.NotificationTargetType.Channel && (scope & exports.SearchScope.Channel) !== 0) ||
3130
+ (target.type === exports.NotificationTargetType.Group && (scope & exports.SearchScope.Group) !== 0) ||
3131
+ (target.type === exports.NotificationTargetType.Person && (scope & exports.SearchScope.Person) !== 0));
3132
+ }
3133
+ }
3134
+ /**
3135
+ * The search scope when calling {@link NotificationBot.findMember} and {@link NotificationBot.findAllMembers}.
3136
+ * The search scope is a flagged enum and it can be combined with `|`.
3137
+ * For example, to search from personal chat and group chat, use `SearchScope.Person | SearchScope.Group`.
3138
+ */
3139
+ exports.SearchScope = void 0;
3140
+ (function (SearchScope) {
3141
+ /**
3142
+ * Search members from the installations in personal chat only.
3143
+ */
3144
+ SearchScope[SearchScope["Person"] = 1] = "Person";
3145
+ /**
3146
+ * Search members from the installations in group chat only.
3147
+ */
3148
+ SearchScope[SearchScope["Group"] = 2] = "Group";
3149
+ /**
3150
+ * Search members from the installations in Teams channel only.
3151
+ */
3152
+ SearchScope[SearchScope["Channel"] = 4] = "Channel";
3153
+ /**
3154
+ * Search members from all installations including personal chat, group chat and Teams channel.
3155
+ */
3156
+ SearchScope[SearchScope["All"] = 7] = "All";
3157
+ })(exports.SearchScope || (exports.SearchScope = {}));
3158
+
3159
+ // Copyright (c) Microsoft Corporation.
3160
+ let DIALOG_NAME = "BotSsoExecutionDialog";
3161
+ let TEAMS_SSO_PROMPT_ID = "TeamsFxSsoPrompt";
3162
+ let COMMAND_ROUTE_DIALOG = "CommandRouteDialog";
3163
+ /**
3164
+ * Sso execution dialog, use to handle sso command
3165
+ */
3166
+ class BotSsoExecutionDialog extends botbuilderDialogs.ComponentDialog {
3167
+ /**
3168
+ * Creates a new instance of the BotSsoExecutionDialog.
3169
+ * @param dedupStorage Helper storage to remove duplicated messages
3170
+ * @param settings The list of scopes for which the token will have access
3171
+ * @param teamsfx {@link TeamsFx} instance for authentication
3172
+ */
3173
+ constructor(dedupStorage, ssoPromptSettings, teamsfx, dialogName) {
3174
+ super(dialogName !== null && dialogName !== void 0 ? dialogName : DIALOG_NAME);
3175
+ this.dedupStorageKeys = [];
3176
+ // Map to store the commandId and triggerPatterns, key: commandId, value: triggerPatterns
3177
+ this.commandMapping = new Map();
3178
+ if (dialogName) {
3179
+ DIALOG_NAME = dialogName;
3180
+ TEAMS_SSO_PROMPT_ID = dialogName + TEAMS_SSO_PROMPT_ID;
3181
+ COMMAND_ROUTE_DIALOG = dialogName + COMMAND_ROUTE_DIALOG;
3182
+ }
3183
+ this.initialDialogId = COMMAND_ROUTE_DIALOG;
3184
+ this.dedupStorage = dedupStorage;
3185
+ this.dedupStorageKeys = [];
3186
+ const ssoDialog = new TeamsBotSsoPrompt(teamsfx, TEAMS_SSO_PROMPT_ID, ssoPromptSettings);
3187
+ this.addDialog(ssoDialog);
3188
+ const commandRouteDialog = new botbuilderDialogs.WaterfallDialog(COMMAND_ROUTE_DIALOG, [
3189
+ this.commandRouteStep.bind(this),
3190
+ ]);
3191
+ this.addDialog(commandRouteDialog);
3192
+ }
3193
+ /**
3194
+ * Add TeamsFxBotSsoCommandHandler instance
3195
+ * @param handler {@link BotSsoExecutionDialogHandler} callback function
3196
+ * @param triggerPatterns The trigger pattern
3197
+ */
3198
+ addCommand(handler, triggerPatterns) {
3199
+ const commandId = this.getCommandHash(triggerPatterns);
3200
+ const dialog = new botbuilderDialogs.WaterfallDialog(commandId, [
3201
+ this.ssoStep.bind(this),
3202
+ this.dedupStep.bind(this),
3203
+ (stepContext) => tslib.__awaiter(this, void 0, void 0, function* () {
3204
+ const tokenResponse = stepContext.result.tokenResponse;
3205
+ const context = stepContext.context;
3206
+ const message = stepContext.result.message;
3207
+ try {
3208
+ if (tokenResponse) {
3209
+ yield handler(context, tokenResponse, message);
3210
+ }
3211
+ else {
3212
+ throw new Error(ErrorMessage.FailedToRetrieveSsoToken);
3213
+ }
3214
+ return yield stepContext.endDialog();
3215
+ }
3216
+ catch (error) {
3217
+ const errorMsg = formatString(ErrorMessage.FailedToProcessSsoHandler, error.message);
3218
+ internalLogger.error(errorMsg);
3219
+ return yield stepContext.endDialog(new ErrorWithCode(errorMsg, exports.ErrorCode.FailedToProcessSsoHandler));
3220
+ }
3221
+ }),
3222
+ ]);
3223
+ this.commandMapping.set(commandId, triggerPatterns);
3224
+ this.addDialog(dialog);
3225
+ }
3226
+ getCommandHash(patterns) {
3227
+ const expressions = Array.isArray(patterns) ? patterns : [patterns];
3228
+ const patternStr = expressions.join();
3229
+ const patternStrWithoutSpecialChar = patternStr.replace(/[^a-zA-Z0-9]/g, "");
3230
+ const hash = crypto.createHash("sha256").update(patternStr).digest("hex").toLowerCase();
3231
+ return patternStrWithoutSpecialChar + hash;
3232
+ }
3233
+ /**
3234
+ * The run method handles the incoming activity (in the form of a DialogContext) and passes it through the dialog system.
3235
+ *
3236
+ * @param context The context object for the current turn.
3237
+ * @param accessor The instance of StatePropertyAccessor for dialog system.
3238
+ */
3239
+ run(context, accessor) {
3240
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3241
+ const dialogSet = new botbuilderDialogs.DialogSet(accessor);
3242
+ dialogSet.add(this);
3243
+ const dialogContext = yield dialogSet.createContext(context);
3244
+ this.ensureMsTeamsChannel(dialogContext);
3245
+ const results = yield dialogContext.continueDialog();
3246
+ if (results && results.status === botbuilderDialogs.DialogTurnStatus.empty) {
3247
+ yield dialogContext.beginDialog(this.id);
3248
+ }
3249
+ else if (results &&
3250
+ results.status === botbuilderDialogs.DialogTurnStatus.complete &&
3251
+ results.result instanceof Error) {
3252
+ throw results.result;
3253
+ }
3254
+ });
3255
+ }
3256
+ getActivityText(activity) {
3257
+ let text = activity.text;
3258
+ const removedMentionText = botbuilder.TurnContext.removeRecipientMention(activity);
3259
+ if (removedMentionText) {
3260
+ text = removedMentionText
3261
+ .toLowerCase()
3262
+ .replace(/\n|\r\n/g, "")
3263
+ .trim();
3264
+ }
3265
+ return text;
3266
+ }
3267
+ commandRouteStep(stepContext) {
3268
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3269
+ const turnContext = stepContext.context;
3270
+ const text = this.getActivityText(turnContext.activity);
3271
+ const commandId = this.getMatchesCommandId(text);
3272
+ if (commandId) {
3273
+ return yield stepContext.beginDialog(commandId);
3274
+ }
3275
+ const errorMsg = formatString(ErrorMessage.CannotFindCommand, turnContext.activity.text);
3276
+ internalLogger.error(errorMsg);
3277
+ throw new ErrorWithCode(errorMsg, exports.ErrorCode.CannotFindCommand);
3278
+ });
3279
+ }
3280
+ ssoStep(stepContext) {
3281
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3282
+ try {
3283
+ const turnContext = stepContext.context;
3284
+ const text = this.getActivityText(turnContext.activity);
3285
+ const message = {
3286
+ text,
3287
+ };
3288
+ stepContext.options.commandMessage = message;
3289
+ return yield stepContext.beginDialog(TEAMS_SSO_PROMPT_ID);
3290
+ }
3291
+ catch (error) {
3292
+ const errorMsg = formatString(ErrorMessage.FailedToRunSsoStep, error.message);
3293
+ internalLogger.error(errorMsg);
3294
+ return yield stepContext.endDialog(new ErrorWithCode(errorMsg, exports.ErrorCode.FailedToRunSsoStep));
3295
+ }
3296
+ });
3297
+ }
3298
+ dedupStep(stepContext) {
3299
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3300
+ const tokenResponse = stepContext.result;
3301
+ if (!tokenResponse) {
3302
+ internalLogger.error(ErrorMessage.FailedToRetrieveSsoToken);
3303
+ return yield stepContext.endDialog(new ErrorWithCode(ErrorMessage.FailedToRetrieveSsoToken, exports.ErrorCode.FailedToRunSsoStep));
3304
+ }
3305
+ try {
3306
+ // Only dedup after ssoStep to make sure that all Teams client would receive the login request
3307
+ if (tokenResponse && (yield this.shouldDedup(stepContext.context))) {
3308
+ return botbuilderDialogs.Dialog.EndOfTurn;
3309
+ }
3310
+ return yield stepContext.next({
3311
+ tokenResponse,
3312
+ message: stepContext.options.commandMessage,
3313
+ });
3314
+ }
3315
+ catch (error) {
3316
+ const errorMsg = formatString(ErrorMessage.FailedToRunDedupStep, error.message);
3317
+ internalLogger.error(errorMsg);
3318
+ return yield stepContext.endDialog(new ErrorWithCode(errorMsg, exports.ErrorCode.FailedToRunDedupStep));
3319
+ }
3320
+ });
3321
+ }
3322
+ /**
3323
+ * Called when the component is ending.
3324
+ *
3325
+ * @param context Context for the current turn of conversation.
3326
+ */
3327
+ onEndDialog(context) {
3328
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3329
+ const conversationId = context.activity.conversation.id;
3330
+ const currentDedupKeys = this.dedupStorageKeys.filter((key) => key.indexOf(conversationId) > 0);
3331
+ yield this.dedupStorage.delete(currentDedupKeys);
3332
+ this.dedupStorageKeys = this.dedupStorageKeys.filter((key) => key.indexOf(conversationId) < 0);
3333
+ });
3334
+ }
3335
+ /**
3336
+ * If a user is signed into multiple Teams clients, the Bot might receive a "signin/tokenExchange" from each client.
3337
+ * Each token exchange request for a specific user login will have an identical activity.value.Id.
3338
+ * Only one of these token exchange requests should be processed by the bot. For a distributed bot in production,
3339
+ * this requires a distributed storage to ensure only one token exchange is processed.
3340
+ * @param context Context for the current turn of conversation.
3341
+ * @returns boolean value indicate whether the message should be removed
3342
+ */
3343
+ shouldDedup(context) {
3344
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3345
+ const storeItem = {
3346
+ eTag: context.activity.value.id,
3347
+ };
3348
+ const key = this.getStorageKey(context);
3349
+ const storeItems = { [key]: storeItem };
3350
+ try {
3351
+ yield this.dedupStorage.write(storeItems);
3352
+ this.dedupStorageKeys.push(key);
3353
+ }
3354
+ catch (err) {
3355
+ if (err instanceof Error && err.message.indexOf("eTag conflict")) {
3356
+ return true;
3357
+ }
3358
+ throw err;
3359
+ }
3360
+ return false;
3361
+ });
3362
+ }
3363
+ getStorageKey(context) {
3364
+ if (!context || !context.activity || !context.activity.conversation) {
3365
+ throw new Error("Invalid context, can not get storage key!");
3366
+ }
3367
+ const activity = context.activity;
3368
+ const channelId = activity.channelId;
3369
+ const conversationId = activity.conversation.id;
3370
+ if (activity.type !== botbuilder.ActivityTypes.Invoke || activity.name !== botbuilder.tokenExchangeOperationName) {
3371
+ throw new Error("TokenExchangeState can only be used with Invokes of signin/tokenExchange.");
3372
+ }
3373
+ const value = activity.value;
3374
+ if (!value || !value.id) {
3375
+ throw new Error("Invalid signin/tokenExchange. Missing activity.value.id.");
3376
+ }
3377
+ return `${channelId}/${conversationId}/${value.id}`;
3378
+ }
3379
+ matchPattern(pattern, text) {
3380
+ if (text) {
3381
+ if (typeof pattern === "string") {
3382
+ const regExp = new RegExp(pattern, "i");
3383
+ return regExp.test(text);
3384
+ }
3385
+ if (pattern instanceof RegExp) {
3386
+ const matches = text.match(pattern);
3387
+ return matches !== null && matches !== void 0 ? matches : false;
3388
+ }
3389
+ }
3390
+ return false;
3391
+ }
3392
+ isPatternMatched(patterns, text) {
3393
+ const expressions = Array.isArray(patterns) ? patterns : [patterns];
3394
+ for (const ex of expressions) {
3395
+ const matches = this.matchPattern(ex, text);
3396
+ return !!matches;
3397
+ }
3398
+ return false;
3399
+ }
3400
+ getMatchesCommandId(text) {
3401
+ for (const command of this.commandMapping) {
3402
+ const pattern = command[1];
3403
+ if (this.isPatternMatched(pattern, text)) {
3404
+ return command[0];
3405
+ }
3406
+ }
3407
+ return undefined;
3408
+ }
3409
+ /**
3410
+ * Ensure bot is running in MS Teams since TeamsBotSsoPrompt is only supported in MS Teams channel.
3411
+ * @param dc dialog context
3412
+ * @throws {@link ErrorCode|ChannelNotSupported} if bot channel is not MS Teams
3413
+ * @internal
3414
+ */
3415
+ ensureMsTeamsChannel(dc) {
3416
+ if (dc.context.activity.channelId != botbuilder.Channels.Msteams) {
3417
+ const errorMsg = formatString(ErrorMessage.OnlyMSTeamsChannelSupported, "SSO execution dialog");
3418
+ internalLogger.error(errorMsg);
3419
+ throw new ErrorWithCode(errorMsg, exports.ErrorCode.ChannelNotSupported);
3420
+ }
3421
+ }
3422
+ }
3423
+
3424
+ // Copyright (c) Microsoft Corporation.
3425
+ /**
3426
+ * Default SSO execution activity handler
3427
+ */
3428
+ class DefaultBotSsoExecutionActivityHandler extends botbuilder.TeamsActivityHandler {
3429
+ /**
3430
+ * Creates a new instance of the DefaultBotSsoExecutionActivityHandler.
3431
+ * @param ssoConfig configuration for SSO command bot
3432
+ *
3433
+ * @remarks
3434
+ * In the constructor, it uses BotSsoConfig parameter which from {@link ConversationBot} options to initialize {@link BotSsoExecutionDialog}.
3435
+ * It also need to register an event handler for the message event which trigger {@link BotSsoExecutionDialog} instance.
3436
+ */
3437
+ constructor(ssoConfig) {
3438
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3439
+ super();
3440
+ const memoryStorage = new botbuilder.MemoryStorage();
3441
+ const userState = (_b = (_a = ssoConfig.dialog) === null || _a === void 0 ? void 0 : _a.userState) !== null && _b !== void 0 ? _b : new botbuilder.UserState(memoryStorage);
3442
+ const conversationState = (_d = (_c = ssoConfig.dialog) === null || _c === void 0 ? void 0 : _c.conversationState) !== null && _d !== void 0 ? _d : new botbuilder.ConversationState(memoryStorage);
3443
+ const dedupStorage = (_f = (_e = ssoConfig.dialog) === null || _e === void 0 ? void 0 : _e.dedupStorage) !== null && _f !== void 0 ? _f : memoryStorage;
3444
+ const _l = ssoConfig.aad, { scopes } = _l, customConfig = tslib.__rest(_l, ["scopes"]);
3445
+ const settings = {
3446
+ scopes: scopes,
3447
+ timeout: (_h = (_g = ssoConfig.dialog) === null || _g === void 0 ? void 0 : _g.ssoPromptConfig) === null || _h === void 0 ? void 0 : _h.timeout,
3448
+ endOnInvalidMessage: (_k = (_j = ssoConfig.dialog) === null || _j === void 0 ? void 0 : _j.ssoPromptConfig) === null || _k === void 0 ? void 0 : _k.endOnInvalidMessage,
3449
+ };
3450
+ const teamsfx = new TeamsFx(exports.IdentityType.User, Object.assign({}, customConfig));
3451
+ this.ssoExecutionDialog = new BotSsoExecutionDialog(dedupStorage, settings, teamsfx);
3452
+ this.conversationState = conversationState;
3453
+ this.dialogState = conversationState.createProperty("DialogState");
3454
+ this.userState = userState;
3455
+ this.onMessage((context, next) => tslib.__awaiter(this, void 0, void 0, function* () {
3456
+ yield this.ssoExecutionDialog.run(context, this.dialogState);
3457
+ yield next();
3458
+ }));
3459
+ }
3460
+ /**
3461
+ * Add TeamsFxBotSsoCommandHandler instance to SSO execution dialog
3462
+ * @param handler {@link BotSsoExecutionDialogHandler} callback function
3463
+ * @param triggerPatterns The trigger pattern
3464
+ *
3465
+ * @remarks
3466
+ * This function is used to add SSO command to {@link BotSsoExecutionDialog} instance.
3467
+ */
3468
+ addCommand(handler, triggerPatterns) {
3469
+ this.ssoExecutionDialog.addCommand(handler, triggerPatterns);
3470
+ }
3471
+ /**
3472
+ * Called to initiate the event emission process.
3473
+ * @param context The context object for the current turn.
3474
+ */
3475
+ run(context) {
3476
+ const _super = Object.create(null, {
3477
+ run: { get: () => super.run }
3478
+ });
3479
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3480
+ try {
3481
+ yield _super.run.call(this, context);
3482
+ }
3483
+ finally {
3484
+ yield this.conversationState.saveChanges(context, false);
3485
+ yield this.userState.saveChanges(context, false);
3486
+ }
3487
+ });
3488
+ }
3489
+ /**
3490
+ * Receives invoke activities with Activity name of 'signin/verifyState'.
3491
+ * @param context A context object for this turn.
3492
+ * @param query Signin state (part of signin action auth flow) verification invoke query.
3493
+ * @returns A promise that represents the work queued.
3494
+ *
3495
+ * @remarks
3496
+ * It should trigger {@link BotSsoExecutionDialog} instance to handle signin process
3497
+ */
3498
+ handleTeamsSigninVerifyState(context, query) {
3499
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3500
+ yield this.ssoExecutionDialog.run(context, this.dialogState);
3501
+ });
3502
+ }
3503
+ /**
3504
+ * Receives invoke activities with Activity name of 'signin/tokenExchange'
3505
+ * @param context A context object for this turn.
3506
+ * @param query Signin state (part of signin action auth flow) verification invoke query
3507
+ * @returns A promise that represents the work queued.
3508
+ *
3509
+ * @remark
3510
+ * It should trigger {@link BotSsoExecutionDialog} instance to handle signin process
3511
+ */
3512
+ handleTeamsSigninTokenExchange(context, query) {
3513
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3514
+ yield this.ssoExecutionDialog.run(context, this.dialogState);
3515
+ });
3516
+ }
2908
3517
  }
2909
3518
 
2910
3519
  // Copyright (c) Microsoft Corporation.
@@ -2969,20 +3578,30 @@ class ConversationBot {
2969
3578
  * @param options - initialize options
2970
3579
  */
2971
3580
  constructor(options) {
2972
- var _a, _b, _c;
3581
+ var _a, _b, _c, _d;
2973
3582
  if (options.adapter) {
2974
3583
  this.adapter = options.adapter;
2975
3584
  }
2976
3585
  else {
2977
3586
  this.adapter = this.createDefaultAdapter(options.adapterConfig);
2978
3587
  }
2979
- if ((_a = options.command) === null || _a === void 0 ? void 0 : _a.enabled) {
2980
- this.command = new CommandBot(this.adapter, options.command);
3588
+ let ssoCommandActivityHandler;
3589
+ if (options === null || options === void 0 ? void 0 : options.ssoConfig) {
3590
+ if ((_a = options.ssoConfig.dialog) === null || _a === void 0 ? void 0 : _a.CustomBotSsoExecutionActivityHandler) {
3591
+ ssoCommandActivityHandler =
3592
+ new options.ssoConfig.dialog.CustomBotSsoExecutionActivityHandler(options.ssoConfig);
3593
+ }
3594
+ else {
3595
+ ssoCommandActivityHandler = new DefaultBotSsoExecutionActivityHandler(options.ssoConfig);
3596
+ }
3597
+ }
3598
+ if ((_b = options.command) === null || _b === void 0 ? void 0 : _b.enabled) {
3599
+ this.command = new CommandBot(this.adapter, options.command, ssoCommandActivityHandler, options.ssoConfig);
2981
3600
  }
2982
- if ((_b = options.notification) === null || _b === void 0 ? void 0 : _b.enabled) {
3601
+ if ((_c = options.notification) === null || _c === void 0 ? void 0 : _c.enabled) {
2983
3602
  this.notification = new NotificationBot(this.adapter, options.notification);
2984
3603
  }
2985
- if ((_c = options.cardAction) === null || _c === void 0 ? void 0 : _c.enabled) {
3604
+ if ((_d = options.cardAction) === null || _d === void 0 ? void 0 : _d.enabled) {
2986
3605
  this.cardAction = new CardActionBot(this.adapter, options.cardAction);
2987
3606
  }
2988
3607
  }
@@ -3173,15 +3792,121 @@ class MessageBuilder {
3173
3792
  }
3174
3793
  }
3175
3794
 
3795
+ // Copyright (c) Microsoft Corporation.
3796
+ /**
3797
+ * Retrieve the OAuth Sign in Link to use in the MessagingExtensionResult Suggested Actions.
3798
+ * This method only work on MessageExtension with Query now.
3799
+ *
3800
+ * @param {TeamsFx} teamsfx - Used to provide configuration and auth.
3801
+ * @param {string | string[]} scopes - The list of scopes for which the token will have access.
3802
+ *
3803
+ * @returns SignIn link CardAction with 200 status code.
3804
+ */
3805
+ function getSignInResponseForMessageExtension(teamsfx, scopes) {
3806
+ const scopesArray = getScopesArray(scopes);
3807
+ const signInLink = `${teamsfx.getConfig("initiateLoginEndpoint")}?scope=${encodeURI(scopesArray.join(" "))}&clientId=${teamsfx.getConfig("clientId")}&tenantId=${teamsfx.getConfig("tenantId")}`;
3808
+ return {
3809
+ composeExtension: {
3810
+ type: "silentAuth",
3811
+ suggestedActions: {
3812
+ actions: [
3813
+ {
3814
+ type: "openUrl",
3815
+ value: signInLink,
3816
+ title: "Message Extension OAuth",
3817
+ },
3818
+ ],
3819
+ },
3820
+ },
3821
+ };
3822
+ }
3823
+ /**
3824
+ * execution in message extension with SSO token.
3825
+ *
3826
+ * @param {TurnContext} context - The context object for the current turn.
3827
+ * @param {AuthenticationConfiguration} config - User custom the message extension authentication configuration.
3828
+ * @param {string[]} scopes - The list of scopes for which the token will have access.
3829
+ * @param {function} logic - Business logic when executing the query in message extension with SSO or access token.
3830
+ *
3831
+ * @throws {@link ErrorCode|InternalError} when failed to get access token with unknown error.
3832
+ * @throws {@link ErrorCode|TokenExpiredError} when SSO token has already expired.
3833
+ * @throws {@link ErrorCode|ServiceError} when failed to get access token from simple auth server.
3834
+ * @throws {@link ErrorCode|InvalidParameter} when scopes is not a valid string or string array.
3835
+ * @throws {@link ErrorCode|RuntimeNotSupported} when runtime is nodeJS.
3836
+ *
3837
+ * @returns A MessageExtension Response for the activity. If the logic not return any, return void instead.
3838
+ */
3839
+ function executionWithToken(context, config, scopes, logic) {
3840
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3841
+ const valueObj = context.activity.value;
3842
+ if (!valueObj.authentication || !valueObj.authentication.token) {
3843
+ internalLogger.verbose("No AccessToken in request, return silentAuth for AccessToken");
3844
+ return getSignInResponseForMessageExtension(new TeamsFx(exports.IdentityType.User, config), scopes);
3845
+ }
3846
+ try {
3847
+ const teamsfx = new TeamsFx(exports.IdentityType.User, config).setSsoToken(valueObj.authentication.token);
3848
+ const token = yield teamsfx.getCredential().getToken(scopes);
3849
+ const ssoTokenExpiration = parseJwt(valueObj.authentication.token).exp;
3850
+ const tokenRes = {
3851
+ ssoToken: valueObj.authentication.token,
3852
+ ssoTokenExpiration: new Date(ssoTokenExpiration * 1000).toISOString(),
3853
+ token: token.token,
3854
+ expiration: token.expiresOnTimestamp.toString(),
3855
+ connectionName: "",
3856
+ };
3857
+ if (logic) {
3858
+ return yield logic(tokenRes);
3859
+ }
3860
+ }
3861
+ catch (err) {
3862
+ if (err instanceof ErrorWithCode && err.code === exports.ErrorCode.UiRequiredError) {
3863
+ internalLogger.verbose("User not consent yet, return 412 to user consent first.");
3864
+ const response = { status: 412 };
3865
+ yield context.sendActivity({ value: response, type: botbuilder.ActivityTypes.InvokeResponse });
3866
+ return;
3867
+ }
3868
+ throw err;
3869
+ }
3870
+ });
3871
+ }
3872
+ /**
3873
+ * Users execute query in message extension with SSO or access token.
3874
+ *
3875
+ * @param {TurnContext} context - The context object for the current turn.
3876
+ * @param {AuthenticationConfiguration} config - User custom the message extension authentication configuration.
3877
+ * @param {string| string[]} scopes - The list of scopes for which the token will have access.
3878
+ * @param {function} logic - Business logic when executing the query in message extension with SSO or access token.
3879
+ *
3880
+ * @throws {@link ErrorCode|InternalError} when User invoke not response to message extension query.
3881
+ * @throws {@link ErrorCode|InternalError} when failed to get access token with unknown error.
3882
+ * @throws {@link ErrorCode|TokenExpiredError} when SSO token has already expired.
3883
+ * @throws {@link ErrorCode|ServiceError} when failed to get access token from simple auth server.
3884
+ * @throws {@link ErrorCode|InvalidParameter} when scopes is not a valid string or string array.
3885
+ * @throws {@link ErrorCode|RuntimeNotSupported} when runtime is nodeJS.
3886
+ *
3887
+ * @returns A MessageExtension Response for the activity. If the logic not return any, return void instead.
3888
+ */
3889
+ function handleMessageExtensionQueryWithToken(context, config, scopes, logic) {
3890
+ return tslib.__awaiter(this, void 0, void 0, function* () {
3891
+ if (context.activity.name != "composeExtension/query") {
3892
+ internalLogger.error(ErrorMessage.OnlySupportInQueryActivity);
3893
+ throw new ErrorWithCode(formatString(ErrorMessage.OnlySupportInQueryActivity), exports.ErrorCode.FailedOperation);
3894
+ }
3895
+ return yield executionWithToken(context, config !== null && config !== void 0 ? config : {}, scopes, logic);
3896
+ });
3897
+ }
3898
+
3176
3899
  exports.ApiKeyProvider = ApiKeyProvider;
3177
3900
  exports.AppCredential = AppCredential;
3178
3901
  exports.BasicAuthProvider = BasicAuthProvider;
3179
3902
  exports.BearerTokenAuthProvider = BearerTokenAuthProvider;
3903
+ exports.BotSsoExecutionDialog = BotSsoExecutionDialog;
3180
3904
  exports.CardActionBot = CardActionBot;
3181
3905
  exports.CertificateAuthProvider = CertificateAuthProvider;
3182
3906
  exports.Channel = Channel;
3183
3907
  exports.CommandBot = CommandBot;
3184
3908
  exports.ConversationBot = ConversationBot;
3909
+ exports.DefaultBotSsoExecutionActivityHandler = DefaultBotSsoExecutionActivityHandler;
3185
3910
  exports.ErrorWithCode = ErrorWithCode;
3186
3911
  exports.InvokeResponseFactory = InvokeResponseFactory;
3187
3912
  exports.Member = Member;
@@ -3199,6 +3924,7 @@ exports.createPemCertOption = createPemCertOption;
3199
3924
  exports.createPfxCertOption = createPfxCertOption;
3200
3925
  exports.getLogLevel = getLogLevel;
3201
3926
  exports.getTediousConnectionConfig = getTediousConnectionConfig;
3927
+ exports.handleMessageExtensionQueryWithToken = handleMessageExtensionQueryWithToken;
3202
3928
  exports.sendAdaptiveCard = sendAdaptiveCard;
3203
3929
  exports.sendMessage = sendMessage;
3204
3930
  exports.setLogFunction = setLogFunction;