Web React JS

To understand and test the process of integrating Personetics Widget React JS Component, into the bank’s React JS Application, the customer may initially integrate the sample Personetics code files into their own application. The same process can then be used to replace the sample code with the bank’s custom code provided by Personetics.

Operating Systems

Supported os versions:

18.2

Deprecation policy:

  • Time frame: Personetics client version 3.9 will support the React versions mentioned above for a period of two years from the launch date of the Personetics client version, as long as the operating system provider continues to support these versions.
  • Scope: During this period, support for the Personetics client version includes updates from both Personetics client and the operating system versions.
  • Upgrade implications: In case a customer upgrades their Personetics client version, this may result in changes to the supported operating system versions. Please contact your project manager for further details.

Package Description and Process

The customer is provided an SDK package via sFTP. Initially, the bank is provided with a package consisting of two items:

  • sample-app Folder that includes files for running the sample application. This application is used for analysis and understanding the integration process, and interaction with the Personetics widget components.
  • personetics-vx.x.x.tgz Personetics widget React JS node module. Required for integrating both the sample-app and the customized Personetics client into your app.

When the customization process is complete, the bank is then provided with a different personetics-vx.x.x.tgz package that is implemented in the bank’s own client. (In some cases there may be an additional sample-app to view any added or modified capabilities).

The preparation, implementation, and configuration processes are the same in both cases: instead of doing the implementation on the sample application, it is done on the bank’s own client.

Additional Requirements
Relevant npm required for the integration: https://www.npmjs.com/package/ncp

Integration and Configuration Process

The integration process is identical for the sample-app and for the customized application. First perform all the steps for the sample-app and then for the customized client product.

Process Overview

  1. Install the client packages using NPM.
  2. Integrate the client into the bank’s app.
  3. Implement Response to user actions and other events using the function handlePersoneticsEvents.
  4. Configure support for local assets.
  5. Call startWidget to open the widget – detailed in the Client API document

NPM Install

Follow the procedure for both sample app and the bank’s client (when relevant). When running the bank’s client, replace the ‘sample-app’ instructions for the bank’s client location.

Install the client package as follows:

  1. Copy the integration package folder to an accessible location.
  2. Open a terminal and navigate to the sample-app folder (as illustrated in the partial image below):
    cd <integration-package-path>/sample-app
  1. Install npm related packages (not included in the Personetics package):
    npm install
  2. Install the personetics-x.x.x.tgz package:
    npm install <integration-package-path>/personetics-x.x.x.tgz
    After the installation, the following occurs:
    • The new “Personetics” folder appears under node_modules folder.
    • A new dependency appears in the package.json file.
      dependencies: {

      “personetics”: “file:../personetics-[version].tgz”,

      }
  3. Run the sample app to view an example of the Personetics widget.
    npm start

Sample App Integration

Integrate the sample-app into the bank’s app according to the instructions in this section. The code in the sample app is already written.

Note: When integrating the actual product app, the sample app code can be used as reference. Refer to the example in /sample-app/src/mainView.jsx.

To integrate the sample application:

  1. Import the Personetics widget component by adding the following code in the bank’s app:
    import PersoneticsWidget from ‘personetics’

  2. Create a reference to the widget component in order to enable using the widgets public methods.

     class App extends Components: {
    …
    constructor() {
      super(props);
    	    this.persoWidgets = widgetTypes.reduce(
          (acc, widgetType, widgetIndex) => ({
            ...acc,
            [widgetIndex]: React.createRef(),
          }),
          {}
        );
        this.widgetCallStack = widgetTypes.reduce(
          (acc, widgetType, widgetIndex) => ({ ...acc, [widgetIndex]: [] }),
          {}
        );
    };
    …
    }
    
  3. Use the config object from the personeticsWidget component to hold a local instance of the widget state object.

    const getWidgetConfig = (selectorString) => ({  
          configurations: {  
            widgetType: localStorage.widgetType || Constants.WidgetTypes[0].value,  
            params: {  
              userId: localStorage.userId || Constants.UserId,  
            },  
            deviceType: "RJS",
            selectorString,
          },  
          assets: {  
            baseUrl: localStorage.baseUrl || Constants.BASE_ASSETS_URL,  
            useRemoteAssets: localStorage.useRemoteAssets === "true",  
          },  
          internationalization: {  
            language: localStorage.lang || Constants.Lang,  
          },
        });
    
    this.widgetsConfig = Array.from({ length: personeticsWidgetsCount }).map((_, index) => {
      const baseConfig = getWidgetConfig(`#root-${index}`);
      
      // Attaching the eventHandler
      baseConfig.eventHandler = this.handlePersoEvents.bind(this);
      
      return baseConfig;
    });
    
  4. Add the persoWidget as a ref property.

    render() {
      return (
      	<>
          {Object.entries(this.widgetsConfig).map(
          	([widgetIndex, widgetConfig]) => (
            	<PersoneticsWidget
              	ref={this.persoWidgets[widgetIndex]}
                id={`root-${widgetIndex}`}
                config={widgetConfig}
                widgetSelector={widgetIndex} // This is only needed when rendering more than 1 widget
              />
            )
          )}  
        </>
      );
    }
    

