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
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,16 @@ public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise pr
}
}

public void trackEmbeddedClick(ReadableMap messageMap, String buttonId, String clickedUrl) {
IterableLogger.d(TAG, "trackEmbeddedClick: buttonId: " + buttonId + " clickedUrl: " + clickedUrl);
IterableEmbeddedMessage message = Serialization.embeddedMessageFromReadableMap(messageMap);
if (message != null) {
IterableApi.getInstance().trackEmbeddedClick(message, buttonId, clickedUrl);
} else {
IterableLogger.e(TAG, "Failed to convert message map to IterableEmbeddedMessage");
}
}

// ---------------------------------------------------------------------------------------
// endregion
}
Expand Down
16 changes: 16 additions & 0 deletions android/src/main/java/com/iterable/reactnative/Serialization.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ static JSONArray serializeEmbeddedMessages(List<IterableEmbeddedMessage> embedde
return embeddedMessagesJson;
}

/**
* Converts a ReadableMap to an IterableEmbeddedMessage.
*
* This is needed as in new arch you can only pass in basic types, which
* then need to be converted in the native layer.
*/
static IterableEmbeddedMessage embeddedMessageFromReadableMap(ReadableMap messageMap) {
try {
JSONObject messageJson = convertMapToJson(messageMap);
return IterableEmbeddedMessage.Companion.fromJSONObject(messageJson);
} catch (JSONException e) {
IterableLogger.e(TAG, "Failed to convert ReadableMap to IterableEmbeddedMessage: " + e.getLocalizedMessage());
return null;
}
}

