fix: route Teams sign-in invokes to OAuth dialog + use UserTokenClient for sign-out
All checks were successful
Build and Deploy Teams Planner Bot / build-and-run (push) Successful in 30s
All checks were successful
Build and Deploy Teams Planner Bot / build-and-run (push) Successful in 30s
This commit is contained in:
parent
fd504738eb
commit
e42b3d7c43
BIN
appPackage/color.png
Normal file
BIN
appPackage/color.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
|
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
|
||||||
"manifestVersion": "1.16",
|
"manifestVersion": "1.16",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"id": "REPLACE_WITH_YOUR_BOT_APP_ID",
|
"id": "1d0c2212-0bae-46af-babd-6c5223d2ee4c",
|
||||||
"packageName": "com.example.teamsplannerbot",
|
"packageName": "com.example.teamsplannerbot",
|
||||||
"developer": {
|
"developer": {
|
||||||
"name": "Internal",
|
"name": "Internal",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"accentColor": "#4F6BED",
|
"accentColor": "#4F6BED",
|
||||||
"bots": [
|
"bots": [
|
||||||
{
|
{
|
||||||
"botId": "REPLACE_WITH_YOUR_BOT_APP_ID",
|
"botId": "1d0c2212-0bae-46af-babd-6c5223d2ee4c",
|
||||||
"scopes": ["personal", "team", "groupchat"],
|
"scopes": ["personal", "team", "groupchat"],
|
||||||
"supportsFiles": false,
|
"supportsFiles": false,
|
||||||
"isNotificationOnly": false,
|
"isNotificationOnly": false,
|
||||||
@ -42,7 +42,7 @@
|
|||||||
"permissions": ["identity", "messageTeamMembers"],
|
"permissions": ["identity", "messageTeamMembers"],
|
||||||
"validDomains": ["token.botframework.com"],
|
"validDomains": ["token.botframework.com"],
|
||||||
"webApplicationInfo": {
|
"webApplicationInfo": {
|
||||||
"id": "REPLACE_WITH_YOUR_BOT_APP_ID",
|
"id": "1d0c2212-0bae-46af-babd-6c5223d2ee4c",
|
||||||
"resource": "https://graph.microsoft.com/"
|
"resource": "https://graph.microsoft.com/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
appPackage/outline.png
Normal file
BIN
appPackage/outline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
@ -1,17 +1,34 @@
|
|||||||
import {
|
import {
|
||||||
ActivityHandler,
|
|
||||||
CardFactory,
|
CardFactory,
|
||||||
|
CloudAdapter,
|
||||||
ConversationState,
|
ConversationState,
|
||||||
StatePropertyAccessor,
|
StatePropertyAccessor,
|
||||||
|
TeamsActivityHandler,
|
||||||
TurnContext,
|
TurnContext,
|
||||||
UserState,
|
UserState,
|
||||||
} from "botbuilder";
|
} from "botbuilder";
|
||||||
|
import { UserTokenClient } from "botframework-connector";
|
||||||
import { OAuthPrompt, DialogSet, DialogTurnStatus, WaterfallDialog } from "botbuilder-dialogs";
|
import { OAuthPrompt, DialogSet, DialogTurnStatus, WaterfallDialog } from "botbuilder-dialogs";
|
||||||
import { createGraphClient } from "../graph/graphClientFactory";
|
import { createGraphClient } from "../graph/graphClientFactory";
|
||||||
import { PlannerClient } from "../graph/plannerClient";
|
import { PlannerClient } from "../graph/plannerClient";
|
||||||
import { LlmClassifier } from "../llm/types";
|
import { LlmClassifier } from "../llm/types";
|
||||||
import { buildResultCard } from "../cards/confirmationCard";
|
import { buildResultCard } from "../cards/confirmationCard";
|
||||||
|
|
||||||
|
async function signOutUser(context: TurnContext, connectionName: string): Promise<void> {
|
||||||
|
const adapter = context.adapter as CloudAdapter;
|
||||||
|
const userTokenClient = context.turnState.get<UserTokenClient>(
|
||||||
|
(adapter as unknown as { UserTokenClientKey: symbol }).UserTokenClientKey,
|
||||||
|
);
|
||||||
|
if (!userTokenClient) {
|
||||||
|
throw new Error("UserTokenClient not available on this adapter");
|
||||||
|
}
|
||||||
|
await userTokenClient.signOutUser(
|
||||||
|
context.activity.from.id,
|
||||||
|
connectionName,
|
||||||
|
context.activity.channelId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const OAUTH_PROMPT = "graphOAuthPrompt";
|
const OAUTH_PROMPT = "graphOAuthPrompt";
|
||||||
const MAIN_DIALOG = "mainDialog";
|
const MAIN_DIALOG = "mainDialog";
|
||||||
|
|
||||||
@ -22,12 +39,13 @@ export interface PlannerBotDeps {
|
|||||||
connectionName: string; // Azure Bot OAuth connection name
|
connectionName: string; // Azure Bot OAuth connection name
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PlannerBot extends ActivityHandler {
|
export class PlannerBot extends TeamsActivityHandler {
|
||||||
private readonly conversationState: ConversationState;
|
private readonly conversationState: ConversationState;
|
||||||
private readonly userState: UserState;
|
private readonly userState: UserState;
|
||||||
private readonly classifier: LlmClassifier;
|
private readonly classifier: LlmClassifier;
|
||||||
private readonly dialogs: DialogSet;
|
private readonly dialogs: DialogSet;
|
||||||
private readonly dialogStateAccessor: StatePropertyAccessor;
|
private readonly dialogStateAccessor: StatePropertyAccessor;
|
||||||
|
private readonly connectionName: string;
|
||||||
|
|
||||||
constructor(deps: PlannerBotDeps) {
|
constructor(deps: PlannerBotDeps) {
|
||||||
super();
|
super();
|
||||||
@ -35,6 +53,7 @@ export class PlannerBot extends ActivityHandler {
|
|||||||
this.userState = deps.userState;
|
this.userState = deps.userState;
|
||||||
this.classifier = deps.classifier;
|
this.classifier = deps.classifier;
|
||||||
|
|
||||||
|
this.connectionName = deps.connectionName;
|
||||||
this.dialogStateAccessor = this.conversationState.createProperty("DialogState");
|
this.dialogStateAccessor = this.conversationState.createProperty("DialogState");
|
||||||
this.dialogs = new DialogSet(this.dialogStateAccessor);
|
this.dialogs = new DialogSet(this.dialogStateAccessor);
|
||||||
|
|
||||||
@ -67,10 +86,7 @@ export class PlannerBot extends ActivityHandler {
|
|||||||
const text = (context.activity.text ?? "").trim();
|
const text = (context.activity.text ?? "").trim();
|
||||||
|
|
||||||
if (text.toLowerCase() === "logout") {
|
if (text.toLowerCase() === "logout") {
|
||||||
const adapter = context.adapter as unknown as {
|
await signOutUser(context, this.connectionName);
|
||||||
signOutUser: (ctx: TurnContext, connName: string) => Promise<void>;
|
|
||||||
};
|
|
||||||
await adapter.signOutUser(context, deps.connectionName);
|
|
||||||
await context.sendActivity("로그아웃 완료.");
|
await context.sendActivity("로그아웃 완료.");
|
||||||
await next();
|
await next();
|
||||||
return;
|
return;
|
||||||
@ -89,6 +105,15 @@ export class PlannerBot extends ActivityHandler {
|
|||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Teams sends an invoke activity ("signin/verifyState" or "signin/tokenExchange")
|
||||||
|
// after the user completes the OAuth flow. We must route it back into the
|
||||||
|
// active dialog so OAuthPrompt can finish.
|
||||||
|
this.onTokenResponseEvent(async (context, next) => {
|
||||||
|
const dialogContext = await this.dialogs.createContext(context);
|
||||||
|
await dialogContext.continueDialog();
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
this.onMembersAdded(async (context, next) => {
|
this.onMembersAdded(async (context, next) => {
|
||||||
const greeting =
|
const greeting =
|
||||||
"안녕하세요! 작업 진행 상황을 평소 말로 알려주시면 Planner에 자동으로 정리해 드릴게요.\n" +
|
"안녕하세요! 작업 진행 상황을 평소 말로 알려주시면 Planner에 자동으로 정리해 드릴게요.\n" +
|
||||||
@ -108,6 +133,16 @@ export class PlannerBot extends ActivityHandler {
|
|||||||
await this.userState.saveChanges(context, false);
|
await this.userState.saveChanges(context, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async handleTeamsSigninVerifyState(context: TurnContext): Promise<void> {
|
||||||
|
const dialogContext = await this.dialogs.createContext(context);
|
||||||
|
await dialogContext.continueDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async handleTeamsSigninTokenExchange(context: TurnContext): Promise<void> {
|
||||||
|
const dialogContext = await this.dialogs.createContext(context);
|
||||||
|
await dialogContext.continueDialog();
|
||||||
|
}
|
||||||
|
|
||||||
private async handleUtterance(
|
private async handleUtterance(
|
||||||
context: TurnContext,
|
context: TurnContext,
|
||||||
token: string,
|
token: string,
|
||||||
|
|||||||
BIN
teams-planner-bot.zip
Normal file
BIN
teams-planner-bot.zip
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user