From df015c45ec757aa382384bbf95bf9d33a59d798b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marcin=20D=C4=99bski?= <m.debski@livechat.com>
Date: Wed, 10 Jan 2024 14:46:09 +0100
Subject: [PATCH 1/3] Enhanced timeouts for the WebsocketClient

---
 changelog.md                |  1 +
 livechat/utils/ws_client.py | 17 ++++++++++++-----
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/changelog.md b/changelog.md
index 62cdba7..28bb5a9 100644
--- a/changelog.md
+++ b/changelog.md
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
 ### Changed
 - Updated outdated packages.
 - Enhanced error logging for improved troubleshooting: Automatically includes response headers in the log for server errors, providing detailed information (such as x-debug-id) for more effective issue diagnosis.
+- Enhanced timeouts for the `WebsocketClient`.
 
 ### Bugfixes
 - Enabled instantiation for `CustomerRtmV36` within the 3.6 version of the Customer RTM API.
diff --git a/livechat/utils/ws_client.py b/livechat/utils/ws_client.py
index 06f938f..1f18607 100644
--- a/livechat/utils/ws_client.py
+++ b/livechat/utils/ws_client.py
@@ -32,26 +32,33 @@ def __init__(self, *args, **kwargs):
 
     def open(self,
              origin: dict = None,
-             timeout: float = 3,
+             ping_timeout: float = 3,
+             ping_interval: float = 5,
+             ws_conn_timeout: float = 10,
              keep_alive: bool = True) -> NoReturn:
         ''' Opens websocket connection and keep running forever.
             Args:
                 origin (dict): Specifies origin while creating websocket connection.
-                timeout (int or float): time [seconds] to wait for server in ping/pong frame.
+                ping_timeout (int or float): timeout (in seconds) if the pong message is not received,
+                    by default sets to 3 seconds.
+                ping_interval (int or float): automatically sends "ping" command every specified period (in seconds).
+                    If set to 0, no ping is sent periodically, by default sets to 5 seconds.
+                ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection,
+                    by default sets to 10 seconds.
                 keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`. '''
         run_forever_kwargs = {
             'sslopt': {
                 'cert_reqs': ssl.CERT_NONE
             },
             'origin': origin,
-            'ping_timeout': timeout,
-            'ping_interval': 5
+            'ping_timeout': ping_timeout,
+            'ping_interval': ping_interval,
         }
         if keep_alive:
             ping_thread = threading.Thread(target=self.run_forever,
                                            kwargs=run_forever_kwargs)
             ping_thread.start()
-            self._wait_till_sock_connected()
+            self._wait_till_sock_connected(ws_conn_timeout)
             return
         self.run_forever(**run_forever_kwargs)
 

From dfc4f8a304399063a1900b041ccef3918e94dc93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marcin=20D=C4=99bski?= <m.debski@livechat.com>
Date: Thu, 11 Jan 2024 13:40:58 +0100
Subject: [PATCH 2/3] Enhanced timeouts for the WebsocketClient part2

---
 livechat/agent/rtm/api/v33.py     | 29 +++++++++++++++++++++++++----
 livechat/agent/rtm/api/v34.py     | 29 +++++++++++++++++++++++++----
 livechat/agent/rtm/api/v35.py     | 29 +++++++++++++++++++++++++----
 livechat/agent/rtm/api/v36.py     | 23 ++++++++++++++++++++---
 livechat/agent/web/api/v33.py     |  5 +++--
 livechat/agent/web/api/v34.py     |  5 +++--
 livechat/agent/web/api/v35.py     |  5 +++--
 livechat/agent/web/api/v36.py     |  5 +++--
 livechat/agent/web/base.py        | 13 +++++++++----
 livechat/billing/api/v1.py        |  5 +++--
 livechat/billing/base.py          |  7 ++++++-
 livechat/configuration/api/v33.py |  5 +++--
 livechat/configuration/api/v34.py |  5 +++--
 livechat/configuration/api/v35.py |  5 +++--
 livechat/configuration/api/v36.py |  5 +++--
 livechat/configuration/base.py    | 13 +++++++++----
 livechat/customer/rtm/api/v33.py  | 21 ++++++++++++++++-----
 livechat/customer/rtm/api/v34.py  | 21 ++++++++++++++++-----
 livechat/customer/rtm/api/v35.py  | 21 ++++++++++++++++-----
 livechat/customer/rtm/api/v36.py  | 21 ++++++++++++++++-----
 livechat/customer/web/api/v33.py  |  5 +++--
 livechat/customer/web/api/v34.py  |  5 +++--
 livechat/customer/web/api/v35.py  |  5 +++--
 livechat/customer/web/api/v36.py  |  5 +++--
 livechat/customer/web/base.py     | 17 +++++++++++++----
 livechat/reports/api/v33.py       |  5 +++--
 livechat/reports/api/v34.py       |  5 +++--
 livechat/reports/api/v35.py       |  5 +++--
 livechat/reports/api/v36.py       |  5 +++--
 livechat/reports/base.py          | 13 +++++++++----
 30 files changed, 256 insertions(+), 86 deletions(-)

