Skip to content

Commit 45ef4eb

Browse files
committed
feat: implement handleClick method in IterableEmbeddedManager
1 parent 00506f0 commit 45ef4eb

9 files changed

Lines changed: 196 additions & 25 deletions

File tree

example/src/components/Embedded/Embedded.tsx

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
22
import { useCallback, useState } from 'react';
33
import {
44
Iterable,
5+
type IterableAction,
56
type IterableEmbeddedMessage,
67
} from '@iterable/react-native-sdk';
78

@@ -68,6 +69,18 @@ export const Embedded = () => {
6869
[]
6970
);
7071

72+
const handleClick = useCallback(
73+
(
74+
message: IterableEmbeddedMessage,
75+
buttonId: string | null,
76+
action?: IterableAction | null
77+
) => {
78+
console.log(`handleClick:`, message);
79+
Iterable.embeddedManager.handleClick(message, buttonId, action);
80+
},
81+
[]
82+
);
83+
7184
return (
7285
<View style={styles.container}>
7386
<Text style={styles.text}>EMBEDDED</Text>
@@ -104,7 +117,9 @@ export const Embedded = () => {
104117
{embeddedMessages.map((message) => (
105118
<View key={message.metadata.messageId}>
106119
<View style={styles.embeddedTitleContainer}>
107-
<Text style={styles.embeddedTitle}>Embedded message | </Text>
120+
<Text style={styles.embeddedTitle}>Embedded message</Text>
121+
</View>
122+
<View style={styles.embeddedTitleContainer}>
108123
<TouchableOpacity
109124
onPress={() => startEmbeddedImpression(message)}
110125
>
@@ -116,15 +131,42 @@ export const Embedded = () => {
116131
>
117132
<Text style={styles.link}>Pause impression</Text>
118133
</TouchableOpacity>
134+
<Text style={styles.embeddedTitle}> | </Text>
135+
<TouchableOpacity
136+
onPress={() =>
137+
handleClick(message, null, message.elements?.defaultAction)
138+
}
139+
>
140+
<Text style={styles.link}>Handle click</Text>
141+
</TouchableOpacity>
119142
</View>
120143

121144
<Text>metadata.messageId: {message.metadata.messageId}</Text>
122145
<Text>metadata.placementId: {message.metadata.placementId}</Text>
123146
<Text>elements.title: {message.elements?.title}</Text>
124147
<Text>elements.body: {message.elements?.body}</Text>
148+
<Text>
149+
elements.defaultAction.data:{' '}
150+
{message.elements?.defaultAction?.data}
151+
</Text>
152+
<Text>
153+
elements.defaultAction.type:{' '}
154+
{message.elements?.defaultAction?.type}
155+
</Text>
125156
{(message.elements?.buttons ?? []).map((button, buttonIndex) => (
126157
<View key={`${button.id}-${buttonIndex}`}>
127-
<Text>Button {buttonIndex + 1}</Text>
158+
<View style={styles.embeddedTitleContainer}>
159+
<Text>Button {buttonIndex + 1}</Text>
160+
<Text style={styles.embeddedTitle}> | </Text>
161+
<TouchableOpacity
162+
onPress={() =>
163+
handleClick(message, button.id, button.action)
164+
}
165+
>
166+
<Text style={styles.link}>Handle click</Text>
167+
</TouchableOpacity>
168+
</View>
169+
128170
<Text>button.id: {button.id}</Text>
129171
<Text>button.title: {button.title}</Text>
130172
<Text>button.action?.data: {button.action?.data}</Text>

src/core/classes/Iterable.ts

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Linking, NativeEventEmitter, Platform } from 'react-native';
1+
import { NativeEventEmitter, Platform } from 'react-native';
22

33
import { buildInfo } from '../../itblBuildInfo';
44

55
import { RNIterableAPI } from '../../api';
6+
import { IterableEmbeddedManager } from '../../embedded/classes/IterableEmbeddedManager';
67
import { IterableInAppManager } from '../../inApp/classes/IterableInAppManager';
78
import { IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage';
89
import { IterableInAppCloseSource } from '../../inApp/enums/IterableInAppCloseSource';
@@ -11,6 +12,7 @@ import { IterableInAppLocation } from '../../inApp/enums/IterableInAppLocation';
1112
import { IterableAuthResponseResult } from '../enums/IterableAuthResponseResult';
1213
import { IterableEventName } from '../enums/IterableEventName';
1314
import type { IterableAuthFailure } from '../types/IterableAuthFailure';
15+
import { callUrlHandler } from '../utils/callUrlHandler';
1416
import { IterableAction } from './IterableAction';
1517
import { IterableActionContext } from './IterableActionContext';
1618
import { IterableApi } from './IterableApi';
@@ -20,10 +22,11 @@ import { IterableAuthResponse } from './IterableAuthResponse';
2022
import type { IterableCommerceItem } from './IterableCommerceItem';
2123
import { IterableConfig } from './IterableConfig';
2224
import { IterableLogger } from './IterableLogger';
23-
import { IterableEmbeddedManager } from '../../embedded/classes/IterableEmbeddedManager';
2425

2526
const RNEventEmitter = new NativeEventEmitter(RNIterableAPI);
2627

28+
const defaultConfig = new IterableConfig();
29+
2730
/* eslint-disable tsdoc/syntax */
2831
/**
2932
* The main class for the Iterable React Native SDK.
@@ -46,7 +49,7 @@ export class Iterable {
4649
/**
4750
* Current configuration of the Iterable SDK
4851
*/
49-
static savedConfig: IterableConfig = new IterableConfig();
52+
static savedConfig: IterableConfig = defaultConfig;
5053

5154
/**
5255
* In-app message manager for the current user.
@@ -98,8 +101,9 @@ export class Iterable {
98101
* });
99102
* ```
100103
*/
101-
static embeddedManager: IterableEmbeddedManager =
102-
new IterableEmbeddedManager();
104+
static embeddedManager: IterableEmbeddedManager = new IterableEmbeddedManager(
105+
defaultConfig
106+
);
103107

104108
/**
105109
* Initializes the Iterable React Native SDK in your app's Javascript or Typescript code.
@@ -177,6 +181,8 @@ export class Iterable {
177181

178182
IterableLogger.setLoggingEnabled(config.logReactNativeSdkCalls ?? true);
179183
IterableLogger.setLogLevel(config.logLevel);
184+
185+
Iterable.embeddedManager = new IterableEmbeddedManager(config);
180186
}
181187

182188
this.setupEventHandlers();
@@ -933,10 +939,10 @@ export class Iterable {
933939
if (Platform.OS === 'android') {
934940
//Give enough time for Activity to wake up.
935941
setTimeout(() => {
936-
callUrlHandler(url, context);
942+
callUrlHandler(Iterable.savedConfig, url, context);
937943
}, 1000);
938944
} else {
939-
callUrlHandler(url, context);
945+
callUrlHandler(Iterable.savedConfig, url, context);
940946
}
941947
});
942948
}
@@ -1031,22 +1037,6 @@ export class Iterable {
10311037
}
10321038
);
10331039
}
1034-
1035-
function callUrlHandler(url: string, context: IterableActionContext) {
1036-
// MOB-10424: Figure out if this is purposeful
1037-
// eslint-disable-next-line eqeqeq
1038-
if (Iterable.savedConfig.urlHandler?.(url, context) == false) {
1039-
Linking.canOpenURL(url)
1040-
.then((canOpen) => {
1041-
if (canOpen) {
1042-
Linking.openURL(url);
1043-
}
1044-
})
1045-
.catch((reason) => {
1046-
IterableLogger?.log('could not open url: ' + reason);
1047-
});
1048-
}
1049-
}
10501040
}
10511041

10521042
/**

src/core/enums/IterableActionSource.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ export enum IterableActionSource {
88
appLink = 1,
99
/** The action source was an in-app message */
1010
inApp = 2,
11+
/** The action source was an embedded message */
12+
embedded = 3,
1113
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Enum representing the prefix of build-in custom action URL.
3+
*/
4+
export enum IterableCustomActionPrefix {
5+
/** Current action prefix */
6+
Action = 'action://',
7+
/** Deprecated action prefix */
8+
Itbl = 'itbl://',
9+
}

src/core/enums/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './IterableEventName';
66
export * from './IterableLogLevel';
77
export * from './IterablePushPlatform';
88
export * from './IterableRetryBackoff';
9+
export * from './IterableCustomActionPrefix';

src/core/utils/callUrlHandler.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Linking } from 'react-native';
2+
import type { IterableActionContext } from '../classes/IterableActionContext';
3+
import { IterableLogger } from '../classes/IterableLogger';
4+
import type { IterableConfig } from '../classes/IterableConfig';
5+
6+
/**
7+
* Calls the URL handler and attempts to open the URL if the handler returns false.
8+
*
9+
* @param config - The config to use.
10+
* @param url - The URL to call.
11+
* @param context - The context to use.
12+
*/
13+
export function callUrlHandler(
14+
config: IterableConfig,
15+
url: string,
16+
context: IterableActionContext
17+
) {
18+
// MOB-10424: Figure out if this is purposeful
19+
// eslint-disable-next-line eqeqeq
20+
if (config.urlHandler?.(url, context) == false) {
21+
Linking.canOpenURL(url)
22+
.then((canOpen) => {
23+
if (canOpen) {
24+
Linking.openURL(url);
25+
}
26+
})
27+
.catch((reason) => {
28+
IterableLogger?.log('could not open url: ' + reason);
29+
});
30+
}
31+
}

src/core/utils/getActionPrefix.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { IterableCustomActionPrefix } from '../enums/IterableCustomActionPrefix';
2+
3+
/**
4+
* Gets the action prefix from a string.
5+
*
6+
* @param str - The string to get the action prefix from.
7+
* @returns The action prefix.
8+
*/
9+
export const getActionPrefix = (
10+
str?: string | null
11+
): IterableCustomActionPrefix | null => {
12+
if (!str) return null;
13+
if (str.startsWith(IterableCustomActionPrefix.Action)) {
14+
return IterableCustomActionPrefix.Action;
15+
}
16+
if (str.startsWith(IterableCustomActionPrefix.Itbl)) {
17+
return IterableCustomActionPrefix.Itbl;
18+
}
19+
return null;
20+
};

src/core/utils/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './getActionPrefix';
2+
export * from './callUrlHandler';

src/embedded/classes/IterableEmbeddedManager.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1+
import { IterableAction } from '../../core/classes/IterableAction';
2+
import { IterableActionContext } from '../../core/classes/IterableActionContext';
13
import { IterableApi } from '../../core/classes/IterableApi';
4+
import { IterableConfig } from '../../core/classes/IterableConfig';
5+
import { IterableLogger } from '../../core/classes/IterableLogger';
6+
import { IterableActionSource } from '../../core/enums/IterableActionSource';
7+
import { callUrlHandler } from '../../core/utils/callUrlHandler';
8+
import { getActionPrefix } from '../../core/utils/getActionPrefix';
29
import type { IterableEmbeddedMessage } from '../types/IterableEmbeddedMessage';
310

411
/**
@@ -21,6 +28,15 @@ export class IterableEmbeddedManager {
2128
*/
2229
isEnabled = false;
2330

31+
/**
32+
* The config for the Iterable SDK.
33+
*/
34+
config: IterableConfig = new IterableConfig();
35+
36+
constructor(config: IterableConfig) {
37+
this.config = config;
38+
}
39+
2440
/**
2541
* Syncs embedded local cache with the server.
2642
*
@@ -165,4 +181,62 @@ export class IterableEmbeddedManager {
165181
) {
166182
return IterableApi.trackEmbeddedClick(message, buttonId, clickedUrl);
167183
}
184+
185+
/**
186+
* Handles a click on an embedded message.
187+
*
188+
* This will fire the correct handers set in the config, and will track the
189+
* click. It should be use on either a button click or a click on the message itself.
190+
*
191+
* @param message - The embedded message.
192+
* @param buttonId - The button ID.
193+
* @param clickedUrl - The clicked URL.
194+
*
195+
* @example
196+
* ```typescript
197+
* IterableEmbeddedManager.handleClick(message, buttonId, clickedUrl);
198+
* ```
199+
*/
200+
handleClick(
201+
message: IterableEmbeddedMessage,
202+
buttonId: string | null,
203+
action?: IterableAction | null
204+
) {
205+
const { data, type: actionType } = action ?? {};
206+
const clickedUrl = data && data?.length > 0 ? data : actionType;
207+
208+
IterableLogger.log(
209+
'IterableEmbeddedManager.handleClick',
210+
message,
211+
buttonId,
212+
clickedUrl
213+
);
214+
215+
if (!clickedUrl) {
216+
IterableLogger.log(
217+
'IterableEmbeddedManager.handleClick:',
218+
'A url or action is required to handle an embedded click',
219+
clickedUrl
220+
);
221+
return;
222+
}
223+
224+
const actionPrefix = getActionPrefix(clickedUrl);
225+
const source = IterableActionSource.embedded;
226+
227+
this.trackClick(message, buttonId, clickedUrl);
228+
229+
if (actionPrefix) {
230+
const actionName = clickedUrl?.replace(actionPrefix, '');
231+
const actionDetails = new IterableAction(actionName, '', '');
232+
const context = new IterableActionContext(actionDetails, source);
233+
if (this.config.customActionHandler) {
234+
this.config.customActionHandler(actionDetails, context);
235+
}
236+
} else {
237+
const actionDetails = new IterableAction('openUrl', clickedUrl, '');
238+
const context = new IterableActionContext(actionDetails, source);
239+
callUrlHandler(this.config, clickedUrl, context);
240+
}
241+
}
168242
}

0 commit comments

Comments
 (0)