AEM CS - Geo Targeting in React SPA Using Experience Fragments and Adobe Target

Goal


Adobe Experience Manager 2021.9.5899.20210929T093525Z-210800 (Sep 29, 2021)

Create a AEM Geo Targeting React SPA Component for showing Geo based Personalized Experience in a Page. In this sample, visitors from Texas see a Texas flag on home page, CA users see CA flag and rest of US (actually rest of the world) see the US flag...



Home page in Texas



Home page in California



Home page in rest of US




Solution


Here is a high level design of the Geo based personalization...

1) Integrate AEM with Adobe Target

2) Create a Geo XF React SPA Component 

3) Extend Model JSON Requests to send the XF url to Target

4) Create Personalized Experience Fragments (XF) using HTL Core Components, Export to Target

5) Create the Home page with Geo XF Component and Publish

6) Create the Target Activity using exported XF Offers in step 4, check personalized Home pages using Activity QA and make Activity Live...






Setup IMS in AEM for Target Integration

                    https://experienceleague.adobe.com/docs/experience-manager-65/administering/integration/integration-ims-adobe-io.html

                    Tools > Security > Adobe IMS Configurations


                    1. Create a Certificate in AEM...



                    2. Upload Certificate in https://console.adobe.io




                    3. Continue the IMS integration in AEM...



 


                    4. Provide the necessary permissions for Integration in Admin Console. Without this you might see the following error when exporting XFs to Target...

Caused by: com.day.cq.analytics.testandtarget.impl.service.WebServiceException: Unexpected response status code [403] for request [https://mc.adobe.io/ags959/target/offers/content?includeMarketingCloudMetadata=true].{"httpStatus":403,"requestId":"SKeC0FKYUiWHkDxi2D47KS33xMkRKpdy","requestTime":"2021-06-15T17:12:51.320578Z","errors":[{"errorCode":"Forbidden.Resource","message":"Access denied. To perform this operation, all of the following privileges are required \"[editor]\".","meta":{}}]}at com.day.cq.analytics.testandtarget.impl.service.WebServiceImpl.request(WebServiceImpl.java:610) [com.adobe.cq.cq-target-integration:1.4.30]... 163 common frames omitted




Create Target Integration using IMS

                    Tools > Cloud Services > Legacy Cloud Services > Adobe Target


Create Geo XF SPA Component in AEM

1) Create the project using following maven command

mvn -B archetype:generate -D archetypeGroupId=com.adobe.aem -D archetypeArtifactId=aem-project-archetype -D archetypeVersion=30 -D aemVersion=cloud -D appTitle="Experience AEM SPA Geo Targeting" -D appId="eaem-spa-geo-target" -D groupId="apps.experienceaem.sites" -D frontendModule=react -D includeExamples=n -D includeDispatcherConfig=y


2) Create /apps/eaem-spa-geo-target/components/geo-xf component with the following dialog field to enter Target Activity MBox name


3) Add the component render script in eaem-spa-geo-target\ui.frontend\src\components\geo-xf\geo-xf.tsx

import {MappedComponentProperties, MapTo} from "@adobe/aem-react-editable-components";
import React, { FC, useState } from "react";
import Helmet from "react-helmet";
import useScript from 'react-script-hook';
import {AuthoringUtils} from "@adobe/aem-spa-page-model-manager";

const GeoXFConfig = {
    emptyLabel: "Geo XF - Experience AEM",

    isEmpty: function (props: any) {
        return !props;
    }
};

type GeoXFProps = MappedComponentProperties & {
    mboxName ?: string;
}

const isInProxyOrAuthoring = () => process.env.REACT_APP_PROXY_ENABLED || AuthoringUtils.isInEditor()
                || (window.location.search.indexOf("wcmmode=disabled") !== -1)

const GeoXF: FC<GeoXFProps> = props => {
    // @ts-ignore
    window.targetGlobalSettings = {
        cookieDomain: window.location.hostname
    };

    const [html, setHtml] = useState("<div>Loading...</div>");

    const successFn = (offer : any) => {
        const offerJSON = JSON.parse(offer[0].content);

        if (!offerJSON.xfHtmlPath) {
            setHtml("<div>Target Error loading offer : xfHtmlPath not available</div>");
            return;
        }

        let xfHtmlPath = offerJSON.xfHtmlPath;

        if(isInProxyOrAuthoring()){
            xfHtmlPath = xfHtmlPath.substring(xfHtmlPath.indexOf("/content"));
        }

        const respPromise = process.env.REACT_APP_PROXY_ENABLED ? fetch(xfHtmlPath, {
            credentials: 'same-origin',
            headers: {
                'Authorization': process.env.REACT_APP_AEM_AUTHORIZATION_HEADER
            } as any
        }): fetch(xfHtmlPath);

        respPromise.then( response => response.text()).then( (html) => setHtml(html))
    }

    useScript({
        src: '/etc.clientlibs/eaem-spa-geo-target/clientlibs/clientlib-react/resources/at.js',
        onload: () => {
            // @ts-ignore
            window.adobe.target.getOffer({
                mbox: props.mboxName || 'eaem-state-flag-box',
                success: successFn,
                error: () => setHtml("<div>Target Error loading offer</div>")
            })
        }
    });

    return (
        <>
            <Helmet>
                <link rel="preconnect" href="//ags959.tt.omtrdc.net?lang=en"/>
                <link rel="dns-prefetch" href="//ags959.tt.omtrdc.net?lang=en"/>
            </Helmet>

            <div dangerouslySetInnerHTML={{ __html: html }} />
        </>
    );
}

