"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SocketModeClient = void 0;
const eventemitter3_1 = require("eventemitter3");
const ws_1 = __importDefault(require("ws"));
const finity_1 = __importDefault(require("finity"));
const web_api_1 = require("@slack/web-api");
const logger_1 = require("./logger");
const errors_1 = require("./errors");
const UnrecoverableSocketModeStartError_1 = require("./UnrecoverableSocketModeStartError");
const packageJson = require('../package.json'); // eslint-disable-line import/no-commonjs, @typescript-eslint/no-var-requires
// These enum values are used only in the state machine
var State;
(function (State) {
    State["Connecting"] = "connecting";
    State["Connected"] = "connected";
    State["Reconnecting"] = "reconnecting";
    State["Disconnecting"] = "disconnecting";
    State["Disconnected"] = "disconnected";
    State["Failed"] = "failed";
})(State || (State = {}));
var ConnectingState;
(function (ConnectingState) {
    ConnectingState["Handshaking"] = "handshaking";
    ConnectingState["Authenticating"] = "authenticating";
    ConnectingState["Authenticated"] = "authenticated";
    ConnectingState["Reconnecting"] = "reconnecting";
    ConnectingState["Failed"] = "failed";
})(ConnectingState || (ConnectingState = {}));
var ConnectedState;
(function (ConnectedState) {
    ConnectedState["Preparing"] = "preparing";
    ConnectedState["Ready"] = "ready";
    ConnectedState["Failed"] = "failed";
})(ConnectedState || (ConnectedState = {}));
// These enum values are used only in the state machine
var Event;
(function (Event) {
    Event["Start"] = "start";
    Event["Failure"] = "failure";
    Event["WebSocketOpen"] = "websocket open";
    Event["WebSocketClose"] = "websocket close";
    Event["ServerHello"] = "server hello";
    Event["ServerExplicitDisconnect"] = "server explicit disconnect";
    Event["ServerPingsNotReceived"] = "server pings not received";
    Event["ServerPongsNotReceived"] = "server pongs not received";
    Event["ClientExplicitDisconnect"] = "client explicit disconnect";
    Event["UnableToSocketModeStart"] = "unable_to_socket_mode_start";
})(Event || (Event = {}));
/**
 * An Socket Mode Client allows programs to communicate with the
 * [Slack Platform's Events API](https://api.slack.com/events-api) over WebSocket connections.
 * This object uses the EventEmitter pattern to dispatch incoming events
 * and has a built in send method to acknowledge incoming events over the WebSocket connection.
 */
