Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Unreleased

### Updates

- Added `IterableConfig.identityResolution` and the `IterableIdentityResolution` type so RN apps can configure default visitor replay and unknown-to-known merge behavior (SDK-556).

## 3.2.1

### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.iterable.iterableapi.IterableInAppHandler;
import com.iterable.iterableapi.IterableInAppLocation;
import com.iterable.iterableapi.IterableInAppMessage;
import com.iterable.iterableapi.IterableIdentityResolution;
import com.iterable.iterableapi.IterableInboxSession;
import com.iterable.iterableapi.IterableLogger;
import com.iterable.iterableapi.RetryPolicy;
Expand Down Expand Up @@ -262,6 +263,17 @@ static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableConte
configBuilder.setAuthRetryPolicy(new RetryPolicy(maxRetry, retryInterval, retryPolicyType));
}

if (iterableContextJSON.has("identityResolution")) {
JSONObject identityResolutionJson =
iterableContextJSON.getJSONObject("identityResolution");
boolean replayOnVisitorToKnown =
identityResolutionJson.optBoolean("replayOnVisitorToKnown", true);
boolean mergeOnUnknownToKnown =
identityResolutionJson.optBoolean("mergeOnUnknownToKnown", true);
configBuilder.setIdentityResolution(
new IterableIdentityResolution(replayOnVisitorToKnown, mergeOnUnknownToKnown));
}

return configBuilder;
} catch (JSONException e) {
e.printStackTrace();
Expand Down
10 changes: 10 additions & 0 deletions ios/RNIterableAPI/Serialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ extension IterableConfig {
}
}

if let identityResolutionDict = dict["identityResolution"] as? [AnyHashable: Any] {
let replayOnVisitorToKnown =
identityResolutionDict["replayOnVisitorToKnown"] as? Bool ?? true
let mergeOnUnknownToKnown =
identityResolutionDict["mergeOnUnknownToKnown"] as? Bool ?? true
config.identityResolution = IterableIdentityResolution(
replayOnVisitorToKnown: replayOnVisitorToKnown,
mergeOnUnknownToKnown: mergeOnUnknownToKnown)
}

return config
}

