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
72 changes: 72 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* ESLint configuration for react-native-appsflyer plugin
* Optimized for TypeScript + React Native library development
*/

module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: { jsx: true },
},

env: {
node: true,
es6: true,
},

plugins: [
'@typescript-eslint',
],

extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],

rules: {
// JS/TS hygiene
'no-console': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off', // Allow require() in CommonJS files

// Import/export rules - disabled because TypeScript handles this
// and our index.d.ts properly declares all exports for ESLint compatibility
'import/default': 'off',
'import/named': 'off',
'import/no-unresolved': 'off',
},
overrides: [
{
files: ['expo/**/*.js'],
rules: {
'@typescript-eslint/no-var-requires': 'off', // Expo config plugins use require()
},
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
// TypeScript-specific rules
'no-undef': 'off', // TypeScript handles this
},
},
],

ignorePatterns: [
'node_modules/**',
'demos/**',
'android/**',
'ios/**',
'build/**',
'dist/**',
'__tests__/**',
'*.config.js',
'babel.config.js',
'metro.config.js',
'jest.config.js',
'react-native.config.js',
],
};
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
## 6.17.8
Release date: *2025-12-17*

- React Native >> Update Android SDK to 6.17.5
- React Native >> Update iOS SDK to 6.17.8
- React Native >> Update iOS Purchase Connector to 6.17.8
- React Native >> Fixed callback double-invocation crash in React Native New Architecture on Android
- React Native >> Fixed TypeScript return type for `validateAndLogInAppPurchaseV2`
- React Native >> Fixed TypeScript type issues (`onFailure` → `OnFailure`, import paths)
- React Native >> Fixed Expo Android build failure related to backup rules
- React Native >> Added `preferAppsFlyerBackupRules` flag for Expo Android (default: false)
- React Native >> Enhanced iOS Swift header import with CocoaPods fallback
- React Native >> Added ESLint configuration and lint scripts
- React Native >> Update Plugin to v6.17.8

## 6.17.7
Release date: *2025-10-22*
Release date: 2025-10-22

- React Native >> Update Android SDK to 6.17.7
- React Native >> Update iOS SDK to 6.17.7
Expand Down
73 changes: 57 additions & 16 deletions Docs/RN_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,9 @@ appsFlyer.setSharingFilterForPartners(['googleadwords_int', 'all']);

### validateAndLogInAppPurchase
`validateAndLogInAppPurchase(purchaseInfo, successC, errorC): Response<string>`

> ⚠️ **Deprecated**: This API is deprecated. Use `validateAndLogInAppPurchaseV2` instead.

