Skip to content

Commit 1014a49

Browse files
authored
Merge branch 'loren/embedded/MOB-12265-start-end-session' into loren/embedded/MOB-12264-android-sync-and-get-messages
2 parents 0984e9e + fd42bac commit 1014a49

File tree

13 files changed

+710
-74
lines changed

13 files changed

+710
-74
lines changed

CHANGELOG.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
## 2.2.0-alpha.1
2-
3-
### Updates
4-
* [SDK-149] Added logout functionality
5-
6-
### Fixes
7-
* [SDK-151] Fixed "cannot read property authtoken of undefined" error
8-
9-
10-
## 2.2.0-alpha.0
1+
## 2.2.0
112

123
### Updates
134
- Updated Android SDK version to [3.6.2](https://github.com/Iterable/iterable-android-sdk/releases/tag/3.6.2)
@@ -18,13 +9,19 @@
189
- Added `onJwtError` and `retryPolicy` for control over JWT flow
1910
- Moved all native calls to `IterableApi.ts`
2011
- Added JWT example to our example app
12+
- Changed `onJWTError` to `onJwtError`
13+
- Changed `IterableRetryBackoff` enum keys to be lowercase for consistency
14+
across application
15+
- [SDK-149] Added logout functionality
2116

2217
### Fixes
2318
- Created a standalone `IterableLogger` to avoid circular dependencies
19+
- [SDK-151] Fixed "cannot read property authtoken of undefined" error
20+
- Fixed Android `retryInterval` not being updated on re-initialization.
2421

2522
## 2.1.0
2623
### Updates
27-
* SDK is now compatible with both New Architecture and Legacy Architecture. Fix
24+
- SDK is now compatible with both New Architecture and Legacy Architecture. Fix
2825
for #691, #602, #563.
2926

3027
### Fixes

android/src/main/java/com/iterable/reactnative/RNIterableAPIModuleImpl.java

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.iterable.iterableapi.IterableApi;
2727
import com.iterable.iterableapi.IterableAttributionInfo;
2828
import com.iterable.iterableapi.IterableAuthHandler;
29+
import com.iterable.iterableapi.IterableAuthManager;
2930
import com.iterable.iterableapi.IterableConfig;
3031
import com.iterable.iterableapi.IterableCustomActionHandler;
3132
import com.iterable.iterableapi.IterableEmbeddedMessage;
@@ -91,11 +92,36 @@ public void initializeWithApiKey(String apiKey, ReadableMap configReadableMap, S
9192
configBuilder.setAuthHandler(this);
9293
}
9394

94-
if (configReadableMap.hasKey("enableEmbeddedMessaging")) {
95-
configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging"));
95+
IterableConfig config = configBuilder.build();
96+
IterableApi.initialize(reactContext, apiKey, config);
97+
98+
// Update retry policy on existing authManager if it was already created
99+
// This fixes the issue where retryInterval is not respected after
100+
// re-initialization
101+
// TODO [SDK-197]: Fix the root cause of this issue, instead of this hack
102+
try {
103+
// Use reflection to access package-private fields and methods
104+
java.lang.reflect.Field configRetryPolicyField = config.getClass().getDeclaredField("retryPolicy");
105+
configRetryPolicyField.setAccessible(true);
106+
Object retryPolicy = configRetryPolicyField.get(config);
107+
108+
if (retryPolicy != null) {
109+
java.lang.reflect.Method getAuthManagerMethod = IterableApi.getInstance().getClass().getDeclaredMethod("getAuthManager");
110+
getAuthManagerMethod.setAccessible(true);
111+
IterableAuthManager authManager = (IterableAuthManager) getAuthManagerMethod.invoke(IterableApi.getInstance());
112+
113+
if (authManager != null) {
114+
// Update the retry policy field on the authManager
115+
java.lang.reflect.Field authRetryPolicyField = authManager.getClass().getDeclaredField("authRetryPolicy");
116+
authRetryPolicyField.setAccessible(true);
117+
authRetryPolicyField.set(authManager, retryPolicy);
118+
IterableLogger.d(TAG, "Updated retry policy on existing authManager");
119+
}
120+
}
121+
} catch (Exception e) {
122+
IterableLogger.e(TAG, "Failed to update retry policy: " + e.getMessage());
96123
}
97124

98-
IterableApi.initialize(reactContext, apiKey, configBuilder.build());
99125
IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version);
100126

101127
IterableApi.getInstance().getInAppManager().addListener(this);
@@ -126,15 +152,40 @@ public void initialize2WithApiKey(String apiKey, ReadableMap configReadableMap,
126152
configBuilder.setAuthHandler(this);
127153
}
128154

129-
if (configReadableMap.hasKey("enableEmbeddedMessaging")) {
130-
configBuilder.setEnableEmbeddedMessaging(configReadableMap.getBoolean("enableEmbeddedMessaging"));
131-
}
132-
133155
// NOTE: There does not seem to be a way to set the API endpoint
134156
// override in the Android SDK. Check with @Ayyanchira and @evantk91 to
135157
// see what the best approach is.
136158

137-
IterableApi.initialize(reactContext, apiKey, configBuilder.build());
159+
IterableConfig config = configBuilder.build();
160+
IterableApi.initialize(reactContext, apiKey, config);
161+
162+
// Update retry policy on existing authManager if it was already created
163+
// This fixes the issue where retryInterval is not respected after
164+
// re-initialization
165+
// TODO [SDK-197]: Fix the root cause of this issue, instead of this hack
166+
try {
167+
// Use reflection to access package-private fields and methods
168+
java.lang.reflect.Field configRetryPolicyField = config.getClass().getDeclaredField("retryPolicy");
169+
configRetryPolicyField.setAccessible(true);
170+
Object retryPolicy = configRetryPolicyField.get(config);
171+
172+
if (retryPolicy != null) {
173+
java.lang.reflect.Method getAuthManagerMethod = IterableApi.getInstance().getClass().getDeclaredMethod("getAuthManager");
174+
getAuthManagerMethod.setAccessible(true);
175+
IterableAuthManager authManager = (IterableAuthManager) getAuthManagerMethod.invoke(IterableApi.getInstance());
176+
177+
if (authManager != null) {
178+
// Update the retry policy field on the authManager
179+
java.lang.reflect.Field authRetryPolicyField = authManager.getClass().getDeclaredField("authRetryPolicy");
180+
authRetryPolicyField.setAccessible(true);
181+
authRetryPolicyField.set(authManager, retryPolicy);
182+
IterableLogger.d(TAG, "Updated retry policy on existing authManager");
183+
}
184+
}
185+
} catch (Exception e) {
186+
IterableLogger.e(TAG, "Failed to update retry policy: " + e.getMessage());
187+
}
188+
138189
IterableApi.getInstance().setDeviceAttribute("reactNativeSDKVersion", version);
139190

140191
IterableApi.getInstance().getInAppManager().addListener(this);

example/ios/ReactNativeSdkExample/Info.plist

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,5 @@
5252
</array>
5353
<key>UIViewControllerBasedStatusBarAppearance</key>
5454
<false/>
55-
<key>UIAppFonts</key>
56-
<array>
57-
<string>AntDesign.ttf</string>
58-
<string>Entypo.ttf</string>
59-
<string>EvilIcons.ttf</string>
60-
<string>Feather.ttf</string>
61-
<string>FontAwesome.ttf</string>
62-
<string>FontAwesome5_Brands.ttf</string>
63-
<string>FontAwesome5_Regular.ttf</string>
64-
<string>FontAwesome5_Solid.ttf</string>
65-
<string>FontAwesome6_Brands.ttf</string>
66-
<string>FontAwesome6_Regular.ttf</string>
67-
<string>FontAwesome6_Solid.ttf</string>
68-
<string>Foundation.ttf</string>
69-
<string>Ionicons.ttf</string>
70-
<string>MaterialIcons.ttf</string>
71-
<string>MaterialCommunityIcons.ttf</string>
72-
<string>SimpleLineIcons.ttf</string>
73-
<string>Octicons.ttf</string>
74-
<string>Zocial.ttf</string>
75-
<string>Fontisto.ttf</string>
76-
</array>
7755
</dict>
7856
</plist>

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"@babel/core": "^7.25.2",
2626
"@babel/preset-env": "^7.25.3",
2727
"@babel/runtime": "^7.25.0",
28-
"@react-native-community/cli": "18.0.0",
28+
"@react-native-community/cli": "18.0.1",
2929
"@react-native-community/cli-platform-android": "18.0.0",
3030
"@react-native-community/cli-platform-ios": "18.0.0",
3131
"@react-native/babel-preset": "0.79.3",

example/src/hooks/useIterableApp.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
8989

9090
const getIsEmail = (id: string) => EMAIL_REGEX.test(id);
9191

92+
let lastTimeStamp = 0;
93+
9294
export const IterableAppProvider: FunctionComponent<
9395
React.PropsWithChildren<unknown>
9496
> = ({ children }) => {
@@ -147,17 +149,15 @@ export const IterableAppProvider: FunctionComponent<
147149

148150
const initialize = useCallback(
149151
(navigation: Navigation) => {
150-
if (getUserId()) {
151-
login();
152-
}
152+
logout();
153153

154154
const config = new IterableConfig();
155155

156156
config.inAppDisplayInterval = 1.0; // Min gap between in-apps. No need to set this in production.
157157

158158
config.retryPolicy = {
159159
maxRetry: 5,
160-
retryInterval: 10,
160+
retryInterval: 5,
161161
retryBackoff: IterableRetryBackoff.linear,
162162
};
163163

@@ -209,8 +209,16 @@ export const IterableAppProvider: FunctionComponent<
209209
process.env.ITBL_JWT_SECRET
210210
) {
211211
config.authHandler = async () => {
212+
console.group('authHandler');
213+
const now = Date.now();
214+
if (lastTimeStamp !== 0) {
215+
console.log('Time since last call:', now - lastTimeStamp);
216+
}
217+
lastTimeStamp = now;
218+
console.groupEnd();
219+
220+
// return 'InvalidToken'; // Uncomment this to test the failure callback
212221
const token = await getJwtToken();
213-
// return 'SomethingNotValid'; // Uncomment this to test the failure callback
214222
return token;
215223
};
216224
}
@@ -229,6 +237,10 @@ export const IterableAppProvider: FunctionComponent<
229237
.then((isSuccessful) => {
230238
setIsInitialized(isSuccessful);
231239

240+
if (isSuccessful && getUserId()) {
241+
return login();
242+
}
243+
232244
if (!isSuccessful) {
233245
return Promise.reject('`Iterable.initialize` failed');
234246
}
@@ -252,6 +264,8 @@ export const IterableAppProvider: FunctionComponent<
252264
const logout = useCallback(() => {
253265
Iterable.setEmail(null);
254266
Iterable.setUserId(null);
267+
Iterable.logout();
268+
lastTimeStamp = 0;
255269
setIsLoggedIn(false);
256270
}, []);
257271

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@iterable/react-native-sdk",
3-
"version": "2.2.0-alpha.1",
3+
"version": "2.2.0",
44
"description": "Iterable SDK for React Native.",
55
"source": "./src/index.tsx",
66
"main": "./lib/module/index.js",

src/__mocks__/MockRNIterableAPI.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,28 @@ export class MockRNIterableAPI {
8686
});
8787
}
8888

89+
static async getInboxMessages(): Promise<IterableInAppMessage[] | undefined> {
90+
return await new Promise((resolve) => {
91+
// Filter messages that are marked for inbox
92+
const inboxMessages =
93+
MockRNIterableAPI.messages?.filter((msg) => msg.saveToInbox) || [];
94+
resolve(inboxMessages);
95+
});
96+
}
97+
98+
static async getHtmlInAppContentForMessage(
99+
messageId: string
100+
): Promise<unknown> {
101+
return await new Promise((resolve) => {
102+
// Mock HTML content for testing
103+
const mockHtmlContent = {
104+
edgeInsets: { top: 10, left: 20, bottom: 30, right: 40 },
105+
html: `<div>Mock HTML content for message ${messageId}</div>`,
106+
};
107+
resolve(mockHtmlContent);
108+
});
109+
}
110+
89111
static setAutoDisplayPaused = jest.fn();
90112

91113
static showMessage = jest.fn(
@@ -113,22 +135,12 @@ export class MockRNIterableAPI {
113135

114136
static updateSubscriptions = jest.fn();
115137

116-
static getInboxMessages = jest.fn(
117-
async (): Promise<IterableInAppMessage[] | undefined> => {
118-
return await new Promise((resolve) => {
119-
resolve(MockRNIterableAPI.messages);
120-
});
121-
}
122-
);
123-
124138
static startSession = jest.fn();
125139

126140
static endSession = jest.fn();
127141

128142
static updateVisibleRows = jest.fn();
129143

130-
static getHtmlInAppContentForMessage = jest.fn();
131-
132144
static startEmbeddedSession = jest.fn();
133145

134146
static endEmbeddedSession = jest.fn();

src/core/classes/Iterable.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -938,10 +938,16 @@ export class Iterable {
938938
private static removeAllEventListeners() {
939939
RNEventEmitter.removeAllListeners(IterableEventName.handleUrlCalled);
940940
RNEventEmitter.removeAllListeners(IterableEventName.handleInAppCalled);
941-
RNEventEmitter.removeAllListeners(IterableEventName.handleCustomActionCalled);
941+
RNEventEmitter.removeAllListeners(
942+
IterableEventName.handleCustomActionCalled
943+
);
942944
RNEventEmitter.removeAllListeners(IterableEventName.handleAuthCalled);
943-
RNEventEmitter.removeAllListeners(IterableEventName.handleAuthSuccessCalled);
944-
RNEventEmitter.removeAllListeners(IterableEventName.handleAuthFailureCalled);
945+
RNEventEmitter.removeAllListeners(
946+
IterableEventName.handleAuthSuccessCalled
947+
);
948+
RNEventEmitter.removeAllListeners(
949+
IterableEventName.handleAuthFailureCalled
950+
);
945951
}
946952

947953
/**
@@ -1041,11 +1047,15 @@ export class Iterable {
10411047
}
10421048
}, 1000);
10431049
} else if (typeof promiseResult === 'string') {
1044-
//If promise only returns string
1050+
// If promise only returns string
1051+
Iterable.authManager.passAlongAuthToken(promiseResult);
1052+
} else if (promiseResult === null || promiseResult === undefined) {
1053+
// Even though this will cause authentication to fail, we want to
1054+
// allow for this for JWT handling.
10451055
Iterable.authManager.passAlongAuthToken(promiseResult);
10461056
} else {
10471057
IterableLogger?.log(
1048-
'Unexpected promise returned. Auth token expects promise of String or AuthResponse type.'
1058+
'Unexpected promise returned. Auth token expects promise of String, null, undefined, or AuthResponse type.'
10491059
);
10501060
}
10511061
})

src/core/classes/IterableApi.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ describe('IterableApi', () => {
730730
const result = await IterableApi.getInboxMessages();
731731

732732
// THEN the messages are returned
733-
expect(result).toBe(mockMessages);
733+
expect(result).toStrictEqual(mockMessages);
734734
});
735735
});
736736

src/core/classes/IterableConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,9 @@ export class IterableConfig {
202202
* ```
203203
*
204204
* @returns A promise that resolves to an `IterableAuthResponse`, a `string`,
205-
* or `undefined`.
205+
* `null`, or `undefined`.
206206
*/
207-
authHandler?: () => Promise<IterableAuthResponse | string | undefined>;
207+
authHandler?: () => Promise<IterableAuthResponse | string | null | undefined>;
208208

209209
/**
210210
* A callback function that is called when the SDK encounters an error while

0 commit comments

Comments
 (0)