Skip to content

Commit 46744f7

Browse files
fix: refactor function handler for follow up auto ack behavior (#2424)
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
1 parent 657416f commit 46744f7

11 files changed

Lines changed: 508 additions & 402 deletions

File tree

src/App.ts

Lines changed: 56 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import axios, { type AxiosInstance, type AxiosResponse } from 'axios';
77
import type { Assistant } from './Assistant';
88
import {
99
CustomFunction,
10-
type CustomFunctionMiddleware,
1110
type FunctionCompleteFn,
1211
type FunctionFailFn,
12+
type SlackCustomFunctionMiddlewareArgs,
13+
createFunctionComplete,
14+
createFunctionFail,
1315
} from './CustomFunction';
1416
import type { WorkflowStep } from './WorkflowStep';
1517
import { type ConversationStore, MemoryStore, conversationContext } from './conversation-store';
@@ -529,10 +531,9 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
529531
/**
530532
* Register CustomFunction middleware
531533
*/
532-
public function(callbackId: string, ...listeners: CustomFunctionMiddleware): this {
533-
const fn = new CustomFunction(callbackId, listeners, this.webClientOptions);
534-
const m = fn.getMiddleware();
535-
this.middleware.push(m);
534+
public function(callbackId: string, ...listeners: Middleware<SlackCustomFunctionMiddlewareArgs>[]): this {
535+
const fn = new CustomFunction(callbackId, listeners);
536+
this.listeners.push(fn.getListeners());
536537
return this;
537538
}
538539

@@ -978,8 +979,9 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
978979
}
979980

980981
// Factory for say() utility
982+
// TODO: could this be move out of processEvent, use the same token from below or perhaps even a client from the pool
981983
const createSay = (channelId: string): SayFn => {
982-
const token = selectToken(context);
984+
const token = selectToken(context, this.attachFunctionToken);
983985
return (message) => {
984986
let postMessageArguments: ChatPostMessageArguments;
985987
if (typeof message === 'string') {
@@ -1049,6 +1051,37 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
10491051
payload,
10501052
};
10511053

1054+
// Get the client arg
1055+
let { client } = this;
1056+
1057+
const token = selectToken(context, this.attachFunctionToken);
1058+
1059+
// TODO: this logic should be isolated and tested according to the expected behavior
1060+
if (token !== undefined) {
1061+
let pool: WebClientPool | undefined = undefined;
1062+
const clientOptionsCopy = { ...this.clientOptions };
1063+
if (authorizeResult.teamId !== undefined) {
1064+
pool = this.clients[authorizeResult.teamId];
1065+
if (pool === undefined) {
1066+
pool = this.clients[authorizeResult.teamId] = new WebClientPool();
1067+
}
1068+
// Add teamId to clientOptions so it can be automatically added to web-api calls
1069+
clientOptionsCopy.teamId = authorizeResult.teamId;
1070+
} else if (authorizeResult.enterpriseId !== undefined) {
1071+
pool = this.clients[authorizeResult.enterpriseId];
1072+
if (pool === undefined) {
1073+
pool = this.clients[authorizeResult.enterpriseId] = new WebClientPool();
1074+
}
1075+
}
1076+
1077+
if (this.attachFunctionToken && context.functionBotAccessToken) {
1078+
// workflow tokens are always unique, they should not be added to the pool
1079+
client = new WebClient(token, clientOptionsCopy);
1080+
} else if (pool !== undefined) {
1081+
client = pool.getOrCreate(token, clientOptionsCopy);
1082+
}
1083+
}
1084+
10521085
// TODO: can we instead use type predicates in these switch cases to allow for narrowing of the body simultaneously? we have isEvent, isView, isShortcut, isAction already in types/utilities / helpers
10531086
// Set aliases
10541087
if (type === IncomingEventType.Event) {
@@ -1058,9 +1091,21 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
10581091
const messageEventListenerArgs = eventListenerArgs as SlackEventMiddlewareArgs<'message'>;
10591092
messageEventListenerArgs.message = messageEventListenerArgs.payload;
10601093
}
1094+
// Add complete() and fail() utilities for function-related interactivity
1095+
if (eventListenerArgs.event.type === 'function_executed') {
1096+
listenerArgs.complete = createFunctionComplete(context, client);
1097+
listenerArgs.fail = createFunctionFail(context, client);
1098+
listenerArgs.inputs = eventListenerArgs.event.inputs;
1099+
}
10611100
} else if (type === IncomingEventType.Action) {
10621101
const actionListenerArgs = listenerArgs as SlackActionMiddlewareArgs;
10631102
actionListenerArgs.action = actionListenerArgs.payload;
1103+
// Add complete() and fail() utilities for function-related interactivity
1104+
if (context.functionExecutionId !== undefined) {
1105+
listenerArgs.complete = createFunctionComplete(context, client);
1106+
listenerArgs.fail = createFunctionFail(context, client);
1107+
listenerArgs.inputs = context.functionInputs;
1108+
}
10641109
} else if (type === IncomingEventType.Command) {
10651110
const commandListenerArgs = listenerArgs as SlackCommandMiddlewareArgs;
10661111
commandListenerArgs.command = commandListenerArgs.payload;
@@ -1096,42 +1141,6 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
10961141
await ack();
10971142
}
10981143

1099-
// Get the client arg
1100-
let { client } = this;
1101-
1102-
// If functionBotAccessToken exists on context, the incoming event is function-related *and* the
1103-
// user has `attachFunctionToken` enabled. In that case, subsequent calls with the client should
1104-
// use the function-related/JIT token in lieu of the botToken or userToken.
1105-
const token = context.functionBotAccessToken ? context.functionBotAccessToken : selectToken(context);
1106-
1107-
// Add complete() and fail() utilities for function-related interactivity
1108-
if (type === IncomingEventType.Action && context.functionExecutionId !== undefined) {
1109-
listenerArgs.complete = CustomFunction.createFunctionComplete(context, client);
1110-
listenerArgs.fail = CustomFunction.createFunctionFail(context, client);
1111-
listenerArgs.inputs = context.functionInputs;
1112-
}
1113-
1114-
if (token !== undefined) {
1115-
let pool: WebClientPool | undefined = undefined;
1116-
const clientOptionsCopy = { ...this.clientOptions };
1117-
if (authorizeResult.teamId !== undefined) {
1118-
pool = this.clients[authorizeResult.teamId];
1119-
if (pool === undefined) {
1120-
pool = this.clients[authorizeResult.teamId] = new WebClientPool();
1121-
}
1122-
// Add teamId to clientOptions so it can be automatically added to web-api calls
1123-
clientOptionsCopy.teamId = authorizeResult.teamId;
1124-
} else if (authorizeResult.enterpriseId !== undefined) {
1125-
pool = this.clients[authorizeResult.enterpriseId];
1126-
if (pool === undefined) {
1127-
pool = this.clients[authorizeResult.enterpriseId] = new WebClientPool();
1128-
}
1129-
}
1130-
if (pool !== undefined) {
1131-
client = pool.getOrCreate(token, clientOptionsCopy);
1132-
}
1133-
}
1134-
11351144
// Dispatch event through the global middleware chain
11361145
try {
11371146
await processMiddleware(
@@ -1574,8 +1583,11 @@ function isBlockActionOrInteractiveMessageBody(
15741583
return (body as SlackActionMiddlewareArgs<BlockAction | InteractiveMessage>['body']).actions !== undefined;
15751584
}
15761585

1577-
// Returns either a bot token or a user token for client, say()
1578-
function selectToken(context: Context): string | undefined {
1586+
// Returns either a bot token, a user token or a workflow token for client, say()
1587+
function selectToken(context: Context, attachFunctionToken: boolean): string | undefined {
1588+
if (attachFunctionToken && context.functionBotAccessToken) {
1589+
return context.functionBotAccessToken;
1590+
}
15791591
return context.botToken !== undefined ? context.botToken : context.userToken;
15801592
}
15811593

0 commit comments

Comments
 (0)