Skip to content

feat: App Events + Facebook iOS SDK 19.0.0 (1.2.0) - #6

Closed
Kapusch wants to merge 12 commits into
masterfrom
feat/app-events
Closed

Kapusch wants to merge 12 commits into
masterfrom
feat/app-events

Conversation

@Kapusch

@Kapusch Kapusch commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

Kapusch.Facebook.iOS 1.2.0 (first published as 1.2.0-rc.1):

  • Facebook iOS SDK 19.0.0, consumed through its Objective-C surface.
  • New App Events API (FacebookAppEvents) plus constants for every standard event name, parameter name, parameter value and user-data type.
  • New AppEvents feature selection: a CoreKit-only build, without LoginKit or ShareKit. The default selection stays Login.
  • Two existing link/runtime bugs fixed along the way (Login entry points stripped, Login-only builds failing to link).

The commits are split so each one can be reviewed or bisected on its own. The Objective-C port is validated on 18.0.2 first, then the SDK bump follows in its own commit.

Why Objective-C facades instead of Swift

Meta built 19.0.0 with Xcode 27.1 / Swift 6.4. A Swift facade has to import the SDK's .swiftinterface, which only compiles with that exact compiler. With Xcode 26.3 (Swift 6.2.4), the toolchain CI now pins on macos-15 (#5) and the local one, it fails: this SDK is not supported by the compiler (the SDK is built with 'Apple Swift version 6.4 …'). Staying on Swift would have limited us to SDK ≤ 18.0.3: 18.1.1 is already built with Swift 6.3.3.

The SDK also ships Objective-C headers, including the generated *-Swift.h. Any recent clang compiles against them, so the facades are now Objective-C:

  • they keep the same kfb_* C entry points and the same Login/Share behaviour;
  • they compile and link with Xcode 26.x;
  • they no longer have to move in lockstep with Meta's Xcode version on every SDK bump.

A probe showed that Swift cannot use the Clang-only module either: classes declared with SWIFT_CLASS_NAMED become "unavailable", so a Swift facade really is ruled out.

Because Meta's SwiftPM wrapper targets are Swift too, the build no longer goes through SwiftPM. It downloads Meta's release *-Dynamic_XCFramework.zip archives, pinned by URL and SHA-256 in DependencyLocks/iOS/lockstate.txt, the same technique as the Android wrapper. The hashes are the same as the upstream Package.swift remoteChecksum values. It then builds with clang + libtool per slice. The 18.0.2 package built this way has the same file list as the published 1.1.0, apart from the archive names inside the xcframeworks.

App Events API (Kapusch.Facebook.iOS.FacebookAppEvents)

  • Lifecycle:

    • InitializeSdk() runs didFinishLaunching with no launch options, on the main thread.
    • FinishedLaunching(app, launchOptions) passes the launch options, so a cold-start URL reaches AEM.
    • HandleOpenUrl and ContinueUserActivity forward URLs and Universal Links (AEM, deep links).
    • SdkVersion.
  • Settings: Set…Enabled / Is…Enabled pairs for:

    • automatic logging;
    • advertiser ID collection;
    • advertiser tracking (only used on iOS 15–16);
    • SKAdNetwork reporting;
    • SDK 19 metadata collection and product-improvement switches.

    Also SetDataProcessingOptions(string[], country, state) and SetDebugLoggingEnabled.

  • Logging:

    • ActivateApp;
    • LogEvent(name, parameters?, valueToSum?);
    • LogPurchase(decimal, currency, parameters?);
    • typed LogStartTrial, LogSubscribe and LogCompleteRegistration (value, currency, fb_order_id, extra parameters);
    • Flush, SetFlushBehavior / FlushBehavior.
  • Identity:

    • GetAnonymousId;
    • SetUserId / ClearUserId / GetUserId;
    • SetUserData(FacebookAppEventsUserData) / ClearUserData, for advanced matching (the SDK hashes the values);
    • SetUserDataValue / ClearUserDataValue, for example for external_id.
  • Constants: FacebookAppEventNames, FacebookAppEventParameterNames, FacebookAppEventParameterValues, FacebookAppEventUserDataTypes and FacebookDataProcessingOptions.

  • Not available on iOS (documented):

    • Android's setAutoInitEnabled has no counterpart: on iOS the SDK starts only when the app initializes it.
    • SetFlushBehavior exists, but there is no iOS equivalent of Android's setIsDebugEnabled. SetDebugLoggingEnabled toggles the SDK's app-events, developer-error and informational logging behaviours instead, and restores the previous behaviours when turned off.

