Creates an RTM API client using `/rtm.connect`.

@see "https://api.slack.com/docs/rate-limits#rtm"
545498
public RTMClient rtmConnect(String apiToken, boolean fullUserInfoRequired) throws IOException {
        try {
            RTMConnectResponse response = methods().rtmConnect(RTMConnectRequest.builder().token(apiToken).build());
            if (response.isOk()) {
                User connectedBotUser = response.getSelf();
                if (fullUserInfoRequired) {
                    String userId = response.getSelf().getId();
                    UsersInfoResponse resp = this.methods().usersInfo(UsersInfoRequest.builder().token(apiToken).user(userId).build());
                    if (resp.isOk()) {
                        connectedBotUser = resp.getUser();
                    } else {
                        String errorMessage = "Failed to get fill user info (user id: " + response.getSelf().getId() + ", error: " + resp.getError() + ")";
                        throw new IllegalStateException(errorMessage);
                    }
                }
                return new RTMClient(this, apiToken, response.getUrl(), connectedBotUser);
            } else {
                throw new IllegalStateException("Failed to the RTM endpoint URL (error: " + response.getError() + ")");
            }
        } catch (SlackApiException e) {
            throw new IllegalStateException(
                    "Failed to connect to the RTM API endpoint. (" +
                            "status: " + e.getResponse().code() + ", " +
                            "error: " + e.getError().getError() +
                            ")", e);
        } catch (URISyntaxException e) {
            throw new IllegalStateException(
                    "Failed to connect to the RTM API endpoint. (message: " + e.getMessage() + ")", e);
        }
    }
245303
private CloseableHttpClient createHttpClient(HttpClientConnectionManager connectionManager) {
        HttpClientBuilder builder =
                HttpClients.custom().setConnectionManager(connectionManager).disableAutomaticRetries();

        int socketBufferSizeInBytes = this.config.getSocketBufferSizeInBytes();
        if (socketBufferSizeInBytes > 0) {
            builder.setDefaultConnectionConfig(
                    ConnectionConfig.custom().setBufferSize(socketBufferSizeInBytes).build());
        }

        return builder.build();
    }
355463
def loop(server, test_loop=None):
    
    try:
        loops_without_activity = 0
        while test_loop is None or test_loop > 0:
            start = time.time()
            loops_without_activity += 1

            events = server.slack.rtm_read()
            for event in events:
                loops_without_activity = 0
                logger.debug("got {0}".format(event))
                response = handle_event(event, server)

                # The docs (https://api.slack.com/docs/message-threading)
                # suggest looking for messages where `thread_ts` != `ts`,
                # but I can't see anywhere that it would make a practical
                # difference. If a message is part of a thread, respond to
                # that thread.
                thread_ts = None
                if 'thread_ts' in event:
                    thread_ts = event['thread_ts']

                while response:
                    # The Slack API documentation says:
                    #
                    # Clients should limit messages sent to channels to 4000
                    # characters, which will always be under 16k bytes even
                    # with a message comprised solely of non-BMP Unicode
                    # characters at 4 bytes each.
                    #
                    # but empirical testing shows that I'm getting disconnected
                    # at 4000 characters and even quite a bit lower. Use 1000
                    # to be safe
                    server.slack.rtm_send_message(event["channel"],
                                                  response[:1000], thread_ts)
                    response = response[1000:]

            # Run the loop hook. This doesn't send messages it receives,
            # because it doesn't know where to send them. Use
            # server.slack.post_message to send messages from a loop hook
            run_hook(server.hooks, "loop", server)

            # The Slack RTM API docs say:
            #
            # > When there is no other activity clients should send a ping
            # > every few seconds
            #
            # So, if we've gone >5 seconds without any activity, send a ping.
            # If the connection has broken, this will reveal it so slack can
            # quit
            if loops_without_activity > 5:
                server.slack.ping()
                loops_without_activity = 0

            end = time.time()
            runtime = start - end
            time.sleep(max(1 - runtime, 0))

            if test_loop:
                test_loop -= 1
    except KeyboardInterrupt:
        if os.environ.get("LIMBO_DEBUG"):
            import ipdb
            ipdb.set_trace()
        raise
