mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-24 20:07:13 +00:00
bd6a6aba43
* feat(pia): add login-and-add WireGuard outbounds (#2) * fix(pia): keep PIA outbounds identifiable after the editor strips hostname The outbound editor drops piaHostname, so last-segment matching failed for hyphenated servers. Identify rows by the computed tag, re-encrypt stored tokens onto the active key, skip unusable catalog rows, and always release the catalog refresh latch.
87 lines
1.9 KiB
Go
87 lines
1.9 KiB
Go
package pia
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Catalog struct {
|
|
Source ServerListSource
|
|
CacheTTL time.Duration
|
|
Now func() time.Time
|
|
|
|
mu sync.Mutex
|
|
cached []Region
|
|
schema string
|
|
verified bool
|
|
fetchedAt time.Time
|
|
refreshing chan struct{}
|
|
}
|
|
|
|
func NewCatalog(source ServerListSource) *Catalog {
|
|
return &Catalog{Source: source, CacheTTL: DefaultCatalogFreshTTL, Now: time.Now}
|
|
}
|
|
|
|
func (c *Catalog) ListRegions(ctx context.Context) ([]Region, string, error) {
|
|
for {
|
|
c.mu.Lock()
|
|
age := c.Now().Sub(c.fetchedAt)
|
|
if len(c.cached) > 0 && c.verified && c.CacheTTL > 0 && age >= 0 && age < c.CacheTTL {
|
|
regions, schema := cloneRegions(c.cached), c.schema
|
|
c.mu.Unlock()
|
|
return regions, schema, nil
|
|
}
|
|
if wait := c.refreshing; wait != nil {
|
|
c.mu.Unlock()
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, "", ctx.Err()
|
|
case <-wait:
|
|
continue
|
|
}
|
|
}
|
|
done := make(chan struct{})
|
|
c.refreshing = done
|
|
c.mu.Unlock()
|
|
return c.fetchAndPublish(ctx, done)
|
|
}
|
|
}
|
|
|
|
func (c *Catalog) fetchAndPublish(ctx context.Context, done chan struct{}) ([]Region, string, error) {
|
|
defer func() {
|
|
c.mu.Lock()
|
|
c.refreshing = nil
|
|
close(done)
|
|
c.mu.Unlock()
|
|
}()
|
|
snapshot, err := c.Source.Fetch(ctx)
|
|
var regions []Region
|
|
var schema string
|
|
if err == nil && !snapshot.SignatureVerified {
|
|
err = NewError(CodeCatalogSignatureInvalid, "The PIA region list was not signature-verified.")
|
|
}
|
|
if err == nil {
|
|
regions, schema, err = ParseServerList(snapshot.Payload, snapshot.SchemaHint)
|
|
}
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
c.mu.Lock()
|
|
c.cached = cloneRegions(regions)
|
|
c.schema = schema
|
|
c.verified = true
|
|
c.fetchedAt = c.Now()
|
|
c.mu.Unlock()
|
|
return cloneRegions(regions), schema, nil
|
|
}
|
|
|
|
func cloneRegions(regions []Region) []Region {
|
|
result := make([]Region, len(regions))
|
|
for i, region := range regions {
|
|
result[i] = region
|
|
result[i].WireGuard = append([]WireGuardServer(nil), region.WireGuard...)
|
|
}
|
|
return result
|
|
}
|