Skip to content
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

### Updates

- Added optional placement IDs to `Iterable.embeddedManager.syncMessages()` so apps can sync embedded messages for specific placements only (SDK-751). When omitted, null, or empty, behavior matches the previous sync-all call.
- **Android:** Scoped sync (`syncMessages([id, …])`) matches the pinned iterableapi behavior and may remove cached messages for placements not included in the sync response (SDK-798). iOS preserves unrequested placements. Prefer `syncMessages()` without placement IDs on Android until the native fix ships and the Android SDK pin is bumped.
- Added `IterableInAppMessage.jsonOnly` so `inAppHandler` and in-app message fetches can distinguish JSON-only messages from HTML in-apps (SDK-750).
- Added `IterableConfig.onEmbeddedMessagingSyncSucceeded` and
`IterableConfig.onEmbeddedMessagingSyncFailed` so apps can observe embedded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -796,9 +796,19 @@ public void onEmbeddedMessagingSyncFailed(@Nullable String reason) {
// ---------------------------------------------------------------------------------------
// region Embedded messaging

public void syncEmbeddedMessages() {
IterableLogger.d(TAG, "syncEmbeddedMessages");
IterableApi.getInstance().getEmbeddedManager().syncMessages();
public void syncEmbeddedMessages(@Nullable ReadableArray placementIds) {
IterableLogger.d(TAG, "syncEmbeddedMessages for placements: " + placementIds);

if (placementIds == null || placementIds.size() == 0) {
IterableApi.getInstance().getEmbeddedManager().syncMessages();
} else {
IterableLogger.w(TAG, "Scoped syncEmbeddedMessages may remove cached messages for placements not in the sync response (SDK-798). Prefer syncMessages() without placement IDs on Android until iterableapi is fixed.");
Long[] ids = new Long[placementIds.size()];
for (int i = 0; i < placementIds.size(); i++) {
ids[i] = (long) placementIds.getInt(i);
}
IterableApi.getInstance().getEmbeddedManager().syncMessages(ids);
Comment thread
jferrao-itrbl marked this conversation as resolved.
}
Comment thread
jferrao-itrbl marked this conversation as resolved.
}

public void startEmbeddedSession() {
Expand Down
4 changes: 2 additions & 2 deletions android/src/newarch/java/com/RNIterableAPIModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ public void pauseAuthRetries(boolean pauseRetry) {
}

@Override
public void syncEmbeddedMessages() {
moduleImpl.syncEmbeddedMessages();
public void syncEmbeddedMessages(@Nullable ReadableArray placementIds) {
moduleImpl.syncEmbeddedMessages(placementIds);
}

@Override
Expand Down
4 changes: 2 additions & 2 deletions android/src/oldarch/java/com/RNIterableAPIModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ public void pauseAuthRetries(boolean pauseRetry) {
}

@ReactMethod
public void syncEmbeddedMessages() {
moduleImpl.syncEmbeddedMessages();
public void syncEmbeddedMessages(@Nullable ReadableArray placementIds) {
moduleImpl.syncEmbeddedMessages(placementIds);
}

@ReactMethod
Expand Down
8 changes: 4 additions & 4 deletions ios/RNIterableAPI/RNIterableAPI.mm
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,8 @@ - (void)endEmbeddedSession {
[_swiftAPI endEmbeddedSession];
}

- (void)syncEmbeddedMessages {
[_swiftAPI syncEmbeddedMessages];
- (void)syncEmbeddedMessages:(NSArray *_Nullable)placementIds {
[_swiftAPI syncEmbeddedMessages:placementIds];
}

- (void)getEmbeddedMessages:(NSArray *_Nullable)placementIds
Expand Down Expand Up @@ -597,8 +597,8 @@ - (void)wakeApp {
[_swiftAPI endEmbeddedSession];
}

RCT_EXPORT_METHOD(syncEmbeddedMessages) {
[_swiftAPI syncEmbeddedMessages];
RCT_EXPORT_METHOD(syncEmbeddedMessages : (NSArray *_Nullable)placementIds) {
[_swiftAPI syncEmbeddedMessages:placementIds];
}

RCT_EXPORT_METHOD(getEmbeddedMessages : (NSArray *_Nullable)placementIds resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
Expand Down
11 changes: 8 additions & 3 deletions ios/RNIterableAPI/ReactIterableAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -537,10 +537,15 @@ import React
EmbeddedSessionManager.shared.endSession()
}

@objc(syncEmbeddedMessages)
public func syncEmbeddedMessages() {
@objc(syncEmbeddedMessages:)
public func syncEmbeddedMessages(placementIds: [NSNumber]?) {
ITBInfo()
IterableAPI.embeddedManager.syncMessages { }
if let placementIds = placementIds, !placementIds.isEmpty {
let ids = placementIds.map { $0.intValue }
IterableAPI.embeddedManager.syncMessages(placementIds: ids) { }
} else {
IterableAPI.embeddedManager.syncMessages { }
}
}

@objc(getEmbeddedMessages:resolver:rejecter:)
Expand Down
2 changes: 1 addition & 1 deletion src/api/NativeRNIterableAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export interface Spec extends TurboModule {
pauseAuthRetries(pauseRetry: boolean): void;

// Embedded Messaging
syncEmbeddedMessages(): void;
syncEmbeddedMessages(placementIds: Array<number> | null): void;
startEmbeddedSession(): void;
endEmbeddedSession(): void;
startEmbeddedImpression(messageId: string, placementId: number): void;
Expand Down
35 changes: 32 additions & 3 deletions src/core/classes/IterableApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1299,13 +1299,42 @@ describe('IterableApi', () => {
});

describe('syncEmbeddedMessages', () => {
it('should call RNIterableAPI.syncEmbeddedMessages', () => {
it('should call RNIterableAPI.syncEmbeddedMessages with null when omitted', () => {
// GIVEN no parameters
// WHEN syncEmbeddedMessages is called
IterableApi.syncEmbeddedMessages();

// THEN RNIterableAPI.syncEmbeddedMessages is called
expect(MockRNIterableAPI.syncEmbeddedMessages).toBeCalled();
// THEN RNIterableAPI.syncEmbeddedMessages is called with null (sync all)
expect(MockRNIterableAPI.syncEmbeddedMessages).toBeCalledWith(null);
});

it('should call RNIterableAPI.syncEmbeddedMessages with null when passed null', () => {
// WHEN syncEmbeddedMessages is called with null
IterableApi.syncEmbeddedMessages(null);

// THEN RNIterableAPI.syncEmbeddedMessages is called with null (sync all)
expect(MockRNIterableAPI.syncEmbeddedMessages).toBeCalledWith(null);
});

it('should call RNIterableAPI.syncEmbeddedMessages with placement IDs', () => {
// GIVEN placement IDs
const placementIds = [1, 2];

// WHEN syncEmbeddedMessages is called
IterableApi.syncEmbeddedMessages(placementIds);

// THEN RNIterableAPI.syncEmbeddedMessages is called with placement IDs
expect(MockRNIterableAPI.syncEmbeddedMessages).toBeCalledWith(
placementIds
);
});

it('should call RNIterableAPI.syncEmbeddedMessages with empty array for sync all', () => {
// WHEN syncEmbeddedMessages is called with an empty array
IterableApi.syncEmbeddedMessages([]);

// THEN the empty array is forwarded (native syncs all placements)
expect(MockRNIterableAPI.syncEmbeddedMessages).toBeCalledWith([]);
});
});

Expand Down
12 changes: 9 additions & 3 deletions src/core/classes/IterableApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,10 +550,16 @@ export class IterableApi {

/**
* Syncs embedded local cache with the server.
*
* @param placementIds - When provided, limits the sync to those placements.
* See {@link IterableEmbeddedManager.syncMessages} for Android scoped-sync
* caveats (SDK-798).
*/
static syncEmbeddedMessages() {
IterableLogger.log('syncEmbeddedMessages');
return RNIterableAPI.syncEmbeddedMessages();
static syncEmbeddedMessages(placementIds?: number[] | null) {
IterableLogger.log('syncEmbeddedMessages: ', placementIds);
return RNIterableAPI.syncEmbeddedMessages(
placementIds === undefined ? null : placementIds
);
}

/**
Expand Down
37 changes: 35 additions & 2 deletions src/embedded/classes/IterableEmbeddedManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,49 @@ describe('IterableEmbeddedManager', () => {
});

describe('syncMessages', () => {
it('should call IterableApi.syncEmbeddedMessages', async () => {
it('should call IterableApi.syncEmbeddedMessages with null when omitted', async () => {
// WHEN syncMessages is called
const result = await embeddedManager.syncMessages();

// THEN IterableApi.syncEmbeddedMessages is called
// THEN IterableApi.syncEmbeddedMessages is called with null
expect(MockRNIterableAPI.syncEmbeddedMessages).toHaveBeenCalledTimes(1);
expect(MockRNIterableAPI.syncEmbeddedMessages).toHaveBeenCalledWith(null);

// AND the result is returned
expect(result).toBeUndefined();
});

it('should call IterableApi.syncEmbeddedMessages with placement IDs', () => {
// GIVEN placement IDs
const placementIds = [1, 2];

// WHEN syncMessages is called
embeddedManager.syncMessages(placementIds);

// THEN IterableApi.syncEmbeddedMessages is called with placement IDs
expect(MockRNIterableAPI.syncEmbeddedMessages).toHaveBeenCalledTimes(1);
expect(MockRNIterableAPI.syncEmbeddedMessages).toHaveBeenCalledWith(
placementIds
);
});

it('should call IterableApi.syncEmbeddedMessages with null placement IDs', () => {
// WHEN syncMessages is called with null
embeddedManager.syncMessages(null);

// THEN IterableApi.syncEmbeddedMessages is called with null
expect(MockRNIterableAPI.syncEmbeddedMessages).toHaveBeenCalledTimes(1);
expect(MockRNIterableAPI.syncEmbeddedMessages).toHaveBeenCalledWith(null);
});

it('should call IterableApi.syncEmbeddedMessages with empty placement IDs', () => {
// WHEN syncMessages is called with an empty array
embeddedManager.syncMessages([]);

// THEN the empty array is forwarded (native syncs all placements)
expect(MockRNIterableAPI.syncEmbeddedMessages).toHaveBeenCalledTimes(1);
expect(MockRNIterableAPI.syncEmbeddedMessages).toHaveBeenCalledWith([]);
});
});

describe('getMessages', () => {
Expand Down
14 changes: 12 additions & 2 deletions src/embedded/classes/IterableEmbeddedManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,23 @@ export class IterableEmbeddedManager {
*
* However, do not poll for new embedded messages at a regular interval.
*
* @param placementIds - Optional placement IDs to sync. When omitted, null, or
* empty, syncs all placements.
*
* @remarks On Android, scoped sync may clear cached messages for placements
* that are not in the sync response (iterableapi SDK-798). iOS merges
* unrequested placements from the existing cache. Until Android iterableapi
* is fixed and this SDK bumps its Android pin, prefer calling `syncMessages()`
* without placement IDs on Android when multiple placements are in use.
*
* @example
* ```typescript
* IterableEmbeddedManager.syncMessages();
* IterableEmbeddedManager.syncMessages([1, 2, 3]);
* ```
*/
syncMessages() {
return IterableApi.syncEmbeddedMessages();
syncMessages(placementIds?: number[] | null) {
return IterableApi.syncEmbeddedMessages(placementIds);
}

/**
Expand Down
Loading