Non-obvious choices

  • The App Events facade is linked with every selection.
    • It only needs FBSDKCoreKit, which every selection already ships.
    • Its entry points are static __Internal imports, so the .NET iOS build asks the native linker for them in every app.
    • Selecting AppEvents therefore means "no LoginKit, no ShareKit" (CoreKit, AEMKit and Basics only).
    • Login-only apps gain only a few KB of facade code, which does nothing unless called.
  • Event parameters travel as JSON.
    • They are written with Utf8JsonWriter, which is safe under trimming and AOT.
    • Accepted values are strings, integers, floating-point numbers and decimals, and booleans, which are sent as "1"/"0" like the SDK's FBSDKAppEventParameterValueYes/No.
    • Any other value type throws ArgumentException.
  • The SDK persists runtime settings, and they override Info.plist on later launches. A value set before initialization only lives in memory inside the SDK. The facade records the values set through it and applies them again right after initialization, whichever wrapper initializes. That way "set before init" and "set after init" behave the same.
  • Share no longer silently overrides App Events settings.
    • ConfigureAndInitialize still forces auto-log off and advertiser ID collection / tracking to trackingAllowed (identical defaults).
    • It now skips any of these settings that the app set explicitly through FacebookAppEvents.
    • The registry is looked up through the Objective-C runtime (NSClassFromString), so no wrapper depends on another at link time.
  • Unknown or wrongly cased KapuschFacebookFeatures values now produce a build warning. Before, they were silently ignored.

Bugs fixed on the way (they also affect 1.1.0)

  1. Login was never linked. NativeFacebookLogin used a run-time lookup (NativeLibrary.GetExport). Nothing referenced the symbols at link time, so the linker dead-stripped them:

    • nm on a Login;Share build of master shows only the three Share symbols;
    • Initialize and HandleOpenUrl silently did nothing.

    It now uses [LibraryImport("__Internal")], like Share. The public API and its best-effort semantics are unchanged.

  2. Login-only builds (the default) failed to link, with Undefined symbols: _kfb_facebook_share_*. Share's static imports are required symbols for every app. Reproduced on master with the sample.

    Fix: when a feature is not selected, a Foundation-only placeholder is linked (kfbsharestub / kfbloginstub). It defines the same entry points and fails with share_feature_not_selected / login_feature_not_selected.

Other changes

  • SDK 19 slice name. The device slice is now ios-arm64_arm64e. The targets resolve the device slice from the package, so the PrivacyInfo.xcprivacy BundleResource items keep pointing at files that exist.
  • New validators:
    • scripts/validate-package.py:
      • layout;
      • every header entry point exported by both slices;
      • every kfb_appevents_* symbol bound by a static import;
      • no GetExport;
      • constants covering every symbol of the bundled SDK headers, and each value present in the bundled FBSDKCoreKit binary (negative-tested).
    • scripts/verify-public.py: genericity scan for consuming-app names and real Facebook IDs or tokens.
    • Feature-selection tests for AppEvents.
  • CI: caches the pinned archives instead of the SwiftPM scratch, and runs both scripts.
  • Docs: Docs/Integration.md §4 covers:
    • the Info.plist keys with their SDK defaults: FacebookAutoLogAppEventsEnabled, FacebookAdvertiserIDCollectionEnabled, FBSDKAutoLogMetaDataEnabled, FBSDKAutoLogProductImprovementEnabled, FacebookSKAdNetworkReportEnabled;
    • the consent recipe: switches off in Info.plist, initialize after consent, manual ActivateApp, withdrawal steps;
    • AEM forwarding;
    • double counting with server-side purchase events: keep FacebookAutoLogAppEventsEnabled=false + ActivateApp(), and/or set "Log In-App Events Automatically = No". SDK 18+ scans StoreKit 2 transactions, renewals included;
    • a single SKAdNetwork owner;
    • diagnostics.
  • Sample:
    • selects Login;AppEvents;
    • the template ships the switches off;
    • adds grant / log / flush / withdraw buttons;
    • no IDs committed.

Validation

  • bash src/…/Native/iOS/build.sh builds all 5 wrapper xcframeworks with -Wall -Werror against SDK 19.0.0 on Xcode 26.3. It was also built on 18.0.2 at the port commit, and a cold download restore was tested.
  • bash tests/validate-feature-selection.sh, python3 scripts/verify-public.py and python3 scripts/validate-package.py all pass.
  • dotnet pack → Kapusch.Facebook.iOS.1.2.0.nupkg.
  • Sample builds:
    • simulator (arm64/x64) for Login, Share, AppEvents, Login;Share, Login;AppEvents and Login;Share;AppEvents;
    • device ios-arm64 Release for Login;Share and AppEvents (AOT).
    • AppEvents bundles only CoreKit, AEMKit and Basics.
    • All 40 kfb_* entry points are present in the executables.
  • Runtime smoke on the iOS 26.3 simulator, with a fake app ID (no real credentials) and a temporary call sequence that was not committed:
    • SDK 19.0.0 initialized;
    • explicit settings kept by Share (advertiserId=True despite trackingAllowed:false; tracking=False from the Share default);
    • metadata, product-improvement and SKAN switches applied;
    • LDU, flush behaviour, user ID and user data set and cleared;
    • anonymous ID returned;
    • StartTrial, Subscribe, fb_mobile_complete_registration, fb_mobile_purchase and a custom event recorded with the expected _valueToSum, fb_currency, fb_order_id and boolean "1" (seen in the SDK debug log);
    • invalid parameter types rejected;
    • no crash.

