Skip to content

Commit bdb25c5

Browse files
authored
Merge pull request #803 from Iterable/loren/embedded/SDK-234-ios-add-real-time-updates-and-track-message-rece
[SDK-234] Add real time updates to iOS
2 parents e64a452 + ac42052 commit bdb25c5

7 files changed

Lines changed: 348 additions & 0 deletions

File tree

example/src/hooks/useIterableApp.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,14 @@ export const IterableAppProvider: FunctionComponent<
200200

201201
config.enableEmbeddedMessaging = true;
202202

203+
config.onEmbeddedMessageUpdate = () => {
204+
console.log('onEmbeddedMessageUpdate');
205+
};
206+
207+
config.onEmbeddedMessagingDisabled = () => {
208+
console.log('onEmbeddedMessagingDisabled');
209+
};
210+
203211
config.inAppHandler = () => IterableInAppShowResponse.show;
204212

205213
if (

ios/RNIterableAPI/RNIterableAPI.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ @protocol IterableInAppDelegate;
1313
@protocol IterableCustomActionDelegate;
1414
@protocol IterableAuthDelegate;
1515
@protocol IterableURLDelegate;
16+
@protocol IterableEmbeddedUpdateDelegate;
1617
typedef NS_ENUM(NSInteger, InAppShowResponse) {
1718
show = 0,
1819
skip = 1,

ios/RNIterableAPI/ReactIterableAPI.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import React
3333
case receivedIterableInboxChanged
3434
case handleAuthSuccessCalled
3535
case handleAuthFailureCalled
36+
case handleEmbeddedMessageUpdateCalled
37+
case handleEmbeddedMessagingDisabledCalled
3638
}
3739

3840
@objc public static var supportedEvents: [String] {
@@ -651,6 +653,14 @@ import React
651653
}
652654

653655
IterableAPI.setDeviceAttribute(name: "reactNativeSDKVersion", value: version)
656+
657+
// Add embedded update listener if any callback is present
658+
let onEmbeddedMessageUpdatePresent = configDict["onEmbeddedMessageUpdatePresent"] as? Bool ?? false
659+
let onEmbeddedMessagingDisabledPresent = configDict["onEmbeddedMessagingDisabledPresent"] as? Bool ?? false
660+
661+
if onEmbeddedMessageUpdatePresent || onEmbeddedMessagingDisabledPresent {
662+
IterableAPI.embeddedManager.addUpdateListener(self)
663+
}
654664
}
655665
}
656666

@@ -808,3 +818,25 @@ extension ReactIterableAPI: IterableAuthDelegate {
808818
public func onTokenRegistrationFailed(_ reason: String?) {
809819
}
810820
}
821+
822+
extension ReactIterableAPI: IterableEmbeddedUpdateDelegate {
823+
public func onMessagesUpdated() {
824+
ITBInfo()
825+
guard shouldEmit else {
826+
return
827+
}
828+
delegate?.sendEvent(
829+
withName: EventName.handleEmbeddedMessageUpdateCalled.rawValue,
830+
body: nil as Any?)
831+
}
832+
833+
public func onEmbeddedMessagingDisabled() {
834+
ITBInfo()
835+
guard shouldEmit else {
836+
return
837+
}
838+
delegate?.sendEvent(
839+
withName: EventName.handleEmbeddedMessagingDisabledCalled.rawValue,
840+
body: nil as Any?)
841+
}
842+
}

