Repository navigation
fix(api): answer /api/v1/product/agreementAndPolicy with agreement documents - #243
Open
NicolasBonduel wants to merge 2 commits into
Open
NicolasBonduel wants to merge 2 commits into
NicolasBonduel wants to merge 2 commits into
Conversation
…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>
Contributor
There was a problem hiding this comment.
🟢 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/agreementAndPolicyand 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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 sendsget_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 readsuserAgreement.langUrlandprivacyProtocol.langUrlfrom that and passes them to the nativePluginSDKModule.openPrivacyLicense. That method throws aNullPointerExceptionon the null arguments, and its callback never fires.You can't see this in logcat.
MessageQueueThreadHandler.dispatchMessagecatches everyNullPointerExceptionand logs a generic React Native warning without the original exception. I found it with a diagnostic build that logs the swallowed throwable: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_v1route that returns the app'sAgreementAndPrivacyInfoshape. It reuses theuserAgreementandprivacyProtocoldocuments (version +langUrl) that/api/v4/app/agreement/latestalready serves._AGREEMENT_LATEST_DATAinroutes/api/v4/agreement.pybecomes public asAGREEMENT_LATEST_DATAso both routes share one copy.Tests
tests/test_product_agreement_and_policy.pyhttps://English URL/api/v4/app/agreement/latestBoth tests fail on
mainand 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