Skip to content

Pro glue: expose auto_renewing (key A) and grace period (key G) - #68

Draft
mpretty-cyro wants to merge 1 commit into
session-foundation:mainfrom
mpretty-cyro:pro-auto-renewing-glue
Draft

Pro glue: expose auto_renewing (key A) and grace period (key G)#68
mpretty-cyro wants to merge 1 commit into
session-foundation:mainfrom
mpretty-cyro:pro-auto-renewing-glue

Conversation

@mpretty-cyro

Copy link
Copy Markdown
Collaborator

Adds the libsession_util_nodejs bindings for two synced user-profile config keys that Session Desktop's
Pro status-refresh work needs. No behaviour change to anything existing — four new methods, nothing
altered.

JS core shape
getProAutoRenewing() / setProAutoRenewing(bool) user_profile_{get,set}_pro_auto_renewing boolean
getProGracePeriod() / setProGracePeriod(graceMs) user_profile_{get,set}_pro_grace_period number (ms)

Mirrors the existing getProPrepaid/setProPrepaid pair — implementation plus InstanceMethod
registration in src/user_config.cpp, declarations in include/user_config.hpp, and the three
declaration sites in types/user/userconfig.d.ts (interface, UserConfigWrapperNode, MakeActionCall
union).

⚠️ Depends on TWO libsession-util changes, one of them not upstream yet

A wrapper reviewer won't have either of these in mind, so stating them plainly:

key core API where it lives
A user_profile_{get,set}_pro_auto_renewing libsession-util #121, 8e5634b8 — open
G user_profile_{get,set}_pro_grace_period 086a420e on feature/pro-auto-renewing-tristate, not yet pushed or reviewed

086a420e sits directly on top of 8e5634b8 (verified: 8e5634b8 is an ancestor), so the two are a
chain rather than alternatives — but the second is a branch that completes #121 rather than part of it as
currently proposed. This PR cannot build until both are merged and the submodule pin moves.

What is verified, and what is not

Stated explicitly because "it compiled" is not available here:

  • Verified by inspection: the four JS-side strings match their InstanceMethod registrations exactly.
    That pairing is unchecked at compile time on this platform — napi dispatch is by string — so it's worth
    saying it was checked rather than assumed. The core signatures were read from
    include/session/config/user_profile.hpp at 086a420e, not taken from a description.
  • Structural argument: the worker's dispatch is (wrapper as any)[action] with no registry or
    allow-list, so there's no third site to keep in sync; the two string literals are the whole surface. A
    missing or misnamed binding throws a named error on first call rather than failing silently.
  • NOT verified: this was never compiled or executed. The submodule is deliberately left at its
    recorded commit, because neither core change is merged. So there is no artefact to inspect for symbols,
    and CI is the first real build. Two of three verification legs, by construction rather than by omission.

Two deliberate choices a reviewer may want to challenge

G is milliseconds on the JS side, converted to core's whole seconds here. The wrapper has two
conventions in tension: timestamps are ms-in-JS with C++ converting (setProAccessExpiry,
setProPrepaid, setRefundRequested), while the one existing duration accessor,
setNoteToSelfExpiry, takes seconds straight through. G is a duration in the Pro family. Chose ms
because callers hold gracePeriodDurationMs from the get_pro_status response, and it keeps the
conversion in one place instead of at every call site. The conversion floors, so a derived paid-through
never claims coverage the backend didn't grant. Easy to flip if seconds is preferred.

G is not optional, unlike A. The backend sends 0 whenever the subscription isn't auto-renewing,
and E - 0 == E, so "no grace stored" and "a grace of zero" describe the same account. There's nothing
for a presence check to disambiguate, and adding one by symmetry with A would copy a trap rather than a
pattern.

Caveats documented at the declarations, not at call sites

The next reader of this glue won't have the design thread, so both live in the type declaration:

  • A is presence-only. Core writes it with set_nonzero_int, so setProAutoRenewing(false) erases
    the key. The getter therefore returns false for all of "not auto-renewing", "never fetched yet" and
    "written by a client predating A". Callers must not treat false as "definitely not renewing", and
    must not key a change check on whether the key is present.
  • E and G are only jointly meaningful when written from the same get_pro_status response. They're
    then internally consistent whatever grace was in force. No signature can enforce that, so it's stated:
    write them together, and clear G when clearing E, or a later E pairs with a stale grace.

Not included: has_pro_auto_renewing

086a420e also adds user_profile_has_pro_auto_renewing(), which distinguishes "never written" from
"explicitly false". Deliberately not mirrored here — no client calls it yet. It becomes useful only if
the cross-client decision on bootstrapping an unpopulated A goes that way, and it's ~6 lines to add in a
follow-up commit. Absent rather than overlooked.

Mirrors the getProPrepaid/setProPrepaid pattern for libsession's user_profile
auto-renewing and grace-period accessors, added by core #121.

A is presence-only: set_pro_auto_renewing uses set_nonzero_int, so writing false
erases the key and the getter cannot distinguish "not auto-renewing" from "never
written". G is deliberately not optional, because the backend sends 0 when the
subscription isn't auto-renewing and E - 0 == E, so an absent key and a stored
zero describe the same account. Both caveats are documented on the declarations
rather than at a call site, since the next reader of the glue meets them there.

G is milliseconds in the JS domain, converted to core's whole seconds here. That
follows the other Pro accessors and the gracePeriodDurationMs field callers get
from get_pro_status, rather than setNoteToSelfExpiry's seconds-through shape.

E and G are only jointly meaningful when written from the same get_pro_status
response, and a client clearing E must clear G or a later E pairs with a stale
grace. Noted in the type declaration because no signature can enforce it.

Not compiled against core: #121 is unmerged and the submodule is left at its
recorded commit.
@mpretty-cyro mpretty-cyro self-assigned this Aug 7, 2026
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.

1 participant