diff --git a/internal/webview/README.md b/internal/webview/README.md index a644bd7f..bd46c554 100644 --- a/internal/webview/README.md +++ b/internal/webview/README.md @@ -3,7 +3,10 @@ The downloader wraps each provider with the extension's stable identity before constructing the runtime. `OpenOptions.ProfileID`, `DataPath`, and `ProxyURL` are host-only fields. `parseOpenOptions` never reads them from extension JavaScript. -The extension API is unchanged. +The extension API is unchanged. The Flutter host uses the fork's public +`WebViewProfile`, `InAppWebViewSettings.profileId`, and profile-scoped +`CookieManager` APIs; the library has no Gopeed-specific method-channel bridge. +Only the Gopeed extension JavaScript boundary keeps these options host-only. Profiles are persistent and shared across invocations/pages of the same extension. Different identities use different native stores. Go providers use a SHA-256-based diff --git a/internal/webview/rpcprovider/README.md b/internal/webview/rpcprovider/README.md index 3590f557..5036ddfc 100644 --- a/internal/webview/rpcprovider/README.md +++ b/internal/webview/rpcprovider/README.md @@ -1,5 +1,11 @@ # rpcprovider +The private Flutter native profile bridge is maintained in the +[GopeedLab flutter_inappwebview fork](https://github.com/GopeedLab/flutter_inappwebview/tree/codex/gopeed-profiles). +All three platform overrides pin one commit in `ui/flutter/pubspec.yaml`; +see the fork’s [GOPEED.md](https://github.com/GopeedLab/flutter_inappwebview/blob/codex/gopeed-profiles/GOPEED.md) +for patch scope and upstream synchronization. + `rpcprovider` is the RPC-backed implementation of Gopeed's `webview.Provider` interface. It is intended for environments where WebView capability is owned by another process, most notably mobile hosts. This package does not implement a WebView itself. Instead, it translates the `pkg/download/engine/webview` page API into single-request `POST + JSON` calls sent to a local host-side RPC service. diff --git a/ui/flutter/ios/Runner/GopeedContinuedProcessingManager.swift b/ui/flutter/ios/Runner/GopeedContinuedProcessingManager.swift index 7ef2b852..bc8f65a9 100644 --- a/ui/flutter/ios/Runner/GopeedContinuedProcessingManager.swift +++ b/ui/flutter/ios/Runner/GopeedContinuedProcessingManager.swift @@ -32,6 +32,7 @@ final class GopeedContinuedProcessingManager: NSObject { private var expiringTasks: [String: ExpiringTaskState] = [:] private var taskGenerations: [String: UUID] = [:] + private var progressRecoveryTokens: [String: UUID] = [:] private var activeTasks: [String: BGContinuedProcessingTask] = [:] @@ -224,6 +225,10 @@ final class GopeedContinuedProcessingManager: NSObject { switch type { case "task.start": + progressRecoveryTokens.removeValue( + forKey: taskID + ) + beginTask( taskID: taskID, name: name @@ -237,6 +242,11 @@ final class GopeedContinuedProcessingManager: NSObject { generation: generation, force: false ) + } else { + recoverTaskFromProgress( + taskID: taskID, + name: name + ) } case "task.pause": @@ -285,6 +295,72 @@ final class GopeedContinuedProcessingManager: NSObject { } + // MARK: - Recover active task after setting changes + + private func recoverTaskFromProgress( + taskID: String, + name: String + ) { + guard + currentEnabledSnapshot(), + taskGenerations[taskID] == nil, + !pendingTaskIDs.contains(taskID), + activeTasks[taskID] == nil, + expiringTasks[taskID] == nil, + progressRecoveryTokens[taskID] == nil + else { + return + } + + let recoveryToken = UUID() + + progressRecoveryTokens[taskID] = + recoveryToken + + getRuntimeStatus( + taskID: taskID + ) { [weak self] runtime in + guard + let self, + self.progressRecoveryTokens[taskID] + == recoveryToken + else { + return + } + + // The status request has completed. Remove the token so + // another progress event may retry if recovery is still + // necessary. + self.progressRecoveryTokens.removeValue( + forKey: taskID + ) + + guard + self.currentEnabledSnapshot(), + let runtime, + runtime.status == "running", + self.taskGenerations[taskID] == nil, + !self.pendingTaskIDs.contains(taskID), + self.activeTasks[taskID] == nil, + self.expiringTasks[taskID] == nil + else { + return + } + + print( + "ContinuedProcessing:", + "recovering active task:", + taskID + ) + + self.beginTask( + taskID: taskID, + name: name + ) + } + } + + // MARK: - Used by Objective-C forwarder @objc(isHandlingTaskId:) @@ -825,6 +901,10 @@ final class GopeedContinuedProcessingManager: NSObject { success: Bool, finalSubtitle: String ) { + progressRecoveryTokens.removeValue( + forKey: taskID + ) + taskGenerations.removeValue( forKey: taskID ) @@ -1063,6 +1143,7 @@ final class GopeedContinuedProcessingManager: NSObject { pendingTaskIDs.removeAll() expiringTasks.removeAll() taskGenerations.removeAll() + progressRecoveryTokens.removeAll() taskIdentifiers.removeAll() taskNames.removeAll() lastProgressUpdate.removeAll() diff --git a/ui/flutter/ios/Runner/GopeedLiveActivityManager.swift b/ui/flutter/ios/Runner/GopeedLiveActivityManager.swift index 84f965ba..6889dec9 100644 --- a/ui/flutter/ios/Runner/GopeedLiveActivityManager.swift +++ b/ui/flutter/ios/Runner/GopeedLiveActivityManager.swift @@ -108,6 +108,39 @@ final class GopeedLiveActivityManager: NSObject { return generation } + private func generationForProgress( + taskID: String + ) -> UUID? { + stateLock.lock() + defer { stateLock.unlock() } + + guard + !suppressedTaskIDs.contains(taskID) + else { + return nil + } + + if let generation = + taskGenerations[taskID] { + return generation + } + + // Progress may be the first event after Continued Processing + // releases ownership. Establish a fresh lifecycle generation. + // refreshActivity still verifies that Gopeed reports the task + // as running before it can create a Live Activity. + let generation = UUID() + + taskGenerations[taskID] = + generation + + lastUpdateTime.removeValue( + forKey: taskID + ) + + return generation + } + @discardableResult private func invalidateGeneration( taskID: String @@ -252,7 +285,7 @@ final class GopeedLiveActivityManager: NSObject { case "task.progress": guard let generation = - currentGeneration( + generationForProgress( taskID: taskID ) else { diff --git a/ui/flutter/lib/app/rpc/webview_rpc_service.dart b/ui/flutter/lib/app/rpc/webview_rpc_service.dart index 9bf7e1f0..3a75941e 100644 --- a/ui/flutter/lib/app/rpc/webview_rpc_service.dart +++ b/ui/flutter/lib/app/rpc/webview_rpc_service.dart @@ -167,11 +167,11 @@ class WebViewRpcService { Future> _openPage(Map params) async { final profile = WebViewProfile(_string(params, 'profileId')); - await profile.prepare(_string(params, 'proxyUrl')); + await profile.prepare(proxyUrl: Uri.parse(_string(params, 'proxyUrl'))); final pageId = 'page-${++_pageSeq}'; final session = WebViewRpcPageSession( pageId: pageId, - cookieManager: profile, + profile: profile, headless: params['headless'] as bool? ?? false, debug: params['debug'] as bool? ?? false, title: params['title'] as String? ?? '', @@ -192,7 +192,7 @@ class WebViewRpcService { } Future _removeProfile(String id) async { - for (final page in _pagesById.values.where((page) => page.cookieManager.id == id).toList()) { + for (final page in _pagesById.values.where((page) => page.profile.id == id).toList()) { await _closePage(page.pageId); } // Let visible platform views unmount before releasing their native store. @@ -301,7 +301,7 @@ class WebViewRpcService { class WebViewRpcPageSession { WebViewRpcPageSession({ required this.pageId, - required this.cookieManager, + required this.profile, required this.headless, required this.debug, required this.title, @@ -311,7 +311,8 @@ class WebViewRpcPageSession { }); final String pageId; - final WebViewProfile cookieManager; + final WebViewProfile profile; + late final CookieManager cookieManager = profile.cookieManager; final bool headless; final bool debug; final String title; @@ -543,9 +544,11 @@ class WebViewRpcPageSession { ); } - InAppWebViewSettings get _settings => WebViewProfileSettings( - profileId: cookieManager.id, - debug: debug, + InAppWebViewSettings get _settings => InAppWebViewSettings( + profileId: profile.id, + javaScriptEnabled: true, + transparentBackground: true, + isInspectable: debug, userAgent: userAgent.isNotEmpty ? userAgent : null, ); diff --git a/ui/flutter/pubspec.lock b/ui/flutter/pubspec.lock index 0ad35006..06828e78 100644 --- a/ui/flutter/pubspec.lock +++ b/ui/flutter/pubspec.lock @@ -457,17 +457,20 @@ packages: flutter_inappwebview: dependency: "direct main" description: - name: flutter_inappwebview - sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" - url: "https://pub.dev" - source: hosted + path: flutter_inappwebview + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + resolved-ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + url: "https://github.com/GopeedLab/flutter_inappwebview.git" + source: git version: "6.1.5" flutter_inappwebview_android: dependency: "direct overridden" description: - path: "third_party/flutter_inappwebview_android" - relative: true - source: path + path: flutter_inappwebview_android + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + resolved-ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + url: "https://github.com/GopeedLab/flutter_inappwebview.git" + source: git version: "1.1.3" flutter_inappwebview_internal_annotations: dependency: transitive @@ -480,24 +483,29 @@ packages: flutter_inappwebview_ios: dependency: "direct overridden" description: - path: "third_party/flutter_inappwebview_ios" - relative: true - source: path + path: flutter_inappwebview_ios + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + resolved-ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + url: "https://github.com/GopeedLab/flutter_inappwebview.git" + source: git version: "1.1.2" flutter_inappwebview_macos: dependency: "direct overridden" description: - path: "third_party/flutter_inappwebview_macos" - relative: true - source: path + path: flutter_inappwebview_macos + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + resolved-ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + url: "https://github.com/GopeedLab/flutter_inappwebview.git" + source: git version: "1.1.2" flutter_inappwebview_platform_interface: - dependency: transitive + dependency: "direct overridden" description: - name: flutter_inappwebview_platform_interface - sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 - url: "https://pub.dev" - source: hosted + path: flutter_inappwebview_platform_interface + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + resolved-ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + url: "https://github.com/GopeedLab/flutter_inappwebview.git" + source: git version: "1.3.0+1" flutter_inappwebview_web: dependency: transitive diff --git a/ui/flutter/pubspec.yaml b/ui/flutter/pubspec.yaml index 74d102c0..405a645f 100644 --- a/ui/flutter/pubspec.yaml +++ b/ui/flutter/pubspec.yaml @@ -86,13 +86,32 @@ dependencies: go_router: ^16.2.1 dependency_overrides: - # Host-only native WebView profile/proxy bridge. + # Public persistent-profile and proxy APIs; keep all packages on one commit. + flutter_inappwebview: + git: + url: https://github.com/GopeedLab/flutter_inappwebview.git + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + path: flutter_inappwebview + flutter_inappwebview_platform_interface: + git: + url: https://github.com/GopeedLab/flutter_inappwebview.git + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + path: flutter_inappwebview_platform_interface flutter_inappwebview_macos: - path: third_party/flutter_inappwebview_macos + git: + url: https://github.com/GopeedLab/flutter_inappwebview.git + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + path: flutter_inappwebview_macos flutter_inappwebview_ios: - path: third_party/flutter_inappwebview_ios + git: + url: https://github.com/GopeedLab/flutter_inappwebview.git + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + path: flutter_inappwebview_ios flutter_inappwebview_android: - path: third_party/flutter_inappwebview_android + git: + url: https://github.com/GopeedLab/flutter_inappwebview.git + ref: d579215760e1c1e58a4ff7601a6055b5e6adf8e8 + path: flutter_inappwebview_android # Prevent a native Windows crash when the push notification service is unavailable. # Keep this fork until https://github.com/MaikuB/flutter_local_notifications/pull/2813 is merged and released. flutter_local_notifications: