From d163bc7c8187b8054fb0fa336b7745d78e5f7c06 Mon Sep 17 00:00:00 2001 From: tafilovic Date: Wed, 2 Sep 2026 01:42:45 +0200 Subject: [PATCH 1/2] Mapp.engage function is now avaitable and should be called with: await Mapp.engage() --- CHANGELOG.md | 1 + MIGRATION_2.0.md | 2 +- Mapp.js | 2 +- README.md | 6 +- __tests__/MappBridge.test.js | 12 ++-- __tests__/MappLogic.test.js | 4 +- android/.classpath | 2 +- .../org.eclipse.buildship.core.prefs | 2 +- .../MappEngagementDispatcher.java | 40 +++++++++-- .../NativeRNMappPluginModuleSpec.java | 2 +- .../com/reactlibrary/RNMappPluginModule.java | 41 ++++++++--- .../src/main/jni/RNMappPlugin-generated.cpp | 2 +- .../components/RNMappPlugin/RNMappPluginJSI.h | 4 +- .../RNMappPluginModuleApiSignatureTest.java | 2 +- .../reactlibrary/RNMappPluginModuleTest.java | 70 +++++++++++++++---- ios/RNMappPluginModule.mm | 24 ++++++- package.json | 2 +- specs/NativeRNMappPluginModule.js | 2 +- 18 files changed, 172 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00eef14..5feb1e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ***Bug Fixes*** +- `Mapp.engage(...)` is now awaitable so singleton-dependent calls can safely run after native initialization; engagement failures reject instead of being logged silently. - Android: Failed Firebase token registration now rejects with `FCM_REGISTRATION_FAILED` instead of crashing while reading a failed task result. - Android: All Mapp engage calls run on the main looper. Background Firebase callbacks wait for a bounded engage attempt and safely return failure after SDK errors, timeout, or interruption. - Android/Expo: Mapp and custom push ownership now remove the SDK v7 Firebase service and remain idempotent when prebuild runs repeatedly or changes mode. diff --git a/MIGRATION_2.0.md b/MIGRATION_2.0.md index a887e33..41f7bdf 100644 --- a/MIGRATION_2.0.md +++ b/MIGRATION_2.0.md @@ -31,7 +31,7 @@ Read [Breaking changes in 2.0.0](BREAKING_CHANGES.md) first to determine which c | `Mapp.inAppMarkAsRead(templateId, eventId)` | Android no-op. | Android fetches the Mapp Engage 7.1.2 inbox message and updates it to `READ`. | | `Mapp.inAppMarkAsUnRead(templateId, eventId)` | Android no-op. | Android fetches the inbox message and updates it to `UNREAD`. | | `Mapp.inAppMarkAsDeleted(templateId, eventId)` | Android no-op. | Android fetches the inbox message and updates it to `DELETED`. | -| `Mapp.engage(...)` | iOS JavaScript called the private native `autoengage` and `engageInapp` methods separately. | All platforms use the public native `engage` entry point. On iOS it still initializes both push and in-app, using `AppoxeeConfig.plist` as the credential source of truth. | +| `Mapp.engage(...)` | iOS JavaScript called the private native `autoengage` and `engageInapp` methods separately, and Android engagement returned before its main-thread task completed. | All platforms use an awaitable native engagement entry point. Await it before calling singleton-dependent APIs. On iOS it still initializes both push and in-app, using `AppoxeeConfig.plist` as the credential source of truth. | | iOS event listeners | Events emitted before a JavaScript listener was attached were dropped. | Up to 50 cold-start events are buffered and delivered after a listener attaches. Consumers should tolerate receiving an initial queued event. | For the Android inbox methods, `eventId` remains accepted for source compatibility but Mapp Engage 7.1.2 identifies and fetches the message using `templateId`. diff --git a/Mapp.js b/Mapp.js index 73162b1..49e6f6b 100755 --- a/Mapp.js +++ b/Mapp.js @@ -127,7 +127,7 @@ export class Mapp { server: string, appID: string, tenantID: string - ) { + ): Promise { return RNMappPluginModule.engage( sdkKey, googleProjectId, diff --git a/README.md b/README.md index 4952536..a04d45a 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,11 @@ const subscription = events.addListener('com.mapp.deep_link_received', event => // Route the deep link. }); -Mapp.engage('ANDROID_SDK_KEY', 'FCM_PROJECT_ID', 'EMC', 'APP_ID', 'TENANT_ID'); +await Mapp.engage('ANDROID_SDK_KEY', 'FCM_PROJECT_ID', 'EMC', 'APP_ID', 'TENANT_ID'); ``` +Always await `Mapp.engage(...)` before calling APIs that use the native Mapp singleton. The promise resolves after native engagement and bridge setup complete; use `Mapp.onInitCompletedListener()` or `Mapp.isReady()` when a feature specifically requires the SDK's later ready state. + ### Android push ownership `pushHandling: "mapp"` is the default. It requires `expo.android.googleServicesFile` and retains `com.reactlibrary.MessageService` as the sole normal-priority Mapp FCM callback owner. The config plugin removes the Mapp SDK v7 service (`com.appoxee.shared.MappMessagingService`) from the merged app manifest. @@ -160,7 +162,7 @@ Basic usage: ```js import { Mapp } from 'react-native-mapp-plugin'; -Mapp.engage('SDK_KEY', 'FCM_PROJECT_ID', 'EMC', 'APP_ID', 'TENANT_ID'); +await Mapp.engage('SDK_KEY', 'FCM_PROJECT_ID', 'EMC', 'APP_ID', 'TENANT_ID'); ``` See the [Mapp integration documentation](https://mapp-wiki.atlassian.net/wiki/spaces/MIC/pages/1154875400/React+Native+Integration+for+Mapp+Cloud) for the full JavaScript API and native Mapp configuration values. diff --git a/__tests__/MappBridge.test.js b/__tests__/MappBridge.test.js index 13aa72b..042b011 100644 --- a/__tests__/MappBridge.test.js +++ b/__tests__/MappBridge.test.js @@ -105,8 +105,10 @@ describe("getAlias", () => { // --------------------------------------------------------------------------- describe("engage (Android)", () => { - test("passes all 5 params to native engage", () => { - Mapp.engage("sdkKey", "projectId", "L3", "appId", "tenantId"); + test("returns the awaitable native engagement and passes all 5 params", async () => { + native.engage.mockResolvedValueOnce(true); + await expect(Mapp.engage("sdkKey", "projectId", "L3", "appId", "tenantId")) + .resolves.toBe(true); expect(native.engage).toHaveBeenCalledWith( "sdkKey", "projectId", "L3", "appId", "tenantId" ); @@ -118,8 +120,10 @@ describe("engage (Android)", () => { describe("engage (iOS)", () => { beforeEach(() => { platform.OS = "ios"; }); - test("uses the generated TurboModule engage method; native iOS reads credentials from the generated plist", () => { - Mapp.engage("sdkKey", "projectId", "L3", "appId", "tenantId"); + test("uses the awaitable TurboModule method; native iOS reads credentials from the generated plist", async () => { + native.engage.mockResolvedValueOnce(true); + await expect(Mapp.engage("sdkKey", "projectId", "L3", "appId", "tenantId")) + .resolves.toBe(true); expect(native.engage).toHaveBeenCalledWith("sdkKey", "projectId", "L3", "appId", "tenantId"); expect(native.autoengage).not.toHaveBeenCalled(); expect(native.engageInapp).not.toHaveBeenCalled(); diff --git a/__tests__/MappLogic.test.js b/__tests__/MappLogic.test.js index 80b3c11..7db972f 100644 --- a/__tests__/MappLogic.test.js +++ b/__tests__/MappLogic.test.js @@ -85,13 +85,13 @@ describe("convertEventEnum", () => { // --------------------------------------------------------------------------- describe("Mapp.js platform dispatch", () => { - test("engage() uses the generated cross-platform TurboModule method", () => { + test("engage() uses the generated awaitable cross-platform TurboModule method", () => { expect(mappSource).toMatch(/RNMappPluginModule\.engage\(/); expect(mappSource).not.toMatch(/RNMappPluginModule\.autoengage/); expect(mappSource).not.toMatch(/RNMappPluginModule\.engageInapp/); }); - test("engage() calls RNMappPluginModule.engage on Android path", () => { + test("engage() calls RNMappPluginModule.engage", () => { expect(mappSource).toMatch(/RNMappPluginModule\.engage\s*\(/); }); diff --git a/android/.classpath b/android/.classpath index bbe97e5..0a3280e 100644 --- a/android/.classpath +++ b/android/.classpath @@ -1,6 +1,6 @@ - + diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index 0ee1919..7a99f69 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -5,7 +5,7 @@ connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= -java.home=/Users/semsudin.tafilovic/.sdkman/candidates/java/11.0.26-tem +java.home=/Users/semsudin.tafilovic/.sdkman/candidates/java/21.0.11-tem jvm.arguments= offline.mode=false override.workspace.settings=true diff --git a/android/src/main/java/com/reactlibrary/MappEngagementDispatcher.java b/android/src/main/java/com/reactlibrary/MappEngagementDispatcher.java index cda4a85..b8e21ee 100644 --- a/android/src/main/java/com/reactlibrary/MappEngagementDispatcher.java +++ b/android/src/main/java/com/reactlibrary/MappEngagementDispatcher.java @@ -22,14 +22,40 @@ final class MappEngagementDispatcher { private MappEngagementDispatcher() {} + interface EngagementCallback { + void onSuccess(); + void onFailure(@NonNull Exception error); + } + static void engageAsync( @NonNull Application application, @Nullable AppoxeeOptions options, @Nullable Runnable afterEngage + ) { + engageAsync(application, options, afterEngage, null); + } + + static void engageAsync( + @NonNull Application application, + @Nullable AppoxeeOptions options, + @Nullable Runnable afterEngage, + @Nullable EngagementCallback callback ) { Runnable operation = () -> { - if (engageNow(application, options) && afterEngage != null) { - afterEngage.run(); + try { + Appoxee.engage(application, options); + if (afterEngage != null) { + afterEngage.run(); + } + } catch (Exception error) { + Log.e(TAG, "Mapp initialization failed", error); + if (callback != null) { + callback.onFailure(error); + } + return; + } + if (callback != null) { + callback.onSuccess(); } }; if (Looper.myLooper() == Looper.getMainLooper()) { @@ -37,7 +63,13 @@ static void engageAsync( return; } if (!new Handler(Looper.getMainLooper()).post(operation)) { - Log.e(TAG, "Unable to post Mapp initialization to the main looper"); + IllegalStateException error = new IllegalStateException( + "Unable to post Mapp initialization to the main looper" + ); + Log.e(TAG, error.getMessage(), error); + if (callback != null) { + callback.onFailure(error); + } } } @@ -94,7 +126,7 @@ private static boolean engageNow( try { Appoxee.engage(application, options); return true; - } catch (RuntimeException error) { + } catch (Exception error) { Log.e(TAG, "Mapp initialization failed", error); return false; } diff --git a/android/src/main/java/com/reactlibrary/NativeRNMappPluginModuleSpec.java b/android/src/main/java/com/reactlibrary/NativeRNMappPluginModuleSpec.java index 8c25e28..0caff07 100644 --- a/android/src/main/java/com/reactlibrary/NativeRNMappPluginModuleSpec.java +++ b/android/src/main/java/com/reactlibrary/NativeRNMappPluginModuleSpec.java @@ -76,7 +76,7 @@ public NativeRNMappPluginModuleSpec(ReactApplicationContext reactContext) { @ReactMethod @DoNotStrip - public abstract void engage(String sdkKey, String googleProjectId, String server, String appID, String tenantID); + public abstract void engage(String sdkKey, String googleProjectId, String server, String appID, String tenantID, Promise promise); @ReactMethod @DoNotStrip diff --git a/android/src/main/java/com/reactlibrary/RNMappPluginModule.java b/android/src/main/java/com/reactlibrary/RNMappPluginModule.java index 2986a8d..1a3b3f6 100644 --- a/android/src/main/java/com/reactlibrary/RNMappPluginModule.java +++ b/android/src/main/java/com/reactlibrary/RNMappPluginModule.java @@ -384,19 +384,42 @@ public void engage2() { } @ReactMethod - public void engage(String sdkKey, String googleProjectId, String server, String appID, String tenantID) { - AppoxeeOptions opt = createOptions(server, sdkKey, appID, tenantID); - opt.setNotificationMode(NotificationMode.BACKGROUND_AND_FOREGROUND); + public void engage(String sdkKey, String googleProjectId, String server, String appID, + String tenantID, Promise promise) { + final AppoxeeOptions opt; + try { + opt = createOptions(server, sdkKey, appID, tenantID); + opt.setNotificationMode(NotificationMode.BACKGROUND_AND_FOREGROUND); + } catch (RuntimeException error) { + promise.reject("MAPP_ENGAGE_INVALID_CONFIGURATION", error.getMessage(), error); + return; + } - MappEngagementDispatcher.engageAsync(Objects.requireNonNull(application), opt, () -> { - Appoxee.instance().subscribe(new AppoxeeObserver() { - @Override - public void onReadyStatusChanged(boolean status, MappResult result) { + MappEngagementDispatcher.engageAsync( + Objects.requireNonNull(application), + opt, + this::configureAfterEngage, + new MappEngagementDispatcher.EngagementCallback() { + @Override + public void onSuccess() { + promise.resolve(true); + } + + @Override + public void onFailure(@NonNull Exception error) { + promise.reject("MAPP_ENGAGE_FAILED", "Mapp initialization failed", error); + } } - }); + ); + } - Appoxee.instance().setPushBroadcast(MyPushBroadcastReceiver.class); + private void configureAfterEngage() { + Appoxee.instance().subscribe(new AppoxeeObserver() { + @Override + public void onReadyStatusChanged(boolean status, MappResult result) { + } }); + Appoxee.instance().setPushBroadcast(MyPushBroadcastReceiver.class); } @ReactMethod diff --git a/android/src/main/jni/RNMappPlugin-generated.cpp b/android/src/main/jni/RNMappPlugin-generated.cpp index 616b9d3..9262bfb 100644 --- a/android/src/main/jni/RNMappPlugin-generated.cpp +++ b/android/src/main/jni/RNMappPlugin-generated.cpp @@ -64,7 +64,7 @@ static facebook::jsi::Value __hostFunction_NativeRNMappPluginModuleSpecJSI_engag static facebook::jsi::Value __hostFunction_NativeRNMappPluginModuleSpecJSI_engage(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, "engage", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", args, count, cachedMethodId); + return static_cast(turboModule).invokeJavaMethod(rt, PromiseKind, "engage", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId); } static facebook::jsi::Value __hostFunction_NativeRNMappPluginModuleSpecJSI_engageTestServer(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) { diff --git a/android/src/main/jni/react/renderer/components/RNMappPlugin/RNMappPluginJSI.h b/android/src/main/jni/react/renderer/components/RNMappPlugin/RNMappPluginJSI.h index f6b84e8..f40fccc 100644 --- a/android/src/main/jni/react/renderer/components/RNMappPlugin/RNMappPluginJSI.h +++ b/android/src/main/jni/react/renderer/components/RNMappPlugin/RNMappPluginJSI.h @@ -154,12 +154,12 @@ class JSI_EXPORT NativeRNMappPluginModuleCxxSpec : public TurboModule { static_assert( bridging::getParameterCount(&T::engage) == 6, "Expected engage(...) to have 6 parameters"); - bridging::callFromJs(rt, &T::engage, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule), + return bridging::callFromJs(rt, &T::engage, static_cast(&turboModule)->jsInvoker_, static_cast(&turboModule), count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt), count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt), count <= 2 ? throw jsi::JSError(rt, "Expected argument in position 2 to be passed") : args[2].asString(rt), count <= 3 ? throw jsi::JSError(rt, "Expected argument in position 3 to be passed") : args[3].asString(rt), - count <= 4 ? throw jsi::JSError(rt, "Expected argument in position 4 to be passed") : args[4].asString(rt));return jsi::Value::undefined(); + count <= 4 ? throw jsi::JSError(rt, "Expected argument in position 4 to be passed") : args[4].asString(rt)); } static jsi::Value __engageTestServer(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { diff --git a/android/src/test/java/com/reactlibrary/RNMappPluginModuleApiSignatureTest.java b/android/src/test/java/com/reactlibrary/RNMappPluginModuleApiSignatureTest.java index c3178be..765e2f0 100644 --- a/android/src/test/java/com/reactlibrary/RNMappPluginModuleApiSignatureTest.java +++ b/android/src/test/java/com/reactlibrary/RNMappPluginModuleApiSignatureTest.java @@ -78,7 +78,7 @@ public void testAllReactMethodSignatures() { // --- Engage / init --- assertMethod("engage2"); assertMethod("engage", - String.class, String.class, String.class, String.class, String.class); + String.class, String.class, String.class, String.class, String.class, Promise.class); assertMethod("engageTestServer", String.class, String.class, String.class, String.class, String.class, String.class); assertMethod("onInitCompletedListener", Promise.class); diff --git a/android/src/test/java/com/reactlibrary/RNMappPluginModuleTest.java b/android/src/test/java/com/reactlibrary/RNMappPluginModuleTest.java index 1435278..ab0d76d 100644 --- a/android/src/test/java/com/reactlibrary/RNMappPluginModuleTest.java +++ b/android/src/test/java/com/reactlibrary/RNMappPluginModuleTest.java @@ -17,6 +17,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.MockedStatic; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -69,6 +70,7 @@ public class RNMappPluginModuleTest { private RNMappPluginModule module; private Appoxee mockAppoxeeInstance; private MockedStatic mockedAppoxee; + private Promise engagePromise; @Before public void setUp() { @@ -78,6 +80,7 @@ public void setUp() { when(reactContext.getApplicationContext()).thenReturn(app); module = new RNMappPluginModule(reactContext); + engagePromise = mock(Promise.class); // Mock the Appoxee interface instance returned by Appoxee.instance() mockAppoxeeInstance = mock(Appoxee.class); @@ -103,7 +106,7 @@ public void engage_callsAppoxeeEngageOnMainThread() { return null; }); - module.engage("myKey", "projectId", "L3", "appId", "tenantId"); + module.engage("myKey", "projectId", "L3", "appId", "tenantId", engagePromise); Shadows.shadowOf(Looper.getMainLooper()).idle(); mockedAppoxee.verify(() -> @@ -111,6 +114,50 @@ public void engage_callsAppoxeeEngageOnMainThread() { ); } + @Test + public void engage_resolvesAfterEngageAndSingletonSetup() { + Promise promise = mock(Promise.class); + + module.engage("myKey", "projectId", "L3", "appId", "tenantId", promise); + Shadows.shadowOf(Looper.getMainLooper()).idle(); + + mockedAppoxee.verify(() -> + Appoxee.engage(eq(RuntimeEnvironment.getApplication()), any(AppoxeeOptions.class)) + ); + InOrder completionOrder = org.mockito.Mockito.inOrder(mockAppoxeeInstance, promise); + completionOrder.verify(mockAppoxeeInstance).setPushBroadcast(MyPushBroadcastReceiver.class); + completionOrder.verify(promise).resolve(true); + verify(promise, never()).reject(anyString(), anyString(), any(Throwable.class)); + } + + @Test + public void engage_rejectsSdkFailureInsteadOfResolving() { + Promise promise = mock(Promise.class); + IllegalStateException failure = new IllegalStateException("engage failed"); + mockedAppoxee.when(() -> Appoxee.engage(any(), any())).thenThrow(failure); + + module.engage("myKey", "projectId", "L3", "appId", "tenantId", promise); + Shadows.shadowOf(Looper.getMainLooper()).idle(); + + verify(promise).reject("MAPP_ENGAGE_FAILED", "Mapp initialization failed", failure); + verify(promise, never()).resolve(any()); + verify(mockAppoxeeInstance, never()).setPushBroadcast(any()); + } + + @Test + public void engage_rejectsInvalidConfigurationBeforeCallingSdk() { + Promise promise = mock(Promise.class); + + module.engage("myKey", "projectId", "BOGUS", "appId", "tenantId", promise); + + verify(promise).reject( + eq("MAPP_ENGAGE_INVALID_CONFIGURATION"), + anyString(), + any(IllegalArgumentException.class) + ); + mockedAppoxee.verify(() -> Appoxee.engage(any(), any()), never()); + } + @Test public void engage2_callsAppoxeeEngageOnMainThread() { mockedAppoxee.when(() -> Appoxee.engage(any(), any())).thenAnswer(invocation -> { @@ -128,7 +175,7 @@ public void engage2_callsAppoxeeEngageOnMainThread() { @Test public void engage_passesCorrectServerToSdk() { - module.engage("myKey", "projectId", "L3", "appId", "tenantId"); + module.engage("myKey", "projectId", "L3", "appId", "tenantId", engagePromise); Shadows.shadowOf(Looper.getMainLooper()).idle(); mockedAppoxee.verify(() -> @@ -140,7 +187,7 @@ public void engage_passesCorrectServerToSdk() { @Test public void engage_passesCorrectSdkKeyToSdk() { - module.engage("myKey", "projectId", "L3", "appId", "tenantId"); + module.engage("myKey", "projectId", "L3", "appId", "tenantId", engagePromise); Shadows.shadowOf(Looper.getMainLooper()).idle(); mockedAppoxee.verify(() -> @@ -152,7 +199,7 @@ public void engage_passesCorrectSdkKeyToSdk() { @Test public void engage_passesCorrectAppIdAndTenantIdToSdk() { - module.engage("myKey", "projectId", "L3", "appId", "tenantId"); + module.engage("myKey", "projectId", "L3", "appId", "tenantId", engagePromise); Shadows.shadowOf(Looper.getMainLooper()).idle(); mockedAppoxee.verify(() -> @@ -164,7 +211,7 @@ public void engage_passesCorrectAppIdAndTenantIdToSdk() { @Test public void engage_setsBackgroundAndForegroundNotificationMode() { - module.engage("myKey", "projectId", "L3", "appId", "tenantId"); + module.engage("myKey", "projectId", "L3", "appId", "tenantId", engagePromise); Shadows.shadowOf(Looper.getMainLooper()).idle(); mockedAppoxee.verify(() -> @@ -176,7 +223,7 @@ public void engage_setsBackgroundAndForegroundNotificationMode() { @Test public void engage_legacyAlias_L3US_resolvesToL3_US() { - module.engage("myKey", "projectId", "L3US", "appId", "tenantId"); + module.engage("myKey", "projectId", "L3US", "appId", "tenantId", engagePromise); Shadows.shadowOf(Looper.getMainLooper()).idle(); mockedAppoxee.verify(() -> @@ -188,7 +235,7 @@ public void engage_legacyAlias_L3US_resolvesToL3_US() { @Test public void engage_legacyAlias_TEST55_resolvesToTEST_55() { - module.engage("myKey", "projectId", "TEST55", "appId", "tenantId"); + module.engage("myKey", "projectId", "TEST55", "appId", "tenantId", engagePromise); Shadows.shadowOf(Looper.getMainLooper()).idle(); mockedAppoxee.verify(() -> @@ -200,7 +247,7 @@ public void engage_legacyAlias_TEST55_resolvesToTEST_55() { @Test public void engage_legacyAlias_TEST61_resolvesToTEST_61() { - module.engage("myKey", "projectId", "TEST61", "appId", "tenantId"); + module.engage("myKey", "projectId", "TEST61", "appId", "tenantId", engagePromise); Shadows.shadowOf(Looper.getMainLooper()).idle(); mockedAppoxee.verify(() -> @@ -212,7 +259,7 @@ public void engage_legacyAlias_TEST61_resolvesToTEST_61() { @Test public void engage_legacyAlias_EMCUS_resolvesToEMC_US() { - module.engage("myKey", "projectId", "EMCUS", "appId", "tenantId"); + module.engage("myKey", "projectId", "EMCUS", "appId", "tenantId", engagePromise); Shadows.shadowOf(Looper.getMainLooper()).idle(); mockedAppoxee.verify(() -> @@ -222,11 +269,6 @@ public void engage_legacyAlias_EMCUS_resolvesToEMC_US() { ); } - @Test(expected = IllegalArgumentException.class) - public void engage_invalidServer_throwsBeforeCallingSDK() { - module.engage("myKey", "projectId", "BOGUS", "appId", "tenantId"); - } - // ========================================================================= // engageTestServer() // ========================================================================= diff --git a/ios/RNMappPluginModule.mm b/ios/RNMappPluginModule.mm index ae5555a..7a959b9 100644 --- a/ios/RNMappPluginModule.mm +++ b/ios/RNMappPluginModule.mm @@ -82,7 +82,7 @@ - (dispatch_queue_t)methodQueue { RCT_EXPORT_METHOD(engage2) {} RCT_EXPORT_METHOD(engageTestServer:(NSString *)cepUrl sdkKey:(NSString *)sdkKey googleProjectId:(NSString *)projectId server:(NSString *)server appID:(NSString *)appID tenantID:(NSString *)tenantID) { - [self engage:sdkKey googleProjectId:projectId server:server appID:appID tenantID:tenantID]; + [self performEngage:sdkKey googleProjectId:projectId server:server appID:appID tenantID:tenantID]; } RCT_EXPORT_METHOD(onInitCompletedListener:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { @@ -119,13 +119,33 @@ - (dispatch_queue_t)methodQueue { #pragma mark Exported methods - Notifications -RCT_EXPORT_METHOD(engage: (NSString *)sdkKey googleProjectId: (NSString *)projectId server:(NSString *)server appID:(NSString *)appID tenantID:(NSString *)tenantID) { +- (void)performEngage:(NSString *)sdkKey + googleProjectId:(NSString *)projectId + server:(NSString *)server + appID:(NSString *)appID + tenantID:(NSString *)tenantID { SERVER serv = [self getServerKeyFor:server]; [[Appoxee shared] engageAndAutoIntegrateWithLaunchOptions:nil andDelegate:[RNMappEventEmmiter shared] with:serv]; [[Appoxee shared] addObserver: [RNMappEventEmmiter shared] forKeyPath:@"isReady" options:NSKeyValueObservingOptionNew context:nil]; [[AppoxeeInapp shared] engageWithDelegate:[RNMappEventEmmiter shared] with:[self getInappServerKeyFor:server]]; } +RCT_EXPORT_METHOD(engage:(NSString *)sdkKey + googleProjectId:(NSString *)projectId + server:(NSString *)server + appID:(NSString *)appID + tenantID:(NSString *)tenantID + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + @try { + [self performEngage:sdkKey googleProjectId:projectId server:server appID:appID tenantID:tenantID]; + resolve(@YES); + } @catch (NSException *exception) { + NSString *message = exception.reason ?: @"Mapp initialization failed"; + reject(@"MAPP_ENGAGE_FAILED", message, nil); + } +} + RCT_EXPORT_METHOD(getAlias:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { [[Appoxee shared] getDeviceAliasWithCompletionHandler:^(NSError * _Nullable appoxeeError, id _Nullable data) { if (appoxeeError == nil && data != nil) { diff --git a/package.json b/package.json index 4db0bdf..54a7b08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-mapp-plugin", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.3", "description": "Mapp SDK for React Native.", "main": "index.js", "files": [ diff --git a/specs/NativeRNMappPluginModule.js b/specs/NativeRNMappPluginModule.js index a20b4af..77b303a 100644 --- a/specs/NativeRNMappPluginModule.js +++ b/specs/NativeRNMappPluginModule.js @@ -16,7 +16,7 @@ export interface Spec extends TurboModule { getAlias(): Promise; /** @deprecated Use engage(...). */ engage2(): void; - engage(sdkKey: string, googleProjectId: string, server: string, appID: string, tenantID: string): void; + engage(sdkKey: string, googleProjectId: string, server: string, appID: string, tenantID: string): Promise; engageTestServer(cepURl: string, sdkKey: string, googleProjectId: string, server: string, appID: string, tenantID: string): void; onInitCompletedListener(): Promise; isReady(): Promise; From 55627027e0d1457f5b6a84f49f6bb4117b22b3a7 Mon Sep 17 00:00:00 2001 From: tafilovic Date: Thu, 3 Sep 2026 11:37:49 +0200 Subject: [PATCH 2/2] updated package.json --- package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 54a7b08..3ede8e8 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,12 @@ ], "transform": { "^.+\\.js$": "babel-jest", - "^.+\\.ts$": ["ts-jest", { "tsconfig": "plugin/tsconfig.json" }] + "^.+\\.ts$": [ + "ts-jest", + { + "tsconfig": "plugin/tsconfig.json" + } + ] }, "moduleNameMapper": { "^react-native$": "/__mocks__/react-native.js"