PR CI passed on the pinned macos-15 / Xcode 26.3 runner (build_pack_ios, including both validators). The branch is rebased on #5.

Not verified here: delivery to a real Events Manager app and the Login UI flow (it needs a real Meta app).

Release

After merge: tag v1.2.0-rc.1 on the merge commit of master (see Docs/Release.md).

🤖 Generated with Claude Code

Kapusch and others added 12 commits October 26, 2026 23:59
…ive-C facades

Meta now builds the Facebook iOS SDK binaries with Xcode releases newer
than the ones available on CI runners, and a Swift facade can only import
the SDK's Swift interfaces with the exact Swift compiler Meta used.

- Port the Login and Share facades from Swift to Objective-C. They call the
  SDK through its Objective-C headers (including the generated -Swift.h
  headers), keep the same C entry points and keep the same behaviour.
- Replace the SwiftPM dependency with release archives pinned by URL and
  SHA-256 in DependencyLocks/iOS/lockstate.txt (still SDK 18.0.2). The
  hashes equal the upstream Package.swift remoteChecksum values.
- Build each facade with clang + libtool per slice, then wrap it in the
  same kfb/kfbshare xcframeworks as before.
- Port the Share source invariants to the Objective-C implementation.
- Cache the pinned archives in CI instead of the SwiftPM scratch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NativeFacebookShare binds its entry points with static __Internal imports,
so the .NET iOS build passes them to the native linker as required symbols
for every app. A Login-only build (the default selection) therefore failed
to link with "Undefined symbols: _kfb_facebook_share_*" since 1.1.0.

When Share is not selected, inject kfbsharestub.xcframework instead of
kfbshare.xcframework. It defines the same three entry points on top of
Foundation only: configuring logs an error, sharing completes with
Failed/share_feature_not_selected and URL handling returns false. Share
selections are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pin the 19.0.0 dynamic xcframework archives. Their SHA-256 values equal
  the upstream Package.swift remoteChecksum values at tag v19.0.0.
- SDK 19 builds its device slice as ios-arm64_arm64e. Resolve the device
  slice from the packaged xcframework so the privacy-manifest resources
  keep pointing at existing files.
- Document the new iOS 15.0 minimum of the SDK.

Meta built 19.0.0 with Xcode 27.1 / Swift 6.4. The Objective-C facades
compile and link with Xcode 26.x, which a Swift facade cannot do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add kfbappevents.xcframework, an Objective-C facade over FBSDKAppEvents,
FBSDKSettings and FBSDKApplicationDelegate exported as kfb_appevents_* C
symbols:

- SDK lifecycle: initialize, finished-launching hook, URL and user-activity
  forwarding (AEM / deep links), SDK version.
- Settings: auto-log app events, advertiser ID collection, advertiser
  tracking (iOS 15-16 only), SKAdNetwork reporting, SDK 19 metadata
  collection and product-improvement switches, data processing options
  (LDU) and app-events debug logging.
- Logging: activateApp, logEvent (optional valueToSum), logPurchase, flush
  and flush behaviour. Parameters travel as a JSON object of string or
  number values.
- Identity: anonymous ID, user ID and advanced-matching user data.

It only depends on FBSDKCoreKit, which every feature selection already
links, so the package injects it for every selection: the managed App
Events entry points will be static __Internal imports and must always
resolve. The facade never changes a setting on its own. Values set through
it are recorded and applied again after SDK initialization so the SDK
persists them even when they were set before initialization.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Expose the App Events facade as Kapusch.Facebook.iOS.FacebookAppEvents,
bound with static __Internal LibraryImport entry points:

- InitializeSdk / FinishedLaunching, HandleOpenUrl and
  ContinueUserActivity forwarding for AEM and deep links, SdkVersion.
- Set/Is pairs for automatic logging, advertiser ID collection,
  advertiser tracking (iOS 15-16), SKAdNetwork reporting, metadata
  collection and product improvement; SetDataProcessingOptions and
  SetDebugLoggingEnabled.
- ActivateApp, LogEvent (parameters + optional valueToSum), LogPurchase,
  typed LogStartTrial / LogSubscribe / LogCompleteRegistration helpers,
  Flush and flush behaviour.
- GetAnonymousId, Set/Clear/GetUserId and advanced-matching user data.