class SocketModeClient extends eventemitter3_1.EventEmitter {
    /**
     * Returns true if the underlying WebSocket connection is active.
     */
    isActive() {
        this.logger.debug(`Details of isActive() response (connected: ${this.connected}, authenticated: ${this.authenticated}, badConnection: ${this.badConnection})`);
        return this.connected && this.authenticated && !this.badConnection;
    }
    constructor({ logger = undefined, logLevel = undefined, autoReconnectEnabled = true, pingPongLoggingEnabled = false, clientPingTimeout = 5000, serverPingTimeout = 30000, appToken = undefined, clientOptions = {}, } = {}) {
        super();
        /**
         * Whether or not the client is currently connected to the web socket
         */
        this.connected = false;
        /**
         * Whether or not the client has authenticated to the Socket Mode API.
         * This occurs when the connect method completes,
         * and a WebSocket URL is available for the client's connection.
         */
        this.authenticated = false;
        /**
         * Internal count for managing the reconnection state
         */
        this.numOfConsecutiveReconnectionFailures = 0;
        /* eslint-disable @typescript-eslint/indent, newline-per-chained-call */
        this.connectingStateMachineConfig = finity_1.default.configure()
            .global()
            .onStateEnter((state) => {
            this.logger.debug(`Transitioning to state: ${State.Connecting}:${state}`);
        })
            .initialState(ConnectingState.Authenticating)
            .do(this.retrieveWSSURL.bind(this))
            .onSuccess().transitionTo(ConnectingState.Authenticated)
            .onFailure()
            .transitionTo(ConnectingState.Reconnecting).withCondition(this.reconnectingCondition.bind(this))
            .transitionTo(ConnectingState.Failed)
            .state(ConnectingState.Reconnecting)
            .do(() => new Promise((res, _rej) => {
            // Trying to reconnect after waiting for a bit...
            this.numOfConsecutiveReconnectionFailures += 1;
            const millisBeforeRetry = this.clientPingTimeoutMillis * this.numOfConsecutiveReconnectionFailures;
            this.logger.debug(`Before trying to reconnect, this client will wait for ${millisBeforeRetry} milliseconds`);
            setTimeout(() => {
                this.emit(ConnectingState.Authenticating);
                res(true);
            }, millisBeforeRetry);
        }))
            .onSuccess().transitionTo(ConnectingState.Authenticating)
            .onFailure().transitionTo(ConnectingState.Failed)
            .state(ConnectingState.Authenticated)
            .onEnter(this.configureAuthenticatedWebSocket.bind(this))
            .on(Event.WebSocketOpen).transitionTo(ConnectingState.Handshaking)
            .state(ConnectingState.Handshaking) // a state in which to wait until the Event.ServerHello event
            .state(ConnectingState.Failed)
            .onEnter(this.handleConnectionFailure.bind(this))
            .getConfig();
        this.connectedStateMachineConfig = finity_1.default.configure()
            .global()
            .onStateEnter((state) => {
            this.logger.debug(`Transitioning to state: ${State.Connected}:${state}`);
        })
            .initialState(ConnectedState.Preparing)
            .do(async () => {
            if (this.isSwitchingConnection) {
                this.switchWebSocketConnection();
                this.badConnection = false;
            }
            // Start heartbeat to keep track of the WebSocket connection continuing to be alive
            // Proactively verifying the connection health by sending ping from this client side
            this.startPeriodicallySendingPingToSlack();
            // Reactively verifying the connection health by checking the interval of ping from Slack
            this.startMonitoringPingFromSlack();
        })
            .onSuccess().transitionTo(ConnectedState.Ready)
            .onFailure().transitionTo(ConnectedState.Failed)
            .state(ConnectedState.Failed)
            .onEnter(this.handleConnectionFailure.bind(this))
            .getConfig();
        /**
         * Configuration for the state machine
         */
        this.stateMachineConfig = finity_1.default.configure()
            .global()
            .onStateEnter((state, context) => {
            this.logger.debug(`Transitioning to state: ${state}`);
            if (state === State.Disconnected) {
                // Emits a `disconnected` event with a possible error object (might be undefined)
                this.emit(state, context.eventPayload);
            }
            else {
                // Emits events: `connecting`, `connected`, `disconnecting`, `reconnecting`
                this.emit(state);
            }
        })
            .initialState(State.Disconnected)
            .on(Event.Start)
            .transitionTo(State.Connecting)
            .on(Event.ClientExplicitDisconnect)
            .transitionTo(State.Disconnected)
            .state(State.Connecting)
            .onEnter(() => {
            this.logger.info('Going to establish a new connection to Slack ...');
        })
            .submachine(this.connectingStateMachineConfig)
            .on(Event.ServerHello)
            .transitionTo(State.Connected)
            .on(Event.WebSocketClose)
            .transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this))
            .transitionTo(State.Disconnecting)
            .on(Event.ClientExplicitDisconnect)
            .transitionTo(State.Disconnecting)
            .on(Event.Failure)
            .transitionTo(State.Disconnected)
            .on(Event.WebSocketOpen)
            // If submachine not `authenticated` ignore event
            .ignore()
            .state(State.Connected)
            .onEnter(() => {
            this.connected = true;
            this.logger.info('Now connected to Slack');
        })
            .submachine(this.connectedStateMachineConfig)
            .on(Event.WebSocketClose)
            .transitionTo(State.Reconnecting)
            .withCondition(this.autoReconnectCondition.bind(this))
            .withAction(() => this.markCurrentWebSocketAsInactive())
            .transitionTo(State.Disconnecting)
            .on(Event.ClientExplicitDisconnect)
            .transitionTo(State.Disconnecting)
            .withAction(() => this.markCurrentWebSocketAsInactive())
            .on(Event.ServerPingsNotReceived)
            .transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this))
            .transitionTo(State.Disconnecting)
            .on(Event.ServerPongsNotReceived)
            .transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this))
            .transitionTo(State.Disconnecting)
            .on(Event.ServerExplicitDisconnect)
            .transitionTo(State.Reconnecting).withCondition(this.autoReconnectCondition.bind(this))
            .transitionTo(State.Disconnecting)
            .onExit(() => {
            this.terminateActiveHeartBeatJobs();
        })
            .state(State.Reconnecting)
            .onEnter(() => {
            this.logger.info('Reconnecting to Slack ...');
        })
            .do(async () => {
            this.isSwitchingConnection = true;
        })
            .onSuccess().transitionTo(State.Connecting)
            .onFailure().transitionTo(State.Failed)
            .state(State.Disconnecting)
            .onEnter(() => {
            this.logger.info('Disconnecting ...');
        })
            .do(async () => {
            this.terminateActiveHeartBeatJobs();
            this.terminateAllConnections();
            this.logger.info('Disconnected from Slack');
        })
            .onSuccess().transitionTo(State.Disconnected)
            .onFailure().transitionTo(State.Failed)
            .getConfig();
        /**
         * Used to see if a WebSocket stops sending heartbeats and is deemed bad
         */
        this.badConnection = false;
        /**
         * This flag can be true when this client is switching to a new connection.
         */
        this.isSwitchingConnection = false;
        if (appToken === undefined) {
            throw new Error('Must provide an App-Level Token when initializing a Socket Mode Client');
        }
        this.pingPongLoggingEnabled = pingPongLoggingEnabled;
        this.clientPingTimeoutMillis = clientPingTimeout;
        this.lastPongReceivedTimestamp = undefined;
        this.serverPingTimeoutMillis = serverPingTimeout;
        // Setup the logger
        if (typeof logger !== 'undefined') {
            this.logger = logger;
            if (typeof logLevel !== 'undefined') {
                this.logger.debug('The logLevel given to Socket Mode was ignored as you also gave logger');
            }
        }
        else {
            this.logger = (0, logger_1.getLogger)(SocketModeClient.loggerName, logLevel !== null && logLevel !== void 0 ? logLevel : logger_1.LogLevel.INFO, logger);
        }
        this.clientOptions = clientOptions;
        if (this.clientOptions.retryConfig === undefined) {
            // For faster retries of apps.connections.open API calls for reconnecting
            this.clientOptions.retryConfig = { retries: 100, factor: 1.3 };
        }
        this.webClient = new web_api_1.WebClient('', Object.assign({ logger, logLevel: this.logger.getLevel(), headers: { Authorization: `Bearer ${appToken}` } }, clientOptions));
        this.autoReconnectEnabled = autoReconnectEnabled;
        this.stateMachine = finity_1.default.start(this.stateMachineConfig);
        this.logger.debug('The Socket Mode client is successfully initialized');
    }
    /**
     * Start a Socket Mode session app.
     * It may take a few milliseconds before being connected.
     * This method must be called before any messages can be sent or received.
     */
    start() {
        this.logger.debug('Starting a Socket Mode client ...');
        // Delegate behavior to state machine
        this.stateMachine.handle(Event.Start);
        // Return a promise that resolves with the connection information
        return new Promise((resolve, reject) => {
            this.once(ConnectingState.Authenticated, (result) => {
                this.removeListener(State.Disconnected, reject);
                resolve(result);
            });
            this.once(State.Disconnected, (err) => {
                this.removeListener(ConnectingState.Authenticated, resolve);
                reject(err);
            });
        });
    }
    /**
     * End a Socket Mode session. After this method is called no messages will be sent or received
     * unless you call start() again later.
     */
    disconnect() {
        return new Promise((resolve, reject) => {
            this.logger.debug('Manually disconnecting this Socket Mode client');
            // Resolve (or reject) on disconnect
            this.once(State.Disconnected, (err) => {
                if (err instanceof Error) {
                    reject(err);
                }
                else {
                    resolve();
                }
            });
            // Delegate behavior to state machine
            this.stateMachine.handle(Event.ClientExplicitDisconnect);
        });
    }
    /**
     * Method for sending an outgoing message of an arbitrary type over the WebSocket connection.
     * Primarily used to send acknowledgements back to slack for incoming events
     * @param id the envelope id
     * @param body the message body or string text
     */
    send(id, body = {}) {
        const _body = typeof body === 'string' ? { text: body } : body;
        const message = { envelope_id: id, payload: Object.assign({}, _body) };
        return new Promise((resolve, reject) => {
            this.logger.debug(`send() method was called in state: ${this.stateMachine.getCurrentState()}, state hierarchy: ${this.stateMachine.getStateHierarchy()}`);
            if (this.websocket === undefined) {
                this.logger.error('Failed to send a message as the client is not connected');
                reject((0, errors_1.sendWhileDisconnectedError)());
            }
            else if (!this.isConnectionReady()) {
                this.logger.error('Failed to send a message as the client is not ready');
                reject((0, errors_1.sendWhileNotReadyError)());
            }
            else {
                this.emit('outgoing_message', message);
                const flatMessage = JSON.stringify(message);
                this.logger.debug(`Sending a WebSocket message: ${flatMessage}`);
                this.websocket.send(flatMessage, (error) => {
                    if (error !== undefined && error !== null) {
                        this.logger.error(`Failed to send a WebSocket message (error: ${error.message})`);
                        return reject((0, errors_1.websocketErrorWithOriginal)(error));
                    }
                    return resolve();
                });
            }
        });
    }
    async retrieveWSSURL() {
        try {
            this.logger.debug('Going to retrieve a new WSS URL ...');
            return await this.webClient.apps.connections.open();
        }
        catch (error) {
            this.logger.error(`Failed to retrieve a new WSS URL for reconnection (error: ${error})`);
            throw error;
        }
    }
    autoReconnectCondition() {
        return this.autoReconnectEnabled;
    }
    reconnectingCondition(context) {
        const error = context.error;
        this.logger.warn(`Failed to start a Socket Mode connection (error: ${error.message})`);
        // Observe this event when the error which causes reconnecting or disconnecting is meaningful
        this.emit(Event.UnableToSocketModeStart, error);
        let isRecoverable = true;
        if (error.code === web_api_1.ErrorCode.PlatformError &&
            Object.values(UnrecoverableSocketModeStartError_1.UnrecoverableSocketModeStartError).includes(error.data.error)) {
            isRecoverable = false;
        }
        else if (error.code === web_api_1.ErrorCode.RequestError) {
            isRecoverable = false;
        }
        else if (error.code === web_api_1.ErrorCode.HTTPError) {
            isRecoverable = false;
        }
        return this.autoReconnectEnabled && isRecoverable;
    }
    configureAuthenticatedWebSocket(_state, context) {
        this.numOfConsecutiveReconnectionFailures = 0; // Reset the failure count
        this.authenticated = true;
        this.setupWebSocket(context.result.url);
        setImmediate(() => {
            this.emit(ConnectingState.Authenticated, context.result);
        });
    }
    handleConnectionFailure(_state, context) {
        this.logger.error(`The internal logic unexpectedly failed (error: ${context.error})`);
        // Terminate everything, just in case
        this.terminateActiveHeartBeatJobs();
        this.terminateAllConnections();
        // dispatch 'failure' on parent machine to transition out of this submachine's states
        this.stateMachine.handle(Event.Failure, context.error);
    }
    markCurrentWebSocketAsInactive() {
        this.badConnection = true;
        this.connected = false;
        this.authenticated = false;
    }
    /**
     * Clean up all the remaining connections.
     */
    terminateAllConnections() {
        if (this.secondaryWebsocket !== undefined) {
            this.terminateWebSocketSafely(this.secondaryWebsocket);
            this.secondaryWebsocket = undefined;
        }
        if (this.websocket !== undefined) {
            this.terminateWebSocketSafely(this.websocket);
            this.websocket = undefined;
        }
    }
    /**
     * Set up method for the client's WebSocket instance. This method will attach event listeners.
     */
    setupWebSocket(url) {
        // initialize the websocket
        const options = {
            perMessageDeflate: false,
            agent: this.clientOptions.agent,
        };
        let websocket;
        let socketId;
        if (this.websocket === undefined) {
            this.websocket = new ws_1.default(url, options);
            socketId = 'Primary';
            websocket = this.websocket;
        }
        else {
            // Set up secondary websocket
            // This is used when creating a new connection because the first is about to disconnect
            this.secondaryWebsocket = new ws_1.default(url, options);
            socketId = 'Secondary';
            websocket = this.secondaryWebsocket;
        }
        // Attach event listeners
        websocket.addEventListener('open', (event) => {
            this.logger.debug(`${socketId} WebSocket open event received (connection established)`);
            this.stateMachine.handle(Event.WebSocketOpen, event);
        });
        websocket.addEventListener('close', (event) => {
            this.logger.debug(`${socketId} WebSocket close event received (code: ${event.code}, reason: ${event.reason})`);
            this.stateMachine.handle(Event.WebSocketClose, event);
        });
        websocket.addEventListener('error', (event) => {
            this.logger.error(`${socketId} WebSocket error occurred: ${event.message}`);
            this.emit('error', (0, errors_1.websocketErrorWithOriginal)(event.error));
        });
        websocket.addEventListener('message', this.onWebSocketMessage.bind(this));
        // Confirm WebSocket connection is still active
        websocket.addEventListener('ping', ((data) => {
            if (this.pingPongLoggingEnabled) {
                this.logger.debug(`${socketId} WebSocket received ping from Slack server (data: ${data})`);
            }
            this.startMonitoringPingFromSlack();
            // Since the `addEventListener` method does not accept listener with data arg in TypeScript,
            // we cast this function to any as a workaround
        })); // eslint-disable-line @typescript-eslint/no-explicit-any
        websocket.addEventListener('pong', ((data) => {
            if (this.pingPongLoggingEnabled) {
                this.logger.debug(`${socketId} WebSocket received pong from Slack server (data: ${data})`);
            }
            this.lastPongReceivedTimestamp = new Date().getTime();
            // Since the `addEventListener` method does not accept listener with data arg in TypeScript,
            // we cast this function to any as a workaround
        })); // eslint-disable-line @typescript-eslint/no-explicit-any
    }
    /**
     * Tear down the currently working heartbeat jobs.
     */
    terminateActiveHeartBeatJobs() {
        if (this.serverPingTimeout !== undefined) {
            clearTimeout(this.serverPingTimeout);
            this.serverPingTimeout = undefined;
            this.logger.debug('Cancelled the job waiting for ping from Slack');
        }
        if (this.clientPingTimeout !== undefined) {
            clearTimeout(this.clientPingTimeout);
            this.clientPingTimeout = undefined;
            this.logger.debug('Terminated the heart beat job');
        }
    }
    /**
     * Switch the active connection to the secondary if exists.
     */
    switchWebSocketConnection() {
        if (this.secondaryWebsocket !== undefined && this.websocket !== undefined) {
            this.logger.debug('Switching to the secondary connection ...');
            // Currently have two WebSocket objects, so tear down the older one
            const oldWebsocket = this.websocket;
            // Switch to the new one here
            this.websocket = this.secondaryWebsocket;
            this.secondaryWebsocket = undefined;
            this.logger.debug('Switched to the secondary connection');
            // Swithcing the connection is done
            this.isSwitchingConnection = false;
            // Clean up the old one
            this.terminateWebSocketSafely(oldWebsocket);
            this.logger.debug('Terminated the old connection');
        }
    }
    /**
     * Tear down method for the client's WebSocket instance.
     * This method undoes the work in setupWebSocket(url).
     */
    terminateWebSocketSafely(websocket) {
        if (websocket !== undefined) {
            try {
                websocket.removeAllListeners('open');
                websocket.removeAllListeners('close');
                websocket.removeAllListeners('error');
                websocket.removeAllListeners('message');
                websocket.terminate();
            }
            catch (e) {
                this.logger.error(`Failed to terminate a connection (error: ${e})`);
            }
        }
    }
    startPeriodicallySendingPingToSlack() {
        if (this.clientPingTimeout !== undefined) {
            clearTimeout(this.clientPingTimeout);
        }
        // re-init for new monitoring loop
        this.lastPongReceivedTimestamp = undefined;
        let pingAttemptCount = 0;
        if (!this.badConnection) {
            this.clientPingTimeout = setInterval(() => {
                var _a;
                const nowMillis = new Date().getTime();
                try {
                    const pingMessage = `Ping from client (${nowMillis})`;
                    (_a = this.websocket) === null || _a === void 0 ? void 0 : _a.ping(pingMessage);
                    if (this.lastPongReceivedTimestamp === undefined) {
                        pingAttemptCount += 1;
                    }
                    else {
                        pingAttemptCount = 0;
                    }
                    if (this.pingPongLoggingEnabled) {
                        this.logger.debug(`Sent ping to Slack: ${pingMessage}`);
                    }
                }
                catch (e) {
                    this.logger.error(`Failed to send ping to Slack (error: ${e})`);
                    this.handlePingPongErrorReconnection();
                    return;
                }
                let isInvalid = pingAttemptCount > 5;
                if (this.lastPongReceivedTimestamp !== undefined) {
                    const millis = nowMillis - this.lastPongReceivedTimestamp;
                    isInvalid = millis > this.clientPingTimeoutMillis;
                }
                if (isInvalid) {
                    this.logger.info(`A pong wasn't received from the server before the timeout of ${this.clientPingTimeoutMillis}ms!`);
                    this.handlePingPongErrorReconnection();
                }
            }, this.clientPingTimeoutMillis / 3);
            this.logger.debug('Started running a new heart beat job');
        }
    }
    handlePingPongErrorReconnection() {
        try {
            this.badConnection = true;
            this.stateMachine.handle(Event.ServerPongsNotReceived);
        }
        catch (e) {
            this.logger.error(`Failed to reconnect to Slack (error: ${e})`);
        }
    }
    /**
     * Confirms WebSocket connection is still active
     * fires whenever a ping event is received
     */
    startMonitoringPingFromSlack() {
        if (this.serverPingTimeout !== undefined) {
            clearTimeout(this.serverPingTimeout);
        }
        // Don't start heartbeat if connection is already deemed bad
        if (!this.badConnection) {
            this.serverPingTimeout = setTimeout(() => {
                this.logger.info(`A ping wasn't received from the server before the timeout of ${this.serverPingTimeoutMillis}ms!`);
                if (this.isConnectionReady()) {
                    this.badConnection = true;
                    // Opens secondary WebSocket and teardown original once that is ready
                    this.stateMachine.handle(Event.ServerPingsNotReceived);
                }
            }, this.serverPingTimeoutMillis);
        }
    }
    isConnectionReady() {
        const currentState = this.stateMachine.getCurrentState();
        const stateHierarchy = this.stateMachine.getStateHierarchy();
        return currentState === State.Connected &&
            stateHierarchy !== undefined &&
            stateHierarchy.length >= 2 &&
            // When the primary state is State.Connected, the second one is always set by the sub state machine
            stateHierarchy[1].toString() === ConnectedState.Ready;
    }
    /**
     * `onmessage` handler for the client's WebSocket.
     * This will parse the payload and dispatch the relevant events for each incoming message.
     */
    async onWebSocketMessage({ data }) {
        this.logger.debug(`Received a message on the WebSocket: ${data}`);
        // Parse message into slack event
        let event;
        try {
            event = JSON.parse(data);
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
        }
        catch (parseError) {
            // Prevent application from crashing on a bad message, but log an error to bring attention
            this.logger.error(`Unable to parse an incoming WebSocket message: ${parseError.message}`);
            return;
        }
        // Internal event handlers
        if (event.type === 'hello') {
            this.stateMachine.handle(Event.ServerHello);
            return;
        }
        if (event.type === 'disconnect') {
            // Refresh the WebSocket connection when prompted by Slack backend
            this.logger.debug(`Received "disconnect" (reason: ${event.reason}) message - will ${this.autoReconnectEnabled ? 'attempt reconnect' : 'disconnect (due to autoReconnectEnabled=false)'}`);
            this.stateMachine.handle(Event.ServerExplicitDisconnect);
            return;
        }
        // Define Ack
        const ack = async (response) => {
            if (this.logger.getLevel() === logger_1.LogLevel.DEBUG) {
                this.logger.debug(`Calling ack() - type: ${event.type}, envelope_id: ${event.envelope_id}, data: ${JSON.stringify(response)}`);
            }
            await this.send(event.envelope_id, response);
        };
        // For events_api messages, expose the type of the event
        if (event.type === 'events_api') {
            this.emit(event.payload.event.type, {
                ack,
                envelope_id: event.envelope_id,
                body: event.payload,
                event: event.payload.event,
                retry_num: event.retry_attempt,
                retry_reason: event.retry_reason,
                accepts_response_payload: event.accepts_response_payload,
            });
        }
        else {
            // Emit just ack and body for all other types of messages
            this.emit(event.type, {
                ack,
                envelope_id: event.envelope_id,
                body: event.payload,
                accepts_response_payload: event.accepts_response_payload,
            });
        }
        // Emitter for all slack events
        // (this can be used in tools like bolt-js)
        this.emit('slack_event', {
            ack,
            envelope_id: event.envelope_id,
            type: event.type,
            body: event.payload,
            retry_num: event.retry_attempt,
            retry_reason: event.retry_reason,
            accepts_response_payload: event.accepts_response_payload,
        });
    }
}
exports.SocketModeClient = SocketModeClient;
/**
 * The name used to prefix all logging generated from this object
 */
SocketModeClient.loggerName = 'SocketModeClient';
/* Instrumentation */
(0, web_api_1.addAppMetadata)({ name: packageJson.name, version: packageJson.version });
exports.default = SocketModeClient;
//# sourceMappingURL=SocketModeClient.js.map