static IterableConfig.Builder getConfigFromReadableMap(ReadableMap iterableContextMap) {
try {
JSONObject iterableContextJSON = convertMapToJson(iterableContextMap);
Expand Down
5 changes: 5 additions & 0 deletions android/src/newarch/java/com/RNIterableAPIModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise pr
moduleImpl.getEmbeddedMessages(placementIds, promise);
}

@Override
public void trackEmbeddedClick(ReadableMap message, String buttonId, String clickedUrl) {
moduleImpl.trackEmbeddedClick(message, buttonId, clickedUrl);
}

public void sendEvent(@NonNull String eventName, @Nullable Object eventData) {
moduleImpl.sendEvent(eventName, eventData);
}
Expand Down
5 changes: 5 additions & 0 deletions android/src/oldarch/java/com/RNIterableAPIModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,11 @@ public void getEmbeddedMessages(@Nullable ReadableArray placementIds, Promise pr
moduleImpl.getEmbeddedMessages(placementIds, promise);
}

@ReactMethod
public void trackEmbeddedClick(ReadableMap message, String buttonId, String clickedUrl) {
moduleImpl.trackEmbeddedClick(message, buttonId, clickedUrl);
}

public void sendEvent(@NonNull String eventName, @Nullable Object eventData) {
moduleImpl.sendEvent(eventName, eventData);
}
Expand Down
46 changes: 44 additions & 2 deletions example/src/components/Embedded/Embedded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { useCallback, useState } from 'react';
import {
Iterable,
type IterableAction,
type IterableEmbeddedMessage,
} from '@iterable/react-native-sdk';

Expand Down Expand Up @@ -68,6 +69,18 @@ export const Embedded = () => {
[]
);

const handleClick = useCallback(
(
message: IterableEmbeddedMessage,
buttonId: string | null,
action?: IterableAction | null
) => {
console.log(`handleClick:`, message);
Iterable.embeddedManager.handleClick(message, buttonId, action);
},
[]
);

return (
<View style={styles.container}>
<Text style={styles.text}>EMBEDDED</Text>
Expand Down Expand Up @@ -104,7 +117,9 @@ export const Embedded = () => {
{embeddedMessages.map((message) => (
<View key={message.metadata.messageId}>
<View style={styles.embeddedTitleContainer}>
<Text style={styles.embeddedTitle}>Embedded message | </Text>
<Text style={styles.embeddedTitle}>Embedded message</Text>
</View>
<View style={styles.embeddedTitleContainer}>
<TouchableOpacity
onPress={() => startEmbeddedImpression(message)}
>
Expand All @@ -116,15 +131,42 @@ export const Embedded = () => {
>
<Text style={styles.link}>Pause impression</Text>
</TouchableOpacity>
<Text style={styles.embeddedTitle}> | </Text>
<TouchableOpacity
onPress={() =>
handleClick(message, null, message.elements?.defaultAction)
}
>
<Text style={styles.link}>Handle click</Text>
</TouchableOpacity>
</View>

<Text>metadata.messageId: {message.metadata.messageId}</Text>
<Text>metadata.placementId: {message.metadata.placementId}</Text>
<Text>elements.title: {message.elements?.title}</Text>
<Text>elements.body: {message.elements?.body}</Text>
<Text>
elements.defaultAction.data:{' '}
{message.elements?.defaultAction?.data}
</Text>
<Text>
elements.defaultAction.type:{' '}
{message.elements?.defaultAction?.type}
</Text>
{(message.elements?.buttons ?? []).map((button, buttonIndex) => (
<View key={`${button.id}-${buttonIndex}`}>
<Text>Button {buttonIndex + 1}</Text>
<View style={styles.embeddedTitleContainer}>
<Text>Button {buttonIndex + 1}</Text>
<Text style={styles.embeddedTitle}> | </Text>
<TouchableOpacity
onPress={() =>
handleClick(message, button.id, button.action)
}
>
<Text style={styles.link}>Handle click</Text>
</TouchableOpacity>
</View>

<Text>button.id: {button.id}</Text>
<Text>button.title: {button.title}</Text>
<Text>button.action?.data: {button.action?.data}</Text>
Expand Down
5 changes: 5 additions & 0 deletions src/api/NativeRNIterableAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ export interface Spec extends TurboModule {
getEmbeddedMessages(
placementIds: number[] | null
): Promise<EmbeddedMessage[]>;
trackEmbeddedClick(
message: EmbeddedMessage,
buttonId: string | null,
clickedUrl: string | null
): void;

// Wake app -- android only
wakeApp(): void;
Expand Down
36 changes: 13 additions & 23 deletions src/core/classes/Iterable.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Linking, NativeEventEmitter, Platform } from 'react-native';
import { NativeEventEmitter, Platform } from 'react-native';

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

import { RNIterableAPI } from '../../api';
import { IterableEmbeddedManager } from '../../embedded/classes/IterableEmbeddedManager';
import { IterableInAppManager } from '../../inApp/classes/IterableInAppManager';
import { IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage';
import { IterableInAppCloseSource } from '../../inApp/enums/IterableInAppCloseSource';
Expand All @@ -11,6 +12,7 @@ import { IterableInAppLocation } from '../../inApp/enums/IterableInAppLocation';
import { IterableAuthResponseResult } from '../enums/IterableAuthResponseResult';
import { IterableEventName } from '../enums/IterableEventName';
import type { IterableAuthFailure } from '../types/IterableAuthFailure';
import { callUrlHandler } from '../utils/callUrlHandler';
import { IterableAction } from './IterableAction';
import { IterableActionContext } from './IterableActionContext';
import { IterableApi } from './IterableApi';
Expand All @@ -20,10 +22,11 @@ import { IterableAuthResponse } from './IterableAuthResponse';
import type { IterableCommerceItem } from './IterableCommerceItem';
import { IterableConfig } from './IterableConfig';
import { IterableLogger } from './IterableLogger';
import { IterableEmbeddedManager } from '../../embedded/classes/IterableEmbeddedManager';

const RNEventEmitter = new NativeEventEmitter(RNIterableAPI);

const defaultConfig = new IterableConfig();

/* eslint-disable tsdoc/syntax */
/**
* The main class for the Iterable React Native SDK.
Expand All @@ -46,7 +49,7 @@ export class Iterable {
/**
* Current configuration of the Iterable SDK
*/
static savedConfig: IterableConfig = new IterableConfig();
static savedConfig: IterableConfig = defaultConfig;

/**
* In-app message manager for the current user.
Expand Down Expand Up @@ -98,8 +101,9 @@ export class Iterable {
* });
* ```
*/
static embeddedManager: IterableEmbeddedManager =
new IterableEmbeddedManager();
static embeddedManager: IterableEmbeddedManager = new IterableEmbeddedManager(
defaultConfig
);

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

IterableLogger.setLoggingEnabled(config.logReactNativeSdkCalls ?? true);
IterableLogger.setLogLevel(config.logLevel);

Iterable.embeddedManager = new IterableEmbeddedManager(config);
}

this.setupEventHandlers();
Expand Down Expand Up @@ -933,10 +939,10 @@ export class Iterable {
if (Platform.OS === 'android') {
//Give enough time for Activity to wake up.
setTimeout(() => {
callUrlHandler(url, context);
callUrlHandler(Iterable.savedConfig, url, context);
}, 1000);
} else {
callUrlHandler(url, context);
callUrlHandler(Iterable.savedConfig, url, context);
}
});
}
Expand Down Expand Up @@ -1031,22 +1037,6 @@ export class Iterable {
}
);
}

function callUrlHandler(url: string, context: IterableActionContext) {
// MOB-10424: Figure out if this is purposeful
// eslint-disable-next-line eqeqeq
if (Iterable.savedConfig.urlHandler?.(url, context) == false) {
Linking.canOpenURL(url)
.then((canOpen) => {
if (canOpen) {
Linking.openURL(url);
}
})
.catch((reason) => {
IterableLogger?.log('could not open url: ' + reason);
});
}
}
}

/**
Expand Down
12 changes: 12 additions & 0 deletions src/core/classes/IterableApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,18 @@ export class IterableApi {
return RNIterableAPI.getEmbeddedMessages(placementIds);
}

/**
* Track an embedded click.
*/
static trackEmbeddedClick(
message: IterableEmbeddedMessage,
buttonId: string | null,
clickedUrl: string | null
) {
IterableLogger.log('trackEmbeddedClick: ', message, buttonId, clickedUrl);
return RNIterableAPI.trackEmbeddedClick(message, buttonId, clickedUrl);
}

// ---- End EMBEDDED ---- //

// ====================================================== //
Expand Down
2 changes: 2 additions & 0 deletions src/core/enums/IterableActionSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export enum IterableActionSource {
appLink = 1,
/** The action source was an in-app message */
inApp = 2,
/** The action source was an embedded message */
embedded = 3,
}
9 changes: 9 additions & 0 deletions src/core/enums/IterableCustomActionPrefix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Enum representing the prefix of build-in custom action URL.
*/
export enum IterableCustomActionPrefix {
/** Current action prefix */
Action = 'action://',
/** Deprecated action prefix */
Itbl = 'itbl://',
}
1 change: 1 addition & 0 deletions src/core/enums/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './IterableEventName';
export * from './IterableLogLevel';
export * from './IterablePushPlatform';
export * from './IterableRetryBackoff';
export * from './IterableCustomActionPrefix';
31 changes: 31 additions & 0 deletions src/core/utils/callUrlHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Linking } from 'react-native';
import type { IterableActionContext } from '../classes/IterableActionContext';
import { IterableLogger } from '../classes/IterableLogger';
import type { IterableConfig } from '../classes/IterableConfig';

/**
* Calls the URL handler and attempts to open the URL if the handler returns false.
*
* @param config - The config to use.
* @param url - The URL to call.
* @param context - The context to use.
*/
export function callUrlHandler(
config: IterableConfig,
url: string,
context: IterableActionContext
) {
// MOB-10424: Figure out if this is purposeful
// eslint-disable-next-line eqeqeq
if (config.urlHandler?.(url, context) == false) {
Linking.canOpenURL(url)
.then((canOpen) => {
if (canOpen) {
Linking.openURL(url);
}
})
.catch((reason) => {
IterableLogger?.log('could not open url: ' + reason);
});
}
}
20 changes: 20 additions & 0 deletions src/core/utils/getActionPrefix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { IterableCustomActionPrefix } from '../enums/IterableCustomActionPrefix';

/**
* Gets the action prefix from a string.
*
* @param str - The string to get the action prefix from.
* @returns The action prefix.
*/
export const getActionPrefix = (
str?: string | null
): IterableCustomActionPrefix | null => {
if (!str) return null;
if (str.startsWith(IterableCustomActionPrefix.Action)) {
return IterableCustomActionPrefix.Action;
}
if (str.startsWith(IterableCustomActionPrefix.Itbl)) {
return IterableCustomActionPrefix.Itbl;
}
return null;
Comment on lines +9 to +19
Copy link

Choose a reason for hiding this comment

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

Found 2 issues:

1. Function with high complexity (count = 6): getActionPrefix [qlty:function-complexity]


2. Function with many returns (count = 4): getActionPrefix [qlty:return-statements]

};
2 changes: 2 additions & 0 deletions src/core/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './getActionPrefix';
export * from './callUrlHandler';
Loading