Update On Mon Mar 3 19:34:41 CET 2025

This commit is contained in:
github-action[bot]
2025-03-03 19:34:42 +01:00
parent f673e85f4b
commit bb2cbae785
249 changed files with 11549 additions and 2089 deletions

1
.github/update.log vendored
View File

@@ -930,3 +930,4 @@ Update On Thu Feb 27 19:34:57 CET 2025
Update On Fri Feb 28 19:34:20 CET 2025
Update On Sat Mar 1 19:31:26 CET 2025
Update On Sun Mar 2 19:33:02 CET 2025
Update On Mon Mar 3 19:34:32 CET 2025

View File

@@ -46,8 +46,8 @@ subprojects {
minSdk = 21
targetSdk = 35
versionName = "2.11.6"
versionCode = 211006
versionName = "2.11.7"
versionCode = 211007
resValue("string", "release_name", "v$versionName")
resValue("integer", "release_code", "$versionCode")

View File

@@ -1,17 +1,17 @@
[Unit]
Description=mihomo Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service
Documentation=https://wiki.metacubex.one
After=network.target nss-lookup.target network-online.target
[Service]
Type=simple
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
Restart=always
ExecStartPre=/usr/bin/sleep 2s
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,17 @@
[Unit]
Description=mihomo Daemon, Another Clash Kernel.
Documentation=https://wiki.metacubex.one
After=network.target nss-lookup.target network-online.target
[Service]
Type=simple
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target

View File

@@ -276,17 +276,20 @@ jobs:
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/mihomo
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/
cp LICENSE mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo/
cp .github/mihomo.service mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
cp .github/{mihomo.service,mihomo@.service} mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system/
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo/config.yaml <<EOF
mixed-port: 7890
external-controller: 127.0.0.1:9090
EOF
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/conffiles <<EOF
/etc/mihomo/config.yaml
EOF
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF

View File

@@ -42,6 +42,7 @@ type AnyTLSOption struct {
UDP bool `proxy:"udp,omitempty"`
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
MinIdleSession int `proxy:"min-idle-session,omitempty"`
}
func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
@@ -98,6 +99,7 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
Dialer: singDialer,
IdleSessionCheckInterval: time.Duration(option.IdleSessionCheckInterval) * time.Second,
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
MinIdleSession: option.MinIdleSession,
}
tlsConfig := &vmess.TLSConfig{
Host: option.SNI,

View File

@@ -459,6 +459,11 @@ func NewVmess(option VmessOption) (*Vmess, error) {
option: &option,
}
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
}
switch option.Network {
case "h2":
if len(option.HTTP2Opts.Host) == 0 {
@@ -507,11 +512,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
}
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
}
return v, nil
}

View File

@@ -139,10 +139,13 @@ func (ranges IntRanges[T]) Range(f func(t T) bool) {
}
for _, r := range ranges {
for i := r.Start(); i <= r.End(); i++ {
for i := r.Start(); i <= r.End() && i >= r.Start(); i++ {
if !f(i) {
return
}
if i+1 < i { // integer overflow
break
}
}
}
}

View File