Parameters accept strings, numbers and booleans (sent as "1"/"0") and are
serialized with Utf8JsonWriter (trim and AOT safe). Add constants for every
event name, parameter name, parameter value and user-data type declared by
the bundled SDK headers, plus the Android-public activate/deactivate and
_valueToSum names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Parse AppEvents in KapuschFacebookFeatures. App Events only needs
  FBSDKCoreKit, which every selection links, so its facade is always
  linked; selecting AppEvents alone produces a CoreKit-only build without
  LoginKit or ShareKit. The default selection stays Login.
- Warn about unknown (or wrongly cased) feature names, which were silently
  ignored before.
- Cover the AppEvents, Login;Share;AppEvents and default selections in the
  feature-selection test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NativeFacebookShare.ConfigureAndInitialize forces automatic app events
logging off and advertiser ID collection / advertiser tracking to
trackingAllowed. Once the app manages these settings through
FacebookAppEvents (for example after a consent decision), Share must not
silently override them.

- The App Events facade records explicit values in the Objective-C class
  KFBAppEventsSettingsRegistry. Share and Login look it up through the
  Objective-C runtime, so there is no link-time dependency.
- Share only applies its historical defaults to settings the app did not
  set explicitly. Apps that never call FacebookAppEvents keep the exact
  previous behaviour.
- After Share or Login initializes the SDK, the recorded values are
  applied again so the SDK persists them (no-op when nothing was set).
- Guard the behaviour in the Share source invariants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- scripts/validate-package.py replaces the inline layout check in CI and
  publish. On top of the layout (now including kfbsharestub and
  kfbappevents) it checks that both slices of each wrapper export every
  entry point of its header, that FacebookAppEvents binds every
  kfb_appevents_* symbol with a static __Internal import, that the managed
  constants cover every App Events name of the bundled SDK headers and that
  each constant value exists in the bundled FBSDKCoreKit binary.
- scripts/verify-public.py fails on consuming-app names or real Facebook
  app IDs, URL schemes and client tokens in tracked files.
- Move FacebookDataProcessingOptions next to the other App Events types:
  it is a caller-supplied value, not an SDK constant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Integration.md: AppEvents feature selection; Info.plist switches with SDK
  defaults (FacebookAutoLogAppEventsEnabled,
  FacebookAdvertiserIDCollectionEnabled, FBSDKAutoLogMetaDataEnabled,
  FBSDKAutoLogProductImprovementEnabled, FacebookSKAdNetworkReportEnabled)
  and the missing iOS AutoInit counterpart; a consent recipe (switches off
  in Info.plist, initialize after consent, manual ActivateApp, withdrawal
  steps); AEM URL / Universal Link forwarding; the double-counting rule for
  server-side purchase events; single SKAdNetwork owner; diagnostics and
  platform gaps.
- README, NuGet readme, SourceMode and third-party notices: App Events,
  new xcframeworks, pinned archives and the CoreKit tracking manifest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Select Login;AppEvents in the sample (overridable with
  KapuschFacebookFeatures) so it links the App Events facade explicitly.
- Ship the App Events switches off in Info.template.plist and add buttons
  for the consent recipe of Docs/Integration.md: grant consent (initialize,
  enable advertiser ID collection and debug logging, manual activation),
  log StartTrial and a custom event, flush and withdraw consent. Log
  activations from OnActivated after consent and forward Universal Links.
- Without a FacebookAppID in the local, gitignored Info.plist the App
  Events buttons only print a message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NativeFacebookLogin resolved kfb_facebook_* with a run-time lookup in the
main program. Nothing referenced those symbols at link time, so the native
linker dead-stripped them: Initialize and HandleOpenUrl silently did
nothing and SignInAsync could not reach the SDK. A Login;Share build of
master showed only the three Share symbols in the app executable.

- Bind the four Login entry points with [LibraryImport("__Internal")], like
  Share and App Events. The public API and its best-effort semantics are
  unchanged.
- When Login is not selected, link kfbloginstub.xcframework (Foundation
  only), which defines the same entry points: SignInAsync completes with
  Failed/login_feature_not_selected.
- Validate the static imports and package the placeholder.

Verified with nm on simulator builds for Login;AppEvents, AppEvents and
Share: all 40 kfb_* entry points are present in the executable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.2.0 bundles Facebook iOS SDK 19.0.0 and adds App Events. It is first
published as 1.2.0-rc.1 from a v1.2.0-rc.1 tag on master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Kapusch

Kapusch commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #7. The history is rebuilt from master so no commit carries the encoded pattern of this branch's scripts/verify-public.py (the scanner now reads private terms at run time). The bundled Facebook iOS SDK stays 18.0.2, and #7 adds the independent review and its fixes. The branch is left in place.

@Kapusch Kapusch closed this Oct 27, 2026
@Kapusch
Kapusch deleted the feat/app-events branch October 27, 2026 21:37
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