Add a link to backend in connection error (#3044)
* Automatic authentication for backend on Github Codespaces
When running reflex on Github codespaces, the port forwarding mechanism
requires authentication, which happens automatically when first accessing the
port via HTTPS; however since the backend connects over the WSS protocol
instead, it gets an access error with no way to redirect to Github's
authentication servers to get the port open.
This PR adds an automatic redirection mechanism to a backend route when there
is a connection error accessing the frontend. After the backend route loads, it
redirects back to the frontend, but now it can connect to the backend via
websocket because the port forward is authenticated.
* manually update .pyi file 🫨
This commit is contained in:
parent
82a0d7f3fb
commit
8eb834f816
@ -80,7 +80,7 @@ from reflex.state import (
|
||||
_substate_key,
|
||||
code_uses_state_contexts,
|
||||
)
|
||||
from reflex.utils import console, exceptions, format, prerequisites, types
|
||||
from reflex.utils import codespaces, console, exceptions, format, prerequisites, types
|
||||
from reflex.utils.exec import is_testing_env, should_skip_compile
|
||||
from reflex.utils.imports import ImportVar
|
||||
|
||||
@ -95,7 +95,11 @@ def default_overlay_component() -> Component:
|
||||
Returns:
|
||||
The default overlay_component, which is a connection_modal.
|
||||
"""
|
||||
return Fragment.create(connection_pulser(), connection_toaster())
|
||||
return Fragment.create(
|
||||
connection_pulser(),
|
||||
connection_toaster(),
|
||||
*codespaces.codespaces_auto_redirect(),
|
||||
)
|
||||
|
||||
|
||||
class OverlayFragment(Fragment):
|
||||
@ -346,6 +350,10 @@ class App(LifespanMixin, Base):
|
||||
StaticFiles(directory=get_upload_dir()),
|
||||
name="uploaded_files",
|
||||
)
|
||||
if codespaces.is_running_in_codespaces():
|
||||
self.api.get(str(constants.Endpoint.AUTH_CODESPACE))(
|
||||
codespaces.auth_codespace
|
||||
)
|
||||
|
||||
def _add_cors(self):
|
||||
"""Add CORS middleware to the app."""
|
||||
|
@ -19,8 +19,8 @@ class Image(NextComponent):
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
width: Optional[Union[str, int]] = None,
|
||||
height: Optional[Union[str, int]] = None,
|
||||
width: Optional[Union[int, str]] = None,
|
||||
height: Optional[Union[int, str]] = None,
|
||||
src: Optional[Union[Var[Any], Any]] = None,
|
||||
alt: Optional[Union[Var[str], str]] = None,
|
||||
loader: Optional[Union[Var[Any], Any]] = None,
|
||||
|
@ -10,6 +10,7 @@ class Endpoint(Enum):
|
||||
PING = "ping"
|
||||
EVENT = "_event"
|
||||
UPLOAD = "_upload"
|
||||
AUTH_CODESPACE = "auth-codespace"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Get the string representation of the endpoint.
|
||||
|
94
reflex/utils/codespaces.py
Normal file
94
reflex/utils/codespaces.py
Normal file
@ -0,0 +1,94 @@
|
||||
"""Utilities for working with Github Codespaces."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from reflex.components.base.script import Script
|
||||
from reflex.components.component import Component
|
||||
from reflex.components.core.banner import has_connection_errors
|
||||
from reflex.components.core.cond import cond
|
||||
from reflex.constants import Endpoint
|
||||
|
||||
redirect_script = """
|
||||
const thisUrl = new URL(window.location.href);
|
||||
const params = new URLSearchParams(thisUrl.search)
|
||||
|
||||
function doRedirect(url) {
|
||||
if (!window.sessionStorage.getItem("authenticated_github_codespaces")) {
|
||||
const a = document.createElement("a");
|
||||
if (params.has("redirect_to")) {
|
||||
a.href = params.get("redirect_to")
|
||||
} else if (!window.location.href.startsWith(url)) {
|
||||
a.href = url + `?redirect_to=${window.location.href}`
|
||||
} else {
|
||||
return
|
||||
}
|
||||
a.hidden = true;
|
||||
a.click();
|
||||
a.remove();
|
||||
window.sessionStorage.setItem("authenticated_github_codespaces", "true")
|
||||
}
|
||||
}
|
||||
doRedirect("%s")
|
||||
""" % Endpoint.AUTH_CODESPACE.get_url()
|
||||
|
||||
|
||||
def codespaces_port_forwarding_domain() -> str | None:
|
||||
"""Get the domain for port forwarding in Github Codespaces.
|
||||
|
||||
Returns:
|
||||
The domain for port forwarding in Github Codespaces, or None if not running in Codespaces.
|
||||
"""
|
||||
GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN = os.getenv(
|
||||
"GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"
|
||||
)
|
||||
return GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
|
||||
|
||||
|
||||
def is_running_in_codespaces() -> bool:
|
||||
"""Check if the app is running in Github Codespaces.
|
||||
|
||||
Returns:
|
||||
True if running in Github Codespaces, False otherwise.
|
||||
"""
|
||||
return codespaces_port_forwarding_domain() is not None
|
||||
|
||||
|
||||
def codespaces_auto_redirect() -> list[Component]:
|
||||
"""Get the components for automatically redirecting back to the app after authenticating a codespace port forward.
|
||||
|
||||
Returns:
|
||||
A list containing the conditional redirect component, or empty list.
|
||||
"""
|
||||
if is_running_in_codespaces():
|
||||
return [cond(has_connection_errors, Script.create(redirect_script))]
|
||||
return []
|
||||
|
||||
|
||||
async def auth_codespace() -> HTMLResponse:
|
||||
"""Page automatically redirecting back to the app after authenticating a codespace port forward.
|
||||
|
||||
Returns:
|
||||
An HTML response with an embedded script to redirect back to the app.
|
||||
"""
|
||||
return HTMLResponse(
|
||||
"""
|
||||
<html>
|
||||
<head>
|
||||
<title>Reflex Github Codespace Forward Successfully Authenticated</title>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<h2>Successfully Authenticated</h2>
|
||||
</center>
|
||||
<script language="javascript">
|
||||
%s
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
% redirect_script
|
||||
)
|
Loading…
Reference in New Issue
Block a user