@@ -17,6 +17,7 @@ import (
1717 "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
1818 "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
1919 "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff"
20+ "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/synthesizer"
2021 coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
2122 log "github.com/sirupsen/logrus"
2223)
@@ -75,6 +76,7 @@ func (w *Watcher) reloadClients(rescanAuth bool, affectedOAuthProviders []string
7576
7677 w .lastAuthHashes = make (map [string ]string )
7778 w .lastAuthContents = make (map [string ]* coreauth.Auth )
79+ w .fileAuthsByPath = make (map [string ]map [string ]* coreauth.Auth )
7880 if resolvedAuthDir , errResolveAuthDir := util .ResolveAuthDir (cfg .AuthDir ); errResolveAuthDir != nil {
7981 log .Errorf ("failed to resolve auth directory for hash cache: %v" , errResolveAuthDir )
8082 } else if resolvedAuthDir != "" {
@@ -92,6 +94,24 @@ func (w *Watcher) reloadClients(rescanAuth bool, affectedOAuthProviders []string
9294 if errParse := json .Unmarshal (data , & auth ); errParse == nil {
9395 w .lastAuthContents [normalizedPath ] = & auth
9496 }
97+ ctx := & synthesizer.SynthesisContext {
98+ Config : cfg ,
99+ AuthDir : resolvedAuthDir ,
100+ Now : time .Now (),
101+ IDGenerator : synthesizer .NewStableIDGenerator (),
102+ }
103+ if generated := synthesizer .SynthesizeAuthFile (ctx , path , data ); len (generated ) > 0 {
104+ pathAuths := make (map [string ]* coreauth.Auth , len (generated ))
105+ for _ , a := range generated {
106+ if a == nil || strings .TrimSpace (a .ID ) == "" {
107+ continue
108+ }
109+ pathAuths [a .ID ] = a .Clone ()
110+ }
111+ if len (pathAuths ) > 0 {
112+ w .fileAuthsByPath [normalizedPath ] = pathAuths
113+ }
114+ }
95115 }
96116 }
97117 return nil
@@ -143,13 +163,14 @@ func (w *Watcher) addOrUpdateClient(path string) {
143163 }
144164
145165 w .clientsMutex .Lock ()
146-
147- cfg := w .config
148- if cfg == nil {
166+ if w .config == nil {
149167 log .Error ("config is nil, cannot add or update client" )
150168 w .clientsMutex .Unlock ()
151169 return
152170 }
171+ if w .fileAuthsByPath == nil {
172+ w .fileAuthsByPath = make (map [string ]map [string ]* coreauth.Auth )
173+ }
153174 if prev , ok := w .lastAuthHashes [normalized ]; ok && prev == curHash {
154175 log .Debugf ("auth file unchanged (hash match), skipping reload: %s" , filepath .Base (path ))
155176 w .clientsMutex .Unlock ()
@@ -177,34 +198,85 @@ func (w *Watcher) addOrUpdateClient(path string) {
177198 }
178199 w .lastAuthContents [normalized ] = & newAuth
179200
180- w .clientsMutex .Unlock () // Unlock before the callback
181-
182- w .refreshAuthState (false )
201+ oldByID := make (map [string ]* coreauth.Auth )
202+ if existing := w .fileAuthsByPath [normalized ]; len (existing ) > 0 {
203+ for id , a := range existing {
204+ oldByID [id ] = a
205+ }
206+ }
183207
184- if w .reloadCallback != nil {
185- log .Debugf ("triggering server update callback after add/update" )
186- w .reloadCallback (cfg )
208+ // Build synthesized auth entries for this single file only.
209+ sctx := & synthesizer.SynthesisContext {
210+ Config : w .config ,
211+ AuthDir : w .authDir ,
212+ Now : time .Now (),
213+ IDGenerator : synthesizer .NewStableIDGenerator (),
214+ }
215+ generated := synthesizer .SynthesizeAuthFile (sctx , path , data )
216+ newByID := make (map [string ]* coreauth.Auth )
217+ for _ , a := range generated {
218+ if a == nil || strings .TrimSpace (a .ID ) == "" {
219+ continue
220+ }
221+ newByID [a .ID ] = a .Clone ()
222+ }
223+ if len (newByID ) > 0 {
224+ w .fileAuthsByPath [normalized ] = newByID
225+ } else {
226+ delete (w .fileAuthsByPath , normalized )
187227 }
228+ updates := w .computePerPathUpdatesLocked (oldByID , newByID )
229+ w .clientsMutex .Unlock ()
230+
188231 w .persistAuthAsync (fmt .Sprintf ("Sync auth %s" , filepath .Base (path )), path )
232+ w .dispatchAuthUpdates (updates )
189233}
190234
191235func (w * Watcher ) removeClient (path string ) {
192236 normalized := w .normalizeAuthPath (path )
193237 w .clientsMutex .Lock ()
194-
195- cfg := w .config
238+ oldByID := make (map [string ]* coreauth.Auth )
239+ if existing := w .fileAuthsByPath [normalized ]; len (existing ) > 0 {
240+ for id , a := range existing {
241+ oldByID [id ] = a
242+ }
243+ }
196244 delete (w .lastAuthHashes , normalized )
197245 delete (w .lastAuthContents , normalized )
246+ delete (w .fileAuthsByPath , normalized )
198247
199- w .clientsMutex .Unlock () // Release the lock before the callback
248+ updates := w .computePerPathUpdatesLocked (oldByID , map [string ]* coreauth.Auth {})
249+ w .clientsMutex .Unlock ()
200250
201- w .refreshAuthState (false )
251+ w .persistAuthAsync (fmt .Sprintf ("Remove auth %s" , filepath .Base (path )), path )
252+ w .dispatchAuthUpdates (updates )
253+ }
202254
203- if w . reloadCallback != nil {
204- log . Debugf ( "triggering server update callback after removal" )
205- w .reloadCallback ( cfg )
255+ func ( w * Watcher ) computePerPathUpdatesLocked ( oldByID , newByID map [ string ] * coreauth. Auth ) [] AuthUpdate {
256+ if w . currentAuths == nil {
257+ w .currentAuths = make ( map [ string ] * coreauth. Auth )
206258 }
207- w .persistAuthAsync (fmt .Sprintf ("Remove auth %s" , filepath .Base (path )), path )
259+ updates := make ([]AuthUpdate , 0 , len (oldByID )+ len (newByID ))
260+ for id , newAuth := range newByID {
261+ existing , ok := w .currentAuths [id ]
262+ if ! ok {
263+ w .currentAuths [id ] = newAuth .Clone ()
264+ updates = append (updates , AuthUpdate {Action : AuthUpdateActionAdd , ID : id , Auth : newAuth .Clone ()})
265+ continue
266+ }
267+ if ! authEqual (existing , newAuth ) {
268+ w .currentAuths [id ] = newAuth .Clone ()
269+ updates = append (updates , AuthUpdate {Action : AuthUpdateActionModify , ID : id , Auth : newAuth .Clone ()})
270+ }
271+ }
272+ for id := range oldByID {
273+ if _ , stillExists := newByID [id ]; stillExists {
274+ continue
275+ }
276+ delete (w .currentAuths , id )
277+ updates = append (updates , AuthUpdate {Action : AuthUpdateActionDelete , ID : id })
278+ }
279+ return updates
208280}
209281
210282func (w * Watcher ) loadFileClients (cfg * config.Config ) int {
0 commit comments