返回 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
15 "reasonix/internal/config"
16 )
17
18 // telemetry_app.go is the anonymous launch ping: one POST per app start carrying a
19 // random install id, version, and OS facts — never conversation, key, or file data.
20 // Gated on config desktop.telemetry (default on) and skipped entirely in dev builds.
21
22 var pingEndpoint = "https://crash.reasonix.io/v1/ping"
23
24 var installIDPattern = regexp.MustCompile(`^[0-9a-f]{32}$`)
25
26 type startupPing struct {
27 InstallID string `json:"installId"`
28 Version string `json:"version"`
29 OS string `json:"os"`
30 Arch string `json:"arch"`
31 OSVersion string `json:"osVersion,omitempty"`
32 }
33
34 func installID() (string, error) {
35 path := filepath.Join(config.MemoryUserDir(), "install-id")
36 if b, err := readFileUTF8(path); err == nil {
37 if id := string(bytes.TrimSpace(b)); installIDPattern.MatchString(id) {
38 return id, nil
39 }
40 }
41 raw := make([]byte, 16)
42 if _, err := rand.Read(raw); err != nil {
43 return "", err
44 }
45 id := hex.EncodeToString(raw)
46 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
47 return "", err
48 }
49 if err := os.WriteFile(path, []byte(id+"\n"), 0o644); err != nil {
50 return "", err
51 }
52 return id, nil
53 }
54
55 func (a *App) sendStartupPing() {
56 if version == "dev" {
57 return
58 }
59 cfg, err := config.Load()
60 if err != nil || !cfg.DesktopTelemetry() {
61 return
62 }
63 id, err := installID()
64 if err != nil {
65 return
66 }
67 c, err := httpClient()
68 if err != nil {
69 return
70 }
71 _ = postStartupPing(a.bootContext(), c, pingEndpoint, startupPing{
72 InstallID: id,
73 Version: version,
74 OS: runtime.GOOS,
75 Arch: runtime.GOARCH,
76 OSVersion: platformOSVersion(),
77 })
78 }
79
80 func postStartupPing(ctx context.Context, c *http.Client, endpoint string, p startupPing) error {
81 body, err := json.Marshal(p)
82 if err != nil {
83 return err
84 }
85 req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
86 if err != nil {
87 return err
88 }
89 req.Header.Set("Content-Type", "application/json")
90 resp, err := c.Do(req)
91 if err != nil {
92 return err
93 }
94 resp.Body.Close()
95 return nil
96 }
97
97 lines GO