Pro: correct the link destinations and read the home CTA windows from network time - #2167
Merged
mpretty-cyro merged 3 commits intoAug 18, 2026
Merged
Conversation
…nition The five Pro URLs had drifted from the values libsession's URL registry holds, and each was a string literal at the use site that needed it — so they diverged one at a time, and correcting them in place would have left that intact. They now live together in ProUrls, which is what makes comparing them against the registry a single check. Copies rather than reads: the registry is a C struct of const char* with no accessor exposed to Kotlin, so consuming it directly would mean adding JNI surface for five constants. URL_PRO_SUPPORT moves in with them. It was already a named constant, so leaving it in place would have meant two conventions for one kind of value. The roadmap link is reached from two surfaces — a Pro settings row and the no-billing choose-plan screen — and they now name one constant rather than repeating a destination.
Both home CTAs gated backend-supplied instants against the device clock: the expiring window against the renewal date, the expired window against coverage end. Clock skew moved the boundary rather than the subscription, and the rendered label took the same reading via a defaulted parameter. The two skew in opposite directions — a fast clock warns of expiry early and stops warning of expiration early — so a wrong device clock does not produce a uniform bias that would be easy to notice. The Pro settings screen already reads network time for the same values; this makes the home surface match it, and passes the instant into the label so both share one reading.
The parameter defaulted to the device clock, so a caller that omitted it silently compared a backend-supplied expiry against the wrong clock while looking correct at the call site. Every caller already passes it, so this changes no behaviour. It removes the way back: the next caller cannot reintroduce the device clock by leaving an argument off.
Bilb
approved these changes
Aug 18, 2026
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.
The Android half of two fixes iOS has just had (#746). Three commits, each independent.
The Pro link destinations
Five Pro URLs had drifted from the libSession registry they mirror (
session_protocol.cpp, theurl_pro_*fields):/pro/terms,/pro/privacy,/pro-roadmap,/faq#proand/pro-formare now/pro-terms,/pro-privacy,/pro#roadmap,/pro#faqand/pro-support.They were literals at six use sites —
/pro-roadmaphad two copies — which is how they diverged one at atime. They now come from a single
ProUrlsobject, with the existingProStatusManager.URL_PRO_SUPPORTmoved into it so there is one convention rather than two. Constant names mirror the registry's field
suffixes so comparing the two is a line-up rather than a translation.
The registry is a C struct of
const char*with no accessor exposed to Kotlin, so these stay copies:reading it directly would mean new JNI surface for five constants.
The home CTAs and the device clock
HomeViewModeltookInstant.now()— the device clock — and compared it against two backend-suppliedinstants:
renewingAtfor the expiring CTA andcoverageEndedAtfor the expired one, plus the rendered"expires in N days" label. Every entitlement comparison already used network time; this was the CTA
layer only.
Skew does not fail in one direction here: a fast clock fires the expiring warning early and stops
warning of expiration early, so one over-warns while the other under-warns. It now reads
SnodeClock,matching
ProSettingsViewModel, and passes that one instant into the label so the window and the textcannot disagree.
DateUtils.getExpiryStringdefaulted itsnowparameter toInstant.now(), which is what let the wrongclock in without anything looking wrong at the call site. The parameter is now required. All three callers
already pass it, so this changes no behaviour — it removes the way back in.
Testing
:app:compilePlayDebugKotlingreen, and the generatedHomeViewModel_Factoryconfirmed to carry theSnodeClockprovider. No device run yet: the clock change alters when two CTAs fire, and thesession-appium spec that covers that will be re-run against this branch before merge.