Pro backend 0.2 + libsession 1.7 compatibility updates#710
Open
jagerman wants to merge 17 commits into
Open
Conversation
mpretty-cyro
requested changes
Jul 21, 2026
Comment on lines
+22
to
+24
| static let oneMonthCode: String = "1m" | ||
| static let threeMonthsCode: String = "3m" | ||
| static let twelveMonthsCode: String = "1y" |
Member
Author
There was a problem hiding this comment.
These ones are more of an issue. These aren't permanently fixed strings, but are rather string-encoded duration values, and I think it's libsession's job to interpret them rather than passing them through raw like this.
I'm looking into that change now.
…d1e6) Adapt the in-repo Swift C-API glue + Pro consumers to the redesigned libsession pro-backend / proof C API. Not yet compiled (iOS builds need macOS/Xcode; also requires libsession re-pinned to the 73e4d1e6-equivalent, e.g. via the Session_CompileLibSession scheme against a local checkout). Pro-backend API layer (SessionNetworkingKit/SessionPro): - Provider & plan are now opaque wire slugs (PaymentProvider/Plan gain .other(String); drop the removed C enums). PaymentStatus REFUNDED->REVOKED. - Requests drop the `version` field; timestamps ms->seconds (`ts`, `refund_requested_ts`); revocations `ticket` widened to Int64. - add-payment/refund send provider_code + one opaque payment_id (UserTransaction: providerCode+paymentId; drop typed google/apple fields and order_id). - Responses adapt to reshaped C structs: pro_proof_response_parse (covers add+generate), payment item purchased_ts/revoked_ts (fractional-second doubles) + string plan/provider + opaque payment_id (appleTransactionId is now a computed accessor over payment_id for App Store), revocations retry_in/retain_for + revocation_tag/effective_ts, refund response drops `version`. - Provider URLs from session_pro_backend_get_provider_urls(code); provider display NAMES hardcoded English placeholders in Constants+LibSession (TODO: Crowdin) since libsession stopped shipping the metadata table. Proof / revocation / config format (forced by the same re-pin): - session_protocol_pro_proof: gen_index_hash->revocation_tag, expiry ms->sec at the C boundary (ProProof keeps its ms Swift API; conversion is boundary- only and round-trips losslessly). - Protobuf proof expiry now travels as whole seconds (Message/VisibleMessage). - convo_info_volatile config: pro_gen_index_hash->pro_revocation_tag, pro_expiry_unix_ts_ms->pro_expiry_ts (seconds). ClientPlatform(provider) made total over the new cases (Google Play->android, everything else incl. nil/rangeproof/unknown->iOS, with the nil-origin limitation documented for the display phase). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up from a symbol sweep of the re-pinned libsession C API: - user_profile pro access expiry: the `_ms` convenience variants were removed; call user_profile_get/set_pro_access_expiry (whole seconds) and convert at the boundary (LibSession+UserProfile). - session_protocol_pro_proof_status / _is_active now take a whole-second `ts` (compared against the proof's second-precision expiry); pass seconds (SessionProManager). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…URL/PUBKEY) Since 73e4d1e6, get-details status/user_status became opaque string codes (the SESSION_PRO_BACKEND_PAYMENT_STATUS / _USER_PRO_STATUS enums were deleted), and libsession now owns the backend URL + signing pubkey. - BackendUserProStatus / PaymentStatus: reworked from C-enum bridges to string codes. user-status fails closed (unknown -> .neverBeenPro, never grants Pro); payment-status (informational only) passes unknowns through via .other. - Parse the now-`char[64]` status fields from the get-details response + payment item (added CAccessible for the response struct). - SessionPro.swift: read the backend base URL from SESSION_PRO_BACKEND_URL and the Ed pubkey from SESSION_PRO_BACKEND_PUBKEY (32 bytes -> hex) instead of the stale hard-coded pro-backend-dev / fc9477… copies; X25519 still derived on the fly from the Ed key. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ble/JSON round-trip) Previously each pro response was a Decodable type, so the networking layer JSON-decoded the raw bytes (JSONDecoder), the init then fell through to the AnyCodable branch and re-encoded back to JSON, and only then handed the bytes to libsession's C parser: raw -> JSONDecoder -> AnyCodable -> JSONEncoder -> libsession. That baked a "the wire is JSON" assumption into the client. Now the pro API fetches the response as raw `Data` (responseType: Data.self — the same first-class path used for attachment/display-picture downloads) and `.map`s it through a plain `init(parsing: Data)` that runs libsession's C parser directly. No Codable, no AnyCodable, no round-trip — the wire format is a libsession<->backend contract the client never inspects. Callers still receive the typed response structs. (The request-body build still round-trips libsession's JSON through AnyCodable; that's the mirror-image cleanup, left for a follow-up.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
libsession's C request API is now a thin shim over the C++ `*_request()`: each
`session_pro_backend_<x>_request_build(...)` returns an owning
`session_pro_backend_request { endpoint, content_type, string8 data, … }`. So the
client stops re-implementing the request in Swift.
- New `ProRequest` wraps the shim: calls the builder, reads endpoint/content_type/
raw body out of the owned C struct, frees it. One call per request.
- `SessionProAPI`: each function now just builds via the shim and POSTs — routing on
`proRequest.endpoint` (libsession owns the route; no client endpoint copies), setting
`Content-Type` from `proRequest.contentType` (no hardcoded format), and sending the
raw `body` bytes. Response side unchanged (already parses raw Data via libsession).
- `Endpoint` is now a thin string-carrier (drops the hardcoded path enum).
- Deleted the five request structs, `Signatures`/`Signature`, and `UserTransaction`
(signing + serialisation + the JSON round-trip all live in libsession now).
This closes the request-side "client never builds/knows the wire format" gap: raw body,
libsession-supplied content-type, libsession-supplied endpoint — all in one shim call.
NOTE (Mac/Xcode): pin the in-repo libsession to 418cdaba, and update the Xcode project —
remove the 8 deleted files' references and add ProRequest.swift (project.pbxproj isn't
edited here).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ResponseHeader is now a closed status enum (.ok/.fail/.error) + optional errorCode slug + optional diagnostic error, built from the reshaped C session_pro_backend_response_header; isSuccess == .ok. Drops the errors[] array and the already-redeemed special-case (needsRefreshProProof): an ok add-payment always carries a proof now, so SessionProManager always updates the config on success and derives failure text from error/errorCode. Delete the obsolete AddProPaymentResponseStatus enum. NOTE (Mac): remove AddProPaymentResponseStatus.swift from project.pbxproj and bump the libsession build to a9e790a8 (Delta session-foundation#12); Delta session-foundation#13 (67087e93) needs no client change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e messages
Resolve provider display from pro_provider_<slug>_<suffix> (LocalizationHelper, fallback to English);
build the {pro_stores} purchasable-store list from session_pro_backend_visible_platforms (app_store
first, gated on the store translation) via a new SNUIKit proVisiblePlatformStores() hook; map error_code
slugs to localized pro_error_<slug> strings (ResponseHeader.userFacingMessage).
NB: ProErrorMessage.swift still needs adding to project.pbxproj; iOS libsession pin must include
visible_platforms (c6e5a210).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The request-building collapse onto libsession's one-call C shim (f7d595e) deleted 9 per-request/type source files but left their PBXFileReference / PBXBuildFile / sources-phase entries in the SessionNetworkingKit target, so the branch failed at input-file validation before any Swift compiled. Also registers two on-disk files that were never added to a target. Removed (dead refs, SessionNetworkingKit): Requests/{AppProPaymentRequest,GenerateProProofRequest,GetProDetailsRequest, GetProRevocationsRequest,SetPaymentRefundRequestedRequest}.swift Types/{AddProPaymentResponseStatus,Signature,Signatures,UserTransaction}.swift Added: SessionNetworkingKit/SessionPro/Types/ProRequest.swift -> SessionNetworkingKit SessionMessagingKit/SessionPro/ProErrorMessage.swift -> SessionMessagingKit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (generate-proof, refund, revocations) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jagerman
force-pushed
the
pro-wire-format-updates
branch
from
July 22, 2026 00:39
f28e59c to
b1a2f53
Compare
Three fixes to build the Pro wire-format branch from source against
libsession pro-backend-updates (c6e5a210):
- CAccessible: add CChar64 typealias + get/set overloads. libsession's
pro_payment_item now carries `status`/`plan`/`payment_provider` as
opaque char[64] strings (with `_count`); the string-buffer helper only
covered 8/65/67/101/128/224/254/268, so char[64] had no `get`.
- SessionPro+Convenience: handle the opaque `Plan.other(_)` case in the
plan-title/subtitle switches (they were non-exhaustive). Unrecognised
period codes fall back to the generic per-month framing rather than
breaking — locale-aware formatting of arbitrary codes is deferred
display work.
- Crypto+LibSession: session_protocol_decode_for_community's `ts` param
is now int64_t; pass Int64 (a posted timestamp is always positive).
Session_CompileLibSession scheme now builds GREEN (Swift compiles + links).
Remaining: 5 non-fatal LintLocalizableStrings findings (1 dynamic-key
false positive in ProErrorMessage, 4 real {pro_stores} vs
{platform_store} token mismatches in the string catalog) — reported to
betwixt for the coordinated Crowdin/string work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aligns the iOS Swift domain with libsession's `revocation_tag` and with android/desktop naming. Pro has never shipped, so no data migration — the DB column + Codable keys rename straight across. - Domain field: `ProProof.genIndexHash` -> `revocationTag`. - DB column / Codable keys / state carriers: `proGenIndexHashHex` -> `proRevocationTagHex`, `genIndexHashHex` -> `revocationTagHex` (migration _048, Profile, LibSession config handling, Profile+Updating, ConversationDataHelper, ObservableKey, SessionProManager, settings VM). LEFT UNCHANGED (per betwixt): the shared cross-platform protobuf field `SessionProtos.proto: genIndexHash = 2` and its generated accessors (SNProto/SessionProtos.pb.swift). At the two proto boundaries the domain `revocationTag` maps to/from the proto's `.genIndexHash`/`setGenIndexHash` (Message.swift, VisibleMessage.swift). `revocation_tag` C keypaths were already correct. Session_CompileLibSession builds GREEN. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oto revocationTag Task 05 (Jason's three decisions): 1. Proto field genIndexHash -> revocationTag (iOS's hand-synced .proto + regenerated-equivalent edits to SNProto.swift/SessionProtos.pb.swift; field number 2 unchanged so wire/serialization is identical). Boundary call sites use the new setRevocationTag/.revocationTag accessors. iOS is now fully clean of genIndexHash (only the wire field name changed). 2. BackendUserProStatus is now opaque: added `.unknown(String)` carrying the raw wire value for display/telemetry. CRITICAL: every gating/display switch treats `.unknown` exactly like `.neverBeenPro` (fail closed — an unknown status NEVER grants Pro). Central gate `currentUserIsCurrentlyPro` is `status == .active` (fail-closed by construction). Kept CaseIterable/ MockableFeature by supplying a manual `allCases` (the 3 known cases; the free-form `.unknown` is a real-backend value, not a dev-picker option). 3. PaymentProvider slugs now derive from libsession's SESSION_PRO_BACKEND_PAYMENT_PROVIDER_CODE_* C constants (imported as CChar tuples, read via a small cString helper) so libsession is the single source of truth. Dropped the "keep in sync" literal comment. (status/plan have no libsession constants to derive from — kept opaque + unknown-safe as-is.) Session_CompileLibSession builds GREEN against c6e5a210. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…trip)
The ProProof expiry travels on the wire (proto ProProof.expiryUnixTs) and in
libsession (session_protocol_pro_proof.expiry_ts) as whole unix SECONDS, and
iOS's own clock is seconds (Date.timeIntervalSince1970). The Swift domain was
needlessly carrying it as milliseconds, doing seconds->ms->seconds at every
boundary — exactly where a forgotten conversion becomes a 1000x proof-expiry
bug. Make the proof-expiry domain seconds-native:
- Rename ProProof.expiryUnixTimestampMs -> expiryUnixTimestampSeconds and the
carriers/DB column that hold the PROOF expiry (proExpiryUnixTimestampMs ->
proExpiryUnixTimestampSeconds; ProProofMetadata, Profile.ProState,
ObservableKey). No migration (Pro never shipped) — column semantics change
from ms to seconds; all readers/writers agree on seconds.
- Boundaries are now identity (no *1000 / /1000):
- proto: Message.setExpiryUnixTs(...Seconds) / VisibleMessage reads expiryUnixTs raw
- libsession: ProProof.expiry_ts <-> ...Seconds direct; convo pro_expiry_ts direct
- clock: SessionProManager entitlement check compares timeIntervalSince1970 to
the raw seconds value.
- refreshProProofIfNeeded now computes in seconds; the still-ms account
access-expiry (accessExpiryTimestampMs) + network clock are bridged to seconds
locally (their seconds migration is a deliberate follow-up — it cascades into
the GetProDetailsResponse/PaymentItem ms domain, bigger blast radius).
- Fixed the stale "milliseconds" proto comment on expiryUnixTs (whole seconds).
Wire is unchanged (still seconds; field number 2/4 unchanged).
Session_CompileLibSession builds GREEN against c6e5a210.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… in-place) Session Pro ships behind a feature flag, so migration _048_SessionProChanges already ran on live databases — the earlier commits renamed its `profile` columns IN PLACE, which silently broke every already-migrated DB (the app hung at splash: `column not found: "proExpiryUnixTimestampSeconds"` because the live schema still had `proExpiryUnixTimestampMs`/`proGenIndexHashHex`). Fix: - Restore _048 to its released form (proExpiryUnixTimestampMs, proGenIndexHashHex). - Add _052_RenameProColumnsForWireFormat: ALTER TABLE profile RENAME COLUMN proGenIndexHashHex -> proRevocationTagHex and proExpiryUnixTimestampMs -> proExpiryUnixTimestampSeconds, then convert existing ms values to whole seconds (/1000). Fresh installs (_048 then _052) and upgraded installs converge on the same schema. - Register _052 in SNMessagingKit.migrations + pbxproj (SessionMessagingKit). Verified: existing simulator DB upgrades cleanly, app launches past splash with account data intact. Session_CompileLibSession builds GREEN against c6e5a210. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the seconds-native pass (task07 did the message-proof expiry). All Pro timestamps/durations are now whole unix seconds (matching libsession, the wire, and iOS's clock), represented as integer UInt64 to stay consistent with the app's integer-timestamp convention (not Date/TimeInterval). Eliminates the seconds->ms->seconds round-trips at the libsession/response boundaries. - PaymentItem: expiry/redeemed/platformRefundExpiry/refundRequested + gracePeriodDuration -> `...Seconds` (UInt64), assigned directly from the int64 libsession values. purchased/revoked are `double` seconds carrying only millisecond precision (sys_ms-backed), so they're kept as integer MILLISECONDS (`...TimestampMs`, ×1000+truncate) to retain that precision — the one deliberate ms exception, clearly labelled. - RevocationItem.effective, GetProDetailsResponse (expiry/gracePeriod/ nextAutoRenewing), SessionPro.State (accessExpiry/nextAutoRenewing/ displayTimestamp) -> `...Seconds`. - libSession boundaries are now identity: user_profile pro_access_expiry read/write drop ×1000//1000; refund-request hands over whole seconds. - Removed the temporary ms bridge in refreshProProofIfNeeded (both sides are seconds now); the only remaining /1000s are legit conversions of the app's millisecond network clock to seconds for comparison/signing. - Fixed the dev-mock: mockCurrentUserAccessExpiryTimestamp (whole seconds) now lands in a seconds field (was assigned into an ms field -> Jan 1970). Wire stays whole seconds. Session_CompileLibSession builds GREEN vs c6e5a210. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x compute) The app hung on the Session splash on every launch. Root cause: computing a `MockableFeature<BuildVariant>`'s integer raw index eventually resolved a *localized* string, and that ran inside feature-store init while the SNUIKit config lock was held — deadlocking a cluster of threads around that lock and the `Constants.buildVariants`/`PaymentProvider.appStore` static-init dispatch_once. Chain: `MockableFeatureValue.rawValue` matches `self` to `allCases` via `String(reflecting:)`, which for `BuildVariant` invoked its `CustomStringConvertible` `description` -> `buildVariantStringProvider()` -> `Constants.buildVariants` -> `PaymentProvider.appStore` -> `providerString` -> `LocalizationHelper.localized()` -> `SNUIKit.shouldShowStringKeys()` (configLock). Fix: give `BuildVariant` a plain, non-localised `debugDescription`, which `String(reflecting:)` prefers over `description` — so raw-index bookkeeping no longer triggers localization. `rawValue` keeps using `String(reflecting:)` (NOT `==`: some conformers, e.g. `SessionPro.LoadingState`, implement `==` via `rawValue`, so matching by `==` there recurses infinitely — added a comment documenting both constraints). Pre-existing branch bug (betwixt's dynamic-provider-display work), exposed once the branch could actually run. Verified: the app now launches past the splash to the home screen. Session_CompileLibSession builds GREEN vs c6e5a210. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
iOS counterpart of the Pro wire-format + display rework. Same wire contract; provider display becomes data-driven off the slug via a new
SNUIKithook.This is required for iOS compatibility with session-foundation/session-pro-backend#2 and session-foundation/libsession-util#95.
Changes (see commits for more details)
ResponseHeaderreshaped tostatus(ok/fail/error) +errorCode+ singleerror; consumers updated;already_redeemedspecial-case removed.Constants.PaymentProviderresolvespro_provider_<slug>_<suffix>viaLocalizationHelper(falling back to the existing English so there's no regression before the translations land).{pro_stores}store list: built fromsession_pro_backend_visible_platforms(own platform first, gated on the store translation) via a newSNUIKit.proVisiblePlatformStores()config hook (implemented by the app and the share extension); substituted into the "purchase via …" messages, replacing the two-store enumeration.ResponseHeader.userFacingMessagemapserror_codeto a localizedpro_error_<slug>string (falling back to the backend diagnostic).Dependencies / follow-ups (Mac)
libsessionpin once the libsession PR is mergedSessionMessagingKit/SessionPro/ProErrorMessage.swifttoproject.pbxproj(and remove the deleted
AddProPaymentResponseStatus.swift).