Skip to content

Commit 1f83a61

Browse files
committed
feat: service auto installation
1 parent 1028153 commit 1f83a61

10 files changed

Lines changed: 176 additions & 123 deletions

File tree

sdk/go/lib.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,16 @@ type serviceInfo struct {
3535
URL string `json:"url"`
3636
}
3737

38-
// appRegisterRequest represents the request body for app registration
3938
type appRegisterRequest struct {
4039
Name string `json:"name"`
4140
Description string `json:"description"`
4241
UID string `json:"uid,omitempty"`
4342
}
4443

45-
// appRegisterResponse represents the response from app registration
4644
type appRegisterResponse struct {
4745
Token string `json:"token"`
4846
}
4947

50-
// uniTokenDetectionResponse represents the response for service detection
5148
type uniTokenDetectionResponse struct {
5249
UniToken bool `json:"__uni_token"`
5350
}
@@ -188,18 +185,18 @@ func downloadService(execPath string) error {
188185
}
189186

190187
// RequestUniTokenOpenAI requests user for OpenAI token via UniToken service
191-
// Returns the baseURL and apiKey, or nil if the user does not grant permission
192-
func RequestUniTokenOpenAI(options UniTokenOptions) (*UniTokenResult, error) {
188+
// Returns the baseURL and apiKey. apiKey is empty if the user does not grant permission
189+
func RequestUniTokenOpenAI(options UniTokenOptions) (UniTokenResult, error) {
193190
rootPath, err := setupServiceRootPath()
194191
if err != nil {
195-
return nil, fmt.Errorf("failed to setup service root path: %w", err)
192+
return UniTokenResult{}, fmt.Errorf("failed to setup service root path: %w", err)
196193
}
197194
serverURL, err := detectRunningURLFromFile(rootPath)
198195

199196
if err != nil || serverURL == "" {
200197
serverURL, err = startService(rootPath)
201198
if err != nil {
202-
return nil, fmt.Errorf("failed to start service: %w", err)
199+
return UniTokenResult{}, fmt.Errorf("failed to start service: %w", err)
203200
}
204201
}
205202

@@ -211,7 +208,7 @@ func RequestUniTokenOpenAI(options UniTokenOptions) (*UniTokenResult, error) {
211208

212209
jsonData, err := json.Marshal(requestBody)
213210
if err != nil {
214-
return nil, fmt.Errorf("failed to marshal request: %w", err)
211+
return UniTokenResult{}, fmt.Errorf("failed to marshal request: %w", err)
215212
}
216213

217214
client := &http.Client{Timeout: 30 * time.Second}
@@ -221,28 +218,28 @@ func RequestUniTokenOpenAI(options UniTokenOptions) (*UniTokenResult, error) {
221218
bytes.NewBuffer(jsonData),
222219
)
223220
if err != nil {
224-
return nil, fmt.Errorf("registration request failed: %w", err)
221+
return UniTokenResult{}, fmt.Errorf("registration request failed: %w", err)
225222
}
226223
defer resp.Body.Close()
227224

228225
if resp.StatusCode == http.StatusForbidden {
229-
return &UniTokenResult{
226+
return UniTokenResult{
230227
BaseURL: fmt.Sprintf("%sopenai/", serverURL),
231228
APIKey: "",
232229
}, nil // User denied permission
233230
}
234231

235232
if resp.StatusCode != http.StatusOK {
236233
body, _ := io.ReadAll(resp.Body)
237-
return nil, fmt.Errorf("registration failed: HTTP %d - %s", resp.StatusCode, string(body))
234+
return UniTokenResult{}, fmt.Errorf("registration failed: HTTP %d - %s", resp.StatusCode, string(body))
238235
}
239236

240237
var registerResp appRegisterResponse
241238
if err := json.NewDecoder(resp.Body).Decode(&registerResp); err != nil {
242-
return nil, fmt.Errorf("failed to decode response: %w", err)
239+
return UniTokenResult{}, fmt.Errorf("failed to decode response: %w", err)
243240
}
244241

245-
return &UniTokenResult{
242+
return UniTokenResult{
246243
BaseURL: fmt.Sprintf("%sopenai/", serverURL),
247244
APIKey: registerResp.Token,
248245
}, nil

service/discovery/data.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,26 @@ import (
77
"time"
88
)
99

10-
type ServiceData struct {
10+
type ServiceInfo struct {
1111
Command []string `json:"command"`
1212
PID int `json:"pid"`
13-
URL *string `json:"url"`
13+
URL string `json:"url"`
1414
Timestamp int64 `json:"timestamp"`
1515
}
1616

17-
func GetData(port *int) string {
18-
var url *string
17+
func GetServiceInfo(port *int) string {
18+
var url string
1919
if port != nil {
20-
urlStr := fmt.Sprintf("http://localhost:%d/", *port)
21-
url = &urlStr
20+
url = fmt.Sprintf("http://localhost:%d/", *port)
2221
}
2322

24-
data := ServiceData{
23+
service := ServiceInfo{
2524
Command: os.Args,
2625
PID: os.Getpid(),
2726
URL: url,
2827
Timestamp: time.Now().UnixMilli(),
2928
}
3029

31-
jsonData, _ := json.MarshalIndent(data, "", " ")
30+
jsonData, _ := json.MarshalIndent(service, "", " ")
3231
return string(jsonData)
3332
}

service/discovery/file.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,38 @@ import (
99
"syscall"
1010
)
1111

12-
func getFilePath() string {
12+
func getServiceRootPath() string {
1313
if runtime.GOOS == "windows" {
1414
localAppData := os.Getenv("LOCALAPPDATA")
15-
return filepath.Join(localAppData, "UniToken", "service.json")
15+
return filepath.Join(localAppData, "UniToken")
16+
} else {
17+
home := os.Getenv("HOME")
18+
return filepath.Join(home, ".local", "share", "uni-token")
1619
}
17-
home := os.Getenv("HOME")
18-
return filepath.Join(home, ".local", "share", "uni-token", "service.json")
20+
}
21+
22+
func GetServiceExecutablePath() string {
23+
if runtime.GOOS == "windows" {
24+
return filepath.Join(getServiceRootPath(), "service.exe")
25+
} else {
26+
return filepath.Join(getServiceRootPath(), "service")
27+
}
28+
}
29+
30+
func getServiceJsonPath() string {
31+
return filepath.Join(getServiceRootPath(), "service.json")
1932
}
2033

2134
func SetupFileDiscovery(port int) error {
22-
filePath := getFilePath()
35+
filePath := getServiceJsonPath()
2336

2437
// Create directory if it doesn't exist
2538
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
2639
return err
2740
}
2841

2942
// Write initial service data
30-
if err := os.WriteFile(filePath, []byte(GetData(&port)), 0644); err != nil {
43+
if err := os.WriteFile(filePath, []byte(GetServiceInfo(&port)), 0644); err != nil {
3144
return err
3245
}
3346

@@ -40,7 +53,7 @@ func SetupFileDiscovery(port int) error {
4053
go func() {
4154
<-c
4255
// Write final service data with null port on exit
43-
os.WriteFile(filePath, []byte(GetData(nil)), 0644)
56+
os.WriteFile(filePath, []byte(GetServiceInfo(nil)), 0644)
4457
os.Exit(0)
4558
}()
4659

service/discovery/install.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package discovery
2+
3+
import (
4+
"io"
5+
"os"
6+
"path/filepath"
7+
"runtime"
8+
)
9+
10+
func InstallExecutable() error {
11+
targetPath := GetServiceExecutablePath()
12+
selfPath := os.Args[0]
13+
if runtime.GOOS != "windows" {
14+
var err error
15+
selfPath, err = os.Executable()
16+
if err != nil {
17+
return err
18+
}
19+
}
20+
21+
if targetPath == selfPath {
22+
return nil
23+
}
24+
25+
if _, err := os.Stat(targetPath); os.IsNotExist(err) {
26+
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
27+
return err
28+
}
29+
if err := copyFile(selfPath, targetPath); err != nil {
30+
return err
31+
}
32+
}
33+
34+
return nil
35+
}
36+
37+
func copyFile(src, dst string) error {
38+
sourceFile, err := os.Open(src)
39+
if err != nil {
40+
return err
41+
}
42+
defer sourceFile.Close()
43+
44+
destinationFile, err := os.Create(dst)
45+
if err != nil {
46+
return err
47+
}
48+
defer destinationFile.Close()
49+
50+
_, err = io.Copy(destinationFile, sourceFile)
51+
if err != nil {
52+
return err
53+
}
54+
55+
err = destinationFile.Sync()
56+
if err != nil {
57+
return err
58+
}
59+
60+
err = os.Chmod(dst, 0755)
61+
if err != nil {
62+
return err
63+
}
64+
65+
return nil
66+
}

service/discovery/named_pipe.go

Lines changed: 0 additions & 69 deletions
This file was deleted.

service/discovery/singleton.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package discovery
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"os"
7+
"time"
8+
)
9+
10+
type uniTokenDetectionResponse struct {
11+
UniToken bool `json:"__uni_token"`
12+
}
13+
14+
func IsServiceRunning() bool {
15+
filePath := getServiceJsonPath()
16+
17+
if _, err := os.Stat(filePath); os.IsNotExist(err) {
18+
return false
19+
}
20+
21+
fileContent, err := os.ReadFile(filePath)
22+
if err != nil {
23+
return false
24+
}
25+
26+
var info ServiceInfo
27+
if err := json.Unmarshal(fileContent, &info); err != nil {
28+
return false
29+
}
30+
31+
if info.URL == "" {
32+
return false
33+
}
34+
35+
// Verify the service is actually running
36+
client := &http.Client{Timeout: 5 * time.Second}
37+
resp, err := client.Get(info.URL)
38+
if err != nil {
39+
return false
40+
}
41+
defer resp.Body.Close()
42+
43+
var detection uniTokenDetectionResponse
44+
if err := json.NewDecoder(resp.Body).Decode(&detection); err != nil {
45+
return false
46+
}
47+
48+
return detection.UniToken
49+
}

service/logic/url_scheme/linux.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@ func registerURLSchemeLinux(options UrlSchemeRegisterOption) error {
3333
desktopFileName := fmt.Sprintf("%s.desktop", options.AppName)
3434
desktopFilePath := filepath.Join(desktopDir, desktopFileName)
3535

36-
// Get current executable path
37-
exePath, err := os.Executable()
38-
if err != nil {
39-
return fmt.Errorf("failed to get executable path: %w", err)
40-
}
41-
4236
// Write .desktop file content
4337
content := fmt.Sprintf(`[Desktop Entry]
4438
Name=%s
@@ -47,7 +41,7 @@ Exec=%s url %%u
4741
Terminal=false
4842
Type=Application
4943
MimeType=x-scheme-handler/%s;
50-
`, options.AppName, options.Scheme, exePath, options.Scheme)
44+
`, options.AppName, options.Scheme, options.ExecutablePath, options.Scheme)
5145

5246
if err := os.WriteFile(desktopFilePath, []byte(content), 0644); err != nil {
5347
return fmt.Errorf("failed to write .desktop file: %w", err)

service/logic/url_scheme/register.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import (
66
)
77

88
type UrlSchemeRegisterOption struct {
9-
Scheme string
10-
AppName string
9+
Scheme string
10+
AppName string
11+
ExecutablePath string
1112
}
1213

1314
func RegisterURLScheme(options UrlSchemeRegisterOption) error {

0 commit comments

Comments
 (0)