We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Should st.experimental_user.email local value be test@example.com?
### Checklist

- [X] I have searched the [existing issues](https://github.com/streamlit/streamlit/issues) for similar issues.
- [X] I added a very descriptive title to this issue.
- [X] I have provided sufficient information below to help reproduce this issue.

### Summary

Currently st.experimental_user.email returns `test@localhost.com` when run locally (cite [docs](https://docs.streamlit.io/library/api-reference/personalization/st.experimental_user#local-development)).

However, a more standard and correct approach is to use `example.com` domain ([cite](https://en.wikipedia.org/wiki/Example.com)) which is safer.

Should we switch?

### Reproducible Code Example

```Python
import streamlit as st

st.write(st.experimental_user.email)
```


### Steps To Reproduce

1. Run the code example in a local environment

### Expected Behavior

I see `test@example.com` which is standard

### Current Behavior

I see `test@localhost.com`, which might not be totally safe and isn't a standard approach.

### Is this a regression?

- [ ] Yes, this used to work in a previous version.

### Debug info

- Streamlit version: 1.25
- Python version: 3.11
- Operating System: MacOS
- Browser: Chrome


### Additional Information

Thanks @jroes for finding this!

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/streamlit/web/server/browser_websocket_handler.py`
Content:
```
1 # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import base64
16 import binascii
17 import json
18 from typing import Any, Awaitable, Dict, List, Optional, Union
19 
20 import tornado.concurrent
21 import tornado.locks
22 import tornado.netutil
23 import tornado.web
24 import tornado.websocket
25 from tornado.websocket import WebSocketHandler
26 from typing_extensions import Final
27 
28 from streamlit import config
29 from streamlit.logger import get_logger
30 from streamlit.proto.BackMsg_pb2 import BackMsg
31 from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
32 from streamlit.runtime import Runtime, SessionClient, SessionClientDisconnectedError
33 from streamlit.runtime.runtime_util import serialize_forward_msg
34 from streamlit.web.server.server_util import is_url_from_allowed_origins
35 
36 _LOGGER: Final = get_logger(__name__)
37 
38 
39 class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
40     """Handles a WebSocket connection from the browser"""
41 
42     def initialize(self, runtime: Runtime) -> None:
43         self._runtime = runtime
44         self._session_id: Optional[str] = None
45         # The XSRF cookie is normally set when xsrf_form_html is used, but in a
46         # pure-Javascript application that does not use any regular forms we just
47         # need to read the self.xsrf_token manually to set the cookie as a side
48         # effect. See https://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection
49         # for more details.
50         if config.get_option("server.enableXsrfProtection"):
51             _ = self.xsrf_token
52 
53     def check_origin(self, origin: str) -> bool:
54         """Set up CORS."""
55         return super().check_origin(origin) or is_url_from_allowed_origins(origin)
56 
57     def write_forward_msg(self, msg: ForwardMsg) -> None:
58         """Send a ForwardMsg to the browser."""
59         try:
60             self.write_message(serialize_forward_msg(msg), binary=True)
61         except tornado.websocket.WebSocketClosedError as e:
62             raise SessionClientDisconnectedError from e
63 
64     def select_subprotocol(self, subprotocols: List[str]) -> Optional[str]:
65         """Return the first subprotocol in the given list.
66 
67         This method is used by Tornado to select a protocol when the
68         Sec-WebSocket-Protocol header is set in an HTTP Upgrade request.
69 
70         NOTE: We repurpose the Sec-WebSocket-Protocol header here in a slightly
71         unfortunate (but necessary) way. The browser WebSocket API doesn't allow us to
72         set arbitrary HTTP headers, and this header is the only one where we have the
73         ability to set it to arbitrary values, so we use it to pass tokens (in this
74         case, the previous session ID to allow us to reconnect to it) from client to
75         server as the *second* value in the list.
76 
77         The reason why the auth token is set as the second value is that, when
78         Sec-WebSocket-Protocol is set, many clients expect the server to respond with a
79         selected subprotocol to use. We don't want that reply to be the token, so we
80         by convention have the client always set the first protocol to "streamlit" and
81         select that.
82         """
83         if subprotocols:
84             return subprotocols[0]
85 
86         return None
87 
88     def open(self, *args, **kwargs) -> Optional[Awaitable[None]]:
89         # Extract user info from the X-Streamlit-User header
90         is_public_cloud_app = False
91 
92         try:
93             header_content = self.request.headers["X-Streamlit-User"]
94             payload = base64.b64decode(header_content)
95             user_obj = json.loads(payload)
96             email = user_obj["email"]
97             is_public_cloud_app = user_obj["isPublicCloudApp"]
98         except (KeyError, binascii.Error, json.decoder.JSONDecodeError):
99             email = "test@localhost.com"
100 
101         user_info: Dict[str, Optional[str]] = dict()
102         if is_public_cloud_app:
103             user_info["email"] = None
104         else:
105             user_info["email"] = email
106 
107         existing_session_id = None
108         try:
109             ws_protocols = [
110                 p.strip()
111                 for p in self.request.headers["Sec-Websocket-Protocol"].split(",")
112             ]
113 
114             if len(ws_protocols) > 1:
115                 # See the NOTE in the docstring of the select_subprotocol method above
116                 # for a detailed explanation of why this is done.
117                 existing_session_id = ws_protocols[1]
118         except KeyError:
119             # Just let existing_session_id=None if we run into any error while trying to
120             # extract it from the Sec-Websocket-Protocol header.
121             pass
122 
123         self._session_id = self._runtime.connect_session(
124             client=self,
125             user_info=user_info,
126             existing_session_id=existing_session_id,
127         )
128         return None
129 
130     def on_close(self) -> None:
131         if not self._session_id:
132             return
133         self._runtime.disconnect_session(self._session_id)
134         self._session_id = None
135 
136     def get_compression_options(self) -> Optional[Dict[Any, Any]]:
137         """Enable WebSocket compression.
138 
139         Returning an empty dict enables websocket compression. Returning
140         None disables it.
141 
142         (See the docstring in the parent class.)
143         """
144         if config.get_option("server.enableWebsocketCompression"):
145             return {}
146         return None
147 
148     def on_message(self, payload: Union[str, bytes]) -> None:
149         if not self._session_id:
150             return
151 
152         try:
153             if isinstance(payload, str):
154                 # Sanity check. (The frontend should only be sending us bytes;
155                 # Protobuf.ParseFromString does not accept str input.)
156                 raise RuntimeError(
157                     "WebSocket received an unexpected `str` message. "
158                     "(We expect `bytes` only.)"
159                 )
160 
161             msg = BackMsg()
162             msg.ParseFromString(payload)
163             _LOGGER.debug("Received the following back message:\n%s", msg)
164 
165         except Exception as ex:
166             _LOGGER.error(ex)
167             self._runtime.handle_backmsg_deserialization_exception(self._session_id, ex)
168             return
169 
170         # "debug_disconnect_websocket" and "debug_shutdown_runtime" are special
171         # developmentMode-only messages used in e2e tests to test reconnect handling and
172         # disabling widgets.
173         if msg.WhichOneof("type") == "debug_disconnect_websocket":
174             if config.get_option("global.developmentMode"):
175                 self.close()
176             else:
177                 _LOGGER.warning(
178                     "Client tried to disconnect websocket when not in development mode."
179                 )
180         elif msg.WhichOneof("type") == "debug_shutdown_runtime":
181             if config.get_option("global.developmentMode"):
182                 self._runtime.stop()
183             else:
184                 _LOGGER.warning(
185                     "Client tried to shut down runtime when not in development mode."
186                 )
187         else:
188             # AppSession handles all other BackMsg types.
189             self._runtime.handle_backmsg(self._session_id, msg)
190
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/streamlit/web/server/browser_websocket_handler.py b/lib/streamlit/web/server/browser_websocket_handler.py
--- a/lib/streamlit/web/server/browser_websocket_handler.py
+++ b/lib/streamlit/web/server/browser_websocket_handler.py
@@ -96,7 +96,7 @@
             email = user_obj["email"]
             is_public_cloud_app = user_obj["isPublicCloudApp"]
         except (KeyError, binascii.Error, json.decoder.JSONDecodeError):
-            email = "test@localhost.com"
+            email = "test@example.com"
 
         user_info: Dict[str, Optional[str]] = dict()
         if is_public_cloud_app:

{"golden_diff": "diff --git a/lib/streamlit/web/server/browser_websocket_handler.py b/lib/streamlit/web/server/browser_websocket_handler.py\n--- a/lib/streamlit/web/server/browser_websocket_handler.py\n+++ b/lib/streamlit/web/server/browser_websocket_handler.py\n@@ -96,7 +96,7 @@\n             email = user_obj[\"email\"]\n             is_public_cloud_app = user_obj[\"isPublicCloudApp\"]\n         except (KeyError, binascii.Error, json.decoder.JSONDecodeError):\n-            email = \"test@localhost.com\"\n+            email = \"test@example.com\"\n \n         user_info: Dict[str, Optional[str]] = dict()\n         if is_public_cloud_app:\n", "issue": "Should st.experimental_user.email local value be test@example.com?\n### Checklist\n\n- [X] I have searched the [existing issues](https://github.com/streamlit/streamlit/issues) for similar issues.\n- [X] I added a very descriptive title to this issue.\n- [X] I have provided sufficient information below to help reproduce this issue.\n\n### Summary\n\nCurrently st.experimental_user.email returns `test@localhost.com` when run locally (cite [docs](https://docs.streamlit.io/library/api-reference/personalization/st.experimental_user#local-development)).\r\n\r\nHowever, a more standard and correct approach is to use `example.com` domain ([cite](https://en.wikipedia.org/wiki/Example.com)) which is safer.\r\n\r\nShould we switch?\n\n### Reproducible Code Example\n\n```Python\nimport streamlit as st\r\n\r\nst.write(st.experimental_user.email)\n```\n\n\n### Steps To Reproduce\n\n1. Run the code example in a local environment\n\n### Expected Behavior\n\nI see `test@example.com` which is standard\n\n### Current Behavior\n\nI see `test@localhost.com`, which might not be totally safe and isn't a standard approach.\n\n### Is this a regression?\n\n- [ ] Yes, this used to work in a previous version.\n\n### Debug info\n\n- Streamlit version: 1.25\r\n- Python version: 3.11\r\n- Operating System: MacOS\r\n- Browser: Chrome\r\n\n\n### Additional Information\n\nThanks @jroes for finding this!\n", "before_files": [{"content": "# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport base64\nimport binascii\nimport json\nfrom typing import Any, Awaitable, Dict, List, Optional, Union\n\nimport tornado.concurrent\nimport tornado.locks\nimport tornado.netutil\nimport tornado.web\nimport tornado.websocket\nfrom tornado.websocket import WebSocketHandler\nfrom typing_extensions import Final\n\nfrom streamlit import config\nfrom streamlit.logger import get_logger\nfrom streamlit.proto.BackMsg_pb2 import BackMsg\nfrom streamlit.proto.ForwardMsg_pb2 import ForwardMsg\nfrom streamlit.runtime import Runtime, SessionClient, SessionClientDisconnectedError\nfrom streamlit.runtime.runtime_util import serialize_forward_msg\nfrom streamlit.web.server.server_util import is_url_from_allowed_origins\n\n_LOGGER: Final = get_logger(__name__)\n\n\nclass BrowserWebSocketHandler(WebSocketHandler, SessionClient):\n    \"\"\"Handles a WebSocket connection from the browser\"\"\"\n\n    def initialize(self, runtime: Runtime) -> None:\n        self._runtime = runtime\n        self._session_id: Optional[str] = None\n        # The XSRF cookie is normally set when xsrf_form_html is used, but in a\n        # pure-Javascript application that does not use any regular forms we just\n        # need to read the self.xsrf_token manually to set the cookie as a side\n        # effect. See https://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection\n        # for more details.\n        if config.get_option(\"server.enableXsrfProtection\"):\n            _ = self.xsrf_token\n\n    def check_origin(self, origin: str) -> bool:\n        \"\"\"Set up CORS.\"\"\"\n        return super().check_origin(origin) or is_url_from_allowed_origins(origin)\n\n    def write_forward_msg(self, msg: ForwardMsg) -> None:\n        \"\"\"Send a ForwardMsg to the browser.\"\"\"\n        try:\n            self.write_message(serialize_forward_msg(msg), binary=True)\n        except tornado.websocket.WebSocketClosedError as e:\n            raise SessionClientDisconnectedError from e\n\n    def select_subprotocol(self, subprotocols: List[str]) -> Optional[str]:\n        \"\"\"Return the first subprotocol in the given list.\n\n        This method is used by Tornado to select a protocol when the\n        Sec-WebSocket-Protocol header is set in an HTTP Upgrade request.\n\n        NOTE: We repurpose the Sec-WebSocket-Protocol header here in a slightly\n        unfortunate (but necessary) way. The browser WebSocket API doesn't allow us to\n        set arbitrary HTTP headers, and this header is the only one where we have the\n        ability to set it to arbitrary values, so we use it to pass tokens (in this\n        case, the previous session ID to allow us to reconnect to it) from client to\n        server as the *second* value in the list.\n\n        The reason why the auth token is set as the second value is that, when\n        Sec-WebSocket-Protocol is set, many clients expect the server to respond with a\n        selected subprotocol to use. We don't want that reply to be the token, so we\n        by convention have the client always set the first protocol to \"streamlit\" and\n        select that.\n        \"\"\"\n        if subprotocols:\n            return subprotocols[0]\n\n        return None\n\n    def open(self, *args, **kwargs) -> Optional[Awaitable[None]]:\n        # Extract user info from the X-Streamlit-User header\n        is_public_cloud_app = False\n\n        try:\n            header_content = self.request.headers[\"X-Streamlit-User\"]\n            payload = base64.b64decode(header_content)\n            user_obj = json.loads(payload)\n            email = user_obj[\"email\"]\n            is_public_cloud_app = user_obj[\"isPublicCloudApp\"]\n        except (KeyError, binascii.Error, json.decoder.JSONDecodeError):\n            email = \"test@localhost.com\"\n\n        user_info: Dict[str, Optional[str]] = dict()\n        if is_public_cloud_app:\n            user_info[\"email\"] = None\n        else:\n            user_info[\"email\"] = email\n\n        existing_session_id = None\n        try:\n            ws_protocols = [\n                p.strip()\n                for p in self.request.headers[\"Sec-Websocket-Protocol\"].split(\",\")\n            ]\n\n            if len(ws_protocols) > 1:\n                # See the NOTE in the docstring of the select_subprotocol method above\n                # for a detailed explanation of why this is done.\n                existing_session_id = ws_protocols[1]\n        except KeyError:\n            # Just let existing_session_id=None if we run into any error while trying to\n            # extract it from the Sec-Websocket-Protocol header.\n            pass\n\n        self._session_id = self._runtime.connect_session(\n            client=self,\n            user_info=user_info,\n            existing_session_id=existing_session_id,\n        )\n        return None\n\n    def on_close(self) -> None:\n        if not self._session_id:\n            return\n        self._runtime.disconnect_session(self._session_id)\n        self._session_id = None\n\n    def get_compression_options(self) -> Optional[Dict[Any, Any]]:\n        \"\"\"Enable WebSocket compression.\n\n        Returning an empty dict enables websocket compression. Returning\n        None disables it.\n\n        (See the docstring in the parent class.)\n        \"\"\"\n        if config.get_option(\"server.enableWebsocketCompression\"):\n            return {}\n        return None\n\n    def on_message(self, payload: Union[str, bytes]) -> None:\n        if not self._session_id:\n            return\n\n        try:\n            if isinstance(payload, str):\n                # Sanity check. (The frontend should only be sending us bytes;\n                # Protobuf.ParseFromString does not accept str input.)\n                raise RuntimeError(\n                    \"WebSocket received an unexpected `str` message. \"\n                    \"(We expect `bytes` only.)\"\n                )\n\n            msg = BackMsg()\n            msg.ParseFromString(payload)\n            _LOGGER.debug(\"Received the following back message:\\n%s\", msg)\n\n        except Exception as ex:\n            _LOGGER.error(ex)\n            self._runtime.handle_backmsg_deserialization_exception(self._session_id, ex)\n            return\n\n        # \"debug_disconnect_websocket\" and \"debug_shutdown_runtime\" are special\n        # developmentMode-only messages used in e2e tests to test reconnect handling and\n        # disabling widgets.\n        if msg.WhichOneof(\"type\") == \"debug_disconnect_websocket\":\n            if config.get_option(\"global.developmentMode\"):\n                self.close()\n            else:\n                _LOGGER.warning(\n                    \"Client tried to disconnect websocket when not in development mode.\"\n                )\n        elif msg.WhichOneof(\"type\") == \"debug_shutdown_runtime\":\n            if config.get_option(\"global.developmentMode\"):\n                self._runtime.stop()\n            else:\n                _LOGGER.warning(\n                    \"Client tried to shut down runtime when not in development mode.\"\n                )\n        else:\n            # AppSession handles all other BackMsg types.\n            self._runtime.handle_backmsg(self._session_id, msg)\n", "path": "lib/streamlit/web/server/browser_websocket_handler.py"}], "after_files": [{"content": "# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport base64\nimport binascii\nimport json\nfrom typing import Any, Awaitable, Dict, List, Optional, Union\n\nimport tornado.concurrent\nimport tornado.locks\nimport tornado.netutil\nimport tornado.web\nimport tornado.websocket\nfrom tornado.websocket import WebSocketHandler\nfrom typing_extensions import Final\n\nfrom streamlit import config\nfrom streamlit.logger import get_logger\nfrom streamlit.proto.BackMsg_pb2 import BackMsg\nfrom streamlit.proto.ForwardMsg_pb2 import ForwardMsg\nfrom streamlit.runtime import Runtime, SessionClient, SessionClientDisconnectedError\nfrom streamlit.runtime.runtime_util import serialize_forward_msg\nfrom streamlit.web.server.server_util import is_url_from_allowed_origins\n\n_LOGGER: Final = get_logger(__name__)\n\n\nclass BrowserWebSocketHandler(WebSocketHandler, SessionClient):\n    \"\"\"Handles a WebSocket connection from the browser\"\"\"\n\n    def initialize(self, runtime: Runtime) -> None:\n        self._runtime = runtime\n        self._session_id: Optional[str] = None\n        # The XSRF cookie is normally set when xsrf_form_html is used, but in a\n        # pure-Javascript application that does not use any regular forms we just\n        # need to read the self.xsrf_token manually to set the cookie as a side\n        # effect. See https://www.tornadoweb.org/en/stable/guide/security.html#cross-site-request-forgery-protection\n        # for more details.\n        if config.get_option(\"server.enableXsrfProtection\"):\n            _ = self.xsrf_token\n\n    def check_origin(self, origin: str) -> bool:\n        \"\"\"Set up CORS.\"\"\"\n        return super().check_origin(origin) or is_url_from_allowed_origins(origin)\n\n    def write_forward_msg(self, msg: ForwardMsg) -> None:\n        \"\"\"Send a ForwardMsg to the browser.\"\"\"\n        try:\n            self.write_message(serialize_forward_msg(msg), binary=True)\n        except tornado.websocket.WebSocketClosedError as e:\n            raise SessionClientDisconnectedError from e\n\n    def select_subprotocol(self, subprotocols: List[str]) -> Optional[str]:\n        \"\"\"Return the first subprotocol in the given list.\n\n        This method is used by Tornado to select a protocol when the\n        Sec-WebSocket-Protocol header is set in an HTTP Upgrade request.\n\n        NOTE: We repurpose the Sec-WebSocket-Protocol header here in a slightly\n        unfortunate (but necessary) way. The browser WebSocket API doesn't allow us to\n        set arbitrary HTTP headers, and this header is the only one where we have the\n        ability to set it to arbitrary values, so we use it to pass tokens (in this\n        case, the previous session ID to allow us to reconnect to it) from client to\n        server as the *second* value in the list.\n\n        The reason why the auth token is set as the second value is that, when\n        Sec-WebSocket-Protocol is set, many clients expect the server to respond with a\n        selected subprotocol to use. We don't want that reply to be the token, so we\n        by convention have the client always set the first protocol to \"streamlit\" and\n        select that.\n        \"\"\"\n        if subprotocols:\n            return subprotocols[0]\n\n        return None\n\n    def open(self, *args, **kwargs) -> Optional[Awaitable[None]]:\n        # Extract user info from the X-Streamlit-User header\n        is_public_cloud_app = False\n\n        try:\n            header_content = self.request.headers[\"X-Streamlit-User\"]\n            payload = base64.b64decode(header_content)\n            user_obj = json.loads(payload)\n            email = user_obj[\"email\"]\n            is_public_cloud_app = user_obj[\"isPublicCloudApp\"]\n        except (KeyError, binascii.Error, json.decoder.JSONDecodeError):\n            email = \"test@example.com\"\n\n        user_info: Dict[str, Optional[str]] = dict()\n        if is_public_cloud_app:\n            user_info[\"email\"] = None\n        else:\n            user_info[\"email\"] = email\n\n        existing_session_id = None\n        try:\n            ws_protocols = [\n                p.strip()\n                for p in self.request.headers[\"Sec-Websocket-Protocol\"].split(\",\")\n            ]\n\n            if len(ws_protocols) > 1:\n                # See the NOTE in the docstring of the select_subprotocol method above\n                # for a detailed explanation of why this is done.\n                existing_session_id = ws_protocols[1]\n        except KeyError:\n            # Just let existing_session_id=None if we run into any error while trying to\n            # extract it from the Sec-Websocket-Protocol header.\n            pass\n\n        self._session_id = self._runtime.connect_session(\n            client=self,\n            user_info=user_info,\n            existing_session_id=existing_session_id,\n        )\n        return None\n\n    def on_close(self) -> None:\n        if not self._session_id:\n            return\n        self._runtime.disconnect_session(self._session_id)\n        self._session_id = None\n\n    def get_compression_options(self) -> Optional[Dict[Any, Any]]:\n        \"\"\"Enable WebSocket compression.\n\n        Returning an empty dict enables websocket compression. Returning\n        None disables it.\n\n        (See the docstring in the parent class.)\n        \"\"\"\n        if config.get_option(\"server.enableWebsocketCompression\"):\n            return {}\n        return None\n\n    def on_message(self, payload: Union[str, bytes]) -> None:\n        if not self._session_id:\n            return\n\n        try:\n            if isinstance(payload, str):\n                # Sanity check. (The frontend should only be sending us bytes;\n                # Protobuf.ParseFromString does not accept str input.)\n                raise RuntimeError(\n                    \"WebSocket received an unexpected `str` message. \"\n                    \"(We expect `bytes` only.)\"\n                )\n\n            msg = BackMsg()\n            msg.ParseFromString(payload)\n            _LOGGER.debug(\"Received the following back message:\\n%s\", msg)\n\n        except Exception as ex:\n            _LOGGER.error(ex)\n            self._runtime.handle_backmsg_deserialization_exception(self._session_id, ex)\n            return\n\n        # \"debug_disconnect_websocket\" and \"debug_shutdown_runtime\" are special\n        # developmentMode-only messages used in e2e tests to test reconnect handling and\n        # disabling widgets.\n        if msg.WhichOneof(\"type\") == \"debug_disconnect_websocket\":\n            if config.get_option(\"global.developmentMode\"):\n                self.close()\n            else:\n                _LOGGER.warning(\n                    \"Client tried to disconnect websocket when not in development mode.\"\n                )\n        elif msg.WhichOneof(\"type\") == \"debug_shutdown_runtime\":\n            if config.get_option(\"global.developmentMode\"):\n                self._runtime.stop()\n            else:\n                _LOGGER.warning(\n                    \"Client tried to shut down runtime when not in development mode.\"\n                )\n        else:\n            # AppSession handles all other BackMsg types.\n            self._runtime.handle_backmsg(self._session_id, msg)\n", "path": "lib/streamlit/web/server/browser_websocket_handler.py"}]}