From 95884dfc83ed3fcac6fb3e2f86769daeb43dc0fd Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Wed, 4 Feb 2026 23:25:04 +0800 Subject: [PATCH] feat: Dynamically generate VLESS URIs from server-provided templates, update QR card node labels, and add a troubleshooting runbook. --- .../vless-uri-scheme-troubleshooting.md | 794 ++++++++++++++++++ .../user-center/components/VlessQrCard.tsx | 3 +- .../builtin/user-center/lib/vless.ts | 130 ++- 3 files changed, 842 insertions(+), 85 deletions(-) create mode 100644 docs/runbooks/vless-uri-scheme-troubleshooting.md diff --git a/docs/runbooks/vless-uri-scheme-troubleshooting.md b/docs/runbooks/vless-uri-scheme-troubleshooting.md new file mode 100644 index 0000000..23e2ba1 --- /dev/null +++ b/docs/runbooks/vless-uri-scheme-troubleshooting.md @@ -0,0 +1,794 @@ +# VLESS URI Scheme Troubleshooting Runbook + +## Overview + +This runbook helps diagnose and fix issues with VLESS QR code generation, copy link, and download functionality in the user center. The system **requires valid node data from the server** - there are no hardcoded fallbacks. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ agent.svc.plus │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ Agent Registration & Heartbeat │ │ +│ │ - Registers with metadata (name, region, etc.) │ │ +│ │ - Sends periodic heartbeats │ │ +│ └────────────────────────┬─────────────────────────────────┘ │ +└───────────────────────────┼─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ accounts.svc.plus │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ URI Scheme Templates (embedded in binary) │ │ +│ │ - VLESS-TCP-URI.Scheme │ │ +│ │ - VLESS-XHTTP-URI.Scheme │ │ +│ └────────────────────────┬─────────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────────▼─────────────────────────────────┐ │ +│ │ /api/agent/nodes Handler │ │ +│ │ 1. Reads registered agent metadata │ │ +│ │ 2. Loads URI scheme templates │ │ +│ │ 3. Renders templates with user UUID, domain, etc. │ │ +│ │ 4. Returns array of VlessNode objects │ │ +│ └────────────────────────┬─────────────────────────────────┘ │ +└───────────────────────────┼─────────────────────────────────────┘ + │ + ▼ JSON Response + { + "name": "NODE-NAME", + "address": "example.com", + "transport": "tcp", + "uri_scheme_tcp": "vless://...", + "uri_scheme_xhttp": "vless://..." + } + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ console.svc.plus │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ /api/agent/nodes (proxy to accounts.svc.plus) │ │ +│ └────────────────────────┬─────────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────────▼─────────────────────────────────┐ │ +│ │ VlessQrCard Component │ │ +│ │ - Fetches nodes via useSWR │ │ +│ │ - User selects node and transport (TCP/XHTTP) │ │ +│ └────────────────────────┬─────────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────────▼─────────────────────────────────┐ │ +│ │ buildVlessUri(uuid, node) │ │ +│ │ ✅ Validates: uuid, node, node.transport │ │ +│ │ ✅ Selects: uri_scheme_tcp or uri_scheme_xhttp │ │ +│ │ ❌ No fallbacks - returns null if data missing │ │ +│ └────────────────────────┬─────────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────────▼─────────────────────────────────┐ │ +│ │ renderVlessUriFromScheme(template, values) │ │ +│ │ - Substitutes ${UUID}, ${DOMAIN}, etc. │ │ +│ │ - Returns complete VLESS URI │ │ +│ └────────────────────────┬─────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ vless://uuid@host:port?... │ +│ │ │ +│ ┌─────────────┼─────────────┐ │ +│ ▼ ▼ ▼ │ +│ QR Code Copy Link Download QR │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Key Design Principles + +### ✅ What the System Does + +1. **Server-Driven Configuration**: All node information comes from `accounts.svc.plus` +2. **Dynamic Node Discovery**: Nodes are discovered from registered agents in `agent.svc.plus` +3. **Template-Based URI Generation**: URI schemes are defined in `accounts.svc.plus` and rendered server-side +4. **Strict Validation**: Frontend validates all required fields and fails fast with clear errors +5. **No Hardcoded Defaults**: No fake nodes, hosts, or labels in the UI code + +### ❌ What the System Does NOT Do + +1. **No Fallback Nodes**: If no agents are registered, no nodes are shown (not "TOKYO-NODE") +2. **No Hardcoded Hosts**: No `'ha-proxy-jp.svc.plus'` or similar in frontend code +3. **No Manual URI Construction**: Frontend never builds URIs from scratch using URLSearchParams +4. **No Default Labels**: No hardcoded "TOKYO-NODE" or similar labels + +## Common Issues + +### Issue 1: No Nodes Displayed / Empty Node List + +**Symptoms:** +- Node selector is empty or not shown +- QR code shows error or "Generating QR code..." indefinitely + +**Diagnosis:** + +1. Check `/api/agent/nodes` response: + ```bash + curl -H "Cookie: xc_session=$TOKEN" \ + https://console.svc.plus/api/agent/nodes + ``` + + Expected: Array with at least one node + ```json + [ + { + "name": "NODE-NAME", + "address": "example.com", + "transport": "tcp", + "uri_scheme_tcp": "vless://...", + "uri_scheme_xhttp": "vless://..." + } + ] + ``` + +2. Check browser console for errors: + ``` + [VLESS] Cannot build URI: node is undefined + ``` + +**Root Causes & Fixes:** + +| Cause | Diagnosis | Fix | +|-------|-----------|-----| +| No agents registered | Check agent.svc.plus logs for registration | Register at least one agent | +| `XRAY_PROXY_NODES` env var empty | Check accounts.svc.plus env vars | Set `XRAY_PROXY_NODES=host1.com,host2.com` | +| Agent heartbeat failing | Check agent.svc.plus health endpoint | Fix agent connectivity | +| API proxy misconfigured | Check console.svc.plus API routes | Verify `/api/agent/*` proxies to accounts.svc.plus | + +### Issue 2: QR Code Not Displaying + +**Symptoms:** +- Nodes are listed but QR code doesn't generate +- Error in browser console + +**Diagnosis:** + +Check browser console for `[VLESS]` errors: + +```javascript +// Error 1: Missing transport +[VLESS] Missing transport type from node: example.com + +// Error 2: Missing URI scheme +[VLESS] Missing URI scheme template from server for transport: tcp. +Node: example.com. +Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields. + +// Error 3: Invalid UUID +[VLESS] Cannot build URI: node is undefined +``` + +**Root Causes & Fixes:** + +| Error | Cause | Fix | +|-------|-------|-----| +| Missing transport | Node object missing `transport` field | Check accounts.svc.plus `listAgentNodes` function | +| Missing URI scheme | `uri_scheme_tcp` or `uri_scheme_xhttp` not in response | Verify `VLESSTCPScheme()` and `VLESSXHTTPScheme()` are called | +| Invalid UUID | User doesn't have proxy UUID | Check user record in database | + +### Issue 3: Copy Link Returns Empty + +**Symptoms:** +- Click "Copy Link" button +- Nothing copied to clipboard +- Or clipboard contains "null" + +**Diagnosis:** + +This is the same as Issue 2 - `buildVlessUri()` is returning `null`. + +**Fix:** + +See Issue 2 root causes and fixes. + +### Issue 4: Switching TCP/XHTTP Doesn't Update QR + +**Symptoms:** +- Click TCP or XHTTP button +- QR code doesn't change + +**Diagnosis:** + +1. Check if both `uri_scheme_tcp` and `uri_scheme_xhttp` exist in node data +2. Check React DevTools to see if `preferredTransport` state is updating +3. Check if `vlessUri` is recomputing + +**Root Cause:** + +Usually a React rendering issue, not related to URI scheme logic. The `useEffect` hook should automatically trigger when `vlessUri` changes. + +**Fix:** + +```typescript +// In VlessQrCard.tsx, verify this useEffect exists: +useEffect(() => { + if (!vlessUri) { + setQrDataUrl(null) + return + } + + toDataURL(vlessUri, { /* ... */ }) + .then(setQrDataUrl) + .catch(console.error) +}, [vlessUri]) // ✅ Depends on vlessUri +``` + +### Issue 5: Node Shows Generic "Node" Label + +**Symptoms:** +- Node badge shows "Node" instead of actual node name + +**Diagnosis:** + +Check node data structure: +```javascript +// In browser console +console.log(effectiveNode) +``` + +Expected: +```javascript +{ + name: "TOKYO-NODE", // ✅ Should have name + address: "example.com", + // ... +} +``` + +**Root Causes & Fixes:** + +| Cause | Fix | +|-------|-----| +| Agent not sending name metadata | Update agent to send `name` in registration | +| `resolveNodeName()` logic broken | Check accounts.svc.plus `resolveNodeName` function | +| Fallback to generic label | Expected behavior if `name` and `address` both missing | + +## Debugging Steps + +### Step 1: Verify Backend URI Schemes + +```bash +# SSH into accounts.svc.plus container or check source code +cd /app/internal/xrayconfig + +# Check scheme files exist +ls -la VLESS-*.Scheme + +# View TCP scheme +cat VLESS-TCP-URI.Scheme +# Expected: vless://${UUID}@${DOMAIN}:1443?encryption=none&type=tcp&security=tls&sni=${SNI}&fp=${FP}&flow=${FLOW}#${TAG} + +# View XHTTP scheme +cat VLESS-XHTTP-URI.Scheme +# Expected: vless://${UUID}@${DOMAIN}:443?encryption=none&type=xhttp&security=tls&host=${DOMAIN}&path=${PATH}&mode=${MODE}&sni=${SNI}&fp=${FP}&alpn=h2%2Chttp%2F1.1%2Ch3#${TAG} +``` + +### Step 2: Test API Endpoint + +```bash +# Get auth token from browser cookies +TOKEN="your-xc_session-cookie-value" + +# Test nodes endpoint +curl -H "Cookie: xc_session=$TOKEN" \ + https://console.svc.plus/api/agent/nodes | jq '.' + +# Expected output: +[ + { + "name": "NODE-NAME", + "address": "example.com", + "port": 443, + "users": ["uuid-here"], + "transport": "xhttp", + "path": "/split", + "mode": "auto", + "security": "tls", + "flow": "xtls-rprx-vision", + "server_name": "example.com", + "xhttp_port": 443, + "tcp_port": 1443, + "uri_scheme_xhttp": "vless://uuid@example.com:443?...", + "uri_scheme_tcp": "vless://uuid@example.com:1443?..." + } +] + +# Check specific fields +curl -H "Cookie: xc_session=$TOKEN" \ + https://console.svc.plus/api/agent/nodes | \ + jq '.[0] | {name, address, uri_scheme_tcp, uri_scheme_xhttp}' +``` + +### Step 3: Check Frontend Logs + +Open browser DevTools Console and look for: + +``` +✅ Good (no errors): +(no [VLESS] messages) + +❌ Bad: +[VLESS] Cannot build URI: node is undefined +[VLESS] Missing transport type from node: example.com +[VLESS] Missing URI scheme template from server for transport: tcp. Node: example.com. Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields. +``` + +### Step 4: Verify QR Code Generation + +```javascript +// In browser console, test QR generation manually +import { toDataURL } from 'qrcode' + +const testUri = 'vless://test-uuid@example.com:1443?encryption=none&type=tcp&security=tls#TEST' + +toDataURL(testUri, { + errorCorrectionLevel: 'M', + margin: 1, + scale: 8, +}).then(url => { + console.log('QR generated successfully:', url.substring(0, 50) + '...') + // Create img element to view + const img = document.createElement('img') + img.src = url + document.body.appendChild(img) +}) +``` + +### Step 5: Check Agent Registration + +```bash +# Check if agents are registered +# (This depends on your agent.svc.plus implementation) + +# Example: Check agent heartbeat logs +kubectl logs -n your-namespace deployment/agent-svc-plus | grep heartbeat + +# Example: Check registered agents in database +psql -c "SELECT * FROM agents WHERE last_heartbeat > NOW() - INTERVAL '5 minutes';" +``` + +## Code References + +### Frontend (console.svc.plus) + +**Main Logic:** +- [vless.ts](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus/src/modules/extensions/builtin/user-center/lib/vless.ts) + - `VLESS_DEFAULTS` - Technical constants (fingerprint, tcpFlow) + - `buildVlessUri()` - Renders URI from server template (**no fallbacks**) + - `renderVlessUriFromScheme()` - Template variable substitution + - `buildVlessConfig()` - Builds Xray config for download + +**UI Component:** +- [VlessQrCard.tsx](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus/src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx) + - Fetches nodes from `/api/agent/nodes` via `useSWR` + - Manages TCP/XHTTP transport switching + - Generates QR code via `toDataURL()` + - Displays node selector if multiple nodes available + +### Backend (accounts.svc.plus) + +**URI Schemes:** +- `internal/xrayconfig/VLESS-TCP-URI.Scheme` - TCP URI template +- `internal/xrayconfig/VLESS-XHTTP-URI.Scheme` - XHTTP URI template + +**Template Loading:** +- `internal/xrayconfig/templates.go` + - `VLESSTCPScheme()` - Returns embedded TCP URI template + - `VLESSXHTTPScheme()` - Returns embedded XHTTP URI template + +**API Handler:** +- `api/user_agents.go` + - `listAgentNodes()` - Returns node list with rendered URI schemes + - `renderVLESSURIScheme()` - Substitutes template variables (${UUID}, ${DOMAIN}, etc.) + - `registeredNodeMetadata()` - Reads agent registration data + - `parseProxyNodeHosts()` - Parses `XRAY_PROXY_NODES` env var + +## Environment Variables + +### accounts.svc.plus + +| Variable | Purpose | Default | Example | +|----------|---------|---------|---------| +| `XRAY_PROXY_NODES` | Additional proxy nodes (comma-separated) | - | `node1.com,node2.com` | +| `XRAY_XHTTP_PATH` | XHTTP path | `/split` | `/custom-path` | +| `XRAY_XHTTP_MODE` | XHTTP mode | `auto` | `stream` | +| `XRAY_XHTTP_PORT` | XHTTP port | `443` | `8443` | +| `XRAY_TCP_PORT` | TCP port | `1443` | `1444` | + +## Recovery Procedures + +### Procedure 1: Fix Missing URI Schemes + +If `uri_scheme_tcp` or `uri_scheme_xhttp` are missing from API response: + +1. **Check scheme files exist:** + ```bash + ls -la /app/internal/xrayconfig/VLESS-*.Scheme + ``` + +2. **Verify templates.go loads them:** + ```go + // Should have these embed directives: + //go:embed VLESS-TCP-URI.Scheme + vlessTCPScheme []byte + + //go:embed VLESS-XHTTP-URI.Scheme + vlessXHTTPScheme []byte + ``` + +3. **Rebuild and redeploy accounts.svc.plus:** + ```bash + cd /Users/shenlan/workspaces/cloud-neutral-toolkit/accounts.svc.plus + go build -o accounts-svc-plus + # Deploy to Cloud Run or your platform + ``` + +4. **Verify schemes are returned:** + ```bash + curl https://accounts.svc.plus/api/agent/nodes | jq '.[0].uri_scheme_tcp' + ``` + +### Procedure 2: Fix No Nodes Returned + +If `/api/agent/nodes` returns empty array `[]`: + +1. **Check `XRAY_PROXY_NODES` env var:** + ```bash + # In accounts.svc.plus container + echo $XRAY_PROXY_NODES + # Should output: host1.com,host2.com + ``` + +2. **Check agent registration:** + ```bash + # Verify agents are sending heartbeats + # (Implementation-specific) + ``` + +3. **Add manual proxy nodes:** + ```bash + # Set env var in Cloud Run or your platform + XRAY_PROXY_NODES=node1.example.com,node2.example.com + ``` + +4. **Restart accounts.svc.plus** + +### Procedure 3: Reset Frontend State + +If QR code is stuck or showing old data: + +1. **Clear browser cache:** + - Chrome: Cmd+Shift+Delete (Mac) or Ctrl+Shift+Delete (Windows) + - Select "Cached images and files" + +2. **Hard refresh:** + - Mac: Cmd+Shift+R + - Windows: Ctrl+Shift+R + +3. **Clear SWR cache:** + ```javascript + // In browser console + localStorage.clear() + sessionStorage.clear() + location.reload() + ``` + +4. **Verify API returns valid data:** + ```bash + curl -H "Cookie: xc_session=$TOKEN" \ + https://console.svc.plus/api/agent/nodes + ``` + +## Monitoring + +### Key Metrics + +1. **QR Code Generation Success Rate** + - Track `toDataURL()` success/failure ratio + - Alert if failure rate > 5% + +2. **API Response Time** + - Monitor `/api/agent/nodes` latency + - Alert if p95 > 500ms + +3. **Error Rate** + - Count `[VLESS]` console errors + - Alert if error rate > 1% of page loads + +4. **Node Availability** + - Track number of nodes returned by `/api/agent/nodes` + - Alert if drops to 0 + +### Alerts + +Set up alerts for: + +- High rate of `[VLESS] Missing URI scheme` errors (> 10/min) +- `/api/agent/nodes` returning empty array for > 5 minutes +- QR code generation failures (> 5% of attempts) +- Agent heartbeat failures (no heartbeat for > 5 minutes) + +## Testing Checklist + +Before deploying changes: + +- [ ] `/api/agent/nodes` returns valid node data with `uri_scheme_tcp` and `uri_scheme_xhttp` +- [ ] QR code generates successfully for TCP transport +- [ ] QR code generates successfully for XHTTP transport +- [ ] Copy link button copies valid VLESS URI +- [ ] Download QR button downloads valid QR code image +- [ ] Switching TCP ↔ XHTTP regenerates QR code +- [ ] Node selector shows all registered nodes (if multiple exist) +- [ ] No `[VLESS]` errors in browser console (with valid data) +- [ ] Clear error messages in console when data is missing +- [ ] No hardcoded "TOKYO-NODE" or fake hosts appear in UI + +## Related Documentation + +- [VLESS Protocol Specification](https://github.com/XTLS/Xray-core) +- [Implementation Plan](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/implementation_plan.md) +- [Walkthrough](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/walkthrough.md) + +## Change Log + +| Date | Change | Impact | +|------|--------|--------| +| 2026-02-04 | Removed `DEFAULT_VLESS_TEMPLATE` hardcoded values | No more fake "TOKYO-NODE" or `ha-proxy-jp.svc.plus` | +| 2026-02-04 | Removed fallback URI construction logic | System now fails fast with clear errors | +| 2026-02-04 | Removed `DEFAULT_VLESS_LABEL` export | Node labels come from server only | +| 2026-02-04 | Added strict validation in `buildVlessUri` | Better error messages for debugging | +| 2026-02-04 | Created troubleshooting runbook | Easier diagnosis and recovery | + + +### Issue 1: QR Code Not Displaying + +**Symptoms:** +- QR code shows "Generating QR code..." indefinitely +- Or shows error message + +**Diagnosis:** + +1. Check browser console for `[VLESS]` errors: + ``` + [VLESS] Missing URI scheme template from server for transport: tcp + ``` + +2. Check `/api/agent/nodes` response in Network tab: + ```bash + # Should include these fields: + { + "uri_scheme_tcp": "vless://...", + "uri_scheme_xhttp": "vless://..." + } + ``` + +**Root Causes:** + +| Cause | Fix | +|-------|-----| +| accounts.svc.plus not returning URI schemes | Check `user_agents.go` is calling `xrayconfig.VLESSXHTTPScheme()` and `xrayconfig.VLESSTCPScheme()` | +| URI scheme files missing from binary | Ensure `VLESS-TCP-URI.Scheme` and `VLESS-XHTTP-URI.Scheme` exist and are embedded via `//go:embed` | +| API proxy not working | Check console.svc.plus API proxy configuration for `/api/agent/*` routes | + +### Issue 2: Copy Link Returns Empty + +**Symptoms:** +- Click "Copy Link" button +- Nothing is copied to clipboard +- Or error in console + +**Diagnosis:** + +Check if `buildVlessUri()` is returning null: +```javascript +// In browser console +console.log(vlessUri) // Should be a string starting with "vless://" +``` + +**Root Causes:** + +Same as Issue 1 - missing URI schemes from server. + +### Issue 3: Switching TCP/XHTTP Doesn't Update QR + +**Symptoms:** +- Click TCP or XHTTP button +- QR code doesn't change + +**Diagnosis:** + +This should work automatically via React state. Check: +1. `preferredTransport` state is updating +2. `effectiveNode` is recomputing +3. `vlessUri` is changing +4. QR code `useEffect` is triggering + +**Root Cause:** + +Likely a React rendering issue, not related to URI scheme logic. + +### Issue 4: Node Selector Not Showing + +**Symptoms:** +- Only one node shown even though multiple agents are registered +- Node selector dropdown doesn't appear + +**Diagnosis:** + +1. Check `/api/agent/nodes` returns multiple nodes: + ```bash + curl -H "Authorization: Bearer " \ + https://accounts.svc.plus/api/agent/nodes | jq 'length' + ``` + +2. Check agent.svc.plus has registered multiple nodes + +**Root Causes:** + +| Cause | Fix | +|-------|-----| +| Only one agent registered | Register more agents via agent.svc.plus | +| `XRAY_PROXY_NODES` env var only has one host | Add more hosts to env var in accounts.svc.plus | +| Agent registration not working | Check agent.svc.plus heartbeat and registration logic | + +## Debugging Steps + +### Step 1: Verify Backend URI Schemes + +```bash +# SSH into accounts.svc.plus container +cd /app/internal/xrayconfig + +# Check scheme files exist +ls -la VLESS-*.Scheme + +# View TCP scheme +cat VLESS-TCP-URI.Scheme +# Expected: vless://${UUID}@${DOMAIN}:1443?encryption=none&type=tcp&security=tls&sni=${SNI}&fp=${FP}&flow=${FLOW}#${TAG} + +# View XHTTP scheme +cat VLESS-XHTTP-URI.Scheme +# Expected: vless://${UUID}@${DOMAIN}:443?encryption=none&type=xhttp&security=tls&host=${DOMAIN}&path=${PATH}&mode=${MODE}&sni=${SNI}&fp=${FP}&alpn=h2%2Chttp%2F1.1%2Ch3#${TAG} +``` + +### Step 2: Test API Endpoint + +```bash +# Get auth token from browser (check cookies or localStorage) +TOKEN="your-session-token" + +# Test nodes endpoint +curl -H "Cookie: xc_session=$TOKEN" \ + https://console.svc.plus/api/agent/nodes | jq '.' + +# Should return array with uri_scheme_tcp and uri_scheme_xhttp fields +``` + +### Step 3: Check Frontend Logs + +Open browser DevTools Console and look for: + +``` +✅ Good: +(no [VLESS] errors) + +❌ Bad: +[VLESS] Cannot build URI: node is undefined +[VLESS] Missing URI scheme template from server for transport: tcp. Node: TOKYO-NODE. Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields. +``` + +### Step 4: Verify QR Code Generation + +```javascript +// In browser console, test QR generation manually +import { toDataURL } from 'qrcode' + +const testUri = 'vless://test-uuid@example.com:1443?encryption=none&type=tcp&security=tls#TEST' + +toDataURL(testUri, { + errorCorrectionLevel: 'M', + margin: 1, + scale: 8, +}).then(url => console.log('QR generated:', url)) +``` + +## Code References + +### Frontend (console.svc.plus) + +- **Main logic:** `src/modules/extensions/builtin/user-center/lib/vless.ts` + - `buildVlessUri()` - Renders URI from server template + - `renderVlessUriFromScheme()` - Template variable substitution + +- **UI component:** `src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx` + - Fetches nodes from `/api/agent/nodes` + - Manages TCP/XHTTP transport switching + - Generates QR code via `toDataURL()` + +### Backend (accounts.svc.plus) + +- **URI schemes:** `internal/xrayconfig/VLESS-TCP-URI.Scheme`, `VLESS-XHTTP-URI.Scheme` +- **Template loading:** `internal/xrayconfig/templates.go` + - `VLESSTCPScheme()` - Returns TCP URI template + - `VLESSXHTTPScheme()` - Returns XHTTP URI template + +- **API handler:** `api/user_agents.go` + - `listAgentNodes()` - Returns node list with rendered URI schemes + - `renderVLESSURIScheme()` - Substitutes template variables + +## Environment Variables + +### accounts.svc.plus + +| Variable | Purpose | Example | +|----------|---------|---------| +| `XRAY_PROXY_NODES` | Additional proxy nodes | `ha-proxy-jp.svc.plus,ha-proxy-us.svc.plus` | +| `XRAY_XHTTP_PATH` | XHTTP path | `/split` | +| `XRAY_XHTTP_MODE` | XHTTP mode | `auto` | +| `XRAY_XHTTP_PORT` | XHTTP port | `443` | +| `XRAY_TCP_PORT` | TCP port | `1443` | + +## Recovery Procedures + +### Procedure 1: Fix Missing URI Schemes + +If URI schemes are missing from API response: + +1. Check scheme files exist in accounts.svc.plus: + ```bash + ls internal/xrayconfig/VLESS-*.Scheme + ``` + +2. Rebuild accounts.svc.plus to embed schemes: + ```bash + cd /Users/shenlan/workspaces/cloud-neutral-toolkit/accounts.svc.plus + go build -o accounts-svc-plus + ``` + +3. Redeploy accounts.svc.plus + +4. Verify schemes are returned: + ```bash + curl https://accounts.svc.plus/api/agent/nodes | jq '.[0].uri_scheme_tcp' + ``` + +### Procedure 2: Reset Frontend State + +If QR code is stuck: + +1. Clear browser cache +2. Hard refresh (Cmd+Shift+R on Mac) +3. Check for console errors +4. Verify `/api/agent/nodes` returns valid data + +## Monitoring + +### Key Metrics + +- **QR Code Generation Success Rate:** Track `toDataURL()` success/failure +- **API Response Time:** `/api/agent/nodes` latency +- **Error Rate:** Count of `[VLESS]` console errors + +### Alerts + +Set up alerts for: +- High rate of `[VLESS] Missing URI scheme` errors +- `/api/agent/nodes` returning empty array +- QR code generation failures + +## Related Documentation + +- [VLESS Protocol Specification](https://github.com/XTLS/Xray-core) +- [Implementation Plan](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/implementation_plan.md) +- [Walkthrough](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/walkthrough.md) + +## Change Log + +| Date | Change | Author | +|------|--------|--------| +| 2026-02-04 | Removed fallback URI construction logic | System | +| 2026-02-04 | Added error logging for missing URI schemes | System | +| 2026-02-04 | Created troubleshooting runbook | System | diff --git a/src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx b/src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx index d9036e8..8d64f94 100644 --- a/src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx +++ b/src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx @@ -10,7 +10,6 @@ import Card from './Card' import { buildVlessConfig, buildVlessUri, - DEFAULT_VLESS_LABEL, serializeConfigForDownload, VlessNode, VlessTransport, @@ -175,7 +174,7 @@ export default function VlessQrCard({ uuid, copy }: VlessQrCardProps) {