Expand Down
51 changes: 51 additions & 0 deletions src/core/classes/Iterable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ describe('Iterable', () => {
expect(config.useInMemoryStorageForInApps).toBe(false);
expect(config.androidWakeDelayMs).toBe(1000);
expect(config.authCallbackTimeoutMs).toBe(6000);
expect(config.identityResolution).toEqual({
replayOnVisitorToKnown: true,
mergeOnUnknownToKnown: true,
});
const configDict = config.toDict();
expect(configDict.allowedProtocols).toEqual([]);
expect(configDict.androidSdkUseInMemoryStorageForInApps).toBe(false);
Expand All @@ -355,6 +359,10 @@ describe('Iterable', () => {
expect(configDict.useInMemoryStorageForInApps).toBe(false);
expect(configDict.androidWakeDelayMs).toBe(1000);
expect(configDict.authCallbackTimeoutMs).toBe(6000);
expect(configDict.identityResolution).toEqual({
replayOnVisitorToKnown: true,
mergeOnUnknownToKnown: true,
});
});

it('should allow overriding androidWakeDelayMs and authCallbackTimeoutMs', () => {
Expand All @@ -365,6 +373,49 @@ describe('Iterable', () => {
expect(configDict.androidWakeDelayMs).toBe(1500);
expect(configDict.authCallbackTimeoutMs).toBe(2500);
});

it('should include identityResolution in toDict() with overridden values', () => {
const config = new IterableConfig();
config.identityResolution = {
replayOnVisitorToKnown: false,
mergeOnUnknownToKnown: false,
};
const configDict = config.toDict();
expect(configDict.identityResolution).toEqual({
replayOnVisitorToKnown: false,
mergeOnUnknownToKnown: false,
});
});

it('should default missing identityResolution keys to true in toDict()', () => {
const config = new IterableConfig();
config.identityResolution = { mergeOnUnknownToKnown: false };
const configDict = config.toDict();
expect(configDict.identityResolution).toEqual({
replayOnVisitorToKnown: true,
mergeOnUnknownToKnown: false,
});
});

it('should default mergeOnUnknownToKnown to true when only replayOnVisitorToKnown is set', () => {
const config = new IterableConfig();
config.identityResolution = { replayOnVisitorToKnown: false };
const configDict = config.toDict();
expect(configDict.identityResolution).toEqual({
replayOnVisitorToKnown: false,
mergeOnUnknownToKnown: true,
});
});

it('should default both identityResolution keys to true for an empty object', () => {
const config = new IterableConfig();
config.identityResolution = {};
const configDict = config.toDict();
expect(configDict.identityResolution).toEqual({
replayOnVisitorToKnown: true,
mergeOnUnknownToKnown: true,
});
});
});

describe('urlHandler', () => {
Expand Down
19 changes: 19 additions & 0 deletions src/core/classes/IterableConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IterableDataRegion } from '../enums/IterableDataRegion';
import { IterableLogLevel } from '../enums/IterableLogLevel';
import { IterablePushPlatform } from '../enums/IterablePushPlatform';
import type { IterableAuthFailure } from '../types/IterableAuthFailure';
import type { IterableIdentityResolution } from '../types/IterableIdentityResolution';
import type { IterableRetryPolicy } from '../types/IterableRetryPolicy';
import { IterableAction } from './IterableAction';
import type { IterableActionContext } from './IterableActionContext';
Expand Down Expand Up @@ -391,6 +392,18 @@ export class IterableConfig {
*/
authCallbackTimeoutMs = 6000;

/**
* Default identity-resolution behavior when identifying a user with
* `Iterable.setEmail` or `Iterable.setUserId`.
*
* Applies when unknown-user activation and visitor tracking are enabled on the
* native SDK. Omitted keys in a partial object default to `true`.
*/
identityResolution: IterableIdentityResolution = {
replayOnVisitorToKnown: true,
mergeOnUnknownToKnown: true,
};

/**
* Should the SDK enable and use embedded messaging?
*
Expand Down Expand Up @@ -484,6 +497,12 @@ export class IterableConfig {
pushPlatform: this.pushPlatform,
encryptionEnforced: this.encryptionEnforced,
retryPolicy: this.retryPolicy,
identityResolution: {
replayOnVisitorToKnown:
this.identityResolution.replayOnVisitorToKnown ?? true,
mergeOnUnknownToKnown:
this.identityResolution.mergeOnUnknownToKnown ?? true,
},
enableEmbeddedMessaging: this.enableEmbeddedMessaging,
androidWakeDelayMs: this.androidWakeDelayMs,
authCallbackTimeoutMs: this.authCallbackTimeoutMs,
Expand Down
20 changes: 20 additions & 0 deletions src/core/types/IterableIdentityResolution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Controls how the SDK resolves anonymous (visitor / unknown-user) data when a
* user is identified with `setEmail` or `setUserId`.
*
* Values set on `IterableConfig.identityResolution` are defaults; native
* `setEmail` / `setUserId` can override them per call (not exposed in RN yet).
*/
export interface IterableIdentityResolution {
/**
* When `true`, locally stored visitor events are replayed to the known user
* profile on identification.
*/
replayOnVisitorToKnown?: boolean;

/**
* When `true`, the generated unknown-user profile is merged into the known
* user profile on identification.
*/
mergeOnUnknownToKnown?: boolean;
}
1 change: 1 addition & 0 deletions src/core/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './IterableAuthFailure';
export * from './IterableEdgeInsetDetails';
export * from './IterableIdentityResolution';
export * from './IterableRetryPolicy';
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export {
export type {
IterableAuthFailure,
IterableEdgeInsetDetails,
IterableIdentityResolution,
IterableRetryPolicy,
} from './core/types';
export {
Expand Down
Loading