@@ -28,6 +28,7 @@ const (
VLESS
REDIR
TPROXY
TROJAN
TUNNEL
TUN
TUIC
@@ -77,6 +78,8 @@ func (t Type) String() string {
return "Redir"
case TPROXY:
return "TProxy"
case TROJAN:
return "Trojan"
case TUNNEL:
return "Tunnel"
case TUN:
@@ -115,6 +118,8 @@ func ParseType(t string) (*Type, error) {
res = REDIR
case "TPROXY":
res = TPROXY
case "TROJAN":
res = TROJAN
case "TUNNEL":
res = TUNNEL
case "TUN":

View File

@@ -874,6 +874,7 @@ proxies: # socks5
udp: true
# idle-session-check-interval: 30 # seconds
# idle-session-timeout: 30 # seconds
# min-idle-session: 0
# sni: "example.com"
# alpn:
# - h2
@@ -1095,7 +1096,7 @@ sub-rules:
listeners:
- name: socks5-in-1
type: socks
port: 10808
port: 10808 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
#listen: 0.0.0.0 # 默认监听 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理
@@ -1103,20 +1104,26 @@ listeners:
# users: # 如果不填写users项则遵从全局authentication设置如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
# - username: aaa
# password: aaa
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
- name: http-in-1
type: http
port: 10809
port: 10809 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# users: # 如果不填写users项则遵从全局authentication设置如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
# - username: aaa
# password: aaa
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
- name: mixed-in-1
type: mixed # HTTP(S) 和 SOCKS 代理混合
port: 10810
port: 10810 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
@@ -1124,17 +1131,20 @@ listeners:
# users: # 如果不填写users项则遵从全局authentication设置如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
# - username: aaa
# password: aaa
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
- name: reidr-in-1
type: redir
port: 10811
port: 10811 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
- name: tproxy-in-1
type: tproxy
port: 10812
port: 10812 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
@@ -1142,7 +1152,7 @@ listeners:
- name: shadowsocks-in-1
type: shadowsocks
port: 10813
port: 10813 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
@@ -1151,7 +1161,7 @@ listeners:
- name: vmess-in-1
type: vmess
port: 10814
port: 10814 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
@@ -1160,6 +1170,7 @@ listeners:
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
alterId: 1
# ws-path: "/" # 如果不为空则开启 websocket 传输层
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
@@ -1174,7 +1185,7 @@ listeners:
- name: tuic-in-1
type: tuic
port: 10815
port: 10815 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
@@ -1194,7 +1205,7 @@ listeners:
- name: tunnel-in-1
type: tunnel
port: 10816
port: 10816 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
@@ -1203,7 +1214,7 @@ listeners:
- name: vless-in-1
type: vless
port: 10817
port: 10817 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
@@ -1212,6 +1223,7 @@ listeners:
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
flow: xtls-rprx-vision
# ws-path: "/" # 如果不为空则开启 websocket 传输层
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
@@ -1227,7 +1239,7 @@ listeners:
- name: anytls-in-1
type: anytls
port: 10818
port: 10818 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
users:
username1: password1
@@ -1237,6 +1249,34 @@ listeners:
private-key: ./server.key
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
- name: trojan-in-1
type: trojan
port: 10819 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
users:
- username: 1
password: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
# ws-path: "/" # 如果不为空则开启 websocket 传输层
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
# 下面两项如果填写则开启 tls需要同时填写
certificate: ./server.crt
private-key: ./server.key
# 如果填写reality-config则开启reality注意不可与certificate和private-key同时填写
# reality-config:
# dest: test.com:443
# private-key: jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0 # 可由 mihomo generate reality-keypair 命令生成
# short-id:
# - 0123456789abcdef
# server-names:
# - test.com
# ss-option: # like trojan-go's `shadowsocks` config
# enabled: false
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
# password: "example"
### 注意对于trojan listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “ss-option” 的其中一项 ###
- name: tun-in-1
type: tun
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules

View File

@@ -28,7 +28,7 @@ require (
github.com/metacubex/sing-shadowsocks v0.2.8
github.com/metacubex/sing-shadowsocks2 v0.2.2
github.com/metacubex/sing-tun v0.4.5
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
github.com/metacubex/utls v1.6.6
@@ -41,9 +41,10 @@ require (
github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
github.com/sagernet/sing v0.5.1
github.com/sagernet/sing v0.5.2
github.com/sagernet/sing-mux v0.2.1
github.com/sagernet/sing-shadowtls v0.1.5
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/samber/lo v1.49.1
github.com/shirou/gopsutil/v4 v4.25.1
github.com/sirupsen/logrus v1.9.3
@@ -98,7 +99,6 @@ require (
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
@@ -117,4 +117,4 @@ require (
golang.org/x/tools v0.24.0 // indirect
)
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a

View File

@@ -111,8 +111,8 @@ github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiL
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 h1:aHsYiTvubfgMa3JMTDY//hDXVvFWrHg6ARckR52ttZs=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8=
github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000 h1:gUbMXcQXhXGj0vCpCVFTUyIH7TMpD1dpTcNv/MCS+ok=
github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a h1:xjPXdDTlIKq4U/KnKpoCtkxD03T8GimtQrvHy/3dN00=
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 h1:UkPoRAnoBQMn7IK5qpoIV3OejU15q+rqel3NrbSCFKA=
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
@@ -121,8 +121,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhD
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0=
github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3 h1:2kq6azIvsTjTnyw66xXDl5zMzIJqF7GTbvLpkroHssg=
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=

View File

@@ -0,0 +1,16 @@
package config
import (
"github.com/metacubex/mihomo/component/auth"
"github.com/metacubex/mihomo/listener/reality"
)
// AuthServer for http/socks/mixed server
type AuthServer struct {
Enable bool
Listen string
AuthStore auth.AuthStore
Certificate string
PrivateKey string
RealityConfig reality.Config
}

View File

@@ -0,0 +1,38 @@
package config
import (
"encoding/json"
"github.com/metacubex/mihomo/listener/reality"
"github.com/metacubex/mihomo/listener/sing"
)
type TrojanUser struct {
Username string
Password string
}
type TrojanServer struct {
Enable bool
Listen string
Users []TrojanUser
WsPath string
GrpcServiceName string
Certificate string
PrivateKey string
RealityConfig reality.Config
MuxOption sing.MuxOption
TrojanSSOption TrojanSSOption
}
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
type TrojanSSOption struct {
Enabled bool
Method string
Password string
}
func (t TrojanServer) String() string {
b, _ := json.Marshal(t)
return string(b)
}

View File

@@ -14,14 +14,15 @@ type VlessUser struct {
}
type VlessServer struct {
Enable bool
Listen string
Users []VlessUser
WsPath string
Certificate string
PrivateKey string
RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
Enable bool
Listen string
Users []VlessUser
WsPath string
GrpcServiceName string
Certificate string
PrivateKey string
RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
}
func (t VlessServer) String() string {

View File

@@ -14,14 +14,15 @@ type VmessUser struct {
}
type VmessServer struct {
Enable bool
Listen string
Users []VmessUser
WsPath string
Certificate string
PrivateKey string
RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
Enable bool
Listen string
Users []VmessUser
WsPath string
GrpcServiceName string
Certificate string
PrivateKey string
RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
}
func (t VmessServer) String() string {

View File

@@ -1,12 +1,16 @@
package http
import (
"crypto/tls"
"errors"
"net"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/auth"
N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/reality"
)
type Listener struct {
@@ -32,7 +36,7 @@ func (l *Listener) Close() error {
}
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...)
return NewWithConfig(LC.AuthServer{Enable: true, Listen: addr, AuthStore: authStore.Default}, tunnel, additions...)
}
// NewWithAuthenticate
@@ -40,12 +44,12 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
store := authStore.Default
if !authenticate {
store = authStore.Default
store = authStore.Nil
}
return NewWithAuthenticator(addr, tunnel, store, additions...)
return NewWithConfig(LC.AuthServer{Enable: true, Listen: addr, AuthStore: store}, tunnel, additions...)
}
func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) {
func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
isDefault := false
if len(additions) == 0 {
isDefault = true
@@ -55,15 +59,42 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
}
}
l, err := inbound.Listen("tcp", addr)
l, err := inbound.Listen("tcp", config.Listen)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{}
var realityBuilder *reality.Builder
if config.Certificate != "" && config.PrivateKey != "" {
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")
}
realityBuilder, err = config.RealityConfig.Build()
if err != nil {
return nil, err
}
}
if realityBuilder != nil {
l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig)
}
hl := &Listener{
listener: l,
addr: addr,
addr: config.Listen,
}
go func() {
for {
conn, err := hl.listener.Accept()
@@ -74,7 +105,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
continue
}
store := store
store := config.AuthStore
if isDefault || store == authStore.Default { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
_ = conn.Close()

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/listener/anytls"
LC "github.com/metacubex/mihomo/listener/config"
@@ -52,12 +54,13 @@ func (v *AnyTLS) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (v *AnyTLS) Address() string {
var addrList []string
if v.l != nil {
for _, addr := range v.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener

View File

@@ -5,8 +5,10 @@ import (
"net"
"net/netip"
"strconv"
"strings"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
)
@@ -15,7 +17,7 @@ type Base struct {
name string
specialRules string
listenAddr netip.Addr
port int
ports utils.IntRanges[uint16]
}
func NewBase(options *BaseOption) (*Base, error) {
@@ -26,11 +28,15 @@ func NewBase(options *BaseOption) (*Base, error) {
if err != nil {
return nil, err
}
ports, err := utils.NewUnsignedRanges[uint16](options.Port)
if err != nil {
return nil, err
}
return &Base{
name: options.Name(),
listenAddr: addr,
specialRules: options.SpecialRules,
port: options.Port,
ports: ports,
config: options,
}, nil
}
@@ -57,7 +63,15 @@ func (b *Base) Name() string {
// RawAddress implements constant.InboundListener
func (b *Base) RawAddress() string {
return net.JoinHostPort(b.listenAddr.String(), strconv.Itoa(int(b.port)))
if len(b.ports) == 0 {
return net.JoinHostPort(b.listenAddr.String(), "0")
}
address := make([]string, 0, len(b.ports))
b.ports.Range(func(port uint16) bool {
address = append(address, net.JoinHostPort(b.listenAddr.String(), strconv.Itoa(int(port))))
return true
})
return strings.Join(address, ",")
}
// Listen implements constant.InboundListener
@@ -74,7 +88,7 @@ var _ C.InboundListener = (*Base)(nil)
type BaseOption struct {
NameStr string `inbound:"name"`
Listen string `inbound:"listen,omitempty"`
Port int `inbound:"port,omitempty"`
Port string `inbound:"port,omitempty"`
SpecialRules string `inbound:"rule,omitempty"`
SpecialProxy string `inbound:"proxy,omitempty"`
}

View File

@@ -1,14 +1,22 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/http"
"github.com/metacubex/mihomo/log"
)
type HTTPOption struct {
BaseOption
Users AuthUsers `inbound:"users,omitempty"`
Users AuthUsers `inbound:"users,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
}
func (o HTTPOption) Equal(config C.InboundConfig) bool {
@@ -18,7 +26,7 @@ func (o HTTPOption) Equal(config C.InboundConfig) bool {
type HTTP struct {
*Base
config *HTTPOption
l *http.Listener
l []*http.Listener
}
func NewHTTP(options *HTTPOption) (*HTTP, error) {
@@ -39,15 +47,32 @@ func (h *HTTP) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (h *HTTP) Address() string {
return h.l.Address()
var addrList []string
for _, l := range h.l {
addrList = append(addrList, l.Address())
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (h *HTTP) Listen(tunnel C.Tunnel) error {
var err error
h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuthStore(), h.Additions()...)
if err != nil {
return err
for _, addr := range strings.Split(h.RawAddress(), ",") {
l, err := http.NewWithConfig(
LC.AuthServer{
Enable: true,
Listen: addr,
AuthStore: h.config.Users.GetAuthStore(),
Certificate: h.config.Certificate,
PrivateKey: h.config.PrivateKey,
RealityConfig: h.config.RealityConfig.Build(),
},
tunnel,
h.Additions()...,
)
if err != nil {
return err
}
h.l = append(h.l, l)
}
log.Infoln("HTTP[%s] proxy listening at: %s", h.Name(), h.Address())
return nil
@@ -55,8 +80,15 @@ func (h *HTTP) Listen(tunnel C.Tunnel) error {
// Close implements constant.InboundListener
func (h *HTTP) Close() error {
if h.l != nil {
return h.l.Close()
var errs []error
for _, l := range h.l {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_hysteria2"
@@ -83,12 +85,13 @@ func (t *Hysteria2) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (t *Hysteria2) Address() string {
var addrList []string
if t.l != nil {
for _, addr := range t.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener

View File

@@ -1,19 +1,24 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/mixed"
"github.com/metacubex/mihomo/listener/socks"
"github.com/metacubex/mihomo/log"
)
type MixedOption struct {
BaseOption
Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
}
func (o MixedOption) Equal(config C.InboundConfig) bool {
@@ -23,8 +28,8 @@ func (o MixedOption) Equal(config C.InboundConfig) bool {
type Mixed struct {
*Base
config *MixedOption
l *mixed.Listener
lUDP *socks.UDPListener
l []*mixed.Listener
lUDP []*socks.UDPListener
udp bool
}
@@ -47,21 +52,39 @@ func (m *Mixed) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (m *Mixed) Address() string {
return m.l.Address()
var addrList []string
for _, l := range m.l {
addrList = append(addrList, l.Address())
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (m *Mixed) Listen(tunnel C.Tunnel) error {
var err error
m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuthStore(), m.Additions()...)
if err != nil {
return err
}
if m.udp {
m.lUDP, err = socks.NewUDP(m.RawAddress(), tunnel, m.Additions()...)
for _, addr := range strings.Split(m.RawAddress(), ",") {
l, err := mixed.NewWithConfig(
LC.AuthServer{
Enable: true,
Listen: addr,
AuthStore: m.config.Users.GetAuthStore(),
Certificate: m.config.Certificate,
PrivateKey: m.config.PrivateKey,
RealityConfig: m.config.RealityConfig.Build(),
},
tunnel,
m.Additions()...,
)
if err != nil {
return err
}
m.l = append(m.l, l)
if m.udp {
lUDP, err := socks.NewUDP(addr, tunnel, m.Additions()...)
if err != nil {
return err
}
m.lUDP = append(m.lUDP, lUDP)
}
}
log.Infoln("Mixed(http+socks)[%s] proxy listening at: %s", m.Name(), m.Address())
return nil
@@ -69,22 +92,23 @@ func (m *Mixed) Listen(tunnel C.Tunnel) error {
// Close implements constant.InboundListener
func (m *Mixed) Close() error {
var err error
if m.l != nil {
if tcpErr := m.l.Close(); tcpErr != nil {
err = tcpErr
var errs []error
for _, l := range m.l {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
}
}
if m.udp && m.lUDP != nil {
if udpErr := m.lUDP.Close(); udpErr != nil {
if err == nil {
err = udpErr
} else {
return fmt.Errorf("close tcp err: %s, close udp err: %s", err.Error(), udpErr.Error())
}
for _, l := range m.lUDP {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close udp listener %s err: %w", l.Address(), err))
}
}
return err
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
var _ C.InboundListener = (*Mixed)(nil)

View File

@@ -1,6 +1,10 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/listener/redir"
"github.com/metacubex/mihomo/log"
@@ -17,7 +21,7 @@ func (o RedirOption) Equal(config C.InboundConfig) bool {
type Redir struct {
*Base
config *RedirOption
l *redir.Listener
l []*redir.Listener
}
func NewRedir(options *RedirOption) (*Redir, error) {
@@ -38,15 +42,21 @@ func (r *Redir) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (r *Redir) Address() string {
return r.l.Address()
var addrList []string
for _, l := range r.l {
addrList = append(addrList, l.Address())
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (r *Redir) Listen(tunnel C.Tunnel) error {
var err error
r.l, err = redir.New(r.RawAddress(), tunnel, r.Additions()...)
if err != nil {
return err
for _, addr := range strings.Split(r.RawAddress(), ",") {
l, err := redir.New(addr, tunnel, r.Additions()...)
if err != nil {
return err
}
r.l = append(r.l, l)
}
log.Infoln("Redir[%s] proxy listening at: %s", r.Name(), r.Address())
return nil
@@ -54,8 +64,15 @@ func (r *Redir) Listen(tunnel C.Tunnel) error {
// Close implements constant.InboundListener
func (r *Redir) Close() error {
if r.l != nil {
r.l.Close()
var errs []error
for _, l := range r.l {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close redir listener %s err: %w", l.Address(), err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_shadowsocks"
@@ -52,12 +54,13 @@ func (s *ShadowSocks) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (s *ShadowSocks) Address() string {
var addrList []string
if s.l != nil {
for _, addr := range s.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener

View File

@@ -1,16 +1,23 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/socks"
"github.com/metacubex/mihomo/log"
)
type SocksOption struct {
BaseOption
Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
}
func (o SocksOption) Equal(config C.InboundConfig) bool {
@@ -21,8 +28,8 @@ type Socks struct {
*Base
config *SocksOption
udp bool
stl *socks.Listener
sul *socks.UDPListener
stl []*socks.Listener
sul []*socks.UDPListener
}
func NewSocks(options *SocksOption) (*Socks, error) {
@@ -44,40 +51,60 @@ func (s *Socks) Config() C.InboundConfig {
// Close implements constant.InboundListener
func (s *Socks) Close() error {
var err error
if s.stl != nil {
if tcpErr := s.stl.Close(); tcpErr != nil {
err = tcpErr
var errs []error
for _, l := range s.stl {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
}
}
if s.udp && s.sul != nil {
if udpErr := s.sul.Close(); udpErr != nil {
if err == nil {
err = udpErr
} else {
return fmt.Errorf("close tcp err: %s, close udp err: %s", err.Error(), udpErr.Error())
}
for _, l := range s.sul {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close udp listener %s err: %w", l.Address(), err))
}
}
return err
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
// Address implements constant.InboundListener
func (s *Socks) Address() string {
return s.stl.Address()
var addrList []string
for _, l := range s.stl {
addrList = append(addrList, l.Address())
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (s *Socks) Listen(tunnel C.Tunnel) error {
var err error
if s.stl, err = socks.NewWithAuthenticator(s.RawAddress(), tunnel, s.config.Users.GetAuthStore(), s.Additions()...); err != nil {
return err
}
if s.udp {
if s.sul, err = socks.NewUDP(s.RawAddress(), tunnel, s.Additions()...); err != nil {
for _, addr := range strings.Split(s.RawAddress(), ",") {
stl, err := socks.NewWithConfig(
LC.AuthServer{
Enable: true,
Listen: addr,
AuthStore: s.config.Users.GetAuthStore(),
Certificate: s.config.Certificate,
PrivateKey: s.config.PrivateKey,
RealityConfig: s.config.RealityConfig.Build(),
},
tunnel,
s.Additions()...,
)
if err != nil {
return err
}
s.stl = append(s.stl, stl)
if s.udp {
sul, err := socks.NewUDP(addr, tunnel, s.Additions()...)
if err != nil {
return err
}
s.sul = append(s.sul, sul)
}
}
log.Infoln("SOCKS[%s] proxy listening at: %s", s.Name(), s.Address())

View File

@@ -1,7 +1,9 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/listener/tproxy"
@@ -20,8 +22,8 @@ func (o TProxyOption) Equal(config C.InboundConfig) bool {
type TProxy struct {
*Base
config *TProxyOption
lUDP *tproxy.UDPListener
lTCP *tproxy.Listener
lUDP []*tproxy.UDPListener
lTCP []*tproxy.Listener
udp bool
}
@@ -45,21 +47,28 @@ func (t *TProxy) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (t *TProxy) Address() string {
return t.lTCP.Address()
var addrList []string
for _, l := range t.lTCP {
addrList = append(addrList, l.Address())
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (t *TProxy) Listen(tunnel C.Tunnel) error {
var err error
t.lTCP, err = tproxy.New(t.RawAddress(), tunnel, t.Additions()...)
if err != nil {
return err
}
if t.udp {
t.lUDP, err = tproxy.NewUDP(t.RawAddress(), tunnel, t.Additions()...)
for _, addr := range strings.Split(t.RawAddress(), ",") {
lTCP, err := tproxy.New(addr, tunnel, t.Additions()...)
if err != nil {
return err
}
t.lTCP = append(t.lTCP, lTCP)
if t.udp {
lUDP, err := tproxy.NewUDP(addr, tunnel, t.Additions()...)
if err != nil {
return err
}
t.lUDP = append(t.lUDP, lUDP)
}
}
log.Infoln("TProxy[%s] proxy listening at: %s", t.Name(), t.Address())
return nil
@@ -67,23 +76,21 @@ func (t *TProxy) Listen(tunnel C.Tunnel) error {
// Close implements constant.InboundListener
func (t *TProxy) Close() error {
var tcpErr error
var udpErr error
if t.lTCP != nil {
tcpErr = t.lTCP.Close()
var errs []error
for _, l := range t.lTCP {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
}
}
if t.lUDP != nil {
udpErr = t.lUDP.Close()
for _, l := range t.lUDP {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close udp listener %s err: %w", l.Address(), err))
}
}
if tcpErr != nil && udpErr != nil {
return fmt.Errorf("tcp close err: %s and udp close err: %s", tcpErr, udpErr)
}
if tcpErr != nil {
return tcpErr
}
if udpErr != nil {
return udpErr
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}

View File

@@ -0,0 +1,113 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/trojan"
"github.com/metacubex/mihomo/log"
)
type TrojanOption struct {
BaseOption
Users []TrojanUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
SSOption TrojanSSOption `inbound:"ss-option,omitempty"`
}
type TrojanUser struct {
Username string `inbound:"username,omitempty"`
Password string `inbound:"password"`
}
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
type TrojanSSOption struct {
Enabled bool `inbound:"enabled,omitempty"`
Method string `inbound:"method,omitempty"`
Password string `inbound:"password,omitempty"`
}
func (o TrojanOption) Equal(config C.InboundConfig) bool {
return optionToString(o) == optionToString(config)
}
type Trojan struct {
*Base
config *TrojanOption
l C.MultiAddrListener
vs LC.TrojanServer
}
func NewTrojan(options *TrojanOption) (*Trojan, error) {
base, err := NewBase(&options.BaseOption)
if err != nil {
return nil, err
}
users := make([]LC.TrojanUser, len(options.Users))
for i, v := range options.Users {
users[i] = LC.TrojanUser{
Username: v.Username,
Password: v.Password,
}
}
return &Trojan{
Base: base,
config: options,
vs: LC.TrojanServer{
Enable: true,
Listen: base.RawAddress(),
Users: users,
WsPath: options.WsPath,
GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),
TrojanSSOption: LC.TrojanSSOption{
Enabled: options.SSOption.Enabled,
Method: options.SSOption.Method,
Password: options.SSOption.Password,
},
},
}, nil
}
// Config implements constant.InboundListener
func (v *Trojan) Config() C.InboundConfig {
return v.config
}
// Address implements constant.InboundListener
func (v *Trojan) Address() string {
var addrList []string
if v.l != nil {
for _, addr := range v.l.AddrList() {
addrList = append(addrList, addr.String())
}
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (v *Trojan) Listen(tunnel C.Tunnel) error {
var err error
v.l, err = trojan.New(v.vs, tunnel, v.Additions()...)
if err != nil {
return err
}
log.Infoln("Trojan[%s] proxy listening at: %s", v.Name(), v.Address())
return nil
}
// Close implements constant.InboundListener
func (v *Trojan) Close() error {
return v.l.Close()
}
var _ C.InboundListener = (*Trojan)(nil)

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/tuic"
@@ -66,12 +68,13 @@ func (t *Tuic) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (t *Tuic) Address() string {
var addrList []string
if t.l != nil {
for _, addr := range t.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener

View File

@@ -1,7 +1,9 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
LT "github.com/metacubex/mihomo/listener/tunnel"
@@ -21,8 +23,8 @@ func (o TunnelOption) Equal(config C.InboundConfig) bool {
type Tunnel struct {
*Base
config *TunnelOption
ttl *LT.Listener
tul *LT.PacketConn
ttl []*LT.Listener
tul []*LT.PacketConn
}
func NewTunnel(options *TunnelOption) (*Tunnel, error) {
@@ -43,56 +45,62 @@ func (t *Tunnel) Config() C.InboundConfig {
// Close implements constant.InboundListener
func (t *Tunnel) Close() error {
var err error
if t.ttl != nil {
if tcpErr := t.ttl.Close(); tcpErr != nil {
err = tcpErr
var errs []error
for _, l := range t.ttl {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
}
}
if t.tul != nil {
if udpErr := t.tul.Close(); udpErr != nil {
if err == nil {
err = udpErr
} else {
return fmt.Errorf("close tcp err: %s, close udp err: %s", err.Error(), udpErr.Error())
}
for _, l := range t.tul {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close udp listener %s err: %w", l.Address(), err))
}
}
return err
}
// Address implements constant.InboundListener
func (t *Tunnel) Address() string {
if t.ttl != nil {
return t.ttl.Address()
}
if t.tul != nil {
return t.tul.Address()
}
return ""
}
// Listen implements constant.InboundListener
func (t *Tunnel) Listen(tunnel C.Tunnel) error {
var err error
for _, network := range t.config.Network {
switch network {
case "tcp":
if t.ttl, err = LT.New(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...); err != nil {
return err
}
case "udp":
if t.tul, err = LT.NewUDP(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...); err != nil {
return err
}
default:
log.Warnln("unknown network type: %s, passed", network)
continue
}
log.Infoln("Tunnel[%s](%s/%s)proxy listening at: %s", t.Name(), network, t.config.Target, t.Address())
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
// Address implements constant.InboundListener
func (t *Tunnel) Address() string {
var addrList []string
for _, l := range t.ttl {
addrList = append(addrList, "tcp://"+l.Address())
}
for _, l := range t.tul {
addrList = append(addrList, "udp://"+l.Address())
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (t *Tunnel) Listen(tunnel C.Tunnel) error {
for _, addr := range strings.Split(t.RawAddress(), ",") {
for _, network := range t.config.Network {
switch network {
case "tcp":
ttl, err := LT.New(addr, t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...)
if err != nil {
return err
}
t.ttl = append(t.ttl, ttl)
case "udp":
tul, err := LT.NewUDP(addr, t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...)
if err != nil {
return err
}
t.tul = append(t.tul, tul)
default:
log.Warnln("unknown network type: %s, passed", network)
continue
}
}
}
log.Infoln("Tunnel[%s](%s)proxy listening at: %s", t.Name(), t.config.Target, t.Address())
return nil
}
var _ C.InboundListener = (*Tunnel)(nil)

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_vless"
@@ -9,12 +11,13 @@ import (
type VlessOption struct {
BaseOption
Users []VlessUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
Users []VlessUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
}
type VlessUser struct {
@@ -51,14 +54,15 @@ func NewVless(options *VlessOption) (*Vless, error) {
Base: base,
config: options,
vs: LC.VlessServer{
Enable: true,
Listen: base.RawAddress(),
Users: users,
WsPath: options.WsPath,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),
Enable: true,
Listen: base.RawAddress(),
Users: users,
WsPath: options.WsPath,
GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),
},
}, nil
}
@@ -70,25 +74,18 @@ func (v *Vless) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (v *Vless) Address() string {
var addrList []string
if v.l != nil {
for _, addr := range v.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (v *Vless) Listen(tunnel C.Tunnel) error {
var err error
users := make([]LC.VlessUser, len(v.config.Users))
for i, v := range v.config.Users {
users[i] = LC.VlessUser{
Username: v.Username,
UUID: v.UUID,
Flow: v.Flow,
}
}
v.l, err = sing_vless.New(v.vs, tunnel, v.Additions()...)
if err != nil {
return err

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_vmess"
@@ -9,12 +11,13 @@ import (
type VmessOption struct {
BaseOption
Users []VmessUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
Users []VmessUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
}
type VmessUser struct {
@@ -51,14 +54,15 @@ func NewVmess(options *VmessOption) (*Vmess, error) {
Base: base,
config: options,
vs: LC.VmessServer{
Enable: true,
Listen: base.RawAddress(),
Users: users,
WsPath: options.WsPath,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),
Enable: true,
Listen: base.RawAddress(),
Users: users,
WsPath: options.WsPath,
GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),
},
}, nil
}
@@ -70,25 +74,18 @@ func (v *Vmess) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (v *Vmess) Address() string {
var addrList []string
if v.l != nil {
for _, addr := range v.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (v *Vmess) Listen(tunnel C.Tunnel) error {
var err error
users := make([]LC.VmessUser, len(v.config.Users))
for i, v := range v.config.Users {
users[i] = LC.VmessUser{
Username: v.Username,
UUID: v.UUID,
AlterID: v.AlterID,
}
}
v.l, err = sing_vmess.New(v.vs, tunnel, v.Additions()...)
if err != nil {
return err

View File

@@ -1,6 +1,8 @@
package mixed
import (
"crypto/tls"
"errors"
"net"
"github.com/metacubex/mihomo/adapter/inbound"
@@ -8,7 +10,9 @@ import (
"github.com/metacubex/mihomo/component/auth"
C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/http"
"github.com/metacubex/mihomo/listener/reality"
"github.com/metacubex/mihomo/listener/socks"
"github.com/metacubex/mihomo/transport/socks4"
"github.com/metacubex/mihomo/transport/socks5"
@@ -37,10 +41,10 @@ func (l *Listener) Close() error {
}
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...)
return NewWithConfig(LC.AuthServer{Enable: true, Listen: addr, AuthStore: authStore.Default}, tunnel, additions...)
}
func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) {
func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
isDefault := false
if len(additions) == 0 {
isDefault = true
@@ -50,14 +54,40 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
}
}
l, err := inbound.Listen("tcp", addr)
l, err := inbound.Listen("tcp", config.Listen)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{}
var realityBuilder *reality.Builder
if config.Certificate != "" && config.PrivateKey != "" {
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")
}
realityBuilder, err = config.RealityConfig.Build()
if err != nil {
return nil, err
}
}
if realityBuilder != nil {
l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig)
}
ml := &Listener{
listener: l,
addr: addr,
addr: config.Listen,
}
go func() {
for {
@@ -68,7 +98,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
}
continue
}
store := store
store := config.AuthStore
if isDefault || store == authStore.Default { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
_ = c.Close()

View File

@@ -93,6 +93,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
return nil, err
}
listener, err = IN.NewVless(vlessOption)
case "trojan":
trojanOption := &IN.TrojanOption{}
err = decoder.Decode(mapping, trojanOption)
if err != nil {
return nil, err
}
listener, err = IN.NewTrojan(trojanOption)
case "hysteria2":
hysteria2Option := &IN.Hysteria2Option{}
err = decoder.Decode(mapping, hysteria2Option)

View File

@@ -8,6 +8,7 @@ import (
N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/socks5"
)
@@ -18,6 +19,7 @@ type Listener struct {
listeners []net.Listener
udpListeners []*UDPListener
pickCipher core.Cipher
handler *sing.ListenerHandler
}
var _listener *Listener
@@ -28,7 +30,17 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
return nil, err
}
sl := &Listener{false, config, nil, nil, pickCipher}
h, err := sing.NewListenerHandler(sing.ListenerConfig{
Tunnel: tunnel,
Type: C.SHADOWSOCKS,
Additions: additions,
MuxOption: config.MuxOption,
})
if err != nil {
return nil, err
}
sl := &Listener{false, config, nil, nil, pickCipher, h}
_listener = sl
for _, addr := range strings.Split(config.Listen, ",") {
@@ -107,7 +119,8 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
_ = conn.Close()
return
}
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SHADOWSOCKS, additions...))
l.handler.HandleSocket(target, conn, additions...)
//tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SHADOWSOCKS, additions...))
}
func HandleShadowSocks(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) bool {

View File

@@ -136,8 +136,8 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
cMetadata.RawDstAddr = metadata.Destination.Unwrap().TCPAddr()
}
inbound.ApplyAdditions(cMetadata, inbound.WithDstAddr(metadata.Destination), inbound.WithSrcAddr(metadata.Source), inbound.WithInAddr(conn.LocalAddr()))
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
inbound.ApplyAdditions(cMetadata, h.Additions...)
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
h.Tunnel.HandleTCPConn(conn, cMetadata) // this goroutine must exit after conn unused
return nil
@@ -198,8 +198,8 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
cMetadata.RawDstAddr = dest.Unwrap().UDPAddr()
}
inbound.ApplyAdditions(cMetadata, inbound.WithDstAddr(dest), inbound.WithSrcAddr(metadata.Source), inbound.WithInAddr(conn.LocalAddr()))
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
inbound.ApplyAdditions(cMetadata, h.Additions...)
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
h.Tunnel.HandleUDPPacket(cPacket, cMetadata)
}

View File

@@ -0,0 +1,24 @@
package sing
import (
"context"
"net"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/transport/socks5"
)
// HandleSocket like inbound.NewSocket combine with Tunnel.HandleTCPConn but also handel specialFqdn
func (h *ListenerHandler) HandleSocket(target socks5.Addr, conn net.Conn, _additions ...inbound.Addition) {
conn, metadata := inbound.NewSocket(target, conn, h.Type, h.Additions...)
if h.IsSpecialFqdn(metadata.Host) {
_ = h.ParseSpecialFqdn(
WithAdditions(context.Background(), _additions...),
conn,
ConvertMetadata(metadata),
)
} else {
inbound.ApplyAdditions(metadata, _additions...)
h.Tunnel.HandleTCPConn(conn, metadata)
}
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/metacubex/mihomo/listener/reality"
"github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/transport/gun"
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
"github.com/metacubex/sing-vmess/vless"
@@ -92,7 +93,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
tlsConfig := &tls.Config{}
var realityBuilder *reality.Builder
var httpMux *http.ServeMux
var httpHandler http.Handler
if config.Certificate != "" && config.PrivateKey != "" {
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
@@ -111,17 +112,28 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
}
}
if config.WsPath != "" {
httpMux = http.NewServeMux()
httpMux := http.NewServeMux()
httpMux.HandleFunc(config.WsPath, func(w http.ResponseWriter, r *http.Request) {
conn, err := mihomoVMess.StreamUpgradedWebsocketConn(w, r)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
sl.HandleConn(conn, tunnel)
sl.HandleConn(conn, tunnel, additions...)
})
httpHandler = httpMux
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
}
if config.GrpcServiceName != "" {
httpHandler = gun.NewServerHandler(gun.ServerOption{
ServiceName: config.GrpcServiceName,
ConnHandler: func(conn net.Conn) {
sl.HandleConn(conn, tunnel, additions...)
},
HttpHandler: httpHandler,
})
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
}
for _, addr := range strings.Split(config.Listen, ",") {
addr := addr
@@ -141,8 +153,8 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
sl.listeners = append(sl.listeners, l)
go func() {
if httpMux != nil {
_ = http.Serve(l, httpMux)
if httpHandler != nil {
_ = http.Serve(l, httpHandler)
return
}
for {

View File

@@ -16,6 +16,7 @@ import (
"github.com/metacubex/mihomo/listener/reality"
"github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/ntp"
"github.com/metacubex/mihomo/transport/gun"
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
vmess "github.com/metacubex/sing-vmess"
@@ -76,7 +77,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
tlsConfig := &tls.Config{}
var realityBuilder *reality.Builder
var httpMux *http.ServeMux
var httpHandler http.Handler
if config.Certificate != "" && config.PrivateKey != "" {
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
@@ -95,17 +96,28 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
}
}
if config.WsPath != "" {
httpMux = http.NewServeMux()
httpMux := http.NewServeMux()
httpMux.HandleFunc(config.WsPath, func(w http.ResponseWriter, r *http.Request) {
conn, err := mihomoVMess.StreamUpgradedWebsocketConn(w, r)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
sl.HandleConn(conn, tunnel)
sl.HandleConn(conn, tunnel, additions...)
})
httpHandler = httpMux
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
}
if config.GrpcServiceName != "" {
httpHandler = gun.NewServerHandler(gun.ServerOption{
ServiceName: config.GrpcServiceName,
ConnHandler: func(conn net.Conn) {
sl.HandleConn(conn, tunnel, additions...)
},
HttpHandler: httpHandler,
})
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
}
for _, addr := range strings.Split(config.Listen, ",") {
addr := addr
@@ -123,8 +135,8 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
sl.listeners = append(sl.listeners, l)
go func() {
if httpMux != nil {
_ = http.Serve(l, httpMux)
if httpHandler != nil {
_ = http.Serve(l, httpHandler)
return
}
for {

View File

@@ -1,6 +1,8 @@
package socks
import (
"crypto/tls"
"errors"
"io"
"net"
@@ -9,6 +11,8 @@ import (
"github.com/metacubex/mihomo/component/auth"
C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/reality"
"github.com/metacubex/mihomo/transport/socks4"
"github.com/metacubex/mihomo/transport/socks5"
)
@@ -36,10 +40,10 @@ func (l *Listener) Close() error {
}
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...)
return NewWithConfig(LC.AuthServer{Enable: true, Listen: addr, AuthStore: authStore.Default}, tunnel, additions...)
}
func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) {
func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
isDefault := false
if len(additions) == 0 {
isDefault = true
@@ -49,14 +53,40 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
}
}
l, err := inbound.Listen("tcp", addr)
l, err := inbound.Listen("tcp", config.Listen)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{}
var realityBuilder *reality.Builder
if config.Certificate != "" && config.PrivateKey != "" {
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")
}
realityBuilder, err = config.RealityConfig.Build()
if err != nil {
return nil, err
}
}
if realityBuilder != nil {
l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig)
}
sl := &Listener{
listener: l,
addr: addr,
addr: config.Listen,
}
go func() {
for {
@@ -67,7 +97,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
}
continue
}
store := store
store := config.AuthStore
if isDefault || store == authStore.Default { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
_ = c.Close()

View File

@@ -0,0 +1,43 @@
package trojan
import (
"errors"
"net"
)
type packet struct {
pc net.PacketConn
rAddr net.Addr
payload []byte
put func()
}
func (c *packet) Data() []byte {
return c.payload
}
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
if addr == nil {
err = errors.New("address is invalid")
return
}
return c.pc.WriteTo(b, addr)
}
// LocalAddr returns the source IP/Port of UDP Packet
func (c *packet) LocalAddr() net.Addr {
return c.rAddr
}
func (c *packet) Drop() {
if c.put != nil {
c.put()
c.put = nil
}
c.payload = nil
}
func (c *packet) InAddr() net.Addr {
return c.pc.LocalAddr()
}

View File

@@ -0,0 +1,283 @@
package trojan
import (
"crypto/tls"
"errors"
"io"
"net"
"net/http"
"strings"
"github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/reality"
"github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/socks5"
"github.com/metacubex/mihomo/transport/trojan"
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
"github.com/sagernet/smux"
)
type Listener struct {
closed bool
config LC.TrojanServer
listeners []net.Listener
keys map[[trojan.KeyLength]byte]string
pickCipher core.Cipher
handler *sing.ListenerHandler
}
func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
if len(additions) == 0 {
additions = []inbound.Addition{
inbound.WithInName("DEFAULT-TROJAN"),
inbound.WithSpecialRules(""),
}
}
h, err := sing.NewListenerHandler(sing.ListenerConfig{
Tunnel: tunnel,
Type: C.TROJAN,
Additions: additions,
MuxOption: config.MuxOption,
})
if err != nil {
return nil, err
}
keys := make(map[[trojan.KeyLength]byte]string)
for _, user := range config.Users {
keys[trojan.Key(user.Password)] = user.Username
}
var pickCipher core.Cipher
if config.TrojanSSOption.Enabled {
if config.TrojanSSOption.Password == "" {
return nil, errors.New("empty password")
}
if config.TrojanSSOption.Method == "" {
config.TrojanSSOption.Method = "AES-128-GCM"
}
pickCipher, err = core.PickCipher(config.TrojanSSOption.Method, nil, config.TrojanSSOption.Password)
if err != nil {
return nil, err
}
}
sl = &Listener{false, config, nil, keys, pickCipher, h}
tlsConfig := &tls.Config{}
var realityBuilder *reality.Builder
var httpHandler http.Handler
if config.Certificate != "" && config.PrivateKey != "" {
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality")
}
realityBuilder, err = config.RealityConfig.Build()
if err != nil {
return nil, err
}
}
if config.WsPath != "" {
httpMux := http.NewServeMux()
httpMux.HandleFunc(config.WsPath, func(w http.ResponseWriter, r *http.Request) {
conn, err := mihomoVMess.StreamUpgradedWebsocketConn(w, r)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
sl.HandleConn(conn, tunnel, additions...)
})
httpHandler = httpMux
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
}
if config.GrpcServiceName != "" {
httpHandler = gun.NewServerHandler(gun.ServerOption{
ServiceName: config.GrpcServiceName,
ConnHandler: func(conn net.Conn) {
sl.HandleConn(conn, tunnel, additions...)
},
HttpHandler: httpHandler,
})
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
}
for _, addr := range strings.Split(config.Listen, ",") {
addr := addr
//TCP
l, err := inbound.Listen("tcp", addr)
if err != nil {
return nil, err
}
if realityBuilder != nil {
l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig)
} else if !config.TrojanSSOption.Enabled {
return nil, errors.New("disallow using Trojan without both certificates/reality/ss config")
}
sl.listeners = append(sl.listeners, l)
go func() {
if httpHandler != nil {
_ = http.Serve(l, httpHandler)
return
}
for {
c, err := l.Accept()
if err != nil {
if sl.closed {
break
}
continue
}
go sl.HandleConn(c, tunnel, additions...)
}
}()
}
return sl, nil
}
func (l *Listener) Close() error {
l.closed = true
var retErr error
for _, lis := range l.listeners {
err := lis.Close()
if err != nil {
retErr = err
}
}
return retErr
}
func (l *Listener) Config() string {
return l.config.String()
}
func (l *Listener) AddrList() (addrList []net.Addr) {
for _, lis := range l.listeners {
addrList = append(addrList, lis.Addr())
}
return
}
func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
defer conn.Close()
if l.pickCipher != nil {
conn = l.pickCipher.StreamConn(conn)
}
var key [trojan.KeyLength]byte
if _, err := io.ReadFull(conn, key[:]); err != nil {
//log.Warnln("read key error: %s", err.Error())
return
}
if user, ok := l.keys[key]; ok {
additions = append(additions, inbound.WithInUser(user))
} else {
//log.Warnln("no such key")
return
}
var crlf [2]byte
if _, err := io.ReadFull(conn, crlf[:]); err != nil {
//log.Warnln("read crlf error: %s", err.Error())
return
}
l.handleConn(false, conn, tunnel, additions...)
}
func (l *Listener) handleConn(inMux bool, conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
if inMux {
defer conn.Close()
}
command, err := socks5.ReadByte(conn)
if err != nil {
//log.Warnln("read command error: %s", err.Error())
return
}
switch command {
case trojan.CommandTCP, trojan.CommandUDP, trojan.CommandMux:
default:
//log.Warnln("unknown command: %d", command)
return
}
target, err := socks5.ReadAddr0(conn)
if err != nil {
//log.Warnln("read target error: %s", err.Error())
return
}
if !inMux {
var crlf [2]byte
if _, err := io.ReadFull(conn, crlf[:]); err != nil {
//log.Warnln("read crlf error: %s", err.Error())
return
}
}
switch command {
case trojan.CommandTCP:
//tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.TROJAN, additions...))
l.handler.HandleSocket(target, conn, additions...)
case trojan.CommandUDP:
pc := trojan.NewPacketConn(conn)
for {
data, put, remoteAddr, err := pc.WaitReadFrom()
if err != nil {
if put != nil {
put()
}
break
}
cPacket := &packet{
pc: pc,
rAddr: remoteAddr,
payload: data,
put: put,
}
tunnel.HandleUDPPacket(inbound.NewPacket(target, cPacket, C.TROJAN, additions...))
}
case trojan.CommandMux:
if inMux {
//log.Warnln("invalid command: %d", command)
return
}
smuxConfig := smux.DefaultConfig()
smuxConfig.KeepAliveDisabled = true
session, err := smux.Server(conn, smuxConfig)
if err != nil {
//log.Warnln("smux server error: %s", err.Error())
return
}
defer session.Close()
for {
stream, err := session.AcceptStream()
if err != nil {
return
}
go l.handleConn(true, stream, tunnel, additions...)
}
}
}

View File

@@ -1,7 +1,6 @@
package tuic
import (
"context"
"crypto/tls"
"net"
"strings"
@@ -93,23 +92,14 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (
quicConfig.MaxDatagramFrameSize = int64(maxDatagramFrameSize)
handleTcpFn := func(conn net.Conn, addr socks5.Addr, _additions ...inbound.Addition) error {
newAdditions := additions
if len(_additions) > 0 {
newAdditions = slices.Clone(additions)
newAdditions = append(newAdditions, _additions...)
}
conn, metadata := inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
if h.IsSpecialFqdn(metadata.Host) {
go func() { // ParseSpecialFqdn will block, so open a new goroutine
_ = h.ParseSpecialFqdn(
sing.WithAdditions(context.Background(), newAdditions...),
conn,
sing.ConvertMetadata(metadata),
)
}()
return nil
}
go tunnel.HandleTCPConn(conn, metadata)
//newAdditions := additions
//if len(_additions) > 0 {
// newAdditions = slices.Clone(additions)
// newAdditions = append(newAdditions, _additions...)
//}
//conn, metadata := inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
//go tunnel.HandleTCPConn(conn, metadata)
go h.HandleSocket(addr, conn, _additions...) // h.HandleSocket will block, so open a new goroutine
return nil
}
handleUdpFn := func(addr socks5.Addr, packet C.UDPPacket, _additions ...inbound.Addition) error {

View File

@@ -22,19 +22,19 @@ type ClientConfig struct {
Password string
IdleSessionCheckInterval time.Duration
IdleSessionTimeout time.Duration
MinIdleSession int
Server M.Socksaddr
Dialer N.Dialer
TLSConfig *vmess.TLSConfig
}
type Client struct {
passwordSha256 []byte
tlsConfig *vmess.TLSConfig
clientFingerprint string
dialer N.Dialer
server M.Socksaddr
sessionClient *session.Client
padding atomic.TypedValue[*padding.PaddingFactory]
passwordSha256 []byte
tlsConfig *vmess.TLSConfig
dialer N.Dialer
server M.Socksaddr
sessionClient *session.Client
padding atomic.TypedValue[*padding.PaddingFactory]
}
func NewClient(ctx context.Context, config ClientConfig) *Client {
@@ -47,7 +47,7 @@ func NewClient(ctx context.Context, config ClientConfig) *Client {
}
// Initialize the padding state of this client
padding.UpdatePaddingScheme(padding.DefaultPaddingScheme, &c.padding)
c.sessionClient = session.NewClient(ctx, c.CreateOutboundTLSConnection, &c.padding, config.IdleSessionCheckInterval, config.IdleSessionTimeout)
c.sessionClient = session.NewClient(ctx, c.CreateOutboundTLSConnection, &c.padding, config.IdleSessionCheckInterval, config.IdleSessionTimeout, config.MinIdleSession)
return c
}

View File

@@ -19,22 +19,29 @@ type Client struct {
die context.Context
dieCancel context.CancelFunc
dialOut func(ctx context.Context) (net.Conn, error)
dialOut util.DialOutFunc
sessionCounter atomic.Uint64
sessionCounter atomic.Uint64
idleSession *skiplist.SkipList[uint64, *Session]
idleSessionLock sync.Mutex
sessions map[uint64]*Session
sessionsLock sync.Mutex
padding *atomic.TypedValue[*padding.PaddingFactory]
idleSessionTimeout time.Duration
minIdleSession int
}
func NewClient(ctx context.Context, dialOut func(ctx context.Context) (net.Conn, error), _padding *atomic.TypedValue[*padding.PaddingFactory], idleSessionCheckInterval, idleSessionTimeout time.Duration) *Client {
func NewClient(ctx context.Context, dialOut util.DialOutFunc, _padding *atomic.TypedValue[*padding.PaddingFactory], idleSessionCheckInterval, idleSessionTimeout time.Duration, minIdleSession int) *Client {
c := &Client{
sessions: make(map[uint64]*Session),
dialOut: dialOut,
padding: _padding,
idleSessionTimeout: idleSessionTimeout,
minIdleSession: minIdleSession,
}
if idleSessionCheckInterval <= time.Second*5 {
idleSessionCheckInterval = time.Second * 30
@@ -81,10 +88,16 @@ func (c *Client) CreateStream(ctx context.Context) (net.Conn, error) {
session.dieHook()
}
} else {
c.idleSessionLock.Lock()
session.idleSince = time.Now()
c.idleSession.Insert(math.MaxUint64-session.seq, session)
c.idleSessionLock.Unlock()
select {
case <-c.die.Done():
// Now client has been closed
go session.Close()
default:
c.idleSessionLock.Lock()
session.idleSince = time.Now()
c.idleSession.Insert(math.MaxUint64-session.seq, session)
c.idleSessionLock.Unlock()
}
}
}
@@ -122,14 +135,35 @@ func (c *Client) createSession(ctx context.Context) (*Session, error) {
c.idleSessionLock.Lock()
c.idleSession.Remove(math.MaxUint64 - session.seq)
c.idleSessionLock.Unlock()
c.sessionsLock.Lock()
delete(c.sessions, session.seq)
c.sessionsLock.Unlock()
}
c.sessionsLock.Lock()
c.sessions[session.seq] = session
c.sessionsLock.Unlock()
session.Run()
return session, nil
}
func (c *Client) Close() error {
c.dieCancel()
go c.idleCleanupExpTime(time.Now())
c.sessionsLock.Lock()
sessionToClose := make([]*Session, 0, len(c.sessions))
for seq, session := range c.sessions {
sessionToClose = append(sessionToClose, session)
delete(c.sessions, seq)
}
c.sessionsLock.Unlock()
for _, session := range sessionToClose {
session.Close()
}
return nil
}
@@ -138,17 +172,30 @@ func (c *Client) idleCleanup() {
}
func (c *Client) idleCleanupExpTime(expTime time.Time) {
var sessionToRemove = make([]*Session, 0)
sessionToRemove := make([]*Session, 0, c.idleSession.Len())
c.idleSessionLock.Lock()
it := c.idleSession.Iterate()
activeCount := 0
for it.IsNotEnd() {
session := it.Value()
if session.idleSince.Before(expTime) {
sessionToRemove = append(sessionToRemove, session)
c.idleSession.Remove(it.Key())
}
key := it.Key()
it.MoveToNext()
if !session.idleSince.Before(expTime) {
activeCount++
continue
}
if activeCount < c.minIdleSession {
session.idleSince = time.Now()
activeCount++
continue
}
sessionToRemove = append(sessionToRemove, session)
c.idleSession.Remove(key)
}
c.idleSessionLock.Unlock()

View File

@@ -0,0 +1,8 @@
package util
import (
"context"
"net"
)
type DialOutFunc func(ctx context.Context) (net.Conn, error)

View File

@@ -38,15 +38,17 @@ var defaultHeader = http.Header{
type DialFn = func(network, addr string) (net.Conn, error)
type Conn struct {
response *http.Response
request *http.Request
transport *TransportWrap
writer *io.PipeWriter
once sync.Once
close atomic.Bool
err error
remain int
br *bufio.Reader
initFn func() (io.ReadCloser, error)
writer io.Writer
flusher http.Flusher
netAddr
reader io.ReadCloser
once sync.Once
close atomic.Bool
err error
remain int
br *bufio.Reader
// deadlines
deadline *time.Timer
}
@@ -57,26 +59,32 @@ type Config struct {
ClientFingerprint string
}
func (g *Conn) initRequest() {
response, err := g.transport.RoundTrip(g.request)
func (g *Conn) initReader() {
reader, err := g.initFn()
if err != nil {
g.err = err
g.writer.Close()
if closer, ok := g.writer.(io.Closer); ok {
closer.Close()
}
return
}
if !g.close.Load() {
g.response = response
g.br = bufio.NewReader(response.Body)
g.reader = reader
g.br = bufio.NewReader(reader)
} else {
response.Body.Close()
reader.Close()
}
}
func (g *Conn) Init() error {
g.once.Do(g.initReader)
return g.err
}
func (g *Conn) Read(b []byte) (n int, err error) {
g.once.Do(g.initRequest)
if g.err != nil {
return 0, g.err
if err = g.Init(); err != nil {
return
}
if g.remain > 0 {
@@ -88,7 +96,7 @@ func (g *Conn) Read(b []byte) (n int, err error) {
n, err = io.ReadFull(g.br, b[:size])
g.remain -= n
return
} else if g.response == nil {
} else if g.reader == nil {
return 0, net.ErrClosed
}
@@ -139,6 +147,10 @@ func (g *Conn) Write(b []byte) (n int, err error) {
err = g.err
}
if g.flusher != nil {
g.flusher.Flush()
}
return len(b), err
}
@@ -158,6 +170,10 @@ func (g *Conn) WriteBuffer(buffer *buf.Buffer) error {
err = g.err
}
if g.flusher != nil {
g.flusher.Flush()
}
return err
}
@@ -167,15 +183,16 @@ func (g *Conn) FrontHeadroom() int {
func (g *Conn) Close() error {
g.close.Store(true)
if r := g.response; r != nil {
r.Body.Close()
if reader := g.reader; reader != nil {
reader.Close()
}
return g.writer.Close()
if closer, ok := g.writer.(io.Closer); ok {
return closer.Close()
}
return nil
}
func (g *Conn) LocalAddr() net.Addr { return g.transport.LocalAddr() }
func (g *Conn) RemoteAddr() net.Addr { return g.transport.RemoteAddr() }
func (g *Conn) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) }
func (g *Conn) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) }
@@ -200,6 +217,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
return nil, err
}
wrap.remoteAddr = pconn.RemoteAddr()
wrap.localAddr = pconn.LocalAddr()
if tlsConfig == nil {
return pconn, nil
@@ -286,13 +304,18 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
}
conn := &Conn{
request: request,
transport: transport,
writer: writer,
close: atomic.NewBool(false),
initFn: func() (io.ReadCloser, error) {
response, err := transport.RoundTrip(request)
if err != nil {
return nil, err
}
return response.Body, nil
},
writer: writer,
netAddr: transport.netAddr,
}
go conn.once.Do(conn.initRequest)
go conn.Init()
return conn, nil
}

View File

@@ -0,0 +1,117 @@
package gun
import (
"io"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/metacubex/mihomo/common/buf"
N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
const idleTimeout = 30 * time.Second
type ServerOption struct {
ServiceName string
ConnHandler func(conn net.Conn)
HttpHandler http.Handler
}
func NewServerHandler(options ServerOption) http.Handler {
path := "/" + options.ServiceName + "/Tun"
connHandler := options.ConnHandler
httpHandler := options.HttpHandler
if httpHandler == nil {
httpHandler = http.NewServeMux()
}
// using h2c.NewHandler to ensure we can work in plain http2
// and some tls conn is not *tls.Conn (like *reality.Conn)
return h2c.NewHandler(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if request.URL.Path == path &&
request.Method == http.MethodPost &&
strings.HasPrefix(request.Header.Get("Content-Type"), "application/grpc") {
writer.Header().Set("Content-Type", "application/grpc")
writer.Header().Set("TE", "trailers")
writer.WriteHeader(http.StatusOK)
conn := &Conn{
initFn: func() (io.ReadCloser, error) {
return request.Body, nil
},
writer: writer,
flusher: writer.(http.Flusher),
}
if request.RemoteAddr != "" {
metadata := C.Metadata{}
if err := metadata.SetRemoteAddress(request.RemoteAddr); err == nil {
conn.remoteAddr = net.TCPAddrFromAddrPort(metadata.AddrPort())
}
}
if addr, ok := request.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
conn.localAddr = addr
}
wrapper := &h2ConnWrapper{
// gun.Conn can't correct handle ReadDeadline
// so call N.NewDeadlineConn to add a safe wrapper
ExtendedConn: N.NewDeadlineConn(conn),
}
connHandler(wrapper)
wrapper.CloseWrapper()
return
}
httpHandler.ServeHTTP(writer, request)
}), &http2.Server{
IdleTimeout: idleTimeout,
})
}
// h2ConnWrapper used to avoid "panic: Write called after Handler finished" for gun.Conn
type h2ConnWrapper struct {
N.ExtendedConn
access sync.Mutex
closed bool
}
func (w *h2ConnWrapper) Write(p []byte) (n int, err error) {
w.access.Lock()
defer w.access.Unlock()
if w.closed {
return 0, net.ErrClosed
}
return w.ExtendedConn.Write(p)
}
func (w *h2ConnWrapper) WriteBuffer(buffer *buf.Buffer) error {
w.access.Lock()
defer w.access.Unlock()
if w.closed {
return net.ErrClosed
}
return w.ExtendedConn.WriteBuffer(buffer)
}
func (w *h2ConnWrapper) CloseWrapper() {
w.access.Lock()
defer w.access.Unlock()
w.closed = true
}
func (w *h2ConnWrapper) Close() error {
w.CloseWrapper()
return w.ExtendedConn.Close()
}
func (w *h2ConnWrapper) Upstream() any {
return w.ExtendedConn
}

View File

@@ -7,8 +7,7 @@ import (
type TransportWrap struct {
*http2.Transport
remoteAddr net.Addr
localAddr net.Addr
netAddr
}
func (tw *TransportWrap) RemoteAddr() net.Addr {
@@ -18,3 +17,16 @@ func (tw *TransportWrap) RemoteAddr() net.Addr {
func (tw *TransportWrap) LocalAddr() net.Addr {
return tw.localAddr
}
type netAddr struct {
remoteAddr net.Addr
localAddr net.Addr
}
func (addr *netAddr) RemoteAddr() net.Addr {
return addr.remoteAddr
}
func (addr *netAddr) LocalAddr() net.Addr {
return addr.localAddr
}

View File

@@ -38,10 +38,9 @@ type Command = byte
const (
CommandTCP byte = 1
CommandUDP byte = 3
CommandMux byte = 0x7f
// deprecated XTLS commands, as souvenirs
commandXRD byte = 0xf0 // XTLS direct mode
commandXRO byte = 0xf1 // XTLS origin mode
KeyLength = 56
)
type Option struct {
@@ -65,7 +64,7 @@ type WebsocketOption struct {
type Trojan struct {
option *Option
hexPassword []byte
hexPassword [KeyLength]byte
}
func (t *Trojan) StreamConn(ctx context.Context, conn net.Conn) (net.Conn, error) {
@@ -152,7 +151,7 @@ func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) er
buf := pool.GetBuffer()
defer pool.PutBuffer(buf)
buf.Write(t.hexPassword)
buf.Write(t.hexPassword[:])
buf.Write(crlf)
buf.WriteByte(command)
@@ -245,7 +244,7 @@ func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) {
}
func New(option *Option) *Trojan {
return &Trojan{option, hexSha224([]byte(option.Password))}
return &Trojan{option, Key(option.Password)}
}
var _ N.EnhancePacketConn = (*PacketConn)(nil)
@@ -340,9 +339,12 @@ func (pc *PacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, er
return
}
func hexSha224(data []byte) []byte {
buf := make([]byte, 56)
hash := sha256.Sum224(data)
hex.Encode(buf, hash[:])
return buf
func NewPacketConn(conn net.Conn) *PacketConn {
return &PacketConn{Conn: conn}
}
func Key(password string) (key [56]byte) {
hash := sha256.Sum224([]byte(password))
hex.Encode(key[:], hash[:])
return
}

View File

@@ -57,7 +57,7 @@ require (
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
github.com/metacubex/sing-tun v0.4.5 // indirect
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3 // indirect
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // indirect
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
github.com/metacubex/utls v1.6.6 // indirect
@@ -77,7 +77,7 @@ require (
github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.1 // indirect
github.com/sagernet/sing v0.5.2 // indirect
github.com/sagernet/sing-mux v0.2.1 // indirect
github.com/sagernet/sing-shadowtls v0.1.5 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
@@ -113,7 +113,7 @@ require (
lukechampine.com/blake3 v1.3.0 // indirect
)
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a
replace cfa => ../../main/golang

View File

@@ -111,8 +111,8 @@ github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiL
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 h1:aHsYiTvubfgMa3JMTDY//hDXVvFWrHg6ARckR52ttZs=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8=
github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000 h1:gUbMXcQXhXGj0vCpCVFTUyIH7TMpD1dpTcNv/MCS+ok=
github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a h1:xjPXdDTlIKq4U/KnKpoCtkxD03T8GimtQrvHy/3dN00=
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 h1:UkPoRAnoBQMn7IK5qpoIV3OejU15q+rqel3NrbSCFKA=
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
@@ -121,8 +121,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhD
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0=
github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3 h1:2kq6azIvsTjTnyw66xXDl5zMzIJqF7GTbvLpkroHssg=
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=

View File

@@ -11,7 +11,7 @@ require (
replace github.com/metacubex/mihomo => ../../foss/golang/clash
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a
require (
github.com/3andne/restls-client-go v0.1.6 // indirect
@@ -65,7 +65,7 @@ require (
github.com/metacubex/sing-shadowsocks v0.2.8 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect
github.com/metacubex/sing-tun v0.4.5 // indirect
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3 // indirect
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // indirect
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
github.com/metacubex/utls v1.6.6 // indirect
@@ -85,7 +85,7 @@ require (
github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing v0.5.1 // indirect
github.com/sagernet/sing v0.5.2 // indirect
github.com/sagernet/sing-mux v0.2.1 // indirect
github.com/sagernet/sing-shadowtls v0.1.5 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect

View File

@@ -112,8 +112,8 @@ github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiL
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 h1:aHsYiTvubfgMa3JMTDY//hDXVvFWrHg6ARckR52ttZs=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8=
github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000 h1:gUbMXcQXhXGj0vCpCVFTUyIH7TMpD1dpTcNv/MCS+ok=
github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a h1:xjPXdDTlIKq4U/KnKpoCtkxD03T8GimtQrvHy/3dN00=
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 h1:UkPoRAnoBQMn7IK5qpoIV3OejU15q+rqel3NrbSCFKA=
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
@@ -122,8 +122,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhD
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0=
github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3 h1:2kq6azIvsTjTnyw66xXDl5zMzIJqF7GTbvLpkroHssg=
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=

View File

@@ -1,17 +1,17 @@
[Unit]
Description=mihomo Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service
Documentation=https://wiki.metacubex.one
After=network.target nss-lookup.target network-online.target
[Service]
Type=simple
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
Restart=always
ExecStartPre=/usr/bin/sleep 2s
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target

17
clash-meta/.github/mihomo@.service vendored Normal file
View File

@@ -0,0 +1,17 @@
[Unit]
Description=mihomo Daemon, Another Clash Kernel.
Documentation=https://wiki.metacubex.one
After=network.target nss-lookup.target network-online.target
[Service]
Type=simple
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target

View File

@@ -276,17 +276,20 @@ jobs:
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/mihomo
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/
cp LICENSE mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo/
cp .github/mihomo.service mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
cp .github/{mihomo.service,mihomo@.service} mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system/
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo/config.yaml <<EOF
mixed-port: 7890
external-controller: 127.0.0.1:9090
EOF
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/conffiles <<EOF
/etc/mihomo/config.yaml
EOF
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF

View File

@@ -114,6 +114,9 @@ jobs:
- os: macos-latest
- os: windows-latest
fail-fast: false
if: >
github.event_name != 'pull_request' ||
contains(github.event.pull_request.title, 'crate')
runs-on: ${{ matrix.targets.os }}
needs: lint
steps:
@@ -175,77 +178,103 @@ jobs:
- name: Prepare sidecar and resources
run: pnpm check
- name: Build Tauri
run: pnpm tauri build
- name: Prepare frontend
run: pnpm -r build
env:
NODE_OPTIONS: '--max_old_space_size=4096'
# 以后完善了新的测试套件后再添加
# test_unit:
# name: Unit Test
# needs: lint
# # we want to run on the latest linux environment
# runs-on: ubuntu-latest
# # the steps our job runs **in order**
# steps:
# # checkout the code on the workflow runner
# - uses: actions/checkout@v4
- name: Build Backend
run: cargo build --release --manifest-path backend/Cargo.toml
# # install system dependencies that Tauri needs to compile on Linux.
# # note the extra dependencies for `tauri-driver` to run which are: `webkit2gtk-driver` and `xvfb`
# - name: Tauri dependencies
# run: >-
# sudo apt-get update &&
# sudo apt-get install -y
# libgtk-3-dev
# libayatana-appindicator3-dev
# libwebkit2gtk-4.0-dev
# webkit2gtk-driver
# xvfb
test_unit:
name: Unit Test
needs: lint
if: >
github.event_name != 'pull_request' ||
contains(github.event.pull_request.title, 'crate')
# # install the latest Rust stable
# - name: Rust stable
# run: rustup toolchain install stable --profile minimal && rustup default stable
# - uses: Swatinem/rust-cache@v2
# with:
# workspaces: "./backend/"
# prefix-key: "rust-stable"
# shared-key: "ci"
# save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }}
# we want to run on the latest linux environment
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
fail-fast: false
runs-on: ${{ matrix.os }}
# - name: Install Node.js
# uses: actions/setup-node@v4
# with:
# node-version: 20
# the steps our job runs **in order**
steps:
# checkout the code on the workflow runner
- uses: actions/checkout@v4
# - uses: pnpm/action-setup@v2
# name: Install pnpm
# with:
# version: 8
# run_install: false
# install system dependencies that Tauri needs to compile on Linux.
# note the extra dependencies for `tauri-driver` to run which are: `webkit2gtk-driver` and `xvfb`
- name: Tauri dependencies
if: startsWith(matrix.os, 'ubuntu-')
run: >-
sudo apt-get update &&
sudo apt-get install -y
libgtk-3-dev
libayatana-appindicator3-dev
libwebkit2gtk-4.1-dev
librsvg2-dev
libxdo-dev
webkit2gtk-driver
xvfb
# - name: Get pnpm store directory
# shell: bash
# run: |
# echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: maxim-lobanov/setup-xcode@v1
if: startsWith(matrix.os, 'macos-')
with:
xcode-version: 'latest-stable'
# - uses: actions/cache@v4
# name: Setup pnpm cache
# with:
# path: ${{ env.STORE_PATH }}
# key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
# restore-keys: |
# ${{ runner.os }}-pnpm-store-
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
# - name: Install dependencies
# run: pnpm install
# - name: Prepare fronend
# run: pnpm web:build # Build frontend
# - name: Prepare sidecar and resources
# run: pnpm check
# - name: Test
# # run: pnpm test:unit && pnpm test:backend
# run: pnpm test:backend
- uses: actions/cache@v4
name: Cache Rust dependencies
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Prepare sidecar and resources
run: pnpm check
- name: Prepare frontend
run: pnpm -r build
env:
NODE_OPTIONS: '--max_old_space_size=4096'
- name: Test
run: pnpm test
# test_e2e:
# # the display name of the test job

View File

@@ -41,7 +41,7 @@ on:
jobs:
build:
runs-on: 'macos-13'
runs-on: macos-latest
steps:
- name: Checkout repository
@@ -49,7 +49,7 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 14
xcode-version: 15
- name: install Rust stable
run: |
@@ -69,6 +69,9 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: latest
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -114,15 +117,12 @@ jobs:
NODE_OPTIONS: '--max_old_space_size=4096'
- name: Upload to release
shell: bash
run: |
find ./backend/target \( -name "*.dmg" -o -name "*.sig" -o -name "*.tar.gz" \) | while read file; do
echo "Uploading $file to release"
gh release upload ${{ inputs.tag }} "$file" --clobber
done
deno run -A scripts/deno/upload-macos-updater.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TAG: ${{ inputs.tag }}
TARGET_ARCH: ${{ inputs.aarch64 == true && 'aarch64' || 'x86_64' }}
- name: Upload to Github Artifact
uses: actions/upload-artifact@v4

View File

@@ -767,9 +767,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
version = "0.1.86"
version = "0.1.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97"
dependencies = [
"proc-macro2",
"quote",
@@ -810,6 +810,15 @@ dependencies = [
"system-deps",
]
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@@ -884,7 +893,7 @@ version = "0.5.0"
source = "git+https://github.com/libnyanpasu/auto-launch.git#729d5429dd689067047489af4a0a32f7013854c8"
dependencies = [
"dirs 5.0.1",
"thiserror 2.0.11",
"thiserror 2.0.12",
"windows-registry 0.3.0",
"windows-result 0.2.0",
]
@@ -1297,7 +1306,7 @@ dependencies = [
"static_assertions",
"tap",
"thin-vec",
"thiserror 2.0.11",
"thiserror 2.0.12",
"time",
]
@@ -1384,6 +1393,8 @@ dependencies = [
name = "boa_utils"
version = "0.1.0"
dependencies = [
"anyhow",
"async-fs",
"boa_engine",
"boa_gc",
"boa_parser",
@@ -1392,8 +1403,13 @@ dependencies = [
"indoc",
"isahc",
"log",
"mime",
"postcard",
"rustc-hash 2.1.1",
"serde",
"serde_json",
"smol",
"tempfile",
"test-log",
"textwrap",
"tracing",
@@ -1631,7 +1647,7 @@ dependencies = [
"semver 1.0.25",
"serde",
"serde_json",
"thiserror 2.0.11",
"thiserror 2.0.12",
]
[[package]]
@@ -1910,7 +1926,7 @@ dependencies = [
"chrono",
"clap",
"colored",
"convert_case 0.7.1",
"convert_case 0.8.0",
"ctrlc",
"dashmap 6.1.0",
"deelevate",
@@ -1999,7 +2015,7 @@ dependencies = [
"tauri-specta",
"tempfile",
"test-log",
"thiserror 2.0.11",
"thiserror 2.0.12",
"time",
"timeago",
"tokio",
@@ -2018,7 +2034,7 @@ dependencies = [
"webview2-com",
"which 7.0.2",
"whoami",
"window-vibrancy 0.5.3",
"window-vibrancy",
"windows-core 0.60.1",
"windows-registry 0.5.0",
"windows-sys 0.59.0",
@@ -2036,6 +2052,12 @@ dependencies = [
"error-code",
]
[[package]]
name = "cobs"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@@ -2130,9 +2152,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "convert_case"
version = "0.7.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
dependencies = [
"unicode-segmentation",
]
@@ -2279,6 +2301,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "cron_clock"
version = "0.8.0"
@@ -2743,7 +2771,7 @@ version = "0.1.0"
source = "git+https://github.com/libnyanpasu/nyanpasu-utils.git#757e46139a3710fb26a89724e24cdb7f2fe83885"
dependencies = [
"dirs 6.0.0",
"thiserror 2.0.11",
"thiserror 2.0.12",
"tracing",
"windows 0.59.0",
]
@@ -2769,7 +2797,7 @@ dependencies = [
"objc2-foundation 0.3.0",
"scopeguard",
"smithay-client-toolkit",
"thiserror 2.0.11",
"thiserror 2.0.12",
"widestring 1.1.0",
"windows 0.59.0",
"xcb",
@@ -3086,6 +3114,18 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "ena"
version = "0.14.3"
@@ -4057,7 +4097,7 @@ dependencies = [
"objc2-app-kit 0.3.0",
"once_cell",
"serde",
"thiserror 2.0.11",
"thiserror 2.0.12",
"windows-sys 0.59.0",
"x11-dl",
]
@@ -4331,6 +4371,15 @@ dependencies = [
"serde_json",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "hash32"
version = "0.3.1"
@@ -4370,13 +4419,27 @@ dependencies = [
"foldhash",
]
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32 0.2.1",
"rustc_version",
"serde",
"spin",
"stable_deref_trait",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32",
"hash32 0.3.1",
"stable_deref_trait",
]
@@ -5938,7 +6001,7 @@ dependencies = [
"once_cell",
"png",
"serde",
"thiserror 2.0.11",
"thiserror 2.0.12",
"windows-sys 0.59.0",
]
@@ -5977,7 +6040,7 @@ dependencies = [
"spirv",
"strum 0.26.3",
"termcolor",
"thiserror 2.0.11",
"thiserror 2.0.12",
"unicode-xid",
]
@@ -6443,7 +6506,7 @@ dependencies = [
"serde_json",
"simd-json",
"specta",
"thiserror 2.0.11",
"thiserror 2.0.12",
"tokio",
"tokio-util",
"tracing",
@@ -6478,7 +6541,7 @@ dependencies = [
"shared_child",
"specta",
"sysinfo",
"thiserror 2.0.11",
"thiserror 2.0.12",
"tokio",
"tracing",
"tracing-attributes",
@@ -7096,7 +7159,7 @@ dependencies = [
"objc2-osa-kit",
"serde",
"serde_json",
"thiserror 2.0.11",
"thiserror 2.0.12",
]
[[package]]
@@ -7426,7 +7489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
dependencies = [
"memchr",
"thiserror 2.0.11",
"thiserror 2.0.12",
"ucd-trie",
]
@@ -7760,6 +7823,19 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "postcard"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"heapless 0.7.17",
"serde",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -7861,9 +7937,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.93"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
@@ -8011,7 +8087,7 @@ dependencies = [
"rustc-hash 2.1.1",
"rustls",
"socket2",
"thiserror 2.0.11",
"thiserror 2.0.12",
"tokio",
"tracing",
]
@@ -8030,7 +8106,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.11",
"thiserror 2.0.12",
"tinyvec",
"tracing",
"web-time",
@@ -8333,7 +8409,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [
"getrandom 0.2.15",
"libredox",
"thiserror 2.0.11",
"thiserror 2.0.12",
]
[[package]]
@@ -8704,7 +8780,7 @@ version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb"
dependencies = [
"heapless",
"heapless 0.8.0",
"num-traits",
"smallvec",
]
@@ -9460,7 +9536,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
dependencies = [
"num-bigint",
"num-traits",
"thiserror 2.0.11",
"thiserror 2.0.12",
"time",
]
@@ -9754,6 +9830,9 @@ name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "spirv"
@@ -10315,14 +10394,14 @@ dependencies = [
"tauri-runtime",
"tauri-runtime-wry",
"tauri-utils",
"thiserror 2.0.11",
"thiserror 2.0.12",
"tokio",
"tray-icon",
"url",
"urlpattern",
"webkit2gtk",
"webview2-com",
"window-vibrancy 0.6.0",
"window-vibrancy",
"windows 0.60.0",
]
@@ -10368,7 +10447,7 @@ dependencies = [
"sha2 0.10.8",
"syn 2.0.98",
"tauri-utils",
"thiserror 2.0.11",
"thiserror 2.0.12",
"time",
"url",
"uuid",
@@ -10418,7 +10497,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-plugin",
"thiserror 2.0.11",
"thiserror 2.0.12",
]
[[package]]
@@ -10450,7 +10529,7 @@ dependencies = [
"tauri",
"tauri-plugin",
"tauri-plugin-fs",
"thiserror 2.0.11",
"thiserror 2.0.12",
"url",
]
@@ -10471,7 +10550,7 @@ dependencies = [
"tauri",
"tauri-plugin",
"tauri-utils",
"thiserror 2.0.11",
"thiserror 2.0.12",
"toml",
"url",
"uuid",
@@ -10489,7 +10568,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-plugin",
"thiserror 2.0.11",
"thiserror 2.0.12",
]
[[package]]
@@ -10506,7 +10585,7 @@ dependencies = [
"serde_repr",
"tauri",
"tauri-plugin",
"thiserror 2.0.11",
"thiserror 2.0.12",
"time",
"url",
]
@@ -10526,7 +10605,7 @@ dependencies = [
"sys-locale",
"tauri",
"tauri-plugin",
"thiserror 2.0.11",
"thiserror 2.0.12",
]
[[package]]
@@ -10556,7 +10635,7 @@ dependencies = [
"shared_child",
"tauri",
"tauri-plugin",
"thiserror 2.0.11",
"thiserror 2.0.12",
"tokio",
]
@@ -10583,7 +10662,7 @@ dependencies = [
"tauri",
"tauri-plugin",
"tempfile",
"thiserror 2.0.11",
"thiserror 2.0.12",
"time",
"tokio",
"url",
@@ -10605,7 +10684,7 @@ dependencies = [
"serde",
"serde_json",
"tauri-utils",
"thiserror 2.0.11",
"thiserror 2.0.12",
"url",
"windows 0.60.0",
]
@@ -10650,7 +10729,7 @@ dependencies = [
"specta-typescript",
"tauri",
"tauri-specta-macros",
"thiserror 2.0.11",
"thiserror 2.0.12",
]
[[package]]
@@ -10694,7 +10773,7 @@ dependencies = [
"serde_json",
"serde_with",
"swift-rs",
"thiserror 2.0.11",
"thiserror 2.0.12",
"toml",
"url",
"urlpattern",
@@ -10878,11 +10957,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.11"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.11",
"thiserror-impl 2.0.12",
]
[[package]]
@@ -10898,9 +10977,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.11"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
@@ -11348,7 +11427,7 @@ dependencies = [
"once_cell",
"png",
"serde",
"thiserror 2.0.11",
"thiserror 2.0.12",
"windows-sys 0.59.0",
]
@@ -11429,7 +11508,7 @@ dependencies = [
"log",
"rand 0.9.0",
"sha1",
"thiserror 2.0.11",
"thiserror 2.0.12",
"utf-8",
]
@@ -12269,7 +12348,7 @@ version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb27fccd3c27f68e9a6af1bcf48c2d82534b8675b83608a4d81446d095a17ac"
dependencies = [
"thiserror 2.0.11",
"thiserror 2.0.12",
"windows 0.60.0",
"windows-core 0.60.1",
]
@@ -12325,7 +12404,7 @@ dependencies = [
"raw-window-handle",
"rustc-hash 1.1.0",
"smallvec",
"thiserror 2.0.11",
"thiserror 2.0.12",
"wgpu-hal",
"wgpu-types",
]
@@ -12364,7 +12443,7 @@ dependencies = [
"renderdoc-sys",
"rustc-hash 1.1.0",
"smallvec",
"thiserror 2.0.11",
"thiserror 2.0.12",
"wasm-bindgen",
"web-sys",
"wgpu-types",
@@ -12461,20 +12540,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "window-vibrancy"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831ad7678290beae36be6f9fad9234139c7f00f3b536347de7745621716be82d"
dependencies = [
"objc2 0.5.2",
"objc2-app-kit 0.2.2",
"objc2-foundation 0.2.2",
"raw-window-handle",
"windows-sys 0.59.0",
"windows-version",
]
[[package]]
name = "window-vibrancy"
version = "0.6.0"
@@ -13282,7 +13347,7 @@ dependencies = [
"sha2 0.10.8",
"soup3",
"tao-macros",
"thiserror 2.0.11",
"thiserror 2.0.12",
"url",
"webkit2gtk",
"webkit2gtk-sys",
@@ -13757,7 +13822,7 @@ dependencies = [
"pbkdf2",
"rand 0.8.5",
"sha1",
"thiserror 2.0.11",
"thiserror 2.0.12",
"time",
"zeroize",
"zopfli",

View File

@@ -11,7 +11,7 @@ authors = ["zzzgydi", "keiko233"]
[workspace.dependencies]
thiserror = "2"
tracing = "0.1"
boa_engine = { version = "0.20" }
boa_engine = { version = "0.20", features = ["annex-b"] }
[profile.release]
panic = "unwind"

View File

@@ -6,11 +6,14 @@ edition.workspace = true
license.workspace = true
authors.workspace = true
[lib]
doctest = false
[dependencies]
rustc-hash = { version = "2", features = ["std"] }
boa_engine.workspace = true
boa_engine = { workspace = true, features = ["annex-b"] }
boa_gc = { version = "0.20" }
boa_parser = { version = "0.20" }
boa_parser = { version = "0.20", features = ["annex-b"] }
isahc = "1.7"
futures-util = "0.3"
futures-concurrency = "7"
@@ -18,8 +21,19 @@ smol = "2"
tracing = "0.1"
url = "2"
log = "0.4"
anyhow = "1.0"
# for cacheing
mime = "0.3.17"
async-fs = "2.1.2"
# for encoding/decoding
serde = { version = "1.0", features = ["derive"] }
postcard = { version = "1.1.1", features = ["use-std"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
[dev-dependencies]
indoc = "2"
textwrap = "0.16"
test-log = "0.2"
tempfile = "3.17"

View File

@@ -15,20 +15,14 @@
mod tests;
use boa_engine::{
Context, JsArgs, JsData, JsResult, JsStr, JsString, js_str, js_string,
Context, JsArgs, JsData, JsError, JsResult, JsStr, JsString, js_str, js_string,
native_function::NativeFunction,
object::{JsObject, ObjectInitializer},
value::{JsValue, Numeric},
};
use boa_gc::{Finalize, Trace};
use rustc_hash::FxHashMap;
use std::{
cell::RefCell,
collections::hash_map::Entry,
rc::Rc,
sync::{Arc, Mutex, OnceLock},
time::SystemTime,
};
use std::{cell::RefCell, collections::hash_map::Entry, rc::Rc, sync::Arc, time::SystemTime};
/// This represents the different types of log messages.
#[derive(Debug)]
@@ -54,38 +48,37 @@ fn logger(msg: LogMessage, console_state: &Console) {
}
pub trait Logger {
fn log(&self, msg: LogMessage, console_state: &Console);
type Item;
fn log(&mut self, msg: LogMessage, console_state: &Console);
fn take(&mut self) -> Vec<Self::Item>;
}
trait LoggerBox = Logger + Sync + Send + 'static;
pub trait LoggerBox = Logger<Item = LogMessage> + Sync + Send + 'static;
struct ConsoleLogger;
impl Logger for ConsoleLogger {
fn log(&self, msg: LogMessage, console_state: &Console) {
type Item = LogMessage;
fn log(&mut self, msg: LogMessage, console_state: &Console) {
logger(msg, console_state);
}
}
pub static LOGGER: OnceLock<Mutex<Arc<dyn LoggerBox>>> = OnceLock::new();
fn default_logger() -> Mutex<Arc<dyn LoggerBox>> {
Mutex::new(Arc::new(ConsoleLogger))
}
impl Logger for OnceLock<Mutex<Arc<dyn LoggerBox>>> {
fn log(&self, msg: LogMessage, console_state: &Console) {
let guard = self.get_or_init(default_logger);
guard
.lock()
.expect("failed to lock")
.log(msg, console_state);
fn take(&mut self) -> Vec<Self::Item> {
vec![]
}
}
pub fn set_logger(logger: Arc<dyn LoggerBox>) {
let guard = LOGGER.get_or_init(default_logger);
*guard.lock().expect("failed to lock") = logger;
thread_local! {
static LOGGER: RefCell<Box<dyn LoggerBox>> = RefCell::new(Box::new(ConsoleLogger));
}
pub fn inspect_logger<R>(f: impl FnOnce(&mut dyn LoggerBox) -> R) -> R {
LOGGER.with(|cell| f(cell.borrow_mut().as_mut()))
}
pub fn set_logger(logger: Box<dyn LoggerBox>) {
LOGGER.with(|cell| {
*cell.borrow_mut() = logger;
});
}
/// This represents the `console` formatter.
@@ -323,7 +316,12 @@ impl Console {
args[0] = JsValue::new(concat);
}
LOGGER.log(LogMessage::Error(formatter(&args, context)?), console);
LOGGER.with(|logger| {
logger
.borrow_mut()
.log(LogMessage::Error(formatter(&args, context)?), console);
Ok::<_, JsError>(())
})?;
}
Ok(JsValue::undefined())
@@ -361,7 +359,12 @@ impl Console {
console: &Self,
context: &mut Context,
) -> JsResult<JsValue> {
LOGGER.log(LogMessage::Log(formatter(args, context)?), console);
LOGGER.with(|logger| {
logger
.borrow_mut()
.log(LogMessage::Log(formatter(args, context)?), console);
Ok::<_, JsError>(())
})?;
Ok(JsValue::undefined())
}
@@ -381,7 +384,12 @@ impl Console {
console: &Self,
context: &mut Context,
) -> JsResult<JsValue> {
LOGGER.log(LogMessage::Error(formatter(args, context)?), console);
LOGGER.with(|logger| {
logger
.borrow_mut()
.log(LogMessage::Error(formatter(args, context)?), console);
Ok::<_, JsError>(())
})?;
Ok(JsValue::undefined())
}
@@ -401,7 +409,12 @@ impl Console {
console: &Self,
context: &mut Context,
) -> JsResult<JsValue> {
LOGGER.log(LogMessage::Info(formatter(args, context)?), console);
LOGGER.with(|logger| {
logger
.borrow_mut()
.log(LogMessage::Info(formatter(args, context)?), console);
Ok::<_, JsError>(())
})?;
Ok(JsValue::undefined())
}
@@ -421,7 +434,12 @@ impl Console {
console: &Self,
context: &mut Context,
) -> JsResult<JsValue> {
LOGGER.log(LogMessage::Log(formatter(args, context)?), console);
LOGGER.with(|logger| {
logger
.borrow_mut()
.log(LogMessage::Log(formatter(args, context)?), console);
Ok::<_, JsError>(())
})?;
Ok(JsValue::undefined())
}
@@ -442,7 +460,12 @@ impl Console {
context: &mut Context,
) -> JsResult<JsValue> {
if !args.is_empty() {
LOGGER.log(LogMessage::Log(formatter(args, context)?), console);
LOGGER.with(|logger| {
logger
.borrow_mut()
.log(LogMessage::Log(formatter(args, context)?), console);
Ok::<_, JsError>(())
})?;
}
let stack_trace_dump = context
@@ -453,7 +476,12 @@ impl Console {
.map(JsString::to_std_string_escaped)
.collect::<Vec<_>>()
.join("\n");
LOGGER.log(LogMessage::Log(stack_trace_dump), console);
LOGGER.with(|logger| {
logger
.borrow_mut()
.log(LogMessage::Log(stack_trace_dump), console);
Ok::<_, JsError>(())
})?;
Ok(JsValue::undefined())
}
@@ -474,7 +502,12 @@ impl Console {
console: &Self,
context: &mut Context,
) -> JsResult<JsValue> {
LOGGER.log(LogMessage::Warn(formatter(args, context)?), console);
LOGGER.with(|logger| {
logger
.borrow_mut()
.log(LogMessage::Warn(formatter(args, context)?), console);
Ok::<_, JsError>(())
})?;
Ok(JsValue::undefined())
}
@@ -503,7 +536,12 @@ impl Console {
let c = console.count_map.entry(label).or_insert(0);
*c += 1;
LOGGER.log(LogMessage::Info(format!("{msg} {c}")), console);
let msg = format!("{msg} {c}");
LOGGER.with(|logger| {
logger.borrow_mut().log(LogMessage::Info(msg), console);
Ok::<_, JsError>(())
})?;
Ok(JsValue::undefined())
}
@@ -620,7 +658,9 @@ impl Console {
for msg in args.iter().skip(1) {
concat = concat + " " + &msg.display().to_string();
}
LOGGER.log(LogMessage::Log(concat), console);
LOGGER.with(|logger| {
logger.borrow_mut().log(LogMessage::Log(concat), console);
});
},
);
@@ -692,7 +732,12 @@ impl Console {
) -> JsResult<JsValue> {
let group_label = formatter(args, context)?;
LOGGER.log(LogMessage::Info(format!("group: {group_label}")), console);
LOGGER.with(|logger| {
logger
.borrow_mut()
.log(LogMessage::Info(format!("group: {group_label}")), console);
Ok::<_, JsError>(())
})?;
console.groups.push(group_label);
Ok(JsValue::undefined())

View File

@@ -1,6 +1,7 @@
#![feature(trait_alias)]
#![feature(auto_traits)]
#![feature(negative_impls)]
#![feature(let_chains)]
//! Boa's **boa_runtime** crate contains an example runtime and basic runtime features and
//! functionality for the `boa_engine` crate for runtime implementors.
@@ -9,7 +10,7 @@
//!
//! 1. Add **boa_runtime** as a dependency to your project along with **boa_engine**.
//!
//! ```
//! ```no_run
//! use boa_engine::{js_string, property::Attribute, Context, Source};
//! use boa_runtime::Console;
//!
@@ -58,7 +59,7 @@ mod console;
pub mod module;
#[doc(inline)]
pub use console::Console;
pub use console::{LogMessage, Logger, set_logger};
pub use console::{LogMessage, Logger, LoggerBox, inspect_logger, set_logger};
#[cfg(test)]
pub(crate) mod test {

View File

@@ -1,14 +1,15 @@
use std::{
cell::{Cell, RefCell},
collections::VecDeque,
rc::Rc,
path::PathBuf,
str::FromStr,
time::{Duration, SystemTime},
};
use async_fs::create_dir_all;
use boa_engine::{
Context, JsNativeError, JsResult, JsString, JsValue, Module,
builtins::promise::PromiseState,
job::{FutureJob, JobQueue, NativeJob},
js_string,
module::ModuleLoader,
};
use boa_parser::Source;
@@ -17,13 +18,94 @@ use isahc::{
AsyncReadResponseExt, Request, RequestExt,
config::{Configurable, RedirectPolicy},
};
use mime::Mime;
use smol::{LocalExecutor, future};
use url::Url;
// Most of the boilerplate is taken from the `futures.rs` example.
// This file only explains what is exclusive of async module loading.
#[derive(Debug, Default)]
pub struct HttpModuleLoader;
pub struct HttpModuleLoader {
cache_dir: PathBuf,
max_age: Duration,
}
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct CachedItem {
pub mime: String,
/// raw string content
/// We have no plan for now to support binary content,
/// so we just use `String` to store the content.
pub content: String,
}
impl HttpModuleLoader {
pub fn new(cache_dir: PathBuf, max_age: Duration) -> Self {
Self { cache_dir, max_age }
}
fn mapping_cache_dir(&self, url: &url::Url) -> PathBuf {
let mut buf = self.cache_dir.clone();
let host = match url.host() {
Some(host) => host.to_string().replace('.', "--"),
None => "unknown".to_string(),
};
let port = match url.port() {
Some(port) => format!("__{port}"),
None => "".to_string(),
};
buf.push(format!("{}_{}{}", url.scheme(), host, port));
buf.push(url.path().replace('/', "_").replace(".", "--"));
buf
}
#[tracing::instrument(skip(finish_load, context))]
fn handle_cached_item(
item: CachedItem,
finish_load: Box<dyn FnOnce(JsResult<Module>, &mut Context)>,
context: &mut Context,
) {
let Ok(mime) = Mime::from_str(item.mime.as_str()) else {
log::error!("failed to parse mime type `{}`", item.mime);
finish_load(
Err(JsNativeError::typ()
.with_message("failed to parse mime type")
.into()),
context,
);
return;
};
let source_str = match (mime.type_(), mime.subtype()) {
(mime::APPLICATION, mime::JAVASCRIPT) => item.content.clone(),
(mime::APPLICATION, mime::JSON) => {
format!("export default {};", item.content)
}
_ => {
let Ok(escaped_str) = serde_json::to_string(&item.content) else {
log::error!("failed to serialize content.");
finish_load(
Err(JsNativeError::typ()
.with_message("failed to serialize content")
.into()),
context,
);
return;
};
format!("export const text = {escaped_str};")
}
};
// Could also add a path if needed.
let source = Source::from_bytes(source_str.as_bytes());
let module = Module::parse(source, None, context);
// TODO: rm cache or create cache after judge module is ok
// We don't do any error handling, `finish_load` takes care of that for us.
finish_load(module, context);
}
}
impl ModuleLoader for HttpModuleLoader {
fn load_imported_module(
@@ -34,30 +116,119 @@ impl ModuleLoader for HttpModuleLoader {
context: &mut Context,
) {
let url = specifier.to_std_string_escaped();
let url = Url::from_str(&url).expect("invalid url"); // SAFETY: `url` is a valid URL, if it's not, its caller side issue
let cache_path = self.mapping_cache_dir(&url);
let parent_dir = match cache_path.parent() {
Some(parent) => parent.to_path_buf(),
None => {
log::error!("failed to get parent directory for `{url}`");
finish_load(
Err(JsNativeError::typ()
.with_message(format!(
"failed to get cache parent directory for `{url}`; path: `{}`",
cache_path.display()
))
.into()),
context,
);
return;
}
};
let max_age = self.max_age;
let fetch = async move {
log::debug!("checking cache for `{url}`...");
let now = SystemTime::now();
let should_use_cached_content = match async_fs::metadata(&cache_path).await {
Ok(metadata)
if metadata
.modified()
.is_ok_and(|modified| modified > now - max_age) =>
{
true
}
Err(err) => {
// create dir if not exists
if err.kind() == std::io::ErrorKind::NotFound
&& let Err(e) = async_fs::metadata(&parent_dir).await
&& e.kind() == std::io::ErrorKind::NotFound
{
if let Err(err) = create_dir_all(parent_dir).await {
log::error!(
"failed to create cache directory for `{url}`; path: `{}`. error: `{}`",
cache_path.display(),
err
);
}
}
false
}
_ => false,
};
// Adding some prints to show the non-deterministic nature of the async fetches.
// Try to run the example several times to see how sometimes the fetches start in order
// but finish in disorder.
log::debug!("fetching `{url}`...");
// This could also retry fetching in case there's an error while requesting the module.
let body: Result<_, isahc::Error> = async {
let mut response = Request::get(&url)
.redirect_policy(RedirectPolicy::Limit(5))
.body(())?
.send_async()
.await?;
Ok(response.text().await?)
// This could also retry fetching in case there's an error while requesting the module.
let item: anyhow::Result<CachedItem> = if should_use_cached_content {
async {
log::debug!("fetching `{url}` from cache...");
let item = async_fs::read(&cache_path).await?;
let item = postcard::from_bytes(&item)?;
log::debug!("finished fetching `{url}` from cache");
Ok(item)
}
.await
} else {
async {
log::debug!("fetching `{url}`...");
let mut response = Request::get(url.as_str())
.redirect_policy(RedirectPolicy::Limit(5))
.body(())?
.send_async()
.await?;
let mime = response
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.map(|v| v.to_string())
.unwrap_or(mime::TEXT_PLAIN.to_string());
let body = response.text().await?;
log::debug!("finished fetching `{url}`");
Ok(CachedItem {
mime,
content: body,
})
}
.await
};
if let Ok(item) = &item {
match postcard::to_stdvec(&item) {
Ok(item) => {
if let Err(err) = async_fs::write(&cache_path, &item).await {
log::error!(
"failed to write cache for `{url}`; path: `{}`. error: `{}`",
cache_path.display(),
err
);
}
}
Err(err) => {
log::error!("failed to serialize content: {err}");
}
}
}
.await;
log::debug!("finished fetching `{url}`");
// Since the async context cannot take the `context` by ref, we have to continue
// parsing inside a new `NativeJob` that will be enqueued into the promise job queue.
NativeJob::new(move |context| -> JsResult<JsValue> {
let body = match body {
Ok(body) => body,
let item = match item {
Ok(item) => item,
Err(err) => {
// On error we always call `finish_load` to notify the load promise about the
// error.
@@ -70,15 +241,7 @@ impl ModuleLoader for HttpModuleLoader {
return Ok(JsValue::undefined());
}
};
// Could also add a path if needed.
let source = Source::from_bytes(body.as_bytes());
let module = Module::parse(source, None, context);
// We don't do any error handling, `finish_load` takes care of that for us.
finish_load(module, context);
Self::handle_cached_item(item, finish_load, context);
// Also needed to match `NativeJob::new`.
Ok(JsValue::undefined())
})
@@ -94,6 +257,9 @@ impl ModuleLoader for HttpModuleLoader {
#[test]
fn test_http_module_loader() -> JsResult<()> {
use boa_engine::{builtins::promise::PromiseState, js_string};
use std::rc::Rc;
let temp_dir = tempfile::tempdir().unwrap();
// A simple snippet that imports modules from the web instead of the file system.
const SRC: &str = r#"
import YAML from 'https://esm.run/yaml@2.3.4';
@@ -120,7 +286,10 @@ fn test_http_module_loader() -> JsResult<()> {
let context = &mut Context::builder()
.job_queue(queue)
// NEW: sets the context module loader to our custom loader
.module_loader(Rc::new(HttpModuleLoader))
.module_loader(Rc::new(HttpModuleLoader::new(
temp_dir.path().to_path_buf(),
Duration::from_secs(10),
)))
.build()?;
let module = Module::parse(Source::from_bytes(SRC.as_bytes()), None, context)?;

View File

@@ -10,6 +10,9 @@ license = "MIT OR Apache-2.0"
readme = "README.md"
include = ["src/**", "Cargo.toml", "LICENSE_*"]
[lib]
doctest = false
[dependencies]
dirs = "6"
log = "0.4"

View File

@@ -12,6 +12,7 @@ build = "build.rs"
[lib]
name = "clash_nyanpasu_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
doctest = false
[build-dependencies]
tauri-build = { version = "2.0.1", features = [] }
@@ -42,7 +43,7 @@ futures-util = "0.3"
glob = "0.3.1"
timeago = "0.4"
humansize = "2.1.3"
convert_case = "0.7.0"
convert_case = "0.8.0"
anyhow = "1.0"
pretty_assertions = "1.4.0"
chrono = "0.4.31"
@@ -182,8 +183,8 @@ mlua = { version = "0.10", features = [
] }
# JavaScript Integration
boa_utils = { path = "../boa_utils" } # should be removed when boa support console customize
boa_engine.workspace = true
boa_utils = { path = "../boa_utils" } # should be removed when boa support console customize
boa_engine = { workspace = true, features = ["annex-b"] }
# Tauri Dependencies
tauri = { version = "2.2", features = ["tray-icon", "image-png", "image-ico"] }
@@ -196,7 +197,7 @@ tauri-plugin-process = "2.2"
tauri-plugin-updater = "2.2"
tauri-plugin-shell = "2.2"
tauri-plugin-notification = "2.2"
window-vibrancy = { version = "0.5.2" }
window-vibrancy = { version = "0.6.0" }
# Strong typed api binding between typescript and rust
specta-typescript = "0.0.9"

View File

@@ -1,12 +1,12 @@
use super::runner::{ProcessOutput, Runner, wrap_result};
use crate::enhance::utils::{Logs, LogsExt, take_logs};
use crate::enhance::utils::{LogSpan, Logs, LogsExt, take_logs};
use anyhow::Context as _;
use async_trait::async_trait;
use boa_engine::{
Context, JsError, JsNativeError, JsValue, Source,
builtins::promise::PromiseState,
js_string,
module::{Module, ModuleLoader as BoaModuleLoader, SimpleModuleLoader},
module::{Module, SimpleModuleLoader},
property::Attribute,
};
use boa_utils::{
@@ -17,13 +17,12 @@ use boa_utils::{
},
};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use serde_yaml::Mapping;
use std::{
cell::RefCell,
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
time::Duration,
};
use tracing_attributes::instrument;
use utils::wrap_script_if_not_esm;
@@ -55,16 +54,49 @@ pub enum JsRunnerError {
Other(String),
}
pub struct BoaConsoleLogger(Arc<Mutex<Option<Logs>>>);
pub struct BoaConsoleLogger(Logs);
impl boa_utils::Logger for BoaConsoleLogger {
fn log(&self, msg: boa_utils::LogMessage, _: &Console) {
type Item = boa_utils::LogMessage;
fn log(&mut self, msg: boa_utils::LogMessage, _: &Console) {
match msg {
boa_utils::LogMessage::Log(msg) => self.0.lock().as_mut().unwrap().log(msg),
boa_utils::LogMessage::Info(msg) => self.0.lock().as_mut().unwrap().info(msg),
boa_utils::LogMessage::Warn(msg) => self.0.lock().as_mut().unwrap().warn(msg),
boa_utils::LogMessage::Error(msg) => self.0.lock().as_mut().unwrap().error(msg),
boa_utils::LogMessage::Log(msg) => self.0.log(msg),
boa_utils::LogMessage::Info(msg) => self.0.info(msg),
boa_utils::LogMessage::Warn(msg) => self.0.warn(msg),
boa_utils::LogMessage::Error(msg) => self.0.error(msg),
}
}
#[inline]
fn take(&mut self) -> Vec<Self::Item> {
std::mem::take(&mut self.0)
.into_iter()
.map(|(span, msg)| match span {
LogSpan::Log => boa_utils::LogMessage::Log(msg),
LogSpan::Info => boa_utils::LogMessage::Info(msg),
LogSpan::Warn => boa_utils::LogMessage::Warn(msg),
LogSpan::Error => boa_utils::LogMessage::Error(msg),
})
.collect()
}
}
impl BoaConsoleLogger {
pub fn take(&mut self) -> Logs {
std::mem::take(&mut self.0)
}
}
#[inline]
fn take_console_logs() -> Logs {
let logs = boa_utils::inspect_logger(|logger| logger.take());
logs.into_iter()
.map(|msg| match msg {
boa_utils::LogMessage::Log(msg) => (LogSpan::Log, msg),
boa_utils::LogMessage::Info(msg) => (LogSpan::Info, msg),
boa_utils::LogMessage::Warn(msg) => (LogSpan::Warn, msg),
boa_utils::LogMessage::Error(msg) => (LogSpan::Error, msg),
})
.collect()
}
pub struct JSRunner;
@@ -77,9 +109,10 @@ pub struct BoaRunner {
impl BoaRunner {
pub fn try_new() -> Result<Self> {
let cache_dir = crate::utils::dirs::cache_dir().unwrap();
let loader = Rc::new(CombineModuleLoader::new(
SimpleModuleLoader::new(CUSTOM_SCRIPTS_DIR.as_path())?,
HttpModuleLoader::default(),
HttpModuleLoader::new(cache_dir, Duration::from_secs(60 * 60 * 24 * 30)),
));
let simple_loader = loader.clone_simple();
let queue = Rc::new(Queue::default());
@@ -96,7 +129,7 @@ impl BoaRunner {
pub fn setup_console(&self, logger: BoaConsoleLogger) -> Result<()> {
let ctx = &mut self.ctx.borrow_mut();
// it not concurrency safe. we should move to new boa_runtime console when it is ready for custom logger
boa_utils::set_logger(Arc::new(logger));
boa_utils::set_logger(Box::new(logger) as Box<dyn boa_utils::LoggerBox>);
let console = Console::init(ctx);
ctx.register_global_property(js_string!(Console::NAME), console, Attribute::all())?;
Ok(())
@@ -180,14 +213,13 @@ impl Runner for JSRunner {
// boa engine is single-thread runner so that we can use it in tokio::task::spawn_blocking
let res = tokio::task::spawn_blocking(move || {
let wrapped_fn = move || {
let logs = Arc::new(Mutex::new(Some(Logs::new())));
let logger = BoaConsoleLogger(logs.clone());
let boa_runner = wrap_result!(BoaRunner::try_new(), take_logs(logs));
wrap_result!(boa_runner.setup_console(logger), take_logs(logs));
let mut logger = BoaConsoleLogger(Logs::new());
let boa_runner = wrap_result!(BoaRunner::try_new(), logger.take());
wrap_result!(boa_runner.setup_console(logger), take_console_logs());
let config = wrap_result!(
serde_json::to_string(&mapping)
.map_err(|e| { std::io::Error::new(std::io::ErrorKind::InvalidData, e) }),
take_logs(logs)
take_console_logs()
);
let config = serde_json::to_string(&config).unwrap(); // escape the string
let execute_module = format!(
@@ -206,28 +238,28 @@ impl Runner for JSRunner {
// wrap_result!(boa_runner.execute_module(&process_module));
let main_module = wrap_result!(
boa_runner.parse_module(&execute_module, "main"),
take_logs(logs)
take_console_logs()
);
wrap_result!(boa_runner.execute_module(&main_module));
let ctx = boa_runner.get_ctx();
let namespace = main_module.namespace(&mut ctx.borrow_mut());
let result = wrap_result!(
namespace.get(js_string!("result"), &mut ctx.borrow_mut()),
take_logs(logs)
take_console_logs()
);
let mut result = wrap_result!(
let result = wrap_result!(
result
.as_string()
.ok_or_else(|| JsNativeError::typ().with_message("Expected string"))
.map(|str| str.to_std_string_escaped()),
take_logs(logs)
take_console_logs()
);
let mapping = wrap_result!(
serde_json::from_str(&result)
.map_err(|e| { std::io::Error::new(std::io::ErrorKind::InvalidData, e) }),
take_logs(logs)
take_console_logs()
);
(Ok::<Mapping, JsRunnerError>(mapping), take_logs(logs))
(Ok::<Mapping, JsRunnerError>(mapping), take_console_logs())
};
let (res, logs) = wrapped_fn();
match res {
@@ -517,7 +549,9 @@ const foreignNameservers = [
serde_yaml::Value::Sequence(vec![
serde_yaml::Value::String("RULE-SET,custom-reject,REJECT".to_string()),
serde_yaml::Value::String("RULE-SET,custom-direct,DIRECT".to_string()),
serde_yaml::Value::String("RULE-SET,custom-proxy,🚀".to_string())
serde_yaml::Value::String("RULE-SET,custom-proxy,🚀".to_string()),
serde_yaml::Value::String("aGVsbG8=".to_string()),
serde_yaml::Value::String("d29ybGQ=".to_string()),
])
);
let outs = serde_json::to_string(&logs).unwrap();

View File

@@ -324,6 +324,7 @@ pub fn check_core_permission(core: &nyanpasu_utils::core::CoreType) -> anyhow::R
mod test {
#[test]
#[ignore]
fn test_dir_placeholder() {
let placeholder = super::APP_DIR_PLACEHOLDER.clone();
if cfg!(windows) {

View File

@@ -53,12 +53,12 @@
"@csstools/normalize.css": "12.1.1",
"@emotion/babel-plugin": "11.13.5",
"@emotion/react": "11.14.0",
"@iconify/json": "2.2.311",
"@iconify/json": "2.2.312",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.66.11",
"@tanstack/react-router": "1.112.0",
"@tanstack/router-devtools": "1.112.0",
"@tanstack/router-plugin": "1.112.0",
"@tanstack/router-devtools": "1.112.6",
"@tanstack/router-plugin": "1.112.3",
"@tauri-apps/plugin-clipboard-manager": "2.2.1",
"@tauri-apps/plugin-dialog": "2.2.0",
"@tauri-apps/plugin-fs": "2.2.0",
@@ -75,7 +75,7 @@
"@vitejs/plugin-react-swc": "3.8.0",
"change-case": "5.4.4",
"clsx": "2.1.1",
"core-js": "3.40.0",
"core-js": "3.41.0",
"filesize": "10.1.6",
"meta-json-schema": "1.19.1",
"monaco-yaml": "5.3.1",

View File

@@ -46,6 +46,6 @@
"sass-embedded": "1.85.1",
"tailwind-merge": "3.0.2",
"typescript-plugin-css-modules": "5.1.0",
"vite-plugin-dts": "4.5.1"
"vite-plugin-dts": "4.5.3"
}
}

View File

@@ -2,7 +2,7 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.2",
"mihomo_alpha": "alpha-05e8f13",
"mihomo_alpha": "alpha-a7e56f1",
"clash_rs": "v0.7.6",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.7.6-alpha+sha.14e7d32"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2025-02-28T22:20:51.050Z"
"updated_at": "2025-03-02T22:21:03.218Z"
}

View File

@@ -333,8 +333,8 @@ importers:
specifier: 11.14.0
version: 11.14.0(@types/react@19.0.10)(react@19.0.0)
'@iconify/json':
specifier: 2.2.311
version: 2.2.311
specifier: 2.2.312
version: 2.2.312
'@monaco-editor/react':
specifier: 4.7.0
version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -345,11 +345,11 @@ importers:
specifier: 1.112.0
version: 1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tanstack/router-devtools':
specifier: 1.112.0
version: 1.112.0(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
specifier: 1.112.6
version: 1.112.6(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tanstack/router-plugin':
specifier: 1.112.0
version: 1.112.0(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
specifier: 1.112.3
version: 1.112.3(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
'@tauri-apps/plugin-clipboard-manager':
specifier: 2.2.1
version: 2.2.1
@@ -399,8 +399,8 @@ importers:
specifier: 2.1.1
version: 2.1.1
core-js:
specifier: 3.40.0
version: 3.40.0
specifier: 3.41.0
version: 3.41.0
filesize:
specifier: 10.1.6
version: 10.1.6
@@ -535,8 +535,8 @@ importers:
specifier: 5.1.0
version: 5.1.0(typescript@5.8.2)
vite-plugin-dts:
specifier: 4.5.1
version: 4.5.1(@types/node@22.13.8)(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
specifier: 4.5.3
version: 4.5.3(@types/node@22.13.8)(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
scripts:
dependencies:
@@ -605,8 +605,8 @@ importers:
specifier: 2.26.22
version: 2.26.22
undici:
specifier: 7.3.0
version: 7.3.0
specifier: 7.4.0
version: 7.4.0
yargs:
specifier: 17.7.2
version: 17.7.2
@@ -1666,8 +1666,8 @@ packages:
'@vue/compiler-sfc':
optional: true
'@iconify/json@2.2.311':
resolution: {integrity: sha512-Qt9Q9MuyEfKpd+3027fzbCMhEkhNpUTc9mnUQImbVYr5BiyFxgAxwGcTl+oD6WEblzdnGU3KIXqH/Laekud35w==}
'@iconify/json@2.2.312':
resolution: {integrity: sha512-sujKsprCACPMVZHeNaxvc7HyONW916tUVw4zfEK+GmT7PkH73PdplmW1w2UWC07IC6oUYPZ2EE0pfdWHhWHjpQ==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -2768,8 +2768,8 @@ packages:
resolution: {integrity: sha512-kmpMiBuz17Hxyl+ZO+B6/F98p07NSEmgr2JlZkKXcdupLIBAWqcXw+bjowFXNcTEwe9RWsS/WjAC/bBTftr0rA==}
engines: {node: '>=12'}
'@tanstack/router-devtools@1.112.0':
resolution: {integrity: sha512-kbCvkY8y6vDnF09At9Q9q0yitXBFpO/BEuvMtLTkAKWwoZqPoEp7981AiWZeN62sphtdREn/1sTxvMNY8bBW2Q==}
'@tanstack/router-devtools@1.112.6':
resolution: {integrity: sha512-OhLZsDnrItA+8BiVdmyyWB2VgQyoCZAjSRshQJpbZdeAV69OvT2rqN2TtiLJsbSAJWmqL4/UcY/13DI9Iv+k3Q==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/react-router': ^1.112.0
@@ -2780,8 +2780,8 @@ packages:
csstype:
optional: true
'@tanstack/router-generator@1.112.0':
resolution: {integrity: sha512-c1wA2TMfmL1igw6OFKdOZVrFqAJ/PB3ZJE0+upofmwVydUMH7tipvmztWGiRmcxGd66sl6o1l1X39308ObwAGQ==}
'@tanstack/router-generator@1.112.3':
resolution: {integrity: sha512-RUT+O/j7YIjbemVJjkP4qM8MYaaOltKYhyp9VGtcWxWGS8U2QDwC9UsskjBVOj7QV7aq3UcnExicABwK/AMCCQ==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/react-router': ^1.112.0
@@ -2789,8 +2789,8 @@ packages:
'@tanstack/react-router':
optional: true
'@tanstack/router-plugin@1.112.0':
resolution: {integrity: sha512-0ZFbHqAHtvbJzdDlIuxuJiOcbR5oue9IVA5OR93gS7sPCueZI1uZQB/hdtC+kcpdnYQKDcxjeNranhqbcaZZnQ==}
'@tanstack/router-plugin@1.112.3':
resolution: {integrity: sha512-XhKXFoJ7eajqghAPwHXfggyB8khopr5yVXiYQRiL+9Gek2q5M8N4z9+Uh2MM31KjTuiaJ72lZpUgT5FDj1m6Tg==}
engines: {node: '>=12'}
peerDependencies:
'@rsbuild/core': '>=1.0.2'
@@ -3288,8 +3288,8 @@ packages:
'@vue/compiler-vue2@2.7.16':
resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
'@vue/language-core@2.2.4':
resolution: {integrity: sha512-eGGdw7eWUwdIn9Fy/irJ7uavCGfgemuHQABgJ/hU1UgZFnbTg9VWeXvHQdhY+2SPQZWJqWXvRWIg67t4iWEa+Q==}
'@vue/language-core@2.2.0':
resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
@@ -3362,8 +3362,8 @@ packages:
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
alien-signals@1.0.4:
resolution: {integrity: sha512-DJqqQD3XcsaQcQ1s+iE2jDUZmmQpXwHiR6fCAim/w87luaW+vmLY8fMlrdkmRwzaFXhkxf3rqPCR59tKVv1MDw==}
alien-signals@0.4.14:
resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==}
allotment@1.20.3:
resolution: {integrity: sha512-JCnklt7j0OsyDjD7A9AdT6wqJ3FSoo1ASV6w02Am02lo6NwO25yhG1DcWW8ueBV38ppXQmvrXBXuzX7iVkq6Tw==}
@@ -3842,8 +3842,8 @@ packages:
core-js-compat@3.40.0:
resolution: {integrity: sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==}
core-js@3.40.0:
resolution: {integrity: sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==}
core-js@3.41.0:
resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==}
cosmiconfig-typescript-loader@6.1.0:
resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==}
@@ -7672,8 +7672,8 @@ packages:
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
engines: {node: '>=14.0'}
undici@7.3.0:
resolution: {integrity: sha512-Qy96NND4Dou5jKoSJ2gm8ax8AJM/Ey9o9mz7KN1bb9GP+G0l20Zw8afxTnY2f4b7hmhn/z8aC2kfArVQlAhFBw==}
undici@7.4.0:
resolution: {integrity: sha512-PUQM3/es3noM24oUn10u3kNNap0AbxESOmnssmW+dOi9yGwlUSi5nTNYl3bNbTkWOF8YZDkx2tCmj9OtQ3iGGw==}
engines: {node: '>=20.18.1'}
unicode-canonical-property-names-ecmascript@2.0.1:
@@ -7868,8 +7868,8 @@ packages:
engines: {node: ^18.19.0 || >=20.6.0}
hasBin: true
vite-plugin-dts@4.5.1:
resolution: {integrity: sha512-Yo1dHT05B2nD47AVB7b0+wK1FPFpJJnUf/muRF7+tP+sbPFRhLs70TTRGwJw7NDBwAUAmSwhrD+ZPTe4P6Wv9w==}
vite-plugin-dts@4.5.3:
resolution: {integrity: sha512-P64VnD00dR+e8S26ESoFELqc17+w7pKkwlBpgXteOljFyT0zDwD8hH4zXp49M/kciy//7ZbVXIwQCekBJjfWzA==}
peerDependencies:
typescript: '*'
vite: '*'
@@ -9426,7 +9426,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@iconify/json@2.2.311':
'@iconify/json@2.2.312':
dependencies:
'@iconify/types': 2.0.0
pathe: 1.1.2
@@ -10500,7 +10500,7 @@ snapshots:
'@tanstack/history': 1.99.13
'@tanstack/store': 0.7.0
'@tanstack/router-devtools@1.112.0(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
'@tanstack/router-devtools@1.112.6(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@tanstack/react-router': 1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
clsx: 2.1.1
@@ -10510,7 +10510,7 @@ snapshots:
optionalDependencies:
csstype: 3.1.3
'@tanstack/router-generator@1.112.0(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))':
'@tanstack/router-generator@1.112.3(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))':
dependencies:
'@tanstack/virtual-file-routes': 1.99.0
prettier: 3.5.2
@@ -10519,7 +10519,7 @@ snapshots:
optionalDependencies:
'@tanstack/react-router': 1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tanstack/router-plugin@1.112.0(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
'@tanstack/router-plugin@1.112.3(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))':
dependencies:
'@babel/core': 7.26.9
'@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9)
@@ -10528,7 +10528,7 @@ snapshots:
'@babel/traverse': 7.26.9
'@babel/types': 7.26.9
'@tanstack/router-core': 1.112.0
'@tanstack/router-generator': 1.112.0(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))
'@tanstack/router-generator': 1.112.3(@tanstack/react-router@1.112.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))
'@tanstack/router-utils': 1.102.2
'@tanstack/virtual-file-routes': 1.99.0
'@types/babel__core': 7.20.5
@@ -11051,7 +11051,7 @@ snapshots:
'@babel/preset-env': 7.26.9(@babel/core@7.26.9)
browserslist: 4.24.4
browserslist-to-esbuild: 2.1.1(browserslist@4.24.4)
core-js: 3.40.0
core-js: 3.41.0
magic-string: 0.30.17
regenerator-runtime: 0.14.1
systemjs: 6.15.1
@@ -11108,13 +11108,13 @@ snapshots:
de-indent: 1.0.2
he: 1.2.0
'@vue/language-core@2.2.4(typescript@5.8.2)':
'@vue/language-core@2.2.0(typescript@5.8.2)':
dependencies:
'@volar/language-core': 2.4.11
'@vue/compiler-dom': 3.5.13
'@vue/compiler-vue2': 2.7.16
'@vue/shared': 3.5.13
alien-signals: 1.0.4
alien-signals: 0.4.14
minimatch: 9.0.5
muggle-string: 0.4.1
path-browserify: 1.0.1
@@ -11198,7 +11198,7 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
alien-signals@1.0.4: {}
alien-signals@0.4.14: {}
allotment@1.20.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
@@ -11715,7 +11715,7 @@ snapshots:
dependencies:
browserslist: 4.24.4
core-js@3.40.0: {}
core-js@3.41.0: {}
cosmiconfig-typescript-loader@6.1.0(@types/node@22.13.8)(cosmiconfig@9.0.0(typescript@5.8.2))(typescript@5.8.2):
dependencies:
@@ -16010,7 +16010,7 @@ snapshots:
dependencies:
'@fastify/busboy': 2.1.1
undici@7.3.0: {}
undici@7.4.0: {}
unicode-canonical-property-names-ecmascript@2.0.1: {}
@@ -16206,12 +16206,12 @@ snapshots:
- rollup
- supports-color
vite-plugin-dts@4.5.1(@types/node@22.13.8)(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
vite-plugin-dts@4.5.3(@types/node@22.13.8)(rollup@4.34.3)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)):
dependencies:
'@microsoft/api-extractor': 7.51.0(@types/node@22.13.8)
'@rollup/pluginutils': 5.1.4(rollup@4.34.3)
'@volar/typescript': 2.4.11
'@vue/language-core': 2.2.4(typescript@5.8.2)
'@vue/language-core': 2.2.0(typescript@5.8.2)
compare-versions: 6.1.1
debug: 4.4.0
kolorist: 1.8.0

2
clash-nyanpasu/scripts/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
!.vscode/settings.json
!.vscode/

View File

@@ -0,0 +1,4 @@
{
"deno.enable": true,
"deno.enablePaths": ["./deno"]
}

View File

@@ -0,0 +1,3 @@
# Deno scripts
When we migrated all the scripts to Deno, let's move them to outer directory.

View File

@@ -0,0 +1,8 @@
{
"tasks": {
"upload-macos-updater": {
"description": "Upload macOS updater to GitHub Releases",
"command": "deno run -A upload-macos-updater.ts",
},
},
}

146
clash-nyanpasu/scripts/deno/deno.lock generated Normal file
View File

@@ -0,0 +1,146 @@
{
"version": "4",
"specifiers": {
"jsr:@std/path@*": "1.0.8",
"npm:colorize-template@*": "1.0.0",
"npm:consola@*": "3.4.0",
"npm:globby@*": "14.1.0",
"npm:picocolors@*": "1.1.1"
},
"jsr": {
"@std/path@1.0.8": {
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
}
},
"npm": {
"@nodelib/fs.scandir@2.1.5": {
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dependencies": [
"@nodelib/fs.stat",
"run-parallel"
]
},
"@nodelib/fs.stat@2.0.5": {
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
},
"@nodelib/fs.walk@1.2.8": {
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dependencies": [
"@nodelib/fs.scandir",
"fastq"
]
},
"@sindresorhus/merge-streams@2.3.0": {
"integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="
},
"braces@3.0.3": {
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": [
"fill-range"
]
},
"colorize-template@1.0.0": {
"integrity": "sha512-beJ9v9RjpbYZ8OdwJgIRZD3YUkZPXmi1MK+yX0J24UupKVHa9yk0jiARgt2i6MBX6AKjYA0SNsBn65bUPuVQiw=="
},
"consola@3.4.0": {
"integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA=="
},
"fast-glob@3.3.3": {
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dependencies": [
"@nodelib/fs.stat",
"@nodelib/fs.walk",
"glob-parent",
"merge2",
"micromatch"
]
},
"fastq@1.19.1": {
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dependencies": [
"reusify"
]
},
"fill-range@7.1.1": {
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": [
"to-regex-range"
]
},
"glob-parent@5.1.2": {
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dependencies": [
"is-glob"
]
},
"globby@14.1.0": {
"integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==",
"dependencies": [
"@sindresorhus/merge-streams",
"fast-glob",
"ignore",
"path-type",
"slash",
"unicorn-magic"
]
},
"ignore@7.0.3": {
"integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA=="
},
"is-extglob@2.1.1": {
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
},
"is-glob@4.0.3": {
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dependencies": [
"is-extglob"
]
},
"is-number@7.0.0": {
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"merge2@1.4.1": {
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
},
"micromatch@4.0.8": {
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": [
"braces",
"picomatch"
]
},
"path-type@6.0.0": {
"integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ=="
},
"picocolors@1.1.1": {
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"picomatch@2.3.1": {
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"queue-microtask@1.2.3": {
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
"reusify@1.1.0": {
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
},
"run-parallel@1.2.0": {
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dependencies": [
"queue-microtask"
]
},
"slash@5.1.0": {
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="
},
"to-regex-range@5.0.1": {
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": [
"is-number"
]
},
"unicorn-magic@0.3.0": {
"integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="
}
}
}

View File

@@ -0,0 +1,58 @@
import * as path from 'jsr:@std/path'
import { globby } from 'npm:globby'
import { consola } from './utils/logger.ts'
const WORKSPACE_ROOT = path.join(Deno.cwd(), '../..')
consola.info(`WORKSPACE_ROOT: ${WORKSPACE_ROOT}`)
const GITHUB_TOKEN = Deno.env.get('GITHUB_TOKEN') || Deno.env.get('GH_TOKEN')
const GITHUB_TAG = Deno.env.get('GITHUB_TAG')
const TARGET_ARCH = Deno.env.get('TARGET_ARCH') || Deno.build.arch
if (!GITHUB_TOKEN) {
consola.fatal('GITHUB_TOKEN is not set')
Deno.exit(1)
}
if (!GITHUB_TAG) {
consola.fatal('GITHUB_TAG is not set')
Deno.exit(1)
}
const BACKEND_BUILD_DIR = path.join(WORKSPACE_ROOT, 'backend/target')
const files = await globby(['**/*.tar.gz', '**/*.sig', '**/*.dmg'], {
cwd: BACKEND_BUILD_DIR,
})
for (let file of files) {
file = path.join(BACKEND_BUILD_DIR, file)
const p = path.parse(file)
consola.info(`Found file: ${p.base}`)
if (p.base.endsWith('.app.tar.gz')) {
const newName = p.name.split('.')[0] + `.${TARGET_ARCH}.app.tar.gz`
const newPath = path.join(p.dir, newName)
consola.info(`Renaming ${file} to ${newPath}`)
await Deno.rename(file, newPath)
file = newPath
}
consola.info(`Uploading ${file}...`)
const cmd = new Deno.Command('gh', {
args: ['release', 'upload', GITHUB_TAG, file, '--clobber'],
stdout: 'piped',
stderr: 'piped',
env: {
GH_TOKEN: GITHUB_TOKEN,
GITHUB_TOKEN,
},
})
const output = await cmd.output()
if (output.code !== 0) {
consola.error(output.stderr)
consola.error(`Failed to upload ${file}`)
Deno.exit(1)
}
}
consola.success('Uploaded all files')

View File

@@ -0,0 +1,21 @@
import { createColorize } from 'npm:colorize-template'
import { createConsola } from 'npm:consola'
import pc from 'npm:picocolors'
const logLevelStr = Deno.env.get('LOG_LEVEL')
export const consola = createConsola({
level: logLevelStr ? Number.parseInt(logLevelStr) : 5,
fancy: true,
formatOptions: {
colors: true,
compact: false,
date: true,
},
})
export const colorize = createColorize({
...pc,
success: pc.green,
error: pc.red,
})

View File

@@ -26,7 +26,7 @@
"picocolors": "1.1.1",
"tar": "7.4.3",
"telegram": "2.26.22",
"undici": "7.3.0",
"undici": "7.4.0",
"yargs": "17.7.2"
}
}

View File

@@ -17,4 +17,5 @@
"composite": true,
},
"include": ["./"],
"exclude": ["deno"],
}

View File

@@ -116,6 +116,56 @@ dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.95"
@@ -191,6 +241,16 @@ dependencies = [
"zbus",
]
[[package]]
name = "assert-json-diff"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "async-broadcast"
version = "0.7.2"
@@ -1003,6 +1063,7 @@ version = "2.1.2"
dependencies = [
"aes-gcm",
"anyhow",
"async-trait",
"base64 0.22.1",
"boa_engine",
"chrono",
@@ -1010,12 +1071,14 @@ dependencies = [
"delay_timer",
"dirs 6.0.0",
"dunce",
"env_logger",
"futures",
"getrandom 0.2.15",
"image 0.24.9",
"imageproc",
"log",
"log4rs",
"mockito",
"nanoid",
"network-interface",
"once_cell",
@@ -1047,6 +1110,7 @@ dependencies = [
"tauri-plugin-shell",
"tauri-plugin-updater",
"tauri-plugin-window-state",
"tempfile",
"tokio",
"tokio-tungstenite 0.26.1",
"url",
@@ -1132,6 +1196,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "colored"
version = "2.2.0"
@@ -1927,6 +1997,29 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@@ -2924,6 +3017,7 @@ dependencies = [
"http 1.2.0",
"http-body 1.0.1",
"httparse",
"httpdate",
"itoa 1.0.14",
"pin-project-lite",
"smallvec",
@@ -3344,6 +3438,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.10.5"
@@ -3845,6 +3945,30 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "mockito"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "652cd6d169a36eaf9d1e6bce1a221130439a966d7f27858af66a33a66e9c4ee2"
dependencies = [
"assert-json-diff",
"bytes",
"colored",
"futures-util",
"http 1.2.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.6.0",
"hyper-util",
"log",
"rand 0.8.5",
"regex",
"serde_json",
"serde_urlencoded",
"similar",
"tokio",
]
[[package]]
name = "muda"
version = "0.15.3"
@@ -6161,6 +6285,12 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "siphasher"
version = "0.3.11"
@@ -7002,9 +7132,9 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.16.0"
version = "3.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
dependencies = [
"cfg-if",
"fastrand 2.3.0",

View File

@@ -67,6 +67,7 @@ getrandom = "0.2"
tokio-tungstenite = "0.26.1"
futures = "0.3"
sys-locale = "0.3.1"
async-trait = "0.1.86"
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"
@@ -120,3 +121,8 @@ strip = false # 不剥离符号,保留调试信息
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[dev-dependencies]
env_logger = "0.11.0"
mockito = "1.2.0"
tempfile = "3.17.1"

View File

@@ -14,6 +14,8 @@ pub mod clash;
pub mod verge;
pub mod runtime;
pub mod save_profile;
pub mod system;
pub mod proxy;
// Re-export all command functions for backwards compatibility
pub use profile::*;
@@ -26,3 +28,5 @@ pub use clash::*;
pub use verge::*;
pub use runtime::*;
pub use save_profile::*;
pub use system::*;
pub use proxy::*;

View File

@@ -0,0 +1,35 @@
use super::CmdResult;
use crate::module::mihomo::MihomoManager;
use tauri::async_runtime;
#[tauri::command]
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
let proxies = async_runtime::spawn_blocking(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
let manager = MihomoManager::new();
{
let mut write_guard = manager.write();
rt.block_on(write_guard.refresh_proxies());
}
let read_guard = manager.read();
read_guard.fetch_proxies().clone()
})
.await.map_err(|e| e.to_string())?;
Ok(proxies)
}
#[tauri::command]
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
let providers_proxies = async_runtime::spawn_blocking(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
let manager = MihomoManager::new();
{
let mut write_guard = manager.write();
rt.block_on(write_guard.refresh_providers_proxies());
}
let read_guard = manager.read();
read_guard.fetch_providers_proxies().clone()
})
.await.map_err(|e| e.to_string())?;
Ok(providers_proxies)
}

View File

@@ -0,0 +1,34 @@
use super::CmdResult;
use crate::{core::handle, model::sysinfo::PlatformSpecification};
use tauri_plugin_clipboard_manager::ClipboardExt;
use crate::{core::{self, CoreManager, service}, wrap_err};
#[tauri::command]
pub async fn export_diagnostic_info() -> CmdResult<()> {
let sysinfo = PlatformSpecification::new();
let info = format!("{:?}", sysinfo);
let app_handle = handle::Handle::global().app_handle().unwrap();
let cliboard = app_handle.clipboard();
if let Err(_) = cliboard.write_text(info) {
log::error!(target: "app", "Failed to write to clipboard");
}
Ok(())
}
/// 获取当前内核运行模式
#[tauri::command]
pub async fn get_running_mode() -> Result<String, String> {
match CoreManager::global().get_running_mode().await {
core::RunningMode::Service => Ok("service".to_string()),
core::RunningMode::Sidecar => Ok("sidecar".to_string()),
core::RunningMode::NotRunning => Ok("not_running".to_string()),
}
}
/// 安装/重装系统服务
#[tauri::command]
pub async fn install_service() -> CmdResult {
wrap_err!(service::reinstall_service().await)
}

View File

@@ -0,0 +1 @@
pub const MIHOMO_URL: &str = concat!("http://", "127.0.0.1", ":", "9097");

View File

@@ -0,0 +1 @@
pub mod mihomo;

View File

@@ -21,3 +21,6 @@ pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) {
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
}
"#;
pub mod api;

View File

@@ -7,7 +7,7 @@ use crate::utils::{dirs, help};
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use serde_yaml::Mapping;
use std::{sync::Arc, time::Duration};
use std::{sync::Arc, time::Duration, path::PathBuf};
use tauri_plugin_shell::ShellExt;
use tokio::sync::Mutex;
use tokio::time::sleep;
@@ -17,6 +17,17 @@ pub struct CoreManager {
running: Arc<Mutex<bool>>,
}
/// 内核运行模式
#[derive(Debug, Clone, serde::Serialize)]
pub enum RunningMode {
/// 服务模式运行
Service,
/// Sidecar模式运行
Sidecar,
/// 未运行
NotRunning,
}
impl CoreManager {
pub fn global() -> &'static CoreManager {
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
@@ -53,12 +64,37 @@ impl CoreManager {
// 服务模式
if service::check_service().await.is_ok() {
log::info!(target: "app", "stop the core by service");
service::stop_core_by_service().await?;
match service::stop_core_by_service().await {
Ok(_) => {
log::info!(target: "app", "core stopped successfully by service");
}
Err(err) => {
log::warn!(target: "app", "failed to stop core by service: {}", err);
// 服务停止失败尝试停止可能的sidecar进程
self.stop_sidecar_process();
}
}
} else {
// 如果没有使用服务尝试停止sidecar进程
self.stop_sidecar_process();
}
*running = false;
Ok(())
}
/// 停止通过sidecar启动的进程
fn stop_sidecar_process(&self) {
if let Some(process) = handle::Handle::global().take_core_process() {
log::info!(target: "app", "stopping core process in sidecar mode");
if let Err(e) = process.kill() {
log::warn!(target: "app", "failed to kill core process: {}", e);
} else {
log::info!(target: "app", "core process stopped successfully");
}
}
}
/// 启动核心
pub async fn start_core(&self) -> Result<()> {
let mut running = self.running.lock().await;
@@ -69,11 +105,26 @@ impl CoreManager {
let config_path = Config::generate_file(ConfigType::Run)?;
// 服务模式
// 先尝试服务模式
if service::check_service().await.is_ok() {
log::info!(target: "app", "try to run core in service mode");
service::run_core_by_service(&config_path).await?;
match service::run_core_by_service(&config_path).await {
Ok(_) => {
log::info!(target: "app", "core started successfully in service mode");
},
Err(err) => {
// 服务启动失败尝试sidecar模式
log::warn!(target: "app", "failed to start core in service mode: {}", err);
log::info!(target: "app", "trying to run core in sidecar mode");
self.run_core_by_sidecar(&config_path).await?;
}
}
} else {
// 服务不可用直接使用sidecar模式
log::info!(target: "app", "service not available, running core in sidecar mode");
self.run_core_by_sidecar(&config_path).await?;
}
// 流量订阅
#[cfg(target_os = "macos")]
log_err!(Tray::global().subscribe_traffic().await);
@@ -83,11 +134,43 @@ impl CoreManager {
Ok(())
}
/// 通过sidecar启动内核
async fn run_core_by_sidecar(&self, config_path: &PathBuf) -> Result<()> {
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
log::info!(target: "app", "starting core {} in sidecar mode", clash_core);
let app_handle = handle::Handle::global().app_handle().ok_or(anyhow::anyhow!("failed to get app handle"))?;
// 获取配置目录
let config_dir = dirs::app_home_dir()?;
let config_path_str = dirs::path_to_str(config_path)?;
// 启动核心进程并转入后台运行
let (_, child) = app_handle
.shell()
.sidecar(clash_core)?
.args(["-d", dirs::path_to_str(&config_dir)?, "-f", config_path_str])
.spawn()?;
// 保存进程ID以便后续管理
handle::Handle::global().set_core_process(child);
// 等待短暂时间确保启动成功
sleep(Duration::from_millis(300)).await;
log::info!(target: "app", "core started in sidecar mode");
Ok(())
}
/// 重启内核
pub async fn restart_core(&self) -> Result<()> {
// 重新启动app
log::info!(target: "app", "restarting core");
self.stop_core().await?;
self.start_core().await?;
log::info!(target: "app", "core restarted successfully");
Ok(())
}
@@ -139,9 +222,11 @@ impl CoreManager {
}
Err(err) => {
println!("[切换内核] 内核切换失败: {}", err);
Config::verge().discard();
Config::runtime().discard();
Err(err)
// 即使使用服务失败我们也尝试使用sidecar模式启动
log::info!(target: "app", "trying sidecar mode after service failure");
self.start_core().await?;
Config::runtime().apply();
Ok(())
}
}
}
@@ -159,8 +244,10 @@ impl CoreManager {
}
Err(err) => {
println!("[切换内核] 内核切换失败: {}", err);
Config::verge().discard();
Err(err)
// 即使使用服务失败我们也尝试使用sidecar模式启动
log::info!(target: "app", "trying sidecar mode after service failure with default config");
self.start_core().await?;
Ok(())
}
}
}
@@ -495,4 +582,38 @@ impl CoreManager {
}
}
}
/// 获取当前内核运行模式
pub async fn get_running_mode(&self) -> RunningMode {
let running = self.running.lock().await;
if !*running {
return RunningMode::NotRunning;
}
// 检查服务状态
match service::check_service().await {
Ok(_) => {
// 检查服务是否实际运行核心
match service::is_service_running().await {
Ok(true) => RunningMode::Service,
_ => {
// 服务存在但可能没有运行检查是否有sidecar进程
if handle::Handle::global().has_core_process() {
RunningMode::Sidecar
} else {
RunningMode::NotRunning
}
}
}
},
Err(_) => {
// 服务不可用检查是否有sidecar进程
if handle::Handle::global().has_core_process() {
RunningMode::Sidecar
} else {
RunningMode::NotRunning
}
}
}
}
}

View File

@@ -3,11 +3,13 @@ use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use std::sync::Arc;
use tauri::{AppHandle, Emitter, Manager, WebviewWindow};
use tauri_plugin_shell::process::CommandChild;
#[derive(Debug, Default, Clone)]
pub struct Handle {
pub app_handle: Arc<RwLock<Option<AppHandle>>>,
pub is_exiting: Arc<RwLock<bool>>,
pub core_process: Arc<RwLock<Option<CommandChild>>>,
}
impl Handle {
@@ -17,6 +19,7 @@ impl Handle {
HANDLE.get_or_init(|| Handle {
app_handle: Arc::new(RwLock::new(None)),
is_exiting: Arc::new(RwLock::new(false)),
core_process: Arc::new(RwLock::new(None)),
})
}
@@ -68,6 +71,21 @@ impl Handle {
*is_exiting = true;
}
pub fn set_core_process(&self, process: CommandChild) {
let mut core_process = self.core_process.write();
*core_process = Some(process);
}
pub fn take_core_process(&self) -> Option<CommandChild> {
let mut core_process = self.core_process.write();
core_process.take()
}
/// 检查是否有运行中的核心进程
pub fn has_core_process(&self) -> bool {
self.core_process.read().is_some()
}
pub fn is_exiting(&self) -> bool {
*self.is_exiting.read()
}

View File

@@ -279,3 +279,15 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
Ok(())
}
/// 检查服务是否正在运行
pub async fn is_service_running() -> Result<bool> {
let resp = check_service().await?;
// 检查服务状态码和消息
if resp.code == 200 && resp.msg == "success" && resp.data.is_some() {
Ok(true)
} else {
Ok(false)
}
}

View File

@@ -85,6 +85,7 @@ pub fn use_seq(seq: SeqMap, mut config: Mapping, field: &str) -> Mapping {
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused_imports)]
use serde_yaml::Value;
#[test]

View File

@@ -4,6 +4,8 @@ mod core;
mod enhance;
mod feat;
mod utils;
mod model;
mod module;
use crate::core::hotkey;
use crate::utils::{resolve, resolve::resolve_scheme, server};
use config::Config;
@@ -146,6 +148,9 @@ pub fn run() {
cmd::get_network_interfaces,
cmd::restart_core,
cmd::restart_app,
// 添加新的命令
cmd::get_running_mode,
cmd::install_service,
// clash
cmd::get_clash_info,
cmd::patch_clash_config,
@@ -157,6 +162,8 @@ pub fn run() {
cmd::get_runtime_logs,
cmd::invoke_uwp_tool,
cmd::copy_clash_env,
cmd::get_proxies,
cmd::get_providers_proxies,
// verge
cmd::get_verge_config,
cmd::patch_verge_config,
@@ -191,6 +198,8 @@ pub fn run() {
cmd::list_webdav_backup,
cmd::delete_webdav_backup,
cmd::restore_webdav_backup,
// export diagnostic info for issue reporting
cmd::export_diagnostic_info,
]);
#[cfg(debug_assertions)]

View File

@@ -0,0 +1,20 @@
use reqwest::Client;
#[allow(unused)]
pub(crate) struct ApiCaller<'a> {
pub(crate) url: &'a str,
pub(crate) client: Client,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_caller() {
let _api_caller = ApiCaller {
url: "https://example.com",
client: Client::new(),
};
}
}

View File

@@ -0,0 +1,5 @@
use super::common::ApiCaller;
pub struct MihomoAPICaller {
pub(crate) caller: ApiCaller<'static>,
}

View File

@@ -0,0 +1,2 @@
pub mod common;
pub mod mihomo;

View File

@@ -0,0 +1,2 @@
pub mod api;
pub mod sysinfo;

View File

@@ -0,0 +1,18 @@
use std::fmt::{self, Debug, Formatter};
pub struct PlatformSpecification {
pub system_name: String,
pub system_version: String,
pub system_kernel_version: String,
pub system_arch: String,
}
impl Debug for PlatformSpecification {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}",
self.system_name, self.system_version, self.system_kernel_version, self.system_arch
)
}
}

View File

@@ -0,0 +1,70 @@
use crate::model::api::common::ApiCaller;
use async_trait::async_trait;
use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
RequestBuilder,
};
use serde::de::DeserializeOwned;
impl<'a> ApiCaller<'a> {
pub async fn send_request(
&self,
method: &str,
path: &str,
body: Option<&str>,
headers: Option<Vec<(&str, &str)>>,
) -> Result<String, String> {
let full_url = format!("{}{}", self.url, path); // 拼接完整 URL
let mut request: RequestBuilder = match method {
"GET" => self.client.get(&full_url),
"POST" => self
.client
.post(&full_url)
.body(body.unwrap_or("").to_string()),
"PUT" => self
.client
.put(&full_url)
.body(body.unwrap_or("").to_string()),
"DELETE" => self.client.delete(&full_url),
_ => return Err("Unsupported HTTP method".to_string()),
};
// 处理 headers
if let Some(hdrs) = headers {
let mut header_map = HeaderMap::new();
for (key, value) in hdrs {
if let (Ok(header_name), Ok(header_value)) = (
HeaderName::from_bytes(key.as_bytes()),
HeaderValue::from_str(value),
) {
header_map.insert(header_name, header_value);
}
}
request = request.headers(header_map);
}
let response = request.send().await.map_err(|e| e.to_string())?;
response.text().await.map_err(|e| e.to_string())
}
}
#[allow(unused)]
#[async_trait]
pub trait ApiCallerTrait: Send + Sync {
async fn call_api<T>(
&self,
method: &str,
path: &str,
body: Option<&str>,
headers: Option<Vec<(&str, &str)>>
) -> Result<T, String>
where
T: DeserializeOwned + Send + Sync;
fn parse_json_response<T>(json_str: &str) -> Result<T, String>
where
T: DeserializeOwned,
{
serde_json::from_str(json_str).map_err(|e| e.to_string())
}
}

Some files were not shown because too many files have changed in this diff Show More