-
Notifications
You must be signed in to change notification settings - Fork 393
/
helpers.ts
153 lines (142 loc) · 5.37 KB
/
helpers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import type {
AnyMiddlewareArgs,
MessageShortcut,
OptionsSource,
ReceiverEvent,
SlackAction,
SlackActionMiddlewareArgs,
SlackCommandMiddlewareArgs,
SlackEventMiddlewareArgs,
SlackOptionsMiddlewareArgs,
SlackShortcutMiddlewareArgs,
} from './types';
/**
* Internal data type for capturing the class of event processed in App#onIncomingEvent()
*/
export enum IncomingEventType {
Event = 0,
Action = 1,
Command = 2,
Options = 3,
ViewAction = 4, // TODO: terminology: ViewAction? Why Action?
Shortcut = 5,
}
// ----------------------------
// For skipping authorize with event
const eventTypesToSkipAuthorize = ['app_uninstalled', 'tokens_revoked'];
/**
* Helper which finds the type and channel (if any) that any specific incoming event is related to.
*
* This is analogous to WhenEventHasChannelContext and the conditional type that checks SlackAction for a channel
* context.
*/
// biome-ignore lint/suspicious/noExplicitAny: response bodies can be anything
export function getTypeAndConversation(body: any): { type?: IncomingEventType; conversationId?: string } {
if (body.event !== undefined) {
const { event } = body as SlackEventMiddlewareArgs<string>['body'];
// Find conversationId
const conversationId: string | undefined = (() => {
let foundConversationId: string;
if ('channel' in event) {
if (typeof event.channel === 'string') {
foundConversationId = event.channel;
} else if ('id' in event.channel) {
foundConversationId = event.channel.id;
}
}
if ('channel_id' in event) {
foundConversationId = event.channel_id;
}
if ('item' in event && 'channel' in event.item) {
// no channel for reaction_added, reaction_removed, star_added, or star_removed with file or file_comment items
foundConversationId = event.item.channel as string;
}
// Using non-null assertion (!) because the alternative is to use `foundConversation: (string | undefined)`, which
// impedes the very useful type checker help above that ensures the value is only defined to strings, not
// undefined. This is safe when used in combination with the || operator with a default value.
// biome-ignore lint/style/noNonNullAssertion: TODO: revisit this and use the types
return foundConversationId! || undefined;
})();
return {
conversationId,
type: IncomingEventType.Event,
};
}
if (body.command !== undefined) {
return {
type: IncomingEventType.Command,
conversationId: (body as SlackCommandMiddlewareArgs['body']).channel_id,
};
}
if (body.name !== undefined || body.type === 'block_suggestion') {
const optionsBody = body as SlackOptionsMiddlewareArgs<OptionsSource>['body'];
return {
type: IncomingEventType.Options,
conversationId: optionsBody.channel !== undefined ? optionsBody.channel.id : undefined,
};
}
// TODO: remove workflow_step stuff in v5
if (body.actions !== undefined || body.type === 'dialog_submission' || body.type === 'workflow_step_edit') {
const actionBody = body as SlackActionMiddlewareArgs<SlackAction>['body'];
return {
type: IncomingEventType.Action,
conversationId: actionBody.channel !== undefined ? actionBody.channel.id : undefined,
};
}
if (body.type === 'shortcut') {
return {
type: IncomingEventType.Shortcut,
};
}
if (body.type === 'message_action') {
const shortcutBody = body as SlackShortcutMiddlewareArgs<MessageShortcut>['body'];
return {
type: IncomingEventType.Shortcut,
conversationId: shortcutBody.channel !== undefined ? shortcutBody.channel.id : undefined,
};
}
if (body.type === 'view_submission' || body.type === 'view_closed') {
return {
type: IncomingEventType.ViewAction,
};
}
return {};
}
/**
* Helper which determines if the body of a request is enterprise install.
*
* Providing the type is optional but if you do the execution will be faster
*/
export function isBodyWithTypeEnterpriseInstall(body: AnyMiddlewareArgs['body'], type?: IncomingEventType): boolean {
const _type = type !== undefined ? type : getTypeAndConversation(body).type;
if (_type === IncomingEventType.Event) {
const bodyAsEvent = body as SlackEventMiddlewareArgs['body'];
if (Array.isArray(bodyAsEvent.authorizations) && bodyAsEvent.authorizations[0] !== undefined) {
return !!bodyAsEvent.authorizations[0].is_enterprise_install;
}
}
// command payloads have this property set as a string
if (typeof body.is_enterprise_install === 'string') {
return body.is_enterprise_install === 'true';
}
// all remaining types have a boolean property
if (body.is_enterprise_install !== undefined) {
return body.is_enterprise_install;
}
// as a fallback we assume it's a single team installation (but this should never happen)
return false;
}
/**
* Helper which determines if the event type will skip Authorize.
*
* Token revocation use cases
* https://github.com/slackapi/bolt-js/issues/674
*/
export function isEventTypeToSkipAuthorize(event: ReceiverEvent): boolean {
return eventTypesToSkipAuthorize.includes(event.body.event?.type);
}
/* istanbul ignore next */
/** Helper that should never be called, but is useful for exhaustiveness checking in conditional branches */
export function assertNever(x?: never): never {
throw new Error(`Unexpected object: ${x}`);
}