Skip to content
Open
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
8 changes: 8 additions & 0 deletions example/src/hooks/useIterableApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,14 @@ export const IterableAppProvider: FunctionComponent<

config.enableEmbeddedMessaging = true;

config.onEmbeddedMessageUpdate = () => {
console.log('onEmbeddedMessageUpdate');
};

config.onEmbeddedMessagingDisabled = () => {
console.log('onEmbeddedMessagingDisabled');
};

config.inAppHandler = () => IterableInAppShowResponse.show;

if (
Expand Down
1 change: 1 addition & 0 deletions ios/RNIterableAPI/RNIterableAPI.mm
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ @protocol IterableInAppDelegate;
@protocol IterableCustomActionDelegate;
@protocol IterableAuthDelegate;
@protocol IterableURLDelegate;
@protocol IterableEmbeddedUpdateDelegate;
typedef NS_ENUM(NSInteger, InAppShowResponse) {
show = 0,
skip = 1,
Expand Down
32 changes: 32 additions & 0 deletions ios/RNIterableAPI/ReactIterableAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import React
case receivedIterableInboxChanged
case handleAuthSuccessCalled
case handleAuthFailureCalled
case handleEmbeddedMessageUpdateCalled
case handleEmbeddedMessagingDisabledCalled
}

@objc public static var supportedEvents: [String] {
Expand Down Expand Up @@ -651,6 +653,14 @@ import React
}

IterableAPI.setDeviceAttribute(name: "reactNativeSDKVersion", value: version)

// Add embedded update listener if any callback is present
let onEmbeddedMessageUpdatePresent = configDict["onEmbeddedMessageUpdatePresent"] as? Bool ?? false
let onEmbeddedMessagingDisabledPresent = configDict["onEmbeddedMessagingDisabledPresent"] as? Bool ?? false

if onEmbeddedMessageUpdatePresent || onEmbeddedMessagingDisabledPresent {
IterableAPI.embeddedManager.addUpdateListener(self)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 6): initialize [qlty:function-complexity]

}

Expand Down Expand Up @@ -808,3 +818,25 @@ extension ReactIterableAPI: IterableAuthDelegate {
public func onTokenRegistrationFailed(_ reason: String?) {
}
}

extension ReactIterableAPI: IterableEmbeddedUpdateDelegate {
public func onMessagesUpdated() {
ITBInfo()
guard shouldEmit else {
return
}
delegate?.sendEvent(
withName: EventName.handleEmbeddedMessageUpdateCalled.rawValue,
body: nil as Any?)
}

public func onEmbeddedMessagingDisabled() {
ITBInfo()
guard shouldEmit else {
return
}
delegate?.sendEvent(
withName: EventName.handleEmbeddedMessagingDisabledCalled.rawValue,
body: nil as Any?)
}
}
226 changes: 226 additions & 0 deletions src/core/classes/Iterable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ describe('Iterable', () => {
nativeEmitter.removeAllListeners(IterableEventName.handleAuthCalled);
nativeEmitter.removeAllListeners(IterableEventName.handleAuthSuccessCalled);
nativeEmitter.removeAllListeners(IterableEventName.handleAuthFailureCalled);
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);

// Clear any pending timers
jest.clearAllTimers();
Expand Down Expand Up @@ -1229,4 +1235,224 @@ describe('Iterable', () => {
expect(Iterable.embeddedManager.isEnabled).toBe(true);
});
});

