-
Notifications
You must be signed in to change notification settings - Fork 380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for remote functions #2026
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome 💯 first pass everything looks good! I left some comments on naming alignment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good!
One higher-level API that bolt needs to consider (and that the deno SDK does not) is token selection when creating the helper API client and other convenience utility functions like say
. Some bolt apps implement OAuth and ask for user scopes, so a user token may come into play. While the function complete/fail helpers should always use the JIT / workflow / function-execution-specific token, that is not so clear for say
or for the general API client
available in certain event handlers. We need to think about how to expose the option of which token to use in these situations.
src/WorkflowFunction.ts
Outdated
|
||
function selectToken(context: Context): string | undefined { | ||
// If attachFunctionToken = false, fallback to botToken or userToken | ||
return context.functionBotToken ? context.functionBotToken : context.botToken || context.userToken; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To @WilliamBergamin's earlier point, this area may need to be exposed as an API to the dev. Here we are making an assumption that for non-function-scoped events, the dev will want to use a bot token over a user token - that will not always be the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how WorkflowStep
was implemented, so merely a carry over. That was long ago, but is there a reality where an app has both present -- a bot token and a user token? In my mind, I've always thought of it as one or the other (either it's a bot, or an app acting on behalf of the user), but you bringing this up makes me think that's a faulty mental model.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, an app can ask for scopes of both types (if you head to the OAuth & Permissions app config page, you can subscribe to both).
Just this week I was helping someone who was using a user token for one API (uploading files) but a bot token in another (posting a message containing the uploaded file).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, for any event that currently comes in, when we run selectToken
when that event is processed, we opt for botToken
first, followed by userToken
. I think it's always been this way, or has for the last 4+ years.
@seratch your name is attached to one of those implementation lines. Can you advise here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've always found this area to be poorly documented on our part and glossed over. For example bolt-js only mentions user scopes if you REALLY dig deep: you gotta scroll to the end of the OAuth docs, expand the hidden "Customizing OAuth options" section and then piece together the pieces based on the example and the one-line description of the userScopes
field.
I believe, but may be wrong on this, that this has always been a userland concern that we don't provide guidance on. Like, for an app that is installable on-demand to workspace (implements OAuth) and collects both bot and user tokens, I'm pretty sure it is up to the installation store implementation (so userland code) to both: figure out which token to store as well as which token to select and return when doing the 'authorization' process that bolt exposes APIs for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I overlooked this thread last week. The logic to select a primary token for context object from bot / user tokens is by design from the beginning, and all Bolt frameworks align.
We basically encourage developers to have a bot token, but technically it still should be allowed to go with only a user token (I don't think this is a common use case though). Thus, when a bot token is missing, setting a user token instead is a reassonable design for supporting such a scenario.
I don't think bolt should expose a way to customize this because:
- we've never received such feature request in the last few years because most apps have their bot token
- developers still can accesss both a bot and user token
As for the documentation topic, indeed this could be improved in the advanced topic section although the number of devs who need it is not so large.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK good to know and thanks for that context Kaz 🙇
If Kaz says so then I defer to his suggestion / feel free to ignore my point
c63f1f4
to
1b79873
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2026 +/- ##
==========================================
- Coverage 82.03% 81.45% -0.58%
==========================================
Files 18 19 +1
Lines 1536 1618 +82
Branches 442 458 +16
==========================================
+ Hits 1260 1318 +58
- Misses 178 193 +15
- Partials 98 107 +9 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! Left a few comments
|
||
// Making calls with a functionBotAccessToken establishes continuity between | ||
// a function_executed event and subsequent interactive events (actions) | ||
const client = new WebClient(token, { ...functionArgs.client }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the first time to see this way to initialize a new instance. Other code in bolt-js uses clientOptions for the second argument instead. I haven't verified on my end yet but have you confirmed this code can make the same effect with new WebClient(token, clientOptions)
?
@@ -425,6 +426,42 @@ export interface FileUnsharedEvent { | |||
event_ts: string; | |||
} | |||
|
|||
export interface FunctionParams { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: here is my investigation result: https://github.com/seratch/slack-edge/blob/0.10.1/src/request/payload/event.ts#L420-L427
type, name, is_required cannot be optional for sure.
There are a few more optional ones such as maximum, minimum, maxLength, and minLength. But you don't need to cover all the things this time.
id: string; | ||
callback_id: string; | ||
title: string; | ||
description: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
description: string; | |
description?: string; |
* 1. removes the next() passed in from App-level middleware processing | ||
* - events will *not* continue down global middleware chain to subsequent listeners |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 Is removing this needed for correct function handling? I can imagine adding middleware after the function listener handler to do something specific to functions after some response like so:
app.function("example_callback", filterInputs, async ({ inputs, complete, next }) => {
await complete({ outputs: inputs });
await next();
}, saveResponseToDatabase);
It's not super clear that this is possible in docs - looking into this now - but I'm finding that listener middleware can go in any order, unrelated to the main listener handler.
Things do get interesting if functionBotAccessToken: true
is used in that example though since the attached xwfp
token
is revoked soon after complete
, meaning saveResponseToDatabase
will get a token_revoked
error if it tries using it...
But I think that error is better than swapping for the original bot
or user
token too since those tokens can be found again. I'm curious about the middleware handling most though! Leaning towards having processMiddleware
handle callbacks and middleware without change if that's possible 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Global middleware should also run before these listeners too, but app.error
might catch thrown errors if that happens. Thinking this is all fine as is!
Summary
This PR adds support for remote (i.e., remotely hosted) functions for use in Workflow Builder.
What's New
A shiny new listener:
.function()
Support of remote functions brings a new
.function()
listener that featurescomplete
andfail
utilities to quickly and easily signal if the function has successfully executed or not.Function-specific interactivity support
We've beefed up the existing
action
listener to automatically determine if the action/interactivity event being received is associated with a function. If so, the callback will make available thecomplete
andfail
utility functions. Note: this requires that the interactivity call was made with the incoming JIT token. See below for details.Automatic use of JIT token (with opt-out option)
When a function-related event is received, a JIT token is included in the payload. The subsequent use of this token when making API calls is what allows for interactivity to be associated with that function (and eventually see the function through to
complete
orfail
). Out of respect for all of our developers and their individual setups, we've provided an easy way to opt-out of this JIT token attachment with theattachFunctionToken
property inAppOptions
.Outstanding / To Do
complete
/fail
utility logic (App.ts
/WorkflowFunction.ts
)Requirements (place an
x
in each[ ]
)