forked from slackapi/bolt-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
helpers.ts
152 lines (141 loc) · 5.21 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
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
SlackEventMiddlewareArgs,
SlackCommandMiddlewareArgs,
SlackOptionsMiddlewareArgs,
SlackActionMiddlewareArgs,
SlackShortcutMiddlewareArgs,
SlackAction,
OptionsSource,
MessageShortcut,
AnyMiddlewareArgs,
ReceiverEvent,
} from './types';
/**
* Internal data type for capturing the class of event processed in App#onIncomingEvent()
*/
export enum IncomingEventType {
Event,
Action,
Command,
Options,
ViewAction,
Shortcut,
}
// ----------------------------
// 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.
*/
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.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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,
};
}
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}`);
}