Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion core/mitm/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (s *Server) intercept(w http.ResponseWriter, r *http.Request, tool Tool) {
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+s.apiKey)
resp, err := http.DefaultClient.Do(req)
resp, err := gatewayClient.Do(req)
if err != nil {
s.passthroughBody(w, r, body)
return
Expand Down Expand Up @@ -201,6 +201,8 @@ var passthroughClient = &http.Client{
},
}

var gatewayClient = &http.Client{Timeout: 5 * time.Minute}

func copyHeaders(dst, src http.Header) {
for k, vs := range src {
if strings.EqualFold(k, "Connection") || strings.EqualFold(k, "Transfer-Encoding") {
Expand Down
78 changes: 57 additions & 21 deletions core/sync/items.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,17 +223,25 @@ type syncCombo struct {

// --- apply: pulled items → local rows ---

type fullSyncLookup struct {
accounts map[string]bool // shortHash(secret+creds) → true
keys map[string]bool // shortHash(secret) → true
proxies map[string]bool // shortHash(scheme+host+port+username) → true
prefixes map[string]bool // custom provider prefix → true
}

// applyFullItem applies one non-playlist pulled item. Returns true if handled.
func (m *Manager) applyFullItem(ctx context.Context, ri item) bool {
// lookup maps, when non-nil, provide O(1) dedup instead of scanning the store.
func (m *Manager) applyFullItem(ctx context.Context, ri item, lk *fullSyncLookup) bool {
switch ri.Type {
case typeCustomProvider:
return m.applyCustomProvider(ctx, ri)
return m.applyCustomProvider(ctx, ri, lk)
case typeAccount:
return m.applyAccount(ctx, ri)
return m.applyAccount(ctx, ri, lk)
case typeAPIKey:
return m.applyAPIKey(ctx, ri)
return m.applyAPIKey(ctx, ri, lk)
case typeProxy:
return m.applyProxy(ctx, ri)
return m.applyProxy(ctx, ri, lk)
case typeAlias:
return m.applyAlias(ctx, ri)
case typeCombo:
Expand All @@ -242,7 +250,7 @@ func (m *Manager) applyFullItem(ctx context.Context, ri item) bool {
return false
}

func (m *Manager) applyCustomProvider(ctx context.Context, ri item) bool {
func (m *Manager) applyCustomProvider(ctx context.Context, ri item, lk *fullSyncLookup) bool {
if m.custom == nil {
return false
}
Expand Down Expand Up @@ -270,10 +278,16 @@ func (m *Manager) applyCustomProvider(ctx context.Context, ri item) bool {
if json.Unmarshal([]byte(ri.Payload), &cp) != nil {
return false
}
// Upsert by prefix: skip if we already have this prefix, else create + register.
for _, e := range existing {
if e.Prefix == cp.Prefix {
return true // already present (LWW: keep local)
if lk != nil && lk.prefixes != nil {
if lk.prefixes[cp.Prefix] {
return true
}
} else {
// Upsert by prefix: skip if we already have this prefix, else create + register.
for _, e := range existing {
if e.Prefix == cp.Prefix {
return true // already present (LWW: keep local)
}
}
}
id, err := m.custom.Create(ctx, cp)
Expand All @@ -287,7 +301,7 @@ func (m *Manager) applyCustomProvider(ctx context.Context, ri item) bool {
return true
}

func (m *Manager) applyAccount(ctx context.Context, ri item) bool {
func (m *Manager) applyAccount(ctx context.Context, ri item, lk *fullSyncLookup) bool {
if m.accounts == nil {
return false
}
Expand All @@ -309,6 +323,14 @@ func (m *Manager) applyAccount(ctx context.Context, ri item) bool {
if !ri.Encrypted {
return false
}
// Fast-path: skip decryption for accounts we already have locally.
// The item id embeds shortHash(secret+creds) which matches the dedup
// map key, so we check before any crypto or SQLite work.
if lk != nil && lk.accounts != nil {
if _, hash, ok := parseAccountID(ri.ItemID); ok && lk.accounts[hash] {
return true
}
}
key := m.credKey(ctx)
if key == nil {
return false
Expand All @@ -321,19 +343,21 @@ func (m *Manager) applyAccount(ctx context.Context, ri item) bool {
if json.Unmarshal(raw, &sa) != nil {
return false
}
// Dedup: skip if an account with the same secret+creds already exists.
existing, _ := m.accounts.List(ctx, sa.Provider)
target := shortHash(sa.Secret + fmt.Sprint(sa.Creds))
for _, e := range existing {
if shortHash(e.Secret+fmt.Sprint(e.Creds)) == target {
return true
// Fallback dedup when no lookup map was provided (legacy path).
if lk == nil || lk.accounts == nil {
target := shortHash(sa.Secret + fmt.Sprint(sa.Creds))
existing, _ := m.accounts.List(ctx, sa.Provider)
for _, e := range existing {
if shortHash(e.Secret+fmt.Sprint(e.Creds)) == target {
return true
}
}
}
_, _ = m.accounts.Add(ctx, store.Account{Provider: sa.Provider, Label: sa.Label, Secret: sa.Secret, Creds: sa.Creds, Status: sa.Status, Disabled: sa.Disabled})
return true
}

func (m *Manager) applyProxy(ctx context.Context, ri item) bool {
func (m *Manager) applyProxy(ctx context.Context, ri item, lk *fullSyncLookup) bool {
if m.proxies == nil {
return false
}
Expand Down Expand Up @@ -366,6 +390,12 @@ func (m *Manager) applyProxy(ctx context.Context, ri item) bool {
if json.Unmarshal(raw, &sp) != nil {
return false
}
target := shortHash(sp.Scheme + sp.Host + fmt.Sprint(sp.Port) + sp.Username)
if lk != nil && lk.proxies != nil {
if lk.proxies[target] {
return true
}
}
// Add upserts on identity, so this is idempotent whether or not it exists.
_, _ = m.proxies.Add(ctx, store.Proxy{
Label: sp.Label, Scheme: sp.Scheme, Host: sp.Host, Port: sp.Port,
Expand All @@ -387,7 +417,7 @@ func parseAccountID(id string) (provider, hash string, ok bool) {
return rest[:i], rest[i+1:], true
}

func (m *Manager) applyAPIKey(ctx context.Context, ri item) bool {
func (m *Manager) applyAPIKey(ctx context.Context, ri item, lk *fullSyncLookup) bool {
if m.keys == nil {
return false
}
Expand Down Expand Up @@ -421,8 +451,14 @@ func (m *Manager) applyAPIKey(ctx context.Context, ri item) bool {
if json.Unmarshal(raw, &sk) != nil {
return false
}
if existing, _ := m.keys.BySecret(ctx, sk.Secret); existing != nil {
return true // already have it
if lk != nil && lk.keys != nil {
if lk.keys[shortHash(sk.Secret)] {
return true
}
} else {
if existing, _ := m.keys.BySecret(ctx, sk.Secret); existing != nil {
return true // already have it
}
}
_, _ = m.keys.Add(ctx, store.APIKey{Label: sk.Label, Secret: sk.Secret, TokenLimit: sk.TokenLimit, MaxConcurrent: sk.MaxConcurrent, Enabled: sk.Enabled})
return true
Expand Down
14 changes: 11 additions & 3 deletions core/sync/presence.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,28 @@ func (m *Manager) BrowserDisconnected() {
close(presenceStop)
presenceStop = nil
// Best-effort "going offline" so the server doesn't wait for the TTL.
go m.Presence(context.Background(), false)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
go func() {
defer cancel()
m.Presence(ctx, false)
}()
}
}

func (m *Manager) presenceLoop(stop <-chan struct{}) {
m.Presence(context.Background(), true) // immediate first beat
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
m.Presence(ctx, true) // immediate first beat
cancel()
t := time.NewTicker(presenceInterval)
defer t.Stop()
for {
select {
case <-stop:
return
case <-t.C:
m.Presence(context.Background(), true)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
m.Presence(ctx, true)
cancel()
}
}
}
43 changes: 41 additions & 2 deletions core/sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ func (m *Manager) PublicProfile(ctx context.Context, id string) (string, error)
// UserByName resolves a username to a user id (for @mention profile links).
func (m *Manager) UserByName(ctx context.Context, name string) (string, error) {
var raw json.RawMessage
if err := m.call(ctx, http.MethodGet, "/users/by-name/"+name, nil, &raw); err != nil {
if err := m.call(ctx, http.MethodGet, "/users/by-name/"+url.PathEscape(name), nil, &raw); err != nil {
return "", err
}
return string(raw), nil
Expand Down Expand Up @@ -1529,10 +1529,49 @@ func (m *Manager) Sync(ctx context.Context) (pushed, pulled int, err error) {
if err := m.call(ctx, http.MethodGet, "/sync/pull?since="+fmt.Sprint(cursor), nil, &pull); err != nil {
return pushed, 0, err
}

// Pre-build dedup lookup maps so each pulled item does O(1) checks
// instead of scanning the entire store per item (O(n^2) at 1300+ accounts).
var lk fullSyncLookup
if m.hasFullSync(ctx) {
if m.accounts != nil {
if all, err := m.accounts.List(ctx, ""); err == nil {
lk.accounts = make(map[string]bool, len(all))
for _, a := range all {
lk.accounts[shortHash(a.Secret+fmt.Sprint(a.Creds))] = true
}
}
}
if m.keys != nil {
if all, err := m.keys.List(ctx); err == nil {
lk.keys = make(map[string]bool, len(all))
for _, k := range all {
lk.keys[shortHash(k.Secret)] = true
}
}
}
if m.proxies != nil {
if all, err := m.proxies.List(ctx); err == nil {
lk.proxies = make(map[string]bool, len(all))
for _, p := range all {
lk.proxies[shortHash(p.Scheme+p.Host+fmt.Sprint(p.Port)+p.Username)] = true
}
}
}
if m.custom != nil {
if all, err := m.custom.List(ctx); err == nil {
lk.prefixes = make(map[string]bool, len(all))
for _, cp := range all {
lk.prefixes[cp.Prefix] = true
}
}
}
}

for _, ri := range pull.Items {
if ri.Type != typePlaylist {
// Full-sync types (accounts/keys/aliases/custom providers).
if m.applyFullItem(ctx, ri) {
if m.applyFullItem(ctx, ri, &lk) {
pulled++
}
continue
Expand Down
4 changes: 4 additions & 0 deletions server/handlers/api_debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"runtime"
"runtime/debug"
"sync"
"time"

"github.com/shirou/gopsutil/v4/process"
Expand All @@ -15,6 +16,7 @@ type Debug struct {
version string
started time.Time
proc *process.Process
mu sync.Mutex
}

func NewDebug(version string, started time.Time) *Debug {
Expand All @@ -35,12 +37,14 @@ func (h *Debug) Get(w http.ResponseWriter, _ *http.Request) {
cpuPct := 0.0
var rss uint64
if h.proc != nil {
h.mu.Lock()
if v, err := h.proc.Percent(0); err == nil {
cpuPct = v
}
if mi, err := h.proc.MemoryInfo(); err == nil && mi != nil {
rss = mi.RSS
}
h.mu.Unlock()
}

lastGC := ""
Expand Down