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:
Masen Furer 2024-06-27 12:20:03 -07:00 committed by GitHub
parent 82a0d7f3fb
commit 8eb834f816
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 107 additions and 4 deletions

View File

@ -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."""

View File

@ -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,

View File

@ -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.

View 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
)