Skip to content

Commit b25e30d

Browse files
committed
Merge pull request #15 from sqlrsync/v0.0.5
v0.0.5 release
2 parents 9a0da1d + f03cc20 commit b25e30d

File tree

8 files changed

+204
-23
lines changed

8 files changed

+204
-23
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: 2 additions & 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
@@ -13,6 +14,7 @@ require (
1314

1415
require (
1516
github.com/fsnotify/fsnotify v1.9.0 // indirect
17+
github.com/google/uuid v1.6.0 // indirect
1618
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1719
github.com/mattn/go-colorable v0.1.13 // indirect
1820
github.com/mattn/go-isatty v0.0.20 // indirect

client/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
77
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
88
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
99
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
10+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
11+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1012
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
1113
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
1214
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=

client/main.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"github.com/sqlrsync/sqlrsync.com/sync"
1616
)
1717

18-
var VERSION = "0.0.4"
18+
var VERSION = "0.0.5"
1919
var (
2020
serverURL string
2121
verbose bool
@@ -163,6 +163,13 @@ func runSync(cmd *cobra.Command, args []string) error {
163163
visibility = 1
164164
}
165165

166+
// Get workspace ID for client identification
167+
wsID, err := GetWsID()
168+
if err != nil {
169+
logger.Warn("Failed to get workspace ID", zap.Error(err))
170+
wsID = "" // Continue with empty wsID
171+
}
172+
166173
// Create sync coordinator
167174
coordinator := sync.NewCoordinator(&sync.CoordinatorConfig{
168175
ServerURL: serverURL,
@@ -185,6 +192,8 @@ func runSync(cmd *cobra.Command, args []string) error {
185192
MaxInterval: maxInterval,
186193
MinInterval: minInterval,
187194
AutoMerge: autoMerge,
195+
WsID: wsID, // Add websocket ID
196+
ClientVersion: VERSION,
188197
})
189198

190199
// Execute the operation

client/remote/client.go

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

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

412414
// Progress tracking
413415
ProgressConfig *ProgressConfig
@@ -701,6 +703,14 @@ func (c *Client) Connect() error {
701703
headers.Set("X-ClientID", wsID)
702704
}
703705

706+
headers.Set("X-ClientVersion", c.config.ClientVersion);
707+
708+
if c.config.WsID != "" {
709+
headers.Set("X-ClientID", c.config.WsID)
710+
} else {
711+
c.logger.Fatal("No wsID provided for X-ClientID header")
712+
}
713+
704714
if c.config.LocalHostname != "" {
705715
headers.Set("X-LocalHostname", c.config.LocalHostname)
706716
}
@@ -719,12 +729,58 @@ func (c *Client) Connect() error {
719729

720730
conn, response, err := dialer.DialContext(connectCtx, u.String(), headers)
721731
if err != nil {
722-
if response != nil && response.Body != nil {
723-
respStr, _ := io.ReadAll(response.Body)
724-
response.Body.Close()
725-
return fmt.Errorf("%s", respStr)
732+
if response != nil {
733+
// Extract detailed error information from the response
734+
statusCode := response.StatusCode
735+
statusText := response.Status
736+
737+
var respBodyStr string
738+
if response.Body != nil {
739+
respBytes, readErr := io.ReadAll(response.Body)
740+
response.Body.Close()
741+
if readErr == nil {
742+
respBodyStr = strings.TrimSpace(string(respBytes))
743+
}
744+
}
745+
746+
// Create a clean error message
747+
var errorMsg strings.Builder
748+
errorMsg.WriteString(fmt.Sprintf("HTTP %d (%s)", statusCode, statusText))
749+
750+
if respBodyStr != "" {
751+
errorMsg.WriteString(fmt.Sprintf(": %s", respBodyStr))
752+
}
753+
754+
return fmt.Errorf("%s", errorMsg.String())
726755
}
727-
return fmt.Errorf("failed to connect to WebSocket: %w", err)
756+
757+
// Handle cases where response is nil (e.g., network errors, bad handshake)
758+
var errorMsg strings.Builder
759+
errorMsg.WriteString("Failed to connect to WebSocket")
760+
761+
// Analyze the error type and provide helpful context
762+
errorStr := err.Error()
763+
if strings.Contains(errorStr, "bad handshake") {
764+
errorMsg.WriteString(" - WebSocket handshake failed")
765+
errorMsg.WriteString("\nThis could be due to:")
766+
errorMsg.WriteString("\n• Invalid server URL or endpoint")
767+
errorMsg.WriteString("\n• Server not supporting WebSocket connections")
768+
errorMsg.WriteString("\n• Network connectivity issues")
769+
errorMsg.WriteString("\n• Authentication problems")
770+
} else if strings.Contains(errorStr, "timeout") {
771+
errorMsg.WriteString(" - Connection timeout")
772+
errorMsg.WriteString("\nThe server may be overloaded or unreachable")
773+
} else if strings.Contains(errorStr, "refused") {
774+
errorMsg.WriteString(" - Connection refused")
775+
errorMsg.WriteString("\nThe server may be down or the port may be blocked")
776+
} else if strings.Contains(errorStr, "no such host") {
777+
errorMsg.WriteString(" - DNS resolution failed")
778+
errorMsg.WriteString("\nCheck the server hostname in your configuration")
779+
}
780+
781+
errorMsg.WriteString(fmt.Sprintf("\nOriginal error: %v", err))
782+
783+
return fmt.Errorf("%s", errorMsg.String())
728784
}
729785
defer response.Body.Close()
730786

client/subscription/manager.go

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"io"
78
"net/http"
89
"net/url"
910
"strings"
@@ -33,12 +34,14 @@ type Message struct {
3334
Timestamp time.Time `json:"timestamp"`
3435
}
3536

36-
// Config holds subscription manager configuration
37-
type Config struct {
37+
// ManagerConfig holds subscription manager configuration
38+
type ManagerConfig struct {
3839
ServerURL string
3940
ReplicaPath string
4041
AuthToken string
4142
ReplicaID string
43+
WsID string // websocket ID for client identification
44+
ClientVersion string // version of the client software
4245
Logger *zap.Logger
4346
MaxReconnectAttempts int // Maximum number of reconnect attempts (0 = infinite)
4447
InitialReconnectDelay time.Duration // Initial delay before first reconnect
@@ -53,7 +56,7 @@ type Config struct {
5356
// MaxReconnectDelay is reached. Reconnection attempts continue indefinitely unless
5457
// MaxReconnectAttempts is set to a positive value.
5558
type Manager struct {
56-
config *Config
59+
config *ManagerConfig
5760
logger *zap.Logger
5861
conn *websocket.Conn
5962
ctx context.Context
@@ -72,7 +75,7 @@ type Manager struct {
7275
}
7376

7477
// NewManager creates a new subscription manager
75-
func NewManager(config *Config) *Manager {
78+
func NewManager(config *ManagerConfig) *Manager {
7679
ctx, cancel := context.WithCancel(context.Background())
7780

7881
// Set default reconnection parameters if not provided
@@ -201,15 +204,69 @@ func (m *Manager) doConnect() error {
201204
headers.Set("X-ReplicaID", m.config.ReplicaID)
202205
}
203206

207+
headers.Set("X-ClientVersion", m.config.ClientVersion)
208+
headers.Set("X-ClientID", m.config.WsID)
209+
204210
dialer := websocket.Dialer{
205211
HandshakeTimeout: 10 * time.Second,
206212
}
207213

208214
m.logger.Debug("Dialing WebSocket", zap.String("url", u.String()))
209215

210-
conn, _, err := dialer.DialContext(m.ctx, u.String(), headers)
216+
conn, response, err := dialer.DialContext(m.ctx, u.String(), headers)
211217
if err != nil {
212-
return fmt.Errorf("failed to connect to subscription service: %w", err)
218+
if response != nil {
219+
// Extract detailed error information from the response
220+
statusCode := response.StatusCode
221+
statusText := response.Status
222+
223+
var respBodyStr string
224+
if response.Body != nil {
225+
respBytes, readErr := io.ReadAll(response.Body)
226+
response.Body.Close()
227+
if readErr == nil {
228+
respBodyStr = strings.TrimSpace(string(respBytes))
229+
}
230+
}
231+
232+
// Create a clean error message
233+
var errorMsg strings.Builder
234+
errorMsg.WriteString(fmt.Sprintf("HTTP %d (%s)", statusCode, statusText))
235+
236+
if respBodyStr != "" {
237+
errorMsg.WriteString(fmt.Sprintf(": %s", respBodyStr))
238+
}
239+
240+
return fmt.Errorf("%s", errorMsg.String())
241+
}
242+
243+
// Handle cases where response is nil (e.g., network errors, bad handshake)
244+
var errorMsg strings.Builder
245+
errorMsg.WriteString("Failed to connect to subscription service")
246+
247+
// Analyze the error type and provide helpful context
248+
errorStr := err.Error()
249+
if strings.Contains(errorStr, "bad handshake") {
250+
errorMsg.WriteString(" - WebSocket handshake failed")
251+
errorMsg.WriteString("\nThis could be due to:")
252+
errorMsg.WriteString("\n• Invalid server URL or endpoint")
253+
errorMsg.WriteString("\n• Server not supporting WebSocket connections")
254+
errorMsg.WriteString("\n• Network connectivity issues")
255+
errorMsg.WriteString("\n• Authentication problems")
256+
} else if strings.Contains(errorStr, "timeout") {
257+
errorMsg.WriteString(" - Connection timeout")
258+
errorMsg.WriteString("\nThe server may be overloaded or unreachable")
259+
} else if strings.Contains(errorStr, "refused") {
260+
errorMsg.WriteString(" - Connection refused")
261+
errorMsg.WriteString("\nThe server may be down or the port may be blocked")
262+
} else if strings.Contains(errorStr, "no such host") {
263+
errorMsg.WriteString(" - DNS resolution failed")
264+
errorMsg.WriteString("\nCheck the server hostname in your configuration")
265+
}
266+
267+
errorMsg.WriteString(fmt.Sprintf("\nOriginal error: %v", err))
268+
269+
return fmt.Errorf("%s", errorMsg.String())
213270
}
214271

215272
m.mu.Lock()

0 commit comments

Comments
 (0)