forked from slackapi/java-slack-sdk
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Jakarta EE compatible Socket Mode client ref: slackapi#919
- Loading branch information
Showing
50 changed files
with
4,942 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>com.slack.api</groupId> | ||
<artifactId>slack-sdk-parent</artifactId> | ||
<version>1.41.1-SNAPSHOT</version> | ||
</parent> | ||
|
||
<properties> | ||
<tyrus-standalone-client.version>2.2.0</tyrus-standalone-client.version> | ||
<jakarta.websocket-api.version>2.2.0</jakarta.websocket-api.version> | ||
</properties> | ||
|
||
<artifactId>bolt-jakarta-socket-mode</artifactId> | ||
<version>1.41.1-SNAPSHOT</version> | ||
<packaging>jar</packaging> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.slack.api</groupId> | ||
<artifactId>slack-api-model</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.slack.api</groupId> | ||
<artifactId>slack-api-client</artifactId> | ||
<version>${project.version}</version> | ||
<exclusions> | ||
<exclusion> | ||
<groupId>javax.websocket</groupId> | ||
<artifactId>javax.websocket-api</artifactId> | ||
</exclusion> | ||
<exclusion> | ||
<groupId>org.glassfish.tyrus.bundles</groupId> | ||
<artifactId>tyrus-standalone-client</artifactId> | ||
</exclusion> | ||
</exclusions> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.slack.api</groupId> | ||
<artifactId>slack-jakarta-socket-mode-client</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.slack.api</groupId> | ||
<artifactId>slack-app-backend</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.slack.api</groupId> | ||
<artifactId>bolt</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>jakarta.websocket</groupId> | ||
<artifactId>jakarta.websocket-client-api</artifactId> | ||
<version>${jakarta.websocket-api.version}</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.glassfish.tyrus.bundles</groupId> | ||
<artifactId>tyrus-standalone-client</artifactId> | ||
<version>${tyrus-standalone-client.version}</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>org.eclipse.jetty</groupId> | ||
<artifactId>jetty-servlet</artifactId> | ||
<version>${jetty-for-tests.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.eclipse.jetty</groupId> | ||
<artifactId>jetty-server</artifactId> | ||
<version>${jetty-for-tests.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.eclipse.jetty</groupId> | ||
<artifactId>jetty-webapp</artifactId> | ||
<version>${jetty-for-tests.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.eclipse.jetty.websocket</groupId> | ||
<artifactId>websocket-server</artifactId> | ||
<version>${jetty-for-tests.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
212 changes: 212 additions & 0 deletions
212
...karta-socket-mode/src/main/java/com/slack/api/bolt/jakarta_socket_mode/SocketModeApp.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
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<SocketModeClient> clientFactory; | ||
private SocketModeClient client; | ||
|
||
private static final Function<ErrorContext, Response> 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<String, Object> 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<String, Object> 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<SocketModeClient> buildSocketModeClientFactory( | ||
App app, | ||
String appToken, | ||
Function<ErrorContext, Response> 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<ErrorContext, Response> errorHandler, | ||
App app | ||
) throws IOException { | ||
this(buildSocketModeClientFactory(app, appToken, errorHandler), app); | ||
} | ||
|
||
public SocketModeApp( | ||
String appToken, | ||
App app, | ||
Function<ErrorContext, Response> errorHandler | ||
) throws IOException { | ||
this(buildSocketModeClientFactory(app, appToken, errorHandler), app); | ||
} | ||
|
||
public SocketModeApp(Supplier<SocketModeClient> 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. | ||
* | ||
* <code> | ||
* SocketModeClient client = JakartaSocketModeClientFactory.create(appToken); | ||
* SocketModeApp socketModeApp = new SocketModeApp(client, app); | ||
* </code> | ||
*/ | ||
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; | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
...akarta-socket-mode/src/main/java/com/slack/api/bolt/jakarta_socket_mode/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/** | ||
* Built-in Socket Mode adapter supports. | ||
*/ | ||
package com.slack.api.bolt.jakarta_socket_mode; |
17 changes: 17 additions & 0 deletions
17
...-mode/src/main/java/com/slack/api/bolt/jakarta_socket_mode/request/SocketModeRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.slack.api.bolt.jakarta_socket_mode.request; | ||
|
||
import com.slack.api.bolt.request.Request; | ||
import com.slack.api.socket_mode.request.SocketModeEnvelope; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Data | ||
@AllArgsConstructor | ||
@NoArgsConstructor | ||
@Builder | ||
public class SocketModeRequest { | ||
private SocketModeEnvelope envelope; | ||
private Request<?> boltRequest; | ||
} |
Oops, something went wrong.