704104
public RabbitmqClient createRabbitmqClient() throws RabbitmqCommunicateException
    {
        List<RabbitmqClusterContext> contextList = this.reader.readConfiguration();
        return createRabbitmqClient(contextList);
    }
637099
function SlackRtmOut(n) {
    RED.nodes.createNode(this, n);

    var node = this;
    this.client = n.client;
    this.clientNode = RED.nodes.getNode(this.client);

    node.status(statuses.disconnected);

    if (node.client) {
      SetConfigNodeConnectionListeners(node);

      node.on("input", function(msg) {
        SlackDebug("slack-rtm-out incomiming message", msg);

        /**
         * message, presence_sub, presence_query, ping, and typing.
         */
        var method = msg.topic;
        var options = msg.payload;

        if (
          !method ||
          typeof method !== "string" ||
          !method instanceof String
        ) {
          node.error("invalid msg.topic");
          return null;
        }

        /**
         * Try to help with common use case, set channel based on lookup if starts with @ or #
         */
        if (
          method == "message" &&
          (options.channel[0] == "@" || options.channel[0] == "#")
        ) {
          var channel = node.clientNode.findChannelByName(options.channel);
          if (channel && channel.id) {
            options.channel = channel.id;
          }
        }

        /**
         * special handling for simple use-case
         */
        if (method[0] == "@" || method[0] == "#") {
          var channel = node.clientNode.findChannelByName(method);
          if (!channel || !channel.id) {
            node.error("invalid channel: " + method);
            return null;
          }
          method = "message";
          options = {
            text: msg.payload,
            channel: channel.id
          };
        }

        /**
         * custom per-method logic
         */
        switch (method) {
          case "message":
            // force text to be a string
            options.text = ValueToString(options.text);
            break;
        }

        /**
         * something seems to block when it's not message events
         * keeping a hard-coded list for now until the 'bug' is squashed
         *
         * https://github.com/slackapi/node-slack-sdk/issues/706
         *
         * There's only 5 (public) writable events on RTM: message,
         * presence_sub, presence_query, ping, and typing.
         *
         * The only two that expect responses are message and ping.
         */
        var awaitReply = false;
        switch (method) {
          case "message":
          case "ping":
            awaitReply = true;
            break;
          case "presence_sub":
          case "presence_query":
          case "typing":
            awaitReply = false;
            break;
          default:
            /**
             * go ahead and let invalid methods await so a negative/error
             * response is returned
             */
            awaitReply = true;
            break;
        }

        node.status(statuses.sending);
        msg.slackState = node.clientNode.state;

        SlackDebug("slack-rtm-out call", method, options);
        node.clientNode.rtmClient
          .addOutgoingEvent(awaitReply, method, options)
          .then(res => {
            /**
             * mock a response for methods which return nothing
             * ie: everything but message and ping
             */
            if (typeof res === "undefined") {
              res = {
                ok: true,
                type: method
              };
            }

            SlackDebug("slack-rtm-out call response", res);
            msg.payload = res;
            node.send(msg);
            node.status(statuses.connected);
          })
          .catch(e => {
            SlackDebug("slack-rtm-out error response", e.data);
            msg.payload = e.data;
            node.send(msg);
            node.status(statuses.connected);
          });
      });
    } else {
      node.status(statuses.misconfigured);
    }
  }
