Experience Manager SPA API - Bloomreach Experience - Headless Digital Experience Platform

Experience Manager SPA API

This page contains a detailed description of how SPAs integrate with the Experience manager application. It is provided as background information. If you follow our best practices and use the Bloomreach SPA SDKs, this integration is provided out-of-the-box through the SDK.

Introduction

Integrating your SPA with the Experience manager enables dynamic management of parts of your SPA. If you integrate your SPA with the Experience manager, CMS webmasters can customize, add, move, and remove components of your SPA, just like they can (and may be used to) for non-SPA pages. Such manageable components must be known to HST, and HST exposes them through the Page Delivery API to the SPA, which must be aware of the concept of components, as well as of the specific set of components in use by / available to the SPA.

Experience manager - SPA integration is optional. While it is possible to only integrate your SPA with Bloomreach Experience Manager's Page Delivery API, you then miss the ability to let CMS users manage the components exposed by the Page Delivery API.

Conceptually, the integration between your SPA and the Experience manager is bi-directional: If a CMS user changes parts of the Page Delivery, the SPA must be notified, and if the visitor of the (preview) page, displayed in the Experience manager, changes the state/route of the SPA, the Experience manager must be notified. In both scenarios, the goal is that your SPA and the Experience manager stay in sync.

API

The integration API between your SPA and the Experience manager consists of three parts, described in separate sub-sections:

  1. A JavaScript part
  2. A DOM part
  3. Page Delivery API part

JavaScript

PostMessage-based Remote Procedure Call (RPC)

All the communication between an SPA and the Experience manager relies on the postMessage API. The API provides an event-based communication between the SPA context and the brXM context, which works even with the Cross-Origin resources.

Since the API is based on the event model, there is no way to track the progress of processing one particular message. Because of that, all the two-communication with the Experience manager should be fully asynchronous. In that case, the SPA should send a message with a unique identifier so the brXM will reuse the identifier in the response message. The message identifier will be used by the SPA to determine the result of the remote call. The following communication should work both ways when the SPA responds to some of the requests coming from brXM.

Security Concerns

The browser does not limit a message origin for an SPA. That means that the SPA should always verify the origin of the message. In the SPA SDK, the event origin should always strictly match with the origin determined from the cmsBaseUrl configuration option. On the other side, brXM requires the origin to match with the origin from the org.hippoecm.hst.configuration.channel.PreviewURLChannelInfo_url property.

The same concerns are taken into account for the targetOrigin parameter. The SPA SDK as well as brXM use the cmsBaseUrl and org.hippoecm.hst.configuration.channel.PreviewURLChannelInfo_url accordingly to limit the recipients of an outgoing message.

Events

Some of the messages do not require an acknowldgement or a result from the remote side. Such messages are of the event type and do not contain any identifying data in the payload.

So those messages can be sent like in the following example:

window.parent.postMessage(
  {
    type: 'brxm:event',
    event: 'something',
    payload: { some: 'data' }
  },
  'http://example.com',
);

In the example above, the event message object consists of the following properties:

  • type - message type should always be brxm:event for the events.
  • event - the event name.
  • payload - the event parameters which will be serialized into a plain object by the browser before sending.

Requests

This type of message requires a response from the remote end and represents remote procedures. On the other side those procedures always have a 1 to 1 connection with some code or a function. The message payload must contain a unique identifier so the caller can identify the response.

const pool = new Map();

function callSomething(...payload) {
  return new Promise((resolve, reject) => {
    const id = Math.random();
    pool.set(id, [resolve, reject]);

    window.parent.postMessage(
      {
        id,
        payload,
        type: 'brxm:request',
        command: 'something',
      },
      'http://example.com',
    );
  });
}

In the example above, the event message object consists of the following properties:

  • type - message type should always be brxm:request for the remote calls.
  • command - the remote procedure name.
  • id - the remote call identifier.
  • payload - the remote procedure parameters array, which will be passed to the remote function.
Do not use the code snippet above because it is incomplete and does not handle message identifier collision.

Responses

This type of message contains a result or a thrown error of the remote call. The message should include an identifier received in the request. The following code shows handling messages on the SPA side:

const pool = new Map();

window.addEventListener('message', async (event) => {
  if (event.origin !== 'http://example.com') {
    return;
  }

  switch (event.data && event.data.type) {
    case 'brxm:request': return processRequest(event.data);
    case 'brxm:response': return processResponse(pool, event.data);
  }
});

async function processRequest(request) {
  try {
    const result = await window[request.command](...request.payload);

    window.parent.postMessage(
      {
        result,
        id: request.id,
        state: 'fulfilled',
        type: 'brxm:response',
      },
      'http://example.com',
    );
  } catch (error) {
    window.parent.postMessage(
      {
        result,
        id: request.id,
        state: 'rejected',
        type: 'brxm:response',
      },
      'http://example.com',
    );
  }
}

function processResponse(pool, response) {
  if (!pool.has(response.id)) {
    return;
  }

  const [resolve, reject] = pool.get(response.id);
  pool.delete(response.id);

  if (response.state === 'rejected') {
    reject(response.result);

    return;
  }

  resolve(response.resolve);
}

In the example above, the event message object consists of the following properties:

  • type - message type should always be brxm:response for the responses.
  • state - the call state. It can be either fulfilled or rejected.
  • id - the remote call identifier.
  • payload - the remote call result or a thrown exception.

Initial Rendering

The initial rendering flow is described in Image 1. The initialization consists of three steps:

  1. Notify brXM that the integration part is ready.
  2. Inject the requested brXM JavaScript asset.
  3. Synchronize all the overlays after the rendering.

Image 1. Initial rendering

Component Update

The component update flow is described in Image 2. The update consists of two steps:

  1. Notify the SPA about the component update.
  2. Synchronize all the overlays after the rendering.

Image 2. Component update

Reference

Origin Type Name Parameters Description
SPA Event ready none The event is being triggered by the SPA when the integration layer is ready.
SPA Procedure inject
  • string - The JavaScript asset absolute URL.
The remote procedure is requesting a JavaScript asset injection. The injected JavaScript asset initializes the Experience manager user interface.
brXM Event update
  • id: string - The updated component id.
  • properties: object - The updated component properties map.
The event is being triggered by brXM whenever the component updates via the Experience manager user interface. This event should trigger component rerendering on the SPA side using properties passed in the event payload.
brXM Procedure sync none* The remote procedure is initiating the Experience manager user interface synchronization. The procedure should be called after the initial rendering and after every component rerendering. This should synchronize the positions and sizes of the Experience manager controls.

* When no parameters are required, provide an empty array as payload ("[ ]")

 

Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?