QR Code Share Token — Resolve & Add Friend Flow¶
Scope: Handles the processing when a user scans a QR code, imports a photo from the gallery, or taps on another user's Universal Link. It exclusively uses the resolve API to fetch the Target ID. Legacy token formats are no longer supported (No Legacy Path).
flowchart TD
classDef input fill:#EDE9FE,stroke:#7C3AED,color:#5B21B6,stroke-width:2px;
classDef client fill:#E0F2FE,stroke:#0284C7,color:#0369A1,stroke-width:2px;
classDef server fill:#D1FAE5,stroke:#059669,color:#047857,stroke-width:2px;
classDef error fill:#FEE2E2,stroke:#DC2626,color:#B91C1C,stroke-width:2px;
%% ────────────────────────────────────────────────────────
%% 1. INPUT SOURCES
%% ────────────────────────────────────────────────────────
subgraph Inputs ["Input Sources"]
direction LR
Cam(["📷 Camera Scan"]):::input
Photo(["🖼️ Photo Import"]):::input
Link(["🔗 Universal Link / ecall://"]):::input
end
%% ────────────────────────────────────────────────────────
%% 2. PARSE TOKEN
%% ────────────────────────────────────────────────────────
subgraph App ["iOS Client - AddFriendViewModel"]
Cam --> Extract["Extract {token}<br/>from URL string"]:::client
Photo --> Extract
Link --> Extract
Extract --> TriggerAPI("POST /app/api/share-token/resolve<br/>body: { token }"):::client
end
%% ────────────────────────────────────────────────────────
%% 3. BACKEND RESOLVE
%% ────────────────────────────────────────────────────────
subgraph Backend ["Backend / Token Service"]
TriggerAPI --> Validate{"Validate Token"}:::server
Validate -->|Check 1| TTL["Within 24h?"]:::server
Validate -->|Check 2| Used["Not yet used?"]:::server
TTL --> ChecksDone
Used --> ChecksDone
ChecksDone(("Verify")):::server -->|Failed| Err410["HTTP 410 Gone<br/>Token expired or used"]:::server
ChecksDone -->|Pass| Success200["Return: { token, displayName }<br/>(Token is NOT burned here)"]:::server
end
%% ────────────────────────────────────────────────────────
%% 4. RESOLVE RESULTS
%% ────────────────────────────────────────────────────────
Err410 --> UIErr(["UI: Show 'Link Expired / Used' alert"]):::error
Success200 --> UIContact["Update UI: Show Profile<br/>Store token for friend request"]:::client
%% ────────────────────────────────────────────────────────
%% 5. FRIEND REQUEST ACTION
%% ────────────────────────────────────────────────────────
subgraph FriendAction ["Friend Request Phase"]
UIContact --> UserDecision{"User Action"}:::client
UserDecision -->|"Cancel"| UICancel(["Discard token<br/>Dismiss Sheet"]):::client
UserDecision -->|"Add Friend"| FriendReq(["POST /friend-request/by-token<br/>body: { token }"]):::client
FriendReq --> VerifySession{"Validate Token<br/>(One-time use)"}:::server
VerifySession -->|"Expired (24h)<br/>or Used"| ErrSession["HTTP 410 / 401<br/>Token Expired"]:::server
VerifySession -->|"Valid"| ExecuteAdd["Map token to targetUserId<br/>Create friendship<br/>Burn token (used_count = 1)"]:::server
ErrSession --> UISessionErr(["UI: 'Session expired. Please scan again.'"]):::error
ExecuteAdd --> UIDone(["UI: 'Friend Request Sent'"]):::client
end