976353
private RTPFormat createAudioFormat(int payload, Text description) {
        Iterator<Text> it = description.split('/').iterator();

        //encoding name
        Text token = it.next();
        token.trim();
        EncodingName name = new EncodingName(token);

        //clock rate
        //TODO : convert to sample rate
        token = it.next();
        token.trim();
        int clockRate = token.toInteger();

        //channels
        int channels = 1;
        if (it.hasNext()) {
            token = it.next();
            token.trim();
            channels = token.toInteger();
        }

        RTPFormat rtpFormat = getFormat(payload);
        if (rtpFormat == null) {
            formats.add(new RTPFormat(payload, FormatFactory.createAudioFormat(name, clockRate, -1, channels)));
        } else {
            //TODO: recreate format anyway. it is illegal to use clock rate as sample rate
            ((AudioFormat)rtpFormat.getFormat()).setName(name);
            ((AudioFormat)rtpFormat.getFormat()).setSampleRate(clockRate);
            ((AudioFormat)rtpFormat.getFormat()).setChannels(channels);
        }

        return rtpFormat;
    }
584562
private TransportChannelHandler createChannelHandler(Channel channel, RpcHandler rpcHandler) {
    TransportResponseHandler responseHandler = new TransportResponseHandler(channel);
    TransportClient client = new TransportClient(channel, responseHandler);
    TransportRequestHandler requestHandler = new TransportRequestHandler(channel, client,
      rpcHandler, conf.maxChunksBeingTransferred());
    return new TransportChannelHandler(client, responseHandler, requestHandler,
      conf.connectionTimeoutMs(), closeIdleConnections, this);
  }
495019
public void connect(IConnection conn) {
        if (conn instanceof RTMPConnection) {
            if (!isConnected()) {
                source = conn;
                SharedObjectMessage msg = new SharedObjectMessage(name, 0, isPersistent());
                msg.addEvent(new SharedObjectEvent(Type.SERVER_CONNECT, null, null));
                Channel c = ((RTMPConnection) conn).getChannel(3);
                c.write(msg);
            } else {
                throw new UnsupportedOperationException("Already connected");
            }
        } else {
            throw new UnsupportedOperationException("Only RTMP connections are supported");
        }
    }
298558
public void startRtm() throws IOException {
    String accessToken;
    synchronized (this) {
      accessToken = session.access_token;
    }

    RtmSession rtmSession = new RtmSession(slackApi);
    rtmSession.open(accessToken);
  }
1074648
public static Message create(final BandwidthClient client, final Map<String, Object> params) throws Exception {
        final String messageUri = client.getUserResourceUri(BandwidthConstants.MESSAGES_URI_PATH);
        final RestResponse response = client.post(messageUri, params);
    	final String messageId = response.getLocation().substring(client.getPath(messageUri).length() + 1);

        return get(client, messageId);
    }
1231155
private ClientConnection createHttpClientConnection(final StreamConnection connection, final OptionMap options, final ByteBufferPool bufferPool) {
		try {
			Class<?> cls = Class.forName("io.undertow.client.http.HttpClientConnection");
			
			Constructor<?> o = cls.getDeclaredConstructor(StreamConnection.class, OptionMap.class, ByteBufferPool.class);
			
			o.setAccessible(true);
			
			return (ClientConnection) o.newInstance(connection, options, bufferPool);			
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
		}

		return null;
	}
69948
def do_http(uri, request)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true
      # Let then know about us
      request['User-Agent'] = 'SlackRubyAPIWrapper'
      begin
        http.request(request)
      rescue OpenSSL::SSL::SSLError => e
        raise Slack::Error, 'SSL error connecting to Slack.'
      end
    end
1322580
function connect (conf) {
  // Instantiating API client, this is the one that will be passed to other methods
  var client = new Client();

  if (conf["my-host.token"]) {
    // Authorize client
    client.setToken(conf["my-host.token"]);
    // Check token…
    return checkToken(client)
      .then(null, function () {
        // …and create a new one if it failed
        console.error("Existing token is invalid");
        return createToken(client);
      });
  } else {
    // No token found: create new one
    return createToken(client);
  }
}
595606
def connect(token, protocol=RtmProtocol, factory=WebSocketClientFactory, factory_kwargs=None, api_url=None, debug=False):
	
	if factory_kwargs is None:
		factory_kwargs = dict()

	metadata = request_session(token, api_url)
	wsfactory = factory(metadata.url, **factory_kwargs)
	if debug:
		warnings.warn('debug=True has been deprecated in autobahn 0.14.0')

	wsfactory.protocol = lambda *a,**k: protocol(*a,**k)._seedMetadata(metadata)
	connection = connectWS(wsfactory)
	return connection
