Skip to content

chore!: hide wrapper-only APIs behind SPI for v4 - #874

Merged
turnipdabeets merged 1 commit into
chore/v4-remove-deprecatedfrom
chore/v4-internalize-wrapper-apis
Oct 5, 2026
Merged

turnipdabeets merged 1 commit into
chore/v4-remove-deprecatedfrom
chore/v4-internalize-wrapper-apis

Conversation

@turnipdabeets

@turnipdabeets turnipdabeets commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Second PR in the 4.0 stack, on top of #873. It shrinks the public surface to what apps are meant to use, by hiding SDK internals that were public only so PostHog's wrapper SDKs could reach them.

Public API change: breaking, see the api/posthog-ios.public-api.txt diff. The snapshot does not record SPI symbols, so everything below shows as removed there.

Moved behind @_spi(PostHogInternal):

Symbol Used by
postHogSdkName, postHogVersion Flutter, React Native
PostHogSessionManager React Native
PostHogStorageManager, PostHogConfig.storageManager React Native
PostHogConfig.snapshotEndpoint React Native
dateToMillis(_:), imageToBase64(_:_:) Flutter

Made internal, because no wrapper uses them:

  • toISO8601String(_:), toISO8601Date(_:), sanitizeDictionary(_:), deleteSafely(_:), postHogiOSSdkName
  • Gzip, GzipError, CompressionLevel
  • ReachabilityError
  • UIColor.init(hex:), UIColor.hexDescription(_:)

None of these symbols appear in the posthog.com docs.

Effect on wrapper SDKs

Wrapper Effect
posthog-flutter None. The plugin already imports with @_spi(PostHogInternal).
React Native plugin (posthog-js) None. The plugin already imports with @_spi(PostHogInternal).
posthog-react-native-session-replay Uses a plain import PostHog, so it would not compile against 4.0. The repo is archived and pins posthog-ios to 3.58.x, so it cannot resolve 4.0.
posthog-kmp None. It reads the Objective-C header and uses none of these symbols.

Third-party wrappers that set postHogSdkName or postHogVersion need @_spi(PostHogInternal) import PostHog.

Things reviewers may want to weigh in on:

  • postHogVersion is now SPI, so an app reading the SDK version with a plain import loses access.
  • SPI does not hide @objc members from Objective-C, so this PR also drops @objc from PostHogSessionManager.shared, setSessionId(_:) and PostHogConfig.snapshotEndpoint. The generated PostHog-Swift.h now only declares an empty PostHogSessionManager class (it is still an NSObject subclass). The wrappers call these from Swift.
  • @testable import does not grant SPI access, so the tests and the compliance adapter now import with @_spi(PostHogInternal). The compliance adapter reads postHogSdkName, since postHogiOSSdkName is now internal.
  • PostHogExample's "Restart & Rotate Session Id" button now uses endSession()/startSession() instead of PostHogSessionManager, so the example doesn't show the SPI import.

💚 How did you test it?

  • make test passes locally (909 tests, macOS).
  • iOS simulator tests pass locally (1282 tests, iPhone 17 Pro, iOS 26.4). Run with xcodebuild test and an explicit simulator id rather than make testOniOSSimulator.
  • make apiCheck reports the snapshot up to date.
  • The posthog-flutter example app builds for the iOS simulator against this branch, using the branch of chore(ios): stop calling the deprecated getFeatureFlagPayload of posthog-ios posthog-flutter#630.
  • The React Native example-rn-native-plugin app builds for the iOS simulator against this branch, CocoaPods path only.
  • Not run locally: make build for tvOS, watchOS, visionOS and the other example apps. posthog-kmp was not compiled against this branch.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

The changeset is major and was written by hand in the changeset format.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

  • Written with Claude Code (Opus 5.5).
  • A symbol went to SPI if a first-party wrapper uses it, and to internal otherwise. App-facing API that wrappers also happen to use (startSession, endSession, isSessionReplayActive, the survey display types, the URLSession.postHog* helpers) is left public.
  • Wrapper usage was determined by scanning the default branches of posthog-flutter, posthog-js, posthog-react-native-session-replay, posthog-kmp and posthog-unity, then confirmed for Flutter and React Native by building their example apps against this branch.

🤖 Generated with Claude Code

