diff --git a/handshake.go b/handshake.go new file mode 100644 index 0000000..6673f00 --- /dev/null +++ b/handshake.go @@ -0,0 +1,66 @@ +package main + +import ( + "net/http" + "runtime/debug" + "sync" +) + +const ( + clientName = "schematic-datastream-replicator" + clientModulePath = "github.com/schematichq/schematic-datastream-replicator" +) + +var ( + clientVersionOnce sync.Once + clientVersion string +) + +// ClientVersion returns the replicator version reported on the DataStream +// handshake. It prefers the ldflag-injected `main.version` used by the +// Dockerfile build, then falls back to module build info (a tag for +// `go install …@v1.2.3` builds, "(devel)" for local `go build .`), and +// finally "unknown". +func ClientVersion() string { + clientVersionOnce.Do(func() { + clientVersion = resolveClientVersion() + }) + return clientVersion +} + +func resolveClientVersion() string { + if version != "" { + return version + } + info, ok := debug.ReadBuildInfo() + if !ok { + return "unknown" + } + if info.Main.Path == clientModulePath && info.Main.Version != "" { + return info.Main.Version + } + for _, dep := range info.Deps { + if dep == nil || dep.Path != clientModulePath { + continue + } + if dep.Replace != nil && dep.Replace.Version != "" { + return dep.Replace.Version + } + if dep.Version != "" { + return dep.Version + } + } + return "unknown" +} + +// datastreamHandshakeHeaders returns the HTTP headers attached to the +// outbound DataStream WebSocket handshake so the backend can distinguish +// replicator connections from direct-SDK connections and correlate them to a +// release. +func datastreamHandshakeHeaders() http.Header { + h := http.Header{} + h.Set("X-Schematic-Datastream-Mode", "replicator") + h.Set("X-Schematic-Client", clientName) + h.Set("X-Schematic-Client-Version", ClientVersion()) + return h +} diff --git a/main.go b/main.go index 8d9e260..87440d5 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,20 @@ import ( "github.com/schematichq/schematic-go/option" ) +// Populated at build time via -ldflags="-X main.version=... -X main.commit=... -X main.buildTime=..." +var ( + version string + commit string + buildTime string +) + +func valueOrUnknown(s string) string { + if s == "" { + return "unknown" + } + return s +} + const ( defaultAPIURL = "https://api.schematichq.com" apiKeyEnvVar = "SCHEMATIC_API_KEY" @@ -286,6 +300,7 @@ func main() { MaxReconnectAttempts: 10, MinReconnectDelay: 1 * time.Second, MaxReconnectDelay: 30 * time.Second, + ExtraHeaders: datastreamHandshakeHeaders(), } // Get WebSocket ping/pong intervals from environment (optional) @@ -309,6 +324,7 @@ func main() { } logger.Info(context.Background(), "Starting Schematic Datastream Replicator...") + logger.Info(context.Background(), fmt.Sprintf("Version: %s, Commit: %s, Build time: %s", ClientVersion(), valueOrUnknown(commit), valueOrUnknown(buildTime))) logger.Info(context.Background(), fmt.Sprintf("API URL: %s", apiBaseURL)) if os.Getenv("SCHEMATIC_DATASTREAM_URL") != "" { logger.Info(context.Background(), fmt.Sprintf("Datastream URL: %s (explicit)", datastreamURL))