Skip to content

Add SIP002 URI & QR code generator#303

Merged
madeye merged 3 commits intomainfrom
feat/sip002-uri-generator
Feb 11, 2026
Merged

Add SIP002 URI & QR code generator#303
madeye merged 3 commits intomainfrom
feat/sip002-uri-generator

Conversation

@madeye
Copy link
Copy Markdown
Contributor

@madeye madeye commented Feb 11, 2026

Summary

  • Add an interactive SIP002 URI generator component to the SIP002 docs page, with QR code generation, copy-to-clipboard, and full light/dark theme support
  • Install qrcode dependency for client-side QR code rendering (SSR-safe via dynamic import)
  • Create VitePress custom theme extending default theme to register the SIP002Generator Vue component globally
  • Fix typos and modernize outdated references across documentation pages (deprecated ciphers, old install commands, removed Python section, etc.)

Test plan

  • yarn install && yarn docs:dev — component renders at /doc/sip002.html, form fields work, QR code generates
  • Verify generated URIs match spec examples (e.g. aes-128-gcm:testYWVzLTEyOC1nY206dGVzdA)
  • Verify AEAD-2022 URIs use percent-encoding, not base64
  • Toggle dark mode — styles adapt correctly via VitePress CSS variables
  • yarn docs:build — SSR build succeeds without errors

🤖 Generated with Claude Code

Add an interactive URI generator component to the SIP002 page that
lets users build Shadowsocks URIs with QR codes directly in the docs.
Also fix typos, update outdated references, and modernize examples
across documentation pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@madeye madeye force-pushed the feat/sip002-uri-generator branch from 8fd1f1a to 55d3022 Compare February 11, 2026 01:21
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@madeye madeye force-pushed the feat/sip002-uri-generator branch from 82507e6 to 7f9ad04 Compare February 11, 2026 01:35
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@madeye madeye requested a review from Copilot February 11, 2026 01:45
@madeye madeye merged commit e94f7ea into main Feb 11, 2026
4 of 5 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an interactive SIP002 URI + QR generator to the VitePress docs site, while upgrading the docs toolchain and modernizing several documentation pages/links.