describe('embedded messaging callbacks', () => {
describe('onEmbeddedMessageUpdate', () => {
it('should call onEmbeddedMessageUpdate when handleEmbeddedMessageUpdateCalled event is emitted', () => {
// sets up event emitter
const nativeEmitter = new NativeEventEmitter();
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
// sets up config file and onEmbeddedMessageUpdate callback
const config = new IterableConfig();
config.logReactNativeSdkCalls = false;
config.onEmbeddedMessageUpdate = jest.fn();
// initialize Iterable object
Iterable.initialize('apiKey', config);
// WHEN handleEmbeddedMessageUpdateCalled event is emitted
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
// THEN onEmbeddedMessageUpdate callback is called
expect(config.onEmbeddedMessageUpdate).toHaveBeenCalled();
expect(config.onEmbeddedMessageUpdate).toHaveBeenCalledTimes(1);
});

it('should not set up listener if onEmbeddedMessageUpdate is not provided', () => {
// sets up event emitter
const nativeEmitter = new NativeEventEmitter();
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
// sets up config without onEmbeddedMessageUpdate callback
const config = new IterableConfig();
config.logReactNativeSdkCalls = false;
// initialize Iterable object
Iterable.initialize('apiKey', config);
// WHEN handleEmbeddedMessageUpdateCalled event is emitted
// THEN no error should occur (no listener was set up)
expect(() => {
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
}).not.toThrow();
});

it('should call onEmbeddedMessageUpdate multiple times when event is emitted multiple times', () => {
// sets up event emitter
const nativeEmitter = new NativeEventEmitter();
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
// sets up config with callback
const config = new IterableConfig();
config.logReactNativeSdkCalls = false;
config.onEmbeddedMessageUpdate = jest.fn();
// initialize Iterable object
Iterable.initialize('apiKey', config);
// WHEN handleEmbeddedMessageUpdateCalled event is emitted multiple times
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
// THEN onEmbeddedMessageUpdate callback is called three times
expect(config.onEmbeddedMessageUpdate).toHaveBeenCalledTimes(3);
});

it('should include onEmbeddedMessageUpdatePresent flag in config dict when callback is provided', () => {
// GIVEN a config with onEmbeddedMessageUpdate callback
const config = new IterableConfig();
config.onEmbeddedMessageUpdate = jest.fn();
// WHEN toDict is called
const configDict = config.toDict();
// THEN onEmbeddedMessageUpdatePresent is true
expect(configDict.onEmbeddedMessageUpdatePresent).toBe(true);
});

it('should set onEmbeddedMessageUpdatePresent flag to false when callback is not provided', () => {
// GIVEN a config without onEmbeddedMessageUpdate callback
const config = new IterableConfig();
// WHEN toDict is called
const configDict = config.toDict();
// THEN onEmbeddedMessageUpdatePresent is false
expect(configDict.onEmbeddedMessageUpdatePresent).toBe(false);
});
});

describe('onEmbeddedMessagingDisabled', () => {
it('should call onEmbeddedMessagingDisabled when handleEmbeddedMessagingDisabledCalled event is emitted', () => {
// sets up event emitter
const nativeEmitter = new NativeEventEmitter();
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);
// sets up config file and onEmbeddedMessagingDisabled callback
const config = new IterableConfig();
config.logReactNativeSdkCalls = false;
config.onEmbeddedMessagingDisabled = jest.fn();
// initialize Iterable object
Iterable.initialize('apiKey', config);
// WHEN handleEmbeddedMessagingDisabledCalled event is emitted
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);
// THEN onEmbeddedMessagingDisabled callback is called
expect(config.onEmbeddedMessagingDisabled).toHaveBeenCalled();
expect(config.onEmbeddedMessagingDisabled).toHaveBeenCalledTimes(1);
});

it('should not set up listener if onEmbeddedMessagingDisabled is not provided', () => {
// sets up event emitter
const nativeEmitter = new NativeEventEmitter();
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);
// sets up config without onEmbeddedMessagingDisabled callback
const config = new IterableConfig();
config.logReactNativeSdkCalls = false;
// initialize Iterable object
Iterable.initialize('apiKey', config);
// WHEN handleEmbeddedMessagingDisabledCalled event is emitted
// THEN no error should occur (no listener was set up)
expect(() => {
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);
}).not.toThrow();
});

it('should call onEmbeddedMessagingDisabled when embedded messaging becomes unavailable', () => {
// sets up event emitter
const nativeEmitter = new NativeEventEmitter();
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);
// sets up config with callback
const config = new IterableConfig();
config.logReactNativeSdkCalls = false;
config.onEmbeddedMessagingDisabled = jest.fn();
// initialize Iterable object
Iterable.initialize('apiKey', config);
// WHEN handleEmbeddedMessagingDisabledCalled event is emitted
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);
// THEN onEmbeddedMessagingDisabled callback is called
expect(config.onEmbeddedMessagingDisabled).toHaveBeenCalled();
});