src/core/classes/Iterable.test.ts

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ describe('Iterable', () => {
4141
nativeEmitter.removeAllListeners(IterableEventName.handleAuthCalled);
4242
nativeEmitter.removeAllListeners(IterableEventName.handleAuthSuccessCalled);
4343
nativeEmitter.removeAllListeners(IterableEventName.handleAuthFailureCalled);
44+
nativeEmitter.removeAllListeners(
45+
IterableEventName.handleEmbeddedMessageUpdateCalled
46+
);
47+
nativeEmitter.removeAllListeners(
48+
IterableEventName.handleEmbeddedMessagingDisabledCalled
49+
);
4450

4551
// Clear any pending timers
4652
jest.clearAllTimers();
@@ -1229,4 +1235,224 @@ describe('Iterable', () => {
12291235
expect(Iterable.embeddedManager.isEnabled).toBe(true);
12301236
});
12311237
});
1238+
1239+
describe('embedded messaging callbacks', () => {
1240+
describe('onEmbeddedMessageUpdate', () => {
1241+
it('should call onEmbeddedMessageUpdate when handleEmbeddedMessageUpdateCalled event is emitted', () => {
1242+
// sets up event emitter
1243+
const nativeEmitter = new NativeEventEmitter();
1244+
nativeEmitter.removeAllListeners(
1245+
IterableEventName.handleEmbeddedMessageUpdateCalled
1246+
);
1247+
// sets up config file and onEmbeddedMessageUpdate callback
1248+
const config = new IterableConfig();
1249+
config.logReactNativeSdkCalls = false;
1250+
config.onEmbeddedMessageUpdate = jest.fn();
1251+
// initialize Iterable object
1252+
Iterable.initialize('apiKey', config);
1253+
// WHEN handleEmbeddedMessageUpdateCalled event is emitted
1254+
nativeEmitter.emit(
1255+
IterableEventName.handleEmbeddedMessageUpdateCalled
1256+
);
1257+
// THEN onEmbeddedMessageUpdate callback is called
1258+
expect(config.onEmbeddedMessageUpdate).toHaveBeenCalled();
1259+
expect(config.onEmbeddedMessageUpdate).toHaveBeenCalledTimes(1);
1260+
});
1261+
1262+
it('should not set up listener if onEmbeddedMessageUpdate is not provided', () => {
1263+
// sets up event emitter
1264+
const nativeEmitter = new NativeEventEmitter();
1265+
nativeEmitter.removeAllListeners(
1266+
IterableEventName.handleEmbeddedMessageUpdateCalled
1267+
);
1268+
// sets up config without onEmbeddedMessageUpdate callback
1269+
const config = new IterableConfig();
1270+
config.logReactNativeSdkCalls = false;
1271+
// initialize Iterable object
1272+
Iterable.initialize('apiKey', config);
1273+
// WHEN handleEmbeddedMessageUpdateCalled event is emitted
1274+
// THEN no error should occur (no listener was set up)
1275+
expect(() => {
1276+
nativeEmitter.emit(
1277+
IterableEventName.handleEmbeddedMessageUpdateCalled
1278+
);
1279+
}).not.toThrow();
1280+
});
1281+
1282+
it('should call onEmbeddedMessageUpdate multiple times when event is emitted multiple times', () => {
1283+
// sets up event emitter
1284+
const nativeEmitter = new NativeEventEmitter();
1285+
nativeEmitter.removeAllListeners(
1286+
IterableEventName.handleEmbeddedMessageUpdateCalled
1287+
);
1288+
// sets up config with callback
1289+
const config = new IterableConfig();
1290+
config.logReactNativeSdkCalls = false;
1291+
config.onEmbeddedMessageUpdate = jest.fn();
1292+
// initialize Iterable object
1293+
Iterable.initialize('apiKey', config);
1294+
// WHEN handleEmbeddedMessageUpdateCalled event is emitted multiple times
1295+
nativeEmitter.emit(
1296+
IterableEventName.handleEmbeddedMessageUpdateCalled
1297+
);
1298+
nativeEmitter.emit(
1299+
IterableEventName.handleEmbeddedMessageUpdateCalled
1300+
);
1301+
nativeEmitter.emit(
1302+
IterableEventName.handleEmbeddedMessageUpdateCalled
1303+
);
1304+
// THEN onEmbeddedMessageUpdate callback is called three times
1305+
expect(config.onEmbeddedMessageUpdate).toHaveBeenCalledTimes(3);
1306+
});
1307+
1308+
it('should include onEmbeddedMessageUpdatePresent flag in config dict when callback is provided', () => {
1309+
// GIVEN a config with onEmbeddedMessageUpdate callback
1310+
const config = new IterableConfig();
1311+
config.onEmbeddedMessageUpdate = jest.fn();
1312+
// WHEN toDict is called
1313+
const configDict = config.toDict();
1314+
// THEN onEmbeddedMessageUpdatePresent is true
1315+
expect(configDict.onEmbeddedMessageUpdatePresent).toBe(true);
1316+
});
1317+
1318+
it('should set onEmbeddedMessageUpdatePresent flag to false when callback is not provided', () => {
1319+
// GIVEN a config without onEmbeddedMessageUpdate callback
1320+
const config = new IterableConfig();
1321+
// WHEN toDict is called
1322+
const configDict = config.toDict();
1323+
// THEN onEmbeddedMessageUpdatePresent is false
1324+
expect(configDict.onEmbeddedMessageUpdatePresent).toBe(false);
1325+
});
1326+
});
1327+
1328+
describe('onEmbeddedMessagingDisabled', () => {
1329+
it('should call onEmbeddedMessagingDisabled when handleEmbeddedMessagingDisabledCalled event is emitted', () => {
1330+
// sets up event emitter
1331+
const nativeEmitter = new NativeEventEmitter();
1332+
nativeEmitter.removeAllListeners(
1333+
IterableEventName.handleEmbeddedMessagingDisabledCalled
1334+
);
1335+
// sets up config file and onEmbeddedMessagingDisabled callback
1336+
const config = new IterableConfig();
1337+
config.logReactNativeSdkCalls = false;
1338+
config.onEmbeddedMessagingDisabled = jest.fn();
1339+
// initialize Iterable object
1340+
Iterable.initialize('apiKey', config);
1341+
// WHEN handleEmbeddedMessagingDisabledCalled event is emitted
1342+
nativeEmitter.emit(
1343+
IterableEventName.handleEmbeddedMessagingDisabledCalled
1344+
);
1345+
// THEN onEmbeddedMessagingDisabled callback is called
1346+
expect(config.onEmbeddedMessagingDisabled).toHaveBeenCalled();
1347+
expect(config.onEmbeddedMessagingDisabled).toHaveBeenCalledTimes(1);
1348+
});
1349+
1350+
it('should not set up listener if onEmbeddedMessagingDisabled is not provided', () => {
1351+
// sets up event emitter
1352+
const nativeEmitter = new NativeEventEmitter();
1353+
nativeEmitter.removeAllListeners(
1354+
IterableEventName.handleEmbeddedMessagingDisabledCalled
1355+
);
1356+
// sets up config without onEmbeddedMessagingDisabled callback
1357+
const config = new IterableConfig();
1358+
config.logReactNativeSdkCalls = false;
1359+
// initialize Iterable object
1360+
Iterable.initialize('apiKey', config);
1361+
// WHEN handleEmbeddedMessagingDisabledCalled event is emitted
1362+
// THEN no error should occur (no listener was set up)
1363+
expect(() => {
1364+
nativeEmitter.emit(
1365+
IterableEventName.handleEmbeddedMessagingDisabledCalled
1366+
);
1367+
}).not.toThrow();
1368+
});
1369+
1370+
it('should call onEmbeddedMessagingDisabled when embedded messaging becomes unavailable', () => {
1371+
// sets up event emitter
1372+
const nativeEmitter = new NativeEventEmitter();
1373+
nativeEmitter.removeAllListeners(
1374+
IterableEventName.handleEmbeddedMessagingDisabledCalled
1375+
);
1376+
// sets up config with callback
1377+
const config = new IterableConfig();
1378+
config.logReactNativeSdkCalls = false;
1379+
config.onEmbeddedMessagingDisabled = jest.fn();
1380+
// initialize Iterable object
1381+
Iterable.initialize('apiKey', config);
1382+
// WHEN handleEmbeddedMessagingDisabledCalled event is emitted
1383+
nativeEmitter.emit(
1384+
IterableEventName.handleEmbeddedMessagingDisabledCalled
1385+
);
1386+
// THEN onEmbeddedMessagingDisabled callback is called
1387+
expect(config.onEmbeddedMessagingDisabled).toHaveBeenCalled();
1388+
});
1389+
1390+
it('should include onEmbeddedMessagingDisabledPresent flag in config dict when callback is provided', () => {
1391+
// GIVEN a config with onEmbeddedMessagingDisabled callback
1392+
const config = new IterableConfig();
1393+
config.onEmbeddedMessagingDisabled = jest.fn();
1394+
// WHEN toDict is called
1395+
const configDict = config.toDict();
1396+
// THEN onEmbeddedMessagingDisabledPresent is true
1397+
expect(configDict.onEmbeddedMessagingDisabledPresent).toBe(true);
1398+
});
1399+
1400+
it('should set onEmbeddedMessagingDisabledPresent flag to false when callback is not provided', () => {
1401+
// GIVEN a config without onEmbeddedMessagingDisabled callback
1402+
const config = new IterableConfig();
1403+
// WHEN toDict is called
1404+
const configDict = config.toDict();
1405+
// THEN onEmbeddedMessagingDisabledPresent is false
1406+
expect(configDict.onEmbeddedMessagingDisabledPresent).toBe(false);
1407+
});
1408+
});
1409+
1410+
describe('both embedded callbacks', () => {
1411+
it('should call both callbacks independently when both are provided', () => {
1412+
// sets up event emitter
1413+
const nativeEmitter = new NativeEventEmitter();
1414+
nativeEmitter.removeAllListeners(
1415+
IterableEventName.handleEmbeddedMessageUpdateCalled
1416+
);
1417+
nativeEmitter.removeAllListeners(
1418+
IterableEventName.handleEmbeddedMessagingDisabledCalled
1419+
);
1420+
// sets up config with both callbacks
1421+
const config = new IterableConfig();
1422+
config.logReactNativeSdkCalls = false;
1423+
config.onEmbeddedMessageUpdate = jest.fn();
1424+
config.onEmbeddedMessagingDisabled = jest.fn();
1425+
// initialize Iterable object
1426+
Iterable.initialize('apiKey', config);
1427+
// WHEN handleEmbeddedMessageUpdateCalled event is emitted
1428+
nativeEmitter.emit(
1429+
IterableEventName.handleEmbeddedMessageUpdateCalled
1430+
);
1431+
// THEN only onEmbeddedMessageUpdate is called
1432+
expect(config.onEmbeddedMessageUpdate).toHaveBeenCalled();
1433+
expect(config.onEmbeddedMessagingDisabled).not.toHaveBeenCalled();
1434+
// Reset mocks
1435+
jest.clearAllMocks();
1436+
// WHEN handleEmbeddedMessagingDisabledCalled event is emitted
1437+
nativeEmitter.emit(
1438+
IterableEventName.handleEmbeddedMessagingDisabledCalled
1439+
);
1440+
// THEN only onEmbeddedMessagingDisabled is called
1441+
expect(config.onEmbeddedMessagingDisabled).toHaveBeenCalled();
1442+
expect(config.onEmbeddedMessageUpdate).not.toHaveBeenCalled();
1443+
});
1444+
1445+
it('should set both presence flags in config dict when both callbacks are provided', () => {
1446+
// GIVEN a config with both callbacks
1447+
const config = new IterableConfig();
1448+
config.onEmbeddedMessageUpdate = jest.fn();
1449+
config.onEmbeddedMessagingDisabled = jest.fn();
1450+
// WHEN toDict is called
1451+
const configDict = config.toDict();
1452+
// THEN both presence flags are true
1453+
expect(configDict.onEmbeddedMessageUpdatePresent).toBe(true);
1454+
expect(configDict.onEmbeddedMessagingDisabledPresent).toBe(true);
1455+
});
1456+
});
1457+
});
12321458
});

