@@ -7,9 +7,11 @@ import axios, { type AxiosInstance, type AxiosResponse } from 'axios';
77import type { Assistant } from './Assistant' ;
88import {
99 CustomFunction ,
10- type CustomFunctionMiddleware ,
1110 type FunctionCompleteFn ,
1211 type FunctionFailFn ,
12+ type SlackCustomFunctionMiddlewareArgs ,
13+ createFunctionComplete ,
14+ createFunctionFail ,
1315} from './CustomFunction' ;
1416import type { WorkflowStep } from './WorkflowStep' ;
1517import { type ConversationStore , MemoryStore , conversationContext } from './conversation-store' ;
@@ -29,7 +31,9 @@ import {
2931 isEventTypeToSkipAuthorize ,
3032} from './helpers' ;
3133import {
34+ autoAcknowledge ,
3235 ignoreSelf as ignoreSelfMiddleware ,
36+ isSlackEventMiddlewareArgsOptions ,
3337 matchCommandName ,
3438 matchConstraints ,
3539 matchEventType ,
@@ -47,7 +51,6 @@ import SocketModeReceiver from './receivers/SocketModeReceiver';
4751import type {
4852 AckFn ,
4953 ActionConstraints ,
50- AllMiddlewareArgs ,
5154 AnyMiddlewareArgs ,
5255 BlockAction ,
5356 BlockElementAction ,
@@ -72,6 +75,7 @@ import type {
7275 SlackActionMiddlewareArgs ,
7376 SlackCommandMiddlewareArgs ,
7477 SlackEventMiddlewareArgs ,
78+ SlackEventMiddlewareArgsOptions ,
7579 SlackOptionsMiddlewareArgs ,
7680 SlackShortcut ,
7781 SlackShortcutMiddlewareArgs ,
@@ -82,7 +86,7 @@ import type {
8286 ViewOutput ,
8387 WorkflowStepEdit ,
8488} from './types' ;
85- import { contextBuiltinKeys } from './types' ;
89+ import { type AllMiddlewareArgs , contextBuiltinKeys } from './types/middleware ' ;
8690import { type StringIndexed , isRejected } from './types/utilities' ;
8791const packageJson = require ( '../package.json' ) ;
8892
@@ -496,7 +500,7 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
496500 * @param m global middleware function
497501 */
498502 public use < MiddlewareCustomContext extends StringIndexed = StringIndexed > (
499- m : Middleware < AnyMiddlewareArgs , AppCustomContext & MiddlewareCustomContext > ,
503+ m : Middleware < AnyMiddlewareArgs < { autoAcknowledge : false } > , AppCustomContext & MiddlewareCustomContext > ,
500504 ) : this {
501505 this . middleware . push ( m as Middleware < AnyMiddlewareArgs > ) ;
502506 return this ;
@@ -529,10 +533,31 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
529533 /**
530534 * Register CustomFunction middleware
531535 */
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 ) ;
536+ public function < Options extends SlackEventMiddlewareArgsOptions = { autoAcknowledge : true } > (
537+ callbackId : string ,
538+ options : Options ,
539+ ...listeners : Middleware < SlackCustomFunctionMiddlewareArgs < Options > > [ ]
540+ ) : this;
541+ public function < Options extends SlackEventMiddlewareArgsOptions = { autoAcknowledge : true } > (
542+ callbackId : string ,
543+ ...listeners : Middleware < SlackCustomFunctionMiddlewareArgs < Options > > [ ]
544+ ) : this;
545+ public function < Options extends SlackEventMiddlewareArgsOptions = { autoAcknowledge : true } > (
546+ callbackId : string ,
547+ ...optionOrListeners : ( Options | Middleware < SlackCustomFunctionMiddlewareArgs < Options > > ) [ ]
548+ ) : this {
549+ // TODO: fix this casting; edge case is if dev specifically sets AutoAck generic as false, this true assignment is invalid according to TS.
550+ const options = isSlackEventMiddlewareArgsOptions ( optionOrListeners [ 0 ] )
551+ ? optionOrListeners [ 0 ]
552+ : ( { autoAcknowledge : true } as Options ) ;
553+ const listeners = optionOrListeners . filter (
554+ ( optionOrListener ) : optionOrListener is Middleware < SlackCustomFunctionMiddlewareArgs < Options > > => {
555+ return ! isSlackEventMiddlewareArgsOptions ( optionOrListener ) ;
556+ } ,
557+ ) ;
558+
559+ const fn = new CustomFunction < Options > ( callbackId , listeners , options ) ;
560+ this . listeners . push ( fn . getListeners ( ) ) ;
536561 return this ;
537562 }
538563
@@ -594,6 +619,7 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
594619 this . listeners . push ( [
595620 onlyEvents ,
596621 matchEventType ( eventNameOrPattern ) ,
622+ autoAcknowledge ,
597623 ..._listeners ,
598624 ] as Middleware < AnyMiddlewareArgs > [ ] ) ;
599625 }
@@ -662,6 +688,7 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
662688 this . listeners . push ( [
663689 onlyEvents ,
664690 matchEventType ( 'message' ) ,
691+ autoAcknowledge ,
665692 ...messageMiddleware ,
666693 ] as Middleware < AnyMiddlewareArgs > [ ] ) ;
667694 }
@@ -979,7 +1006,7 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
9791006
9801007 // Factory for say() utility
9811008 const createSay = ( channelId : string ) : SayFn => {
982- const token = selectToken ( context ) ;
1009+ const token = selectToken ( context , this . attachFunctionToken ) ;
9831010 return ( message ) => {
9841011 let postMessageArguments : ChatPostMessageArguments ;
9851012 if ( typeof message === 'string' ) {
@@ -1040,27 +1067,66 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
10401067 respond ?: RespondFn ;
10411068 /** Ack function might be set below */
10421069 // biome-ignore lint/suspicious/noExplicitAny: different kinds of acks accept different arguments, TODO: revisit this to see if we can type better
1043- ack ? : AckFn < any > ;
1070+ ack : AckFn < any > ;
10441071 complete ?: FunctionCompleteFn ;
10451072 fail ?: FunctionFailFn ;
10461073 inputs ?: FunctionInputs ;
10471074 } = {
10481075 body : bodyArg ,
1076+ ack,
10491077 payload,
10501078 } ;
10511079
1080+ // Get the client arg
1081+ let { client } = this ;
1082+
1083+ const token = selectToken ( context , this . attachFunctionToken ) ;
1084+
1085+ if ( token !== undefined ) {
1086+ let pool : WebClientPool | undefined = undefined ;
1087+ const clientOptionsCopy = { ...this . clientOptions } ;
1088+ if ( authorizeResult . teamId !== undefined ) {
1089+ pool = this . clients [ authorizeResult . teamId ] ;
1090+ if ( pool === undefined ) {
1091+ pool = this . clients [ authorizeResult . teamId ] = new WebClientPool ( ) ;
1092+ }
1093+ // Add teamId to clientOptions so it can be automatically added to web-api calls
1094+ clientOptionsCopy . teamId = authorizeResult . teamId ;
1095+ } else if ( authorizeResult . enterpriseId !== undefined ) {
1096+ pool = this . clients [ authorizeResult . enterpriseId ] ;
1097+ if ( pool === undefined ) {
1098+ pool = this . clients [ authorizeResult . enterpriseId ] = new WebClientPool ( ) ;
1099+ }
1100+ }
1101+ if ( pool !== undefined ) {
1102+ client = pool . getOrCreate ( token , clientOptionsCopy ) ;
1103+ }
1104+ }
1105+
10521106 // 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
10531107 // Set aliases
10541108 if ( type === IncomingEventType . Event ) {
1055- const eventListenerArgs = listenerArgs as SlackEventMiddlewareArgs ;
1109+ // TODO: assigning eventListenerArgs by reference to set properties of listenerArgs is error prone, there should be a better way to do this!
1110+ const eventListenerArgs = listenerArgs as unknown as SlackEventMiddlewareArgs ;
10561111 eventListenerArgs . event = eventListenerArgs . payload ;
10571112 if ( eventListenerArgs . event . type === 'message' ) {
10581113 const messageEventListenerArgs = eventListenerArgs as SlackEventMiddlewareArgs < 'message' > ;
10591114 messageEventListenerArgs . message = messageEventListenerArgs . payload ;
10601115 }
1116+ if ( eventListenerArgs . event . type === 'function_executed' ) {
1117+ listenerArgs . complete = createFunctionComplete ( context , client ) ;
1118+ listenerArgs . fail = createFunctionFail ( context , client ) ;
1119+ listenerArgs . inputs = eventListenerArgs . event . inputs ;
1120+ }
10611121 } else if ( type === IncomingEventType . Action ) {
10621122 const actionListenerArgs = listenerArgs as SlackActionMiddlewareArgs ;
10631123 actionListenerArgs . action = actionListenerArgs . payload ;
1124+ // Add complete() and fail() utilities for function-related interactivity
1125+ if ( context . functionExecutionId !== undefined ) {
1126+ listenerArgs . complete = createFunctionComplete ( context , client ) ;
1127+ listenerArgs . fail = createFunctionFail ( context , client ) ;
1128+ listenerArgs . inputs = context . functionInputs ;
1129+ }
10641130 } else if ( type === IncomingEventType . Command ) {
10651131 const commandListenerArgs = listenerArgs as SlackCommandMiddlewareArgs ;
10661132 commandListenerArgs . command = commandListenerArgs . payload ;
@@ -1088,50 +1154,6 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
10881154 listenerArgs . respond = buildRespondFn ( this . axios , body . response_urls [ 0 ] . response_url ) ;
10891155 }
10901156
1091- // Set ack() utility
1092- if ( type !== IncomingEventType . Event ) {
1093- listenerArgs . ack = ack ;
1094- } else {
1095- // Events API requests are acknowledged right away, since there's no data expected
1096- await ack ( ) ;
1097- }
1098-
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-
11351157 // Dispatch event through the global middleware chain
11361158 try {
11371159 await processMiddleware (
@@ -1575,7 +1597,15 @@ function isBlockActionOrInteractiveMessageBody(
15751597}
15761598
15771599// Returns either a bot token or a user token for client, say()
1578- function selectToken ( context : Context ) : string | undefined {
1600+ function selectToken ( context : Context , attachFunctionToken : boolean ) : string | undefined {
1601+ if ( attachFunctionToken ) {
1602+ // If functionBotAccessToken exists on context, the incoming event is function-related *and* the
1603+ // user has `attachFunctionToken` enabled. In that case, subsequent calls with the client should
1604+ // use the function-related/JIT token in lieu of the botToken or userToken.
1605+ if ( context . functionBotAccessToken ) {
1606+ return context . functionBotAccessToken ;
1607+ }
1608+ }
15791609 return context . botToken !== undefined ? context . botToken : context . userToken ;
15801610}
15811611
0 commit comments