From a2eae9dd64a1f145d4161b96c0e116812147e567 Mon Sep 17 00:00:00 2001 From: Ken Lee Shu Ming Date: Sun, 12 Jan 2025 20:33:08 +0800 Subject: [PATCH] feat(iframe): add frame messaging for paysg (#7979) * feat: update iframe parent on form submission * fix: remove localhost from trusted origins --------- Co-authored-by: Antariksh Mahajan --- .../public-form/PublicFormProvider.tsx | 7 +++++++ .../public-form/utils/iframeMessaging.ts | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 frontend/src/features/public-form/utils/iframeMessaging.ts diff --git a/frontend/src/features/public-form/PublicFormProvider.tsx b/frontend/src/features/public-form/PublicFormProvider.tsx index 5e29cb7f4e..fa36191b26 100644 --- a/frontend/src/features/public-form/PublicFormProvider.tsx +++ b/frontend/src/features/public-form/PublicFormProvider.tsx @@ -69,6 +69,7 @@ import { import { FormNotFound } from './components/FormNotFound' import { decryptAttachment, decryptSubmission } from './utils/decryptSubmission' +import { postIFrameMessage } from './utils/iframeMessaging' import { usePublicAuthMutations, usePublicFormMutations } from './mutations' import { PublicFormContext, SubmissionData } from './PublicFormContext' import { useEncryptedSubmission, usePublicFormView } from './queries' @@ -629,6 +630,8 @@ export const PublicFormProvider = ({ } } + postIFrameMessage({ state: 'submitting' }) + switch (form.responseMode) { case FormResponseMode.Email: { // Using mutateAsync so react-hook-form goes into loading state. @@ -762,6 +765,7 @@ export const PublicFormProvider = ({ paymentData, }) => { trackSubmitForm(form) + postIFrameMessage({ state: 'submitted', submissionId }) if (paymentData) { navigate(getPaymentPageUrl(formId, paymentData.paymentId)) @@ -784,6 +788,7 @@ export const PublicFormProvider = ({ }, ) .catch(async (error) => { + postIFrameMessage({ state: 'submitError' }) datadogLogs.logger.warn(`handleSubmitForm: ${error.message}`, { meta: { ...logMeta, @@ -826,6 +831,7 @@ export const PublicFormProvider = ({ paymentData, }) => { trackSubmitForm(form) + postIFrameMessage({ state: 'submitted', submissionId }) if (paymentData) { navigate(getPaymentPageUrl(formId, paymentData.paymentId)) storePaymentMemory(paymentData.paymentId) @@ -847,6 +853,7 @@ export const PublicFormProvider = ({ }, ) .catch(async (error) => { + postIFrameMessage({ state: 'submitError' }) // TODO(#5826): Remove when we have resolved the Network Error datadogLogs.logger.warn( `handleSubmitForm: submit with virus scan`, diff --git a/frontend/src/features/public-form/utils/iframeMessaging.ts b/frontend/src/features/public-form/utils/iframeMessaging.ts new file mode 100644 index 0000000000..199ccf8f19 --- /dev/null +++ b/frontend/src/features/public-form/utils/iframeMessaging.ts @@ -0,0 +1,21 @@ +export type PublicFormIFrameMessage = + | { state: 'submitting' | 'submitError' } + | { state: 'submitted'; submissionId: string } + +const TRUSTED_TARGET_ORIGINS = [ + 'https://pay.gov.sg', + 'https://exp.pay.gov.sg', + 'https://staging.pay.gov.sg', +] + +export const postIFrameMessage = (message: PublicFormIFrameMessage): void => { + // De-risk by wrapping in try-catch even though this is synchronous. This should + // never block form submission. + try { + TRUSTED_TARGET_ORIGINS.forEach((origin) => { + window.parent.postMessage(message, origin) + }) + } catch (error) { + console.warn('Error while posting form state to iframe parent', error) + } +}