diff --git a/livechat/agent/rtm/api/v33.py b/livechat/agent/rtm/api/v33.py
index 028feff..4a9766e 100644
--- a/livechat/agent/rtm/api/v33.py
+++ b/livechat/agent/rtm/api/v33.py
@@ -14,9 +14,26 @@ class AgentRtmV33:
     def __init__(self, url: str):
         self.ws = WebsocketClient(url=f'wss://{url}/v3.3/agent/rtm/ws')
 
-    def open_connection(self) -> None:
-        ''' Opens WebSocket connection. '''
-        self.ws.open()
+    def open_connection(self,
+                        origin: dict = None,
+                        ping_timeout: float = 3,
+                        ping_interval: float = 5,
+                        ws_conn_timeout: float = 10,
+                        keep_alive: bool = True) -> None:
+        ''' Opens WebSocket connection.
+
+            Args:
+                origin (dict): Specifies origin while creating websocket connection.
+                ping_timeout (int or float): timeout (in seconds) if the pong message is not received,
+                    by default sets to 3 seconds.
+                ping_interval (int or float): automatically sends "ping" command every specified period (in seconds).
+                    If set to 0, no ping is sent periodically, by default sets to 5 seconds.
+                ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection,
+                    by default sets to 10 seconds.
+                keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`.
+        '''
+        self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout,
+                     keep_alive)
 
     def close_connection(self) -> None:
         ''' Closes WebSocket connection. '''
