File

libs/core-forms/src/lib/backend-integration-plugin/backend-integration-plugin.ts

Index

Methods

Constructor

constructor()

Methods

registerActions
registerActions()
Returns : void


type: plugin title: backend-integration-plugin description: This plugin transforms the input data of a dynamic form and sends it to the CoreForms backend. In addition, it fetches the sitekey that is required for the Turnstile.

⚠️ This plugin is for internal usage only!

How to use the CoreForms backend plugin?

  1. In your pfe.jsonc file, add the CORE_FORMS_BACKEND_INTEGRATION to the onPageLeaveActions of the page where the data should be sent to the backend. There is no action provided for catching the sitekey of the Turnstile. This is done automatically by the plugin as soon as it has been initialized. The sitekey will be stored in the $._taly.turnstileSitekey key of the PFE state. If the request for the sitekey fails, turnstileSitekey will be set to an empty string.
"pages": [
  {
    "pageId": "some-page-id",
    "onPageLeaveActions": [
      {
        "type": "CORE_FORMS_BACKEND_INTEGRATION"
      }
    ]
  }
]
  1. Add the @allianz/taly-core-forms package to the libraries array of the pages.jsonc file
"libraries": [
  {
    "package": "@allianz/taly-core-forms",
    "version": "CURRENT_VERSION"
  }
]
  1. Add the BackendIntegrationPluginModule with the options below to the plugins array of the pages.jsonc file. talyPrefix describes how the dynamic form ids have to start to be recognized by the plugin.
"plugins": [
  {
    "package": "@allianz/taly-core-forms",
    "modules": [
      {
        "name": "BackendIntegrationPluginModule",
        "options": {
          "formId": "1740391937943_v3",
          "tenant": "sandbox",
          "site": "coreforms",
          "instance": "emeaprd",
          "talyTurnstileFormfieldId": "turnstile-token",
          "talyPrefix": "core-forms-"
        }
      }
    ]
  }
],
  1. Ensure that exactly one Turnstile custom component is added to your form with the id (talyTurnstileFormfieldId) you specified in step 2. This Turnstile custom component has to be a child of a dynamic form with an id starting with the talyPrefix you defined in step 2.

Example Configuration:

The snippet below shows an example pages.jsonc configuration of the backend and turnstile plugin in combination with a dynamic form with one input field.

  "libraries": [
    {
      "package": "@allianz/taly-core-forms",
      "version": ""
    }
  ],
  "plugins": [
    {
      "package": "@allianz/taly-core-forms",
      "modules": [
        {
          "name": "BackendIntegrationPluginModule",
          "options": {
            "formId": "123456789",
            "tenant": "sandbox",
            "site": "coreforms",
            "instance": "emeaprd",
            "talyTurnstileFormfieldId": "turnstile-token",
            "talyPrefix": "core-forms-"
          }
        }
      ]
    },
    {
      "package": "@allianz/taly-core-forms",
      "modules": [
        {
          "name": "TurnstileComponentPluginModule"
        }
      ]
    }
  ],
  "pages": [
    {
      "id": "first-page",
      "blocks": [
        {
          "id": "core-forms-my-dynamic-form1",
          "form": {
            "layout": {
              "type": "ONE_COLUMN"
            },
            "fields": [
              {
                "id": "street",
                "type": "INPUT",
                "label": "street",
                "inputType": "text"
              },
              {
                "type": "CUSTOM_COMPONENT",
                "name": "TurnstileComponent",
                "id": "turnstile-token",
                "config": {
                  "sitekey": "1x00000000000000000000AA"
                }
              }
            ]
          }
        }
      ],
    }
  ]

import { PfeActionsService, PfeBusinessService } from '@allianz/ngx-pfe';
import {
  BuildingBlockConfiguration,
  DynamicFormBBConfiguration,
  PageConfiguration
} from '@allianz/taly-core/schemas';
import { PfeRuntimeConfigService } from '@allianz/taly-pfe-connector';
import { HttpClient, HttpParams } from '@angular/common/http';
import { effect, inject, Injectable } from '@angular/core';
import { CoreFormsBackendIntegrationPluginOptions } from './backend-integration-plugin.module';

@Injectable()
export class BackendIntegrationPlugin {
  private readonly pfeActionService = inject(PfeActionsService);
  private readonly pfeBusinessService = inject(PfeBusinessService);
  private readonly runtimeConfigService = inject(PfeRuntimeConfigService);
  private readonly http = inject(HttpClient);
  private options!: CoreFormsBackendIntegrationPluginOptions;
  private readonly requiredStringProperties: Array<keyof CoreFormsBackendIntegrationPluginOptions> =
    ['formId', 'tenant', 'site', 'instance', 'talyTurnstileFormfieldId', 'talyPrefix'];
  private readonly coreFormsBaseUrl = 'https://forms.di-api.allianz.com/prd/forms';