783355
@Override
	public TopicConnection createTopicConnection(String userName, String password) throws JMSException
    {
        URI providerURL = getProviderURI();     
        
        String scheme = providerURL.getScheme();
        if (scheme.equals(PacketTransportType.VM))
        {
            String engineName = providerURL.getHost();
            return FFMQEngine.getDeployedInstance(engineName).openTopicConnection(userName, password, clientID);
        }
        else 
    	if (scheme.equals(PacketTransportType.TCP) ||
            scheme.equals(PacketTransportType.TCPS) ||
            scheme.equals(PacketTransportType.TCPNIO))
    	{
            return new RemoteTopicConnection(providerURL, userName, password, clientID);
    	}
        else
            throw new FFMQException("Unknown transport protocol : " + scheme,"INVALID_TRANSPORT_PROTOCOL");
    }
920641
public TrmClientAttachRequest2 createNewTrmClientAttachRequest2() throws MessageCreateFailedException {
    if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) SibTr.entry(tc, "createNewTrmClientAttachRequest2");
    TrmClientAttachRequest2 msg = null;
    try {
      msg = new TrmClientAttachRequest2Impl();
    }
    catch (MessageDecodeFailedException e) {
      /* No need to FFDC this as JsMsgObject will already have done so */
      // No FFDC code needed
      throw new MessageCreateFailedException(e);
    }
    if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) SibTr.exit(tc, "createNewTrmClientAttachRequest2");
    return msg;
  }
