diff --git a/cmd/host/main.go b/cmd/host/main.go index ede8e97c..7dcf1359 100644 --- a/cmd/host/main.go +++ b/cmd/host/main.go @@ -60,10 +60,39 @@ func wakeup(hidden bool) error { return fmt.Errorf("start gopeed failed") } +// postToFlutter sends a POST request to Flutter RPC server +func postToFlutter(path string, body []byte, headers map[string]string, timeout time.Duration) (*http.Response, error) { + client := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return Dial() + }, + }, + Timeout: timeout, + } + req, err := http.NewRequest("POST", "http://127.0.0.1"+path, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + for k, v := range headers { + req.Header.Set(k, v) + } + return client.Do(req) +} + var apiMap = map[string]func(message *Message) (data any, err error){ "ping": func(message *Message) (data any, err error) { return check() }, + "wakeup": func(message *Message) (data any, err error) { + silent := false + if v, ok := message.Meta["silent"]; ok { + silent, _ = v.(bool) + } + err = wakeup(silent) + return + }, "create": func(message *Message) (data any, err error) { buf, err := message.Params.MarshalJSON() if err != nil { @@ -79,24 +108,36 @@ var apiMap = map[string]func(message *Message) (data any, err error){ return nil, err } - client := &http.Client{ - Transport: &http.Transport{ - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return Dial() - }, - }, - Timeout: 10 * time.Second, + headers := make(map[string]string) + if message.Meta != nil { + metaJson, _ := json.Marshal(message.Meta) + headers["X-Gopeed-Host-Meta"] = string(metaJson) } - req, err := http.NewRequest("POST", "http://127.0.0.1/create", bytes.NewBuffer(buf)) + _, err = postToFlutter("/create", buf, headers, 10*time.Second) + return + }, + "forward": func(message *Message) (data any, err error) { + buf, err := message.Params.MarshalJSON() if err != nil { return } - if message.Meta != nil { - metaJson, _ := json.Marshal(message.Meta) - req.Header.Set("X-Gopeed-Host-Meta", string(metaJson)) + + resp, err := postToFlutter("/forward", buf, nil, 60*time.Second) + if err != nil { + return } - _, err = client.Do(req) - return + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var respData map[string]json.RawMessage + if err := json.Unmarshal(respBody, &respData); err != nil { + return nil, err + } + return respData, nil }, } @@ -108,7 +149,6 @@ func main() { var length uint32 if err := binary.Read(os.Stdin, binary.NativeEndian, &length); err != nil { if err == io.EOF { - // Connection closed by Chrome return } sendError("Failed to read message length: " + err.Error()) diff --git a/ui/flutter/lib/api/api.dart b/ui/flutter/lib/api/api.dart index 296a3699..b3033e0d 100644 --- a/ui/flutter/lib/api/api.dart +++ b/ui/flutter/lib/api/api.dart @@ -14,7 +14,6 @@ import 'model/downloader_config.dart'; import 'model/extension.dart'; import 'model/install_extension.dart'; import 'model/login.dart'; -import 'model/request.dart'; import 'model/resolve_result.dart'; import 'model/resolve_task.dart'; import 'model/result.dart'; @@ -267,3 +266,19 @@ String join(String path) { : baseUrl; return "$cleanBaseUrl/${Util.cleanPath(path)}"; } + +/// Generic request method for API proxy +/// Directly forwards requests to gopeed REST API +Future forward( + String path, { + String method = 'GET', + dynamic data, + Map? queryParameters, +}) async { + return _client.dio.request( + path, + data: data, + queryParameters: queryParameters, + options: Options(method: method), + ); +} diff --git a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart index f1b6577b..e52e6926 100644 --- a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart +++ b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'dart:ui'; import 'package:app_links/app_links.dart'; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:get/get.dart'; @@ -20,6 +21,7 @@ import '../../../../api/api.dart'; import '../../../../api/model/create_task.dart'; import '../../../../api/model/downloader_config.dart'; import '../../../../api/model/request.dart'; +import '../../../../api/model/result.dart'; import '../../../../core/common/start_config.dart'; import '../../../../core/libgopeed_boot.dart'; import '../../../../database/database.dart'; @@ -304,6 +306,33 @@ class AppController extends GetxController with WindowListener, TrayListener { } } }, + "/forward": (ctx) async { + try { + final body = await ctx.readJSON(); + final method = (body['method'] as String?)?.toUpperCase() ?? 'GET'; + final path = (body['path'] as String?) ?? "/"; + final data = body['data']; + final query = body['query'] as Map?; + + // Forward request to gopeed REST API + final response = await forward( + path, + method: method, + data: data, + queryParameters: query, + ); + + // Return raw response + await ctx.writeJSON(response.data); + } catch (e) { + if (e is DioException && e.response != null) { + // Return API error response + await ctx.writeJSON(e.response!.data); + } else { + await ctx.writeJSON(Result(code: 1, msg: e.toString()).toJson()); + } + } + }, }); } catch (e) { logger.w("start rpc server fail", e, StackTrace.current); diff --git a/ui/flutter/lib/app/rpc/rpc.dart b/ui/flutter/lib/app/rpc/rpc.dart index 4bcfb037..dc142b04 100644 --- a/ui/flutter/lib/app/rpc/rpc.dart +++ b/ui/flutter/lib/app/rpc/rpc.dart @@ -1,8 +1,7 @@ -import 'dart:io'; import 'dart:convert'; +import 'dart:io'; import 'package:dart_ipc/dart_ipc.dart'; -import 'package:path_provider/path_provider.dart'; import '../../util/util.dart';