Bläddra i källkod

Remove cookies (#1223)

Elijah Ahianyo 1 år sedan
förälder
incheckning
be48e13c71
7 ändrade filer med 91 tillägg och 18 borttagningar
  1. 8 2
      reflex/.templates/web/utils/state.js
  2. 1 0
      reflex/__init__.py
  3. 22 4
      reflex/event.py
  4. 18 10
      reflex/state.py
  5. 4 1
      tests/conftest.py
  6. 31 0
      tests/test_event.py
  7. 7 1
      tests/test_state.py

+ 8 - 2
reflex/.templates/web/utils/state.js

@@ -17,6 +17,9 @@ let token;
 // Key for the token in the session storage.
 const TOKEN_KEY = "token";
 
+// create cookie instance
+const cookies = new Cookies();
+
 // Dictionary holding component references.
 export const refs = {};
 
@@ -114,9 +117,12 @@ export const applyEvent = async (event, router, socket) => {
   }
 
   if (event.name == "_set_cookie") {
-    const cookies = new Cookies();
     cookies.set(event.payload.key, event.payload.value);
-    localStorage.setItem(event.payload.key, event.payload.value);
+    return false;
+  }
+
+  if (event.name == "_remove_cookie") {
+    cookies.remove(event.payload.key, event.payload.options)
     return false;
   }
 

+ 1 - 0
reflex/__init__.py

@@ -23,6 +23,7 @@ from .event import EventChain as EventChain
 from .event import FileUpload as upload_files
 from .event import console_log as console_log
 from .event import redirect as redirect
+from .event import remove_cookie as remove_cookie
 from .event import set_clipboard as set_clipboard
 from .event import set_cookie as set_cookie
 from .event import set_focus as set_focus

+ 22 - 4
reflex/event.py

@@ -240,8 +240,8 @@ def set_cookie(key: str, value: str) -> EventSpec:
     """Set a cookie on the frontend.
 
     Args:
-        key (str): The key identifying the cookie.
-        value (str): The value contained in the cookie.
+        key: The key identifying the cookie.
+        value: The value contained in the cookie.
 
     Returns:
         EventSpec: An event to set a cookie.
@@ -254,12 +254,30 @@ def set_cookie(key: str, value: str) -> EventSpec:
     )
 
 
+def remove_cookie(key: str, options: Dict[str, Any] = {}) -> EventSpec:  # noqa: B006
+    """Remove a cookie on the frontend.
+
+    Args:
+        key: The key identifying the cookie to be removed.
+        options: Support all the cookie options from RFC 6265
+
+    Returns:
+        EventSpec: An event to remove a cookie.
+    """
+    return server_side(
+        "_remove_cookie",
+        get_fn_signature(remove_cookie),
+        key=key,
+        options=options,
+    )
+
+
 def set_local_storage(key: str, value: str) -> EventSpec:
     """Set a value in the local storage on the frontend.
 
     Args:
-        key (str): The key identifying the variable in the local storage.
-        value (str): The value contained in the local storage.
+        key: The key identifying the variable in the local storage.
+        value: The value contained in the local storage.
 
     Returns:
         EventSpec: An event to set a key-value in local storage.

+ 18 - 10
reflex/state.py

@@ -5,7 +5,9 @@ import asyncio
 import copy
 import functools
 import inspect
+import json
 import traceback
+import urllib.parse
 from abc import ABC
 from collections import defaultdict
 from typing import (
@@ -351,7 +353,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
         """Initialize a variable.
 
         Args:
-            prop (BaseVar): The variable to initialize
+            prop: The variable to initialize
 
         Raises:
             TypeError: if the variable has an incorrect type
@@ -496,15 +498,21 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
         Returns:
                 The dict of cookies.
         """
-        headers = self.get_headers().get(constants.RouteVar.COOKIE)
-        return (
-            {
-                pair[0].strip(): pair[1].strip()
-                for pair in (item.split("=") for item in headers.split(";"))
-            }
-            if headers
-            else {}
-        )
+        cookie_dict = {}
+        cookies = self.get_headers().get(constants.RouteVar.COOKIE, "").split(";")
+
+        cookie_pairs = [cookie.split("=") for cookie in cookies if cookie]
+
+        for pair in cookie_pairs:
+            key, value = pair[0].strip(), urllib.parse.unquote(pair[1].strip())
+            try:
+                # cast non-string values to the actual types.
+                value = json.loads(value)
+            except json.JSONDecodeError:
+                pass
+            finally:
+                cookie_dict[key] = value
+        return cookie_dict
 
     @classmethod
     def setup_dynamic_args(cls, args: dict[str, str]):

+ 4 - 1
tests/conftest.py

@@ -452,7 +452,10 @@ def router_data_headers() -> Dict[str, str]:
         "sec-websocket-version": "13",
         "accept-encoding": "gzip, deflate, br",
         "accept-language": "en-US,en;q=0.9",
-        "cookie": "csrftoken=mocktoken; name=reflex",
+        "cookie": "csrftoken=mocktoken; "
+        "name=reflex;"
+        " list_cookies=%5B%22some%22%2C%20%22random%22%2C%20%22cookies%22%5D;"
+        " dict_cookies=%7B%22name%22%3A%20%22reflex%22%7D; val=true",
         "sec-websocket-key": "mock-websocket-key",
         "sec-websocket-extensions": "permessage-deflate; client_max_window_bits",
     }

+ 31 - 0
tests/test_event.py

@@ -1,3 +1,5 @@
+import json
+
 import pytest
 
 from reflex import event
@@ -160,6 +162,35 @@ def test_set_cookie():
     )
 
 
+def test_remove_cookie():
+    """Test the event remove_cookie."""
+    spec = event.remove_cookie("testkey")
+    assert isinstance(spec, EventSpec)
+    assert spec.handler.fn.__qualname__ == "_remove_cookie"
+    assert spec.args == (("key", "testkey"), ("options", {}))
+    assert (
+        format.format_event(spec) == 'E("_remove_cookie", {key:"testkey",options:{}})'
+    )
+
+
+def test_remove_cookie_with_options():
+    """Test the event remove_cookie with options."""
+    options = {
+        "path": "/",
+        "domain": "example.com",
+        "secure": True,
+        "sameSite": "strict",
+    }
+    spec = event.remove_cookie("testkey", options)
+    assert isinstance(spec, EventSpec)
+    assert spec.handler.fn.__qualname__ == "_remove_cookie"
+    assert spec.args == (("key", "testkey"), ("options", options))
+    assert (
+        format.format_event(spec)
+        == f'E("_remove_cookie", {{key:"testkey",options:{json.dumps(options)}}})'
+    )
+
+
 def test_set_local_storage():
     """Test the event set_local_storage."""
     spec = event.set_local_storage("testkey", "testvalue")

+ 7 - 1
tests/test_state.py

@@ -737,7 +737,13 @@ def test_get_cookies(test_state, mocker, router_data):
     """
     mocker.patch.object(test_state, "router_data", router_data)
 
-    assert test_state.get_cookies() == {"csrftoken": "mocktoken", "name": "reflex"}
+    assert test_state.get_cookies() == {
+        "csrftoken": "mocktoken",
+        "name": "reflex",
+        "list_cookies": ["some", "random", "cookies"],
+        "dict_cookies": {"name": "reflex"},
+        "val": True,
+    }
 
 
 def test_get_current_page(test_state):