Response Implementation

Implement Response to user actions and other events.
The function handlePersoneticsEvents is used to implement the interaction logic with the widget by defining the function cases. Refer to Class Delegate Functions for examples of the implementation of each case.

handlePersoEvents (data) {  
switch (data.requestString) {  
case 'sessionEnded' :
    const { widgetSelector } = data.json.requestData; // Used to identify different widgets
			…  
case 'sessionError' :
    const { widgetSelector } = data.json.requestData; // Used to identify different widgets
			…  
case 'navigateToPage' :
    const { widgetSelector } = data.json.requestData; // Used to identify different widgets
			…  
case 'sendRequestToPServer' :
    const { widgetSelector } = data.json.requestData; // Used to identify different widgets
			…  
case 'sendRequestToEnrichServer' :
    const { widgetSelector } = data.json.requestData; // Used to identify different widgets
			…  
case 'personeticsEvent' :
      const { widgetSelector } = data.json.requestData.data; // Used to identify different widgets
   
			switch(data.json.requestData.data.eventType){  
				case "teaserClick":  
				…  
			};  
		}

Local Assets & Modules Support

Configure Support for Local Assets & Modules from the Personetics Package

  1. In the sample-app (or client-app), package.json file, modify the build command as follows:

    1. "scripts": {  
       		... 
          "copyAssets": "ncp './node_modules/personetics/assets/personetics-product/resources/assets' './public/personetics-pkg-assets'",
          "copyModules": "ncp './node_modules/personetics/assets/personetics-react-product/modules' './public/modules'",
       		"build": "npm run copyAssets && npm run copyModules && react-scripts build",  
       		...  
       },
    

    The assets from personetics package will be copied into the directory ./public/personetics-pkg-assets
    The “ncp package” version used.

"devDependencies": {  
		"ncp": "^2.0.0"  
}\`

Call startWidget

Call startWidget to Open the Widget
This procedure is detailed in the Client API document.

Class Delegate Functions

This section describes the functions required for the creation of the Class Delegate Protocol file, and provides examples of the implementation of each function in React JS.
Refer to the Widget API document for a full description of the functions’ parameters. In addition, implementation examples are provided in the mainView.jsx file.

navigateToPage

Values can optionally be assigned (function must be included). This function is invoked by Personetics widget to pass the navigation information.
The Bank application can use this information to navigate to a different page within or outside the native app.

Case

handlePersoEvents(data) {
    switch (data.requestString) {  
		  case "navigateToPage": {
        const { widgetSelector } = data.json.requestData;

        if (data.json && data.json.requestData && data.json.requestData.data)
          var requestData = data.json.requestData.data;

        const { payload, userId, insightId, instanceId, useCaseId, lang, navigateTarget } =
          requestData;

        const language =
          typeof lang !== "undefined"
            ? lang
            : this.widgetsConfig[widgetSelector].internationalization
                .language;

        switch (navigateTarget) {
          case "manageBudget": {
            this.navigateToWidget({
              widgetSelector,
              widgetType: "manage-budgets",
              configOverride: {
                configurations: {
                  params: {
                    userId,
                    insightId,
                    instanceId,
                    useCaseId,
                    params: payload,
                  },
                },
                internationalization: {
                  language,
                },
                bankId: localStorage.bankId,
                initiationMode: localStorage.initiationMode,
              },
            });

            break;
          }

          case "subscriptions": {
            this.navigateToWidget({
              widgetSelector,
              widgetType: "subscription-dashboard",
              configOverride: {
                configurations: {
                  params: {
                    userId,
                    insightId,
                    instanceId,
                    useCaseId,
                  },
                },
                internationalization: {
                  language,
                },
                pserverUrl: localStorage.enrichServerUrl,
              },
            });

            break;
          }
           
          case "recap": {
            this.navigateToWidget({
              widgetSelector,
              widgetType: "recap",
              configOverride: {
                configurations: {
                  params: {
                    userId,
                    insightId,
                    instanceId,
                    useCaseId,
                  },
                },
                internationalization: {
                  language,
                },
              },
            });

            break;
          } 

          default: {
            break;
          }
        }

        break;
      }
    }
}

navigateToWidget({ widgetType, widgetSelector, configOverride = {} }) {
    if (
      widgetType ===
      this.widgetsConfig[widgetSelector].configurations.widgetType
    )
      return;

    const widgetConfig = Utils.deepMerge(
      Utils.deepClone(this.widgetsConfig[widgetSelector]),
      {
        configurations: {
          widgetType,
          params: {
            ...(this.payload && {
              payload: widgetToRequestTypePayload(widgetType),
            }),
          },
        },
      },
      configOverride
    );
  
  	this.widgetsConfig = widgetConfig;
  	this.widgetCallStack[widgetSelector] = [...this.widgetCallStack[widgetSelector], widgetType];
  }

personeticsEvent

Description

Triggered by events of an incident captured on the web. This is a generic event that triggered by Personetics widget. Value definitions required for specific products.

Example:
Event ‘teaserClick’ is generated when the end-user clicks on a teaser. In this case, the bank should implement (write code) for relevant behaviour, where the default behavior implementation opens the story widget.
For a full list of events, refer to Widget Client API doc, Delegate Callbacks section, WidgetEvent, Event Types.

Note: There are various event types. Be sure to check what is the event type and act according to the business use-case.

  handlePersoEvents(data) {
    switch (data.requestString) {
      case "personeticsEvent": {
        const { eventType, insightId, instanceId, useCaseId, widgetSelector } =
          data.json.requestData.data;

        switch (eventType) {
          case "teaserClick": {
            this.navigateToWidget({ // Same function from previous example
              widgetSelector,
              widgetType: "story-widget",
              configOverride: {
                configurations: {
                  params: {
                    insightId,
                    instanceId,
                    useCaseId,
                  },
                },
              },
            });

            break;
          }

          case "inboxWidgetStarted": {
            this.navigateToWidget({  // Same function from previous example
              widgetSelector,
              widgetType: "inbox",
              configOverride: {
                configurations: {
                  params: {
                    insightId,
                    instanceId,
                    useCaseId,
                  },
                },
              },
            });

            break;
          }

          case "reporting": {
            console.log(
              "reporting event" + JSON.stringify(data.json.requestData.data)
            );
            break;
          }

          default:
            console.log(
              "widget event" + JSON.stringify(data.json.requestData.data)
            );
        }
        break;
      }
    }
  }

sendRequestToPServer/sendRequestToEnrichServer

sendRequestToPServer and sendRequestToEnrichServer are under the same case.

Case

  handlePersoEvents(data) {
    switch (data.requestString) {
      case "sendRequestToPServer":
      case "sendRequestToEnrichServer": {
        const { widgetSelector } = data.json.requestData;
        let pserverUrl = localStorage.serverUrl || Constants.PServerUrl;

        if (!this.isPredefined) {
          const headers = {
            "Content-Type": "application/json",
            authToken: data.requestHeaders.authToken,
            ...(data.json.requestData.type === "setBudgetSettings" && {
              bankId: localStorage.bankId,
              initiationMode: localStorage.initiationMode,
            }),
          };

          if (data.requestString === "sendRequestToEnrichServer")
            pserverUrl =
              localStorage.enrichServerUrl || Constants.EnrichServerUrl;

          if (data.json.requestData.hasOwnProperty("bankId")) {
            localStorage.bankId = data.json.requestData.bankId;
            localStorage.initiationMode = data.json.requestData.initiationMode;
          }

          let currentScope = this;
          NetworkManager.sendHttpRequest(
            "POST",
            pserverUrl,
            data.json.requestData,
            headers,
            function (response) {
              const persoWidget =
                currentScope.persoWidgets[widgetSelector].current;

              if (!persoWidget) return;

              persoWidget.handleServerResponse(
                response.data,
                data.json.requestId,
                widgetSelector
              );
            },
            function (error) {
              alert("error");
            }
          );
        } else {
          // Simulating response from server from predefine json files and injecting to the persoWidget
          var serverResponse = getPredefinedResponseServer(
            data.json.requestData.type,
            data.json.requestData.insightId
          );

          if (serverResponse) {
            this.persoWidgets[
              widgetSelector
            ].current.handleServerResponse(
              serverResponse,
              data.json.requestId,
              widgetSelector
            );
          }
        }

        break;
      }
    }
  }

sessionEnded

Indicates that the user finished the session with Personetics. Optional behavior in this case can be: Close the widget. You can also see the use of the config object we discussed in the previous section.
Case

  handlePersoEvents(data) {
    switch (data.requestString) {
      case "sessionEnded": {
        const { widgetSelector } = data.json.requestData;

        const widgetCallStack = this.state.widgetCallStack;

        this.widgetCallStack[widgetSelector].pop();
        const currentWidget =
          this.widgetCallStack[widgetSelector].at(-1)

        if (currentWidget) {
          this.widgetsConfig[widgetSelector].configurations.widgetType = currentWidget;
        } else {
          this.props.history.push("/loginPage");
        }

        this.navigateToWidget({  // Same function from previous examples
          widgetSelector,
          widgetType: currentWidget,
        });

        break;
      }
    }
  }

sessionError

Personetics sends the event in case of widget error. Banks can then use this event info as needed.
case 'sessionError': break;