it('should include onEmbeddedMessagingDisabledPresent flag in config dict when callback is provided', () => {
// GIVEN a config with onEmbeddedMessagingDisabled callback
const config = new IterableConfig();
config.onEmbeddedMessagingDisabled = jest.fn();
// WHEN toDict is called
const configDict = config.toDict();
// THEN onEmbeddedMessagingDisabledPresent is true
expect(configDict.onEmbeddedMessagingDisabledPresent).toBe(true);
});

it('should set onEmbeddedMessagingDisabledPresent flag to false when callback is not provided', () => {
// GIVEN a config without onEmbeddedMessagingDisabled callback
const config = new IterableConfig();
// WHEN toDict is called
const configDict = config.toDict();
// THEN onEmbeddedMessagingDisabledPresent is false
expect(configDict.onEmbeddedMessagingDisabledPresent).toBe(false);
});
});

describe('both embedded callbacks', () => {
it('should call both callbacks independently when both are provided', () => {
// sets up event emitter
const nativeEmitter = new NativeEventEmitter();
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
nativeEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);
// sets up config with both callbacks
const config = new IterableConfig();
config.logReactNativeSdkCalls = false;
config.onEmbeddedMessageUpdate = jest.fn();
config.onEmbeddedMessagingDisabled = jest.fn();
// initialize Iterable object
Iterable.initialize('apiKey', config);
// WHEN handleEmbeddedMessageUpdateCalled event is emitted
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
// THEN only onEmbeddedMessageUpdate is called
expect(config.onEmbeddedMessageUpdate).toHaveBeenCalled();
expect(config.onEmbeddedMessagingDisabled).not.toHaveBeenCalled();
// Reset mocks
jest.clearAllMocks();
// WHEN handleEmbeddedMessagingDisabledCalled event is emitted
nativeEmitter.emit(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);
// THEN only onEmbeddedMessagingDisabled is called
expect(config.onEmbeddedMessagingDisabled).toHaveBeenCalled();
expect(config.onEmbeddedMessageUpdate).not.toHaveBeenCalled();
});

it('should set both presence flags in config dict when both callbacks are provided', () => {
// GIVEN a config with both callbacks
const config = new IterableConfig();
config.onEmbeddedMessageUpdate = jest.fn();
config.onEmbeddedMessagingDisabled = jest.fn();
// WHEN toDict is called
const configDict = config.toDict();
// THEN both presence flags are true
expect(configDict.onEmbeddedMessageUpdatePresent).toBe(true);
expect(configDict.onEmbeddedMessagingDisabledPresent).toBe(true);
});
});
});
});
26 changes: 26 additions & 0 deletions src/core/classes/Iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,12 @@ export class Iterable {
RNEventEmitter.removeAllListeners(
IterableEventName.handleAuthFailureCalled
);
RNEventEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessageUpdateCalled
);
RNEventEmitter.removeAllListeners(
IterableEventName.handleEmbeddedMessagingDisabledCalled
);
}

/**
Expand Down Expand Up @@ -1083,6 +1089,26 @@ export class Iterable {
}
);
}

if (Iterable.savedConfig.enableEmbeddedMessaging) {
if (Iterable.savedConfig.onEmbeddedMessageUpdate) {
RNEventEmitter.addListener(
IterableEventName.handleEmbeddedMessageUpdateCalled,
() => {
Iterable.savedConfig.onEmbeddedMessageUpdate?.();
}
);
}

if (Iterable.savedConfig.onEmbeddedMessagingDisabled) {
RNEventEmitter.addListener(
IterableEventName.handleEmbeddedMessagingDisabledCalled,
() => {
Iterable.savedConfig.onEmbeddedMessagingDisabled?.();
}
);
}
}
}

/**
Expand Down
Loading
Loading