feat(forms): gate Form A production embedding
This commit is contained in:
17 files changed
+284
-47
No files matched your search
@@ -20,8 +20,8 @@ if (!config.enabled || !config.url || !config.title || !config.fallbackText || !
|
||||
role="group"
|
||||
aria-labelledby="jotform-iframe-heading"
|
||||
data-jotform-form-a
|
||||
data-form-mode="isolated-test"
|
||||
data-form-environment="isolated-test"
|
||||
data-form-mode={config.mode}
|
||||
data-form-environment={config.environmentRole}
|
||||
data-logical-form="general"
|
||||
data-form-context={config.context}
|
||||
data-jotform-source="approved-form-a"
|
||||
|
||||
@@ -26,6 +26,7 @@ const formAIframe = resolveFormAIframe({
|
||||
rawMode: import.meta.env.PUBLIC_AIA_FORMS_MODE,
|
||||
liveApproved: import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED,
|
||||
isolatedTestApproved: import.meta.env.PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED,
|
||||
productionApproved: import.meta.env.PUBLIC_AIA_FORM_A_PRODUCTION_APPROVED,
|
||||
environmentRole: import.meta.env.PUBLIC_AIA_FORMS_ENVIRONMENT,
|
||||
allowIndexing: import.meta.env.PUBLIC_ALLOW_INDEXING,
|
||||
route: campaignRoute,
|
||||
|
||||
@@ -6,6 +6,7 @@ const formAIframe = resolveFormAIframe({
|
||||
rawMode: import.meta.env.PUBLIC_AIA_FORMS_MODE,
|
||||
liveApproved: import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED,
|
||||
isolatedTestApproved: import.meta.env.PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED,
|
||||
productionApproved: import.meta.env.PUBLIC_AIA_FORM_A_PRODUCTION_APPROVED,
|
||||
environmentRole: import.meta.env.PUBLIC_AIA_FORMS_ENVIRONMENT,
|
||||
allowIndexing: import.meta.env.PUBLIC_ALLOW_INDEXING,
|
||||
route: '/lp/early-intervention',
|
||||
@@ -38,7 +39,7 @@ const formAIframe = resolveFormAIframe({
|
||||
</div>
|
||||
</section>
|
||||
<div class="qr-process-band">
|
||||
<section class="qr-section qr-contact-section" aria-label="Contact AIA about early intervention"><div id="consultation" class="lead-form" aria-labelledby="consultation-heading" role="group" data-forms-mode={formAIframe.enabled ? 'isolated-test' : 'off'}>
|
||||
<section class="qr-section qr-contact-section" aria-label="Contact AIA about early intervention"><div id="consultation" class="lead-form" aria-labelledby="consultation-heading" role="group" data-forms-mode={formAIframe.enabled ? formAIframe.mode : 'off'}>
|
||||
|
||||
<div class="form-heading">
|
||||
<h2 id="consultation-heading">Contact AIA about early intervention</h2>
|
||||
|
||||
@@ -19,6 +19,7 @@ const formAIframe = resolveFormAIframe({
|
||||
rawMode: import.meta.env.PUBLIC_AIA_FORMS_MODE,
|
||||
liveApproved: import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED,
|
||||
isolatedTestApproved: import.meta.env.PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED,
|
||||
productionApproved: import.meta.env.PUBLIC_AIA_FORM_A_PRODUCTION_APPROVED,
|
||||
environmentRole: import.meta.env.PUBLIC_AIA_FORMS_ENVIRONMENT,
|
||||
allowIndexing: import.meta.env.PUBLIC_ALLOW_INDEXING,
|
||||
route,
|
||||
|
||||
@@ -25,9 +25,10 @@ export function formModeForRuntime(config: FormModeConfig): 'off' | 'mock' {
|
||||
|
||||
export type FormAIframeReason =
|
||||
| 'enabled'
|
||||
| 'mode-not-isolated-test'
|
||||
| 'mode-not-authorized'
|
||||
| 'isolated-test-not-approved'
|
||||
| 'live-approval-not-disabled'
|
||||
| 'production-approval-not-granted'
|
||||
| 'invalid-environment-role'
|
||||
| 'indexing-enabled'
|
||||
| 'route-not-authorized'
|
||||
@@ -38,6 +39,8 @@ export interface FormAIframeConfig {
|
||||
reason: FormAIframeReason;
|
||||
route: FormAIframeRoute | string;
|
||||
locale: 'en' | 'es' | string;
|
||||
mode?: 'isolated-test' | 'live';
|
||||
environmentRole?: 'isolated-test' | 'production';
|
||||
context?: FormAIframeContext;
|
||||
url?: string;
|
||||
title?: string;
|
||||
@@ -49,6 +52,7 @@ export interface ResolveFormAIframeOptions {
|
||||
rawMode: string | undefined;
|
||||
liveApproved: string | undefined;
|
||||
isolatedTestApproved: string | undefined;
|
||||
productionApproved: string | undefined;
|
||||
environmentRole: string | undefined;
|
||||
allowIndexing: string | undefined;
|
||||
route: string;
|
||||
@@ -57,12 +61,13 @@ export interface ResolveFormAIframeOptions {
|
||||
|
||||
/**
|
||||
* The owner-approved Form A identifier is deliberately only made into an
|
||||
* iframe URL after every isolated-test gate has passed. It is not a
|
||||
* production setting and it is not enabled by an API URL or a live mode.
|
||||
* iframe URL after every explicit isolated-test or production gate has
|
||||
* passed. It is never enabled by an API URL, hostname, or publication flag.
|
||||
*/
|
||||
export const formAIframeOrigin = 'https://form.jotform.com' as const;
|
||||
export const formAIframeId = '262946078314059' as const;
|
||||
const isolatedTestEnvironmentRole = 'isolated-test';
|
||||
const productionEnvironmentRole = 'production';
|
||||
|
||||
export type FormAIframeContext = 'contact' | 'landing';
|
||||
export type FormAIframeRoute =
|
||||
@@ -81,7 +86,8 @@ interface FormAIframePlacement {
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the complete fixed allowlist for the isolated Form A adapter.
|
||||
* This is the complete fixed allowlist for the Form A adapter in both
|
||||
* explicitly approved modes.
|
||||
* Campaign placements are an owner-approved override of their earlier
|
||||
* logical Form B mapping. The shared compact landing presentation uses the
|
||||
* same fixed `landing` context; no route may supply its own context or URL.
|
||||
@@ -121,8 +127,9 @@ export function buildFormAIframeUrl(locale: 'en' | 'es', context: FormAIframeCon
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the real Form A iframe only in an explicitly approved isolated
|
||||
* test build. `live` is intentionally not a valid route to this adapter.
|
||||
* Enables the real Form A iframe only through one of two explicit gates:
|
||||
* a non-indexing isolated test, or a separately approved production build.
|
||||
* No API URL, hostname, indexing setting, or form ID can activate it alone.
|
||||
*/
|
||||
export function resolveFormAIframe(options: ResolveFormAIframeOptions): FormAIframeConfig {
|
||||
const disabled = (reason: Exclude<FormAIframeReason, 'enabled'>): FormAIframeConfig => ({
|
||||
@@ -132,11 +139,25 @@ export function resolveFormAIframe(options: ResolveFormAIframeOptions): FormAIfr
|
||||
locale: options.locale,
|
||||
});
|
||||
const config = resolveFormMode(options.rawMode, options.liveApproved);
|
||||
if (config.mode !== 'isolated-test') return disabled('mode-not-isolated-test');
|
||||
if (options.liveApproved?.trim().toLowerCase() !== 'false') return disabled('live-approval-not-disabled');
|
||||
if (!isTrue(options.isolatedTestApproved)) return disabled('isolated-test-not-approved');
|
||||
if (options.environmentRole?.trim().toLowerCase() !== isolatedTestEnvironmentRole) return disabled('invalid-environment-role');
|
||||
if (isTrue(options.allowIndexing)) return disabled('indexing-enabled');
|
||||
const environmentRole = options.environmentRole?.trim().toLowerCase();
|
||||
let mode: 'isolated-test' | 'live';
|
||||
let approvedEnvironment: 'isolated-test' | 'production';
|
||||
|
||||
if (config.mode === 'isolated-test') {
|
||||
if (options.liveApproved?.trim().toLowerCase() !== 'false') return disabled('live-approval-not-disabled');
|
||||
if (!isTrue(options.isolatedTestApproved)) return disabled('isolated-test-not-approved');
|
||||
if (environmentRole !== isolatedTestEnvironmentRole) return disabled('invalid-environment-role');
|
||||
if (isTrue(options.allowIndexing)) return disabled('indexing-enabled');
|
||||
mode = 'isolated-test';
|
||||
approvedEnvironment = 'isolated-test';
|
||||
} else if (config.mode === 'live') {
|
||||
if (!isTrue(options.productionApproved)) return disabled('production-approval-not-granted');
|
||||
if (environmentRole !== productionEnvironmentRole) return disabled('invalid-environment-role');
|
||||
mode = 'live';
|
||||
approvedEnvironment = 'production';
|
||||
} else {
|
||||
return disabled('mode-not-authorized');
|
||||
}
|
||||
|
||||
const placement = formAIframePlacements.find((candidate) => candidate.route === options.route);
|
||||
if (!placement) return disabled('route-not-authorized');
|
||||
@@ -147,6 +168,8 @@ export function resolveFormAIframe(options: ResolveFormAIframeOptions): FormAIfr
|
||||
reason: 'enabled',
|
||||
route: options.route,
|
||||
locale: placement.locale,
|
||||
mode,
|
||||
environmentRole: approvedEnvironment,
|
||||
context: placement.context,
|
||||
url: buildFormAIframeUrl(placement.locale, placement.context),
|
||||
...formAIframeCopy(placement.locale),
|
||||
|
||||
Reference in new issue
Block a user