fix(embed): single, connection-aware submission error across hosted-web flows - #691
fix(embed): single, connection-aware submission error across hosted-web flows#691Kwame Yeboah (Yeboahmedia) wants to merge 3 commits into
Conversation
…pload
When id_info is supplied, the selection and input screens are skipped and
accepting the selfie ("Yes, use this") submits directly via the publish
handler, which calls handleFormSubmit() with no event. That path never runs
resetForm() — which clears prior `.validation-message` nodes — so every failed
submission prepended a brand-new error paragraph, stacking one message per
click.
- Reuse a single #submission-error-message element so repeated failures update
the message in place instead of stacking.
- Surface the actionable "check your connection" copy for network-level
failures (offline/timeout, detected via the isNetworkError flag set by
fetchWithTimeout and walked up the cause chain) instead of the generic
"Something went wrong".
Adds an id_info regression test covering repeated failed uploads.
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
🔍 Semgrep Security Scan Results✅ No security findings detected by |
PR Code Suggestions ✨No code suggestions found for the PR. |
Extends the biometric-kyc fix to every hosted-web product flow and removes the duplicated per-entry-point copies by moving the logic into a shared submission-error.js module used by biometric-kyc, ekyc, basic-kyc, doc-verification, enhanced-document-verification and smartselfie-auth. - displayErrorMessage now reuses a single #submission-error-message element so repeated failed submissions update the message in place instead of stacking a new one per click. Guards against a missing <main> so it can never mask the original error inside a catch handler. - Network-level failures (offline / timeout) show the actionable pages.error.checkInternet copy instead of the generic message, via a shared submissionErrorMessage() helper. The isNetworkError cause-chain walk is depth-capped so a circular cause chain can't loop forever. Addresses the automated reviewer feedback (cycle guard + null-main guard). e-signature is left unchanged — its only displayErrorMessage use is a checkbox validation already cleared by resetForm(), so it has no stacking bug.
|
Thanks @prfectionist — both points were useful and are addressed in the new shared
While here, I also extended the fix beyond Biometric KYC to every hosted-web flow with the same stacking/generic-message pattern and de-duplicated the per-entry-point copies into the shared module. |
|
This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days. |
|
This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days. |
|
This branch has been deployed to s3 / cloudfront. ✅ Preview URL for Smart Camera Web: ✅ Preview URL for Embed: ✅ Preview URL for Web Client (Sandbox): ✅ Preview URL for Web Client (Production): |
Summary
When a hosted-web submission fails on a network outage, the on-page error was generic ("Something went wrong") and stacked a new copy on every retry. This surfaces an actionable connection message and shows it only once. Originally scoped to Biometric KYC (per the report); now applied to every hosted-web flow that had the same pattern, with the duplicated code consolidated into one shared module.
Root cause
displayErrorMessagecreated a new<p>and prepended it to<main>every time. Most flows relied onresetForm()(which clears.validation-messagenodes) to remove the previous one — but theid_infoskip path in Biometric KYC accepts the selfie via thesmart-camera-web.publishhandler, which callshandleFormSubmit()without an event and therefore never runsresetForm(). So each failed click stacked another message. Separately, the caught error always mapped topages.error.generic, which doesn't tell the user the real problem is their connection.Changes
New shared module
packages/embed/src/js/submission-error.js:displayErrorMessage(message)— reuses a single#submission-error-messageelement (updates text in place, no stacking). Keeps thevalidation-messageclass so existingresetForm()cleanup still removes it, disables the class'stext-transformso full-sentence copy reads naturally, and no-ops when there is no<main>so it can't mask the original error inside a catch.isNetworkFailure(error)— walks thecausechain for theisNetworkErrorflag set byfetchWithTimeout, depth-capped so a circularcausechain can't loop forever.submissionErrorMessage(error, translate)— returnspages.error.checkInternetfor network drops (ornavigator.onLine === false), otherwisepages.error.generic.Wired into:
biometric-kyc,ekyc,basic-kyc,doc-verification,enhanced-document-verification,smartselfie-auth(each now imports the shared helpers and drops its localdisplayErrorMessage). No new locale keys —pages.error.checkInternetalready exists in all three bundled locales.e-signatureis intentionally left unchanged: its onlydisplayErrorMessageuse is a checkbox validation already cleared byresetForm(), so it has no stacking bug.Cypress regression test (
cypress/tests/id-info.cy.cjs) forces the upload to fail and clicks "Yes, use this" twice, asserting the descriptive copy appears and exactly one message is shown.Reviewer feedback
Both
prfectionistfindings are addressed in the shared module:cause-chain walk → depth cap (max 10).main→ earlyreturnguard.Affected packages
packages/web-componentspackages/embedpackages/smart-camera-webexample/Test plan
npx prettier --checkon all touched files — cleaneslinton touched files — no new violations (added code is clean; pre-existing file errors are unrelated and unchanged)npm run build(embed) — bundles cleanly; verified each entry point's output contains the new single-element + connection-aware logicnpm test(embed) — the Cypress binary on this machine fails its own smoke test (bad option: --no-sandbox), so the e2e was not run locally; the spec follows the existing passingid_infopatterns and CI will exercise itChangelog
Unreleasedentry: