Skip to content

Social sharing entry points assert on publicize-capable blogs with unusable WP.com auth (split blog.supports from v2 reachability) #25750

Description

@jkmassel

The social sharing entry points conflate two different questions — "does this site support publicize?" (capability) and "can the app reach the v2 Publicize API for this site right now?" (reachability) — and wpAssertionFailure when they disagree. They disagree whenever a publicize-capable blog's WP.com token is unusable, which is a normal runtime state, not a programmer error.

Introduced by #25747: on trunk today socialSharingV2 is debug-only, so production still falls through to the legacy SharingViewController. Once #25747 ships, the legacy fallback is gone and the else branch asserts.

Summary

  • blog.supports(.publicize) validates capability only. Its auth floor is supportsRestAPI (= account != nil); it does not check that the account's authToken is present/usable.
  • ManageConnectionsHostingController.make(for:) (→ JetpackSocialFactory.serviceConfiguration(for:)) validates reachability: it additionally requires dotComID > 0 and a non-empty authToken.
  • Two entry points treat make(for:) == nil as an assertion + bail, so a capable-but-unreachable blog gets a debug crash / release no-op where the Sharing screen should be.

Root cause

supports(.publicize) and make(for:) answer different questions and diverge on exactly the broken-auth state the code comment claims to guard:

  • Blog+Features.swiftsupportsPublicizesupportsRestAPI (account != nil) + isPublishingPostsAllowed() + (isHostedAtWPcom and not publicize_permanently_disabled). No token check.
  • WPAccount.handleInvalidToken(...) nulls authToken but keeps the WPAccount, so supports(.publicize) stays true.
  • JetpackSocialFactory.serviceConfiguration(for:) requires dotComID > 0 and !authToken.isEmpty → returns nilmake(for:) returns nil.

Affected entry points

  • BlogDetailsViewController+Swift.swiftshowSharing(from:): else { return wpAssertionFailure("social connections service unavailable") }
  • SiteStatsInsightsTableViewController.swiftgrowAudienceEnablePostSharingButtonTapped(): same make(for:) else { wpAssertionFailure(...) }
  • CustomPostSettingsViewModel.resolveSocialConnectionsService(...) already does it correctly — it checks blog.supports(.publicize) and JetpackSocialFactory.shared.connectionsService(for:) != nil, and returns nil gracefully (the section just doesn't render). This is the model the other two should follow.

Repro

  1. Sign in to a WP.com-hosted blog and confirm Sharing / the Stats grow-audience "Enable post sharing" nudge is available.
  2. Put the account into the invalid-token state (server-side token revocation / password change, or drive WPAccount.handleInvalidToken directly — authToken becomes nil, the WPAccount remains).
  3. Tap My Site → Sharing (or the Stats "Enable post sharing" nudge).

Expected: the sharing/connections screen opens, or the user is routed to re-auth.
Actual: debug build asserts (wpAssertionFailure); release build presents nothing.

Proposed fix (separate PR)

Split the two checks, per @crazytonyli's / review suggestion:

  1. Capability gates visibility of the entry point → keep blog.supports(.publicize).
  2. Reachability is validated at open time by building the service — ManageConnectionsHostingController.make(for:) / JetpackSocialFactory.shared.connectionsService(for:). Treat nil as an expected outcome, not an assertion. Don't re-derive dotComID/authToken at call sites — the factory is the single source of truth (the two asserting sites drifting from it is what produced this bug).
  3. On nil, degrade instead of asserting:
    • auth problem → route to re-auth. blog.isAccessibleThroughWPCom (= account?.wordPressComRestApi != nil) is the existing helper; touching wordPressComRestApi on an empty token already posts .wpAccountRequiresShowingSigninForWPComFixingAuthToken.
    • structurally not v2-serviceable → fall back to SharingButtonsViewController or hide the affordance.
  4. Consider consolidating the three entry points behind one presenter so the "service unavailable" policy (and the duplicated "social connections service unavailable" string) lives in one place next to make(for:).

Token validity is ultimately a runtime answer — the client can only check a token is present synchronously; server-side validity is only known once a call returns 200 vs 401 (already handled by wordPressComRestApi.setInvalidTokenHandler). So the model is three tiers — capable → service constructible → first loadConnections() doesn't 401 — degrading at each, asserting at none.

Test plan

  • Publicize-capable WP.com blog with a valid token → Sharing / nudge open the v2 screen.
  • Same blog after token invalidation → routed to re-auth (no crash, no silent no-op).
  • !blog.supports(.publicize) blog → still gets SharingButtonsViewController.
  • No wpAssertionFailure on any reachable auth state.

Related (out of scope for this issue)

Same PR (#25747) also drops the grow-audience Publicize nudge's completion path — the deleted SharingViewControllerDelegate.didChangePublicizeServices() was the only caller of markCurrentNudgeAsCompleted() for that flow, so the nudge no longer auto-dismisses after connecting and the .statsPublicizeNudgeCompleted Tracks event is now never fired. Worth a separate follow-up (re-add a completion signal, or mark complete on present).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions