Release 3.8.2.13: iOS Packet Tunnel client and Clash URL subscriptions.
ci / test (macos-latest) (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run

Add SwiftUI + Network Extension (LibXray/hev), parse Clash Meta YAML from subscription URLs into vless/trojan/hy2 share links, keep the selected server on connect, and ship Navis-3.8.2.13.ipa with versioned changelog.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
M4
2026-08-01 22:46:32 +03:00
co-authored by Cursor
parent 8b564110d5
commit 7ceb3a5148
44 changed files with 4165 additions and 72 deletions
+90 -6
View File
@@ -2,6 +2,8 @@ package subscription
import (
"encoding/base64"
"os"
"path/filepath"
"strings"
"testing"
)
@@ -43,18 +45,100 @@ proxies:
sni: de.example.com
obfs: salamander
obfs-password: obfspass
- name: CZ Vless
type: vless
server: cz.example.com
port: 443
uuid: 7e1c0fbf-1758-4781-97f6-ae8715ed3f60
network: xhttp
tls: true
servername: cz.example.com
alpn:
- h2
- http/1.1
xhttp-opts:
path: /xhttp
host: cz.example.com
mode: auto
client-fingerprint: chrome
- name: CZ Trojan
type: trojan
server: cz.example.com
port: 20000
password: secretpass
network: ws
tls: true
sni: cz.example.com
ws-opts:
path: /ws
proxy-groups:
- name: Auto
icon: https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/Proxy.png
type: select
`
items, err := ParseBody(body)
if err != nil {
t.Fatal(err)
}
if len(items) != 1 {
t.Fatalf("got %d", len(items))
if len(items) != 3 {
t.Fatalf("got %d items: %+v", len(items), items)
}
if items[0].Protocol != "hysteria2" {
t.Fatalf("proto %s", items[0].Protocol)
byProto := map[string]int{}
var vlessURI string
for _, it := range items {
byProto[string(it.Protocol)]++
if it.Protocol == "naive" {
t.Fatalf("unexpected naive: %s %s", it.Name, it.URI)
}
if it.Protocol == "vless" {
vlessURI = it.URI
}
}
if !strings.Contains(items[0].URI, "de.example.com:443") {
t.Fatalf("uri %s", items[0].URI)
if byProto["hysteria2"] != 1 || byProto["vless"] != 1 || byProto["trojan"] != 1 {
t.Fatalf("protos %+v", byProto)
}
if !strings.Contains(items[0].URI+items[1].URI+items[2].URI, "de.example.com:443") {
t.Fatalf("missing hy2 host in %+v", items)
}
if !strings.Contains(vlessURI, "alpn=h2") {
t.Fatalf("alpn list broken in %s", vlessURI)
}
if strings.Contains(vlessURI, "alpn=-") {
t.Fatalf("alpn bled list marker in %s", vlessURI)
}
if !strings.Contains(vlessURI, "mode=auto") {
t.Fatalf("xhttp mode missing in %s", vlessURI)
}
}
func TestNaiveAuthIgnoresPathAt(t *testing.T) {
if naiveLinkHasAuth("https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/Proxy.png") {
t.Fatal("path @master must not count as auth")
}
if !naiveLinkHasAuth("https://user:pass@naive.example.com") {
t.Fatal("real auth required")
}
}
func TestParseBodyRealClashSubscriptionFile(t *testing.T) {
path := filepath.Join("testdata", "evilfox_clash.yaml")
raw, err := os.ReadFile(path)
if err != nil {
t.Skip("optional fixture missing:", path)
}
items, err := ParseBody(string(raw))
if err != nil {
t.Fatal(err)
}
if len(items) < 80 {
t.Fatalf("expected ~89 proxies, got %d", len(items))
}
for _, it := range items {
if it.Protocol == "naive" {
t.Fatalf("naive noise: %s %s", it.Name, it.URI)
}
if strings.EqualFold(it.Name, "master") || strings.HasPrefix(strings.ToLower(it.Name), "master-") {
t.Fatalf("icon false-positive name: %s", it.Name)
}
}
}