Skip to content

Commit fa8e0d6

Browse files
authored
Merge pull request #16 from sqlrsync/sendWsID
feat: send wsID and clientVersion with client requests
2 parents 7e11c94 + 3f2dd13 commit fa8e0d6

File tree

8 files changed

+104
-22
lines changed

8 files changed

+104
-22
lines changed

client/auth/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
type DefaultsConfig struct {
1616
Defaults struct {
1717
Server string `toml:"server"`
18+
WsID string `toml:"wsID"`
1819
} `toml:"defaults"`
1920
}
2021

client/config.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import (
99
"time"
1010

1111
"github.com/BurntSushi/toml"
12+
"github.com/google/uuid"
1213
)
1314

1415
// .config/sqlrsync/defaults.toml
1516
type DefaultsConfig struct {
1617
Defaults struct {
1718
Server string `toml:"server"`
19+
WsID string `toml:"wsID"`
1820
} `toml:"defaults"`
1921
}
2022

@@ -78,6 +80,14 @@ func LoadDefaultsConfig() (*DefaultsConfig, error) {
7880
// Return default config if file doesn't exist
7981
config := &DefaultsConfig{}
8082
config.Defaults.Server = "wss://sqlrsync.com"
83+
// Generate wsID if it doesn't exist
84+
if err := generateAndSetWsID(config); err != nil {
85+
return nil, fmt.Errorf("failed to generate wsID: %w", err)
86+
}
87+
// Save the new config with wsID
88+
if err := SaveDefaultsConfig(config); err != nil {
89+
return nil, fmt.Errorf("failed to save defaults config with wsID: %w", err)
90+
}
8191
return config, nil
8292
}
8393
return nil, fmt.Errorf("failed to read defaults config file %s: %w", path, err)
@@ -93,9 +103,45 @@ func LoadDefaultsConfig() (*DefaultsConfig, error) {
93103
config.Defaults.Server = "wss://sqlrsync.com"
94104
}
95105

106+
// Generate wsID if it doesn't exist
107+
needsSave := false
108+
if config.Defaults.WsID == "" {
109+
if err := generateAndSetWsID(&config); err != nil {
110+
return nil, fmt.Errorf("failed to generate wsID: %w", err)
111+
}
112+
needsSave = true
113+
}
114+
115+
// Save config if we made changes
116+
if needsSave {
117+
if err := SaveDefaultsConfig(&config); err != nil {
118+
return nil, fmt.Errorf("failed to save defaults config with wsID: %w", err)
119+
}
120+
}
121+
96122
return &config, nil
97123
}
98124

