Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 56 additions & 50 deletions src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Agent } from 'node:http';
import type { SecureContextOptions } from 'node:tls';
import util from 'node:util';
import { ConsoleLogger, LogLevel, type Logger } from '@slack/logger';
import type { SocketModeOptions } from '@slack/socket-mode';
import { type ChatPostMessageArguments, WebClient, type WebClientOptions, addAppMetadata } from '@slack/web-api';
import axios, { type AxiosInstance, type AxiosResponse } from 'axios';
import type { Assistant } from './Assistant';
Expand Down Expand Up @@ -106,43 +107,44 @@ const tokenUsage =

/** App initialization options */
export interface AppOptions {
signingSecret?: HTTPReceiverOptions['signingSecret'];
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These all still exist, just made them alphabetical order bc it was driving me nuts 🫠 (but a sanity check is very welcome!).

endpoints?: HTTPReceiverOptions['endpoints'];
port?: HTTPReceiverOptions['port'];
customRoutes?: HTTPReceiverOptions['customRoutes'];
processBeforeResponse?: HTTPReceiverOptions['processBeforeResponse'];
signatureVerification?: HTTPReceiverOptions['signatureVerification'];
clientId?: HTTPReceiverOptions['clientId'];
clientSecret?: HTTPReceiverOptions['clientSecret'];
stateSecret?: HTTPReceiverOptions['stateSecret']; // required when using default stateStore
redirectUri?: HTTPReceiverOptions['redirectUri'];
installationStore?: HTTPReceiverOptions['installationStore']; // default MemoryInstallationStore
scopes?: HTTPReceiverOptions['scopes'];
installerOptions?: HTTPReceiverOptions['installerOptions'];
agent?: Agent;
clientTls?: Pick<SecureContextOptions, 'pfx' | 'key' | 'passphrase' | 'cert' | 'ca'>;
convoStore?: ConversationStore | false;
token?: AuthorizeResult['botToken']; // either token or authorize
appToken?: string; // TODO should this be included in AuthorizeResult
attachFunctionToken?: boolean;
authorize?: Authorize<boolean>; // either token or authorize
botId?: AuthorizeResult['botId']; // only used when authorize is not defined, shortcut for fetching
botUserId?: AuthorizeResult['botUserId']; // only used when authorize is not defined, shortcut for fetching
authorize?: Authorize<boolean>; // either token or authorize
receiver?: Receiver;
logger?: Logger;
logLevel?: LogLevel;
ignoreSelf?: boolean;
clientId?: HTTPReceiverOptions['clientId'];
/**
* Configurations for the web client used to send Slack API method requests.
*
* See {@link https://tools.slack.dev/node-slack-sdk/reference/web-api/interfaces/WebClientOptions} for more information.
*/
clientOptions?: WebClientOptions;
socketMode?: boolean;
developerMode?: boolean;
tokenVerificationEnabled?: boolean;
clientSecret?: HTTPReceiverOptions['clientSecret'];
clientTls?: Pick<SecureContextOptions, 'pfx' | 'key' | 'passphrase' | 'cert' | 'ca'>;
convoStore?: ConversationStore | false;
customRoutes?: HTTPReceiverOptions['customRoutes'];
deferInitialization?: boolean;
developerMode?: boolean;
endpoints?: HTTPReceiverOptions['endpoints'];
extendedErrorHandler?: boolean;
attachFunctionToken?: boolean;
ignoreSelf?: boolean;
installationStore?: HTTPReceiverOptions['installationStore']; // default MemoryInstallationStore
installerOptions?: HTTPReceiverOptions['installerOptions'];
logger?: Logger;
logLevel?: LogLevel;
port?: HTTPReceiverOptions['port'];
processBeforeResponse?: HTTPReceiverOptions['processBeforeResponse'];
receiver?: Receiver;
redirectUri?: HTTPReceiverOptions['redirectUri'];
scopes?: HTTPReceiverOptions['scopes'];
socketModeOptions?: Omit<SocketModeOptions, 'appToken'>;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mwbrooks @WilliamBergamin Bundled!

The caveat here is that SocketModeOptions (from the socket-mode pkg) requires appToken; we derive that from the root level initialization here. I've leveraged Omit to appease the type expectation, but open to alternatives (though it seems preferable at first blush to avoid the creation of a near-identical type).

signatureVerification?: HTTPReceiverOptions['signatureVerification'];
signingSecret?: HTTPReceiverOptions['signingSecret'];
socketMode?: boolean;
stateSecret?: HTTPReceiverOptions['stateSecret']; // required when using default stateStore
token?: AuthorizeResult['botToken']; // either token or authorize
tokenVerificationEnabled?: boolean;
}

export { LogLevel, Logger } from '@slack/logger';
Expand Down Expand Up @@ -275,38 +277,39 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
private attachFunctionToken: boolean;