src/core/classes/Iterable.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,12 @@ export class Iterable {
950950
RNEventEmitter.removeAllListeners(
951951
IterableEventName.handleAuthFailureCalled
952952
);
953+
RNEventEmitter.removeAllListeners(
954+
IterableEventName.handleEmbeddedMessageUpdateCalled
955+
);
956+
RNEventEmitter.removeAllListeners(
957+
IterableEventName.handleEmbeddedMessagingDisabledCalled
958+
);
953959
}
954960

955961
/**
@@ -1083,6 +1089,26 @@ export class Iterable {
10831089
}
10841090
);
10851091
}
1092+
1093+
if (Iterable.savedConfig.enableEmbeddedMessaging) {
1094+
if (Iterable.savedConfig.onEmbeddedMessageUpdate) {
1095+
RNEventEmitter.addListener(
1096+
IterableEventName.handleEmbeddedMessageUpdateCalled,
1097+
() => {
1098+
Iterable.savedConfig.onEmbeddedMessageUpdate?.();
1099+
}
1100+
);
1101+
}
1102+
1103+
if (Iterable.savedConfig.onEmbeddedMessagingDisabled) {
1104+
RNEventEmitter.addListener(
1105+
IterableEventName.handleEmbeddedMessagingDisabledCalled,
1106+
() => {
1107+
Iterable.savedConfig.onEmbeddedMessagingDisabled?.();
1108+
}
1109+
);
1110+
}
1111+
}
10861112
}
10871113

10881114
/**

0 commit comments

Comments
 (0)