Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
22717d6
feat(types): add v6 cross-platform façade (Presentation builder, outc…
kherembourg May 28, 2026
ed749bc
feat(android): wire React Native bridge to v6 SDK (builder, outcome, …
kherembourg May 28, 2026
0ddd82f
chore(ios): scaffold v6 bridge category header + ignore caches
kherembourg May 28, 2026
816d479
feat(ios): implement v6 bridge category on PurchaselyRN
kherembourg May 28, 2026
7c52604
feat(ios): register v6 events on PurchaselyRN
kherembourg May 28, 2026
8d90635
chore(example): demonstrate v6 contract in example app
kherembourg May 28, 2026
62efeb5
docs: add v6 migration guide, changelog and bump to 6.0.0-beta.0
kherembourg May 28, 2026
2db370c
test(v6): add integration tests for façade ↔ native bridge contract
kherembourg May 28, 2026
3d1921e
fix(v6): address Greptile review findings
claude May 28, 2026
8dc55cc
fix(v6): bound Android interceptor wait; document default() resolution
kherembourg May 29, 2026
5de0998
feat(v6)!: remove the v5 paywall API — v6 is the only paywall surface
kherembourg May 29, 2026
4acc414
refactor(android): merge PurchaselyV6Bridge into PurchaselyModule — s…
kherembourg Jun 1, 2026
f5057a4
refactor: single iOS bridge + drop the "v6" naming everywhere
kherembourg Jun 1, 2026
a3a7290
fix(v6): align native bridge migration
kherembourg Jun 1, 2026
103bf5f
fix(v6): wire synchronize callback, pin natives to 6.0.0-rc.1, bump v…
kherembourg Jun 16, 2026
82f3582
chore(deps): migrate React Native to 0.86.0 (#253)
kherembourg Jun 16, 2026
a0be41e
fix(v6): complete iOS native bridge migration to rc.1; drop presentSu…
kherembourg Jun 16, 2026
b94856e
docs(v6): record iOS bridge completion and presentSubscriptions removal
kherembourg Jun 16, 2026
7b411cd
fix(v6): default unknown runningMode to observer, not full
kherembourg Jun 16, 2026
fe92031
fix(example): drop the removed presentSubscriptions button
kherembourg Jun 16, 2026
37dece6
chore(deps): pin Android native SDK to 6.0.0-beta.12
kherembourg Jun 23, 2026
e2e6b55
feat(v6)!: add setDefaultPresentationDismissHandler global handler
kherembourg Jun 23, 2026
ab24a6a
test(v6): cover the default dismiss handler
kherembourg Jun 23, 2026
16a6131
docs(v6): document setDefaultPresentationDismissHandler
kherembourg Jun 23, 2026
68c1ace
feat(v6)!: align RN public API with native v6 (outcome/deeplink/close…
kherembourg Jun 24, 2026
d6a6bff
test(e2e): add Android E2E functional suite T1–T10
kherembourg Jun 24, 2026
fb4bbd5
ci(e2e): add Android E2E nightly workflow with manual trigger
kherembourg Jun 24, 2026
48ea012
test(ci): temporary push trigger to validate E2E workflow on feature …
kherembourg Jun 24, 2026
0e3d1c2
chore(android): bump all Purchasely native deps to 6.0.0-rc.2
kherembourg Jun 24, 2026
8a7d23f
fix(ci): switch E2E emulator to ubuntu-latest + KVM + x86_64
kherembourg Jun 24, 2026
3863adb
test(e2e): drop T6, simplify T9 purchase interceptor
kherembourg Jun 25, 2026
51336ed
test(e2e): renumber T7-T10 → T6-T9, add E2E_TEST_INDEX.md
kherembourg Jun 25, 2026
825f85d
test(e2e): expand suite T1-T13 with property assertions + iOS skeleton
kherembourg Jun 25, 2026
7e88605
ci(e2e): switch to Mac mini self-hosted runner + fix JS bundle + fix …
kherembourg Jun 25, 2026
52f871a
ci(e2e): use macos-14 runner (Apple Silicon, ARM64, no KVM)
kherembourg Jun 25, 2026
98d57eb
ci(e2e): add android-actions/setup-android to fix sdkmanager PATH on …
kherembourg Jun 26, 2026
70fb664
ci(e2e): switch back to ubuntu-latest + KVM + x86_64 for Android E2E
kherembourg Jun 26, 2026
26151e5
ci(e2e): add android-actions/setup-android on ubuntu (sdkmanager not …
kherembourg Jun 26, 2026
f261257
ci(e2e): use reactivecircuit/android-emulator-runner to fix AVD setup
kherembourg Jun 26, 2026
da6047c
ci(e2e): fix action name ReactiveCircus/android-emulator-runner (typo)
kherembourg Jun 26, 2026
5bb9d5f
ci(e2e): switch to release APK + verify bundle + better logcat dump
kherembourg Jun 26, 2026
e0b117d
chore(ios): bump native iOS SDK dependency to 6.0.0-rc.2
kherembourg Jun 26, 2026
6e1733f
test(e2e): add iOS E2E suite (T1-T13) + CI workflow
kherembourg Jun 26, 2026
b4864bf
ci(e2e-ios): use macos-15 + select Xcode 16 (RN 0.79 needs >=16.1)
kherembourg Jun 26, 2026
b7f4344
chore: reconcile yarn.lock after rebase onto main
kherembourg Jun 26, 2026
76e3848
test(e2e): pin exact closeReason + assert purchaseResult in T7/T9
kherembourg Jun 26, 2026
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
102 changes: 102 additions & 0 deletions .github/workflows/e2e-android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: E2E Android

on:
workflow_dispatch:
schedule:
# 2h00 UTC chaque nuit
- cron: '0 2 * * *'
push:
branches:
- feat/sdk-v6-migration
paths:
- 'packages/purchasely/android/build.gradle'
- 'packages/*/android/build.gradle'
- '.github/workflows/e2e-android.yml'
- 'integration_test/**'
- 'example/src/E2ETestRunner.tsx'
- 'example/android/app/build.gradle'

concurrency:
group: e2e-android
cancel-in-progress: true

jobs:
e2e-android:
name: E2E Tests (Android T1-T13)
# ubuntu-latest: KVM disponible pour x86_64, requis par reactivecircuit/android-emulator-runner.
# macOS sera utilisé dans e2e-ios.yml (simulateur iOS).
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'

- name: Setup Java 17
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
with:
gradle-version: wrapper
cache-read-only: false

- name: Install JS dependencies
run: yarn install --immutable

- name: Build JS SDK
run: yarn purchasely:prepare

- name: Build release APK (JS always bundled in release)
# Release builds always embed the JS bundle — no Metro bundler needed in CI.
# ProGuard is disabled in build.gradle (enableProguardInReleaseBuilds = false).
# -x lintVitalRelease skips lint step that false-positives on ReactActivity.
env:
JAVA_OPTS: "-XX:MaxHeapSize=4g"
run: |
cd example/android
./gradlew :app:assembleRelease -x lintVitalRelease
# Verify the JS bundle is embedded
APK="app/build/outputs/apk/release/app-release.apk"
if unzip -l "$APK" | grep -q "index.android.bundle"; then
echo "✅ JS bundle confirmed in APK"
else
echo "❌ JS bundle NOT found in APK"
exit 1
fi

- name: Run E2E suite on emulator
# ReactiveCircus/android-emulator-runner handles: system-image install,
# AVD creation, emulator start with KVM, boot wait, animations disable.
uses: ReactiveCircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
target: google_apis
avd-name: rn_e2e_api34
disable-animations: true
emulator-options: -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -no-snapshot
script: bash integration_test/run_e2e.sh emulator-5554 --skip-build

- name: Upload logcat on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: logcat-${{ github.run_id }}
path: /tmp/e2e_rn_logcat_*.log
retention-days: 7
107 changes: 107 additions & 0 deletions .github/workflows/e2e-ios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: E2E iOS

on:
workflow_dispatch:
schedule:
# 2h30 UTC chaque nuit (décalé de l'E2E Android à 2h00)
- cron: '30 2 * * *'
push:
branches:
- feat/sdk-v6-migration
paths:
- 'packages/purchasely/react-native-purchasely.podspec'
- 'packages/purchasely/ios/**'
- '.github/workflows/e2e-ios.yml'
- 'integration_test/**'
- 'example/src/E2ETestRunner.tsx'
- 'example/ios/**'

concurrency:
group: e2e-ios
cancel-in-progress: true

jobs:
e2e-ios:
name: E2E Tests (iOS T1-T13)
# macOS requis : simulateur iOS + xcodebuild. Pas de HVF nécessaire (le
# simulateur iOS ne virtualise pas un OS invité, contrairement à l'émulateur
# Android ARM — c'est pourquoi Android tourne sur ubuntu + KVM).
# macos-15 : Xcode 16.x par défaut (RN 0.79 exige Xcode >= 16.1).
runs-on: macos-15
timeout-minutes: 60

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Select latest Xcode 16 (RN 0.79 requires >= 16.1)
run: |
LATEST=$(ls -d /Applications/Xcode_16*.app 2>/dev/null | sort -V | tail -1)
if [ -n "$LATEST" ]; then sudo xcode-select -s "$LATEST/Contents/Developer"; fi
xcodebuild -version

- name: Setup (Node + Yarn)
uses: ./.github/actions/setup

- name: Build JS SDK
run: yarn prepare

- name: Cache CocoaPods
uses: actions/cache@v4
with:
path: |
example/ios/Pods
~/.cocoapods
~/Library/Caches/CocoaPods
key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock', 'packages/purchasely/react-native-purchasely.podspec') }}
restore-keys: |
${{ runner.os }}-cocoapods-

- name: Install CocoaPods
run: |
cd example/ios
pod install --repo-update
env:
NO_FLIPPER: 1

- name: Install idb (companion + client)
run: |
# idb_companion : tap Facebook (nécessite brew trust sur les brew récents).
brew tap facebook/fb
brew trust facebook/fb || true
brew install idb-companion
# fb-idb (client Python) ne supporte pas Python 3.14 (asyncio.get_event_loop
# y lève) — on l'isole dans un venv 3.11/3.12/3.13 déterministe et on
# exporte le binaire via $IDB (lu par les drivers tap/swipe).
PY=$(command -v python3.12 || command -v python3.11 || command -v python3.13 || command -v python3)
"$PY" -m venv "$RUNNER_TEMP/idbvenv"
"$RUNNER_TEMP/idbvenv/bin/pip" install -q --upgrade pip fb-idb
echo "IDB=$RUNNER_TEMP/idbvenv/bin/idb" >> "$GITHUB_ENV"
"$RUNNER_TEMP/idbvenv/bin/idb" --help >/dev/null && echo "✅ idb client OK"

- name: Boot iOS simulator
run: |
UDID=$(xcrun simctl list devices available -j | python3 -c "
import sys, json
d = json.load(sys.stdin)['devices']
cands = [v for k, vs in d.items() if 'iOS' in k for v in vs if 'iPhone' in v['name']]
# iPhone 15/16 de préférence, sinon le dernier iPhone disponible
pref = [c for c in cands if any(n in c['name'] for n in ('iPhone 16', 'iPhone 15'))]
chosen = (pref or cands)[-1]
print(chosen['udid'])
")
echo "Booting simulator $UDID"
xcrun simctl boot "$UDID"
xcrun simctl bootstatus "$UDID" -b
echo "IOS_SIMULATOR_UDID=$UDID" >> "$GITHUB_ENV"

- name: Run E2E suite on simulator (build Release + T1-T13)
run: bash integration_test/run_e2e_ios.sh "$IOS_SIMULATOR_UDID"

- name: Upload logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: ios-e2e-logs-${{ github.run_id }}
path: /tmp/e2e_rn_ios_*.log
retention-days: 7
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,7 @@ nitrogen/


.nx/cache
.nx/workspace-data
.nx/workspace-data
jest_dx/
node-compile-cache/
**/coverage/
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20
v22
79 changes: 48 additions & 31 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@

| Property | Value |
|----------|-------|
| Current Version | 5.7.3 |
| Current Version | 6.0.0-rc.1 |
| React Native | 0.79.2 |
| TypeScript | 5.2.2 (strict mode) |
| Node.js | v20 (see `.nvmrc`) |
| Package Manager | Yarn 3.6.1 (workspaces) |
| Native iOS SDK | 5.7.4 |
| Native Android SDK | 5.7.4 |
| Native iOS SDK | 6.0.0 |
| Native Android SDK | 6.0.0 |

### Supported App Stores
- Apple App Store (iOS)
Expand Down Expand Up @@ -231,39 +231,56 @@ The following sections provide quick API examples. For comprehensive documentati

Refer to the [SDK Public Documentation](../sdk_public_doc.md).

### Initialization
> **v6 paywall API only.** The v5 paywall methods (`start({...})`,
> `presentPresentationForPlacement`, `presentPresentationWithIdentifier`,
> `presentProductWithIdentifier`, `presentPlanWithIdentifier`,
> `fetchPresentation`, `setPaywallActionInterceptorCallback`, `onProcessAction`,
> `setDefaultPresentationResultCallback`, `readyToOpenDeeplink`, …) are
> **removed**. Use the builders below. Full mapping: `MIGRATION-v6.md`.

### Initialization (v6 builder)

```typescript
import Purchasely, { LogLevels, RunningMode } from 'react-native-purchasely'

await Purchasely.start({
apiKey: 'YOUR_API_KEY',
androidStores: ['Google'], // or ['Huawei', 'Amazon']
storeKit1: false, // iOS: use StoreKit 2
userId: 'user_id', // optional
logLevel: LogLevels.DEBUG,
runningMode: RunningMode.FULL
})
import Purchasely from 'react-native-purchasely'

await Purchasely.builder('YOUR_API_KEY')
.appUserId('user_id') // optional
.runningMode('full') // 'observer' (default) | 'full'
.logLevel('debug') // 'debug' | 'info' | 'warn' | 'error'
.allowDeeplink(true) // replaces readyToOpenDeeplink(true)
.stores(['google']) // Android only: 'google' | 'huawei' | 'amazon'
.storekitVersion('storeKit2') // iOS only: 'storeKit1' | 'storeKit2'
.start()
```

### Presentation Methods
### Presentation Methods (v6 builders)

`Purchasely.presentation` is the `PresentationBuilder`. `build()` returns a
`PresentationRequest`; `display()` resolves at dismiss with a 5-field
`PresentationOutcome` (`{ presentation, purchaseResult, plan, closeReason,
error }`).

```typescript
// Fetch presentation data
const presentation = await Purchasely.fetchPresentation({
placementVendorId: 'ONBOARDING',
contentId: 'content_123'
})
// Preload a placement (was fetchPresentation)
const request = Purchasely.presentation.placement('ONBOARDING').build()
const presentation = await request.preload()

// Present full-screen paywall
const result = await Purchasely.presentPresentationForPlacement({
placementVendorId: 'ONBOARDING',
isFullscreen: true
})
// Present a placement full-screen (was presentPresentationForPlacement)
const outcome = await Purchasely.presentation.placement('ONBOARDING').build().display()

// Present specific product or plan
await Purchasely.presentProductWithIdentifier('product_id')
await Purchasely.presentPlanWithIdentifier('plan_id')
// Present a specific screen (was presentPresentationWithIdentifier)
await Purchasely.presentation.screen('SCREEN_ID').build().display()

// Present a specific product / plan (was presentProductWithIdentifier / presentPlanWithIdentifier)
await Purchasely.presentation.screen('SCREEN_ID').contentId('CONTENT_ID').build().display()

// Lifecycle: request.display() (show) / request.close() (hide) / request.back()

// Action interception (was setPaywallActionInterceptorCallback + onProcessAction)
Purchasely.interceptAction('purchase', async (info, payload) => {
// return 'success' | 'failed' | 'notHandled'
return 'notHandled'
})
```

### Event Listening
Expand Down Expand Up @@ -388,13 +405,13 @@ Build orchestration with caching:
### Native Dependencies

**iOS (CocoaPods):**
- Purchasely SDK v5.7.4
- Purchasely SDK v6.0.0
- Deployment target: iOS 13.4

**Android (Gradle):**
- io.purchasely:core:5.7.4
- io.purchasely:core:6.0.0
- Min SDK: 21
- Kotlin: 1.9+
- Kotlin: 2.1+
- Java: 11

---
Expand Down
Loading
Loading