Moves postHogSdkName, postHogVersion, PostHogSessionManager,
PostHogStorageManager, PostHogConfig.storageManager,
PostHogConfig.snapshotEndpoint, dateToMillis and imageToBase64 behind
@_spi(PostHogInternal), and makes unused public helpers internal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@turnipdabeets
turnipdabeets force-pushed the chore/v4-internalize-wrapper-apis branch from 8c739c7 to 11a2791 Compare October 5, 2026 16:42
@turnipdabeets
turnipdabeets marked this pull request as ready for review October 5, 2026 16:57
@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 5, 2026 16:57
@greptile-apps

greptile-apps Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[High risk] Hides internal SDK APIs behind a sealed SPI barrier.

The PR appears safe to merge based on the reviewed changes.

Reviews (1) · Last reviewed commit: "chore!: hide wrapper-only APIs behind SP..."

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

posthog-ios Compliance Report

Date: 2026-10-05 17:01:12 UTC
Duration: 239941ms

⚠️ Some Tests Failed

44/45 tests passed, 1 failed


Capture Tests

⚠️ 28/29 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 3495ms
Format Validation.Event Has Uuid ✅ 2944ms
Format Validation.Event Has Lib Properties ✅ 3008ms
Format Validation.Distinct Id Is String ✅ 3471ms
Format Validation.Token Is Present ✅ 2944ms
Format Validation.Custom Properties Preserved ✅ 3047ms
Format Validation.Event Has Timestamp ✅ 2942ms
Retry Behavior.Retries On 503 ✅ 11434ms
Retry Behavior.Does Not Retry On 400 ✅ 5194ms
Retry Behavior.Does Not Retry On 401 ✅ 5012ms
Retry Behavior.Respects Retry After Header ✅ 8074ms
Retry Behavior.Implements Backoff ✅ 21428ms
Retry Behavior.Retries On 500 ✅ 5739ms
Retry Behavior.Retries On 502 ✅ 5611ms
Retry Behavior.Retries On 504 ✅ 9965ms
Retry Behavior.Max Retries Respected ❌ 21886ms
Deduplication.Generates Unique Uuids ✅ 3389ms
Deduplication.Preserves Uuid On Retry ✅ 9527ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 16834ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 8495ms
Deduplication.No Duplicate Events In Batch ✅ 3337ms
Deduplication.Different Events Have Different Uuids ✅ 3368ms
Compression.Sends Gzip When Enabled ✅ 2906ms
Batch Format.Uses Proper Batch Structure ✅ 2977ms
Batch Format.Flush With No Events Sends Nothing ✅ 470ms
Batch Format.Multiple Events Batched Together ✅ 3023ms
Error Handling.Does Not Retry On 403 ✅ 5005ms
Error Handling.Does Not Retry On 413 ✅ 4943ms
Error Handling.Retries On 408 ✅ 9241ms

Failures

retry_behavior.max_retries_respected

Expected 4 requests, got 6

Feature_Flags Tests

✅ 16/16 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 2937ms
Request Payload.Flags Request Uses V2 Query Param ✅ 2943ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 2908ms
Request Payload.Flags Request Omits Authorization Header ✅ 3011ms
Request Payload.Token In Flags Body Matches Init ✅ 2856ms
Request Payload.Groups Round Trip ✅ 2985ms
Request Payload.Groups Default To Empty Object ✅ 3032ms
Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It ✅ 2974ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 3069ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 2957ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 2951ms
Request Lifecycle.No Flags Request On Init Alone ✅ 129ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 2942ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 5788ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 2923ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 3253ms

@turnipdabeets
turnipdabeets merged commit 8b67c8a into chore/v4-remove-deprecated Oct 5, 2026
46 checks passed
@turnipdabeets
turnipdabeets deleted the chore/v4-internalize-wrapper-apis branch October 5, 2026 22:49
turnipdabeets added a commit that referenced this pull request Oct 7, 2026
Moves postHogSdkName, postHogVersion, PostHogSessionManager,
PostHogStorageManager, PostHogConfig.storageManager,
PostHogConfig.snapshotEndpoint, dateToMillis and imageToBase64 behind
@_spi(PostHogInternal), and makes unused public helpers internal.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Oct 9, 2026
Moves postHogSdkName, postHogVersion, PostHogSessionManager,
PostHogStorageManager, PostHogConfig.storageManager,
PostHogConfig.snapshotEndpoint, dateToMillis and imageToBase64 behind
@_spi(PostHogInternal), and makes unused public helpers internal.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

2 participants