fix(web): install the share-link provider before the panel serves (#6914)

The panel started answering requests before the share-link generator was
registered, so an "all links", client sub-links or Telegram link request that
arrived during start-up got "sub link provider not registered". NewServer now
takes the provider and installs it before anything listens, and the provider
registry is guarded by a lock, since start-up writes it while requests read it.
This commit is contained in:
25f2002654
2026-10-24 09:20:46 +05:30
committed by GitHub
parent ad470681ef
commit d2a0216e01
9 changed files with 125 additions and 20 deletions
+24 -7
View File
@@ -1,6 +1,8 @@
package service
import (
"sync"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
)
@@ -11,35 +13,50 @@ type SubLinkProvider interface {
LinksForInbounds(host string, inbounds []*model.Inbound) []string
}
var registeredSubLinkProvider SubLinkProvider
var (
subLinkProviderMu sync.RWMutex
registeredSubLinkProvider SubLinkProvider
)
func RegisterSubLinkProvider(p SubLinkProvider) {
subLinkProviderMu.Lock()
defer subLinkProviderMu.Unlock()
registeredSubLinkProvider = p
}
// subLinkProvider guards the registry: startup writes it while link requests read it.
func subLinkProvider() SubLinkProvider {
subLinkProviderMu.RLock()
defer subLinkProviderMu.RUnlock()
return registeredSubLinkProvider
}
func (s *InboundService) GetSubLinks(host, subId string) ([]string, error) {
if registeredSubLinkProvider == nil {
provider := subLinkProvider()
if provider == nil {
return nil, common.NewError("sub link provider not registered")
}
return registeredSubLinkProvider.SubLinksForSubId(host, subId)
return provider.SubLinksForSubId(host, subId)
}
func (s *InboundService) GetAllInboundLinks(host string, userId int) ([]string, error) {
if registeredSubLinkProvider == nil {
provider := subLinkProvider()
if provider == nil {
return nil, common.NewError("sub link provider not registered")
}
inbounds, err := s.GetInbounds(userId)
if err != nil {
return nil, err
}
return registeredSubLinkProvider.LinksForInbounds(host, inbounds), nil
return provider.LinksForInbounds(host, inbounds), nil
}
func (s *InboundService) GetAllClientLinks(host string, email string) ([]string, error) {
if email == "" {
return nil, common.NewError("client email is required")
}
if registeredSubLinkProvider == nil {
provider := subLinkProvider()
if provider == nil {
return nil, common.NewError("sub link provider not registered")
}
rec, err := s.clientService.GetRecordByEmail(nil, email)
@@ -56,7 +73,7 @@ func (s *InboundService) GetAllClientLinks(host string, email string) ([]string,
if getErr != nil {
return nil, getErr
}
links = append(links, registeredSubLinkProvider.LinksForClient(host, inbound, email)...)
links = append(links, provider.LinksForClient(host, inbound, email)...)
}
return links, nil
}
@@ -0,0 +1,44 @@
package service
import (
"sync"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
type fakeSubLinkProvider struct {
links []string
}
func (f fakeSubLinkProvider) SubLinksForSubId(host, subId string) ([]string, error) {
return f.links, nil
}
func (f fakeSubLinkProvider) LinksForClient(host string, inbound *model.Inbound, email string) []string {
return f.links
}
func (f fakeSubLinkProvider) LinksForInbounds(host string, inbounds []*model.Inbound) []string {
return f.links
}
// Startup writes the provider registry while link requests read it; under -race an
// unguarded registry fails here.
func TestSubLinkProviderRegistrationIsRaceFree(t *testing.T) {
t.Cleanup(func() { RegisterSubLinkProvider(nil) })
var wg sync.WaitGroup
for range 8 {
wg.Add(2)
go func() {
defer wg.Done()
RegisterSubLinkProvider(fakeSubLinkProvider{links: []string{"vless://example"}})
}()
go func() {
defer wg.Done()
_, _ = (&InboundService{}).GetSubLinks("panel.example", "subid")
}()
}
wg.Wait()
}