From 38455432b5a44194ea033122e123b0a8cbbbd63c Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Tue, 12 Aug 2025 11:07:06 +0530 Subject: [PATCH 1/8] Messaging - handle javascript message --- .../app/MessagingView.tsx | 8 ++++- .../messaging/RCTAEPMessagingConstants.java | 20 +++++++++++++ .../messaging/RCTAEPMessagingModule.java | 23 ++++++++++++++ packages/messaging/src/Messaging.ts | 30 +++++++++++++++++++ packages/messaging/src/MessagingUtil.ts | 9 ++++++ packages/messaging/src/models/Message.ts | 12 ++++++++ 6 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java create mode 100644 packages/messaging/src/MessagingUtil.ts diff --git a/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx b/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx index 270d5adbf..db71a31b6 100644 --- a/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx +++ b/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx @@ -33,7 +33,12 @@ const refreshInAppMessages = () => { const setMessagingDelegate = () => { Messaging.setMessagingDelegate({ onDismiss: msg => console.log('dismissed!', msg), - onShow: msg => console.log('show', msg), + onShow: msg => { + console.log('show', msg); + msg.handleJavascriptMessage('myInappCallback', (content) => { + console.log('Received webview content in onShow:', content); + }); + }, shouldShowMessage: () => true, shouldSaveMessage: () => true, urlLoaded: (url, message) => console.log(url, message), @@ -42,6 +47,7 @@ const setMessagingDelegate = () => { }; const getPropositionsForSurfaces = async () => { + console.log('getPropositionsForSurfaces'); const messages = await Messaging.getPropositionsForSurfaces(SURFACES); console.log(JSON.stringify(messages)); }; diff --git a/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java b/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java new file mode 100644 index 000000000..14edfa92d --- /dev/null +++ b/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java @@ -0,0 +1,20 @@ +/* + Copyright 2025 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law + or agreed to in writing, software distributed under the License is + distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF + ANY KIND, either express or implied. See the License for the specific + language governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.reactnative.messaging; + +class RCTAEPMessagingConstants { + static final String MESSAGE_ID_KEY = "messageId"; + static final String HANDLER_NAME_KEY = "handlerName"; + static final String CONTENT_KEY = "content"; + static final String ON_JAVASCRIPT_MESSAGE_EVENT = "onJavascriptMessage"; + } + \ No newline at end of file diff --git a/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java b/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java index 2d52139a6..87a267415 100644 --- a/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java +++ b/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java @@ -31,6 +31,7 @@ import com.adobe.marketing.mobile.messaging.Surface; import com.adobe.marketing.mobile.services.ServiceProvider; import com.adobe.marketing.mobile.services.ui.InAppMessage; +import com.adobe.marketing.mobile.services.ui.message.InAppMessageEventHandler; import com.adobe.marketing.mobile.services.ui.Presentable; import com.adobe.marketing.mobile.services.ui.PresentationDelegate; import com.facebook.react.bridge.Arguments; @@ -57,6 +58,7 @@ public final class RCTAEPMessagingModule private boolean shouldShowMessage = false; private CountDownLatch latch = new CountDownLatch(1); private Message latestMessage = null; + private final Map> presentableCache = new HashMap<>(); public RCTAEPMessagingModule(ReactApplicationContext reactContext) { super(reactContext); @@ -175,11 +177,30 @@ public void track(final String messageId, final String interaction, } } + @ReactMethod + public void handleJavascriptMessage(final String messageId, final String handlerName) { + Presentable presentable = presentableCache.get(messageId); + if (presentable == null || !(presentable.getPresentation() instanceof InAppMessage)) return; + + Presentable inAppMessagePresentable = (Presentable) presentable; + InAppMessageEventHandler eventHandler = inAppMessagePresentable.getPresentation().getEventHandler(); + + eventHandler.handleJavascriptMessage(handlerName, content -> { + Map params = new HashMap<>(); + params.put(RCTAEPMessagingConstants.MESSAGE_ID_KEY, messageId); + params.put(RCTAEPMessagingConstants.HANDLER_NAME_KEY, handlerName); + params.put(RCTAEPMessagingConstants.CONTENT_KEY, content); + emitEvent(RCTAEPMessagingConstants.ON_JAVASCRIPT_MESSAGE_EVENT, params); + }); + } + // Messaging Delegate functions @Override public void onShow(final Presentable presentable) { if (!(presentable.getPresentation() instanceof InAppMessage)) return; Message message = MessagingUtils.getMessageForPresentable((Presentable) presentable); + presentableCache.put(message.getId(), presentable); + if (message != null) { Map data = convertMessageToMap(message); @@ -191,6 +212,8 @@ public void onShow(final Presentable presentable) { public void onDismiss(final Presentable presentable) { if (!(presentable.getPresentation() instanceof InAppMessage)) return; Message message = MessagingUtils.getMessageForPresentable((Presentable) presentable); + presentableCache.remove(message.getId()); + if (message != null) { Map data = convertMessageToMap(message); diff --git a/packages/messaging/src/Messaging.ts b/packages/messaging/src/Messaging.ts index 25cecdbb3..53deaf6cf 100644 --- a/packages/messaging/src/Messaging.ts +++ b/packages/messaging/src/Messaging.ts @@ -45,6 +45,35 @@ const RCTAEPMessaging: NativeModule & NativeMessagingModule = declare var messagingDelegate: MessagingDelegate; var messagingDelegate: MessagingDelegate; +// Registery to store callbacks for each message in handleJavascriptMessage +// Record - {messageId : {handlerName : callback}} +const jsMessageHandlers: Record void>> = {}; +const handleJSMessageEventEmitter = new NativeEventEmitter(RCTAEPMessaging); + +handleJSMessageEventEmitter.addListener('onJavascriptMessage', (event) => { + const {messageId, handlerName, content} = event; + if (jsMessageHandlers[messageId] && jsMessageHandlers[messageId][handlerName]) { + jsMessageHandlers[messageId][handlerName](content); + } +}); + +handleJSMessageEventEmitter.addListener('cacheJavascriptCallback', (event) => { + const {messageId, handlerName, callback} = event; + if (!jsMessageHandlers[messageId]) { + jsMessageHandlers[messageId] = {}; + } + jsMessageHandlers[messageId][handlerName] = callback; +}); + +class MessagingCacheUtil { + static cacheJavascriptCallback(messageId: string, handlerName: string, callback: (content: string) => void) { + if (!jsMessageHandlers[messageId]) { + jsMessageHandlers[messageId] = {}; + } + jsMessageHandlers[messageId][handlerName] = callback; + } +} + class Messaging { /** * Returns the version of the AEPMessaging extension @@ -173,3 +202,4 @@ class Messaging { } export default Messaging; +export { MessagingCacheUtil }; diff --git a/packages/messaging/src/MessagingUtil.ts b/packages/messaging/src/MessagingUtil.ts new file mode 100644 index 000000000..dab3b3884 --- /dev/null +++ b/packages/messaging/src/MessagingUtil.ts @@ -0,0 +1,9 @@ +import { MessagingCacheUtil } from './Messaging'; + +class MessagingUtil { + static cacheJavascriptCallback(messageId: string, handlerName: string, callback: (content: string) => void) { + MessagingCacheUtil.cacheJavascriptCallback(messageId, handlerName, callback); + } +} + +export default MessagingUtil; \ No newline at end of file diff --git a/packages/messaging/src/models/Message.ts b/packages/messaging/src/models/Message.ts index bee749d30..9a0880fbb 100644 --- a/packages/messaging/src/models/Message.ts +++ b/packages/messaging/src/models/Message.ts @@ -11,6 +11,8 @@ governing permissions and limitations under the License. */ import { NativeModules } from 'react-native'; +// import MessagingUtil from '../MessagingUtil'; + const RCTAEPMessaging = NativeModules.AEPMessaging; class Message { @@ -67,6 +69,16 @@ class Message { clear() { RCTAEPMessaging.clear(this.id); } + + handleJavascriptMessage(handlerName: string, callback: (content: string) => void) { + // MessagingUtil.cacheJavascriptCallback(this.id, handlerName, callback); + EventEmitter.emit('cacheJavascriptCallback', { + messageId: this.id, + handlerName: handlerName, + callback: callback + }); + RCTAEPMessaging.handleJavascriptMessage(this.id, handlerName); + } } export default Message; From aacdb67a9573368acac879c22089529c00754f56 Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Thu, 21 Aug 2025 02:44:32 +0530 Subject: [PATCH 2/8] add js event listener and ios --- .../app/MessagingView.tsx | 12 ++++----- .../app/_layout.tsx | 3 ++- packages/messaging/ios/src/RCTAEPMessaging.mm | 4 +++ .../messaging/ios/src/RCTAEPMessaging.swift | 25 +++++++++++++++++++ .../ios/src/RCTAEPMessagingConstants.swift | 6 ++++- packages/messaging/package.json | 3 +++ packages/messaging/src/Messaging.ts | 23 ++++++----------- packages/messaging/src/MessagingUtil.ts | 10 +++----- packages/messaging/src/models/Message.ts | 4 +-- yarn.lock | 5 ++++ 10 files changed, 62 insertions(+), 33 deletions(-) diff --git a/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx b/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx index db71a31b6..e529f48e3 100644 --- a/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx +++ b/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx @@ -13,7 +13,7 @@ governing permissions and limitations under the License. import React from 'react'; import {Button, Text, View, ScrollView} from 'react-native'; import {MobileCore} from '@adobe/react-native-aepcore'; -import {Messaging, PersonalizationSchema} from '@adobe/react-native-aepmessaging' +import {Message, Messaging, PersonalizationSchema} from '@adobe/react-native-aepmessaging' import styles from '../styles/styles'; import { useRouter } from 'expo-router'; @@ -32,16 +32,16 @@ const refreshInAppMessages = () => { const setMessagingDelegate = () => { Messaging.setMessagingDelegate({ - onDismiss: msg => console.log('dismissed!', msg), - onShow: msg => { + onDismiss: (msg: Message) => console.log('dismissed!', msg), + onShow: (msg: Message) => { console.log('show', msg); - msg.handleJavascriptMessage('myInappCallback', (content) => { + msg.handleJavascriptMessage('myInappCallback', (content: string) => { console.log('Received webview content in onShow:', content); }); }, shouldShowMessage: () => true, shouldSaveMessage: () => true, - urlLoaded: (url, message) => console.log(url, message), + urlLoaded: (url: string, message: Message) => console.log(url, message), }); console.log('messaging delegate set'); }; @@ -53,7 +53,7 @@ const getPropositionsForSurfaces = async () => { }; const trackAction = async () => { - MobileCore.trackAction('tuesday', {full: true}); + MobileCore.trackAction('iamjs', {full: true}); }; const updatePropositionsForSurfaces = async () => { diff --git a/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx b/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx index 6f4ec0456..ad011f179 100644 --- a/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx +++ b/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx @@ -38,7 +38,8 @@ export default function RootLayout() { // For functional components, use useEffect with an empty dependency array. // For class components, call initializeWithAppId inside componentDidMount. MobileCore.setLogLevel(LogLevel.DEBUG); - MobileCore.initializeWithAppId("YOUR-APP-ID") + const ENV_ID_Messaging = "3149c49c3910/4f6b2fbf2986/launch-7d78a5fd1de3-development"; + MobileCore.initializeWithAppId(ENV_ID_Messaging) .then(() => { console.log("AEP SDK Initialized"); }) diff --git a/packages/messaging/ios/src/RCTAEPMessaging.mm b/packages/messaging/ios/src/RCTAEPMessaging.mm index 6a227ed73..9c643eb2b 100644 --- a/packages/messaging/ios/src/RCTAEPMessaging.mm +++ b/packages/messaging/ios/src/RCTAEPMessaging.mm @@ -55,4 +55,8 @@ @interface RCT_EXTERN_MODULE (RCTAEPMessaging, RCTEventEmitter) : (NSDictionary *)propositionMap contentCardMap : (NSDictionary *)contentCardMap); +RCT_EXTERN_METHOD(handleJavascriptMessage + : (NSString *)messageId handlerName + : (NSString *)handlerName) + @end diff --git a/packages/messaging/ios/src/RCTAEPMessaging.swift b/packages/messaging/ios/src/RCTAEPMessaging.swift index 86cf4cef5..ef18d1079 100644 --- a/packages/messaging/ios/src/RCTAEPMessaging.swift +++ b/packages/messaging/ios/src/RCTAEPMessaging.swift @@ -21,6 +21,7 @@ import WebKit @objc(RCTAEPMessaging) public class RCTAEPMessaging: RCTEventEmitter, MessagingDelegate { private var messageCache = [String: Message]() + private var jsHandlerMessageCache = [String: Message]() private var latestMessage: Message? = nil private let semaphore = DispatchSemaphore(value: 0) private var shouldSaveMessage = false @@ -249,11 +250,34 @@ public class RCTAEPMessaging: RCTEventEmitter, MessagingDelegate { } } + @objc + func handleJavascriptMessage( + _ messageId: String, + handlerName: String + ) { + guard let message = jsHandlerMessageCache[messageId] else { + print("[RCTAEPMessaging] handleJavascriptMessage: No message found in cache for messageId: \(messageId)") + return + } + + message.handleJavascriptMessage(handlerName) { [weak self] content in + self?.emitNativeEvent( + name: Constants.ON_JAVASCRIPT_MESSAGE_EVENT, + body: [ + Constants.MESSAGE_ID_KEY: messageId, + Constants.HANDLER_NAME_KEY: handlerName, + Constants.CONTENT_KEY: content ?? "" + ] + ) + } + } + // Messaging Delegate Methods public func onDismiss(message: Showable) { if let fullscreenMessage = message as? FullscreenMessage, let parentMessage = fullscreenMessage.parent { + jsHandlerMessageCache.removeValue(forKey: parentMessage.id) emitNativeEvent( name: Constants.ON_DISMISS_EVENT, body: RCTAEPMessagingDataBridge.transformToMessage( @@ -267,6 +291,7 @@ public class RCTAEPMessaging: RCTEventEmitter, MessagingDelegate { if let fullscreenMessage = message as? FullscreenMessage, let message = fullscreenMessage.parent { + jsHandlerMessageCache[message.id] = message emitNativeEvent( name: Constants.ON_SHOW_EVENT, body: RCTAEPMessagingDataBridge.transformToMessage(message: message) diff --git a/packages/messaging/ios/src/RCTAEPMessagingConstants.swift b/packages/messaging/ios/src/RCTAEPMessagingConstants.swift index 9b662d434..64f26aa03 100644 --- a/packages/messaging/ios/src/RCTAEPMessagingConstants.swift +++ b/packages/messaging/ios/src/RCTAEPMessagingConstants.swift @@ -16,7 +16,11 @@ class Constants { static let ON_SHOW_EVENT = "onShow" static let SHOULD_SHOW_MESSAGE_EVENT = "shouldShowMessage" static let URL_LOADED_EVENT = "urlLoaded" + static let ON_JAVASCRIPT_MESSAGE_EVENT = "onJavascriptMessage" static let SUPPORTED_EVENTS = [ - ON_DISMISS_EVENT, ON_SHOW_EVENT, SHOULD_SHOW_MESSAGE_EVENT, URL_LOADED_EVENT, + ON_DISMISS_EVENT, ON_SHOW_EVENT, SHOULD_SHOW_MESSAGE_EVENT, URL_LOADED_EVENT, ON_JAVASCRIPT_MESSAGE_EVENT ] + static let MESSAGE_ID_KEY = "messageId" + static let HANDLER_NAME_KEY = "handlerName" + static let CONTENT_KEY = "content" } diff --git a/packages/messaging/package.json b/packages/messaging/package.json index 42e8654fa..098b9b339 100644 --- a/packages/messaging/package.json +++ b/packages/messaging/package.json @@ -38,5 +38,8 @@ }, "installConfig": { "hoistingLimits": "dependencies" + }, + "dependencies": { + "events": "^3.3.0" } } diff --git a/packages/messaging/src/Messaging.ts b/packages/messaging/src/Messaging.ts index 53deaf6cf..346ffa9dc 100644 --- a/packages/messaging/src/Messaging.ts +++ b/packages/messaging/src/Messaging.ts @@ -20,6 +20,7 @@ import Message from './models/Message'; import { MessagingDelegate } from './models/MessagingDelegate'; import { MessagingProposition } from './models/MessagingProposition'; import { ContentCard } from './models/ContentCard'; +import { eventEmitter } from './MessagingUtil'; export interface NativeMessagingModule { extensionVersion: () => Promise; @@ -57,7 +58,7 @@ handleJSMessageEventEmitter.addListener('onJavascriptMessage', (event) => { } }); -handleJSMessageEventEmitter.addListener('cacheJavascriptCallback', (event) => { +eventEmitter.on('cacheJavascriptCallback', (event) => { const {messageId, handlerName, callback} = event; if (!jsMessageHandlers[messageId]) { jsMessageHandlers[messageId] = {}; @@ -65,15 +66,6 @@ handleJSMessageEventEmitter.addListener('cacheJavascriptCallback', (event) => { jsMessageHandlers[messageId][handlerName] = callback; }); -class MessagingCacheUtil { - static cacheJavascriptCallback(messageId: string, handlerName: string, callback: (content: string) => void) { - if (!jsMessageHandlers[messageId]) { - jsMessageHandlers[messageId] = {}; - } - jsMessageHandlers[messageId][handlerName] = callback; - } -} - class Messaging { /** * Returns the version of the AEPMessaging extension @@ -139,15 +131,15 @@ class Messaging { const eventEmitter = new NativeEventEmitter(RCTAEPMessaging); - eventEmitter.addListener('onShow', (message) => + eventEmitter.addListener('onShow', (message: Message) => messagingDelegate?.onShow?.(message) ); - eventEmitter.addListener('onDismiss', (message) => { + eventEmitter.addListener('onDismiss', (message: Message) => { messagingDelegate?.onDismiss?.(message); }); - eventEmitter.addListener('shouldShowMessage', (message) => { + eventEmitter.addListener('shouldShowMessage', (message: Message) => { const shouldShowMessage = messagingDelegate?.shouldShowMessage?.(message) ?? true; const shouldSaveMessage = @@ -156,13 +148,13 @@ class Messaging { }); if (Platform.OS === 'ios') { - eventEmitter.addListener('urlLoaded', (event) => + eventEmitter.addListener('urlLoaded', (event: {url: string, message: Message}) => messagingDelegate?.urlLoaded?.(event.url, event.message) ); } if (Platform.OS === 'android') { - eventEmitter.addListener('onContentLoaded', (event) => + eventEmitter.addListener('onContentLoaded', (event: {message: Message}) => messagingDelegate?.onContentLoaded?.(event.message) ); } @@ -202,4 +194,3 @@ class Messaging { } export default Messaging; -export { MessagingCacheUtil }; diff --git a/packages/messaging/src/MessagingUtil.ts b/packages/messaging/src/MessagingUtil.ts index dab3b3884..24d17df94 100644 --- a/packages/messaging/src/MessagingUtil.ts +++ b/packages/messaging/src/MessagingUtil.ts @@ -1,9 +1,5 @@ -import { MessagingCacheUtil } from './Messaging'; +import { EventEmitter } from 'events'; -class MessagingUtil { - static cacheJavascriptCallback(messageId: string, handlerName: string, callback: (content: string) => void) { - MessagingCacheUtil.cacheJavascriptCallback(messageId, handlerName, callback); - } -} +const eventEmitter = new EventEmitter(); -export default MessagingUtil; \ No newline at end of file +export { eventEmitter }; diff --git a/packages/messaging/src/models/Message.ts b/packages/messaging/src/models/Message.ts index 9a0880fbb..a1f6a56f9 100644 --- a/packages/messaging/src/models/Message.ts +++ b/packages/messaging/src/models/Message.ts @@ -11,7 +11,7 @@ governing permissions and limitations under the License. */ import { NativeModules } from 'react-native'; -// import MessagingUtil from '../MessagingUtil'; +import { eventEmitter } from '../MessagingUtil'; const RCTAEPMessaging = NativeModules.AEPMessaging; @@ -72,7 +72,7 @@ class Message { handleJavascriptMessage(handlerName: string, callback: (content: string) => void) { // MessagingUtil.cacheJavascriptCallback(this.id, handlerName, callback); - EventEmitter.emit('cacheJavascriptCallback', { + eventEmitter.emit('cacheJavascriptCallback', { messageId: this.id, handlerName: handlerName, callback: callback diff --git a/yarn.lock b/yarn.lock index ee667ecfc..2f74aeb99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4969,6 +4969,11 @@ eventemitter3@^4.0.4: resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + exec-async@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz" From 063a77173c100c338c4595989eba9e8cc2bd3485 Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Thu, 21 Aug 2025 10:55:10 +0530 Subject: [PATCH 3/8] typecaste Message object --- packages/messaging/src/Messaging.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/messaging/src/Messaging.ts b/packages/messaging/src/Messaging.ts index 346ffa9dc..48e42ebc2 100644 --- a/packages/messaging/src/Messaging.ts +++ b/packages/messaging/src/Messaging.ts @@ -132,30 +132,31 @@ class Messaging { const eventEmitter = new NativeEventEmitter(RCTAEPMessaging); eventEmitter.addListener('onShow', (message: Message) => - messagingDelegate?.onShow?.(message) + messagingDelegate?.onShow?.(new Message(message)) ); eventEmitter.addListener('onDismiss', (message: Message) => { - messagingDelegate?.onDismiss?.(message); + messagingDelegate?.onDismiss?.(new Message(message)); }); eventEmitter.addListener('shouldShowMessage', (message: Message) => { + const messageInstance = new Message(message); const shouldShowMessage = - messagingDelegate?.shouldShowMessage?.(message) ?? true; + messagingDelegate?.shouldShowMessage?.(messageInstance) ?? true; const shouldSaveMessage = - messagingDelegate?.shouldSaveMessage?.(message) ?? false; + messagingDelegate?.shouldSaveMessage?.(messageInstance) ?? false; RCTAEPMessaging.setMessageSettings(shouldShowMessage, shouldSaveMessage); }); if (Platform.OS === 'ios') { eventEmitter.addListener('urlLoaded', (event: {url: string, message: Message}) => - messagingDelegate?.urlLoaded?.(event.url, event.message) + messagingDelegate?.urlLoaded?.(event.url, new Message(event.message)) ); } if (Platform.OS === 'android') { eventEmitter.addListener('onContentLoaded', (event: {message: Message}) => - messagingDelegate?.onContentLoaded?.(event.message) + messagingDelegate?.onContentLoaded?.(new Message(event.message)) ); } From d0831eb7624a9bfffa8e1787c4f594f0ccb14669 Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Wed, 27 Aug 2025 15:44:24 +0530 Subject: [PATCH 4/8] address PR comments --- .../app/MessagingView.tsx | 8 +-- packages/messaging/README.md | 14 ++++ .../messaging/__tests__/MessagingTests.ts | 11 +++ .../messaging/RCTAEPMessagingConstants.java | 1 - packages/messaging/package.json | 3 - packages/messaging/src/Messaging.ts | 21 ------ packages/messaging/src/MessagingUtil.ts | 5 -- packages/messaging/src/models/Message.ts | 35 +++++++--- .../messaging/tutorials/In-App Messaging.md | 67 +++++++++++++++++++ yarn.lock | 5 -- 10 files changed, 122 insertions(+), 48 deletions(-) delete mode 100644 packages/messaging/src/MessagingUtil.ts create mode 100644 packages/messaging/tutorials/In-App Messaging.md diff --git a/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx b/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx index e529f48e3..464c7a9a4 100644 --- a/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx +++ b/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx @@ -13,7 +13,7 @@ governing permissions and limitations under the License. import React from 'react'; import {Button, Text, View, ScrollView} from 'react-native'; import {MobileCore} from '@adobe/react-native-aepcore'; -import {Message, Messaging, PersonalizationSchema} from '@adobe/react-native-aepmessaging' +import {Messaging, PersonalizationSchema} from '@adobe/react-native-aepmessaging' import styles from '../styles/styles'; import { useRouter } from 'expo-router'; @@ -32,8 +32,8 @@ const refreshInAppMessages = () => { const setMessagingDelegate = () => { Messaging.setMessagingDelegate({ - onDismiss: (msg: Message) => console.log('dismissed!', msg), - onShow: (msg: Message) => { + onDismiss: msg => console.log('dismissed!', msg), + onShow: msg => { console.log('show', msg); msg.handleJavascriptMessage('myInappCallback', (content: string) => { console.log('Received webview content in onShow:', content); @@ -41,7 +41,7 @@ const setMessagingDelegate = () => { }, shouldShowMessage: () => true, shouldSaveMessage: () => true, - urlLoaded: (url: string, message: Message) => console.log(url, message), + urlLoaded: (url, message) => console.log(url, message), }); console.log('messaging delegate set'); }; diff --git a/packages/messaging/README.md b/packages/messaging/README.md index 1c05aa3de..fd50f7834 100644 --- a/packages/messaging/README.md +++ b/packages/messaging/README.md @@ -340,6 +340,20 @@ var message: Message; message.clear(); ``` +### handleJavascriptMessage + +Registers a javascript interface for the provided handler name to the WebView associated with the InAppMessage presentation to handle Javascript messages. When the registered handlers are executed via the HTML the result will be passed back to the associated handler. + +**Syntax** + +```typescript +handleJavascriptMessage(handlerName: string, handler: (content: string) => void); +``` + +**Example** + +It can be used for the native handling of JavaScript events. Please refer to the [tutorial](./tutorials/In-App%20Messaging.md#native-handling-of-javascript-events) for more information. + ## Programmatically control the display of in-app messages App developers can now create a type `MessagingDelegate` in order to be alerted when specific events occur during the lifecycle of an in-app message. diff --git a/packages/messaging/__tests__/MessagingTests.ts b/packages/messaging/__tests__/MessagingTests.ts index f0ed77cd8..c27edfe4a 100644 --- a/packages/messaging/__tests__/MessagingTests.ts +++ b/packages/messaging/__tests__/MessagingTests.ts @@ -87,6 +87,17 @@ describe('Messaging', () => { expect(spy).toHaveBeenCalledWith(id); }); + it('handleJavascriptMessage is called', async () => { + const spy = jest.spyOn(NativeModules.AEPMessaging, 'handleJavascriptMessage'); + let id = 'id'; + let autoTrack = true; + let message = new Message({id, autoTrack}); + let handlerName = 'handlerName'; + let handler = jest.fn(); + await message.handleJavascriptMessage(handlerName, handler); + expect(spy).toHaveBeenCalledWith(id, handlerName); + }); + it('should call updatePropositionsForSurfaces', async () => { const spy = jest.spyOn(NativeModules.AEPMessaging, 'updatePropositionsForSurfaces'); await Messaging.updatePropositionsForSurfaces([ diff --git a/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java b/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java index 14edfa92d..445c19c0f 100644 --- a/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java +++ b/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingConstants.java @@ -17,4 +17,3 @@ class RCTAEPMessagingConstants { static final String CONTENT_KEY = "content"; static final String ON_JAVASCRIPT_MESSAGE_EVENT = "onJavascriptMessage"; } - \ No newline at end of file diff --git a/packages/messaging/package.json b/packages/messaging/package.json index 098b9b339..42e8654fa 100644 --- a/packages/messaging/package.json +++ b/packages/messaging/package.json @@ -38,8 +38,5 @@ }, "installConfig": { "hoistingLimits": "dependencies" - }, - "dependencies": { - "events": "^3.3.0" } } diff --git a/packages/messaging/src/Messaging.ts b/packages/messaging/src/Messaging.ts index 48e42ebc2..93ffb9ac1 100644 --- a/packages/messaging/src/Messaging.ts +++ b/packages/messaging/src/Messaging.ts @@ -20,7 +20,6 @@ import Message from './models/Message'; import { MessagingDelegate } from './models/MessagingDelegate'; import { MessagingProposition } from './models/MessagingProposition'; import { ContentCard } from './models/ContentCard'; -import { eventEmitter } from './MessagingUtil'; export interface NativeMessagingModule { extensionVersion: () => Promise; @@ -46,26 +45,6 @@ const RCTAEPMessaging: NativeModule & NativeMessagingModule = declare var messagingDelegate: MessagingDelegate; var messagingDelegate: MessagingDelegate; -// Registery to store callbacks for each message in handleJavascriptMessage -// Record - {messageId : {handlerName : callback}} -const jsMessageHandlers: Record void>> = {}; -const handleJSMessageEventEmitter = new NativeEventEmitter(RCTAEPMessaging); - -handleJSMessageEventEmitter.addListener('onJavascriptMessage', (event) => { - const {messageId, handlerName, content} = event; - if (jsMessageHandlers[messageId] && jsMessageHandlers[messageId][handlerName]) { - jsMessageHandlers[messageId][handlerName](content); - } -}); - -eventEmitter.on('cacheJavascriptCallback', (event) => { - const {messageId, handlerName, callback} = event; - if (!jsMessageHandlers[messageId]) { - jsMessageHandlers[messageId] = {}; - } - jsMessageHandlers[messageId][handlerName] = callback; -}); - class Messaging { /** * Returns the version of the AEPMessaging extension diff --git a/packages/messaging/src/MessagingUtil.ts b/packages/messaging/src/MessagingUtil.ts deleted file mode 100644 index 24d17df94..000000000 --- a/packages/messaging/src/MessagingUtil.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { EventEmitter } from 'events'; - -const eventEmitter = new EventEmitter(); - -export { eventEmitter }; diff --git a/packages/messaging/src/models/Message.ts b/packages/messaging/src/models/Message.ts index a1f6a56f9..79d350418 100644 --- a/packages/messaging/src/models/Message.ts +++ b/packages/messaging/src/models/Message.ts @@ -10,11 +10,23 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -import { NativeModules } from 'react-native'; -import { eventEmitter } from '../MessagingUtil'; +import { NativeEventEmitter, NativeModules } from 'react-native'; const RCTAEPMessaging = NativeModules.AEPMessaging; +// Registery to store inAppMessage callbacks for each message in Message.handleJavascriptMessage +// Record - {messageId : {handlerName : callback}} +const jsMessageHandlers: Record void>> = {}; +const handleJSMessageEventEmitter = new NativeEventEmitter(RCTAEPMessaging); + +// invokes the callback registered in Message.handleJavascriptMessage with the content received from the inAppMessage webview +handleJSMessageEventEmitter.addListener('onJavascriptMessage', (event) => { + const {messageId, handlerName, content} = event; + if (jsMessageHandlers[messageId] && jsMessageHandlers[messageId][handlerName]) { + jsMessageHandlers[messageId][handlerName](content); + } +}); + class Message { id: string; autoTrack: boolean; @@ -70,13 +82,18 @@ class Message { RCTAEPMessaging.clear(this.id); } - handleJavascriptMessage(handlerName: string, callback: (content: string) => void) { - // MessagingUtil.cacheJavascriptCallback(this.id, handlerName, callback); - eventEmitter.emit('cacheJavascriptCallback', { - messageId: this.id, - handlerName: handlerName, - callback: callback - }); + /** + * Adds a handler for named JavaScript messages sent from the message's WebView. + * The parameter passed to handler will contain the body of the message passed from the WebView's JavaScript. + * @param {string} handlerName: The name of the message that should be handled by the handler + * @param {function} handler: The method or closure to be called with the body of the message created in the Message's JavaScript + */ + handleJavascriptMessage(handlerName: string, handler: (content: string) => void) { + // cache the callback + if (!jsMessageHandlers[this.id]) { + jsMessageHandlers[this.id] = {}; + } + jsMessageHandlers[this.id][handlerName] = handler; RCTAEPMessaging.handleJavascriptMessage(this.id, handlerName); } } diff --git a/packages/messaging/tutorials/In-App Messaging.md b/packages/messaging/tutorials/In-App Messaging.md new file mode 100644 index 000000000..4ab8ca78a --- /dev/null +++ b/packages/messaging/tutorials/In-App Messaging.md @@ -0,0 +1,67 @@ +# Native handling of JavaScript events + +You can handle events from in-app message interactions natively within your application by completing the following steps: +- [Implement and assign a `Messaging Delegate`](#implement-and-assign-a-messaging-delegate) +- [Register a JavaScript handler for your In-App Message](#register-a-javascript-handler-for-your-in-app-message) +- [Post the JavaScript message from your In-App Message](#post-the-javascript-message-from-your-in-app-message) + +## Implement and assign a `Messaging Delegate` + +To register a JavaScript event handler with a Message object, you will first need to implement and set a MessagingDelegate. +Please read the [documentation](../README.md/#programmatically-control-the-display-of-in-app-messages) for more detailed instructions on implementing and using a MessagingDelegate. + +## Register a JavaScript handler for your In-App Message + +In the `onShow` function of `MessagingDelegate`, call `handleJavascriptMessage(handlerName: string, handler: (content: string) => void)` to register your handler. + +The name of the message you intend to pass from the JavaScript side should be specified in the first parameter. + +### Example + +```typescript +Messaging.setMessagingDelegate({ + onShow: msg => { + console.log('show', msg); + msg.handleJavascriptMessage( + 'myInappCallback', + (content) => { + console.log('Received webview content:', content); + } + ); + } + }); +``` + +## Post the JavaScript message from your In-App Message + +Now that the in-app message has been displayed, the final step is to post the JavaScript message. + +Continuing from the previous example, the developer is going to post the `myInappCallback` message from their HTML, which will in turn call the handler previously configured: + +```html + + + + + + + + +``` + +Note: (The above HTML is not representative of a valid in-app message, and is intended only to demonstrate how to post the JavaScript message). + +When the user clicks the button inside of this in-app message, the handler configured in the previous step will be called. The handler will send an Experience Event tracking the interaction, and print the following message to the console: + +```bash +JavaScript body passed to react native callback: callbacks are cool! +``` diff --git a/yarn.lock b/yarn.lock index 2f74aeb99..ee667ecfc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4969,11 +4969,6 @@ eventemitter3@^4.0.4: resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - exec-async@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz" From 8903ad097e993185947a33cfabd1ea77c1d0b898 Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Wed, 27 Aug 2025 16:59:11 +0530 Subject: [PATCH 5/8] refactor log statements --- apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx b/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx index 464c7a9a4..3dbb449b3 100644 --- a/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx +++ b/apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx @@ -47,9 +47,8 @@ const setMessagingDelegate = () => { }; const getPropositionsForSurfaces = async () => { - console.log('getPropositionsForSurfaces'); const messages = await Messaging.getPropositionsForSurfaces(SURFACES); - console.log(JSON.stringify(messages)); + console.log('getPropositionsForSurfaces', JSON.stringify(messages)); }; const trackAction = async () => { From 67d2f52349cc0304dc244448684281889e1dcaf2 Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Tue, 2 Sep 2025 13:04:38 +0530 Subject: [PATCH 6/8] address PR comments --- packages/messaging/README.md | 17 ++++++++++++++++ .../messaging/RCTAEPMessagingModule.java | 5 ++++- packages/messaging/src/models/Message.ts | 20 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/messaging/README.md b/packages/messaging/README.md index fd50f7834..0521660d3 100644 --- a/packages/messaging/README.md +++ b/packages/messaging/README.md @@ -354,6 +354,23 @@ handleJavascriptMessage(handlerName: string, handler: (content: string) => void) It can be used for the native handling of JavaScript events. Please refer to the [tutorial](./tutorials/In-App%20Messaging.md#native-handling-of-javascript-events) for more information. +### clearJavascriptMessageHandlers + +Clears all the javascript message handlers for the message. This function must be called if the callbacks registered in `handleJavascriptMessage` are no longer needed. Failure to call this function may lead to memory leaks. + +**Syntax** + +```typescript +clearJavascriptMessageHandlers(); +``` + +**Example** + +```typescript +var message: Message; +message.clearJavascriptMessageHandlers(); +``` + ## Programmatically control the display of in-app messages App developers can now create a type `MessagingDelegate` in order to be alerted when specific events occur during the lifecycle of an in-app message. diff --git a/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java b/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java index 87a267415..a134fc6b8 100644 --- a/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java +++ b/packages/messaging/android/src/main/java/com/adobe/marketing/mobile/reactnative/messaging/RCTAEPMessagingModule.java @@ -180,7 +180,10 @@ public void track(final String messageId, final String interaction, @ReactMethod public void handleJavascriptMessage(final String messageId, final String handlerName) { Presentable presentable = presentableCache.get(messageId); - if (presentable == null || !(presentable.getPresentation() instanceof InAppMessage)) return; + if (presentable == null || !(presentable.getPresentation() instanceof InAppMessage)) { + Log.w(TAG, "handleJavascriptMessage: No presentable found for messageId: " + messageId); + return; + } Presentable inAppMessagePresentable = (Presentable) presentable; InAppMessageEventHandler eventHandler = inAppMessagePresentable.getPresentation().getEventHandler(); diff --git a/packages/messaging/src/models/Message.ts b/packages/messaging/src/models/Message.ts index 79d350418..3dcb79c1b 100644 --- a/packages/messaging/src/models/Message.ts +++ b/packages/messaging/src/models/Message.ts @@ -89,6 +89,17 @@ class Message { * @param {function} handler: The method or closure to be called with the body of the message created in the Message's JavaScript */ handleJavascriptMessage(handlerName: string, handler: (content: string) => void) { + // Validate parameters + if (!handlerName) { + console.warn('[AEP Messaging] handleJavascriptMessage: handlerName is required'); + return; + } + + if (typeof handler !== 'function') { + console.warn('[AEP Messaging] handleJavascriptMessage: handler must be a function'); + return; + } + // cache the callback if (!jsMessageHandlers[this.id]) { jsMessageHandlers[this.id] = {}; @@ -96,6 +107,15 @@ class Message { jsMessageHandlers[this.id][handlerName] = handler; RCTAEPMessaging.handleJavascriptMessage(this.id, handlerName); } + + /** + * Clears all the javascript message handlers for the message. + * This function must be called if the callbacks registered in handleJavascriptMessage are no longer needed. + * Failure to call this function may lead to memory leaks. + */ + clearJavascriptMessageHandlers() { + delete jsMessageHandlers[this.id]; + } } export default Message; From 0d1b44bd5edc151b2d1601e399e2fb8a66c028e5 Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Tue, 2 Sep 2025 13:15:08 +0530 Subject: [PATCH 7/8] revert env file id changes --- apps/AEPSampleAppNewArchEnabled/app/_layout.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx b/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx index 292ba5da4..8c9217900 100644 --- a/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx +++ b/apps/AEPSampleAppNewArchEnabled/app/_layout.tsx @@ -39,8 +39,7 @@ export default function RootLayout() { // For functional components, use useEffect with an empty dependency array. // For class components, call initializeWithAppId inside componentDidMount. MobileCore.setLogLevel(LogLevel.DEBUG); - const ENV_ID_Messaging = "3149c49c3910/4f6b2fbf2986/launch-7d78a5fd1de3-development"; - MobileCore.initializeWithAppId(ENV_ID_Messaging) + MobileCore.initializeWithAppId("YOUR-APP-ID") .then(() => { console.log("AEP SDK Initialized"); From 377b71a2391750fa4d0d3df438120492951888ea Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Wed, 3 Sep 2025 12:39:08 +0530 Subject: [PATCH 8/8] clear js handler when onDismiss is called --- packages/messaging/README.md | 20 +------------------- packages/messaging/src/Messaging.ts | 1 + packages/messaging/src/models/Message.ts | 3 ++- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/packages/messaging/README.md b/packages/messaging/README.md index c25d0decc..58fca58d7 100644 --- a/packages/messaging/README.md +++ b/packages/messaging/README.md @@ -401,23 +401,6 @@ handleJavascriptMessage(handlerName: string, handler: (content: string) => void) It can be used for the native handling of JavaScript events. Please refer to the [tutorial](./tutorials/In-App%20Messaging.md#native-handling-of-javascript-events) for more information. -### clearJavascriptMessageHandlers - -Clears all the javascript message handlers for the message. This function must be called if the callbacks registered in `handleJavascriptMessage` are no longer needed. Failure to call this function may lead to memory leaks. - -**Syntax** - -```typescript -clearJavascriptMessageHandlers(); -``` - -**Example** - -```typescript -var message: Message; -message.clearJavascriptMessageHandlers(); -``` - ## Programmatically control the display of in-app messages App developers can now create a type `MessagingDelegate` in order to be alerted when specific events occur during the lifecycle of an in-app message. @@ -540,5 +523,4 @@ Messaging.trackContentCardInteraction(proposition, contentCard); ## Tutorials -[Content Cards](./tutorials/ContentCards.md) - +[Native handling of Javascript Events](./tutorials/In-App%20Messaging.md) diff --git a/packages/messaging/src/Messaging.ts b/packages/messaging/src/Messaging.ts index e9d2459b6..8a171efed 100644 --- a/packages/messaging/src/Messaging.ts +++ b/packages/messaging/src/Messaging.ts @@ -134,6 +134,7 @@ class Messaging { ); eventEmitter.addListener('onDismiss', (message: Message) => { + message._clearJavascriptMessageHandlers(); messagingDelegate?.onDismiss?.(new Message(message)); }); diff --git a/packages/messaging/src/models/Message.ts b/packages/messaging/src/models/Message.ts index 3dcb79c1b..458c7c69f 100644 --- a/packages/messaging/src/models/Message.ts +++ b/packages/messaging/src/models/Message.ts @@ -109,11 +109,12 @@ class Message { } /** + * @internal - For internal use only. * Clears all the javascript message handlers for the message. * This function must be called if the callbacks registered in handleJavascriptMessage are no longer needed. * Failure to call this function may lead to memory leaks. */ - clearJavascriptMessageHandlers() { + _clearJavascriptMessageHandlers() { delete jsMessageHandlers[this.id]; } }