com.slack.api.bolt.jakarta_socket_mode.SocketModeApp Maven / Gradle / Ivy
The newest version!
package com.slack.api.bolt.jakarta_socket_mode;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.slack.api.bolt.App;
import com.slack.api.bolt.request.Request;
import com.slack.api.bolt.response.Response;
import com.slack.api.bolt.jakarta_socket_mode.request.SocketModeRequest;
import com.slack.api.bolt.jakarta_socket_mode.request.SocketModeRequestParser;
import com.slack.api.jakarta_socket_mode.JakartaSocketModeClientFactory;
import com.slack.api.socket_mode.SocketModeClient;
import com.slack.api.socket_mode.response.AckResponse;
import com.slack.api.util.json.GsonFactory;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
@Slf4j
public class SocketModeApp {
private boolean clientStopped = true;
private final App app;
private final Supplier clientFactory;
private SocketModeClient client;
private static final Function DEFAULT_ERROR_HANDLER = (context) -> {
Exception e = context.getException();
log.error("Failed to handle a request: {}", e.getMessage(), e);
return null;
};
@Data
@Builder
public static class ErrorContext {
private Request> request;
private Exception exception;
}
// -------------------------------------------
private static void sendSocketModeResponse(
SocketModeClient client,
Gson gson,
SocketModeRequest req,
Response boltResponse
) {
if (boltResponse.getBody() != null) {
Map response = new HashMap<>();
if (boltResponse.getContentType().startsWith("application/json")) {
response.put("envelope_id", req.getEnvelope().getEnvelopeId());
response.put("payload", gson.fromJson(boltResponse.getBody(), JsonElement.class));
} else {
response.put("envelope_id", req.getEnvelope().getEnvelopeId());
Map payload = new HashMap<>();
payload.put("text", boltResponse.getBody());
response.put("payload", payload);
}
client.sendSocketModeResponse(gson.toJson(response));
} else {
client.sendSocketModeResponse(new AckResponse(req.getEnvelope().getEnvelopeId()));
}
}
private static Supplier buildSocketModeClientFactory(
App app,
String appToken,
Function errorHandler
) {
return () -> {
try {
final SocketModeClient client = JakartaSocketModeClientFactory.create(app.slack(), appToken);
final SocketModeRequestParser requestParser = new SocketModeRequestParser(app.config());
final Gson gson = GsonFactory.createSnakeCase(app.slack().getConfig());
client.addWebSocketMessageListener(message -> {
long startMillis = System.currentTimeMillis();
SocketModeRequest req = requestParser.parse(message);
if (req != null) {
try {
Response boltResponse = app.run(req.getBoltRequest());
if (boltResponse.getStatusCode() != 200) {
log.warn("Unsuccessful Bolt app execution (status: {}, body: {})",
boltResponse.getStatusCode(), boltResponse.getBody());
return;
}
sendSocketModeResponse(client, gson, req, boltResponse);
} catch (Exception e) {
ErrorContext context = ErrorContext.builder().request(req.getBoltRequest()).exception(e).build();
Response errorResponse = errorHandler.apply(context);
if (errorResponse != null) {
sendSocketModeResponse(client, gson, req, errorResponse);
}
} finally {
long spentMillis = System.currentTimeMillis() - startMillis;
log.debug("Response time: {} milliseconds", spentMillis);
}
}
});
return client;
} catch (IOException e) {
log.error("Failed to start a new Socket Mode client (error: {})", e.getMessage(), e);
return null;
}
};
}
public SocketModeApp(App app) throws IOException {
this(System.getenv("SLACK_APP_TOKEN"), app);
}
public SocketModeApp(String appToken, App app) throws IOException {
this(appToken, DEFAULT_ERROR_HANDLER, app);
}
public SocketModeApp(
String appToken,
Function errorHandler,
App app
) throws IOException {
this(buildSocketModeClientFactory(app, appToken, errorHandler), app);
}
public SocketModeApp(
String appToken,
App app,
Function errorHandler
) throws IOException {
this(buildSocketModeClientFactory(app, appToken, errorHandler), app);
}
public SocketModeApp(Supplier clientFactory, App app) {
this.clientFactory = clientFactory;
this.app = app;
}
/**
* If you would like to synchronously detect the connection error as an exception when bootstrapping,
* use this constructor. The first line can throw an exception
* in the case where either the token or network settings are valid.
*
*
* SocketModeClient client = JakartaSocketModeClientFactory.create(appToken);
* SocketModeApp socketModeApp = new SocketModeApp(client, app);
*
*/
public SocketModeApp(SocketModeClient socketModeClient, App app) {
this.client = socketModeClient;
this.clientFactory = () -> socketModeClient;
this.app = app;
}
// -------------------------------------------
public void start() throws Exception {
run(true);
}
public void startAsync() throws Exception {
run(false);
}
public void run(boolean blockCurrentThread) throws Exception {
this.app.start();
if (this.client == null) {
this.client = clientFactory.get();
}
if (this.isClientStopped()) {
this.client.connectToNewEndpoint();
} else {
this.client.connect();
}
this.client.setAutoReconnectEnabled(true);
this.clientStopped = false;
if (blockCurrentThread) {
Thread.sleep(Long.MAX_VALUE);
}
}
public void stop() throws Exception {
if (this.client != null && this.client.verifyConnection()) {
this.client.disconnect();
}
this.clientStopped = true;
this.app.stop();
}
public void close() throws Exception {
this.stop();
this.client = null;
}
// -------------------------------------------
// Accessors
// -------------------------------------------
public boolean isClientStopped() {
return clientStopped;
}
public SocketModeClient getClient() {
return client;
}
public App getApp() {
return app;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy