From 7c2e23cfcbea1578a671f57898616157a6d13bc5 Mon Sep 17 00:00:00 2001
From: JCMarques15 <jcnmarques@protonmail.com>
Date: Tue, 15 Oct 2024 13:41:58 +0200
Subject: [PATCH 1/3] [Add] Implement Slack Socket Mode support

- Add Slack Socket Mode handler in server.py
- Create start_socket_mode function in slack/app.py
- Add example script for testing Slack websocket handler

Introduce Slack Socket Mode support alongside existing HTTP handler.
This allows for real-time message processing using websockets when
SLACK_WEBSOCKET_TOKEN is set. The HTTP handler remains active when
only SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET are provided.
---
 backend/chainlit/server.py               | 15 +++++++++++++--
 backend/chainlit/slack/app.py            | 11 +++++++++++
 backend/chainlit/slack_websocket_test.py | 18 ++++++++++++++++++
 3 files changed, 42 insertions(+), 2 deletions(-)
 create mode 100644 backend/chainlit/slack_websocket_test.py

diff --git a/backend/chainlit/server.py b/backend/chainlit/server.py
index cfbef30ee4..373700f41d 100644
--- a/backend/chainlit/server.py
+++ b/backend/chainlit/server.py
@@ -131,6 +131,13 @@ async def watch_files_for_changes():
 
         discord_task = asyncio.create_task(client.start(discord_bot_token))
 
+    slack_task = None
+
+    # Slack Socket Handler if env variable SLACK_APP_TOKEN is set
+    if os.environ.get("SLACK_BOT_TOKEN") and os.environ.get("SLACK_WEBSOCKET_TOKEN"):
+        from chainlit.slack.app import start_socket_mode
+        slack_task = asyncio.create_task(start_socket_mode())
+
     try:
         yield
     finally:
@@ -143,6 +150,10 @@ async def watch_files_for_changes():
             if discord_task:
                 discord_task.cancel()
                 await discord_task
+
+            if slack_task:
+                slack_task.cancel()
+                await slack_task
         except asyncio.exceptions.CancelledError:
             pass
 
@@ -230,10 +241,10 @@ def get_build_dir(local_target: str, packaged_target: str) -> str:
 
 
 # -------------------------------------------------------------------------------
-#                               SLACK HANDLER
+#                               SLACK HTTP HANDLER
 # -------------------------------------------------------------------------------
 
-if os.environ.get("SLACK_BOT_TOKEN") and os.environ.get("SLACK_SIGNING_SECRET"):
+if os.environ.get("SLACK_BOT_TOKEN") and os.environ.get("SLACK_SIGNING_SECRET") and not os.environ.get("SLACK_WEBSOCKET_TOKEN"):
     from chainlit.slack.app import slack_app_handler
 
     @router.post("/slack/events")
diff --git a/backend/chainlit/slack/app.py b/backend/chainlit/slack/app.py
index 747d0af9dd..b727678dc5 100644
--- a/backend/chainlit/slack/app.py
+++ b/backend/chainlit/slack/app.py
@@ -19,6 +19,7 @@
 from chainlit.user import PersistedUser, User
 from chainlit.user_session import user_session
 from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
+from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
 from slack_bolt.async_app import AsyncApp
 
 
@@ -126,6 +127,16 @@ async def update_step(self, step_dict: StepDict):
 )
 
 
+async def start_socket_mode():
+    """
+    Initializes and starts the Slack app in Socket Mode asynchronously.
+
+    Uses the SLACK_APP_TOKEN from environment variables to authenticate.
+    """
+    handler = AsyncSocketModeHandler(slack_app, os.environ.get("SLACK_WEBSOCKET_TOKEN"))
+    await handler.start_async()
+
+
 @trace
 def init_slack_context(
     session: HTTPSession,
diff --git a/backend/chainlit/slack_websocket_test.py b/backend/chainlit/slack_websocket_test.py
new file mode 100644
index 0000000000..1df6e1e2da
--- /dev/null
+++ b/backend/chainlit/slack_websocket_test.py
@@ -0,0 +1,18 @@
+# This is a simple example to test the slack websocket handler.
+# To initiate the websocket dont forget to set the variables:
+#   - SLACK_BOT_TOKEN
+#   - SLACK_SIGNING_SECRET
+#   - SLACK_WEBSOCKET_TOKEN <- this one dictates if websocket or http handler
+
+from chainlit import Message, on_message, user_session
+
+
+@on_message
+async def main(message: Message):
+    client_type = user_session.get("client_type")
+    if client_type == "slack":
+        user_email = user_session.get("user").metadata.get("email")
+        print(f"Received a message from: {user_email}")
+        await Message(
+            content=f"Hi {user_email}, I have received the following message:\n{message.content}",
+        ).send()

From c58c1eb491ffab86d336d13b9530714384b702a0 Mon Sep 17 00:00:00 2001
From: JCMarques15 <jcnmarques@protonmail.com>
Date: Tue, 15 Oct 2024 13:52:56 +0200
Subject: [PATCH 2/3] [Fix] Correct comment for Slack socket handler env
 variable

- Update comment to mention "SLACK_WEBSOCKET_TOKEN" instead of
  "SLACK_APP_TOKEN"

Fix a typo in the comment describing the environment variable check
for the Slack socket handler. This change aligns the comment with
the actual code implementation, improving code readability and
preventing potential confusion.
---
 backend/chainlit/server.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/backend/chainlit/server.py b/backend/chainlit/server.py
index 373700f41d..7e935039b7 100644
--- a/backend/chainlit/server.py
+++ b/backend/chainlit/server.py
@@ -133,7 +133,7 @@ async def watch_files_for_changes():
 
     slack_task = None
 
-    # Slack Socket Handler if env variable SLACK_APP_TOKEN is set
+    # Slack Socket Handler if env variable SLACK_WEBSOCKET_TOKEN is set
     if os.environ.get("SLACK_BOT_TOKEN") and os.environ.get("SLACK_WEBSOCKET_TOKEN"):
         from chainlit.slack.app import start_socket_mode
         slack_task = asyncio.create_task(start_socket_mode())

From f1aa97841932e75eda1c5b3d955bbb4b9e6fd7cd Mon Sep 17 00:00:00 2001
From: JCMarques15 <jcnmarques@protonmail.com>
Date: Tue, 15 Oct 2024 13:55:51 +0200
Subject: [PATCH 3/3] [Fix] Correct Slack token reference in app.py docstring

- Update docstring to mention "SLACK_WEBSOCKET_TOKEN" instead of
  "SLACK_APP_TOKEN"

Correct the documentation in the `start_socket_mode` function to
accurately reflect the environment variable used for Slack
authentication. This change aligns the docstring with the actual
code implementation, improving clarity and preventing potential
confusion for developers.
---
 backend/chainlit/slack/app.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/backend/chainlit/slack/app.py b/backend/chainlit/slack/app.py
index b727678dc5..26b29c2cf8 100644
--- a/backend/chainlit/slack/app.py
+++ b/backend/chainlit/slack/app.py
@@ -131,7 +131,7 @@ async def start_socket_mode():
     """
     Initializes and starts the Slack app in Socket Mode asynchronously.
 
-    Uses the SLACK_APP_TOKEN from environment variables to authenticate.
+    Uses the SLACK_WEBSOCKET_TOKEN from environment variables to authenticate.
     """
     handler = AsyncSocketModeHandler(slack_app, os.environ.get("SLACK_WEBSOCKET_TOKEN"))
     await handler.start_async()