@@ -378,7 +395,11 @@ def send_event(self,
             opts['author_id'] = author_id
         if payload is None:
             payload = prepare_payload(locals())
-        return self.ws.send({'action': 'send_event', 'payload': payload, **opts})
+        return self.ws.send({
+            'action': 'send_event',
+            'payload': payload,
+            **opts
+        })
 
     def send_rich_message_postback(self,
                                    chat_id: str = None,
diff --git a/livechat/agent/rtm/api/v34.py b/livechat/agent/rtm/api/v34.py
index 1fca262..7c24903 100644
--- a/livechat/agent/rtm/api/v34.py
+++ b/livechat/agent/rtm/api/v34.py
@@ -14,9 +14,26 @@ class AgentRtmV34:
     def __init__(self, url: str):
         self.ws = WebsocketClient(url=f'wss://{url}/v3.4/agent/rtm/ws')
 
-    def open_connection(self) -> None:
-        ''' Opens WebSocket connection. '''
-        self.ws.open()
+    def open_connection(self,
+                        origin: dict = None,
+                        ping_timeout: float = 3,
+                        ping_interval: float = 5,
+                        ws_conn_timeout: float = 10,
+                        keep_alive: bool = True) -> None:
+        ''' Opens WebSocket connection.
+
+            Args:
+                origin (dict): Specifies origin while creating websocket connection.
+                ping_timeout (int or float): timeout (in seconds) if the pong message is not received,
+                    by default sets to 3 seconds.
+                ping_interval (int or float): automatically sends "ping" command every specified period (in seconds).
+                    If set to 0, no ping is sent periodically, by default sets to 5 seconds.
+                ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection,
+                    by default sets to 10 seconds.
+                keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`.
+        '''
+        self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout,
+                     keep_alive)
 
     def close_connection(self) -> None:
         ''' Closes WebSocket connection. '''
@@ -344,7 +361,11 @@ def send_event(self,
             opts['author_id'] = author_id
         if payload is None:
             payload = prepare_payload(locals())
-        return self.ws.send({'action': 'send_event', 'payload': payload, **opts})
+        return self.ws.send({
+            'action': 'send_event',
+            'payload': payload,
+            **opts
+        })
 
     def send_rich_message_postback(self,
                                    chat_id: str = None,
diff --git a/livechat/agent/rtm/api/v35.py b/livechat/agent/rtm/api/v35.py
index 7ee5934..76cb371 100644
--- a/livechat/agent/rtm/api/v35.py
+++ b/livechat/agent/rtm/api/v35.py
@@ -14,9 +14,26 @@ class AgentRtmV35:
     def __init__(self, url: str):
         self.ws = WebsocketClient(url=f'wss://{url}/v3.5/agent/rtm/ws')
 
-    def open_connection(self) -> None:
-        ''' Opens WebSocket connection. '''
-        self.ws.open()
+    def open_connection(self,
+                        origin: dict = None,
+                        ping_timeout: float = 3,
+                        ping_interval: float = 5,
+                        ws_conn_timeout: float = 10,
+                        keep_alive: bool = True) -> None:
+        ''' Opens WebSocket connection.
+
+            Args:
+                origin (dict): Specifies origin while creating websocket connection.
+                ping_timeout (int or float): timeout (in seconds) if the pong message is not received,
+                    by default sets to 3 seconds.
+                ping_interval (int or float): automatically sends "ping" command every specified period (in seconds).
+                    If set to 0, no ping is sent periodically, by default sets to 5 seconds.
+                ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection,
+                    by default sets to 10 seconds.
+                keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`.
+        '''
+        self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout,
+                     keep_alive)
 
     def close_connection(self) -> None:
         ''' Closes WebSocket connection. '''
@@ -344,7 +361,11 @@ def send_event(self,
             opts['author_id'] = author_id
         if payload is None:
             payload = prepare_payload(locals())
-        return self.ws.send({'action': 'send_event', 'payload': payload, **opts})
+        return self.ws.send({
+            'action': 'send_event',
+            'payload': payload,
+            **opts
+        })
 
     def send_rich_message_postback(self,
                                    chat_id: str = None,
diff --git a/livechat/agent/rtm/api/v36.py b/livechat/agent/rtm/api/v36.py
index c253460..7885997 100644
--- a/livechat/agent/rtm/api/v36.py
+++ b/livechat/agent/rtm/api/v36.py
@@ -14,9 +14,26 @@ class AgentRtmV36:
     def __init__(self, url: str):
         self.ws = WebsocketClient(url=f'wss://{url}/v3.6/agent/rtm/ws')
 
-    def open_connection(self) -> None:
-        ''' Opens WebSocket connection. '''
-        self.ws.open()
+    def open_connection(self,
+                        origin: dict = None,
+                        ping_timeout: float = 3,
+                        ping_interval: float = 5,
+                        ws_conn_timeout: float = 10,
+                        keep_alive: bool = True) -> None:
+        ''' Opens WebSocket connection.
+
+            Args:
+                origin (dict): Specifies origin while creating websocket connection.
+                ping_timeout (int or float): timeout (in seconds) if the pong message is not received,
+                    by default sets to 3 seconds.
+                ping_interval (int or float): automatically sends "ping" command every specified period (in seconds).
+                    If set to 0, no ping is sent periodically, by default sets to 5 seconds.
+                ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection,
+                    by default sets to 10 seconds.
+                keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`.
+        '''
+        self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout,
+                     keep_alive)
 
     def close_connection(self) -> None:
         ''' Closes WebSocket connection. '''
diff --git a/livechat/agent/web/api/v33.py b/livechat/agent/web/api/v33.py
index 3b5d840..0ca4c13 100644
--- a/livechat/agent/web/api/v33.py
+++ b/livechat/agent/web/api/v33.py
@@ -19,9 +19,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(access_token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.3/agent/action'
 
     # Chats
diff --git a/livechat/agent/web/api/v34.py b/livechat/agent/web/api/v34.py
index 61d4dac..03b756b 100644
--- a/livechat/agent/web/api/v34.py
+++ b/livechat/agent/web/api/v34.py
@@ -21,9 +21,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(access_token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.4/agent/action'
 
     # Chats
diff --git a/livechat/agent/web/api/v35.py b/livechat/agent/web/api/v35.py
index 1538cba..6196f78 100644
--- a/livechat/agent/web/api/v35.py
+++ b/livechat/agent/web/api/v35.py
@@ -21,9 +21,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(access_token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.5/agent/action'
 
     # Chats
diff --git a/livechat/agent/web/api/v36.py b/livechat/agent/web/api/v36.py
index 1a014b3..e25e242 100644
--- a/livechat/agent/web/api/v36.py
+++ b/livechat/agent/web/api/v36.py
@@ -21,9 +21,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(access_token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.6/agent/action'
 
     # Chats
diff --git a/livechat/agent/web/base.py b/livechat/agent/web/base.py
index 6f181a8..18f1eb0 100644
--- a/livechat/agent/web/base.py
+++ b/livechat/agent/web/base.py
@@ -5,6 +5,8 @@
 
 from typing import Union
 
+import httpx
+
 from livechat.agent.web.api.v33 import AgentWebV33
 from livechat.agent.web.api.v34 import AgentWebV34
 from livechat.agent.web.api.v35 import AgentWebV35
@@ -27,6 +29,7 @@ def get_client(
         proxies: dict = None,
         verify: bool = True,
         disable_logging: bool = False,
+        timeout: float = httpx.Timeout(15)
     ) -> Union[AgentWebV33, AgentWebV34, AgentWebV35, AgentWebV36]:
         ''' Returns client for specific API version.
 
@@ -43,6 +46,8 @@ def get_client(
                                a path to an SSL certificate file, an `ssl.SSLContext`, or `False`
                                (which will disable verification). Defaults to `True`.
                 disable_logging (bool): indicates if logging should be disabled.
+                timeout (float): The timeout configuration to use when sending requests.
+                                 Defaults to 15 seconds.
 
             Returns:
                 API client object for specified version.
@@ -53,16 +58,16 @@ def get_client(
         client = {
             '3.3':
             AgentWebV33(access_token, base_url, http2, proxies, verify,
-                        disable_logging),
+                        disable_logging, timeout),
             '3.4':
             AgentWebV34(access_token, base_url, http2, proxies, verify,
-                        disable_logging),
+                        disable_logging, timeout),
             '3.5':
             AgentWebV35(access_token, base_url, http2, proxies, verify,
-                        disable_logging),
+                        disable_logging, timeout),
             '3.6':
             AgentWebV36(access_token, base_url, http2, proxies, verify,
-                        disable_logging),
+                        disable_logging, timeout),
         }.get(version)
         if not client:
             raise ValueError('Provided version does not exist.')
diff --git a/livechat/billing/api/v1.py b/livechat/billing/api/v1.py
index 9b8ca22..b2a19ee 100644
--- a/livechat/billing/api/v1.py
+++ b/livechat/billing/api/v1.py
@@ -14,9 +14,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v1'
 
     # direct_charge
diff --git a/livechat/billing/base.py b/livechat/billing/base.py
index 00cf548..a54390d 100644
--- a/livechat/billing/base.py
+++ b/livechat/billing/base.py
@@ -5,6 +5,8 @@
 
 from __future__ import annotations
 
+import httpx
+
 from livechat.config import CONFIG
 
 from .api import BillingApiV1
@@ -25,6 +27,7 @@ def get_client(
         proxies: dict = None,
         verify: bool = True,
         disable_logging: bool = False,
+        timeout: float = httpx.Timeout(15)
     ) -> BillingApiV1:
         ''' Returns client for specific Billing API version.
 
@@ -41,6 +44,8 @@ def get_client(
                                a path to an SSL certificate file, an `ssl.SSLContext`, or `False`
                                (which will disable verification). Defaults to `True`.
                 disable_logging (bool): indicates if logging should be disabled.
+                timeout (float): The timeout configuration to use when sending requests.
+                                 Defaults to 15 seconds.
 
             Returns:
                 BillingApi: API client object for specified version.
@@ -51,7 +56,7 @@ def get_client(
         client = {
             '1':
             BillingApiV1(token, base_url, http2, proxies, verify,
-                         disable_logging),
+                         disable_logging, timeout),
         }.get(version)
         if not client:
             raise ValueError('Provided version does not exist.')
diff --git a/livechat/configuration/api/v33.py b/livechat/configuration/api/v33.py
index 0829a5c..9efec0c 100644
--- a/livechat/configuration/api/v33.py
+++ b/livechat/configuration/api/v33.py
@@ -14,9 +14,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.3/configuration/action'
 
 # Agents
diff --git a/livechat/configuration/api/v34.py b/livechat/configuration/api/v34.py
index 19764bc..04c3461 100644
--- a/livechat/configuration/api/v34.py
+++ b/livechat/configuration/api/v34.py
@@ -14,9 +14,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.4/configuration/action'
 
 # Agents
diff --git a/livechat/configuration/api/v35.py b/livechat/configuration/api/v35.py
index c214b54..1e3d454 100644
--- a/livechat/configuration/api/v35.py
+++ b/livechat/configuration/api/v35.py
@@ -16,9 +16,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.5/configuration/action'
 
 # Agents
diff --git a/livechat/configuration/api/v36.py b/livechat/configuration/api/v36.py
index 1a0b33e..5a72e2b 100644
--- a/livechat/configuration/api/v36.py
+++ b/livechat/configuration/api/v36.py
@@ -18,9 +18,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.6/configuration/action'
 
 # Agents
diff --git a/livechat/configuration/base.py b/livechat/configuration/base.py
index bf6e45c..9b95faa 100644
--- a/livechat/configuration/base.py
+++ b/livechat/configuration/base.py
@@ -6,6 +6,8 @@
 
 from typing import Union
 
+import httpx
+
 from livechat.config import CONFIG
 from livechat.configuration.api.v33 import ConfigurationApiV33
 from livechat.configuration.api.v34 import ConfigurationApiV34
@@ -28,6 +30,7 @@ def get_client(
         proxies: dict = None,
         verify: bool = True,
         disable_logging: bool = False,
+        timeout: float = httpx.Timeout(15)
     ) -> Union[ConfigurationApiV33, ConfigurationApiV34, ConfigurationApiV35,
                ConfigurationApiV36]:
         ''' Returns client for specific Configuration API version.
@@ -45,6 +48,8 @@ def get_client(
                                a path to an SSL certificate file, an `ssl.SSLContext`, or `False`
                                (which will disable verification). Defaults to `True`.
                 disable_logging (bool): indicates if logging should be disabled.
+                timeout (float): The timeout configuration to use when sending requests.
+                                 Defaults to 15 seconds.
 
             Returns:
                 ConfigurationApi: API client object for specified version.
@@ -55,16 +60,16 @@ def get_client(
         client = {
             '3.3':
             ConfigurationApiV33(token, base_url, http2, proxies, verify,
-                                disable_logging),
+                                disable_logging, timeout),
             '3.4':
             ConfigurationApiV34(token, base_url, http2, proxies, verify,
-                                disable_logging),
+                                disable_logging, timeout),
             '3.5':
             ConfigurationApiV35(token, base_url, http2, proxies, verify,
-                                disable_logging),
+                                disable_logging, timeout),
             '3.6':
             ConfigurationApiV36(token, base_url, http2, proxies, verify,
-                                disable_logging),
+                                disable_logging, timeout),
         }.get(version)
         if not client:
             raise ValueError('Provided version does not exist.')
diff --git a/livechat/customer/rtm/api/v33.py b/livechat/customer/rtm/api/v33.py
index a6929fe..b877b23 100644
--- a/livechat/customer/rtm/api/v33.py
+++ b/livechat/customer/rtm/api/v33.py
@@ -20,15 +20,26 @@ def __init__(self, license_id: str, base_url: str):
                 f'Provided `license_id` (`{license_id}`) seems invalid. Websocket connection may not open.'
             )
 
-    def open_connection(self, origin: dict = None) -> None:
+    def open_connection(self,
+                        origin: dict = None,
+                        ping_timeout: float = 3,
+                        ping_interval: float = 5,
+                        ws_conn_timeout: float = 10,
+                        keep_alive: bool = True) -> None:
         ''' Opens WebSocket connection.
+
             Args:
                 origin (dict): Specifies origin while creating websocket connection.
+                ping_timeout (int or float): timeout (in seconds) if the pong message is not received,
+                    by default sets to 3 seconds.
+                ping_interval (int or float): automatically sends "ping" command every specified period (in seconds).
+                    If set to 0, no ping is sent periodically, by default sets to 5 seconds.
+                ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection,
+                    by default sets to 10 seconds.
+                keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`.
         '''
-        if origin:
-            self.ws.open(origin=origin)
-        else:
-            self.ws.open()
+        self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout,
+                     keep_alive)
 
     def close_connection(self) -> None:
         ''' Closes WebSocket connection. '''
diff --git a/livechat/customer/rtm/api/v34.py b/livechat/customer/rtm/api/v34.py
index b0d3edb..b61a2e3 100644
--- a/livechat/customer/rtm/api/v34.py
+++ b/livechat/customer/rtm/api/v34.py
@@ -20,15 +20,26 @@ def __init__(self, organization_id: str, base_url: str):
                 f'Provided `organization_id` (`{organization_id}`) seems invalid. Websocket connection may not open.'
             )
 
-    def open_connection(self, origin: dict = None) -> None:
+    def open_connection(self,
+                        origin: dict = None,
+                        ping_timeout: float = 3,
+                        ping_interval: float = 5,
+                        ws_conn_timeout: float = 10,
+                        keep_alive: bool = True) -> None:
         ''' Opens WebSocket connection.
+
             Args:
                 origin (dict): Specifies origin while creating websocket connection.
+                ping_timeout (int or float): timeout (in seconds) if the pong message is not received,
+                    by default sets to 3 seconds.
+                ping_interval (int or float): automatically sends "ping" command every specified period (in seconds).
+                    If set to 0, no ping is sent periodically, by default sets to 5 seconds.
+                ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection,
+                    by default sets to 10 seconds.
+                keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`.
         '''
-        if origin:
-            self.ws.open(origin=origin)
-        else:
-            self.ws.open()
+        self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout,
+                     keep_alive)
 
     def close_connection(self) -> None:
         ''' Closes WebSocket connection. '''
diff --git a/livechat/customer/rtm/api/v35.py b/livechat/customer/rtm/api/v35.py
index 749befe..563b3d0 100644
--- a/livechat/customer/rtm/api/v35.py
+++ b/livechat/customer/rtm/api/v35.py
@@ -20,15 +20,26 @@ def __init__(self, organization_id: str, base_url: str):
                 f'Provided `organization_id` (`{organization_id}`) seems invalid. Websocket connection may not open.'
             )
 
-    def open_connection(self, origin: dict = None) -> None:
+    def open_connection(self,
+                        origin: dict = None,
+                        ping_timeout: float = 3,
+                        ping_interval: float = 5,
+                        ws_conn_timeout: float = 10,
+                        keep_alive: bool = True) -> None:
         ''' Opens WebSocket connection.
+
             Args:
                 origin (dict): Specifies origin while creating websocket connection.
+                ping_timeout (int or float): timeout (in seconds) if the pong message is not received,
+                    by default sets to 3 seconds.
+                ping_interval (int or float): automatically sends "ping" command every specified period (in seconds).
+                    If set to 0, no ping is sent periodically, by default sets to 5 seconds.
+                ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection,
+                    by default sets to 10 seconds.
+                keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`.
         '''
-        if origin:
-            self.ws.open(origin=origin)
-        else:
-            self.ws.open()
+        self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout,
+                     keep_alive)
 
     def close_connection(self) -> None:
         ''' Closes WebSocket connection. '''
diff --git a/livechat/customer/rtm/api/v36.py b/livechat/customer/rtm/api/v36.py
index 17df9fa..6255605 100644
--- a/livechat/customer/rtm/api/v36.py
+++ b/livechat/customer/rtm/api/v36.py
@@ -20,15 +20,26 @@ def __init__(self, organization_id: str, base_url: str):
                 f'Provided `organization_id` (`{organization_id}`) seems invalid. Websocket connection may not open.'
             )
 
-    def open_connection(self, origin: dict = None) -> None:
+    def open_connection(self,
+                        origin: dict = None,
+                        ping_timeout: float = 3,
+                        ping_interval: float = 5,
+                        ws_conn_timeout: float = 10,
+                        keep_alive: bool = True) -> None:
         ''' Opens WebSocket connection.
+
             Args:
                 origin (dict): Specifies origin while creating websocket connection.
+                ping_timeout (int or float): timeout (in seconds) if the pong message is not received,
+                    by default sets to 3 seconds.
+                ping_interval (int or float): automatically sends "ping" command every specified period (in seconds).
+                    If set to 0, no ping is sent periodically, by default sets to 5 seconds.
+                ws_conn_timeout (int or float): timeout (in seconds) to wait for WebSocket connection,
+                    by default sets to 10 seconds.
+                keep_alive(bool): Bool which states if connection should be kept, by default sets to `True`.
         '''
-        if origin:
-            self.ws.open(origin=origin)
-        else:
-            self.ws.open()
+        self.ws.open(origin, ping_timeout, ping_interval, ws_conn_timeout,
+                     keep_alive)
 
     def close_connection(self) -> None:
         ''' Closes WebSocket connection. '''
diff --git a/livechat/customer/web/api/v33.py b/livechat/customer/web/api/v33.py
index 70f6117..7403c42 100644
--- a/livechat/customer/web/api/v33.py
+++ b/livechat/customer/web/api/v33.py
@@ -18,10 +18,11 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         if all([access_token, isinstance(access_token, str)]):
             super().__init__(access_token, base_url, http2, proxies, verify,
-                             disable_logging)
+                             disable_logging, timeout)
         else:
             raise ValueError(
                 'Incorrect or missing `access_token` argument (should be of type str.)'
diff --git a/livechat/customer/web/api/v34.py b/livechat/customer/web/api/v34.py
index 4cc8c37..c6b54ff 100644
--- a/livechat/customer/web/api/v34.py
+++ b/livechat/customer/web/api/v34.py
@@ -18,10 +18,11 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         if all([access_token, isinstance(access_token, str)]):
             super().__init__(access_token, base_url, http2, proxies, verify,
-                             disable_logging)
+                             disable_logging, timeout)
         else:
             raise ValueError(
                 'Incorrect or missing `access_token` argument (should be of type str.)'
diff --git a/livechat/customer/web/api/v35.py b/livechat/customer/web/api/v35.py
index 3dfe03c..dd63d73 100644
--- a/livechat/customer/web/api/v35.py
+++ b/livechat/customer/web/api/v35.py
@@ -18,10 +18,11 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         if all([access_token, isinstance(access_token, str)]):
             super().__init__(access_token, base_url, http2, proxies, verify,
-                             disable_logging)
+                             disable_logging, timeout)
         else:
             raise ValueError(
                 'Incorrect or missing `access_token` argument (should be of type str.)'
diff --git a/livechat/customer/web/api/v36.py b/livechat/customer/web/api/v36.py
index 327fcd1..401aa58 100644
--- a/livechat/customer/web/api/v36.py
+++ b/livechat/customer/web/api/v36.py
@@ -18,10 +18,11 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         if all([access_token, isinstance(access_token, str)]):
             super().__init__(access_token, base_url, http2, proxies, verify,
-                             disable_logging)
+                             disable_logging, timeout)
         else:
             raise ValueError(
                 'Incorrect or missing `access_token` argument (should be of type str.)'
diff --git a/livechat/customer/web/base.py b/livechat/customer/web/base.py
index 3e5c8ed..a403f24 100644
--- a/livechat/customer/web/base.py
+++ b/livechat/customer/web/base.py
@@ -5,6 +5,8 @@
 
 from typing import Union
 
+import httpx
+
 from livechat.config import CONFIG
 from livechat.customer.web.api.v33 import CustomerWebV33
 from livechat.customer.web.api.v34 import CustomerWebV34
@@ -30,6 +32,7 @@ def get_client(
         verify: bool = True,
         organization_id: str = None,
         disable_logging: bool = False,
+        timeout: float = httpx.Timeout(15)
     ) -> Union[CustomerWebV33, CustomerWebV34, CustomerWebV35, CustomerWebV36]:
         ''' Returns client for specific API version.
 
@@ -48,6 +51,8 @@ def get_client(
                                (which will disable verification). Defaults to `True`.
                 organization_id (str): Organization ID, replaced license ID in v3.4.
                 disable_logging (bool): indicates if logging should be disabled.
+                timeout (float): The timeout configuration to use when sending requests.
+                                 Defaults to 15 seconds.
 
             Returns:
                 API client object for specified version based on
@@ -70,7 +75,8 @@ def get_client(
                 'http2': http2,
                 'proxies': proxies,
                 'verify': verify,
-                'disable_logging': disable_logging
+                'disable_logging': disable_logging,
+                'timeout': timeout
             },
             '3.4': {
                 'organization_id': organization_id,
@@ -79,7 +85,8 @@ def get_client(
                 'http2': http2,
                 'proxies': proxies,
                 'verify': verify,
-                'disable_logging': disable_logging
+                'disable_logging': disable_logging,
+                'timeout': timeout
             },
             '3.5': {
                 'organization_id': organization_id,
@@ -88,7 +95,8 @@ def get_client(
                 'http2': http2,
                 'proxies': proxies,
                 'verify': verify,
-                'disable_logging': disable_logging
+                'disable_logging': disable_logging,
+                'timeout': timeout
             },
             '3.6': {
                 'organization_id': organization_id,
@@ -97,7 +105,8 @@ def get_client(
                 'http2': http2,
                 'proxies': proxies,
                 'verify': verify,
-                'disable_logging': disable_logging
+                'disable_logging': disable_logging,
+                'timeout': timeout
             },
         }.get(version)
         if client:
diff --git a/livechat/reports/api/v33.py b/livechat/reports/api/v33.py
index e755581..56d96a8 100644
--- a/livechat/reports/api/v33.py
+++ b/livechat/reports/api/v33.py
@@ -14,9 +14,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.3/reports'
 
     # Chats
diff --git a/livechat/reports/api/v34.py b/livechat/reports/api/v34.py
index 28defb2..94c4765 100644
--- a/livechat/reports/api/v34.py
+++ b/livechat/reports/api/v34.py
@@ -14,9 +14,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.4/reports'
 
     # Chats
diff --git a/livechat/reports/api/v35.py b/livechat/reports/api/v35.py
index 18dbec9..caa3566 100644
--- a/livechat/reports/api/v35.py
+++ b/livechat/reports/api/v35.py
@@ -14,9 +14,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.5/reports'
 
 # Chats
diff --git a/livechat/reports/api/v36.py b/livechat/reports/api/v36.py
index 2117b20..9998d4f 100644
--- a/livechat/reports/api/v36.py
+++ b/livechat/reports/api/v36.py
@@ -14,9 +14,10 @@ def __init__(self,
                  http2: bool,
                  proxies=None,
                  verify: bool = True,
-                 disable_logging: bool = False):
+                 disable_logging: bool = False,
+                 timeout: float = httpx.Timeout(15)):
         super().__init__(token, base_url, http2, proxies, verify,
-                         disable_logging)
+                         disable_logging, timeout)
         self.api_url = f'https://{base_url}/v3.6/reports'
 
 # Chats
diff --git a/livechat/reports/base.py b/livechat/reports/base.py
index 6761e0b..5ed134e 100644
--- a/livechat/reports/base.py
+++ b/livechat/reports/base.py
@@ -7,6 +7,8 @@
 
 from typing import Union
 
+import httpx
+
 from livechat.config import CONFIG
 from livechat.reports.api.v33 import ReportsApiV33
 from livechat.reports.api.v34 import ReportsApiV34
@@ -29,6 +31,7 @@ def get_client(
         proxies: dict = None,
         verify: bool = True,
         disable_logging: bool = False,
+        timeout: float = httpx.Timeout(15)
     ) -> Union[ReportsApiV33, ReportsApiV34, ReportsApiV35, ReportsApiV36]:
         ''' Returns client for specific Reports API version.
 
@@ -45,6 +48,8 @@ def get_client(
                                a path to an SSL certificate file, an `ssl.SSLContext`, or `False`
                                (which will disable verification). Defaults to `True`.
                 disable_logging (bool): indicates if logging should be disabled.
+                timeout (float): The timeout configuration to use when sending requests.
+                                 Defaults to 15 seconds.
 
             Returns:
                 ReportsApi: API client object for specified version.
@@ -55,16 +60,16 @@ def get_client(
         client = {
             '3.3':
             ReportsApiV33(token, base_url, http2, proxies, verify,
-                          disable_logging),
+                          disable_logging, timeout),
             '3.4':
             ReportsApiV34(token, base_url, http2, proxies, verify,
-                          disable_logging),
+                          disable_logging, timeout),
             '3.5':
             ReportsApiV35(token, base_url, http2, proxies, verify,
-                          disable_logging),
+                          disable_logging, timeout),
             '3.6':
             ReportsApiV36(token, base_url, http2, proxies, verify,
-                          disable_logging),
+                          disable_logging, timeout),
         }.get(version)
         if not client:
             raise ValueError('Provided version does not exist.')

From 138f9d74cb02287cd1ec6dfec0ad62a637259805 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marcin=20D=C4=99bski?= <m.debski@livechat.com>
Date: Thu, 11 Jan 2024 13:43:41 +0100
Subject: [PATCH 3/3] Updated changelog

---
 changelog.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/changelog.md b/changelog.md
index 28bb5a9..51fd980 100644
--- a/changelog.md
+++ b/changelog.md
@@ -9,7 +9,7 @@ All notable changes to this project will be documented in this file.
 ### Changed
 - Updated outdated packages.
 - Enhanced error logging for improved troubleshooting: Automatically includes response headers in the log for server errors, providing detailed information (such as x-debug-id) for more effective issue diagnosis.
-- Enhanced timeouts for the `WebsocketClient`.
+- Enhanced timeouts for the RTM and WEB clients.
 
 ### Bugfixes
 - Enabled instantiation for `CustomerRtmV36` within the 3.6 version of the Customer RTM API.