Changes:

  • Upgrade VitePress (and related toolchain deps) and add qrcode for client-side QR rendering.
  • Add a custom VitePress theme that globally registers a new <SIP002Generator /> Vue component, and embed it in the SIP002 page.
  • Update/modernize multiple docs pages (clean URLs, typo fixes, outdated install instructions, removed legacy Python section).

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
package.json Upgrades VitePress and adds qrcode dependency used by the generator.
yarn.lock Lockfile refresh for VitePress upgrade + new dependencies (incl. qrcode).
docs/.vitepress/theme/index.ts Introduces a custom theme to register SIP002Generator globally.
docs/.vitepress/theme/components/SIP002Generator.vue Implements SIP002 URI + QR generation UI and copy-to-clipboard.
docs/doc/sip002.md Embeds the new generator component on the SIP002 docs page.
docs/.vitepress/config.ts Minor footer update (copyright range).
docs/doc/sip023.md Updates internal link to extensionless route.
docs/doc/sip022.md Fixes section numbering.
docs/doc/sip008.md Fixes typos/grammar in transport guidance.
docs/doc/getting-started.md Removes legacy Python implementation references; updates internal links.
docs/doc/deploying.md Modernizes OS guidance and install commands; removes Python 2-era section.
docs/doc/configs.md Updates cipher guidance, examples, and SIP002 references/links.
docs/doc/advanced.md Updates kernel guidance and removes deprecated sysctl (tcp_tw_recycle).
.github/workflows/ci.yml Adds CI workflow to build the docs site on PRs/pushes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +6 to +7
enhanceApp({ app }) {
app.component('SIP002Generator', SIP002Generator)
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enhanceApp overrides the default theme’s enhanceApp hook, so any setup performed by DefaultTheme.enhanceApp will be skipped. Call DefaultTheme.enhanceApp?.(ctx) inside your enhanceApp implementation (and keep the same ctx signature) before registering the global component.

Suggested change
enhanceApp({ app }) {
app.component('SIP002Generator', SIP002Generator)
enhanceApp(ctx) {
DefaultTheme.enhanceApp?.(ctx)
ctx.app.component('SIP002Generator', SIP002Generator)

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +61
function base64urlEncode(str: string): string {
const encoded = btoa(str)
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

const uri = computed(() => {
if (!password.value || !hostname.value || !port.value) return ''

let userinfo: string
if (isAead2022(method.value)) {
userinfo = encodeURIComponent(method.value) + ':' + encodeURIComponent(password.value)
} else {
userinfo = base64urlEncode(method.value + ':' + password.value)
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base64urlEncode uses btoa, which is not available during VitePress SSR/build, and uri is computed during SSR render. This can cause yarn docs:build to fail with btoa is not defined. Use an SSR-safe base64 implementation (e.g., globalThis.btoa fallback to Buffer.from(...).toString('base64')) or gate base64 encoding behind a client-only check.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +61
function base64urlEncode(str: string): string {
const encoded = btoa(str)
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

const uri = computed(() => {
if (!password.value || !hostname.value || !port.value) return ''

let userinfo: string
if (isAead2022(method.value)) {
userinfo = encodeURIComponent(method.value) + ':' + encodeURIComponent(password.value)
} else {
userinfo = base64urlEncode(method.value + ':' + password.value)
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btoa(str) only supports Latin-1; if users enter a non-ASCII password/tag, this will throw and prevent URI generation. Consider base64-encoding UTF-8 bytes (e.g., via TextEncoder) before applying base64url transformations.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +83
onMounted(async () => {
const qr = await import('qrcode')
toDataURL = qr.toDataURL

watch(uri, async (val) => {
if (val && toDataURL) {
qrDataUrl.value = await toDataURL(val)
} else {
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const qr = await import('qrcode') is likely to return a module namespace where the QRCode API is under default (CJS interop). Using qr.toDataURL can be undefined, breaking QR generation. Prefer const mod = await import('qrcode'); toDataURL = (mod.default ?? mod).toDataURL (or equivalent) to support both ESM/CJS shapes.

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +93
await navigator.clipboard.writeText(uri.value)
copied.value = true
setTimeout(() => { copied.value = false }, 2000)
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

navigator.clipboard.writeText can throw/reject (permissions, non-secure context, unsupported browsers). Right now an exception will bubble and leave copied stuck false with an unhandled rejection. Wrap this in try/catch and provide a fallback (or at least a user-visible failure state).

Suggested change
await navigator.clipboard.writeText(uri.value)
copied.value = true
setTimeout(() => { copied.value = false }, 2000)
// Prefer modern Clipboard API when available
if (typeof navigator !== 'undefined' &&
navigator.clipboard &&
typeof navigator.clipboard.writeText === 'function') {
try {
await navigator.clipboard.writeText(uri.value)
copied.value = true
setTimeout(() => { copied.value = false }, 2000)
return
} catch (e) {
console.error('Failed to copy using navigator.clipboard:', e)
// fall through to legacy fallback
}
}
// Fallback: use a temporary textarea and document.execCommand('copy')
try {
const textarea = document.createElement('textarea')
textarea.value = uri.value
// Avoid scrolling to bottom
textarea.style.position = 'fixed'
textarea.style.top = '0'
textarea.style.left = '0'
textarea.style.width = '1px'
textarea.style.height = '1px'
textarea.style.padding = '0'
textarea.style.border = 'none'
textarea.style.outline = 'none'
textarea.style.boxShadow = 'none'
textarea.style.background = 'transparent'
document.body.appendChild(textarea)
textarea.focus()
textarea.select()
const successful = document.execCommand && document.execCommand('copy')
document.body.removeChild(textarea)
if (successful) {
copied.value = true
setTimeout(() => { copied.value = false }, 2000)
return
}
} catch (e) {
console.error('Failed to copy using fallback method:', e)
}
// If we reach here, copying failed; provide a visible failure state
copied.value = false
if (typeof window !== 'undefined') {
alert('Failed to copy URI to clipboard. Please copy it manually.')
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants