Skip to content
Merged
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
e8150ac
Unify callback pattern: remove iOS/Android platform branching
wmathurin May 22, 2026
c7ef13a
Point iosTests at rn-migration branch for unified callback code
wmathurin May 22, 2026
4ef4b15
Rebuild dist/ with unified callback pattern
wmathurin May 22, 2026
6f72e65
Fix: Make TurboModule lookups lazy for bridgeless mode
wmathurin May 23, 2026
040679e
Add react-native.config.js for Android autolinking
wmathurin May 23, 2026
970422a
Add android/ stub and react-native.config.js for autolinking
wmathurin May 23, 2026
224ec38
Move Android bridge source from SalesforceMobileSDK-Android
wmathurin May 23, 2026
49b40d6
Add androidTests/ mirroring iosTests/ pattern
wmathurin May 23, 2026
60a5f08
Set cmakeListsPath to null - no C++ JNI bindings needed
wmathurin May 23, 2026
b703cda
Remove android from codegenConfig - bridges are manually implemented
wmathurin May 23, 2026
de26cd6
Add JNI stub for autolinking C++ registration
wmathurin May 23, 2026
0650c91
Restore codegenConfig (iOS needs it for spec generation)
wmathurin May 23, 2026
adef4ab
Fix androidTests build config and add settings.gradle.kts to android/
wmathurin May 23, 2026
2d52749
Add shared/test/ for test credentials with symlinks
wmathurin May 23, 2026
51e2bf5
Fix androidTests build: AGP alignment, repos, gradle.properties
wmathurin May 23, 2026
f9664e6
Fix androidTests: compilation, credentials setup, gitignore build output
wmathurin May 23, 2026
fbf856a
Add test_credentials.json to gitignore for androidTests
wmathurin May 23, 2026
688fd61
Migrate test host to new architecture (DefaultReactNativeHost)
wmathurin May 23, 2026
85d6b69
Fix androidTests: use RN gradle plugin for new arch native build
wmathurin May 23, 2026
a0241bf
Gitignore .kotlin/ and .cxx/ build caches
wmathurin May 23, 2026
95c9e8d
Fix androidTests: use project include for SalesforceReact (same class…
wmathurin May 23, 2026
09422c3
Convert android/build.gradle.kts to Groovy for subproject compatibility
wmathurin May 23, 2026
874433b
Gitignore android/ library build output
wmathurin May 23, 2026
4a8ef1c
Use PackageList + compileOnly react-android for test project
wmathurin May 23, 2026
d9238c8
Pure autolinking for test project + debug logging
wmathurin May 23, 2026
ba8eecb
Fix SalesforceReactActivity for bridgeless mode
wmathurin May 23, 2026
af425c3
Revert react-android to api dependency
wmathurin May 23, 2026
3be03a4
Fix: C++ module provider creates JavaTurboModule wrappers
wmathurin May 23, 2026
73c89ba
Add DebugSalesforceReactPackage for TurboModule diagnosis
wmathurin May 23, 2026
13df888
Use proper codegen for Android TurboModule C++ wrappers
wmathurin May 23, 2026
b86319e
Revert react plugin from library, keep codegenConfig in package.json
wmathurin May 23, 2026
1ab1fe6
Pre-build Android codegen output for TurboModule JNI wrappers
wmathurin May 23, 2026
6ffb5f6
Unify module names: Android bridges now use spec names (SF* prefix)
wmathurin May 23, 2026
e7622e6
TurboModule discovery WORKING: fix ReactModuleInfo names
wmathurin May 23, 2026
f7c3801
Add retry mechanism for getCurrentActivity in OAuth bridge
wmathurin May 23, 2026
d24bb18
Point iosTests at rn-android-in-react-native branch
wmathurin May 23, 2026
16f3f34
Update docs: Android bridge now in this repo, unified module names
wmathurin May 23, 2026
84b0fa6
Add activity retry to Net bridge sendRequest (fixes timeout)
wmathurin May 24, 2026
f0387ee
Fix stale 'Android repo' references in CLAUDE.md
wmathurin May 24, 2026
26033eb
Refactor androidTests scripts + add Android CI
wmathurin May 26, 2026
48c223b
Update Android CI: use Firebase Test Lab (mirrors Android SDK repo pa…
wmathurin May 26, 2026
a24fa6b
Move codegen from android/build/ to android/generated/ for npm packaging
wmathurin May 26, 2026
bc4ca76
Fix: postinstall symlink for codegen at RN-expected path
wmathurin May 26, 2026
a59190d
Add Android codegen regeneration to setversion.sh
wmathurin May 26, 2026
9fa1576
CLAUDE.md: note codegen is regenerated by setversion.sh
wmathurin May 26, 2026
5995a6f
Revert test package.json to forcedotcom#dev before merge
wmathurin May 28, 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
4 changes: 4 additions & 0 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ jobs:
with:
ios: ${{ matrix.ios }}
xcode: ${{ matrix.xcode }}
secrets: inherit

android-nightly:
uses: ./.github/workflows/reusable-android-workflow.yaml
secrets: inherit
7 changes: 7 additions & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,11 @@ jobs:
is_pr: true
ios: ${{ matrix.ios }}
xcode: ${{ matrix.xcode }}
secrets: inherit

android-pr:
needs: [permission-check]
uses: ./.github/workflows/reusable-android-workflow.yaml
with:
is_pr: true
secrets: inherit
86 changes: 86 additions & 0 deletions .github/workflows/reusable-android-workflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
on:
workflow_call:
inputs:
is_pr:
type: boolean
default: false

jobs:
test-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
if: ${{ inputs.is_pr }}
with:
fetch-depth: 100
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/checkout@v4
if: ${{ ! inputs.is_pr }}
with:
ref: ${{ github.head_ref }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Dependencies
env:
TEST_CREDENTIALS: ${{ secrets.TEST_CREDENTIALS }}
run: |
npm install -g typescript
cd androidTests
./prepareandroid.js
./create_test_credentials_from_env.js
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '21'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.14.3"
Copy link
Copy Markdown
Contributor

@brandonpage brandonpage May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very NIT: This is probably out of date. This won't cause the build to fail or anything, it will just take more time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saw your comments after merging.
Will revisit in the next PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done here: c9941ba

add-job-summary: on-failure
- name: Build for Testing
run: |
cd androidTests/android
./gradlew :app:assembleDebug :app:assembleAndroidTest
- uses: 'google-github-actions/auth@v2'
if: success() || failure()
with:
credentials_json: '${{ secrets.GCLOUD_SERVICE_KEY }}'
- uses: 'google-github-actions/setup-gcloud@v2'
if: success() || failure()
- name: Run Tests on Firebase Test Lab
if: success() || failure()
run: |
APP_APK="androidTests/android/app/build/outputs/apk/debug/app-debug.apk"
TEST_APK="androidTests/android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"
gcloud firebase test android run \
--type instrumentation \
--app "$APP_APK" \
--test "$TEST_APK" \
--device model=Pixel2,version=33 \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did this device configuration come from?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saw your comments after merging.
Will revisit in the next PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done here: c9941ba

--timeout 10m \
--results-bucket cloud-test-${GITHUB_REPOSITORY_OWNER} \
--results-dir "SalesforceReactNative/${GITHUB_RUN_ID}" \
--no-record-video
- name: Copy Test Results
if: success() || failure()
run: |
BUCKET_PATH="gs://cloud-test-${GITHUB_REPOSITORY_OWNER}/SalesforceReactNative/${GITHUB_RUN_ID}"
mkdir -p firebase_results
gsutil -m cp -r "${BUCKET_PATH}/*/test_result_*.xml" firebase_results/ 2>/dev/null || true
- name: Test Report
uses: mikepenz/action-junit-report@v6
if: success() || failure()
with:
check_name: Android Test Results
require_tests: true
fail_on_failure: true
report_paths: 'firebase_results/**.xml'
- name: Archive Test Results
uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: test-results-android
path: 'firebase_results/**.xml'
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,23 @@ Podfile.lock
yarn.lock
.DS_Store

# Test credentials (contains real Salesforce org credentials)
shared/test/test_credentials.json

# Android test generated assets and credentials
androidTests/android/app/src/main/assets/index.android.bundle
androidTests/android/app/src/main/assets/test_credentials.json
androidTests/android/app/src/main/assets/drawable-mdpi/
androidTests/mobile_sdk/


# Gradle build output
androidTests/android/.gradle/
androidTests/android/app/build/

androidTests/android/.kotlin/
androidTests/android/app/.cxx/

# Android library build output
android/.gradle/
android/build/
87 changes: 49 additions & 38 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
The Salesforce Mobile SDK for React Native provides JavaScript/TypeScript bindings and native bridge modules that enable React Native applications to integrate with the Salesforce platform. This repo contains:

- **JavaScript/TypeScript libraries** (`src/`) - The public API exposed to React Native apps
- **iOS bridge code** (`ios/SalesforceReact/`) - Objective-C modules that bridge to iOS SDK
- **iOS bridge code** (`ios/SalesforceReact/`) - Objective-C++ TurboModule bridges to iOS SDK
- **Android bridge code** (`android/`) - Kotlin TurboModule bridges to Android SDK
- **iOS tests** (`iosTests/`) - XCTest suite for iOS bridge functionality
- **Android tests** (`androidTests/`) - AndroidX Test suite for Android bridge functionality
- **JavaScript tests** (`test/`) - Test suite for JavaScript API
- **Pre-built codegen** (`android/generated/`) - C++ JavaTurboModule wrappers committed to npm package. Regenerated by `setversion.sh` during releases. Must be regenerated manually if bridge methods change in `src/specs/`.

**Key constraint**: This is a **public SDK**. Every change is visible to external developers. Backward compatibility, deprecation cycles, and semver discipline are non-negotiable.

Expand All @@ -23,11 +26,11 @@ React Native App
├── JavaScript/TypeScript API (src/)
├── iOS Bridge (ios/SalesforceReact/)
│ └── depends on → SalesforceMobileSDK-iOS libraries
└── Android Bridge → SalesforceMobileSDK-Android/libs/SalesforceReact
└── Android Bridge (android/)
└── depends on → SalesforceMobileSDK-Android libraries
```

**Important**: The Android bridge code lives in `SalesforceMobileSDK-Android/libs/SalesforceReact`, NOT in this repo. This repo only contains iOS bridge code and shared JavaScript/TypeScript.
**Note**: As of SDK 14.0, the Android bridge code lives in this repo (`android/`), matching the iOS bridge layout. It was previously in `SalesforceMobileSDK-Android/libs/SalesforceReact/`. Both platforms now use unified `SF*` prefix module names (e.g. `SFOauthReactBridge`, `SFNetReactBridge`). React Native autolinking handles module registration automatically.

## Repository Structure

Expand All @@ -42,17 +45,25 @@ SalesforceMobileSDK-ReactNative/
│ ├── react.force.util.ts # Utilities
│ ├── react.force.log.ts # Logging
│ ├── react.force.test.ts # Test harness utilities
│ ├── specs/ # TurboModule codegen specs
│ └── typings/ # TypeScript type definitions
├── ios/SalesforceReact/ # iOS bridge modules (Objective-C)
│ ├── SFOauthReactBridge.m # OAuth bridge
│ ├── SFNetReactBridge.m # REST client bridge
│ ├── SFSmartStoreReactBridge.m # SmartStore bridge
│ ├── SFMobileSyncReactBridge.m # MobileSync bridge
│ ├── SFSDKReactLogger.m # Logging bridge
├── ios/SalesforceReact/ # iOS bridge modules (Objective-C++)
│ ├── SFOauthReactBridge.mm # OAuth bridge
│ ├── SFNetReactBridge.mm # REST client bridge
│ ├── SFSmartStoreReactBridge.mm # SmartStore bridge
│ ├── SFMobileSyncReactBridge.mm # MobileSync bridge
│ ├── SFSDKReactLogger.mm # Logging bridge
│ └── SalesforceReactSDKManager.m # SDK manager
├── android/ # Android bridge modules (Kotlin)
│ ├── src/main/java/.../bridge/ # Bridge classes (SF*ReactBridge.kt)
│ ├── src/main/java/.../ui/ # SalesforceReactActivity (bridgeless mode)
│ ├── src/main/java/.../app/ # SDK manager
│ └── build.gradle.kts # Gradle build config
├── iosTests/ # iOS test application
│ ├── ios/ # Xcode project with XCTest tests
│ └── prepareios.js # Test setup script
├── androidTests/ # Android test application (mirrors iosTests/)
│ └── mobile_sdk/ # Android SDK clone for testing
├── test/ # JavaScript test suite
│ ├── alltests.js # Test entry point
│ ├── oauth.test.js # OAuth tests
Expand All @@ -61,7 +72,7 @@ SalesforceMobileSDK-ReactNative/
│ ├── mobilesync.test.js # MobileSync tests
│ └── harness.test.js # Test infrastructure
├── dist/ # Compiled TypeScript output
├── package.json # npm package definition
├── package.json # npm package definition (includes codegenConfig)
├── SalesforceReact.podspec # CocoaPods spec for iOS
└── tsconfig.json # TypeScript configuration
```
Expand All @@ -79,7 +90,7 @@ The `SalesforceReact.podspec` declares dependencies on iOS SDK libraries:
These are pulled from the `SalesforceMobileSDK-iOS` repository via CocoaPods (published to `SalesforceMobileSDK-iOS-Specs`).

### Android Dependencies (via Gradle)
The Android bridge in `SalesforceMobileSDK-Android/libs/SalesforceReact` depends on:
The Android bridge in `android/` depends on:
- `MobileSync` library (which transitively includes SmartStore and SalesforceSDK)
- `react-android` from Facebook

Expand All @@ -96,17 +107,18 @@ React Native templates depend on this package via npm:
Each JavaScript module follows this pattern:

1. **JavaScript API** (`src/react.force.*.ts`): Public-facing TypeScript API
2. **Native Bridge**: Calls to native modules via React Native's `NativeModules`
3. **iOS Implementation** (`ios/SalesforceReact/SF*Bridge.m`): Objective-C code that calls iOS SDK
4. **Android Implementation** (`../Android/libs/SalesforceReact/src/`): Kotlin code that calls Android SDK
2. **TurboModule Spec** (`src/specs/NativeSF*.ts`): Codegen type definitions
3. **Native Bridge**: Calls to native modules via `TurboModuleRegistry` (with `NativeModules` fallback)
4. **iOS Implementation** (`ios/SalesforceReact/SF*Bridge.mm`): Objective-C++ code that calls iOS SDK
5. **Android Implementation** (`android/src/.../bridge/SF*Bridge.kt`): Kotlin code that calls Android SDK

Example data flow for a REST API call:
```
JS: net.sendRequest(...)
NativeModules.SFNetReactBridge.sendRequest(...)
→ iOS: SFNetReactBridge.m calls RestClient from SalesforceSDKCore
→ Android: NetReactBridge.kt calls RestClient from SalesforceSDK
← Native bridge returns promise/callback
→ SFNetReactBridge.sendRequest(...) (same module name on both platforms)
→ iOS: SFNetReactBridge.mm calls RestClient from SalesforceSDKCore
→ Android: SFNetReactBridge.kt calls RestClient from SalesforceSDK
← Native bridge returns via unified single-callback pattern
← JS: Returns promise to app
```

Expand All @@ -116,7 +128,7 @@ JS: net.sendRequest(...)
- **Node.js**: 20+ (see `package.json` engines)
- **TypeScript**: Installed via devDependencies
- **iOS Development**: Xcode 15+, macOS
- **Android Development**: For Android bridge testing, see Android repo
- **Android Development**: For Android bridge testing, see `androidTests/`

### Build JavaScript/TypeScript

Expand Down Expand Up @@ -161,7 +173,7 @@ The `test/` directory contains the shared test suite used by both iOS and Androi
```bash
# Tests are bundled into iOS/Android test apps
# See iosTests/README.md for iOS testing
# See Android repo for Android testing
# See androidTests/ for Android testing
```

**Test files**:
Expand Down Expand Up @@ -198,10 +210,11 @@ The `test/` directory contains the shared test suite used by both iOS and Androi
- **No direct UI work on bridge thread**: Dispatch to main queue when needed

### Android Bridge Standards (Kotlin)
See `SalesforceMobileSDK-Android/CLAUDE.md` for Android-specific standards. Key points:
- **Kotlin for all new code** in Android bridge
- **Kotlin for all new code** in Android bridge (`android/`)
- **TurboModule interface**: All bridges implement `TurboModule` and use the unified single-callback pattern
- **Coroutines preferred** for async operations
- Follow Android SDK conventions
- **Module names**: Must use `SF*` prefix matching iOS (e.g. `SFOauthReactBridge`)
- Follow Android SDK conventions (see `SalesforceMobileSDK-Android/CLAUDE.md`)

## Testing Standards

Expand All @@ -220,8 +233,8 @@ See `SalesforceMobileSDK-Android/CLAUDE.md` for Android-specific standards. Key

### Android Bridge Tests
- **Framework**: AndroidX Test with JUnit
- **Location**: `SalesforceMobileSDK-Android/libs/SalesforceReact` (separate repo)
- **Run command**: `./gradlew :libs:SalesforceReact:connectedAndroidTest`
- **Location**: `androidTests/` (mirrors `iosTests/`)
- **Run command**: See `androidTests/` for setup and execution

### What to Test for Each Module

Expand Down Expand Up @@ -258,18 +271,16 @@ See `SalesforceMobileSDK-Android/CLAUDE.md` for Android-specific standards. Key
When making changes that affect the public API:

1. **Modify TypeScript API** in `src/`
2. **Update iOS bridge** in `ios/SalesforceReact/`
3. **Update Android bridge** in `SalesforceMobileSDK-Android/libs/SalesforceReact/`
4. **Update type definitions** in `src/typings/`
5. **Add tests** in `test/` (shared by both platforms)
6. **Run iOS tests** via `iosTests/`
7. **Run Android tests** in Android repo
8. **Update React Native templates** if needed (in Templates repo)

**Important**: A complete feature requires changes in multiple repos:
- This repo (JavaScript + iOS bridge)
- Android repo (Android bridge)
- Templates repo (if template updates needed)
2. **Update TurboModule spec** in `src/specs/`
3. **Update iOS bridge** in `ios/SalesforceReact/`
4. **Update Android bridge** in `android/`
5. **Update type definitions** in `src/typings/`
6. **Add tests** in `test/` (shared by both platforms)
7. **Run iOS tests** via `iosTests/`
8. **Run Android tests** via `androidTests/`
9. **Update React Native templates** if needed (in Templates repo)

**Important**: A complete feature requires changes in this repo (JavaScript + iOS bridge + Android bridge) plus the Templates repo if template updates are needed. No changes to the Android SDK repo are needed for bridge work.

## Code Review Checklist

Expand All @@ -280,7 +291,7 @@ When reviewing PRs:
- [ ] **Backward compatibility**: No breaking changes without deprecation cycle
- [ ] **Tests included**: JavaScript tests cover new functionality
- [ ] **iOS tests pass**: Run iosTests suite
- [ ] **Android tests pass**: Verify in Android repo
- [ ] **Android tests pass**: Run androidTests/
- [ ] **Documentation**: JSDoc comments on public APIs
- [ ] **Templates work**: Test with ReactNativeTemplate if API changed
- [ ] **No console warnings**: No ESLint or TypeScript compiler warnings
Expand Down
11 changes: 11 additions & 0 deletions android/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="94"
android:versionName="14.0.0.dev">

<application>
<activity android:name=".ui.SalesforceReactActivity"
android:enableOnBackInvokedCallback="false" />
</application>
</manifest>
47 changes: 47 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'


android {
namespace "com.salesforce.androidsdk.reactnative"
compileSdk 36

defaultConfig {
minSdk 28
}

sourceSets {
main {
manifest.srcFile "AndroidManifest.xml"
java.srcDirs = ["src/main/java"]
res.srcDirs = ["res"]
assets.srcDirs = ["assets"]
}
}

buildFeatures {
buildConfig true
aidl true
}

packaging {
resources {
excludes += ['META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/NOTICE']
}
}

lint {
abortOnError false
}
}

kotlin {
jvmToolchain(17)
}

dependencies {
api 'com.salesforce.mobilesdk:MobileSync:14.0.0'
api 'com.facebook.react:react-android:0.81.5'
implementation 'androidx.core:core-ktx:1.18.0'
}

Loading
Loading