diff --git a/jest.config.js b/jest.config.js index 300d22a8..bbefa4fe 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,5 +16,5 @@ module.exports = { moduleNameMapper: pathsToModuleNameMapper(config.compilerOptions.paths, { prefix: '/' }), - transformIgnorePatterns: ['node_modules/(?!axios)'], + transformIgnorePatterns: ['node_modules/(?!axios)'] }; diff --git a/react-example/src/views/EmbeddedMsgs.tsx b/react-example/src/views/EmbeddedMsgs.tsx index a09485fb..10e37b59 100644 --- a/react-example/src/views/EmbeddedMsgs.tsx +++ b/react-example/src/views/EmbeddedMsgs.tsx @@ -18,11 +18,13 @@ interface Props {} export const EmbeddedMsgs: FC = () => { const { loggedInUser } = useUser(); - + const appPackageName = 'my-website'; const [selectedButtonIndex, setSelectedButtonIndex] = useState(0); const [messages, setMessages] = useState([]); - const [embeddedManager] = useState(new IterableEmbeddedManager('my-website')); + const [embeddedManager] = useState( + new IterableEmbeddedManager(appPackageName) + ); const changeCustomElement = () => { const titleElement = document.getElementById('notification-title-custom-0'); @@ -49,7 +51,7 @@ export const EmbeddedMsgs: FC = () => { const customActionHandler: IterableCustomActionHandler = { handleIterableCustomAction: function (action: IterableAction): boolean { if (action.data === 'news') { - // handle the custom action here + // handle the custom action here and navigate based on action data return true; } return false; @@ -71,6 +73,7 @@ export const EmbeddedMsgs: FC = () => { try { const updateListener: IterableEmbeddedMessageUpdateHandler = { onMessagesUpdated: function (): void { + // this callback gets called when messages are fetched/updated setMessages(embeddedManager.getMessages()); }, onEmbeddedMessagingDisabled: function (): void { @@ -166,43 +169,46 @@ export const EmbeddedMsgs: FC = () => { > {messages.length > 0 ? ( messages.map((message: IterableEmbeddedMessage, index: number) => { - const data = message; - const notification = IterableEmbeddedNotification({ - embeddedManager, - message: data, - titleId: `notification-title-custom-${index}`, - textStyle: ` - font-size: 20px; - ` - }); - const banner = IterableEmbeddedBanner({ - embeddedManager, - message: data, - parentStyle: ' margin-bottom: 10; ', - primaryBtnStyle: ` - background-color: #000fff; - border-radius: 8px; - padding: 10px; - color: #ffffff; - `, - imageId: `banner-image-custom-${index}` - }); - const card = IterableEmbeddedCard({ - embeddedManager, - message: data, - parentStyle: ' margin-bottom: 10; ' - }); switch (selectedButtonIndex) { - case 0: + case 0: { + const card = IterableEmbeddedCard({ + appPackageName, + message, + parentStyle: ' margin-bottom: 10; ', + errorCallback: (error) => console.log('handleError: ', error) + }); return
; - - case 1: + } + + case 1: { + const banner = IterableEmbeddedBanner({ + appPackageName, + message, + parentStyle: ' margin-bottom: 10; ', + primaryBtnStyle: ` + background-color: #000fff; + border-radius: 8px; + padding: 10px; + color: #ffffff; + `, + imageId: `banner-image-custom-${index}` + }); return
; - - case 2: + } + + case 2: { + const notification = IterableEmbeddedNotification({ + appPackageName, + message, + titleId: `notification-title-custom-${index}`, + textStyle: ` + font-size: 20px; + ` + }); return (
); + } default: return null; diff --git a/react-example/src/views/EmbeddedMsgsImpressionTracker.tsx b/react-example/src/views/EmbeddedMsgsImpressionTracker.tsx index 5a0f5a0d..b2b0d4e3 100644 --- a/react-example/src/views/EmbeddedMsgsImpressionTracker.tsx +++ b/react-example/src/views/EmbeddedMsgsImpressionTracker.tsx @@ -120,7 +120,7 @@ export const EmbeddedMsgsImpressionTracker: FC = () => { const card = IterableEmbeddedCard({ embeddedManager, message: data, - parentStyle: ` margin-bottom: 10; ` + parentStyle: ' margin-bottom: 10; ' }); return (
- handleElementClick(embeddedManager, message) + handleElementClick(message, appPackageName, errorCallback) ); } if (primaryButtonClick) { - addButtonClickEvent(embeddedManager, primaryButtonClick, 0, message); + addButtonClickEvent( + primaryButtonClick, + 0, + message, + appPackageName, + errorCallback + ); } if (secondaryButtonClick) { - addButtonClickEvent(embeddedManager, secondaryButtonClick, 1, message); + addButtonClickEvent( + secondaryButtonClick, + 1, + message, + appPackageName, + errorCallback + ); } }, 0); diff --git a/src/components/card/index.tsx b/src/components/card/index.tsx index 5dcff783..80823ef6 100644 --- a/src/components/card/index.tsx +++ b/src/components/card/index.tsx @@ -1,12 +1,13 @@ -import { EmbeddedMessageData } from '../types'; import { handleElementClick, addButtonClickEvent } from '../../embedded/embeddedClickEvents'; import { IterableEmbeddedButton } from 'src/embedded'; +import { EmbeddedMessageData } from '../types'; export function IterableEmbeddedCard({ - embeddedManager, + appPackageName, + message, parentStyle, disablePrimaryBtn = false, disableSecondaryBtn = false, @@ -17,7 +18,6 @@ export function IterableEmbeddedCard({ secondaryDisableBtnStyle, textStyle, titleStyle, - message, titleId = 'card-title', textId = 'card-text', primaryButtonId = 'card-primary-button', @@ -25,7 +25,8 @@ export function IterableEmbeddedCard({ parentId = 'card-parent', imageId = 'card-image', buttonsDivId = 'card-buttons-div', - textTitleDivId = 'card-text-title-div' + textTitleDivId = 'card-text-title-div', + errorCallback }: EmbeddedMessageData): string { const defaultCardStyles = ` border: 1px solid #ccc; @@ -106,14 +107,26 @@ export function IterableEmbeddedCard({ )[0]; if (cardDiv) { cardDiv.addEventListener('click', () => - handleElementClick(embeddedManager, message) + handleElementClick(message, appPackageName, errorCallback) ); } if (primaryButtonClick) { - addButtonClickEvent(embeddedManager, primaryButtonClick, 0, message); + addButtonClickEvent( + primaryButtonClick, + 0, + message, + appPackageName, + errorCallback + ); } if (secondaryButtonClick) { - addButtonClickEvent(embeddedManager, secondaryButtonClick, 1, message); + addButtonClickEvent( + secondaryButtonClick, + 1, + message, + appPackageName, + errorCallback + ); } }, 0); diff --git a/src/components/notification/index.tsx b/src/components/notification/index.tsx index bcac1ae1..dfc82992 100644 --- a/src/components/notification/index.tsx +++ b/src/components/notification/index.tsx @@ -6,7 +6,7 @@ import { import { IterableEmbeddedButton } from 'src/embedded'; export function IterableEmbeddedNotification({ - embeddedManager, + appPackageName, message, disablePrimaryBtn = false, disableSecondaryBtn = false, @@ -22,7 +22,8 @@ export function IterableEmbeddedNotification({ secondaryButtonId = 'notification-secondary-button', parentId = 'notification-parent', buttonsDivId = 'notification-buttons-div', - textTitleDivId = 'notification-text-title-div' + textTitleDivId = 'notification-text-title-div', + errorCallback }: EmbeddedMessageData): string { const defaultTitleStyles = ` font-size: 20px; @@ -78,14 +79,26 @@ export function IterableEmbeddedNotification({ )[0]; if (notificationDiv) { notificationDiv.addEventListener('click', () => - handleElementClick(embeddedManager, message) + handleElementClick(message, appPackageName, errorCallback) ); } if (primaryButtonClick) { - addButtonClickEvent(embeddedManager, primaryButtonClick, 0, message); + addButtonClickEvent( + primaryButtonClick, + 0, + message, + appPackageName, + errorCallback + ); } if (secondaryButtonClick) { - addButtonClickEvent(embeddedManager, secondaryButtonClick, 1, message); + addButtonClickEvent( + secondaryButtonClick, + 1, + message, + appPackageName, + errorCallback + ); } }, 0); diff --git a/src/components/types.ts b/src/components/types.ts index 06bbd7a4..88bfb896 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -1,7 +1,8 @@ -import { IterableEmbeddedManager, IterableEmbeddedMessage } from '..'; +import { ErrorHandler } from '../types'; +import { IterableEmbeddedMessage } from '../embedded/types'; export interface EmbeddedMessageData { - embeddedManager: IterableEmbeddedManager; + appPackageName: string; message: IterableEmbeddedMessage; disablePrimaryBtn?: boolean; disableSecondaryBtn?: boolean; @@ -22,4 +23,5 @@ export interface EmbeddedMessageData { buttonsDivId?: string; textTitleDivId?: string; textTitleImageDivId?: string; + errorCallback?: ErrorHandler; } diff --git a/src/embedded/embeddedClickEvents.ts b/src/embedded/embeddedClickEvents.ts index 2a8cf0c8..52c0c333 100644 --- a/src/embedded/embeddedClickEvents.ts +++ b/src/embedded/embeddedClickEvents.ts @@ -1,36 +1,79 @@ +import { trackEmbeddedClick } from '../events/embedded/events'; import { - IterableEmbeddedManager, IterableEmbeddedButton, + IterableAction, + IterableActionRunner, + IterableActionSource, IterableEmbeddedMessage } from '../embedded'; +import { ErrorHandler } from '../types'; +import { + URL_SCHEME_ACTION, + URL_SCHEME_ITBL, + URL_SCHEME_OPEN +} from 'src/constants'; + +function getClickedUrl(action?: any): string { + if (!action) return ''; + + if (action.type === URL_SCHEME_OPEN) { + return action?.data || ''; + } else { + return action.type; + } +} export const handleElementClick = ( - embeddedManager: IterableEmbeddedManager, - message: IterableEmbeddedMessage + message: IterableEmbeddedMessage, + appPackageName: string, + errorCallback?: ErrorHandler ) => { - const clickedUrl = - message?.elements?.defaultAction?.data?.trim() || - message?.elements?.defaultAction?.type || - null; - embeddedManager.handleEmbeddedClick(clickedUrl); - embeddedManager.trackEmbeddedClick(message, '', clickedUrl ? clickedUrl : ''); + const clickedUrl = getClickedUrl(message?.elements?.defaultAction); + handleEmbeddedClick(clickedUrl); + trackEmbeddedClick({ + messageId: message.metadata.messageId, + buttonIdentifier: '', + clickedUrl: clickedUrl, + appPackageName + }).catch((error) => { + if (errorCallback) { + errorCallback({ + ...error?.response?.data, + statusCode: error?.response?.status + }); + } + }); }; export const handleButtonClick = ( - embeddedManager: IterableEmbeddedManager, button: IterableEmbeddedButton, - message: IterableEmbeddedMessage + message: IterableEmbeddedMessage, + appPackageName: string, + errorCallback?: ErrorHandler ) => { - const clickedUrl = button?.action?.data?.trim() || button?.action?.type || ''; - embeddedManager.handleEmbeddedClick(clickedUrl); - embeddedManager.trackEmbeddedClick(message, button?.id || '', clickedUrl); + const clickedUrl = getClickedUrl(button?.action); + handleEmbeddedClick(clickedUrl); + trackEmbeddedClick({ + messageId: message.metadata.messageId, + buttonIdentifier: button?.id || '', + clickedUrl, + appPackageName + }).catch((error) => { + if (errorCallback) { + errorCallback({ + ...error?.response?.data, + statusCode: error?.response?.status + }); + } + }); }; export const addButtonClickEvent = ( - embeddedManager: IterableEmbeddedManager, button: HTMLElement, index: number, - message: IterableEmbeddedMessage + message: IterableEmbeddedMessage, + appPackageName: string, + errorCallback?: ErrorHandler ) => { button.addEventListener('click', (event) => { // Prevent the click event from bubbling up to the div @@ -39,9 +82,39 @@ export const addButtonClickEvent = ( return ''; } handleButtonClick( - embeddedManager, message?.elements?.buttons[index], - message + message, + appPackageName, + errorCallback ); }); }; + +const handleEmbeddedClick = (clickedUrl: string | null) => { + if (clickedUrl && clickedUrl.trim() !== '') { + let actionType: string; + let actionName: string; + + if (clickedUrl.startsWith(URL_SCHEME_ACTION)) { + actionName = ''; + actionType = clickedUrl; + } else if (clickedUrl.startsWith(URL_SCHEME_ITBL)) { + actionName = ''; + actionType = clickedUrl.replace(URL_SCHEME_ITBL, ''); + } else { + actionType = URL_SCHEME_OPEN; + actionName = clickedUrl; + } + + const iterableAction: IterableAction = { + type: actionType, + data: actionName + }; + + IterableActionRunner.executeAction( + null, + iterableAction, + IterableActionSource.EMBEDDED + ); + } +}; diff --git a/src/embedded/embeddedManager.ts b/src/embedded/embeddedManager.ts index e38115ab..b39d7995 100644 --- a/src/embedded/embeddedManager.ts +++ b/src/embedded/embeddedManager.ts @@ -1,23 +1,12 @@ import { baseIterableRequest } from '../request'; import { IterableEmbeddedMessageUpdateHandler, - IterableActionSource, - IterableAction, IterableEmbeddedMessage } from './types'; import { IterableResponse } from '../types'; import { EmbeddedMessagingProcessor } from './embeddedMessageProcessor'; import { embedded_msg_endpoint, ErrorMessage } from './consts'; -import { IterableActionRunner } from 'src/utils/IterableActionRunner'; -import { - URL_SCHEME_ITBL, - URL_SCHEME_ACTION, - URL_SCHEME_OPEN, - WEB_PLATFORM, - SDK_VERSION -} from '../constants'; -import { EndPoints } from 'src/events/consts'; -import { trackEmbeddedClickSchema } from 'src/events/embedded/events.schema'; +import { SDK_VERSION, WEB_PLATFORM } from '../constants'; import { trackEmbeddedReceived } from '../events/embedded/events'; export class IterableEmbeddedManager { @@ -43,20 +32,18 @@ export class IterableEmbeddedManager { placementIds: number[] ) { try { - let url = `${embedded_msg_endpoint}?`; const params: any = {}; if (placementIds.length > 0) { params.placementIds = placementIds .map((id) => `&placementIds=${id}`) .join(''); } - url = url.replace(/&$/, ''); const iterableResult: any = await baseIterableRequest({ method: 'GET', - url: url, + url: embedded_msg_endpoint, params: { ...params, - platform: 'Web', + platform: WEB_PLATFORM, sdkVersion: SDK_VERSION, packageName: packageName } @@ -151,60 +138,4 @@ export class IterableEmbeddedManager { public getUpdateHandlers(): IterableEmbeddedMessageUpdateHandler[] { return this.updateListeners; } - - handleEmbeddedClick(clickedUrl: string | null) { - if (clickedUrl && clickedUrl.trim() !== '') { - let actionType: string; - let actionName: string; - - if (clickedUrl.startsWith(URL_SCHEME_ACTION)) { - actionName = ''; - actionType = clickedUrl; - } else if (clickedUrl.startsWith(URL_SCHEME_ITBL)) { - actionName = ''; - actionType = clickedUrl.replace(URL_SCHEME_ITBL, ''); - } else { - actionType = URL_SCHEME_OPEN; - actionName = clickedUrl; - } - - const iterableAction: IterableAction = { - type: actionType, - data: actionName - }; - - IterableActionRunner.executeAction( - null, - iterableAction, - IterableActionSource.EMBEDDED - ); - } - } - - trackEmbeddedClick( - message: IterableEmbeddedMessage, - buttonIdentifier: string, - clickedUrl: string - ) { - const payload = { - messageId: message?.metadata?.messageId, - buttonIdentifier: buttonIdentifier, - targetUrl: clickedUrl, - deviceInfo: { - platform: WEB_PLATFORM, - deviceId: global.navigator.userAgent || '', - appPackageName: window.location.hostname - }, - createdAt: Date.now() - }; - - return baseIterableRequest({ - method: 'POST', - url: EndPoints.msg_click_event_track, - data: payload, - validation: { - data: trackEmbeddedClickSchema - } - }); - } } diff --git a/src/types.ts b/src/types.ts index a0bfc881..418f2a51 100644 --- a/src/types.ts +++ b/src/types.ts @@ -34,3 +34,7 @@ export interface IterableResponse { msg: string; params?: null | Record; } + +export interface ErrorHandler { + (error: any): void; +} diff --git a/src/utils/IterableActionRunner.ts b/src/utils/IterableActionRunner.ts index 7f222b1e..e7873637 100644 --- a/src/utils/IterableActionRunner.ts +++ b/src/utils/IterableActionRunner.ts @@ -4,6 +4,7 @@ import { IterableActionContext, IterableActionSource } from '../embedded/types'; +import { URL_SCHEME_OPEN } from 'src/constants'; class IterableActionRunnerImpl { static executeAction( @@ -16,7 +17,7 @@ class IterableActionRunnerImpl { } const actionContext: IterableActionContext = { action, source }; - if (action.type === 'openUrl') { + if (action.type === URL_SCHEME_OPEN) { return IterableActionRunnerImpl.openUri(action.data, actionContext); } else { return IterableActionRunnerImpl.callCustomActionIfSpecified(