-
Notifications
You must be signed in to change notification settings - Fork 35
add handleJavascriptMessage in AEPMessaging #519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3845543
aacdb67
063a771
d0831eb
8903ad0
67d2f52
c77e85d
0d1b44b
377b71a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,7 +44,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: string) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: would recommend to use constants from a file |
||
console.log('Received webview content in onShow:', content); | ||
}); | ||
}, | ||
shouldShowMessage: () => true, | ||
shouldSaveMessage: () => true, | ||
urlLoaded: (url, message) => console.log(url, message), | ||
|
@@ -53,10 +58,10 @@ const setMessagingDelegate = () => { | |
}; | ||
const getPropositionsForSurfaces = async () => { | ||
const messages = await Messaging.getPropositionsForSurfaces(SURFACES); | ||
console.log(JSON.stringify(messages)); | ||
console.log('getPropositionsForSurfaces', JSON.stringify(messages)); | ||
}; | ||
const trackAction = async () => { | ||
MobileCore.trackAction('tuesday', {full: true}); | ||
MobileCore.trackAction('iamjs', {full: true}); | ||
ishita-gambhir-adobe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
const updatePropositionsForSurfaces = async () => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,6 +87,17 @@ describe('Messaging', () => { | |
expect(spy).toHaveBeenCalledWith(id); | ||
}); | ||
|
||
it('handleJavascriptMessage is called', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The unit tests seems a little incomplete. can we add a test to verify that when a message is not found (due to a bad handlerName or expired cache), the sdk gracefully handles the error without emitting incorrect events or crashing. |
||
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([ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* | ||
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"; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -263,6 +264,28 @@ 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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add consistency across platform for emitting events ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the emitEvent and emitNativeEvent functions are existing helper methods from before. Lets keep this PR focused on handleJavascriptMessage related changes. Refactoring can be taken as a separate task if needed. |
||
name: Constants.ON_JAVASCRIPT_MESSAGE_EVENT, | ||
body: [ | ||
Constants.MESSAGE_ID_KEY: messageId, | ||
Constants.HANDLER_NAME_KEY: handlerName, | ||
Constants.CONTENT_KEY: content ?? "" | ||
] | ||
) | ||
} | ||
} | ||
|
||
/// MARK: - Unified PropositionItem Tracking Methods | ||
|
||
/** | ||
|
@@ -329,6 +352,7 @@ public class RCTAEPMessaging: RCTEventEmitter, MessagingDelegate { | |
if let fullscreenMessage = message as? FullscreenMessage, | ||
let parentMessage = fullscreenMessage.parent | ||
{ | ||
jsHandlerMessageCache.removeValue(forKey: parentMessage.id) | ||
emitNativeEvent( | ||
name: Constants.ON_DISMISS_EVENT, | ||
body: RCTAEPMessagingDataBridge.transformToMessage( | ||
|
@@ -342,6 +366,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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,9 +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 { 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<string, Record<string, (content: string) => void>> = {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every time we show a new in-app message, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added clearJavascriptMessageHandlers method for clearing the memory. |
||
const handleJSMessageEventEmitter = new NativeEventEmitter(RCTAEPMessaging); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we create a type for this callback as this is being used in multiple places |
||
// 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; | ||
|
@@ -67,6 +81,42 @@ class Message { | |
clear() { | ||
RCTAEPMessaging.clear(this.id); | ||
} | ||
|
||
/** | ||
* 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) { | ||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we add checks for the handler |
||
if (!jsMessageHandlers[this.id]) { | ||
jsMessageHandlers[this.id] = {}; | ||
} | ||
jsMessageHandlers[this.id][handlerName] = handler; | ||
RCTAEPMessaging.handleJavascriptMessage(this.id, handlerName); | ||
} | ||
|
||
/** | ||
* @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() { | ||
delete jsMessageHandlers[this.id]; | ||
} | ||
} | ||
|
||
export default Message; |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we going to use console log functions directly
or there a global log method available for logging purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We dont have a logging utility available, we use the console log functions directly in the test app.