返回 DeepSeek-Reasonix
telemetry_app.go
根目录 / desktop / telemetry_app.go
1 package main
2
3 import (
4 "bytes"
5 "context"
6 "crypto/rand"
7 "encoding/hex"
8 "encoding/json"
9 "net/http"
10 "os"
11 "path/filepath"
12 "regexp"
13 "runtime"
14 "time"
15
16 "reasonix/internal/config"
17 "reasonix/internal/fileutil"
18 filelock "reasonix/internal/identitylock"
19 )
20
21 // telemetry_app.go is the anonymous launch ping: one POST per app start carrying a
22 // random install id, version, and OS facts — never conversation, key, or file data.
23 // Gated on config desktop.telemetry (default on) and skipped entirely in dev builds.
24
25 var pingEndpoint = "https://crash.reasonix.io/v1/ping"
26
27 // desktopRendererEngine is the one renderer the desktop ships now: the
28 // Electron shell's Chromium. Kept as a telemetry dimension so dashboards do
29 // not lose the field; the shell owns the exact Chromium version.
30 const desktopRendererEngine = "chromium"
31
32 var installIDPattern = regexp.MustCompile(`^[0-9a-f]{32}$`)
33
34 var installIDRandRead = rand.Read
35
36 type startupPing struct {
37 InstallID string `json:"installId"`
38 Version string `json:"version"`
39 OS string `json:"os"`
40 Arch string `json:"arch"`
41 Channel string `json:"channel,omitempty"`
42 OSVersion string `json:"osVersion,omitempty"`
43 OSBuild int `json:"osBuild,omitempty"`
44 OSRevision int `json:"osRevision,omitempty"`
45 DistroID string `json:"distroId,omitempty"`
46 DistroVersion string `json:"distroVersion,omitempty"`
47 KernelVersion string `json:"kernelVersion,omitempty"`
48 SessionType string `json:"sessionType,omitempty"`
49 RuntimeEngine string `json:"runtimeEngine,omitempty"`
50 RuntimeVersion string `json:"runtimeVersion,omitempty"`
51 GPUMode string `json:"gpuMode,omitempty"`
52 }
53
54 func installID() (string, error) {
55 path := filepath.Join(config.MemoryUserDir(), "install-id")
56 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
57 return "", err
58 }
59 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
60 defer cancel()
61 release, err := filelock.Acquire(ctx, path+".lock")
62 if err != nil {
63 return "", err
64 }
65 defer release()
66
67 if b, err := readFileUTF8(path); err == nil {
68 if id := string(bytes.TrimSpace(b)); installIDPattern.MatchString(id) {
69 return id, nil
70 }
71 }
72 raw := make([]byte, 16)
73 if _, err := installIDRandRead(raw); err != nil {
74 return "", err
75 }
76 id := hex.EncodeToString(raw)
77 if err := fileutil.AtomicWriteFile(path, []byte(id+"\n"), 0o644); err != nil {
78 return "", err
79 }
80 return id, nil
81 }
82
83 func (a *App) sendStartupPing() {
84 if version == "dev" {
85 return
86 }
87 cfg, err := config.Load()
88 if err != nil || !cfg.DesktopTelemetry() {
89 return
90 }
91 id, err := installID()
92 if err != nil {
93 return
94 }
95 c, err := httpClient()
96 if err != nil {
97 return
98 }
99 device := collectDeviceInfo()
100 _ = postStartupPing(a.bootContext(), c, pingEndpoint, startupPing{
101 InstallID: id, Version: version, OS: runtime.GOOS, Arch: runtime.GOARCH, Channel: channel,
102 OSVersion: device.OSVersion, OSBuild: device.OSBuild, OSRevision: device.OSRevision,
103 DistroID: device.DistroID, DistroVersion: device.DistroVersion,
104 KernelVersion: device.KernelVersion, SessionType: device.SessionType,
105 RuntimeEngine: desktopRendererEngine,
106 })
107 }
108
109 func postStartupPing(ctx context.Context, c *http.Client, endpoint string, p startupPing) error {
110 body, err := json.Marshal(p)
111 if err != nil {
112 return err
113 }
114 req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
115 if err != nil {
116 return err
117 }
118 req.Header.Set("Content-Type", "application/json")
119 resp, err := c.Do(req)
120 if err != nil {
121 return err
122 }
123 resp.Body.Close()
124 return nil
125 }
126
126 lines GO