| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- package service
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "os"
- "time"
- "github.com/mhsanaei/3x-ui/v2/util/common"
- )
- // WarpService provides business logic for Cloudflare WARP integration.
- // It manages WARP configuration and connectivity settings.
- type WarpService struct {
- SettingService
- }
- const (
- warpAPIBase = "https://api.cloudflareclient.com/v0a4005"
- warpClientVer = "a-6.30-3596"
- )
- var warpHTTPClient = &http.Client{Timeout: 15 * time.Second}
- func (s *WarpService) GetWarpData() (string, error) {
- return s.SettingService.GetWarp()
- }
- func (s *WarpService) DelWarpData() error {
- return s.SettingService.SetWarp("")
- }
- func (s *WarpService) GetWarpConfig() (string, error) {
- warpData, err := s.loadWarpCreds()
- if err != nil {
- return "", err
- }
- url := fmt.Sprintf("%s/reg/%s", warpAPIBase, warpData["device_id"])
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- return "", err
- }
- req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
- body, err := doWarpRequest(req)
- if err != nil {
- return "", err
- }
- return string(body), nil
- }
- func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error) {
- hostName, _ := os.Hostname()
- reqBody, err := json.Marshal(map[string]any{
- "key": publicKey,
- "tos": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"),
- "type": "PC",
- "model": "x-ui",
- "name": hostName,
- })
- if err != nil {
- return "", err
- }
- req, err := http.NewRequest(http.MethodPost, warpAPIBase+"/reg", bytes.NewReader(reqBody))
- if err != nil {
- return "", err
- }
- req.Header.Set("CF-Client-Version", warpClientVer)
- req.Header.Set("Content-Type", "application/json")
- body, err := doWarpRequest(req)
- if err != nil {
- return "", err
- }
- var rsp map[string]any
- if err := json.Unmarshal(body, &rsp); err != nil {
- return "", err
- }
- deviceID, ok := rsp["id"].(string)
- if !ok {
- return "", common.NewError("warp register: missing 'id' in response")
- }
- token, ok := rsp["token"].(string)
- if !ok {
- return "", common.NewError("warp register: missing 'token' in response")
- }
- account, ok := rsp["account"].(map[string]any)
- if !ok {
- return "", common.NewError("warp register: missing 'account' in response")
- }
- license, ok := account["license"].(string)
- if !ok {
- return "", common.NewError("warp register: missing 'account.license' in response")
- }
- warpData := map[string]string{
- "access_token": token,
- "device_id": deviceID,
- "license_key": license,
- "private_key": secretKey,
- }
- warpJSON, err := json.MarshalIndent(warpData, "", " ")
- if err != nil {
- return "", err
- }
- if err := s.SettingService.SetWarp(string(warpJSON)); err != nil {
- return "", err
- }
- result, err := json.MarshalIndent(map[string]any{
- "data": warpData,
- "config": json.RawMessage(body),
- }, "", " ")
- if err != nil {
- return "", err
- }
- return string(result), nil
- }
- func (s *WarpService) SetWarpLicense(license string) (string, error) {
- warpData, err := s.loadWarpCreds()
- if err != nil {
- return "", err
- }
- url := fmt.Sprintf("%s/reg/%s/account", warpAPIBase, warpData["device_id"])
- reqBody, err := json.Marshal(map[string]string{"license": license})
- if err != nil {
- return "", err
- }
- req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(reqBody))
- if err != nil {
- return "", err
- }
- req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
- req.Header.Set("Content-Type", "application/json")
- body, err := doWarpRequest(req)
- if err != nil {
- return "", err
- }
- var response map[string]any
- if err := json.Unmarshal(body, &response); err != nil {
- return "", err
- }
- if success, _ := response["success"].(bool); !success {
- if errorArr, ok := response["errors"].([]any); ok && len(errorArr) > 0 {
- if errorObj, ok := errorArr[0].(map[string]any); ok {
- return "", common.NewError(errorObj["code"], errorObj["message"])
- }
- }
- return "", common.NewError("warp set license failed: unknown error")
- }
- warpData["license_key"] = license
- newWarpData, err := json.MarshalIndent(warpData, "", " ")
- if err != nil {
- return "", err
- }
- if err := s.SettingService.SetWarp(string(newWarpData)); err != nil {
- return "", err
- }
- return string(newWarpData), nil
- }
- // loadWarpCreds reads the stored warp JSON and ensures access_token + device_id are set.
- func (s *WarpService) loadWarpCreds() (map[string]string, error) {
- warp, err := s.SettingService.GetWarp()
- if err != nil {
- return nil, err
- }
- var data map[string]string
- if err := json.Unmarshal([]byte(warp), &data); err != nil {
- return nil, err
- }
- if data["access_token"] == "" || data["device_id"] == "" {
- return nil, common.NewError("warp not registered: missing access_token or device_id")
- }
- return data, nil
- }
- // doWarpRequest sends the request and returns the response body on 2xx.
- // Non-2xx responses are returned as errors including the status code and body.
- func doWarpRequest(req *http.Request) ([]byte, error) {
- resp, err := warpHTTPClient.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- if resp.StatusCode < 200 || resp.StatusCode >= 300 {
- return nil, common.NewErrorf("warp api %s %s returned status %d: %s",
- req.Method, req.URL.Path, resp.StatusCode, string(body))
- }
- return body, nil
- }
|