Skip to content
Merged
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
53 changes: 45 additions & 8 deletions apps/AEPSampleAppNewArchEnabled/app/MessagingView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,24 @@ 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 {
Messaging,
PersonalizationSchema,
MessagingEdgeEventType,
PropositionItem,
Message,
ContentCard,
HTMLProposition,
JSONPropositionItem
} from '@adobe/react-native-aepmessaging'
import { MessagingProposition } from '@adobe/react-native-aepmessaging';
import styles from '../styles/styles';
import { useRouter } from 'expo-router';

const SURFACES = ['android-cbe-preview', 'cbe/json', 'android-cc'];
const SURFACES_WITH_CONTENT_CARDS = ['android-cc'];


const messagingExtensionVersion = async () => {
const version = await Messaging.extensionVersion();
console.log(`AdobeExperienceSDK: Messaging version: ${version}`);
Expand All @@ -33,21 +44,24 @@ 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) => {
console.log('Received webview content in onShow:', content);
});
},
shouldShowMessage: () => true,
shouldSaveMessage: () => true,
urlLoaded: (url, message) => console.log(url, message),
});
console.log('messaging delegate set');
};

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});
};

const updatePropositionsForSurfaces = async () => {
Expand Down Expand Up @@ -75,7 +89,8 @@ const trackContentCardInteraction = async () => {
for (const proposition of propositions) {
for (const propositionItem of proposition.items) {
if (propositionItem.schema === PersonalizationSchema.CONTENT_CARD) {
Messaging.trackContentCardInteraction(proposition, propositionItem);
// Cast to ContentCard for the legacy tracking method
Messaging.trackContentCardInteraction(proposition, propositionItem as any);
console.log('trackContentCardInteraction', proposition, propositionItem);
}
}
Expand All @@ -93,14 +108,35 @@ const trackContentCardDisplay = async () => {
for (const proposition of propositions) {
for (const propositionItem of proposition.items) {
if (propositionItem.schema === PersonalizationSchema.CONTENT_CARD) {
Messaging.trackContentCardDisplay(proposition, propositionItem);
// Cast to ContentCard for the legacy tracking method
Messaging.trackContentCardDisplay(proposition, propositionItem as any);
console.log('trackContentCardDisplay', proposition, propositionItem);
}
}
}
}
}


// Method demonstrating unified tracking using PropositionItem methods
const unifiedTrackingExample = async () => {
const messages = await Messaging.getPropositionsForSurfaces(SURFACES);
for (const surface of SURFACES) {
const propositions = messages[surface] || [];

for (const proposition of propositions) {
const propositionWrapper = new MessagingProposition(proposition);
if (propositionWrapper.items.length > 0) {
const propositionItem = propositionWrapper.items[0];
propositionItem.track(MessagingEdgeEventType.DISPLAY);
propositionItem.track('content_card_clicked', MessagingEdgeEventType.INTERACT, null);
}
}
}
}



function MessagingView() {
const router = useRouter();

Expand All @@ -125,6 +161,7 @@ function MessagingView() {
<Button title="trackAction()" onPress={trackAction} />
<Button title="trackPropositionInteraction()" onPress={trackContentCardInteraction} />
<Button title="trackContentCardDisplay()" onPress={trackContentCardDisplay} />
<Button title="Unified Tracking Example" onPress={unifiedTrackingExample} />
</ScrollView>
</View>
);
Expand Down
68 changes: 66 additions & 2 deletions packages/messaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,53 @@ for (let item in proposition.items) {
}
```


### PropositionItem.track

A unified tracking API is available for any proposition item (Content Cards, HTML, JSON, code-based items). You can use the same track() method regardless of content type, making your code more consistent and maintainable.

#### Using PropositionItem.track (recommended)

**Syntax**

```javascript
// Track display event
propositionItem.track(MessagingEdgeEventType.DISPLAY);

// Track interaction with custom data + event + optional tokens
propositionItem.track(
interaction /* string | null */,
MessagingEdgeEventType.INTERACT /* enum value */,
[/* tokens */] /* string[] | null */
);
```

When using `getPropositionsForSurfaces`, the returned objects can be wrapped with `MessagingProposition` to get typed items and convenient tracking via `PropositionItem.track(...)`.

```javascript
import { Messaging, MessagingProposition, MessagingEdgeEventType } from '@adobe/react-native-aepmessaging';

const SURFACES = ['mobileapp://my-surface'];
const messages = await Messaging.getPropositionsForSurfaces(SURFACES);

for (const surface of SURFACES) {
const propositions = messages[surface] || [];

for (const proposition of propositions) {
const msgProp = new MessagingProposition(proposition);

if (msgProp.items.length > 0) {
const propositionItem = msgProp.items[0];

// Track interaction with custom data
propositionItem.track('content_card_clicked', MessagingEdgeEventType.INTERACT, null);
// Track with tokens for sub-item tracking
propositionItem.track('button_click', MessagingEdgeEventType.INTERACT, ['token-1', 'token-2']);
}
}
}
```

### getLatestMessage

Retrieves the most recently displayed message object
Expand Down Expand Up @@ -340,6 +387,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.
Expand Down Expand Up @@ -440,6 +501,8 @@ function otherWorkflowFinished() {

### trackContentCardDisplay

Deprecated: Use `PropositionItem.track(...)` instead. This API will be removed in a future release.

Tracks a Display interaction with the given ContentCard

**Syntax**
Expand All @@ -449,6 +512,8 @@ Messaging.trackContentCardDisplay(proposition, contentCard);

### trackContentCardInteraction

Deprecated: Use `PropositionItem.track(...)` instead. This API will be removed in a future release.

Tracks a Click interaction with the given ContentCard

**Syntax**
Expand All @@ -458,5 +523,4 @@ Messaging.trackContentCardInteraction(proposition, contentCard);


## Tutorials
[Content Cards](./tutorials/ContentCards.md)

[Native handling of Javascript Events](./tutorials/In-App%20Messaging.md)
11 changes: 11 additions & 0 deletions packages/messaging/__tests__/MessagingTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down
84 changes: 84 additions & 0 deletions packages/messaging/__tests__/PropositionItemTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
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.
*/

import { NativeModules } from 'react-native';
import { MessagingEdgeEventType, PropositionItem, PropositionItemData, PersonalizationSchema } from '../src';

describe('PropositionItem', () => {
const uuid = 'activity-uuid-123';
const id = 'item-abc';
const activityID = uuid; // mirrors native mapping

const baseData: PropositionItemData = {
id,
uuid,
activityID,
schema: PersonalizationSchema.CONTENT_CARD,
data: { foo: 'bar' },
};

beforeEach(() => {
jest.clearAllMocks();
});

it('track(eventType) calls native with null interaction and tokens', () => {
const item = new PropositionItem(baseData);
item.track(MessagingEdgeEventType.DISPLAY);

expect(NativeModules.AEPMessaging.trackPropositionItem).toHaveBeenCalledWith(
activityID,
null,
MessagingEdgeEventType.DISPLAY,
null
);
});

it('track(interaction, eventType, tokens=null) forwards interaction', () => {
const item = new PropositionItem(baseData);
item.track('click', MessagingEdgeEventType.INTERACT, null);

expect(NativeModules.AEPMessaging.trackPropositionItem).toHaveBeenCalledWith(
activityID,
'click',
MessagingEdgeEventType.INTERACT,
null
);
});

it('track(interaction, eventType, tokens[]) forwards tokens array', () => {
const item = new PropositionItem(baseData);
const tokens = ['t1', 't2'];
item.track('click', MessagingEdgeEventType.INTERACT, tokens);

expect(NativeModules.AEPMessaging.trackPropositionItem).toHaveBeenCalledWith(
activityID,
'click',
MessagingEdgeEventType.INTERACT,
tokens
);
});

it('track(null, eventType, tokens[]) supports null interaction with tokens', () => {
const item = new PropositionItem(baseData);
const tokens = ['a'];
item.track(null, MessagingEdgeEventType.DISPLAY, tokens);

expect(NativeModules.AEPMessaging.trackPropositionItem).toHaveBeenCalledWith(
activityID,
null,
MessagingEdgeEventType.DISPLAY,
tokens
);
});
});


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";
}
Loading