public constructor({
signingSecret = undefined,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. All still exist, just alphabetized.

endpoints = undefined,
port = undefined,
customRoutes = undefined,
authorize = undefined,
agent = undefined,
clientTls = undefined,
receiver = undefined,
convoStore = undefined,
token = undefined,
appToken = undefined,
attachFunctionToken = true,
botId = undefined,
botUserId = undefined,
authorize = undefined,
clientId = undefined,
clientOptions = undefined,
clientSecret = undefined,
clientTls = undefined,
convoStore = undefined,
customRoutes = undefined,
deferInitialization = false,
developerMode = false,
endpoints = undefined,
extendedErrorHandler = false,
ignoreSelf = true,
installationStore = undefined,
installerOptions = undefined,
logger = undefined,
logLevel = undefined,
ignoreSelf = true,
clientOptions = undefined,
processBeforeResponse = false,
signatureVerification = true,
clientId = undefined,
clientSecret = undefined,
stateSecret = undefined,
port = undefined,
receiver = undefined,
token = undefined,
redirectUri = undefined,
installationStore = undefined,
scopes = undefined,
installerOptions = undefined,
signatureVerification = true,
signingSecret = undefined,
socketMode = undefined,
developerMode = false,
socketModeOptions = undefined,
stateSecret = undefined,
tokenVerificationEnabled = true,
extendedErrorHandler = false,
deferInitialization = false,
attachFunctionToken = true,
}: AppOptions = {}) {
/* ------------------------ Developer mode ----------------------------- */
this.developerMode = developerMode;
Expand Down Expand Up @@ -420,6 +423,7 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
scopes,
appToken,
logger,
socketModeOptions,
);

/* ------------------------ Set authorize ----------------------------- */
Expand Down Expand Up @@ -1267,6 +1271,7 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
scopes?: HTTPReceiverOptions['scopes'],
appToken?: string,
logger?: Logger,
socketModeOptions?: Omit<SocketModeOptions, 'appToken'>,
): Receiver {
if (receiver !== undefined) {
// Custom receiver supplied
Expand All @@ -1286,14 +1291,15 @@ export default class App<AppCustomContext extends StringIndexed = StringIndexed>
appToken,
clientId,
clientSecret,
stateSecret,
redirectUri,
customRoutes,
installerOptions: this.installerOptions,
installationStore,
redirectUri,
...socketModeOptions,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mwbrooks @WilliamBergamin

installerOptions is made public on the instance. I'm not sure why, exactly, but we can do the same for socketModeOptions if we think there's a use case for exposing it.

stateSecret,
scopes,
logger,
logLevel: this.logLevel,
installerOptions: this.installerOptions,
customRoutes,
});
}
if (signatureVerification === true && signingSecret === undefined) {
Expand Down
50 changes: 31 additions & 19 deletions src/receivers/SocketModeReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,24 @@ import { verifyRedirectOpts } from './verify-redirect-opts';
// TODO: we throw away the key names for endpoints, so maybe we should use this interface. is it better for migrations?
// if that's the reason, let's document that with a comment.
export interface SocketModeReceiverOptions {
logger?: Logger;
logLevel?: LogLevel;
appToken: string; // App Level Token
autoReconnectEnabled?: boolean;
clientId?: string;
clientPingTimeout?: number;
clientSecret?: string;
stateSecret?: InstallProviderOptions['stateSecret']; // required when using default stateStore
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And again 😉 (alphabetized)

redirectUri?: string;
installationStore?: InstallProviderOptions['installationStore']; // default MemoryInstallationStore
scopes?: InstallURLOptions['scopes'];
installerOptions?: InstallerOptions;
appToken: string; // App Level Token
customRoutes?: CustomRoute[];
// biome-ignore lint/suspicious/noExplicitAny: user-provided custom properties can be anything
customPropertiesExtractor?: (args: any) => StringIndexed;
customRoutes?: CustomRoute[];
installationStore?: InstallProviderOptions['installationStore']; // default MemoryInstallationStore
installerOptions?: InstallerOptions;
logger?: Logger;
logLevel?: LogLevel;
pingPongLoggingEnabled?: boolean;
processEventErrorHandler?: (args: SocketModeReceiverProcessEventErrorHandlerArgs) => Promise<boolean>;
redirectUri?: string;
scopes?: InstallURLOptions['scopes'];
serverPingTimeout?: number;
stateSecret?: InstallProviderOptions['stateSecret']; // required when using default stateStore
}

export interface CustomRoute {
Expand Down Expand Up @@ -94,24 +98,32 @@ export default class SocketModeReceiver implements Receiver {

public constructor({
appToken,
logger = undefined,
logLevel = LogLevel.INFO,
autoReconnectEnabled = false,
clientId = undefined,
clientPingTimeout = undefined,
clientSecret = undefined,
stateSecret = undefined,
redirectUri = undefined,
installationStore = undefined,
scopes = undefined,
installerOptions = {},
customRoutes = [],
customPropertiesExtractor = (_args) => ({}),
customRoutes = [],
installerOptions = {},
installationStore = undefined,
logger = undefined,
logLevel = LogLevel.INFO,
pingPongLoggingEnabled = false,
processEventErrorHandler = defaultProcessEventErrorHandler,
redirectUri = undefined,
scopes = undefined,
serverPingTimeout = undefined,
stateSecret = undefined,
}: SocketModeReceiverOptions) {
this.client = new SocketModeClient({
appToken,
logLevel,
logger,
autoReconnectEnabled,
clientOptions: installerOptions.clientOptions,
clientPingTimeout,
logger,
logLevel,
pingPongLoggingEnabled,
serverPingTimeout,
});

this.logger =
Expand Down