feat(react-native): identity verification component and takeover flag - #2019
Merged
Conversation
🦋 Changeset detectedLatest commit: bc17d69 The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
|
Reviews (1): Last reviewed commit: "feat(checkout): accept identityVerificat..." | Re-trigger Greptile |
…dentity verification
Contributor
|
Reviews (2): Last reviewed commit: "refactor(react-native): drop speculative..." | Re-trigger Greptile |
Contributor
|
Reviews (3): Last reviewed commit: "fix(react-native): enable DOM storage fo..." | Re-trigger Greptile |
luans-qa
reviewed
Aug 12, 2026
luans-qa
reviewed
Aug 12, 2026
luans-qa
reviewed
Aug 12, 2026
Contributor
|
Reviews (4): Last reviewed commit: "fix(react-native): report failed identit..." | Re-trigger Greptile |
…and react-native (#2020)
Contributor
|
Reviews (5): Last reviewed commit: "feat(checkout): add useIdentityVerificat..." | Re-trigger Greptile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ships
CrossmintIdentityVerificationfor React Native, plus theidentityVerificationHandling="external"takeover flag, so an RN merchant can render Crossmint's hosted verification step in their own screen instead of inside embedded checkout.The hosted page at
/sdk/unstable/identity-verificationalready detectswindow.ReactNativeWebViewand speaks the RN bridge protocol, so this is client-only: no crossbit-main change, no new wire contract, no new dependency.What's here
CrossmintIdentityVerificationhosts that page inRNWebView, builds its URL through the existingcreateIdentityVerificationService(...).iframe.getUrl(props), and subscribes the five shipped events (ui:height.changed,kyc:ready,kyc:completed,kyc:cancelled,kyc:error) throughWebViewParent, surfacing them as the same props the web component takes.Details worth review:
onError. Without it a 500 or an offline device produces nokyc:readyand nokyc:error, and the view stays 0px tall, so the merchant cannot distinguish a dead page from a loading one. BothonErrorandonHttpErrormap to the shipped contract's non-retriablewidget-unavailable. No main-frame guard is needed, unlike Flutter's equivalent:RNCWebViewClientoverrides only the legacy 4-argonReceivedError(main-frame only by Android contract, never the modernWebResourceRequestoverload) and wrapsonReceivedHttpErrorinif (request.isForMainFrame()), so a Persona sub-frame hiccup cannot reach these.retriable: falsematches Flutter and is honest: the component does not retry on its own.client.on()returns, not the event name.off(name)removes nothing, which is the leakEmbeddedCheckoutV3WebView.tsxhas today. The unmount test covers it.domStorageEnabledis set explicitly. Android'sWebSettingsdefault is off and react-native-webview applies no default of its own: the codegen spec declaresdomStorageEnabled?: booleanwith noWithDefault, and the JS component has nodefaultProps, so an unset prop means the native setter is never called. (The prop's own JSDoc claiming "The default value istrue" is stale.) The hosted Persona widget touchinglocalStoragewould then fail on Android only. Checkout's WebView andExportPrivateKeyButtonboth set it for the same reason. Not copying the rest of checkout's Android block:mixedContentMode="always",allowFileAccessandallowUniversalAccessFromFileURLsare security loosenings with no identified need here.WebViewParentreceiveswebViewRefitself rather than a{ current: node }snapshot, so the transport reads the live node. Inert today (identityVerificationOutgoingEventsis{}so nothing sends, and norecoveryis configured so nothing reloads), but it is also less code than the pattern it was copied from.heightstarting at 0 is deliberate, matching both in-repo precedents: the hosted page reports ~660 off a fixed-pixel Persona container regardless of the WebView's own height, and a 0-height RN WebView still loads and runs JS.getIdentityVerificationCredentialsis re-exported fromclient-sdk-base. It is platform-neutral and already tested there, so this is a re-export rather than an implementation, and the build is what proves the symbol exists.identityVerificationHandlingmoves fromCrossmintEmbeddedCheckoutV3WebPropsonto the shared common props. RN builds its checkout URL with the samegetUrl(props)andappendObjectToQueryParams, so moving the field is the entire change: no forwarding code.CrossmintEmbeddedCheckoutV3WebPropsstays as a deprecated alias because deleting an exported type is breaking.Test runner
The package had a
vitest.config.tsand aturbo.jsondeclaringtest:vitest, but no script, no test deps and no test files, so CI ran nothing here. Added the script and deps; this is the package's first suite, 10 tests.Two test-environment notes:
react-native-get-random-valuesis stubbed via avitest.config.tsalias rather thanvi.mock. rn-window's transport imports that polyfill, which requires Flow-typed react-native and is unparseable by vitest, and rn-ui and rn-window resolve different installed copies (2.0.0 vs 1.11.0), so avi.mockin the test file never intercepts the copy rn-window's dist actually imports. The alias works regardless of importer and covers future tests in this package.react-native-webviewmock forwardsref. React 19 does not assign refs to function components, and without itwebViewRef.currentstays null, the client is never constructed, and every event assertion passes vacuously.Known and accepted
An event arriving before the post-mount effect constructs the client is dropped, since
onMessageforwards throughclient?.handleMessage. The window is one render against a full page load, and checkout has the same shape.onMessageitself stays a defined function across that window on purpose: react-native-webview derivesmessagingEnabledfromtypeof onMessage === "function", so passingclient?.handleMessagedirectly would create the native view with messaging off and nowindow.ReactNativeWebViewfor the hosted page to detect.Not here
Camera permission code (
mediaCapturePermissionGrantTypeon iOS, aCAMERAdeclaration inwithCrossmintUI) is gated on Leg 1 of the device-test protocol. The documentation is not:RNCWebChromeClient.onPermissionRequestmapsRESOURCE_VIDEO_CAPTUREtoManifest.permission.CAMERAand callsActivity.requestPermissions, which cannot succeed unless the host app declares it, and that holds regardless of what Leg 1 finds. So the reference snippet now carries a note naming both the Android manifest entry andNSCameraUsageDescription. Run A may showreact-native-webview@13.15.0already grants capture, in which case the deliverable is a docs note. If it shows Android denies it, the fix belongs inEmbeddedCheckoutV3WebView.tsxtoo, since the shipped in-checkout KYC step has the same gap. Writing it before the test means guessing.Manual end-to-end verification of the flag is also not possible yet: no RN app with embedded checkout exists (the repo's only Expo app is wallets-only, and
embedded-checkout-playgroundis Next.js).Release note
The flag is a footgun while the component is unpublished: a merchant setting
"external"with nothing to mount leaves the buyer unable to finish. Both changesets are in this PR so the component and the flag go out in the same release.Separately, published
client-sdk-base@2.7.0still builds the dead/sdk/unstable/kyc-verificationroute. The fix is already on main; it is waiting in CI's release PR #2013.