func (rtm *RTM) handleAck(event json.RawMessage) {
	ack := &AckMessage{}
	if err := json.Unmarshal(event, ack); err != nil {
		rtm.Debugln("RTM Error unmarshalling 'ack' event:", err)
		rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
		return
	}
	if ack.Ok {
		rtm.IncomingEvents <- RTMEvent{"ack", ack}
	} else if ack.RTMResponse.Error != nil {
		if ack.RTMResponse.Error.Code == -1 && ack.RTMResponse.Error.Msg == "slow down, too many messages..." {
			rtm.IncomingEvents <- RTMEvent{"ack_error", &RateLimitEvent{}}
		} else {
			rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
		}
	} else {
		rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{fmt.Errorf("ack decode failure")}}
	}
}
handleAck handles an incoming 'ACK' message.
package slack
import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	stdurl "net/url"
	"reflect"
	"time"
	"github.com/gorilla/websocket"
	"github.com/slack-go/slack/internal/errorsx"
	"github.com/slack-go/slack/internal/timex"
)
func (rtm *RTM) ManageConnection() {
	var (
		err  error
		info *Info
		conn *websocket.Conn
	)
	for connectionCount := 0; ; connectionCount++ {
		if info, conn, err = rtm.connect(connectionCount, rtm.useRTMStart); err != nil {
			rtm.Debugf("Failed to connect with RTM on try %d: %s", connectionCount, err)
			rtm.disconnect()
			return
		}
		rtm.mu.Lock()
		rtm.conn = conn
		rtm.info = info
		rtm.mu.Unlock()
		rtm.IncomingEvents <- RTMEvent{"connected", &ConnectedEvent{
			ConnectionCount: connectionCount,
			Info:            info,
		}}
		rtm.Debugf("RTM connection succeeded on try %d", connectionCount)
		rawEvents := make(chan json.RawMessage)
		go rtm.handleIncomingEvents(rawEvents)
		rtm.handleEvents(rawEvents)
		select {
		case <-rtm.disconnected:
			if err = conn.Close(); err != nil {
				rtm.Debugln("failed to close conn on disconnected RTM", err)
			}
			return
		default:
		}
	}
}
func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocket.Conn, error) {
	const (
		errInvalidAuth      = "invalid_auth"
		errInactiveAccount  = "account_inactive"
		errMissingAuthToken = "not_authed"
	)
	boff := &backoff{
		Max: 5 * time.Minute,
	}
	for {
		var (
			backoff time.Duration
		)
		rtm.IncomingEvents <- RTMEvent{"connecting", &ConnectingEvent{
			Attempt:         boff.attempts + 1,
			ConnectionCount: connectionCount,
		}}
		info, conn, err := rtm.startRTMAndDial(useRTMStart)
		if err == nil {
			return info, conn, nil
		}
		switch err.Error() {
		case errInvalidAuth, errInactiveAccount, errMissingAuthToken:
			rtm.Debugf("invalid auth when connecting with RTM: %s", err)
			rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}}
			return nil, nil, err
		default:
		}
		switch actual := err.(type) {
		case statusCodeError:
			if actual.Code == http.StatusNotFound {
				rtm.Debugf("invalid auth when connecting with RTM: %s", err)
				rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}}
				return nil, nil, err
			}
		case *RateLimitedError:
			backoff = actual.RetryAfter
		default:
		}
		backoff = timex.Max(backoff, boff.Duration())
		rtm.IncomingEvents <- RTMEvent{"connection_error", &ConnectionErrorEvent{
			Attempt:  boff.attempts,
			Backoff:  backoff,
			ErrorObj: err,
		}}
		rtm.Debugf("reconnection %d failed: %s reconnecting in %v\n", boff.attempts, err, backoff)
		select {
		case <-time.After(backoff):  
		case intentional := <-rtm.killChannel:
			if intentional {
				rtm.killConnection(intentional, ErrRTMDisconnected)
				return nil, nil, ErrRTMDisconnected
			}
		case <-rtm.disconnected:
			return nil, nil, ErrRTMDisconnected
		}
	}
}
func (rtm *RTM) startRTMAndDial(useRTMStart bool) (info *Info, _ *websocket.Conn, err error) {
	var (
		url string
	)
	if useRTMStart {
		rtm.Debugf("Starting RTM")
		info, url, err = rtm.StartRTM()
	} else {
		rtm.Debugf("Connecting to RTM")
		info, url, err = rtm.ConnectRTM()
	}
	if err != nil {
		rtm.Debugf("Failed to start or connect to RTM: %s", err)
		return nil, nil, err
	}
	u, err := stdurl.Parse(url)
	if err != nil {
		return nil, nil, err
	}
	u.RawQuery = rtm.connParams.Encode()
	url = u.String()
	rtm.Debugf("Dialing to websocket on url %s", url)
	upgradeHeader := http.Header{}
	upgradeHeader.Add("Origin", "https://api.slack.com")
	dialer := websocket.DefaultDialer
	if rtm.dialer != nil {
		dialer = rtm.dialer
	}
	conn, _, err := dialer.Dial(url, upgradeHeader)
	if err != nil {
		rtm.Debugf("Failed to dial to the websocket: %s", err)
		return nil, nil, err
	}
	return info, conn, err
}
func (rtm *RTM) killConnection(intentional bool, cause error) (err error) {
	rtm.Debugln("killing connection", cause)
	if rtm.conn != nil {
		err = rtm.conn.Close()
	}
	rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{Intentional: intentional, Cause: cause}}
	if intentional {
		rtm.disconnect()
	}
	return err
}
func (rtm *RTM) handleEvents(events chan json.RawMessage) {
	ticker := time.NewTicker(rtm.pingInterval)
	defer ticker.Stop()
	for {
		select {
		case intentional := <-rtm.killChannel:
			_ = rtm.killConnection(intentional, errorsx.String("signaled"))
			return
		case <-rtm.pingDeadman.C:
			_ = rtm.killConnection(false, ErrRTMDeadman)
			return
		case <-ticker.C:
			if err := rtm.ping(); err != nil {
				_ = rtm.killConnection(false, err)
				return
			}
		case <-rtm.forcePing:
			if err := rtm.ping(); err != nil {
				_ = rtm.killConnection(false, err)
				return
			}
		case msg := <-rtm.outgoingMessages:
			rtm.sendOutgoingMessage(msg)
		case rawEvent := <-events:
			switch rtm.handleRawEvent(rawEvent) {
			case rtmEventTypeGoodbye:
				_ = rtm.killConnection(false, ErrRTMGoodbye)
			default:
			}
		}
	}
}
func (rtm *RTM) handleIncomingEvents(events chan json.RawMessage) {
	for {
		if err := rtm.receiveIncomingEvent(events); err != nil {
			select {
			case rtm.killChannel <- false:
			case <-rtm.disconnected:
			}
			return
		}
	}
}
func (rtm *RTM) sendWithDeadline(msg interface{}) error {
	if err := rtm.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)); err != nil {
		return err
	}
	if err := rtm.conn.WriteJSON(msg); err != nil {
		return err
	}
	return rtm.conn.SetWriteDeadline(time.Time{})
}
func (rtm *RTM) sendOutgoingMessage(msg OutgoingMessage) {
	rtm.Debugln("Sending message:", msg)
	if len([]rune(msg.Text)) > MaxMessageTextLength {
		rtm.IncomingEvents <- RTMEvent{"outgoing_error", &MessageTooLongEvent{
			Message:   msg,
			MaxLength: MaxMessageTextLength,
		}}
		return
	}
	if err := rtm.sendWithDeadline(msg); err != nil {
		rtm.IncomingEvents <- RTMEvent{"outgoing_error", &OutgoingErrorEvent{
			Message:  msg,
			ErrorObj: err,
		}}
	}
}
func (rtm *RTM) ping() error {
	id := rtm.idGen.Next()
	rtm.Debugln("Sending PING ", id)
	msg := &Ping{ID: id, Type: "ping", Timestamp: time.Now().Unix()}
	if err := rtm.sendWithDeadline(msg); err != nil {
		rtm.Debugf("RTM Error sending 'PING %d': %s", id, err.Error())
		return err
	}
	return nil
}
func (rtm *RTM) receiveIncomingEvent(events chan json.RawMessage) error {
	event := json.RawMessage{}
	err := rtm.conn.ReadJSON(&event)
	if websocket.IsUnexpectedCloseError(err) {
		return err
	}
	switch {
	case err == io.ErrUnexpectedEOF:
		select {
		case rtm.forcePing <- true:
		case <-rtm.disconnected:
		}
	case err != nil:
		rtm.IncomingEvents <- RTMEvent{"incoming_error", &IncomingEventError{
			ErrorObj: err,
		}}
		return err
	case len(event) == 0:
		rtm.Debugln("Received empty event")
	default:
		rtm.Debugln("Incoming Event:", string(event))
		select {
		case events <- event:
		case <-rtm.disconnected:
			rtm.Debugln("disonnected while attempting to send raw event")
		}
	}
	return nil
}
func (rtm *RTM) handleRawEvent(rawEvent json.RawMessage) string {
	event := &Event{}
	err := json.Unmarshal(rawEvent, event)
	if err != nil {
		rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
		return ""
	}
	switch event.Type {
	case rtmEventTypeAck:
		rtm.handleAck(rawEvent)
	case rtmEventTypeHello:
		rtm.IncomingEvents <- RTMEvent{"hello", &HelloEvent{}}
	case rtmEventTypePong:
		rtm.handlePong(rawEvent)
	case rtmEventTypeGoodbye:
	default:
		rtm.handleEvent(event.Type, rawEvent)
	}
	return event.Type
}