diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 3b3b531..ac81f89 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -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 \ No newline at end of file diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 576780b..d0f68da 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -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 \ No newline at end of file diff --git a/.github/workflows/reusable-android-workflow.yaml b/.github/workflows/reusable-android-workflow.yaml new file mode 100644 index 0000000..1fa781b --- /dev/null +++ b/.github/workflows/reusable-android-workflow.yaml @@ -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" + 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 \ + --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' diff --git a/.gitignore b/.gitignore index a50a191..8b9da2d 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/CLAUDE.md b/CLAUDE.md index bb226bb..c4e212f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. @@ -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 @@ -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 @@ -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 ``` @@ -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 @@ -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 ``` @@ -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 @@ -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**: @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 0000000..959afc0 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..21b41ca --- /dev/null +++ b/android/build.gradle @@ -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' +} + diff --git a/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFMobileSyncReactBridgeSpec.java b/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFMobileSyncReactBridgeSpec.java new file mode 100644 index 0000000..8165b5d --- /dev/null +++ b/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFMobileSyncReactBridgeSpec.java @@ -0,0 +1,59 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJavaSpec.js + * + * @nolint + */ + +package com.facebook.fbreact.specs; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import javax.annotation.Nonnull; + +public abstract class NativeSFMobileSyncReactBridgeSpec extends ReactContextBaseJavaModule implements TurboModule { + public static final String NAME = "SFMobileSyncReactBridge"; + + public NativeSFMobileSyncReactBridgeSpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public @Nonnull String getName() { + return NAME; + } + + @ReactMethod + @DoNotStrip + public abstract void syncDown(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void syncUp(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void reSync(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void getSyncStatus(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void deleteSync(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void cleanResyncGhosts(ReadableMap args, Callback callback); +} diff --git a/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFNetReactBridgeSpec.java b/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFNetReactBridgeSpec.java new file mode 100644 index 0000000..d3cb95b --- /dev/null +++ b/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFNetReactBridgeSpec.java @@ -0,0 +1,39 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJavaSpec.js + * + * @nolint + */ + +package com.facebook.fbreact.specs; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import javax.annotation.Nonnull; + +public abstract class NativeSFNetReactBridgeSpec extends ReactContextBaseJavaModule implements TurboModule { + public static final String NAME = "SFNetReactBridge"; + + public NativeSFNetReactBridgeSpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public @Nonnull String getName() { + return NAME; + } + + @ReactMethod + @DoNotStrip + public abstract void sendRequest(ReadableMap args, Callback callback); +} diff --git a/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFOauthReactBridgeSpec.java b/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFOauthReactBridgeSpec.java new file mode 100644 index 0000000..7745a65 --- /dev/null +++ b/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFOauthReactBridgeSpec.java @@ -0,0 +1,47 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJavaSpec.js + * + * @nolint + */ + +package com.facebook.fbreact.specs; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import javax.annotation.Nonnull; + +public abstract class NativeSFOauthReactBridgeSpec extends ReactContextBaseJavaModule implements TurboModule { + public static final String NAME = "SFOauthReactBridge"; + + public NativeSFOauthReactBridgeSpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public @Nonnull String getName() { + return NAME; + } + + @ReactMethod + @DoNotStrip + public abstract void getAuthCredentials(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void authenticate(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void logoutCurrentUser(ReadableMap args, Callback callback); +} diff --git a/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFSmartStoreReactBridgeSpec.java b/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFSmartStoreReactBridgeSpec.java new file mode 100644 index 0000000..50a8646 --- /dev/null +++ b/android/generated/source/codegen/java/com/facebook/fbreact/specs/NativeSFSmartStoreReactBridgeSpec.java @@ -0,0 +1,115 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJavaSpec.js + * + * @nolint + */ + +package com.facebook.fbreact.specs; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.turbomodule.core.interfaces.TurboModule; +import javax.annotation.Nonnull; + +public abstract class NativeSFSmartStoreReactBridgeSpec extends ReactContextBaseJavaModule implements TurboModule { + public static final String NAME = "SFSmartStoreReactBridge"; + + public NativeSFSmartStoreReactBridgeSpec(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public @Nonnull String getName() { + return NAME; + } + + @ReactMethod + @DoNotStrip + public abstract void soupExists(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void registerSoup(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void removeSoup(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void getSoupIndexSpecs(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void alterSoup(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void reIndexSoup(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void clearSoup(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void upsertSoupEntries(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void retrieveSoupEntries(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void removeFromSoup(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void querySoup(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void runSmartQuery(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void moveCursorToPageIndex(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void closeCursor(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void getDatabaseSize(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void getAllStores(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void getAllGlobalStores(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void removeStore(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void removeAllStores(ReadableMap args, Callback callback); + + @ReactMethod + @DoNotStrip + public abstract void removeAllGlobalStores(ReadableMap args, Callback callback); +} diff --git a/android/generated/source/codegen/jni/CMakeLists.txt b/android/generated/source/codegen/jni/CMakeLists.txt new file mode 100644 index 0000000..f53d718 --- /dev/null +++ b/android/generated/source/codegen/jni/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/RNSalesforceReactSpec/*.cpp) + +add_library( + react_codegen_RNSalesforceReactSpec + OBJECT + ${react_codegen_SRCS} +) + +target_include_directories(react_codegen_RNSalesforceReactSpec PUBLIC . react/renderer/components/RNSalesforceReactSpec) + +target_link_libraries( + react_codegen_RNSalesforceReactSpec + fbjni + jsi + # We need to link different libraries based on whether we are building rncore or not, that's necessary + # because we want to break a circular dependency between react_codegen_rncore and reactnative + reactnative +) + +target_compile_reactnative_options(react_codegen_RNSalesforceReactSpec PRIVATE) diff --git a/android/generated/source/codegen/jni/RNSalesforceReactSpec-generated.cpp b/android/generated/source/codegen/jni/RNSalesforceReactSpec-generated.cpp new file mode 100644 index 0000000..646d81d --- /dev/null +++ b/android/generated/source/codegen/jni/RNSalesforceReactSpec-generated.cpp @@ -0,0 +1,224 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJniCpp.js + */ + +#include "RNSalesforceReactSpec.h" + +namespace facebook::react { + +static facebook::jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_syncDown(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "syncDown", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_syncUp(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "syncUp", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_reSync(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "reSync", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_getSyncStatus(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "getSyncStatus", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_deleteSync(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "deleteSync", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_cleanResyncGhosts(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "cleanResyncGhosts", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +NativeSFMobileSyncReactBridgeSpecJSI::NativeSFMobileSyncReactBridgeSpecJSI(const JavaTurboModule::InitParams ¶ms) + : JavaTurboModule(params) { + methodMap_["syncDown"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_syncDown}; + methodMap_["syncUp"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_syncUp}; + methodMap_["reSync"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_reSync}; + methodMap_["getSyncStatus"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_getSyncStatus}; + methodMap_["deleteSync"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_deleteSync}; + methodMap_["cleanResyncGhosts"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeSpecJSI_cleanResyncGhosts}; +} +static facebook::jsi::Value __hostFunction_NativeSFNetReactBridgeSpecJSI_sendRequest(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "sendRequest", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +NativeSFNetReactBridgeSpecJSI::NativeSFNetReactBridgeSpecJSI(const JavaTurboModule::InitParams ¶ms) + : JavaTurboModule(params) { + methodMap_["sendRequest"] = MethodMetadata {2, __hostFunction_NativeSFNetReactBridgeSpecJSI_sendRequest}; +} +static facebook::jsi::Value __hostFunction_NativeSFOauthReactBridgeSpecJSI_getAuthCredentials(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "getAuthCredentials", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFOauthReactBridgeSpecJSI_authenticate(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "authenticate", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFOauthReactBridgeSpecJSI_logoutCurrentUser(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "logoutCurrentUser", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +NativeSFOauthReactBridgeSpecJSI::NativeSFOauthReactBridgeSpecJSI(const JavaTurboModule::InitParams ¶ms) + : JavaTurboModule(params) { + methodMap_["getAuthCredentials"] = MethodMetadata {2, __hostFunction_NativeSFOauthReactBridgeSpecJSI_getAuthCredentials}; + methodMap_["authenticate"] = MethodMetadata {2, __hostFunction_NativeSFOauthReactBridgeSpecJSI_authenticate}; + methodMap_["logoutCurrentUser"] = MethodMetadata {2, __hostFunction_NativeSFOauthReactBridgeSpecJSI_logoutCurrentUser}; +} +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_soupExists(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "soupExists", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_registerSoup(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "registerSoup", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeSoup(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "removeSoup", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_getSoupIndexSpecs(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "getSoupIndexSpecs", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_alterSoup(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "alterSoup", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_reIndexSoup(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "reIndexSoup", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_clearSoup(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "clearSoup", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_upsertSoupEntries(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "upsertSoupEntries", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_retrieveSoupEntries(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "retrieveSoupEntries", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeFromSoup(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "removeFromSoup", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_querySoup(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "querySoup", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_runSmartQuery(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "runSmartQuery", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_moveCursorToPageIndex(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "moveCursorToPageIndex", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_closeCursor(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "closeCursor", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_getDatabaseSize(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "getDatabaseSize", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_getAllStores(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "getAllStores", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_getAllGlobalStores(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "getAllGlobalStores", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeStore(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "removeStore", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeAllStores(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "removeAllStores", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +static facebook::jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeAllGlobalStores(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { + static jmethodID cachedMethodId = nullptr; + return static_cast(turboModule).invokeJavaMethod(rt, VoidKind, "removeAllGlobalStores", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Callback;)V", args, count, cachedMethodId); +} + +NativeSFSmartStoreReactBridgeSpecJSI::NativeSFSmartStoreReactBridgeSpecJSI(const JavaTurboModule::InitParams ¶ms) + : JavaTurboModule(params) { + methodMap_["soupExists"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_soupExists}; + methodMap_["registerSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_registerSoup}; + methodMap_["removeSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeSoup}; + methodMap_["getSoupIndexSpecs"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_getSoupIndexSpecs}; + methodMap_["alterSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_alterSoup}; + methodMap_["reIndexSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_reIndexSoup}; + methodMap_["clearSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_clearSoup}; + methodMap_["upsertSoupEntries"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_upsertSoupEntries}; + methodMap_["retrieveSoupEntries"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_retrieveSoupEntries}; + methodMap_["removeFromSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeFromSoup}; + methodMap_["querySoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_querySoup}; + methodMap_["runSmartQuery"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_runSmartQuery}; + methodMap_["moveCursorToPageIndex"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_moveCursorToPageIndex}; + methodMap_["closeCursor"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_closeCursor}; + methodMap_["getDatabaseSize"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_getDatabaseSize}; + methodMap_["getAllStores"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_getAllStores}; + methodMap_["getAllGlobalStores"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_getAllGlobalStores}; + methodMap_["removeStore"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeStore}; + methodMap_["removeAllStores"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeAllStores}; + methodMap_["removeAllGlobalStores"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeSpecJSI_removeAllGlobalStores}; +} + +std::shared_ptr RNSalesforceReactSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { + if (moduleName == "SFMobileSyncReactBridge") { + return std::make_shared(params); + } + if (moduleName == "SFNetReactBridge") { + return std::make_shared(params); + } + if (moduleName == "SFOauthReactBridge") { + return std::make_shared(params); + } + if (moduleName == "SFSmartStoreReactBridge") { + return std::make_shared(params); + } + return nullptr; +} + +} // namespace facebook::react diff --git a/android/generated/source/codegen/jni/RNSalesforceReactSpec.h b/android/generated/source/codegen/jni/RNSalesforceReactSpec.h new file mode 100644 index 0000000..b1b46cb --- /dev/null +++ b/android/generated/source/codegen/jni/RNSalesforceReactSpec.h @@ -0,0 +1,55 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleJniH.js + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +/** + * JNI C++ class for module 'NativeSFMobileSyncReactBridge' + */ +class JSI_EXPORT NativeSFMobileSyncReactBridgeSpecJSI : public JavaTurboModule { +public: + NativeSFMobileSyncReactBridgeSpecJSI(const JavaTurboModule::InitParams ¶ms); +}; + +/** + * JNI C++ class for module 'NativeSFNetReactBridge' + */ +class JSI_EXPORT NativeSFNetReactBridgeSpecJSI : public JavaTurboModule { +public: + NativeSFNetReactBridgeSpecJSI(const JavaTurboModule::InitParams ¶ms); +}; + +/** + * JNI C++ class for module 'NativeSFOauthReactBridge' + */ +class JSI_EXPORT NativeSFOauthReactBridgeSpecJSI : public JavaTurboModule { +public: + NativeSFOauthReactBridgeSpecJSI(const JavaTurboModule::InitParams ¶ms); +}; + +/** + * JNI C++ class for module 'NativeSFSmartStoreReactBridge' + */ +class JSI_EXPORT NativeSFSmartStoreReactBridgeSpecJSI : public JavaTurboModule { +public: + NativeSFSmartStoreReactBridgeSpecJSI(const JavaTurboModule::InitParams ¶ms); +}; + + +JSI_EXPORT +std::shared_ptr RNSalesforceReactSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms); + +} // namespace facebook::react diff --git a/android/generated/source/codegen/jni/react/renderer/components/RNSalesforceReactSpec/RNSalesforceReactSpecJSI-generated.cpp b/android/generated/source/codegen/jni/react/renderer/components/RNSalesforceReactSpec/RNSalesforceReactSpecJSI-generated.cpp new file mode 100644 index 0000000..e1c7e81 --- /dev/null +++ b/android/generated/source/codegen/jni/react/renderer/components/RNSalesforceReactSpec/RNSalesforceReactSpecJSI-generated.cpp @@ -0,0 +1,302 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleCpp.js + */ + +#include "RNSalesforceReactSpecJSI.h" + +namespace facebook::react { + +static jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_syncDown(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->syncDown( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_syncUp(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->syncUp( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_reSync(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->reSync( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_getSyncStatus(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->getSyncStatus( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_deleteSync(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->deleteSync( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_cleanResyncGhosts(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->cleanResyncGhosts( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} + +NativeSFMobileSyncReactBridgeCxxSpecJSI::NativeSFMobileSyncReactBridgeCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule("SFMobileSyncReactBridge", jsInvoker) { + methodMap_["syncDown"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_syncDown}; + methodMap_["syncUp"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_syncUp}; + methodMap_["reSync"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_reSync}; + methodMap_["getSyncStatus"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_getSyncStatus}; + methodMap_["deleteSync"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_deleteSync}; + methodMap_["cleanResyncGhosts"] = MethodMetadata {2, __hostFunction_NativeSFMobileSyncReactBridgeCxxSpecJSI_cleanResyncGhosts}; +} +static jsi::Value __hostFunction_NativeSFNetReactBridgeCxxSpecJSI_sendRequest(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->sendRequest( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} + +NativeSFNetReactBridgeCxxSpecJSI::NativeSFNetReactBridgeCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule("SFNetReactBridge", jsInvoker) { + methodMap_["sendRequest"] = MethodMetadata {2, __hostFunction_NativeSFNetReactBridgeCxxSpecJSI_sendRequest}; +} +static jsi::Value __hostFunction_NativeSFOauthReactBridgeCxxSpecJSI_getAuthCredentials(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->getAuthCredentials( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFOauthReactBridgeCxxSpecJSI_authenticate(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->authenticate( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFOauthReactBridgeCxxSpecJSI_logoutCurrentUser(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->logoutCurrentUser( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} + +NativeSFOauthReactBridgeCxxSpecJSI::NativeSFOauthReactBridgeCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule("SFOauthReactBridge", jsInvoker) { + methodMap_["getAuthCredentials"] = MethodMetadata {2, __hostFunction_NativeSFOauthReactBridgeCxxSpecJSI_getAuthCredentials}; + methodMap_["authenticate"] = MethodMetadata {2, __hostFunction_NativeSFOauthReactBridgeCxxSpecJSI_authenticate}; + methodMap_["logoutCurrentUser"] = MethodMetadata {2, __hostFunction_NativeSFOauthReactBridgeCxxSpecJSI_logoutCurrentUser}; +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_soupExists(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->soupExists( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_registerSoup(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->registerSoup( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeSoup(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->removeSoup( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_getSoupIndexSpecs(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->getSoupIndexSpecs( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_alterSoup(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->alterSoup( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_reIndexSoup(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->reIndexSoup( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_clearSoup(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->clearSoup( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_upsertSoupEntries(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->upsertSoupEntries( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_retrieveSoupEntries(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->retrieveSoupEntries( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeFromSoup(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->removeFromSoup( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_querySoup(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->querySoup( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_runSmartQuery(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->runSmartQuery( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_moveCursorToPageIndex(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->moveCursorToPageIndex( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_closeCursor(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->closeCursor( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_getDatabaseSize(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->getDatabaseSize( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_getAllStores(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->getAllStores( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_getAllGlobalStores(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->getAllGlobalStores( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeStore(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->removeStore( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeAllStores(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->removeAllStores( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} +static jsi::Value __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeAllGlobalStores(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->removeAllGlobalStores( + rt, + count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt), + count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asObject(rt).asFunction(rt) + ); + return jsi::Value::undefined(); +} + +NativeSFSmartStoreReactBridgeCxxSpecJSI::NativeSFSmartStoreReactBridgeCxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule("SFSmartStoreReactBridge", jsInvoker) { + methodMap_["soupExists"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_soupExists}; + methodMap_["registerSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_registerSoup}; + methodMap_["removeSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeSoup}; + methodMap_["getSoupIndexSpecs"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_getSoupIndexSpecs}; + methodMap_["alterSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_alterSoup}; + methodMap_["reIndexSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_reIndexSoup}; + methodMap_["clearSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_clearSoup}; + methodMap_["upsertSoupEntries"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_upsertSoupEntries}; + methodMap_["retrieveSoupEntries"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_retrieveSoupEntries}; + methodMap_["removeFromSoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeFromSoup}; + methodMap_["querySoup"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_querySoup}; + methodMap_["runSmartQuery"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_runSmartQuery}; + methodMap_["moveCursorToPageIndex"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_moveCursorToPageIndex}; + methodMap_["closeCursor"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_closeCursor}; + methodMap_["getDatabaseSize"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_getDatabaseSize}; + methodMap_["getAllStores"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_getAllStores}; + methodMap_["getAllGlobalStores"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_getAllGlobalStores}; + methodMap_["removeStore"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeStore}; + methodMap_["removeAllStores"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeAllStores}; + methodMap_["removeAllGlobalStores"] = MethodMetadata {2, __hostFunction_NativeSFSmartStoreReactBridgeCxxSpecJSI_removeAllGlobalStores}; +} + + +} // namespace facebook::react diff --git a/android/generated/source/codegen/jni/react/renderer/components/RNSalesforceReactSpec/RNSalesforceReactSpecJSI.h b/android/generated/source/codegen/jni/react/renderer/components/RNSalesforceReactSpec/RNSalesforceReactSpecJSI.h new file mode 100644 index 0000000..878c484 --- /dev/null +++ b/android/generated/source/codegen/jni/react/renderer/components/RNSalesforceReactSpec/RNSalesforceReactSpecJSI.h @@ -0,0 +1,467 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateModuleH.js + */ + +#pragma once + +#include +#include + +namespace facebook::react { + + + class JSI_EXPORT NativeSFMobileSyncReactBridgeCxxSpecJSI : public TurboModule { +protected: + NativeSFMobileSyncReactBridgeCxxSpecJSI(std::shared_ptr jsInvoker); + +public: + virtual void syncDown(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void syncUp(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void reSync(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void getSyncStatus(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void deleteSync(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void cleanResyncGhosts(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + +}; + +template +class JSI_EXPORT NativeSFMobileSyncReactBridgeCxxSpec : public TurboModule { +public: + jsi::Value create(jsi::Runtime &rt, const jsi::PropNameID &propName) override { + return delegate_.create(rt, propName); + } + + std::vector getPropertyNames(jsi::Runtime& runtime) override { + return delegate_.getPropertyNames(runtime); + } + + static constexpr std::string_view kModuleName = "SFMobileSyncReactBridge"; + +protected: + NativeSFMobileSyncReactBridgeCxxSpec(std::shared_ptr jsInvoker) + : TurboModule(std::string{NativeSFMobileSyncReactBridgeCxxSpec::kModuleName}, jsInvoker), + delegate_(reinterpret_cast(this), jsInvoker) {} + + +private: + class Delegate : public NativeSFMobileSyncReactBridgeCxxSpecJSI { + public: + Delegate(T *instance, std::shared_ptr jsInvoker) : + NativeSFMobileSyncReactBridgeCxxSpecJSI(std::move(jsInvoker)), instance_(instance) { + + } + + void syncDown(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::syncDown) == 3, + "Expected syncDown(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::syncDown, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void syncUp(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::syncUp) == 3, + "Expected syncUp(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::syncUp, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void reSync(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::reSync) == 3, + "Expected reSync(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::reSync, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void getSyncStatus(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::getSyncStatus) == 3, + "Expected getSyncStatus(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::getSyncStatus, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void deleteSync(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::deleteSync) == 3, + "Expected deleteSync(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::deleteSync, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void cleanResyncGhosts(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::cleanResyncGhosts) == 3, + "Expected cleanResyncGhosts(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::cleanResyncGhosts, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + + private: + friend class NativeSFMobileSyncReactBridgeCxxSpec; + T *instance_; + }; + + Delegate delegate_; +}; + + + class JSI_EXPORT NativeSFNetReactBridgeCxxSpecJSI : public TurboModule { +protected: + NativeSFNetReactBridgeCxxSpecJSI(std::shared_ptr jsInvoker); + +public: + virtual void sendRequest(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + +}; + +template +class JSI_EXPORT NativeSFNetReactBridgeCxxSpec : public TurboModule { +public: + jsi::Value create(jsi::Runtime &rt, const jsi::PropNameID &propName) override { + return delegate_.create(rt, propName); + } + + std::vector getPropertyNames(jsi::Runtime& runtime) override { + return delegate_.getPropertyNames(runtime); + } + + static constexpr std::string_view kModuleName = "SFNetReactBridge"; + +protected: + NativeSFNetReactBridgeCxxSpec(std::shared_ptr jsInvoker) + : TurboModule(std::string{NativeSFNetReactBridgeCxxSpec::kModuleName}, jsInvoker), + delegate_(reinterpret_cast(this), jsInvoker) {} + + +private: + class Delegate : public NativeSFNetReactBridgeCxxSpecJSI { + public: + Delegate(T *instance, std::shared_ptr jsInvoker) : + NativeSFNetReactBridgeCxxSpecJSI(std::move(jsInvoker)), instance_(instance) { + + } + + void sendRequest(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::sendRequest) == 3, + "Expected sendRequest(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::sendRequest, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + + private: + friend class NativeSFNetReactBridgeCxxSpec; + T *instance_; + }; + + Delegate delegate_; +}; + + + class JSI_EXPORT NativeSFOauthReactBridgeCxxSpecJSI : public TurboModule { +protected: + NativeSFOauthReactBridgeCxxSpecJSI(std::shared_ptr jsInvoker); + +public: + virtual void getAuthCredentials(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void authenticate(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void logoutCurrentUser(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + +}; + +template +class JSI_EXPORT NativeSFOauthReactBridgeCxxSpec : public TurboModule { +public: + jsi::Value create(jsi::Runtime &rt, const jsi::PropNameID &propName) override { + return delegate_.create(rt, propName); + } + + std::vector getPropertyNames(jsi::Runtime& runtime) override { + return delegate_.getPropertyNames(runtime); + } + + static constexpr std::string_view kModuleName = "SFOauthReactBridge"; + +protected: + NativeSFOauthReactBridgeCxxSpec(std::shared_ptr jsInvoker) + : TurboModule(std::string{NativeSFOauthReactBridgeCxxSpec::kModuleName}, jsInvoker), + delegate_(reinterpret_cast(this), jsInvoker) {} + + +private: + class Delegate : public NativeSFOauthReactBridgeCxxSpecJSI { + public: + Delegate(T *instance, std::shared_ptr jsInvoker) : + NativeSFOauthReactBridgeCxxSpecJSI(std::move(jsInvoker)), instance_(instance) { + + } + + void getAuthCredentials(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::getAuthCredentials) == 3, + "Expected getAuthCredentials(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::getAuthCredentials, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void authenticate(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::authenticate) == 3, + "Expected authenticate(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::authenticate, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void logoutCurrentUser(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::logoutCurrentUser) == 3, + "Expected logoutCurrentUser(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::logoutCurrentUser, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + + private: + friend class NativeSFOauthReactBridgeCxxSpec; + T *instance_; + }; + + Delegate delegate_; +}; + + + class JSI_EXPORT NativeSFSmartStoreReactBridgeCxxSpecJSI : public TurboModule { +protected: + NativeSFSmartStoreReactBridgeCxxSpecJSI(std::shared_ptr jsInvoker); + +public: + virtual void soupExists(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void registerSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void removeSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void getSoupIndexSpecs(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void alterSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void reIndexSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void clearSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void upsertSoupEntries(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void retrieveSoupEntries(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void removeFromSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void querySoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void runSmartQuery(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void moveCursorToPageIndex(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void closeCursor(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void getDatabaseSize(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void getAllStores(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void getAllGlobalStores(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void removeStore(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void removeAllStores(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + virtual void removeAllGlobalStores(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) = 0; + +}; + +template +class JSI_EXPORT NativeSFSmartStoreReactBridgeCxxSpec : public TurboModule { +public: + jsi::Value create(jsi::Runtime &rt, const jsi::PropNameID &propName) override { + return delegate_.create(rt, propName); + } + + std::vector getPropertyNames(jsi::Runtime& runtime) override { + return delegate_.getPropertyNames(runtime); + } + + static constexpr std::string_view kModuleName = "SFSmartStoreReactBridge"; + +protected: + NativeSFSmartStoreReactBridgeCxxSpec(std::shared_ptr jsInvoker) + : TurboModule(std::string{NativeSFSmartStoreReactBridgeCxxSpec::kModuleName}, jsInvoker), + delegate_(reinterpret_cast(this), jsInvoker) {} + + +private: + class Delegate : public NativeSFSmartStoreReactBridgeCxxSpecJSI { + public: + Delegate(T *instance, std::shared_ptr jsInvoker) : + NativeSFSmartStoreReactBridgeCxxSpecJSI(std::move(jsInvoker)), instance_(instance) { + + } + + void soupExists(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::soupExists) == 3, + "Expected soupExists(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::soupExists, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void registerSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::registerSoup) == 3, + "Expected registerSoup(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::registerSoup, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void removeSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::removeSoup) == 3, + "Expected removeSoup(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::removeSoup, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void getSoupIndexSpecs(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::getSoupIndexSpecs) == 3, + "Expected getSoupIndexSpecs(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::getSoupIndexSpecs, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void alterSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::alterSoup) == 3, + "Expected alterSoup(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::alterSoup, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void reIndexSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::reIndexSoup) == 3, + "Expected reIndexSoup(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::reIndexSoup, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void clearSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::clearSoup) == 3, + "Expected clearSoup(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::clearSoup, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void upsertSoupEntries(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::upsertSoupEntries) == 3, + "Expected upsertSoupEntries(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::upsertSoupEntries, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void retrieveSoupEntries(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::retrieveSoupEntries) == 3, + "Expected retrieveSoupEntries(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::retrieveSoupEntries, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void removeFromSoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::removeFromSoup) == 3, + "Expected removeFromSoup(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::removeFromSoup, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void querySoup(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::querySoup) == 3, + "Expected querySoup(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::querySoup, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void runSmartQuery(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::runSmartQuery) == 3, + "Expected runSmartQuery(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::runSmartQuery, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void moveCursorToPageIndex(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::moveCursorToPageIndex) == 3, + "Expected moveCursorToPageIndex(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::moveCursorToPageIndex, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void closeCursor(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::closeCursor) == 3, + "Expected closeCursor(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::closeCursor, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void getDatabaseSize(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::getDatabaseSize) == 3, + "Expected getDatabaseSize(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::getDatabaseSize, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void getAllStores(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::getAllStores) == 3, + "Expected getAllStores(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::getAllStores, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void getAllGlobalStores(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::getAllGlobalStores) == 3, + "Expected getAllGlobalStores(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::getAllGlobalStores, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void removeStore(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::removeStore) == 3, + "Expected removeStore(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::removeStore, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void removeAllStores(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::removeAllStores) == 3, + "Expected removeAllStores(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::removeAllStores, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + void removeAllGlobalStores(jsi::Runtime &rt, jsi::Object args, jsi::Function callback) override { + static_assert( + bridging::getParameterCount(&T::removeAllGlobalStores) == 3, + "Expected removeAllGlobalStores(...) to have 3 parameters"); + + return bridging::callFromJs( + rt, &T::removeAllGlobalStores, jsInvoker_, instance_, std::move(args), std::move(callback)); + } + + private: + friend class NativeSFSmartStoreReactBridgeCxxSpec; + T *instance_; + }; + + Delegate delegate_; +}; + +} // namespace facebook::react diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..5bac8ac --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/android/res/values/sf__strings.xml b/android/res/values/sf__strings.xml new file mode 100644 index 0000000..6e2411f --- /dev/null +++ b/android/res/values/sf__strings.xml @@ -0,0 +1,4 @@ + + + Should authenticate but is offline - cannot proceed + \ No newline at end of file diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml new file mode 100644 index 0000000..03423bf --- /dev/null +++ b/android/res/values/strings.xml @@ -0,0 +1,5 @@ + + + SalesforceReact + com.salesforce.androidsdk.reactnative + \ No newline at end of file diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..301c741 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,15 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + @Suppress("UnstableApiUsage") + repositories { + google() + mavenCentral() + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/app/SalesforceReactPackage.kt b/android/src/main/java/com/salesforce/androidsdk/reactnative/app/SalesforceReactPackage.kt new file mode 100644 index 0000000..417635d --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/app/SalesforceReactPackage.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.app + +import com.facebook.react.BaseReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.facebook.react.uimanager.ViewManager +import com.salesforce.androidsdk.reactnative.bridge.MobileSyncReactBridge +import com.salesforce.androidsdk.reactnative.bridge.SalesforceNetReactBridge +import com.salesforce.androidsdk.reactnative.bridge.SalesforceOauthReactBridge +import com.salesforce.androidsdk.reactnative.bridge.SmartStoreReactBridge + +class SalesforceReactPackage : BaseReactPackage() { + + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + return when (name) { + "SFOauthReactBridge" -> SalesforceOauthReactBridge(reactContext) + "SFNetReactBridge" -> SalesforceNetReactBridge(reactContext) + "SFSmartStoreReactBridge" -> SmartStoreReactBridge(reactContext) + "SFMobileSyncReactBridge" -> MobileSyncReactBridge(reactContext) + else -> null + } + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + return ReactModuleInfoProvider { + mapOf( + "SFOauthReactBridge" to ReactModuleInfo( + "SFOauthReactBridge", "SFOauthReactBridge", false, false, false, true + ), + "SFNetReactBridge" to ReactModuleInfo( + "SFNetReactBridge", "SFNetReactBridge", false, false, false, true + ), + "SFSmartStoreReactBridge" to ReactModuleInfo( + "SFSmartStoreReactBridge", "SFSmartStoreReactBridge", false, false, false, true + ), + "SFMobileSyncReactBridge" to ReactModuleInfo( + "SFMobileSyncReactBridge", "SFMobileSyncReactBridge", false, false, false, true + ), + ) + } + } + + // Also provide createNativeModules for legacy interop in composite builds where the + // TurboModule JNI classloader check may fail due to different classloaders. + override fun createNativeModules(reactContext: ReactApplicationContext): List { + return listOf( + SalesforceOauthReactBridge(reactContext), + SalesforceNetReactBridge(reactContext), + SmartStoreReactBridge(reactContext), + MobileSyncReactBridge(reactContext), + ) + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return emptyList() + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/app/SalesforceReactSDKManager.java b/android/src/main/java/com/salesforce/androidsdk/reactnative/app/SalesforceReactSDKManager.java new file mode 100644 index 0000000..9cc795c --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/app/SalesforceReactSDKManager.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2014-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.app; + +import static androidx.annotation.VisibleForTesting.PROTECTED; + +import android.app.Activity; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.facebook.react.ReactPackage; +import com.salesforce.androidsdk.mobilesync.app.MobileSyncSDKManager; +import com.salesforce.androidsdk.reactnative.ui.SalesforceReactActivity; +import com.salesforce.androidsdk.ui.LoginActivity; +import com.salesforce.androidsdk.util.EventsObservable; +import com.salesforce.androidsdk.util.EventsObservable.EventType; + +import java.util.List; +import java.util.Map; + +/** + * SDK Manager for all react native applications + */ +public class SalesforceReactSDKManager extends MobileSyncSDKManager { + + /** + * Protected constructor. + * + * @param context Application context. + * @param mainActivity Activity that should be launched after the login flow. + * @param loginActivity Login activity. + */ + protected SalesforceReactSDKManager(Context context, Class mainActivity, + Class loginActivity) { + super(context, mainActivity, loginActivity, null); + } + + private static void init(Context context, Class mainActivity, + Class loginActivity) { + if (INSTANCE == null) { + INSTANCE = new SalesforceReactSDKManager(context, mainActivity, loginActivity); + } + + // Upgrade to the latest version. + SalesforceReactUpgradeManager.getInstance().upgrade(); + initInternal(context); + EventsObservable.get().notifyEvent(EventType.AppCreateComplete); + } + + /** + * Initializes components required for this class + * to properly function. This method should be called + * by react native apps using the Salesforce Mobile SDK. + * + * @param context Application context. + * @param mainActivity Activity that should be launched after the login flow. + */ + public static void initReactNative(Context context, Class mainActivity) { + SalesforceReactSDKManager.init(context, mainActivity, LoginActivity.class); + } + + /** + * Initializes components required for this class + * to properly function. This method should be called + * by react native apps using the Salesforce Mobile SDK. + * + * @param context Application context. + * @param mainActivity Activity that should be launched after the login flow. + * @param loginActivity Login activity. + * @noinspection unused + */ + public static void initReactNative(Context context, Class mainActivity, + Class loginActivity) { + SalesforceReactSDKManager.init(context, mainActivity, loginActivity); + } + + /** + * Returns a singleton instance of this class. + * + * @return Singleton instance of SalesforceReactSDKManager. + */ + @NonNull + public static SalesforceReactSDKManager getInstance() { + if (INSTANCE != null) { + return (SalesforceReactSDKManager) INSTANCE; + } else { + throw new RuntimeException("Applications need to call SalesforceReactSDKManager.init() first."); + } + } + + @NonNull + @Override + public String getAppType() { + return "ReactNative"; + } + + /** + * Call this method when setting up ReactInstanceManager + * + * @return ReactPackage for this application + */ + public ReactPackage getReactPackage() { + return new SalesforceReactPackage(); + } + + @NonNull + @Override + @VisibleForTesting(otherwise = PROTECTED) + public Map getDevActions( + @NonNull final Activity frontActivity + ) { + Map devActions = super.getDevActions(frontActivity); + devActions.put( + "React Native Dev Support", + () -> ((SalesforceReactActivity) frontActivity).showReactDevOptionsDialog() + ); + + return devActions; + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/app/SalesforceReactUpgradeManager.java b/android/src/main/java/com/salesforce/androidsdk/reactnative/app/SalesforceReactUpgradeManager.java new file mode 100644 index 0000000..5c9ef5d --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/app/SalesforceReactUpgradeManager.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.app; + +import com.salesforce.androidsdk.mobilesync.app.MobileSyncUpgradeManager; + +/** + * This class handles upgrades from one version to another. + * + * @author bhariharan + */ +public class SalesforceReactUpgradeManager extends MobileSyncUpgradeManager { + + private static SalesforceReactUpgradeManager INSTANCE = null; + + /** + * Returns an instance of this class. + * + * @return Instance of this class. + */ + public static synchronized SalesforceReactUpgradeManager getInstance() { + if (INSTANCE == null) { + INSTANCE = new SalesforceReactUpgradeManager(); + } + return INSTANCE; + } + + @Override + public void upgrade() { + super.upgrade(); + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/MobileSyncReactBridge.kt b/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/MobileSyncReactBridge.kt new file mode 100644 index 0000000..b1ffa5d --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/MobileSyncReactBridge.kt @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2015-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.bridge + +import com.facebook.react.bridge.Callback +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.turbomodule.core.interfaces.TurboModule +import com.salesforce.androidsdk.mobilesync.manager.SyncManager +import com.salesforce.androidsdk.mobilesync.target.SyncDownTarget +import com.salesforce.androidsdk.mobilesync.target.SyncUpTarget +import com.salesforce.androidsdk.mobilesync.util.SyncOptions +import com.salesforce.androidsdk.mobilesync.util.SyncState +import com.salesforce.androidsdk.reactnative.util.SalesforceReactLogger +import org.json.JSONObject + +class MobileSyncReactBridge(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext), TurboModule { + + companion object { + // Keys in json from/to javascript + const val TARGET = "target" + const val SOUP_NAME = "soupName" + const val OPTIONS = "options" + const val SYNC_ID = "syncId" + const val SYNC_NAME = "syncName" + const val TAG = "SFMobileSyncReactBridge" + } + + override fun getName(): String = TAG + + /** + * Native implementation of syncUp + * @param args + * @param callback + */ + @ReactMethod + fun syncUp(args: ReadableMap, callback: Callback) { + // Parse args + val target = JSONObject(ReactBridgeHelper.toJavaMap(args.getMap(TARGET))) + val soupName = args.getString(SOUP_NAME)!! + val options = JSONObject(ReactBridgeHelper.toJavaMap(args.getMap(OPTIONS))) + val syncName = if (args.hasKey(SYNC_NAME)) args.getString(SYNC_NAME) else null + try { + val syncManager = getSyncManager(args) + syncManager.syncUp( + SyncUpTarget.fromJSON(target), + SyncOptions.fromJSON(options), + soupName, + syncName, + object : SyncManager.SyncUpdateCallback { + override fun onUpdate(sync: SyncState) { + handleSyncUpdate(sync, callback) + } + } + ) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "syncUp call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of syncDown + * @param args + * @param callback + */ + @ReactMethod + fun syncDown(args: ReadableMap, callback: Callback) { + // Parse args + val target = JSONObject(ReactBridgeHelper.toJavaMap(args.getMap(TARGET))) + val soupName = args.getString(SOUP_NAME)!! + val options = JSONObject(ReactBridgeHelper.toJavaMap(args.getMap(OPTIONS))) + val syncName = if (args.hasKey(SYNC_NAME)) args.getString(SYNC_NAME) else null + try { + val syncManager = getSyncManager(args) + syncManager.syncDown( + SyncDownTarget.fromJSON(target), + SyncOptions.fromJSON(options), + soupName, + syncName, + object : SyncManager.SyncUpdateCallback { + override fun onUpdate(sync: SyncState) { + handleSyncUpdate(sync, callback) + } + } + ) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "syncDown call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of getSyncStatus + * @param args + * @param callback + */ + @ReactMethod + fun getSyncStatus(args: ReadableMap, callback: Callback) { + try { + val syncManager = getSyncManager(args) + val sync = when { + args.hasKey(SYNC_ID) && !args.isNull(SYNC_ID) -> + syncManager.getSyncStatus(args.getInt(SYNC_ID).toLong()) + args.hasKey(SYNC_NAME) && !args.isNull(SYNC_NAME) -> + syncManager.getSyncStatus(args.getString(SYNC_NAME)) + else -> + throw SyncManager.MobileSyncException("neither $SYNC_ID nor $SYNC_NAME were specified") + } + ReactBridgeHelper.invokeSuccess(callback, sync?.asJSON()) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "getSyncStatusByName call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of deleteSync + * @param args + * @param callback + */ + @ReactMethod + fun deleteSync(args: ReadableMap, callback: Callback) { + try { + val syncManager = getSyncManager(args) + when { + args.hasKey(SYNC_ID) && !args.isNull(SYNC_ID) -> + syncManager.deleteSync(args.getInt(SYNC_ID).toLong()) + args.hasKey(SYNC_NAME) && !args.isNull(SYNC_NAME) -> + syncManager.deleteSync(args.getString(SYNC_NAME)) + else -> + throw SyncManager.MobileSyncException("neither $SYNC_ID nor $SYNC_NAME were specified") + } + ReactBridgeHelper.invokeSuccess(callback) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "deleteSyncById call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of reSync + * @param args + * @param callback + */ + @ReactMethod + fun reSync(args: ReadableMap, callback: Callback) { + try { + val syncManager = getSyncManager(args) + val syncUpdateCallback = object : SyncManager.SyncUpdateCallback { + override fun onUpdate(sync: SyncState) { + handleSyncUpdate(sync, callback) + } + } + when { + args.hasKey(SYNC_ID) && !args.isNull(SYNC_ID) -> + syncManager.reSync(args.getInt(SYNC_ID).toLong(), syncUpdateCallback) + args.hasKey(SYNC_NAME) && !args.isNull(SYNC_NAME) -> + syncManager.reSync(args.getString(SYNC_NAME)!!, syncUpdateCallback) + else -> + throw SyncManager.MobileSyncException("neither $SYNC_ID nor $SYNC_NAME were specified") + } + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "reSync call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of cleanResyncGhosts + * @param args + * @param callback + */ + @ReactMethod + fun cleanResyncGhosts(args: ReadableMap, callback: Callback) { + // Parse args + val syncId = args.getInt(SYNC_ID).toLong() + try { + val syncManager = getSyncManager(args) + syncManager.cleanResyncGhosts(syncId, object : SyncManager.CleanResyncGhostsCallback { + override fun onSuccess(numRecords: Int) { + ReactBridgeHelper.invokeSuccess(callback, numRecords) + } + + override fun onError(e: Exception?) { + ReactBridgeHelper.invokeError(callback, e?.toString() ?: "Unknown error") + } + }) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "cleanResyncGhosts call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Sync update handler + * @param sync + * @param callback + */ + private fun handleSyncUpdate(sync: SyncState, callback: Callback) { + try { + when (sync.status) { + SyncState.Status.NEW -> {} + SyncState.Status.RUNNING -> {} + SyncState.Status.DONE -> + ReactBridgeHelper.invokeSuccess(callback, sync.asJSON()) + SyncState.Status.FAILED -> + ReactBridgeHelper.invokeError(callback, sync.asJSON().toString()) + else -> {} + } + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "handleSyncUpdate call failed", e) + } + } + + /** + * Return sync manager to use + * @param args Arguments passed to the bridge + * @return + */ + @Throws(Exception::class) + private fun getSyncManager(args: ReadableMap): SyncManager { + val smartStore = SmartStoreReactBridge.getSmartStore(args) + return SyncManager.getInstance(null, null, smartStore) + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/ReactBridgeHelper.java b/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/ReactBridgeHelper.java new file mode 100644 index 0000000..01bfac3 --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/ReactBridgeHelper.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2015-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.bridge; + +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableMapKeySetIterator; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ReactBridgeHelper { + + /** + * Invokes callback with success result using single-callback pattern: callback(null, result) + */ + public static void invokeSuccess(Callback callback, JSONObject json) { + callback.invoke(null, json == null ? null : json.toString()); + } + + public static void invokeSuccess(Callback callback, JSONArray json) { + callback.invoke(null, json == null ? null : json.toString()); + } + + public static void invokeSuccess(Callback callback, String value) { + callback.invoke(null, "\"" + value + "\""); + } + + public static void invokeSuccess(Callback callback, boolean value) { + callback.invoke(null, "" + value); + } + + public static void invokeSuccess(Callback callback, int value) { + callback.invoke(null, "" + value); + } + + /** + * Invokes callback with no result (void success): callback(null) + */ + public static void invokeSuccess(Callback callback) { + callback.invoke(null, "null"); + } + + /** + * Invokes callback with error: callback(errorMessage) + */ + public static void invokeError(Callback callback, String error) { + callback.invoke(error); + } + + public static Map toJavaMap(ReadableMap map) { + Map result = new HashMap<>(); + ReadableMapKeySetIterator iterator = map.keySetIterator(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + switch (map.getType(key)) { + case Null: + result.put(key, null); + break; + case Boolean: + result.put(key, map.getBoolean(key)); + break; + case Number: + result.put(key, map.getDouble(key)); + break; + case String: + result.put(key, map.getString(key)); + break; + case Map: + result.put(key, toJavaMap(map.getMap(key))); + break; + case Array: + result.put(key, toJavaList(map.getArray(key))); + break; + } + } + return result; + } + + public static Map toJavaStringStringMap(ReadableMap map) { + Map result = new HashMap<>(); + ReadableMapKeySetIterator iterator = map.keySetIterator(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + switch (map.getType(key)) { + case String: + result.put(key, map.getString(key)); + break; + default: + break; + } + } + return result; + } + + public static Map> toJavaStringMapMap(ReadableMap map) { + Map> result = new HashMap<>(); + ReadableMapKeySetIterator iterator = map.keySetIterator(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + switch (map.getType(key)) { + case Map: + result.put(key, toJavaStringStringMap(map.getMap(key))); + break; + default: + break; + } + } + return result; + } + + + public static List toJavaStringList(ReadableArray array) { + List result = new ArrayList<>(); + for (int i = 0; i toJavaList(ReadableArray array) { + List result = new ArrayList<>(); + for (int i = 0; i 0) { + android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({ + sendRequestWithRetry(args, callback, retries - 1) + }, 500) + return + } else { + ReactBridgeHelper.invokeError(callback, "SalesforceReactActivity not found") + return + } + } + restClient.sendAsync(request, object : RestClient.AsyncRequestCallback { + + override fun onSuccess(request: RestRequest, response: RestResponse) { + try { + // Not a 2xx status + if (!response.isSuccess) { + val responseObject = JSONObject() + responseObject.put("headers", JSONObject(response.allHeaders)) + responseObject.put("statusCode", response.statusCode) + responseObject.put("body", parsedResponse(response)) + val errorObject = JSONObject() + errorObject.put("response", responseObject) + ReactBridgeHelper.invokeError(callback, errorObject.toString()) + } + // Binary response + else if (returnBinary) { + val result = JSONObject() + result.put(CONTENT_TYPE, response.contentType) + result.put(ENCODED_BODY, Base64.encodeToString(response.asBytes(), Base64.DEFAULT)) + ReactBridgeHelper.invokeSuccess(callback, result) + } + // Other cases + else { + callback.invoke(null, response.asString()) + } + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "sendRequest failed", e) + onError(e) + } + } + + override fun onError(exception: Exception) { + val errorObject = JSONObject() + try { + errorObject.put("error", exception.message) + } catch (jsonException: JSONException) { + SalesforceReactLogger.e(TAG, "Error creating error object", jsonException) + } + ReactBridgeHelper.invokeError(callback, errorObject.toString()) + } + }) + } catch (exception: Exception) { + val errorObject = JSONObject() + try { + errorObject.put("error", exception.message) + } catch (jsonException: JSONException) { + SalesforceReactLogger.e(TAG, "Error creating error object", jsonException) + } + ReactBridgeHelper.invokeError(callback, errorObject.toString()) + } + } + + @Throws(IOException::class) + private fun parsedResponse(response: RestResponse): Any { + // Is it a JSONObject? + val responseAsJSONObject = parseResponseAsJSONObject(response) + if (responseAsJSONObject != null) { + return responseAsJSONObject + } + + // Is it a JSONArray? + val responseAsJSONArray = parseResponseAsJSONArray(response) + if (responseAsJSONArray != null) { + return responseAsJSONArray + } + + // Otherwise return as string + return response.asString() + } + + @Throws(IOException::class) + private fun parseResponseAsJSONObject(response: RestResponse): JSONObject? { + return try { + response.asJSONObject() + } catch (e: JSONException) { + null + } + } + + @Throws(IOException::class) + private fun parseResponseAsJSONArray(response: RestResponse): JSONArray? { + return try { + response.asJSONArray() + } catch (e: JSONException) { + null + } + } + + private fun prepareRestRequest(args: ReadableMap): RestRequest { + // Parse args + val method = RestRequest.RestMethod.valueOf(args.getString(METHOD_KEY)!!) + val endPoint = if (!args.hasKey(END_POINT_KEY) || args.isNull(END_POINT_KEY)) "" else args.getString(END_POINT_KEY)!! + val path = args.getString(PATH_KEY)!! + val queryParams = args.getMap(QUERY_PARAMS_KEY)!! + val headerParams = args.getMap(HEADER_PARAMS_KEY)!! + val fileParams = args.getMap(FILE_PARAMS_KEY)!! + + // Preparing request + val additionalHeaders = ReactBridgeHelper.toJavaStringStringMap(headerParams) + val queryParamsMap = ReactBridgeHelper.toJavaMap(queryParams) + val fileParamsMap = ReactBridgeHelper.toJavaStringMapMap(fileParams) + var urlParams = "" + var requestBody: RequestBody? = null + if (method == RestRequest.RestMethod.DELETE || method == RestRequest.RestMethod.GET + || method == RestRequest.RestMethod.HEAD) { + urlParams = buildQueryString(queryParamsMap) + } else { + requestBody = buildRequestBody(queryParamsMap, fileParamsMap) + } + val separator = if (urlParams.isEmpty()) { + "" + } else if (path.contains("?")) { + if (path.endsWith("&")) "" else "&" + } else { + "?" + } + return RestRequest(method, endPoint + path + separator + urlParams, requestBody, additionalHeaders) + } + + /** + * Returns the RestClient instance being used by this bridge. + * + * @param doesNotRequireAuth True - if an unauthenticated client should be used, False - otherwise. + * @return RestClient instance. + */ + protected open fun getRestClient(doesNotRequireAuth: Boolean): RestClient? { + val currentActivity = getCurrentActivity() as? SalesforceReactActivity ?: return null + return if (doesNotRequireAuth) { + currentActivity.buildClientManager().peekUnauthenticatedRestClient() + } else { + currentActivity.restClient + } + } + + private fun buildQueryString(params: Map): String { + val sb = StringBuilder() + for ((key, value) in params) { + sb.append(key) + .append("=") + .append(URLEncoder.encode(value.toString(), RestRequest.UTF_8)) + .append("&") + } + return sb.toString() + } + + private fun buildRequestBody(params: Map, fileParams: Map>): RequestBody { + val paramsRequestBody = JSONObject(params).toString() + .toRequestBody(RestRequest.MEDIA_TYPE_JSON) + if (fileParams.isEmpty()) { + return paramsRequestBody + } else { + val builder = MultipartBody.Builder().setType(MultipartBody.FORM) + builder.addFormDataPart("", null, paramsRequestBody) + + // File params expected to be of the form: + // {: {fileMimeType:, fileUrl:, fileName:}} + for ((fileParamName, fileParam) in fileParams) { + val mimeType = fileParam[FILE_MIME_TYPE_KEY]!! + val name = fileParam[FILE_NAME_KEY]!! + val url = URI(fileParam[FILE_URL_KEY]!!) + val file = File(url) + val mediaType = mimeType.toMediaType() + builder.addFormDataPart(fileParamName, name, file.asRequestBody(mediaType)) + } + return builder.build() + } + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/SalesforceOauthReactBridge.kt b/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/SalesforceOauthReactBridge.kt new file mode 100644 index 0000000..16d7e75 --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/SalesforceOauthReactBridge.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2015-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.bridge + +import android.os.Handler +import android.os.Looper +import com.facebook.react.bridge.Callback +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.turbomodule.core.interfaces.TurboModule +import com.salesforce.androidsdk.reactnative.ui.SalesforceReactActivity + +class SalesforceOauthReactBridge(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext), TurboModule { + + override fun getName(): String = TAG + + @ReactMethod + fun authenticate(args: ReadableMap, callback: Callback) { + withActivity(callback) { activity -> + activity.authenticate(callback) + } + } + + @ReactMethod + fun getAuthCredentials(args: ReadableMap, callback: Callback) { + withActivity(callback) { activity -> + activity.getAuthCredentials(callback) + } + } + + @ReactMethod + fun logoutCurrentUser(args: ReadableMap, callback: Callback) { + withActivity(callback) { activity -> + activity.logout(callback) + } + } + + private fun withActivity(callback: Callback, retries: Int = 5, action: (SalesforceReactActivity) -> Unit) { + val activity = getCurrentActivity() as? SalesforceReactActivity + if (activity != null) { + action(activity) + } else if (retries > 0) { + Handler(Looper.getMainLooper()).postDelayed({ + withActivity(callback, retries - 1, action) + }, 500) + } else { + ReactBridgeHelper.invokeError(callback, "SalesforceReactActivity not found") + } + } + + companion object { + private const val TAG = "SFOauthReactBridge" + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/SmartStoreReactBridge.kt b/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/SmartStoreReactBridge.kt new file mode 100644 index 0000000..87ebcc7 --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/bridge/SmartStoreReactBridge.kt @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2015-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.bridge + +import android.util.SparseArray +import com.facebook.react.bridge.Callback +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.turbomodule.core.interfaces.TurboModule +import com.salesforce.androidsdk.accounts.UserAccountManager +import com.salesforce.androidsdk.reactnative.util.SalesforceReactLogger +import com.salesforce.androidsdk.smartstore.app.SmartStoreSDKManager +import com.salesforce.androidsdk.smartstore.store.DBOpenHelper +import com.salesforce.androidsdk.smartstore.store.IndexSpec +import com.salesforce.androidsdk.smartstore.store.QuerySpec +import com.salesforce.androidsdk.smartstore.store.SmartStore +import com.salesforce.androidsdk.smartstore.store.StoreCursor +import net.zetetic.database.sqlcipher.SQLiteDatabase +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +class SmartStoreReactBridge(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext), TurboModule { + + override fun getName(): String = "SFSmartStoreReactBridge" + + /** + * Native implementation of removeFromSoup + */ + @ReactMethod + fun removeFromSoup(args: ReadableMap, callback: Callback) { + try { + val soupName = args.getString(SOUP_NAME) + val smartStore = getSmartStore(args) + val arraySoupEntryIds = if (!args.hasKey(ENTRY_IDS) || args.isNull(ENTRY_IDS)) null else args.getArray(ENTRY_IDS) + val mapQuerySpec = if (!args.hasKey(QUERY_SPEC) || args.isNull(QUERY_SPEC)) null else args.getMap(QUERY_SPEC) + if (arraySoupEntryIds != null) { + val ids = ReactBridgeHelper.toJavaList(arraySoupEntryIds) + val soupEntryIds = Array(ids.size) { i -> (ids[i] as Double).toLong() } + smartStore.delete(soupName, *soupEntryIds) + } else { + val querySpecJson = JSONObject(ReactBridgeHelper.toJavaMap(mapQuerySpec)) + val querySpec = QuerySpec.fromJSON(soupName, querySpecJson) + smartStore.deleteByQuery(soupName, querySpec) + } + ReactBridgeHelper.invokeSuccess(callback) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "removeFromSoup call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of retrieveSoupEntries + */ + @ReactMethod + fun retrieveSoupEntries(args: ReadableMap, callback: Callback) { + try { + val soupName = args.getString(SOUP_NAME) + val smartStore = getSmartStore(args) + val soupEntryIdsFromJs = ReactBridgeHelper.toJavaList(args.getArray(ENTRY_IDS)).toTypedArray() + val soupEntryIds = Array(soupEntryIdsFromJs.size) { i -> (soupEntryIdsFromJs[i] as Double).toLong() } + val result = smartStore.retrieve(soupName, *soupEntryIds) + ReactBridgeHelper.invokeSuccess(callback, result) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "retrieveSoupEntries call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of closeCursor + */ + @ReactMethod + fun closeCursor(args: ReadableMap, callback: Callback) { + try { + val cursorId = args.getInt(CURSOR_ID) + val smartStore = getSmartStore(args) + getSmartStoreCursors(smartStore).remove(cursorId) + ReactBridgeHelper.invokeSuccess(callback) + } catch (e: Exception) { + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of moveCursorToPageIndex + */ + @ReactMethod + fun moveCursorToPageIndex(args: ReadableMap, callback: Callback) { + val cursorId = args.getInt(CURSOR_ID) + val index = args.getInt(INDEX) + val smartStore: SmartStore + try { + smartStore = getSmartStore(args) + } catch (e: Exception) { + ReactBridgeHelper.invokeError(callback, e.toString()) + return + } + + // Get cursor + val storeCursor = getSmartStoreCursors(smartStore).get(cursorId) + if (storeCursor == null) { + ReactBridgeHelper.invokeError(callback, "Invalid cursor id") + return + } + + // Change page + storeCursor.moveToPageIndex(index) + + // Build json result + val result = storeCursor.getDataSerialized(smartStore) + ReactBridgeHelper.invokeSuccess(callback, result) + } + + /** + * Native implementation of soupExists + */ + @ReactMethod + fun soupExists(args: ReadableMap, callback: Callback) { + try { + val soupName = args.getString(SOUP_NAME) + val smartStore = getSmartStore(args) + val exists = smartStore.hasSoup(soupName) + ReactBridgeHelper.invokeSuccess(callback, exists) + } catch (e: Exception) { + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of upsertSoupEntries + */ + @ReactMethod + fun upsertSoupEntries(args: ReadableMap, callback: Callback) { + val soupName = args.getString(SOUP_NAME) + val smartStore: SmartStore + try { + smartStore = getSmartStore(args) + } catch (e: Exception) { + ReactBridgeHelper.invokeError(callback, e.toString()) + return + } + val entriesList = ReactBridgeHelper.toJavaList(args.getArray(ENTRIES)) + val externalIdPath = args.getString(EXTERNAL_ID_PATH) + val entries = entriesList.map { JSONObject(it as Map<*, *>) } + + // Run upsert + synchronized(smartStore.database) { + smartStore.beginTransaction() + try { + val results = JSONArray() + for (entry in entries) { + results.put(smartStore.upsert(soupName, entry, externalIdPath, false)) + } + smartStore.setTransactionSuccessful() + ReactBridgeHelper.invokeSuccess(callback, results) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "upsertSoupEntries call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } finally { + smartStore.endTransaction() + } + } + } + + /** + * Native implementation of registerSoup + */ + @ReactMethod + fun registerSoup(args: ReadableMap, callback: Callback) { + try { + val smartStore = getSmartStore(args) + val soupName = if (args.isNull(SOUP_NAME)) null else args.getString(SOUP_NAME) + val indexSpecs = getIndexSpecsFromArg(args) + smartStore.registerSoup(soupName, indexSpecs) + ReactBridgeHelper.invokeSuccess(callback, soupName!!) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "registerSoup call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of querySoup + */ + @ReactMethod + fun querySoup(args: ReadableMap, callback: Callback) { + try { + val soupName = args.getString(SOUP_NAME) + val smartStore = getSmartStore(args) + val querySpecJson = JSONObject(ReactBridgeHelper.toJavaMap(args.getMap(QUERY_SPEC))) + val querySpec = QuerySpec.fromJSON(soupName, querySpecJson) + if (querySpec.queryType == QuerySpec.QueryType.smart) { + throw RuntimeException("Smart queries can only be run through runSmartQuery") + } + runQuery(smartStore, querySpec, callback) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "querySoup call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of runSmartSql + */ + @ReactMethod + fun runSmartQuery(args: ReadableMap, callback: Callback) { + val querySpecJson = JSONObject(ReactBridgeHelper.toJavaMap(args.getMap(QUERY_SPEC))) + try { + val smartStore = getSmartStore(args) + val querySpec = QuerySpec.fromJSON(null, querySpecJson) + if (querySpec.queryType != QuerySpec.QueryType.smart) { + throw RuntimeException("runSmartQuery can only run smart queries") + } + runQuery(smartStore, querySpec, callback) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "runSmartQuery call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Helper for querySoup and runSmartSql + */ + @Throws(JSONException::class) + private fun runQuery(smartStore: SmartStore, querySpec: QuerySpec, callback: Callback) { + val storeCursor = StoreCursor(smartStore, querySpec) + getSmartStoreCursors(smartStore).put(storeCursor.cursorId, storeCursor) + val result = storeCursor.getDataSerialized(smartStore) + ReactBridgeHelper.invokeSuccess(callback, result) + } + + /** + * Native implementation of removeSoup + */ + @ReactMethod + fun removeSoup(args: ReadableMap, callback: Callback) { + try { + val soupName = args.getString(SOUP_NAME) + val smartStore = getSmartStore(args) + smartStore.dropSoup(soupName) + ReactBridgeHelper.invokeSuccess(callback) + } catch (e: Exception) { + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of clearSoup + */ + @ReactMethod + fun clearSoup(args: ReadableMap, callback: Callback) { + try { + val soupName = args.getString(SOUP_NAME) + val smartStore = getSmartStore(args) + smartStore.clearSoup(soupName) + ReactBridgeHelper.invokeSuccess(callback) + } catch (e: Exception) { + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of getDatabaseSize + */ + @ReactMethod + fun getDatabaseSize(args: ReadableMap, callback: Callback) { + try { + val smartStore = getSmartStore(args) + val databaseSize = smartStore.databaseSize + ReactBridgeHelper.invokeSuccess(callback, databaseSize) + } catch (e: Exception) { + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of alterSoup + */ + @ReactMethod + fun alterSoup(args: ReadableMap, callback: Callback) { + try { + val smartStore = getSmartStore(args) + val soupName = args.getString(SOUP_NAME) + val indexSpecs = getIndexSpecsFromArg(args) + val reIndexData = args.getBoolean(RE_INDEX_DATA) + smartStore.alterSoup(soupName, indexSpecs, reIndexData) + ReactBridgeHelper.invokeSuccess(callback, soupName!!) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "alterSoup call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of reIndexSoup + */ + @ReactMethod + fun reIndexSoup(args: ReadableMap, callback: Callback) { + try { + val soupName = args.getString(SOUP_NAME) + val smartStore = getSmartStore(args) + val indexPaths = ReactBridgeHelper.toJavaStringList(args.getArray(PATHS)) + smartStore.reIndexSoup(soupName, indexPaths.toTypedArray(), true) + ReactBridgeHelper.invokeSuccess(callback, soupName!!) + } catch (e: Exception) { + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of getSoupIndexSpecs + */ + @ReactMethod + fun getSoupIndexSpecs(args: ReadableMap, callback: Callback) { + try { + val soupName = args.getString(SOUP_NAME) + val smartStore = getSmartStore(args) + val indexSpecs = smartStore.getSoupIndexSpecs(soupName) + val indexSpecsJson = JSONArray() + for (indexSpec in indexSpecs) { + val indexSpecJson = JSONObject() + indexSpecJson.put(PATH, indexSpec.path) + indexSpecJson.put(TYPE, indexSpec.type) + indexSpecsJson.put(indexSpecJson) + } + ReactBridgeHelper.invokeSuccess(callback, indexSpecsJson) + } catch (e: Exception) { + SalesforceReactLogger.e(TAG, "getSoupIndexSpecs call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of getAllGlobalStores + */ + @ReactMethod + fun getAllGlobalStores(args: ReadableMap, callback: Callback) { + try { + val globalDBNames = SmartStoreSDKManager.getInstance().globalStoresPrefixList + val storeList = JSONArray() + if (globalDBNames != null) { + for (name in globalDBNames) { + val dbName = JSONObject() + dbName.put(IS_GLOBAL_STORE, true) + dbName.put(STORE_NAME, name) + storeList.put(dbName) + } + } + ReactBridgeHelper.invokeSuccess(callback, storeList) + } catch (e: JSONException) { + SalesforceReactLogger.e(TAG, "getAllGlobalStorePrefixes call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of getAllStores + */ + @ReactMethod + fun getAllStores(args: ReadableMap, callback: Callback) { + try { + val userStoreNames = SmartStoreSDKManager.getInstance().userStoresPrefixList + val storeList = JSONArray() + if (userStoreNames != null) { + for (name in userStoreNames) { + val dbName = JSONObject() + dbName.put(IS_GLOBAL_STORE, false) + dbName.put(STORE_NAME, name) + storeList.put(dbName) + } + } + ReactBridgeHelper.invokeSuccess(callback, storeList) + } catch (e: JSONException) { + SalesforceReactLogger.e(TAG, "getAllStorePrefixes call failed", e) + ReactBridgeHelper.invokeError(callback, e.toString()) + } + } + + /** + * Native implementation of removeStore + */ + @ReactMethod + fun removeStore(args: ReadableMap, callback: Callback) { + val isGlobal = getIsGlobal(args) + val storeName = getStoreName(args) + if (isGlobal) { + SmartStoreSDKManager.getInstance().removeGlobalSmartStore(storeName) + ReactBridgeHelper.invokeSuccess(callback, true) + } else { + val account = UserAccountManager.getInstance().cachedCurrentUser + if (account == null) { + ReactBridgeHelper.invokeError(callback, "No user account found") + } else { + SmartStoreSDKManager.getInstance().removeSmartStore(storeName, account, account.communityId) + ReactBridgeHelper.invokeSuccess(callback, true) + } + } + } + + /** + * Native implementation of removeAllGlobalStores + */ + @ReactMethod + fun removeAllGlobalStores(args: ReadableMap, callback: Callback) { + SmartStoreSDKManager.getInstance().removeAllGlobalStores() + ReactBridgeHelper.invokeSuccess(callback, true) + } + + /** + * Native implementation of removeAllStores + */ + @ReactMethod + fun removeAllStores(args: ReadableMap, callback: Callback) { + SmartStoreSDKManager.getInstance().removeAllUserStores() + ReactBridgeHelper.invokeSuccess(callback, true) + } + + /** + * Build index specs array from javascript argument + */ + @Throws(JSONException::class) + private fun getIndexSpecsFromArg(args: ReadableMap): Array { + val indexesJson = JSONArray(ReactBridgeHelper.toJavaList(args.getArray(INDEXES))) + return IndexSpec.fromJSON(indexesJson) + } + + companion object { + // Log tag + const val TAG = "SmartStoreReactBridge" + + // Keys in json from/to javascript + const val RE_INDEX_DATA = "reIndexData" + const val CURSOR_ID = "cursorId" + const val TYPE = "type" + const val SOUP_NAME = "soupName" + const val PATH = "path" + const val PATHS = "paths" + const val QUERY_SPEC = "querySpec" + const val SOUP_SPEC = "soupSpec" + const val SOUP_SPEC_NAME = "name" + const val SOUP_SPEC_FEATURES = "features" + const val EXTERNAL_ID_PATH = "externalIdPath" + const val ENTRIES = "entries" + const val ENTRY_IDS = "entryIds" + const val INDEX = "index" + const val INDEXES = "indexes" + const val IS_GLOBAL_STORE = "isGlobalStore" + const val STORE_NAME = "storeName" + + // Map of cursor id to StoreCursor, per database. + private val STORE_CURSORS = HashMap>() + + @Synchronized + @JvmStatic + fun getSmartStoreCursors(store: SmartStore): SparseArray { + val db = store.database + if (!STORE_CURSORS.containsKey(db)) { + STORE_CURSORS[db] = SparseArray() + } + return STORE_CURSORS[db]!! + } + + /** + * Return the value of the isGlobalStore argument + */ + @JvmStatic + fun getIsGlobal(args: ReadableMap?): Boolean { + return args?.getBoolean(IS_GLOBAL_STORE) ?: false + } + + /** + * Return the value of the storename argument + */ + @JvmStatic + fun getStoreName(args: ReadableMap?): String { + val storeName = if (args != null && args.hasKey(STORE_NAME)) args.getString(STORE_NAME) else DBOpenHelper.DEFAULT_DB_NAME + return if (!storeName.isNullOrBlank()) storeName else DBOpenHelper.DEFAULT_DB_NAME + } + + /** + * Return smartstore to use + */ + @JvmStatic + @Throws(Exception::class) + fun getSmartStore(args: ReadableMap): SmartStore { + val isGlobal = getIsGlobal(args) + val storeName = getStoreName(args) + return if (isGlobal) { + SmartStoreSDKManager.getInstance().getGlobalSmartStore(storeName) + } else { + val account = UserAccountManager.getInstance().cachedCurrentUser + ?: throw Exception("No user account found") + SmartStoreSDKManager.getInstance().getSmartStore(storeName, account, account.communityId) + } + } + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivity.java b/android/src/main/java/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivity.java new file mode 100644 index 0000000..c1e0113 --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivity.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2016-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.ui; + +import android.os.Bundle; +import android.view.KeyEvent; +import android.widget.Toast; + +import com.facebook.react.ReactActivity; +import com.facebook.react.ReactActivityDelegate; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.bridge.Callback; +import com.salesforce.androidsdk.reactnative.R; +import com.salesforce.androidsdk.reactnative.app.SalesforceReactSDKManager; +import com.salesforce.androidsdk.reactnative.bridge.ReactBridgeHelper; +import com.salesforce.androidsdk.reactnative.util.SalesforceReactLogger; +import com.salesforce.androidsdk.rest.ClientManager; +import com.salesforce.androidsdk.rest.ClientManager.RestClientCallback; +import com.salesforce.androidsdk.rest.RestClient; +import com.salesforce.androidsdk.ui.SalesforceActivityDelegate; +import com.salesforce.androidsdk.ui.SalesforceActivityInterface; + +/** + * Main activity for a Salesforce ReactNative app. + */ +public abstract class SalesforceReactActivity extends ReactActivity implements SalesforceActivityInterface { + + private static final String TAG = "SFReactActivity"; + + private final SalesforceActivityDelegate delegate; + private RestClient client; + private ClientManager clientManager; + private SalesforceReactActivityDelegate reactActivityDelegate; + + /** + * Pending callback for authentication requests from the React Native bridge. + * Uses single-callback pattern: callback(null, result) for success, callback(error) for error. + */ + private Callback pendingAuthCallback; + + protected SalesforceReactActivity() { + super(); + delegate = new SalesforceActivityDelegate(this); + } + + /** + * Returns if authentication should be performed automatically or not. + * + * @return True - if you want login to happen as soon as activity is loaded, False - otherwise. + */ + public boolean shouldAuthenticate() { + return true; + } + + /** + * Called if shouldAuthenticate() returned true but device is offline. + */ + public void onErrorAuthenticateOffline() { + final Toast t = Toast.makeText(this, + R.string.sf__should_authenticate_but_is_offline, Toast.LENGTH_LONG); + t.show(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + SalesforceReactLogger.i(TAG, "onCreate called"); + super.onCreate(savedInstanceState); + clientManager = buildClientManager(); + delegate.onCreate(); + } + + @Override + public void onResume() { + super.onResume(); + delegate.onResume(false); + loadReactAppOnceIfReady(); + } + + @Override + public void onResume(RestClient c) { + try { + setRestClient(clientManager.peekRestClient()); + } catch (ClientManager.AccountInfoNotFoundException e) { + setRestClient(client); + } + + // Not logged in. + if (client == null) { + onResumeNotLoggedIn(); + } + + // Logged in. + else { + SalesforceReactLogger.i(TAG, "onResume - already logged in"); + + if (pendingAuthCallback != null) { + SalesforceReactLogger.i(TAG, "onResume - invoking pending auth callback"); + getAuthCredentials(pendingAuthCallback); + pendingAuthCallback = null; + } + } + } + + private void onResumeNotLoggedIn() { + if (shouldAuthenticate()) { + if (SalesforceReactSDKManager.getInstance().hasNetwork()) { + SalesforceReactLogger.i(TAG, "onResumeNotLoggedIn - should authenticate/online - authenticating"); + login(); + } else { + SalesforceReactLogger.w(TAG, "onResumeNotLoggedIn - should authenticate/offline - can not proceed"); + onErrorAuthenticateOffline(); + } + } else { + SalesforceReactLogger.i(TAG, "onResumeNotLoggedIn - should not authenticate"); + } + } + + @Override + public void onPause() { + super.onPause(); + delegate.onPause(); + } + + @Override + public void onDestroy() { + delegate.onDestroy(); + super.onDestroy(); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return delegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); + } + + public void showReactDevOptionsDialog() { + ReactInstanceManager instanceManager = peekReactInstanceManager(); + if (instanceManager != null) { + instanceManager.showDevOptionsDialog(); + } + } + + protected void login() { + SalesforceReactLogger.i(TAG, "login called"); + clientManager.getRestClient(this, new RestClientCallback() { + + @Override + public void authenticatedRestClient(RestClient client) { + if (client == null) { + SalesforceReactLogger.i(TAG, "login callback triggered with null client"); + logout(null); + } else { + SalesforceReactLogger.i(TAG, "login callback triggered with actual client"); + SalesforceReactActivity.this.restartReactNativeApp(); + } + } + }); + } + + /** + * Method called from bridge to logout. + * + * @param callback Single callback: callback(null, result) on success. + */ + public void logout(final Callback callback) { + SalesforceReactLogger.i(TAG, "logout called"); + SalesforceReactSDKManager.getInstance().logout(this); + if (callback != null) { + ReactBridgeHelper.invokeSuccess(callback, "Logout complete"); + } + } + + /** + * Method called from bridge to authenticate. + * + * @param callback Single callback: callback(null, result) on success, callback(error) on error. + */ + public void authenticate(final Callback callback) { + SalesforceReactLogger.i(TAG, "authenticate called"); + pendingAuthCallback = callback; + + clientManager.getRestClient(this, new RestClientCallback() { + + @Override + public void authenticatedRestClient(RestClient client) { + SalesforceReactLogger.i(TAG, "authenticatedRestClient callback invoked"); + SalesforceReactActivity.this.setRestClient(client); + + if (pendingAuthCallback != null) { + SalesforceReactLogger.i(TAG, "authenticatedRestClient - invoking pending callback"); + getAuthCredentials(pendingAuthCallback); + pendingAuthCallback = null; + } + } + }); + } + + /** + * Method called from bridge to get auth credentials. + * + * @param callback Single callback: callback(null, result) on success, callback(error) on error. + */ + public void getAuthCredentials(Callback callback) { + SalesforceReactLogger.i(TAG, "getAuthCredentials called"); + if (client != null) { + if (callback != null) { + ReactBridgeHelper.invokeSuccess(callback, client.getJSONCredentials()); + } + } else { + if (callback != null) { + ReactBridgeHelper.invokeError(callback, "Not authenticated"); + } + } + } + + /** + * Returns an instance of RestClient. + * + * @return An instance of RestClient. + */ + public RestClient getRestClient() { + return client; + } + + protected void setRestClient(RestClient restClient) { + client = restClient; + if (client != null) { + loadReactAppOnceIfReady(); + } + } + + public ClientManager buildClientManager() { + return SalesforceReactSDKManager.getInstance().getClientManager(); + } + + @Override + public void onLogoutComplete() { + } + + @Override + public void onUserSwitched() { + } + + @Override + protected ReactActivityDelegate createReactActivityDelegate() { + reactActivityDelegate = new SalesforceReactActivityDelegate(this, getMainComponentName()); + return reactActivityDelegate; + } + + protected boolean shouldReactBeRunning() { + return !shouldAuthenticate() || client != null; + } + + protected void restartReactNativeApp() { + ReactInstanceManager instanceManager = peekReactInstanceManager(); + if (instanceManager != null) { + instanceManager.destroy(); + if (shouldReactBeRunning()) { + instanceManager.createReactContextInBackground(); + } + } else { + // Bridgeless mode: recreate the activity to restart React + if (shouldReactBeRunning()) { + recreate(); + } + } + } + + /** + * Returns the ReactInstanceManager if available (bridge mode only). + * Returns null in bridgeless (new architecture) mode. + */ + private ReactInstanceManager peekReactInstanceManager() { + try { + return getReactNativeHost().getReactInstanceManager(); + } catch (Exception e) { + return null; + } + } + + private void loadReactAppOnceIfReady() { + if (reactActivityDelegate != null ) { + reactActivityDelegate.loadReactAppOnceIfReady(getMainComponentName()); + } + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivityDelegate.java b/android/src/main/java/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivityDelegate.java new file mode 100644 index 0000000..742495a --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/ui/SalesforceReactActivityDelegate.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.ui; + +import com.facebook.react.ReactActivityDelegate; + +/** + * Delegate that loads the app context when ready. + */ +public class SalesforceReactActivityDelegate extends ReactActivityDelegate { + + private SalesforceReactActivity salesforceReactActivity; + private boolean loaded = false; + + SalesforceReactActivityDelegate(SalesforceReactActivity activity, String mainComponentName) { + super(activity, mainComponentName); + salesforceReactActivity = activity; + } + + @Override + protected void loadApp(String appKey) { + loadReactAppOnceIfReady(appKey); + } + + /** + * Loads the app once it's ready. + * + * @param appKey App key. + */ + public void loadReactAppOnceIfReady(String appKey) { + if (!loaded && salesforceReactActivity != null && salesforceReactActivity.shouldReactBeRunning()) { + super.loadApp(appKey); + loaded = true; + super.onResume(); + } + } +} diff --git a/android/src/main/java/com/salesforce/androidsdk/reactnative/util/SalesforceReactLogger.java b/android/src/main/java/com/salesforce/androidsdk/reactnative/util/SalesforceReactLogger.java new file mode 100644 index 0000000..0444750 --- /dev/null +++ b/android/src/main/java/com/salesforce/androidsdk/reactnative/util/SalesforceReactLogger.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.util; + +import com.salesforce.androidsdk.analytics.logger.SalesforceLogger; +import com.salesforce.androidsdk.app.SalesforceSDKManager; + +/** + * A simple logger util class for the SalesforceReact library. This class simply acts + * as a wrapper around SalesforceLogger specific to the SalesforceReact library. + * + * @author bhariharan + */ +public class SalesforceReactLogger { + + private static final String COMPONENT_NAME = "SalesforceReact"; + + /** + * Logs an error log line. + * + * @param tag Log tag. + * @param message Log message. + */ + public static void e(String tag, String message) { + getLogger().e(tag, message); + } + + /** + * Logs an error log line. + * + * @param tag Log tag. + * @param message Log message. + * @param e Exception to be logged. + */ + public static void e(String tag, String message, Throwable e) { + getLogger().e(tag, message, e); + } + + /** + * Logs a warning log line. + * + * @param tag Log tag. + * @param message Log message. + */ + public static void w(String tag, String message) { + getLogger().w(tag, message); + } + + /** + * Logs a warning log line. + * + * @param tag Log tag. + * @param message Log message. + * @param e Exception to be logged. + */ + public static void w(String tag, String message, Throwable e) { + getLogger().w(tag, message, e); + } + + /** + * Logs an info log line. + * + * @param tag Log tag. + * @param message Log message. + */ + public static void i(String tag, String message) { + getLogger().i(tag, message); + } + + /** + * Logs an info log line. + * + * @param tag Log tag. + * @param message Log message. + * @param e Exception to be logged. + */ + public static void i(String tag, String message, Throwable e) { + getLogger().i(tag, message, e); + } + + /** + * Logs a debug log line. + * + * @param tag Log tag. + * @param message Log message. + */ + public static void d(String tag, String message) { + getLogger().d(tag, message); + } + + /** + * Logs a debug log line. + * + * @param tag Log tag. + * @param message Log message. + * @param e Exception to be logged. + */ + public static void d(String tag, String message, Throwable e) { + getLogger().d(tag, message, e); + } + + /** + * Logs a verbose log line. + * + * @param tag Log tag. + * @param message Log message. + */ + public static void v(String tag, String message) { + getLogger().v(tag, message); + } + + /** + * Logs a verbose log line. + * + * @param tag Log tag. + * @param message Log message. + * @param e Exception to be logged. + */ + public static void v(String tag, String message, Throwable e) { + getLogger().v(tag, message, e); + } + + /** + * Sets the log level to be used. + * + * @param level Log level. + */ + public static void setLogLevel(SalesforceLogger.Level level) { + getLogger().setLogLevel(level); + } + + private static SalesforceLogger getLogger() { + return SalesforceLogger.getLogger(COMPONENT_NAME, + SalesforceSDKManager.getInstance().getAppContext()); + } +} diff --git a/androidTests/android/app/build.gradle.kts b/androidTests/android/app/build.gradle.kts new file mode 100644 index 0000000..4d81e50 --- /dev/null +++ b/androidTests/android/app/build.gradle.kts @@ -0,0 +1,67 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.facebook.react") +} + +react { + autolinkLibrariesWithApp() +} + +android { + namespace = "com.salesforce.androidsdk.reactnative.tests" + compileSdk = 36 + + defaultConfig { + applicationId = "com.salesforce.androidsdk.reactnative.tests" + minSdk = 28 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + + testApplicationId = "com.salesforce.androidsdk.salesforcereact.tests" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + sourceSets { + getByName("main") { + java.srcDirs("src/main/java") + res.srcDirs("src/main/res") + assets.srcDirs("src/main/assets") + manifest.srcFile("src/main/AndroidManifest.xml") + } + getByName("androidTest") { + java.srcDirs("src/androidTest/java") + } + } + + buildFeatures { + buildConfig = true + } + + packaging { + resources { + excludes += setOf("META-INF/LICENSE", "META-INF/LICENSE.txt", "META-INF/DEPENDENCIES", "META-INF/NOTICE") + } + } + + lint { + abortOnError = false + } +} + +kotlin { + jvmToolchain(17) +} + +dependencies { + // SalesforceReact is provided by react-native-force autolinking + implementation("com.facebook.react:react-android:0.81.5") + implementation("com.facebook.react:react-android:0.81.5") + implementation("com.facebook.react:hermes-android:0.81.5") + + androidTestImplementation("androidx.test:runner:1.6.2") + androidTestImplementation("androidx.test:rules:1.6.1") + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0") +} diff --git a/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactHarnessTest.java b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactHarnessTest.java new file mode 100644 index 0000000..cf60b1b --- /dev/null +++ b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactHarnessTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.androidsdk.reactnative; + + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +/** + * Tests for the harness + * To make sure that synchronous/asynchronous passing/failing are correctly reported + */ +@RunWith(Parameterized.class) +@SmallTest +public class ReactHarnessTest extends ReactTestCase { + @Parameterized.Parameter(0) public String testName; + + @Parameterized.Parameters(name = "{0}") + public static Collection data() { + return Arrays.asList(new Object[][]{ + {"testPassing"}, + {"testAsyncPassing"} + }); + } + + @Test + public void test() throws Exception { + runReactNativeTest(testName); + } +} diff --git a/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactMobileSyncTest.java b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactMobileSyncTest.java new file mode 100644 index 0000000..ed8d0a4 --- /dev/null +++ b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactMobileSyncTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative; + +import androidx.test.filters.SmallTest; + +import com.salesforce.androidsdk.mobilesync.manager.SyncManager; +import com.salesforce.androidsdk.smartstore.app.SmartStoreSDKManager; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.List; + +/** + * Running javascript tests for MobileSync react module. + */ +@RunWith(Parameterized.class) +@SmallTest +public class ReactMobileSyncTest extends ReactTestCase { + + @Parameterized.Parameter + public String testName; + + @Parameterized.Parameters(name = "{0}") + public static List data() { + return Arrays.asList( + "testCleanResyncGhosts", + "testSyncUp", + "testSyncDown", + "testReSync", + "testGetSyncStatusDeleteSync"); + } + + @After + public void tearDown() { + SyncManager.reset(); + SmartStoreSDKManager.getInstance().removeAllUserStores(); + } + + @Test + public void test() throws Exception { + runReactNativeTest(testName); + } +} \ No newline at end of file diff --git a/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactNetTest.java b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactNetTest.java new file mode 100644 index 0000000..0d2d2bd --- /dev/null +++ b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactNetTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.List; + +import androidx.test.filters.SmallTest; + +/** + * Running javascript tests for Net react module. + */ + +@RunWith(Parameterized.class) +@SmallTest +public class ReactNetTest extends ReactTestCase { + + @Parameterized.Parameter + public String testName; + + @Parameterized.Parameters(name = "{0}") + public static List data() { + return Arrays.asList( + "testGetApiVersion", + "testVersions", + "testResources", + "testDescribeGlobal", + "testMetaData", + "testDescribe", + "testDescribeLayout", + "testCreateRetrieve", + "testUpsertUpdateRetrieve", + "testCreateDelRetrieve", + "testQuery", + "testSearch", + "testPublicApiCall", + "testCollectionCreateRetrieve", + "testCollectionUpsertUpdateRetrieve", + "testCollectionCreateDeleteRetrieve" + ); + } + + @Test + public void test() throws Exception { + runReactNativeTest(testName); + } +} + diff --git a/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactOAuthTest.java b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactOAuthTest.java new file mode 100644 index 0000000..4d98941 --- /dev/null +++ b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactOAuthTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.androidsdk.reactnative; + + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.List; + +/** + * Running javascript tests for OAuth react module. + */ + +@RunWith(Parameterized.class) +@SmallTest +public class ReactOAuthTest extends ReactTestCase { + + @Parameterized.Parameter + public String testName; + + @Parameterized.Parameters(name = "{0}") + public static List data() { + return Arrays.asList("testGetAuthCredentials"); + } + + @Test + public void test() throws Exception { + runReactNativeTest(testName); + } +} + diff --git a/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactSmartStoreTest.java b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactSmartStoreTest.java new file mode 100644 index 0000000..73aee31 --- /dev/null +++ b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactSmartStoreTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.androidsdk.reactnative; + + +import androidx.test.filters.SmallTest; + +import com.salesforce.androidsdk.smartstore.app.SmartStoreSDKManager; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.List; + +/** + * Running javascript tests for SmartStore react module. + */ + +@RunWith(Parameterized.class) +@SmallTest +public class ReactSmartStoreTest extends ReactTestCase { + + @Parameterized.Parameter + public String testName; + + @Parameterized.Parameters(name = "{0}") + public static List data() { + return Arrays.asList( + "testGetDatabaseSize", + "testRegisterExistsRemoveExists", + "testGetSoupIndexSpecs", + "testUpsertRetrieve", + "testQuerySoup", + "testMoveCursor", + "testSmartQuerySoup", + "testRemoveFromSoup", + "testClearSoup", + "testGetRemoveStores", + "testGetRemoveGlobalStores"); + } + + @After + public void tearDown() { + SmartStoreSDKManager.getInstance().removeAllUserStores(); + SmartStoreSDKManager.getInstance().removeAllGlobalStores(); + } + + @Test + public void test() throws Exception { + runReactNativeTest(testName); + } +} diff --git a/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactTestCase.java b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactTestCase.java new file mode 100644 index 0000000..1d19299 --- /dev/null +++ b/androidTests/android/app/src/androidTest/java/com/salesforce/androidsdk/reactnative/ReactTestCase.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.androidsdk.reactnative; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import android.content.Context; +import android.content.Intent; + +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiObjectNotFoundException; +import androidx.test.uiautomator.UiSelector; + +import static com.salesforce.androidsdk.reactnative.util.ReactActivityTestDelegate.TEST_NAME; + +import com.salesforce.androidsdk.reactnative.util.ReactTestActivity; +import com.salesforce.androidsdk.reactnative.util.TestResult; + +import org.junit.Assert; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public abstract class ReactTestCase { + + private static final long TEST_TIMEOUT_SECONDS = 120; + + // Dismissing system dialog if shown + // See https://stackoverflow.com/questions/39457305/android-testing-waited-for-the-root-of-the-view-hierarchy-to-have-window-focus + private void dismissSystemDialog() { + UiDevice device = UiDevice.getInstance(getInstrumentation()); + UiObject okButton = device.findObject(new UiSelector().textContains("OK")); + try { + okButton.click(); + } catch (UiObjectNotFoundException e) { + // Nothing to do + } + } + + protected void runReactNativeTest(String testName) throws InterruptedException { + TestResult result = getTestResult(testName); + if (result == null) { + Assert.fail(testName + " timed out"); + } + else { + Assert.assertTrue(result.message, result.status); + } + } + + private TestResult getTestResult(String testName) throws InterruptedException { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Intent intent = new Intent(context, ReactTestActivity.class); + intent.putExtra(TEST_NAME, testName); + try (ActivityScenario ignored = ActivityScenario.launch(intent)) { + dismissSystemDialog(); + return TestResult.waitForTestResult(getTestTimeoutSeconds()); + } + } + + protected long getTestTimeoutSeconds() { + return TEST_TIMEOUT_SECONDS; + } +} \ No newline at end of file diff --git a/androidTests/android/app/src/main/AndroidManifest.xml b/androidTests/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2dc06fc --- /dev/null +++ b/androidTests/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/androidTests/android/app/src/main/assets/raw/keep.xml b/androidTests/android/app/src/main/assets/raw/keep.xml new file mode 100644 index 0000000..dde0dc2 --- /dev/null +++ b/androidTests/android/app/src/main/assets/raw/keep.xml @@ -0,0 +1 @@ + diff --git a/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/bridge/SalesforceTestBridge.java b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/bridge/SalesforceTestBridge.java new file mode 100644 index 0000000..7c779fe --- /dev/null +++ b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/bridge/SalesforceTestBridge.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.bridge; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.salesforce.androidsdk.reactnative.util.TestResult; + +public class SalesforceTestBridge extends ReactContextBaseJavaModule { + + private static final String TAG = "SalesforceTestBridge"; + + public SalesforceTestBridge(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return TAG; + } + + @ReactMethod + public void markTestCompleted() { + TestResult.recordTestResult(TestResult.success()); + } + +} \ No newline at end of file diff --git a/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/DebugSalesforceReactPackage.kt b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/DebugSalesforceReactPackage.kt new file mode 100644 index 0000000..b8bc21c --- /dev/null +++ b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/DebugSalesforceReactPackage.kt @@ -0,0 +1,56 @@ +package com.salesforce.androidsdk.reactnative.util + +import android.util.Log +import com.facebook.react.BaseReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.facebook.react.turbomodule.core.interfaces.TurboModule +import com.facebook.react.uimanager.ViewManager +import com.salesforce.androidsdk.reactnative.bridge.MobileSyncReactBridge +import com.salesforce.androidsdk.reactnative.bridge.SalesforceNetReactBridge +import com.salesforce.androidsdk.reactnative.bridge.SalesforceOauthReactBridge +import com.salesforce.androidsdk.reactnative.bridge.SmartStoreReactBridge + +class DebugSalesforceReactPackage : BaseReactPackage() { + + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + Log.i("DebugSFPackage", "getModule called for: $name") + val module = when (name) { + "SFOauthReactBridge" -> SalesforceOauthReactBridge(reactContext) + "SFNetReactBridge" -> SalesforceNetReactBridge(reactContext) + "SFSmartStoreReactBridge" -> SmartStoreReactBridge(reactContext) + "SFMobileSyncReactBridge" -> MobileSyncReactBridge(reactContext) + else -> null + } + Log.i("DebugSFPackage", " returned: $module, isTurboModule=${module is TurboModule}") + return module + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + Log.i("DebugSFPackage", "getReactModuleInfoProvider called") + return ReactModuleInfoProvider { + val map = mapOf( + "SFOauthReactBridge" to ReactModuleInfo("SFOauthReactBridge", "SFOauthReactBridge", false, false, false, true), + "SFNetReactBridge" to ReactModuleInfo("SFNetReactBridge", "SFNetReactBridge", false, false, false, true), + "SFSmartStoreReactBridge" to ReactModuleInfo("SFSmartStoreReactBridge", "SFSmartStoreReactBridge", false, false, false, true), + "SFMobileSyncReactBridge" to ReactModuleInfo("SFMobileSyncReactBridge", "SFMobileSyncReactBridge", false, false, false, true), + ) + Log.i("DebugSFPackage", " ReactModuleInfoProvider returning: ${map.keys}") + map + } + } + + override fun createNativeModules(reactContext: ReactApplicationContext): List { + Log.i("DebugSFPackage", "createNativeModules called") + return listOf( + SalesforceOauthReactBridge(reactContext), + SalesforceNetReactBridge(reactContext), + SmartStoreReactBridge(reactContext), + MobileSyncReactBridge(reactContext), + ) + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> = emptyList() +} diff --git a/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/ReactActivityTestDelegate.java b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/ReactActivityTestDelegate.java new file mode 100644 index 0000000..4d68bb5 --- /dev/null +++ b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/ReactActivityTestDelegate.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.androidsdk.reactnative.util; + +import android.app.Activity; +import android.os.Bundle; + +import com.facebook.react.ReactActivityDelegate; + +import javax.annotation.Nullable; + +/** + * Subclass of ReactActivityDelegate used for testing + * + * It loads the registered app for the test (if the test is called testxxx, we expected a registered app called xxx) + * Also it destroys the instance manager when destroyed so that a fresh one gets build for the next test + */ +public class ReactActivityTestDelegate extends ReactActivityDelegate { + + public static final String TEST_NAME = "testName"; + public static final String TEST_PREFIX = "test"; + + private Activity activity; + private String testName; + + public ReactActivityTestDelegate(Activity activity, @Nullable String mainComponentName) { + super(activity, "xxx" /* the value to use will be computed in loadApp - but we need a non-null value otherwise loadApp won't be called */); + this.activity = activity; + } + + @Override + protected void loadApp(String appKey) { + Bundle extras = activity.getIntent().getExtras(); + testName = extras.getString(TEST_NAME); + super.loadApp(testName.substring(TEST_PREFIX.length())); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } +} diff --git a/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/ReactNativeTestHost.java b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/ReactNativeTestHost.java new file mode 100644 index 0000000..26579b4 --- /dev/null +++ b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/ReactNativeTestHost.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.androidsdk.reactnative.util; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.facebook.react.ReactPackage; +import com.facebook.react.defaults.DefaultReactNativeHost; +import com.facebook.react.shell.MainReactPackage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Subclass of DefaultReactNativeHost used for testing. + * + * Loads SalesforceReactPackage and SalesforceReactTestPackage (which brings SalesforceTestBridge). + * Uses new architecture (TurboModules) with Hermes and reads from a pre-built JS bundle. + */ +public class ReactNativeTestHost extends DefaultReactNativeHost { + + protected ReactNativeTestHost(Application application) { + super(application); + } + + @Override + public boolean getUseDeveloperSupport() { + return false; + } + + @NonNull + @Override + protected List getPackages() { + List packages = new ArrayList<>(); + packages.add(new MainReactPackage()); + packages.add(new DebugSalesforceReactPackage()); + packages.add(new SalesforceReactTestPackage()); + return packages; + } + + // Exposed for debugging + public List debugGetPackages() { + return getPackages(); + } + + @NonNull + @Override + protected String getJSMainModuleName() { + return "index"; + } + + @Override + protected String getBundleAssetName() { + return "index.android.bundle"; + } + + @Override + public boolean isNewArchEnabled() { + return true; + } + + @Override + public boolean isHermesEnabled() { + return true; + } +} diff --git a/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/ReactTestActivity.java b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/ReactTestActivity.java new file mode 100644 index 0000000..7e06604 --- /dev/null +++ b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/ReactTestActivity.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.androidsdk.reactnative.util; + +import com.facebook.react.ReactActivityDelegate; +import com.salesforce.androidsdk.reactnative.ui.SalesforceReactActivity; +import com.salesforce.androidsdk.rest.ClientManager; +import com.salesforce.androidsdk.util.test.TestCredentials; + +/** + * Sub-class of SalesforceReactActivity that authenticates using hard-coded credentials. + * + * Also uses ReactActivityTestDelegate as delegate + */ +public class ReactTestActivity extends SalesforceReactActivity { + + static String username = TestCredentials.USERNAME; + static String accountName = TestCredentials.ACCOUNT_NAME; + static String refreshToken = TestCredentials.REFRESH_TOKEN; + static String authToken = "--will-be-set-through-refresh--"; + static String identityUrl = TestCredentials.IDENTITY_URL; + static String instanceUrl = TestCredentials.INSTANCE_URL; + static String loginUrl = TestCredentials.LOGIN_URL; + static String orgId = TestCredentials.ORG_ID; + static String userId = TestCredentials.USER_ID; + static String photoUrl = TestCredentials.PHOTO_URL; + static String clientId = TestCredentials.CLIENT_ID; + static String language = TestCredentials.LANGUAGE; + static String locale = TestCredentials.LOCALE; + + @Override + public ClientManager buildClientManager() { + final ClientManager clientManager = super.buildClientManager(); + clientManager.createNewAccount(accountName, username, refreshToken, authToken, instanceUrl, + loginUrl, identityUrl, clientId, orgId, userId, + null, null, null, null, null, + null, photoUrl, null, null, null, + null, null, null, null, null, null, false, + language, locale); + return clientManager; + } + + protected ReactActivityDelegate createReactActivityDelegate() { + return new ReactActivityTestDelegate(this, null); + } +} diff --git a/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/SalesforceReactTestApp.kt b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/SalesforceReactTestApp.kt new file mode 100644 index 0000000..31feb02 --- /dev/null +++ b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/SalesforceReactTestApp.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.util + +import android.app.Application +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.ReactNativeHost +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost +import com.facebook.react.soloader.OpenSourceMergedSoMapping +import com.facebook.soloader.SoLoader +import com.salesforce.androidsdk.analytics.EventBuilderHelper +import com.salesforce.androidsdk.reactnative.app.SalesforceReactSDKManager +import com.salesforce.androidsdk.util.test.TestCredentials + +class SalesforceReactTestApp : Application(), ReactApplication { + + override val reactNativeHost: ReactNativeHost = ReactNativeTestHost(this) + + override val reactHost: ReactHost + get() = getDefaultReactHost(applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + SoLoader.init(this, OpenSourceMergedSoMapping) + load() + EventBuilderHelper.enableDisable(false) + SalesforceReactSDKManager.initReactNative(applicationContext, ReactTestActivity::class.java) + TestCredentials.init(this) + + // Debug: log packages from our host + val host = reactNativeHost as ReactNativeTestHost + val packages = host.debugGetPackages() + android.util.Log.i("TestApp", "Packages: ${packages.map { it.javaClass.simpleName }}") + packages.forEach { pkg -> + android.util.Log.i("TestApp", " ${pkg.javaClass.simpleName} is BaseReactPackage: ${pkg is com.facebook.react.BaseReactPackage}") + } + } +} diff --git a/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/SalesforceReactTestPackage.java b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/SalesforceReactTestPackage.java new file mode 100644 index 0000000..3f0dac8 --- /dev/null +++ b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/SalesforceReactTestPackage.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.androidsdk.reactnative.util; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; +import com.salesforce.androidsdk.reactnative.bridge.SalesforceTestBridge; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * React package that contains the SalesforceTestBridge module + */ +public class SalesforceReactTestPackage implements ReactPackage { + + @Override + public List createNativeModules(ReactApplicationContext reactApplicationContext) { + return Arrays.asList(new NativeModule[] { new SalesforceTestBridge(reactApplicationContext)}); + } + + @Override + public List createViewManagers(ReactApplicationContext reactApplicationContext) { + return Collections.emptyList(); + } + +} diff --git a/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/TestResult.java b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/TestResult.java new file mode 100644 index 0000000..599b594 --- /dev/null +++ b/androidTests/android/app/src/main/java/com/salesforce/androidsdk/reactnative/util/TestResult.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of salesforce.com, inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission of salesforce.com, inc. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.androidsdk.reactnative.util; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * Class to describe a test result + * + * Also holds a static blocking queue to keep track of current test completion + */ +public class TestResult { + + private static ArrayBlockingQueue testCompleted = new ArrayBlockingQueue(1); + + public final boolean status; + public final String message; + + private TestResult(boolean status, String message) { + this.status = status; + this.message = message; + } + + public static TestResult success() { + return new TestResult(true, null); + } + + public static TestResult failure(String message) { + return new TestResult(false, message); + } + + + public static void recordTestResult(TestResult result) { + testCompleted.offer(result); + } + + public static TestResult waitForTestResult(long timeoutSeconds) throws InterruptedException { + return testCompleted.poll(timeoutSeconds, TimeUnit.SECONDS); + } +} \ No newline at end of file diff --git a/androidTests/android/app/src/main/res/drawable-hdpi/icon.png b/androidTests/android/app/src/main/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..8074c4c Binary files /dev/null and b/androidTests/android/app/src/main/res/drawable-hdpi/icon.png differ diff --git a/androidTests/android/app/src/main/res/drawable-ldpi/icon.png b/androidTests/android/app/src/main/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..1095584 Binary files /dev/null and b/androidTests/android/app/src/main/res/drawable-ldpi/icon.png differ diff --git a/androidTests/android/app/src/main/res/drawable-mdpi/icon.png b/androidTests/android/app/src/main/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..a07c69f Binary files /dev/null and b/androidTests/android/app/src/main/res/drawable-mdpi/icon.png differ diff --git a/androidTests/android/app/src/main/res/layout/main.xml b/androidTests/android/app/src/main/res/layout/main.xml new file mode 100644 index 0000000..c5deb6b --- /dev/null +++ b/androidTests/android/app/src/main/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/androidTests/android/app/src/main/res/values/bootconfig.xml b/androidTests/android/app/src/main/res/values/bootconfig.xml new file mode 100644 index 0000000..8daa444 --- /dev/null +++ b/androidTests/android/app/src/main/res/values/bootconfig.xml @@ -0,0 +1,6 @@ + + + + __CONSUMER_KEY__ + __REDIRECT_URI__ + diff --git a/androidTests/android/app/src/main/res/values/strings.xml b/androidTests/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..e2f1b21 --- /dev/null +++ b/androidTests/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + Hello World! + com.salesforce.androidsdk.salesforcereact.login + ReactNativeTest + com.salesforce.androidsdk.reactnative.tests + diff --git a/androidTests/android/app/src/main/res/xml/servers.xml b/androidTests/android/app/src/main/res/xml/servers.xml new file mode 100644 index 0000000..0b31e38 --- /dev/null +++ b/androidTests/android/app/src/main/res/xml/servers.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/androidTests/android/build.gradle b/androidTests/android/build.gradle new file mode 100644 index 0000000..1e486e8 --- /dev/null +++ b/androidTests/android/build.gradle @@ -0,0 +1,24 @@ +buildscript { + ext { + buildToolsVersion = "35.0.0" + minSdkVersion = 28 + compileSdkVersion = 36 + targetSdkVersion = 36 + ndkVersion = "27.1.12297006" + kotlinVersion = "2.3.20" + } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath("com.android.tools.build:gradle:8.12.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + classpath("org.jetbrains.dokka:dokka-gradle-plugin:2.0.0") + classpath("org.jacoco:org.jacoco.core:0.8.14") + classpath("io.github.gradle-nexus:publish-plugin:2.0.0") + } +} + +// Note: com.facebook.react.rootproject not applied - not needed for test app diff --git a/androidTests/android/build/generated/autolinking/autolinking.json b/androidTests/android/build/generated/autolinking/autolinking.json new file mode 100644 index 0000000..92586ee --- /dev/null +++ b/androidTests/android/build/generated/autolinking/autolinking.json @@ -0,0 +1,503 @@ +{ + "root": "/Users/wmathurin/Workspaces/rn-new-architecture-2/SalesforceMobileSDK-Workspace/SalesforceMobileSDK-ReactNative/androidTests", + "reactNativePath": "/Users/wmathurin/Workspaces/rn-new-architecture-2/SalesforceMobileSDK-Workspace/SalesforceMobileSDK-ReactNative/androidTests/node_modules/react-native", + "reactNativeVersion": "0.81", + "dependencies": { + "react-native-force": { + "root": "/Users/wmathurin/Workspaces/rn-new-architecture-2/SalesforceMobileSDK-Workspace/SalesforceMobileSDK-ReactNative/androidTests/node_modules/react-native-force", + "name": "react-native-force", + "platforms": { + "ios": { + "podspecPath": "/Users/wmathurin/Workspaces/rn-new-architecture-2/SalesforceMobileSDK-Workspace/SalesforceMobileSDK-ReactNative/androidTests/node_modules/react-native-force/SalesforceReact.podspec", + "version": "14.0.0", + "configurations": [], + "scriptPhases": [] + }, + "android": { + "sourceDir": "/Users/wmathurin/Workspaces/rn-new-architecture-2/SalesforceMobileSDK-Workspace/SalesforceMobileSDK-ReactNative/androidTests/node_modules/react-native-force/android", + "packageImportPath": "import com.salesforce.androidsdk.reactnative.app.SalesforceReactPackage;", + "packageInstance": "new SalesforceReactPackage()", + "buildTypes": [], + "libraryName": "RNSalesforceReactSpec", + "componentDescriptors": [], + "cmakeListsPath": "/Users/wmathurin/Workspaces/rn-new-architecture-2/SalesforceMobileSDK-Workspace/SalesforceMobileSDK-ReactNative/androidTests/node_modules/react-native-force/android/build/generated/source/codegen/jni/CMakeLists.txt", + "cxxModuleCMakeListsModuleName": null, + "cxxModuleCMakeListsPath": null, + "cxxModuleHeaderName": null, + "isPureCxxDependency": false + } + } + } + }, + "commands": [ + { + "name": "bundle", + "description": "Build the bundle for the provided JavaScript entry file.", + "options": [ + { + "name": "--entry-file ", + "description": "Path to the root JS file, either absolute or relative to JS root" + }, + { + "name": "--platform ", + "description": "Either \"ios\" or \"android\"", + "default": "ios" + }, + { + "name": "--transformer ", + "description": "Specify a custom transformer to be used" + }, + { + "name": "--dev [boolean]", + "description": "If false, warnings are disabled and the bundle is minified", + "default": true + }, + { + "name": "--minify [boolean]", + "description": "Allows overriding whether bundle is minified. This defaults to false if dev is true, and true if dev is false. Disabling minification can be useful for speeding up production builds for testing purposes." + }, + { + "name": "--bundle-output ", + "description": "File name where to store the resulting bundle, ex. /tmp/groups.bundle" + }, + { + "name": "--bundle-encoding ", + "description": "Encoding the bundle should be written in (https://nodejs.org/api/buffer.html#buffer_buffer).", + "default": "utf8" + }, + { + "name": "--max-workers ", + "description": "Specifies the maximum number of workers the worker-pool will spawn for transforming files. This defaults to the number of the cores available on your machine." + }, + { + "name": "--sourcemap-output ", + "description": "File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map" + }, + { + "name": "--sourcemap-sources-root ", + "description": "Path to make sourcemap's sources entries relative to, ex. /root/dir" + }, + { + "name": "--sourcemap-use-absolute-path", + "description": "Report SourceMapURL using its full path", + "default": false + }, + { + "name": "--assets-dest ", + "description": "Directory name where to store assets referenced in the bundle" + }, + { + "name": "--unstable-transform-profile ", + "description": "Experimental, transform JS for a specific JS engine. Currently supported: hermes, hermes-canary, default", + "default": "default" + }, + { + "name": "--asset-catalog-dest [string]", + "description": "Path where to create an iOS Asset Catalog for images" + }, + { + "name": "--reset-cache", + "description": "Removes cached files", + "default": false + }, + { + "name": "--read-global-cache", + "description": "Try to fetch transformed JS code from the global cache, if configured.", + "default": false + }, + { + "name": "--config ", + "description": "Path to the CLI configuration file" + }, + { + "name": "--resolver-option ", + "description": "Custom resolver options of the form key=value. URL-encoded. May be specified multiple times." + } + ] + }, + { + "name": "start", + "description": "Start the React Native development server.", + "options": [ + { + "name": "--port " + }, + { + "name": "--host ", + "default": "" + }, + { + "name": "--projectRoot ", + "description": "Path to a custom project root" + }, + { + "name": "--watchFolders ", + "description": "Specify any additional folders to be added to the watch list" + }, + { + "name": "--assetPlugins ", + "description": "Specify any additional asset plugins to be used by the packager by full filepath" + }, + { + "name": "--sourceExts ", + "description": "Specify any additional source extensions to be used by the packager" + }, + { + "name": "--max-workers ", + "description": "Specifies the maximum number of workers the worker-pool will spawn for transforming files. This defaults to the number of the cores available on your machine." + }, + { + "name": "--transformer ", + "description": "Specify a custom transformer to be used" + }, + { + "name": "--reset-cache, --resetCache", + "description": "Removes cached files" + }, + { + "name": "--custom-log-reporter-path, --customLogReporterPath ", + "description": "Path to a JavaScript file that exports a log reporter as a replacement for TerminalReporter" + }, + { + "name": "--https", + "description": "Enables https connections to the server" + }, + { + "name": "--key ", + "description": "Path to custom SSL key" + }, + { + "name": "--cert ", + "description": "Path to custom SSL cert" + }, + { + "name": "--config ", + "description": "Path to the CLI configuration file" + }, + { + "name": "--no-interactive", + "description": "Disables interactive mode" + }, + { + "name": "--client-logs", + "description": "[Deprecated] Enable plain text JavaScript log streaming for all connected apps. This feature is deprecated and will be removed in future.", + "default": false + } + ] + }, + { + "name": "codegen", + "options": [ + { + "name": "--path ", + "description": "Path to the React Native project root.", + "default": "/Users/wmathurin/Workspaces/rn-new-architecture-2/SalesforceMobileSDK-Workspace/SalesforceMobileSDK-ReactNative/androidTests" + }, + { + "name": "--platform ", + "description": "Target platform. Supported values: \"android\", \"ios\", \"all\".", + "default": "all" + }, + { + "name": "--outputPath ", + "description": "Path where generated artifacts will be output to." + }, + { + "name": "--source ", + "description": "Whether the script is invoked from an `app` or a `library`", + "default": "app" + } + ] + }, + { + "name": "log-ios", + "description": "starts iOS device syslog tail", + "options": [ + { + "name": "-i --interactive", + "description": "Explicitly select simulator to tail logs from. By default it will tail logs from the first booted and available simulator." + } + ] + }, + { + "name": "run-ios", + "description": "builds your app and starts it on iOS simulator", + "examples": [ + { + "desc": "Run on a different simulator, e.g. iPhone SE (2nd generation)", + "cmd": "npx react-native run-ios --simulator \"iPhone SE (2nd generation)\"" + }, + { + "desc": "Run on a connected device, e.g. Max's iPhone", + "cmd": "npx react-native run-ios --device \"Max's iPhone\"" + }, + { + "desc": "Run on the AppleTV simulator", + "cmd": "npx react-native run-ios --simulator \"Apple TV\" --scheme \"helloworld-tvOS\"" + } + ], + "options": [ + { + "name": "--no-packager", + "description": "Do not launch packager while running the app" + }, + { + "name": "--port ", + "default": 8081 + }, + { + "name": "--terminal ", + "description": "Launches the Metro Bundler in a new window using the specified terminal path.", + "default": "Apple_Terminal" + }, + { + "name": "--binary-path ", + "description": "Path relative to project root where pre-built .app binary lives." + }, + { + "name": "--list-devices", + "description": "List all available iOS devices and simulators and let you choose one to run the app. " + }, + { + "name": "--udid ", + "description": "Explicitly set the device to use by UDID" + }, + { + "name": "--simulator ", + "description": "Explicitly set the simulator to use. Optionally set the iOS version between parentheses at the end to match an exact version: \"iPhone 15 (17.0)\"" + }, + { + "name": "--mode ", + "description": "Explicitly set the scheme configuration to use. This option is case sensitive." + }, + { + "name": "--scheme ", + "description": "Explicitly set Xcode scheme to use" + }, + { + "name": "--destination ", + "description": "Explicitly extend destination e.g. \"arch=x86_64\"" + }, + { + "name": "--verbose", + "description": "Do not use xcbeautify or xcpretty even if installed" + }, + { + "name": "--xcconfig [string]", + "description": "Explicitly set xcconfig to use" + }, + { + "name": "--buildFolder ", + "description": "Location for iOS build artifacts. Corresponds to Xcode's \"-derivedDataPath\"." + }, + { + "name": "--extra-params ", + "description": "Custom params that will be passed to xcodebuild command." + }, + { + "name": "--target ", + "description": "Explicitly set Xcode target to use." + }, + { + "name": "-i --interactive", + "description": "Explicitly select which scheme and configuration to use before running a build" + }, + { + "name": "--force-pods", + "description": "Force CocoaPods installation" + }, + { + "name": "--only-pods", + "description": "Only install Cocoapods, do not build the app" + }, + { + "name": "--device [string]", + "description": "Explicitly set the device to use by name or by unique device identifier . If the value is not provided,the app will run on the first available physical device." + } + ] + }, + { + "name": "build-ios", + "description": "builds your app for iOS platform", + "examples": [ + { + "desc": "Build the app for all iOS devices in Release mode", + "cmd": "npx react-native build-ios --mode \"Release\"" + } + ], + "options": [ + { + "name": "--mode ", + "description": "Explicitly set the scheme configuration to use. This option is case sensitive." + }, + { + "name": "--scheme ", + "description": "Explicitly set Xcode scheme to use" + }, + { + "name": "--destination ", + "description": "Explicitly extend destination e.g. \"arch=x86_64\"" + }, + { + "name": "--verbose", + "description": "Do not use xcbeautify or xcpretty even if installed" + }, + { + "name": "--xcconfig [string]", + "description": "Explicitly set xcconfig to use" + }, + { + "name": "--buildFolder ", + "description": "Location for iOS build artifacts. Corresponds to Xcode's \"-derivedDataPath\"." + }, + { + "name": "--extra-params ", + "description": "Custom params that will be passed to xcodebuild command." + }, + { + "name": "--target ", + "description": "Explicitly set Xcode target to use." + }, + { + "name": "-i --interactive", + "description": "Explicitly select which scheme and configuration to use before running a build" + }, + { + "name": "--force-pods", + "description": "Force CocoaPods installation" + }, + { + "name": "--only-pods", + "description": "Only install Cocoapods, do not build the app" + }, + { + "name": "--device [string]", + "description": "Explicitly set the device to use by name or by unique device identifier . If the value is not provided,the app will run on the first available physical device." + } + ] + }, + { + "name": "log-android", + "description": "starts logkitty" + }, + { + "name": "run-android", + "description": "builds your app and starts it on a connected Android emulator or device", + "options": [ + { + "name": "--mode ", + "description": "Specify your app's build variant" + }, + { + "name": "--tasks ", + "description": "Run custom Gradle tasks. By default it's \"assembleDebug\". Will override passed mode and variant arguments." + }, + { + "name": "--active-arch-only", + "description": "Build native libraries only for the current device architecture for debug builds.", + "default": false + }, + { + "name": "--extra-params ", + "description": "Custom params passed to gradle build command" + }, + { + "name": "-i --interactive", + "description": "Explicitly select build type and flavour to use before running a build" + }, + { + "name": "--no-packager", + "description": "Do not launch packager while running the app" + }, + { + "name": "--port ", + "default": 8081 + }, + { + "name": "--terminal ", + "description": "Launches the Metro Bundler in a new window using the specified terminal path.", + "default": "Apple_Terminal" + }, + { + "name": "--appId ", + "description": "Specify an applicationId to launch after build. If not specified, `package` from AndroidManifest.xml will be used.", + "default": "" + }, + { + "name": "--appIdSuffix ", + "description": "Specify an applicationIdSuffix to launch after build.", + "default": "" + }, + { + "name": "--main-activity ", + "description": "Name of the activity to start" + }, + { + "name": "--device ", + "description": "Explicitly set the device to use by name. The value is not required if you have a single device connected." + }, + { + "name": "--deviceId ", + "description": "**DEPRECATED** Builds your app and starts it on a specific device/simulator with the given device id (listed by running \"adb devices\" on the command line)." + }, + { + "name": "--list-devices", + "description": "Lists all available Android devices and simulators and let you choose one to run the app", + "default": false + }, + { + "name": "--binary-path ", + "description": "Path relative to project root where pre-built .apk binary lives." + }, + { + "name": "--user ", + "description": "Id of the User Profile you want to install the app on." + } + ] + }, + { + "name": "build-android", + "description": "builds your app", + "options": [ + { + "name": "--mode ", + "description": "Specify your app's build variant" + }, + { + "name": "--tasks ", + "description": "Run custom Gradle tasks. By default it's \"assembleDebug\". Will override passed mode and variant arguments." + }, + { + "name": "--active-arch-only", + "description": "Build native libraries only for the current device architecture for debug builds.", + "default": false + }, + { + "name": "--extra-params ", + "description": "Custom params passed to gradle build command" + }, + { + "name": "-i --interactive", + "description": "Explicitly select build type and flavour to use before running a build" + } + ] + } + ], + "healthChecks": [], + "platforms": { + "ios": {}, + "android": {} + }, + "assets": [], + "project": { + "ios": null, + "android": { + "sourceDir": "/Users/wmathurin/Workspaces/rn-new-architecture-2/SalesforceMobileSDK-Workspace/SalesforceMobileSDK-ReactNative/androidTests/android", + "appName": "app", + "packageName": "com.salesforce.androidsdk.reactnative.tests", + "applicationId": "com.salesforce.androidsdk.reactnative.tests", + "mainActivity": "com.salesforce.androidsdk.reactnative.util.ReactTestActivity", + "assets": [] + } + } +} diff --git a/androidTests/android/build/generated/autolinking/package.json.sha b/androidTests/android/build/generated/autolinking/package.json.sha new file mode 100644 index 0000000..15e9c1f --- /dev/null +++ b/androidTests/android/build/generated/autolinking/package.json.sha @@ -0,0 +1 @@ +2849a2da5b6cf0cb723c286504cdc0df1d2a536a6a271a6f35e4c43d134e19aa \ No newline at end of file diff --git a/androidTests/android/build/generated/autolinking/yarn.lock.sha b/androidTests/android/build/generated/autolinking/yarn.lock.sha new file mode 100644 index 0000000..9e8c236 --- /dev/null +++ b/androidTests/android/build/generated/autolinking/yarn.lock.sha @@ -0,0 +1 @@ +449670a2c5cd1915d897c50656a692fe6d03863fdacebfd9a8d926e25ff8378a \ No newline at end of file diff --git a/androidTests/android/build/reports/problems/problems-report.html b/androidTests/android/build/reports/problems/problems-report.html new file mode 100644 index 0000000..6900b44 --- /dev/null +++ b/androidTests/android/build/reports/problems/problems-report.html @@ -0,0 +1,663 @@ + + + + + + + + + + + + + Gradle Configuration Cache + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/androidTests/android/gradle.properties b/androidTests/android/gradle.properties new file mode 100644 index 0000000..8028274 --- /dev/null +++ b/androidTests/android/gradle.properties @@ -0,0 +1,4 @@ +android.useAndroidX=true +org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m +newArchEnabled=true +hermesEnabled=true diff --git a/androidTests/android/gradle/wrapper/gradle-wrapper.jar b/androidTests/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1b33c55 Binary files /dev/null and b/androidTests/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/androidTests/android/gradle/wrapper/gradle-wrapper.properties b/androidTests/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d4081da --- /dev/null +++ b/androidTests/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/androidTests/android/gradlew b/androidTests/android/gradlew new file mode 100755 index 0000000..23d15a9 --- /dev/null +++ b/androidTests/android/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/androidTests/android/gradlew.bat b/androidTests/android/gradlew.bat new file mode 100644 index 0000000..5eed7ee --- /dev/null +++ b/androidTests/android/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/androidTests/android/settings.gradle b/androidTests/android/settings.gradle new file mode 100644 index 0000000..2311750 --- /dev/null +++ b/androidTests/android/settings.gradle @@ -0,0 +1,27 @@ +pluginManagement { + includeBuild("../node_modules/@react-native/gradle-plugin") + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +plugins { id("com.facebook.react.settings") } +extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> ex.autolinkLibrariesFromCommand() } + +rootProject.name = 'SalesforceReactTests' +include ':app' +includeBuild('../node_modules/@react-native/gradle-plugin') + +// Include Android SDK libraries from mobile_sdk clone +def androidSdkRoot = new File(rootProject.projectDir, '../mobile_sdk/SalesforceMobileSDK-Android') +if (androidSdkRoot.exists()) { + includeBuild(androidSdkRoot) { + dependencySubstitution { + substitute(module('com.salesforce.mobilesdk:SalesforceSDK')).using(project(':libs:SalesforceSDK')) + substitute(module('com.salesforce.mobilesdk:SalesforceAnalytics')).using(project(':libs:SalesforceAnalytics')) + substitute(module('com.salesforce.mobilesdk:SmartStore')).using(project(':libs:SmartStore')) + substitute(module('com.salesforce.mobilesdk:MobileSync')).using(project(':libs:MobileSync')) + } + } +} diff --git a/androidTests/babel.config.js b/androidTests/babel.config.js new file mode 100644 index 0000000..f7b3da3 --- /dev/null +++ b/androidTests/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['module:@react-native/babel-preset'], +}; diff --git a/androidTests/create_test_credentials_from_env.js b/androidTests/create_test_credentials_from_env.js new file mode 100644 index 0000000..2e545e5 --- /dev/null +++ b/androidTests/create_test_credentials_from_env.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +var fs = require('fs'); +var path = require('path'); + +var credentialsFile = path.join('android', 'app', 'src', 'main', 'assets', 'test_credentials.json'); + +if (process.env.TEST_CREDENTIALS) { + console.log('Writing test credentials from TEST_CREDENTIALS env var'); + fs.writeFileSync(credentialsFile, process.env.TEST_CREDENTIALS, 'utf8'); +} else { + console.log('TEST_CREDENTIALS env var not set - using placeholder'); + if (!fs.existsSync(credentialsFile)) { + fs.writeFileSync(credentialsFile, '{}', 'utf8'); + } +} diff --git a/androidTests/metro.config.js b/androidTests/metro.config.js new file mode 100644 index 0000000..1d3b96e --- /dev/null +++ b/androidTests/metro.config.js @@ -0,0 +1,3 @@ +const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); +const config = {}; +module.exports = mergeConfig(getDefaultConfig(__dirname), config); diff --git a/androidTests/package.json b/androidTests/package.json new file mode 100644 index 0000000..6d3bcb2 --- /dev/null +++ b/androidTests/package.json @@ -0,0 +1,37 @@ +{ + "name": "SalesforceReactAndroidTests", + "version": "14.0.0", + "private": true, + "scripts": { + "start": "react-native start" + }, + "sdkDependencies": { + "SalesforceMobileSDK-Android": "https://github.com/forcedotcom/SalesforceMobileSDK-Android.git#dev" + }, + "dependencies": { + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "create-react-class": "^15.7.0", + "jsc-android": "^250231.0.0", + "react": "19.1.0", + "react-native": "0.81.5", + "react-native-force": "git+https://github.com/forcedotcom/SalesforceMobileSDK-ReactNative.git#dev" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", + "@babel/runtime": "^7.25.0", + "@react-native-community/cli": "20.0.0", + "@react-native-community/cli-platform-android": "20.0.0", + "@react-native-community/cli-platform-ios": "20.0.0", + "@react-native/babel-preset": "0.81.5", + "@react-native/metro-config": "0.81.5", + "@react-native/typescript-config": "0.81.5", + "babel-jest": "^30.0.0", + "chai": "4.4.1", + "metro-react-native-babel-preset": "0.77.0", + "typescript": "^5.8.3" + }, + "engines": { + "node": ">=20" + } +} diff --git a/androidTests/prepareandroid.js b/androidTests/prepareandroid.js new file mode 100755 index 0000000..de6fef7 --- /dev/null +++ b/androidTests/prepareandroid.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +var execSync = require('child_process').execSync; +var path = require('path'); +var fs = require('fs'); + +console.log('=== Installing npm dependencies'); +execSync('rm -rf node_modules', {stdio:[0,1,2]}); +execSync('rm -f yarn.lock', {stdio:[0,1,2]}); +execSync('yarn install', {stdio:[0,1,2]}); + +console.log('=== Installing sdk dependencies'); +execSync('node ./updatesdk.js', {stdio: [0,1,2]}); + +console.log('=== Creating test_credentials.json'); +var assetsDir = path.join('android', 'app', 'src', 'main', 'assets'); +if (!fs.existsSync(assetsDir)) { + fs.mkdirSync(assetsDir, {recursive: true}); +} +var credentialsFile = path.join(assetsDir, 'test_credentials.json'); +if (!fs.existsSync(credentialsFile)) { + fs.writeFileSync(credentialsFile, '{}', 'utf8'); +} + +console.log('=== Creating index.android.bundle'); +execSync('node ./updatebundle.js', {stdio: [0,1,2]}); + +console.log('=== Android test preparation complete.'); diff --git a/androidTests/updatebundle.js b/androidTests/updatebundle.js new file mode 100644 index 0000000..c832d7b --- /dev/null +++ b/androidTests/updatebundle.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +var execFileSync = require('child_process').execFileSync; +var path = require('path'); +var fs = require('fs'); + +var assetsDir = path.join('android', 'app', 'src', 'main', 'assets'); +if (!fs.existsSync(assetsDir)) { + fs.mkdirSync(assetsDir, {recursive: true}); +} + +execFileSync('node', [ + 'node_modules/react-native/cli.js', 'bundle', + '--platform', 'android', + '--dev', 'true', + '--entry-file', 'node_modules/react-native-force/test/alltests.js', + '--bundle-output', path.join(assetsDir, 'index.android.bundle'), + '--assets-dest', assetsDir +], {stdio:[0,1,2]}); diff --git a/androidTests/updatesdk.js b/androidTests/updatesdk.js new file mode 100644 index 0000000..6c49dc3 --- /dev/null +++ b/androidTests/updatesdk.js @@ -0,0 +1,82 @@ +#!/usr/bin/env node + +var packageJson = require('./package.json'); +var execFileSync = require('child_process').execFileSync; +var path = require('path'); +var fs = require('fs'); +var rimraf = require('rimraf'); + +var sdkDependency = 'SalesforceMobileSDK-Android'; +var repoUrlWithBranch = packageJson.sdkDependencies[sdkDependency]; +var parts = repoUrlWithBranch.split('#'); +var repoUrl = parts[0]; +var branch = parts.length > 1 ? parts[1] : 'dev'; +var targetDir = path.join('mobile_sdk', sdkDependency); + +rimraf.sync(targetDir); +execFileSync('git', ['clone', '--branch', branch, '--single-branch', '--depth', '1', repoUrl, targetDir], {stdio:[0,1,2]}); + +// Remove unnecessary directories +rimraf.sync(path.join(targetDir, 'hybrid')); +rimraf.sync(path.join(targetDir, 'native')); +rimraf.sync(path.join(targetDir, 'libs', 'SalesforceHybrid')); +rimraf.sync(path.join(targetDir, 'libs', 'SalesforceReact')); +rimraf.sync(path.join(targetDir, 'libs', 'test')); + +// Patch settings.gradle.kts to exclude sample apps and removed libs +var settingsFile = path.join(targetDir, 'settings.gradle.kts'); +if (fs.existsSync(settingsFile)) { + var settings = fs.readFileSync(settingsFile, 'utf8'); + settings = settings.replace(/^include\("(hybrid|native|libs:SalesforceReact|libs:SalesforceHybrid).*$/gm, '// $&'); + fs.writeFileSync(settingsFile, settings, 'utf8'); + console.log('Patched settings.gradle.kts'); +} + +// Patch root build.gradle.kts to remove publish/dokka plugins +var rootBuildFile = path.join(targetDir, 'build.gradle.kts'); +if (fs.existsSync(rootBuildFile)) { + var content = fs.readFileSync(rootBuildFile, 'utf8'); + content = content.replace(/^apply\(plugin = "io\.github\.gradle-nexus\.publish-plugin"\).*$/gm, ''); + content = content.replace(/^apply\(from = .*publish-root.*\).*$/gm, ''); + content = content.replace(/^apply\(plugin = "org\.jetbrains\.dokka"\).*$/gm, ''); + content = content.replace(/^extensions\.configure[\s\S]*?^}/gm, ''); + content = content.replace(/^dependencies \{[\s\S]*?^}/gm, ''); + content = content.replace(/^tasks\.register[\s\S]*?^}/gm, ''); + fs.writeFileSync(rootBuildFile, content, 'utf8'); + console.log('Patched root build.gradle.kts (removed publish/dokka)'); +} + +// Strip publish-module plugin +var buildSrcFile = path.join(targetDir, 'buildSrc', 'src', 'main', 'kotlin', 'publish-module.gradle.kts'); +if (fs.existsSync(buildSrcFile)) { + fs.writeFileSync(buildSrcFile, '// Stripped for test builds\n', 'utf8'); + console.log('Stripped publish-module plugin'); +} + +// Remove dokka plugin from individual lib build.gradle.kts files +var libsDir = path.join(targetDir, 'libs'); +var libNames = ['SalesforceAnalytics', 'SalesforceSDK', 'SmartStore', 'MobileSync']; +libNames.forEach(function(lib) { + var libBuildFile = path.join(libsDir, lib, 'build.gradle.kts'); + if (fs.existsSync(libBuildFile)) { + var libContent = fs.readFileSync(libBuildFile, 'utf8'); + libContent = libContent.replace(/\s*id\("org\.jetbrains\.dokka"\)/g, ''); + fs.writeFileSync(libBuildFile, libContent, 'utf8'); + } +}); +console.log('Removed dokka from lib build files'); + +// Patch AGP version to 8.12.0 for compatibility with test project +var buildSrcBuildFile = path.join(targetDir, 'buildSrc', 'build.gradle.kts'); +if (fs.existsSync(buildSrcBuildFile)) { + var bsContent = fs.readFileSync(buildSrcBuildFile, 'utf8'); + bsContent = bsContent.replace(/"com\.android\.tools\.build:gradle:[^"]+"/g, '"com.android.tools.build:gradle:8.12.0"'); + fs.writeFileSync(buildSrcBuildFile, bsContent, 'utf8'); + console.log('Patched buildSrc AGP to 8.12.0'); +} +if (fs.existsSync(rootBuildFile)) { + var rootContent = fs.readFileSync(rootBuildFile, 'utf8'); + rootContent = rootContent.replace(/"com\.android\.tools\.build:gradle:[^"]+"/g, '"com.android.tools.build:gradle:8.12.0"'); + fs.writeFileSync(rootBuildFile, rootContent, 'utf8'); + console.log('Patched root build.gradle.kts AGP to 8.12.0'); +} diff --git a/dist/index.js b/dist/index.js index c85c2ae..2edfadb 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,4 +1,29 @@ "use strict"; +/* + * Copyright (c) 2016-present, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -50,4 +75,3 @@ const forceUtil = __importStar(require("./react.force.util")); exports.forceUtil = forceUtil; const forceClient = net; exports.forceClient = forceClient; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/react.force.common.d.ts b/dist/react.force.common.d.ts index 243f3ee..a558fec 100644 --- a/dist/react.force.common.d.ts +++ b/dist/react.force.common.d.ts @@ -1,15 +1,27 @@ import { ModuleAndroidName, ModuleIOSName } from "./typings"; -export type iOSModuleCallback = (err: Error, result: T) => void; -export type AndroidSuccessCallback = (result: string) => void; -export type AndroidErrorCallback = (err: string) => void; +/** + * Represents an exec() success callback + */ export type ExecSuccessCallback = (result: T) => void; +/** + * Represents an exec() error callback + */ export type ExecErrorCallback = (err: Error) => void; -interface ModuleIOS { - [key: string]: (args: unknown, callback: iOSModuleCallback) => void; +/** + * Represents a native module with unified single-callback pattern. + * Both iOS and Android now use: module.method(args, (error, result) => {...}) + */ +interface NativeModule { + [key: string]: (args: unknown, callback: (error: any, result: any) => void) => void; } -interface ModuleAndroid { - [key: string]: (args: unknown, successCB: AndroidSuccessCallback, errorCB: AndroidErrorCallback) => void; -} -export declare const exec: (moduleIOSName: ModuleIOSName, moduleAndroidName: ModuleAndroidName, moduleIOS: ModuleIOS, moduleAndroid: ModuleAndroid, successCB: ExecSuccessCallback | null, errorCB: ExecErrorCallback | null, methodName: string, args: Record) => void; +/** + * Executes an action using the React Native Mobile SDK Bridge. + * Both iOS and Android use a unified single-callback pattern: + * callback(error, result) where error is null on success. + */ +export declare const exec: (moduleIOSName: ModuleIOSName, moduleAndroidName: ModuleAndroidName, moduleIOS: NativeModule, moduleAndroid: NativeModule, successCB: ExecSuccessCallback | null, errorCB: ExecErrorCallback | null, methodName: string, args: Record) => void; +/** + * Parses a JSON string safely, returning the original value if parsing fails. + */ export declare const safeJSONparse: (str: string) => T; export {}; diff --git a/dist/react.force.common.js b/dist/react.force.common.js index 60d4dfd..b45e97d 100644 --- a/dist/react.force.common.js +++ b/dist/react.force.common.js @@ -1,47 +1,72 @@ "use strict"; +/* + * Copyright (c) 2015-present, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ Object.defineProperty(exports, "__esModule", { value: true }); exports.safeJSONparse = exports.exec = void 0; const react_force_log_1 = require("./react.force.log"); +/** + * Executes an action using the React Native Mobile SDK Bridge. + * Both iOS and Android use a unified single-callback pattern: + * callback(error, result) where error is null on success. + */ const exec = (moduleIOSName, moduleAndroidName, moduleIOS, moduleAndroid, successCB, errorCB, methodName, args) => { - if (moduleIOS) { - const func = `${moduleIOSName}.${methodName}`; - react_force_log_1.sdkConsole.debug(`${func} called: ${JSON.stringify(args)}`); - moduleIOS[methodName](args, (error, result) => { - if (error) { - react_force_log_1.sdkConsole.error(`${func} failed: ${JSON.stringify(error)}`); - if (errorCB) - errorCB(error); - } - else { - react_force_log_1.sdkConsole.debug(`${func} succeeded`); - if (successCB) - successCB(result); - } - }); + const module = moduleIOS !== null && moduleIOS !== void 0 ? moduleIOS : moduleAndroid; + const moduleName = moduleIOS ? moduleIOSName : moduleAndroidName; + if (!module) { + react_force_log_1.sdkConsole.error(`No native module found for ${moduleIOSName}/${moduleAndroidName}`); + if (errorCB) + errorCB(new Error("Native module not available")); + return; } - else if (moduleAndroid) { - const func = `${moduleAndroidName}.${methodName}`; - react_force_log_1.sdkConsole.debug(`${func} called: ${JSON.stringify(args)}`); - moduleAndroid[methodName](args, (result) => { - react_force_log_1.sdkConsole.debug(`${func} succeeded`); - if (successCB) { - successCB((0, exports.safeJSONparse)(result)); - } - }, (error) => { + const func = `${moduleName}.${methodName}`; + react_force_log_1.sdkConsole.debug(`${func} called: ${JSON.stringify(args)}`); + module[methodName](args, (error, result) => { + if (error) { react_force_log_1.sdkConsole.error(`${func} failed: ${JSON.stringify(error)}`); if (errorCB) - errorCB((0, exports.safeJSONparse)(error)); - }); - } + errorCB(typeof error === "string" ? (0, exports.safeJSONparse)(error) : error); + } + else { + react_force_log_1.sdkConsole.debug(`${func} succeeded`); + if (successCB) + successCB(typeof result === "string" ? (0, exports.safeJSONparse)(result) : result); + } + }); }; exports.exec = exec; +/** + * Parses a JSON string safely, returning the original value if parsing fails. + */ const safeJSONparse = (str) => { try { return JSON.parse(str); } catch (e) { + // @ts-ignore return str; } }; exports.safeJSONparse = safeJSONparse; -//# sourceMappingURL=react.force.common.js.map \ No newline at end of file diff --git a/dist/react.force.log.js b/dist/react.force.log.js index 3c81896..d4c4948 100644 --- a/dist/react.force.log.js +++ b/dist/react.force.log.js @@ -1,6 +1,34 @@ "use strict"; +/* + * Copyright (c) 2020-present, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ Object.defineProperty(exports, "__esModule", { value: true }); exports.setLogLevel = exports.sdkConsole = exports.getLogLevel = void 0; +/** + * logging support + */ let logLevel = "info"; const getLogLevel = () => { return logLevel; @@ -10,8 +38,10 @@ exports.sdkConsole = { debug: console.debug.bind(console), info: console.info.bind(console), warn: () => { + /** */ }, error: () => { + /** */ }, log: console.log.bind(console), }; @@ -20,13 +50,13 @@ const setLogLevel = (level) => { const methods = ["debug", "info", "warn", "error"]; const levelAsInt = methods.indexOf(level.toLowerCase()); const noop = () => { + /** */ }; exports.sdkConsole.debug = levelAsInt <= 0 ? console.debug.bind(console) : noop; exports.sdkConsole.info = levelAsInt <= 1 ? console.info.bind(console) : noop; - exports.sdkConsole.warn = levelAsInt <= 2 ? console.log.bind(console) : noop; - exports.sdkConsole.error = levelAsInt <= 3 ? console.log.bind(console) : noop; + exports.sdkConsole.warn = levelAsInt <= 2 ? console.log.bind(console) : noop; // we don't want the yellow box + exports.sdkConsole.error = levelAsInt <= 3 ? console.log.bind(console) : noop; // we don't want the red box exports.sdkConsole.log = console.log.bind(console); }; exports.setLogLevel = setLogLevel; (0, exports.setLogLevel)("info"); -//# sourceMappingURL=react.force.log.js.map \ No newline at end of file diff --git a/dist/react.force.mobilesync.js b/dist/react.force.mobilesync.js index a395d2c..5cfaf63 100644 --- a/dist/react.force.mobilesync.js +++ b/dist/react.force.mobilesync.js @@ -1,10 +1,43 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MERGE_MODE = exports.deleteSync = exports.getSyncStatus = exports.syncUp = exports.cleanResyncGhosts = exports.reSync = exports.syncDown = void 0; +/* + * Copyright (c) 2015-present, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ const react_native_1 = require("react-native"); const react_force_common_1 = require("./react.force.common"); -const { MobileSyncReactBridge, SFMobileSyncReactBridge } = react_native_1.NativeModules; +// New architecture: TurboModuleRegistry first, fall back to NativeModules. +// Lazy lookup - bridgeless mode doesn't have modules ready at import time. +const getSFMobileSyncReactBridge = () => { var _a; return (_a = react_native_1.TurboModuleRegistry.get("SFMobileSyncReactBridge")) !== null && _a !== void 0 ? _a : react_native_1.NativeModules.SFMobileSyncReactBridge; }; +const getMobileSyncReactBridge = () => { var _a; return (_a = react_native_1.TurboModuleRegistry.get("MobileSyncReactBridge")) !== null && _a !== void 0 ? _a : react_native_1.NativeModules.MobileSyncReactBridge; }; +// If param is a storeconfig return the same storeconfig +// If param is a boolean, returns a storeconfig object {'isGlobalStore': boolean} +// Otherwise, returns a default storeconfig object const checkFirstArg = (arg) => { + // Turning arguments into array + // If first argument is a store config if (typeof arg === "object" && Object.prototype.hasOwnProperty.call(arg, "isGlobalStore")) { return arg; } @@ -15,13 +48,14 @@ const checkFirstArg = (arg) => { return { isGlobalStore: isGlobalStore }; }; const exec = (successCB, errorCB, methodName, args) => { - (0, react_force_common_1.exec)("SFMobileSyncReactBridge", "MobileSyncReactBridge", SFMobileSyncReactBridge, MobileSyncReactBridge, successCB, errorCB, methodName, args); + (0, react_force_common_1.exec)("SFMobileSyncReactBridge", "MobileSyncReactBridge", getSFMobileSyncReactBridge(), getMobileSyncReactBridge(), successCB, errorCB, methodName, args); }; const syncDown = (storeConfig, target, soupName, options, x, y, z) => { storeConfig = checkFirstArg(storeConfig); let syncName; let successCB; let errorCB; + // syncName optional (new in 6.0) if (typeof x === "function") { syncName = undefined; successCB = x; @@ -66,6 +100,7 @@ const syncUp = (storeConfig, target, soupName, options, x, y, z) => { let syncName; let successCB; let errorCB; + // syncName optional (new in 6.0) if (typeof x === "function") { syncName = undefined; successCB = x; @@ -110,4 +145,3 @@ exports.MERGE_MODE = { OVERWRITE: "OVERWRITE", LEAVE_IF_CHANGED: "LEAVE_IF_CHANGED", }; -//# sourceMappingURL=react.force.mobilesync.js.map \ No newline at end of file diff --git a/dist/react.force.net.d.ts b/dist/react.force.net.d.ts index 040a793..1120e07 100644 --- a/dist/react.force.net.d.ts +++ b/dist/react.force.net.d.ts @@ -1,30 +1,193 @@ import { ExecErrorCallback, ExecSuccessCallback } from "./react.force.common"; import { HttpMethod } from "./typings"; +/** + * Set apiVersion to be used + */ export declare const setApiVersion: (version: string) => void; +/** + * Return apiVersion used + */ export declare const getApiVersion: () => string; +/** + * Send arbitray force.com request + */ export declare const sendRequest: (endPoint: string, path: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback, method?: HttpMethod, payload?: Record | null, headerParams?: Record | null, fileParams?: unknown, returnBinary?: boolean, doesNotRequireAuthentication?: boolean) => void; +/** + * Lists summary information about each Salesforce.com version currently + * available, including the version, label, and a link to each version's + * root. + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const versions: (successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Lists available resources for the client's API version, including + * resource name and URI. + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const resources: (successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Lists the available objects and their metadata for your organization's + * data. + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const describeGlobal: (successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Describes the individual metadata for the specified object. + * @param objtype object type; e.g. "Account" + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const metadata: (objtype: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Completely describes the individual metadata at all levels for the + * specified object. + * @param objtype object type; e.g. "Account" + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const describe: (objtype: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Fetches the layout configuration for a particular sobject type and record type id. + * @param objtype object type; e.g. "Account" + * @param (Optional) recordTypeId Id of the layout's associated record type + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const describeLayout: (objtype: string, recordTypeId: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Creates a new record of the given type. + * @param objtype object type; e.g. "Account" + * @param fields an object containing initial field names and values for + * the record, e.g. {:Name "salesforce.com", :TickerSymbol + * "CRM"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const create: (objtype: string, fields: Record, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; type RetrieveOverload = { (objtype: string, id: string, fieldlist: string[], successCB: ExecSuccessCallback, errorCB: ExecErrorCallback): void; (objtype: string, id: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback): void; }; +/** + * Retrieves field values for a record of the given type. + * @param objtype object type; e.g. "Account" + * @param id the record's object ID + * @param [fields=null] list of fields for which + * to return values; e.g. Name,Industry,TickerSymbol + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const retrieve: RetrieveOverload; +/** + * Upsert - creates or updates record of the given type, based on the + * given external Id. + * @param objtype object type; e.g. "Account" + * @param externalIdField external ID field name; e.g. "accountMaster__c" + * @param externalId the record's external ID value + * @param fields an object containing field names and values for + * the record, e.g. {:Name "salesforce.com", :TickerSymbol + * "CRM"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const upsert: (objtype: string, externalIdField: string, externalId: string, fields: Record, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Updates field values on a record of the given type. + * @param objtype object type; e.g. "Account" + * @param id the record's object ID + * @param fields an object containing initial field names and values for + * the record, e.g. {:Name "salesforce.com", :TickerSymbol + * "CRM"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const update: (objtype: string, id: string, fields: Record, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Deletes a record of the given type. Unfortunately, 'delete' is a + * reserved word in JavaScript. + * @param objtype object type; e.g. "Account" + * @param id the record's object ID + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const del: (objtype: string, id: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Executes the specified SOQL query. + * @param soql a string containing the query to execute - e.g. "SELECT Id, + * Name from Account ORDER BY Name LIMIT 20" + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const query: (soql: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Queries the next set of records based on pagination. + *

This should be used if performing a query that retrieves more than can be returned + * in accordance with http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_query.htm

+ + * @param url - the url retrieved from nextRecordsUrl or prevRecordsUrl + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const queryMore: (url: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Executes the specified SOSL search. + * @param sosl a string containing the search to execute - e.g. "FIND + * {needle}" + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const search: (sosl: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Convenience function to retrieve an attachment + * @param id + * @param callback function to which response will be passed (attachment is returned as {encodedBody:"base64-encoded-response", contentType:"content-type"}) + * @param [error=null] function called in case of error + */ export declare const getAttachment: (id: string, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Creates up to 2000 new records in one roundtrip to the server. + * @param allOrNone indicates whether to roll back the entire request when one record fails + * @param records array of objects containing field names and values as well as a "attributes" property with the object type e.g. {type: "Account"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const collectionCreate: (allOrNone: boolean, records: Array>, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Updates up to 200 records in one roundtrip to the server. + * @param allOrNone indicates whether to roll back the entire request when one record fails + * @param records array of objects containing field names and values as well as a "attributes" property with the object type e.g. {type: "Account"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const collectionUpdate: (allOrNone: boolean, records: Array>, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Upserts up to 200 records in one roundtrip to the server. + * @param allOrNone indicates whether to roll back the entire request when one record fails + * @param objectType object type; e.g. "Account" + * @param externalIdField name of ID field in source data + * @param records array of objects containing field names and values as well as a "attributes" property with the object type e.g. {type: "Account"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const collectionUpsert: (allOrNone: boolean, objectType: string, externalIdField: string, records: Array>, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Retrieves up to 2000 records in one roundtrip to the server. + * @param objectType object type; e.g. "Account" + * @param ids the ids of records to retrieve + * @param fields list of fields for which to return values; e.g. ["Name", "Industry"] + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const collectionRetrieve: (objectType: string, ids: Array, fields: Array, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Delete up to 200 records in one roundtrip to the server. + * @param allOrNone indicates whether to roll back the entire request when one record fails + * @param ids the ids of records to delete + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ export declare const collectionDelete: (allOrNone: boolean, ids: Array, successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; export {}; diff --git a/dist/react.force.net.js b/dist/react.force.net.js index cd4e82f..3cb863a 100644 --- a/dist/react.force.net.js +++ b/dist/react.force.net.js @@ -1,21 +1,61 @@ "use strict"; +/* + * Copyright (c) 2015-present, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ Object.defineProperty(exports, "__esModule", { value: true }); exports.collectionDelete = exports.collectionRetrieve = exports.collectionUpsert = exports.collectionUpdate = exports.collectionCreate = exports.getAttachment = exports.search = exports.queryMore = exports.query = exports.del = exports.update = exports.upsert = exports.retrieve = exports.create = exports.describeLayout = exports.describe = exports.metadata = exports.describeGlobal = exports.resources = exports.versions = exports.sendRequest = exports.getApiVersion = exports.setApiVersion = void 0; const react_native_1 = require("react-native"); const react_force_common_1 = require("./react.force.common"); -const { SalesforceNetReactBridge, SFNetReactBridge } = react_native_1.NativeModules; -var apiVersion = 'v63.0'; +// New architecture: TurboModuleRegistry first, fall back to NativeModules. +// Lazy lookup - bridgeless mode doesn't have modules ready at import time. +const getSFNetReactBridge = () => { var _a; return (_a = react_native_1.TurboModuleRegistry.get("SFNetReactBridge")) !== null && _a !== void 0 ? _a : react_native_1.NativeModules.SFNetReactBridge; }; +const getSalesforceNetReactBridge = () => { + var _a; + return (_a = react_native_1.TurboModuleRegistry.get("SalesforceNetReactBridge")) !== null && _a !== void 0 ? _a : react_native_1.NativeModules.SalesforceNetReactBridge; +}; +var apiVersion = 'v66.0'; +/** + * Set apiVersion to be used + */ const setApiVersion = (version) => { apiVersion = version; }; exports.setApiVersion = setApiVersion; +/** + * Return apiVersion used + */ const getApiVersion = () => apiVersion; exports.getApiVersion = getApiVersion; +/** + * Send arbitray force.com request + */ const sendRequest = (endPoint, path, successCB, errorCB, method, payload, headerParams, fileParams, returnBinary, doesNotRequireAuthentication) => { method = method || "GET"; payload = payload || {}; headerParams = headerParams || {}; - fileParams = fileParams || {}; + fileParams = fileParams || {}; // File params expected to be of the form: {: {fileMimeType:, fileUrl:, fileName:}} returnBinary = !!returnBinary; doesNotRequireAuthentication = !!doesNotRequireAuthentication; const args = { @@ -28,26 +68,85 @@ const sendRequest = (endPoint, path, successCB, errorCB, method, payload, header returnBinary, doesNotRequireAuthentication, }; - (0, react_force_common_1.exec)("SFNetReactBridge", "SalesforceNetReactBridge", SFNetReactBridge, SalesforceNetReactBridge, successCB, errorCB, "sendRequest", args); + (0, react_force_common_1.exec)("SFNetReactBridge", "SalesforceNetReactBridge", getSFNetReactBridge(), getSalesforceNetReactBridge(), successCB, errorCB, "sendRequest", args); }; exports.sendRequest = sendRequest; +/** + * Lists summary information about each Salesforce.com version currently + * available, including the version, label, and a link to each version's + * root. + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const versions = (successCB, errorCB) => (0, exports.sendRequest)("/services/data", "/", successCB, errorCB); exports.versions = versions; +/** + * Lists available resources for the client's API version, including + * resource name and URI. + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const resources = (successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/`, successCB, errorCB); exports.resources = resources; +/** + * Lists the available objects and their metadata for your organization's + * data. + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const describeGlobal = (successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/`, successCB, errorCB); exports.describeGlobal = describeGlobal; +/** + * Describes the individual metadata for the specified object. + * @param objtype object type; e.g. "Account" + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const metadata = (objtype, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/${objtype}/`, successCB, errorCB); exports.metadata = metadata; +/** + * Completely describes the individual metadata at all levels for the + * specified object. + * @param objtype object type; e.g. "Account" + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const describe = (objtype, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/${objtype}/describe/`, successCB, errorCB); exports.describe = describe; -const describeLayout = (objtype, recordTypeId, successCB, errorCB) => { +/** + * Fetches the layout configuration for a particular sobject type and record type id. + * @param objtype object type; e.g. "Account" + * @param (Optional) recordTypeId Id of the layout's associated record type + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ +const describeLayout = (objtype, recordTypeId, +// todo: add describe typings +successCB, errorCB) => { recordTypeId = recordTypeId ? recordTypeId : ""; return (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/${objtype}/describe/layouts/${recordTypeId}`, successCB, errorCB); }; exports.describeLayout = describeLayout; +/** + * Creates a new record of the given type. + * @param objtype object type; e.g. "Account" + * @param fields an object containing initial field names and values for + * the record, e.g. {:Name "salesforce.com", :TickerSymbol + * "CRM"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const create = (objtype, fields, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/${objtype}/`, successCB, errorCB, "POST", fields); exports.create = create; +/** + * Retrieves field values for a record of the given type. + * @param objtype object type; e.g. "Account" + * @param id the record's object ID + * @param [fields=null] list of fields for which + * to return values; e.g. Name,Industry,TickerSymbol + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const retrieve = (objtype, id, x, y, z) => { let fieldlist; let successCB; @@ -66,14 +165,60 @@ const retrieve = (objtype, id, x, y, z) => { return (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/${objtype}/${id}`, successCB, errorCB, "GET", fields); }; exports.retrieve = retrieve; +/** + * Upsert - creates or updates record of the given type, based on the + * given external Id. + * @param objtype object type; e.g. "Account" + * @param externalIdField external ID field name; e.g. "accountMaster__c" + * @param externalId the record's external ID value + * @param fields an object containing field names and values for + * the record, e.g. {:Name "salesforce.com", :TickerSymbol + * "CRM"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const upsert = (objtype, externalIdField, externalId, fields, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/${objtype}/${externalIdField}/${externalId ? externalId : ""}`, successCB, errorCB, externalId ? "PATCH" : "POST", fields); exports.upsert = upsert; +/** + * Updates field values on a record of the given type. + * @param objtype object type; e.g. "Account" + * @param id the record's object ID + * @param fields an object containing initial field names and values for + * the record, e.g. {:Name "salesforce.com", :TickerSymbol + * "CRM"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const update = (objtype, id, fields, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/${objtype}/${id}`, successCB, errorCB, "PATCH", fields); exports.update = update; +/** + * Deletes a record of the given type. Unfortunately, 'delete' is a + * reserved word in JavaScript. + * @param objtype object type; e.g. "Account" + * @param id the record's object ID + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const del = (objtype, id, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/${objtype}/${id}`, successCB, errorCB, "DELETE"); exports.del = del; +/** + * Executes the specified SOQL query. + * @param soql a string containing the query to execute - e.g. "SELECT Id, + * Name from Account ORDER BY Name LIMIT 20" + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const query = (soql, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/query`, successCB, errorCB, "GET", { q: soql }); exports.query = query; +/** + * Queries the next set of records based on pagination. + *

This should be used if performing a query that retrieves more than can be returned + * in accordance with http://www.salesforce.com/us/developer/docs/api_rest/Content/dome_query.htm

+ + * @param url - the url retrieved from nextRecordsUrl or prevRecordsUrl + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const queryMore = (url, successCB, errorCB) => { const pathFromUrl = url.match(/https:\/\/[^/]*(.*)/); if (pathFromUrl && pathFromUrl.length === 2) { @@ -84,18 +229,70 @@ const queryMore = (url, successCB, errorCB) => { } }; exports.queryMore = queryMore; +/** + * Executes the specified SOSL search. + * @param sosl a string containing the search to execute - e.g. "FIND + * {needle}" + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const search = (sosl, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/search`, successCB, errorCB, "GET", { q: sosl }); exports.search = search; -const getAttachment = (id, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/Attachment/${id}/Body`, successCB, errorCB, "GET", null, null, null, true); +/** + * Convenience function to retrieve an attachment + * @param id + * @param callback function to which response will be passed (attachment is returned as {encodedBody:"base64-encoded-response", contentType:"content-type"}) + * @param [error=null] function called in case of error + */ +const getAttachment = (id, +// todo: add attachment typings +successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/sobjects/Attachment/${id}/Body`, successCB, errorCB, "GET", null, null, null, true /* return binary */); exports.getAttachment = getAttachment; +/** + * Creates up to 2000 new records in one roundtrip to the server. + * @param allOrNone indicates whether to roll back the entire request when one record fails + * @param records array of objects containing field names and values as well as a "attributes" property with the object type e.g. {type: "Account"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const collectionCreate = (allOrNone, records, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/composite/sobjects`, successCB, errorCB, "POST", { allOrNone: allOrNone, records: records }); exports.collectionCreate = collectionCreate; +/** + * Updates up to 200 records in one roundtrip to the server. + * @param allOrNone indicates whether to roll back the entire request when one record fails + * @param records array of objects containing field names and values as well as a "attributes" property with the object type e.g. {type: "Account"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const collectionUpdate = (allOrNone, records, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/composite/sobjects`, successCB, errorCB, "PATCH", { allOrNone: allOrNone, records: records }); exports.collectionUpdate = collectionUpdate; +/** + * Upserts up to 200 records in one roundtrip to the server. + * @param allOrNone indicates whether to roll back the entire request when one record fails + * @param objectType object type; e.g. "Account" + * @param externalIdField name of ID field in source data + * @param records array of objects containing field names and values as well as a "attributes" property with the object type e.g. {type: "Account"} + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const collectionUpsert = (allOrNone, objectType, externalIdField, records, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/composite/sobjects/${objectType}/${externalIdField}`, successCB, errorCB, "PATCH", { allOrNone: allOrNone, records: records }); exports.collectionUpsert = collectionUpsert; +/** + * Retrieves up to 2000 records in one roundtrip to the server. + * @param objectType object type; e.g. "Account" + * @param ids the ids of records to retrieve + * @param fields list of fields for which to return values; e.g. ["Name", "Industry"] + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const collectionRetrieve = (objectType, ids, fields, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/composite/sobjects/${objectType}`, successCB, errorCB, "POST", { ids: ids, fields: fields }); exports.collectionRetrieve = collectionRetrieve; +/** + * Delete up to 200 records in one roundtrip to the server. + * @param allOrNone indicates whether to roll back the entire request when one record fails + * @param ids the ids of records to delete + * @param callback function to which response will be passed + * @param [error=null] function called in case of error + */ const collectionDelete = (allOrNone, ids, successCB, errorCB) => (0, exports.sendRequest)("/services/data", `/${apiVersion}/composite/sobjects?allOrNone=${allOrNone}&ids=${ids.join(',')}`, successCB, errorCB, "DELETE"); exports.collectionDelete = collectionDelete; -//# sourceMappingURL=react.force.net.js.map \ No newline at end of file diff --git a/dist/react.force.oauth.d.ts b/dist/react.force.oauth.d.ts index cd266bb..ff2eba8 100644 --- a/dist/react.force.oauth.d.ts +++ b/dist/react.force.oauth.d.ts @@ -1,5 +1,41 @@ import { ExecSuccessCallback, ExecErrorCallback } from "./react.force.common"; import { UserAccount } from "./typings/oauth"; +/** + * Initiates the authentication process, with the given app configuration. + * success - The success callback function to use. + * fail - The failure/error callback function to use. + * Returns a dictionary with: + * accessToken + * refreshToken + * clientId + * userId + * orgId + * loginUrl + * instanceUrl + * userAgent + * community id + * community url + */ export declare const authenticate: (successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Obtain authentication credentials. + * success - The success callback function to use. + * fail - The failure/error callback function to use. + * Returns a dictionary with: + * accessToken + * refreshToken + * clientId + * userId + * orgId + * loginUrl + * instanceUrl + * userAgent + * community id + * community url + */ export declare const getAuthCredentials: (successCB: ExecSuccessCallback, errorCB: ExecErrorCallback) => void; +/** + * Logout the current authenticated user. This removes any current valid session token + * as well as any OAuth refresh token. + */ export declare const logout: (success: ExecSuccessCallback, fail: ExecErrorCallback) => void; diff --git a/dist/react.force.oauth.js b/dist/react.force.oauth.js index 626fcdd..d324fdd 100644 --- a/dist/react.force.oauth.js +++ b/dist/react.force.oauth.js @@ -1,22 +1,88 @@ "use strict"; +/* + * Copyright (c) 2015-present, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ Object.defineProperty(exports, "__esModule", { value: true }); exports.logout = exports.getAuthCredentials = exports.authenticate = void 0; const react_native_1 = require("react-native"); const react_force_common_1 = require("./react.force.common"); -const { SalesforceOauthReactBridge, SFOauthReactBridge } = react_native_1.NativeModules; +// Lazy module lookup: modules may not be available at import time in bridgeless mode. +const getSFOauthReactBridge = () => { var _a; return (_a = react_native_1.TurboModuleRegistry.get("SFOauthReactBridge")) !== null && _a !== void 0 ? _a : react_native_1.NativeModules.SFOauthReactBridge; }; +const getSalesforceOauthReactBridge = () => { + var _a; + return (_a = react_native_1.TurboModuleRegistry.get("SalesforceOauthReactBridge")) !== null && _a !== void 0 ? _a : react_native_1.NativeModules.SalesforceOauthReactBridge; +}; const exec = (successCB, errorCB, methodName, args) => { - (0, react_force_common_1.exec)("SFOauthReactBridge", "SalesforceOauthReactBridge", SFOauthReactBridge, SalesforceOauthReactBridge, successCB, errorCB, methodName, args); + (0, react_force_common_1.exec)("SFOauthReactBridge", "SalesforceOauthReactBridge", getSFOauthReactBridge(), getSalesforceOauthReactBridge(), successCB, errorCB, methodName, args); }; +/** + * Initiates the authentication process, with the given app configuration. + * success - The success callback function to use. + * fail - The failure/error callback function to use. + * Returns a dictionary with: + * accessToken + * refreshToken + * clientId + * userId + * orgId + * loginUrl + * instanceUrl + * userAgent + * community id + * community url + */ const authenticate = (successCB, errorCB) => { exec(successCB, errorCB, "authenticate", {}); }; exports.authenticate = authenticate; +/** + * Obtain authentication credentials. + * success - The success callback function to use. + * fail - The failure/error callback function to use. + * Returns a dictionary with: + * accessToken + * refreshToken + * clientId + * userId + * orgId + * loginUrl + * instanceUrl + * userAgent + * community id + * community url + */ const getAuthCredentials = (successCB, errorCB) => { exec(successCB, errorCB, "getAuthCredentials", {}); }; exports.getAuthCredentials = getAuthCredentials; +/** + * Logout the current authenticated user. This removes any current valid session token + * as well as any OAuth refresh token. + */ const logout = (success, fail) => { + // @ts-ignore exec(success, fail, "logoutCurrentUser", {}); }; exports.logout = logout; -//# sourceMappingURL=react.force.oauth.js.map \ No newline at end of file diff --git a/dist/react.force.smartstore.d.ts b/dist/react.force.smartstore.d.ts index 07a8b07..61eedf2 100644 --- a/dist/react.force.smartstore.d.ts +++ b/dist/react.force.smartstore.d.ts @@ -1,15 +1,24 @@ import { ExecErrorCallback, ExecSuccessCallback } from "./react.force.common"; import { QuerySpecType, StoreOrder } from "./typings"; +/** + * StoreConfig class + */ export declare class StoreConfig { storeName?: string; isGlobalStore?: boolean; constructor(storeName: string, isGlobalStore: boolean); } +/** + * SoupIndexSpec class + */ export declare class SoupIndexSpec { path: string; type: string; constructor(path: string, type: string); } +/** + * QuerySpec class + */ export declare class QuerySpec { queryType: QuerySpecType; indexPath?: string; @@ -24,6 +33,9 @@ export declare class QuerySpec { selectPaths?: string[]; constructor(path?: string); } +/** + * StoreCursor class + */ export declare class StoreCursor { cursorId?: string; pageSize: number; diff --git a/dist/react.force.smartstore.js b/dist/react.force.smartstore.js index 02dc2a9..6a5bca9 100644 --- a/dist/react.force.smartstore.js +++ b/dist/react.force.smartstore.js @@ -1,12 +1,43 @@ "use strict"; +/* + * Copyright (c) 2015-present, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ Object.defineProperty(exports, "__esModule", { value: true }); exports.removeAllStores = exports.removeAllGlobalStores = exports.removeStore = exports.getAllGlobalStores = exports.getAllStores = exports.closeCursor = exports.moveCursorToPreviousPage = exports.moveCursorToNextPage = exports.moveCursorToPageIndex = exports.removeFromSoup = exports.upsertSoupEntriesWithExternalId = exports.upsertSoupEntries = exports.retrieveSoupEntries = exports.runSmartQuery = exports.querySoup = exports.soupExists = exports.clearSoup = exports.reIndexSoup = exports.alterSoup = exports.getSoupIndexSpecs = exports.removeSoup = exports.registerSoup = exports.getDatabaseSize = exports.buildSmartQuerySpec = exports.buildMatchQuerySpec = exports.buildLikeQuerySpec = exports.buildRangeQuerySpec = exports.buildExactQuerySpec = exports.buildAllQuerySpec = exports.StoreCursor = exports.QuerySpec = exports.SoupIndexSpec = exports.StoreConfig = void 0; const react_native_1 = require("react-native"); const react_force_common_1 = require("./react.force.common"); -const { SmartStoreReactBridge, SFSmartStoreReactBridge } = react_native_1.NativeModules; +// New architecture: TurboModuleRegistry first, fall back to NativeModules. +// Lazy lookup - bridgeless mode doesn't have modules ready at import time. +const getSFSmartStoreReactBridge = () => { var _a; return (_a = react_native_1.TurboModuleRegistry.get("SFSmartStoreReactBridge")) !== null && _a !== void 0 ? _a : react_native_1.NativeModules.SFSmartStoreReactBridge; }; +const getSmartStoreReactBridge = () => { var _a; return (_a = react_native_1.TurboModuleRegistry.get("SmartStoreReactBridge")) !== null && _a !== void 0 ? _a : react_native_1.NativeModules.SmartStoreReactBridge; }; const exec = (successCB, errorCB, methodName, args) => { - (0, react_force_common_1.exec)("SFSmartStoreReactBridge", "SmartStoreReactBridge", SFSmartStoreReactBridge, SmartStoreReactBridge, successCB, errorCB, methodName, args); + (0, react_force_common_1.exec)("SFSmartStoreReactBridge", "SmartStoreReactBridge", getSFSmartStoreReactBridge(), getSmartStoreReactBridge(), successCB, errorCB, methodName, args); }; +/** + * StoreConfig class + */ class StoreConfig { constructor(storeName, isGlobalStore) { this.storeName = storeName; @@ -14,6 +45,9 @@ class StoreConfig { } } exports.StoreConfig = StoreConfig; +/** + * SoupIndexSpec class + */ class SoupIndexSpec { constructor(path, type) { this.path = path; @@ -21,50 +55,69 @@ class SoupIndexSpec { } } exports.SoupIndexSpec = SoupIndexSpec; +/** + * QuerySpec class + */ class QuerySpec { constructor(path) { + // the kind of query, one of: "exact","range", "like" or "smart": + // "exact" uses matchKey, "range" uses beginKey and endKey, "like" uses likeKey, "smart" uses smartSql this.queryType = "exact"; + // "ascending" or "descending" : optional this.order = "ascending"; + // the number of entries to copy from native to javascript per each cursor page this.pageSize = 10; this.indexPath = path; } } exports.QuerySpec = QuerySpec; +/** + * StoreCursor class + */ class StoreCursor { constructor() { + // the maximum number of entries returned per page this.pageSize = 0; + // the total number of results this.totalEntries = 0; + // the total number of pages of results available this.totalPages = 0; + // the current page index among all the pages available this.currentPageIndex = 0; + // the list of current page entries, ordered as requested in the querySpec this.currentPageOrderedEntries = []; } } exports.StoreCursor = StoreCursor; +// ====== querySpec factory methods +// Returns a query spec that will page through all soup entries in order by the given path value +// Internally it simply does a range query with null begin and end keys const buildAllQuerySpec = (path, order, pageSize, selectPaths) => { const inst = new QuerySpec(path); inst.queryType = "range"; inst.orderPath = path; if (order) { inst.order = order; - } + } // override default only if a value was specified if (pageSize) { inst.pageSize = pageSize; - } + } // override default only if a value was specified if (selectPaths) { inst.selectPaths = selectPaths; } return inst; }; exports.buildAllQuerySpec = buildAllQuerySpec; +// Returns a query spec that will page all entries exactly matching the matchKey value for path const buildExactQuerySpec = (path, matchKey, pageSize, order, orderPath, selectPaths) => { const inst = new QuerySpec(path); inst.matchKey = matchKey; if (pageSize) { inst.pageSize = pageSize; - } + } // override default only if a value was specified if (order) { inst.order = order; - } + } // override default only if a value was specified inst.orderPath = orderPath ? orderPath : path; if (selectPaths) { inst.selectPaths = selectPaths; @@ -72,6 +125,7 @@ const buildExactQuerySpec = (path, matchKey, pageSize, order, orderPath, selectP return inst; }; exports.buildExactQuerySpec = buildExactQuerySpec; +// Returns a query spec that will page all entries in the range beginKey ...endKey for path const buildRangeQuerySpec = (path, beginKey, endKey, order, pageSize, orderPath, selectPaths) => { const inst = new QuerySpec(path); inst.queryType = "range"; @@ -79,10 +133,10 @@ const buildRangeQuerySpec = (path, beginKey, endKey, order, pageSize, orderPath, inst.endKey = endKey; if (order) { inst.order = order; - } + } // override default only if a value was specified if (pageSize) { inst.pageSize = pageSize; - } + } // override default only if a value was specified inst.orderPath = orderPath ? orderPath : path; if (selectPaths) { inst.selectPaths = selectPaths; @@ -90,16 +144,17 @@ const buildRangeQuerySpec = (path, beginKey, endKey, order, pageSize, orderPath, return inst; }; exports.buildRangeQuerySpec = buildRangeQuerySpec; +// Returns a query spec that will page all entries matching the given likeKey value for path const buildLikeQuerySpec = (path, likeKey, order, pageSize, orderPath, selectPaths) => { const inst = new QuerySpec(path); inst.queryType = "like"; inst.likeKey = likeKey; if (order) { inst.order = order; - } + } // override default only if a value was specified if (pageSize) { inst.pageSize = pageSize; - } + } // override default only if a value was specified inst.orderPath = orderPath ? orderPath : path; if (selectPaths) { inst.selectPaths = selectPaths; @@ -107,6 +162,8 @@ const buildLikeQuerySpec = (path, likeKey, order, pageSize, orderPath, selectPat return inst; }; exports.buildLikeQuerySpec = buildLikeQuerySpec; +// Returns a query spec that will page all entries matching the given full-text search matchKey value for path +// Pass null for path to match matchKey across all full-text indexed fields const buildMatchQuerySpec = (path, matchKey, order, pageSize, orderPath, selectPaths) => { const inst = new QuerySpec(path); inst.queryType = "match"; @@ -114,10 +171,10 @@ const buildMatchQuerySpec = (path, matchKey, order, pageSize, orderPath, selectP inst.orderPath = orderPath; if (order) { inst.order = order; - } + } // override default only if a value was specified if (pageSize) { inst.pageSize = pageSize; - } + } // override default only if a value was specified inst.orderPath = orderPath ? orderPath : path; if (selectPaths) { inst.selectPaths = selectPaths; @@ -125,17 +182,23 @@ const buildMatchQuerySpec = (path, matchKey, order, pageSize, orderPath, selectP return inst; }; exports.buildMatchQuerySpec = buildMatchQuerySpec; +// Returns a query spec that will page all results returned by smartSql const buildSmartQuerySpec = (smartSql, pageSize) => { const inst = new QuerySpec(); inst.queryType = "smart"; inst.smartSql = smartSql; if (pageSize) { inst.pageSize = pageSize; - } + } // override default only if a value was specified return inst; }; exports.buildSmartQuerySpec = buildSmartQuerySpec; +// If param is a storeconfig return the same storeconfig +// If param is a boolean, returns a storeconfig object {'isGlobalStore': boolean} +// Otherwise, returns a default storeconfig object const checkFirstArg = (arg) => { + // Turning arguments into array + // If first argument is a store config if (typeof arg === "object" && arg.hasOwnProperty("isGlobalStore")) { return arg; } @@ -145,6 +208,7 @@ const checkFirstArg = (arg) => { } return { isGlobalStore }; }; +// ====== Soup manipulation ====== const getDatabaseSize = (storeConfig, successCB, errorCB) => { storeConfig = checkFirstArg(storeConfig); exec(successCB, errorCB, "getDatabaseSize", { @@ -227,7 +291,8 @@ const querySoup = (storeConfig, soupName, querySpec, successCB, errorCB) => { } if (querySpec.order != null && querySpec.orderPath == null) { querySpec.orderPath = querySpec.indexPath; - } + } // for backward compatibility with pre-3.3 code + // query returns serialized json on iOS starting in 7.0 const successCBdeserializing = successCB ? (result) => successCB(typeof result === "string" ? (0, react_force_common_1.safeJSONparse)(result) : result) : successCB; @@ -244,6 +309,7 @@ const runSmartQuery = (storeConfig, querySpec, successCB, errorCB) => { if (querySpec.queryType !== "smart") { throw new Error("runSmartQuery can only run smart queries"); } + // query returns serialized json on iOS starting in 7.0 const successCBdeserializing = successCB ? (result) => successCB(typeof result === "string" ? (0, react_force_common_1.safeJSONparse)(result) : result) : successCB; @@ -298,8 +364,10 @@ const removeFromSoup = (storeConfig, soupName, entryIdsOrQuerySpec, successCB, e exec(successCB, errorCB, "removeFromSoup", execArgs); }; exports.removeFromSoup = removeFromSoup; +// ====== Cursor manipulation ====== const moveCursorToPageIndex = (storeConfig, cursor, newPageIndex, successCB, errorCB) => { storeConfig = checkFirstArg(storeConfig); + // query returns serialized json on iOS starting in 7.0 let successCBdeserializing; if (successCB) { successCBdeserializing = (result) => successCB(typeof result === "string" ? (0, react_force_common_1.safeJSONparse)(result) : result); @@ -319,7 +387,9 @@ const moveCursorToNextPage = (storeConfig, cursor, successCB, errorCB) => { storeConfig = checkFirstArg(storeConfig); const newPageIndex = cursor.currentPageIndex + 1; if (newPageIndex >= cursor.totalPages) { - errorCB(new Error("moveCursorToNextPage called while on last page")); + errorCB( + // cursor, + new Error("moveCursorToNextPage called while on last page")); } else { (0, exports.moveCursorToPageIndex)(storeConfig, cursor, newPageIndex, successCB, errorCB); @@ -330,7 +400,9 @@ const moveCursorToPreviousPage = (storeConfig, cursor, successCB, errorCB) => { storeConfig = checkFirstArg(storeConfig); const newPageIndex = cursor.currentPageIndex - 1; if (newPageIndex < 0) { - errorCB(new Error("moveCursorToPreviousPage called while on first page")); + errorCB( + // cursor, + new Error("moveCursorToPreviousPage called while on first page")); } else { (0, exports.moveCursorToPageIndex)(storeConfig, cursor, newPageIndex, successCB, errorCB); @@ -346,6 +418,7 @@ const closeCursor = (storeConfig, cursor, successCB, errorCB) => { }); }; exports.closeCursor = closeCursor; +// ====== Store Operations ====== const getAllStores = (successCB, errorCB) => { exec(successCB, errorCB, "getAllStores", {}); }; @@ -370,4 +443,3 @@ const removeAllStores = (successCB, errorCB) => { exec(successCB, errorCB, "removeAllStores", {}); }; exports.removeAllStores = removeAllStores; -//# sourceMappingURL=react.force.smartstore.js.map \ No newline at end of file diff --git a/dist/react.force.test.js b/dist/react.force.test.js index 6be0580..02f8d3b 100644 --- a/dist/react.force.test.js +++ b/dist/react.force.test.js @@ -1,4 +1,29 @@ "use strict"; +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -53,12 +78,13 @@ const registerTest = (test) => { }; exports.registerTest = registerTest; const testDone = () => { + // iOS if (TestModule) { TestModule.markTestCompleted(); } + // Android else if (SalesforceTestBridge) { SalesforceTestBridge.markTestCompleted(); } }; exports.testDone = testDone; -//# sourceMappingURL=react.force.test.js.map \ No newline at end of file diff --git a/dist/react.force.util.js b/dist/react.force.util.js index 870a445..8d20c2d 100644 --- a/dist/react.force.util.js +++ b/dist/react.force.util.js @@ -4,7 +4,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.timeoutPromiser = exports.promiserNoRejection = exports.promiser = void 0; +/* + * Copyright (c) 2018-present, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ const react_force_log_1 = require("./react.force.log"); +// @ts-ignore const react_native_timer_1 = __importDefault(require("react-native-timer")); const rejectionTracking = require("promise/setimmediate/rejection-tracking"); const enableErrorOnUnhandledPromiseRejection = () => { @@ -52,6 +78,7 @@ const promiserNoRejection = (func) => { const retfn = function () { const args = Array.prototype.slice.call(arguments); return new Promise(function (resolve) { + // then() will be called whether it succeeded or failed const callback = () => { try { resolve.apply(null, arguments); @@ -78,4 +105,3 @@ const timeoutPromiser = (millis) => { }); }; exports.timeoutPromiser = timeoutPromiser; -//# sourceMappingURL=react.force.util.js.map \ No newline at end of file diff --git a/dist/typings/index.js b/dist/typings/index.js index aa219d8..c8ad2e5 100644 --- a/dist/typings/index.js +++ b/dist/typings/index.js @@ -1,3 +1,2 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/typings/mobilesync.js b/dist/typings/mobilesync.js index 54c66fd..c8ad2e5 100644 --- a/dist/typings/mobilesync.js +++ b/dist/typings/mobilesync.js @@ -1,3 +1,2 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -//# sourceMappingURL=mobilesync.js.map \ No newline at end of file diff --git a/dist/typings/oauth.js b/dist/typings/oauth.js index 101b718..c8ad2e5 100644 --- a/dist/typings/oauth.js +++ b/dist/typings/oauth.js @@ -1,3 +1,2 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -//# sourceMappingURL=oauth.js.map \ No newline at end of file diff --git a/dist/typings/smartstore.js b/dist/typings/smartstore.js index af8c7b3..c8ad2e5 100644 --- a/dist/typings/smartstore.js +++ b/dist/typings/smartstore.js @@ -1,3 +1,2 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -//# sourceMappingURL=smartstore.js.map \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 27bdca0..d38caa0 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -50,11 +50,11 @@ graph TB M[SFMobileSyncReactBridge.m] end - subgraph "Layer 3b: Android Native (Android repo)" - N[OauthReactBridge.kt] - O[NetReactBridge.kt] - P[SmartStoreReactBridge.kt] - Q[MobileSyncReactBridge.kt] + subgraph "Layer 3b: Android Native (this repo - android/)" + N[SFOauthReactBridge.kt] + O[SFNetReactBridge.kt] + P[SFSmartStoreReactBridge.kt] + Q[SFMobileSyncReactBridge.kt] end subgraph "iOS SDK Libraries" @@ -116,9 +116,9 @@ The public-facing API that React Native developers use. Written in TypeScript wi ### Layer 2: React Native Bridge -**Location**: React Native's `NativeModules` API +**Location**: React Native's TurboModuleRegistry / `NativeModules` API -The communication layer between JavaScript and native code. React Native provides this infrastructure. +The communication layer between JavaScript and native code. React Native provides this infrastructure. With the new architecture, modules are resolved via `TurboModuleRegistry.get()` with a `NativeModules` fallback. **Responsibilities**: - Serialize JavaScript arguments to JSON @@ -126,11 +126,13 @@ The communication layer between JavaScript and native code. React Native provide - Return results via callbacks/promises - Handle errors and exceptions -**Key APIs**: -- `NativeModules.SFOauthReactBridge` (iOS) / `SalesforceOauthReactBridge` (Android) -- `NativeModules.SFNetReactBridge` (iOS) / `SalesforceNetReactBridge` (Android) -- `NativeModules.SFSmartStoreReactBridge` (iOS) / `SmartStoreReactBridge` (Android) -- `NativeModules.SFMobileSyncReactBridge` (iOS) / `MobileSyncReactBridge` (Android) +**Key APIs** (unified module names on both platforms): +- `NativeModules.SFOauthReactBridge` +- `NativeModules.SFNetReactBridge` +- `NativeModules.SFSmartStoreReactBridge` +- `NativeModules.SFMobileSyncReactBridge` + +Module registration is handled by React Native autolinking (no manual `PackageList` registration needed). ### Layer 3a: iOS Native Bridge (This Repository) @@ -153,13 +155,21 @@ Objective-C modules that implement the React Native bridge protocol and call iOS - `ios/SalesforceReact/SFSDKReactLogger.{h,m}` - Logging utilities - `ios/SalesforceReact/SalesforceReactSDKManager.{h,m}` - SDK initialization -### Layer 3b: Android Native Bridge (Separate Repository) +### Layer 3b: Android Native Bridge (This Repository) + +**Location**: `android/` directory -**Location**: `SalesforceMobileSDK-Android/libs/SalesforceReact/` +Kotlin modules that implement React Native's TurboModule interface and call Android SDK libraries. -Java modules that implement React Native's bridge interface and call Android SDK libraries. (Per project standards new code should be Kotlin; existing bridge files are currently Java with a planned Kotlin migration.) +**Note**: As of SDK 14.0, the Android bridge code has moved from `SalesforceMobileSDK-Android/libs/SalesforceReact/` to this repository's `android/` directory. This mirrors the iOS bridge layout and enables React Native autolinking. -**Note**: This code lives in the Android repository, NOT this repository. +**Key Files**: +- `android/src/main/java/.../bridge/SFOauthReactBridge.kt` - OAuth bridge +- `android/src/main/java/.../bridge/SFNetReactBridge.kt` - REST API bridge +- `android/src/main/java/.../bridge/SFSmartStoreReactBridge.kt` - SmartStore bridge +- `android/src/main/java/.../bridge/SFMobileSyncReactBridge.kt` - MobileSync bridge +- `android/src/main/java/.../bridge/ReactBridgeHelper.kt` - Bridge utilities +- `android/src/main/java/.../ui/SalesforceReactActivity.kt` - Activity (bridgeless mode) ### Layer 4: Native SDK Libraries @@ -280,8 +290,8 @@ Each module follows the same pattern: graph LR A[JavaScript Module] --> B[react.force.common.exec] B --> C{Platform?} - C -->|iOS| D[SFxxxReactBridge.m] - C -->|Android| E[XxxReactBridge.kt] + C -->|iOS| D[SFxxxReactBridge.mm] + C -->|Android| E[SFxxxReactBridge.kt] D --> F[iOS SDK Class] E --> G[Android SDK Class] F --> H[Result] @@ -307,10 +317,8 @@ const exec = ( args: Record, ): void => { forceExec( - "SFOauthReactBridge", // iOS module name - "SalesforceOauthReactBridge", // Android module name - SFOauthReactBridge, // iOS NativeModule - SalesforceOauthReactBridge, // Android NativeModule + "SFOauthReactBridge", // Module name (same on both platforms) + SFOauthReactBridge, // NativeModule (resolved via TurboModuleRegistry) successCB, errorCB, methodName, @@ -409,29 +417,35 @@ sequenceDiagram While the JavaScript API is identical on both platforms, there are implementation differences: -### Module Names +### Module Names (Unified) + +As of SDK 14.0, both platforms use the same `SF*` prefix module names: | JavaScript | iOS Module | Android Module | |-----------|-----------|---------------| -| oauth | `SFOauthReactBridge` | `SalesforceOauthReactBridge` | -| net | `SFNetReactBridge` | `SalesforceNetReactBridge` | -| smartstore | `SFSmartStoreReactBridge` | `SmartStoreReactBridge` | -| mobilesync | `SFMobileSyncReactBridge` | `MobileSyncReactBridge` | +| oauth | `SFOauthReactBridge` | `SFOauthReactBridge` | +| net | `SFNetReactBridge` | `SFNetReactBridge` | +| smartstore | `SFSmartStoreReactBridge` | `SFSmartStoreReactBridge` | +| mobilesync | `SFMobileSyncReactBridge` | `SFMobileSyncReactBridge` | -### Callback Signature +### Callback Signature (Unified) -**iOS**: Single callback with `(error, result)` tuple +Both platforms now use the same single-callback pattern with `(error, result)`: + +**iOS** (Objective-C): ```objective-c callback(@[[NSNull null], result]); // success callback(@[error, [NSNull null]]); // error ``` -**Android**: Separate success and error callbacks -```java -successCallback.invoke(result.toString()); // success -errorCallback.invoke(error.getMessage()); // error +**Android** (Kotlin): +```kotlin +ReactBridgeHelper.invokeSuccess(callback, result) // invokes callback(null, resultString) +ReactBridgeHelper.invokeError(callback, error) // invokes callback(errorMessage) ``` +The JavaScript bridge function handles both platforms with one unified code path. + ### Data Serialization **iOS**: @@ -465,6 +479,7 @@ graph TB subgraph "This Repository: SalesforceMobileSDK-ReactNative" A[JavaScript/TypeScript API
src/] B[iOS Bridge
ios/SalesforceReact/] + BA[Android Bridge
android/] end subgraph "iOS Dependencies (CocoaPods)" @@ -474,10 +489,6 @@ graph TB F[MobileSync] end - subgraph "Android Repository" - G[Android Bridge
libs/SalesforceReact/] - end - subgraph "Android Dependencies (Gradle)" H[SalesforceSDK] I[SmartStore] @@ -494,18 +505,19 @@ graph TB end A --> B + A --> BA B --> C B --> D D --> E E --> F - A -.Android uses.-> G - G --> H + BA --> H H --> I I --> J A --> K B --> K + BA --> K K --> L K --> M @@ -574,7 +586,7 @@ pod install # Installs SalesforceReact and dependencies 3. Compiles Objective-C bridge code 4. Links into React Native app -### Android Build (via Gradle) +### Android Build (via Gradle + Autolinking) ```bash # In React Native app @@ -583,10 +595,10 @@ cd android ``` **Process**: -1. Gradle resolves `react-native-force` npm package -2. Finds Android bridge in separate Android repo (currently Java; planned Kotlin migration) -3. Compiles bridge code -4. Links Android SDK libraries from Maven Central +1. React Native autolinking discovers `react-native-force` and its `android/` source +2. Gradle compiles the Kotlin bridge code from this repo +3. Links Android SDK libraries from Maven Central +4. Pre-built C++ JavaTurboModule wrappers (committed to npm package) are used for codegen ## Module Registration @@ -613,20 +625,16 @@ RCT_EXPORT_METHOD(getAuthCredentials:(NSDictionary *)args ### Android Module Registration -Modules are registered via a `ReactPackage` (currently Java in `SalesforceMobileSDK-Android/libs/SalesforceReact/`): +Modules are registered via React Native autolinking. The `react-native-force` package declares its native modules in `react-native.config.js`, so no manual `PackageList` registration is needed in app code. -```java -public class SalesforceReactPackage implements ReactPackage { - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList( - new SalesforceOauthReactBridge(reactContext), - new SalesforceNetReactBridge(reactContext), - new SmartStoreReactBridge(reactContext), - new MobileSyncReactBridge(reactContext) - ); - } -} +Under the hood, autolinking generates a `ReactPackage` that creates the TurboModule instances: + +```kotlin +// Auto-generated by React Native autolinking +SFOauthReactBridge(reactContext) +SFNetReactBridge(reactContext) +SFSmartStoreReactBridge(reactContext) +SFMobileSyncReactBridge(reactContext) ``` ## Performance Considerations @@ -701,14 +709,15 @@ iOS Bridge → iOS SDK ↓ Test Results -Android Tests (JUnit runs JavaScript) +androidTests/ (AndroidX Test/JUnit runs JavaScript) ↓ Android Bridge → Android SDK ↓ Test Results ``` -See [ios-tests/README.md](ios-tests/README.md) for testing details. +See [ios-tests/README.md](ios-tests/README.md) for iOS testing details. +The `androidTests/` directory mirrors `iosTests/` for Android testing. ## Further Reading diff --git a/iosTests/ios/test_credentials.json b/iosTests/ios/test_credentials.json new file mode 120000 index 0000000..e1a199d --- /dev/null +++ b/iosTests/ios/test_credentials.json @@ -0,0 +1 @@ +../../shared/test/test_credentials.json \ No newline at end of file diff --git a/iosTests/package.json b/iosTests/package.json index 9cc3d6d..531553e 100644 --- a/iosTests/package.json +++ b/iosTests/package.json @@ -39,4 +39,4 @@ "engines": { "node": ">=20" } -} +} \ No newline at end of file diff --git a/iosTests/test_credentials.json b/iosTests/test_credentials.json new file mode 120000 index 0000000..58d8fd7 --- /dev/null +++ b/iosTests/test_credentials.json @@ -0,0 +1 @@ +../shared/test/test_credentials.json \ No newline at end of file diff --git a/package.json b/package.json index acac0e3..466ebec 100644 --- a/package.json +++ b/package.json @@ -46,12 +46,13 @@ "type": "modules", "jsSrcsDir": "src/specs", "android": { - "javaPackageName": "com.salesforce.androidsdk.reactnative.generated" + "javaPackageName": "com.salesforce.androidsdk.reactnative.codegen" } }, "scripts": { "build": "tsc --build", - "prepublish": "npm run build" + "prepublish": "npm run build", + "postinstall": "node -e \"var fs=require('fs'),p=require('path');var src=p.resolve(__dirname,'android','generated','source','codegen');var dstDir=p.resolve(__dirname,'android','build','generated','source');var dst=p.join(dstDir,'codegen');if(fs.existsSync(src)&&!fs.existsSync(dst)){fs.mkdirSync(dstDir,{recursive:true});fs.symlinkSync(p.relative(dstDir,src),dst,'dir');}\"" }, "peerDependencies": { "react-native": "0.81.5" @@ -66,7 +67,9 @@ "tsconfig.json", "src/**/*", "ios/SalesforceReact/**/*", - "SalesforceReact.podspec" + "SalesforceReact.podspec", + "android/**/*", + "react-native.config.js" ], "devDependencies": { "@babel/core": "^7.25.2", diff --git a/react-native.config.js b/react-native.config.js new file mode 100644 index 0000000..858b6c3 --- /dev/null +++ b/react-native.config.js @@ -0,0 +1,11 @@ +module.exports = { + dependency: { + platforms: { + android: { + sourceDir: './android', + packageImportPath: 'import com.salesforce.androidsdk.reactnative.app.SalesforceReactPackage;', + packageInstance: 'new SalesforceReactPackage()', + }, + }, + }, +}; diff --git a/setversion.sh b/setversion.sh index d8de731..351e90d 100755 --- a/setversion.sh +++ b/setversion.sh @@ -65,6 +65,7 @@ echo -e "${YELLOW}*** POINTING TO SDK TAG ${SDK_TAG} ***${NC}" echo "*** Updating package.json ***" update_package_json "./package.json" "${OPT_VERSION}" "${SDK_TAG}" update_package_json "./iosTests/package.json" "${OPT_VERSION}" "${SDK_TAG}" +update_package_json "./androidTests/package.json" "${OPT_VERSION}" "${SDK_TAG}" echo "*** Updating podspecs ***" update_podspec "./SalesforceReact.podspec" "${OPT_VERSION}" @@ -72,3 +73,9 @@ update_podspec "./SalesforceReact.podspec" "${OPT_VERSION}" echo "*** Updating dist ***" npm install npm run build + +echo "*** Updating Android codegen ***" +rm -rf android/generated/source/codegen +npx react-native codegen --path . --outputPath . --platform android +mv android/app/build/generated/source/codegen android/generated/source/codegen +rm -rf android/app diff --git a/shared/test/.gitkeep b/shared/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/react.force.common.ts b/src/react.force.common.ts index 36fda9b..5e0737b 100644 --- a/src/react.force.common.ts +++ b/src/react.force.common.ts @@ -27,21 +27,6 @@ import { sdkConsole } from "./react.force.log"; import { ModuleAndroidName, ModuleIOSName } from "./typings"; -/** - * Represents an iOS Module callback - */ -export type iOSModuleCallback = (err: Error, result: T) => void; - -/** - * Represents an Android Module success callback - */ -export type AndroidSuccessCallback = (result: string) => void; - -/** - * Represents an Android Module error callback - */ -export type AndroidErrorCallback = (err: string) => void; - /** * Represents an exec() success callback */ @@ -53,87 +38,53 @@ export type ExecSuccessCallback = (result: T) => void; export type ExecErrorCallback = (err: Error) => void; /** - * Represents a Mobile SDK iOS Module - * - * @interface ModuleIOS - * @template T - */ -interface ModuleIOS { - [key: string]: (args: unknown, callback: iOSModuleCallback) => void; -} - -/** - * Represents a Mobile SDK Android Module - * - * @interface ModuleAndroid + * Represents a native module with unified single-callback pattern. + * Both iOS and Android now use: module.method(args, (error, result) => {...}) */ -interface ModuleAndroid { - [key: string]: (args: unknown, successCB: AndroidSuccessCallback, errorCB: AndroidErrorCallback) => void; +interface NativeModule { + [key: string]: (args: unknown, callback: (error: any, result: any) => void) => void; } /** - * Executes an action using the React Native Mobile SDK Bridge - * - * @template T - * @param {ModuleIOSName} moduleIOSName - * @param {ModuleAndroidName} moduleAndroidName - * @param {ModuleIOS} moduleIOS - * @param {ModuleAndroid} moduleAndroid - * @param {ExecSuccessCallback | null} successCB - * @param {ExecErrorCallback | null} errorCB - * @param {string} methodName - * @param {Record} args + * Executes an action using the React Native Mobile SDK Bridge. + * Both iOS and Android use a unified single-callback pattern: + * callback(error, result) where error is null on success. */ export const exec = ( moduleIOSName: ModuleIOSName, moduleAndroidName: ModuleAndroidName, - moduleIOS: ModuleIOS, - moduleAndroid: ModuleAndroid, + moduleIOS: NativeModule, + moduleAndroid: NativeModule, successCB: ExecSuccessCallback | null, errorCB: ExecErrorCallback | null, methodName: string, args: Record, ): void => { - if (moduleIOS) { - const func = `${moduleIOSName}.${methodName}`; - sdkConsole.debug(`${func} called: ${JSON.stringify(args)}`); + const module = moduleIOS ?? moduleAndroid; + const moduleName = moduleIOS ? moduleIOSName : moduleAndroidName; - moduleIOS[methodName](args, (error: Error, result) => { - if (error) { - sdkConsole.error(`${func} failed: ${JSON.stringify(error)}`); - if (errorCB) errorCB(error); - } else { - sdkConsole.debug(`${func} succeeded`); - if (successCB) successCB(result); - } - }); - } - // android - else if (moduleAndroid) { - const func = `${moduleAndroidName}.${methodName}`; - sdkConsole.debug(`${func} called: ${JSON.stringify(args)}`); - moduleAndroid[methodName]( - args, - (result) => { - sdkConsole.debug(`${func} succeeded`); - if (successCB) { - successCB(safeJSONparse(result)); - } - }, - (error) => { - sdkConsole.error(`${func} failed: ${JSON.stringify(error)}`); - if (errorCB) errorCB(safeJSONparse(error)); - }, - ); + if (!module) { + sdkConsole.error(`No native module found for ${moduleIOSName}/${moduleAndroidName}`); + if (errorCB) errorCB(new Error("Native module not available")); + return; } + + const func = `${moduleName}.${methodName}`; + sdkConsole.debug(`${func} called: ${JSON.stringify(args)}`); + + module[methodName](args, (error: any, result: any) => { + if (error) { + sdkConsole.error(`${func} failed: ${JSON.stringify(error)}`); + if (errorCB) errorCB(typeof error === "string" ? safeJSONparse(error) : error); + } else { + sdkConsole.debug(`${func} succeeded`); + if (successCB) successCB(typeof result === "string" ? safeJSONparse(result) : result); + } + }); }; /** - * Returns a parsed JSON Android result - * - * @template T - * @param {string} str - * @returns {T} + * Parses a JSON string safely, returning the original value if parsing fails. */ export const safeJSONparse = (str: string): T => { try { diff --git a/src/react.force.mobilesync.ts b/src/react.force.mobilesync.ts index 882c55b..5ed0566 100644 --- a/src/react.force.mobilesync.ts +++ b/src/react.force.mobilesync.ts @@ -29,9 +29,10 @@ import { StoreConfig } from "./react.force.smartstore"; import { SyncDownTarget, SyncEvent, SyncMethod, SyncOptions, SyncStatus, SyncUpTarget } from "./typings/mobilesync"; // New architecture: TurboModuleRegistry first, fall back to NativeModules. -const SFMobileSyncReactBridge = +// Lazy lookup - bridgeless mode doesn't have modules ready at import time. +const getSFMobileSyncReactBridge = () => TurboModuleRegistry.get("SFMobileSyncReactBridge") ?? NativeModules.SFMobileSyncReactBridge; -const MobileSyncReactBridge = +const getMobileSyncReactBridge = () => TurboModuleRegistry.get("MobileSyncReactBridge") ?? NativeModules.MobileSyncReactBridge; // If param is a storeconfig return the same storeconfig @@ -60,8 +61,8 @@ const exec = ( forceExec( "SFMobileSyncReactBridge", "MobileSyncReactBridge", - SFMobileSyncReactBridge, - MobileSyncReactBridge, + getSFMobileSyncReactBridge(), + getMobileSyncReactBridge(), successCB, errorCB, methodName, diff --git a/src/react.force.net.ts b/src/react.force.net.ts index 37d05da..2e8f1c2 100644 --- a/src/react.force.net.ts +++ b/src/react.force.net.ts @@ -29,9 +29,10 @@ import { exec as forceExec, ExecErrorCallback, ExecSuccessCallback } from "./rea import { HttpMethod } from "./typings"; // New architecture: TurboModuleRegistry first, fall back to NativeModules. -const SFNetReactBridge = +// Lazy lookup - bridgeless mode doesn't have modules ready at import time. +const getSFNetReactBridge = () => TurboModuleRegistry.get("SFNetReactBridge") ?? NativeModules.SFNetReactBridge; -const SalesforceNetReactBridge = +const getSalesforceNetReactBridge = () => TurboModuleRegistry.get("SalesforceNetReactBridge") ?? NativeModules.SalesforceNetReactBridge; @@ -83,8 +84,8 @@ export const sendRequest = ( forceExec( "SFNetReactBridge", "SalesforceNetReactBridge", - SFNetReactBridge, - SalesforceNetReactBridge, + getSFNetReactBridge(), + getSalesforceNetReactBridge(), successCB, errorCB, "sendRequest", diff --git a/src/react.force.oauth.ts b/src/react.force.oauth.ts index ecc3b54..ad50af7 100644 --- a/src/react.force.oauth.ts +++ b/src/react.force.oauth.ts @@ -28,11 +28,10 @@ import { NativeModules, TurboModuleRegistry } from "react-native"; import { exec as forceExec, ExecSuccessCallback, ExecErrorCallback } from "./react.force.common"; import { OAuthMethod, UserAccount } from "./typings/oauth"; -// New architecture: TurboModuleRegistry returns the module if registered as a -// TurboModule. Falls back to NativeModules (legacy bridge / interop mode). -const SFOauthReactBridge = +// Lazy module lookup: modules may not be available at import time in bridgeless mode. +const getSFOauthReactBridge = () => TurboModuleRegistry.get("SFOauthReactBridge") ?? NativeModules.SFOauthReactBridge; -const SalesforceOauthReactBridge = +const getSalesforceOauthReactBridge = () => TurboModuleRegistry.get("SalesforceOauthReactBridge") ?? NativeModules.SalesforceOauthReactBridge; @@ -45,8 +44,8 @@ const exec = ( forceExec( "SFOauthReactBridge", "SalesforceOauthReactBridge", - SFOauthReactBridge, - SalesforceOauthReactBridge, + getSFOauthReactBridge(), + getSalesforceOauthReactBridge(), successCB, errorCB, methodName, diff --git a/src/react.force.smartstore.ts b/src/react.force.smartstore.ts index 31069f8..3f45bd4 100644 --- a/src/react.force.smartstore.ts +++ b/src/react.force.smartstore.ts @@ -30,9 +30,10 @@ import { QuerySpecType, StoreOrder } from "./typings"; import { SmartStoreMethod } from "./typings/smartstore"; // New architecture: TurboModuleRegistry first, fall back to NativeModules. -const SFSmartStoreReactBridge = +// Lazy lookup - bridgeless mode doesn't have modules ready at import time. +const getSFSmartStoreReactBridge = () => TurboModuleRegistry.get("SFSmartStoreReactBridge") ?? NativeModules.SFSmartStoreReactBridge; -const SmartStoreReactBridge = +const getSmartStoreReactBridge = () => TurboModuleRegistry.get("SmartStoreReactBridge") ?? NativeModules.SmartStoreReactBridge; const exec = ( @@ -44,8 +45,8 @@ const exec = ( forceExec( "SFSmartStoreReactBridge", "SmartStoreReactBridge", - SFSmartStoreReactBridge, - SmartStoreReactBridge, + getSFSmartStoreReactBridge(), + getSmartStoreReactBridge(), successCB, errorCB, methodName,