Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,68 @@
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

@Slf4j
public class SocketModeApp {
private boolean clientStopped = false;
private final SocketModeClient client;
private boolean clientStopped = true;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default state needs to be corrected.

private final App app;
private final Supplier<SocketModeClient> clientFactory;
private SocketModeClient client;

private static SocketModeClient buildSocketModeClient(
// -------------------------------------------

private static Supplier<SocketModeClient> buildSocketModeClientFactory(
App app,
String appToken,
SocketModeClient.Backend backend
) throws IOException {
final SocketModeClient client = app.slack().socketMode(appToken, backend);
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;
}
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);
) {
return () -> {
try {
final SocketModeClient client = app.slack().socketMode(appToken, backend);
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;
}
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()));
}
long spentMillis = System.currentTimeMillis() - startMillis;
log.debug("Response time: {} milliseconds", spentMillis);
return;
} catch (Exception e) {
log.error("Failed to handle a request: {}", e.getMessage(), e);
return;
}
client.sendSocketModeResponse(gson.toJson(response));
} else {
client.sendSocketModeResponse(new AckResponse(req.getEnvelope().getEnvelopeId()));
}
long spentMillis = System.currentTimeMillis() - startMillis;
log.debug("Response time: {} milliseconds", spentMillis);
return;
} catch (Exception e) {
log.error("Failed to handle a request: {}", e.getMessage(), e);
return;
}
});
return client;
} catch (IOException e) {
log.error("Failed to start a new Socket Mode client (error: {})", e.getMessage(), e);
return null;
}
});
return client;
};
}

public SocketModeApp(App app) throws IOException {
Expand All @@ -80,14 +91,32 @@ public SocketModeApp(
SocketModeClient.Backend backend,
App app
) throws IOException {
this(buildSocketModeClient(app, appToken, backend), app);
this(buildSocketModeClientFactory(app, appToken, backend), 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 = Slack.getInstance().socketMode(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);
}
Expand All @@ -97,21 +126,48 @@ public void startAsync() throws Exception {
}

public void run(boolean blockCurrentThread) throws Exception {
app.start();
if (clientStopped) {
client.connectToNewEndpoint();
this.app.start();
if (this.client == null) {
this.client = clientFactory.get();
Comment thread
seratch marked this conversation as resolved.
}
if (this.isClientStopped()) {
this.client.connectToNewEndpoint();
} else {
client.connect();
this.client.connect();
}
client.setAutoReconnectEnabled(true);
this.client.setAutoReconnectEnabled(true);
this.clientStopped = false;
if (blockCurrentThread) {
Thread.sleep(Long.MAX_VALUE);
}
}

public void stop() throws Exception {
client.disconnect();
clientStopped = true;
app.stop();
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;
Comment thread
seratch marked this conversation as resolved.
}

public SocketModeClient getClient() {
return client;
Comment thread
seratch marked this conversation as resolved.
}

public App getApp() {
return app;
Comment thread
seratch marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;

public class SocketModeAppTest {

Expand Down Expand Up @@ -78,6 +78,26 @@ public void payloadHandling() throws Exception {
}
}

@Test
public void issue_937_it_should_not_be_connected_before_start_method_call() throws Exception {
App app = new App(AppConfig.builder()
.slack(slack)
.singleTeamBotToken(VALID_BOT_TOKEN)
.build());
SocketModeApp socketModeApp = new SocketModeApp(VALID_APP_TOKEN, app);
try {
assertTrue(socketModeApp.isClientStopped());
assertNull(socketModeApp.getClient());

// Check again to make sure if the auto-connection does not work in this scenario
Thread.sleep(3_000L);
assertTrue(socketModeApp.isClientStopped());
assertNull(socketModeApp.getClient());
} finally {
socketModeApp.close();
}
}

// -------------------------------------------------
// JavaWebSocket implementation
// -------------------------------------------------
Expand Down