{copy.label}

- {effectiveNode?.name || DEFAULT_VLESS_LABEL} + {effectiveNode?.name || effectiveNode?.address || 'Node'}

{copy.description}

diff --git a/src/modules/extensions/builtin/user-center/lib/vless.ts b/src/modules/extensions/builtin/user-center/lib/vless.ts index fb703a9..e056a8b 100644 --- a/src/modules/extensions/builtin/user-center/lib/vless.ts +++ b/src/modules/extensions/builtin/user-center/lib/vless.ts @@ -1,34 +1,8 @@ -export type VlessEndpoint = { - host: string - port: number - type: string - security: string - flow: string - encryption: string - serverName: string - fingerprint: string - allowInsecure: boolean - label: string -} - -export type VlessTemplate = { - endpoint: VlessEndpoint -} - -const DEFAULT_VLESS_TEMPLATE: VlessTemplate = { - endpoint: { - host: 'ha-proxy-jp.svc.plus', - port: 1443, - type: 'tcp', - security: 'tls', - flow: 'xtls-rprx-vision', - encryption: 'none', - serverName: 'ha-proxy-jp.svc.plus', - fingerprint: 'chrome', - allowInsecure: false, - label: 'TOKYO-NODE', - }, -} +// Technical constants for VLESS protocol +const VLESS_DEFAULTS = { + fingerprint: 'chrome', + tcpFlow: 'xtls-rprx-vision', +} as const const DEFAULT_XRAY_CONFIG = { log: { @@ -169,58 +143,49 @@ export function buildVlessUri(rawUuid: string | null | undefined, node?: VlessNo return null } - const { endpoint: defaultEndpoint } = DEFAULT_VLESS_TEMPLATE - - const host = node?.address ?? defaultEndpoint.host - const serverName = node?.server_name ?? node?.address ?? defaultEndpoint.serverName - const label = node?.name ?? defaultEndpoint.label - const transport = node?.transport ?? (defaultEndpoint.type as VlessTransport) - const flow = node?.flow ?? (transport === 'tcp' ? defaultEndpoint.flow : '') - const port = resolveTransportPort(node, transport, transport === 'xhttp' ? 443 : defaultEndpoint.port) - - const schemeTemplate = transport === 'xhttp' ? node?.uri_scheme_xhttp : node?.uri_scheme_tcp - if (schemeTemplate) { - const rendered = renderVlessUriFromScheme(schemeTemplate, { - UUID: uuid, - DOMAIN: host, - NODE: host, - PATH: encodeURIComponent(node?.path ?? '/split'), - MODE: encodeURIComponent(node?.mode ?? 'auto'), - SNI: serverName, - FP: defaultEndpoint.fingerprint, - FLOW: flow || defaultEndpoint.flow, - TAG: encodeURIComponent(label), - }) - if (rendered) { - return rendered - } + if (!node) { + console.error('[VLESS] Cannot build URI: node is undefined') + return null } - const params = new URLSearchParams({ - type: transport, - security: defaultEndpoint.security, - encryption: defaultEndpoint.encryption, - sni: serverName, - fp: defaultEndpoint.fingerprint, - allowInsecure: defaultEndpoint.allowInsecure ? '1' : '0', + if (!node.transport) { + console.error('[VLESS] Missing transport type from node:', node.name || node.address) + return null + } + + const transport = node.transport + const schemeTemplate = transport === 'xhttp' ? node.uri_scheme_xhttp : node.uri_scheme_tcp + + if (!schemeTemplate) { + console.error( + `[VLESS] Missing URI scheme template from server for transport: ${transport}. ` + + `Node: ${node.name || node.address}. ` + + `Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields.` + ) + return null + } + + const host = node.address + const serverName = node.server_name ?? node.address + const label = node.name || node.address + const flow = node.flow ?? (transport === 'tcp' ? VLESS_DEFAULTS.tcpFlow : '') + + return renderVlessUriFromScheme(schemeTemplate, { + UUID: uuid, + DOMAIN: host, + NODE: host, + PATH: encodeURIComponent(node.path ?? '/split'), + MODE: encodeURIComponent(node.mode ?? 'auto'), + SNI: serverName, + FP: VLESS_DEFAULTS.fingerprint, + FLOW: flow || VLESS_DEFAULTS.tcpFlow, + TAG: encodeURIComponent(label), }) - - if (flow) { - params.set('flow', flow) - } - - if (transport === 'xhttp') { - params.set('host', host) - params.set('path', node?.path ?? '/split') - params.set('mode', node?.mode ?? 'auto') - } - - return `vless://${uuid}@${host}:${port}?${params.toString()}#${encodeURIComponent(label)}` } export function buildVlessConfig(rawUuid: string | null | undefined, node?: VlessNode): XrayConfig | null { const uuid = (rawUuid ?? '').trim() - if (!uuid) { + if (!uuid || !node) { return null } @@ -229,12 +194,11 @@ export function buildVlessConfig(rawUuid: string | null | undefined, node?: Vles const vnext = outbound?.settings?.vnext?.[0] const user = vnext?.users?.[0] - const { endpoint: defaultEndpoint } = DEFAULT_VLESS_TEMPLATE - const address = node?.address ?? defaultEndpoint.host - const serverName = node?.server_name ?? node?.address ?? defaultEndpoint.serverName - const transport = node?.transport ?? (defaultEndpoint.type as VlessTransport) - const flow = node?.flow ?? (transport === 'tcp' ? defaultEndpoint.flow : '') - const port = resolveTransportPort(node, transport, transport === 'xhttp' ? 443 : defaultEndpoint.port) + const address = node.address + const serverName = node.server_name ?? node.address + const transport = node.transport ?? 'tcp' + const flow = node.flow ?? (transport === 'tcp' ? VLESS_DEFAULTS.tcpFlow : '') + const port = resolveTransportPort(node, transport, transport === 'xhttp' ? 443 : 1443) if (vnext) { vnext.address = address @@ -271,4 +235,4 @@ export function serializeConfigForDownload(config: XrayConfig): string { return `${JSON.stringify(config, null, 2)}\n` } -export const DEFAULT_VLESS_LABEL = DEFAULT_VLESS_TEMPLATE.endpoint.label +