125+
// generateAndSetWsID generates a new wsID (UUID + hostname) and sets it in the config
126+
func generateAndSetWsID(config *DefaultsConfig) error {
127+
hostname, err := os.Hostname()
128+
if err != nil {
129+
return fmt.Errorf("failed to get hostname: %w", err)
130+
}
131+
132+
config.Defaults.WsID = hostname + ":" + uuid.New().String()
133+
return nil
134+
}
135+
136+
// GetWsID loads the defaults config and returns the wsID
137+
func GetWsID() (string, error) {
138+
config, err := LoadDefaultsConfig()
139+
if err != nil {
140+
return "", fmt.Errorf("failed to load defaults config: %w", err)
141+
}
142+
return config.Defaults.WsID, nil
143+
}
144+
99145
func SaveDefaultsConfig(config *DefaultsConfig) error {
100146
path, err := GetDefaultsPath()
101147
if err != nil {

client/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.24.5
55
require (
66
github.com/BurntSushi/toml v1.5.0
77
github.com/fatih/color v1.18.0
8+
github.com/google/uuid v1.6.0
89
github.com/gorilla/websocket v1.5.0
910
github.com/spf13/cobra v1.8.0
1011
github.com/sqlrsync/sqlrsync.com/bridge v0.0.0-00010101000000-000000000000

client/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
66
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
77
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
8+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
9+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
810
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
911
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
1012
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=

client/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ func runSync(cmd *cobra.Command, args []string) error {
145145
visibility = 1
146146
}
147147

148+
// Get workspace ID for client identification
149+
wsID, err := GetWsID()
150+
if err != nil {
151+
logger.Warn("Failed to get workspace ID", zap.Error(err))
152+
wsID = "" // Continue with empty wsID
153+
}
154+
148155
// Create sync coordinator
149156
coordinator := sync.NewCoordinator(&sync.CoordinatorConfig{
150157
ServerURL: serverURL,
@@ -162,6 +169,8 @@ func runSync(cmd *cobra.Command, args []string) error {
162169
DryRun: dryRun,
163170
Logger: logger,
164171
Verbose: verbose,
172+
WsID: wsID, // Add websocket ID
173+
ClientVersion: VERSION,
165174
})
166175

167176
// Execute the operation

client/remote/client.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,13 @@ type Config struct {
402402
InspectionDepth int // How many bytes to inspect (default: 32)
403403
PingPong bool
404404
AuthToken string
405+
ClientVersion string // version of the client software
405406
SendKeyRequest bool // the -sqlrsync file doesn't exist, so make a token
406407

407408
SendConfigCmd bool // we don't have the version number or remote path
408409
LocalHostname string
409410
LocalAbsolutePath string
411+
WsID string // Workspace ID for X-ClientID header
410412

411413
// Progress tracking
412414
ProgressConfig *ProgressConfig
@@ -685,6 +687,14 @@ func (c *Client) Connect() error {
685687

686688
headers.Set("Authorization", c.config.AuthToken)
687689

690+
headers.Set("X-ClientVersion", c.config.ClientVersion);
691+
692+
if c.config.WsID != "" {
693+
headers.Set("X-ClientID", c.config.WsID)
694+
} else {
695+
c.logger.Fatal("No wsID provided for X-ClientID header")
696+
}
697+
688698
if c.config.LocalHostname != "" {
689699
headers.Set("X-LocalHostname", c.config.LocalHostname)
690700
}
@@ -707,7 +717,7 @@ func (c *Client) Connect() error {
707717
// Extract detailed error information from the response
708718
statusCode := response.StatusCode
709719
statusText := response.Status
710-
720+
711721
var respBodyStr string
712722
if response.Body != nil {
713723
respBytes, readErr := io.ReadAll(response.Body)
@@ -716,22 +726,22 @@ func (c *Client) Connect() error {
716726
respBodyStr = strings.TrimSpace(string(respBytes))
717727
}
718728
}
719-
729+
720730
// Create a clean error message
721731
var errorMsg strings.Builder
722732
errorMsg.WriteString(fmt.Sprintf("HTTP %d (%s)", statusCode, statusText))
723-
733+
724734
if respBodyStr != "" {
725735
errorMsg.WriteString(fmt.Sprintf(": %s", respBodyStr))
726736
}
727-
737+
728738
return fmt.Errorf("%s", errorMsg.String())
729739
}
730-
740+
731741
// Handle cases where response is nil (e.g., network errors, bad handshake)
732742
var errorMsg strings.Builder
733743
errorMsg.WriteString("Failed to connect to WebSocket")
734-
744+
735745
// Analyze the error type and provide helpful context
736746
errorStr := err.Error()
737747
if strings.Contains(errorStr, "bad handshake") {
@@ -751,9 +761,9 @@ func (c *Client) Connect() error {
751761
errorMsg.WriteString(" - DNS resolution failed")
752762
errorMsg.WriteString("\nCheck the server hostname in your configuration")
753763
}
754-
764+
755765
errorMsg.WriteString(fmt.Sprintf("\nOriginal error: %v", err))
756-
766+
757767
return fmt.Errorf("%s", errorMsg.String())
758768
}
759769
defer response.Body.Close()

client/subscription/manager.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ type Message struct {
3434
Timestamp time.Time `json:"timestamp"`
3535
}
3636

37-
// Config holds subscription manager configuration
38-
type Config struct {
37+
// ManagerConfig holds subscription manager configuration
38+
type ManagerConfig struct {
3939
ServerURL string
4040
ReplicaPath string
4141
AuthToken string
4242
ReplicaID string
43+
WsID string // websocket ID for client identification
44+
ClientVersion string // version of the client software
4345
Logger *zap.Logger
4446
MaxReconnectAttempts int // Maximum number of reconnect attempts (0 = infinite)
4547
InitialReconnectDelay time.Duration // Initial delay before first reconnect
@@ -54,7 +56,7 @@ type Config struct {
5456
// MaxReconnectDelay is reached. Reconnection attempts continue indefinitely unless
5557
// MaxReconnectAttempts is set to a positive value.
5658
type Manager struct {
57-
config *Config
59+
config *ManagerConfig
5860
logger *zap.Logger
5961
conn *websocket.Conn
6062
ctx context.Context
@@ -73,7 +75,7 @@ type Manager struct {
7375
}
7476

7577
// NewManager creates a new subscription manager
76-
func NewManager(config *Config) *Manager {
78+
func NewManager(config *ManagerConfig) *Manager {
7779
ctx, cancel := context.WithCancel(context.Background())
7880

7981
// Set default reconnection parameters if not provided
@@ -202,6 +204,9 @@ func (m *Manager) doConnect() error {
202204
headers.Set("X-ReplicaID", m.config.ReplicaID)
203205
}
204206

207+
headers.Set("X-ClientVersion", m.config.ClientVersion)
208+
headers.Set("X-ClientID", m.config.WsID)
209+
205210
dialer := websocket.Dialer{
206211
HandshakeTimeout: 10 * time.Second,
207212
}
@@ -214,7 +219,7 @@ func (m *Manager) doConnect() error {
214219
// Extract detailed error information from the response
215220
statusCode := response.StatusCode
216221
statusText := response.Status
217-
222+
218223
var respBodyStr string
219224
if response.Body != nil {
220225
respBytes, readErr := io.ReadAll(response.Body)
@@ -223,22 +228,22 @@ func (m *Manager) doConnect() error {
223228
respBodyStr = strings.TrimSpace(string(respBytes))
224229
}
225230
}
226-
231+
227232
// Create a clean error message
228233
var errorMsg strings.Builder
229234
errorMsg.WriteString(fmt.Sprintf("HTTP %d (%s)", statusCode, statusText))
230-
235+
231236
if respBodyStr != "" {
232237
errorMsg.WriteString(fmt.Sprintf(": %s", respBodyStr))
233238
}
234-
239+
235240
return fmt.Errorf("%s", errorMsg.String())
236241
}
237-
242+
238243
// Handle cases where response is nil (e.g., network errors, bad handshake)
239244
var errorMsg strings.Builder
240245
errorMsg.WriteString("Failed to connect to subscription service")
241-
246+
242247
// Analyze the error type and provide helpful context
243248
errorStr := err.Error()
244249
if strings.Contains(errorStr, "bad handshake") {
@@ -258,9 +263,9 @@ func (m *Manager) doConnect() error {
258263
errorMsg.WriteString(" - DNS resolution failed")
259264
errorMsg.WriteString("\nCheck the server hostname in your configuration")
260265
}
261-
266+
262267
errorMsg.WriteString(fmt.Sprintf("\nOriginal error: %v", err))
263-
268+
264269
return fmt.Errorf("%s", errorMsg.String())
265270
}
266271

client/sync/coordinator.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ type CoordinatorConfig struct {
4646
DryRun bool
4747
Logger *zap.Logger
4848
Verbose bool
49+
WsID string // Websocket ID for client identification
50+
ClientVersion string // version of the client software
4951
}
5052

5153
// Coordinator manages sync operations and subscriptions
@@ -240,11 +242,13 @@ func (c *Coordinator) executeSubscribe() error {
240242
}
241243

242244
// Create subscription manager with reconnection configuration
243-
c.subManager = subscription.NewManager(&subscription.Config{
245+
c.subManager = subscription.NewManager(&subscription.ManagerConfig{
244246
ServerURL: authResult.ServerURL,
245247
ReplicaPath: authResult.RemotePath,
246248
AuthToken: authResult.AccessToken,
247249
ReplicaID: authResult.ReplicaID,
250+
WsID: c.config.WsID,
251+
ClientVersion: c.config.ClientVersion,
248252
Logger: c.logger.Named("subscription"),
249253
MaxReconnectAttempts: 20, // Infinite reconnect attempts
250254
InitialReconnectDelay: 5 * time.Second, // Start with 5 seconds delay
@@ -373,6 +377,8 @@ func (c *Coordinator) executePull(isSubscription bool) error {
373377
Version: version,
374378
SendConfigCmd: true,
375379
SendKeyRequest: c.authResolver.CheckNeedsDashFile(c.config.LocalPath, remotePath),
380+
WsID: c.config.WsID, // Add websocket ID
381+
ClientVersion: c.config.ClientVersion,
376382
//ProgressCallback: remote.DefaultProgressCallback(remote.FormatSimple),
377383
ProgressCallback: nil,
378384
ProgressConfig: &remote.ProgressConfig{
@@ -499,7 +505,9 @@ func (c *Coordinator) executePush() error {
499505
SendConfigCmd: true,
500506
SetVisibility: c.config.SetVisibility,
501507
CommitMessage: c.config.CommitMessage,
502-
ProgressCallback: nil, //remote.DefaultProgressCallback(remote.FormatSimple),
508+
WsID: c.config.WsID, // Add websocket ID
509+
ClientVersion: c.config.ClientVersion,
510+
ProgressCallback: nil, //remote.DefaultProgressCallback(remote.FormatSimple),
503511
ProgressConfig: &remote.ProgressConfig{
504512
Enabled: true,
505513
Format: remote.FormatSimple,

0 commit comments

Comments
 (0)