  constructor() {
    effect(async () => {
      const { pages, plugins } = this.runtimeConfigService.pagesConfig();
      const coreFormsPluginConfig = plugins?.find(
        (plugin) => plugin.package === '@allianz/taly-core-forms'
      );
      const backendIntegrationPluginModule = coreFormsPluginConfig?.modules?.find(
        (module) => module.name === 'BackendIntegrationPluginModule'
      );

      const isCoreFormsBackendIntegrationPluginOptions = (
        obj: unknown
      ): obj is CoreFormsBackendIntegrationPluginOptions => {
        if (!obj || typeof obj !== 'object') {
          return false;
        }
        const options = obj as Record<string, unknown>;
        return this.requiredStringProperties.every(
          (prop) => prop in options && typeof options[prop] === 'string'
        );
      };

      if (isCoreFormsBackendIntegrationPluginOptions(backendIntegrationPluginModule?.options)) {
        this.options = backendIntegrationPluginModule.options;
        await this.fetchTurnstileSitekey(pages);
      } else {
        this.pfeBusinessService.storeValueByExpression('$._taly.turnstileSitekey', '');
        console.error(
          `Invalid options format for BackendIntegrationPlugin. Please provide the following plugin options: ${this.requiredStringProperties.join(
            ', '
          )}.`
        );
      }
    });
  }

  registerActions() {
    this.pfeActionService.registerAction(
      'CORE_FORMS_BACKEND_INTEGRATION',
      this.sendFormDataCoreFormsBackend.bind(this)
    );
  }

  private sendFormDataCoreFormsBackend(): Promise<void> {
    const fields = this.getCoreFormsState(this.options.talyPrefix);

    const turnstileToken = fields?.[this.options.talyTurnstileFormfieldId];

    if (!turnstileToken || typeof turnstileToken !== 'string') {
      throw new Error('Could not find the turnstile token in the form');
    }

    delete fields[this.options.talyTurnstileFormfieldId];

    return this.postFormData(turnstileToken, fields);
  }

  private postFormData(
    turnstileToken: string,
    fieldsForBackend: Record<string, unknown>
  ): Promise<void> {
    const { formId, tenant, site, instance } = this.options;
    const referrer = window.location.href;
    const data = JSON.stringify({
      version: 1,
      formId,
      tenant,
      site,
      instance,
      referrer,
      fields: fieldsForBackend,
      turnstileCaptcha: {
        cfturnstileresponse: turnstileToken
      },
      captchaType: 'turnstile'
    });

    const options = {
      hostname: 'forms.di-api.allianz.com',
      port: 443,
      path: '/prd/forms',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        accept: 'application/json'
      }
    };

    return new Promise<void>((resolve) => {
      this.http.post<{ message: string }>(this.coreFormsBaseUrl, data, options).subscribe({
        next: (response) => {
          this.pfeBusinessService.storeValueByExpression(
            '$.coreFormSubmissionSuccessful',
            response.message === 'successful'
          );
          resolve();
        },
        error: () => {
          this.pfeBusinessService.storeValueByExpression('$.coreFormSubmissionSuccessful', false);
          resolve();
        }
      });
    });
  }

  private getCoreFormsState(prefix: string): Record<string, unknown> {
    const pfeState = this.pfeBusinessService.getFullState();

    return Object.entries(pfeState)
      .filter(([formKey]) => formKey.startsWith(prefix)) // Filter keys that start with the prefix
      .filter(([, formfields]) => typeof formfields === 'object' && formfields !== null)
      .reduce<Record<string, unknown>>((relevantFields, [, formfields]) => {
        Object.entries(formfields).forEach(([formfieldKey, formfieldValue]) => {
          if (relevantFields[formfieldKey]) {
            console.warn(
              `The form field with id "${formfieldKey}" is present in more than one form with prefix "${prefix}". Please provide a unique id for each field.`
            );
            return;
          }
          if (formfieldValue !== null) {
            relevantFields[formfieldKey] = formfieldValue;
          }
        });
        return relevantFields;
      }, {});
  }

  private fetchTurnstileSitekey(pages: PageConfiguration[]): Promise<void> {
    const requestUrl = [
      this.coreFormsBaseUrl,
      this.options.instance,
      this.options.tenant,
      this.options.site,
      this.options.formId
    ].join('/');

    const referrer = window.location.href;
    const relevantFormFieldIds = this.getRelevantFormFieldIds(pages);
    const params = new HttpParams()
      .set('referrer', referrer)
      .set('formIds', relevantFormFieldIds.join(','));

    return new Promise<void>((resolve) => {
      this.http.head(requestUrl, { params, observe: 'response' }).subscribe({
        next: (response) => {
          const sitekey = response.headers.get('x-cf-turnstile-sitekey');
          this.pfeBusinessService.storeValueByExpression('$._taly.turnstileSitekey', sitekey || '');
          resolve();
        },
        error: (error) => {
          this.pfeBusinessService.storeValueByExpression('$._taly.turnstileSitekey', '');
          console.error(error);
          resolve();
        }
      });
    });
  }

  private getRelevantFormFieldIds(pages: PageConfiguration[]) {
    const { talyTurnstileFormfieldId, talyPrefix } = this.options;

    function isDynamicFormBbConfig(
      block: DynamicFormBBConfiguration | BuildingBlockConfiguration | undefined
    ): block is DynamicFormBBConfiguration {
      if (!block) {
        return false;
      }
      return Boolean((block as DynamicFormBBConfiguration).form);
    }

    const formFields = pages
      .flatMap((page) => page?.blocks)
      .filter(isDynamicFormBbConfig)
      .filter((block) => block?.id.startsWith(talyPrefix))
      .flatMap((block) => block?.form?.fields);

    const relevantFormFields = formFields
      .filter((formfield) => formfield?.id !== talyTurnstileFormfieldId)
      .filter(
        (formfield) =>
          formfield.type !== 'LINE_BREAK' &&
          formfield.type !== 'HEADLINE' &&
          formfield.type !== 'PARAGRAPH'
      );

    return relevantFormFields.map((formfield) => formfield.id);
  }
}

results matching ""

    No results matching ""