libs/core-forms/src/lib/backend-integration-plugin/backend-integration-plugin.ts
Methods |
constructor()
|
| registerActions |
registerActions()
|
|
Returns :
void
|
⚠️ This plugin is for internal usage only!
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"
}
]
}
]@allianz/taly-core-forms package to the libraries array of the pages.jsonc file"libraries": [
{
"package": "@allianz/taly-core-forms",
"version": "CURRENT_VERSION"
}
]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-"
}
}
]
}
],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.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,
PfeServiceActivatorService
} from '@allianz/ngx-pfe';
import { BFF_BASE_URL_TOKEN } from '@allianz/taly-core';
import { BuildingBlockConfiguration, DynamicFormBBConfiguration } from '@allianz/taly-core/schemas';
import { PfeRuntimeConfigService } from '@allianz/taly-pfe-connector';
import { HttpClient, HttpParams } from '@angular/common/http';
import { effect, inject, Injectable, Signal } from '@angular/core';
import {
CORE_FORMS_BACKEND_INTEGRATION_PLUGIN_OPTIONS,
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 pfeServiceActivatorService = inject(PfeServiceActivatorService);
private readonly http = inject(HttpClient);
private options = inject<Signal<CoreFormsBackendIntegrationPluginOptions>>(
CORE_FORMS_BACKEND_INTEGRATION_PLUGIN_OPTIONS
);
private readonly requiredStringProperties: Array<keyof CoreFormsBackendIntegrationPluginOptions> =
['formId', 'tenant', 'site', 'instance', 'talyTurnstileFormfieldId', 'talyPrefix'];
private readonly coreFormsBaseUrl = inject(BFF_BASE_URL_TOKEN);
constructor() {
effect(async () => {
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 (!this.options() || !Object.keys(this.options()).length) {
return;
}
if (isCoreFormsBackendIntegrationPluginOptions(this.options())) {
await this.fetchTurnstileSitekey();
} else {
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'
});
this.pfeServiceActivatorService.serviceActivatorCallInProgress$.next(true);
return new Promise<void>((resolve) => {
this.http.post<{ message: string }>(this.coreFormsBaseUrl(), data).subscribe({
next: (response) => {
this.pfeBusinessService.storeValueByExpression(
'$.coreFormSubmissionSuccessful',
response.message === 'successful'
);
this.pfeServiceActivatorService.serviceActivatorCallInProgress$.next(false);
resolve();
},
error: () => {
this.pfeBusinessService.storeValueByExpression('$.coreFormSubmissionSuccessful', false);
this.pfeServiceActivatorService.serviceActivatorCallInProgress$.next(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(): Promise<void> {
if (!this.coreFormsBaseUrl()) {
return Promise.reject(
'Base URL is not defined. Please forward `{"bffBaseUrl": ""}` via `runtime-app-config` input'
);
}
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();
const params = new HttpParams()
.set('referrer', referrer)
.set('fields', relevantFormFieldIds.join(','));
this.pfeServiceActivatorService.serviceActivatorCallInProgress$.next(true);
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);
this.pfeBusinessService.storeValueByExpression('$._taly.turnstileSitekeyError', null);
this.pfeServiceActivatorService.serviceActivatorCallInProgress$.next(false);
resolve();
},
error: (error) => {
this.pfeBusinessService.storeValueByExpression('$._taly.turnstileSitekey', '');
this.pfeBusinessService.storeValueByExpression('$._taly.turnstileSitekeyError', error);
console.error(error);
this.pfeServiceActivatorService.serviceActivatorCallInProgress$.next(false);
resolve();
}
});
});
}
private getRelevantFormFieldIds() {
const { pages } = this.runtimeConfigService.pagesConfig();
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);
}
}