feat: qr document loader#62
Conversation
📝 WalkthroughWalkthroughThis PR adds URL-driven document loading with OA-encryption support. A new ChangesURL-driven document loading with OA encryption
Sequence DiagramsequenceDiagram
participant Location as URL Hash/Query
participant ActionLoader
participant Fetch as Document Server
participant Decrypt as decryptString
participant LoadDoc as loadDocument callback
Location->>ActionLoader: q parameter + anchor key
ActionLoader->>ActionLoader: parse q (JSON)
ActionLoader->>ActionLoader: clear URL navigate('/', replace)
ActionLoader->>Fetch: fetch payload.uri
Fetch-->>ActionLoader: document JSON
alt document.type = OPEN-ATTESTATION-TYPE-1
ActionLoader->>Decrypt: decryptString(document, key)
Decrypt-->>ActionLoader: decrypted payload
else
ActionLoader->>ActionLoader: use document as-is
end
ActionLoader->>ActionLoader: unwrap { document }
ActionLoader->>ActionLoader: derive filename from uri
ActionLoader->>LoadDoc: call with doc, chainId, filename
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/home/VerifySection/ActionLoader.tsx`:
- Around line 23-24: Move the potentially-throwing decode logic inside the
loader's try/catch and stop double-decoding the `q` param: don't call
decodeURIComponent on `location.hash` at top-level (replace const anchorStr =
decodeURIComponent(location.hash.substring(1)) with a safe read like const
rawHash = location.hash.substring(1)), then inside the existing try block do a
guarded decode for the hash (wrap decodeURIComponent(rawHash) in try/catch and
fallback to '{}' or an empty string on failure) to populate `anchor` (the
variable currently declared as const anchor: { key?: string }), and when reading
the search params use URLSearchParams.get('q') directly and pass that result
into JSON.parse (do NOT call decodeURIComponent on the `q` value since get
already returns a decoded string); also null-check the result of get('q') before
JSON.parse to avoid exceptions.
In `@src/components/home/VerifySection/useVerify.ts`:
- Around line 516-535: loadDocument currently proceeds to runVerification even
when chainId is missing, which bypasses the same "missing-chain /
network-select" branch used in processFile and risks verifying against a wrong
default chain; change loadDocument so that if chainId is null or undefined it
invokes the same missing-chain handling used by processFile (for example call
the existing network-select opener/handler such as setShowNetworkSelect or
handleMissingChain) or return early instead of calling runVerification, keeping
the rest of the verification-init logic (incrementing verificationIdRef,
setFileName, setVerifyStatus, clearing metadata) intact and ensuring you still
pass currentId to runVerification when a valid chainId is present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e008132c-7ba2-443d-b2e1-6d0b766de290
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
package.jsonsrc/components/home/VerifySection/ActionLoader.test.tsxsrc/components/home/VerifySection/ActionLoader.tsxsrc/components/home/VerifySection/VerifySection.test.tsxsrc/components/home/VerifySection/VerifySection.tsxsrc/components/home/VerifySection/useVerify.test.tssrc/components/home/VerifySection/useVerify.ts
| const anchorStr = decodeURIComponent(location.hash.substring(1)) | ||
| const anchor: { key?: string } = anchorStr |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate file (in case of path differences)
ls -la src/components/home/VerifySection || true
# Show the relevant file with line numbers around the cited lines
if [ -f "src/components/home/VerifySection/ActionLoader.tsx" ]; then
sed -n '1,140p' src/components/home/VerifySection/ActionLoader.tsx | nl -ba | sed -n '1,140p'
else
echo "File not found: src/components/home/VerifySection/ActionLoader.tsx"
exit 1
fi
# Focus on decoding usage
rg -n "decodeURIComponent|URLSearchParams|location\.hash|location\.search|substring\(1\)" src/components/home/VerifySection/ActionLoader.tsxRepository: TrustVC/trustvc-website
Length of output: 1723
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/components/home/VerifySection/ActionLoader.tsx"
# Show the file with line numbers (cat -n is available)
if [ -f "$FILE" ]; then
echo "=== File: $FILE ==="
sed -n '1,120p' "$FILE" | cat -n
else
echo "File not found: $FILE"
exit 1
fi
echo
echo "=== rg matches ==="
rg -n "decodeURIComponent|URLSearchParams|location\.hash|location\.search|substring\(1\)|JSON\.parse" "$FILE"Repository: TrustVC/trustvc-website
Length of output: 3898
Avoid double-decoding q and guard malformed hash decoding.
location.hashis decoded (decodeURIComponent(location.hash.substring(1))) outside the asynctry/catch; decode failures will escape and abort the loader.qis decoded again viaJSON.parse(decodeURIComponent(query))even thoughURLSearchParams.get('q')already returns a decoded value.
💡 Suggested fix
export const ActionLoader: React.FC<ActionLoaderProps> = ({ loadDocument }) => {
const location = useLocation()
const navigate = useNavigate()
useEffect(() => {
+ const safeDecode = (value: string): string => {
+ try {
+ return decodeURIComponent(value)
+ } catch {
+ return value
+ }
+ }
+
const params = new URLSearchParams(location.search)
const query = params.get('q')
if (!query) return
// Decode optional decryption key from URL hash — anchor key takes priority over payload key
- const anchorStr = decodeURIComponent(location.hash.substring(1))
+ const anchorStr = safeDecode(location.hash.substring(1))
const anchor: { key?: string } = anchorStr
? (() => {
try {
return JSON.parse(anchorStr)
} catch {
return {}
}
})()
: {}
// Clean URL immediately so a refresh doesn't re-trigger
navigate('/', { replace: true })
;(async () => {
try {
- const action = JSON.parse(decodeURIComponent(query))
+ const action = JSON.parse(query)
const { type, payload } = action ?? {}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const anchorStr = decodeURIComponent(location.hash.substring(1)) | |
| const anchor: { key?: string } = anchorStr | |
| export const ActionLoader: React.FC<ActionLoaderProps> = ({ loadDocument }) => { | |
| const location = useLocation() | |
| const navigate = useNavigate() | |
| useEffect(() => { | |
| const safeDecode = (value: string): string => { | |
| try { | |
| return decodeURIComponent(value) | |
| } catch { | |
| return value | |
| } | |
| } | |
| const params = new URLSearchParams(location.search) | |
| const query = params.get('q') | |
| if (!query) return | |
| // Decode optional decryption key from URL hash — anchor key takes priority over payload key | |
| const anchorStr = safeDecode(location.hash.substring(1)) | |
| const anchor: { key?: string } = anchorStr | |
| ? (() => { | |
| try { | |
| return JSON.parse(anchorStr) | |
| } catch { | |
| return {} | |
| } | |
| })() | |
| : {} | |
| // Clean URL immediately so a refresh doesn't re-trigger | |
| navigate('/', { replace: true }) | |
| ;(async () => { | |
| try { | |
| const action = JSON.parse(query) | |
| const { type, payload } = action ?? {} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/home/VerifySection/ActionLoader.tsx` around lines 23 - 24,
Move the potentially-throwing decode logic inside the loader's try/catch and
stop double-decoding the `q` param: don't call decodeURIComponent on
`location.hash` at top-level (replace const anchorStr =
decodeURIComponent(location.hash.substring(1)) with a safe read like const
rawHash = location.hash.substring(1)), then inside the existing try block do a
guarded decode for the hash (wrap decodeURIComponent(rawHash) in try/catch and
fallback to '{}' or an empty string on failure) to populate `anchor` (the
variable currently declared as const anchor: { key?: string }), and when reading
the search params use URLSearchParams.get('q') directly and pass that result
into JSON.parse (do NOT call decodeURIComponent on the `q` value since get
already returns a decoded string); also null-check the result of get('q') before
JSON.parse to avoid exceptions.
| const loadDocument = async ( | ||
| doc: unknown, | ||
| chainId: string | null | undefined, | ||
| name: string | ||
| ) => { | ||
| const currentId = ++verificationIdRef.current | ||
| setFileName(name) | ||
| setVerifyStatus('verifying') | ||
| setFragments([]) | ||
| setPendingDoc(null) | ||
| clearVerificationMetadata() | ||
|
|
||
| try { | ||
| await runVerification(doc, chainId, currentId) | ||
| } catch (err) { | ||
| clearVerificationMetadata() | ||
| setErrorType(getErrorTypeFromError(err)) | ||
| setVerifyStatus('error') | ||
| } | ||
| } |
There was a problem hiding this comment.
Keep loadDocument consistent with missing-chain handling in processFile.
When chainId is absent, loadDocument skips the network-select branch used elsewhere and can verify against the wrong default chain. This is especially risky because action payload chainId is optional.
💡 Suggested fix
const loadDocument = async (
doc: unknown,
chainId: string | null | undefined,
name: string
) => {
@@
try {
+ if (!chainId && (isTransferableRecord(doc as any) || isDocumentRevokable(doc as any))) {
+ setPendingDoc(doc)
+ setVerifyStatus('network-select')
+ return
+ }
await runVerification(doc, chainId, currentId)
} catch (err) {
clearVerificationMetadata()
setErrorType(getErrorTypeFromError(err))
setVerifyStatus('error')🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/home/VerifySection/useVerify.ts` around lines 516 - 535,
loadDocument currently proceeds to runVerification even when chainId is missing,
which bypasses the same "missing-chain / network-select" branch used in
processFile and risks verifying against a wrong default chain; change
loadDocument so that if chainId is null or undefined it invokes the same
missing-chain handling used by processFile (for example call the existing
network-select opener/handler such as setShowNetworkSelect or
handleMissingChain) or return early instead of calling runVerification, keeping
the rest of the verification-init logic (incrementing verificationIdRef,
setFileName, setVerifyStatus, clearing metadata) intact and ensuring you still
pass currentId to runVerification when a valid chainId is present.
02524fa to
a5085e0
Compare
feat: QR document loader via action URL (
?q=)What this does
Adds support for loading and verifying a TrustVC document directly from a URL query parameter — the same mechanism used by QR codes generated from TradeTrust/TrustVC.
When the app is visited with a
?q=param:https://actions.trustvc.io/?q={"type":"DOCUMENT","payload":{"uri":"...","chainId":11155111}}
the app automatically fetches the document, decrypts it if encrypted, runs verification, and displays the result — with no manual file upload needed.
Changes
ActionLoader.tsx(new)?q=from the URL on mounttype,payload.uri,payload.chainId,payload.key)#) — takes priority overpayload.key{ document: ... }wrapperOPEN-ATTESTATION-TYPE-1) via@govtechsg/oa-encryptionloadDocumentto run the existing verification flownavigate('/', { replace: true })) to prevent re-trigger on refreshnull)useVerify.tsloadDocument(doc, chainId, name)— mirrorsprocessFilebut accepts an already-fetched document; plugs into the samerunVerificationpathVerifySection.tsx<ActionLoader loadDocument={loadDocument} />at the top of the component@govtechsg/oa-encryption(new dependency)decryptStringto handleOPEN-ATTESTATION-TYPE-1encrypted documentsTests
ActionLoader.test.tsx?q=, early returns for unsupported types, fetch errors, invalid JSON, OA decryption (key from payload + anchor), missing key erroruseVerify.test.tsloadDocumentvalid/invalid/error flows,verifyingstate transition, second call overwrites state, null chainIdVerifySection.test.tsxJira Ticket
Summary by CodeRabbit
Release Notes