Receipt validation is a secure mechanism whereby the payment platform (e.g. Apple or Google) validates that an in-app purchase indeed occurred as reported.
Learn more - https://support.appsflyer.com/hc/en-us/articles/207032106-Receipt-validation-for-in-app-purchases
❗Important❗ for iOS - set SandBox to ```true```
Expand Down Expand Up @@ -628,20 +631,22 @@ appsFlyer.validateAndLogInAppPurchase(info, res => console.log(res), err => cons
```

---
<!--
## New In-App Purchase Validation API
The new `validateAndLogInAppPurchase` API uses `AFPurchaseDetails` and `AFPurchaseType` enum for structured purchase validation.

### AFPurchaseType Enum
### validateAndLogInAppPurchaseV2
`validateAndLogInAppPurchaseV2(purchaseDetails, additionalParameters, callback): void`

The `validateAndLogInAppPurchaseV2` API uses `AFPurchaseDetails` and `AFPurchaseType` enum for structured purchase validation.

#### AFPurchaseType Enum

```javascript
import { AFPurchaseType } from 'react-native-appsflyer';

AFPurchaseType.SUBSCRIPTION // "subscription"
AFPurchaseType.ONE_TIME_PURCHASE // "one-time-purchase"
AFPurchaseType.ONE_TIME_PURCHASE // "one_time_purchase"
```

### AFPurchaseDetails Interface
#### AFPurchaseDetails Interface

```typescript
interface AFPurchaseDetails {
Expand All @@ -651,15 +656,15 @@ interface AFPurchaseDetails {
}
```

### Usage Example
#### Usage Example

```javascript
import appsFlyer, { AFPurchaseType } from 'react-native-appsflyer';

const purchaseDetails = {
purchaseType: AFPurchaseType.SUBSCRIPTION,
transactionId: "google_play_purchase_token_123",
productId: "com.example.app.premium_monthly"
purchaseType: AFPurchaseType.ONE_TIME_PURCHASE,
transactionId: "2000000569065806",
productId: "deviceIdconsumableid"
};

const additionalParams = {
Expand All @@ -670,10 +675,19 @@ const additionalParams = {
appsFlyer.validateAndLogInAppPurchaseV2(
purchaseDetails,
additionalParams,
(result) => console.log(result)
(result) => {
if (result.error) {
console.error('Validation failed:', result.error);
} else {
console.log('Validation success:', result);
}
}
);
```
-->
**Important Notes:**
- The callback receives both `result` and `error` parameters
- Always check for `error` first before processing `result`
- Handle the case where `AFSDKPurchaseDetails` creation might fail
---

### updateServerUninstallToken
Expand Down Expand Up @@ -824,12 +838,39 @@ appsFlyer.enableTCFDataCollection(true);
### setConsentData
`setConsentData(consentObject): void`

When GDPR applies to the user and your app does not use a CMP compatible with TCF v2.2, use this API to provide the consent data directly to the SDK.<br>
The AppsFlyerConsent object has 2 methods:
When GDPR applies to the user and your app does not use a CMP compatible with TCF v2.2, use this API to provide the consent data directly to the SDK.

1. `AppsFlyerConsent.forNonGDPRUser`: Indicates that GDPR doesn’t apply to the user and generates nonGDPR consent object. This method doesn’t accept any parameters.
2. `AppsFlyerConsent.forGDPRUser`: create an AppsFlyerConsent object with 2 parameters:
**Recommended approach (since v6.16.2):**
Use the `AppsFlyerConsent` constructor:

```javascript
import appsFlyer, {AppsFlyerConsent} from 'react-native-appsflyer';

// Full consent for GDPR user
const consent1 = new AppsFlyerConsent(true, true, true, true);

// No consent for GDPR user
const consent2 = new AppsFlyerConsent(true, false, false, false);

// Non-GDPR user
const consent3 = new AppsFlyerConsent(false);

appsFlyer.setConsentData(consent1);
```

**Constructor parameters:**
| parameter | type | description |
| ---------- |----------|------------------ |
| isUserSubjectToGDPR | boolean | Whether GDPR applies to the user (required) |
| hasConsentForDataUsage | boolean | Consent for data usage (optional) |
| hasConsentForAdsPersonalization | boolean | Consent for ads personalization (optional) |
| hasConsentForAdStorage | boolean | Consent for ad storage (optional) |

**Deprecated approach (still supported):**
The AppsFlyerConsent object has 2 deprecated methods:

1. `AppsFlyerConsent.forNonGDPRUser`: Indicates that GDPR doesn't apply to the user and generates nonGDPR consent object. This method doesn't accept any parameters.
2. `AppsFlyerConsent.forGDPRUser`: create an AppsFlyerConsent object with 2 parameters:

| parameter | type | description |
| ---------- |----------|------------------ |
Expand Down
1 change: 1 addition & 0 deletions Docs/RN_EspIntegration.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ adb shell am start -W -a android.intent.action.VIEW -d "https://your-onelink-dom
<manifest xmlns:tools="http://schemas.android.com/tools">
<application android:allowBackup="false" tools:replace="android:allowBackup">
```
See [AppsFlyer Android SDK documentation](https://dev.appsflyer.com/hc/docs/install-android-sdk#backup-rules) for more details.

**4. Package attribute deprecated:**
- Remove `package="com.yourapp"` from AndroidManifest.xml
Expand Down
47 changes: 44 additions & 3 deletions Docs/RN_ExpoInstallation.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ expo install react-native-appsflyer
"react-native-appsflyer",
{
"shouldUseStrictMode": false, // optional – kids-apps strict mode
"shouldUsePurchaseConnector": true // NEW – enables Purchase Connector
"shouldUsePurchaseConnector": true, // optional – enables Purchase Connector
"preferAppsFlyerBackupRules": false // optional – use AppsFlyer SDK backup rules (default: false)
}
]
],
Expand All @@ -42,8 +43,40 @@ expo install react-native-appsflyer
],
...
```
### Fix for build failure with RN 0.76 and Expo 52
To ensure seamless integration of the AppsFlyer plugin in your Expo-managed project, it’s essential to handle modifications to the AndroidManifest.xml correctly. Since direct edits to the AndroidManifest.xml aren’t feasible in the managed workflow, you’ll need to create a custom configuration to include the necessary changes.
### Backup Rules Configuration (Android)

The AppsFlyer SDK includes built-in backup rules in its Android manifest to ensure accurate install/reinstall detection. By default, the plugin respects your app's backup rules and does not modify them.

**Default Behavior** (`preferAppsFlyerBackupRules: false` or omitted):
- Your app's `android:dataExtractionRules` and `android:fullBackupContent` attributes are left untouched
- You maintain full control over your app's backup policy
- No manifest merge conflicts occur

**Opt-in Behavior** (`preferAppsFlyerBackupRules: true`):
- If your app defines backup rules, they will be removed to let AppsFlyer SDK's built-in rules take precedence
- This ensures AppsFlyer SDK's backup rules are used, which may improve install/reinstall detection accuracy
- Use this flag if you want AppsFlyer SDK to manage backup rules for you

**When to use `preferAppsFlyerBackupRules: true`:**
- You want AppsFlyer SDK to handle backup rules automatically
- You're experiencing issues with install/reinstall detection that may be related to backup rules
- You don't have specific backup requirements for your app

**Example configuration:**
```json
{
"expo": {
"plugins": [
[
"react-native-appsflyer",
{
"preferAppsFlyerBackupRules": true
}
]
]
}
}
```

### Handling dataExtractionRules Conflict

Expand Down Expand Up @@ -123,3 +156,11 @@ Setting `"shouldUsePurchaseConnector": true` will:

* **iOS** – add the `PurchaseConnector` CocoaPod automatically
* **Android** – add `appsflyer.enable_purchase_connector=true` to `gradle.properties`

### Plugin Options Summary

| Option | Type | Default | Description |
|-------|------|---------|-------------|
| `shouldUseStrictMode` | boolean | `false` | Enable strict mode for kids apps |
| `shouldUsePurchaseConnector` | boolean | `false` | Enable Purchase Connector support |
| `preferAppsFlyerBackupRules` | boolean | `false` | Remove app's backup rules to use AppsFlyer SDK's built-in rules (Android only) |
3 changes: 3 additions & 0 deletions Docs/RN_InAppEvents.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ appsFlyer.logEvent(

---
## In-app purchase validation

> ⚠️ **Deprecated**: The `validateAndLogInAppPurchase` API is deprecated. Use `validateAndLogInAppPurchaseV2` instead. See the [API reference](/Docs/RN_API.md#validateandloginapppurchasev2) for details.

Receipt validation is a secure mechanism whereby the payment platform (e.g. Apple or Google) validates that an in-app purchase indeed occurred as reported.
Learn more [here](https://support.appsflyer.com/hc/en-us/articles/207032106-Receipt-validation-for-in-app-purchases).

Expand Down
6 changes: 3 additions & 3 deletions PurchaseConnector/models/canceled_state_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class CanceledStateContext {
class DeveloperInitiatedCancellation {
constructor() {}

static fromJson(json: any): DeveloperInitiatedCancellation {
static fromJson(_json: any): DeveloperInitiatedCancellation {
// Here you would implement the conversion from JSON to DeveloperInitiatedCancellation instance
return new DeveloperInitiatedCancellation();
}
Expand All @@ -66,7 +66,7 @@ class DeveloperInitiatedCancellation {
class ReplacementCancellation {
constructor() {}

static fromJson(json: any): ReplacementCancellation {
static fromJson(_json: any): ReplacementCancellation {
// Here you would implement the conversion from JSON to ReplacementCancellation instance
return new ReplacementCancellation();
}
Expand All @@ -79,7 +79,7 @@ class ReplacementCancellation {
class SystemInitiatedCancellation {
constructor() {}

static fromJson(json: any): SystemInitiatedCancellation {
static fromJson(_json: any): SystemInitiatedCancellation {
// Here you would implement the conversion from JSON to SystemInitiatedCancellation instance
return new SystemInitiatedCancellation();
}
Expand Down
2 changes: 1 addition & 1 deletion PurchaseConnector/models/test_purchase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ interface TestPurchaseJson {}
export class TestPurchase {
constructor() {}

static fromJson(json: TestPurchaseJson): TestPurchase {
static fromJson(_json: TestPurchaseJson): TestPurchase {
return new TestPurchase();
}

Expand Down
3 changes: 2 additions & 1 deletion PurchaseConnector/utils/connector_callbacks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IosError, JVMThrowable } from "../models";
import { IosError } from "../models/ios_errors";
import { JVMThrowable } from "../models/jvm_throwable";

// Type definition for a general-purpose listener.
export type PurchaseConnectorListener = (data: any) => void;
Expand Down
Loading
Loading