@@ -26,11 +26,16 @@ const (
2626// ErrNetworkAlreadyExists is returned when trying to add a network with a name that already exists
2727var ErrNetworkAlreadyExists = errors .New ("network already exists" )
2828
29+ // NetworkChangeCallback is a function type called when the network changes.
30+ type NetworkChangeCallback func ()
31+
2932// MSConfigManager represents a manager for network configurations.
3033type MSConfigManager struct {
31- Network NetworkConfig
32- configMutex sync.RWMutex
33- stopRefresh func ()
34+ Network NetworkConfig
35+ configMutex sync.RWMutex
36+ stopRefresh func ()
37+ onNetworkChange NetworkChangeCallback
38+ networkChangeMutex sync.RWMutex
3439}
3540
3641// NetworkManager is an alias for backward compatibility
@@ -130,7 +135,14 @@ func newMSConfigManager() (*MSConfigManager, error) {
130135}
131136
132137func (n * MSConfigManager ) CurrentNetwork () * RPCInfos {
133- return n .Network .currentNetwork
138+ n .configMutex .RLock ()
139+ defer n .configMutex .RUnlock ()
140+ if n .Network .currentNetwork == nil {
141+ return nil
142+ }
143+ // Return a copy to avoid data races when caller accesses fields after lock release
144+ networkCopy := * n .Network .currentNetwork
145+ return & networkCopy
134146}
135147
136148// Networks returns a slice of available network names (for backward compatibility)
@@ -145,12 +157,28 @@ func (n *MSConfigManager) Networks() *[]string {
145157 return & options
146158}
147159
160+ // GetNetworkInfos returns a thread-safe copy of all network configurations.
161+ func (n * MSConfigManager ) GetNetworkInfos () []RPCInfos {
162+ n .configMutex .RLock ()
163+ defer n .configMutex .RUnlock ()
164+
165+ networks := make ([]RPCInfos , len (n .Network .Networks ))
166+ copy (networks , n .Network .Networks )
167+ return networks
168+ }
169+
170+ // SetNetworkChangeCallback sets a callback function that will be called whenever the network changes.
171+ func (n * MSConfigManager ) SetNetworkChangeCallback (callback NetworkChangeCallback ) {
172+ n .networkChangeMutex .Lock ()
173+ defer n .networkChangeMutex .Unlock ()
174+ n .onNetworkChange = callback
175+ }
176+
148177// SwitchNetwork switches the current network configuration to the specified network.
149178// rpcName: The name of the network configuration to switch to.
150179// Returns any error encountered during the switch operation.
151180func (n * MSConfigManager ) SwitchNetwork (rpcName string ) error {
152181 n .configMutex .Lock ()
153- defer n .configMutex .Unlock ()
154182
155183 // Find the network with the specified name
156184 var targetNetwork * RPCInfos
@@ -162,6 +190,7 @@ func (n *MSConfigManager) SwitchNetwork(rpcName string) error {
162190 }
163191
164192 if targetNetwork == nil {
193+ n .configMutex .Unlock ()
165194 return fmt .Errorf ("unknown network: %s" , rpcName )
166195 }
167196
@@ -170,6 +199,16 @@ func (n *MSConfigManager) SwitchNetwork(rpcName string) error {
170199
171200 logger .Debugf ("Switched to network: %s" , rpcName )
172201
202+ n .configMutex .Unlock ()
203+
204+ // Call the network change callback if set
205+ n .networkChangeMutex .RLock ()
206+ callback := n .onNetworkChange
207+ n .networkChangeMutex .RUnlock ()
208+ if callback != nil {
209+ callback ()
210+ }
211+
173212 return nil
174213}
175214
@@ -277,15 +316,16 @@ func (n *MSConfigManager) SaveConfig(cfg *ConfigFile) error {
277316// AddNetwork adds a new network to both memory and persistent configuration
278317func (n * MSConfigManager ) AddNetwork (name , url string , makeDefault bool ) error {
279318 n .configMutex .Lock ()
280- defer n .configMutex .Unlock ()
281319
282320 if name == "" || url == "" {
321+ n .configMutex .Unlock ()
283322 return fmt .Errorf ("name and url are required" )
284323 }
285324
286325 // Load current persisted configuration
287326 cfg , err := LoadConfig ()
288327 if err != nil {
328+ n .configMutex .Unlock ()
289329 return fmt .Errorf ("load config: %w" , err )
290330 }
291331
@@ -297,6 +337,7 @@ func (n *MSConfigManager) AddNetwork(name, url string, makeDefault bool) error {
297337 nameLower := strings .ToLower (strings .TrimSpace (name ))
298338 for existingName := range cfg .Networks {
299339 if strings .ToLower (strings .TrimSpace (existingName )) == nameLower {
340+ n .configMutex .Unlock ()
300341 return fmt .Errorf ("%w: %s" , ErrNetworkAlreadyExists , name )
301342 }
302343 }
@@ -315,6 +356,7 @@ func (n *MSConfigManager) AddNetwork(name, url string, makeDefault bool) error {
315356 }
316357
317358 if err := saveConfigUnsafe (cfg ); err != nil {
359+ n .configMutex .Unlock ()
318360 return err
319361 }
320362
@@ -334,8 +376,22 @@ func (n *MSConfigManager) AddNetwork(name, url string, makeDefault bool) error {
334376 }
335377 n .Network .Networks = append (n .Network .Networks , newNet )
336378
379+ networkChanged := false
337380 if makeDefault {
338381 n .Network .currentNetwork = & n .Network .Networks [len (n .Network .Networks )- 1 ]
382+ networkChanged = true
383+ }
384+
385+ n .configMutex .Unlock ()
386+
387+ // Call the network change callback if set
388+ if networkChanged {
389+ n .networkChangeMutex .RLock ()
390+ callback := n .onNetworkChange
391+ n .networkChangeMutex .RUnlock ()
392+ if callback != nil {
393+ callback ()
394+ }
339395 }
340396
341397 return nil
@@ -346,22 +402,25 @@ func (n *MSConfigManager) AddNetwork(name, url string, makeDefault bool) error {
346402// and the current network is switched in memory as well.
347403func (n * MSConfigManager ) EditNetwork (currentName string , newURL * string , makeDefault * bool , newName * string ) error {
348404 n .configMutex .Lock ()
349- defer n .configMutex .Unlock ()
350405
351406 if currentName == "" {
407+ n .configMutex .Unlock ()
352408 return fmt .Errorf ("currentName is required" )
353409 }
354410
355411 cfg , err := LoadConfig ()
356412 if err != nil {
413+ n .configMutex .Unlock ()
357414 return fmt .Errorf ("load config: %w" , err )
358415 }
359416 if cfg .Networks == nil {
417+ n .configMutex .Unlock ()
360418 return fmt .Errorf ("no networks configured" )
361419 }
362420
363421 item , exists := cfg .Networks [currentName ]
364422 if ! exists {
423+ n .configMutex .Unlock ()
365424 return fmt .Errorf ("unknown network: %s" , currentName )
366425 }
367426
@@ -371,6 +430,7 @@ func (n *MSConfigManager) EditNetwork(currentName string, newURL *string, makeDe
371430 newNameLower := strings .ToLower (strings .TrimSpace (* newName ))
372431 for existingName := range cfg .Networks {
373432 if strings .ToLower (strings .TrimSpace (existingName )) == newNameLower {
433+ n .configMutex .Unlock ()
374434 return fmt .Errorf ("%w: %s" , ErrNetworkAlreadyExists , * newName )
375435 }
376436 }
@@ -405,6 +465,7 @@ func (n *MSConfigManager) EditNetwork(currentName string, newURL *string, makeDe
405465 }
406466
407467 if err := saveConfigUnsafe (cfg ); err != nil {
468+ n .configMutex .Unlock ()
408469 return err
409470 }
410471
@@ -450,10 +511,25 @@ func (n *MSConfigManager) EditNetwork(currentName string, newURL *string, makeDe
450511 }
451512
452513 // Switch current network if default requested or if current was renamed
514+ networkChanged := false
453515 if makeDefault != nil && * makeDefault {
454516 n .Network .currentNetwork = & n .Network .Networks [targetIdx ]
517+ networkChanged = true
455518 } else if n .Network .currentNetwork != nil && n .Network .currentNetwork .Name == currentName && targetName != currentName {
456519 n .Network .currentNetwork = & n .Network .Networks [targetIdx ]
520+ networkChanged = true
521+ }
522+
523+ n .configMutex .Unlock ()
524+
525+ // Call the network change callback if set
526+ if networkChanged {
527+ n .networkChangeMutex .RLock ()
528+ callback := n .onNetworkChange
529+ n .networkChangeMutex .RUnlock ()
530+ if callback != nil {
531+ callback ()
532+ }
457533 }
458534
459535 return nil
0 commit comments