1208099
function SlackConfig(n) {
    RED.nodes.createNode(this, n);

    var node = this;
    this.options = {};
    this.state = {};
    this.webClient = null;
    this.rtmClient = null;

    this.shortToken = function() {
      if (this.credentials && this.credentials.hasOwnProperty("token")) {
        var token = this.credentials.token;
        return token.substr(0, 8) + "..." + token.substr(token.length - 3);
      }
    };

    if (this.credentials && this.credentials.hasOwnProperty("token")) {
      /**
       * This can be used to pre-emptively valid API calls when check against
       * this scopes the user has
       *
       * https://api.slack.com/docs/token-types
       */
      var tokenType = null;
      if (this.credentials.token.startsWith("xoxp-")) {
        tokenType = "user";
      } else if (this.credentials.token.startsWith("xoxb-")) {
        tokenType = "bot";
      } else {
        // TODO: validate this on the config node
        tokenType = "invalid";
      }

      /**
       * meta method to refresh all state data
       */
      this.refreshState = function() {
        SlackDebug("refreshing state " + this.shortToken());
        var promises = [];
        promises.push(this.refreshTeam());
        promises.push(this.refreshChannels());
        promises.push(this.refreshMembers());
        promises.push(this.refreshBots());
        promises.push(this.refreshUser());
        promises.push(this.refreshDnd());

        return Promise.all(promises)
          .then(() => {
            this.emit("state_refreshed", {});
          })
          .catch(e => {
            this.error(e);
          });
      };

      /**
       * update local cache of channels
       */
      this.refreshChannels = function() {
        /**
         * TODO: ensure this does not happen too frequent
         * get all channels
         * https://api.slack.com/methods/conversations.list
         */
        return RecursivePaginatedWebCall(
          this.webClient,
          "conversations.list",
          "channels",
          {
            limit: 1000,
            exclude_archived: true,
            types: "public_channel,private_channel,im,mpim"
          }
        )
          .then(res => {
            var channels = {};
            res.forEach(item => {
              channels[item.id] = item;
            });
            this.state.channels = channels;
          })
          .catch(e => {
            this.error(e);
          });
      };

      /**
       * update local cache of members
       */
      this.refreshMembers = function() {
        /**
         * TODO: ensure this does not happen too frequent
         * get all members
         * https://api.slack.com/methods/users.list
         */
        return RecursivePaginatedWebCall(
          this.webClient,
          "users.list",
          "members",
          {
            limit: 1000
          }
        )
          .then(res => {
            this.state.presence = this.state.presence || {};
            var members = {};
            res.forEach(item => {
              members[item.id] = item;
            });
            this.state.members = members;
          })
          .catch(e => {
            this.error(e);
          });
      };

      /**
       * update local cache of bots
       */
      this.refreshBots = function() {
        var promises = [];
        this.state.bots = this.state.bots || {};

        for (var id in this.state.bots) {
          if (this.state.bots.hasOwnProperty(id)) {
            SlackDebug("webrequest to find bot: " + id);
            var p = this.webClient
              .apiCall("bots.info", { bot: id })
              .then(res => {
                this.state.bots[id] = res.bot;
              })
              .catch(e => {
                this.error(e);
              });

            promises.push(p);
          }
        }

        return Promise.all(promises)
          .then(() => {
            // TODO: update any internal flags?
          })
          .catch(e => {
            this.error(e);
          });
      };

      /**
       * update local cache of team
       */
      this.refreshTeam = function() {
        /**
         * TODO: ensure this does not happen too frequent
         * https://api.slack.com/methods/team.info
         */
        return this.webClient
          .apiCall("team.info")
          .then(res => {
            this.state.team = res.team;
          })
          .catch(e => {
            this.error(e);
          });
      };

      /**
       * update local cache of identity
       */
      this.refreshUser = function() {
        /**
         * TODO: ensure this does not happen too frequent
         * https://api.slack.com/methods/users.identity
         */
        if (this.state.connection.self.id) {
          return this.webClient
            .apiCall("users.identity", { user: this.state.connection.self.id })
            .then(res => {
              this.state.user = res.user;
            })
            .catch(e => {
              //this.error(e);
            });
        }
      };

      /**
       * update local cache of dnd
       */
      this.refreshDnd = function() {
        return this.webClient
          .apiCall("dnd.teamInfo")
          .then(res => {
            this.state.dnd = res.users;
          })
          .catch(e => {
            //this.error(e);
          });
      };

      /**
       * lookup member based off of id
       */
      this.findMemberById = function(id) {
        SlackDebug("looking up member: " + id);
        this.state.members = this.state.members || {};
        return this.state.members[id];
      };

      /**
       * lookup member based off of name or @name syntax
       */
      this.findMemberByName = function(name) {
        SlackDebug("looking up member: " + name);
        this.state.members = this.state.members || {};
        for (var id in this.state.members) {
          if (this.state.members.hasOwnProperty(id)) {
            if (
              this.state.members[id].deleted === false &&
              this.state.members[id].name == name.replace(/^@/, "")
            ) {
              return this.state.members[id];
            }
          }
        }
      };

      /**
       * lookup channel based off of id
       */
      this.findChannelById = function(id) {
        SlackDebug("looking up channel: " + id);
        this.state.channels = this.state.channels || {};
        return this.state.channels[id];
      };

      /**
       * channels start with:
       *  C - Conference (normal rooms)
       *  G - Group (multiuser, and private channels)
       *  D - Direct (user to user)
       */
      this.findChannelByName = function(name, type = null) {
        SlackDebug("looking up channel: " + name);
        this.state.channels = this.state.channels || {};

        /**
         * this purposely does a lookup on both regardless to handle
         * use-cases where the # or @ is ommitted completely
         */
        var directChannel = null;
        var roomChannel = null;

        // do lockup by member name
        var member = this.findMemberByName(name);
        if (member) {
          for (var id in this.state.channels) {
            if (this.state.channels.hasOwnProperty(id)) {
              if (
                this.state.channels[id].id[0] == "D" &&
                this.state.channels[id].is_user_deleted === false &&
                this.state.channels[id].user == member.id
              ) {
                directChannel = this.state.channels[id];
                break;
              }
            }
          }
        }

        // do lookup by channel name
        for (var id in this.state.channels) {
          if (this.state.channels.hasOwnProperty(id)) {
            if (
              this.state.channels[id].is_archived === false &&
              this.state.channels[id].name == name.replace(/^#/, "")
            ) {
              roomChannel = this.state.channels[id];
              break;
            }
          }
        }

        if (name[0] == "@") {
          return directChannel;
        } else if (name[0] == "#") {
          return roomChannel;
        } else {
          return directChannel || roomChannel;
        }
      };

      /**
       * lookup bot based off of id
       */
      this.findBotById = function(id) {
        SlackDebug("looking up bot: " + id);
        this.state.bots = this.state.bots || {};
        var bot = this.state.bots[id];

        if (!bot) {
          SlackDebug("webrequest to find bot: " + id);
          this.webClient
            .apiCall("bots.info", { bot: id })
            .then(res => {
              this.state.bots[id] = res.bot;
            })
            .catch(e => {
              //ignore
            });
        }

        return bot;
      };

      /**
       * Takes raw responses from RTM/Web API and adds additional properties
       * to help looking up names/etc as msg flows through nodered
       */
      this.dressResponseMessage = function(res) {
        var node = this;
        var depth = 0;
        function recurse(data) {
          depth++;
          for (var key in data) {
            if (data.hasOwnProperty(key)) {
              var value = data[key];

              /**
               * prevent infinite recursion by only going 2 deep
               */
              if (typeof value === "object" && depth < 2) {
                recurse(value);
                continue;
              }

              /**
               * dress users, channels, teams, etc
               */
              if (key.includes("user")) {
                if (typeof value === "string" || value instanceof String) {
                  if (!data.hasOwnProperty(key + "Object")) {
                    var member = node.findMemberById(value);
                    if (member) {
                      data[key + "Object"] = member;
                    }
                  }
                }
              } else if (key.includes("channel")) {
                if (typeof value === "string" || value instanceof String) {
                  if (!data.hasOwnProperty(key + "Object")) {
                    var channel = node.findChannelById(value);
                    if (channel) {
                      data[key + "Object"] = channel;
                    }
                  }
                }
              } else if (key.includes("team")) {
                if (typeof value === "string" || value instanceof String) {
                  if (!data.hasOwnProperty(key + "Object")) {
                    if (value == node.state.team.id)
                      data[key + "Object"] = node.state.team;
                  }
                }
              } else if (key.includes("bot")) {
                if (typeof value === "string" || value instanceof String) {
                  if (!data.hasOwnProperty(key + "Object")) {
                    var bot = node.findBotById(value);
                    if (bot) {
                      data[key + "Object"] = bot;
                    }
                  }
                }
              }
            }
          }

          --depth;
        }

        try {
          recurse(res);
        } catch (e) {
          node.error(e);
        }

        return res;
      };

      /**
       * https://api.slack.com/methods
       * https://slack.dev/node-slack-sdk/web_api
       * https://github.com/slackapi/node-slack-sdk/blob/master/src/WebClient.ts
       */
      this.webClient = new WebClient(this.credentials.token);

      /**
       * https://slack.dev/node-slack-sdk/rtm_api
       * https://slack.dev/node-slack-sdk/rtm_api#lifecycle-events
       * https://api.slack.com/rtm#events
       * https://github.com/slackapi/node-slack-sdk/blob/master/src/RTMClient.ts
       *
       * by default autoReconnect=true and keepAlive settings are enabled
       */
      this.rtmClient = new RTMClient(this.credentials.token, {
        logLevel: process.env.SLACK_DEBUG ? "debug" : "info",
        retryConfig: {
          forever: true,
          minTimeout: 1 * 1000,
          maxTimeout: 5 * 1000
        }
      });

      this.rtmClient.on("disconnected", e => {
        SlackDebug("disconnected " + this.shortToken(), e);
        this.log(
          RED._("node-red:common.status.disconnected") +
            " from slack with token: " +
            this.shortToken()
        );
        clearInterval(this.refreshIntervalId);
      });

      this.rtmClient.on("connecting", () => {
        SlackDebug("connecting " + this.shortToken());
        clearInterval(this.refreshIntervalId);
      });

      this.rtmClient.on("authenticated", () => {
        SlackDebug("authenticated " + this.shortToken());
      });

      this.rtmClient.on("connected", () => {
        SlackDebug("connected " + this.shortToken());
        this.log(
          RED._("node-red:common.status.connected") +
            " to slack with token: " +
            this.shortToken()
        );
        // what is worse, no data or potentially stale data?
        this.state.presence = {};

        this.refreshState()
          .then(() => {
            this.emit("state_initialized", {});
          })
          .catch(e => {
            this.error(e);
          });

        // TODO: make this a node configurable item
        var interval = 10 * 60 * 1000;
        this.refreshIntervalId = setInterval(function() {
          node.refreshState();
        }, interval);

        this.on("close", () => {
          clearInterval(this.refreshIntervalId);
        });
      });

      this.rtmClient.on("ready", () => {
        SlackDebug("ready " + this.shortToken());
      });

      this.rtmClient.on("disconnecting", () => {
        SlackDebug("disconnecting " + this.shortToken());
        clearInterval(this.refreshIntervalId);
      });

      /**
       * if connectivity is dropped we go from connected -> reconnecting
       * directly bypassing disconnected
       */
      this.rtmClient.on("reconnecting", () => {
        SlackDebug("reconnecting " + this.shortToken());
        this.log(
          RED._("node-red:common.status.disconnected") +
            " from slack with token: " +
            this.shortToken()
        );
        clearInterval(this.refreshIntervalId);
      });

      this.rtmClient.on("error", e => {
        SlackDebug("error " + this.shortToken(), e);
      });

      this.rtmClient.on("unable_to_rtm_start", e => {
        SlackDebug("unable_to_rtm_start " + this.shortToken(), e);
      });

      /**
       * This is meant to keep state current
       *  - bots
       *  - members
       *  - channels
       *  - team
       *  - dnd
       *  - presence (only listen to what the user has subscribed to manually)
       */
      this.rtmClient.on("slack_event", (eventType, event) => {
        switch (eventType) {
          case "user_change":
          case "team_join":
            this.state.members = this.state.members || {};
            this.state.members[event.user.id] = event.user;
            break;
          case "bot_added":
          case "bot_changed":
            this.state.bots = this.state.bots || {};
            this.state.bots[event.bot.id] = event.bot;
            break;
          case "channel_deleted":
          case "group_deleted":
            this.state.channels = this.state.channels || {};
            delete this.state.channels[event.channel];
            break;
          case "channel_created":
          case "channel_joined":
          case "channel_rename":
          case "group_joined":
          case "group_rename":
          case "im_created":
            this.webClient
              .apiCall("channels.info", { channel: event.channel.id })
              .then(res => {
                this.state.channels = this.state.channels || {};
                this.state.channels[res.channel.id] = res.channel;
              })
              .catch(e => {
                console.log("failed getting channel info", e);
              });
            break;
          case "channel_archive":
          case "channel_left":
          case "channel_unarchive":
          case "group_archive":
          case "group_close":
          case "group_left":
          case "group_open":
          case "group_unarchive":
          case "im_close":
          case "im_open":
          case "member_joined_channel":
          case "member_left_channel":
            this.webClient
              .apiCall("channels.info", { channel: event.channel })
              .then(res => {
                this.state.channels = this.state.channels || {};
                this.state.channels[res.channel.id] = res.channel;
              })
              .catch(e => {
                console.log("failed getting channel info", e);
              });
            break;
          case "presence_change":
            this.state.presence = this.state.presence || {};
            if (event.user) {
              var res = {
                type: event.type,
                presence: event.presence,
                user: event.user
              };
              this.state.presence[event.user] = res;
            }

            if (event.users) {
              event.users.forEach(user => {
                var res = {
                  type: event.type,
                  presence: event.presence,
                  user: user
                };
                this.state.presence[user] = res;
              });
            }
            break;
          case "manual_presence_change":
            this.state.presence = this.state.presence || {};
            this.this.state.presence[this.state.connection.self.id] = {
              type: event.type,
              presence: event.presence,
              user: this.state.connection.self.id
            };
            break;
          case "dnd_updated":
          case "dnd_updated_user":
            this.state.dnd = this.state.dnd || {};
            this.state.dnd[event.user] = event.dnd_status;
            break;
          case "team_rename":
          case "team_domain_change":
          case "team_plan_change":
          case "team_pref_change":
          case "team_profile_change":
          case "team_profile_delete":
          case "team_profile_reorder":
            this.webClient
              .apiCall("team.info")
              .then(res => {
                this.state.team = res.team;
              })
              .catch(e => {
                console.log("failed getting team info", e);
              });
            break;
          default:
            // do nothing
            break;
        }
      });

      this.startClient = function() {
        /**
         * may consider using the .connect() method instead
         * https://api.slack.com/methods/rtm.start
         * https://api.slack.com/methods/rtm.connect
         */
        this.rtmClient
          .start()
          .then(res => {
            SlackDebug("start result " + this.shortToken(), res);
            this.state.connection = {};
            this.state.connection.self = res.self;
            this.state.connection.url = res.url;
            this.state.connection.scopes = res.scopes;
            this.state.connection.acceptedScopes = res.acceptedScopes;
          })
          .catch(e => {
            console.log("failed starting slack session", e);
          });
      };
      this.startClient();

      //start watchdog
      this.connectionWatchdog = function() {
        //var interval = (5 * 60 * 1000);
        var interval = 15 * 1000;
        var lastState = null;
        var stateAt = null;
        var maxUnconnectedStateSeconds = 30;
        var intervalId = setInterval(() => {
          SlackDebug("watchdog connection details", {
            connected: this.rtmClient.connected,
            currentState: this.rtmClient.stateMachine.getCurrentState(),
            currentStateAt: stateAt
          });

          if (lastState === null) {
            lastState = this.rtmClient.stateMachine.getCurrentState();
            stateAt = Date.now();
          }

          if (
            !this.rtmClient.connected &&
            lastState == this.rtmClient.stateMachine.getCurrentState() &&
            (Date.now() - stateAt) / 1000 > maxUnconnectedStateSeconds
          ) {
            console.log(
              "sitting in " +
                lastState +
                " for longer than " +
                maxUnconnectedStateSeconds +
                " seconds"
            );
            //console.log("slack watchdog attempting to force reconnect");
            //console.log(this.rtmClient);
          }

          if (lastState != this.rtmClient.stateMachine.getCurrentState()) {
            stateAt = Date.now();
          }
          lastState = this.rtmClient.stateMachine.getCurrentState();
        }, interval);

        this.on("close", () => {
          clearInterval(intervalId);
        });
      };
      //this.connectionWatchdog();

      this.on("close", done => {
        /**
         * attempt a disconnect regardless of current state
         * and simply catch invalid transitions etc
         */
        this.rtmClient
          .disconnect()
          .then(() => {
            this.rtmClient.removeAllListeners();
            done();
          })
          .catch(e => {
            done();
          });
      });
    }
  }