Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ DerivedData/
.DS_Store
*.tgz
coverage/

# Android upload/release signing keys — never commit
*.jks
example/android/upload-keystore.jks
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 0.0.3

- Bump the Android native SDK to **`io.linktrail:sdk:0.0.5`** (from `0.0.4`).

## 0.0.2

Consent gating, a native paste button, and the latest native SDKs.

- Native SDK bumps: iOS `LinkTrailSDK ~> 0.0.10`, Android `io.linktrail:sdk:0.0.4`.
- **Consent gating** (GDPR / ePrivacy): `requireConsent` option (deny-by-default) +
`setConsent(granted)`. Deep links still route without consent; only tracking is gated.
- iOS deferred click token: `clickTokenSource` option + `<LinkTrailPasteButton/>` component
(Apple `UIPasteControl`, themeable) + `trackInstallWithClickToken`.

## 0.0.1

Initial release. React Native New Architecture TurboModule wrapping the native LinkTrail
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ repositories {

dependencies {
implementation "com.facebook.react:react-android"
implementation "io.linktrail:sdk:0.0.4"
implementation "io.linktrail:sdk:0.0.5"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
}
5 changes: 5 additions & 0 deletions example/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# React Native's native build tooling (the Gradle autolinking plugin, CocoaPods)
# assumes a flat node_modules. pnpm's default isolated store leaves
# @react-native/gradle-plugin under .pnpm/, breaking the hardcoded
# ../node_modules/@react-native/gradle-plugin path. Hoist so those paths resolve.
node-linker=hoisted
13 changes: 7 additions & 6 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ export default function App() {

// Route the picked scenario once the simulator sheet has closed (+ a beat for
// the dismiss animation), so the navigation sticks — the iOS example's pattern.
// NOTE: clear `pendingScenario` inside the timeout, not synchronously here —
// clearing it synchronously re-runs this effect and its cleanup cancels the
// timer before it ever fires (which broke navigation on both platforms).
useEffect(() => {
if (state.simulatorOpen || !pendingScenario) return;
const scenario = pendingScenario;
setPendingScenario(null);
const timer = setTimeout(
() =>
dispatch({ type: 'route-link', link: scenario.link, source: 'deferred' }),
350,
);
const timer = setTimeout(() => {
dispatch({ type: 'route-link', link: scenario.link, source: 'deferred' });
setPendingScenario(null);
}, 350);
return () => clearTimeout(timer);
}, [state.simulatorOpen, pendingScenario]);

Expand Down
32 changes: 26 additions & 6 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,29 @@ def enableProguardInReleaseBuilds = false
*/
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'

// Release signing credentials are read from local.properties (gitignored), e.g.
// KICKFLIP_UPLOAD_STORE_FILE=upload-keystore.jks
// KICKFLIP_UPLOAD_STORE_PASSWORD=…
// KICKFLIP_UPLOAD_KEY_ALIAS=…
// KICKFLIP_UPLOAD_KEY_PASSWORD=…
def uploadKeystoreProps = new Properties()
def uploadKeystorePropsFile = rootProject.file('local.properties')
if (uploadKeystorePropsFile.exists()) {
uploadKeystorePropsFile.withInputStream { uploadKeystoreProps.load(it) }
}

android {
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion

namespace "com.kickflip"
defaultConfig {
applicationId "com.kickflip"
applicationId "io.linktrail.example"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
versionCode 17
versionName "0.0.17"
}
signingConfigs {
debug {
Expand All @@ -92,15 +103,24 @@ android {
keyAlias 'androiddebugkey'
keyPassword 'android'
}
release {
// Credentials read from local.properties (gitignored). The keystore
// path is relative to android/ (rootProject). See release buildType.
storeFile rootProject.file(uploadKeystoreProps.getProperty('KICKFLIP_UPLOAD_STORE_FILE', 'upload-keystore.jks'))
storePassword uploadKeystoreProps.getProperty('KICKFLIP_UPLOAD_STORE_PASSWORD', '')
keyAlias uploadKeystoreProps.getProperty('KICKFLIP_UPLOAD_KEY_ALIAS', '')
keyPassword uploadKeystoreProps.getProperty('KICKFLIP_UPLOAD_KEY_PASSWORD', '')
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
// Sign with the upload keystore when its credentials are configured
// in local.properties; otherwise fall back to debug so other builds
// (CI, dev) don't break.
signingConfig uploadKeystoreProps.getProperty('KICKFLIP_UPLOAD_STORE_PASSWORD') ? signingConfigs.release : signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ PODS:
- hermes-engine (0.80.3):
- hermes-engine/Pre-built (= 0.80.3)
- hermes-engine/Pre-built (0.80.3)
- LinktrailReactNative (0.0.1):
- LinktrailReactNative (0.0.3):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2521,7 +2521,7 @@ SPEC CHECKSUMS:
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
hermes-engine: fff23161928673afcd95f63146e651726f68b384
LinktrailReactNative: 3cc4dc425ca445f9363c594b219f4176f80ef339
LinktrailReactNative: 7ec4b501092c29257ae7ab64d4b44a8748139c32
LinkTrailSDK: b243fcedba4f7437df1171e460294cf81f256e25
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
RCTDeprecation: 2ebc9cc0329ede460c5d52956ce5e1ab715c2c2c
Expand Down
5 changes: 4 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest"
"test": "jest",
"android:aab": "cd android && ./gradlew :app:bundleRelease",
"android:apk": "cd android && ./gradlew :app:assembleRelease",
"android:clean": "cd android && ./gradlew clean"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^3.1.1",
Expand Down
22 changes: 11 additions & 11 deletions example/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions example/src/attribution.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Linking } from 'react-native';
import { Linking, Platform } from 'react-native';
import LinkTrail, { type LinkTrailDeepLink } from 'linktrail-react-native';
import type { StoreAction } from './store';
import { ConsentManager, type ConsentState } from './consent';
Expand Down Expand Up @@ -92,13 +92,15 @@ export function startAttribution(
await LinkTrail.configure(API_KEY, {
linkDomains: ['link.kynxlabs.com', 'kick.linktrail.io'],
requireConsent: true,
// 'pasteButton' → the deferred-attribution click token is read from the
// clipboard only when the user taps <LinkTrailPasteButton/> (no iOS
// "Allow Paste" alert). autoTrackInstall MUST be false so the install
// waits for that tap instead of firing token-less at launch (which would
// mark the install tracked and make the paste tap a no-op).
// 'pasteButton' → iOS reads the deferred click token only when the user
// taps <LinkTrailPasteButton/> (no "Allow Paste" alert). Ignored on Android.
clickTokenSource: 'pasteButton',
autoTrackInstall: false,
// iOS: false so the install waits for the paste tap (firing token-less at
// launch would mark the install tracked and make the tap a no-op).
// Android: true — there's no paste button; it uses the Play Install
// Referrer, so the install must auto-track for deferred attribution to
// resolve. (Consent still gates what's *recorded*; links route regardless.)
autoTrackInstall: Platform.OS !== 'ios',
});
} catch (error) {
console.warn('LinkTrail configuration failed:', error);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "linktrail-react-native",
"version": "0.0.2",
"version": "0.0.3",
"description": "React Native SDK for LinkTrail — mobile attribution and deferred deep linking. A New Architecture TurboModule wrapping the native LinkTrail iOS and Android SDKs.",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
Loading