-
Notifications
You must be signed in to change notification settings - Fork 2
✨ implement persona web sdk #683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🦋 Changeset detectedLatest commit: 8780581 The changes in this PR will be included in the next version bump. 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 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #683 +/- ##
==========================================
- Coverage 61.48% 61.07% -0.42%
==========================================
Files 169 169
Lines 5188 5236 +48
Branches 1457 1461 +4
==========================================
+ Hits 3190 3198 +8
- Misses 1836 1876 +40
Partials 162 162
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary of ChangesHello @dieguezguille, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the Know Your Customer (KYC) verification process by integrating the Persona Web SDK. The primary goal is to provide a more seamless and embedded experience for web users, moving away from external redirects. The changes involve a clear separation of concerns for web and native platforms within the KYC initiation logic, ensuring that each environment leverages its optimal Persona SDK implementation. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
WalkthroughAdds Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(30,144,255,0.5)
actor App
end
rect rgba(255,165,0,0.5)
participant TokenSvc as Token Service
end
rect rgba(34,139,34,0.5)
participant Persona as Persona SDK (Web / Native)
end
rect rgba(220,20,60,0.5)
participant Nav as App Navigation / Callbacks
end
App->>TokenSvc: request KYC tokens (clientToken, sessionId)
TokenSvc-->>App: return tokens
App->>Persona: initialize client with clientToken & config
Persona-->>App: onReady
App->>Persona: start/open flow
Persona-->>Nav: onComplete / onCancel / onError
Nav-->>App: navigate / handle result
App->>Persona: destroy/cleanup active client (on finish or abort)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request implements the Persona Web SDK for the KYC process, which is a great addition. The implementation correctly handles platform-specific logic for web and native environments. No specific code review comments are being provided at this time, as the suggested refactoring for startKYC might be considered a premature abstraction according to repository guidelines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/utils/persona.ts
Outdated
| export const startKYC = ( | ||
| Platform.OS === "web" | ||
| ? () => { | ||
| let activeClient: InstanceType<typeof PersonaWeb.Client> | undefined; | ||
|
|
||
| const { Inquiry } = await import("react-native-persona"); | ||
| Inquiry.fromInquiry(inquiryId) | ||
| .sessionToken(sessionToken) | ||
| .onCanceled(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| router.replace("/(main)/(home)"); | ||
| }) | ||
| .onComplete(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| queryClient.setQueryData(["card-upgrade"], 1); | ||
| router.replace("/(main)/(home)"); | ||
| }) | ||
| .onError((error) => reportError(error)) | ||
| .build() | ||
| .start(); | ||
| } | ||
| return async () => { | ||
| const [{ Client }, { inquiryId, sessionToken }] = await Promise.all([ | ||
| import("persona"), | ||
| getKYCTokens("basic", await getRedirectURI()), | ||
| ]); | ||
|
|
||
| activeClient?.destroy(); | ||
|
|
||
| activeClient = new Client({ | ||
| inquiryId, | ||
| sessionToken, | ||
| environment: environment as "production" | "sandbox", // TODO deprecated - use environmentId instead | ||
| // environmentId: "", | ||
| onReady: () => activeClient?.open(), | ||
| onComplete: () => { | ||
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| queryClient.setQueryData(["card-upgrade"], 1); // TODO probably not needed unless the user is upgrading their card | ||
| router.replace("/(main)/(home)"); | ||
| }, | ||
| onCancel: () => { | ||
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| router.replace("/(main)/(home)"); | ||
| }, | ||
| onError: (error) => { | ||
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| reportError(error); | ||
| }, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 KYC flow broken for Farcaster mini apps and embedded web contexts
The refactored startKYC function removes special handling for mini apps and embedded contexts that was present in the old implementation.
Click to expand
Previous Behavior
The old implementation at src/utils/persona.ts had explicit handling for different web contexts:
if (Platform.OS === "web") {
if (await sdk.isInMiniApp()) {
await sdk.actions.openUrl(oneTimeLink); // Mini app specific handling
return;
}
const embeddingContext = queryClient.getQueryData<EmbeddingContext>(["embedding-context"]);
if (embeddingContext && !embeddingContext.endsWith("-web")) {
window.location.replace(oneTimeLink); // Embedded context handling
return;
}
window.open(oneTimeLink, "_blank", "noopener,noreferrer");
}New Behavior
The new implementation uses the Persona Web SDK's Client directly for all web contexts without checking if the user is in a mini app or embedded context.
Impact
-
Farcaster mini apps: The Persona Web SDK likely cannot open its modal/iframe properly in mini app environments where popup/iframe capabilities are restricted. The old code used
sdk.actions.openUrl()to properly navigate within the mini app context. -
Non-web embedded contexts (e.g.,
farcaster,base,metamask,phantom): These contexts previously usedwindow.location.replace()to navigate to the one-time link, which is now not handled at all.
Users in these contexts will likely experience a broken KYC flow where the Persona modal fails to open or displays incorrectly.
Was this helpful? React with 👍 or 👎 to provide feedback.
6dfbe9a to
1442060
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@package.json`:
- Line 95: The Persona v5 client is being initialized with the deprecated
environment field; update the Persona client construction in
src/utils/persona.ts so that new Persona.Client(...) is passed environmentId
instead of environment. Locate the function or constant that calls new
Persona.Client (look for symbols like PersonaClient, createPersonaClient, or the
file-exported client instance), replace the environment:
process.env.PERSONA_ENVIRONMENT (or similar) with environmentId:
process.env.PERSONA_ENVIRONMENT_ID (or the appropriate env var containing the
env_... token), and remove the deprecated environment property so the client
uses the required environmentId parameter.
In `@src/utils/persona.ts`:
- Around line 37-72: The shared startKYC utility currently mutates
queryClient.setQueryData(["card-upgrade"], 1) in both web and native branches
(see the onComplete handlers in persona.ts), which wrongly affects all callers;
remove those setQueryData calls from persona.ts and instead apply the mutation
only in the card-upgrade flow by adding the call inside the
card-upgrade-specific caller (VerifyIdentity component) after the startKYC
promise resolves (or alternatively add an explicit boolean parameter to startKYC
like applyCardUpgrade that defaults to false and only setQueryData when true),
ensuring other callers (useBeginKYC, Card) are unaffected.
♻️ Duplicate comments (1)
src/utils/persona.ts (1)
14-57: Web KYC flow still lacks miniapp/embedded fallback.The web branch now always instantiates the Persona Web Client; in Farcaster mini apps or embedded contexts this can fail to open due to iframe/popup restrictions. Consider restoring context checks and fallback navigation (e.g., miniapp openUrl or full redirect).
Persona Web SDK support for embedded webviews / Farcaster mini apps and recommended integration when popups/iframes are restricted
package.json
Outdated
| "i18n-iso-countries": "^7.14.0", | ||
| "i18next": "^25.7.4", | ||
| "moti": "^0.30.0", | ||
| "persona": "^5.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Persona Web SDK npm v5 Expo React Native compatibility environment vs environmentId init options
💡 Result:
persona(Web/Embedded Flow) v5 (npm i persona): useenvironmentId(anenv_...token) when constructingnew Persona.Client(...). This is the current, non-deprecated way to choose the Persona API environment. Example shown in the Embedded Flow quickstart. [1]environmentoption: this is the deprecated legacy field; it used string values like"sandbox"/"production"and is documented as “Deprecated. UseenvironmentIdinstead.” [2]
Expo / React Native compatibility
- The Web SDK is meant for web (DOM) environments, not a native React Native runtime. For a mobile app, Persona recommends using their native mobile SDKs / React Native SDK instead. [3][4]
- If you must run Persona in an in-app browser, use a WebView/hosted flow and include
is-webview=truein the URL (required for WebView flow). [4]
References
[1] Persona Embedded Flow Quickstart (NPM) – environmentId in Persona.Client init: (docs.withpersona.com)
[2] Persona “Parameters” docs – environment deprecated, use environmentId: (docs.withpersona.com)
[3] Persona React Native Integration Guide (native SDK usage): (docs.withpersona.com)
[4] Persona WebView integration – native SDKs recommended; is-webview=true required: (docs.withpersona.com)
Citations:
- 1: https://docs.withpersona.com/quickstart-embedded-flow?utm_source=openai
- 2: https://docs.withpersona.com/v2022-09-01/docs/parameters?utm_source=openai
- 3: https://docs.withpersona.com/react-native-sdk-v2-integration-guide?utm_source=openai
- 4: https://docs.withpersona.com/webview-integration?utm_source=openai
🏁 Script executed:
# Search for Persona SDK usage in the codebase
rg -A 5 -B 5 "persona" package.json | head -50Repository: exactly/exa
Length of output: 768
🏁 Script executed:
# Look for Persona initialization code
rg -A 10 "new Persona|Persona\(" --type ts --type tsx --type js --type jsxRepository: exactly/exa
Length of output: 82
🏁 Script executed:
# Check for any persona API version constants or configuration
rg "PERSONA_API_VERSION|Persona.*init|persona.*init" -A 5 --type ts --type tsxRepository: exactly/exa
Length of output: 82
🏁 Script executed:
# Search for Persona initialization/client instantiation
rg -i "new Persona|Persona\.Client|import.*persona" -A 3 | head -100Repository: exactly/exa
Length of output: 4274
🏁 Script executed:
# Look for any persona config or environment setup
rg -i "environmentId|environment.*persona|PERSONA" -B 2 -A 2 | head -100Repository: exactly/exa
Length of output: 6081
🏁 Script executed:
# Check what files import persona package (non react-native)
rg "from ['\"]persona['\"]|import.*['\"]persona['\"]" -lRepository: exactly/exa
Length of output: 75
Update Persona v5 initialization: use environmentId instead of deprecated environment field.
The codebase imports Persona v5 but initializes it using the deprecated environment field ("sandbox"/"production"). Persona v5 requires environmentId (an env_... token). Update src/utils/persona.ts to pass environmentId when constructing new Persona.Client(...) to avoid compatibility failures with v5.
🤖 Prompt for AI Agents
In `@package.json` at line 95, The Persona v5 client is being initialized with the
deprecated environment field; update the Persona client construction in
src/utils/persona.ts so that new Persona.Client(...) is passed environmentId
instead of environment. Locate the function or constant that calls new
Persona.Client (look for symbols like PersonaClient, createPersonaClient, or the
file-exported client instance), replace the environment:
process.env.PERSONA_ENVIRONMENT (or similar) with environmentId:
process.env.PERSONA_ENVIRONMENT_ID (or the appropriate env var containing the
env_... token), and remove the deprecated environment property so the client
uses the required environmentId parameter.
src/utils/persona.ts
Outdated
| onComplete: () => { | ||
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| queryClient.setQueryData(["card-upgrade"], 1); // TODO probably not needed unless the user is upgrading their card | ||
| router.replace("/(main)/(home)"); | ||
| }, | ||
| onCancel: () => { | ||
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| router.replace("/(main)/(home)"); | ||
| }, | ||
| onError: (error) => { | ||
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| reportError(error); | ||
| }, | ||
| }); | ||
| }; | ||
| } | ||
| : () => { | ||
| // eslint-disable-next-line unicorn/consistent-function-scoping | ||
| return async () => { | ||
| const { inquiryId, sessionToken } = await getKYCTokens("basic", await getRedirectURI()); | ||
| const { Inquiry } = await import("react-native-persona"); | ||
| Inquiry.fromInquiry(inquiryId) | ||
| .sessionToken(sessionToken) | ||
| .onCanceled(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| router.replace("/(main)/(home)"); | ||
| }) | ||
| .onComplete(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| queryClient.setQueryData(["card-upgrade"], 1); | ||
| router.replace("/(main)/(home)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n --type=ts --type=tsx 'startKYC\s*\(' -A 3 -B 3Repository: exactly/exa
Length of output: 82
🏁 Script executed:
rg -n 'startKYC\s*\(' -A 3 -B 3Repository: exactly/exa
Length of output: 1494
🏁 Script executed:
cat src/utils/persona.tsRepository: exactly/exa
Length of output: 3642
🏁 Script executed:
cat src/utils/useBeginKYC.tsRepository: exactly/exa
Length of output: 1258
🏁 Script executed:
cat src/components/card/Card.tsx | head -210 | tail -30Repository: exactly/exa
Length of output: 848
🏁 Script executed:
head -50 src/components/card/Card.tsxRepository: exactly/exa
Length of output: 2010
🏁 Script executed:
rg -n "card|Card" src/components/card/Card.tsx | head -5Repository: exactly/exa
Length of output: 351
🏁 Script executed:
rg -n "^export\s+(default\s+)?.*Card|^function Card|^const Card" src/components/card/Card.tsxRepository: exactly/exa
Length of output: 90
🏁 Script executed:
rg -n "from.*card/Card|import.*Card.*from.*card/Card" --type js --type tsRepository: exactly/exa
Length of output: 144
🏁 Script executed:
rg -n "GettingStarted|getting-started" --max-count 20Repository: exactly/exa
Length of output: 973
🏁 Script executed:
cat src/components/home/card-upgrade/VerifyIdentity.tsxRepository: exactly/exa
Length of output: 3084
Move setQueryData(["card-upgrade"], 1) calls out of the shared startKYC utility.
The shared utility unconditionally sets ["card-upgrade"] on completion in both web (line 41) and native (line 71) branches, affecting all callers: useBeginKYC (onboarding), Card (general card KYC), and VerifyIdentity (card-upgrade). This mutation should only apply to card-upgrade flows. The existing TODO comment ("probably not needed unless the user is upgrading their card") reflects this concern. Move these setQueryData calls into the card-upgrade-specific caller (VerifyIdentity.tsx) or accept the mutation through a parameter/context to explicitly gate it to relevant flows.
🤖 Prompt for AI Agents
In `@src/utils/persona.ts` around lines 37 - 72, The shared startKYC utility
currently mutates queryClient.setQueryData(["card-upgrade"], 1) in both web and
native branches (see the onComplete handlers in persona.ts), which wrongly
affects all callers; remove those setQueryData calls from persona.ts and instead
apply the mutation only in the card-upgrade flow by adding the call inside the
card-upgrade-specific caller (VerifyIdentity component) after the startKYC
promise resolves (or alternatively add an explicit boolean parameter to startKYC
like applyCardUpgrade that defaults to false and only setQueryData when true),
ensuring other callers (useBeginKYC, Card) are unaffected.
1442060 to
80ba51b
Compare
src/utils/persona.ts
Outdated
| export const startKYC = ( | ||
| Platform.OS === "web" | ||
| ? () => { | ||
| let activeClient: InstanceType<typeof PersonaWeb.Client> | undefined; | ||
|
|
||
| const { Inquiry } = await import("react-native-persona"); | ||
| Inquiry.fromInquiry(inquiryId) | ||
| .sessionToken(sessionToken) | ||
| .onCanceled(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| router.replace("/(main)/(home)"); | ||
| }) | ||
| .onComplete(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| queryClient.setQueryData(["card-upgrade"], 1); | ||
| router.replace("/(main)/(home)"); | ||
| }) | ||
| .onError((error) => reportError(error)) | ||
| .build() | ||
| .start(); | ||
| } | ||
| return async () => { | ||
| const [{ Client }, { inquiryId, sessionToken }] = await Promise.all([ | ||
| import("persona"), | ||
| getKYCTokens("basic", await getRedirectURI()), | ||
| ]); | ||
|
|
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
80ba51b to
7d7085c
Compare
src/utils/persona.ts
Outdated
| onError: (error) => { | ||
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| reportError(error); |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
7d7085c to
aee8c5a
Compare
|
@coderabbitai review the latest force-pushed code |
|
✅ Actions performedReview triggered.
|
aee8c5a to
720bd1e
Compare
src/utils/persona.ts
Outdated
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| handleCancel(); | ||
| }, | ||
| onError: (error) => { | ||
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| reportError(error); | ||
| }, | ||
| }); | ||
| }; |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
|
@coderabbitai what can we deprecate/drop in the server after this change? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain
🏁 Script executed: Length of output: 165158 Let me search for the specific OTL (One-Time Link) and redirect flow logic to identify what's deprecated: 🏁 Script executed: Length of output: 11449
Can Be Deprecated1. OTL (One-Time Link) generation for basic KYC flowIn
The web client now uses the embedded Persona Client SDK which doesn't require one-time links or redirects. It manages the flow inline with callbacks ( 2. Response simplificationThe POST { inquiryId, otl, sessionToken, legacy }Can be simplified to: { inquiryId, sessionToken }Both web (Persona SDK) and native (react-native-persona) only need these two values. 3. Potentially:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/utils/persona.ts`:
- Around line 18-24: The startKYC factory uses an IIFE and multi-line arrow
functions; refactor it into a named function declaration that returns an async
function and convert the onComplete/onCancel/onError handlers into method
shorthand or separate function declarations (locate PersonaWeb.Client usage and
the returned async factory in startKYC); also remove the unconditional
queryClient.setQueryData(["card-upgrade"], 1) from handleComplete and instead
perform that mutation only in the VerifyIdentity flow (where card-upgrade is
relevant), ensuring other callers like Card.tsx and useBeginKYC no longer
trigger the card-upgrade update.
src/utils/persona.ts
Outdated
| export const startKYC = ( | ||
| Platform.OS === "web" | ||
| ? () => { | ||
| let activeClient: InstanceType<typeof PersonaWeb.Client> | undefined; | ||
|
|
||
| const { Inquiry } = await import("react-native-persona"); | ||
| Inquiry.fromInquiry(inquiryId) | ||
| .sessionToken(sessionToken) | ||
| .onCanceled(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| router.replace("/(main)/(home)"); | ||
| }) | ||
| .onComplete(() => { | ||
| queryClient.invalidateQueries({ queryKey: ["kyc", "status"] }).catch(reportError); | ||
| queryClient.setQueryData(["card-upgrade"], 1); | ||
| router.replace("/(main)/(home)"); | ||
| }) | ||
| .onError((error) => reportError(error)) | ||
| .build() | ||
| .start(); | ||
| } | ||
| return async () => { | ||
| const [{ Client }, { inquiryId, sessionToken }] = await Promise.all([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/utils/persona.tsRepository: exactly/exa
Length of output: 3714
🏁 Script executed:
#!/bin/bash
# Find and display eslint rules for function style and object shorthand
find . -name '.eslintrc*' -o -name 'eslint.config.*' | head -20 | while read file; do
echo "=== File: $file ==="
cat "$file"
echo ""
doneRepository: exactly/exa
Length of output: 3944
🏁 Script executed:
#!/bin/bash
# Search for func-style and object-shorthand rules in eslint configs
rg "func-style|object-shorthand" -A 2 -B 2 --type-list | head -100Repository: exactly/exa
Length of output: 444
🏁 Script executed:
find . -path "*/exactly/common/eslint/base.mjs" -o -path "*/common/eslint/base.mjs" | head -5Repository: exactly/exa
Length of output: 79
🏁 Script executed:
cat -n common/eslint/base.mjs | head -100Repository: exactly/exa
Length of output: 5554
🏁 Script executed:
rg "func-style|object-shorthand" common/eslint/ -A 2 -B 2Repository: exactly/exa
Length of output: 37
🏁 Script executed:
#!/bin/bash
# Find usages of startKYC to understand flow
rg "startKYC" -B 2 -A 2 --type ts --type tsxRepository: exactly/exa
Length of output: 82
🏁 Script executed:
#!/bin/bash
# Find where card-upgrade query data is used/set
rg "card-upgrade" -B 2 -A 2 --type ts --type tsxRepository: exactly/exa
Length of output: 82
🏁 Script executed:
#!/bin/bash
# Check unicorn eslint plugin documentation for func-style and object-shorthand
npm list eslint-plugin-unicorn 2>/dev/null | head -20Repository: exactly/exa
Length of output: 108
🏁 Script executed:
#!/bin/bash
# Find usages of startKYC
rg "startKYC" -B 2 -A 2Repository: exactly/exa
Length of output: 2412
🏁 Script executed:
#!/bin/bash
# Find where card-upgrade is used/set
rg "card-upgrade" -B 2 -A 2Repository: exactly/exa
Length of output: 7111
🏁 Script executed:
#!/bin/bash
# Check VerifyIdentity.tsx mentioned in learnings to understand flow-specific behavior
cat -n src/components/home/card-upgrade/VerifyIdentity.tsx | head -80Repository: exactly/exa
Length of output: 3229
Refactor to function declarations and method shorthand; move card-upgrade mutation to flow-specific context.
The IIFE with nested arrow functions (lines 18–54) and multi-line arrow function properties (onComplete, onCancel, onError) violate the coding guideline requiring function declarations for multi-line functions and object method shorthand. Refactor the factory and handlers to use proper function declarations and method shorthand syntax.
Additionally, line 72 unconditionally sets queryClient.setQueryData(["card-upgrade"], 1) in handleComplete(), but this should be flow-specific. The card-upgrade context in VerifyIdentity.tsx (line 28) already handles this mutation, and other KYC flows using startKYC (e.g., Card.tsx, useBeginKYC) should not trigger card-upgrade state changes. Move this mutation out of the general utility.
♻️ Proposed refactor
-export const startKYC = (
- Platform.OS === "web"
- ? () => {
- let activeClient: InstanceType<typeof PersonaWeb.Client> | undefined;
-
- return async () => {
- const [{ Client }, { inquiryId, sessionToken }] = await Promise.all([
- import("persona"),
- getKYCTokens("basic", await getRedirectURI()),
- ]);
-
- activeClient?.destroy();
- activeClient = new Client({
- inquiryId,
- sessionToken,
- environment: environment as "production" | "sandbox", // TODO implement environmentId
- onReady: () => activeClient?.open(),
- onComplete: () => {
- activeClient?.destroy();
- activeClient = undefined;
- handleComplete();
- },
- onCancel: () => {
- activeClient?.destroy();
- activeClient = undefined;
- handleCancel();
- },
- onError: (error) => {
- activeClient?.destroy();
- activeClient = undefined;
- reportError(error);
- },
- });
- };
- }
- : () => startNativeKYC
-)();
+function createStartKYC() {
+ if (Platform.OS === "web") {
+ let activeClient: InstanceType<typeof PersonaWeb.Client> | undefined;
+
+ async function startWebKYC() {
+ const [{ Client }, { inquiryId, sessionToken }] = await Promise.all([
+ import("persona"),
+ getKYCTokens("basic", await getRedirectURI()),
+ ]);
+
+ activeClient?.destroy();
+ activeClient = new Client({
+ inquiryId,
+ sessionToken,
+ environment: environment as "production" | "sandbox", // TODO implement environmentId
+ onReady: () => activeClient?.open(),
+ onComplete() {
+ activeClient?.destroy();
+ activeClient = undefined;
+ handleComplete();
+ },
+ onCancel() {
+ activeClient?.destroy();
+ activeClient = undefined;
+ handleCancel();
+ },
+ onError(error) {
+ activeClient?.destroy();
+ activeClient = undefined;
+ reportError(error);
+ },
+ });
+ }
+
+ return startWebKYC;
+ }
+
+ return startNativeKYC;
+}
+
+export const startKYC = createStartKYC();🤖 Prompt for AI Agents
In `@src/utils/persona.ts` around lines 18 - 24, The startKYC factory uses an IIFE
and multi-line arrow functions; refactor it into a named function declaration
that returns an async function and convert the onComplete/onCancel/onError
handlers into method shorthand or separate function declarations (locate
PersonaWeb.Client usage and the returned async factory in startKYC); also remove
the unconditional queryClient.setQueryData(["card-upgrade"], 1) from
handleComplete and instead perform that mutation only in the VerifyIdentity flow
(where card-upgrade is relevant), ensuring other callers like Card.tsx and
useBeginKYC no longer trigger the card-upgrade update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai is this fixed in the latest force-pushed code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dieguezguille No, the issues have not been addressed yet. The current code still has the same three concerns:
-
IIFE pattern (lines 18-55): The
startKYCexport still uses an IIFE with nested arrow functions instead of a named function declaration. -
Arrow function properties (lines 33, 34-39, 40-45, 46-51): The
onReady,onComplete,onCancel, andonErrorhandlers are still multi-line arrow functions rather than method shorthand. -
Card-upgrade mutation (line 72): The
handleComplete()function still unconditionally callsqueryClient.setQueryData(["card-upgrade"], 1), which should only happen in the card-upgrade flow (e.g.,VerifyIdentity.tsx), not in all KYC flows likeCard.tsxoruseBeginKYC.
The refactor I suggested would address all three issues by:
- Converting the IIFE to a named
createStartKYC()function - Using method shorthand syntax for the event handlers
- Removing the card-upgrade mutation from the shared
handleComplete()utility
Would you like to apply these changes?
🧠 Learnings used
Learnt from: cruzdanilo
Repo: exactly/exa PR: 653
File: src/utils/persona.ts:42-47
Timestamp: 2026-01-22T19:39:06.502Z
Learning: In `src/utils/persona.ts`, when using `Inquiry.fromInquiry(inquiryId)` to resume a server-created inquiry, the `.environment()` call is not needed because the environment was already set when the inquiry was created on the server. The `.environment()` call is only necessary when creating a new inquiry client-side using methods like `.fromTemplate()`.
Learnt from: franm91
Repo: exactly/exa PR: 653
File: src/components/home/card-upgrade/VerifyIdentity.tsx:27-29
Timestamp: 2026-01-22T19:01:57.860Z
Learning: In `src/components/home/card-upgrade/VerifyIdentity.tsx`, the KYC mutation logic intentionally differs from the `GettingStarted.tsx` variants. The differences include calling `queryClient.setQueryData(["card-upgrade"], 1)` on success status and showing a toast via `onError` callback. These are intentional flow-specific behaviors for the card upgrade context.
Learnt from: franm91
Repo: exactly/exa PR: 653
File: src/components/getting-started/GettingStarted.tsx:132-133
Timestamp: 2026-01-22T18:44:56.353Z
Learning: In the KYC flow refactor, the `{ inquiryId, sessionToken }` response from the KYC API is a legacy format that was only returned when a `templateId` parameter was provided. This legacy format is being deprecated. The new flow in `src/components/getting-started/GettingStarted.tsx` and related files calls `getKYCStatus()` without the `templateId` parameter, so the legacy response shape will never be returned, and the `"code" in status` type guard is sufficient.
<!--
Learnt from: mainqueg
Repo: exactly/exa PR: 637
File: server/utils/persona.ts:155-160
Timestamp: 2026-01-15T19:10:12.433Z
Learning: In server/utils/persona.ts, the PERSONA_API_VERSION constant is intentionally set to "2023-01-05" to match the API version configured for the production API key in Persona's admin dashboard. This ensures consistent response behavior across different environments and should not be updated without also updating the production API key configuration.
720bd1e to
3c8c175
Compare
src/utils/persona.ts
Outdated
| return new Promise<void>((resolve, reject) => { | ||
| activeClient = new Client({ | ||
| inquiryId, | ||
| sessionToken, | ||
| environment: environment as "production" | "sandbox", // TODO implement environmentId | ||
| onReady: () => activeClient?.open(), | ||
| onComplete: () => { | ||
| activeClient?.destroy(); | ||
| activeClient = undefined; | ||
| handleComplete(); | ||
| resolve(); |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
3c8c175 to
3fddcfa
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/utils/persona.ts`:
- Line 34: Update the trailing TODO on the environment assignment in
src/utils/persona.ts (the line containing environment: environment as
"production" | "sandbox", // TODO implement environmentId) by either replacing
the TODO with a linked issue reference (e.g. // TODO implement environmentId -
ISSUE-1234) or removing the TODO entirely if no ticket exists; ensure the
comment follows project guidelines and references the issue key in the same
comment when keeping the TODO.
- Around line 18-55: startKYC can leave a pending Promise when a previous
Persona web Client is destroyed (Client.destroy) because destroy doesn't call
onCancel/onComplete; modify startKYC to track the current promise's
resolve/reject alongside activeClient and, before creating a new Client (or when
calling activeClient?.destroy()), explicitly settle the prior promise (call its
resolve or reject with an appropriate Error) so awaiting callers don't hang;
update references in startKYC to use these stored settle functions and ensure
all paths that null out activeClient (onComplete, onCancel, onError, and the
pre-create destroy) also clear and settle the stored promise, keeping functions
like handleComplete, handleCancel, reportError, and the new settle logic
consistent.
src/utils/persona.ts
Outdated
| .start(); | ||
| } | ||
| return () => { | ||
| if (pending) return pending; |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai Persona SDK handles onCancel. is this a valid concern?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Persona web SDK onCancel callback behavior navigation
💡 Result:
In Persona’s Embedded Flow (web SDK), onCancel is only meant to fire when the user explicitly cancels/exits the flow UI before completion (or when your code calls client.cancel(...)). It is not a reliable signal for browser/navigation events like route changes, refresh, tab close, or the user hitting the browser back button—because the page/iframe can unload before the SDK can run callbacks, and Persona explicitly cautions not to rely on callbacks for critical logic. [1]
Key details that matter for “navigation” cases:
- What triggers
onCancel: user cancels the inquiry flow before finishing; the callback receives{ inquiryId, sessionToken }, which can beundefinedif cancellation happens before an inquiry is created. [1] - Cancel vs. “done/close”:
onCancelis distinct from completion; Persona even renamedonExit→onCancelto clarify it’s not the “close after completed” path. [2] - Programmatic cancel: if you want consistent “user is leaving this screen” behavior in your app’s navigation, you can call
client.cancel(false)(show confirmation) orclient.cancel(true)(immediately hide). [3] - If you need truth about inquiry state: use webhooks / server-side inquiry status, not client callbacks. [1]
Sources:
[1] Persona Docs – Client Callbacks (onCancel + callback reliability note) (docs.withpersona.com)
[2] Persona Docs – Embedded Flow Changelog (rename onExit→onCancel) (docs.withpersona.com)
[3] Persona Docs – Client Methods (client.cancel(force)) (docs.withpersona.com)
Citations:
- 1: https://docs.withpersona.com/docs/embedded-flow-client-callbacks
- 2: https://docs.withpersona.com/docs/embedded-flow-changelog?utm_source=openai
- 3: https://docs.withpersona.com/docs/embedded-flow-client-methods?utm_source=openai
@dieguezguille Unfortunately, this is a valid concern. According to Persona's documentation, onCancel only fires when the user explicitly cancels through Persona's UI—it does not fire for browser/app navigation events like:
- Browser back button
- Tab close
- Page refresh
- Route changes via Expo Router
Persona explicitly notes that "the page/iframe can unload before the SDK can run callbacks" and cautions against relying on client callbacks for critical logic.
The problem: If a user navigates away using any of these methods, none of the callbacks (onComplete/onCancel/onError) will fire, leaving pending stuck and blocking all future KYC attempts until app restart.
Suggested fix: Add cleanup logic where startKYC is called. For example, in the calling component:
useEffect(() => {
return () => {
// On component unmount, programmatically cancel the Persona flow
// This will trigger onCancel and clear the pending state
activeClient?.cancel?.(true); // force cancel without confirmation
};
}, []);However, since activeClient is in closure scope, you'd need to expose a cleanup function from startKYC, or track the client in a ref that can be cleaned up externally.
Alternative approach: Add a timeout or expose a reset() function that clears the pending state if needed.
🧠 Learnings used
Learnt from: cruzdanilo
Repo: exactly/exa PR: 653
File: src/utils/persona.ts:42-47
Timestamp: 2026-01-22T19:39:06.502Z
Learning: In `src/utils/persona.ts`, when using `Inquiry.fromInquiry(inquiryId)` to resume a server-created inquiry, the `.environment()` call is not needed because the environment was already set when the inquiry was created on the server. The `.environment()` call is only necessary when creating a new inquiry client-side using methods like `.fromTemplate()`.
Learnt from: franm91
Repo: exactly/exa PR: 653
File: src/components/home/card-upgrade/VerifyIdentity.tsx:27-29
Timestamp: 2026-01-22T19:01:57.860Z
Learning: In `src/components/home/card-upgrade/VerifyIdentity.tsx`, the KYC mutation logic intentionally differs from the `GettingStarted.tsx` variants. The differences include calling `queryClient.setQueryData(["card-upgrade"], 1)` on success status and showing a toast via `onError` callback. These are intentional flow-specific behaviors for the card upgrade context.
Learnt from: franm91
Repo: exactly/exa PR: 653
File: src/components/getting-started/GettingStarted.tsx:132-133
Timestamp: 2026-01-22T18:44:56.353Z
Learning: In the KYC flow refactor, the `{ inquiryId, sessionToken }` response from the KYC API is a legacy format that was only returned when a `templateId` parameter was provided. This legacy format is being deprecated. The new flow in `src/components/getting-started/GettingStarted.tsx` and related files calls `getKYCStatus()` without the `templateId` parameter, so the legacy response shape will never be returned, and the `"code" in status` type guard is sufficient.
<!--
Learnt from: mainqueg
Repo: exactly/exa PR: 637
File: server/utils/persona.ts:155-160
Timestamp: 2026-01-15T19:10:12.433Z
Learning: In server/utils/persona.ts, the PERSONA_API_VERSION constant is intentionally set to "2023-01-05" to match the API version configured for the production API key in Persona's admin dashboard. This ensures consistent response behavior across different environments and should not be updated without also updating the production API key configuration.
Learnt from: mainqueg
Repo: exactly/exa PR: 637
File: server/hooks/persona.ts:186-195
Timestamp: 2026-01-15T19:06:45.697Z
Learning: In server/hooks/persona.ts, the webhook handler has intentionally different error handling for Manteca vs Panda flows: the Manteca path (payload.template === "manteca") lets addDocument errors propagate to enable webhook retries, while the Panda path uses .catch() to swallow addDocument errors and prevent retries since the user creation is already committed.
bb45d9b to
45ed32a
Compare
src/utils/persona.ts
Outdated
| reportError(error); | ||
| reject(error); | ||
| }) | ||
| .build() | ||
| .start(); |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/utils/persona.ts`:
- Around line 71-92: The abort listener attached to signal may remain if the
inquiry finishes first; change the listener to a named function (e.g., onAbort)
so you can remove it when the inquiry finishes or errors. In the Promise where
Inquiry.fromInquiry(inquiryId)... is built, register the listener with a named
callback, then call signal.removeEventListener("abort", onAbort) inside the
.onCanceled, .onComplete, and .onError handlers (before resolve/reject) to
ensure the abort listener is cleaned up; reference symbols: Inquiry.fromInquiry,
signal.addEventListener / removeEventListener, onCanceled, onComplete, onError,
handleCancel, handleComplete, reportError.
45ed32a to
63154fe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/utils/persona.ts`:
- Around line 105-108: Remove the double type cast on the sdk.context call and
use real runtime checks with the actual returned value: replace the `(await
sdk.context) as unknown as undefined | { client: { appUrl?: string } }` pattern
by awaiting sdk.context into a local variable (e.g., miniappContext or ctx) and
then check `miniappContext` and `miniappContext.client?.appUrl` at runtime;
update the Platform.OS === "web" branch to return miniappContext.client.appUrl
only if the runtime check passes, matching how other files use await sdk.context
without type assertions.
63154fe to
8780581
Compare
closes #640
Summary by CodeRabbit
New Features
Refactor
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.