export default MapTo('eaem-spa-geo-target/components/geo-xf')(GeoXF, GeoXFConfig);


4) In above script the following snippet sets the Target cookieDomain to cloud service instance sub domain eg. publish-p10961-e90064.adobeaemcloud.com

                window.targetGlobalSettings = {
                                cookieDomain: window.location.hostname
                };

Without the above code, Target will try to set the cookieDomain to root domain adobeaemcloud.com ,verifies by writing the cookie 'at_check=true; path=/; domain=adobeaemcloud.com' and fails (as the cloud service root domain adobeaemcloud.com is not accessible for setting cookies by code executing in subdomain) eventually resulting in the following error...

VM21:1 AT: [getOffer()] Adobe Target content delivery is disabled. Ensure that you can save cookies to your current domain, there is no "mboxDisable" cookie and there is no "mboxDisable" parameter in query string.


4)  Integrating Target with Launch is the right way to load Target in your app (check documentation) however to keep things simple lets download the Target lib file at.js from your Adobe Target account (check documentation) and add it in the app public folder eaem-spa-geo-target\ui.frontend\public\at.js. When the app is built (npm run build) its copied to /apps/eaem-spa-geo-target/clientlibs/clientlib-react/resources/at.js



Create Personalization Blocks - Experience Fragments

1) Personalization blocks shown on page (welcome message and flag image) are created as Experience Fragments (XF). To facilitate this, create a template type /conf/eaem-spa-geo-target/settings/wcm/template-types/geo-xf and template /conf/eaem-spa-geo-target/settings/wcm/templates/geo-targeting-xf with page sling:resourceType set to eaem-spa-geo-target/components/xfpage (this template uses HTL Core Components for generating the XF html server side injected into the Geo React component)




2) Set the Target Integration configuration in XF structure /content/experience-fragments/eaem-spa-geo-target/us/en/site


3) Create a filter apps.experienceaem.assets.core.filters.ExperienceFragmentJSONOfferFilter with the following code to modify the product model.json response, and provide the publish url of XF...

eg. https://publish-p10961-e90064.adobeaemcloud.com/content/experience-fragments/eaem-spa-geo-target/us/en/site/utah.model.json

{

}

package apps.experienceaem.assets.core.filters;

import com.day.cq.commons.Externalizer;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.json.JSONObject;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import java.io.IOException;

@Component(
        service = Filter.class,
        immediate = true,
        name = "Experience AEM - Change offer JSON exported to Target",
        property = {
                Constants.SERVICE_RANKING + ":Integer=-99",
                "sling.filter.scope=COMPONENT",
                "sling.filter.pattern=(/content/experience-fragments/.*.model.json)",
        }
)
public class ExperienceFragmentJSONOfferFilter implements Filter {
    private static final Logger log = LoggerFactory.getLogger(ExperienceFragmentJSONOfferFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;

        try {
            String uri = slingRequest.getRequestURI();

            if(!uri.endsWith(".model.json")){
                chain.doFilter(request, response);
                return;
            }

            JSONObject model = new JSONObject();

            String masterXFPath = uri.substring(0,uri.lastIndexOf(".model.json"));

            masterXFPath = masterXFPath + ".html";

            ResourceResolver resolver = slingRequest.getResourceResolver();
            Externalizer externalizer = resolver.adaptTo(Externalizer.class);

            model.put("xfHtmlPath", externalizer.publishLink(resolver, masterXFPath));

            response.getWriter().print(model);
        } catch (Exception e) {
            log.error("Error getting json offer response : " + slingRequest.getRequestURI());
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}


4) Create XFs for various states Texas, California, Utah, US etc. and Export to Target (Also, Activate the XF page so its accessible from publish instance eg. https://publish-p10961-e90064.adobeaemcloud.com/content/experience-fragments/eaem-spa-geo-target/us/en/site/utah/master.html)


Create the Target Activity

1) The XF exported from AEM is added in Adobe Target as an Offer.



2) Create the Audiences for your Experience Activity eg. https://experience.adobe.com/#/@ags959/target/audiences



3) Create the necessary experiences in your Target Activity


4) Map the Audiences to Experiences (each one set to the respective XF offer)


5) The individual personalized experiences are available in the Activity QA of the Activity


6) Report of how the Experiences are doing is available in the Reports section of Activity...




No comments:

Post a Comment