Skip to content

Commit 8fd1f1a

Browse files
Max Lvclaude
authored andcommitted
Add SIP002 URI & QR code generator and clean up docs
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>
1 parent 6b1c064 commit 8fd1f1a

13 files changed

Lines changed: 497 additions & 83 deletions

File tree

docs/.vitepress/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default {
3131

3232
footer: {
3333
message: 'This website is released under the MIT License.',
34-
copyright: 'Copyright © 2022 Shadowsocks contributors'
34+
copyright: 'Copyright © 2022-present Shadowsocks contributors'
3535
},
3636

3737
algolia: {
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
<script setup lang="ts">
2+
import { ref, computed, watch, onMounted } from 'vue'
3+
4+
const methods = {
5+
'AEAD-2022 (Recommended)': [
6+
'2022-blake3-aes-128-gcm',
7+
'2022-blake3-aes-256-gcm',
8+
'2022-blake3-chacha20-poly1305',
9+
],
10+
'AEAD': [
11+
'aes-128-gcm',
12+
'aes-192-gcm',
13+
'aes-256-gcm',
14+
'chacha20-ietf-poly1305',
15+
'xchacha20-ietf-poly1305',
16+
],
17+
'Stream (Deprecated)': [
18+
'aes-128-cfb',
19+
'aes-192-cfb',
20+
'aes-256-cfb',
21+
'aes-128-ctr',
22+
'aes-192-ctr',
23+
'aes-256-ctr',
24+
'camellia-128-cfb',
25+
'camellia-192-cfb',
26+
'camellia-256-cfb',
27+
'chacha20-ietf',
28+
'bf-cfb',
29+
'rc4-md5',
30+
],
31+
}
32+
33+
const method = ref('2022-blake3-aes-256-gcm')
34+
const password = ref('')
35+
const hostname = ref('')
36+
const port = ref(8388)
37+
const plugin = ref('')
38+
const tag = ref('')
39+
const qrDataUrl = ref('')
40+
const copied = ref(false)
41+
42+
let toDataURL: ((text: string) => Promise<string>) | null = null
43+
44+
function isAead2022(m: string): boolean {
45+
return m.startsWith('2022-blake3-')
46+
}
47+
48+
function base64urlEncode(str: string): string {
49+
const encoded = btoa(str)
50+
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
51+
}
52+
53+
const uri = computed(() => {
54+
if (!password.value || !hostname.value || !port.value) return ''
55+
56+
let userinfo: string
57+
if (isAead2022(method.value)) {
58+
userinfo = encodeURIComponent(method.value) + ':' + encodeURIComponent(password.value)
59+
} else {
60+
userinfo = base64urlEncode(method.value + ':' + password.value)
61+
}
62+
63+
let result = `ss://${userinfo}@${hostname.value}:${port.value}`
64+
65+
if (plugin.value) {
66+
result += '/?plugin=' + encodeURIComponent(plugin.value)
67+
}
68+
69+
if (tag.value) {
70+
result += '#' + encodeURIComponent(tag.value)
71+
}
72+
73+
return result
74+
})
75+
76+
onMounted(async () => {
77+
const qr = await import('qrcode')
78+
toDataURL = qr.toDataURL
79+
80+
watch(uri, async (val) => {
81+
if (val && toDataURL) {
82+
qrDataUrl.value = await toDataURL(val)
83+
} else {
84+
qrDataUrl.value = ''
85+
}
86+
}, { immediate: true })
87+
})
88+
89+
async function copyUri() {
90+
if (!uri.value) return
91+
await navigator.clipboard.writeText(uri.value)
92+
copied.value = true
93+
setTimeout(() => { copied.value = false }, 2000)
94+
}
95+
</script>
96+
97+
<template>
98+
<div class="sip002-generator">
99+
<div class="form-grid">
100+
<div class="field">
101+
<label for="sip002-method">Method</label>
102+
<select id="sip002-method" v-model="method">
103+
<optgroup v-for="(group, label) in methods" :key="label" :label="label">
104+
<option v-for="m in group" :key="m" :value="m">{{ m }}</option>
105+
</optgroup>
106+
</select>
107+
</div>
108+
109+
<div class="field">
110+
<label for="sip002-password">Password / Key</label>
111+
<input id="sip002-password" v-model="password" type="text" placeholder="Enter password or PSK" />
112+
</div>
113+
114+
<div class="field">
115+
<label for="sip002-hostname">Hostname</label>
116+
<input id="sip002-hostname" v-model="hostname" type="text" placeholder="e.g. 192.168.100.1" />
117+
</div>
118+
119+
<div class="field">
120+
<label for="sip002-port">Port</label>
121+
<input id="sip002-port" v-model.number="port" type="number" min="1" max="65535" />
122+
</div>
123+
124+
<div class="field">
125+
<label for="sip002-plugin">Plugin <span class="optional">(optional)</span></label>
126+
<input id="sip002-plugin" v-model="plugin" type="text" placeholder="e.g. obfs-local;obfs=http" />
127+
</div>
128+
129+
<div class="field">
130+
<label for="sip002-tag">Tag <span class="optional">(optional)</span></label>
131+
<input id="sip002-tag" v-model="tag" type="text" placeholder="e.g. My Server" />
132+
</div>
133+
</div>
134+
135+
<div v-if="uri" class="output">
136+
<label>Generated URI</label>
137+
<div class="uri-row">
138+
<code class="uri-text">{{ uri }}</code>
139+
<button class="copy-btn" @click="copyUri">{{ copied ? 'Copied!' : 'Copy' }}</button>
140+
</div>
141+
142+
<div v-if="qrDataUrl" class="qr-wrapper">
143+
<img :src="qrDataUrl" alt="QR Code" class="qr-code" />
144+
</div>
145+
</div>
146+
</div>
147+
</template>
148+
149+
<style scoped>
150+
.sip002-generator {
151+
margin-top: 1rem;
152+
padding: 1.5rem;
153+
border: 1px solid var(--vp-c-divider);
154+
border-radius: 8px;
155+
background: var(--vp-c-bg-soft);
156+
}
157+
158+
.form-grid {
159+
display: grid;
160+
grid-template-columns: 1fr 1fr;
161+
gap: 1rem;
162+
}
163+
164+
@media (max-width: 640px) {
165+
.form-grid {
166+
grid-template-columns: 1fr;
167+
}
168+
}
169+
170+
.field {
171+
display: flex;
172+
flex-direction: column;
173+
gap: 0.35rem;
174+
}
175+
176+
.field label {
177+
font-size: 0.875rem;
178+
font-weight: 600;
179+
color: var(--vp-c-text-1);
180+
}
181+
182+
.optional {
183+
font-weight: 400;
184+
color: var(--vp-c-text-3);
185+
}
186+
187+
.field input,
188+
.field select {
189+
padding: 0.5rem 0.75rem;
190+
border: 1px solid var(--vp-c-divider);
191+
border-radius: 4px;
192+
background: var(--vp-c-bg);
193+
color: var(--vp-c-text-1);
194+
font-size: 0.875rem;
195+
outline: none;
196+
transition: border-color 0.2s;
197+
}
198+
199+
.field input:focus,
200+
.field select:focus {
201+
border-color: var(--vp-c-brand-1);
202+
}
203+
204+
.output {
205+
margin-top: 1.25rem;
206+
padding-top: 1.25rem;
207+
border-top: 1px solid var(--vp-c-divider);
208+
}
209+
210+
.output > label {
211+
font-size: 0.875rem;
212+
font-weight: 600;
213+
color: var(--vp-c-text-1);
214+
display: block;
215+
margin-bottom: 0.5rem;
216+
}
217+
218+
.uri-row {
219+
display: flex;
220+
align-items: stretch;
221+
gap: 0.5rem;
222+
}
223+
224+
.uri-text {
225+
flex: 1;
226+
padding: 0.5rem 0.75rem;
227+
background: var(--vp-c-bg);
228+
border: 1px solid var(--vp-c-divider);
229+
border-radius: 4px;
230+
font-size: 0.8125rem;
231+
word-break: break-all;
232+
color: var(--vp-c-text-1);
233+
}
234+
235+
.copy-btn {
236+
padding: 0.5rem 1rem;
237+
background: var(--vp-c-brand-1);
238+
color: var(--vp-c-white);
239+
border: none;
240+
border-radius: 4px;
241+
cursor: pointer;
242+
font-size: 0.875rem;
243+
font-weight: 500;
244+
white-space: nowrap;
245+
transition: opacity 0.2s;
246+
}
247+
248+
.copy-btn:hover {
249+
opacity: 0.85;
250+
}
251+
252+
.qr-wrapper {
253+
margin-top: 1rem;
254+
text-align: center;
255+
}
256+
257+
.qr-code {
258+
border-radius: 4px;
259+
max-width: 200px;
260+
}
261+
</style>

docs/.vitepress/theme/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import DefaultTheme from 'vitepress/theme'
2+
import SIP002Generator from './components/SIP002Generator.vue'
3+
4+
export default {
5+
...DefaultTheme,
6+
enhanceApp({ app }) {
7+
app.component('SIP002Generator', SIP002Generator)
8+
}
9+
}

docs/doc/advanced.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Optimize the shadowsocks server on Linux
44

5-
First of all, upgrade your Linux kernel to 3.5 or later.
5+
First of all, make sure your Linux kernel is reasonably up to date (4.9 or later recommended).
66

77
### Step 1, increase the maximum number of open file descriptors
88

@@ -51,7 +51,6 @@ net.core.somaxconn = 4096
5151
5252
net.ipv4.tcp_syncookies = 1
5353
net.ipv4.tcp_tw_reuse = 1
54-
net.ipv4.tcp_tw_recycle = 0
5554
net.ipv4.tcp_fin_timeout = 30
5655
net.ipv4.tcp_keepalive_time = 1200
5756
net.ipv4.ip_local_port_range = 10000 65000

docs/doc/configs.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@ Explanation of each field:
2626

2727
### Encryption Method
2828

29-
The strongest option is an [AEAD cipher](/doc/aead.html). The recommended
30-
choice is "chacha20-ietf-poly1305" or "aes-256-gcm". Other
31-
[stream ciphers](/doc/stream.html) are implemented but do not provide
32-
integrity and authenticity. Unless otherwise specified the encryption method
33-
defaults to "table", which is **not secure**.
29+
The strongest option is an [AEAD cipher](/doc/aead). The recommended
30+
choice is "chacha20-ietf-poly1305" or "aes-256-gcm". For the latest
31+
AEAD-2022 ciphers, see [SIP022](/doc/sip022). [Stream ciphers](/doc/stream)
32+
are deprecated and do not provide integrity or authenticity.
3433

3534
## URI and QR code
3635

@@ -48,21 +47,21 @@ ss://method:password@hostname:port
4847

4948
Note that the above URI doesn't follow RFC3986. It means the password here should be plain text, not percent-encoded.
5049

51-
For example, we have a server at `192.168.100.1:8888` using `bf-cfb` encryption method and password `test/!@#:`. Then, with the plain URI `ss://bf-cfb:test/!@#:@192.168.100.1:8888`, we can generate the BASE64 encoded URI:
50+
For example, we have a server at `192.168.100.1:8888` using `chacha20-ietf-poly1305` encryption method and password `test/!@#:`. Then, with the plain URI `ss://chacha20-ietf-poly1305:test/!@#:@192.168.100.1:8888`, we can generate the BASE64 encoded URI:
5251

5352
```
54-
> console.log( "ss://" + btoa("bf-cfb:test/!@#:@192.168.100.1:8888") )
55-
ss://YmYtY2ZiOnRlc3QvIUAjOkAxOTIuMTY4LjEwMC4xOjg4ODg
53+
> console.log( "ss://" + btoa("chacha20-ietf-poly1305:test/!@#:@192.168.100.1:8888") )
54+
ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTp0ZXN0LyFAIzpAMTkyLjE2OC4xMDAuMTo4ODg4
5655
```
5756

5857
To help organize and identify these URIs, you can append a tag after the BASE64 encoded string:
5958

6059
```
61-
ss://YmYtY2ZiOnRlc3QvIUAjOkAxOTIuMTY4LjEwMC4xOjg4ODg#example-server
60+
ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTp0ZXN0LyFAIzpAMTkyLjE2OC4xMDAuMTo4ODg4#example-server
6261
```
6362

6463
This URI can also be encoded to QR code. Then, just scan it with your Android / iOS devices:
6564

6665
### SIP002
6766

68-
There is also a new URI scheme proposed in <a href="/doc/sip002.html">SIP002</a>. Any client or server which supports SIP003 plugin should use SIP002 URI scheme instead.
67+
There is also a new URI scheme proposed in [SIP002](/doc/sip002). Any client or server which supports SIP003 plugin should use the SIP002 URI scheme instead.

0 commit comments

Comments
 (0)