diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md new file mode 100644 index 0000000..121d736 --- /dev/null +++ b/MIGRATION_GUIDE.md @@ -0,0 +1,228 @@ +# Migration Guide from 2.x.x to 3.0.0 + +Mindbox SDK 3.0 adds support for React Native New Architecture and no longer supports the old React Native architecture. + +This guide describes the required changes when migrating from Mindbox SDK 2.x.x to 3.x.x +## Requirements + +- React Native `>=0.76.0` +- React `>=18.0.0` +- React Native New Architecture enabled +- Android min SDK `24` +- iOS `15.1` + +## Breaking Changes + +### React Native + +- The SDK now requires React Native New Architecture. +- The minimum supported React Native version has been raised to `>=0.76.0`. +- The minimum supported React version has been raised to `>=18.0.0`. +- The SDK now uses TurboModule/codegen integration through `NativeMindboxSdk`. +- Deprecated JS APIs have been removed: + - `getToken` + - `updateToken` + - `updateNotificationPermissionStatus` + +### Android + +- The SDK no longer supports the old React Native architecture. If `newArchEnabled=false`, the build fails. +- `MindboxJsDelivery` is now a Kotlin `object`. +- `MindboxJsDelivery.Shared.getInstance(context)` has been removed. +- Client integrations that pass `Context` or `ReactContext` to `MindboxJsDelivery` must be migrated. +- The minimum Android SDK version has been raised from `21` to `24`. + +### iOS + +- The SDK no longer supports the old React Native architecture. If `RCT_NEW_ARCH_ENABLED != 1`, the build fails. +- The minimum iOS version has been raised from `12.0` to `15.1`. +- Manual calls to `MindboxJsDelivery` from client code are no longer required. + +## React Native API Migration + +### `getToken` + +`getToken` has been removed. Use `getTokens` instead. The method returns push tokens collected by the SDK. + +Before: + +```typescript +MindboxSdk.getToken((token: string) => { + // Use FCM/APNS token +}) +``` + +After: + +```typescript +MindboxSdk.getTokens((tokens: string) => { + // Use push tokens returned by the SDK +}) +``` + +### `updateToken` + +`updateToken` has been removed. Use native Mindbox SDK methods to pass push tokens. + +Android: + +```kotlin + +Mindbox.updatePushToken(context, token) +``` + +iOS: + +```swift +Mindbox.shared.apnsTokenUpdate(deviceToken: deviceToken) +``` + +### `updateNotificationPermissionStatus` + +`updateNotificationPermissionStatus` has been removed. Use `refreshNotificationPermissionStatus` instead. + +Before: + +```typescript +MindboxSdk.updateNotificationPermissionStatus(granted) +``` + +After: + +```typescript +MindboxSdk.refreshNotificationPermissionStatus() +``` + +## Android Migration + +Choose one of the options below. Option 1 is recommended. + +### Option 1. Simplified Integration With Auto Init + +Add the following metadata to the `application` block in `android/app/src/main/AndroidManifest.xml`: + +```xml + +``` + +Remove Mindbox client integration code from `MainActivity` and `MainApplication`. + +In particular, remove manual code that: + +- initializes or stores `MindboxJsDelivery`; +- calls `MindboxJsDelivery.Shared.getInstance(context)`; +- passes `ReactContext` to Mindbox push-click handling; +- manually calls `Mindbox.initPushServices(...)` only for React Native lifecycle integration. + +After this change, the SDK handles React Native push-click delivery internally. + +### Option 2. Simplified Integration Without AndroidManifest Changes + +If you do not want to add `AUTO_INIT_ENABLED` to `AndroidManifest.xml`, remove Mindbox-specific code from `MainActivity` and update `MainApplication`. + +Before: + +```kotlin +Mindbox.initPushServices( + this, + listOf(MindboxFirebase, MindboxHuawei, MindboxRuStore) +) +``` + +After: + +```kotlin +import com.mindboxsdk.initPushServicesForReactNative + +Mindbox.initPushServicesForReactNative( + this, + listOf(MindboxFirebase, MindboxHuawei, MindboxRuStore) +) +``` + +This initializes push services and registers the React Native lifecycle listener required by the SDK. + +### Option 3. Manual MainActivity Migration + +If you need to keep manual push-click handling in `MainActivity`, update the integration to the new `MindboxJsDelivery` API. + +Before: + +```kotlin +private var jsDelivery: MindboxJsDelivery? = null + +private fun initializeAndSendIntent(context: ReactContext) { + jsDelivery = MindboxJsDelivery.Shared.getInstance(context) + jsDelivery?.sendPushClicked(intent) +} +``` + +After: + +```kotlin +class MainActivity : ReactActivity() { + private fun handlePushIntent(intent: Intent) { + Mindbox.onNewIntent(intent) + Mindbox.onPushClicked(applicationContext, intent) + MindboxJsDelivery.sendPushClicked(intent) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + handlePushIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handlePushIntent(intent) + } +} +``` + +## iOS Migration + +Existing iOS integrations can continue to work after enabling React Native New Architecture and updating the minimum iOS version. However, the recommended approach is to simplify `AppDelegate` and let the SDK handle notification delivery. + +### Recommended Simplified Integration + +Keep only the Mindbox-related code shown below in `AppDelegate`. + +All other Mindbox code in `AppDelegate` can be removed, including direct calls to `MindboxJsDelivery`. + +Add SDK configuration during application startup: + +```swift +@main +class AppDelegate: RCTAppDelegate, UNUserNotificationCenterDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + .... + .... + // Set the notification center delegate before configuring Mindbox. + // MindboxApp.configure reads the current delegate during setup. + UNUserNotificationCenter.current().delegate = self + MindboxApp.configure(launchOptions: launchOptions) + .... + } +} +``` + +Warning: if your app calls `MindboxJsDelivery` or `MindboxJSDelivery` directly, remove that code. Manual calls are no longer required. Push-click and in-app event delivery are handled by the SDK. + +## Migration Checklist + +- Enable React Native New Architecture. +- Update React Native to `>=0.76.0`. +- Update React to `>=18.0.0`. +- Update Android min SDK to `24`. +- Update iOS deployment target to `15.1`. +- Remove usages of `getToken`. +- Replace `getToken` with `getTokens`. +- Remove usages of `updateToken`. +- Replace `updateNotificationPermissionStatus` with `refreshNotificationPermissionStatus`. +- Migrate Android push-click integration to one of the supported options. +- Remove direct `MindboxJsDelivery` calls from iOS client code. diff --git a/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt b/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt index e4bd954..3b94682 100644 --- a/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt +++ b/android/src/main/java/com/mindboxsdk/MindboxSdkModule.kt @@ -98,6 +98,11 @@ class MindboxSdkModule( payload.optString("previousUuid", "") ) } + if (payload.has("operationsDomain")) { + configurationBuilder.operationsDomain( + payload.optString("operationsDomain", "") + ) + } val configuration = configurationBuilder.build() val handler = Handler(context.mainLooper) handler.post { diff --git a/ios/MindboxSdkImpl.swift b/ios/MindboxSdkImpl.swift index 341821a..c712d03 100644 --- a/ios/MindboxSdkImpl.swift +++ b/ios/MindboxSdkImpl.swift @@ -9,6 +9,7 @@ struct PayloadData: Codable { var shouldCreateCustomer: Bool? var previousInstallId: String? var previousUuid: String? + var operationsDomain: String? } public typealias ResolveBlock = (Any?) -> Void @@ -48,6 +49,7 @@ public final class MindboxSdkImpl: NSObject { let configuration = try MBConfiguration( endpoint: payload.endpointId, domain: payload.domain, + operationsDomain: payload.operationsDomain, previousInstallationId: payload.previousInstallId, previousDeviceUUID: payload.previousUuid, subscribeCustomerIfCreated: payload.subscribeCustomerIfCreated ?? false, diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 1f98bf4..77451d4 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -378,6 +378,54 @@ describe('Testing Mindbox RN SDK', () => { expect(MindboxSdk.initialized).toBeTruthy() }) + it('initialize passes operationsDomain to native when provided', async () => { + const { + NativeModules: { MindboxSdk: MindboxSdkNative }, + } = require('react-native') + const MindboxSdk = require('../index').default + + expect.assertions(1) + + await MindboxSdk.initialize({ + ...initializationData, + operationsDomain: 'anonymizer.example.com', + }) + + const calledWith = (MindboxSdkNative.initialize as jest.Mock).mock.calls.slice(-1)[0][0] + expect(JSON.parse(calledWith)).toMatchObject({ operationsDomain: 'anonymizer.example.com' }) + }) + + it('initialize does not include operationsDomain in payload when not provided', async () => { + const { + NativeModules: { MindboxSdk: MindboxSdkNative }, + } = require('react-native') + const MindboxSdk = require('../index').default + + expect.assertions(1) + + await MindboxSdk.initialize(initializationData) + + const calledWith = (MindboxSdkNative.initialize as jest.Mock).mock.calls.slice(-1)[0][0] + expect(JSON.parse(calledWith)).not.toHaveProperty('operationsDomain') + }) + + it('initialize does not include operationsDomain in payload when passed as empty string', async () => { + const { + NativeModules: { MindboxSdk: MindboxSdkNative }, + } = require('react-native') + const MindboxSdk = require('../index').default + + expect.assertions(1) + + await MindboxSdk.initialize({ + ...initializationData, + operationsDomain: '', + }) + + const calledWith = (MindboxSdkNative.initialize as jest.Mock).mock.calls.slice(-1)[0][0] + expect(JSON.parse(calledWith)).not.toHaveProperty('operationsDomain') + }) + it('getDeviceUUID method works correctly', async () => { const MindboxSdk = require('../index').default diff --git a/src/index.tsx b/src/index.tsx index bb60785..3ede474 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -89,6 +89,7 @@ class MindboxSdkClass { * shouldCreateCustomer: true, * previousInstallId: '', * previousUuid: '', + * operationsDomain: 'anonymizer.example.com', * }); */ public async initialize(initializationData: InitializationData) { @@ -104,7 +105,7 @@ class MindboxSdkClass { throw new Error('Wrong initialization data!') } - const { domain, endpointId, subscribeCustomerIfCreated, shouldCreateCustomer, previousInstallId, previousUuid } = initializationData + const { domain, endpointId, subscribeCustomerIfCreated, shouldCreateCustomer, previousInstallId, previousUuid, operationsDomain } = initializationData if (!domain || !endpointId) { this._initializing = false @@ -132,6 +133,10 @@ class MindboxSdkClass { payload.previousUuid = previousUuid } + if (typeof operationsDomain !== 'undefined' && operationsDomain.length > 0) { + payload.operationsDomain = operationsDomain + } + try { const payloadString = JSON.stringify(payload) this._initialized = await MindboxSdkNative.initialize(payloadString) @@ -356,19 +361,6 @@ class MindboxSdkClass { return MindboxSdkNative.pushDelivered(uniqKey) } - /** - * This method is kept for backward compatibility. The `granted` argument is ignored. - * The SDK reads the current system authorization status and, if it differs - * from the last known value, sends an update to the backend. - * - * @param granted current permission status - * @deprecated Use `refreshNotificationPermissionStatus()` instead. - */ - public updateNotificationPermissionStatus(granted: Boolean) { - console.warn(`updateNotificationPermissionStatus(granted=${String(granted)}) is deprecated. Use refreshNotificationPermissionStatus instead.`) - return MindboxSdkNative.refreshNotificationPermissionStatus() - } - /** * Checks the current system authorization status for push notifications * and reports any changes to Mindbox. diff --git a/src/types/InitializationData.ts b/src/types/InitializationData.ts index 45de9cb..260a901 100644 --- a/src/types/InitializationData.ts +++ b/src/types/InitializationData.ts @@ -5,4 +5,5 @@ export type InitializationData = { shouldCreateCustomer?: boolean previousInstallId?: string previousUuid?: string + operationsDomain?: string }