Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Unreleased

- New `updatePostbackConversionValue(value, { coarseValue, lockPostback, conversionTypes })` (iOS):
AdAttributionKit postback updates, with locking and per-conversion-type updates on iOS 18+.
Resolves `false` on Android. `registerForSKAdAttribution()` / `updateConversionValue()` now reach
AdAttributionKit too.
- Requires the iOS native SDK **`LinkTrailSDK ~> 0.0.13`** (the first release with AdAttributionKit).

## 0.0.4

- Bump the iOS native SDK to **`LinkTrailSDK ~> 0.0.11`** (from `0.0.10`).
Expand Down
2 changes: 1 addition & 1 deletion LinktrailReactNative.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Pod::Spec.new do |s|

# The binary LinkTrail iOS SDK, published to the CocoaPods trunk — CocoaPods
# resolves it automatically, so the consuming app needs no extra Podfile line.
s.dependency "LinkTrailSDK", "~> 0.0.11"
s.dependency "LinkTrailSDK", "~> 0.0.13"

s.pod_target_xcconfig = {
"DEFINES_MODULE" => "YES",
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ const lastLink = await LinkTrail.getLastDeepLink();
await LinkTrail.requestTrackingAuthorization();
LinkTrail.registerForSKAdAttribution();
LinkTrail.updateConversionValue(42, 'medium');
// iOS 17.4+ AdAttributionKit (SKAdNetwork before); conversionTypes applies on iOS 18+.
// Resolves false on Android.
await LinkTrail.updatePostbackConversionValue(12, {
coarseValue: 'high',
lockPostback: false,
conversionTypes: ['install'],
});
```

`configure` also takes `{ logEnabled, logLevel, requestTimeoutMillis, retryPolicy, linkDomains,
Expand Down
12 changes: 12 additions & 0 deletions android/src/main/java/io/linktrail/reactnative/LinkTrailModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.net.Uri
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import com.facebook.react.bridge.WritableMap
Expand Down Expand Up @@ -135,6 +136,17 @@ class LinkTrailModule(reactContext: ReactApplicationContext) :

override fun updateConversionValue(value: Double, coarseValue: String?) = Unit

override fun updatePostbackConversionValue(
value: Double,
coarseValue: String?,
lockPostback: Boolean,
conversionTypes: ReadableArray,
promise: Promise
) {
// AdAttributionKit is Apple-only.
promise.resolve(false)
}

// ── Consent ──────────────────────────────────────────────────────────────

override fun setConsent(granted: Boolean) {
Expand Down
15 changes: 15 additions & 0 deletions ios/LinkTrailModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ - (void)updateConversionValue:(double)value coarseValue:(NSString *)coarseValue
[_impl updateConversionValue:(NSInteger)llround(value) coarseValue:coarseValue];
}

- (void)updatePostbackConversionValue:(double)value
coarseValue:(NSString *)coarseValue
lockPostback:(BOOL)lockPostback
conversionTypes:(NSArray *)conversionTypes
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject
{
[_impl updatePostbackConversionValue:(NSInteger)llround(value)
coarseValue:coarseValue
lockPostback:lockPostback
conversionTypes:conversionTypes
resolve:resolve
reject:^(NSString *code, NSString *message) { reject(code, message, nil); }];
}

- (void)setConsent:(BOOL)granted
{
[_impl setConsent:granted];
Expand Down
20 changes: 20 additions & 0 deletions ios/LinkTrailModuleImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,26 @@ public final class LinkTrailModuleImpl: NSObject {
sdk?.updateConversionValue(value, coarseValue: coarse)
}

@objc public func updatePostbackConversionValue(
_ value: Int,
coarseValue: String?,
lockPostback: Bool,
conversionTypes: [String],
resolve: @escaping Resolve,
reject: @escaping Reject
) {
guard let sdk = requireSDK(reject) else { return }
let coarse = coarseValue.flatMap(LinkTrailCoarseConversionValue.init(rawValue:))
// An empty list means "all types"; unknown strings are dropped rather than failing the call.
let types = Set(conversionTypes.compactMap(LinkTrailConversionType.init(rawValue:)))
Task {
resolve(
await sdk.updatePostbackConversionValue(
value, coarseValue: coarse, lockPostback: lockPostback,
conversionTypes: types.isEmpty ? nil : types))
}
}

// MARK: - Consent

@objc public func setConsent(_ granted: Bool) {
Expand Down
12 changes: 12 additions & 0 deletions src/NativeLinkTrail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type {
EventEmitter,
UnsafeObject,
} from 'react-native/Libraries/Types/CodegenTypes';

Check warning on line 6 in src/NativeLinkTrail.ts

View workflow job for this annotation

GitHub Actions / Lint · Typecheck · Build · Codegen

'react-native/Libraries/Types/CodegenTypes' React Native deep imports are deprecated. Please use the top level import instead

/**
* Codegen spec for the LinkTrail TurboModule.
Expand Down Expand Up @@ -53,6 +53,18 @@
/** iOS: updates the SKAdNetwork conversion value. Android: no-op. */
updateConversionValue(value: number, coarseValue?: string): void;

/**
* iOS: updates pending AdAttributionKit / SKAdNetwork postbacks. `conversionTypes`
* is a list of 'install' / 'reengagement' (empty = all). Resolves whether every
* framework call succeeded. Android: resolves `false`.
*/
updatePostbackConversionValue(
value: number,
coarseValue: string | undefined,
lockPostback: boolean,
conversionTypes: Array<string>
): Promise<boolean>;

/** Grants/revokes tracking consent (only meaningful with `requireConsent`). */
setConsent(granted: boolean): void;

Expand Down
20 changes: 20 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import NativeLinkTrail from './NativeLinkTrail';
import type {
LinkTrailAttribution,
LinkTrailCoarseConversionValue,
LinkTrailPostbackUpdateOptions,
LinkTrailDeepLink,
LinkTrailErrorEvent,
LinkTrailEventResult,
Expand Down Expand Up @@ -190,6 +191,25 @@ export const LinkTrail = {
NativeLinkTrail.updateConversionValue(value, coarseValue);
},

/**
* iOS: updates the conversion value of pending AdAttributionKit (iOS 17.4+) and
* SKAdNetwork postbacks, with postback locking and, on iOS 18+, per-conversion-type
* updates. Resolves `true` when every framework call succeeded (also when the
* user never saw an ad, since there is nothing to update). Android: resolves
* `false` — there is no equivalent.
*/
updatePostbackConversionValue(
value: number,
options: LinkTrailPostbackUpdateOptions = {}
): Promise<boolean> {
return NativeLinkTrail.updatePostbackConversionValue(
value,
options.coarseValue,
options.lockPostback ?? false,
options.conversionTypes ?? []
);
},

/**
* Grants or revokes the user's tracking consent. Only meaningful when
* `configure` was called with `requireConsent: true` (the default).
Expand Down
16 changes: 16 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ export type LinkTrailClickTokenSource = 'pasteButton' | 'automatic' | 'none';
/** SKAdNetwork 4.0+ coarse conversion value bucket (iOS only). */
export type LinkTrailCoarseConversionValue = 'low' | 'medium' | 'high';

/**
* Which conversions an AdAttributionKit postback update applies to (iOS 18+;
* earlier versions update every type).
*/
export type LinkTrailConversionType = 'install' | 'reengagement';

/** Options for `updatePostbackConversionValue` (iOS only). */
export interface LinkTrailPostbackUpdateOptions {
/** Coarse value for lower crowd-anonymity tiers. Defaults to `'low'`. */
coarseValue?: LinkTrailCoarseConversionValue;
/** Finalise the postback so no later update applies. Defaults to `false`. */
lockPostback?: boolean;
/** Restrict the update to these conversion types (iOS 18+). Omit for all. */
conversionTypes?: LinkTrailConversionType[];
}

/** A subscription returned by the event-listener APIs. */
export interface LinkTrailSubscription {
remove(): void;
Expand Down
Loading