Skip to content

fix(cloudxr): sync and serve webpack lazy chunks for OOB static client#684

Open
gareth-morgan-nv wants to merge 3 commits into
mainfrom
gmorgan/MultipleChunks
Open

fix(cloudxr): sync and serve webpack lazy chunks for OOB static client#684
gareth-morgan-nv wants to merge 3 commits into
mainfrom
gmorgan/MultipleChunks

Conversation

@gareth-morgan-nv

@gareth-morgan-nv gareth-morgan-nv commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Description

Production webpack builds of the WebXR client emit a main bundle.js plus lazy
[id].bundle.js chunks (UIKit msdf text, desktop XR emulation, etc.). OOB modes
(--host-client, --usb-local) only downloaded and served index.html and
bundle.js, so lazy chunks 404'd on the headset and in-VR UI text did not
render (#602).

A temporary workaround inlined all async chunks into one ~9.5 MiB bundle.js
(asyncChunks: false in webpack.prod.js). This change removes that workaround
and fixes static hosting end-to-end:

  • Webpack: Re-enable lazy chunks; add AssetManifestPlugin to write
    asset-manifest.json; enable output.clean on prod builds.
  • Python sync: require_web_client_static_dir() reads the manifest (local or
    from the versioned GitHub Pages client) and downloads every listed asset.
    Falls back to index.html + bundle.js when no manifest exists (older
    published clients).
  • Python serve: WSS /client/ serves the full static tree with
    path-traversal-safe resolution and correct MIME types (not a two-file
    whitelist).
  • Docs: Prod build layout, air-gap staging, and OOB troubleshooting for
    missing UI text.
  • Tests: Manifest-driven chunk download; legacy two-file fallback unchanged.

Fixes #602

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Testing

Unit tests

pytest src/core/cloudxr_tests/python/test_oob_teleop_env.py -k require_web_client_static_dir -v

Covers legacy two-file sync (manifest 404) and manifest-driven download of a
lazy chunk (553.bundle.js).

Web client prod build

cd deps/cloudxr/webxr_client
USE_LOCAL_WEBXR_ASSETS=0 npm run build
ls build/asset-manifest.json build/*.bundle.js

Manual / integration (Quest, USB-local OOB)

export TELEOP_WEB_CLIENT_STATIC_DIR=/path/to/deps/cloudxr/webxr_client/build  # optional
python -m isaacteleop.cloudxr --accept-eula --setup-oob --usb-local

Verified: static cache includes manifest-listed chunks; headset loads UI with
text after cert acceptance. Tested on Linux host with Quest 3 (adb + Wi‑Fi for
ICE).

Pre-commit (run before merge):

SKIP=check-copyright-year pre-commit run --all-files

Checklist

  • I have read and understood the contribution guidelines
  • I have run the linter and formatter with SKIP=check-copyright-year pre-commit run --all-files
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix/feature works (or explained why not)
  • I have signed off all my commits (git commit -s) per the DCO

Summary by CodeRabbit

  • New Features

    • WebXR client now supports manifest-driven asset synchronization for complete offline caching and deployment of all required web assets.
  • Documentation

    • Added production build process documentation with generated manifest output details.
    • Improved offline caching and air-gapped deployment setup instructions.
    • Enhanced troubleshooting guidance for asset configuration issues.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d426928c-f813-476b-b22c-ca9e81351f76

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements manifest-driven lazy-chunk asset delivery for the WebXR web client. The Webpack build now generates asset-manifest.json listing all emitted assets. The launcher syncs assets idempotently: it checks for a local manifest (fast path), fetches the remote manifest if missing, downloads only absent files, and falls back to legacy two-file syncing for older releases. The WSS proxy now serves arbitrary files from the client build tree with path-traversal protection and MIME type detection. Documentation and help text are updated to reflect the new sync behavior and the requirement for a complete asset tree when using lazy chunks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The changes introduce stateful asset-sync logic with manifest parsing, URL fetching, deduplication, and atomic writes. The launcher's conditional fast/slow paths and fallback behavior warrant close review. WSS proxy file resolution requires path-traversal validation. Large docstring updates across the module increase surface area but are straightforward.

Possibly related PRs

  • NVIDIA/IsaacTeleop#671: Introduces --host-client wiring through __main__.py and launcher.py; this PR's WSS changes extend its /client/ serving to handle the full lazy-chunk asset tree.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: enabling webpack lazy chunk syncing and serving for OOB static client deployment to fix missing UI text.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #602 by implementing manifest-driven chunk syncing, proper serving of lazy chunks, and fallback to legacy behavior when manifest unavailable.
Out of Scope Changes check ✅ Passed All changes directly support lazy chunk syncing and serving for OOB static client; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gmorgan/MultipleChunks

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/cloudxr/python/oob_teleop_env.py`:
- Around line 247-270: The _manifest_file_names function currently accepts
arbitrary strings from the manifest; update it to validate that each entry is a
safe basename by rejecting any entry that contains path separators ("/" or
"\\"), any parent-segment (".."), or is an absolute path, before appending to
names; ensure validation happens where entries are checked (inside the for entry
in files loop) so only clean basenames are returned and downstream code that
uses names (e.g., the download/destination logic) cannot write outside the
intended directory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a1de5c26-d3ab-4cff-a609-b89686b81eae

📥 Commits

Reviewing files that changed from the base of the PR and between b212cfb and ea4bb10.

📒 Files selected for processing (9)
  • deps/cloudxr/webxr_client/webpack.common.js
  • deps/cloudxr/webxr_client/webpack.prod.js
  • docs/source/getting_started/build_from_source/webxr.rst
  • docs/source/getting_started/quick_start.rst
  • docs/source/references/oob_teleop_control.rst
  • src/core/cloudxr/python/__main__.py
  • src/core/cloudxr/python/oob_teleop_env.py
  • src/core/cloudxr/python/wss.py
  • src/core/cloudxr_tests/python/test_oob_teleop_env.py

Comment on lines +247 to +270
def _manifest_file_names(payload: object) -> list[str] | None:
"""Parse ``asset-manifest.json`` payload into a deduplicated file list.

Args:
payload: Decoded JSON object from ``asset-manifest.json`` (expects a
top-level ``files`` array of non-empty strings).

Returns:
Deduplicated basenames to sync, or ``None`` if *payload* is not a valid
manifest (wrong shape, empty list, or no usable entries).
"""
# Reject non-object JSON (or wrong schema version).
if not isinstance(payload, dict):
return None
files = payload.get("files")
# Require a non-empty array of basenames.
if not isinstance(files, list) or not files:
return None
# Preserve manifest order; skip duplicates and non-string entries.
names: list[str] = []
for entry in files:
if isinstance(entry, str) and entry and entry not in names:
names.append(entry)
return names or None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Path traversal via malicious manifest filenames.

_manifest_file_names accepts arbitrary strings from the manifest without validating they're safe basenames. If a compromised or malicious manifest contains entries like ../../../.bashrc, the download loop at line 427 (dest = p / name) would write outside the static directory.

Add validation to reject entries containing path separators or .. segments:

Proposed fix
     # Preserve manifest order; skip duplicates and non-string entries.
     names: list[str] = []
     for entry in files:
-        if isinstance(entry, str) and entry and entry not in names:
+        if isinstance(entry, str) and entry and entry not in names:
+            # Reject path traversal attempts and subdirectory paths.
+            if "/" in entry or "\\" in entry or entry == ".." or entry.startswith(".."):
+                continue
             names.append(entry)
     return names or None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/cloudxr/python/oob_teleop_env.py` around lines 247 - 270, The
_manifest_file_names function currently accepts arbitrary strings from the
manifest; update it to validate that each entry is a safe basename by rejecting
any entry that contains path separators ("/" or "\\"), any parent-segment
(".."), or is an absolute path, before appending to names; ensure validation
happens where entries are checked (inside the for entry in files loop) so only
clean basenames are returned and downstream code that uses names (e.g., the
download/destination logic) cannot write outside the intended directory.

@yanziz-nvidia

Copy link
Copy Markdown
Contributor

Do we want to split bundle.js for lazy loading, which is original behavior and is what I disabled. It is less deterministic than having a single bundle.js. Are we able to keep a single bundle.js but reduce the size by removing not needed packages?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Texts Are Missing on Client UI

2 participants