Repository navigation
feat: add Kapusch.TikTok.Business.iOS (TikTok Business SDK 1.7.2) - #4
Merged
Merged
Conversation
Add a dedicated checksum lock for the TikTok Business SDK (App Events SDK) source tarball and a restore script that verifies the SHA-256, the single lock entry and the archive root before extracting. The Share package keeps its own lockstate.txt untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thin Swift facade exporting @_cdecl("ktkb_*") symbols over TikTokBusiness:
initialize with completion (deferred start mapped to a non-failure state),
start/enable tracking, events with JSON properties and event id, identify,
logout, flush, access-token update, state and version queries.
build.sh compiles the facade and the upstream Objective-C core and Swift
StoreKit 2 observer from the locked source with the local toolchain, merges
the objects per slice with libtool -static and produces ktkbusiness.xcframework.
The upstream privacy manifest and license are copied unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New net10.0-ios project (PackageId and namespace Kapusch.TikTok.Business.iOS,
version 1.0.0-rc.1) next to the unchanged Share project.
TikTokBusinessSdk exposes InitializeAsync(TikTokBusinessConfig) with the SDK
completion result, StartTrack, SetTrackingEnabled, TrackEvent with properties
and event id, typed StartTrial/Subscribe/Purchase/Registration/Login helpers,
Identify/Logout, Flush, UpdateAccessToken, IsInitialized, IsTrackingEnabled,
SdkVersion and TestEventCode. Constants cover every standard event and
property name of SDK 1.7.2. Interop uses LibraryImport("__Internal") and a
reflection-free JSON writer so the assembly stays trim and AOT compatible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
buildTransitive props/targets for Kapusch.TikTok.Business.iOS with their own UseKapuschTikTokBusinessIosInteropFromSource and DisableKapuschTikTokBusinessIosNativeAssets switches. The NativeReference is static and force-loaded (covers -ObjC for the SDK categories and the classes it resolves with NSClassFromString), IsCxx adds -lc++, and the SDK frameworks, weak AppTrackingTransparency, sqlite3 and zlib are declared explicitly. The upstream privacy manifest is bundled as TikTokBusinessSDK_Privacy.bundle/PrivacyInfo.xcprivacy, the name used by the official CocoaPods integration. scripts/validate-business-package.sh checks, in the same change: static __Internal imports only, identical C# and Swift ktkb_* sets, the single pinned lock entry, the package layout (no Share content, no dynamic framework), the unchanged upstream privacy manifest and its bundle wiring, the NativeReference metadata, and that every archive slice exports exactly the managed entry points and carries the SDK classes. The Share validator is untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
check-native-symbol-collisions.sh fails when the Share and Business static archives define the same strong global symbol on any slice, so an app can link both packages. verify-public.py scans tracked files for pasted App Secrets, plus any extra deny patterns supplied at run time through VERIFY_PUBLIC_EXTRA_PATTERNS, so no private identifier is kept in the tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI gains a pinned macos-15 / Xcode 26.3 / 10.0.203 job that builds the Business wrapper, checks native symbol collisions against the Share wrapper, packs and runs validate-business-package.sh. A Linux job runs the public-tree scan, with optional extra deny patterns from the VERIFY_PUBLIC_EXTRA_PATTERNS secret. The existing Share job is unchanged. publish.yml now routes by tag prefix: vX.Y.Z[-rc.N] keeps releasing Kapusch.TikTok.iOS exactly as before, business-vX.Y.Z[-rc.N] releases Kapusch.TikTok.Business.iOS (format-checked). Manual runs take a package choice that defaults to share. Keeping a single workflow file lets the existing NuGet trusted-publishing policy cover both packages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Buildable UIKit sample consuming the Business package in source mode. It calls nothing on the SDK until a consent tap, then requests App Tracking Transparency, initializes with debug mode and payment tracking disabled, tracks ViewContent and a StartTrial with an event id, and shows the Events Manager test event code. Ids and the App Secret come from an untracked Info.plist created from a secret-free template. CI links the sample for the simulator and for an unsigned device build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README, AGENTS, CONTRIBUTING, the samples index and the PR template now cover Kapusch.TikTok.iOS and Kapusch.TikTok.Business.iOS. Docs/Release.md documents the v* and business-v* tag channels. Docs/Business/Integration.md covers Events Manager values, what runs before initialization, the consent recipe, automatic purchase double counting, events and properties, SKAdNetwork, the privacy manifest and linking; Docs/Business/SourceMode.md covers source mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Identify, Logout, StartTrack, SetTrackingEnabled and UpdateAccessToken were queued to the main thread asynchronously when called from another thread, while TrackEvent and Flush ran inline. The SDK reads the identity when it creates each event, so a background `Identify(); TrackRegistration();` could send the event without the identity, and `Logout(); TrackEvent()` under the old one. Every export after initialize now calls the SDK synchronously on the caller's thread, as the SDK itself does for trackEvent and flush. Only initialize keeps the main-thread hop that its run-loop timer needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With DisableAutoStart the SDK completes with -1 ("tracking not enabled"),
which the wrapper maps to InitializedTrackingDisabled. The first result still
carried ErrorCode -1 and the SDK message, while a repeated call returned the
same status with ErrorCode 0, and the API documents 0 as "no error". The
deferred-start result now reports code 0 and no message in both cases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The force-loaded Business archive defines two unprefixed upstream globals, _handleConfiguration (KSCrash-derived TTSDKCrashC.c) and _defaultPattern (TikTokViewUtility.m), which make the link fail if another static library in the app defines the same strong symbol. Integration.md section 8 now lists them, and check-native-symbol-collisions.sh takes repeatable --with archives so an app can compare its other static SDKs with the Business archive. The default Share-versus-Business check is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The credential pattern only matched key = "hex" forms, so a sample id or App Secret typed into the tracked Info.template.plist (<key>Sample...</key><string>value</string>) went unnoticed; only the generated Info.plist is gitignored. verify-public.py now parses every samples/**/Info.template.plist and fails when a Sample* key is not empty. CONTRIBUTING describes the scan and its run-time extra patterns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The public-tree scan took caller-supplied regular expressions from VERIFY_PUBLIC_EXTRA_PATTERNS. Align it with the shared private-term approach: literal, case-insensitive terms from the KAPUSCH_PRIVATE_TERMS environment variable (a repository secret in CI) and the untracked <git dir>/info/private-terms file, skipped with a notice when neither provides a term, with failures naming the file and "private term #N" only. The App Secret / access token pattern and the sample template check are kept. Co-Authored-By: Claude Opus 5.5 <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.
Summary
Supersedes #3. This branch carries the same work with a rewritten history. The earlier public-tree scanner commit is replaced, so no commit in the merged history contains a private deny pattern. The CI sample-link steps now arrive with the sample, so every commit builds. Review fixes from #3 are separate commits on top.
This adds a second, independent package to the repo,
Kapusch.TikTok.Business.iOS1.0.0-rc.1. It wraps the TikTok Business SDK (App Events SDK) 1.7.2, the latest stable iOS release (re-checked 2026-10-26).The existing
Kapusch.TikTok.iOSpackage (OpenSDK Share) does not change. Its project, lock, validator, notices and docs are untouched. A local repack matches the published 1.0.0 entry for entry, and its buildTransitive files, notices, readme and privacy manifests are byte-identical. The only difference is NuGet.org's repository signature.Layout
src/Kapusch.TikTokApisForiOSComponentssrc/Kapusch.TikTok.Business.iOSKapusch.TikTok.iOSKapusch.TikTok.Business.iOSDependencyLocks/iOS/lockstate.txtDependencyLocks/iOS/business-lockstate.txtktkshare.xcframework(ktk_*)ktkbusiness.xcframework(ktkb_*)scripts/validate-package.sh(keepsNSPrivacyTracking=false)scripts/validate-business-package.shvX.Y.Z[-rc.N]business-vX.Y.Z[-rc.N]Public C# API (
Kapusch.TikTok.Business.iOS)TikTokBusinessSdk.InitializeAsync(TikTokBusinessConfig)returnsTask<TikTokBusinessInitResult(Status, ErrorCode, ErrorMessage)>.Statusis one ofStarted,InitializedTrackingDisabledorFailed, taken from the SDK completion handler.ErrorCodecarries SDK codes such as-2(App ID mismatch), or the wrapper codes1001/1002.StartTrack(),SetTrackingEnabled(bool),TrackEvent(name, IReadOnlyDictionary<string, object>?, eventId?),Identify(externalId, externalUserName?, phoneNumber?, email?),Logout(),Flush(),UpdateAccessToken(),IsInitialized,IsTrackingEnabled,SdkVersionandTestEventCode.TrackStartTrial,TrackSubscribeandTrackPurchasetakevalue,currency,eventId,contentIdandcontentType.TrackPurchasealso takescontentsanddescription.TrackRegistrationandTrackLogintakeeventId.TikTokBusinessConfigfields:AppId,TikTokAppIds(a list),AccessToken(the App Secret),DebugMode,LogLevel;DisableAutoStart,DisableAutomaticTracking,DisableInstallTracking,DisableLaunchTracking,DisableRetentionTracking,DisablePaymentTracking,DisableAppTrackingDialog,DisableSKAdNetworkSupport,DisableAdvertiserIdCollection,DisableEnhancedDataPostback,LimitedDataUse;AttAuthorizationDelay,CustomUserAgent,LowPerformanceDevice.TikTokBusinessEventNameslists all 28 standard event names of SDK 1.7.2, spelled exactly as the SDK sends them (LaunchAPP,InAppADClick,InAppADImpr, ...), plus the auto-only2DretentionandPurchaseRestored.TikTokBusinessPropertyNameslistsvalue,currency,content_id,content_type,description,contents,price,quantity,content_category,content_name,brandandad_revenue.TikTokBusinessContentis a record for the entries ofcontents.Why these choices
iOS packaging: build from the locked source and merge with
libtool -static, instead of using the upstream release xcframework..swiftinterfaceandInfo.plist. That is newer than this repo's pinned toolchain (Xcode 26.3 / Swift 6.2.4).TikTokBusinessSDK_SPM), so this is not a patched build.Swift runtime,
-ObjC,-lc++_AddSwiftLinkerFlags).LinkWithSwiftSystemLibraries=trueis still set on the NativeReference so the requirement is explicit.ForceLoadcovers-ObjC. It loads every object, including the categories, and the classes the SDK resolves withNSClassFromString(TikTokStoreKitObserver,TTSDKCrashInstallationConsole).IsCxxadds-lc++for the crash monitor's C++ unit.-lsqlite3,-lz, the SDK frameworks and a weakAppTrackingTransparencyare declared explicitly.Privacy manifest
TikTokBusinessSDK_Privacy.bundle/PrivacyInfo.xcprivacy, the CocoaPods bundle name.Nothing starts before
InitializeAsyncSdkVersion, returnsfalseuntil the wrapper has configured the SDK. SoIdentifycannot persist identity data before consent, and no SDK singleton is created.trackBeforeInit=False flushBeforeInit=False, and the SDK printed no log line.+loadhooks that run anyway are documented in Integration.md §2. None of them makes a network call. One stores the launch URL and a launch referrer inNSUserDefaults; for a notification launch, the referrer is the payload.DisableAutoStartmaps todisableTracking()on iOS-1in that case. The wrapper maps it toInitializedTrackingDisabled, not to a failure.ErrorCode0 and no message, the same as a repeated call in that state.InitializeAsyncreturns the current state instead of the SDK's-3error.Threading: only
InitializeAsynchops to the main thread.initializeSdkschedules anNSTimeron the current run loop, so it needs the main thread.Identify(); TrackRegistration();andLogout(); TrackEvent();must run in call order, also from background async code.trackEventand flush, from its network-callback threads. The other setters only write a flag or hand work to the SDK's own queues.Value as a string in the typed helpers. This mirrors
TikTokContentsEvent.setValue:(NSString *)andTikTokContentParams.price, so manual events have the same wire shape as the SDK's own contents events.Config fields that have no effect on iOS:
DisableAppTrackingDialogandDisableAdvertiserIdCollection.A single
publish.yml, routed by tag prefixv*does not matchbusiness-v*. The script also routes by prefix and checks thebusiness-vformat.packagechoice that defaults toshare, so Share behaviour is unchanged.Separate notices file for the Business package. Editing the root
THIRD_PARTY_NOTICES.mdwould have changed the contents of the Share nupkg, which packs that file. The Business notices are insrc/Kapusch.TikTok.Business.iOS/THIRD_PARTY_NOTICES.mdand also include the KSCrash notice for the embeddedTTSDKCrashcomponent.Validation (local, Xcode 26.3, .NET 10.0.203)
bash src/Kapusch.TikTok.Business.iOS/Native/iOS/build.sh: 3 slices with 130 objects each (wrapper, 116 Obj-C/C/C++, 13 Swift), producingktkbusiness.xcframework(5.2 MB device archive).scripts/validate-business-package.sh: passed.scripts/validate-package.sh: passed. The Share repack also matches the published 1.0.0, as described in the summary.scripts/check-native-symbol-collisions.sh: no strong global symbol in common on arm64, simulator arm64 or simulator x86_64 (347 Share and 614 Business symbols).iossimulator-x64(Debug and Release) and for an unsignedios-arm64Release build (full AOT). The device binary links the Swift runtime from/usr/lib/swiftand bundlesTikTokBusinessSDK_Privacy.bundle.sdk=1.7.2 initialized=False trackBeforeInit=False flushBeforeInit=False.python3 -I scripts/verify-public.py: passed, with and without run-time extra patterns. Negative tests: a filledSample*key inInfo.template.plist, a matching extra pattern and an invalid extra pattern all fail.scripts/check-native-symbol-collisions.sh --with <archive>: a test archive defining_defaultPatternis reported as a collision; the Share xcframework is not.publish.ymlrouting forv1.0.1,business-v1.0.0-rc.1, a malformedbusiness-v1.0, and manual runs with and without a version.Public-tree scan without private names.
scripts/verify-public.pykeeps only generic checks in the tree: pasted App Secrets, and emptySample*keys in sampleInfo.template.plistfiles. Deny patterns for private identifiers are read at run time fromVERIFY_PUBLIC_EXTRA_PATTERNS, one regex per line, from a CI secret or an untracked local file. Matches are reported by file name only, so the scan never prints what it looks for.Generic upstream symbols. The force-loaded archive defines two unprefixed upstream globals,
_handleConfiguration(KSCrash-derivedTTSDKCrashC.c) and_defaultPattern(TikTokViewUtility.m). They are not renamed, because the repo never patches upstream sources. Integration.md §8 lists them, andcheck-native-symbol-collisions.sh --with <archive>lets an app compare its other static SDKs with the Business archive.Not done / open
InstallApp,LaunchAPP,StartTrialandSubscribearrive, and that no automaticPurchasearrives withDisablePaymentTracking.publish.ymlcreateKapusch.TikTok.Business.iOS, the firstbusiness-v1.0.0-rc.1push will fail at the push step. The fix is to add or extend the policy on nuget.org; no code change is needed.VERIFY_PUBLIC_EXTRA_PATTERNSsecret. CI passes it to the public-tree job, but the repository secret has not been created yet. Until it is, CI runs only the generic checks.sysctlkern.boottime,NSFileSystemSizeandstat. Integration.md tells host apps to declare them.🤖 Generated with Claude Code