Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions src/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@
import re
import traceback
from urllib.parse import urlparse, parse_qs

from workers import Response
import js
from pyodide.ffi import to_js


def jso(obj):
"""Utility to convert Python types to JS objects (Object.fromEntries for dicts)."""
return to_js(obj, create_pyproxies=False, dict_converter=js.Object.fromEntries)
Comment on lines +48 to +50
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Nice fix for the Map vs Object issue!

This helper correctly addresses the root cause described in the PR — js.Object.fromEntries ensures Web Crypto APIs receive plain JS Objects instead of Maps.

One small improvement for maintainability: consider adding type hints. Since the function accepts various Python types and returns a JS proxy, something like:

from typing import Any

def jso(obj: Any) -> Any:
    ...

This helps future contributors understand the function's flexibility at a glance. As per coding guidelines, "Review Python code for... proper type hints."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/worker.py` around lines 48 - 50, Add type hints to the jso helper to
improve readability and follow project guidelines: import typing.Any and
annotate the function signature as def jso(obj: Any) -> Any, keeping the
implementation intact that calls to_js(...,
dict_converter=js.Object.fromEntries). Reference the existing jso function and
the to_js/js.Object.fromEntries usage so the maintainer knows exactly where to
add the import and signature change.


def capture_exception(exc: Exception, req=None, _env=None, where: str = ""):
"""Best-effort exception logging with full traceback and request context."""
Expand Down Expand Up @@ -109,12 +114,9 @@ def _derive_aes_key_bytes(secret: str) -> bytes:

async def _import_aes_key(key_bytes: bytes) -> object:
"""Import raw bytes as a Web Crypto AES-GCM CryptoKey."""
import js
from pyodide.ffi import to_js
key_buf = to_js(key_bytes, create_pyproxies=False)
algo = to_js({"name": "AES-GCM"}, create_pyproxies=False)
usages = to_js(["encrypt", "decrypt"], create_pyproxies=False)
return await js.crypto.subtle.importKey("raw", key_buf, algo, False, usages)
return await js.crypto.subtle.importKey(
"raw", jso(key_bytes), jso({"name": "AES-GCM"}), False, jso(["encrypt", "decrypt"])
)


async def encrypt_aes(plaintext: str, secret: str) -> str:
Expand All @@ -126,13 +128,11 @@ async def encrypt_aes(plaintext: str, secret: str) -> str:
if not plaintext:
return ""
try:
import js
from pyodide.ffi import to_js
key_bytes = _derive_aes_key_bytes(secret)
crypto_key = await _import_aes_key(key_bytes)
iv = bytes(js.crypto.getRandomValues(to_js(bytearray(12))))
algo = to_js({"name": "AES-GCM", "iv": to_js(iv)}, create_pyproxies=False)
data = to_js(plaintext.encode("utf-8"), create_pyproxies=False)
iv = bytes(js.crypto.getRandomValues(jso(bytearray(12))))
algo = jso({"name": "AES-GCM", "iv": jso(iv)})
data = jso(plaintext.encode("utf-8"))
ct_buf = await js.crypto.subtle.encrypt(algo, crypto_key, data)
ct = bytes(js.Uint8Array.new(ct_buf))
return "v1:" + base64.b64encode(iv + ct).decode("ascii")
Expand All @@ -149,8 +149,6 @@ async def decrypt_aes(ciphertext: str, secret: str) -> str:
return ""
if not ciphertext.startswith("v1:"):
return _decrypt_xor(ciphertext, secret)
import js
from pyodide.ffi import to_js
try:
raw = base64.b64decode(ciphertext[3:])
iv, ct = raw[:12], raw[12:]
Expand All @@ -159,8 +157,8 @@ async def decrypt_aes(ciphertext: str, secret: str) -> str:
return "[decryption error]"
key_bytes = _derive_aes_key_bytes(secret)
crypto_key = await _import_aes_key(key_bytes)
algo = to_js({"name": "AES-GCM", "iv": to_js(iv)}, create_pyproxies=False)
data = to_js(ct, create_pyproxies=False)
algo = jso({"name": "AES-GCM", "iv": jso(iv)})
data = jso(ct)
try:
pt_buf = await js.crypto.subtle.decrypt(algo, crypto_key, data)
return bytes(js.Uint8Array.new(pt_buf)).decode("utf-8")
Expand Down