Skip to content

fix(api): answer /api/v1/product/agreementAndPolicy with agreement documents - #243

Open
NicolasBonduel wants to merge 2 commits into
Python-roborock:mainfrom
NicolasBonduel:fix/product-agreement-and-policy
Open

NicolasBonduel wants to merge 2 commits into
Python-roborock:mainfrom
NicolasBonduel:fix/product-agreement-and-policy

Conversation

@NicolasBonduel

@NicolasBonduel NicolasBonduel commented Nov 10, 2026 •

Copy link
Copy Markdown

Problem

On the official Android app (4.76.08, S7 MaxV a27, device plugin 5208), the device page can stay on "Loading map" forever. MQTT is connected, status and room requests succeed, but the plugin never sends get_map_v1.

When the device page opens, the plugin requests GET /api/v1/product/agreementAndPolicy?country=..&productid=... The server has no route for it, so the catch-all answers {"ok": true, "route": "..."}. The plugin reads userAgreement.langUrl and privacyProtocol.langUrl from that and passes them to the native PluginSDKModule.openPrivacyLicense. That method throws a NullPointerException on the null arguments, and its callback never fires.

You can't see this in logcat. MessageQueueThreadHandler.dispatchMessage catches every NullPointerException and logs a generic React Native warning without the original exception. I found it with a diagnostic build that logs the swallowed throwable:

E RRMapDiagnostic: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
E RRMapDiagnostic:   at com.roborock.smart.react.PluginSDKModule.openPrivacyLicense(...)
E RRMapDiagnostic:   at com.facebook.react.bridge.JavaMethodWrapper.invoke(...)

The plugin only takes this path when the app has no saved agreement acceptance for the device. In my case it started after a fresh login and clearing the app's storage, while the same app and server had shown maps before.

This is a sibling of #209 (checkAppAgreement). Both requests are part of the same agreement gate before the map loads.

Fix

Add a dedicated product_agreement_and_policy_v1 route that returns the app's AgreementAndPrivacyInfo shape. It reuses the userAgreement and privacyProtocol documents (version + langUrl) that /api/v4/app/agreement/latest already serves. _AGREEMENT_LATEST_DATA in routes/api/v4/agreement.py becomes public as AGREEMENT_LATEST_DATA so both routes share one copy.

Tests

tests/test_product_agreement_and_policy.py

  • the path resolves to the new route and returns both documents with a version and an https:// English URL
  • the documents match /api/v4/app/agreement/latest

Both tests fail on main and pass with the fix. Full suite: 460 passed.

Live check (Nov 2026): applied to a running stack with the patched Android app 4.76.08 and an S7 MaxV. After the change, the map rendered on a cold start and after reopening the device page, and the app sent get_map_v1. Before it, the page stayed on "Loading map".

🤖 Generated with Claude Code

…cuments

The Android device plugin requests GET /api/v1/product/agreementAndPolicy
when the device page opens, then passes userAgreement.langUrl and
privacyProtocol.langUrl to the native PluginSDKModule.openPrivacyLicense.
The catch-all object has neither, so the method throws a
NullPointerException on its null arguments. React Native's
MessageQueueThreadHandler swallows it, the callback never fires, and the
plugin never requests the map: the page stays on "Loading map".

Add a dedicated route that returns the userAgreement and privacyProtocol
documents already served by /api/v4/app/agreement/latest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 10, 2026 18:19

Copilot AI 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.

🟢 Approval recommended

No active review issues are reported for the new agreement-document endpoint.

No open issues 🎉

What changed in this PR

Adds a dedicated agreement-document endpoint to prevent the Android device plugin from stalling on “Loading map” when agreement acceptance is not saved. This complements the existing checkAppAgreement route.

Changes:

  • Registers /api/v1/product/agreementAndPolicy and returns user agreement and privacy documents.
  • Shares document data with /api/v4/app/agreement/latest.
  • Adds tests for route resolution, response structure, HTTPS URLs, and consistency with the v4 endpoint.
File Description
tests/​test_product_agreement_and_policy.py Tests routing, document fields, and consistency with v4 agreements.
src/​roborock_local_server/​bundled_backend/​https_server/​routes/​api/​v4/​agreement.py Exposes agreement data for reuse by both routes.
src/​roborock_local_server/​bundled_backend/​https_server/​routes/​api/​v1/​agreement.py Adds the product agreement matcher and response builder.
src/​roborock_local_server/​bundled_backend/​https_server/​endpoint_rules.py Registers the new endpoint.

Review effort: Balanced

@NicolasBonduel
NicolasBonduel marked this pull request as ready for review November 10, 2026 18:26
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

2 participants