Skip to content

Commit 939f642

Browse files
committed
Allow logouts to re-render the component tree
1 parent 1c7c46d commit 939f642

File tree

3 files changed

+26
-23
lines changed

3 files changed

+26
-23
lines changed

src/reactpy_django/auth/components.py

+21-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from uuid import uuid4
88

99
from django.urls import reverse
10-
from reactpy import component, hooks
10+
from reactpy import component, hooks, html
1111

1212
from reactpy_django.javascript_components import HttpRequest
1313
from reactpy_django.models import SwitchSession
@@ -19,13 +19,15 @@
1919

2020

2121
@component
22-
def auth_manager():
23-
"""Component that can force the client to switch HTTP sessions to match the websocket session.
22+
def session_manager(child):
23+
"""This component can force the client (browser) to switch HTTP sessions,
24+
making it match the websocket session.
2425
2526
Used to force persistent authentication between Django's websocket and HTTP stack."""
2627
from reactpy_django import config
2728

2829
switch_sessions, set_switch_sessions = hooks.use_state(False)
30+
_, set_rerender = hooks.use_state(uuid4)
2931
uuid_ref = hooks.use_ref(str(uuid4()))
3032
uuid = uuid_ref.current
3133
scope = hooks.use_connection().scope
@@ -35,11 +37,13 @@ def setup_asgi_scope():
3537
"""Store a trigger function in websocket scope so that ReactPy-Django's hooks can command a session synchronization."""
3638
scope.setdefault("reactpy", {})
3739
scope["reactpy"]["synchronize_session"] = synchronize_session
38-
print("configure_asgi_scope")
40+
scope["reactpy"]["rerender"] = rerender
3941

4042
@hooks.use_effect(dependencies=[switch_sessions])
4143
async def synchronize_session_timeout():
42-
"""Ensure that the ASGI scope is available to this component."""
44+
"""Ensure that the ASGI scope is available to this component.
45+
This effect will automatically be cancelled if the session is successfully
46+
switched (via dependencies=[switch_sessions])."""
4347
if switch_sessions:
4448
await asyncio.sleep(config.REACTPY_AUTH_TIMEOUT + 0.1)
4549
await asyncio.to_thread(
@@ -52,43 +56,44 @@ async def synchronize_session():
5256
"""Entrypoint where the server will command the client to switch HTTP sessions
5357
to match the websocket session. This function is stored in the websocket scope so that
5458
ReactPy-Django's hooks can access it."""
55-
print("sync command")
5659
session: SessionBase | None = scope.get("session")
5760
if not session or not session.session_key:
58-
print("sync error")
5961
return
6062

6163
# Delete any sessions currently associated with this UUID
6264
with contextlib.suppress(SwitchSession.DoesNotExist):
6365
obj = await SwitchSession.objects.aget(uuid=uuid)
6466
await obj.adelete()
6567

68+
# Begin the process of synchronizing HTTP and websocket sessions
6669
obj = await SwitchSession.objects.acreate(uuid=uuid, session_key=session.session_key)
6770
await obj.asave()
68-
6971
set_switch_sessions(True)
7072

7173
async def synchronize_session_callback(status_code: int, response: str):
72-
"""This callback acts as a communication bridge between the client and server, notifying the server
73-
of the client's response to the session switch command."""
74-
print("callback")
74+
"""This callback acts as a communication bridge, allowing the client to notify the server
75+
of the status of session switch command."""
7576
set_switch_sessions(False)
7677
if status_code >= 300 or status_code < 200:
7778
await asyncio.to_thread(
7879
_logger.warning,
7980
f"Client returned unexpected HTTP status code ({status_code}) while trying to sychronize sessions.",
8081
)
8182

82-
# If a session cookie was generated, send it to the client
83-
print("render")
83+
async def rerender():
84+
"""Force a rerender of the entire component tree."""
85+
set_rerender(uuid4())
86+
87+
# Switch sessions using a client side HttpRequest component, if needed
88+
http_request = None
8489
if switch_sessions:
85-
print("Rendering HTTP request component with UUID ", uuid)
86-
return HttpRequest(
90+
http_request = HttpRequest(
8791
{
8892
"method": "GET",
8993
"url": reverse("reactpy:switch_session", args=[uuid]),
9094
"body": None,
9195
"callback": synchronize_session_callback,
9296
},
9397
)
94-
return None
98+
99+
return html._(child, http_request)

src/reactpy_django/hooks.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,10 @@ async def login(user: AbstractUser):
429429
await ensure_async(session_save_func)()
430430
await scope["reactpy"]["synchronize_session"]()
431431

432-
async def logout():
432+
async def logout(rerender: bool = True):
433433
await channels_auth.logout(scope)
434+
if rerender:
435+
await scope["reactpy"]["rerender"]()
434436

435437
return login, logout
436438

src/reactpy_django/websocket/consumer.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ async def encode_json(cls, content):
143143
async def run_dispatcher(self):
144144
"""Runs the main loop that performs component rendering tasks."""
145145
from reactpy_django import models
146-
from reactpy_django.auth.components import auth_manager
146+
from reactpy_django.auth.components import session_manager
147147
from reactpy_django.config import (
148148
REACTPY_REGISTERED_COMPONENTS,
149149
REACTPY_SESSION_MAX_AGE,
@@ -212,11 +212,7 @@ async def run_dispatcher(self):
212212
with contextlib.suppress(Exception):
213213
await serve_layout(
214214
Layout( # type: ignore
215-
ConnectionContext(
216-
root_component,
217-
auth_manager(),
218-
value=connection,
219-
)
215+
ConnectionContext(session_manager(root_component), value=connection)
220216
),
221217
self.send_json,
222218
self.recv_queue.get,

0 commit comments

Comments
 (0)