mirror of
https://github.com/p1neappleXpress/OpenFlux.git
synced 2026-10-02 05:04:39 +08:00
main: wire script:<name> transport type through CLI, factory and spec
--script-dir/--script-pubkey select a signed .js (+.sig) or .flux package by name; --transport=script:<name> and "script:<name>:<priority>" entries in --transports both resolve through the same expandScriptType/ resolveScriptFile path (parseTransportList now peels the priority off the LAST colon field, so a script: reference's own colon is unambiguous). transport_factory.go's existing "script" case (added earlier alongside the runtime) is otherwise unchanged. go.mod/go.sum: goja + goja_nodejs (the interpreter and its eventloop/ require support) - the only genuinely new dependencies; pion/webrtc and pierrec/lz4 were already present via native oneme. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ce52985fb1
commit
70e7a2042c
@@ -14,16 +14,22 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dop251/goja v0.0.0-20260926152631-39ec2650adc9
|
||||
github.com/dop251/goja_nodejs v0.0.0-20260918173711-b481721df8a2
|
||||
github.com/klauspost/compress v1.20.0
|
||||
github.com/pierrec/lz4/v4 v4.1.27
|
||||
github.com/pion/webrtc/v3 v3.3.6
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/xjasonlyu/windivert-go v0.0.0-20201010013527-4239d0afa76f
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dlclark/regexp2/v2 v2.5.2 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/pion/datachannel v1.5.8 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.12 // indirect
|
||||
@@ -44,7 +50,7 @@ require (
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
||||
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2/v2 v2.5.2 h1:HAsucWRhsqcDzl6Ua9aR8JwYOTzrZyPrF0/FNxJVAI0=
|
||||
github.com/dlclark/regexp2/v2 v2.5.2/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||
github.com/dop251/goja v0.0.0-20260926152631-39ec2650adc9 h1:Ksd3Vil12b/ykT5NjGzd3qNc688cJB9OUXJZZChEktI=
|
||||
github.com/dop251/goja v0.0.0-20260926152631-39ec2650adc9/go.mod h1:u8yZRUavu+N4EnFFy6J5fVtjE7lEcZ2YyV2GcBXY9c8=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20260918173711-b481721df8a2 h1:4cOnoHK48VXmDGOPp2h+u7sYZw/1Ou2JbHjTgU0DF0A=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20260918173711-b481721df8a2/go.mod h1:Tb7Xxye4LX7cT3i8YLvmPMGCV92IOi4CDZvm/V8ylc0=
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -81,6 +95,8 @@ github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguH
|
||||
github.com/xjasonlyu/windivert-go v0.0.0-20201010013527-4239d0afa76f h1:glX3VZCYwW1/OmFxOjazfCtBLxXB3YNZk9LF2lYx+Lw=
|
||||
github.com/xjasonlyu/windivert-go v0.0.0-20201010013527-4239d0afa76f/go.mod h1:gh//RKyt2Gesx3eOj3ulzrSQ60ySj2UA4qnOdrtarvg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
@@ -135,6 +151,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -46,11 +46,14 @@ func TestPickSessionContext(t *testing.T) {
|
||||
{"highest-priority transport URL", "", "http://#", specs, "https://docs/doc"},
|
||||
{"no URL anywhere keeps the old default", "", "http://#", specs[:1], "http://#"},
|
||||
{"legacy single transport", "", "http://#", []transportSpec{{Type: "yandex", Priority: 100, URL: "http://#"}}, "http://#"},
|
||||
// cupsonline is script-only: a real spec for it has Type "script"
|
||||
// (expandScriptType rewrites it) but keeps Name "cupsonline" - that's
|
||||
// what pickSessionContext actually keys its exclusion off now.
|
||||
{"cupsonline rooms are not a context", "", "http://#", []transportSpec{
|
||||
{Type: "cupsonline", Priority: 100, URL: "WyIxYzE0NGQwZS1lMDQw"},
|
||||
{Type: "yandex", Priority: 50, URL: "https://docs/doc"},
|
||||
{Name: "cupsonline", Type: "script", Priority: 100, URL: "WyIxYzE0NGQwZS1lMDQw"},
|
||||
{Name: "yandex", Type: "yandex", Priority: 50, URL: "https://docs/doc"},
|
||||
}, "https://docs/doc"},
|
||||
{"cupsonline alone", "", "http://#", []transportSpec{{Type: "cupsonline", Priority: 100, URL: "WyIxYzE0NGQwZS1lMDQw"}}, "http://#"},
|
||||
{"cupsonline alone", "", "http://#", []transportSpec{{Name: "cupsonline", Type: "script", Priority: 100, URL: "WyIxYzE0NGQwZS1lMDQw"}}, "http://#"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := pickSessionContext(c.explicit, c.url, c.specs); got != c.want {
|
||||
|
||||
@@ -16,9 +16,7 @@ import (
|
||||
"openflux/socks5"
|
||||
"openflux/transport"
|
||||
"openflux/transport/control"
|
||||
"openflux/transport/cupsonline"
|
||||
"openflux/transport/ipc"
|
||||
"openflux/transport/mailru"
|
||||
"openflux/transport/manager"
|
||||
"openflux/transport/oneme"
|
||||
"openflux/transport/yandex"
|
||||
@@ -106,8 +104,15 @@ const (
|
||||
// transportHasCookies reports whether the given transport uses HTTP cookies
|
||||
// that can be refreshed via the NegotiatedTransport control channel.
|
||||
func transportHasCookies(t string) bool {
|
||||
if strings.HasPrefix(t, "script:") {
|
||||
// Every script transport gets FetchCookies/ApplyCookies for free
|
||||
// (transport/script's shared cookiejar.Jar) - assume it wants the
|
||||
// store too; a script that never uses cookies just ends up with an
|
||||
// empty jar file, harmless.
|
||||
return true
|
||||
}
|
||||
switch t {
|
||||
case "yandex", "vyandex", "boards", "mailru", "cupsonline":
|
||||
case "yandex", "vyandex":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -147,7 +152,7 @@ func isNumber(s string) bool {
|
||||
// explicit --session-context, if non-empty
|
||||
// --url globalURL, if set and not the placeholder
|
||||
// transports URL of the highest-priority transport that has one,
|
||||
// cupsonline aside
|
||||
// cupsonline (native or script:cupsonline) aside
|
||||
// fallback the placeholder "http://#"
|
||||
//
|
||||
// A cupsonline "URL" is the room list the exit creates when it starts and
|
||||
@@ -167,7 +172,10 @@ func pickSessionContext(explicit, globalURL string, specs []transportSpec) strin
|
||||
}
|
||||
best := -1
|
||||
for i, s := range specs {
|
||||
if s.Type == "cupsonline" || s.URL == "" || s.URL == placeholder {
|
||||
// cupsonline is script-only now (script:cupsonline expands Type to
|
||||
// "script" but keeps Name == "cupsonline"), so the exclusion has to
|
||||
// key off Name, not Type, to still catch it.
|
||||
if s.Name == "cupsonline" || s.URL == "" || s.URL == placeholder {
|
||||
continue
|
||||
}
|
||||
if best < 0 || s.Priority > specs[best].Priority {
|
||||
@@ -206,7 +214,7 @@ func main() {
|
||||
|
||||
role := flag.String("role", roleClient, "client | exit | bench-send | bench-sink")
|
||||
inbound := flag.String("inbound", "", "tun | socks5 (client only; default: tun on macOS, socks5 elsewhere)")
|
||||
transportType := flag.String("transport", "yandex", "Transport type (yandex, vyandex, oneme, cupsonline, mailru)")
|
||||
transportType := flag.String("transport", "yandex", "Transport type (yandex, vyandex, oneme, script:<name> - e.g. script:mailru, script:boards, script:cupsonline)")
|
||||
mode := flag.String("mode", "", "Exit-node mode: l3 (default, Linux only) or l4 (works everywhere)")
|
||||
|
||||
codec := flag.String("codec", codecBatched, "batched (default, zstd+coalescing) or legacy (per-packet LZ4)")
|
||||
@@ -230,15 +238,19 @@ func main() {
|
||||
directListen := flag.String("direct-listen", "", "DirectTransport: local address to listen on (exit). Requires --encryption-key-file")
|
||||
transportsFlag := flag.String("transports", "",
|
||||
"Comma-separated list of transports with priorities, e.g. "+
|
||||
"\"direct:100,yandex:50\". If empty, --transport is used as a single transport.")
|
||||
"\"direct:100,yandex:50,script:mailru:30\". If empty, --transport is used as a single transport.")
|
||||
yandexURL := flag.String("yandex-url", "", "URL for the yandex transport (overrides --url in --transports mode)")
|
||||
vyandexURL := flag.String("vyandex-url", "", "URL for the vyandex transport")
|
||||
flag.StringVar(&yandexCookiesFile, "yandex-cookies-file", "", "Netscape cookies.txt with a Yandex login for vyandex transports")
|
||||
boardsURL := flag.String("boards-url", "", "URL for the boards transport")
|
||||
mailruURL := flag.String("mailru-url", "", "URL (weblink) for the mailru transport")
|
||||
cupsonlineURL := flag.String("cupsonline-url", "", "URL for the cupsonline transport")
|
||||
onemeToken := flag.String("oneme-token", "", "MAX token for the oneme transport")
|
||||
onemeUID := flag.String("oneme-uid", "", "MAX uid for the oneme transport")
|
||||
scriptDir := flag.String("script-dir", "",
|
||||
"Directory holding signed script transports: <name>.js + <name>.js.sig, or a "+
|
||||
"<name>.flux package (script+manifest+icon signed together) - .flux wins if both exist. "+
|
||||
"Select one with --transport=script:<name> or \"script:<name>:<priority>\" in --transports.")
|
||||
scriptPubkey := flag.String("script-pubkey", "",
|
||||
"Hex ed25519 public key every script transport's signature must verify against (the detached "+
|
||||
".sig, or the .flux package's own signature). Required with --script-dir.")
|
||||
configPath := flag.String("config", "",
|
||||
"Path to an OpenFlux .conf file. Command-line flags override values from the file.")
|
||||
shareFlag := flag.Bool("share", false,
|
||||
@@ -289,9 +301,10 @@ TRANSPORT (single-transport mode)
|
||||
-t, --transport=yandex Yandex.Docs over WebSocket. (default)
|
||||
-t, --transport=vyandex Yandex.Volga over HTTP relay + WS.
|
||||
-t, --transport=oneme MAX (VK) over WebRTC.
|
||||
-t, --transport=cupsonline Cups.online interview rooms.
|
||||
-t, --transport=mailru Mail.ru Docs over WebSocket.
|
||||
-t, --transport=direct Plain TCP to a self-hosted exit.
|
||||
-t, --transport=script:<name>
|
||||
Signed JS transport (mailru, boards,
|
||||
cupsonline, ...) loaded from --script-dir.
|
||||
-u, --url=<URL> Document URL.
|
||||
|
||||
TRANSPORTS (multi-transport session; requires --encryption-key-file)
|
||||
@@ -306,9 +319,6 @@ TRANSPORTS (multi-transport session; requires --encryption-key-file)
|
||||
--yandex-cookies-file=<path>
|
||||
Netscape cookies.txt with a Yandex login for
|
||||
vyandex transports.
|
||||
--boards-url=<URL> URL for the boards transport.
|
||||
--mailru-url=<WEBLINK> Weblink for the mailru transport.
|
||||
--cupsonline-url=<URL> URL for the cupsonline transport.
|
||||
--oneme-token=<token> MAX auth token for the oneme transport.
|
||||
--oneme-uid=<uid> MAX user id for the oneme transport.
|
||||
--direct-dial=<addr> DirectTransport: exit host:port (client).
|
||||
@@ -591,24 +601,22 @@ DEPRECATED (removed in v2)
|
||||
specs = confTransports
|
||||
// Per-type URL flags still override config values.
|
||||
urls := map[string]string{
|
||||
"yandex": *yandexURL,
|
||||
"vyandex": *vyandexURL,
|
||||
"boards": *boardsURL,
|
||||
"mailru": *mailruURL,
|
||||
"cupsonline": *cupsonlineURL,
|
||||
"yandex": *yandexURL,
|
||||
"vyandex": *vyandexURL,
|
||||
}
|
||||
var buildErr error
|
||||
specs, buildErr = buildTransportSpecs(specs, urls, nil, *scriptDir, *scriptPubkey)
|
||||
if buildErr != nil {
|
||||
log.Fatalf("[Transport] sections: %v", buildErr)
|
||||
}
|
||||
specs = buildTransportSpecs(specs, urls, nil)
|
||||
} else if *transportsFlag != "" {
|
||||
parsed, err := parseTransportList(*transportsFlag)
|
||||
if err != nil {
|
||||
log.Fatalf("--transports: %v", err)
|
||||
}
|
||||
urls := map[string]string{
|
||||
"yandex": *yandexURL,
|
||||
"vyandex": *vyandexURL,
|
||||
"boards": *boardsURL,
|
||||
"mailru": *mailruURL,
|
||||
"cupsonline": *cupsonlineURL,
|
||||
"yandex": *yandexURL,
|
||||
"vyandex": *vyandexURL,
|
||||
}
|
||||
if globalDocUrl != "" && globalDocUrl != "http://#" && urls["yandex"] == "" {
|
||||
urls["yandex"] = globalDocUrl
|
||||
@@ -621,7 +629,11 @@ DEPRECATED (removed in v2)
|
||||
"is_exit": *role == roleExit,
|
||||
},
|
||||
}
|
||||
specs = buildTransportSpecs(parsed, urls, extra)
|
||||
var buildErr error
|
||||
specs, buildErr = buildTransportSpecs(parsed, urls, extra, *scriptDir, *scriptPubkey)
|
||||
if buildErr != nil {
|
||||
log.Fatalf("--transports: %v", buildErr)
|
||||
}
|
||||
} else {
|
||||
specs = []transportSpec{{
|
||||
Name: "primary",
|
||||
@@ -639,6 +651,13 @@ DEPRECATED (removed in v2)
|
||||
"dial": *directDial, "listen": *directListen, "is_exit": *role == roleExit,
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(*transportType, "script:") {
|
||||
expanded, err := expandScriptType(specs[0], *scriptDir, *scriptPubkey)
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
specs[0] = expanded
|
||||
}
|
||||
}
|
||||
|
||||
// Validate --codec with the multi-transport path. Session always uses
|
||||
@@ -781,8 +800,6 @@ DEPRECATED (removed in v2)
|
||||
// Legacy single-transport path (no negotiate, no multi).
|
||||
var inner transport.Transport
|
||||
switch *transportType {
|
||||
case "boards":
|
||||
inner = yandex.NewBoardsTransport(globalDocUrl, config)
|
||||
case "vyandex":
|
||||
t, err := newVolgaTransport(globalDocUrl, config)
|
||||
if err != nil {
|
||||
@@ -794,12 +811,22 @@ DEPRECATED (removed in v2)
|
||||
case "oneme":
|
||||
uidint, _ := strconv.ParseInt(maxUid, 10, 64)
|
||||
inner = oneme.NewOneMeTransport(*role == roleExit, maxToken, uidint, config)
|
||||
case "cupsonline":
|
||||
inner = cupsonline.NewCupsonlineTransport(globalDocUrl, config, *role != roleExit)
|
||||
case "mailru":
|
||||
inner = mailru.NewMailruDocsTransport(globalDocUrl, config)
|
||||
default:
|
||||
log.Fatalf("Unknown transport type: %s", *transportType)
|
||||
if strings.HasPrefix(*transportType, "script:") {
|
||||
expanded, err := expandScriptType(transportSpec{Type: *transportType}, *scriptDir, *scriptPubkey)
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
t, err := transportFactory(config)(&control.TransportConfig{
|
||||
Name: expanded.Name, Type: expanded.Type, URL: globalDocUrl, Params: expanded.Params,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("script transport: %v", err)
|
||||
}
|
||||
inner = t
|
||||
} else {
|
||||
log.Fatalf("Unknown transport type: %s", *transportType)
|
||||
}
|
||||
}
|
||||
|
||||
// Persist cookie exchanger for the legacy path.
|
||||
|
||||
@@ -2,18 +2,21 @@ package transport_test
|
||||
|
||||
import (
|
||||
"openflux/transport"
|
||||
"openflux/transport/cupsonline"
|
||||
"openflux/transport/mailru"
|
||||
"openflux/transport/script"
|
||||
"openflux/transport/yandex"
|
||||
)
|
||||
|
||||
// Compile-time guarantees that every HTTP-based transport implements
|
||||
// CookieExchanger. If any of these break, main.go's type assertion will
|
||||
// silently skip control wiring and cookie exchange will be dead.
|
||||
//
|
||||
// mailru, boards and cupsonline are script-only now (transport/script/js/
|
||||
// {mailru,boards,cupsonline}.js) - their CookieExchanger guarantee is the
|
||||
// generic script.ScriptTransport one below, not a type-specific assertion.
|
||||
var (
|
||||
_ transport.CookieExchanger = (*yandex.YandexDocsTransport)(nil)
|
||||
_ transport.CookieExchanger = (*yandex.YandexVolgaTransport)(nil)
|
||||
_ transport.CookieExchanger = (*yandex.BoardsTransport)(nil)
|
||||
_ transport.CookieExchanger = (*mailru.MailruDocsTransport)(nil)
|
||||
_ transport.CookieExchanger = (*cupsonline.CupsonlineTransport)(nil)
|
||||
// Every script transport gets this for free (transport/script's shared
|
||||
// cookiejar.Jar) - see ScriptTransport.FetchCookies/ApplyCookies.
|
||||
_ transport.CookieExchanger = (*script.ScriptTransport)(nil)
|
||||
)
|
||||
|
||||
+22
-8
@@ -6,10 +6,9 @@ import (
|
||||
|
||||
"openflux/transport"
|
||||
"openflux/transport/control"
|
||||
"openflux/transport/cupsonline"
|
||||
"openflux/transport/mailru"
|
||||
"openflux/transport/manager"
|
||||
"openflux/transport/oneme"
|
||||
"openflux/transport/script"
|
||||
"openflux/transport/yandex"
|
||||
)
|
||||
|
||||
@@ -41,12 +40,27 @@ func transportFactory(baseCfg transport.TransportConfig) manager.Factory {
|
||||
return yandex.NewYandexDocsTransport(cfg.URL, baseCfg), nil
|
||||
case "vyandex":
|
||||
return newVolgaTransport(cfg.URL, baseCfg)
|
||||
case "boards":
|
||||
return yandex.NewBoardsTransport(cfg.URL, baseCfg), nil
|
||||
case "mailru":
|
||||
return mailru.NewMailruDocsTransport(cfg.URL, baseCfg), nil
|
||||
case "cupsonline":
|
||||
return cupsonline.NewCupsonlineTransport(cfg.URL, baseCfg, false), nil
|
||||
case "script":
|
||||
// path: either a plain .js file (its detached path+".sig" must
|
||||
// verify) or a .flux package (script+manifest+icon signed
|
||||
// together - see transport/script/flux.go), picked by extension.
|
||||
// pubkey: hex ed25519 public key it must be signed with.
|
||||
// name: optional, defaults to the script's own info().name for
|
||||
// logging - the factory doesn't know that until Start().
|
||||
scriptPath, _ := cfg.Params["path"].(string)
|
||||
pubkeyHex, _ := cfg.Params["pubkey"].(string)
|
||||
name, _ := cfg.Params["name"].(string)
|
||||
if name == "" {
|
||||
name = "script"
|
||||
}
|
||||
if scriptPath == "" {
|
||||
return nil, fmt.Errorf("factory: script transport missing \"path\" param")
|
||||
}
|
||||
pubKey, err := script.DecodePublicKeyHex(pubkeyHex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("factory: script transport: %w", err)
|
||||
}
|
||||
return script.New(name, scriptPath, pubKey, cfg.URL, cfg.Params, baseCfg)
|
||||
case "oneme":
|
||||
token, _ := cfg.Params["token"].(string)
|
||||
uidStr, _ := cfg.Params["uid"].(string)
|
||||
|
||||
+85
-13
@@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -19,8 +21,13 @@ type transportSpec struct {
|
||||
Params map[string]interface{}
|
||||
}
|
||||
|
||||
// parseTransportList parses "direct:100,yandex:50,mailru:30".
|
||||
// Priority is optional; default is 50.
|
||||
// parseTransportList parses "direct:100,yandex:50,mailru:30" as well as a
|
||||
// script: reference, whose OWN type carries a colon: "script:mailru:30".
|
||||
// The priority, if present, is always the LAST colon-separated field - that
|
||||
// stays unambiguous even for "script:mailru" (no priority: "mailru" isn't a
|
||||
// number, so the whole string is kept as the type) vs "script:mailru:30"
|
||||
// (30 is the last field and does parse, so it's peeled off as the
|
||||
// priority). Priority is optional; default is 50.
|
||||
func parseTransportList(s string) ([]transportSpec, error) {
|
||||
var out []transportSpec
|
||||
for _, part := range strings.Split(s, ",") {
|
||||
@@ -28,27 +35,87 @@ func parseTransportList(s string) ([]transportSpec, error) {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
name, prioStr, hasPrio := strings.Cut(part, ":")
|
||||
typ := part
|
||||
prio := 50
|
||||
if hasPrio {
|
||||
v, err := strconv.Atoi(prioStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad priority %q: %w", prioStr, err)
|
||||
if i := strings.LastIndex(part, ":"); i >= 0 {
|
||||
if v, err := strconv.Atoi(part[i+1:]); err == nil {
|
||||
typ = part[:i]
|
||||
prio = v
|
||||
}
|
||||
prio = v
|
||||
}
|
||||
if typ == "" {
|
||||
return nil, fmt.Errorf("bad transport entry %q", part)
|
||||
}
|
||||
out = append(out, transportSpec{
|
||||
Name: name,
|
||||
Type: name, // by default the name and the type match
|
||||
Name: typ, // by default the name and the type match
|
||||
Type: typ,
|
||||
Priority: prio,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// expandScriptType rewrites a spec whose Type is a "script:<name>"
|
||||
// reference into the plain "script" type transport_factory.go's factory
|
||||
// case expects, filling Params from --script-dir/--script-pubkey. A spec
|
||||
// whose Type isn't a script: reference is returned unchanged - this is a
|
||||
// no-op for every native transport.
|
||||
func expandScriptType(spec transportSpec, scriptDir, pubkeyHex string) (transportSpec, error) {
|
||||
name, ok := strings.CutPrefix(spec.Type, "script:")
|
||||
if !ok {
|
||||
return spec, nil
|
||||
}
|
||||
if name == "" {
|
||||
return spec, fmt.Errorf("transport %q: script: needs a name, e.g. script:mailru", spec.Type)
|
||||
}
|
||||
if scriptDir == "" {
|
||||
return spec, fmt.Errorf("transport %q: --script-dir is required for script: transports", spec.Type)
|
||||
}
|
||||
if pubkeyHex == "" {
|
||||
return spec, fmt.Errorf("transport %q: --script-pubkey is required for script: transports", spec.Type)
|
||||
}
|
||||
scriptFile, err := resolveScriptFile(scriptDir, name)
|
||||
if err != nil {
|
||||
return spec, err
|
||||
}
|
||||
params := map[string]interface{}{
|
||||
"path": scriptFile,
|
||||
"pubkey": pubkeyHex,
|
||||
"name": name,
|
||||
}
|
||||
// Let any params already on the spec (from a .conf [Transport] section,
|
||||
// or the oneme/direct-style --extra map) add to or override these.
|
||||
for k, v := range spec.Params {
|
||||
params[k] = v
|
||||
}
|
||||
if spec.Name == spec.Type {
|
||||
spec.Name = name
|
||||
}
|
||||
spec.Type = "script"
|
||||
spec.Params = params
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
// resolveScriptFile finds a script: transport's loadable file, preferring
|
||||
// the packaged <name>.flux (script + signed manifest/icon together) and
|
||||
// falling back to the plain <name>.js (+ detached .sig) every script
|
||||
// shipped as before .flux existed.
|
||||
func resolveScriptFile(scriptDir, name string) (string, error) {
|
||||
flux := filepath.Join(scriptDir, name+".flux")
|
||||
if _, err := os.Stat(flux); err == nil {
|
||||
return flux, nil
|
||||
}
|
||||
js := filepath.Join(scriptDir, name+".js")
|
||||
if _, err := os.Stat(js); err == nil {
|
||||
return js, nil
|
||||
}
|
||||
return "", fmt.Errorf("script %q: neither %s nor %s found in %s", name, name+".flux", name+".js", scriptDir)
|
||||
}
|
||||
|
||||
// buildTransportSpecs assembles the config for each name in the list, using
|
||||
// per-type URL flags (--yandex-url, --mailru-url, ...) and global settings.
|
||||
func buildTransportSpecs(specs []transportSpec, urls map[string]string, extra map[string]map[string]interface{}) []transportSpec {
|
||||
// per-type URL flags (--yandex-url, --mailru-url, ...), global settings,
|
||||
// and (for a script: entry) --script-dir/--script-pubkey.
|
||||
func buildTransportSpecs(specs []transportSpec, urls map[string]string, extra map[string]map[string]interface{}, scriptDir, scriptPubkey string) ([]transportSpec, error) {
|
||||
for i := range specs {
|
||||
// Only a flag that was set overrides: the unset ones are "" and
|
||||
// would wipe the URL of a .conf [Transport] section.
|
||||
@@ -58,8 +125,13 @@ func buildTransportSpecs(specs []transportSpec, urls map[string]string, extra ma
|
||||
if p, ok := extra[specs[i].Type]; ok {
|
||||
specs[i].Params = p
|
||||
}
|
||||
expanded, err := expandScriptType(specs[i], scriptDir, scriptPubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
specs[i] = expanded
|
||||
}
|
||||
return specs
|
||||
return specs, nil
|
||||
}
|
||||
|
||||
// makeRawTransport builds a raw transport from a spec without the Manager's
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseTransportListScriptReference(t *testing.T) {
|
||||
specs, err := parseTransportList("direct:100,script:mailru:30,script:boards")
|
||||
if err != nil {
|
||||
t.Fatalf("parseTransportList: %v", err)
|
||||
}
|
||||
if len(specs) != 3 {
|
||||
t.Fatalf("got %d specs, want 3", len(specs))
|
||||
}
|
||||
if specs[0].Type != "direct" || specs[0].Priority != 100 {
|
||||
t.Fatalf("specs[0] = %+v", specs[0])
|
||||
}
|
||||
if specs[1].Type != "script:mailru" || specs[1].Priority != 30 {
|
||||
t.Fatalf("specs[1] = %+v, want Type=script:mailru Priority=30", specs[1])
|
||||
}
|
||||
// No explicit priority - the whole string is the type, default priority.
|
||||
if specs[2].Type != "script:boards" || specs[2].Priority != 50 {
|
||||
t.Fatalf("specs[2] = %+v, want Type=script:boards Priority=50", specs[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandScriptTypeRequiresDirAndPubkey(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "mailru.js"), []byte("//"), 0o644); err != nil {
|
||||
t.Fatalf("seed mailru.js: %v", err)
|
||||
}
|
||||
spec := transportSpec{Name: "script:mailru", Type: "script:mailru", Priority: 30}
|
||||
|
||||
if _, err := expandScriptType(spec, "", "somekeyhex"); err == nil {
|
||||
t.Fatal("expected an error with no --script-dir")
|
||||
}
|
||||
if _, err := expandScriptType(spec, dir, ""); err == nil {
|
||||
t.Fatal("expected an error with no --script-pubkey")
|
||||
}
|
||||
|
||||
got, err := expandScriptType(spec, dir, "somekeyhex")
|
||||
if err != nil {
|
||||
t.Fatalf("expandScriptType: %v", err)
|
||||
}
|
||||
if got.Type != "script" {
|
||||
t.Fatalf("Type = %q, want %q", got.Type, "script")
|
||||
}
|
||||
if got.Name != "mailru" {
|
||||
t.Fatalf("Name = %q, want %q", got.Name, "mailru")
|
||||
}
|
||||
if want := filepath.Join(dir, "mailru.js"); got.Params["path"] != want {
|
||||
t.Fatalf("Params[path] = %v, want %v", got.Params["path"], want)
|
||||
}
|
||||
if got.Params["pubkey"] != "somekeyhex" {
|
||||
t.Fatalf("Params[pubkey] = %v, want somekeyhex", got.Params["pubkey"])
|
||||
}
|
||||
if got.Params["name"] != "mailru" {
|
||||
t.Fatalf("Params[name] = %v, want mailru", got.Params["name"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandScriptTypeMissingScriptErrors(t *testing.T) {
|
||||
dir := t.TempDir() // empty - neither mailru.flux nor mailru.js exists
|
||||
spec := transportSpec{Name: "script:mailru", Type: "script:mailru", Priority: 30}
|
||||
if _, err := expandScriptType(spec, dir, "somekeyhex"); err == nil {
|
||||
t.Fatal("expected an error when neither <name>.flux nor <name>.js exists in --script-dir")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandScriptTypePrefersFluxOverJS(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "mailru.js"), []byte("//"), 0o644); err != nil {
|
||||
t.Fatalf("seed mailru.js: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "mailru.flux"), []byte("PK"), 0o644); err != nil {
|
||||
t.Fatalf("seed mailru.flux: %v", err)
|
||||
}
|
||||
spec := transportSpec{Name: "script:mailru", Type: "script:mailru", Priority: 30}
|
||||
got, err := expandScriptType(spec, dir, "somekeyhex")
|
||||
if err != nil {
|
||||
t.Fatalf("expandScriptType: %v", err)
|
||||
}
|
||||
if want := filepath.Join(dir, "mailru.flux"); got.Params["path"] != want {
|
||||
t.Fatalf("Params[path] = %v, want %v (a .flux package should win over a plain .js)", got.Params["path"], want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandScriptTypeIgnoresNonScriptSpecs(t *testing.T) {
|
||||
spec := transportSpec{Name: "mailru", Type: "mailru", Priority: 50}
|
||||
got, err := expandScriptType(spec, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("expandScriptType: %v", err)
|
||||
}
|
||||
if got.Name != spec.Name || got.Type != spec.Type || got.Priority != spec.Priority || got.Params != nil {
|
||||
t.Fatalf("non-script spec was modified: got %+v, want %+v", got, spec)
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,10 @@ func TestBuildTransportSpecsKeepsConfURL(t *testing.T) {
|
||||
{Name: "yandex", Type: "yandex", URL: "https://disk.yandex.ru/i/conf"},
|
||||
}
|
||||
urls := map[string]string{"vyandex": "", "yandex": "https://disk.yandex.ru/i/flag"}
|
||||
got := buildTransportSpecs(specs, urls, nil)
|
||||
got, err := buildTransportSpecs(specs, urls, nil, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("buildTransportSpecs: %v", err)
|
||||
}
|
||||
if got[0].URL != "https://docs.yandex.ru/edit/d/conf" {
|
||||
t.Fatalf("unset flag wiped the conf URL: %q", got[0].URL)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user